fix search direction on new page
[xxxterm.git] / xxxterm.c
blob5da60795f4a05b4b276b3c8d2a0256c694107733
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 <gnutls/gnutls.h>
78 #include <JavaScriptCore/JavaScript.h>
79 #include <gnutls/x509.h>
81 #include "version.h"
82 #include "javascript.h"
85 javascript.h borrowed from vimprobable2 under the following license:
87 Copyright (c) 2009 Leon Winter
88 Copyright (c) 2009 Hannes Schueller
89 Copyright (c) 2009 Matto Fransen
91 Permission is hereby granted, free of charge, to any person obtaining a copy
92 of this software and associated documentation files (the "Software"), to deal
93 in the Software without restriction, including without limitation the rights
94 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
95 copies of the Software, and to permit persons to whom the Software is
96 furnished to do so, subject to the following conditions:
98 The above copyright notice and this permission notice shall be included in
99 all copies or substantial portions of the Software.
101 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
102 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
103 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
104 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
105 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
106 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
107 THE SOFTWARE.
110 static char *version = XXXTERM_VERSION;
112 /* hooked functions */
113 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
114 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
115 SoupCookie *);
117 /*#define XT_DEBUG*/
118 #ifdef XT_DEBUG
119 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
120 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
121 #define XT_D_MOVE 0x0001
122 #define XT_D_KEY 0x0002
123 #define XT_D_TAB 0x0004
124 #define XT_D_URL 0x0008
125 #define XT_D_CMD 0x0010
126 #define XT_D_NAV 0x0020
127 #define XT_D_DOWNLOAD 0x0040
128 #define XT_D_CONFIG 0x0080
129 #define XT_D_JS 0x0100
130 #define XT_D_FAVORITE 0x0200
131 #define XT_D_PRINTING 0x0400
132 #define XT_D_COOKIE 0x0800
133 #define XT_D_KEYBINDING 0x1000
134 #define XT_D_CLIP 0x2000
135 #define XT_D_BUFFERCMD 0x4000
136 u_int32_t swm_debug = 0
137 | XT_D_MOVE
138 | XT_D_KEY
139 | XT_D_TAB
140 | XT_D_URL
141 | XT_D_CMD
142 | XT_D_NAV
143 | XT_D_DOWNLOAD
144 | XT_D_CONFIG
145 | XT_D_JS
146 | XT_D_FAVORITE
147 | XT_D_PRINTING
148 | XT_D_COOKIE
149 | XT_D_KEYBINDING
150 | XT_D_CLIP
151 | XT_D_BUFFERCMD
153 #else
154 #define DPRINTF(x...)
155 #define DNPRINTF(n,x...)
156 #endif
158 #define LENGTH(x) (sizeof x / sizeof x[0])
159 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
160 ~(GDK_BUTTON1_MASK) & \
161 ~(GDK_BUTTON2_MASK) & \
162 ~(GDK_BUTTON3_MASK) & \
163 ~(GDK_BUTTON4_MASK) & \
164 ~(GDK_BUTTON5_MASK))
166 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
168 char *icons[] = {
169 "xxxtermicon16.png",
170 "xxxtermicon32.png",
171 "xxxtermicon48.png",
172 "xxxtermicon64.png",
173 "xxxtermicon128.png"
176 struct tab {
177 TAILQ_ENTRY(tab) entry;
178 GtkWidget *vbox;
179 GtkWidget *tab_content;
180 struct {
181 GtkWidget *label;
182 GtkWidget *eventbox;
183 GtkWidget *box;
184 GtkWidget *sep;
185 } tab_elems;
186 GtkWidget *label;
187 GtkWidget *spinner;
188 GtkWidget *uri_entry;
189 GtkWidget *search_entry;
190 GtkWidget *toolbar;
191 GtkWidget *browser_win;
192 GtkWidget *statusbar_box;
193 struct {
194 GtkWidget *statusbar;
195 GtkWidget *buffercmd;
196 GtkWidget *zoom;
197 GtkWidget *position;
198 } sbe;
199 GtkWidget *cmd;
200 GtkWidget *buffers;
201 GtkWidget *oops;
202 GtkWidget *backward;
203 GtkWidget *forward;
204 GtkWidget *stop;
205 GtkWidget *gohome;
206 GtkWidget *js_toggle;
207 GtkEntryCompletion *completion;
208 guint tab_id;
209 WebKitWebView *wv;
211 WebKitWebHistoryItem *item;
212 WebKitWebBackForwardList *bfl;
214 /* favicon */
215 WebKitNetworkRequest *icon_request;
216 WebKitDownload *icon_download;
217 gchar *icon_dest_uri;
219 /* adjustments for browser */
220 GtkScrollbar *sb_h;
221 GtkScrollbar *sb_v;
222 GtkAdjustment *adjust_h;
223 GtkAdjustment *adjust_v;
225 /* flags */
226 int focus_wv;
227 int ctrl_click;
228 gchar *status;
229 int xtp_meaning; /* identifies dls/favorites */
230 gchar *tmp_uri;
231 int popup; /* 1 if cmd_entry has popup visible */
233 /* hints */
234 int hints_on;
235 int hint_mode;
236 #define XT_HINT_NONE (0)
237 #define XT_HINT_NUMERICAL (1)
238 #define XT_HINT_ALPHANUM (2)
239 char hint_buf[128];
240 char hint_num[128];
242 /* custom stylesheet */
243 int styled;
244 char *stylesheet;
246 /* search */
247 char *search_text;
248 int search_forward;
249 guint search_id;
251 /* settings */
252 WebKitWebSettings *settings;
253 gchar *user_agent;
255 /* marks */
256 double mark[XT_NOMARKS];
258 TAILQ_HEAD(tab_list, tab);
260 struct history {
261 RB_ENTRY(history) entry;
262 const gchar *uri;
263 const gchar *title;
265 RB_HEAD(history_list, history);
267 struct session {
268 TAILQ_ENTRY(session) entry;
269 const gchar *name;
271 TAILQ_HEAD(session_list, session);
273 struct download {
274 RB_ENTRY(download) entry;
275 int id;
276 WebKitDownload *download;
277 struct tab *tab;
279 RB_HEAD(download_list, download);
281 struct domain {
282 RB_ENTRY(domain) entry;
283 gchar *d;
284 int handy; /* app use */
286 RB_HEAD(domain_list, domain);
288 struct undo {
289 TAILQ_ENTRY(undo) entry;
290 gchar *uri;
291 GList *history;
292 int back; /* Keeps track of how many back
293 * history items there are. */
295 TAILQ_HEAD(undo_tailq, undo);
297 struct sp {
298 char *line;
299 TAILQ_ENTRY(sp) entry;
301 TAILQ_HEAD(sp_list, sp);
303 struct command_entry {
304 char *line;
305 TAILQ_ENTRY(command_entry) entry;
307 TAILQ_HEAD(command_list, command_entry);
309 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
310 int next_download_id = 1;
312 struct karg {
313 int i;
314 char *s;
315 int precount;
318 /* defines */
319 #define XT_NAME ("XXXTerm")
320 #define XT_DIR (".xxxterm")
321 #define XT_CACHE_DIR ("cache")
322 #define XT_CERT_DIR ("certs/")
323 #define XT_SESSIONS_DIR ("sessions/")
324 #define XT_CONF_FILE ("xxxterm.conf")
325 #define XT_FAVS_FILE ("favorites")
326 #define XT_QMARKS_FILE ("quickmarks")
327 #define XT_SAVED_TABS_FILE ("main_session")
328 #define XT_RESTART_TABS_FILE ("restart_tabs")
329 #define XT_SOCKET_FILE ("socket")
330 #define XT_HISTORY_FILE ("history")
331 #define XT_REJECT_FILE ("rejected.txt")
332 #define XT_COOKIE_FILE ("cookies.txt")
333 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
334 #define XT_SEARCH_FILE ("search_history")
335 #define XT_COMMAND_FILE ("command_history")
336 #define XT_CB_HANDLED (TRUE)
337 #define XT_CB_PASSTHROUGH (FALSE)
338 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
339 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
340 #define XT_DLMAN_REFRESH "10"
341 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
342 "td{overflow: hidden;" \
343 " padding: 2px 2px 2px 2px;" \
344 " border: 1px solid black;" \
345 " vertical-align:top;" \
346 " word-wrap: break-word}\n" \
347 "tr:hover{background: #ffff99}\n" \
348 "th{background-color: #cccccc;" \
349 " border: 1px solid black}\n" \
350 "table{width: 100%%;" \
351 " border: 1px black solid;" \
352 " border-collapse:collapse}\n" \
353 ".progress-outer{" \
354 "border: 1px solid black;" \
355 " height: 8px;" \
356 " width: 90%%}\n" \
357 ".progress-inner{float: left;" \
358 " height: 8px;" \
359 " background: green}\n" \
360 ".dlstatus{font-size: small;" \
361 " text-align: center}\n" \
362 "</style>\n"
363 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
364 #define XT_MAX_UNDO_CLOSE_TAB (32)
365 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
366 #define XT_PRINT_EXTRA_MARGIN 10
367 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
368 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
370 /* colors */
371 #define XT_COLOR_RED "#cc0000"
372 #define XT_COLOR_YELLOW "#ffff66"
373 #define XT_COLOR_BLUE "lightblue"
374 #define XT_COLOR_GREEN "#99ff66"
375 #define XT_COLOR_WHITE "white"
376 #define XT_COLOR_BLACK "black"
378 #define XT_COLOR_CT_BACKGROUND "#000000"
379 #define XT_COLOR_CT_INACTIVE "#dddddd"
380 #define XT_COLOR_CT_ACTIVE "#bbbb00"
381 #define XT_COLOR_CT_SEPARATOR "#555555"
383 #define XT_COLOR_SB_SEPARATOR "#555555"
385 #define XT_PROTO_DELIM "://"
388 * xxxterm "protocol" (xtp)
389 * We use this for managing stuff like downloads and favorites. They
390 * make magical HTML pages in memory which have xxxt:// links in order
391 * to communicate with xxxterm's internals. These links take the format:
392 * xxxt://class/session_key/action/arg
394 * Don't begin xtp class/actions as 0. atoi returns that on error.
396 * Typically we have not put addition of items in this framework, as
397 * adding items is either done via an ex-command or via a keybinding instead.
400 #define XT_XTP_STR "xxxt://"
402 /* XTP classes (xxxt://<class>) */
403 #define XT_XTP_INVALID 0 /* invalid */
404 #define XT_XTP_DL 1 /* downloads */
405 #define XT_XTP_HL 2 /* history */
406 #define XT_XTP_CL 3 /* cookies */
407 #define XT_XTP_FL 4 /* favorites */
409 /* XTP download actions */
410 #define XT_XTP_DL_LIST 1
411 #define XT_XTP_DL_CANCEL 2
412 #define XT_XTP_DL_REMOVE 3
414 /* XTP history actions */
415 #define XT_XTP_HL_LIST 1
416 #define XT_XTP_HL_REMOVE 2
418 /* XTP cookie actions */
419 #define XT_XTP_CL_LIST 1
420 #define XT_XTP_CL_REMOVE 2
422 /* XTP cookie actions */
423 #define XT_XTP_FL_LIST 1
424 #define XT_XTP_FL_REMOVE 2
426 /* actions */
427 #define XT_MOVE_INVALID (0)
428 #define XT_MOVE_DOWN (1)
429 #define XT_MOVE_UP (2)
430 #define XT_MOVE_BOTTOM (3)
431 #define XT_MOVE_TOP (4)
432 #define XT_MOVE_PAGEDOWN (5)
433 #define XT_MOVE_PAGEUP (6)
434 #define XT_MOVE_HALFDOWN (7)
435 #define XT_MOVE_HALFUP (8)
436 #define XT_MOVE_LEFT (9)
437 #define XT_MOVE_FARLEFT (10)
438 #define XT_MOVE_RIGHT (11)
439 #define XT_MOVE_FARRIGHT (12)
440 #define XT_MOVE_PERCENT (13)
442 #define XT_QMARK_SET (0)
443 #define XT_QMARK_OPEN (1)
444 #define XT_QMARK_TAB (2)
446 #define XT_MARK_SET (0)
447 #define XT_MARK_GOTO (1)
449 #define XT_TAB_LAST (-4)
450 #define XT_TAB_FIRST (-3)
451 #define XT_TAB_PREV (-2)
452 #define XT_TAB_NEXT (-1)
453 #define XT_TAB_INVALID (0)
454 #define XT_TAB_NEW (1)
455 #define XT_TAB_DELETE (2)
456 #define XT_TAB_DELQUIT (3)
457 #define XT_TAB_OPEN (4)
458 #define XT_TAB_UNDO_CLOSE (5)
459 #define XT_TAB_SHOW (6)
460 #define XT_TAB_HIDE (7)
461 #define XT_TAB_NEXTSTYLE (8)
463 #define XT_NAV_INVALID (0)
464 #define XT_NAV_BACK (1)
465 #define XT_NAV_FORWARD (2)
466 #define XT_NAV_RELOAD (3)
468 #define XT_FOCUS_INVALID (0)
469 #define XT_FOCUS_URI (1)
470 #define XT_FOCUS_SEARCH (2)
472 #define XT_SEARCH_INVALID (0)
473 #define XT_SEARCH_NEXT (1)
474 #define XT_SEARCH_PREV (2)
476 #define XT_PASTE_CURRENT_TAB (0)
477 #define XT_PASTE_NEW_TAB (1)
479 #define XT_ZOOM_IN (-1)
480 #define XT_ZOOM_OUT (-2)
481 #define XT_ZOOM_NORMAL (100)
483 #define XT_URL_SHOW (1)
484 #define XT_URL_HIDE (2)
486 #define XT_WL_TOGGLE (1<<0)
487 #define XT_WL_ENABLE (1<<1)
488 #define XT_WL_DISABLE (1<<2)
489 #define XT_WL_FQDN (1<<3) /* default */
490 #define XT_WL_TOPLEVEL (1<<4)
491 #define XT_WL_PERSISTENT (1<<5)
492 #define XT_WL_SESSION (1<<6)
493 #define XT_WL_RELOAD (1<<7)
495 #define XT_SHOW (1<<7)
496 #define XT_DELETE (1<<8)
497 #define XT_SAVE (1<<9)
498 #define XT_OPEN (1<<10)
500 #define XT_CMD_OPEN (0)
501 #define XT_CMD_OPEN_CURRENT (1)
502 #define XT_CMD_TABNEW (2)
503 #define XT_CMD_TABNEW_CURRENT (3)
505 #define XT_STATUS_NOTHING (0)
506 #define XT_STATUS_LINK (1)
507 #define XT_STATUS_URI (2)
508 #define XT_STATUS_LOADING (3)
510 #define XT_SES_DONOTHING (0)
511 #define XT_SES_CLOSETABS (1)
513 #define XT_BM_NORMAL (0)
514 #define XT_BM_WHITELIST (1)
515 #define XT_BM_KIOSK (2)
517 #define XT_PREFIX (1<<0)
518 #define XT_USERARG (1<<1)
519 #define XT_URLARG (1<<2)
520 #define XT_INTARG (1<<3)
521 #define XT_SESSARG (1<<4)
522 #define XT_SETARG (1<<5)
524 #define XT_TABS_NORMAL 0
525 #define XT_TABS_COMPACT 1
527 #define XT_BUFCMD_SZ (8)
529 /* mime types */
530 struct mime_type {
531 char *mt_type;
532 char *mt_action;
533 int mt_default;
534 int mt_download;
535 TAILQ_ENTRY(mime_type) entry;
537 TAILQ_HEAD(mime_type_list, mime_type);
539 /* uri aliases */
540 struct alias {
541 char *a_name;
542 char *a_uri;
543 TAILQ_ENTRY(alias) entry;
545 TAILQ_HEAD(alias_list, alias);
547 /* settings that require restart */
548 int tabless = 0; /* allow only 1 tab */
549 int enable_socket = 0;
550 int single_instance = 0; /* only allow one xxxterm to run */
551 int fancy_bar = 1; /* fancy toolbar */
552 int browser_mode = XT_BM_NORMAL;
553 int enable_localstorage = 1;
554 char *statusbar_elems = NULL;
556 /* runtime settings */
557 int show_tabs = 1; /* show tabs on notebook */
558 int tab_style = XT_TABS_NORMAL; /* tab bar style */
559 int show_url = 1; /* show url toolbar on notebook */
560 int show_statusbar = 0; /* vimperator style status bar */
561 int ctrl_click_focus = 0; /* ctrl click gets focus */
562 int cookies_enabled = 1; /* enable cookies */
563 int read_only_cookies = 0; /* enable to not write cookies */
564 int enable_scripts = 1;
565 int enable_plugins = 0;
566 gfloat default_zoom_level = 1.0;
567 char default_script[PATH_MAX];
568 int window_height = 768;
569 int window_width = 1024;
570 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
571 int refresh_interval = 10; /* download refresh interval */
572 int enable_cookie_whitelist = 0;
573 int enable_js_whitelist = 0;
574 int session_timeout = 3600; /* cookie session timeout */
575 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
576 char *ssl_ca_file = NULL;
577 char *resource_dir = NULL;
578 gboolean ssl_strict_certs = FALSE;
579 int append_next = 1; /* append tab after current tab */
580 char *home = NULL;
581 char *search_string = NULL;
582 char *http_proxy = NULL;
583 char download_dir[PATH_MAX];
584 char runtime_settings[PATH_MAX]; /* override of settings */
585 int allow_volatile_cookies = 0;
586 int save_global_history = 0; /* save global history to disk */
587 char *user_agent = NULL;
588 int save_rejected_cookies = 0;
589 int session_autosave = 0;
590 int guess_search = 0;
591 int dns_prefetch = FALSE;
592 gint max_connections = 25;
593 gint max_host_connections = 5;
594 gint enable_spell_checking = 0;
595 char *spell_check_languages = NULL;
596 int xterm_workaround = 0;
597 char *url_regex = NULL;
598 int history_autosave = 0;
599 char search_file[PATH_MAX];
600 char command_file[PATH_MAX];
601 char *encoding = NULL;
603 char *cmd_font_name = NULL;
604 char *oops_font_name = NULL;
605 char *statusbar_font_name = NULL;
606 char *tabbar_font_name = NULL;
607 PangoFontDescription *cmd_font;
608 PangoFontDescription *oops_font;
609 PangoFontDescription *statusbar_font;
610 PangoFontDescription *tabbar_font;
611 char *qmarks[XT_NOMARKS];
613 int btn_down; /* M1 down in any wv */
614 regex_t url_re; /* guess_search regex */
616 struct settings;
617 struct key_binding;
618 int set_browser_mode(struct settings *, char *);
619 int set_cookie_policy(struct settings *, char *);
620 int set_download_dir(struct settings *, char *);
621 int set_default_script(struct settings *, char *);
622 int set_runtime_dir(struct settings *, char *);
623 int set_tab_style(struct settings *, char *);
624 int set_work_dir(struct settings *, char *);
625 int add_alias(struct settings *, char *);
626 int add_mime_type(struct settings *, char *);
627 int add_cookie_wl(struct settings *, char *);
628 int add_js_wl(struct settings *, char *);
629 int add_kb(struct settings *, char *);
630 void button_set_stockid(GtkWidget *, char *);
631 GtkWidget * create_button(char *, char *, int);
633 char *get_browser_mode(struct settings *);
634 char *get_cookie_policy(struct settings *);
635 char *get_download_dir(struct settings *);
636 char *get_default_script(struct settings *);
637 char *get_runtime_dir(struct settings *);
638 char *get_tab_style(struct settings *);
639 char *get_work_dir(struct settings *);
640 void startpage_add(const char *, ...);
642 void walk_alias(struct settings *, void (*)(struct settings *,
643 char *, void *), void *);
644 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
645 char *, void *), void *);
646 void walk_js_wl(struct settings *, void (*)(struct settings *,
647 char *, void *), void *);
648 void walk_kb(struct settings *, void (*)(struct settings *, char *,
649 void *), void *);
650 void walk_mime_type(struct settings *, void (*)(struct settings *,
651 char *, void *), void *);
653 void recalc_tabs(void);
654 void recolor_compact_tabs(void);
655 void set_current_tab(int page_num);
656 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
657 void marks_clear(struct tab *t);
659 int set_http_proxy(char *);
661 struct special {
662 int (*set)(struct settings *, char *);
663 char *(*get)(struct settings *);
664 void (*walk)(struct settings *,
665 void (*cb)(struct settings *, char *, void *),
666 void *);
669 struct special s_browser_mode = {
670 set_browser_mode,
671 get_browser_mode,
672 NULL
675 struct special s_cookie = {
676 set_cookie_policy,
677 get_cookie_policy,
678 NULL
681 struct special s_alias = {
682 add_alias,
683 NULL,
684 walk_alias
687 struct special s_mime = {
688 add_mime_type,
689 NULL,
690 walk_mime_type
693 struct special s_js = {
694 add_js_wl,
695 NULL,
696 walk_js_wl
699 struct special s_kb = {
700 add_kb,
701 NULL,
702 walk_kb
705 struct special s_cookie_wl = {
706 add_cookie_wl,
707 NULL,
708 walk_cookie_wl
711 struct special s_default_script = {
712 set_default_script,
713 get_default_script,
714 NULL
717 struct special s_download_dir = {
718 set_download_dir,
719 get_download_dir,
720 NULL
723 struct special s_work_dir = {
724 set_work_dir,
725 get_work_dir,
726 NULL
729 struct special s_tab_style = {
730 set_tab_style,
731 get_tab_style,
732 NULL
735 struct settings {
736 char *name;
737 int type;
738 #define XT_S_INVALID (0)
739 #define XT_S_INT (1)
740 #define XT_S_STR (2)
741 #define XT_S_FLOAT (3)
742 uint32_t flags;
743 #define XT_SF_RESTART (1<<0)
744 #define XT_SF_RUNTIME (1<<1)
745 int *ival;
746 char **sval;
747 struct special *s;
748 gfloat *fval;
749 int (*activate)(char *);
750 } rs[] = {
751 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
752 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
753 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
754 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
755 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
756 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
757 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
758 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
759 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
760 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
761 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
762 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
763 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
764 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
765 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
766 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
767 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
768 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
769 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
770 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
771 { "home", XT_S_STR, 0, NULL, &home, NULL },
772 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
773 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
774 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
775 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
776 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
777 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
778 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
779 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
780 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
781 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
782 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
783 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
784 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
785 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
786 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
787 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
788 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
789 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
790 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
791 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
792 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
793 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
794 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
795 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
796 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
797 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
798 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
800 /* font settings */
801 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
802 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
803 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
804 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
806 /* runtime settings */
807 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
808 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
809 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
810 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
811 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
814 int about(struct tab *, struct karg *);
815 int blank(struct tab *, struct karg *);
816 int ca_cmd(struct tab *, struct karg *);
817 int cookie_show_wl(struct tab *, struct karg *);
818 int js_show_wl(struct tab *, struct karg *);
819 int help(struct tab *, struct karg *);
820 int set(struct tab *, struct karg *);
821 int stats(struct tab *, struct karg *);
822 int marco(struct tab *, struct karg *);
823 int startpage(struct tab *, struct karg *);
824 const char * marco_message(int *);
825 int xtp_page_cl(struct tab *, struct karg *);
826 int xtp_page_dl(struct tab *, struct karg *);
827 int xtp_page_fl(struct tab *, struct karg *);
828 int xtp_page_hl(struct tab *, struct karg *);
829 void xt_icon_from_file(struct tab *, char *);
830 const gchar *get_uri(struct tab *);
831 const gchar *get_title(struct tab *, bool);
833 #define XT_URI_ABOUT ("about:")
834 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
835 #define XT_URI_ABOUT_ABOUT ("about")
836 #define XT_URI_ABOUT_BLANK ("blank")
837 #define XT_URI_ABOUT_CERTS ("certs")
838 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
839 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
840 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
841 #define XT_URI_ABOUT_FAVORITES ("favorites")
842 #define XT_URI_ABOUT_HELP ("help")
843 #define XT_URI_ABOUT_HISTORY ("history")
844 #define XT_URI_ABOUT_JSWL ("jswl")
845 #define XT_URI_ABOUT_SET ("set")
846 #define XT_URI_ABOUT_STATS ("stats")
847 #define XT_URI_ABOUT_MARCO ("marco")
848 #define XT_URI_ABOUT_STARTPAGE ("startpage")
850 struct about_type {
851 char *name;
852 int (*func)(struct tab *, struct karg *);
853 } about_list[] = {
854 { XT_URI_ABOUT_ABOUT, about },
855 { XT_URI_ABOUT_BLANK, blank },
856 { XT_URI_ABOUT_CERTS, ca_cmd },
857 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
858 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
859 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
860 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
861 { XT_URI_ABOUT_HELP, help },
862 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
863 { XT_URI_ABOUT_JSWL, js_show_wl },
864 { XT_URI_ABOUT_SET, set },
865 { XT_URI_ABOUT_STATS, stats },
866 { XT_URI_ABOUT_MARCO, marco },
867 { XT_URI_ABOUT_STARTPAGE, startpage },
870 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
871 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
872 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
873 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
874 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
875 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
876 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
878 /* globals */
879 extern char *__progname;
880 char **start_argv;
881 struct passwd *pwd;
882 GtkWidget *main_window;
883 GtkNotebook *notebook;
884 GtkWidget *tab_bar;
885 GtkWidget *arrow, *abtn;
886 struct tab_list tabs;
887 struct history_list hl;
888 struct session_list sessions;
889 struct download_list downloads;
890 struct domain_list c_wl;
891 struct domain_list js_wl;
892 struct undo_tailq undos;
893 struct keybinding_list kbl;
894 struct sp_list spl;
895 struct command_list chl;
896 struct command_list shl;
897 struct command_entry *history_at;
898 struct command_entry *search_at;
899 int undo_count;
900 int updating_dl_tabs = 0;
901 int updating_hl_tabs = 0;
902 int updating_cl_tabs = 0;
903 int updating_fl_tabs = 0;
904 int cmd_history_count = 0;
905 int search_history_count = 0;
906 char *global_search;
907 uint64_t blocked_cookies = 0;
908 char named_session[PATH_MAX];
909 GtkListStore *completion_model;
910 GtkListStore *buffers_store;
912 void xxx_dir(char *);
913 int icon_size_map(int);
914 void completion_add(struct tab *);
915 void completion_add_uri(const gchar *);
916 void show_oops(struct tab *, const char *, ...);
918 void
919 history_delete(struct command_list *l, int *counter)
921 struct command_entry *c;
923 if (l == NULL || counter == NULL)
924 return;
926 c = TAILQ_LAST(l, command_list);
927 if (c == NULL)
928 return;
930 TAILQ_REMOVE(l, c, entry);
931 *counter -= 1;
932 g_free(c->line);
933 g_free(c);
936 void
937 history_add(struct command_list *list, char *file, char *l, int *counter)
939 struct command_entry *c;
940 FILE *f;
942 if (list == NULL || l == NULL || counter == NULL)
943 return;
945 /* don't add the same line */
946 c = TAILQ_FIRST(list);
947 if (c)
948 if (!strcmp(c->line + 1 /* skip space */, l))
949 return;
951 c = g_malloc0(sizeof *c);
952 c->line = g_strdup_printf(" %s", l);
954 *counter += 1;
955 TAILQ_INSERT_HEAD(list, c, entry);
957 if (*counter > 1000)
958 history_delete(list, counter);
960 if (history_autosave && file) {
961 f = fopen(file, "w");
962 if (f == NULL) {
963 show_oops(NULL, "couldn't write history %s", file);
964 return;
967 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
968 c->line[0] = ' ';
969 fprintf(f, "%s\n", c->line);
972 fclose(f);
977 history_read(struct command_list *list, char *file, int *counter)
979 FILE *f;
980 char *s, line[65536];
982 if (list == NULL || file == NULL)
983 return (1);
985 f = fopen(file, "r");
986 if (f == NULL) {
987 startpage_add("couldn't open history file %s", file);
988 return (1);
991 for (;;) {
992 s = fgets(line, sizeof line, f);
993 if (s == NULL || feof(f) || ferror(f))
994 break;
995 if ((s = strchr(line, '\n')) == NULL) {
996 startpage_add("invalid history file %s", file);
997 fclose(f);
998 return (1);
1000 *s = '\0';
1002 history_add(list, NULL, line + 1, counter);
1005 fclose(f);
1007 return (0);
1010 /* marks and quickmarks array storage.
1011 * first a-z, then A-Z, then 0-9 */
1012 char
1013 indextomark(int i)
1015 if (i < 0)
1016 return 0;
1018 if (i >= 0 && i <= 'z' - 'a')
1019 return 'a' + i;
1021 i -= 'z' - 'a' + 1;
1022 if (i >= 0 && i <= 'Z' - 'A')
1023 return 'A' + i;
1025 i -= 'Z' - 'A' + 1;
1026 if (i >= 10)
1027 return 0;
1029 return i + '0';
1033 marktoindex(char m)
1035 int ret = 0;
1037 if (m >= 'a' && m <= 'z')
1038 return ret + m - 'a';
1040 ret += 'z' - 'a' + 1;
1041 if (m >= 'A' && m <= 'Z')
1042 return ret + m - 'A';
1044 ret += 'Z' - 'A' + 1;
1045 if (m >= '0' && m <= '9')
1046 return ret + m - '0';
1048 return -1;
1052 void
1053 sigchild(int sig)
1055 int saved_errno, status;
1056 pid_t pid;
1058 saved_errno = errno;
1060 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1061 if (pid == -1) {
1062 if (errno == EINTR)
1063 continue;
1064 if (errno != ECHILD) {
1066 clog_warn("sigchild: waitpid:");
1069 break;
1072 if (WIFEXITED(status)) {
1073 if (WEXITSTATUS(status) != 0) {
1075 clog_warnx("sigchild: child exit status: %d",
1076 WEXITSTATUS(status));
1079 } else {
1081 clog_warnx("sigchild: child is terminated abnormally");
1086 errno = saved_errno;
1090 is_g_object_setting(GObject *o, char *str)
1092 guint n_props = 0, i;
1093 GParamSpec **proplist;
1095 if (! G_IS_OBJECT(o))
1096 return (0);
1098 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1099 &n_props);
1101 for (i=0; i < n_props; i++) {
1102 if (! strcmp(proplist[i]->name, str))
1103 return (1);
1105 return (0);
1108 gchar *
1109 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1111 gchar *r;
1113 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1114 "<head>\n"
1115 "<title>%s</title>\n"
1116 "%s"
1117 "%s"
1118 "</head>\n"
1119 "<body>\n"
1120 "<h1>%s</h1>\n"
1121 "%s\n</body>\n"
1122 "</html>",
1123 title,
1124 addstyles ? XT_PAGE_STYLE : "",
1125 head,
1126 title,
1127 body);
1129 return r;
1133 * Display a web page from a HTML string in memory, rather than from a URL
1135 void
1136 load_webkit_string(struct tab *t, const char *str, gchar *title)
1138 char file[PATH_MAX];
1139 int i;
1141 /* we set this to indicate we want to manually do navaction */
1142 if (t->bfl)
1143 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1145 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1146 if (title) {
1147 /* set t->xtp_meaning */
1148 for (i = 0; i < LENGTH(about_list); i++)
1149 if (!strcmp(title, about_list[i].name)) {
1150 t->xtp_meaning = i;
1151 break;
1154 webkit_web_view_load_string(t->wv, str, NULL, encoding,
1155 "file://");
1156 #if GTK_CHECK_VERSION(2, 20, 0)
1157 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1158 gtk_widget_hide(t->spinner);
1159 #endif
1160 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1161 xt_icon_from_file(t, file);
1165 struct tab *
1166 get_current_tab(void)
1168 struct tab *t;
1170 TAILQ_FOREACH(t, &tabs, entry) {
1171 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1172 return (t);
1175 warnx("%s: no current tab", __func__);
1177 return (NULL);
1180 void
1181 set_status(struct tab *t, gchar *s, int status)
1183 gchar *type = NULL;
1185 if (s == NULL)
1186 return;
1188 switch (status) {
1189 case XT_STATUS_LOADING:
1190 type = g_strdup_printf("Loading: %s", s);
1191 s = type;
1192 break;
1193 case XT_STATUS_LINK:
1194 type = g_strdup_printf("Link: %s", s);
1195 if (!t->status)
1196 t->status = g_strdup(gtk_entry_get_text(
1197 GTK_ENTRY(t->sbe.statusbar)));
1198 s = type;
1199 break;
1200 case XT_STATUS_URI:
1201 type = g_strdup_printf("%s", s);
1202 if (!t->status) {
1203 t->status = g_strdup(type);
1205 s = type;
1206 if (!t->status)
1207 t->status = g_strdup(s);
1208 break;
1209 case XT_STATUS_NOTHING:
1210 /* FALL THROUGH */
1211 default:
1212 break;
1214 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1215 if (type)
1216 g_free(type);
1219 void
1220 hide_cmd(struct tab *t)
1222 history_at = NULL; /* just in case */
1223 search_at = NULL; /* just in case */
1224 gtk_widget_hide(t->cmd);
1227 void
1228 show_cmd(struct tab *t)
1230 history_at = NULL;
1231 search_at = NULL;
1232 gtk_widget_hide(t->oops);
1233 gtk_widget_show(t->cmd);
1236 void
1237 hide_buffers(struct tab *t)
1239 gtk_widget_hide(t->buffers);
1240 gtk_list_store_clear(buffers_store);
1243 enum {
1244 COL_ID = 0,
1245 COL_TITLE,
1246 NUM_COLS
1250 sort_tabs_by_page_num(struct tab ***stabs)
1252 int num_tabs = 0;
1253 struct tab *t;
1255 num_tabs = gtk_notebook_get_n_pages(notebook);
1257 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1259 TAILQ_FOREACH(t, &tabs, entry)
1260 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1262 return (num_tabs);
1265 void
1266 buffers_make_list(void)
1268 int i, num_tabs;
1269 const gchar *title = NULL;
1270 GtkTreeIter iter;
1271 struct tab **stabs = NULL;
1273 num_tabs = sort_tabs_by_page_num(&stabs);
1275 for (i = 0; i < num_tabs; i++)
1276 if (stabs[i]) {
1277 gtk_list_store_append(buffers_store, &iter);
1278 title = get_title(stabs[i], FALSE);
1279 gtk_list_store_set(buffers_store, &iter,
1280 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1281 * rather than 0. */
1282 COL_TITLE, title,
1283 -1);
1286 g_free(stabs);
1289 void
1290 show_buffers(struct tab *t)
1292 buffers_make_list();
1293 gtk_widget_show(t->buffers);
1294 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1297 void
1298 toggle_buffers(struct tab *t)
1300 if (gtk_widget_get_visible(t->buffers))
1301 hide_buffers(t);
1302 else
1303 show_buffers(t);
1307 buffers(struct tab *t, struct karg *args)
1309 show_buffers(t);
1311 return (0);
1314 void
1315 hide_oops(struct tab *t)
1317 gtk_widget_hide(t->oops);
1320 void
1321 show_oops(struct tab *at, const char *fmt, ...)
1323 va_list ap;
1324 char *msg = NULL;
1325 struct tab *t = NULL;
1327 if (fmt == NULL)
1328 return;
1330 if (at == NULL) {
1331 if ((t = get_current_tab()) == NULL)
1332 return;
1333 } else
1334 t = at;
1336 va_start(ap, fmt);
1337 if (vasprintf(&msg, fmt, ap) == -1)
1338 errx(1, "show_oops failed");
1339 va_end(ap);
1341 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1342 gtk_widget_hide(t->cmd);
1343 gtk_widget_show(t->oops);
1345 if (msg)
1346 free(msg);
1349 char *
1350 get_as_string(struct settings *s)
1352 char *r = NULL;
1354 if (s == NULL)
1355 return (NULL);
1357 if (s->s) {
1358 if (s->s->get)
1359 r = s->s->get(s);
1360 else
1361 warnx("get_as_string skip %s\n", s->name);
1362 } else if (s->type == XT_S_INT)
1363 r = g_strdup_printf("%d", *s->ival);
1364 else if (s->type == XT_S_STR)
1365 r = g_strdup(*s->sval);
1366 else if (s->type == XT_S_FLOAT)
1367 r = g_strdup_printf("%f", *s->fval);
1368 else
1369 r = g_strdup_printf("INVALID TYPE");
1371 return (r);
1374 void
1375 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1377 int i;
1378 char *s;
1380 for (i = 0; i < LENGTH(rs); i++) {
1381 if (rs[i].s && rs[i].s->walk)
1382 rs[i].s->walk(&rs[i], cb, cb_args);
1383 else {
1384 s = get_as_string(&rs[i]);
1385 cb(&rs[i], s, cb_args);
1386 g_free(s);
1392 set_browser_mode(struct settings *s, char *val)
1394 if (!strcmp(val, "whitelist")) {
1395 browser_mode = XT_BM_WHITELIST;
1396 allow_volatile_cookies = 0;
1397 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1398 cookies_enabled = 1;
1399 enable_cookie_whitelist = 1;
1400 read_only_cookies = 0;
1401 save_rejected_cookies = 0;
1402 session_timeout = 3600;
1403 enable_scripts = 0;
1404 enable_js_whitelist = 1;
1405 enable_localstorage = 0;
1406 } else if (!strcmp(val, "normal")) {
1407 browser_mode = XT_BM_NORMAL;
1408 allow_volatile_cookies = 0;
1409 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1410 cookies_enabled = 1;
1411 enable_cookie_whitelist = 0;
1412 read_only_cookies = 0;
1413 save_rejected_cookies = 0;
1414 session_timeout = 3600;
1415 enable_scripts = 1;
1416 enable_js_whitelist = 0;
1417 enable_localstorage = 1;
1418 } else if (!strcmp(val, "kiosk")) {
1419 browser_mode = XT_BM_KIOSK;
1420 allow_volatile_cookies = 0;
1421 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1422 cookies_enabled = 1;
1423 enable_cookie_whitelist = 0;
1424 read_only_cookies = 0;
1425 save_rejected_cookies = 0;
1426 session_timeout = 3600;
1427 enable_scripts = 1;
1428 enable_js_whitelist = 0;
1429 enable_localstorage = 1;
1430 show_tabs = 0;
1431 tabless = 1;
1432 } else
1433 return (1);
1435 return (0);
1438 char *
1439 get_browser_mode(struct settings *s)
1441 char *r = NULL;
1443 if (browser_mode == XT_BM_WHITELIST)
1444 r = g_strdup("whitelist");
1445 else if (browser_mode == XT_BM_NORMAL)
1446 r = g_strdup("normal");
1447 else if (browser_mode == XT_BM_KIOSK)
1448 r = g_strdup("kiosk");
1449 else
1450 return (NULL);
1452 return (r);
1456 set_cookie_policy(struct settings *s, char *val)
1458 if (!strcmp(val, "no3rdparty"))
1459 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1460 else if (!strcmp(val, "accept"))
1461 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1462 else if (!strcmp(val, "reject"))
1463 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1464 else
1465 return (1);
1467 return (0);
1470 char *
1471 get_cookie_policy(struct settings *s)
1473 char *r = NULL;
1475 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1476 r = g_strdup("no3rdparty");
1477 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1478 r = g_strdup("accept");
1479 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1480 r = g_strdup("reject");
1481 else
1482 return (NULL);
1484 return (r);
1487 char *
1488 get_default_script(struct settings *s)
1490 if (default_script[0] == '\0')
1491 return (0);
1492 return (g_strdup(default_script));
1496 set_default_script(struct settings *s, char *val)
1498 if (val[0] == '~')
1499 snprintf(default_script, sizeof default_script, "%s/%s",
1500 pwd->pw_dir, &val[1]);
1501 else
1502 strlcpy(default_script, val, sizeof default_script);
1504 return (0);
1507 char *
1508 get_download_dir(struct settings *s)
1510 if (download_dir[0] == '\0')
1511 return (0);
1512 return (g_strdup(download_dir));
1516 set_download_dir(struct settings *s, char *val)
1518 if (val[0] == '~')
1519 snprintf(download_dir, sizeof download_dir, "%s/%s",
1520 pwd->pw_dir, &val[1]);
1521 else
1522 strlcpy(download_dir, val, sizeof download_dir);
1524 return (0);
1528 * Session IDs.
1529 * We use these to prevent people putting xxxt:// URLs on
1530 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1532 #define XT_XTP_SES_KEY_SZ 8
1533 #define XT_XTP_SES_KEY_HEX_FMT \
1534 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1535 char *dl_session_key; /* downloads */
1536 char *hl_session_key; /* history list */
1537 char *cl_session_key; /* cookie list */
1538 char *fl_session_key; /* favorites list */
1540 char work_dir[PATH_MAX];
1541 char certs_dir[PATH_MAX];
1542 char cache_dir[PATH_MAX];
1543 char sessions_dir[PATH_MAX];
1544 char cookie_file[PATH_MAX];
1545 SoupURI *proxy_uri = NULL;
1546 SoupSession *session;
1547 SoupCookieJar *s_cookiejar;
1548 SoupCookieJar *p_cookiejar;
1549 char rc_fname[PATH_MAX];
1551 struct mime_type_list mtl;
1552 struct alias_list aliases;
1554 /* protos */
1555 struct tab *create_new_tab(char *, struct undo *, int, int);
1556 void delete_tab(struct tab *);
1557 void setzoom_webkit(struct tab *, int);
1558 int run_script(struct tab *, char *);
1559 int download_rb_cmp(struct download *, struct download *);
1560 gboolean cmd_execute(struct tab *t, char *str);
1563 history_rb_cmp(struct history *h1, struct history *h2)
1565 return (strcmp(h1->uri, h2->uri));
1567 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1570 domain_rb_cmp(struct domain *d1, struct domain *d2)
1572 return (strcmp(d1->d, d2->d));
1574 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1576 char *
1577 get_work_dir(struct settings *s)
1579 if (work_dir[0] == '\0')
1580 return (0);
1581 return (g_strdup(work_dir));
1585 set_work_dir(struct settings *s, char *val)
1587 if (val[0] == '~')
1588 snprintf(work_dir, sizeof work_dir, "%s/%s",
1589 pwd->pw_dir, &val[1]);
1590 else
1591 strlcpy(work_dir, val, sizeof work_dir);
1593 return (0);
1596 char *
1597 get_tab_style(struct settings *s)
1599 if (tab_style == XT_TABS_NORMAL)
1600 return (g_strdup("normal"));
1601 else
1602 return (g_strdup("compact"));
1606 set_tab_style(struct settings *s, char *val)
1608 if (!strcmp(val, "normal"))
1609 tab_style = XT_TABS_NORMAL;
1610 else if (!strcmp(val, "compact"))
1611 tab_style = XT_TABS_COMPACT;
1612 else
1613 return (1);
1615 return (0);
1619 * generate a session key to secure xtp commands.
1620 * pass in a ptr to the key in question and it will
1621 * be modified in place.
1623 void
1624 generate_xtp_session_key(char **key)
1626 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1628 /* free old key */
1629 if (*key)
1630 g_free(*key);
1632 /* make a new one */
1633 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1634 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1635 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1636 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1638 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1642 * validate a xtp session key.
1643 * return 1 if OK
1646 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1648 if (strcmp(trusted, untrusted) != 0) {
1649 show_oops(t, "%s: xtp session key mismatch possible spoof",
1650 __func__);
1651 return (0);
1654 return (1);
1658 download_rb_cmp(struct download *e1, struct download *e2)
1660 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1662 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1664 struct valid_url_types {
1665 char *type;
1666 } vut[] = {
1667 { "http://" },
1668 { "https://" },
1669 { "ftp://" },
1670 { "file://" },
1671 { XT_XTP_STR },
1675 valid_url_type(char *url)
1677 int i;
1679 for (i = 0; i < LENGTH(vut); i++)
1680 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1681 return (0);
1683 return (1);
1686 void
1687 print_cookie(char *msg, SoupCookie *c)
1689 if (c == NULL)
1690 return;
1692 if (msg)
1693 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1694 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1695 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1696 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1697 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1698 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1699 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1700 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1701 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1702 DNPRINTF(XT_D_COOKIE, "====================================\n");
1705 void
1706 walk_alias(struct settings *s,
1707 void (*cb)(struct settings *, char *, void *), void *cb_args)
1709 struct alias *a;
1710 char *str;
1712 if (s == NULL || cb == NULL) {
1713 show_oops(NULL, "walk_alias invalid parameters");
1714 return;
1717 TAILQ_FOREACH(a, &aliases, entry) {
1718 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1719 cb(s, str, cb_args);
1720 g_free(str);
1724 char *
1725 match_alias(char *url_in)
1727 struct alias *a;
1728 char *arg;
1729 char *url_out = NULL, *search, *enc_arg;
1731 search = g_strdup(url_in);
1732 arg = search;
1733 if (strsep(&arg, " \t") == NULL) {
1734 show_oops(NULL, "match_alias: NULL URL");
1735 goto done;
1738 TAILQ_FOREACH(a, &aliases, entry) {
1739 if (!strcmp(search, a->a_name))
1740 break;
1743 if (a != NULL) {
1744 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1745 a->a_name);
1746 if (arg != NULL) {
1747 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1748 url_out = g_strdup_printf(a->a_uri, enc_arg);
1749 g_free(enc_arg);
1750 } else
1751 url_out = g_strdup_printf(a->a_uri, "");
1753 done:
1754 g_free(search);
1755 return (url_out);
1758 char *
1759 guess_url_type(char *url_in)
1761 struct stat sb;
1762 char *url_out = NULL, *enc_search = NULL;
1763 int i;
1765 /* substitute aliases */
1766 url_out = match_alias(url_in);
1767 if (url_out != NULL)
1768 return (url_out);
1770 /* see if we are an about page */
1771 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1772 for (i = 0; i < LENGTH(about_list); i++)
1773 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1774 about_list[i].name)) {
1775 url_out = g_strdup(url_in);
1776 goto done;
1779 if (guess_search && url_regex &&
1780 !(g_str_has_prefix(url_in, "http://") ||
1781 g_str_has_prefix(url_in, "https://"))) {
1782 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1783 /* invalid URI so search instead */
1784 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1785 url_out = g_strdup_printf(search_string, enc_search);
1786 g_free(enc_search);
1787 goto done;
1791 /* XXX not sure about this heuristic */
1792 if (stat(url_in, &sb) == 0)
1793 url_out = g_strdup_printf("file://%s", url_in);
1794 else
1795 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1796 done:
1797 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1799 return (url_out);
1802 void
1803 load_uri(struct tab *t, gchar *uri)
1805 struct karg args;
1806 gchar *newuri = NULL;
1807 int i;
1809 if (uri == NULL)
1810 return;
1812 /* Strip leading spaces. */
1813 while (*uri && isspace(*uri))
1814 uri++;
1816 if (strlen(uri) == 0) {
1817 blank(t, NULL);
1818 return;
1821 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1823 if (valid_url_type(uri)) {
1824 newuri = guess_url_type(uri);
1825 uri = newuri;
1828 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1829 for (i = 0; i < LENGTH(about_list); i++)
1830 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1831 bzero(&args, sizeof args);
1832 about_list[i].func(t, &args);
1833 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1834 FALSE);
1835 goto done;
1837 show_oops(t, "invalid about page");
1838 goto done;
1841 set_status(t, (char *)uri, XT_STATUS_LOADING);
1842 marks_clear(t);
1843 webkit_web_view_load_uri(t->wv, uri);
1844 done:
1845 if (newuri)
1846 g_free(newuri);
1849 const gchar *
1850 get_uri(struct tab *t)
1852 const gchar *uri = NULL;
1854 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1855 return NULL;
1856 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1857 uri = webkit_web_view_get_uri(t->wv);
1858 } else {
1859 /* use tmp_uri to make sure it is g_freed */
1860 if (t->tmp_uri)
1861 g_free(t->tmp_uri);
1862 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1863 about_list[t->xtp_meaning].name);
1864 uri = t->tmp_uri;
1866 return uri;
1869 const gchar *
1870 get_title(struct tab *t, bool window)
1872 const gchar *set = NULL, *title = NULL;
1873 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1875 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1876 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1877 goto notitle;
1879 title = webkit_web_view_get_title(t->wv);
1880 if ((set = title ? title : get_uri(t)))
1881 return set;
1883 notitle:
1884 set = window ? XT_NAME : "(untitled)";
1886 return set;
1890 add_alias(struct settings *s, char *line)
1892 char *l, *alias;
1893 struct alias *a = NULL;
1895 if (s == NULL || line == NULL) {
1896 show_oops(NULL, "add_alias invalid parameters");
1897 return (1);
1900 l = line;
1901 a = g_malloc(sizeof(*a));
1903 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1904 show_oops(NULL, "add_alias: incomplete alias definition");
1905 goto bad;
1907 if (strlen(alias) == 0 || strlen(l) == 0) {
1908 show_oops(NULL, "add_alias: invalid alias definition");
1909 goto bad;
1912 a->a_name = g_strdup(alias);
1913 a->a_uri = g_strdup(l);
1915 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1917 TAILQ_INSERT_TAIL(&aliases, a, entry);
1919 return (0);
1920 bad:
1921 if (a)
1922 g_free(a);
1923 return (1);
1927 add_mime_type(struct settings *s, char *line)
1929 char *mime_type;
1930 char *l;
1931 struct mime_type *m = NULL;
1932 int downloadfirst = 0;
1934 /* XXX this could be smarter */
1936 if (line == NULL || strlen(line) == 0) {
1937 show_oops(NULL, "add_mime_type invalid parameters");
1938 return (1);
1941 l = line;
1942 if (*l == '@') {
1943 downloadfirst = 1;
1944 l++;
1946 m = g_malloc(sizeof(*m));
1948 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1949 show_oops(NULL, "add_mime_type: invalid mime_type");
1950 goto bad;
1952 if (mime_type[strlen(mime_type) - 1] == '*') {
1953 mime_type[strlen(mime_type) - 1] = '\0';
1954 m->mt_default = 1;
1955 } else
1956 m->mt_default = 0;
1958 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1959 show_oops(NULL, "add_mime_type: invalid mime_type");
1960 goto bad;
1963 m->mt_type = g_strdup(mime_type);
1964 m->mt_action = g_strdup(l);
1965 m->mt_download = downloadfirst;
1967 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1968 m->mt_type, m->mt_action, m->mt_default);
1970 TAILQ_INSERT_TAIL(&mtl, m, entry);
1972 return (0);
1973 bad:
1974 if (m)
1975 g_free(m);
1976 return (1);
1979 struct mime_type *
1980 find_mime_type(char *mime_type)
1982 struct mime_type *m, *def = NULL, *rv = NULL;
1984 TAILQ_FOREACH(m, &mtl, entry) {
1985 if (m->mt_default &&
1986 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1987 def = m;
1989 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1990 rv = m;
1991 break;
1995 if (rv == NULL)
1996 rv = def;
1998 return (rv);
2001 void
2002 walk_mime_type(struct settings *s,
2003 void (*cb)(struct settings *, char *, void *), void *cb_args)
2005 struct mime_type *m;
2006 char *str;
2008 if (s == NULL || cb == NULL) {
2009 show_oops(NULL, "walk_mime_type invalid parameters");
2010 return;
2013 TAILQ_FOREACH(m, &mtl, entry) {
2014 str = g_strdup_printf("%s%s --> %s",
2015 m->mt_type,
2016 m->mt_default ? "*" : "",
2017 m->mt_action);
2018 cb(s, str, cb_args);
2019 g_free(str);
2023 void
2024 wl_add(char *str, struct domain_list *wl, int handy)
2026 struct domain *d;
2027 int add_dot = 0;
2028 char *p;
2030 if (str == NULL || wl == NULL || strlen(str) < 2)
2031 return;
2033 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2035 /* treat *.moo.com the same as .moo.com */
2036 if (str[0] == '*' && str[1] == '.')
2037 str = &str[1];
2038 else if (str[0] == '.')
2039 str = &str[0];
2040 else
2041 add_dot = 1;
2043 /* slice off port number */
2044 p = g_strrstr(str, ":");
2045 if (p)
2046 *p = '\0';
2048 d = g_malloc(sizeof *d);
2049 if (add_dot)
2050 d->d = g_strdup_printf(".%s", str);
2051 else
2052 d->d = g_strdup(str);
2053 d->handy = handy;
2055 if (RB_INSERT(domain_list, wl, d))
2056 goto unwind;
2058 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2059 return;
2060 unwind:
2061 if (d) {
2062 if (d->d)
2063 g_free(d->d);
2064 g_free(d);
2069 add_cookie_wl(struct settings *s, char *entry)
2071 wl_add(entry, &c_wl, 1);
2072 return (0);
2075 void
2076 walk_cookie_wl(struct settings *s,
2077 void (*cb)(struct settings *, char *, void *), void *cb_args)
2079 struct domain *d;
2081 if (s == NULL || cb == NULL) {
2082 show_oops(NULL, "walk_cookie_wl invalid parameters");
2083 return;
2086 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2087 cb(s, d->d, cb_args);
2090 void
2091 walk_js_wl(struct settings *s,
2092 void (*cb)(struct settings *, char *, void *), void *cb_args)
2094 struct domain *d;
2096 if (s == NULL || cb == NULL) {
2097 show_oops(NULL, "walk_js_wl invalid parameters");
2098 return;
2101 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2102 cb(s, d->d, cb_args);
2106 add_js_wl(struct settings *s, char *entry)
2108 wl_add(entry, &js_wl, 1 /* persistent */);
2109 return (0);
2112 struct domain *
2113 wl_find(const gchar *search, struct domain_list *wl)
2115 int i;
2116 struct domain *d = NULL, dfind;
2117 gchar *s = NULL;
2119 if (search == NULL || wl == NULL)
2120 return (NULL);
2121 if (strlen(search) < 2)
2122 return (NULL);
2124 if (search[0] != '.')
2125 s = g_strdup_printf(".%s", search);
2126 else
2127 s = g_strdup(search);
2129 for (i = strlen(s) - 1; i >= 0; i--) {
2130 if (s[i] == '.') {
2131 dfind.d = &s[i];
2132 d = RB_FIND(domain_list, wl, &dfind);
2133 if (d)
2134 goto done;
2138 done:
2139 if (s)
2140 g_free(s);
2142 return (d);
2145 struct domain *
2146 wl_find_uri(const gchar *s, struct domain_list *wl)
2148 int i;
2149 char *ss;
2150 struct domain *r;
2152 if (s == NULL || wl == NULL)
2153 return (NULL);
2155 if (!strncmp(s, "http://", strlen("http://")))
2156 s = &s[strlen("http://")];
2157 else if (!strncmp(s, "https://", strlen("https://")))
2158 s = &s[strlen("https://")];
2160 if (strlen(s) < 2)
2161 return (NULL);
2163 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2164 /* chop string at first slash */
2165 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2166 ss = g_strdup(s);
2167 ss[i] = '\0';
2168 r = wl_find(ss, wl);
2169 g_free(ss);
2170 return (r);
2173 return (NULL);
2177 settings_add(char *var, char *val)
2179 int i, rv, *p;
2180 gfloat *f;
2181 char **s;
2183 /* get settings */
2184 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2185 if (strcmp(var, rs[i].name))
2186 continue;
2188 if (rs[i].s) {
2189 if (rs[i].s->set(&rs[i], val))
2190 errx(1, "invalid value for %s: %s", var, val);
2191 rv = 1;
2192 break;
2193 } else
2194 switch (rs[i].type) {
2195 case XT_S_INT:
2196 p = rs[i].ival;
2197 *p = atoi(val);
2198 rv = 1;
2199 break;
2200 case XT_S_STR:
2201 s = rs[i].sval;
2202 if (s == NULL)
2203 errx(1, "invalid sval for %s",
2204 rs[i].name);
2205 if (*s)
2206 g_free(*s);
2207 *s = g_strdup(val);
2208 rv = 1;
2209 break;
2210 case XT_S_FLOAT:
2211 f = rs[i].fval;
2212 *f = atof(val);
2213 rv = 1;
2214 break;
2215 case XT_S_INVALID:
2216 default:
2217 errx(1, "invalid type for %s", var);
2219 break;
2221 return (rv);
2224 #define WS "\n= \t"
2225 void
2226 config_parse(char *filename, int runtime)
2228 FILE *config, *f;
2229 char *line, *cp, *var, *val;
2230 size_t len, lineno = 0;
2231 int handled;
2232 char file[PATH_MAX];
2233 struct stat sb;
2235 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2237 if (filename == NULL)
2238 return;
2240 if (runtime && runtime_settings[0] != '\0') {
2241 snprintf(file, sizeof file, "%s/%s",
2242 work_dir, runtime_settings);
2243 if (stat(file, &sb)) {
2244 warnx("runtime file doesn't exist, creating it");
2245 if ((f = fopen(file, "w")) == NULL)
2246 err(1, "runtime");
2247 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2248 fclose(f);
2250 } else
2251 strlcpy(file, filename, sizeof file);
2253 if ((config = fopen(file, "r")) == NULL) {
2254 warn("config_parse: cannot open %s", filename);
2255 return;
2258 for (;;) {
2259 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2260 if (feof(config) || ferror(config))
2261 break;
2263 cp = line;
2264 cp += (long)strspn(cp, WS);
2265 if (cp[0] == '\0') {
2266 /* empty line */
2267 free(line);
2268 continue;
2271 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2272 startpage_add("invalid configuration file entry: %s",
2273 line);
2275 cp += (long)strspn(cp, WS);
2277 if ((val = strsep(&cp, "\0")) == NULL)
2278 break;
2280 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2281 handled = settings_add(var, val);
2282 if (handled == 0)
2283 startpage_add("invalid configuration file entry: %s=%s",
2284 var, val);
2286 free(line);
2289 fclose(config);
2292 char *
2293 js_ref_to_string(JSContextRef context, JSValueRef ref)
2295 char *s = NULL;
2296 size_t l;
2297 JSStringRef jsref;
2299 jsref = JSValueToStringCopy(context, ref, NULL);
2300 if (jsref == NULL)
2301 return (NULL);
2303 l = JSStringGetMaximumUTF8CStringSize(jsref);
2304 s = g_malloc(l);
2305 if (s)
2306 JSStringGetUTF8CString(jsref, s, l);
2307 JSStringRelease(jsref);
2309 return (s);
2312 void
2313 disable_hints(struct tab *t)
2315 bzero(t->hint_buf, sizeof t->hint_buf);
2316 bzero(t->hint_num, sizeof t->hint_num);
2317 run_script(t, "vimprobable_clear()");
2318 t->hints_on = 0;
2319 t->hint_mode = XT_HINT_NONE;
2322 void
2323 enable_hints(struct tab *t)
2325 bzero(t->hint_buf, sizeof t->hint_buf);
2326 run_script(t, "vimprobable_show_hints()");
2327 t->hints_on = 1;
2328 t->hint_mode = XT_HINT_NONE;
2331 #define XT_JS_OPEN ("open;")
2332 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2333 #define XT_JS_FIRE ("fire;")
2334 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2335 #define XT_JS_FOUND ("found;")
2336 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2339 run_script(struct tab *t, char *s)
2341 JSGlobalContextRef ctx;
2342 WebKitWebFrame *frame;
2343 JSStringRef str;
2344 JSValueRef val, exception;
2345 char *es, buf[128];
2347 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2348 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2350 frame = webkit_web_view_get_main_frame(t->wv);
2351 ctx = webkit_web_frame_get_global_context(frame);
2353 str = JSStringCreateWithUTF8CString(s);
2354 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2355 NULL, 0, &exception);
2356 JSStringRelease(str);
2358 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2359 if (val == NULL) {
2360 es = js_ref_to_string(ctx, exception);
2361 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2362 g_free(es);
2363 return (1);
2364 } else {
2365 es = js_ref_to_string(ctx, val);
2366 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2368 /* handle return value right here */
2369 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2370 disable_hints(t);
2371 marks_clear(t);
2372 load_uri(t, &es[XT_JS_OPEN_LEN]);
2375 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2376 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2377 &es[XT_JS_FIRE_LEN]);
2378 run_script(t, buf);
2379 disable_hints(t);
2382 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2383 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2384 disable_hints(t);
2387 g_free(es);
2390 return (0);
2394 hint(struct tab *t, struct karg *args)
2397 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2399 if (t->hints_on == 0)
2400 enable_hints(t);
2401 else
2402 disable_hints(t);
2404 return (0);
2407 void
2408 apply_style(struct tab *t)
2410 g_object_set(G_OBJECT(t->settings),
2411 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2415 userstyle(struct tab *t, struct karg *args)
2417 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2419 if (t->styled) {
2420 t->styled = 0;
2421 g_object_set(G_OBJECT(t->settings),
2422 "user-stylesheet-uri", NULL, (char *)NULL);
2423 } else {
2424 t->styled = 1;
2425 apply_style(t);
2427 return (0);
2431 * Doesn't work fully, due to the following bug:
2432 * https://bugs.webkit.org/show_bug.cgi?id=51747
2435 restore_global_history(void)
2437 char file[PATH_MAX];
2438 FILE *f;
2439 struct history *h;
2440 gchar *uri;
2441 gchar *title;
2442 const char delim[3] = {'\\', '\\', '\0'};
2444 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2446 if ((f = fopen(file, "r")) == NULL) {
2447 warnx("%s: fopen", __func__);
2448 return (1);
2451 for (;;) {
2452 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2453 if (feof(f) || ferror(f))
2454 break;
2456 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2457 if (feof(f) || ferror(f)) {
2458 free(uri);
2459 warnx("%s: broken history file\n", __func__);
2460 return (1);
2463 if (uri && strlen(uri) && title && strlen(title)) {
2464 webkit_web_history_item_new_with_data(uri, title);
2465 h = g_malloc(sizeof(struct history));
2466 h->uri = g_strdup(uri);
2467 h->title = g_strdup(title);
2468 RB_INSERT(history_list, &hl, h);
2469 completion_add_uri(h->uri);
2470 } else {
2471 warnx("%s: failed to restore history\n", __func__);
2472 free(uri);
2473 free(title);
2474 return (1);
2477 free(uri);
2478 free(title);
2479 uri = NULL;
2480 title = NULL;
2483 return (0);
2487 save_global_history_to_disk(struct tab *t)
2489 char file[PATH_MAX];
2490 FILE *f;
2491 struct history *h;
2493 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2495 if ((f = fopen(file, "w")) == NULL) {
2496 show_oops(t, "%s: global history file: %s",
2497 __func__, strerror(errno));
2498 return (1);
2501 RB_FOREACH_REVERSE(h, history_list, &hl) {
2502 if (h->uri && h->title)
2503 fprintf(f, "%s\n%s\n", h->uri, h->title);
2506 fclose(f);
2508 return (0);
2512 quit(struct tab *t, struct karg *args)
2514 if (save_global_history)
2515 save_global_history_to_disk(t);
2517 gtk_main_quit();
2519 return (1);
2522 void
2523 restore_sessions_list(void)
2525 DIR *sdir = NULL;
2526 struct dirent *dp = NULL;
2527 struct session *s;
2529 sdir = opendir(sessions_dir);
2530 if (sdir) {
2531 while ((dp = readdir(sdir)) != NULL)
2532 if (dp->d_type == DT_REG) {
2533 s = g_malloc(sizeof(struct session));
2534 s->name = g_strdup(dp->d_name);
2535 TAILQ_INSERT_TAIL(&sessions, s, entry);
2537 closedir(sdir);
2542 open_tabs(struct tab *t, struct karg *a)
2544 char file[PATH_MAX];
2545 FILE *f = NULL;
2546 char *uri = NULL;
2547 int rv = 1;
2548 struct tab *ti, *tt;
2550 if (a == NULL)
2551 goto done;
2553 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2554 if ((f = fopen(file, "r")) == NULL)
2555 goto done;
2557 ti = TAILQ_LAST(&tabs, tab_list);
2559 for (;;) {
2560 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2561 if (feof(f) || ferror(f))
2562 break;
2564 /* retrieve session name */
2565 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2566 strlcpy(named_session,
2567 &uri[strlen(XT_SAVE_SESSION_ID)],
2568 sizeof named_session);
2569 continue;
2572 if (uri && strlen(uri))
2573 create_new_tab(uri, NULL, 1, -1);
2575 free(uri);
2576 uri = NULL;
2579 /* close open tabs */
2580 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2581 for (;;) {
2582 tt = TAILQ_FIRST(&tabs);
2583 if (tt != ti) {
2584 delete_tab(tt);
2585 continue;
2587 delete_tab(tt);
2588 break;
2590 recalc_tabs();
2593 rv = 0;
2594 done:
2595 if (f)
2596 fclose(f);
2598 return (rv);
2602 restore_saved_tabs(void)
2604 char file[PATH_MAX];
2605 int unlink_file = 0;
2606 struct stat sb;
2607 struct karg a;
2608 int rv = 0;
2610 snprintf(file, sizeof file, "%s/%s",
2611 sessions_dir, XT_RESTART_TABS_FILE);
2612 if (stat(file, &sb) == -1)
2613 a.s = XT_SAVED_TABS_FILE;
2614 else {
2615 unlink_file = 1;
2616 a.s = XT_RESTART_TABS_FILE;
2619 a.i = XT_SES_DONOTHING;
2620 rv = open_tabs(NULL, &a);
2622 if (unlink_file)
2623 unlink(file);
2625 return (rv);
2629 save_tabs(struct tab *t, struct karg *a)
2631 char file[PATH_MAX];
2632 FILE *f;
2633 int num_tabs = 0, i;
2634 struct tab **stabs = NULL;
2636 if (a == NULL)
2637 return (1);
2638 if (a->s == NULL)
2639 snprintf(file, sizeof file, "%s/%s",
2640 sessions_dir, named_session);
2641 else
2642 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2644 if ((f = fopen(file, "w")) == NULL) {
2645 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2646 return (1);
2649 /* save session name */
2650 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2652 /* Save tabs, in the order they are arranged in the notebook. */
2653 num_tabs = sort_tabs_by_page_num(&stabs);
2655 for (i = 0; i < num_tabs; i++)
2656 if (stabs[i]) {
2657 if (get_uri(stabs[i]) != NULL)
2658 fprintf(f, "%s\n", get_uri(stabs[i]));
2659 else if (gtk_entry_get_text(GTK_ENTRY(
2660 stabs[i]->uri_entry)))
2661 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2662 stabs[i]->uri_entry)));
2665 g_free(stabs);
2667 /* try and make sure this gets to disk NOW. XXX Backup first? */
2668 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2669 show_oops(t, "May not have managed to save session: %s",
2670 strerror(errno));
2673 fclose(f);
2675 return (0);
2679 save_tabs_and_quit(struct tab *t, struct karg *args)
2681 struct karg a;
2683 a.s = NULL;
2684 save_tabs(t, &a);
2685 quit(t, NULL);
2687 return (1);
2691 run_page_script(struct tab *t, struct karg *args)
2693 const gchar *uri;
2694 char *tmp, script[PATH_MAX];
2696 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2697 if (tmp[0] == '\0') {
2698 show_oops(t, "no script specified");
2699 return (1);
2702 if ((uri = get_uri(t)) == NULL) {
2703 show_oops(t, "tab is empty, not running script");
2704 return (1);
2707 if (tmp[0] == '~')
2708 snprintf(script, sizeof script, "%s/%s",
2709 pwd->pw_dir, &tmp[1]);
2710 else
2711 strlcpy(script, tmp, sizeof script);
2713 switch (fork()) {
2714 case -1:
2715 show_oops(t, "can't fork to run script");
2716 return (1);
2717 /* NOTREACHED */
2718 case 0:
2719 break;
2720 default:
2721 return (0);
2724 /* child */
2725 execlp(script, script, uri, (void *)NULL);
2727 _exit(0);
2729 /* NOTREACHED */
2731 return (0);
2735 yank_uri(struct tab *t, struct karg *args)
2737 const gchar *uri;
2738 GtkClipboard *clipboard;
2740 if ((uri = get_uri(t)) == NULL)
2741 return (1);
2743 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2744 gtk_clipboard_set_text(clipboard, uri, -1);
2746 return (0);
2750 paste_uri(struct tab *t, struct karg *args)
2752 GtkClipboard *clipboard;
2753 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2754 gint len;
2755 gchar *p = NULL, *uri;
2757 /* try primary clipboard first */
2758 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2759 p = gtk_clipboard_wait_for_text(clipboard);
2761 /* if it failed get whatever text is in cut_buffer0 */
2762 if (p == NULL && xterm_workaround)
2763 if (gdk_property_get(gdk_get_default_root_window(),
2764 atom,
2765 gdk_atom_intern("STRING", FALSE),
2767 1024 * 1024 /* picked out of my butt */,
2768 FALSE,
2769 NULL,
2770 NULL,
2771 &len,
2772 (guchar **)&p)) {
2773 /* yes sir, we need to NUL the string */
2774 p[len] = '\0';
2777 if (p) {
2778 uri = p;
2779 while (*uri && isspace(*uri))
2780 uri++;
2781 if (strlen(uri) == 0) {
2782 show_oops(t, "empty paste buffer");
2783 goto done;
2785 if (guess_search == 0 && valid_url_type(uri)) {
2786 /* we can be clever and paste this in search box */
2787 show_oops(t, "not a valid URL");
2788 goto done;
2791 if (args->i == XT_PASTE_CURRENT_TAB)
2792 load_uri(t, uri);
2793 else if (args->i == XT_PASTE_NEW_TAB)
2794 create_new_tab(uri, NULL, 1, -1);
2797 done:
2798 if (p)
2799 g_free(p);
2801 return (0);
2804 gchar *
2805 find_domain(const gchar *s, int toplevel)
2807 SoupURI *uri;
2808 gchar *ret, *p;
2810 if (s == NULL)
2811 return (NULL);
2813 uri = soup_uri_new(s);
2815 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2816 return (NULL);
2819 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2820 if ((p = strrchr(uri->host, '.')) != NULL) {
2821 while(--p >= uri->host && *p != '.');
2822 p++;
2823 } else
2824 p = uri->host;
2825 } else
2826 p = uri->host;
2828 ret = g_strdup_printf(".%s", p);
2830 soup_uri_free(uri);
2832 return ret;
2836 toggle_cwl(struct tab *t, struct karg *args)
2838 struct domain *d;
2839 const gchar *uri;
2840 char *dom = NULL;
2841 int es;
2843 if (args == NULL)
2844 return (1);
2846 uri = get_uri(t);
2847 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2849 if (uri == NULL || dom == NULL ||
2850 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2851 show_oops(t, "Can't toggle domain in cookie white list");
2852 goto done;
2854 d = wl_find(dom, &c_wl);
2856 if (d == NULL)
2857 es = 0;
2858 else
2859 es = 1;
2861 if (args->i & XT_WL_TOGGLE)
2862 es = !es;
2863 else if ((args->i & XT_WL_ENABLE) && es != 1)
2864 es = 1;
2865 else if ((args->i & XT_WL_DISABLE) && es != 0)
2866 es = 0;
2868 if (es)
2869 /* enable cookies for domain */
2870 wl_add(dom, &c_wl, 0);
2871 else
2872 /* disable cookies for domain */
2873 RB_REMOVE(domain_list, &c_wl, d);
2875 if (args->i & XT_WL_RELOAD)
2876 webkit_web_view_reload(t->wv);
2878 done:
2879 g_free(dom);
2880 return (0);
2884 toggle_js(struct tab *t, struct karg *args)
2886 int es;
2887 const gchar *uri;
2888 struct domain *d;
2889 char *dom = NULL;
2891 if (args == NULL)
2892 return (1);
2894 g_object_get(G_OBJECT(t->settings),
2895 "enable-scripts", &es, (char *)NULL);
2896 if (args->i & XT_WL_TOGGLE)
2897 es = !es;
2898 else if ((args->i & XT_WL_ENABLE) && es != 1)
2899 es = 1;
2900 else if ((args->i & XT_WL_DISABLE) && es != 0)
2901 es = 0;
2902 else
2903 return (1);
2905 uri = get_uri(t);
2906 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2908 if (uri == NULL || dom == NULL ||
2909 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2910 show_oops(t, "Can't toggle domain in JavaScript white list");
2911 goto done;
2914 if (es) {
2915 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2916 wl_add(dom, &js_wl, 0 /* session */);
2917 } else {
2918 d = wl_find(dom, &js_wl);
2919 if (d)
2920 RB_REMOVE(domain_list, &js_wl, d);
2921 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2923 g_object_set(G_OBJECT(t->settings),
2924 "enable-scripts", es, (char *)NULL);
2925 g_object_set(G_OBJECT(t->settings),
2926 "javascript-can-open-windows-automatically", es, (char *)NULL);
2927 webkit_web_view_set_settings(t->wv, t->settings);
2929 if (args->i & XT_WL_RELOAD)
2930 webkit_web_view_reload(t->wv);
2931 done:
2932 if (dom)
2933 g_free(dom);
2934 return (0);
2937 void
2938 js_toggle_cb(GtkWidget *w, struct tab *t)
2940 struct karg a;
2942 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2943 toggle_cwl(t, &a);
2945 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2946 toggle_js(t, &a);
2950 toggle_src(struct tab *t, struct karg *args)
2952 gboolean mode;
2954 if (t == NULL)
2955 return (0);
2957 mode = webkit_web_view_get_view_source_mode(t->wv);
2958 webkit_web_view_set_view_source_mode(t->wv, !mode);
2959 webkit_web_view_reload(t->wv);
2961 return (0);
2964 void
2965 focus_webview(struct tab *t)
2967 if (t == NULL)
2968 return;
2970 /* only grab focus if we are visible */
2971 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2972 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2976 focus(struct tab *t, struct karg *args)
2978 if (t == NULL || args == NULL)
2979 return (1);
2981 if (show_url == 0)
2982 return (0);
2984 if (args->i == XT_FOCUS_URI)
2985 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2986 else if (args->i == XT_FOCUS_SEARCH)
2987 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2989 return (0);
2993 stats(struct tab *t, struct karg *args)
2995 char *page, *body, *s, line[64 * 1024];
2996 uint64_t line_count = 0;
2997 FILE *r_cookie_f;
2999 if (t == NULL)
3000 show_oops(NULL, "stats invalid parameters");
3002 line[0] = '\0';
3003 if (save_rejected_cookies) {
3004 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3005 for (;;) {
3006 s = fgets(line, sizeof line, r_cookie_f);
3007 if (s == NULL || feof(r_cookie_f) ||
3008 ferror(r_cookie_f))
3009 break;
3010 line_count++;
3012 fclose(r_cookie_f);
3013 snprintf(line, sizeof line,
3014 "<br/>Cookies blocked(*) total: %llu", line_count);
3015 } else
3016 show_oops(t, "Can't open blocked cookies file: %s",
3017 strerror(errno));
3020 body = g_strdup_printf(
3021 "Cookies blocked(*) this session: %llu"
3022 "%s"
3023 "<p><small><b>*</b> results vary based on settings</small></p>",
3024 blocked_cookies,
3025 line);
3027 page = get_html_page("Statistics", body, "", 0);
3028 g_free(body);
3030 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3031 g_free(page);
3033 return (0);
3037 marco(struct tab *t, struct karg *args)
3039 char *page, line[64 * 1024];
3040 int len;
3042 if (t == NULL)
3043 show_oops(NULL, "marco invalid parameters");
3045 line[0] = '\0';
3046 snprintf(line, sizeof line, "%s", marco_message(&len));
3048 page = get_html_page("Marco Sez...", line, "", 0);
3050 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3051 g_free(page);
3053 return (0);
3057 blank(struct tab *t, struct karg *args)
3059 if (t == NULL)
3060 show_oops(NULL, "blank invalid parameters");
3062 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3064 return (0);
3068 about(struct tab *t, struct karg *args)
3070 char *page, *body;
3072 if (t == NULL)
3073 show_oops(NULL, "about invalid parameters");
3075 body = g_strdup_printf("<b>Version: %s</b>"
3076 #ifdef XXXTERM_BUILDSTR
3077 "<br><b>Build: %s</b>"
3078 #endif
3079 "<p>"
3080 "Authors:"
3081 "<ul>"
3082 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3083 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3084 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3085 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3086 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3087 "</ul>"
3088 "Copyrights and licenses can be found on the XXXTerm "
3089 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3090 "</p>",
3091 #ifdef XXXTERM_BUILDSTR
3092 version, XXXTERM_BUILDSTR
3093 #else
3094 version
3095 #endif
3098 page = get_html_page("About", body, "", 0);
3099 g_free(body);
3101 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3102 g_free(page);
3104 return (0);
3108 help(struct tab *t, struct karg *args)
3110 char *page, *head, *body;
3112 if (t == NULL)
3113 show_oops(NULL, "help invalid parameters");
3115 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3116 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3117 "</head>\n";
3118 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3119 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3120 "cgi-bin/man-cgi?xxxterm</a>";
3122 page = get_html_page(XT_NAME, body, head, FALSE);
3124 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3125 g_free(page);
3127 return (0);
3131 startpage(struct tab *t, struct karg *args)
3133 char *page, *body, *b;
3134 struct sp *s;
3136 if (t == NULL)
3137 show_oops(NULL, "startpage invalid parameters");
3139 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3141 TAILQ_FOREACH(s, &spl, entry) {
3142 b = body;
3143 body = g_strdup_printf("%s%s<br>", body, s->line);
3144 g_free(b);
3147 page = get_html_page("Startup Exception", body, "", 0);
3148 g_free(body);
3150 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3151 g_free(page);
3153 return (0);
3156 void
3157 startpage_add(const char *fmt, ...)
3159 va_list ap;
3160 char *msg;
3161 struct sp *s;
3163 if (fmt == NULL)
3164 return;
3166 va_start(ap, fmt);
3167 if (vasprintf(&msg, fmt, ap) == -1)
3168 errx(1, "startpage_add failed");
3169 va_end(ap);
3171 s = g_malloc0(sizeof *s);
3172 s->line = msg;
3174 TAILQ_INSERT_TAIL(&spl, s, entry);
3178 * update all favorite tabs apart from one. Pass NULL if
3179 * you want to update all.
3181 void
3182 update_favorite_tabs(struct tab *apart_from)
3184 struct tab *t;
3185 if (!updating_fl_tabs) {
3186 updating_fl_tabs = 1; /* stop infinite recursion */
3187 TAILQ_FOREACH(t, &tabs, entry)
3188 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3189 && (t != apart_from))
3190 xtp_page_fl(t, NULL);
3191 updating_fl_tabs = 0;
3195 /* show a list of favorites (bookmarks) */
3197 xtp_page_fl(struct tab *t, struct karg *args)
3199 char file[PATH_MAX];
3200 FILE *f;
3201 char *uri = NULL, *title = NULL;
3202 size_t len, lineno = 0;
3203 int i, failed = 0;
3204 char *body, *tmp, *page = NULL;
3205 const char delim[3] = {'\\', '\\', '\0'};
3207 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3209 if (t == NULL)
3210 warn("%s: bad param", __func__);
3212 /* new session key */
3213 if (!updating_fl_tabs)
3214 generate_xtp_session_key(&fl_session_key);
3216 /* open favorites */
3217 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3218 if ((f = fopen(file, "r")) == NULL) {
3219 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3220 return (1);
3223 /* body */
3224 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3225 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3226 "<th style='width: 40px'>Rm</th></tr>\n");
3228 for (i = 1;;) {
3229 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3230 if (feof(f) || ferror(f))
3231 break;
3232 if (strlen(title) == 0 || title[0] == '#') {
3233 free(title);
3234 title = NULL;
3235 continue;
3238 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3239 if (feof(f) || ferror(f)) {
3240 show_oops(t, "favorites file corrupt");
3241 failed = 1;
3242 break;
3245 tmp = body;
3246 body = g_strdup_printf("%s<tr>"
3247 "<td>%d</td>"
3248 "<td><a href='%s'>%s</a></td>"
3249 "<td style='text-align: center'>"
3250 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3251 "</tr>\n",
3252 body, i, uri, title,
3253 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3255 g_free(tmp);
3257 free(uri);
3258 uri = NULL;
3259 free(title);
3260 title = NULL;
3261 i++;
3263 fclose(f);
3265 /* if none, say so */
3266 if (i == 1) {
3267 tmp = body;
3268 body = g_strdup_printf("%s<tr>"
3269 "<td colspan='3' style='text-align: center'>"
3270 "No favorites - To add one use the 'favadd' command."
3271 "</td></tr>", body);
3272 g_free(tmp);
3275 tmp = body;
3276 body = g_strdup_printf("%s</table>", body);
3277 g_free(tmp);
3279 if (uri)
3280 free(uri);
3281 if (title)
3282 free(title);
3284 /* render */
3285 if (!failed) {
3286 page = get_html_page("Favorites", body, "", 1);
3287 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3288 g_free(page);
3291 update_favorite_tabs(t);
3293 if (body)
3294 g_free(body);
3296 return (failed);
3299 void
3300 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3301 size_t cert_count, char *title)
3303 gnutls_datum_t cinfo;
3304 char *tmp, *body;
3305 int i;
3307 body = g_strdup("");
3309 for (i = 0; i < cert_count; i++) {
3310 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3311 &cinfo))
3312 return;
3314 tmp = body;
3315 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3316 body, i, cinfo.data);
3317 gnutls_free(cinfo.data);
3318 g_free(tmp);
3321 tmp = get_html_page(title, body, "", 0);
3322 g_free(body);
3324 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3325 g_free(tmp);
3329 ca_cmd(struct tab *t, struct karg *args)
3331 FILE *f = NULL;
3332 int rv = 1, certs = 0, certs_read;
3333 struct stat sb;
3334 gnutls_datum_t dt;
3335 gnutls_x509_crt_t *c = NULL;
3336 char *certs_buf = NULL, *s;
3338 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3339 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3340 return (1);
3343 if (fstat(fileno(f), &sb) == -1) {
3344 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3345 goto done;
3348 certs_buf = g_malloc(sb.st_size + 1);
3349 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3350 show_oops(t, "Can't read CA file: %s", strerror(errno));
3351 goto done;
3353 certs_buf[sb.st_size] = '\0';
3355 s = certs_buf;
3356 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3357 certs++;
3358 s += strlen("BEGIN CERTIFICATE");
3361 bzero(&dt, sizeof dt);
3362 dt.data = (unsigned char *)certs_buf;
3363 dt.size = sb.st_size;
3364 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3365 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3366 GNUTLS_X509_FMT_PEM, 0);
3367 if (certs_read <= 0) {
3368 show_oops(t, "No cert(s) available");
3369 goto done;
3371 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3372 done:
3373 if (c)
3374 g_free(c);
3375 if (certs_buf)
3376 g_free(certs_buf);
3377 if (f)
3378 fclose(f);
3380 return (rv);
3384 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3385 size_t domain_sz)
3387 SoupURI *su = NULL;
3388 struct addrinfo hints, *res = NULL, *ai;
3389 int rv = -1, s = -1, on, error;
3390 char port[8];
3392 if (uri && !g_str_has_prefix(uri, "https://")) {
3393 show_oops(t, "invalid URI");
3394 goto done;
3397 su = soup_uri_new(uri);
3398 if (su == NULL) {
3399 show_oops(t, "invalid soup URI");
3400 goto done;
3402 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3403 show_oops(t, "invalid HTTPS URI");
3404 goto done;
3407 snprintf(port, sizeof port, "%d", su->port);
3408 bzero(&hints, sizeof(struct addrinfo));
3409 hints.ai_flags = AI_CANONNAME;
3410 hints.ai_family = AF_UNSPEC;
3411 hints.ai_socktype = SOCK_STREAM;
3413 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3414 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3415 goto done;
3418 for (ai = res; ai; ai = ai->ai_next) {
3419 if (s != -1) {
3420 close(s);
3421 s = -1;
3424 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3425 continue;
3426 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3427 if (s == -1)
3428 continue;
3429 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3430 sizeof(on)) == -1)
3431 continue;
3432 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3433 break;
3435 if (s == -1) {
3436 show_oops(t, "could not obtain certificates from: %s",
3437 su->host);
3438 goto done;
3441 if (domain)
3442 strlcpy(domain, su->host, domain_sz);
3443 rv = s;
3444 done:
3445 if (su)
3446 soup_uri_free(su);
3447 if (res)
3448 freeaddrinfo(res);
3449 if (rv == -1 && s != -1)
3450 close(s);
3452 return (rv);
3456 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3458 if (gsession)
3459 gnutls_deinit(gsession);
3460 if (xcred)
3461 gnutls_certificate_free_credentials(xcred);
3463 return (0);
3467 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3468 gnutls_certificate_credentials_t *xc)
3470 gnutls_certificate_credentials_t xcred;
3471 gnutls_session_t gsession;
3472 int rv = 1;
3474 if (gs == NULL || xc == NULL)
3475 goto done;
3477 *gs = NULL;
3478 *xc = NULL;
3480 gnutls_certificate_allocate_credentials(&xcred);
3481 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3482 GNUTLS_X509_FMT_PEM);
3484 gnutls_init(&gsession, GNUTLS_CLIENT);
3485 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3486 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3487 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3488 if ((rv = gnutls_handshake(gsession)) < 0) {
3489 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3491 gnutls_error_is_fatal(rv),
3492 gnutls_strerror_name(rv));
3493 stop_tls(gsession, xcred);
3494 goto done;
3497 gnutls_credentials_type_t cred;
3498 cred = gnutls_auth_get_type(gsession);
3499 if (cred != GNUTLS_CRD_CERTIFICATE) {
3500 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3501 stop_tls(gsession, xcred);
3502 goto done;
3505 *gs = gsession;
3506 *xc = xcred;
3507 rv = 0;
3508 done:
3509 return (rv);
3513 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3514 size_t *cert_count)
3516 unsigned int len;
3517 const gnutls_datum_t *cl;
3518 gnutls_x509_crt_t *all_certs;
3519 int i, rv = 1;
3521 if (certs == NULL || cert_count == NULL)
3522 goto done;
3523 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3524 goto done;
3525 cl = gnutls_certificate_get_peers(gsession, &len);
3526 if (len == 0)
3527 goto done;
3529 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3530 for (i = 0; i < len; i++) {
3531 gnutls_x509_crt_init(&all_certs[i]);
3532 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3533 GNUTLS_X509_FMT_PEM < 0)) {
3534 g_free(all_certs);
3535 goto done;
3539 *certs = all_certs;
3540 *cert_count = len;
3541 rv = 0;
3542 done:
3543 return (rv);
3546 void
3547 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3549 int i;
3551 for (i = 0; i < cert_count; i++)
3552 gnutls_x509_crt_deinit(certs[i]);
3553 g_free(certs);
3556 void
3557 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3559 GdkColor c_text, c_base;
3561 gdk_color_parse(text, &c_text);
3562 gdk_color_parse(base, &c_base);
3564 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3565 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3566 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3567 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3569 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3570 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3571 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3572 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3575 void
3576 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3577 size_t cert_count, char *domain)
3579 size_t cert_buf_sz;
3580 char cert_buf[64 * 1024], file[PATH_MAX];
3581 int i;
3582 FILE *f;
3583 GdkColor color;
3585 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3586 return;
3588 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3589 if ((f = fopen(file, "w")) == NULL) {
3590 show_oops(t, "Can't create cert file %s %s",
3591 file, strerror(errno));
3592 return;
3595 for (i = 0; i < cert_count; i++) {
3596 cert_buf_sz = sizeof cert_buf;
3597 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3598 cert_buf, &cert_buf_sz)) {
3599 show_oops(t, "gnutls_x509_crt_export failed");
3600 goto done;
3602 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3603 show_oops(t, "Can't write certs: %s", strerror(errno));
3604 goto done;
3608 /* not the best spot but oh well */
3609 gdk_color_parse("lightblue", &color);
3610 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3611 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3612 done:
3613 fclose(f);
3616 enum cert_trust {
3617 CERT_LOCAL,
3618 CERT_TRUSTED,
3619 CERT_UNTRUSTED,
3620 CERT_BAD
3623 enum cert_trust
3624 load_compare_cert(struct tab *t, struct karg *args)
3626 const gchar *uri;
3627 char domain[8182], file[PATH_MAX];
3628 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3629 int s = -1, i;
3630 unsigned int error;
3631 FILE *f = NULL;
3632 size_t cert_buf_sz, cert_count;
3633 enum cert_trust rv = CERT_UNTRUSTED;
3634 char serr[80];
3635 gnutls_session_t gsession;
3636 gnutls_x509_crt_t *certs;
3637 gnutls_certificate_credentials_t xcred;
3639 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3641 if (t == NULL)
3642 return (rv);
3644 if ((uri = get_uri(t)) == NULL)
3645 return (rv);
3646 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3648 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3649 return (rv);
3650 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3652 /* go ssl/tls */
3653 if (start_tls(t, s, &gsession, &xcred))
3654 goto done;
3655 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3657 /* verify certs in case cert file doesn't exist */
3658 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3659 GNUTLS_E_SUCCESS) {
3660 show_oops(t, "Invalid certificates");
3661 goto done;
3664 /* get certs */
3665 if (get_connection_certs(gsession, &certs, &cert_count)) {
3666 show_oops(t, "Can't get connection certificates");
3667 goto done;
3670 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3671 if ((f = fopen(file, "r")) == NULL) {
3672 if (!error)
3673 rv = CERT_TRUSTED;
3674 goto freeit;
3677 for (i = 0; i < cert_count; i++) {
3678 cert_buf_sz = sizeof cert_buf;
3679 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3680 cert_buf, &cert_buf_sz)) {
3681 goto freeit;
3683 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3684 rv = CERT_BAD; /* critical */
3685 goto freeit;
3687 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3688 rv = CERT_BAD; /* critical */
3689 goto freeit;
3691 rv = CERT_LOCAL;
3694 freeit:
3695 if (f)
3696 fclose(f);
3697 free_connection_certs(certs, cert_count);
3698 done:
3699 /* we close the socket first for speed */
3700 if (s != -1)
3701 close(s);
3703 /* only complain if we didn't save it locally */
3704 if (error && rv != CERT_LOCAL) {
3705 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3706 if (error & GNUTLS_CERT_INVALID)
3707 strlcat(serr, "invalid, ", sizeof serr);
3708 if (error & GNUTLS_CERT_REVOKED)
3709 strlcat(serr, "revoked, ", sizeof serr);
3710 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3711 strlcat(serr, "signer not found, ", sizeof serr);
3712 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3713 strlcat(serr, "not signed by CA, ", sizeof serr);
3714 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3715 strlcat(serr, "insecure algorithm, ", sizeof serr);
3716 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3717 strlcat(serr, "not activated, ", sizeof serr);
3718 if (error & GNUTLS_CERT_EXPIRED)
3719 strlcat(serr, "expired, ", sizeof serr);
3720 for (i = strlen(serr) - 1; i > 0; i--)
3721 if (serr[i] == ',') {
3722 serr[i] = '\0';
3723 break;
3725 show_oops(t, serr);
3728 stop_tls(gsession, xcred);
3730 return (rv);
3734 cert_cmd(struct tab *t, struct karg *args)
3736 const gchar *uri;
3737 char domain[8182];
3738 int s = -1;
3739 size_t cert_count;
3740 gnutls_session_t gsession;
3741 gnutls_x509_crt_t *certs;
3742 gnutls_certificate_credentials_t xcred;
3744 if (t == NULL)
3745 return (1);
3747 if (ssl_ca_file == NULL) {
3748 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3749 return (1);
3752 if ((uri = get_uri(t)) == NULL) {
3753 show_oops(t, "Invalid URI");
3754 return (1);
3757 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3758 show_oops(t, "Invalid certificate URI: %s", uri);
3759 return (1);
3762 /* go ssl/tls */
3763 if (start_tls(t, s, &gsession, &xcred))
3764 goto done;
3766 /* get certs */
3767 if (get_connection_certs(gsession, &certs, &cert_count)) {
3768 show_oops(t, "get_connection_certs failed");
3769 goto done;
3772 if (args->i & XT_SHOW)
3773 show_certs(t, certs, cert_count, "Certificate Chain");
3774 else if (args->i & XT_SAVE)
3775 save_certs(t, certs, cert_count, domain);
3777 free_connection_certs(certs, cert_count);
3778 done:
3779 /* we close the socket first for speed */
3780 if (s != -1)
3781 close(s);
3782 stop_tls(gsession, xcred);
3784 return (0);
3788 remove_cookie(int index)
3790 int i, rv = 1;
3791 GSList *cf;
3792 SoupCookie *c;
3794 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3796 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3798 for (i = 1; cf; cf = cf->next, i++) {
3799 if (i != index)
3800 continue;
3801 c = cf->data;
3802 print_cookie("remove cookie", c);
3803 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3804 rv = 0;
3805 break;
3808 soup_cookies_free(cf);
3810 return (rv);
3814 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3816 struct domain *d;
3817 char *tmp, *body;
3819 body = g_strdup("");
3821 /* p list */
3822 if (args->i & XT_WL_PERSISTENT) {
3823 tmp = body;
3824 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3825 g_free(tmp);
3826 RB_FOREACH(d, domain_list, wl) {
3827 if (d->handy == 0)
3828 continue;
3829 tmp = body;
3830 body = g_strdup_printf("%s%s<br/>", body, d->d);
3831 g_free(tmp);
3835 /* s list */
3836 if (args->i & XT_WL_SESSION) {
3837 tmp = body;
3838 body = g_strdup_printf("%s<h2>Session</h2>", body);
3839 g_free(tmp);
3840 RB_FOREACH(d, domain_list, wl) {
3841 if (d->handy == 1)
3842 continue;
3843 tmp = body;
3844 body = g_strdup_printf("%s%s<br/>", body, d->d);
3845 g_free(tmp);
3849 tmp = get_html_page(title, body, "", 0);
3850 g_free(body);
3851 if (wl == &js_wl)
3852 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3853 else
3854 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3855 g_free(tmp);
3856 return (0);
3860 wl_save(struct tab *t, struct karg *args, int js)
3862 char file[PATH_MAX];
3863 FILE *f;
3864 char *line = NULL, *lt = NULL, *dom = NULL;
3865 size_t linelen;
3866 const gchar *uri;
3867 struct karg a;
3868 struct domain *d;
3869 GSList *cf;
3870 SoupCookie *ci, *c;
3872 if (t == NULL || args == NULL)
3873 return (1);
3875 if (runtime_settings[0] == '\0')
3876 return (1);
3878 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3879 if ((f = fopen(file, "r+")) == NULL)
3880 return (1);
3882 uri = get_uri(t);
3883 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3884 if (uri == NULL || dom == NULL ||
3885 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3886 show_oops(t, "Can't add domain to %s white list",
3887 js ? "JavaScript" : "cookie");
3888 goto done;
3891 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3893 while (!feof(f)) {
3894 line = fparseln(f, &linelen, NULL, NULL, 0);
3895 if (line == NULL)
3896 continue;
3897 if (!strcmp(line, lt))
3898 goto done;
3899 free(line);
3900 line = NULL;
3903 fprintf(f, "%s\n", lt);
3905 a.i = XT_WL_ENABLE;
3906 a.i |= args->i;
3907 if (js) {
3908 d = wl_find(dom, &js_wl);
3909 if (!d) {
3910 settings_add("js_wl", dom);
3911 d = wl_find(dom, &js_wl);
3913 toggle_js(t, &a);
3914 } else {
3915 d = wl_find(dom, &c_wl);
3916 if (!d) {
3917 settings_add("cookie_wl", dom);
3918 d = wl_find(dom, &c_wl);
3920 toggle_cwl(t, &a);
3922 /* find and add to persistent jar */
3923 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3924 for (;cf; cf = cf->next) {
3925 ci = cf->data;
3926 if (!strcmp(dom, ci->domain) ||
3927 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3928 c = soup_cookie_copy(ci);
3929 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3932 soup_cookies_free(cf);
3934 if (d)
3935 d->handy = 1;
3937 done:
3938 if (line)
3939 free(line);
3940 if (dom)
3941 g_free(dom);
3942 if (lt)
3943 g_free(lt);
3944 fclose(f);
3946 return (0);
3950 js_show_wl(struct tab *t, struct karg *args)
3952 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3953 wl_show(t, args, "JavaScript White List", &js_wl);
3955 return (0);
3959 cookie_show_wl(struct tab *t, struct karg *args)
3961 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3962 wl_show(t, args, "Cookie White List", &c_wl);
3964 return (0);
3968 cookie_cmd(struct tab *t, struct karg *args)
3970 if (args->i & XT_SHOW)
3971 wl_show(t, args, "Cookie White List", &c_wl);
3972 else if (args->i & XT_WL_TOGGLE) {
3973 args->i |= XT_WL_RELOAD;
3974 toggle_cwl(t, args);
3975 } else if (args->i & XT_SAVE) {
3976 args->i |= XT_WL_RELOAD;
3977 wl_save(t, args, 0);
3978 } else if (args->i & XT_DELETE)
3979 show_oops(t, "'cookie delete' currently unimplemented");
3981 return (0);
3985 js_cmd(struct tab *t, struct karg *args)
3987 if (args->i & XT_SHOW)
3988 wl_show(t, args, "JavaScript White List", &js_wl);
3989 else if (args->i & XT_SAVE) {
3990 args->i |= XT_WL_RELOAD;
3991 wl_save(t, args, 1);
3992 } else if (args->i & XT_WL_TOGGLE) {
3993 args->i |= XT_WL_RELOAD;
3994 toggle_js(t, args);
3995 } else if (args->i & XT_DELETE)
3996 show_oops(t, "'js delete' currently unimplemented");
3998 return (0);
4002 toplevel_cmd(struct tab *t, struct karg *args)
4004 js_toggle_cb(t->js_toggle, t);
4006 return (0);
4010 add_favorite(struct tab *t, struct karg *args)
4012 char file[PATH_MAX];
4013 FILE *f;
4014 char *line = NULL;
4015 size_t urilen, linelen;
4016 const gchar *uri, *title;
4018 if (t == NULL)
4019 return (1);
4021 /* don't allow adding of xtp pages to favorites */
4022 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4023 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4024 return (1);
4027 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4028 if ((f = fopen(file, "r+")) == NULL) {
4029 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4030 return (1);
4033 title = get_title(t, FALSE);
4034 uri = get_uri(t);
4036 if (title == NULL || uri == NULL) {
4037 show_oops(t, "can't add page to favorites");
4038 goto done;
4041 urilen = strlen(uri);
4043 for (;;) {
4044 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4045 if (feof(f) || ferror(f))
4046 break;
4048 if (linelen == urilen && !strcmp(line, uri))
4049 goto done;
4051 free(line);
4052 line = NULL;
4055 fprintf(f, "\n%s\n%s", title, uri);
4056 done:
4057 if (line)
4058 free(line);
4059 fclose(f);
4061 update_favorite_tabs(NULL);
4063 return (0);
4067 can_go_back_for_real(struct tab *t)
4069 int i;
4070 WebKitWebHistoryItem *item;
4071 const gchar *uri;
4073 if (t == NULL)
4074 return (FALSE);
4076 /* rely on webkit to make sure we can go backward when on an about page */
4077 uri = get_uri(t);
4078 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4079 return (webkit_web_view_can_go_back(t->wv));
4081 /* the back/forwars list is stupid so help determine if we can go back */
4082 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4083 item != NULL;
4084 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4085 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4086 return (TRUE);
4089 return (FALSE);
4093 can_go_forward_for_real(struct tab *t)
4095 int i;
4096 WebKitWebHistoryItem *item;
4097 const gchar *uri;
4099 if (t == NULL)
4100 return (FALSE);
4102 /* rely on webkit to make sure we can go forward when on an about page */
4103 uri = get_uri(t);
4104 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4105 return (webkit_web_view_can_go_forward(t->wv));
4107 /* the back/forwars list is stupid so help selecting a different item */
4108 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4109 item != NULL;
4110 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4111 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4112 return (TRUE);
4115 return (FALSE);
4118 void
4119 go_back_for_real(struct tab *t)
4121 int i;
4122 WebKitWebHistoryItem *item;
4123 const gchar *uri;
4125 if (t == NULL)
4126 return;
4128 uri = get_uri(t);
4129 if (uri == NULL) {
4130 webkit_web_view_go_back(t->wv);
4131 return;
4133 /* the back/forwars list is stupid so help selecting a different item */
4134 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4135 item != NULL;
4136 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4137 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4138 webkit_web_view_go_to_back_forward_item(t->wv, item);
4139 break;
4144 void
4145 go_forward_for_real(struct tab *t)
4147 int i;
4148 WebKitWebHistoryItem *item;
4149 const gchar *uri;
4151 if (t == NULL)
4152 return;
4154 uri = get_uri(t);
4155 if (uri == NULL) {
4156 webkit_web_view_go_forward(t->wv);
4157 return;
4159 /* the back/forwars list is stupid so help selecting a different item */
4160 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4161 item != NULL;
4162 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4163 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4164 webkit_web_view_go_to_back_forward_item(t->wv, item);
4165 break;
4171 navaction(struct tab *t, struct karg *args)
4173 WebKitWebHistoryItem *item;
4174 WebKitWebFrame *frame;
4176 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4177 t->tab_id, args->i);
4179 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4180 if (t->item) {
4181 if (args->i == XT_NAV_BACK)
4182 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4183 else
4184 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4185 if (item == NULL)
4186 return (XT_CB_PASSTHROUGH);
4187 webkit_web_view_go_to_back_forward_item(t->wv, item);
4188 t->item = NULL;
4189 return (XT_CB_PASSTHROUGH);
4192 switch (args->i) {
4193 case XT_NAV_BACK:
4194 marks_clear(t);
4195 go_back_for_real(t);
4196 break;
4197 case XT_NAV_FORWARD:
4198 marks_clear(t);
4199 go_forward_for_real(t);
4200 break;
4201 case XT_NAV_RELOAD:
4202 frame = webkit_web_view_get_main_frame(t->wv);
4203 webkit_web_frame_reload(frame);
4204 break;
4206 return (XT_CB_PASSTHROUGH);
4210 move(struct tab *t, struct karg *args)
4212 GtkAdjustment *adjust;
4213 double pi, si, pos, ps, upper, lower, max;
4214 double percent;
4216 switch (args->i) {
4217 case XT_MOVE_DOWN:
4218 case XT_MOVE_UP:
4219 case XT_MOVE_BOTTOM:
4220 case XT_MOVE_TOP:
4221 case XT_MOVE_PAGEDOWN:
4222 case XT_MOVE_PAGEUP:
4223 case XT_MOVE_HALFDOWN:
4224 case XT_MOVE_HALFUP:
4225 case XT_MOVE_PERCENT:
4226 adjust = t->adjust_v;
4227 break;
4228 default:
4229 adjust = t->adjust_h;
4230 break;
4233 pos = gtk_adjustment_get_value(adjust);
4234 ps = gtk_adjustment_get_page_size(adjust);
4235 upper = gtk_adjustment_get_upper(adjust);
4236 lower = gtk_adjustment_get_lower(adjust);
4237 si = gtk_adjustment_get_step_increment(adjust);
4238 pi = gtk_adjustment_get_page_increment(adjust);
4239 max = upper - ps;
4241 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4242 "max %f si %f pi %f\n",
4243 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4244 pos, ps, upper, lower, max, si, pi);
4246 switch (args->i) {
4247 case XT_MOVE_DOWN:
4248 case XT_MOVE_RIGHT:
4249 pos += si;
4250 gtk_adjustment_set_value(adjust, MIN(pos, max));
4251 break;
4252 case XT_MOVE_UP:
4253 case XT_MOVE_LEFT:
4254 pos -= si;
4255 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4256 break;
4257 case XT_MOVE_BOTTOM:
4258 case XT_MOVE_FARRIGHT:
4259 gtk_adjustment_set_value(adjust, max);
4260 break;
4261 case XT_MOVE_TOP:
4262 case XT_MOVE_FARLEFT:
4263 gtk_adjustment_set_value(adjust, lower);
4264 break;
4265 case XT_MOVE_PAGEDOWN:
4266 pos += pi;
4267 gtk_adjustment_set_value(adjust, MIN(pos, max));
4268 break;
4269 case XT_MOVE_PAGEUP:
4270 pos -= pi;
4271 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4272 break;
4273 case XT_MOVE_HALFDOWN:
4274 pos += pi / 2;
4275 gtk_adjustment_set_value(adjust, MIN(pos, max));
4276 break;
4277 case XT_MOVE_HALFUP:
4278 pos -= pi / 2;
4279 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4280 break;
4281 case XT_MOVE_PERCENT:
4282 percent = atoi(args->s) / 100.0;
4283 pos = max * percent;
4284 if (pos < 0.0 || pos > max)
4285 break;
4286 gtk_adjustment_set_value(adjust, pos);
4287 break;
4288 default:
4289 return (XT_CB_PASSTHROUGH);
4292 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4294 return (XT_CB_HANDLED);
4297 void
4298 url_set_visibility(void)
4300 struct tab *t;
4302 TAILQ_FOREACH(t, &tabs, entry)
4303 if (show_url == 0) {
4304 gtk_widget_hide(t->toolbar);
4305 focus_webview(t);
4306 } else
4307 gtk_widget_show(t->toolbar);
4310 void
4311 notebook_tab_set_visibility(void)
4313 if (show_tabs == 0) {
4314 gtk_widget_hide(tab_bar);
4315 gtk_notebook_set_show_tabs(notebook, FALSE);
4316 } else {
4317 if (tab_style == XT_TABS_NORMAL) {
4318 gtk_widget_hide(tab_bar);
4319 gtk_notebook_set_show_tabs(notebook, TRUE);
4320 } else if (tab_style == XT_TABS_COMPACT) {
4321 gtk_widget_show(tab_bar);
4322 gtk_notebook_set_show_tabs(notebook, FALSE);
4327 void
4328 statusbar_set_visibility(void)
4330 struct tab *t;
4332 TAILQ_FOREACH(t, &tabs, entry)
4333 if (show_statusbar == 0) {
4334 gtk_widget_hide(t->statusbar_box);
4335 focus_webview(t);
4336 } else
4337 gtk_widget_show(t->statusbar_box);
4340 void
4341 url_set(struct tab *t, int enable_url_entry)
4343 GdkPixbuf *pixbuf;
4344 int progress;
4346 show_url = enable_url_entry;
4348 if (enable_url_entry) {
4349 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4350 GTK_ENTRY_ICON_PRIMARY, NULL);
4351 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4352 } else {
4353 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4354 GTK_ENTRY_ICON_PRIMARY);
4355 progress =
4356 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4357 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4358 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4359 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4360 progress);
4365 fullscreen(struct tab *t, struct karg *args)
4367 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4369 if (t == NULL)
4370 return (XT_CB_PASSTHROUGH);
4372 if (show_url == 0) {
4373 url_set(t, 1);
4374 show_tabs = 1;
4375 } else {
4376 url_set(t, 0);
4377 show_tabs = 0;
4380 url_set_visibility();
4381 notebook_tab_set_visibility();
4383 return (XT_CB_HANDLED);
4387 statustoggle(struct tab *t, struct karg *args)
4389 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4391 if (show_statusbar == 1) {
4392 show_statusbar = 0;
4393 statusbar_set_visibility();
4394 } else if (show_statusbar == 0) {
4395 show_statusbar = 1;
4396 statusbar_set_visibility();
4398 return (XT_CB_HANDLED);
4402 urlaction(struct tab *t, struct karg *args)
4404 int rv = XT_CB_HANDLED;
4406 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4408 if (t == NULL)
4409 return (XT_CB_PASSTHROUGH);
4411 switch (args->i) {
4412 case XT_URL_SHOW:
4413 if (show_url == 0) {
4414 url_set(t, 1);
4415 url_set_visibility();
4417 break;
4418 case XT_URL_HIDE:
4419 if (show_url == 1) {
4420 url_set(t, 0);
4421 url_set_visibility();
4423 break;
4425 return (rv);
4429 tabaction(struct tab *t, struct karg *args)
4431 int rv = XT_CB_HANDLED;
4432 char *url = args->s;
4433 struct undo *u;
4434 struct tab *tt;
4436 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4438 if (t == NULL)
4439 return (XT_CB_PASSTHROUGH);
4441 switch (args->i) {
4442 case XT_TAB_NEW:
4443 if (strlen(url) > 0)
4444 create_new_tab(url, NULL, 1, args->precount);
4445 else
4446 create_new_tab(NULL, NULL, 1, args->precount);
4447 break;
4448 case XT_TAB_DELETE:
4449 if (args->precount < 0)
4450 delete_tab(t);
4451 else
4452 TAILQ_FOREACH(tt, &tabs, entry)
4453 if (tt->tab_id == args->precount - 1) {
4454 delete_tab(tt);
4455 break;
4457 break;
4458 case XT_TAB_DELQUIT:
4459 if (gtk_notebook_get_n_pages(notebook) > 1)
4460 delete_tab(t);
4461 else
4462 quit(t, args);
4463 break;
4464 case XT_TAB_OPEN:
4465 if (strlen(url) > 0)
4467 else {
4468 rv = XT_CB_PASSTHROUGH;
4469 goto done;
4471 load_uri(t, url);
4472 break;
4473 case XT_TAB_SHOW:
4474 if (show_tabs == 0) {
4475 show_tabs = 1;
4476 notebook_tab_set_visibility();
4478 break;
4479 case XT_TAB_HIDE:
4480 if (show_tabs == 1) {
4481 show_tabs = 0;
4482 notebook_tab_set_visibility();
4484 break;
4485 case XT_TAB_NEXTSTYLE:
4486 if (tab_style == XT_TABS_NORMAL) {
4487 tab_style = XT_TABS_COMPACT;
4488 recolor_compact_tabs();
4490 else
4491 tab_style = XT_TABS_NORMAL;
4492 notebook_tab_set_visibility();
4493 break;
4494 case XT_TAB_UNDO_CLOSE:
4495 if (undo_count == 0) {
4496 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4497 __func__);
4498 goto done;
4499 } else {
4500 undo_count--;
4501 u = TAILQ_FIRST(&undos);
4502 create_new_tab(u->uri, u, 1, -1);
4504 TAILQ_REMOVE(&undos, u, entry);
4505 g_free(u->uri);
4506 /* u->history is freed in create_new_tab() */
4507 g_free(u);
4509 break;
4510 default:
4511 rv = XT_CB_PASSTHROUGH;
4512 goto done;
4515 done:
4516 if (args->s) {
4517 g_free(args->s);
4518 args->s = NULL;
4521 return (rv);
4525 resizetab(struct tab *t, struct karg *args)
4527 if (t == NULL || args == NULL) {
4528 show_oops(NULL, "resizetab invalid parameters");
4529 return (XT_CB_PASSTHROUGH);
4532 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4533 t->tab_id, args->i);
4535 setzoom_webkit(t, args->i);
4537 return (XT_CB_HANDLED);
4541 movetab(struct tab *t, struct karg *args)
4543 int n, dest;
4545 if (t == NULL || args == NULL) {
4546 show_oops(NULL, "movetab invalid parameters");
4547 return (XT_CB_PASSTHROUGH);
4550 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4551 t->tab_id, args->i);
4553 if (args->i >= XT_TAB_INVALID)
4554 return (XT_CB_PASSTHROUGH);
4556 if (TAILQ_EMPTY(&tabs))
4557 return (XT_CB_PASSTHROUGH);
4559 n = gtk_notebook_get_n_pages(notebook);
4560 dest = gtk_notebook_get_current_page(notebook);
4562 switch (args->i) {
4563 case XT_TAB_NEXT:
4564 if (args->precount < 0)
4565 dest = dest == n - 1 ? 0 : dest + 1;
4566 else
4567 dest = args->precount - 1;
4569 break;
4570 case XT_TAB_PREV:
4571 if (args->precount < 0)
4572 dest -= 1;
4573 else
4574 dest -= args->precount % n;
4576 if (dest < 0)
4577 dest += n;
4579 break;
4580 case XT_TAB_FIRST:
4581 dest = 0;
4582 break;
4583 case XT_TAB_LAST:
4584 dest = n - 1;
4585 break;
4586 default:
4587 return (XT_CB_PASSTHROUGH);
4590 if (dest < 0 || dest >= n)
4591 return (XT_CB_PASSTHROUGH);
4592 if (t->tab_id == dest) {
4593 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4594 return (XT_CB_HANDLED);
4597 set_current_tab(dest);
4599 return (XT_CB_HANDLED);
4602 int cmd_prefix = 0;
4605 command(struct tab *t, struct karg *args)
4607 char *s = NULL, *ss = NULL;
4608 GdkColor color;
4609 const gchar *uri;
4611 if (t == NULL || args == NULL) {
4612 show_oops(NULL, "command invalid parameters");
4613 return (XT_CB_PASSTHROUGH);
4616 switch (args->i) {
4617 case '/':
4618 s = "/";
4619 break;
4620 case '?':
4621 s = "?";
4622 break;
4623 case ':':
4624 if (cmd_prefix == 0)
4625 s = ":";
4626 else {
4627 ss = g_strdup_printf(":%d", cmd_prefix);
4628 s = ss;
4629 cmd_prefix = 0;
4631 break;
4632 case XT_CMD_OPEN:
4633 s = ":open ";
4634 break;
4635 case XT_CMD_TABNEW:
4636 s = ":tabnew ";
4637 break;
4638 case XT_CMD_OPEN_CURRENT:
4639 s = ":open ";
4640 /* FALL THROUGH */
4641 case XT_CMD_TABNEW_CURRENT:
4642 if (!s) /* FALL THROUGH? */
4643 s = ":tabnew ";
4644 if ((uri = get_uri(t)) != NULL) {
4645 ss = g_strdup_printf("%s%s", s, uri);
4646 s = ss;
4648 break;
4649 default:
4650 show_oops(t, "command: invalid opcode %d", args->i);
4651 return (XT_CB_PASSTHROUGH);
4654 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4656 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4657 gdk_color_parse(XT_COLOR_WHITE, &color);
4658 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4659 show_cmd(t);
4660 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4661 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4663 if (ss)
4664 g_free(ss);
4666 return (XT_CB_HANDLED);
4670 * Return a new string with a download row (in html)
4671 * appended. Old string is freed.
4673 char *
4674 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4677 WebKitDownloadStatus stat;
4678 char *status_html = NULL, *cmd_html = NULL, *new_html;
4679 gdouble progress;
4680 char cur_sz[FMT_SCALED_STRSIZE];
4681 char tot_sz[FMT_SCALED_STRSIZE];
4682 char *xtp_prefix;
4684 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4686 /* All actions wil take this form:
4687 * xxxt://class/seskey
4689 xtp_prefix = g_strdup_printf("%s%d/%s/",
4690 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4692 stat = webkit_download_get_status(dl->download);
4694 switch (stat) {
4695 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4696 status_html = g_strdup_printf("Finished");
4697 cmd_html = g_strdup_printf(
4698 "<a href='%s%d/%d'>Remove</a>",
4699 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4700 break;
4701 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4702 /* gather size info */
4703 progress = 100 * webkit_download_get_progress(dl->download);
4705 fmt_scaled(
4706 webkit_download_get_current_size(dl->download), cur_sz);
4707 fmt_scaled(
4708 webkit_download_get_total_size(dl->download), tot_sz);
4710 status_html = g_strdup_printf(
4711 "<div style='width: 100%%' align='center'>"
4712 "<div class='progress-outer'>"
4713 "<div class='progress-inner' style='width: %.2f%%'>"
4714 "</div></div></div>"
4715 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4716 progress, cur_sz, tot_sz, progress);
4718 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4719 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4721 break;
4722 /* LLL */
4723 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4724 status_html = g_strdup_printf("Cancelled");
4725 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4726 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4727 break;
4728 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4729 status_html = g_strdup_printf("Error!");
4730 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4731 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4732 break;
4733 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4734 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4735 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4736 status_html = g_strdup_printf("Starting");
4737 break;
4738 default:
4739 show_oops(t, "%s: unknown download status", __func__);
4742 new_html = g_strdup_printf(
4743 "%s\n<tr><td>%s</td><td>%s</td>"
4744 "<td style='text-align:center'>%s</td></tr>\n",
4745 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4746 status_html, cmd_html);
4747 g_free(html);
4749 if (status_html)
4750 g_free(status_html);
4752 if (cmd_html)
4753 g_free(cmd_html);
4755 g_free(xtp_prefix);
4757 return new_html;
4761 * update all download tabs apart from one. Pass NULL if
4762 * you want to update all.
4764 void
4765 update_download_tabs(struct tab *apart_from)
4767 struct tab *t;
4768 if (!updating_dl_tabs) {
4769 updating_dl_tabs = 1; /* stop infinite recursion */
4770 TAILQ_FOREACH(t, &tabs, entry)
4771 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4772 && (t != apart_from))
4773 xtp_page_dl(t, NULL);
4774 updating_dl_tabs = 0;
4779 * update all cookie tabs apart from one. Pass NULL if
4780 * you want to update all.
4782 void
4783 update_cookie_tabs(struct tab *apart_from)
4785 struct tab *t;
4786 if (!updating_cl_tabs) {
4787 updating_cl_tabs = 1; /* stop infinite recursion */
4788 TAILQ_FOREACH(t, &tabs, entry)
4789 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4790 && (t != apart_from))
4791 xtp_page_cl(t, NULL);
4792 updating_cl_tabs = 0;
4797 * update all history tabs apart from one. Pass NULL if
4798 * you want to update all.
4800 void
4801 update_history_tabs(struct tab *apart_from)
4803 struct tab *t;
4805 if (!updating_hl_tabs) {
4806 updating_hl_tabs = 1; /* stop infinite recursion */
4807 TAILQ_FOREACH(t, &tabs, entry)
4808 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4809 && (t != apart_from))
4810 xtp_page_hl(t, NULL);
4811 updating_hl_tabs = 0;
4815 /* cookie management XTP page */
4817 xtp_page_cl(struct tab *t, struct karg *args)
4819 char *body, *page, *tmp;
4820 int i = 1; /* all ids start 1 */
4821 GSList *sc, *pc, *pc_start;
4822 SoupCookie *c;
4823 char *type, *table_headers, *last_domain;
4825 DNPRINTF(XT_D_CMD, "%s", __func__);
4827 if (t == NULL) {
4828 show_oops(NULL, "%s invalid parameters", __func__);
4829 return (1);
4832 /* Generate a new session key */
4833 if (!updating_cl_tabs)
4834 generate_xtp_session_key(&cl_session_key);
4836 /* table headers */
4837 table_headers = g_strdup_printf("<table><tr>"
4838 "<th>Type</th>"
4839 "<th>Name</th>"
4840 "<th style='width:200px'>Value</th>"
4841 "<th>Path</th>"
4842 "<th>Expires</th>"
4843 "<th>Secure</th>"
4844 "<th>HTTP<br />only</th>"
4845 "<th style='width:40px'>Rm</th></tr>\n");
4847 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4848 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4849 pc_start = pc;
4851 body = NULL;
4852 last_domain = strdup("");
4853 for (; sc; sc = sc->next) {
4854 c = sc->data;
4856 if (strcmp(last_domain, c->domain) != 0) {
4857 /* new domain */
4858 free(last_domain);
4859 last_domain = strdup(c->domain);
4861 if (body != NULL) {
4862 tmp = body;
4863 body = g_strdup_printf("%s</table>"
4864 "<h2>%s</h2>%s\n",
4865 body, c->domain, table_headers);
4866 g_free(tmp);
4867 } else {
4868 /* first domain */
4869 body = g_strdup_printf("<h2>%s</h2>%s\n",
4870 c->domain, table_headers);
4874 type = "Session";
4875 for (pc = pc_start; pc; pc = pc->next)
4876 if (soup_cookie_equal(pc->data, c)) {
4877 type = "Session + Persistent";
4878 break;
4881 tmp = body;
4882 body = g_strdup_printf(
4883 "%s\n<tr>"
4884 "<td>%s</td>"
4885 "<td style='word-wrap:normal'>%s</td>"
4886 "<td>"
4887 " <textarea rows='4'>%s</textarea>"
4888 "</td>"
4889 "<td>%s</td>"
4890 "<td>%s</td>"
4891 "<td>%d</td>"
4892 "<td>%d</td>"
4893 "<td style='text-align:center'>"
4894 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4895 body,
4896 type,
4897 c->name,
4898 c->value,
4899 c->path,
4900 c->expires ?
4901 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4902 c->secure,
4903 c->http_only,
4905 XT_XTP_STR,
4906 XT_XTP_CL,
4907 cl_session_key,
4908 XT_XTP_CL_REMOVE,
4912 g_free(tmp);
4913 i++;
4916 soup_cookies_free(sc);
4917 soup_cookies_free(pc);
4919 /* small message if there are none */
4920 if (i == 1) {
4921 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4922 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4924 tmp = body;
4925 body = g_strdup_printf("%s</table>", body);
4926 g_free(tmp);
4928 page = get_html_page("Cookie Jar", body, "", TRUE);
4929 g_free(body);
4930 g_free(table_headers);
4931 g_free(last_domain);
4933 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4934 update_cookie_tabs(t);
4936 g_free(page);
4938 return (0);
4942 xtp_page_hl(struct tab *t, struct karg *args)
4944 char *body, *page, *tmp;
4945 struct history *h;
4946 int i = 1; /* all ids start 1 */
4948 DNPRINTF(XT_D_CMD, "%s", __func__);
4950 if (t == NULL) {
4951 show_oops(NULL, "%s invalid parameters", __func__);
4952 return (1);
4955 /* Generate a new session key */
4956 if (!updating_hl_tabs)
4957 generate_xtp_session_key(&hl_session_key);
4959 /* body */
4960 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4961 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4963 RB_FOREACH_REVERSE(h, history_list, &hl) {
4964 tmp = body;
4965 body = g_strdup_printf(
4966 "%s\n<tr>"
4967 "<td><a href='%s'>%s</a></td>"
4968 "<td>%s</td>"
4969 "<td style='text-align: center'>"
4970 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4971 body, h->uri, h->uri, h->title,
4972 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4973 XT_XTP_HL_REMOVE, i);
4975 g_free(tmp);
4976 i++;
4979 /* small message if there are none */
4980 if (i == 1) {
4981 tmp = body;
4982 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4983 "colspan='3'>No History</td></tr>\n", body);
4984 g_free(tmp);
4987 tmp = body;
4988 body = g_strdup_printf("%s</table>", body);
4989 g_free(tmp);
4991 page = get_html_page("History", body, "", TRUE);
4992 g_free(body);
4995 * update all history manager tabs as the xtp session
4996 * key has now changed. No need to update the current tab.
4997 * Already did that above.
4999 update_history_tabs(t);
5001 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5002 g_free(page);
5004 return (0);
5008 * Generate a web page detailing the status of any downloads
5011 xtp_page_dl(struct tab *t, struct karg *args)
5013 struct download *dl;
5014 char *body, *page, *tmp;
5015 char *ref;
5016 int n_dl = 1;
5018 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5020 if (t == NULL) {
5021 show_oops(NULL, "%s invalid parameters", __func__);
5022 return (1);
5026 * Generate a new session key for next page instance.
5027 * This only happens for the top level call to xtp_page_dl()
5028 * in which case updating_dl_tabs is 0.
5030 if (!updating_dl_tabs)
5031 generate_xtp_session_key(&dl_session_key);
5033 /* header - with refresh so as to update */
5034 if (refresh_interval >= 1)
5035 ref = g_strdup_printf(
5036 "<meta http-equiv='refresh' content='%u"
5037 ";url=%s%d/%s/%d' />\n",
5038 refresh_interval,
5039 XT_XTP_STR,
5040 XT_XTP_DL,
5041 dl_session_key,
5042 XT_XTP_DL_LIST);
5043 else
5044 ref = g_strdup("");
5046 body = g_strdup_printf("<div align='center'>"
5047 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5048 "</p><table><tr><th style='width: 60%%'>"
5049 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5050 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5052 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5053 body = xtp_page_dl_row(t, body, dl);
5054 n_dl++;
5057 /* message if no downloads in list */
5058 if (n_dl == 1) {
5059 tmp = body;
5060 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5061 " style='text-align: center'>"
5062 "No downloads</td></tr>\n", body);
5063 g_free(tmp);
5066 tmp = body;
5067 body = g_strdup_printf("%s</table></div>", body);
5068 g_free(tmp);
5070 page = get_html_page("Downloads", body, ref, 1);
5071 g_free(ref);
5072 g_free(body);
5075 * update all download manager tabs as the xtp session
5076 * key has now changed. No need to update the current tab.
5077 * Already did that above.
5079 update_download_tabs(t);
5081 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5082 g_free(page);
5084 return (0);
5088 search(struct tab *t, struct karg *args)
5090 gboolean d;
5092 if (t == NULL || args == NULL) {
5093 show_oops(NULL, "search invalid parameters");
5094 return (1);
5097 switch (args->i) {
5098 case XT_SEARCH_NEXT:
5099 d = t->search_forward;
5100 break;
5101 case XT_SEARCH_PREV:
5102 d = !t->search_forward;
5103 break;
5104 default:
5105 return (XT_CB_PASSTHROUGH);
5108 if (t->search_text == NULL) {
5109 if (global_search == NULL)
5110 return (XT_CB_PASSTHROUGH);
5111 else {
5112 d = t->search_forward = TRUE;
5113 t->search_text = g_strdup(global_search);
5114 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5115 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5119 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5120 t->tab_id, args->i, t->search_forward, t->search_text);
5122 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5124 return (XT_CB_HANDLED);
5127 struct settings_args {
5128 char **body;
5129 int i;
5132 void
5133 print_setting(struct settings *s, char *val, void *cb_args)
5135 char *tmp, *color;
5136 struct settings_args *sa = cb_args;
5138 if (sa == NULL)
5139 return;
5141 if (s->flags & XT_SF_RUNTIME)
5142 color = "#22cc22";
5143 else
5144 color = "#cccccc";
5146 tmp = *sa->body;
5147 *sa->body = g_strdup_printf(
5148 "%s\n<tr>"
5149 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5150 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5151 *sa->body,
5152 color,
5153 s->name,
5154 color,
5157 g_free(tmp);
5158 sa->i++;
5162 set_show(struct tab *t, struct karg *args)
5164 char *body, *page, *tmp;
5165 int i = 1;
5166 struct settings_args sa;
5168 bzero(&sa, sizeof sa);
5169 sa.body = &body;
5171 /* body */
5172 body = g_strdup_printf("<div align='center'><table><tr>"
5173 "<th align='left'>Setting</th>"
5174 "<th align='left'>Value</th></tr>\n");
5176 settings_walk(print_setting, &sa);
5177 i = sa.i;
5179 /* small message if there are none */
5180 if (i == 1) {
5181 tmp = body;
5182 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5183 "colspan='2'>No settings</td></tr>\n", body);
5184 g_free(tmp);
5187 tmp = body;
5188 body = g_strdup_printf("%s</table></div>", body);
5189 g_free(tmp);
5191 page = get_html_page("Settings", body, "", 0);
5193 g_free(body);
5195 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5197 g_free(page);
5199 return (XT_CB_PASSTHROUGH);
5203 set(struct tab *t, struct karg *args)
5205 char *p, *val;
5206 int i;
5208 if (args == NULL || args->s == NULL)
5209 return (set_show(t, args));
5211 /* strip spaces */
5212 p = g_strstrip(args->s);
5214 if (strlen(p) == 0)
5215 return (set_show(t, args));
5217 /* we got some sort of string */
5218 val = g_strrstr(p, "=");
5219 if (val) {
5220 *val++ = '\0';
5221 val = g_strchomp(val);
5222 p = g_strchomp(p);
5224 for (i = 0; i < LENGTH(rs); i++) {
5225 if (strcmp(rs[i].name, p))
5226 continue;
5228 if (rs[i].activate) {
5229 if (rs[i].activate(val))
5230 show_oops(t, "%s invalid value %s",
5231 p, val);
5232 else
5233 show_oops(t, ":set %s = %s", p, val);
5234 goto done;
5235 } else {
5236 show_oops(t, "not a runtime option: %s", p);
5237 goto done;
5240 show_oops(t, "unknown option: %s", p);
5241 } else {
5242 p = g_strchomp(p);
5244 for (i = 0; i < LENGTH(rs); i++) {
5245 if (strcmp(rs[i].name, p))
5246 continue;
5248 /* XXX this could use some cleanup */
5249 switch (rs[i].type) {
5250 case XT_S_INT:
5251 if (rs[i].ival)
5252 show_oops(t, "%s = %d",
5253 rs[i].name, *rs[i].ival);
5254 else if (rs[i].s && rs[i].s->get)
5255 show_oops(t, "%s = %s",
5256 rs[i].name,
5257 rs[i].s->get(&rs[i]));
5258 else if (rs[i].s && rs[i].s->get == NULL)
5259 show_oops(t, "%s = ...", rs[i].name);
5260 else
5261 show_oops(t, "%s = ", rs[i].name);
5262 break;
5263 case XT_S_FLOAT:
5264 if (rs[i].fval)
5265 show_oops(t, "%s = %f",
5266 rs[i].name, *rs[i].fval);
5267 else if (rs[i].s && rs[i].s->get)
5268 show_oops(t, "%s = %s",
5269 rs[i].name,
5270 rs[i].s->get(&rs[i]));
5271 else if (rs[i].s && rs[i].s->get == NULL)
5272 show_oops(t, "%s = ...", rs[i].name);
5273 else
5274 show_oops(t, "%s = ", rs[i].name);
5275 break;
5276 case XT_S_STR:
5277 if (rs[i].sval && *rs[i].sval)
5278 show_oops(t, "%s = %s",
5279 rs[i].name, *rs[i].sval);
5280 else if (rs[i].s && rs[i].s->get)
5281 show_oops(t, "%s = %s",
5282 rs[i].name,
5283 rs[i].s->get(&rs[i]));
5284 else if (rs[i].s && rs[i].s->get == NULL)
5285 show_oops(t, "%s = ...", rs[i].name);
5286 else
5287 show_oops(t, "%s = ", rs[i].name);
5288 break;
5289 default:
5290 show_oops(t, "unknown type for %s", rs[i].name);
5291 goto done;
5294 goto done;
5296 show_oops(t, "unknown option: %s", p);
5298 done:
5299 return (XT_CB_PASSTHROUGH);
5303 session_save(struct tab *t, char *filename)
5305 struct karg a;
5306 int rv = 1;
5307 struct session *s;
5309 if (strlen(filename) == 0)
5310 goto done;
5312 if (filename[0] == '.' || filename[0] == '/')
5313 goto done;
5315 a.s = filename;
5316 if (save_tabs(t, &a))
5317 goto done;
5318 strlcpy(named_session, filename, sizeof named_session);
5320 /* add the new session to the list of sessions */
5321 s = g_malloc(sizeof(struct session));
5322 s->name = g_strdup(filename);
5323 TAILQ_INSERT_TAIL(&sessions, s, entry);
5325 rv = 0;
5326 done:
5327 return (rv);
5331 session_open(struct tab *t, char *filename)
5333 struct karg a;
5334 int rv = 1;
5336 if (strlen(filename) == 0)
5337 goto done;
5339 if (filename[0] == '.' || filename[0] == '/')
5340 goto done;
5342 a.s = filename;
5343 a.i = XT_SES_CLOSETABS;
5344 if (open_tabs(t, &a))
5345 goto done;
5347 strlcpy(named_session, filename, sizeof named_session);
5349 rv = 0;
5350 done:
5351 return (rv);
5355 session_delete(struct tab *t, char *filename)
5357 char file[PATH_MAX];
5358 int rv = 1;
5359 struct session *s;
5361 if (strlen(filename) == 0)
5362 goto done;
5364 if (filename[0] == '.' || filename[0] == '/')
5365 goto done;
5367 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5368 if (unlink(file))
5369 goto done;
5371 if (!strcmp(filename, named_session))
5372 strlcpy(named_session, XT_SAVED_TABS_FILE,
5373 sizeof named_session);
5375 /* remove session from sessions list */
5376 TAILQ_FOREACH(s, &sessions, entry) {
5377 if (!strcmp(s->name, filename))
5378 break;
5380 TAILQ_REMOVE(&sessions, s, entry);
5381 g_free((gpointer) s->name);
5382 g_free(s);
5384 rv = 0;
5385 done:
5386 return (rv);
5390 session_cmd(struct tab *t, struct karg *args)
5392 char *filename = args->s;
5394 if (t == NULL)
5395 return (1);
5397 if (args->i & XT_SHOW)
5398 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5399 XT_SAVED_TABS_FILE : named_session);
5400 else if (args->i & XT_SAVE) {
5401 if (session_save(t, filename)) {
5402 show_oops(t, "Can't save session: %s",
5403 filename ? filename : "INVALID");
5404 goto done;
5406 } else if (args->i & XT_OPEN) {
5407 if (session_open(t, filename)) {
5408 show_oops(t, "Can't open session: %s",
5409 filename ? filename : "INVALID");
5410 goto done;
5412 } else if (args->i & XT_DELETE) {
5413 if (session_delete(t, filename)) {
5414 show_oops(t, "Can't delete session: %s",
5415 filename ? filename : "INVALID");
5416 goto done;
5419 done:
5420 return (XT_CB_PASSTHROUGH);
5424 * Make a hardcopy of the page
5427 print_page(struct tab *t, struct karg *args)
5429 WebKitWebFrame *frame;
5430 GtkPageSetup *ps;
5431 GtkPrintOperation *op;
5432 GtkPrintOperationAction action;
5433 GtkPrintOperationResult print_res;
5434 GError *g_err = NULL;
5435 int marg_l, marg_r, marg_t, marg_b;
5437 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5439 ps = gtk_page_setup_new();
5440 op = gtk_print_operation_new();
5441 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5442 frame = webkit_web_view_get_main_frame(t->wv);
5444 /* the default margins are too small, so we will bump them */
5445 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5446 XT_PRINT_EXTRA_MARGIN;
5447 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5448 XT_PRINT_EXTRA_MARGIN;
5449 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5450 XT_PRINT_EXTRA_MARGIN;
5451 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5452 XT_PRINT_EXTRA_MARGIN;
5454 /* set margins */
5455 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5456 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5457 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5458 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5460 gtk_print_operation_set_default_page_setup(op, ps);
5462 /* this appears to free 'op' and 'ps' */
5463 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5465 /* check it worked */
5466 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5467 show_oops(NULL, "can't print: %s", g_err->message);
5468 g_error_free (g_err);
5469 return (1);
5472 return (0);
5476 go_home(struct tab *t, struct karg *args)
5478 load_uri(t, home);
5479 return (0);
5483 set_encoding(struct tab *t, struct karg *args)
5485 const gchar *e;
5487 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5488 e = webkit_web_view_get_custom_encoding(t->wv);
5489 if (e == NULL)
5490 e = webkit_web_view_get_encoding(t->wv);
5491 show_oops(t, "encoding: %s", e ? e : "N/A");
5492 } else
5493 webkit_web_view_set_custom_encoding(t->wv, args->s);
5495 return (0);
5499 restart(struct tab *t, struct karg *args)
5501 struct karg a;
5503 a.s = XT_RESTART_TABS_FILE;
5504 save_tabs(t, &a);
5505 execvp(start_argv[0], start_argv);
5506 /* NOTREACHED */
5508 return (0);
5511 #define CTRL GDK_CONTROL_MASK
5512 #define MOD1 GDK_MOD1_MASK
5513 #define SHFT GDK_SHIFT_MASK
5515 /* inherent to GTK not all keys will be caught at all times */
5516 /* XXX sort key bindings */
5517 struct key_binding {
5518 char *cmd;
5519 guint mask;
5520 guint use_in_entry;
5521 guint key;
5522 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5523 } keys[] = {
5524 { "cookiejar", MOD1, 0, GDK_j },
5525 { "downloadmgr", MOD1, 0, GDK_d },
5526 { "history", MOD1, 0, GDK_h },
5527 { "print", CTRL, 0, GDK_p },
5528 { "search", 0, 0, GDK_slash },
5529 { "searchb", 0, 0, GDK_question },
5530 { "statustoggle", CTRL, 0, GDK_n },
5531 { "command", 0, 0, GDK_colon },
5532 { "qa", CTRL, 0, GDK_q },
5533 { "restart", MOD1, 0, GDK_q },
5534 { "js toggle", CTRL, 0, GDK_j },
5535 { "cookie toggle", MOD1, 0, GDK_c },
5536 { "togglesrc", CTRL, 0, GDK_s },
5537 { "yankuri", 0, 0, GDK_y },
5538 { "pasteuricur", 0, 0, GDK_p },
5539 { "pasteurinew", 0, 0, GDK_P },
5540 { "toplevel toggle", 0, 0, GDK_F4 },
5541 { "help", 0, 0, GDK_F1 },
5542 { "run_script", MOD1, 0, GDK_r },
5544 /* search */
5545 { "searchnext", 0, 0, GDK_n },
5546 { "searchprevious", 0, 0, GDK_N },
5548 /* focus */
5549 { "focusaddress", 0, 0, GDK_F6 },
5550 { "focussearch", 0, 0, GDK_F7 },
5552 /* hinting */
5553 { "hinting", 0, 0, GDK_f },
5555 /* custom stylesheet */
5556 { "userstyle", 0, 0, GDK_i },
5558 /* navigation */
5559 { "goback", 0, 0, GDK_BackSpace },
5560 { "goback", MOD1, 0, GDK_Left },
5561 { "goforward", SHFT, 0, GDK_BackSpace },
5562 { "goforward", MOD1, 0, GDK_Right },
5563 { "reload", 0, 0, GDK_F5 },
5564 { "reload", CTRL, 0, GDK_r },
5565 { "reload", CTRL, 0, GDK_l },
5566 { "favorites", MOD1, 1, GDK_f },
5568 /* vertical movement */
5569 { "scrolldown", 0, 0, GDK_j },
5570 { "scrolldown", 0, 0, GDK_Down },
5571 { "scrollup", 0, 0, GDK_Up },
5572 { "scrollup", 0, 0, GDK_k },
5573 { "scrollbottom", 0, 0, GDK_G },
5574 { "scrollbottom", 0, 0, GDK_End },
5575 { "scrolltop", 0, 0, GDK_Home },
5576 { "scrollpagedown", 0, 0, GDK_space },
5577 { "scrollpagedown", CTRL, 0, GDK_f },
5578 { "scrollhalfdown", CTRL, 0, GDK_d },
5579 { "scrollpagedown", 0, 0, GDK_Page_Down },
5580 { "scrollpageup", 0, 0, GDK_Page_Up },
5581 { "scrollpageup", CTRL, 0, GDK_b },
5582 { "scrollhalfup", CTRL, 0, GDK_u },
5583 /* horizontal movement */
5584 { "scrollright", 0, 0, GDK_l },
5585 { "scrollright", 0, 0, GDK_Right },
5586 { "scrollleft", 0, 0, GDK_Left },
5587 { "scrollleft", 0, 0, GDK_h },
5588 { "scrollfarright", 0, 0, GDK_dollar },
5589 { "scrollfarleft", 0, 0, GDK_0 },
5591 /* tabs */
5592 { "tabnew", CTRL, 0, GDK_t },
5593 { "999tabnew", CTRL, 0, GDK_T },
5594 { "tabclose", CTRL, 1, GDK_w },
5595 { "tabundoclose", 0, 0, GDK_U },
5596 { "tabnext 1", CTRL, 0, GDK_1 },
5597 { "tabnext 2", CTRL, 0, GDK_2 },
5598 { "tabnext 3", CTRL, 0, GDK_3 },
5599 { "tabnext 4", CTRL, 0, GDK_4 },
5600 { "tabnext 5", CTRL, 0, GDK_5 },
5601 { "tabnext 6", CTRL, 0, GDK_6 },
5602 { "tabnext 7", CTRL, 0, GDK_7 },
5603 { "tabnext 8", CTRL, 0, GDK_8 },
5604 { "tabnext 9", CTRL, 0, GDK_9 },
5605 { "tabfirst", CTRL, 0, GDK_less },
5606 { "tablast", CTRL, 0, GDK_greater },
5607 { "tabprevious", CTRL, 0, GDK_Left },
5608 { "tabnext", CTRL, 0, GDK_Right },
5609 { "focusout", CTRL, 0, GDK_minus },
5610 { "focusin", CTRL, 0, GDK_plus },
5611 { "focusin", CTRL, 0, GDK_equal },
5612 { "focusreset", CTRL, 0, GDK_0 },
5614 /* command aliases (handy when -S flag is used) */
5615 { "promptopen", 0, 0, GDK_F9 },
5616 { "promptopencurrent", 0, 0, GDK_F10 },
5617 { "prompttabnew", 0, 0, GDK_F11 },
5618 { "prompttabnewcurrent",0, 0, GDK_F12 },
5620 TAILQ_HEAD(keybinding_list, key_binding);
5622 void
5623 walk_kb(struct settings *s,
5624 void (*cb)(struct settings *, char *, void *), void *cb_args)
5626 struct key_binding *k;
5627 char str[1024];
5629 if (s == NULL || cb == NULL) {
5630 show_oops(NULL, "walk_kb invalid parameters");
5631 return;
5634 TAILQ_FOREACH(k, &kbl, entry) {
5635 if (k->cmd == NULL)
5636 continue;
5637 str[0] = '\0';
5639 /* sanity */
5640 if (gdk_keyval_name(k->key) == NULL)
5641 continue;
5643 strlcat(str, k->cmd, sizeof str);
5644 strlcat(str, ",", sizeof str);
5646 if (k->mask & GDK_SHIFT_MASK)
5647 strlcat(str, "S-", sizeof str);
5648 if (k->mask & GDK_CONTROL_MASK)
5649 strlcat(str, "C-", sizeof str);
5650 if (k->mask & GDK_MOD1_MASK)
5651 strlcat(str, "M1-", sizeof str);
5652 if (k->mask & GDK_MOD2_MASK)
5653 strlcat(str, "M2-", sizeof str);
5654 if (k->mask & GDK_MOD3_MASK)
5655 strlcat(str, "M3-", sizeof str);
5656 if (k->mask & GDK_MOD4_MASK)
5657 strlcat(str, "M4-", sizeof str);
5658 if (k->mask & GDK_MOD5_MASK)
5659 strlcat(str, "M5-", sizeof str);
5661 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5662 cb(s, str, cb_args);
5666 void
5667 init_keybindings(void)
5669 int i;
5670 struct key_binding *k;
5672 for (i = 0; i < LENGTH(keys); i++) {
5673 k = g_malloc0(sizeof *k);
5674 k->cmd = keys[i].cmd;
5675 k->mask = keys[i].mask;
5676 k->use_in_entry = keys[i].use_in_entry;
5677 k->key = keys[i].key;
5678 TAILQ_INSERT_HEAD(&kbl, k, entry);
5680 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5681 k->cmd ? k->cmd : "unnamed key");
5685 void
5686 keybinding_clearall(void)
5688 struct key_binding *k, *next;
5690 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5691 next = TAILQ_NEXT(k, entry);
5692 if (k->cmd == NULL)
5693 continue;
5695 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5696 k->cmd ? k->cmd : "unnamed key");
5697 TAILQ_REMOVE(&kbl, k, entry);
5698 g_free(k);
5703 keybinding_add(char *cmd, char *key, int use_in_entry)
5705 struct key_binding *k;
5706 guint keyval, mask = 0;
5707 int i;
5709 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5711 /* Keys which are to be used in entry have been prefixed with an
5712 * exclamation mark. */
5713 if (use_in_entry)
5714 key++;
5716 /* find modifier keys */
5717 if (strstr(key, "S-"))
5718 mask |= GDK_SHIFT_MASK;
5719 if (strstr(key, "C-"))
5720 mask |= GDK_CONTROL_MASK;
5721 if (strstr(key, "M1-"))
5722 mask |= GDK_MOD1_MASK;
5723 if (strstr(key, "M2-"))
5724 mask |= GDK_MOD2_MASK;
5725 if (strstr(key, "M3-"))
5726 mask |= GDK_MOD3_MASK;
5727 if (strstr(key, "M4-"))
5728 mask |= GDK_MOD4_MASK;
5729 if (strstr(key, "M5-"))
5730 mask |= GDK_MOD5_MASK;
5732 /* find keyname */
5733 for (i = strlen(key) - 1; i > 0; i--)
5734 if (key[i] == '-')
5735 key = &key[i + 1];
5737 /* validate keyname */
5738 keyval = gdk_keyval_from_name(key);
5739 if (keyval == GDK_VoidSymbol) {
5740 warnx("invalid keybinding name %s", key);
5741 return (1);
5743 /* must run this test too, gtk+ doesn't handle 10 for example */
5744 if (gdk_keyval_name(keyval) == NULL) {
5745 warnx("invalid keybinding name %s", key);
5746 return (1);
5749 /* Remove eventual dupes. */
5750 TAILQ_FOREACH(k, &kbl, entry)
5751 if (k->key == keyval && k->mask == mask) {
5752 TAILQ_REMOVE(&kbl, k, entry);
5753 g_free(k);
5754 break;
5757 /* add keyname */
5758 k = g_malloc0(sizeof *k);
5759 k->cmd = g_strdup(cmd);
5760 k->mask = mask;
5761 k->use_in_entry = use_in_entry;
5762 k->key = keyval;
5764 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5765 k->cmd,
5766 k->mask,
5767 k->use_in_entry,
5768 k->key);
5769 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5770 k->cmd, gdk_keyval_name(keyval));
5772 TAILQ_INSERT_HEAD(&kbl, k, entry);
5774 return (0);
5778 add_kb(struct settings *s, char *entry)
5780 char *kb, *key;
5782 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5784 /* clearall is special */
5785 if (!strcmp(entry, "clearall")) {
5786 keybinding_clearall();
5787 return (0);
5790 kb = strstr(entry, ",");
5791 if (kb == NULL)
5792 return (1);
5793 *kb = '\0';
5794 key = kb + 1;
5796 return (keybinding_add(entry, key, key[0] == '!'));
5799 struct cmd {
5800 char *cmd;
5801 int level;
5802 int (*func)(struct tab *, struct karg *);
5803 int arg;
5804 int type;
5805 } cmds[] = {
5806 { "command", 0, command, ':', 0 },
5807 { "search", 0, command, '/', 0 },
5808 { "searchb", 0, command, '?', 0 },
5809 { "togglesrc", 0, toggle_src, 0, 0 },
5811 /* yanking and pasting */
5812 { "yankuri", 0, yank_uri, 0, 0 },
5813 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5814 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5815 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5817 /* search */
5818 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5819 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5821 /* focus */
5822 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5823 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5825 /* hinting */
5826 { "hinting", 0, hint, 0, 0 },
5828 /* custom stylesheet */
5829 { "userstyle", 0, userstyle, 0, 0 },
5831 /* navigation */
5832 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5833 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5834 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5836 /* vertical movement */
5837 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5838 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5839 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5840 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5841 { "1", 0, move, XT_MOVE_TOP, 0 },
5842 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5843 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5844 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5845 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5846 /* horizontal movement */
5847 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5848 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5849 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5850 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5852 { "favorites", 0, xtp_page_fl, 0, 0 },
5853 { "fav", 0, xtp_page_fl, 0, 0 },
5854 { "favadd", 0, add_favorite, 0, 0 },
5856 { "qall", 0, quit, 0, 0 },
5857 { "quitall", 0, quit, 0, 0 },
5858 { "w", 0, save_tabs, 0, 0 },
5859 { "wq", 0, save_tabs_and_quit, 0, 0 },
5860 { "help", 0, help, 0, 0 },
5861 { "about", 0, about, 0, 0 },
5862 { "stats", 0, stats, 0, 0 },
5863 { "version", 0, about, 0, 0 },
5865 /* js command */
5866 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5867 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5868 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5869 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5870 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5871 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5872 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5873 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5874 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5875 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5876 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5878 /* cookie command */
5879 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5880 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5881 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5882 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5883 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5884 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5885 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5886 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5887 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5888 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5889 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5891 /* toplevel (domain) command */
5892 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5893 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5895 /* cookie jar */
5896 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5898 /* cert command */
5899 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5900 { "save", 1, cert_cmd, XT_SAVE, 0 },
5901 { "show", 1, cert_cmd, XT_SHOW, 0 },
5903 { "ca", 0, ca_cmd, 0, 0 },
5904 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5905 { "dl", 0, xtp_page_dl, 0, 0 },
5906 { "h", 0, xtp_page_hl, 0, 0 },
5907 { "history", 0, xtp_page_hl, 0, 0 },
5908 { "home", 0, go_home, 0, 0 },
5909 { "restart", 0, restart, 0, 0 },
5910 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5911 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5912 { "statustoggle", 0, statustoggle, 0, 0 },
5913 { "run_script", 0, run_page_script, 0, XT_USERARG },
5915 { "print", 0, print_page, 0, 0 },
5917 /* tabs */
5918 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5919 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5920 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5921 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5922 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5923 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5924 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5925 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5926 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5927 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5928 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5929 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5930 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5931 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5932 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5933 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5934 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5935 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5936 { "buffers", 0, buffers, 0, 0 },
5937 { "ls", 0, buffers, 0, 0 },
5938 { "tabs", 0, buffers, 0, 0 },
5939 { "encoding", 0, set_encoding, 0, XT_USERARG },
5941 /* command aliases (handy when -S flag is used) */
5942 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5943 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5944 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5945 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5947 /* settings */
5948 { "set", 0, set, 0, XT_SETARG },
5950 { "fullscreen", 0, fullscreen, 0, 0 },
5951 { "f", 0, fullscreen, 0, 0 },
5953 /* sessions */
5954 { "session", 0, session_cmd, XT_SHOW, 0 },
5955 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5956 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5957 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5958 { "show", 1, session_cmd, XT_SHOW, 0 },
5961 struct {
5962 int index;
5963 int len;
5964 gchar *list[256];
5965 } cmd_status = {-1, 0};
5967 gboolean
5968 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5971 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5972 btn_down = 0;
5974 return (FALSE);
5977 gboolean
5978 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5980 struct karg a;
5982 hide_oops(t);
5983 hide_buffers(t);
5985 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5986 btn_down = 1;
5987 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5988 /* go backward */
5989 a.i = XT_NAV_BACK;
5990 navaction(t, &a);
5992 return (TRUE);
5993 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5994 /* go forward */
5995 a.i = XT_NAV_FORWARD;
5996 navaction(t, &a);
5998 return (TRUE);
6001 return (FALSE);
6004 gboolean
6005 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6007 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6009 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6010 delete_tab(t);
6012 return (FALSE);
6016 * cancel, remove, etc. downloads
6018 void
6019 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6021 struct download find, *d = NULL;
6023 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6025 /* some commands require a valid download id */
6026 if (cmd != XT_XTP_DL_LIST) {
6027 /* lookup download in question */
6028 find.id = id;
6029 d = RB_FIND(download_list, &downloads, &find);
6031 if (d == NULL) {
6032 show_oops(t, "%s: no such download", __func__);
6033 return;
6037 /* decide what to do */
6038 switch (cmd) {
6039 case XT_XTP_DL_CANCEL:
6040 webkit_download_cancel(d->download);
6041 break;
6042 case XT_XTP_DL_REMOVE:
6043 webkit_download_cancel(d->download); /* just incase */
6044 g_object_unref(d->download);
6045 RB_REMOVE(download_list, &downloads, d);
6046 break;
6047 case XT_XTP_DL_LIST:
6048 /* Nothing */
6049 break;
6050 default:
6051 show_oops(t, "%s: unknown command", __func__);
6052 break;
6054 xtp_page_dl(t, NULL);
6058 * Actions on history, only does one thing for now, but
6059 * we provide the function for future actions
6061 void
6062 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6064 struct history *h, *next;
6065 int i = 1;
6067 switch (cmd) {
6068 case XT_XTP_HL_REMOVE:
6069 /* walk backwards, as listed in reverse */
6070 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6071 next = RB_PREV(history_list, &hl, h);
6072 if (id == i) {
6073 RB_REMOVE(history_list, &hl, h);
6074 g_free((gpointer) h->title);
6075 g_free((gpointer) h->uri);
6076 g_free(h);
6077 break;
6079 i++;
6081 break;
6082 case XT_XTP_HL_LIST:
6083 /* Nothing - just xtp_page_hl() below */
6084 break;
6085 default:
6086 show_oops(t, "%s: unknown command", __func__);
6087 break;
6090 xtp_page_hl(t, NULL);
6093 /* remove a favorite */
6094 void
6095 remove_favorite(struct tab *t, int index)
6097 char file[PATH_MAX], *title, *uri = NULL;
6098 char *new_favs, *tmp;
6099 FILE *f;
6100 int i;
6101 size_t len, lineno;
6103 /* open favorites */
6104 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6106 if ((f = fopen(file, "r")) == NULL) {
6107 show_oops(t, "%s: can't open favorites: %s",
6108 __func__, strerror(errno));
6109 return;
6112 /* build a string which will become the new favroites file */
6113 new_favs = g_strdup("");
6115 for (i = 1;;) {
6116 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6117 if (feof(f) || ferror(f))
6118 break;
6119 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6120 if (len == 0) {
6121 free(title);
6122 title = NULL;
6123 continue;
6126 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6127 if (feof(f) || ferror(f)) {
6128 show_oops(t, "%s: can't parse favorites %s",
6129 __func__, strerror(errno));
6130 goto clean;
6134 /* as long as this isn't the one we are deleting add to file */
6135 if (i != index) {
6136 tmp = new_favs;
6137 new_favs = g_strdup_printf("%s%s\n%s\n",
6138 new_favs, title, uri);
6139 g_free(tmp);
6142 free(uri);
6143 uri = NULL;
6144 free(title);
6145 title = NULL;
6146 i++;
6148 fclose(f);
6150 /* write back new favorites file */
6151 if ((f = fopen(file, "w")) == NULL) {
6152 show_oops(t, "%s: can't open favorites: %s",
6153 __func__, strerror(errno));
6154 goto clean;
6157 fwrite(new_favs, strlen(new_favs), 1, f);
6158 fclose(f);
6160 clean:
6161 if (uri)
6162 free(uri);
6163 if (title)
6164 free(title);
6166 g_free(new_favs);
6169 void
6170 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6172 switch (cmd) {
6173 case XT_XTP_FL_LIST:
6174 /* nothing, just the below call to xtp_page_fl() */
6175 break;
6176 case XT_XTP_FL_REMOVE:
6177 remove_favorite(t, arg);
6178 break;
6179 default:
6180 show_oops(t, "%s: invalid favorites command", __func__);
6181 break;
6184 xtp_page_fl(t, NULL);
6187 void
6188 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6190 switch (cmd) {
6191 case XT_XTP_CL_LIST:
6192 /* nothing, just xtp_page_cl() */
6193 break;
6194 case XT_XTP_CL_REMOVE:
6195 remove_cookie(arg);
6196 break;
6197 default:
6198 show_oops(t, "%s: unknown cookie xtp command", __func__);
6199 break;
6202 xtp_page_cl(t, NULL);
6205 /* link an XTP class to it's session key and handler function */
6206 struct xtp_despatch {
6207 uint8_t xtp_class;
6208 char **session_key;
6209 void (*handle_func)(struct tab *, uint8_t, int);
6212 struct xtp_despatch xtp_despatches[] = {
6213 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6214 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6215 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6216 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6217 { XT_XTP_INVALID, NULL, NULL }
6221 * is the url xtp protocol? (xxxt://)
6222 * if so, parse and despatch correct bahvior
6225 parse_xtp_url(struct tab *t, const char *url)
6227 char *dup = NULL, *p, *last;
6228 uint8_t n_tokens = 0;
6229 char *tokens[4] = {NULL, NULL, NULL, ""};
6230 struct xtp_despatch *dsp, *dsp_match = NULL;
6231 uint8_t req_class;
6232 int ret = FALSE;
6235 * tokens array meaning:
6236 * tokens[0] = class
6237 * tokens[1] = session key
6238 * tokens[2] = action
6239 * tokens[3] = optional argument
6242 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6244 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6245 goto clean;
6247 dup = g_strdup(url + strlen(XT_XTP_STR));
6249 /* split out the url */
6250 for ((p = strtok_r(dup, "/", &last)); p;
6251 (p = strtok_r(NULL, "/", &last))) {
6252 if (n_tokens < 4)
6253 tokens[n_tokens++] = p;
6256 /* should be atleast three fields 'class/seskey/command/arg' */
6257 if (n_tokens < 3)
6258 goto clean;
6260 dsp = xtp_despatches;
6261 req_class = atoi(tokens[0]);
6262 while (dsp->xtp_class) {
6263 if (dsp->xtp_class == req_class) {
6264 dsp_match = dsp;
6265 break;
6267 dsp++;
6270 /* did we find one atall? */
6271 if (dsp_match == NULL) {
6272 show_oops(t, "%s: no matching xtp despatch found", __func__);
6273 goto clean;
6276 /* check session key and call despatch function */
6277 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6278 ret = TRUE; /* all is well, this was a valid xtp request */
6279 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6282 clean:
6283 if (dup)
6284 g_free(dup);
6286 return (ret);
6291 void
6292 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6294 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6296 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6298 if (t == NULL) {
6299 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6300 return;
6303 if (uri == NULL) {
6304 show_oops(t, "activate_uri_entry_cb no uri");
6305 return;
6308 uri += strspn(uri, "\t ");
6310 /* if xxxt:// treat specially */
6311 if (parse_xtp_url(t, uri))
6312 return;
6314 /* otherwise continue to load page normally */
6315 load_uri(t, (gchar *)uri);
6316 focus_webview(t);
6319 void
6320 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6322 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6323 char *newuri = NULL;
6324 gchar *enc_search;
6326 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6328 if (t == NULL) {
6329 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6330 return;
6333 if (search_string == NULL) {
6334 show_oops(t, "no search_string");
6335 return;
6338 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6340 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6341 newuri = g_strdup_printf(search_string, enc_search);
6342 g_free(enc_search);
6344 marks_clear(t);
6345 webkit_web_view_load_uri(t->wv, newuri);
6346 focus_webview(t);
6348 if (newuri)
6349 g_free(newuri);
6352 void
6353 check_and_set_cookie(const gchar *uri, struct tab *t)
6355 struct domain *d = NULL;
6356 int es = 0;
6358 if (uri == NULL || t == NULL)
6359 return;
6361 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6362 es = 0;
6363 else
6364 es = 1;
6366 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6367 es ? "enable" : "disable", uri);
6369 g_object_set(G_OBJECT(t->settings),
6370 "enable-html5-local-storage", es, (char *)NULL);
6371 webkit_web_view_set_settings(t->wv, t->settings);
6374 void
6375 check_and_set_js(const gchar *uri, struct tab *t)
6377 struct domain *d = NULL;
6378 int es = 0;
6380 if (uri == NULL || t == NULL)
6381 return;
6383 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6384 es = 0;
6385 else
6386 es = 1;
6388 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6389 es ? "enable" : "disable", uri);
6391 g_object_set(G_OBJECT(t->settings),
6392 "enable-scripts", es, (char *)NULL);
6393 g_object_set(G_OBJECT(t->settings),
6394 "javascript-can-open-windows-automatically", es, (char *)NULL);
6395 webkit_web_view_set_settings(t->wv, t->settings);
6397 button_set_stockid(t->js_toggle,
6398 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6401 gboolean
6402 color_address_bar(gpointer p)
6404 GdkColor color;
6405 struct tab *tt, *t = p;
6406 gchar *col_str = XT_COLOR_YELLOW;
6408 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6410 /* make sure t still exists */
6411 if (t == NULL)
6412 goto done;
6413 TAILQ_FOREACH(tt, &tabs, entry)
6414 if (t == tt)
6415 break;
6416 if (t != tt)
6417 goto done;
6419 switch (load_compare_cert(t, NULL)) {
6420 case CERT_LOCAL:
6421 col_str = XT_COLOR_BLUE;
6422 break;
6423 case CERT_TRUSTED:
6424 col_str = XT_COLOR_GREEN;
6425 break;
6426 case CERT_UNTRUSTED:
6427 col_str = XT_COLOR_YELLOW;
6428 break;
6429 case CERT_BAD:
6430 col_str = XT_COLOR_RED;
6431 break;
6434 gdk_color_parse(col_str, &color);
6435 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6437 if (!strcmp(col_str, XT_COLOR_WHITE))
6438 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6439 else
6440 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6442 col_str = NULL;
6443 done:
6444 return (FALSE);
6447 void
6448 show_ca_status(struct tab *t, const char *uri)
6450 GdkColor color;
6451 gchar *col_str = XT_COLOR_WHITE;
6453 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6454 ssl_strict_certs, ssl_ca_file, uri);
6456 if (t == NULL)
6457 return;
6459 if (uri == NULL)
6460 goto done;
6461 if (ssl_ca_file == NULL) {
6462 if (g_str_has_prefix(uri, "http://"))
6463 goto done;
6464 if (g_str_has_prefix(uri, "https://")) {
6465 col_str = XT_COLOR_RED;
6466 goto done;
6468 return;
6470 if (g_str_has_prefix(uri, "http://") ||
6471 !g_str_has_prefix(uri, "https://"))
6472 goto done;
6474 color_address_bar(t);
6476 return;
6478 done:
6479 if (col_str) {
6480 gdk_color_parse(col_str, &color);
6481 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6483 if (!strcmp(col_str, XT_COLOR_WHITE))
6484 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6485 else
6486 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6490 void
6491 free_favicon(struct tab *t)
6493 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6494 __func__, t->icon_download, t->icon_request);
6496 if (t->icon_request)
6497 g_object_unref(t->icon_request);
6498 if (t->icon_dest_uri)
6499 g_free(t->icon_dest_uri);
6501 t->icon_request = NULL;
6502 t->icon_dest_uri = NULL;
6505 void
6506 xt_icon_from_name(struct tab *t, gchar *name)
6508 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6509 GTK_ENTRY_ICON_PRIMARY, "text-html");
6510 if (show_url == 0)
6511 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6512 GTK_ENTRY_ICON_PRIMARY, "text-html");
6513 else
6514 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6515 GTK_ENTRY_ICON_PRIMARY, NULL);
6518 void
6519 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6521 GdkPixbuf *pb_scaled;
6523 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6524 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6525 GDK_INTERP_BILINEAR);
6526 else
6527 pb_scaled = pb;
6529 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6530 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6531 if (show_url == 0)
6532 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6533 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6534 else
6535 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6536 GTK_ENTRY_ICON_PRIMARY, NULL);
6538 if (pb_scaled != pb)
6539 g_object_unref(pb_scaled);
6542 void
6543 xt_icon_from_file(struct tab *t, char *file)
6545 GdkPixbuf *pb;
6547 if (g_str_has_prefix(file, "file://"))
6548 file += strlen("file://");
6550 pb = gdk_pixbuf_new_from_file(file, NULL);
6551 if (pb) {
6552 xt_icon_from_pixbuf(t, pb);
6553 g_object_unref(pb);
6554 } else
6555 xt_icon_from_name(t, "text-html");
6558 gboolean
6559 is_valid_icon(char *file)
6561 gboolean valid = 0;
6562 const char *mime_type;
6563 GFileInfo *fi;
6564 GFile *gf;
6566 gf = g_file_new_for_path(file);
6567 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6568 NULL, NULL);
6569 mime_type = g_file_info_get_content_type(fi);
6570 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6571 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6572 g_strcmp0(mime_type, "image/png") == 0 ||
6573 g_strcmp0(mime_type, "image/gif") == 0 ||
6574 g_strcmp0(mime_type, "application/octet-stream") == 0;
6575 g_object_unref(fi);
6576 g_object_unref(gf);
6578 return (valid);
6581 void
6582 set_favicon_from_file(struct tab *t, char *file)
6584 struct stat sb;
6586 if (t == NULL || file == NULL)
6587 return;
6589 if (g_str_has_prefix(file, "file://"))
6590 file += strlen("file://");
6591 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6593 if (!stat(file, &sb)) {
6594 if (sb.st_size == 0 || !is_valid_icon(file)) {
6595 /* corrupt icon so trash it */
6596 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6597 __func__, file);
6598 unlink(file);
6599 /* no need to set icon to default here */
6600 return;
6603 xt_icon_from_file(t, file);
6606 void
6607 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6608 WebKitWebView *wv)
6610 WebKitDownloadStatus status = webkit_download_get_status(download);
6611 struct tab *tt = NULL, *t = NULL;
6614 * find the webview instead of passing in the tab as it could have been
6615 * deleted from underneath us.
6617 TAILQ_FOREACH(tt, &tabs, entry) {
6618 if (tt->wv == wv) {
6619 t = tt;
6620 break;
6623 if (t == NULL)
6624 return;
6626 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6627 __func__, t->tab_id, status);
6629 switch (status) {
6630 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6631 /* -1 */
6632 t->icon_download = NULL;
6633 free_favicon(t);
6634 break;
6635 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6636 /* 0 */
6637 break;
6638 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6639 /* 1 */
6640 break;
6641 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6642 /* 2 */
6643 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6644 __func__, t->tab_id);
6645 t->icon_download = NULL;
6646 free_favicon(t);
6647 break;
6648 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6649 /* 3 */
6651 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6652 __func__, t->icon_dest_uri);
6653 set_favicon_from_file(t, t->icon_dest_uri);
6654 /* these will be freed post callback */
6655 t->icon_request = NULL;
6656 t->icon_download = NULL;
6657 break;
6658 default:
6659 break;
6663 void
6664 abort_favicon_download(struct tab *t)
6666 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6668 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6669 if (t->icon_download) {
6670 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6671 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6672 webkit_download_cancel(t->icon_download);
6673 t->icon_download = NULL;
6674 } else
6675 free_favicon(t);
6676 #endif
6678 xt_icon_from_name(t, "text-html");
6681 void
6682 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6684 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6686 if (uri == NULL || t == NULL)
6687 return;
6689 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6690 /* take icon from WebKitIconDatabase */
6691 GdkPixbuf *pb;
6693 pb = webkit_web_view_get_icon_pixbuf(wv);
6694 if (pb) {
6695 xt_icon_from_pixbuf(t, pb);
6696 g_object_unref(pb);
6697 } else
6698 xt_icon_from_name(t, "text-html");
6699 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6700 /* download icon to cache dir */
6701 gchar *name_hash, file[PATH_MAX];
6702 struct stat sb;
6704 if (t->icon_request) {
6705 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6706 return;
6709 /* check to see if we got the icon in cache */
6710 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6711 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6712 g_free(name_hash);
6714 if (!stat(file, &sb)) {
6715 if (sb.st_size > 0) {
6716 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6717 __func__, file);
6718 set_favicon_from_file(t, file);
6719 return;
6722 /* corrupt icon so trash it */
6723 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6724 __func__, file);
6725 unlink(file);
6728 /* create download for icon */
6729 t->icon_request = webkit_network_request_new(uri);
6730 if (t->icon_request == NULL) {
6731 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6732 __func__, uri);
6733 return;
6736 t->icon_download = webkit_download_new(t->icon_request);
6737 if (t->icon_download == NULL)
6738 return;
6740 /* we have to free icon_dest_uri later */
6741 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6742 webkit_download_set_destination_uri(t->icon_download,
6743 t->icon_dest_uri);
6745 if (webkit_download_get_status(t->icon_download) ==
6746 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6747 g_object_unref(t->icon_request);
6748 g_free(t->icon_dest_uri);
6749 t->icon_request = NULL;
6750 t->icon_dest_uri = NULL;
6751 return;
6754 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6755 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6757 webkit_download_start(t->icon_download);
6758 #endif
6761 void
6762 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6764 const gchar *uri = NULL, *title = NULL;
6765 struct history *h, find;
6766 struct karg a;
6767 GdkColor color;
6769 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6770 webkit_web_view_get_load_status(wview),
6771 get_uri(t) ? get_uri(t) : "NOTHING");
6773 if (t == NULL) {
6774 show_oops(NULL, "notify_load_status_cb invalid parameters");
6775 return;
6778 switch (webkit_web_view_get_load_status(wview)) {
6779 case WEBKIT_LOAD_PROVISIONAL:
6780 /* 0 */
6781 abort_favicon_download(t);
6782 #if GTK_CHECK_VERSION(2, 20, 0)
6783 gtk_widget_show(t->spinner);
6784 gtk_spinner_start(GTK_SPINNER(t->spinner));
6785 #endif
6786 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6788 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6790 /* assume we are a new address */
6791 gdk_color_parse("white", &color);
6792 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6793 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6795 /* take focus if we are visible */
6796 focus_webview(t);
6797 t->focus_wv = 1;
6799 break;
6801 case WEBKIT_LOAD_COMMITTED:
6802 /* 1 */
6803 uri = get_uri(t);
6804 if (uri == NULL)
6805 return;
6806 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6808 if (t->status) {
6809 g_free(t->status);
6810 t->status = NULL;
6812 set_status(t, (char *)uri, XT_STATUS_LOADING);
6814 /* check if js white listing is enabled */
6815 if (enable_cookie_whitelist)
6816 check_and_set_cookie(uri, t);
6817 if (enable_js_whitelist)
6818 check_and_set_js(uri, t);
6820 if (t->styled)
6821 apply_style(t);
6824 /* we know enough to autosave the session */
6825 if (session_autosave) {
6826 a.s = NULL;
6827 save_tabs(t, &a);
6830 show_ca_status(t, uri);
6831 break;
6833 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6834 /* 3 */
6835 break;
6837 case WEBKIT_LOAD_FINISHED:
6838 /* 2 */
6839 uri = get_uri(t);
6840 if (uri == NULL)
6841 return;
6843 if (!strncmp(uri, "http://", strlen("http://")) ||
6844 !strncmp(uri, "https://", strlen("https://")) ||
6845 !strncmp(uri, "file://", strlen("file://"))) {
6846 find.uri = uri;
6847 h = RB_FIND(history_list, &hl, &find);
6848 if (!h) {
6849 title = get_title(t, FALSE);
6850 h = g_malloc(sizeof *h);
6851 h->uri = g_strdup(uri);
6852 h->title = g_strdup(title);
6853 RB_INSERT(history_list, &hl, h);
6854 completion_add_uri(h->uri);
6855 update_history_tabs(NULL);
6859 set_status(t, (char *)uri, XT_STATUS_URI);
6860 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6861 case WEBKIT_LOAD_FAILED:
6862 /* 4 */
6863 #endif
6864 #if GTK_CHECK_VERSION(2, 20, 0)
6865 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6866 gtk_widget_hide(t->spinner);
6867 #endif
6868 default:
6869 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6870 break;
6873 if (t->item)
6874 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6875 else
6876 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6877 can_go_back_for_real(t));
6879 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6880 can_go_forward_for_real(t));
6883 void
6884 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6886 const gchar *title = NULL, *win_title = NULL;
6888 title = get_title(t, FALSE);
6889 win_title = get_title(t, TRUE);
6890 gtk_label_set_text(GTK_LABEL(t->label), title);
6891 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6892 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6893 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6896 void
6897 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6899 run_script(t, JS_HINTING);
6902 void
6903 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6905 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6906 progress == 100 ? 0 : (double)progress / 100);
6907 if (show_url == 0) {
6908 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6909 progress == 100 ? 0 : (double)progress / 100);
6912 update_statusbar_position(NULL, NULL);
6916 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6917 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6918 WebKitWebPolicyDecision *pd, struct tab *t)
6920 char *uri;
6921 WebKitWebNavigationReason reason;
6922 struct domain *d = NULL;
6924 if (t == NULL) {
6925 show_oops(NULL, "webview_npd_cb invalid parameters");
6926 return (FALSE);
6929 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6930 t->ctrl_click,
6931 webkit_network_request_get_uri(request));
6933 uri = (char *)webkit_network_request_get_uri(request);
6935 /* if this is an xtp url, we don't load anything else */
6936 if (parse_xtp_url(t, uri))
6937 return (TRUE);
6939 if (t->ctrl_click) {
6940 t->ctrl_click = 0;
6941 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6942 webkit_web_policy_decision_ignore(pd);
6943 return (TRUE); /* we made the decission */
6947 * This is a little hairy but it comes down to this:
6948 * when we run in whitelist mode we have to assist the browser in
6949 * opening the URL that it would have opened in a new tab.
6951 reason = webkit_web_navigation_action_get_reason(na);
6952 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6953 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6954 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6955 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6956 load_uri(t, uri);
6957 webkit_web_policy_decision_use(pd);
6958 return (TRUE); /* we made the decision */
6961 return (FALSE);
6964 WebKitWebView *
6965 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6967 struct tab *tt;
6968 struct domain *d = NULL;
6969 const gchar *uri;
6970 WebKitWebView *webview = NULL;
6972 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6973 webkit_web_view_get_uri(wv));
6975 if (tabless) {
6976 /* open in current tab */
6977 webview = t->wv;
6978 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6979 uri = webkit_web_view_get_uri(wv);
6980 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6981 return (NULL);
6983 tt = create_new_tab(NULL, NULL, 1, -1);
6984 webview = tt->wv;
6985 } else if (enable_scripts == 1) {
6986 tt = create_new_tab(NULL, NULL, 1, -1);
6987 webview = tt->wv;
6990 return (webview);
6993 gboolean
6994 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6996 const gchar *uri;
6997 struct domain *d = NULL;
6999 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7001 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7002 uri = webkit_web_view_get_uri(wv);
7003 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7004 return (FALSE);
7006 delete_tab(t);
7007 } else if (enable_scripts == 1)
7008 delete_tab(t);
7010 return (TRUE);
7014 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7016 /* we can not eat the event without throwing gtk off so defer it */
7018 /* catch middle click */
7019 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7020 t->ctrl_click = 1;
7021 goto done;
7024 /* catch ctrl click */
7025 if (e->type == GDK_BUTTON_RELEASE &&
7026 CLEAN(e->state) == GDK_CONTROL_MASK)
7027 t->ctrl_click = 1;
7028 else
7029 t->ctrl_click = 0;
7030 done:
7031 return (XT_CB_PASSTHROUGH);
7035 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7037 struct mime_type *m;
7039 m = find_mime_type(mime_type);
7040 if (m == NULL)
7041 return (1);
7042 if (m->mt_download)
7043 return (1);
7045 switch (fork()) {
7046 case -1:
7047 show_oops(t, "can't fork mime handler");
7048 return (1);
7049 /* NOTREACHED */
7050 case 0:
7051 break;
7052 default:
7053 return (0);
7056 /* child */
7057 execlp(m->mt_action, m->mt_action,
7058 webkit_network_request_get_uri(request), (void *)NULL);
7060 _exit(0);
7062 /* NOTREACHED */
7063 return (0);
7066 const gchar *
7067 get_mime_type(char *file)
7069 const char *mime_type;
7070 GFileInfo *fi;
7071 GFile *gf;
7073 if (g_str_has_prefix(file, "file://"))
7074 file += strlen("file://");
7076 gf = g_file_new_for_path(file);
7077 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7078 NULL, NULL);
7079 mime_type = g_file_info_get_content_type(fi);
7080 g_object_unref(fi);
7081 g_object_unref(gf);
7083 return (mime_type);
7087 run_download_mimehandler(char *mime_type, char *file)
7089 struct mime_type *m;
7091 m = find_mime_type(mime_type);
7092 if (m == NULL)
7093 return (1);
7095 switch (fork()) {
7096 case -1:
7097 show_oops(NULL, "can't fork download mime handler");
7098 return (1);
7099 /* NOTREACHED */
7100 case 0:
7101 break;
7102 default:
7103 return (0);
7106 /* child */
7107 if (g_str_has_prefix(file, "file://"))
7108 file += strlen("file://");
7109 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7111 _exit(0);
7113 /* NOTREACHED */
7114 return (0);
7117 void
7118 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7119 WebKitWebView *wv)
7121 WebKitDownloadStatus status;
7122 const gchar *file = NULL, *mime = NULL;
7124 if (download == NULL)
7125 return;
7126 status = webkit_download_get_status(download);
7127 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7128 return;
7130 file = webkit_download_get_destination_uri(download);
7131 if (file == NULL)
7132 return;
7133 mime = get_mime_type((char *)file);
7134 if (mime == NULL)
7135 return;
7137 run_download_mimehandler((char *)mime, (char *)file);
7141 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7142 WebKitNetworkRequest *request, char *mime_type,
7143 WebKitWebPolicyDecision *decision, struct tab *t)
7145 if (t == NULL) {
7146 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7147 return (FALSE);
7150 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7151 t->tab_id, mime_type);
7153 if (run_mimehandler(t, mime_type, request) == 0) {
7154 webkit_web_policy_decision_ignore(decision);
7155 focus_webview(t);
7156 return (TRUE);
7159 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7160 webkit_web_policy_decision_download(decision);
7161 return (TRUE);
7164 return (FALSE);
7168 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7169 struct tab *t)
7171 struct stat sb;
7172 const gchar *suggested_name;
7173 gchar *filename = NULL;
7174 char *uri = NULL;
7175 struct download *download_entry;
7176 int i, ret = TRUE;
7178 if (wk_download == NULL || t == NULL) {
7179 show_oops(NULL, "%s invalid parameters", __func__);
7180 return (FALSE);
7183 suggested_name = webkit_download_get_suggested_filename(wk_download);
7184 if (suggested_name == NULL)
7185 return (FALSE); /* abort download */
7187 i = 0;
7188 do {
7189 if (filename) {
7190 g_free(filename);
7191 filename = NULL;
7193 if (i) {
7194 g_free(uri);
7195 uri = NULL;
7196 filename = g_strdup_printf("%d%s", i, suggested_name);
7198 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7199 filename : suggested_name);
7200 i++;
7201 } while (!stat(uri + strlen("file://"), &sb));
7203 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7204 "local %s\n", __func__, t->tab_id, filename, uri);
7206 webkit_download_set_destination_uri(wk_download, uri);
7208 if (webkit_download_get_status(wk_download) ==
7209 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7210 show_oops(t, "%s: download failed to start", __func__);
7211 ret = FALSE;
7212 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7213 } else {
7214 /* connect "download first" mime handler */
7215 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7216 G_CALLBACK(download_status_changed_cb), NULL);
7218 download_entry = g_malloc(sizeof(struct download));
7219 download_entry->download = wk_download;
7220 download_entry->tab = t;
7221 download_entry->id = next_download_id++;
7222 RB_INSERT(download_list, &downloads, download_entry);
7223 /* get from history */
7224 g_object_ref(wk_download);
7225 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7226 show_oops(t, "Download of '%s' started...",
7227 basename((char *)webkit_download_get_destination_uri(wk_download)));
7230 if (uri)
7231 g_free(uri);
7233 if (filename)
7234 g_free(filename);
7236 /* sync other download manager tabs */
7237 update_download_tabs(NULL);
7240 * NOTE: never redirect/render the current tab before this
7241 * function returns. This will cause the download to never start.
7243 return (ret); /* start download */
7246 void
7247 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7249 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7251 if (t == NULL) {
7252 show_oops(NULL, "webview_hover_cb");
7253 return;
7256 if (uri)
7257 set_status(t, uri, XT_STATUS_LINK);
7258 else {
7259 if (t->status)
7260 set_status(t, t->status, XT_STATUS_NOTHING);
7265 mark(struct tab *t, struct karg *arg)
7267 char mark;
7268 int index;
7270 mark = arg->s[1];
7271 if ((index = marktoindex(mark)) == -1)
7272 return -1;
7274 if (arg->i == XT_MARK_SET)
7275 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7276 else if (arg->i == XT_MARK_GOTO) {
7277 if (t->mark[index] == XT_INVALID_MARK) {
7278 show_oops(t, "mark '%c' does not exist", mark);
7279 return -1;
7281 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7282 something changes the document size */
7283 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7286 return 0;
7289 void
7290 marks_clear(struct tab *t)
7292 int i;
7294 for (i = 0; i < LENGTH(t->mark); i++)
7295 t->mark[i] = XT_INVALID_MARK;
7299 qmarks_load(void)
7301 char file[PATH_MAX];
7302 char *line = NULL, *p;
7303 int index, i;
7304 FILE *f;
7305 size_t linelen;
7307 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7308 if ((f = fopen(file, "r+")) == NULL) {
7309 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7310 return (1);
7313 for (i = 1; ; i++) {
7314 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7315 if (feof(f) || ferror(f))
7316 break;
7317 if (strlen(line) == 0 || line[0] == '#') {
7318 free(line);
7319 line = NULL;
7320 continue;
7323 p = strtok(line, " \t");
7325 if (p == NULL || strlen(p) != 1 ||
7326 (index = marktoindex(*p)) == -1) {
7327 warnx("corrupt quickmarks file, line %d", i);
7328 break;
7331 p = strtok(NULL, " \t");
7332 if (qmarks[index] != NULL)
7333 g_free(qmarks[index]);
7334 qmarks[index] = g_strdup(p);
7337 fclose(f);
7339 return (0);
7343 qmarks_save(void)
7345 char file[PATH_MAX];
7346 int i;
7347 FILE *f;
7349 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7350 if ((f = fopen(file, "r+")) == NULL) {
7351 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7352 return (1);
7355 for (i = 0; i < XT_NOMARKS; i++)
7356 if (qmarks[i] != NULL)
7357 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7359 fclose(f);
7361 return (0);
7365 qmark(struct tab *t, struct karg *arg)
7367 char mark;
7368 int index;
7370 mark = arg->s[strlen(arg->s)-1];
7371 index = marktoindex(mark);
7372 if (index == -1)
7373 return (-1);
7375 switch (arg->i) {
7376 case XT_QMARK_SET:
7377 if (qmarks[index] != NULL)
7378 g_free(qmarks[index]);
7380 qmarks_load(); /* sync if multiple instances */
7381 qmarks[index] = g_strdup(get_uri(t));
7382 qmarks_save();
7383 break;
7384 case XT_QMARK_OPEN:
7385 if (qmarks[index] != NULL)
7386 load_uri(t, qmarks[index]);
7387 else {
7388 show_oops(t, "quickmark \"%c\" does not exist",
7389 mark);
7390 return (-1);
7392 break;
7393 case XT_QMARK_TAB:
7394 if (qmarks[index] != NULL)
7395 create_new_tab(qmarks[index], NULL, 1, -1);
7396 else {
7397 show_oops(t, "quickmark \"%c\" does not exist",
7398 mark);
7399 return (-1);
7401 break;
7404 return (0);
7408 go_up(struct tab *t, struct karg *args)
7410 int levels;
7411 char *uri;
7412 char *tmp;
7414 levels = atoi(args->s);
7415 if (levels == 0)
7416 levels = 1;
7418 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7419 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7420 return 1;
7421 tmp += strlen(XT_PROTO_DELIM);
7423 /* if an uri starts with a slash, leave it alone (for file:///) */
7424 if (tmp[0] == '/')
7425 tmp++;
7427 while (levels--) {
7428 char *p;
7430 p = strrchr(tmp, '/');
7431 if (p != NULL)
7432 *p = '\0';
7433 else
7434 break;
7437 load_uri(t, uri);
7438 g_free(uri);
7440 return (0);
7444 gototab(struct tab *t, struct karg *args)
7446 int tab;
7447 struct karg arg = {0, NULL, -1};
7449 tab = atoi(args->s);
7451 arg.i = XT_TAB_NEXT;
7452 arg.precount = tab;
7454 movetab(t, &arg);
7456 return (0);
7460 zoom_amount(struct tab *t, struct karg *arg)
7462 struct karg narg = {0, NULL, -1};
7464 narg.i = atoi(arg->s);
7465 resizetab(t, &narg);
7467 return 0;
7471 flip_colon(struct tab *t, struct karg *arg)
7473 struct karg narg = {0, NULL, -1};
7474 char *p;
7476 if (t == NULL || arg == NULL)
7477 return (1);
7479 p = strstr(arg->s, ":");
7480 if (p == NULL)
7481 return (1);
7482 *p = '\0';
7484 narg.i = ':';
7485 narg.s = arg->s;
7486 command(t, &narg);
7488 return (0);
7491 /* buffer commands receive the regex that triggered them in arg.s */
7492 char bcmd[XT_BUFCMD_SZ];
7493 struct buffercmd {
7494 char *regex;
7495 int precount;
7496 #define XT_PRE_NO (0)
7497 #define XT_PRE_YES (1)
7498 #define XT_PRE_MAYBE (2)
7499 char *cmd;
7500 int (*func)(struct tab *, struct karg *);
7501 int arg;
7502 regex_t cregex;
7503 } buffercmds[] = {
7504 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7505 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7506 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7507 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7508 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7509 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7510 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7511 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7512 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7513 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7514 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7515 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7516 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7517 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7518 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7519 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7520 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7521 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7524 void
7525 buffercmd_init(void)
7527 int i;
7529 for (i = 0; i < LENGTH(buffercmds); i++)
7530 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7531 REG_EXTENDED | REG_NOSUB))
7532 startpage_add("invalid buffercmd regex %s",
7533 buffercmds[i].regex);
7536 void
7537 buffercmd_abort(struct tab *t)
7539 int i;
7541 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7542 for (i = 0; i < LENGTH(bcmd); i++)
7543 bcmd[i] = '\0';
7545 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7546 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7549 void
7550 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7552 struct karg arg = {0, NULL, -1};
7554 arg.i = cmd->arg;
7555 arg.s = g_strdup(bcmd);
7557 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7558 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7559 cmd->func(t, &arg);
7561 if (arg.s)
7562 g_free(arg.s);
7564 buffercmd_abort(t);
7567 gboolean
7568 buffercmd_addkey(struct tab *t, guint keyval)
7570 int i, c, match ;
7571 char s[XT_BUFCMD_SZ];
7573 if (keyval == GDK_Escape) {
7574 buffercmd_abort(t);
7575 return (XT_CB_HANDLED);
7578 /* key with modifier or non-ascii character */
7579 if (!isascii(keyval))
7580 return (XT_CB_PASSTHROUGH);
7582 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7583 "to buffer \"%s\"\n", keyval, bcmd);
7585 for (i = 0; i < LENGTH(bcmd); i++)
7586 if (bcmd[i] == '\0') {
7587 bcmd[i] = keyval;
7588 break;
7591 /* buffer full, ignore input */
7592 if (i >= LENGTH(bcmd) -1) {
7593 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7594 buffercmd_abort(t);
7595 return (XT_CB_HANDLED);
7598 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7600 /* find exact match */
7601 for (i = 0; i < LENGTH(buffercmds); i++)
7602 if (regexec(&buffercmds[i].cregex, bcmd,
7603 (size_t) 0, NULL, 0) == 0) {
7604 buffercmd_execute(t, &buffercmds[i]);
7605 goto done;
7608 /* find non exact matches to see if we need to abort ot not */
7609 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7610 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7611 c = -1;
7612 s[0] = '\0';
7613 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7614 if (isdigit(bcmd[0])) {
7615 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7616 continue;
7617 } else {
7618 c = 0;
7619 if (sscanf(bcmd, "%s", s) == 0)
7620 continue;
7622 } else if (buffercmds[i].precount == XT_PRE_YES) {
7623 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7624 continue;
7625 } else {
7626 if (sscanf(bcmd, "%s", s) == 0)
7627 continue;
7629 if (c == -1 && buffercmds[i].precount)
7630 continue;
7631 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7632 match++;
7634 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7635 i, match, buffercmds[i].cmd, c, s);
7637 if (match == 0) {
7638 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7639 buffercmd_abort(t);
7642 done:
7643 return (XT_CB_HANDLED);
7646 gboolean
7647 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7649 struct key_binding *k;
7651 /* handle keybindings if buffercmd is empty.
7652 if not empty, allow commands like C-n */
7653 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7654 TAILQ_FOREACH(k, &kbl, entry)
7655 if (e->keyval == k->key
7656 && (entry ? k->use_in_entry : 1)) {
7657 if (k->mask == 0) {
7658 if ((e->state & (CTRL | MOD1)) == 0)
7659 return (cmd_execute(t, k->cmd));
7660 } else if ((e->state & k->mask) == k->mask) {
7661 return (cmd_execute(t, k->cmd));
7665 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7666 return buffercmd_addkey(t, e->keyval);
7668 return (XT_CB_PASSTHROUGH);
7672 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7674 char s[2], buf[128];
7675 const char *errstr = NULL;
7677 /* don't use w directly; use t->whatever instead */
7679 if (t == NULL) {
7680 show_oops(NULL, "wv_keypress_after_cb");
7681 return (XT_CB_PASSTHROUGH);
7684 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7685 e->keyval, e->state, t);
7687 if (t->hints_on) {
7688 /* ESC */
7689 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7690 disable_hints(t);
7691 return (XT_CB_HANDLED);
7694 /* RETURN */
7695 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7696 if (errstr) {
7697 /* we have a string */
7698 } else {
7699 /* we have a number */
7700 snprintf(buf, sizeof buf,
7701 "vimprobable_fire(%s)", t->hint_num);
7702 run_script(t, buf);
7704 disable_hints(t);
7707 /* BACKSPACE */
7708 /* XXX unfuck this */
7709 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7710 if (t->hint_mode == XT_HINT_NUMERICAL) {
7711 /* last input was numerical */
7712 int l;
7713 l = strlen(t->hint_num);
7714 if (l > 0) {
7715 l--;
7716 if (l == 0) {
7717 disable_hints(t);
7718 enable_hints(t);
7719 } else {
7720 t->hint_num[l] = '\0';
7721 goto num;
7724 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7725 /* last input was alphanumerical */
7726 int l;
7727 l = strlen(t->hint_buf);
7728 if (l > 0) {
7729 l--;
7730 if (l == 0) {
7731 disable_hints(t);
7732 enable_hints(t);
7733 } else {
7734 t->hint_buf[l] = '\0';
7735 goto anum;
7738 } else {
7739 /* bogus */
7740 disable_hints(t);
7744 /* numerical input */
7745 if (CLEAN(e->state) == 0 &&
7746 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7747 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7748 snprintf(s, sizeof s, "%c", e->keyval);
7749 strlcat(t->hint_num, s, sizeof t->hint_num);
7750 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7751 t->hint_num);
7752 num:
7753 if (errstr) {
7754 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7755 "invalid link number\n");
7756 disable_hints(t);
7757 } else {
7758 snprintf(buf, sizeof buf,
7759 "vimprobable_update_hints(%s)",
7760 t->hint_num);
7761 t->hint_mode = XT_HINT_NUMERICAL;
7762 run_script(t, buf);
7765 /* empty the counter buffer */
7766 bzero(t->hint_buf, sizeof t->hint_buf);
7767 return (XT_CB_HANDLED);
7770 /* alphanumerical input */
7771 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7772 e->keyval <= GDK_z) ||
7773 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7774 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7775 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7776 e->keyval <= GDK_9) ||
7777 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7778 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7779 snprintf(s, sizeof s, "%c", e->keyval);
7780 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7781 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7782 " %s\n", t->hint_buf);
7783 anum:
7784 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7785 run_script(t, buf);
7787 snprintf(buf, sizeof buf,
7788 "vimprobable_show_hints('%s')", t->hint_buf);
7789 t->hint_mode = XT_HINT_ALPHANUM;
7790 run_script(t, buf);
7792 /* empty the counter buffer */
7793 bzero(t->hint_num, sizeof t->hint_num);
7794 return (XT_CB_HANDLED);
7797 return (XT_CB_HANDLED);
7798 } else {
7799 /* prefix input*/
7800 snprintf(s, sizeof s, "%c", e->keyval);
7801 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7802 cmd_prefix = 10 * cmd_prefix + atoi(s);
7805 return (handle_keypress(t, e, 0));
7809 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7811 hide_oops(t);
7813 /* Hide buffers, if they are visible, with escape. */
7814 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7815 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7816 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7817 hide_buffers(t);
7818 return (XT_CB_HANDLED);
7821 return (XT_CB_PASSTHROUGH);
7824 gboolean
7825 search_continue(struct tab *t)
7827 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7828 gboolean rv = FALSE;
7830 if (c[0] == ':')
7831 goto done;
7832 if (strlen(c) == 1) {
7833 webkit_web_view_unmark_text_matches(t->wv);
7834 goto done;
7837 if (c[0] == '/')
7838 t->search_forward = TRUE;
7839 else if (c[0] == '?')
7840 t->search_forward = FALSE;
7841 else
7842 goto done;
7844 rv = TRUE;
7845 done:
7846 return (rv);
7849 gboolean
7850 search_cb(struct tab *t)
7852 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7853 GdkColor color;
7855 if (search_continue(t) == FALSE)
7856 goto done;
7858 /* search */
7859 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7860 TRUE) == FALSE) {
7861 /* not found, mark red */
7862 gdk_color_parse(XT_COLOR_RED, &color);
7863 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7864 /* unmark and remove selection */
7865 webkit_web_view_unmark_text_matches(t->wv);
7866 /* my kingdom for a way to unselect text in webview */
7867 } else {
7868 /* found, highlight all */
7869 webkit_web_view_unmark_text_matches(t->wv);
7870 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7871 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7872 gdk_color_parse(XT_COLOR_WHITE, &color);
7873 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7875 done:
7876 t->search_id = 0;
7877 return (FALSE);
7881 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7883 const gchar *c = gtk_entry_get_text(w);
7885 if (t == NULL) {
7886 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7887 return (XT_CB_PASSTHROUGH);
7890 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7891 e->keyval, e->state, t);
7893 if (search_continue(t) == FALSE)
7894 goto done;
7896 /* if search length is > 4 then no longer play timeout games */
7897 if (strlen(c) > 4) {
7898 if (t->search_id) {
7899 g_source_remove(t->search_id);
7900 t->search_id = 0;
7902 search_cb(t);
7903 goto done;
7906 /* reestablish a new timer if the user types fast */
7907 if (t->search_id)
7908 g_source_remove(t->search_id);
7909 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7911 done:
7912 return (XT_CB_PASSTHROUGH);
7915 gboolean
7916 match_uri(const gchar *uri, const gchar *key) {
7917 gchar *voffset;
7918 size_t len;
7919 gboolean match = FALSE;
7921 len = strlen(key);
7923 if (!strncmp(key, uri, len))
7924 match = TRUE;
7925 else {
7926 voffset = strstr(uri, "/") + 2;
7927 if (!strncmp(key, voffset, len))
7928 match = TRUE;
7929 else if (g_str_has_prefix(voffset, "www.")) {
7930 voffset = voffset + strlen("www.");
7931 if (!strncmp(key, voffset, len))
7932 match = TRUE;
7936 return (match);
7939 gboolean
7940 match_session(const gchar *name, const gchar *key) {
7941 char *sub;
7943 sub = strcasestr(name, key);
7945 return sub == name;
7948 void
7949 cmd_getlist(int id, char *key)
7951 int i, dep, c = 0;
7952 struct history *h;
7953 struct session *s;
7955 if (id >= 0) {
7956 if (cmds[id].type & XT_URLARG) {
7957 RB_FOREACH_REVERSE(h, history_list, &hl)
7958 if (match_uri(h->uri, key)) {
7959 cmd_status.list[c] = (char *)h->uri;
7960 if (++c > 255)
7961 break;
7963 cmd_status.len = c;
7964 return;
7965 } else if (cmds[id].type & XT_SESSARG) {
7966 TAILQ_FOREACH(s, &sessions, entry)
7967 if (match_session(s->name, key)) {
7968 cmd_status.list[c] = (char *)s->name;
7969 if (++c > 255)
7970 break;
7972 cmd_status.len = c;
7973 return;
7974 } else if (cmds[id].type & XT_SETARG) {
7975 for (i = 0; i < LENGTH(rs); i++)
7976 if(!strncmp(key, rs[i].name, strlen(key)))
7977 cmd_status.list[c++] = rs[i].name;
7978 cmd_status.len = c;
7979 return;
7983 dep = (id == -1) ? 0 : cmds[id].level + 1;
7985 for (i = id + 1; i < LENGTH(cmds); i++) {
7986 if (cmds[i].level < dep)
7987 break;
7988 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7989 strlen(key)) && !isdigit(cmds[i].cmd[0]))
7990 cmd_status.list[c++] = cmds[i].cmd;
7994 cmd_status.len = c;
7997 char *
7998 cmd_getnext(int dir)
8000 cmd_status.index += dir;
8002 if (cmd_status.index < 0)
8003 cmd_status.index = cmd_status.len - 1;
8004 else if (cmd_status.index >= cmd_status.len)
8005 cmd_status.index = 0;
8007 return cmd_status.list[cmd_status.index];
8011 cmd_tokenize(char *s, char *tokens[])
8013 int i = 0;
8014 char *tok, *last;
8015 size_t len = strlen(s);
8016 bool blank;
8018 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8019 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8020 tok = strtok_r(NULL, " ", &last), i++)
8021 tokens[i] = tok;
8023 if (blank && i < 3)
8024 tokens[i++] = "";
8026 return (i);
8029 void
8030 cmd_complete(struct tab *t, char *str, int dir)
8032 GtkEntry *w = GTK_ENTRY(t->cmd);
8033 int i, j, levels, c = 0, dep = 0, parent = -1;
8034 int matchcount = 0;
8035 char *tok, *match, *s = g_strdup(str);
8036 char *tokens[3];
8037 char res[XT_MAX_URL_LENGTH + 32] = ":";
8038 char *sc = s;
8040 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8042 /* copy prefix*/
8043 for (i = 0; isdigit(s[i]); i++)
8044 res[i + 1] = s[i];
8046 for (; isspace(s[i]); i++)
8047 res[i + 1] = s[i];
8049 s += i;
8051 levels = cmd_tokenize(s, tokens);
8053 for (i = 0; i < levels - 1; i++) {
8054 tok = tokens[i];
8055 matchcount = 0;
8056 for (j = c; j < LENGTH(cmds); j++) {
8057 if (cmds[j].level < dep)
8058 break;
8059 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8060 strlen(tok))) {
8061 matchcount++;
8062 c = j + 1;
8063 if (strlen(tok) == strlen(cmds[j].cmd)) {
8064 matchcount = 1;
8065 break;
8070 if (matchcount == 1) {
8071 strlcat(res, tok, sizeof res);
8072 strlcat(res, " ", sizeof res);
8073 dep++;
8074 } else {
8075 g_free(sc);
8076 return;
8079 parent = c - 1;
8082 if (cmd_status.index == -1)
8083 cmd_getlist(parent, tokens[i]);
8085 if (cmd_status.len > 0) {
8086 match = cmd_getnext(dir);
8087 strlcat(res, match, sizeof res);
8088 gtk_entry_set_text(w, res);
8089 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8092 g_free(sc);
8095 gboolean
8096 cmd_execute(struct tab *t, char *str)
8098 struct cmd *cmd = NULL;
8099 char *tok, *last, *s = g_strdup(str), *sc;
8100 char prefixstr[4];
8101 int j, len, c = 0, dep = 0, matchcount = 0;
8102 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8103 struct karg arg = {0, NULL, -1};
8105 sc = s;
8107 /* copy prefix*/
8108 for (j = 0; j<3 && isdigit(s[j]); j++)
8109 prefixstr[j]=s[j];
8111 prefixstr[j]='\0';
8113 s += j;
8114 while (isspace(s[0]))
8115 s++;
8117 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8118 prefix = atoi(prefixstr);
8119 else
8120 s = sc;
8122 for (tok = strtok_r(s, " ", &last); tok;
8123 tok = strtok_r(NULL, " ", &last)) {
8124 matchcount = 0;
8125 for (j = c; j < LENGTH(cmds); j++) {
8126 if (cmds[j].level < dep)
8127 break;
8128 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8129 strlen(tok);
8130 if (cmds[j].level == dep &&
8131 !strncmp(tok, cmds[j].cmd, len)) {
8132 matchcount++;
8133 c = j + 1;
8134 cmd = &cmds[j];
8135 if (len == strlen(cmds[j].cmd)) {
8136 matchcount = 1;
8137 break;
8141 if (matchcount == 1) {
8142 if (cmd->type > 0)
8143 goto execute_cmd;
8144 dep++;
8145 } else {
8146 show_oops(t, "Invalid command: %s", str);
8147 goto done;
8150 execute_cmd:
8151 arg.i = cmd->arg;
8153 if (prefix != -1)
8154 arg.precount = prefix;
8155 else if (cmd_prefix > 0)
8156 arg.precount = cmd_prefix;
8158 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8159 show_oops(t, "No prefix allowed: %s", str);
8160 goto done;
8162 if (cmd->type > 1)
8163 arg.s = last ? g_strdup(last) : g_strdup("");
8164 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8165 arg.precount = atoi(arg.s);
8166 if (arg.precount <= 0) {
8167 if (arg.s[0] == '0')
8168 show_oops(t, "Zero count");
8169 else
8170 show_oops(t, "Trailing characters");
8171 goto done;
8175 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8176 __func__, arg.precount, arg.s);
8178 cmd->func(t, &arg);
8180 rv = XT_CB_HANDLED;
8181 done:
8182 if (j > 0)
8183 cmd_prefix = 0;
8184 g_free(sc);
8185 if (arg.s)
8186 g_free(arg.s);
8188 return (rv);
8192 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8194 if (t == NULL) {
8195 show_oops(NULL, "entry_key_cb invalid parameters");
8196 return (XT_CB_PASSTHROUGH);
8199 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8200 e->keyval, e->state, t);
8202 hide_oops(t);
8204 if (e->keyval == GDK_Escape) {
8205 /* don't use focus_webview(t) because we want to type :cmds */
8206 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8209 return (handle_keypress(t, e, 1));
8212 struct command_entry *
8213 history_prev(struct command_list *l, struct command_entry *at)
8215 if (at == NULL)
8216 at = TAILQ_LAST(l, command_list);
8217 else {
8218 at = TAILQ_PREV(at, command_list, entry);
8219 if (at == NULL)
8220 at = TAILQ_LAST(l, command_list);
8223 return (at);
8226 struct command_entry *
8227 history_next(struct command_list *l, struct command_entry *at)
8229 if (at == NULL)
8230 at = TAILQ_FIRST(l);
8231 else {
8232 at = TAILQ_NEXT(at, entry);
8233 if (at == NULL)
8234 at = TAILQ_FIRST(l);
8237 return (at);
8241 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8243 int rv = XT_CB_HANDLED;
8244 const gchar *c = gtk_entry_get_text(w);
8246 if (t == NULL) {
8247 show_oops(NULL, "cmd_keypress_cb parameters");
8248 return (XT_CB_PASSTHROUGH);
8251 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8252 e->keyval, e->state, t);
8254 /* sanity */
8255 if (c == NULL)
8256 e->keyval = GDK_Escape;
8257 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8258 e->keyval = GDK_Escape;
8260 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8261 e->keyval != GDK_ISO_Left_Tab)
8262 cmd_status.index = -1;
8264 switch (e->keyval) {
8265 case GDK_Tab:
8266 if (c[0] == ':')
8267 cmd_complete(t, (char *)&c[1], 1);
8268 goto done;
8269 case GDK_ISO_Left_Tab:
8270 if (c[0] == ':')
8271 cmd_complete(t, (char *)&c[1], -1);
8273 goto done;
8274 case GDK_Down:
8275 if (c[0] != ':') {
8276 if ((search_at = history_next(&shl, search_at))) {
8277 search_at->line[0] = c[0];
8278 gtk_entry_set_text(w, search_at->line);
8279 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8281 } else {
8282 if ((history_at = history_prev(&chl, history_at))) {
8283 history_at->line[0] = c[0];
8284 gtk_entry_set_text(w, history_at->line);
8285 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8289 goto done;
8290 case GDK_Up:
8291 if (c[0] != ':') {
8292 if ((search_at = history_next(&shl, search_at))) {
8293 search_at->line[0] = c[0];
8294 gtk_entry_set_text(w, search_at->line);
8295 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8297 } else {
8298 if ((history_at = history_next(&chl, history_at))) {
8299 history_at->line[0] = c[0];
8300 gtk_entry_set_text(w, history_at->line);
8301 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8305 goto done;
8306 case GDK_BackSpace:
8307 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8308 break;
8309 /* FALLTHROUGH */
8310 case GDK_Escape:
8311 hide_cmd(t);
8312 focus_webview(t);
8314 /* cancel search */
8315 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8316 webkit_web_view_unmark_text_matches(t->wv);
8317 goto done;
8320 rv = XT_CB_PASSTHROUGH;
8321 done:
8322 return (rv);
8325 void
8326 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8328 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8331 void
8332 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8334 /* popup menu enabled */
8335 t->popup = 1;
8339 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8341 if (t == NULL) {
8342 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8343 return (XT_CB_PASSTHROUGH);
8346 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8347 t->tab_id, t->popup);
8349 /* if popup is enabled don't lose focus */
8350 if (t->popup) {
8351 t->popup = 0;
8352 return (XT_CB_PASSTHROUGH);
8355 hide_cmd(t);
8356 hide_oops(t);
8358 if (show_url == 0 || t->focus_wv)
8359 focus_webview(t);
8360 else
8361 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8363 return (XT_CB_PASSTHROUGH);
8366 void
8367 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8369 char *s;
8370 const gchar *c = gtk_entry_get_text(entry);
8372 if (t == NULL) {
8373 show_oops(NULL, "cmd_activate_cb invalid parameters");
8374 return;
8377 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8379 hide_cmd(t);
8381 /* sanity */
8382 if (c == NULL)
8383 goto done;
8384 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8385 goto done;
8386 if (strlen(c) < 2)
8387 goto done;
8388 s = (char *)&c[1];
8390 if (c[0] == '/' || c[0] == '?') {
8391 /* see if there is a timer pending */
8392 if (t->search_id) {
8393 g_source_remove(t->search_id);
8394 t->search_id = 0;
8395 search_cb(t);
8398 if (t->search_text) {
8399 g_free(t->search_text);
8400 t->search_text = NULL;
8403 t->search_text = g_strdup(s);
8404 if (global_search)
8405 g_free(global_search);
8406 global_search = g_strdup(s);
8407 t->search_forward = c[0] == '/';
8409 history_add(&shl, search_file, s, &search_history_count);
8410 goto done;
8413 history_add(&chl, command_file, s, &cmd_history_count);
8414 cmd_execute(t, s);
8415 done:
8416 return;
8419 void
8420 backward_cb(GtkWidget *w, struct tab *t)
8422 struct karg a;
8424 if (t == NULL) {
8425 show_oops(NULL, "backward_cb invalid parameters");
8426 return;
8429 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8431 a.i = XT_NAV_BACK;
8432 navaction(t, &a);
8435 void
8436 forward_cb(GtkWidget *w, struct tab *t)
8438 struct karg a;
8440 if (t == NULL) {
8441 show_oops(NULL, "forward_cb invalid parameters");
8442 return;
8445 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8447 a.i = XT_NAV_FORWARD;
8448 navaction(t, &a);
8451 void
8452 home_cb(GtkWidget *w, struct tab *t)
8454 if (t == NULL) {
8455 show_oops(NULL, "home_cb invalid parameters");
8456 return;
8459 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8461 load_uri(t, home);
8464 void
8465 stop_cb(GtkWidget *w, struct tab *t)
8467 WebKitWebFrame *frame;
8469 if (t == NULL) {
8470 show_oops(NULL, "stop_cb invalid parameters");
8471 return;
8474 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8476 frame = webkit_web_view_get_main_frame(t->wv);
8477 if (frame == NULL) {
8478 show_oops(t, "stop_cb: no frame");
8479 return;
8482 webkit_web_frame_stop_loading(frame);
8483 abort_favicon_download(t);
8486 void
8487 setup_webkit(struct tab *t)
8489 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8490 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8491 FALSE, (char *)NULL);
8492 else
8493 warnx("webkit does not have \"enable-dns-prefetching\" property");
8494 g_object_set(G_OBJECT(t->settings),
8495 "user-agent", t->user_agent, (char *)NULL);
8496 g_object_set(G_OBJECT(t->settings),
8497 "enable-scripts", enable_scripts, (char *)NULL);
8498 g_object_set(G_OBJECT(t->settings),
8499 "enable-plugins", enable_plugins, (char *)NULL);
8500 g_object_set(G_OBJECT(t->settings),
8501 "javascript-can-open-windows-automatically", enable_scripts,
8502 (char *)NULL);
8503 g_object_set(G_OBJECT(t->settings),
8504 "enable-html5-database", FALSE, (char *)NULL);
8505 g_object_set(G_OBJECT(t->settings),
8506 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8507 g_object_set(G_OBJECT(t->settings),
8508 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8509 g_object_set(G_OBJECT(t->settings),
8510 "spell_checking_languages", spell_check_languages, (char *)NULL);
8511 g_object_set(G_OBJECT(t->wv),
8512 "full-content-zoom", TRUE, (char *)NULL);
8514 webkit_web_view_set_settings(t->wv, t->settings);
8517 gboolean
8518 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8520 struct tab *ti, *t = NULL;
8521 gdouble view_size, value, max;
8522 gchar *position;
8524 TAILQ_FOREACH(ti, &tabs, entry)
8525 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8526 t = ti;
8527 break;
8530 if (t == NULL)
8531 return FALSE;
8533 if (adjustment == NULL)
8534 adjustment = gtk_scrolled_window_get_vadjustment(
8535 GTK_SCROLLED_WINDOW(t->browser_win));
8537 view_size = gtk_adjustment_get_page_size(adjustment);
8538 value = gtk_adjustment_get_value(adjustment);
8539 max = gtk_adjustment_get_upper(adjustment) - view_size;
8541 if (max == 0)
8542 position = g_strdup("All");
8543 else if (value == max)
8544 position = g_strdup("Bot");
8545 else if (value == 0)
8546 position = g_strdup("Top");
8547 else
8548 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8550 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8551 g_free(position);
8553 return (TRUE);
8556 GtkWidget *
8557 create_browser(struct tab *t)
8559 GtkWidget *w;
8560 gchar *strval;
8561 GtkAdjustment *adjustment;
8563 if (t == NULL) {
8564 show_oops(NULL, "create_browser invalid parameters");
8565 return (NULL);
8568 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8569 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8570 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8571 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8573 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8574 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8575 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8577 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8578 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8580 /* set defaults */
8581 t->settings = webkit_web_settings_new();
8583 if (user_agent == NULL) {
8584 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8585 (char *)NULL);
8586 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8587 g_free(strval);
8588 } else
8589 t->user_agent = g_strdup(user_agent);
8591 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8593 adjustment =
8594 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8595 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8596 G_CALLBACK(update_statusbar_position), NULL);
8598 setup_webkit(t);
8600 return (w);
8603 GtkWidget *
8604 create_window(void)
8606 GtkWidget *w;
8608 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8609 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8610 gtk_widget_set_name(w, "xxxterm");
8611 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8612 g_signal_connect(G_OBJECT(w), "delete_event",
8613 G_CALLBACK (gtk_main_quit), NULL);
8615 return (w);
8618 GtkWidget *
8619 create_kiosk_toolbar(struct tab *t)
8621 GtkWidget *toolbar = NULL, *b;
8623 b = gtk_hbox_new(FALSE, 0);
8624 toolbar = b;
8625 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8627 /* backward button */
8628 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8629 gtk_widget_set_sensitive(t->backward, FALSE);
8630 g_signal_connect(G_OBJECT(t->backward), "clicked",
8631 G_CALLBACK(backward_cb), t);
8632 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8634 /* forward button */
8635 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8636 gtk_widget_set_sensitive(t->forward, FALSE);
8637 g_signal_connect(G_OBJECT(t->forward), "clicked",
8638 G_CALLBACK(forward_cb), t);
8639 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8641 /* home button */
8642 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8643 gtk_widget_set_sensitive(t->gohome, true);
8644 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8645 G_CALLBACK(home_cb), t);
8646 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8648 /* create widgets but don't use them */
8649 t->uri_entry = gtk_entry_new();
8650 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8651 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8652 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8654 return (toolbar);
8657 GtkWidget *
8658 create_toolbar(struct tab *t)
8660 GtkWidget *toolbar = NULL, *b, *eb1;
8662 b = gtk_hbox_new(FALSE, 0);
8663 toolbar = b;
8664 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8666 /* backward button */
8667 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8668 gtk_widget_set_sensitive(t->backward, FALSE);
8669 g_signal_connect(G_OBJECT(t->backward), "clicked",
8670 G_CALLBACK(backward_cb), t);
8671 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8673 /* forward button */
8674 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8675 gtk_widget_set_sensitive(t->forward, FALSE);
8676 g_signal_connect(G_OBJECT(t->forward), "clicked",
8677 G_CALLBACK(forward_cb), t);
8678 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8679 FALSE, 0);
8681 /* stop button */
8682 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8683 gtk_widget_set_sensitive(t->stop, FALSE);
8684 g_signal_connect(G_OBJECT(t->stop), "clicked",
8685 G_CALLBACK(stop_cb), t);
8686 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8687 FALSE, 0);
8689 /* JS button */
8690 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8691 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8692 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8693 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8694 G_CALLBACK(js_toggle_cb), t);
8695 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8697 t->uri_entry = gtk_entry_new();
8698 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8699 G_CALLBACK(activate_uri_entry_cb), t);
8700 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8701 G_CALLBACK(entry_key_cb), t);
8702 completion_add(t);
8703 eb1 = gtk_hbox_new(FALSE, 0);
8704 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8705 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8706 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8708 /* search entry */
8709 if (search_string) {
8710 GtkWidget *eb2;
8711 t->search_entry = gtk_entry_new();
8712 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8713 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8714 G_CALLBACK(activate_search_entry_cb), t);
8715 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8716 G_CALLBACK(entry_key_cb), t);
8717 gtk_widget_set_size_request(t->search_entry, -1, -1);
8718 eb2 = gtk_hbox_new(FALSE, 0);
8719 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8720 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8722 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8725 return (toolbar);
8728 GtkWidget *
8729 create_buffers(struct tab *t)
8731 GtkCellRenderer *renderer;
8732 GtkWidget *view;
8734 view = gtk_tree_view_new();
8736 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8738 renderer = gtk_cell_renderer_text_new();
8739 gtk_tree_view_insert_column_with_attributes
8740 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8742 renderer = gtk_cell_renderer_text_new();
8743 gtk_tree_view_insert_column_with_attributes
8744 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8745 (char *)NULL);
8747 gtk_tree_view_set_model
8748 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8750 return view;
8753 void
8754 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8755 GtkTreeViewColumn *col, struct tab *t)
8757 GtkTreeIter iter;
8758 guint id;
8760 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8762 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8763 path)) {
8764 gtk_tree_model_get
8765 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8766 set_current_tab(id - 1);
8769 hide_buffers(t);
8772 /* after tab reordering/creation/removal */
8773 void
8774 recalc_tabs(void)
8776 struct tab *t;
8777 int maxid = 0;
8779 TAILQ_FOREACH(t, &tabs, entry) {
8780 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8781 if (t->tab_id > maxid)
8782 maxid = t->tab_id;
8784 gtk_widget_show(t->tab_elems.sep);
8787 TAILQ_FOREACH(t, &tabs, entry) {
8788 if (t->tab_id == maxid) {
8789 gtk_widget_hide(t->tab_elems.sep);
8790 break;
8795 /* after active tab change */
8796 void
8797 recolor_compact_tabs(void)
8799 struct tab *t;
8800 int curid = 0;
8801 GdkColor color;
8803 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8804 TAILQ_FOREACH(t, &tabs, entry)
8805 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8806 &color);
8808 curid = gtk_notebook_get_current_page(notebook);
8809 TAILQ_FOREACH(t, &tabs, entry)
8810 if (t->tab_id == curid) {
8811 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8812 gtk_widget_modify_fg(t->tab_elems.label,
8813 GTK_STATE_NORMAL, &color);
8814 break;
8818 void
8819 set_current_tab(int page_num)
8821 buffercmd_abort(get_current_tab());
8822 gtk_notebook_set_current_page(notebook, page_num);
8823 recolor_compact_tabs();
8827 undo_close_tab_save(struct tab *t)
8829 int m, n;
8830 const gchar *uri;
8831 struct undo *u1, *u2;
8832 GList *items;
8833 WebKitWebHistoryItem *item;
8835 if ((uri = get_uri(t)) == NULL)
8836 return (1);
8838 u1 = g_malloc0(sizeof(struct undo));
8839 u1->uri = g_strdup(uri);
8841 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8843 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8844 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8845 u1->back = n;
8847 /* forward history */
8848 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8850 while (items) {
8851 item = items->data;
8852 u1->history = g_list_prepend(u1->history,
8853 webkit_web_history_item_copy(item));
8854 items = g_list_next(items);
8857 /* current item */
8858 if (m) {
8859 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8860 u1->history = g_list_prepend(u1->history,
8861 webkit_web_history_item_copy(item));
8864 /* back history */
8865 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8867 while (items) {
8868 item = items->data;
8869 u1->history = g_list_prepend(u1->history,
8870 webkit_web_history_item_copy(item));
8871 items = g_list_next(items);
8874 TAILQ_INSERT_HEAD(&undos, u1, entry);
8876 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8877 u2 = TAILQ_LAST(&undos, undo_tailq);
8878 TAILQ_REMOVE(&undos, u2, entry);
8879 g_free(u2->uri);
8880 g_list_free(u2->history);
8881 g_free(u2);
8882 } else
8883 undo_count++;
8885 return (0);
8888 void
8889 delete_tab(struct tab *t)
8891 struct karg a;
8893 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8895 if (t == NULL)
8896 return;
8898 buffercmd_abort(t);
8899 TAILQ_REMOVE(&tabs, t, entry);
8901 /* Halt all webkit activity. */
8902 abort_favicon_download(t);
8903 webkit_web_view_stop_loading(t->wv);
8905 /* Save the tab, so we can undo the close. */
8906 undo_close_tab_save(t);
8908 if (browser_mode == XT_BM_KIOSK) {
8909 gtk_widget_destroy(t->uri_entry);
8910 gtk_widget_destroy(t->stop);
8911 gtk_widget_destroy(t->js_toggle);
8914 gtk_widget_destroy(t->tab_elems.eventbox);
8915 gtk_widget_destroy(t->vbox);
8917 /* just in case */
8918 if (t->search_id)
8919 g_source_remove(t->search_id);
8921 g_free(t->user_agent);
8922 g_free(t->stylesheet);
8923 g_free(t->tmp_uri);
8924 g_free(t);
8926 if (TAILQ_EMPTY(&tabs)) {
8927 if (browser_mode == XT_BM_KIOSK)
8928 create_new_tab(home, NULL, 1, -1);
8929 else
8930 create_new_tab(NULL, NULL, 1, -1);
8933 /* recreate session */
8934 if (session_autosave) {
8935 a.s = NULL;
8936 save_tabs(t, &a);
8939 recalc_tabs();
8940 recolor_compact_tabs();
8943 void
8944 update_statusbar_zoom(struct tab *t)
8946 gfloat zoom;
8947 char s[16] = { '\0' };
8949 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8950 if ((zoom <= 0.99 || zoom >= 1.01))
8951 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8952 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8955 void
8956 setzoom_webkit(struct tab *t, int adjust)
8958 #define XT_ZOOMPERCENT 0.04
8960 gfloat zoom;
8962 if (t == NULL) {
8963 show_oops(NULL, "setzoom_webkit invalid parameters");
8964 return;
8967 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8968 if (adjust == XT_ZOOM_IN)
8969 zoom += XT_ZOOMPERCENT;
8970 else if (adjust == XT_ZOOM_OUT)
8971 zoom -= XT_ZOOMPERCENT;
8972 else if (adjust > 0)
8973 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8974 else {
8975 show_oops(t, "setzoom_webkit invalid zoom value");
8976 return;
8979 if (zoom < XT_ZOOMPERCENT)
8980 zoom = XT_ZOOMPERCENT;
8981 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8982 update_statusbar_zoom(t);
8985 gboolean
8986 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8988 struct tab *t = (struct tab *) data;
8990 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8992 switch (event->button) {
8993 case 1:
8994 set_current_tab(t->tab_id);
8995 break;
8996 case 2:
8997 delete_tab(t);
8998 break;
9001 return TRUE;
9004 void
9005 append_tab(struct tab *t)
9007 if (t == NULL)
9008 return;
9010 TAILQ_INSERT_TAIL(&tabs, t, entry);
9011 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9014 GtkWidget *
9015 create_sbe(int width)
9017 GtkWidget *sbe;
9019 sbe = gtk_entry_new();
9020 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9021 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9022 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9023 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9024 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9025 gtk_widget_set_size_request(sbe, width, -1);
9027 return sbe;
9030 struct tab *
9031 create_new_tab(char *title, struct undo *u, int focus, int position)
9033 struct tab *t;
9034 int load = 1, id;
9035 GtkWidget *b, *bb;
9036 WebKitWebHistoryItem *item;
9037 GList *items;
9038 GdkColor color;
9039 char *p;
9040 int sbe_p = 0, sbe_b = 0,
9041 sbe_z = 0;
9043 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9045 if (tabless && !TAILQ_EMPTY(&tabs)) {
9046 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9047 return (NULL);
9050 t = g_malloc0(sizeof *t);
9052 if (title == NULL) {
9053 title = "(untitled)";
9054 load = 0;
9057 t->vbox = gtk_vbox_new(FALSE, 0);
9059 /* label + button for tab */
9060 b = gtk_hbox_new(FALSE, 0);
9061 t->tab_content = b;
9063 #if GTK_CHECK_VERSION(2, 20, 0)
9064 t->spinner = gtk_spinner_new();
9065 #endif
9066 t->label = gtk_label_new(title);
9067 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9068 gtk_widget_set_size_request(t->label, 100, 0);
9069 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9070 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9071 gtk_widget_set_size_request(b, 130, 0);
9073 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9074 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9075 #if GTK_CHECK_VERSION(2, 20, 0)
9076 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9077 #endif
9079 /* toolbar */
9080 if (browser_mode == XT_BM_KIOSK) {
9081 t->toolbar = create_kiosk_toolbar(t);
9082 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9084 } else {
9085 t->toolbar = create_toolbar(t);
9086 if (fancy_bar)
9087 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9088 FALSE, 0);
9091 /* marks */
9092 marks_clear(t);
9094 /* browser */
9095 t->browser_win = create_browser(t);
9096 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9098 /* oops message for user feedback */
9099 t->oops = gtk_entry_new();
9100 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9101 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9102 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9103 gdk_color_parse(XT_COLOR_RED, &color);
9104 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9105 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9106 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9108 /* command entry */
9109 t->cmd = gtk_entry_new();
9110 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9111 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9112 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9113 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9115 /* status bar */
9116 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9118 t->sbe.statusbar = gtk_entry_new();
9119 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9120 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9121 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9122 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9124 /* create these widgets only if specified in statusbar_elems */
9126 t->sbe.position = create_sbe(40);
9127 t->sbe.zoom = create_sbe(40);
9128 t->sbe.buffercmd = create_sbe(60);
9130 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9132 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9133 TRUE, FALSE);
9135 /* gtk widgets cannot be added to a box twice. sbe_* variables
9136 make sure of this */
9137 for (p = statusbar_elems; *p != '\0'; p++) {
9138 switch (*p) {
9139 case '|':
9141 GtkWidget *sep = gtk_vseparator_new();
9143 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9144 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9145 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9146 FALSE, FALSE, FALSE);
9147 break;
9149 case 'P':
9150 if (sbe_p) {
9151 warnx("flag \"%c\" specified more than "
9152 "once in statusbar_elems\n", *p);
9153 break;
9155 sbe_p = 1;
9156 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9157 t->sbe.position, FALSE, FALSE, FALSE);
9158 break;
9159 case 'B':
9160 if (sbe_b) {
9161 warnx("flag \"%c\" specified more than "
9162 "once in statusbar_elems\n", *p);
9163 break;
9165 sbe_b = 1;
9166 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9167 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9168 break;
9169 case 'Z':
9170 if (sbe_z) {
9171 warnx("flag \"%c\" specified more than "
9172 "once in statusbar_elems\n", *p);
9173 break;
9175 sbe_z = 1;
9176 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9177 t->sbe.zoom, FALSE, FALSE, FALSE);
9178 break;
9179 default:
9180 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9181 break;
9185 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9187 /* buffer list */
9188 t->buffers = create_buffers(t);
9189 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9191 /* xtp meaning is normal by default */
9192 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9194 /* set empty favicon */
9195 xt_icon_from_name(t, "text-html");
9197 /* and show it all */
9198 gtk_widget_show_all(b);
9199 gtk_widget_show_all(t->vbox);
9201 /* compact tab bar */
9202 t->tab_elems.label = gtk_label_new(title);
9203 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9204 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9205 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9206 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9208 t->tab_elems.eventbox = gtk_event_box_new();
9209 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9210 t->tab_elems.sep = gtk_vseparator_new();
9212 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9213 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9214 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9215 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9216 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9217 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9219 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9220 TRUE, 0);
9221 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9222 FALSE, 0);
9223 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9224 t->tab_elems.box);
9226 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9227 TRUE, 0);
9228 gtk_widget_show_all(t->tab_elems.eventbox);
9230 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9231 append_tab(t);
9232 else {
9233 id = position >= 0 ? position :
9234 gtk_notebook_get_current_page(notebook) + 1;
9235 if (id > gtk_notebook_get_n_pages(notebook))
9236 append_tab(t);
9237 else {
9238 TAILQ_INSERT_TAIL(&tabs, t, entry);
9239 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9240 gtk_box_reorder_child(GTK_BOX(tab_bar),
9241 t->tab_elems.eventbox, id);
9242 recalc_tabs();
9246 #if GTK_CHECK_VERSION(2, 20, 0)
9247 /* turn spinner off if we are a new tab without uri */
9248 if (!load) {
9249 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9250 gtk_widget_hide(t->spinner);
9252 #endif
9253 /* make notebook tabs reorderable */
9254 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9256 /* compact tabs clickable */
9257 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9258 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9260 g_object_connect(G_OBJECT(t->cmd),
9261 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9262 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9263 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9264 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9265 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9266 (char *)NULL);
9268 /* reuse wv_button_cb to hide oops */
9269 g_object_connect(G_OBJECT(t->oops),
9270 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9271 (char *)NULL);
9273 g_signal_connect(t->buffers,
9274 "row-activated", G_CALLBACK(row_activated_cb), t);
9275 g_object_connect(G_OBJECT(t->buffers),
9276 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9278 g_object_connect(G_OBJECT(t->wv),
9279 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9280 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9281 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9282 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9283 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9284 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9285 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9286 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9287 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9288 "signal::event", G_CALLBACK(webview_event_cb), t,
9289 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9290 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9291 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9292 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9293 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9294 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9295 (char *)NULL);
9296 g_signal_connect(t->wv,
9297 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9298 g_signal_connect(t->wv,
9299 "notify::title", G_CALLBACK(notify_title_cb), t);
9301 /* hijack the unused keys as if we were the browser */
9302 g_object_connect(G_OBJECT(t->toolbar),
9303 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9304 (char *)NULL);
9306 g_signal_connect(G_OBJECT(bb), "button_press_event",
9307 G_CALLBACK(tab_close_cb), t);
9309 /* setup history */
9310 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9311 /* restore the tab's history */
9312 if (u && u->history) {
9313 items = u->history;
9314 while (items) {
9315 item = items->data;
9316 webkit_web_back_forward_list_add_item(t->bfl, item);
9317 items = g_list_next(items);
9320 item = g_list_nth_data(u->history, u->back);
9321 if (item)
9322 webkit_web_view_go_to_back_forward_item(t->wv, item);
9324 g_list_free(items);
9325 g_list_free(u->history);
9326 } else
9327 webkit_web_back_forward_list_clear(t->bfl);
9329 /* hide stuff */
9330 hide_cmd(t);
9331 hide_oops(t);
9332 hide_buffers(t);
9333 url_set_visibility();
9334 statusbar_set_visibility();
9336 if (focus) {
9337 set_current_tab(t->tab_id);
9338 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9339 t->tab_id);
9341 if (load) {
9342 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9343 load_uri(t, title);
9344 } else {
9345 if (show_url == 1)
9346 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9347 else
9348 focus_webview(t);
9350 } else if (load)
9351 load_uri(t, title);
9353 recolor_compact_tabs();
9354 setzoom_webkit(t, XT_ZOOM_NORMAL);
9355 return (t);
9358 void
9359 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9360 gpointer *udata)
9362 struct tab *t;
9363 const gchar *uri;
9365 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9367 if (gtk_notebook_get_current_page(notebook) == -1)
9368 recalc_tabs();
9370 TAILQ_FOREACH(t, &tabs, entry) {
9371 if (t->tab_id == pn) {
9372 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9373 "%d\n", pn);
9375 uri = get_title(t, TRUE);
9376 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9378 hide_cmd(t);
9379 hide_oops(t);
9381 if (t->focus_wv) {
9382 /* can't use focus_webview here */
9383 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9389 void
9390 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9391 gpointer *udata)
9393 struct tab *t = NULL, *tt;
9395 recalc_tabs();
9397 TAILQ_FOREACH(tt, &tabs, entry)
9398 if (tt->tab_id == pn) {
9399 t = tt;
9400 break;
9403 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9405 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9406 t->tab_id);
9409 void
9410 menuitem_response(struct tab *t)
9412 gtk_notebook_set_current_page(notebook, t->tab_id);
9415 gboolean
9416 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9418 GtkWidget *menu, *menu_items;
9419 GdkEventButton *bevent;
9420 const gchar *uri;
9421 struct tab *ti;
9423 if (event->type == GDK_BUTTON_PRESS) {
9424 bevent = (GdkEventButton *) event;
9425 menu = gtk_menu_new();
9427 TAILQ_FOREACH(ti, &tabs, entry) {
9428 if ((uri = get_uri(ti)) == NULL)
9429 /* XXX make sure there is something to print */
9430 /* XXX add gui pages in here to look purdy */
9431 uri = "(untitled)";
9432 menu_items = gtk_menu_item_new_with_label(uri);
9433 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9434 gtk_widget_show(menu_items);
9436 g_signal_connect_swapped((menu_items),
9437 "activate", G_CALLBACK(menuitem_response),
9438 (gpointer)ti);
9441 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9442 bevent->button, bevent->time);
9444 /* unref object so it'll free itself when popped down */
9445 #if !GTK_CHECK_VERSION(3, 0, 0)
9446 /* XXX does not need unref with gtk+3? */
9447 g_object_ref_sink(menu);
9448 g_object_unref(menu);
9449 #endif
9451 return (TRUE /* eat event */);
9454 return (FALSE /* propagate */);
9458 icon_size_map(int icon_size)
9460 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9461 icon_size > GTK_ICON_SIZE_DIALOG)
9462 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9464 return (icon_size);
9467 GtkWidget *
9468 create_button(char *name, char *stockid, int size)
9470 GtkWidget *button, *image;
9471 gchar *rcstring;
9472 int gtk_icon_size;
9474 rcstring = g_strdup_printf(
9475 "style \"%s-style\"\n"
9476 "{\n"
9477 " GtkWidget::focus-padding = 0\n"
9478 " GtkWidget::focus-line-width = 0\n"
9479 " xthickness = 0\n"
9480 " ythickness = 0\n"
9481 "}\n"
9482 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9483 gtk_rc_parse_string(rcstring);
9484 g_free(rcstring);
9485 button = gtk_button_new();
9486 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9487 gtk_icon_size = icon_size_map(size ? size : icon_size);
9489 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9490 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9491 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9492 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9493 gtk_widget_set_name(button, name);
9494 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9496 return (button);
9499 void
9500 button_set_stockid(GtkWidget *button, char *stockid)
9502 GtkWidget *image;
9504 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9505 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9506 gtk_button_set_image(GTK_BUTTON(button), image);
9509 void
9510 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9512 gchar *p = NULL;
9513 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9514 gint len;
9516 if (xterm_workaround == 0)
9517 return;
9520 * xterm doesn't play nice with clipboards because it clears the
9521 * primary when clicked. We rely on primary being set to properly
9522 * handle middle mouse button clicks (paste). So when someone clears
9523 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9524 * other application behavior (as in DON'T clear primary).
9527 p = gtk_clipboard_wait_for_text(primary);
9528 if (p == NULL) {
9529 if (gdk_property_get(gdk_get_default_root_window(),
9530 atom,
9531 gdk_atom_intern("STRING", FALSE),
9533 1024 * 1024 /* picked out of my butt */,
9534 FALSE,
9535 NULL,
9536 NULL,
9537 &len,
9538 (guchar **)&p)) {
9539 /* yes sir, we need to NUL the string */
9540 p[len] = '\0';
9541 gtk_clipboard_set_text(primary, p, -1);
9545 if (p)
9546 g_free(p);
9549 void
9550 create_canvas(void)
9552 GtkWidget *vbox;
9553 GList *l = NULL;
9554 GdkPixbuf *pb;
9555 char file[PATH_MAX];
9556 int i;
9558 vbox = gtk_vbox_new(FALSE, 0);
9559 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9560 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9561 #if !GTK_CHECK_VERSION(3, 0, 0)
9562 /* XXX seems to be needed with gtk+2 */
9563 gtk_notebook_set_tab_hborder(notebook, 0);
9564 gtk_notebook_set_tab_vborder(notebook, 0);
9565 #endif
9566 gtk_notebook_set_scrollable(notebook, TRUE);
9567 gtk_notebook_set_show_border(notebook, FALSE);
9568 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9570 abtn = gtk_button_new();
9571 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9572 gtk_widget_set_size_request(arrow, -1, -1);
9573 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9574 gtk_widget_set_size_request(abtn, -1, 20);
9576 #if GTK_CHECK_VERSION(2, 20, 0)
9577 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9578 #endif
9579 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9581 /* compact tab bar */
9582 tab_bar = gtk_hbox_new(TRUE, 0);
9584 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9585 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9586 gtk_widget_set_size_request(vbox, -1, -1);
9588 g_object_connect(G_OBJECT(notebook),
9589 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9590 (char *)NULL);
9591 g_object_connect(G_OBJECT(notebook),
9592 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9593 NULL, (char *)NULL);
9594 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9595 G_CALLBACK(arrow_cb), NULL);
9597 main_window = create_window();
9598 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9600 /* icons */
9601 for (i = 0; i < LENGTH(icons); i++) {
9602 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9603 pb = gdk_pixbuf_new_from_file(file, NULL);
9604 l = g_list_append(l, pb);
9606 gtk_window_set_default_icon_list(l);
9608 /* clipboard work around */
9609 if (xterm_workaround)
9610 g_signal_connect(
9611 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9612 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9614 gtk_widget_show_all(abtn);
9615 gtk_widget_show_all(main_window);
9616 notebook_tab_set_visibility();
9619 void
9620 set_hook(void **hook, char *name)
9622 if (hook == NULL)
9623 errx(1, "set_hook");
9625 if (*hook == NULL) {
9626 *hook = dlsym(RTLD_NEXT, name);
9627 if (*hook == NULL)
9628 errx(1, "can't hook %s", name);
9632 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9633 gboolean
9634 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9636 g_return_val_if_fail(cookie1, FALSE);
9637 g_return_val_if_fail(cookie2, FALSE);
9639 return (!strcmp (cookie1->name, cookie2->name) &&
9640 !strcmp (cookie1->value, cookie2->value) &&
9641 !strcmp (cookie1->path, cookie2->path) &&
9642 !strcmp (cookie1->domain, cookie2->domain));
9645 void
9646 transfer_cookies(void)
9648 GSList *cf;
9649 SoupCookie *sc, *pc;
9651 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9653 for (;cf; cf = cf->next) {
9654 pc = cf->data;
9655 sc = soup_cookie_copy(pc);
9656 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9659 soup_cookies_free(cf);
9662 void
9663 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9665 GSList *cf;
9666 SoupCookie *ci;
9668 print_cookie("soup_cookie_jar_delete_cookie", c);
9670 if (cookies_enabled == 0)
9671 return;
9673 if (jar == NULL || c == NULL)
9674 return;
9676 /* find and remove from persistent jar */
9677 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9679 for (;cf; cf = cf->next) {
9680 ci = cf->data;
9681 if (soup_cookie_equal(ci, c)) {
9682 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9683 break;
9687 soup_cookies_free(cf);
9689 /* delete from session jar */
9690 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9693 void
9694 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9696 struct domain *d = NULL;
9697 SoupCookie *c;
9698 FILE *r_cookie_f;
9700 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9701 jar, p_cookiejar, s_cookiejar);
9703 if (cookies_enabled == 0)
9704 return;
9706 /* see if we are up and running */
9707 if (p_cookiejar == NULL) {
9708 _soup_cookie_jar_add_cookie(jar, cookie);
9709 return;
9711 /* disallow p_cookiejar adds, shouldn't happen */
9712 if (jar == p_cookiejar)
9713 return;
9715 /* sanity */
9716 if (jar == NULL || cookie == NULL)
9717 return;
9719 if (enable_cookie_whitelist &&
9720 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9721 blocked_cookies++;
9722 DNPRINTF(XT_D_COOKIE,
9723 "soup_cookie_jar_add_cookie: reject %s\n",
9724 cookie->domain);
9725 if (save_rejected_cookies) {
9726 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9727 show_oops(NULL, "can't open reject cookie file");
9728 return;
9730 fseek(r_cookie_f, 0, SEEK_END);
9731 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9732 cookie->http_only ? "#HttpOnly_" : "",
9733 cookie->domain,
9734 *cookie->domain == '.' ? "TRUE" : "FALSE",
9735 cookie->path,
9736 cookie->secure ? "TRUE" : "FALSE",
9737 cookie->expires ?
9738 (gulong)soup_date_to_time_t(cookie->expires) :
9740 cookie->name,
9741 cookie->value);
9742 fflush(r_cookie_f);
9743 fclose(r_cookie_f);
9745 if (!allow_volatile_cookies)
9746 return;
9749 if (cookie->expires == NULL && session_timeout) {
9750 soup_cookie_set_expires(cookie,
9751 soup_date_new_from_now(session_timeout));
9752 print_cookie("modified add cookie", cookie);
9755 /* see if we are white listed for persistence */
9756 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9757 /* add to persistent jar */
9758 c = soup_cookie_copy(cookie);
9759 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9760 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9763 /* add to session jar */
9764 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9765 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9768 void
9769 setup_cookies(void)
9771 char file[PATH_MAX];
9773 set_hook((void *)&_soup_cookie_jar_add_cookie,
9774 "soup_cookie_jar_add_cookie");
9775 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9776 "soup_cookie_jar_delete_cookie");
9778 if (cookies_enabled == 0)
9779 return;
9782 * the following code is intricate due to overriding several libsoup
9783 * functions.
9784 * do not alter order of these operations.
9787 /* rejected cookies */
9788 if (save_rejected_cookies)
9789 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9790 XT_REJECT_FILE);
9792 /* persistent cookies */
9793 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9794 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9796 /* session cookies */
9797 s_cookiejar = soup_cookie_jar_new();
9798 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9799 cookie_policy, (void *)NULL);
9800 transfer_cookies();
9802 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9805 void
9806 setup_proxy(char *uri)
9808 if (proxy_uri) {
9809 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9810 soup_uri_free(proxy_uri);
9811 proxy_uri = NULL;
9813 if (http_proxy) {
9814 if (http_proxy != uri) {
9815 g_free(http_proxy);
9816 http_proxy = NULL;
9820 if (uri) {
9821 http_proxy = g_strdup(uri);
9822 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9823 proxy_uri = soup_uri_new(http_proxy);
9824 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9825 g_object_set(session, "proxy-uri", proxy_uri,
9826 (char *)NULL);
9831 set_http_proxy(char *proxy)
9833 SoupURI *uri;
9835 if (proxy == NULL)
9836 return (1);
9838 /* see if we need to clear it instead */
9839 if (strlen(proxy) == 0) {
9840 setup_proxy(NULL);
9841 return (0);
9844 uri = soup_uri_new(proxy);
9845 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9846 return (1);
9848 setup_proxy(proxy);
9850 soup_uri_free(uri);
9852 return (0);
9856 send_cmd_to_socket(char *cmd)
9858 int s, len, rv = 1;
9859 struct sockaddr_un sa;
9861 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9862 warnx("%s: socket", __func__);
9863 return (rv);
9866 sa.sun_family = AF_UNIX;
9867 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9868 work_dir, XT_SOCKET_FILE);
9869 len = SUN_LEN(&sa);
9871 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9872 warnx("%s: connect", __func__);
9873 goto done;
9876 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9877 warnx("%s: send", __func__);
9878 goto done;
9881 rv = 0;
9882 done:
9883 close(s);
9884 return (rv);
9887 gboolean
9888 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9890 int s, n;
9891 char str[XT_MAX_URL_LENGTH];
9892 socklen_t t = sizeof(struct sockaddr_un);
9893 struct sockaddr_un sa;
9894 struct passwd *p;
9895 uid_t uid;
9896 gid_t gid;
9897 struct tab *tt;
9898 gint fd = g_io_channel_unix_get_fd(source);
9900 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9901 warn("accept");
9902 return (FALSE);
9905 if (getpeereid(s, &uid, &gid) == -1) {
9906 warn("getpeereid");
9907 return (FALSE);
9909 if (uid != getuid() || gid != getgid()) {
9910 warnx("unauthorized user");
9911 return (FALSE);
9914 p = getpwuid(uid);
9915 if (p == NULL) {
9916 warnx("not a valid user");
9917 return (FALSE);
9920 n = recv(s, str, sizeof(str), 0);
9921 if (n <= 0)
9922 return (TRUE);
9924 tt = TAILQ_LAST(&tabs, tab_list);
9925 cmd_execute(tt, str);
9926 return (TRUE);
9930 is_running(void)
9932 int s, len, rv = 1;
9933 struct sockaddr_un sa;
9935 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9936 warn("is_running: socket");
9937 return (-1);
9940 sa.sun_family = AF_UNIX;
9941 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9942 work_dir, XT_SOCKET_FILE);
9943 len = SUN_LEN(&sa);
9945 /* connect to see if there is a listener */
9946 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9947 rv = 0; /* not running */
9948 else
9949 rv = 1; /* already running */
9951 close(s);
9953 return (rv);
9957 build_socket(void)
9959 int s, len;
9960 struct sockaddr_un sa;
9962 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9963 warn("build_socket: socket");
9964 return (-1);
9967 sa.sun_family = AF_UNIX;
9968 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9969 work_dir, XT_SOCKET_FILE);
9970 len = SUN_LEN(&sa);
9972 /* connect to see if there is a listener */
9973 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9974 /* no listener so we will */
9975 unlink(sa.sun_path);
9977 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9978 warn("build_socket: bind");
9979 goto done;
9982 if (listen(s, 1) == -1) {
9983 warn("build_socket: listen");
9984 goto done;
9987 return (s);
9990 done:
9991 close(s);
9992 return (-1);
9995 gboolean
9996 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9997 GtkTreeIter *iter, struct tab *t)
9999 gchar *value;
10001 gtk_tree_model_get(model, iter, 0, &value, -1);
10002 load_uri(t, value);
10003 g_free(value);
10005 return (FALSE);
10008 gboolean
10009 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10010 GtkTreeIter *iter, struct tab *t)
10012 gchar *value;
10014 gtk_tree_model_get(model, iter, 0, &value, -1);
10015 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10016 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10017 g_free(value);
10019 return (TRUE);
10022 void
10023 completion_add_uri(const gchar *uri)
10025 GtkTreeIter iter;
10027 /* add uri to list_store */
10028 gtk_list_store_append(completion_model, &iter);
10029 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10032 gboolean
10033 completion_match(GtkEntryCompletion *completion, const gchar *key,
10034 GtkTreeIter *iter, gpointer user_data)
10036 gchar *value;
10037 gboolean match = FALSE;
10039 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10040 -1);
10042 if (value == NULL)
10043 return FALSE;
10045 match = match_uri(value, key);
10047 g_free(value);
10048 return (match);
10051 void
10052 completion_add(struct tab *t)
10054 /* enable completion for tab */
10055 t->completion = gtk_entry_completion_new();
10056 gtk_entry_completion_set_text_column(t->completion, 0);
10057 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10058 gtk_entry_completion_set_model(t->completion,
10059 GTK_TREE_MODEL(completion_model));
10060 gtk_entry_completion_set_match_func(t->completion, completion_match,
10061 NULL, NULL);
10062 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10063 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10064 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10065 G_CALLBACK(completion_select_cb), t);
10066 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10067 G_CALLBACK(completion_hover_cb), t);
10070 void
10071 xxx_dir(char *dir)
10073 struct stat sb;
10075 if (stat(dir, &sb)) {
10076 if (mkdir(dir, S_IRWXU) == -1)
10077 err(1, "mkdir %s", dir);
10078 if (stat(dir, &sb))
10079 err(1, "stat %s", dir);
10081 if (S_ISDIR(sb.st_mode) == 0)
10082 errx(1, "%s not a dir", dir);
10083 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10084 warnx("fixing invalid permissions on %s", dir);
10085 if (chmod(dir, S_IRWXU) == -1)
10086 err(1, "chmod %s", dir);
10090 void
10091 usage(void)
10093 fprintf(stderr,
10094 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10095 exit(0);
10100 main(int argc, char *argv[])
10102 struct stat sb;
10103 int c, s, optn = 0, opte = 0, focus = 1;
10104 char conf[PATH_MAX] = { '\0' };
10105 char file[PATH_MAX];
10106 char *env_proxy = NULL;
10107 char *cmd = NULL;
10108 FILE *f = NULL;
10109 struct karg a;
10110 struct sigaction sact;
10111 GIOChannel *channel;
10112 struct rlimit rlp;
10114 start_argv = argv;
10116 /* prepare gtk */
10117 gtk_init(&argc, &argv);
10119 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10121 RB_INIT(&hl);
10122 RB_INIT(&js_wl);
10123 RB_INIT(&downloads);
10125 TAILQ_INIT(&sessions);
10126 TAILQ_INIT(&tabs);
10127 TAILQ_INIT(&mtl);
10128 TAILQ_INIT(&aliases);
10129 TAILQ_INIT(&undos);
10130 TAILQ_INIT(&kbl);
10131 TAILQ_INIT(&spl);
10132 TAILQ_INIT(&chl);
10133 TAILQ_INIT(&shl);
10135 /* fiddle with ulimits */
10136 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10137 warn("getrlimit");
10138 else {
10139 /* just use them all */
10140 rlp.rlim_cur = rlp.rlim_max;
10141 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10142 warn("setrlimit");
10143 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10144 warn("getrlimit");
10145 else if (rlp.rlim_cur <= 256)
10146 startpage_add("%s requires at least 256 file "
10147 "descriptors, currently it has up to %d available",
10148 __progname, rlp.rlim_cur);
10151 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10152 switch (c) {
10153 case 'S':
10154 show_url = 0;
10155 break;
10156 case 'T':
10157 show_tabs = 0;
10158 break;
10159 case 'V':
10160 errx(0 , "Version: %s", version);
10161 break;
10162 case 'f':
10163 strlcpy(conf, optarg, sizeof(conf));
10164 break;
10165 case 's':
10166 strlcpy(named_session, optarg, sizeof(named_session));
10167 break;
10168 case 't':
10169 tabless = 1;
10170 break;
10171 case 'n':
10172 optn = 1;
10173 break;
10174 case 'e':
10175 opte = 1;
10176 break;
10177 default:
10178 usage();
10179 /* NOTREACHED */
10182 argc -= optind;
10183 argv += optind;
10185 init_keybindings();
10187 gnutls_global_init();
10189 /* generate session keys for xtp pages */
10190 generate_xtp_session_key(&dl_session_key);
10191 generate_xtp_session_key(&hl_session_key);
10192 generate_xtp_session_key(&cl_session_key);
10193 generate_xtp_session_key(&fl_session_key);
10195 /* signals */
10196 bzero(&sact, sizeof(sact));
10197 sigemptyset(&sact.sa_mask);
10198 sact.sa_handler = sigchild;
10199 sact.sa_flags = SA_NOCLDSTOP;
10200 sigaction(SIGCHLD, &sact, NULL);
10202 /* set download dir */
10203 pwd = getpwuid(getuid());
10204 if (pwd == NULL)
10205 errx(1, "invalid user %d", getuid());
10206 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10208 /* compile buffer command regexes */
10209 buffercmd_init();
10211 /* set default string settings */
10212 home = g_strdup("https://www.cyphertite.com");
10213 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10214 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10215 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10216 cmd_font_name = g_strdup("monospace normal 9");
10217 oops_font_name = g_strdup("monospace normal 9");
10218 statusbar_font_name = g_strdup("monospace normal 9");
10219 tabbar_font_name = g_strdup("monospace normal 9");
10220 statusbar_elems = g_strdup("BP");
10221 encoding = g_strdup("ISO-8859-1");
10223 /* read config file */
10224 if (strlen(conf) == 0)
10225 snprintf(conf, sizeof conf, "%s/.%s",
10226 pwd->pw_dir, XT_CONF_FILE);
10227 config_parse(conf, 0);
10229 /* init fonts */
10230 cmd_font = pango_font_description_from_string(cmd_font_name);
10231 oops_font = pango_font_description_from_string(oops_font_name);
10232 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10233 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10235 /* working directory */
10236 if (strlen(work_dir) == 0)
10237 snprintf(work_dir, sizeof work_dir, "%s/%s",
10238 pwd->pw_dir, XT_DIR);
10239 xxx_dir(work_dir);
10241 /* icon cache dir */
10242 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10243 xxx_dir(cache_dir);
10245 /* certs dir */
10246 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10247 xxx_dir(certs_dir);
10249 /* sessions dir */
10250 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10251 work_dir, XT_SESSIONS_DIR);
10252 xxx_dir(sessions_dir);
10254 /* runtime settings that can override config file */
10255 if (runtime_settings[0] != '\0')
10256 config_parse(runtime_settings, 1);
10258 /* download dir */
10259 if (!strcmp(download_dir, pwd->pw_dir))
10260 strlcat(download_dir, "/downloads", sizeof download_dir);
10261 xxx_dir(download_dir);
10263 /* favorites file */
10264 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10265 if (stat(file, &sb)) {
10266 warnx("favorites file doesn't exist, creating it");
10267 if ((f = fopen(file, "w")) == NULL)
10268 err(1, "favorites");
10269 fclose(f);
10272 /* quickmarks file */
10273 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10274 if (stat(file, &sb)) {
10275 warnx("quickmarks file doesn't exist, creating it");
10276 if ((f = fopen(file, "w")) == NULL)
10277 err(1, "quickmarks");
10278 fclose(f);
10281 /* search history */
10282 if (history_autosave) {
10283 snprintf(search_file, sizeof search_file, "%s/%s",
10284 work_dir, XT_SEARCH_FILE);
10285 if (stat(search_file, &sb)) {
10286 warnx("search history file doesn't exist, creating it");
10287 if ((f = fopen(search_file, "w")) == NULL)
10288 err(1, "search_history");
10289 fclose(f);
10291 history_read(&shl, search_file, &search_history_count);
10294 /* command history */
10295 if (history_autosave) {
10296 snprintf(command_file, sizeof command_file, "%s/%s",
10297 work_dir, XT_COMMAND_FILE);
10298 if (stat(command_file, &sb)) {
10299 warnx("command history file doesn't exist, creating it");
10300 if ((f = fopen(command_file, "w")) == NULL)
10301 err(1, "command_history");
10302 fclose(f);
10304 history_read(&chl, command_file, &cmd_history_count);
10307 /* cookies */
10308 session = webkit_get_default_session();
10309 setup_cookies();
10311 /* certs */
10312 if (ssl_ca_file) {
10313 if (stat(ssl_ca_file, &sb)) {
10314 warnx("no CA file: %s", ssl_ca_file);
10315 g_free(ssl_ca_file);
10316 ssl_ca_file = NULL;
10317 } else
10318 g_object_set(session,
10319 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10320 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10321 (void *)NULL);
10324 /* set default encoding */
10325 g_object_set(session, "default-encoding", encoding, (char *)NULL);
10327 /* guess_search regex */
10328 if (url_regex == NULL)
10329 url_regex = g_strdup(XT_URL_REGEX);
10330 if (url_regex)
10331 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10332 startpage_add("invalid url regex %s", url_regex);
10334 /* proxy */
10335 env_proxy = getenv("http_proxy");
10336 if (env_proxy)
10337 setup_proxy(env_proxy);
10338 else
10339 setup_proxy(http_proxy);
10341 if (opte) {
10342 send_cmd_to_socket(argv[0]);
10343 exit(0);
10346 /* set some connection parameters */
10347 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10348 g_object_set(session, "max-conns-per-host", max_host_connections,
10349 (char *)NULL);
10351 /* see if there is already an xxxterm running */
10352 if (single_instance && is_running()) {
10353 optn = 1;
10354 warnx("already running");
10357 if (optn) {
10358 while (argc) {
10359 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10360 send_cmd_to_socket(cmd);
10361 if (cmd)
10362 g_free(cmd);
10364 argc--;
10365 argv++;
10367 exit(0);
10370 /* uri completion */
10371 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10373 /* buffers */
10374 buffers_store = gtk_list_store_new
10375 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10377 qmarks_load();
10379 /* go graphical */
10380 create_canvas();
10381 notebook_tab_set_visibility();
10383 if (save_global_history)
10384 restore_global_history();
10386 /* restore session list */
10387 restore_sessions_list();
10389 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10390 restore_saved_tabs();
10391 else {
10392 a.s = named_session;
10393 a.i = XT_SES_DONOTHING;
10394 open_tabs(NULL, &a);
10397 /* see if we have an exception */
10398 if (!TAILQ_EMPTY(&spl)) {
10399 create_new_tab("about:startpage", NULL, focus, -1);
10400 focus = 0;
10403 while (argc) {
10404 create_new_tab(argv[0], NULL, focus, -1);
10405 focus = 0;
10407 argc--;
10408 argv++;
10411 if (TAILQ_EMPTY(&tabs))
10412 create_new_tab(home, NULL, 1, -1);
10414 if (enable_socket)
10415 if ((s = build_socket()) != -1) {
10416 channel = g_io_channel_unix_new(s);
10417 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10420 gtk_main();
10422 gnutls_global_deinit();
10424 if (url_regex)
10425 regfree(&url_re);
10427 return (0);