make sure anchors in sessions don't get killed when loaded.
[xxxterm.git] / xxxterm.c
blobc9a4780d98260c2fe5db9b15f1b4e4d9444562aa
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pthread.h>
35 #include <pwd.h>
36 #include <regex.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <dirent.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
50 #include <libutil.h>
51 #include "freebsd/util.h"
52 #include <sys/tree.h>
53 #else /* OpenBSD */
54 #include <util.h>
55 #include <sys/tree.h>
56 #endif
57 #include <sys/queue.h>
58 #include <sys/resource.h>
59 #include <sys/socket.h>
60 #include <sys/stat.h>
61 #include <sys/time.h>
62 #include <sys/un.h>
64 #include <gtk/gtk.h>
65 #include <gdk/gdkkeysyms.h>
67 #if GTK_CHECK_VERSION(3,0,0)
68 /* we still use GDK_* instead of GDK_KEY_* */
69 #include <gdk/gdkkeysyms-compat.h>
70 #endif
72 #include <webkit/webkit.h>
73 #include <libsoup/soup.h>
74 #include <gnutls/gnutls.h>
75 #include <JavaScriptCore/JavaScript.h>
76 #include <gnutls/x509.h>
78 #include "javascript.h"
81 javascript.h borrowed from vimprobable2 under the following license:
83 Copyright (c) 2009 Leon Winter
84 Copyright (c) 2009 Hannes Schueller
85 Copyright (c) 2009 Matto Fransen
87 Permission is hereby granted, free of charge, to any person obtaining a copy
88 of this software and associated documentation files (the "Software"), to deal
89 in the Software without restriction, including without limitation the rights
90 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
91 copies of the Software, and to permit persons to whom the Software is
92 furnished to do so, subject to the following conditions:
94 The above copyright notice and this permission notice shall be included in
95 all copies or substantial portions of the Software.
97 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
98 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
99 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
100 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
101 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
102 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
103 THE SOFTWARE.
106 static char *version = "$xxxterm$";
108 /* hooked functions */
109 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
110 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
111 SoupCookie *);
113 /*#define XT_DEBUG*/
114 #ifdef XT_DEBUG
115 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
116 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
117 #define XT_D_MOVE 0x0001
118 #define XT_D_KEY 0x0002
119 #define XT_D_TAB 0x0004
120 #define XT_D_URL 0x0008
121 #define XT_D_CMD 0x0010
122 #define XT_D_NAV 0x0020
123 #define XT_D_DOWNLOAD 0x0040
124 #define XT_D_CONFIG 0x0080
125 #define XT_D_JS 0x0100
126 #define XT_D_FAVORITE 0x0200
127 #define XT_D_PRINTING 0x0400
128 #define XT_D_COOKIE 0x0800
129 #define XT_D_KEYBINDING 0x1000
130 #define XT_D_CLIP 0x2000
131 #define XT_D_BUFFERCMD 0x4000
132 u_int32_t swm_debug = 0
133 | XT_D_MOVE
134 | XT_D_KEY
135 | XT_D_TAB
136 | XT_D_URL
137 | XT_D_CMD
138 | XT_D_NAV
139 | XT_D_DOWNLOAD
140 | XT_D_CONFIG
141 | XT_D_JS
142 | XT_D_FAVORITE
143 | XT_D_PRINTING
144 | XT_D_COOKIE
145 | XT_D_KEYBINDING
146 | XT_D_CLIP
147 | XT_D_BUFFERCMD
149 #else
150 #define DPRINTF(x...)
151 #define DNPRINTF(n,x...)
152 #endif
154 #define LENGTH(x) (sizeof x / sizeof x[0])
155 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
156 ~(GDK_BUTTON1_MASK) & \
157 ~(GDK_BUTTON2_MASK) & \
158 ~(GDK_BUTTON3_MASK) & \
159 ~(GDK_BUTTON4_MASK) & \
160 ~(GDK_BUTTON5_MASK))
162 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
164 char *icons[] = {
165 "xxxtermicon16.png",
166 "xxxtermicon32.png",
167 "xxxtermicon48.png",
168 "xxxtermicon64.png",
169 "xxxtermicon128.png"
172 struct tab {
173 TAILQ_ENTRY(tab) entry;
174 GtkWidget *vbox;
175 GtkWidget *tab_content;
176 struct {
177 GtkWidget *label;
178 GtkWidget *eventbox;
179 GtkWidget *box;
180 GtkWidget *sep;
181 } tab_elems;
182 GtkWidget *label;
183 GtkWidget *spinner;
184 GtkWidget *uri_entry;
185 GtkWidget *search_entry;
186 GtkWidget *toolbar;
187 GtkWidget *browser_win;
188 GtkWidget *statusbar_box;
189 struct {
190 GtkWidget *statusbar;
191 GtkWidget *buffercmd;
192 GtkWidget *zoom;
193 GtkWidget *position;
194 } sbe;
195 GtkWidget *cmd;
196 GtkWidget *buffers;
197 GtkWidget *oops;
198 GtkWidget *backward;
199 GtkWidget *forward;
200 GtkWidget *stop;
201 GtkWidget *gohome;
202 GtkWidget *js_toggle;
203 GtkEntryCompletion *completion;
204 guint tab_id;
205 WebKitWebView *wv;
207 WebKitWebHistoryItem *item;
208 WebKitWebBackForwardList *bfl;
210 /* favicon */
211 WebKitNetworkRequest *icon_request;
212 WebKitDownload *icon_download;
213 gchar *icon_dest_uri;
215 /* adjustments for browser */
216 GtkScrollbar *sb_h;
217 GtkScrollbar *sb_v;
218 GtkAdjustment *adjust_h;
219 GtkAdjustment *adjust_v;
221 /* flags */
222 int focus_wv;
223 int ctrl_click;
224 gchar *status;
225 int xtp_meaning; /* identifies dls/favorites */
226 gchar *tmp_uri;
227 int popup; /* 1 if cmd_entry has popup visible */
229 /* hints */
230 int hints_on;
231 int hint_mode;
232 #define XT_HINT_NONE (0)
233 #define XT_HINT_NUMERICAL (1)
234 #define XT_HINT_ALPHANUM (2)
235 char hint_buf[128];
236 char hint_num[128];
238 /* custom stylesheet */
239 int styled;
240 char *stylesheet;
242 /* search */
243 char *search_text;
244 int search_forward;
245 guint search_id;
247 /* settings */
248 WebKitWebSettings *settings;
249 gchar *user_agent;
251 /* marks */
252 double mark[XT_NOMARKS];
254 TAILQ_HEAD(tab_list, tab);
256 struct history {
257 RB_ENTRY(history) entry;
258 const gchar *uri;
259 const gchar *title;
261 RB_HEAD(history_list, history);
263 struct session {
264 TAILQ_ENTRY(session) entry;
265 const gchar *name;
267 TAILQ_HEAD(session_list, session);
269 struct download {
270 RB_ENTRY(download) entry;
271 int id;
272 WebKitDownload *download;
273 struct tab *tab;
275 RB_HEAD(download_list, download);
277 struct domain {
278 RB_ENTRY(domain) entry;
279 gchar *d;
280 int handy; /* app use */
282 RB_HEAD(domain_list, domain);
284 struct undo {
285 TAILQ_ENTRY(undo) entry;
286 gchar *uri;
287 GList *history;
288 int back; /* Keeps track of how many back
289 * history items there are. */
291 TAILQ_HEAD(undo_tailq, undo);
293 struct sp {
294 char *line;
295 TAILQ_ENTRY(sp) entry;
297 TAILQ_HEAD(sp_list, sp);
299 struct command_entry {
300 char *line;
301 TAILQ_ENTRY(command_entry) entry;
303 TAILQ_HEAD(command_list, command_entry);
305 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
306 int next_download_id = 1;
308 struct karg {
309 int i;
310 char *s;
311 int precount;
314 /* defines */
315 #define XT_NAME ("XXXTerm")
316 #define XT_DIR (".xxxterm")
317 #define XT_CACHE_DIR ("cache")
318 #define XT_CERT_DIR ("certs/")
319 #define XT_SESSIONS_DIR ("sessions/")
320 #define XT_CONF_FILE ("xxxterm.conf")
321 #define XT_FAVS_FILE ("favorites")
322 #define XT_QMARKS_FILE ("quickmarks")
323 #define XT_SAVED_TABS_FILE ("main_session")
324 #define XT_RESTART_TABS_FILE ("restart_tabs")
325 #define XT_SOCKET_FILE ("socket")
326 #define XT_HISTORY_FILE ("history")
327 #define XT_REJECT_FILE ("rejected.txt")
328 #define XT_COOKIE_FILE ("cookies.txt")
329 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
330 #define XT_SEARCH_FILE ("search_history")
331 #define XT_COMMAND_FILE ("command_history")
332 #define XT_CB_HANDLED (TRUE)
333 #define XT_CB_PASSTHROUGH (FALSE)
334 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
335 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
336 #define XT_DLMAN_REFRESH "10"
337 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
338 "td{overflow: hidden;" \
339 " padding: 2px 2px 2px 2px;" \
340 " border: 1px solid black;" \
341 " vertical-align:top;" \
342 " word-wrap: break-word}\n" \
343 "tr:hover{background: #ffff99}\n" \
344 "th{background-color: #cccccc;" \
345 " border: 1px solid black}\n" \
346 "table{width: 100%%;" \
347 " border: 1px black solid;" \
348 " border-collapse:collapse}\n" \
349 ".progress-outer{" \
350 "border: 1px solid black;" \
351 " height: 8px;" \
352 " width: 90%%}\n" \
353 ".progress-inner{float: left;" \
354 " height: 8px;" \
355 " background: green}\n" \
356 ".dlstatus{font-size: small;" \
357 " text-align: center}\n" \
358 "</style>\n"
359 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
360 #define XT_MAX_UNDO_CLOSE_TAB (32)
361 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
362 #define XT_PRINT_EXTRA_MARGIN 10
363 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
364 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
366 /* colors */
367 #define XT_COLOR_RED "#cc0000"
368 #define XT_COLOR_YELLOW "#ffff66"
369 #define XT_COLOR_BLUE "lightblue"
370 #define XT_COLOR_GREEN "#99ff66"
371 #define XT_COLOR_WHITE "white"
372 #define XT_COLOR_BLACK "black"
374 #define XT_COLOR_CT_BACKGROUND "#000000"
375 #define XT_COLOR_CT_INACTIVE "#dddddd"
376 #define XT_COLOR_CT_ACTIVE "#bbbb00"
377 #define XT_COLOR_CT_SEPARATOR "#555555"
379 #define XT_COLOR_SB_SEPARATOR "#555555"
381 #define XT_PROTO_DELIM "://"
384 * xxxterm "protocol" (xtp)
385 * We use this for managing stuff like downloads and favorites. They
386 * make magical HTML pages in memory which have xxxt:// links in order
387 * to communicate with xxxterm's internals. These links take the format:
388 * xxxt://class/session_key/action/arg
390 * Don't begin xtp class/actions as 0. atoi returns that on error.
392 * Typically we have not put addition of items in this framework, as
393 * adding items is either done via an ex-command or via a keybinding instead.
396 #define XT_XTP_STR "xxxt://"
398 /* XTP classes (xxxt://<class>) */
399 #define XT_XTP_INVALID 0 /* invalid */
400 #define XT_XTP_DL 1 /* downloads */
401 #define XT_XTP_HL 2 /* history */
402 #define XT_XTP_CL 3 /* cookies */
403 #define XT_XTP_FL 4 /* favorites */
405 /* XTP download actions */
406 #define XT_XTP_DL_LIST 1
407 #define XT_XTP_DL_CANCEL 2
408 #define XT_XTP_DL_REMOVE 3
410 /* XTP history actions */
411 #define XT_XTP_HL_LIST 1
412 #define XT_XTP_HL_REMOVE 2
414 /* XTP cookie actions */
415 #define XT_XTP_CL_LIST 1
416 #define XT_XTP_CL_REMOVE 2
418 /* XTP cookie actions */
419 #define XT_XTP_FL_LIST 1
420 #define XT_XTP_FL_REMOVE 2
422 /* actions */
423 #define XT_MOVE_INVALID (0)
424 #define XT_MOVE_DOWN (1)
425 #define XT_MOVE_UP (2)
426 #define XT_MOVE_BOTTOM (3)
427 #define XT_MOVE_TOP (4)
428 #define XT_MOVE_PAGEDOWN (5)
429 #define XT_MOVE_PAGEUP (6)
430 #define XT_MOVE_HALFDOWN (7)
431 #define XT_MOVE_HALFUP (8)
432 #define XT_MOVE_LEFT (9)
433 #define XT_MOVE_FARLEFT (10)
434 #define XT_MOVE_RIGHT (11)
435 #define XT_MOVE_FARRIGHT (12)
436 #define XT_MOVE_PERCENT (13)
438 #define XT_QMARK_SET (0)
439 #define XT_QMARK_OPEN (1)
440 #define XT_QMARK_TAB (2)
442 #define XT_MARK_SET (0)
443 #define XT_MARK_GOTO (1)
445 #define XT_TAB_LAST (-4)
446 #define XT_TAB_FIRST (-3)
447 #define XT_TAB_PREV (-2)
448 #define XT_TAB_NEXT (-1)
449 #define XT_TAB_INVALID (0)
450 #define XT_TAB_NEW (1)
451 #define XT_TAB_DELETE (2)
452 #define XT_TAB_DELQUIT (3)
453 #define XT_TAB_OPEN (4)
454 #define XT_TAB_UNDO_CLOSE (5)
455 #define XT_TAB_SHOW (6)
456 #define XT_TAB_HIDE (7)
457 #define XT_TAB_NEXTSTYLE (8)
459 #define XT_NAV_INVALID (0)
460 #define XT_NAV_BACK (1)
461 #define XT_NAV_FORWARD (2)
462 #define XT_NAV_RELOAD (3)
464 #define XT_FOCUS_INVALID (0)
465 #define XT_FOCUS_URI (1)
466 #define XT_FOCUS_SEARCH (2)
468 #define XT_SEARCH_INVALID (0)
469 #define XT_SEARCH_NEXT (1)
470 #define XT_SEARCH_PREV (2)
472 #define XT_PASTE_CURRENT_TAB (0)
473 #define XT_PASTE_NEW_TAB (1)
475 #define XT_ZOOM_IN (-1)
476 #define XT_ZOOM_OUT (-2)
477 #define XT_ZOOM_NORMAL (100)
479 #define XT_URL_SHOW (1)
480 #define XT_URL_HIDE (2)
482 #define XT_WL_TOGGLE (1<<0)
483 #define XT_WL_ENABLE (1<<1)
484 #define XT_WL_DISABLE (1<<2)
485 #define XT_WL_FQDN (1<<3) /* default */
486 #define XT_WL_TOPLEVEL (1<<4)
487 #define XT_WL_PERSISTENT (1<<5)
488 #define XT_WL_SESSION (1<<6)
489 #define XT_WL_RELOAD (1<<7)
491 #define XT_SHOW (1<<7)
492 #define XT_DELETE (1<<8)
493 #define XT_SAVE (1<<9)
494 #define XT_OPEN (1<<10)
496 #define XT_CMD_OPEN (0)
497 #define XT_CMD_OPEN_CURRENT (1)
498 #define XT_CMD_TABNEW (2)
499 #define XT_CMD_TABNEW_CURRENT (3)
501 #define XT_STATUS_NOTHING (0)
502 #define XT_STATUS_LINK (1)
503 #define XT_STATUS_URI (2)
504 #define XT_STATUS_LOADING (3)
506 #define XT_SES_DONOTHING (0)
507 #define XT_SES_CLOSETABS (1)
509 #define XT_BM_NORMAL (0)
510 #define XT_BM_WHITELIST (1)
511 #define XT_BM_KIOSK (2)
513 #define XT_PREFIX (1<<0)
514 #define XT_USERARG (1<<1)
515 #define XT_URLARG (1<<2)
516 #define XT_INTARG (1<<3)
517 #define XT_SESSARG (1<<4)
519 #define XT_TABS_NORMAL 0
520 #define XT_TABS_COMPACT 1
522 #define XT_BUFCMD_SZ (8)
524 /* mime types */
525 struct mime_type {
526 char *mt_type;
527 char *mt_action;
528 int mt_default;
529 int mt_download;
530 TAILQ_ENTRY(mime_type) entry;
532 TAILQ_HEAD(mime_type_list, mime_type);
534 /* uri aliases */
535 struct alias {
536 char *a_name;
537 char *a_uri;
538 TAILQ_ENTRY(alias) entry;
540 TAILQ_HEAD(alias_list, alias);
542 /* settings that require restart */
543 int tabless = 0; /* allow only 1 tab */
544 int enable_socket = 0;
545 int single_instance = 0; /* only allow one xxxterm to run */
546 int fancy_bar = 1; /* fancy toolbar */
547 int browser_mode = XT_BM_NORMAL;
548 int enable_localstorage = 1;
549 char *statusbar_elems = NULL;
551 /* runtime settings */
552 int show_tabs = 1; /* show tabs on notebook */
553 int tab_style = XT_TABS_NORMAL; /* tab bar style */
554 int show_url = 1; /* show url toolbar on notebook */
555 int show_statusbar = 0; /* vimperator style status bar */
556 int ctrl_click_focus = 0; /* ctrl click gets focus */
557 int cookies_enabled = 1; /* enable cookies */
558 int read_only_cookies = 0; /* enable to not write cookies */
559 int enable_scripts = 1;
560 int enable_plugins = 0;
561 gfloat default_zoom_level = 1.0;
562 char default_script[PATH_MAX];
563 int window_height = 768;
564 int window_width = 1024;
565 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
566 int refresh_interval = 10; /* download refresh interval */
567 int enable_cookie_whitelist = 0;
568 int enable_js_whitelist = 0;
569 int session_timeout = 3600; /* cookie session timeout */
570 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
571 char *ssl_ca_file = NULL;
572 char *resource_dir = NULL;
573 gboolean ssl_strict_certs = FALSE;
574 int append_next = 1; /* append tab after current tab */
575 char *home = NULL;
576 char *search_string = NULL;
577 char *http_proxy = NULL;
578 char download_dir[PATH_MAX];
579 char runtime_settings[PATH_MAX]; /* override of settings */
580 int allow_volatile_cookies = 0;
581 int save_global_history = 0; /* save global history to disk */
582 char *user_agent = NULL;
583 int save_rejected_cookies = 0;
584 int session_autosave = 0;
585 int guess_search = 0;
586 int dns_prefetch = FALSE;
587 gint max_connections = 25;
588 gint max_host_connections = 5;
589 gint enable_spell_checking = 0;
590 char *spell_check_languages = NULL;
591 int xterm_workaround = 0;
592 char *url_regex = NULL;
593 int history_autosave = 0;
594 char search_file[PATH_MAX];
595 char command_file[PATH_MAX];
597 char *cmd_font_name = NULL;
598 char *oops_font_name = NULL;
599 char *statusbar_font_name = NULL;
600 char *tabbar_font_name = NULL;
601 PangoFontDescription *cmd_font;
602 PangoFontDescription *oops_font;
603 PangoFontDescription *statusbar_font;
604 PangoFontDescription *tabbar_font;
605 char *qmarks[XT_NOMARKS];
607 int btn_down; /* M1 down in any wv */
608 regex_t url_re; /* guess_search regex */
610 struct settings;
611 struct key_binding;
612 int set_browser_mode(struct settings *, char *);
613 int set_cookie_policy(struct settings *, char *);
614 int set_download_dir(struct settings *, char *);
615 int set_default_script(struct settings *, char *);
616 int set_runtime_dir(struct settings *, char *);
617 int set_tab_style(struct settings *, char *);
618 int set_work_dir(struct settings *, char *);
619 int add_alias(struct settings *, char *);
620 int add_mime_type(struct settings *, char *);
621 int add_cookie_wl(struct settings *, char *);
622 int add_js_wl(struct settings *, char *);
623 int add_kb(struct settings *, char *);
624 void button_set_stockid(GtkWidget *, char *);
625 GtkWidget * create_button(char *, char *, int);
627 char *get_browser_mode(struct settings *);
628 char *get_cookie_policy(struct settings *);
629 char *get_download_dir(struct settings *);
630 char *get_default_script(struct settings *);
631 char *get_runtime_dir(struct settings *);
632 char *get_tab_style(struct settings *);
633 char *get_work_dir(struct settings *);
634 void startpage_add(const char *, ...);
636 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
637 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
638 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
639 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
640 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
642 void recalc_tabs(void);
643 void recolor_compact_tabs(void);
644 void set_current_tab(int page_num);
645 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
646 void marks_clear(struct tab *t);
648 int set_http_proxy(char *);
650 struct special {
651 int (*set)(struct settings *, char *);
652 char *(*get)(struct settings *);
653 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
656 struct special s_browser_mode = {
657 set_browser_mode,
658 get_browser_mode,
659 NULL
662 struct special s_cookie = {
663 set_cookie_policy,
664 get_cookie_policy,
665 NULL
668 struct special s_alias = {
669 add_alias,
670 NULL,
671 walk_alias
674 struct special s_mime = {
675 add_mime_type,
676 NULL,
677 walk_mime_type
680 struct special s_js = {
681 add_js_wl,
682 NULL,
683 walk_js_wl
686 struct special s_kb = {
687 add_kb,
688 NULL,
689 walk_kb
692 struct special s_cookie_wl = {
693 add_cookie_wl,
694 NULL,
695 walk_cookie_wl
698 struct special s_default_script = {
699 set_default_script,
700 get_default_script,
701 NULL
704 struct special s_download_dir = {
705 set_download_dir,
706 get_download_dir,
707 NULL
710 struct special s_work_dir = {
711 set_work_dir,
712 get_work_dir,
713 NULL
716 struct special s_tab_style = {
717 set_tab_style,
718 get_tab_style,
719 NULL
722 struct settings {
723 char *name;
724 int type;
725 #define XT_S_INVALID (0)
726 #define XT_S_INT (1)
727 #define XT_S_STR (2)
728 #define XT_S_FLOAT (3)
729 uint32_t flags;
730 #define XT_SF_RESTART (1<<0)
731 #define XT_SF_RUNTIME (1<<1)
732 int *ival;
733 char **sval;
734 struct special *s;
735 gfloat *fval;
736 int (*activate)(char *);
737 } rs[] = {
738 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
739 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
740 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
741 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
742 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
743 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
744 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
745 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
746 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
747 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
748 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
749 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
750 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
751 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
752 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
753 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
754 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
755 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
756 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
757 { "home", XT_S_STR, 0, NULL, &home, NULL },
758 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
759 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
760 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
761 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
762 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
763 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
764 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
765 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
766 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
767 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
768 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
769 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
770 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
771 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
772 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
773 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
774 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
775 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
776 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
777 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
778 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
779 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
780 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
781 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
782 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
783 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
784 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
786 /* font settings */
787 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
788 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
789 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
790 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
792 /* runtime settings */
793 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
794 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
795 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
796 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
797 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
800 int about(struct tab *, struct karg *);
801 int blank(struct tab *, struct karg *);
802 int ca_cmd(struct tab *, struct karg *);
803 int cookie_show_wl(struct tab *, struct karg *);
804 int js_show_wl(struct tab *, struct karg *);
805 int help(struct tab *, struct karg *);
806 int set(struct tab *, struct karg *);
807 int stats(struct tab *, struct karg *);
808 int marco(struct tab *, struct karg *);
809 int startpage(struct tab *, struct karg *);
810 const char * marco_message(int *);
811 int xtp_page_cl(struct tab *, struct karg *);
812 int xtp_page_dl(struct tab *, struct karg *);
813 int xtp_page_fl(struct tab *, struct karg *);
814 int xtp_page_hl(struct tab *, struct karg *);
815 void xt_icon_from_file(struct tab *, char *);
816 const gchar *get_uri(struct tab *);
817 const gchar *get_title(struct tab *, bool);
819 #define XT_URI_ABOUT ("about:")
820 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
821 #define XT_URI_ABOUT_ABOUT ("about")
822 #define XT_URI_ABOUT_BLANK ("blank")
823 #define XT_URI_ABOUT_CERTS ("certs")
824 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
825 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
826 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
827 #define XT_URI_ABOUT_FAVORITES ("favorites")
828 #define XT_URI_ABOUT_HELP ("help")
829 #define XT_URI_ABOUT_HISTORY ("history")
830 #define XT_URI_ABOUT_JSWL ("jswl")
831 #define XT_URI_ABOUT_SET ("set")
832 #define XT_URI_ABOUT_STATS ("stats")
833 #define XT_URI_ABOUT_MARCO ("marco")
834 #define XT_URI_ABOUT_STARTPAGE ("startpage")
836 struct about_type {
837 char *name;
838 int (*func)(struct tab *, struct karg *);
839 } about_list[] = {
840 { XT_URI_ABOUT_ABOUT, about },
841 { XT_URI_ABOUT_BLANK, blank },
842 { XT_URI_ABOUT_CERTS, ca_cmd },
843 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
844 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
845 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
846 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
847 { XT_URI_ABOUT_HELP, help },
848 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
849 { XT_URI_ABOUT_JSWL, js_show_wl },
850 { XT_URI_ABOUT_SET, set },
851 { XT_URI_ABOUT_STATS, stats },
852 { XT_URI_ABOUT_MARCO, marco },
853 { XT_URI_ABOUT_STARTPAGE, startpage },
856 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
857 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
858 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
859 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
860 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
861 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
862 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
864 /* globals */
865 extern char *__progname;
866 char **start_argv;
867 struct passwd *pwd;
868 GtkWidget *main_window;
869 GtkNotebook *notebook;
870 GtkWidget *tab_bar;
871 GtkWidget *arrow, *abtn;
872 struct tab_list tabs;
873 struct history_list hl;
874 struct session_list sessions;
875 struct download_list downloads;
876 struct domain_list c_wl;
877 struct domain_list js_wl;
878 struct undo_tailq undos;
879 struct keybinding_list kbl;
880 struct sp_list spl;
881 struct command_list chl;
882 struct command_list shl;
883 struct command_entry *history_at;
884 struct command_entry *search_at;
885 int undo_count;
886 int updating_dl_tabs = 0;
887 int updating_hl_tabs = 0;
888 int updating_cl_tabs = 0;
889 int updating_fl_tabs = 0;
890 int cmd_history_count = 0;
891 int search_history_count = 0;
892 char *global_search;
893 uint64_t blocked_cookies = 0;
894 char named_session[PATH_MAX];
895 GtkListStore *completion_model;
896 GtkListStore *buffers_store;
898 void xxx_dir(char *);
899 int icon_size_map(int);
900 void completion_add(struct tab *);
901 void completion_add_uri(const gchar *);
902 void show_oops(struct tab *, const char *, ...);
904 void
905 history_delete(struct command_list *l, int *counter)
907 struct command_entry *c;
909 if (l == NULL || counter == NULL)
910 return;
912 c = TAILQ_LAST(l, command_list);
913 if (c == NULL)
914 return;
916 TAILQ_REMOVE(l, c, entry);
917 *counter -= 1;
918 g_free(c->line);
919 g_free(c);
922 void
923 history_add(struct command_list *list, char *file, char *l, int *counter)
925 struct command_entry *c;
926 FILE *f;
928 if (list == NULL || l == NULL || counter == NULL)
929 return;
931 /* don't add the same line */
932 c = TAILQ_FIRST(list);
933 if (c)
934 if (!strcmp(c->line + 1 /* skip space */, l))
935 return;
937 c = g_malloc0(sizeof *c);
938 c->line = g_strdup_printf(" %s", l);
940 *counter += 1;
941 TAILQ_INSERT_HEAD(list, c, entry);
943 if (*counter > 1000)
944 history_delete(list, counter);
946 if (history_autosave && file) {
947 f = fopen(file, "w");
948 if (f == NULL) {
949 show_oops(NULL, "couldn't write history %s", file);
950 return;
953 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
954 c->line[0] = ' ';
955 fprintf(f, "%s\n", c->line);
958 fclose(f);
963 history_read(struct command_list *list, char *file, int *counter)
965 FILE *f;
966 char *s, line[65536];
968 if (list == NULL || file == NULL)
969 return (1);
971 f = fopen(file, "r");
972 if (f == NULL) {
973 startpage_add("couldn't open history file %s", file);
974 return (1);
977 for (;;) {
978 s = fgets(line, sizeof line, f);
979 if (s == NULL || feof(f) || ferror(f))
980 break;
981 if ((s = strchr(line, '\n')) == NULL) {
982 startpage_add("invalid history file %s", file);
983 fclose(f);
984 return (1);
986 *s = '\0';
988 history_add(list, NULL, line + 1, counter);
991 fclose(f);
993 return (0);
996 /* marks and quickmarks array storage.
997 * first a-z, then A-Z, then 0-9 */
998 char
999 indextomark(int i)
1001 if (i < 0)
1002 return 0;
1004 if (i >= 0 && i <= 'z' - 'a')
1005 return 'a' + i;
1007 i -= 'z' - 'a' + 1;
1008 if (i >= 0 && i <= 'Z' - 'A')
1009 return 'A' + i;
1011 i -= 'Z' - 'A' + 1;
1012 if (i >= 10)
1013 return 0;
1015 return i + '0';
1019 marktoindex(char m)
1021 int ret = 0;
1023 if (m >= 'a' && m <= 'z')
1024 return ret + m - 'a';
1026 ret += 'z' - 'a' + 1;
1027 if (m >= 'A' && m <= 'Z')
1028 return ret + m - 'A';
1030 ret += 'Z' - 'A' + 1;
1031 if (m >= '0' && m <= '9')
1032 return ret + m - '0';
1034 return -1;
1038 void
1039 sigchild(int sig)
1041 int saved_errno, status;
1042 pid_t pid;
1044 saved_errno = errno;
1046 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1047 if (pid == -1) {
1048 if (errno == EINTR)
1049 continue;
1050 if (errno != ECHILD) {
1052 clog_warn("sigchild: waitpid:");
1055 break;
1058 if (WIFEXITED(status)) {
1059 if (WEXITSTATUS(status) != 0) {
1061 clog_warnx("sigchild: child exit status: %d",
1062 WEXITSTATUS(status));
1065 } else {
1067 clog_warnx("sigchild: child is terminated abnormally");
1072 errno = saved_errno;
1076 is_g_object_setting(GObject *o, char *str)
1078 guint n_props = 0, i;
1079 GParamSpec **proplist;
1081 if (! G_IS_OBJECT(o))
1082 return (0);
1084 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1085 &n_props);
1087 for (i=0; i < n_props; i++) {
1088 if (! strcmp(proplist[i]->name, str))
1089 return (1);
1091 return (0);
1094 gchar *
1095 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1097 gchar *r;
1099 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1100 "<head>\n"
1101 "<title>%s</title>\n"
1102 "%s"
1103 "%s"
1104 "</head>\n"
1105 "<body>\n"
1106 "<h1>%s</h1>\n"
1107 "%s\n</body>\n"
1108 "</html>",
1109 title,
1110 addstyles ? XT_PAGE_STYLE : "",
1111 head,
1112 title,
1113 body);
1115 return r;
1119 * Display a web page from a HTML string in memory, rather than from a URL
1121 void
1122 load_webkit_string(struct tab *t, const char *str, gchar *title)
1124 char file[PATH_MAX];
1125 int i;
1127 /* we set this to indicate we want to manually do navaction */
1128 if (t->bfl)
1129 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1131 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1132 if (title) {
1133 /* set t->xtp_meaning */
1134 for (i = 0; i < LENGTH(about_list); i++)
1135 if (!strcmp(title, about_list[i].name)) {
1136 t->xtp_meaning = i;
1137 break;
1140 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1141 #if GTK_CHECK_VERSION(2, 20, 0)
1142 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1143 gtk_widget_hide(t->spinner);
1144 #endif
1145 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1146 xt_icon_from_file(t, file);
1150 struct tab *
1151 get_current_tab(void)
1153 struct tab *t;
1155 TAILQ_FOREACH(t, &tabs, entry) {
1156 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1157 return (t);
1160 warnx("%s: no current tab", __func__);
1162 return (NULL);
1165 void
1166 set_status(struct tab *t, gchar *s, int status)
1168 gchar *type = NULL;
1170 if (s == NULL)
1171 return;
1173 switch (status) {
1174 case XT_STATUS_LOADING:
1175 type = g_strdup_printf("Loading: %s", s);
1176 s = type;
1177 break;
1178 case XT_STATUS_LINK:
1179 type = g_strdup_printf("Link: %s", s);
1180 if (!t->status)
1181 t->status = g_strdup(gtk_entry_get_text(
1182 GTK_ENTRY(t->sbe.statusbar)));
1183 s = type;
1184 break;
1185 case XT_STATUS_URI:
1186 type = g_strdup_printf("%s", s);
1187 if (!t->status) {
1188 t->status = g_strdup(type);
1190 s = type;
1191 if (!t->status)
1192 t->status = g_strdup(s);
1193 break;
1194 case XT_STATUS_NOTHING:
1195 /* FALL THROUGH */
1196 default:
1197 break;
1199 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1200 if (type)
1201 g_free(type);
1204 void
1205 hide_cmd(struct tab *t)
1207 history_at = NULL; /* just in case */
1208 search_at = NULL; /* just in case */
1209 gtk_widget_hide(t->cmd);
1212 void
1213 show_cmd(struct tab *t)
1215 history_at = NULL;
1216 search_at = NULL;
1217 gtk_widget_hide(t->oops);
1218 gtk_widget_show(t->cmd);
1221 void
1222 hide_buffers(struct tab *t)
1224 gtk_widget_hide(t->buffers);
1225 gtk_list_store_clear(buffers_store);
1228 enum {
1229 COL_ID = 0,
1230 COL_TITLE,
1231 NUM_COLS
1235 sort_tabs_by_page_num(struct tab ***stabs)
1237 int num_tabs = 0;
1238 struct tab *t;
1240 num_tabs = gtk_notebook_get_n_pages(notebook);
1242 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1244 TAILQ_FOREACH(t, &tabs, entry)
1245 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1247 return (num_tabs);
1250 void
1251 buffers_make_list(void)
1253 int i, num_tabs;
1254 const gchar *title = NULL;
1255 GtkTreeIter iter;
1256 struct tab **stabs = NULL;
1258 num_tabs = sort_tabs_by_page_num(&stabs);
1260 for (i = 0; i < num_tabs; i++)
1261 if (stabs[i]) {
1262 gtk_list_store_append(buffers_store, &iter);
1263 title = get_title(stabs[i], FALSE);
1264 gtk_list_store_set(buffers_store, &iter,
1265 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1266 * rather than 0. */
1267 COL_TITLE, title,
1268 -1);
1271 g_free(stabs);
1274 void
1275 show_buffers(struct tab *t)
1277 buffers_make_list();
1278 gtk_widget_show(t->buffers);
1279 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1282 void
1283 toggle_buffers(struct tab *t)
1285 if (gtk_widget_get_visible(t->buffers))
1286 hide_buffers(t);
1287 else
1288 show_buffers(t);
1292 buffers(struct tab *t, struct karg *args)
1294 show_buffers(t);
1296 return (0);
1299 void
1300 hide_oops(struct tab *t)
1302 gtk_widget_hide(t->oops);
1305 void
1306 show_oops(struct tab *at, const char *fmt, ...)
1308 va_list ap;
1309 char *msg = NULL;
1310 struct tab *t = NULL;
1312 if (fmt == NULL)
1313 return;
1315 if (at == NULL) {
1316 if ((t = get_current_tab()) == NULL)
1317 return;
1318 } else
1319 t = at;
1321 va_start(ap, fmt);
1322 if (vasprintf(&msg, fmt, ap) == -1)
1323 errx(1, "show_oops failed");
1324 va_end(ap);
1326 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1327 gtk_widget_hide(t->cmd);
1328 gtk_widget_show(t->oops);
1330 if (msg)
1331 free(msg);
1334 char *
1335 get_as_string(struct settings *s)
1337 char *r = NULL;
1339 if (s == NULL)
1340 return (NULL);
1342 if (s->s) {
1343 if (s->s->get)
1344 r = s->s->get(s);
1345 else
1346 warnx("get_as_string skip %s\n", s->name);
1347 } else if (s->type == XT_S_INT)
1348 r = g_strdup_printf("%d", *s->ival);
1349 else if (s->type == XT_S_STR)
1350 r = g_strdup(*s->sval);
1351 else if (s->type == XT_S_FLOAT)
1352 r = g_strdup_printf("%f", *s->fval);
1353 else
1354 r = g_strdup_printf("INVALID TYPE");
1356 return (r);
1359 void
1360 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1362 int i;
1363 char *s;
1365 for (i = 0; i < LENGTH(rs); i++) {
1366 if (rs[i].s && rs[i].s->walk)
1367 rs[i].s->walk(&rs[i], cb, cb_args);
1368 else {
1369 s = get_as_string(&rs[i]);
1370 cb(&rs[i], s, cb_args);
1371 g_free(s);
1377 set_browser_mode(struct settings *s, char *val)
1379 if (!strcmp(val, "whitelist")) {
1380 browser_mode = XT_BM_WHITELIST;
1381 allow_volatile_cookies = 0;
1382 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1383 cookies_enabled = 1;
1384 enable_cookie_whitelist = 1;
1385 read_only_cookies = 0;
1386 save_rejected_cookies = 0;
1387 session_timeout = 3600;
1388 enable_scripts = 0;
1389 enable_js_whitelist = 1;
1390 enable_localstorage = 0;
1391 } else if (!strcmp(val, "normal")) {
1392 browser_mode = XT_BM_NORMAL;
1393 allow_volatile_cookies = 0;
1394 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1395 cookies_enabled = 1;
1396 enable_cookie_whitelist = 0;
1397 read_only_cookies = 0;
1398 save_rejected_cookies = 0;
1399 session_timeout = 3600;
1400 enable_scripts = 1;
1401 enable_js_whitelist = 0;
1402 enable_localstorage = 1;
1403 } else if (!strcmp(val, "kiosk")) {
1404 browser_mode = XT_BM_KIOSK;
1405 allow_volatile_cookies = 0;
1406 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1407 cookies_enabled = 1;
1408 enable_cookie_whitelist = 0;
1409 read_only_cookies = 0;
1410 save_rejected_cookies = 0;
1411 session_timeout = 3600;
1412 enable_scripts = 1;
1413 enable_js_whitelist = 0;
1414 enable_localstorage = 1;
1415 show_tabs = 0;
1416 tabless = 1;
1417 } else
1418 return (1);
1420 return (0);
1423 char *
1424 get_browser_mode(struct settings *s)
1426 char *r = NULL;
1428 if (browser_mode == XT_BM_WHITELIST)
1429 r = g_strdup("whitelist");
1430 else if (browser_mode == XT_BM_NORMAL)
1431 r = g_strdup("normal");
1432 else if (browser_mode == XT_BM_KIOSK)
1433 r = g_strdup("kiosk");
1434 else
1435 return (NULL);
1437 return (r);
1441 set_cookie_policy(struct settings *s, char *val)
1443 if (!strcmp(val, "no3rdparty"))
1444 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1445 else if (!strcmp(val, "accept"))
1446 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1447 else if (!strcmp(val, "reject"))
1448 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1449 else
1450 return (1);
1452 return (0);
1455 char *
1456 get_cookie_policy(struct settings *s)
1458 char *r = NULL;
1460 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1461 r = g_strdup("no3rdparty");
1462 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1463 r = g_strdup("accept");
1464 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1465 r = g_strdup("reject");
1466 else
1467 return (NULL);
1469 return (r);
1472 char *
1473 get_default_script(struct settings *s)
1475 if (default_script[0] == '\0')
1476 return (0);
1477 return (g_strdup(default_script));
1481 set_default_script(struct settings *s, char *val)
1483 if (val[0] == '~')
1484 snprintf(default_script, sizeof default_script, "%s/%s",
1485 pwd->pw_dir, &val[1]);
1486 else
1487 strlcpy(default_script, val, sizeof default_script);
1489 return (0);
1492 char *
1493 get_download_dir(struct settings *s)
1495 if (download_dir[0] == '\0')
1496 return (0);
1497 return (g_strdup(download_dir));
1501 set_download_dir(struct settings *s, char *val)
1503 if (val[0] == '~')
1504 snprintf(download_dir, sizeof download_dir, "%s/%s",
1505 pwd->pw_dir, &val[1]);
1506 else
1507 strlcpy(download_dir, val, sizeof download_dir);
1509 return (0);
1513 * Session IDs.
1514 * We use these to prevent people putting xxxt:// URLs on
1515 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1517 #define XT_XTP_SES_KEY_SZ 8
1518 #define XT_XTP_SES_KEY_HEX_FMT \
1519 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1520 char *dl_session_key; /* downloads */
1521 char *hl_session_key; /* history list */
1522 char *cl_session_key; /* cookie list */
1523 char *fl_session_key; /* favorites list */
1525 char work_dir[PATH_MAX];
1526 char certs_dir[PATH_MAX];
1527 char cache_dir[PATH_MAX];
1528 char sessions_dir[PATH_MAX];
1529 char cookie_file[PATH_MAX];
1530 SoupURI *proxy_uri = NULL;
1531 SoupSession *session;
1532 SoupCookieJar *s_cookiejar;
1533 SoupCookieJar *p_cookiejar;
1534 char rc_fname[PATH_MAX];
1536 struct mime_type_list mtl;
1537 struct alias_list aliases;
1539 /* protos */
1540 struct tab *create_new_tab(char *, struct undo *, int, int);
1541 void delete_tab(struct tab *);
1542 void setzoom_webkit(struct tab *, int);
1543 int run_script(struct tab *, char *);
1544 int download_rb_cmp(struct download *, struct download *);
1545 gboolean cmd_execute(struct tab *t, char *str);
1548 history_rb_cmp(struct history *h1, struct history *h2)
1550 return (strcmp(h1->uri, h2->uri));
1552 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1555 domain_rb_cmp(struct domain *d1, struct domain *d2)
1557 return (strcmp(d1->d, d2->d));
1559 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1561 char *
1562 get_work_dir(struct settings *s)
1564 if (work_dir[0] == '\0')
1565 return (0);
1566 return (g_strdup(work_dir));
1570 set_work_dir(struct settings *s, char *val)
1572 if (val[0] == '~')
1573 snprintf(work_dir, sizeof work_dir, "%s/%s",
1574 pwd->pw_dir, &val[1]);
1575 else
1576 strlcpy(work_dir, val, sizeof work_dir);
1578 return (0);
1581 char *
1582 get_tab_style(struct settings *s)
1584 if (tab_style == XT_TABS_NORMAL)
1585 return (g_strdup("normal"));
1586 else
1587 return (g_strdup("compact"));
1591 set_tab_style(struct settings *s, char *val)
1593 if (!strcmp(val, "normal"))
1594 tab_style = XT_TABS_NORMAL;
1595 else if (!strcmp(val, "compact"))
1596 tab_style = XT_TABS_COMPACT;
1597 else
1598 return (1);
1600 return (0);
1604 * generate a session key to secure xtp commands.
1605 * pass in a ptr to the key in question and it will
1606 * be modified in place.
1608 void
1609 generate_xtp_session_key(char **key)
1611 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1613 /* free old key */
1614 if (*key)
1615 g_free(*key);
1617 /* make a new one */
1618 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1619 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1620 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1621 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1623 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1627 * validate a xtp session key.
1628 * return 1 if OK
1631 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1633 if (strcmp(trusted, untrusted) != 0) {
1634 show_oops(t, "%s: xtp session key mismatch possible spoof",
1635 __func__);
1636 return (0);
1639 return (1);
1643 download_rb_cmp(struct download *e1, struct download *e2)
1645 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1647 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1649 struct valid_url_types {
1650 char *type;
1651 } vut[] = {
1652 { "http://" },
1653 { "https://" },
1654 { "ftp://" },
1655 { "file://" },
1656 { XT_XTP_STR },
1660 valid_url_type(char *url)
1662 int i;
1664 for (i = 0; i < LENGTH(vut); i++)
1665 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1666 return (0);
1668 return (1);
1671 void
1672 print_cookie(char *msg, SoupCookie *c)
1674 if (c == NULL)
1675 return;
1677 if (msg)
1678 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1679 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1680 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1681 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1682 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1683 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1684 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1685 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1686 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1687 DNPRINTF(XT_D_COOKIE, "====================================\n");
1690 void
1691 walk_alias(struct settings *s,
1692 void (*cb)(struct settings *, char *, void *), void *cb_args)
1694 struct alias *a;
1695 char *str;
1697 if (s == NULL || cb == NULL) {
1698 show_oops(NULL, "walk_alias invalid parameters");
1699 return;
1702 TAILQ_FOREACH(a, &aliases, entry) {
1703 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1704 cb(s, str, cb_args);
1705 g_free(str);
1709 char *
1710 match_alias(char *url_in)
1712 struct alias *a;
1713 char *arg;
1714 char *url_out = NULL, *search, *enc_arg;
1716 search = g_strdup(url_in);
1717 arg = search;
1718 if (strsep(&arg, " \t") == NULL) {
1719 show_oops(NULL, "match_alias: NULL URL");
1720 goto done;
1723 TAILQ_FOREACH(a, &aliases, entry) {
1724 if (!strcmp(search, a->a_name))
1725 break;
1728 if (a != NULL) {
1729 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1730 a->a_name);
1731 if (arg != NULL) {
1732 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1733 url_out = g_strdup_printf(a->a_uri, enc_arg);
1734 g_free(enc_arg);
1735 } else
1736 url_out = g_strdup_printf(a->a_uri, "");
1738 done:
1739 g_free(search);
1740 return (url_out);
1743 char *
1744 guess_url_type(char *url_in)
1746 struct stat sb;
1747 char *url_out = NULL, *enc_search = NULL;
1748 int i;
1750 /* substitute aliases */
1751 url_out = match_alias(url_in);
1752 if (url_out != NULL)
1753 return (url_out);
1755 /* see if we are an about page */
1756 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1757 for (i = 0; i < LENGTH(about_list); i++)
1758 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1759 about_list[i].name)) {
1760 url_out = g_strdup(url_in);
1761 goto done;
1764 if (guess_search && url_regex &&
1765 !(g_str_has_prefix(url_in, "http://") ||
1766 g_str_has_prefix(url_in, "https://"))) {
1767 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1768 /* invalid URI so search instead */
1769 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1770 url_out = g_strdup_printf(search_string, enc_search);
1771 g_free(enc_search);
1772 goto done;
1776 /* XXX not sure about this heuristic */
1777 if (stat(url_in, &sb) == 0)
1778 url_out = g_strdup_printf("file://%s", url_in);
1779 else
1780 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1781 done:
1782 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1784 return (url_out);
1787 void
1788 load_uri(struct tab *t, gchar *uri)
1790 struct karg args;
1791 gchar *newuri = NULL;
1792 int i;
1794 if (uri == NULL)
1795 return;
1797 /* Strip leading spaces. */
1798 while (*uri && isspace(*uri))
1799 uri++;
1801 if (strlen(uri) == 0) {
1802 blank(t, NULL);
1803 return;
1806 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1808 if (valid_url_type(uri)) {
1809 newuri = guess_url_type(uri);
1810 uri = newuri;
1813 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1814 for (i = 0; i < LENGTH(about_list); i++)
1815 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1816 bzero(&args, sizeof args);
1817 about_list[i].func(t, &args);
1818 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1819 FALSE);
1820 goto done;
1822 show_oops(t, "invalid about page");
1823 goto done;
1826 set_status(t, (char *)uri, XT_STATUS_LOADING);
1827 marks_clear(t);
1828 webkit_web_view_load_uri(t->wv, uri);
1829 done:
1830 if (newuri)
1831 g_free(newuri);
1834 const gchar *
1835 get_uri(struct tab *t)
1837 const gchar *uri = NULL;
1839 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1840 return t->tmp_uri;
1841 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1842 uri = webkit_web_view_get_uri(t->wv);
1843 } else {
1844 /* use tmp_uri to make sure it is g_freed */
1845 if (t->tmp_uri)
1846 g_free(t->tmp_uri);
1847 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1848 about_list[t->xtp_meaning].name);
1849 uri = t->tmp_uri;
1851 return uri;
1854 const gchar *
1855 get_title(struct tab *t, bool window)
1857 const gchar *set = NULL, *title = NULL;
1858 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1860 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1861 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1862 goto notitle;
1864 title = webkit_web_view_get_title(t->wv);
1865 if ((set = title ? title : get_uri(t)))
1866 return set;
1868 notitle:
1869 set = window ? XT_NAME : "(untitled)";
1871 return set;
1875 add_alias(struct settings *s, char *line)
1877 char *l, *alias;
1878 struct alias *a = NULL;
1880 if (s == NULL || line == NULL) {
1881 show_oops(NULL, "add_alias invalid parameters");
1882 return (1);
1885 l = line;
1886 a = g_malloc(sizeof(*a));
1888 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1889 show_oops(NULL, "add_alias: incomplete alias definition");
1890 goto bad;
1892 if (strlen(alias) == 0 || strlen(l) == 0) {
1893 show_oops(NULL, "add_alias: invalid alias definition");
1894 goto bad;
1897 a->a_name = g_strdup(alias);
1898 a->a_uri = g_strdup(l);
1900 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1902 TAILQ_INSERT_TAIL(&aliases, a, entry);
1904 return (0);
1905 bad:
1906 if (a)
1907 g_free(a);
1908 return (1);
1912 add_mime_type(struct settings *s, char *line)
1914 char *mime_type;
1915 char *l;
1916 struct mime_type *m = NULL;
1917 int downloadfirst = 0;
1919 /* XXX this could be smarter */
1921 if (line == NULL || strlen(line) == 0) {
1922 show_oops(NULL, "add_mime_type invalid parameters");
1923 return (1);
1926 l = line;
1927 if (*l == '@') {
1928 downloadfirst = 1;
1929 l++;
1931 m = g_malloc(sizeof(*m));
1933 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1934 show_oops(NULL, "add_mime_type: invalid mime_type");
1935 goto bad;
1937 if (mime_type[strlen(mime_type) - 1] == '*') {
1938 mime_type[strlen(mime_type) - 1] = '\0';
1939 m->mt_default = 1;
1940 } else
1941 m->mt_default = 0;
1943 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1944 show_oops(NULL, "add_mime_type: invalid mime_type");
1945 goto bad;
1948 m->mt_type = g_strdup(mime_type);
1949 m->mt_action = g_strdup(l);
1950 m->mt_download = downloadfirst;
1952 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1953 m->mt_type, m->mt_action, m->mt_default);
1955 TAILQ_INSERT_TAIL(&mtl, m, entry);
1957 return (0);
1958 bad:
1959 if (m)
1960 g_free(m);
1961 return (1);
1964 struct mime_type *
1965 find_mime_type(char *mime_type)
1967 struct mime_type *m, *def = NULL, *rv = NULL;
1969 TAILQ_FOREACH(m, &mtl, entry) {
1970 if (m->mt_default &&
1971 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1972 def = m;
1974 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1975 rv = m;
1976 break;
1980 if (rv == NULL)
1981 rv = def;
1983 return (rv);
1986 void
1987 walk_mime_type(struct settings *s,
1988 void (*cb)(struct settings *, char *, void *), void *cb_args)
1990 struct mime_type *m;
1991 char *str;
1993 if (s == NULL || cb == NULL) {
1994 show_oops(NULL, "walk_mime_type invalid parameters");
1995 return;
1998 TAILQ_FOREACH(m, &mtl, entry) {
1999 str = g_strdup_printf("%s%s --> %s",
2000 m->mt_type,
2001 m->mt_default ? "*" : "",
2002 m->mt_action);
2003 cb(s, str, cb_args);
2004 g_free(str);
2008 void
2009 wl_add(char *str, struct domain_list *wl, int handy)
2011 struct domain *d;
2012 int add_dot = 0;
2013 char *p;
2015 if (str == NULL || wl == NULL || strlen(str) < 2)
2016 return;
2018 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2020 /* treat *.moo.com the same as .moo.com */
2021 if (str[0] == '*' && str[1] == '.')
2022 str = &str[1];
2023 else if (str[0] == '.')
2024 str = &str[0];
2025 else
2026 add_dot = 1;
2028 /* slice off port number */
2029 p = g_strrstr(str, ":");
2030 if (p)
2031 *p = '\0';
2033 d = g_malloc(sizeof *d);
2034 if (add_dot)
2035 d->d = g_strdup_printf(".%s", str);
2036 else
2037 d->d = g_strdup(str);
2038 d->handy = handy;
2040 if (RB_INSERT(domain_list, wl, d))
2041 goto unwind;
2043 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2044 return;
2045 unwind:
2046 if (d) {
2047 if (d->d)
2048 g_free(d->d);
2049 g_free(d);
2054 add_cookie_wl(struct settings *s, char *entry)
2056 wl_add(entry, &c_wl, 1);
2057 return (0);
2060 void
2061 walk_cookie_wl(struct settings *s,
2062 void (*cb)(struct settings *, char *, void *), void *cb_args)
2064 struct domain *d;
2066 if (s == NULL || cb == NULL) {
2067 show_oops(NULL, "walk_cookie_wl invalid parameters");
2068 return;
2071 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2072 cb(s, d->d, cb_args);
2075 void
2076 walk_js_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_js_wl invalid parameters");
2083 return;
2086 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2087 cb(s, d->d, cb_args);
2091 add_js_wl(struct settings *s, char *entry)
2093 wl_add(entry, &js_wl, 1 /* persistent */);
2094 return (0);
2097 struct domain *
2098 wl_find(const gchar *search, struct domain_list *wl)
2100 int i;
2101 struct domain *d = NULL, dfind;
2102 gchar *s = NULL;
2104 if (search == NULL || wl == NULL)
2105 return (NULL);
2106 if (strlen(search) < 2)
2107 return (NULL);
2109 if (search[0] != '.')
2110 s = g_strdup_printf(".%s", search);
2111 else
2112 s = g_strdup(search);
2114 for (i = strlen(s) - 1; i >= 0; i--) {
2115 if (s[i] == '.') {
2116 dfind.d = &s[i];
2117 d = RB_FIND(domain_list, wl, &dfind);
2118 if (d)
2119 goto done;
2123 done:
2124 if (s)
2125 g_free(s);
2127 return (d);
2130 struct domain *
2131 wl_find_uri(const gchar *s, struct domain_list *wl)
2133 int i;
2134 char *ss;
2135 struct domain *r;
2137 if (s == NULL || wl == NULL)
2138 return (NULL);
2140 if (!strncmp(s, "http://", strlen("http://")))
2141 s = &s[strlen("http://")];
2142 else if (!strncmp(s, "https://", strlen("https://")))
2143 s = &s[strlen("https://")];
2145 if (strlen(s) < 2)
2146 return (NULL);
2148 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2149 /* chop string at first slash */
2150 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2151 ss = g_strdup(s);
2152 ss[i] = '\0';
2153 r = wl_find(ss, wl);
2154 g_free(ss);
2155 return (r);
2158 return (NULL);
2162 settings_add(char *var, char *val)
2164 int i, rv, *p;
2165 gfloat *f;
2166 char **s;
2168 /* get settings */
2169 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2170 if (strcmp(var, rs[i].name))
2171 continue;
2173 if (rs[i].s) {
2174 if (rs[i].s->set(&rs[i], val))
2175 errx(1, "invalid value for %s: %s", var, val);
2176 rv = 1;
2177 break;
2178 } else
2179 switch (rs[i].type) {
2180 case XT_S_INT:
2181 p = rs[i].ival;
2182 *p = atoi(val);
2183 rv = 1;
2184 break;
2185 case XT_S_STR:
2186 s = rs[i].sval;
2187 if (s == NULL)
2188 errx(1, "invalid sval for %s",
2189 rs[i].name);
2190 if (*s)
2191 g_free(*s);
2192 *s = g_strdup(val);
2193 rv = 1;
2194 break;
2195 case XT_S_FLOAT:
2196 f = rs[i].fval;
2197 *f = atof(val);
2198 rv = 1;
2199 break;
2200 case XT_S_INVALID:
2201 default:
2202 errx(1, "invalid type for %s", var);
2204 break;
2206 return (rv);
2209 #define WS "\n= \t"
2210 void
2211 config_parse(char *filename, int runtime)
2213 FILE *config, *f;
2214 char *line, *cp, *var, *val;
2215 size_t len, lineno = 0;
2216 int handled;
2217 char file[PATH_MAX];
2218 struct stat sb;
2220 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2222 if (filename == NULL)
2223 return;
2225 if (runtime && runtime_settings[0] != '\0') {
2226 snprintf(file, sizeof file, "%s/%s",
2227 work_dir, runtime_settings);
2228 if (stat(file, &sb)) {
2229 warnx("runtime file doesn't exist, creating it");
2230 if ((f = fopen(file, "w")) == NULL)
2231 err(1, "runtime");
2232 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2233 fclose(f);
2235 } else
2236 strlcpy(file, filename, sizeof file);
2238 if ((config = fopen(file, "r")) == NULL) {
2239 warn("config_parse: cannot open %s", filename);
2240 return;
2243 for (;;) {
2244 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2245 if (feof(config) || ferror(config))
2246 break;
2248 cp = line;
2249 cp += (long)strspn(cp, WS);
2250 if (cp[0] == '\0') {
2251 /* empty line */
2252 free(line);
2253 continue;
2256 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2257 startpage_add("invalid configuration file entry: %s",
2258 line);
2260 cp += (long)strspn(cp, WS);
2262 if ((val = strsep(&cp, "\0")) == NULL)
2263 break;
2265 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2266 handled = settings_add(var, val);
2267 if (handled == 0)
2268 startpage_add("invalid configuration file entry: %s=%s",
2269 var, val);
2271 free(line);
2274 fclose(config);
2277 char *
2278 js_ref_to_string(JSContextRef context, JSValueRef ref)
2280 char *s = NULL;
2281 size_t l;
2282 JSStringRef jsref;
2284 jsref = JSValueToStringCopy(context, ref, NULL);
2285 if (jsref == NULL)
2286 return (NULL);
2288 l = JSStringGetMaximumUTF8CStringSize(jsref);
2289 s = g_malloc(l);
2290 if (s)
2291 JSStringGetUTF8CString(jsref, s, l);
2292 JSStringRelease(jsref);
2294 return (s);
2297 void
2298 disable_hints(struct tab *t)
2300 bzero(t->hint_buf, sizeof t->hint_buf);
2301 bzero(t->hint_num, sizeof t->hint_num);
2302 run_script(t, "vimprobable_clear()");
2303 t->hints_on = 0;
2304 t->hint_mode = XT_HINT_NONE;
2307 void
2308 enable_hints(struct tab *t)
2310 bzero(t->hint_buf, sizeof t->hint_buf);
2311 run_script(t, "vimprobable_show_hints()");
2312 t->hints_on = 1;
2313 t->hint_mode = XT_HINT_NONE;
2316 #define XT_JS_OPEN ("open;")
2317 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2318 #define XT_JS_FIRE ("fire;")
2319 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2320 #define XT_JS_FOUND ("found;")
2321 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2324 run_script(struct tab *t, char *s)
2326 JSGlobalContextRef ctx;
2327 WebKitWebFrame *frame;
2328 JSStringRef str;
2329 JSValueRef val, exception;
2330 char *es, buf[128];
2332 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2333 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2335 frame = webkit_web_view_get_main_frame(t->wv);
2336 ctx = webkit_web_frame_get_global_context(frame);
2338 str = JSStringCreateWithUTF8CString(s);
2339 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2340 NULL, 0, &exception);
2341 JSStringRelease(str);
2343 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2344 if (val == NULL) {
2345 es = js_ref_to_string(ctx, exception);
2346 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2347 g_free(es);
2348 return (1);
2349 } else {
2350 es = js_ref_to_string(ctx, val);
2351 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2353 /* handle return value right here */
2354 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2355 disable_hints(t);
2356 marks_clear(t);
2357 load_uri(t, &es[XT_JS_OPEN_LEN]);
2360 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2361 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2362 &es[XT_JS_FIRE_LEN]);
2363 run_script(t, buf);
2364 disable_hints(t);
2367 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2368 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2369 disable_hints(t);
2372 g_free(es);
2375 return (0);
2379 hint(struct tab *t, struct karg *args)
2382 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2384 if (t->hints_on == 0)
2385 enable_hints(t);
2386 else
2387 disable_hints(t);
2389 return (0);
2392 void
2393 apply_style(struct tab *t)
2395 g_object_set(G_OBJECT(t->settings),
2396 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2400 userstyle(struct tab *t, struct karg *args)
2402 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2404 if (t->styled) {
2405 t->styled = 0;
2406 g_object_set(G_OBJECT(t->settings),
2407 "user-stylesheet-uri", NULL, (char *)NULL);
2408 } else {
2409 t->styled = 1;
2410 apply_style(t);
2412 return (0);
2416 * Doesn't work fully, due to the following bug:
2417 * https://bugs.webkit.org/show_bug.cgi?id=51747
2420 restore_global_history(void)
2422 char file[PATH_MAX];
2423 FILE *f;
2424 struct history *h;
2425 gchar *uri;
2426 gchar *title;
2427 const char delim[3] = {'\\', '\\', '\0'};
2429 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2431 if ((f = fopen(file, "r")) == NULL) {
2432 warnx("%s: fopen", __func__);
2433 return (1);
2436 for (;;) {
2437 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2438 if (feof(f) || ferror(f))
2439 break;
2441 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2442 if (feof(f) || ferror(f)) {
2443 free(uri);
2444 warnx("%s: broken history file\n", __func__);
2445 return (1);
2448 if (uri && strlen(uri) && title && strlen(title)) {
2449 webkit_web_history_item_new_with_data(uri, title);
2450 h = g_malloc(sizeof(struct history));
2451 h->uri = g_strdup(uri);
2452 h->title = g_strdup(title);
2453 RB_INSERT(history_list, &hl, h);
2454 completion_add_uri(h->uri);
2455 } else {
2456 warnx("%s: failed to restore history\n", __func__);
2457 free(uri);
2458 free(title);
2459 return (1);
2462 free(uri);
2463 free(title);
2464 uri = NULL;
2465 title = NULL;
2468 return (0);
2472 save_global_history_to_disk(struct tab *t)
2474 char file[PATH_MAX];
2475 FILE *f;
2476 struct history *h;
2478 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2480 if ((f = fopen(file, "w")) == NULL) {
2481 show_oops(t, "%s: global history file: %s",
2482 __func__, strerror(errno));
2483 return (1);
2486 RB_FOREACH_REVERSE(h, history_list, &hl) {
2487 if (h->uri && h->title)
2488 fprintf(f, "%s\n%s\n", h->uri, h->title);
2491 fclose(f);
2493 return (0);
2497 quit(struct tab *t, struct karg *args)
2499 if (save_global_history)
2500 save_global_history_to_disk(t);
2502 gtk_main_quit();
2504 return (1);
2507 void
2508 restore_sessions_list(void)
2510 DIR *sdir = NULL;
2511 struct dirent *dp = NULL;
2512 struct session *s;
2514 sdir = opendir(sessions_dir);
2515 if (sdir) {
2516 while ((dp = readdir(sdir)) != NULL)
2517 if (dp->d_type == DT_REG) {
2518 s = g_malloc(sizeof(struct session));
2519 s->name = g_strdup(dp->d_name);
2520 TAILQ_INSERT_TAIL(&sessions, s, entry);
2522 closedir(sdir);
2527 open_tabs(struct tab *t, struct karg *a)
2529 char file[PATH_MAX];
2530 FILE *f = NULL;
2531 char *uri = NULL;
2532 int rv = 1;
2533 struct tab *ti, *tt;
2535 if (a == NULL)
2536 goto done;
2538 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2539 if ((f = fopen(file, "r")) == NULL)
2540 goto done;
2542 ti = TAILQ_LAST(&tabs, tab_list);
2544 for (;;) {
2545 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2546 if (feof(f) || ferror(f))
2547 break;
2549 /* retrieve session name */
2550 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2551 strlcpy(named_session,
2552 &uri[strlen(XT_SAVE_SESSION_ID)],
2553 sizeof named_session);
2554 continue;
2557 if (uri && strlen(uri))
2558 create_new_tab(uri, NULL, 1, -1);
2560 free(uri);
2561 uri = NULL;
2564 /* close open tabs */
2565 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2566 for (;;) {
2567 tt = TAILQ_FIRST(&tabs);
2568 if (tt != ti) {
2569 delete_tab(tt);
2570 continue;
2572 delete_tab(tt);
2573 break;
2575 recalc_tabs();
2578 rv = 0;
2579 done:
2580 if (f)
2581 fclose(f);
2583 return (rv);
2587 restore_saved_tabs(void)
2589 char file[PATH_MAX];
2590 int unlink_file = 0;
2591 struct stat sb;
2592 struct karg a;
2593 int rv = 0;
2595 snprintf(file, sizeof file, "%s/%s",
2596 sessions_dir, XT_RESTART_TABS_FILE);
2597 if (stat(file, &sb) == -1)
2598 a.s = XT_SAVED_TABS_FILE;
2599 else {
2600 unlink_file = 1;
2601 a.s = XT_RESTART_TABS_FILE;
2604 a.i = XT_SES_DONOTHING;
2605 rv = open_tabs(NULL, &a);
2607 if (unlink_file)
2608 unlink(file);
2610 return (rv);
2614 save_tabs(struct tab *t, struct karg *a)
2616 char file[PATH_MAX];
2617 FILE *f;
2618 int num_tabs = 0, i;
2619 struct tab **stabs = NULL;
2621 if (a == NULL)
2622 return (1);
2623 if (a->s == NULL)
2624 snprintf(file, sizeof file, "%s/%s",
2625 sessions_dir, named_session);
2626 else
2627 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2629 if ((f = fopen(file, "w")) == NULL) {
2630 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2631 return (1);
2634 /* save session name */
2635 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2637 /* Save tabs, in the order they are arranged in the notebook. */
2638 num_tabs = sort_tabs_by_page_num(&stabs);
2640 for (i = 0; i < num_tabs; i++)
2641 if (stabs[i]) {
2642 if (get_uri(stabs[i]) != NULL)
2643 fprintf(f, "%s\n", get_uri(stabs[i]));
2644 else if (gtk_entry_get_text(GTK_ENTRY(
2645 stabs[i]->uri_entry)))
2646 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2647 stabs[i]->uri_entry)));
2650 g_free(stabs);
2652 /* try and make sure this gets to disk NOW. XXX Backup first? */
2653 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2654 show_oops(t, "May not have managed to save session: %s",
2655 strerror(errno));
2658 fclose(f);
2660 return (0);
2664 save_tabs_and_quit(struct tab *t, struct karg *args)
2666 struct karg a;
2668 a.s = NULL;
2669 save_tabs(t, &a);
2670 quit(t, NULL);
2672 return (1);
2676 run_page_script(struct tab *t, struct karg *args)
2678 const gchar *uri;
2679 char *tmp, script[PATH_MAX];
2681 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2682 if (tmp[0] == '\0') {
2683 show_oops(t, "no script specified");
2684 return (1);
2687 if ((uri = get_uri(t)) == NULL) {
2688 show_oops(t, "tab is empty, not running script");
2689 return (1);
2692 if (tmp[0] == '~')
2693 snprintf(script, sizeof script, "%s/%s",
2694 pwd->pw_dir, &tmp[1]);
2695 else
2696 strlcpy(script, tmp, sizeof script);
2698 switch (fork()) {
2699 case -1:
2700 show_oops(t, "can't fork to run script");
2701 return (1);
2702 /* NOTREACHED */
2703 case 0:
2704 break;
2705 default:
2706 return (0);
2709 /* child */
2710 execlp(script, script, uri, (void *)NULL);
2712 _exit(0);
2714 /* NOTREACHED */
2716 return (0);
2720 yank_uri(struct tab *t, struct karg *args)
2722 const gchar *uri;
2723 GtkClipboard *clipboard;
2725 if ((uri = get_uri(t)) == NULL)
2726 return (1);
2728 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2729 gtk_clipboard_set_text(clipboard, uri, -1);
2731 return (0);
2735 paste_uri(struct tab *t, struct karg *args)
2737 GtkClipboard *clipboard;
2738 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2739 gint len;
2740 gchar *p = NULL, *uri;
2742 /* try primary clipboard first */
2743 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2744 p = gtk_clipboard_wait_for_text(clipboard);
2746 /* if it failed get whatever text is in cut_buffer0 */
2747 if (p == NULL && xterm_workaround)
2748 if (gdk_property_get(gdk_get_default_root_window(),
2749 atom,
2750 gdk_atom_intern("STRING", FALSE),
2752 1024 * 1024 /* picked out of my butt */,
2753 FALSE,
2754 NULL,
2755 NULL,
2756 &len,
2757 (guchar **)&p)) {
2758 /* yes sir, we need to NUL the string */
2759 p[len] = '\0';
2762 if (p) {
2763 uri = p;
2764 while (*uri && isspace(*uri))
2765 uri++;
2766 if (strlen(uri) == 0) {
2767 show_oops(t, "empty paste buffer");
2768 goto done;
2770 if (guess_search == 0 && valid_url_type(uri)) {
2771 /* we can be clever and paste this in search box */
2772 show_oops(t, "not a valid URL");
2773 goto done;
2776 if (args->i == XT_PASTE_CURRENT_TAB)
2777 load_uri(t, uri);
2778 else if (args->i == XT_PASTE_NEW_TAB)
2779 create_new_tab(uri, NULL, 1, -1);
2782 done:
2783 if (p)
2784 g_free(p);
2786 return (0);
2789 gchar *
2790 find_domain(const gchar *s, int toplevel)
2792 SoupURI *uri;
2793 gchar *ret, *p;
2795 if (s == NULL)
2796 return (NULL);
2798 uri = soup_uri_new(s);
2800 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2801 return (NULL);
2804 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2805 if ((p = strrchr(uri->host, '.')) != NULL) {
2806 while(--p >= uri->host && *p != '.');
2807 p++;
2808 } else
2809 p = uri->host;
2810 } else
2811 p = uri->host;
2813 ret = g_strdup_printf(".%s", p);
2815 soup_uri_free(uri);
2817 return ret;
2821 toggle_cwl(struct tab *t, struct karg *args)
2823 struct domain *d;
2824 const gchar *uri;
2825 char *dom = NULL;
2826 int es;
2828 if (args == NULL)
2829 return (1);
2831 uri = get_uri(t);
2832 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2834 if (uri == NULL || dom == NULL ||
2835 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2836 show_oops(t, "Can't toggle domain in cookie white list");
2837 goto done;
2839 d = wl_find(dom, &c_wl);
2841 if (d == NULL)
2842 es = 0;
2843 else
2844 es = 1;
2846 if (args->i & XT_WL_TOGGLE)
2847 es = !es;
2848 else if ((args->i & XT_WL_ENABLE) && es != 1)
2849 es = 1;
2850 else if ((args->i & XT_WL_DISABLE) && es != 0)
2851 es = 0;
2853 if (es)
2854 /* enable cookies for domain */
2855 wl_add(dom, &c_wl, 0);
2856 else
2857 /* disable cookies for domain */
2858 RB_REMOVE(domain_list, &c_wl, d);
2860 if (args->i & XT_WL_RELOAD)
2861 webkit_web_view_reload(t->wv);
2863 done:
2864 g_free(dom);
2865 return (0);
2869 toggle_js(struct tab *t, struct karg *args)
2871 int es;
2872 const gchar *uri;
2873 struct domain *d;
2874 char *dom = NULL;
2876 if (args == NULL)
2877 return (1);
2879 g_object_get(G_OBJECT(t->settings),
2880 "enable-scripts", &es, (char *)NULL);
2881 if (args->i & XT_WL_TOGGLE)
2882 es = !es;
2883 else if ((args->i & XT_WL_ENABLE) && es != 1)
2884 es = 1;
2885 else if ((args->i & XT_WL_DISABLE) && es != 0)
2886 es = 0;
2887 else
2888 return (1);
2890 uri = get_uri(t);
2891 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2893 if (uri == NULL || dom == NULL ||
2894 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2895 show_oops(t, "Can't toggle domain in JavaScript white list");
2896 goto done;
2899 if (es) {
2900 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2901 wl_add(dom, &js_wl, 0 /* session */);
2902 } else {
2903 d = wl_find(dom, &js_wl);
2904 if (d)
2905 RB_REMOVE(domain_list, &js_wl, d);
2906 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2908 g_object_set(G_OBJECT(t->settings),
2909 "enable-scripts", es, (char *)NULL);
2910 g_object_set(G_OBJECT(t->settings),
2911 "javascript-can-open-windows-automatically", es, (char *)NULL);
2912 webkit_web_view_set_settings(t->wv, t->settings);
2914 if (args->i & XT_WL_RELOAD)
2915 webkit_web_view_reload(t->wv);
2916 done:
2917 if (dom)
2918 g_free(dom);
2919 return (0);
2922 void
2923 js_toggle_cb(GtkWidget *w, struct tab *t)
2925 struct karg a;
2927 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2928 toggle_cwl(t, &a);
2930 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2931 toggle_js(t, &a);
2935 toggle_src(struct tab *t, struct karg *args)
2937 gboolean mode;
2939 if (t == NULL)
2940 return (0);
2942 mode = webkit_web_view_get_view_source_mode(t->wv);
2943 webkit_web_view_set_view_source_mode(t->wv, !mode);
2944 webkit_web_view_reload(t->wv);
2946 return (0);
2949 void
2950 focus_webview(struct tab *t)
2952 if (t == NULL)
2953 return;
2955 /* only grab focus if we are visible */
2956 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2957 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2961 focus(struct tab *t, struct karg *args)
2963 if (t == NULL || args == NULL)
2964 return (1);
2966 if (show_url == 0)
2967 return (0);
2969 if (args->i == XT_FOCUS_URI)
2970 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2971 else if (args->i == XT_FOCUS_SEARCH)
2972 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2974 return (0);
2978 stats(struct tab *t, struct karg *args)
2980 char *page, *body, *s, line[64 * 1024];
2981 uint64_t line_count = 0;
2982 FILE *r_cookie_f;
2984 if (t == NULL)
2985 show_oops(NULL, "stats invalid parameters");
2987 line[0] = '\0';
2988 if (save_rejected_cookies) {
2989 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2990 for (;;) {
2991 s = fgets(line, sizeof line, r_cookie_f);
2992 if (s == NULL || feof(r_cookie_f) ||
2993 ferror(r_cookie_f))
2994 break;
2995 line_count++;
2997 fclose(r_cookie_f);
2998 snprintf(line, sizeof line,
2999 "<br/>Cookies blocked(*) total: %llu", line_count);
3000 } else
3001 show_oops(t, "Can't open blocked cookies file: %s",
3002 strerror(errno));
3005 body = g_strdup_printf(
3006 "Cookies blocked(*) this session: %llu"
3007 "%s"
3008 "<p><small><b>*</b> results vary based on settings</small></p>",
3009 blocked_cookies,
3010 line);
3012 page = get_html_page("Statistics", body, "", 0);
3013 g_free(body);
3015 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3016 g_free(page);
3018 return (0);
3022 marco(struct tab *t, struct karg *args)
3024 char *page, line[64 * 1024];
3025 int len;
3027 if (t == NULL)
3028 show_oops(NULL, "marco invalid parameters");
3030 line[0] = '\0';
3031 snprintf(line, sizeof line, "%s", marco_message(&len));
3033 page = get_html_page("Marco Sez...", line, "", 0);
3035 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3036 g_free(page);
3038 return (0);
3042 blank(struct tab *t, struct karg *args)
3044 if (t == NULL)
3045 show_oops(NULL, "blank invalid parameters");
3047 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3049 return (0);
3053 about(struct tab *t, struct karg *args)
3055 char *page, *body;
3057 if (t == NULL)
3058 show_oops(NULL, "about invalid parameters");
3060 body = g_strdup_printf("<b>Version: %s</b><p>"
3061 "Authors:"
3062 "<ul>"
3063 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3064 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3065 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3066 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3067 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3068 "</ul>"
3069 "Copyrights and licenses can be found on the XXXTerm "
3070 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
3071 version
3074 page = get_html_page("About", body, "", 0);
3075 g_free(body);
3077 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3078 g_free(page);
3080 return (0);
3084 help(struct tab *t, struct karg *args)
3086 char *page, *head, *body;
3088 if (t == NULL)
3089 show_oops(NULL, "help invalid parameters");
3091 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3092 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3093 "</head>\n";
3094 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3095 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3096 "cgi-bin/man-cgi?xxxterm</a>";
3098 page = get_html_page(XT_NAME, body, head, FALSE);
3100 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3101 g_free(page);
3103 return (0);
3107 startpage(struct tab *t, struct karg *args)
3109 char *page, *body, *b;
3110 struct sp *s;
3112 if (t == NULL)
3113 show_oops(NULL, "startpage invalid parameters");
3115 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3117 TAILQ_FOREACH(s, &spl, entry) {
3118 b = body;
3119 body = g_strdup_printf("%s%s<br>", body, s->line);
3120 g_free(b);
3123 page = get_html_page("Startup Exception", body, "", 0);
3124 g_free(body);
3126 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3127 g_free(page);
3129 return (0);
3132 void
3133 startpage_add(const char *fmt, ...)
3135 va_list ap;
3136 char *msg;
3137 struct sp *s;
3139 if (fmt == NULL)
3140 return;
3142 va_start(ap, fmt);
3143 if (vasprintf(&msg, fmt, ap) == -1)
3144 errx(1, "startpage_add failed");
3145 va_end(ap);
3147 s = g_malloc0(sizeof *s);
3148 s->line = msg;
3150 TAILQ_INSERT_TAIL(&spl, s, entry);
3154 * update all favorite tabs apart from one. Pass NULL if
3155 * you want to update all.
3157 void
3158 update_favorite_tabs(struct tab *apart_from)
3160 struct tab *t;
3161 if (!updating_fl_tabs) {
3162 updating_fl_tabs = 1; /* stop infinite recursion */
3163 TAILQ_FOREACH(t, &tabs, entry)
3164 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3165 && (t != apart_from))
3166 xtp_page_fl(t, NULL);
3167 updating_fl_tabs = 0;
3171 /* show a list of favorites (bookmarks) */
3173 xtp_page_fl(struct tab *t, struct karg *args)
3175 char file[PATH_MAX];
3176 FILE *f;
3177 char *uri = NULL, *title = NULL;
3178 size_t len, lineno = 0;
3179 int i, failed = 0;
3180 char *body, *tmp, *page = NULL;
3181 const char delim[3] = {'\\', '\\', '\0'};
3183 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3185 if (t == NULL)
3186 warn("%s: bad param", __func__);
3188 /* new session key */
3189 if (!updating_fl_tabs)
3190 generate_xtp_session_key(&fl_session_key);
3192 /* open favorites */
3193 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3194 if ((f = fopen(file, "r")) == NULL) {
3195 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3196 return (1);
3199 /* body */
3200 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3201 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3202 "<th style='width: 40px'>Rm</th></tr>\n");
3204 for (i = 1;;) {
3205 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3206 if (feof(f) || ferror(f))
3207 break;
3208 if (strlen(title) == 0 || title[0] == '#') {
3209 free(title);
3210 title = NULL;
3211 continue;
3214 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3215 if (feof(f) || ferror(f)) {
3216 show_oops(t, "favorites file corrupt");
3217 failed = 1;
3218 break;
3221 tmp = body;
3222 body = g_strdup_printf("%s<tr>"
3223 "<td>%d</td>"
3224 "<td><a href='%s'>%s</a></td>"
3225 "<td style='text-align: center'>"
3226 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3227 "</tr>\n",
3228 body, i, uri, title,
3229 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3231 g_free(tmp);
3233 free(uri);
3234 uri = NULL;
3235 free(title);
3236 title = NULL;
3237 i++;
3239 fclose(f);
3241 /* if none, say so */
3242 if (i == 1) {
3243 tmp = body;
3244 body = g_strdup_printf("%s<tr>"
3245 "<td colspan='3' style='text-align: center'>"
3246 "No favorites - To add one use the 'favadd' command."
3247 "</td></tr>", body);
3248 g_free(tmp);
3251 tmp = body;
3252 body = g_strdup_printf("%s</table>", body);
3253 g_free(tmp);
3255 if (uri)
3256 free(uri);
3257 if (title)
3258 free(title);
3260 /* render */
3261 if (!failed) {
3262 page = get_html_page("Favorites", body, "", 1);
3263 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3264 g_free(page);
3267 update_favorite_tabs(t);
3269 if (body)
3270 g_free(body);
3272 return (failed);
3275 void
3276 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3277 size_t cert_count, char *title)
3279 gnutls_datum_t cinfo;
3280 char *tmp, *body;
3281 int i;
3283 body = g_strdup("");
3285 for (i = 0; i < cert_count; i++) {
3286 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3287 &cinfo))
3288 return;
3290 tmp = body;
3291 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3292 body, i, cinfo.data);
3293 gnutls_free(cinfo.data);
3294 g_free(tmp);
3297 tmp = get_html_page(title, body, "", 0);
3298 g_free(body);
3300 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3301 g_free(tmp);
3305 ca_cmd(struct tab *t, struct karg *args)
3307 FILE *f = NULL;
3308 int rv = 1, certs = 0, certs_read;
3309 struct stat sb;
3310 gnutls_datum_t dt;
3311 gnutls_x509_crt_t *c = NULL;
3312 char *certs_buf = NULL, *s;
3314 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3315 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3316 return (1);
3319 if (fstat(fileno(f), &sb) == -1) {
3320 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3321 goto done;
3324 certs_buf = g_malloc(sb.st_size + 1);
3325 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3326 show_oops(t, "Can't read CA file: %s", strerror(errno));
3327 goto done;
3329 certs_buf[sb.st_size] = '\0';
3331 s = certs_buf;
3332 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3333 certs++;
3334 s += strlen("BEGIN CERTIFICATE");
3337 bzero(&dt, sizeof dt);
3338 dt.data = (unsigned char *)certs_buf;
3339 dt.size = sb.st_size;
3340 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3341 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3342 GNUTLS_X509_FMT_PEM, 0);
3343 if (certs_read <= 0) {
3344 show_oops(t, "No cert(s) available");
3345 goto done;
3347 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3348 done:
3349 if (c)
3350 g_free(c);
3351 if (certs_buf)
3352 g_free(certs_buf);
3353 if (f)
3354 fclose(f);
3356 return (rv);
3360 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3361 size_t domain_sz)
3363 SoupURI *su = NULL;
3364 struct addrinfo hints, *res = NULL, *ai;
3365 int rv = -1, s = -1, on, error;
3366 char port[8];
3368 if (uri && !g_str_has_prefix(uri, "https://")) {
3369 show_oops(t, "invalid URI");
3370 goto done;
3373 su = soup_uri_new(uri);
3374 if (su == NULL) {
3375 show_oops(t, "invalid soup URI");
3376 goto done;
3378 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3379 show_oops(t, "invalid HTTPS URI");
3380 goto done;
3383 snprintf(port, sizeof port, "%d", su->port);
3384 bzero(&hints, sizeof(struct addrinfo));
3385 hints.ai_flags = AI_CANONNAME;
3386 hints.ai_family = AF_UNSPEC;
3387 hints.ai_socktype = SOCK_STREAM;
3389 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3390 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3391 goto done;
3394 for (ai = res; ai; ai = ai->ai_next) {
3395 if (s != -1) {
3396 close(s);
3397 s = -1;
3400 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3401 continue;
3402 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3403 if (s == -1)
3404 continue;
3405 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3406 sizeof(on)) == -1)
3407 continue;
3408 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3409 break;
3411 if (s == -1) {
3412 show_oops(t, "could not obtain certificates from: %s",
3413 su->host);
3414 goto done;
3417 if (domain)
3418 strlcpy(domain, su->host, domain_sz);
3419 rv = s;
3420 done:
3421 if (su)
3422 soup_uri_free(su);
3423 if (res)
3424 freeaddrinfo(res);
3425 if (rv == -1 && s != -1)
3426 close(s);
3428 return (rv);
3432 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3434 if (gsession)
3435 gnutls_deinit(gsession);
3436 if (xcred)
3437 gnutls_certificate_free_credentials(xcred);
3439 return (0);
3443 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3444 gnutls_certificate_credentials_t *xc)
3446 gnutls_certificate_credentials_t xcred;
3447 gnutls_session_t gsession;
3448 int rv = 1;
3450 if (gs == NULL || xc == NULL)
3451 goto done;
3453 *gs = NULL;
3454 *xc = NULL;
3456 gnutls_certificate_allocate_credentials(&xcred);
3457 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3458 GNUTLS_X509_FMT_PEM);
3460 gnutls_init(&gsession, GNUTLS_CLIENT);
3461 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3462 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3463 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3464 if ((rv = gnutls_handshake(gsession)) < 0) {
3465 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3467 gnutls_error_is_fatal(rv),
3468 gnutls_strerror_name(rv));
3469 stop_tls(gsession, xcred);
3470 goto done;
3473 gnutls_credentials_type_t cred;
3474 cred = gnutls_auth_get_type(gsession);
3475 if (cred != GNUTLS_CRD_CERTIFICATE) {
3476 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3477 stop_tls(gsession, xcred);
3478 goto done;
3481 *gs = gsession;
3482 *xc = xcred;
3483 rv = 0;
3484 done:
3485 return (rv);
3489 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3490 size_t *cert_count)
3492 unsigned int len;
3493 const gnutls_datum_t *cl;
3494 gnutls_x509_crt_t *all_certs;
3495 int i, rv = 1;
3497 if (certs == NULL || cert_count == NULL)
3498 goto done;
3499 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3500 goto done;
3501 cl = gnutls_certificate_get_peers(gsession, &len);
3502 if (len == 0)
3503 goto done;
3505 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3506 for (i = 0; i < len; i++) {
3507 gnutls_x509_crt_init(&all_certs[i]);
3508 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3509 GNUTLS_X509_FMT_PEM < 0)) {
3510 g_free(all_certs);
3511 goto done;
3515 *certs = all_certs;
3516 *cert_count = len;
3517 rv = 0;
3518 done:
3519 return (rv);
3522 void
3523 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3525 int i;
3527 for (i = 0; i < cert_count; i++)
3528 gnutls_x509_crt_deinit(certs[i]);
3529 g_free(certs);
3532 void
3533 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3535 GdkColor c_text, c_base;
3537 gdk_color_parse(text, &c_text);
3538 gdk_color_parse(base, &c_base);
3540 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3541 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3542 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3543 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3545 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3546 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3547 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3548 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3551 void
3552 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3553 size_t cert_count, char *domain)
3555 size_t cert_buf_sz;
3556 char cert_buf[64 * 1024], file[PATH_MAX];
3557 int i;
3558 FILE *f;
3559 GdkColor color;
3561 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3562 return;
3564 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3565 if ((f = fopen(file, "w")) == NULL) {
3566 show_oops(t, "Can't create cert file %s %s",
3567 file, strerror(errno));
3568 return;
3571 for (i = 0; i < cert_count; i++) {
3572 cert_buf_sz = sizeof cert_buf;
3573 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3574 cert_buf, &cert_buf_sz)) {
3575 show_oops(t, "gnutls_x509_crt_export failed");
3576 goto done;
3578 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3579 show_oops(t, "Can't write certs: %s", strerror(errno));
3580 goto done;
3584 /* not the best spot but oh well */
3585 gdk_color_parse("lightblue", &color);
3586 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3587 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3588 done:
3589 fclose(f);
3592 enum cert_trust {
3593 CERT_LOCAL,
3594 CERT_TRUSTED,
3595 CERT_UNTRUSTED,
3596 CERT_BAD
3599 enum cert_trust
3600 load_compare_cert(struct tab *t, struct karg *args)
3602 const gchar *uri;
3603 char domain[8182], file[PATH_MAX];
3604 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3605 int s = -1, i, error;
3606 FILE *f = NULL;
3607 size_t cert_buf_sz, cert_count;
3608 enum cert_trust rv = CERT_UNTRUSTED;
3609 char serr[80];
3610 gnutls_session_t gsession;
3611 gnutls_x509_crt_t *certs;
3612 gnutls_certificate_credentials_t xcred;
3614 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3616 if (t == NULL)
3617 return (rv);
3619 if ((uri = get_uri(t)) == NULL)
3620 return (rv);
3621 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3623 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3624 return (rv);
3625 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3627 /* go ssl/tls */
3628 if (start_tls(t, s, &gsession, &xcred))
3629 goto done;
3630 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3632 /* verify certs in case cert file doesn't exist */
3633 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3634 GNUTLS_E_SUCCESS) {
3635 show_oops(t, "Invalid certificates");
3636 goto done;
3639 /* get certs */
3640 if (get_connection_certs(gsession, &certs, &cert_count)) {
3641 show_oops(t, "Can't get connection certificates");
3642 goto done;
3645 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3646 if ((f = fopen(file, "r")) == NULL) {
3647 if (!error)
3648 rv = CERT_TRUSTED;
3649 goto freeit;
3652 for (i = 0; i < cert_count; i++) {
3653 cert_buf_sz = sizeof cert_buf;
3654 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3655 cert_buf, &cert_buf_sz)) {
3656 goto freeit;
3658 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3659 rv = CERT_BAD; /* critical */
3660 goto freeit;
3662 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3663 rv = CERT_BAD; /* critical */
3664 goto freeit;
3666 rv = CERT_LOCAL;
3669 freeit:
3670 if (f)
3671 fclose(f);
3672 free_connection_certs(certs, cert_count);
3673 done:
3674 /* we close the socket first for speed */
3675 if (s != -1)
3676 close(s);
3678 /* only complain if we didn't save it locally */
3679 if (error && rv != CERT_LOCAL) {
3680 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3681 if (error & GNUTLS_CERT_INVALID)
3682 strlcat(serr, "invalid, ", sizeof serr);
3683 if (error & GNUTLS_CERT_REVOKED)
3684 strlcat(serr, "revoked, ", sizeof serr);
3685 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3686 strlcat(serr, "signer not found, ", sizeof serr);
3687 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3688 strlcat(serr, "not signed by CA, ", sizeof serr);
3689 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3690 strlcat(serr, "insecure algorithm, ", sizeof serr);
3691 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3692 strlcat(serr, "not activated, ", sizeof serr);
3693 if (error & GNUTLS_CERT_EXPIRED)
3694 strlcat(serr, "expired, ", sizeof serr);
3695 for (i = strlen(serr) - 1; i > 0; i--)
3696 if (serr[i] == ',') {
3697 serr[i] = '\0';
3698 break;
3700 show_oops(t, serr);
3703 stop_tls(gsession, xcred);
3705 return (rv);
3709 cert_cmd(struct tab *t, struct karg *args)
3711 const gchar *uri;
3712 char domain[8182];
3713 int s = -1;
3714 size_t cert_count;
3715 gnutls_session_t gsession;
3716 gnutls_x509_crt_t *certs;
3717 gnutls_certificate_credentials_t xcred;
3719 if (t == NULL)
3720 return (1);
3722 if (ssl_ca_file == NULL) {
3723 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3724 return (1);
3727 if ((uri = get_uri(t)) == NULL) {
3728 show_oops(t, "Invalid URI");
3729 return (1);
3732 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3733 show_oops(t, "Invalid certificate URI: %s", uri);
3734 return (1);
3737 /* go ssl/tls */
3738 if (start_tls(t, s, &gsession, &xcred))
3739 goto done;
3741 /* get certs */
3742 if (get_connection_certs(gsession, &certs, &cert_count)) {
3743 show_oops(t, "get_connection_certs failed");
3744 goto done;
3747 if (args->i & XT_SHOW)
3748 show_certs(t, certs, cert_count, "Certificate Chain");
3749 else if (args->i & XT_SAVE)
3750 save_certs(t, certs, cert_count, domain);
3752 free_connection_certs(certs, cert_count);
3753 done:
3754 /* we close the socket first for speed */
3755 if (s != -1)
3756 close(s);
3757 stop_tls(gsession, xcred);
3759 return (0);
3763 remove_cookie(int index)
3765 int i, rv = 1;
3766 GSList *cf;
3767 SoupCookie *c;
3769 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3771 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3773 for (i = 1; cf; cf = cf->next, i++) {
3774 if (i != index)
3775 continue;
3776 c = cf->data;
3777 print_cookie("remove cookie", c);
3778 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3779 rv = 0;
3780 break;
3783 soup_cookies_free(cf);
3785 return (rv);
3789 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3791 struct domain *d;
3792 char *tmp, *body;
3794 body = g_strdup("");
3796 /* p list */
3797 if (args->i & XT_WL_PERSISTENT) {
3798 tmp = body;
3799 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3800 g_free(tmp);
3801 RB_FOREACH(d, domain_list, wl) {
3802 if (d->handy == 0)
3803 continue;
3804 tmp = body;
3805 body = g_strdup_printf("%s%s<br/>", body, d->d);
3806 g_free(tmp);
3810 /* s list */
3811 if (args->i & XT_WL_SESSION) {
3812 tmp = body;
3813 body = g_strdup_printf("%s<h2>Session</h2>", body);
3814 g_free(tmp);
3815 RB_FOREACH(d, domain_list, wl) {
3816 if (d->handy == 1)
3817 continue;
3818 tmp = body;
3819 body = g_strdup_printf("%s%s<br/>", body, d->d);
3820 g_free(tmp);
3824 tmp = get_html_page(title, body, "", 0);
3825 g_free(body);
3826 if (wl == &js_wl)
3827 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3828 else
3829 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3830 g_free(tmp);
3831 return (0);
3835 wl_save(struct tab *t, struct karg *args, int js)
3837 char file[PATH_MAX];
3838 FILE *f;
3839 char *line = NULL, *lt = NULL, *dom = NULL;
3840 size_t linelen;
3841 const gchar *uri;
3842 struct karg a;
3843 struct domain *d;
3844 GSList *cf;
3845 SoupCookie *ci, *c;
3847 if (t == NULL || args == NULL)
3848 return (1);
3850 if (runtime_settings[0] == '\0')
3851 return (1);
3853 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3854 if ((f = fopen(file, "r+")) == NULL)
3855 return (1);
3857 uri = get_uri(t);
3858 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3859 if (uri == NULL || dom == NULL ||
3860 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3861 show_oops(t, "Can't add domain to %s white list",
3862 js ? "JavaScript" : "cookie");
3863 goto done;
3866 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3868 while (!feof(f)) {
3869 line = fparseln(f, &linelen, NULL, NULL, 0);
3870 if (line == NULL)
3871 continue;
3872 if (!strcmp(line, lt))
3873 goto done;
3874 free(line);
3875 line = NULL;
3878 fprintf(f, "%s\n", lt);
3880 a.i = XT_WL_ENABLE;
3881 a.i |= args->i;
3882 if (js) {
3883 d = wl_find(dom, &js_wl);
3884 if (!d) {
3885 settings_add("js_wl", dom);
3886 d = wl_find(dom, &js_wl);
3888 toggle_js(t, &a);
3889 } else {
3890 d = wl_find(dom, &c_wl);
3891 if (!d) {
3892 settings_add("cookie_wl", dom);
3893 d = wl_find(dom, &c_wl);
3895 toggle_cwl(t, &a);
3897 /* find and add to persistent jar */
3898 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3899 for (;cf; cf = cf->next) {
3900 ci = cf->data;
3901 if (!strcmp(dom, ci->domain) ||
3902 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3903 c = soup_cookie_copy(ci);
3904 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3907 soup_cookies_free(cf);
3909 if (d)
3910 d->handy = 1;
3912 done:
3913 if (line)
3914 free(line);
3915 if (dom)
3916 g_free(dom);
3917 if (lt)
3918 g_free(lt);
3919 fclose(f);
3921 return (0);
3925 js_show_wl(struct tab *t, struct karg *args)
3927 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3928 wl_show(t, args, "JavaScript White List", &js_wl);
3930 return (0);
3934 cookie_show_wl(struct tab *t, struct karg *args)
3936 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3937 wl_show(t, args, "Cookie White List", &c_wl);
3939 return (0);
3943 cookie_cmd(struct tab *t, struct karg *args)
3945 if (args->i & XT_SHOW)
3946 wl_show(t, args, "Cookie White List", &c_wl);
3947 else if (args->i & XT_WL_TOGGLE) {
3948 args->i |= XT_WL_RELOAD;
3949 toggle_cwl(t, args);
3950 } else if (args->i & XT_SAVE) {
3951 args->i |= XT_WL_RELOAD;
3952 wl_save(t, args, 0);
3953 } else if (args->i & XT_DELETE)
3954 show_oops(t, "'cookie delete' currently unimplemented");
3956 return (0);
3960 js_cmd(struct tab *t, struct karg *args)
3962 if (args->i & XT_SHOW)
3963 wl_show(t, args, "JavaScript White List", &js_wl);
3964 else if (args->i & XT_SAVE) {
3965 args->i |= XT_WL_RELOAD;
3966 wl_save(t, args, 1);
3967 } else if (args->i & XT_WL_TOGGLE) {
3968 args->i |= XT_WL_RELOAD;
3969 toggle_js(t, args);
3970 } else if (args->i & XT_DELETE)
3971 show_oops(t, "'js delete' currently unimplemented");
3973 return (0);
3977 toplevel_cmd(struct tab *t, struct karg *args)
3979 js_toggle_cb(t->js_toggle, t);
3981 return (0);
3985 add_favorite(struct tab *t, struct karg *args)
3987 char file[PATH_MAX];
3988 FILE *f;
3989 char *line = NULL;
3990 size_t urilen, linelen;
3991 const gchar *uri, *title;
3993 if (t == NULL)
3994 return (1);
3996 /* don't allow adding of xtp pages to favorites */
3997 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3998 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3999 return (1);
4002 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4003 if ((f = fopen(file, "r+")) == NULL) {
4004 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4005 return (1);
4008 title = get_title(t, FALSE);
4009 uri = get_uri(t);
4011 if (title == NULL || uri == NULL) {
4012 show_oops(t, "can't add page to favorites");
4013 goto done;
4016 urilen = strlen(uri);
4018 for (;;) {
4019 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4020 if (feof(f) || ferror(f))
4021 break;
4023 if (linelen == urilen && !strcmp(line, uri))
4024 goto done;
4026 free(line);
4027 line = NULL;
4030 fprintf(f, "\n%s\n%s", title, uri);
4031 done:
4032 if (line)
4033 free(line);
4034 fclose(f);
4036 update_favorite_tabs(NULL);
4038 return (0);
4042 can_go_back_for_real(struct tab *t)
4044 int i;
4045 WebKitWebHistoryItem *item;
4047 /* rely on webkit to make sure we can go backward when on an about page */
4048 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4049 return (webkit_web_view_can_go_forward(t->wv));
4052 /* the back/forwars list is stupid so help determine if we can go back */
4053 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4054 item != NULL;
4055 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4056 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4057 return (TRUE);
4060 return (FALSE);
4064 can_go_forward_for_real(struct tab *t)
4066 int i;
4067 WebKitWebHistoryItem *item;
4069 /* rely on webkit to make sure we can go forward when on an about page */
4070 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4071 return (webkit_web_view_can_go_forward(t->wv));
4073 /* the back/forwars list is stupid so help selecting a different item */
4074 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4075 item != NULL;
4076 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4077 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4078 return (TRUE);
4081 return (FALSE);
4084 void
4085 go_back_for_real(struct tab *t)
4087 int i;
4088 WebKitWebHistoryItem *item;
4090 /* the back/forwars list is stupid so help selecting a different item */
4091 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4092 item != NULL;
4093 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4094 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t))) {
4095 webkit_web_view_go_to_back_forward_item(t->wv, item);
4096 break;
4101 void
4102 go_forward_for_real(struct tab *t)
4104 int i;
4105 WebKitWebHistoryItem *item;
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), get_uri(t))) {
4112 webkit_web_view_go_to_back_forward_item(t->wv, item);
4113 break;
4119 navaction(struct tab *t, struct karg *args)
4121 WebKitWebHistoryItem *item;
4122 WebKitWebFrame *frame;
4124 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4125 t->tab_id, args->i);
4127 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4128 if (t->item) {
4129 if (args->i == XT_NAV_BACK)
4130 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4131 else
4132 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4133 if (item == NULL)
4134 return (XT_CB_PASSTHROUGH);
4135 webkit_web_view_go_to_back_forward_item(t->wv, item);
4136 t->item = NULL;
4137 return (XT_CB_PASSTHROUGH);
4140 switch (args->i) {
4141 case XT_NAV_BACK:
4142 marks_clear(t);
4143 go_back_for_real(t);
4144 break;
4145 case XT_NAV_FORWARD:
4146 marks_clear(t);
4147 go_forward_for_real(t);
4148 break;
4149 case XT_NAV_RELOAD:
4150 frame = webkit_web_view_get_main_frame(t->wv);
4151 webkit_web_frame_reload(frame);
4152 break;
4154 return (XT_CB_PASSTHROUGH);
4158 move(struct tab *t, struct karg *args)
4160 GtkAdjustment *adjust;
4161 double pi, si, pos, ps, upper, lower, max;
4162 double percent;
4164 switch (args->i) {
4165 case XT_MOVE_DOWN:
4166 case XT_MOVE_UP:
4167 case XT_MOVE_BOTTOM:
4168 case XT_MOVE_TOP:
4169 case XT_MOVE_PAGEDOWN:
4170 case XT_MOVE_PAGEUP:
4171 case XT_MOVE_HALFDOWN:
4172 case XT_MOVE_HALFUP:
4173 case XT_MOVE_PERCENT:
4174 adjust = t->adjust_v;
4175 break;
4176 default:
4177 adjust = t->adjust_h;
4178 break;
4181 pos = gtk_adjustment_get_value(adjust);
4182 ps = gtk_adjustment_get_page_size(adjust);
4183 upper = gtk_adjustment_get_upper(adjust);
4184 lower = gtk_adjustment_get_lower(adjust);
4185 si = gtk_adjustment_get_step_increment(adjust);
4186 pi = gtk_adjustment_get_page_increment(adjust);
4187 max = upper - ps;
4189 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4190 "max %f si %f pi %f\n",
4191 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4192 pos, ps, upper, lower, max, si, pi);
4194 switch (args->i) {
4195 case XT_MOVE_DOWN:
4196 case XT_MOVE_RIGHT:
4197 pos += si;
4198 gtk_adjustment_set_value(adjust, MIN(pos, max));
4199 break;
4200 case XT_MOVE_UP:
4201 case XT_MOVE_LEFT:
4202 pos -= si;
4203 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4204 break;
4205 case XT_MOVE_BOTTOM:
4206 case XT_MOVE_FARRIGHT:
4207 gtk_adjustment_set_value(adjust, max);
4208 break;
4209 case XT_MOVE_TOP:
4210 case XT_MOVE_FARLEFT:
4211 gtk_adjustment_set_value(adjust, lower);
4212 break;
4213 case XT_MOVE_PAGEDOWN:
4214 pos += pi;
4215 gtk_adjustment_set_value(adjust, MIN(pos, max));
4216 break;
4217 case XT_MOVE_PAGEUP:
4218 pos -= pi;
4219 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4220 break;
4221 case XT_MOVE_HALFDOWN:
4222 pos += pi / 2;
4223 gtk_adjustment_set_value(adjust, MIN(pos, max));
4224 break;
4225 case XT_MOVE_HALFUP:
4226 pos -= pi / 2;
4227 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4228 break;
4229 case XT_MOVE_PERCENT:
4230 percent = atoi(args->s) / 100.0;
4231 pos = max * percent;
4232 if (pos < 0.0 || pos > max)
4233 break;
4234 gtk_adjustment_set_value(adjust, pos);
4235 break;
4236 default:
4237 return (XT_CB_PASSTHROUGH);
4240 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4242 return (XT_CB_HANDLED);
4245 void
4246 url_set_visibility(void)
4248 struct tab *t;
4250 TAILQ_FOREACH(t, &tabs, entry)
4251 if (show_url == 0) {
4252 gtk_widget_hide(t->toolbar);
4253 focus_webview(t);
4254 } else
4255 gtk_widget_show(t->toolbar);
4258 void
4259 notebook_tab_set_visibility(void)
4261 if (show_tabs == 0) {
4262 gtk_widget_hide(tab_bar);
4263 gtk_notebook_set_show_tabs(notebook, FALSE);
4264 } else {
4265 if (tab_style == XT_TABS_NORMAL) {
4266 gtk_widget_hide(tab_bar);
4267 gtk_notebook_set_show_tabs(notebook, TRUE);
4268 } else if (tab_style == XT_TABS_COMPACT) {
4269 gtk_widget_show(tab_bar);
4270 gtk_notebook_set_show_tabs(notebook, FALSE);
4275 void
4276 statusbar_set_visibility(void)
4278 struct tab *t;
4280 TAILQ_FOREACH(t, &tabs, entry)
4281 if (show_statusbar == 0) {
4282 gtk_widget_hide(t->statusbar_box);
4283 focus_webview(t);
4284 } else
4285 gtk_widget_show(t->statusbar_box);
4288 void
4289 url_set(struct tab *t, int enable_url_entry)
4291 GdkPixbuf *pixbuf;
4292 int progress;
4294 show_url = enable_url_entry;
4296 if (enable_url_entry) {
4297 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4298 GTK_ENTRY_ICON_PRIMARY, NULL);
4299 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4300 } else {
4301 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4302 GTK_ENTRY_ICON_PRIMARY);
4303 progress =
4304 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4305 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4306 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4307 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4308 progress);
4313 fullscreen(struct tab *t, struct karg *args)
4315 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4317 if (t == NULL)
4318 return (XT_CB_PASSTHROUGH);
4320 if (show_url == 0) {
4321 url_set(t, 1);
4322 show_tabs = 1;
4323 } else {
4324 url_set(t, 0);
4325 show_tabs = 0;
4328 url_set_visibility();
4329 notebook_tab_set_visibility();
4331 return (XT_CB_HANDLED);
4335 statustoggle(struct tab *t, struct karg *args)
4337 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4339 if (show_statusbar == 1) {
4340 show_statusbar = 0;
4341 statusbar_set_visibility();
4342 } else if (show_statusbar == 0) {
4343 show_statusbar = 1;
4344 statusbar_set_visibility();
4346 return (XT_CB_HANDLED);
4350 urlaction(struct tab *t, struct karg *args)
4352 int rv = XT_CB_HANDLED;
4354 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4356 if (t == NULL)
4357 return (XT_CB_PASSTHROUGH);
4359 switch (args->i) {
4360 case XT_URL_SHOW:
4361 if (show_url == 0) {
4362 url_set(t, 1);
4363 url_set_visibility();
4365 break;
4366 case XT_URL_HIDE:
4367 if (show_url == 1) {
4368 url_set(t, 0);
4369 url_set_visibility();
4371 break;
4373 return (rv);
4377 tabaction(struct tab *t, struct karg *args)
4379 int rv = XT_CB_HANDLED;
4380 char *url = args->s;
4381 struct undo *u;
4382 struct tab *tt;
4384 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4386 if (t == NULL)
4387 return (XT_CB_PASSTHROUGH);
4389 switch (args->i) {
4390 case XT_TAB_NEW:
4391 if (strlen(url) > 0)
4392 create_new_tab(url, NULL, 1, args->precount);
4393 else
4394 create_new_tab(NULL, NULL, 1, args->precount);
4395 break;
4396 case XT_TAB_DELETE:
4397 if (args->precount < 0)
4398 delete_tab(t);
4399 else
4400 TAILQ_FOREACH(tt, &tabs, entry)
4401 if (tt->tab_id == args->precount - 1) {
4402 delete_tab(tt);
4403 break;
4405 break;
4406 case XT_TAB_DELQUIT:
4407 if (gtk_notebook_get_n_pages(notebook) > 1)
4408 delete_tab(t);
4409 else
4410 quit(t, args);
4411 break;
4412 case XT_TAB_OPEN:
4413 if (strlen(url) > 0)
4415 else {
4416 rv = XT_CB_PASSTHROUGH;
4417 goto done;
4419 load_uri(t, url);
4420 break;
4421 case XT_TAB_SHOW:
4422 if (show_tabs == 0) {
4423 show_tabs = 1;
4424 notebook_tab_set_visibility();
4426 break;
4427 case XT_TAB_HIDE:
4428 if (show_tabs == 1) {
4429 show_tabs = 0;
4430 notebook_tab_set_visibility();
4432 break;
4433 case XT_TAB_NEXTSTYLE:
4434 if (tab_style == XT_TABS_NORMAL) {
4435 tab_style = XT_TABS_COMPACT;
4436 recolor_compact_tabs();
4438 else
4439 tab_style = XT_TABS_NORMAL;
4440 notebook_tab_set_visibility();
4441 break;
4442 case XT_TAB_UNDO_CLOSE:
4443 if (undo_count == 0) {
4444 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4445 __func__);
4446 goto done;
4447 } else {
4448 undo_count--;
4449 u = TAILQ_FIRST(&undos);
4450 create_new_tab(u->uri, u, 1, -1);
4452 TAILQ_REMOVE(&undos, u, entry);
4453 g_free(u->uri);
4454 /* u->history is freed in create_new_tab() */
4455 g_free(u);
4457 break;
4458 default:
4459 rv = XT_CB_PASSTHROUGH;
4460 goto done;
4463 done:
4464 if (args->s) {
4465 g_free(args->s);
4466 args->s = NULL;
4469 return (rv);
4473 resizetab(struct tab *t, struct karg *args)
4475 if (t == NULL || args == NULL) {
4476 show_oops(NULL, "resizetab invalid parameters");
4477 return (XT_CB_PASSTHROUGH);
4480 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4481 t->tab_id, args->i);
4483 setzoom_webkit(t, args->i);
4485 return (XT_CB_HANDLED);
4489 movetab(struct tab *t, struct karg *args)
4491 int n, dest;
4493 if (t == NULL || args == NULL) {
4494 show_oops(NULL, "movetab invalid parameters");
4495 return (XT_CB_PASSTHROUGH);
4498 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4499 t->tab_id, args->i);
4501 if (args->i >= XT_TAB_INVALID)
4502 return (XT_CB_PASSTHROUGH);
4504 if (TAILQ_EMPTY(&tabs))
4505 return (XT_CB_PASSTHROUGH);
4507 n = gtk_notebook_get_n_pages(notebook);
4508 dest = gtk_notebook_get_current_page(notebook);
4510 switch (args->i) {
4511 case XT_TAB_NEXT:
4512 if (args->precount < 0)
4513 dest = dest == n - 1 ? 0 : dest + 1;
4514 else
4515 dest = args->precount - 1;
4517 break;
4518 case XT_TAB_PREV:
4519 if (args->precount < 0)
4520 dest -= 1;
4521 else
4522 dest -= args->precount % n;
4524 if (dest < 0)
4525 dest += n;
4527 break;
4528 case XT_TAB_FIRST:
4529 dest = 0;
4530 break;
4531 case XT_TAB_LAST:
4532 dest = n - 1;
4533 break;
4534 default:
4535 return (XT_CB_PASSTHROUGH);
4538 if (dest < 0 || dest >= n)
4539 return (XT_CB_PASSTHROUGH);
4540 if (t->tab_id == dest) {
4541 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4542 return (XT_CB_HANDLED);
4545 set_current_tab(dest);
4547 return (XT_CB_HANDLED);
4550 int cmd_prefix = 0;
4553 command(struct tab *t, struct karg *args)
4555 char *s = NULL, *ss = NULL;
4556 GdkColor color;
4557 const gchar *uri;
4559 if (t == NULL || args == NULL) {
4560 show_oops(NULL, "command invalid parameters");
4561 return (XT_CB_PASSTHROUGH);
4564 switch (args->i) {
4565 case '/':
4566 s = "/";
4567 break;
4568 case '?':
4569 s = "?";
4570 break;
4571 case ':':
4572 if (cmd_prefix == 0)
4573 s = ":";
4574 else {
4575 ss = g_strdup_printf(":%d", cmd_prefix);
4576 s = ss;
4577 cmd_prefix = 0;
4579 break;
4580 case XT_CMD_OPEN:
4581 s = ":open ";
4582 break;
4583 case XT_CMD_TABNEW:
4584 s = ":tabnew ";
4585 break;
4586 case XT_CMD_OPEN_CURRENT:
4587 s = ":open ";
4588 /* FALL THROUGH */
4589 case XT_CMD_TABNEW_CURRENT:
4590 if (!s) /* FALL THROUGH? */
4591 s = ":tabnew ";
4592 if ((uri = get_uri(t)) != NULL) {
4593 ss = g_strdup_printf("%s%s", s, uri);
4594 s = ss;
4596 break;
4597 default:
4598 show_oops(t, "command: invalid opcode %d", args->i);
4599 return (XT_CB_PASSTHROUGH);
4602 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4604 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4605 gdk_color_parse(XT_COLOR_WHITE, &color);
4606 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4607 show_cmd(t);
4608 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4609 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4611 if (ss)
4612 g_free(ss);
4614 return (XT_CB_HANDLED);
4618 * Return a new string with a download row (in html)
4619 * appended. Old string is freed.
4621 char *
4622 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4625 WebKitDownloadStatus stat;
4626 char *status_html = NULL, *cmd_html = NULL, *new_html;
4627 gdouble progress;
4628 char cur_sz[FMT_SCALED_STRSIZE];
4629 char tot_sz[FMT_SCALED_STRSIZE];
4630 char *xtp_prefix;
4632 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4634 /* All actions wil take this form:
4635 * xxxt://class/seskey
4637 xtp_prefix = g_strdup_printf("%s%d/%s/",
4638 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4640 stat = webkit_download_get_status(dl->download);
4642 switch (stat) {
4643 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4644 status_html = g_strdup_printf("Finished");
4645 cmd_html = g_strdup_printf(
4646 "<a href='%s%d/%d'>Remove</a>",
4647 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4648 break;
4649 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4650 /* gather size info */
4651 progress = 100 * webkit_download_get_progress(dl->download);
4653 fmt_scaled(
4654 webkit_download_get_current_size(dl->download), cur_sz);
4655 fmt_scaled(
4656 webkit_download_get_total_size(dl->download), tot_sz);
4658 status_html = g_strdup_printf(
4659 "<div style='width: 100%%' align='center'>"
4660 "<div class='progress-outer'>"
4661 "<div class='progress-inner' style='width: %.2f%%'>"
4662 "</div></div></div>"
4663 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4664 progress, cur_sz, tot_sz, progress);
4666 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4667 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4669 break;
4670 /* LLL */
4671 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4672 status_html = g_strdup_printf("Cancelled");
4673 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4674 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4675 break;
4676 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4677 status_html = g_strdup_printf("Error!");
4678 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4679 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4680 break;
4681 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4682 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4683 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4684 status_html = g_strdup_printf("Starting");
4685 break;
4686 default:
4687 show_oops(t, "%s: unknown download status", __func__);
4690 new_html = g_strdup_printf(
4691 "%s\n<tr><td>%s</td><td>%s</td>"
4692 "<td style='text-align:center'>%s</td></tr>\n",
4693 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4694 status_html, cmd_html);
4695 g_free(html);
4697 if (status_html)
4698 g_free(status_html);
4700 if (cmd_html)
4701 g_free(cmd_html);
4703 g_free(xtp_prefix);
4705 return new_html;
4709 * update all download tabs apart from one. Pass NULL if
4710 * you want to update all.
4712 void
4713 update_download_tabs(struct tab *apart_from)
4715 struct tab *t;
4716 if (!updating_dl_tabs) {
4717 updating_dl_tabs = 1; /* stop infinite recursion */
4718 TAILQ_FOREACH(t, &tabs, entry)
4719 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4720 && (t != apart_from))
4721 xtp_page_dl(t, NULL);
4722 updating_dl_tabs = 0;
4727 * update all cookie tabs apart from one. Pass NULL if
4728 * you want to update all.
4730 void
4731 update_cookie_tabs(struct tab *apart_from)
4733 struct tab *t;
4734 if (!updating_cl_tabs) {
4735 updating_cl_tabs = 1; /* stop infinite recursion */
4736 TAILQ_FOREACH(t, &tabs, entry)
4737 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4738 && (t != apart_from))
4739 xtp_page_cl(t, NULL);
4740 updating_cl_tabs = 0;
4745 * update all history tabs apart from one. Pass NULL if
4746 * you want to update all.
4748 void
4749 update_history_tabs(struct tab *apart_from)
4751 struct tab *t;
4753 if (!updating_hl_tabs) {
4754 updating_hl_tabs = 1; /* stop infinite recursion */
4755 TAILQ_FOREACH(t, &tabs, entry)
4756 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4757 && (t != apart_from))
4758 xtp_page_hl(t, NULL);
4759 updating_hl_tabs = 0;
4763 /* cookie management XTP page */
4765 xtp_page_cl(struct tab *t, struct karg *args)
4767 char *body, *page, *tmp;
4768 int i = 1; /* all ids start 1 */
4769 GSList *sc, *pc, *pc_start;
4770 SoupCookie *c;
4771 char *type, *table_headers, *last_domain;
4773 DNPRINTF(XT_D_CMD, "%s", __func__);
4775 if (t == NULL) {
4776 show_oops(NULL, "%s invalid parameters", __func__);
4777 return (1);
4780 /* Generate a new session key */
4781 if (!updating_cl_tabs)
4782 generate_xtp_session_key(&cl_session_key);
4784 /* table headers */
4785 table_headers = g_strdup_printf("<table><tr>"
4786 "<th>Type</th>"
4787 "<th>Name</th>"
4788 "<th style='width:200px'>Value</th>"
4789 "<th>Path</th>"
4790 "<th>Expires</th>"
4791 "<th>Secure</th>"
4792 "<th>HTTP<br />only</th>"
4793 "<th style='width:40px'>Rm</th></tr>\n");
4795 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4796 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4797 pc_start = pc;
4799 body = NULL;
4800 last_domain = strdup("");
4801 for (; sc; sc = sc->next) {
4802 c = sc->data;
4804 if (strcmp(last_domain, c->domain) != 0) {
4805 /* new domain */
4806 free(last_domain);
4807 last_domain = strdup(c->domain);
4809 if (body != NULL) {
4810 tmp = body;
4811 body = g_strdup_printf("%s</table>"
4812 "<h2>%s</h2>%s\n",
4813 body, c->domain, table_headers);
4814 g_free(tmp);
4815 } else {
4816 /* first domain */
4817 body = g_strdup_printf("<h2>%s</h2>%s\n",
4818 c->domain, table_headers);
4822 type = "Session";
4823 for (pc = pc_start; pc; pc = pc->next)
4824 if (soup_cookie_equal(pc->data, c)) {
4825 type = "Session + Persistent";
4826 break;
4829 tmp = body;
4830 body = g_strdup_printf(
4831 "%s\n<tr>"
4832 "<td>%s</td>"
4833 "<td style='word-wrap:normal'>%s</td>"
4834 "<td>"
4835 " <textarea rows='4'>%s</textarea>"
4836 "</td>"
4837 "<td>%s</td>"
4838 "<td>%s</td>"
4839 "<td>%d</td>"
4840 "<td>%d</td>"
4841 "<td style='text-align:center'>"
4842 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4843 body,
4844 type,
4845 c->name,
4846 c->value,
4847 c->path,
4848 c->expires ?
4849 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4850 c->secure,
4851 c->http_only,
4853 XT_XTP_STR,
4854 XT_XTP_CL,
4855 cl_session_key,
4856 XT_XTP_CL_REMOVE,
4860 g_free(tmp);
4861 i++;
4864 soup_cookies_free(sc);
4865 soup_cookies_free(pc);
4867 /* small message if there are none */
4868 if (i == 1) {
4869 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4870 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4872 tmp = body;
4873 body = g_strdup_printf("%s</table>", body);
4874 g_free(tmp);
4876 page = get_html_page("Cookie Jar", body, "", TRUE);
4877 g_free(body);
4878 g_free(table_headers);
4879 g_free(last_domain);
4881 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4882 update_cookie_tabs(t);
4884 g_free(page);
4886 return (0);
4890 xtp_page_hl(struct tab *t, struct karg *args)
4892 char *body, *page, *tmp;
4893 struct history *h;
4894 int i = 1; /* all ids start 1 */
4896 DNPRINTF(XT_D_CMD, "%s", __func__);
4898 if (t == NULL) {
4899 show_oops(NULL, "%s invalid parameters", __func__);
4900 return (1);
4903 /* Generate a new session key */
4904 if (!updating_hl_tabs)
4905 generate_xtp_session_key(&hl_session_key);
4907 /* body */
4908 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4909 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4911 RB_FOREACH_REVERSE(h, history_list, &hl) {
4912 tmp = body;
4913 body = g_strdup_printf(
4914 "%s\n<tr>"
4915 "<td><a href='%s'>%s</a></td>"
4916 "<td>%s</td>"
4917 "<td style='text-align: center'>"
4918 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4919 body, h->uri, h->uri, h->title,
4920 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4921 XT_XTP_HL_REMOVE, i);
4923 g_free(tmp);
4924 i++;
4927 /* small message if there are none */
4928 if (i == 1) {
4929 tmp = body;
4930 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4931 "colspan='3'>No History</td></tr>\n", body);
4932 g_free(tmp);
4935 tmp = body;
4936 body = g_strdup_printf("%s</table>", body);
4937 g_free(tmp);
4939 page = get_html_page("History", body, "", TRUE);
4940 g_free(body);
4943 * update all history manager tabs as the xtp session
4944 * key has now changed. No need to update the current tab.
4945 * Already did that above.
4947 update_history_tabs(t);
4949 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4950 g_free(page);
4952 return (0);
4956 * Generate a web page detailing the status of any downloads
4959 xtp_page_dl(struct tab *t, struct karg *args)
4961 struct download *dl;
4962 char *body, *page, *tmp;
4963 char *ref;
4964 int n_dl = 1;
4966 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4968 if (t == NULL) {
4969 show_oops(NULL, "%s invalid parameters", __func__);
4970 return (1);
4974 * Generate a new session key for next page instance.
4975 * This only happens for the top level call to xtp_page_dl()
4976 * in which case updating_dl_tabs is 0.
4978 if (!updating_dl_tabs)
4979 generate_xtp_session_key(&dl_session_key);
4981 /* header - with refresh so as to update */
4982 if (refresh_interval >= 1)
4983 ref = g_strdup_printf(
4984 "<meta http-equiv='refresh' content='%u"
4985 ";url=%s%d/%s/%d' />\n",
4986 refresh_interval,
4987 XT_XTP_STR,
4988 XT_XTP_DL,
4989 dl_session_key,
4990 XT_XTP_DL_LIST);
4991 else
4992 ref = g_strdup("");
4994 body = g_strdup_printf("<div align='center'>"
4995 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4996 "</p><table><tr><th style='width: 60%%'>"
4997 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4998 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5000 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5001 body = xtp_page_dl_row(t, body, dl);
5002 n_dl++;
5005 /* message if no downloads in list */
5006 if (n_dl == 1) {
5007 tmp = body;
5008 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5009 " style='text-align: center'>"
5010 "No downloads</td></tr>\n", body);
5011 g_free(tmp);
5014 tmp = body;
5015 body = g_strdup_printf("%s</table></div>", body);
5016 g_free(tmp);
5018 page = get_html_page("Downloads", body, ref, 1);
5019 g_free(ref);
5020 g_free(body);
5023 * update all download manager tabs as the xtp session
5024 * key has now changed. No need to update the current tab.
5025 * Already did that above.
5027 update_download_tabs(t);
5029 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5030 g_free(page);
5032 return (0);
5036 search(struct tab *t, struct karg *args)
5038 gboolean d;
5040 if (t == NULL || args == NULL) {
5041 show_oops(NULL, "search invalid parameters");
5042 return (1);
5044 if (t->search_text == NULL) {
5045 if (global_search == NULL)
5046 return (XT_CB_PASSTHROUGH);
5047 else {
5048 t->search_text = g_strdup(global_search);
5049 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5050 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5054 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5055 t->tab_id, args->i, t->search_forward, t->search_text);
5057 switch (args->i) {
5058 case XT_SEARCH_NEXT:
5059 d = t->search_forward;
5060 break;
5061 case XT_SEARCH_PREV:
5062 d = !t->search_forward;
5063 break;
5064 default:
5065 return (XT_CB_PASSTHROUGH);
5068 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5070 return (XT_CB_HANDLED);
5073 struct settings_args {
5074 char **body;
5075 int i;
5078 void
5079 print_setting(struct settings *s, char *val, void *cb_args)
5081 char *tmp, *color;
5082 struct settings_args *sa = cb_args;
5084 if (sa == NULL)
5085 return;
5087 if (s->flags & XT_SF_RUNTIME)
5088 color = "#22cc22";
5089 else
5090 color = "#cccccc";
5092 tmp = *sa->body;
5093 *sa->body = g_strdup_printf(
5094 "%s\n<tr>"
5095 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5096 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5097 *sa->body,
5098 color,
5099 s->name,
5100 color,
5103 g_free(tmp);
5104 sa->i++;
5108 set_show(struct tab *t, struct karg *args)
5110 char *body, *page, *tmp;
5111 int i = 1;
5112 struct settings_args sa;
5114 bzero(&sa, sizeof sa);
5115 sa.body = &body;
5117 /* body */
5118 body = g_strdup_printf("<div align='center'><table><tr>"
5119 "<th align='left'>Setting</th>"
5120 "<th align='left'>Value</th></tr>\n");
5122 settings_walk(print_setting, &sa);
5123 i = sa.i;
5125 /* small message if there are none */
5126 if (i == 1) {
5127 tmp = body;
5128 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5129 "colspan='2'>No settings</td></tr>\n", body);
5130 g_free(tmp);
5133 tmp = body;
5134 body = g_strdup_printf("%s</table></div>", body);
5135 g_free(tmp);
5137 page = get_html_page("Settings", body, "", 0);
5139 g_free(body);
5141 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5143 g_free(page);
5145 return (XT_CB_PASSTHROUGH);
5149 set(struct tab *t, struct karg *args)
5151 char *p, *val;
5152 int i;
5154 if (args == NULL || args->s == NULL)
5155 return (set_show(t, args));
5157 /* strip spaces */
5158 p = g_strstrip(args->s);
5160 if (strlen(p) == 0)
5161 return (set_show(t, args));
5163 /* we got some sort of string */
5164 val = g_strrstr(p, "=");
5165 if (val) {
5166 *val++ = '\0';
5167 val = g_strchomp(val);
5168 p = g_strchomp(p);
5170 for (i = 0; i < LENGTH(rs); i++) {
5171 if (strcmp(rs[i].name, p))
5172 continue;
5174 if (rs[i].activate) {
5175 if (rs[i].activate(val))
5176 show_oops(t, "%s invalid value %s",
5177 p, val);
5178 else
5179 show_oops(t, ":set %s = %s", p, val);
5180 goto done;
5181 } else {
5182 show_oops(t, "not a runtime option: %s", p);
5183 goto done;
5186 show_oops(t, "unknown option: %s", p);
5187 } else {
5188 p = g_strchomp(p);
5190 for (i = 0; i < LENGTH(rs); i++) {
5191 if (strcmp(rs[i].name, p))
5192 continue;
5194 /* XXX this could use some cleanup */
5195 switch (rs[i].type) {
5196 case XT_S_INT:
5197 if (rs[i].ival)
5198 show_oops(t, "%s = %d",
5199 rs[i].name, *rs[i].ival);
5200 else if (rs[i].s && rs[i].s->get)
5201 show_oops(t, "%s = %s",
5202 rs[i].name,
5203 rs[i].s->get(&rs[i]));
5204 else if (rs[i].s && rs[i].s->get == NULL)
5205 show_oops(t, "%s = ...", rs[i].name);
5206 else
5207 show_oops(t, "%s = ", rs[i].name);
5208 break;
5209 case XT_S_FLOAT:
5210 if (rs[i].fval)
5211 show_oops(t, "%s = %f",
5212 rs[i].name, *rs[i].fval);
5213 else if (rs[i].s && rs[i].s->get)
5214 show_oops(t, "%s = %s",
5215 rs[i].name,
5216 rs[i].s->get(&rs[i]));
5217 else if (rs[i].s && rs[i].s->get == NULL)
5218 show_oops(t, "%s = ...", rs[i].name);
5219 else
5220 show_oops(t, "%s = ", rs[i].name);
5221 break;
5222 case XT_S_STR:
5223 if (rs[i].sval && *rs[i].sval)
5224 show_oops(t, "%s = %s",
5225 rs[i].name, *rs[i].sval);
5226 else if (rs[i].s && rs[i].s->get)
5227 show_oops(t, "%s = %s",
5228 rs[i].name,
5229 rs[i].s->get(&rs[i]));
5230 else if (rs[i].s && rs[i].s->get == NULL)
5231 show_oops(t, "%s = ...", rs[i].name);
5232 else
5233 show_oops(t, "%s = ", rs[i].name);
5234 break;
5235 default:
5236 show_oops(t, "unknown type for %s", rs[i].name);
5237 goto done;
5240 goto done;
5242 show_oops(t, "unknown option: %s", p);
5244 done:
5245 return (XT_CB_PASSTHROUGH);
5249 session_save(struct tab *t, char *filename)
5251 struct karg a;
5252 int rv = 1;
5253 struct session *s;
5255 if (strlen(filename) == 0)
5256 goto done;
5258 if (filename[0] == '.' || filename[0] == '/')
5259 goto done;
5261 a.s = filename;
5262 if (save_tabs(t, &a))
5263 goto done;
5264 strlcpy(named_session, filename, sizeof named_session);
5266 /* add the new session to the list of sessions */
5267 s = g_malloc(sizeof(struct session));
5268 s->name = g_strdup(filename);
5269 TAILQ_INSERT_TAIL(&sessions, s, entry);
5271 rv = 0;
5272 done:
5273 return (rv);
5277 session_open(struct tab *t, char *filename)
5279 struct karg a;
5280 int rv = 1;
5282 if (strlen(filename) == 0)
5283 goto done;
5285 if (filename[0] == '.' || filename[0] == '/')
5286 goto done;
5288 a.s = filename;
5289 a.i = XT_SES_CLOSETABS;
5290 if (open_tabs(t, &a))
5291 goto done;
5293 strlcpy(named_session, filename, sizeof named_session);
5295 rv = 0;
5296 done:
5297 return (rv);
5301 session_delete(struct tab *t, char *filename)
5303 char file[PATH_MAX];
5304 int rv = 1;
5305 struct session *s;
5307 if (strlen(filename) == 0)
5308 goto done;
5310 if (filename[0] == '.' || filename[0] == '/')
5311 goto done;
5313 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5314 if (unlink(file))
5315 goto done;
5317 if (!strcmp(filename, named_session))
5318 strlcpy(named_session, XT_SAVED_TABS_FILE,
5319 sizeof named_session);
5321 /* remove session from sessions list */
5322 TAILQ_FOREACH(s, &sessions, entry) {
5323 if (!strcmp(s->name, filename))
5324 break;
5326 TAILQ_REMOVE(&sessions, s, entry);
5327 g_free((gpointer) s->name);
5328 g_free(s);
5330 rv = 0;
5331 done:
5332 return (rv);
5336 session_cmd(struct tab *t, struct karg *args)
5338 char *filename = args->s;
5340 if (t == NULL)
5341 return (1);
5343 if (args->i & XT_SHOW)
5344 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5345 XT_SAVED_TABS_FILE : named_session);
5346 else if (args->i & XT_SAVE) {
5347 if (session_save(t, filename)) {
5348 show_oops(t, "Can't save session: %s",
5349 filename ? filename : "INVALID");
5350 goto done;
5352 } else if (args->i & XT_OPEN) {
5353 if (session_open(t, filename)) {
5354 show_oops(t, "Can't open session: %s",
5355 filename ? filename : "INVALID");
5356 goto done;
5358 } else if (args->i & XT_DELETE) {
5359 if (session_delete(t, filename)) {
5360 show_oops(t, "Can't delete session: %s",
5361 filename ? filename : "INVALID");
5362 goto done;
5365 done:
5366 return (XT_CB_PASSTHROUGH);
5370 * Make a hardcopy of the page
5373 print_page(struct tab *t, struct karg *args)
5375 WebKitWebFrame *frame;
5376 GtkPageSetup *ps;
5377 GtkPrintOperation *op;
5378 GtkPrintOperationAction action;
5379 GtkPrintOperationResult print_res;
5380 GError *g_err = NULL;
5381 int marg_l, marg_r, marg_t, marg_b;
5383 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5385 ps = gtk_page_setup_new();
5386 op = gtk_print_operation_new();
5387 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5388 frame = webkit_web_view_get_main_frame(t->wv);
5390 /* the default margins are too small, so we will bump them */
5391 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5392 XT_PRINT_EXTRA_MARGIN;
5393 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5394 XT_PRINT_EXTRA_MARGIN;
5395 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5396 XT_PRINT_EXTRA_MARGIN;
5397 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5398 XT_PRINT_EXTRA_MARGIN;
5400 /* set margins */
5401 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5402 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5403 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5404 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5406 gtk_print_operation_set_default_page_setup(op, ps);
5408 /* this appears to free 'op' and 'ps' */
5409 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5411 /* check it worked */
5412 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5413 show_oops(NULL, "can't print: %s", g_err->message);
5414 g_error_free (g_err);
5415 return (1);
5418 return (0);
5422 go_home(struct tab *t, struct karg *args)
5424 load_uri(t, home);
5425 return (0);
5429 restart(struct tab *t, struct karg *args)
5431 struct karg a;
5433 a.s = XT_RESTART_TABS_FILE;
5434 save_tabs(t, &a);
5435 execvp(start_argv[0], start_argv);
5436 /* NOTREACHED */
5438 return (0);
5441 #define CTRL GDK_CONTROL_MASK
5442 #define MOD1 GDK_MOD1_MASK
5443 #define SHFT GDK_SHIFT_MASK
5445 /* inherent to GTK not all keys will be caught at all times */
5446 /* XXX sort key bindings */
5447 struct key_binding {
5448 char *cmd;
5449 guint mask;
5450 guint use_in_entry;
5451 guint key;
5452 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5453 } keys[] = {
5454 { "cookiejar", MOD1, 0, GDK_j },
5455 { "downloadmgr", MOD1, 0, GDK_d },
5456 { "history", MOD1, 0, GDK_h },
5457 { "print", CTRL, 0, GDK_p },
5458 { "search", 0, 0, GDK_slash },
5459 { "searchb", 0, 0, GDK_question },
5460 { "statustoggle", CTRL, 0, GDK_n },
5461 { "command", 0, 0, GDK_colon },
5462 { "qa", CTRL, 0, GDK_q },
5463 { "restart", MOD1, 0, GDK_q },
5464 { "js toggle", CTRL, 0, GDK_j },
5465 { "cookie toggle", MOD1, 0, GDK_c },
5466 { "togglesrc", CTRL, 0, GDK_s },
5467 { "yankuri", 0, 0, GDK_y },
5468 { "pasteuricur", 0, 0, GDK_p },
5469 { "pasteurinew", 0, 0, GDK_P },
5470 { "toplevel toggle", 0, 0, GDK_F4 },
5471 { "help", 0, 0, GDK_F1 },
5472 { "run_script", MOD1, 0, GDK_r },
5474 /* search */
5475 { "searchnext", 0, 0, GDK_n },
5476 { "searchprevious", 0, 0, GDK_N },
5478 /* focus */
5479 { "focusaddress", 0, 0, GDK_F6 },
5480 { "focussearch", 0, 0, GDK_F7 },
5482 /* hinting */
5483 { "hinting", 0, 0, GDK_f },
5485 /* custom stylesheet */
5486 { "userstyle", 0, 0, GDK_i },
5488 /* navigation */
5489 { "goback", 0, 0, GDK_BackSpace },
5490 { "goback", MOD1, 0, GDK_Left },
5491 { "goforward", SHFT, 0, GDK_BackSpace },
5492 { "goforward", MOD1, 0, GDK_Right },
5493 { "reload", 0, 0, GDK_F5 },
5494 { "reload", CTRL, 0, GDK_r },
5495 { "reload", CTRL, 0, GDK_l },
5496 { "favorites", MOD1, 1, GDK_f },
5498 /* vertical movement */
5499 { "scrolldown", 0, 0, GDK_j },
5500 { "scrolldown", 0, 0, GDK_Down },
5501 { "scrollup", 0, 0, GDK_Up },
5502 { "scrollup", 0, 0, GDK_k },
5503 { "scrollbottom", 0, 0, GDK_G },
5504 { "scrollbottom", 0, 0, GDK_End },
5505 { "scrolltop", 0, 0, GDK_Home },
5506 { "scrollpagedown", 0, 0, GDK_space },
5507 { "scrollpagedown", CTRL, 0, GDK_f },
5508 { "scrollhalfdown", CTRL, 0, GDK_d },
5509 { "scrollpagedown", 0, 0, GDK_Page_Down },
5510 { "scrollpageup", 0, 0, GDK_Page_Up },
5511 { "scrollpageup", CTRL, 0, GDK_b },
5512 { "scrollhalfup", CTRL, 0, GDK_u },
5513 /* horizontal movement */
5514 { "scrollright", 0, 0, GDK_l },
5515 { "scrollright", 0, 0, GDK_Right },
5516 { "scrollleft", 0, 0, GDK_Left },
5517 { "scrollleft", 0, 0, GDK_h },
5518 { "scrollfarright", 0, 0, GDK_dollar },
5519 { "scrollfarleft", 0, 0, GDK_0 },
5521 /* tabs */
5522 { "tabnew", CTRL, 0, GDK_t },
5523 { "999tabnew", CTRL, 0, GDK_T },
5524 { "tabclose", CTRL, 1, GDK_w },
5525 { "tabundoclose", 0, 0, GDK_U },
5526 { "tabnext 1", CTRL, 0, GDK_1 },
5527 { "tabnext 2", CTRL, 0, GDK_2 },
5528 { "tabnext 3", CTRL, 0, GDK_3 },
5529 { "tabnext 4", CTRL, 0, GDK_4 },
5530 { "tabnext 5", CTRL, 0, GDK_5 },
5531 { "tabnext 6", CTRL, 0, GDK_6 },
5532 { "tabnext 7", CTRL, 0, GDK_7 },
5533 { "tabnext 8", CTRL, 0, GDK_8 },
5534 { "tabnext 9", CTRL, 0, GDK_9 },
5535 { "tabfirst", CTRL, 0, GDK_less },
5536 { "tablast", CTRL, 0, GDK_greater },
5537 { "tabprevious", CTRL, 0, GDK_Left },
5538 { "tabnext", CTRL, 0, GDK_Right },
5539 { "focusout", CTRL, 0, GDK_minus },
5540 { "focusin", CTRL, 0, GDK_plus },
5541 { "focusin", CTRL, 0, GDK_equal },
5542 { "focusreset", CTRL, 0, GDK_0 },
5544 /* command aliases (handy when -S flag is used) */
5545 { "promptopen", 0, 0, GDK_F9 },
5546 { "promptopencurrent", 0, 0, GDK_F10 },
5547 { "prompttabnew", 0, 0, GDK_F11 },
5548 { "prompttabnewcurrent",0, 0, GDK_F12 },
5550 TAILQ_HEAD(keybinding_list, key_binding);
5552 void
5553 walk_kb(struct settings *s,
5554 void (*cb)(struct settings *, char *, void *), void *cb_args)
5556 struct key_binding *k;
5557 char str[1024];
5559 if (s == NULL || cb == NULL) {
5560 show_oops(NULL, "walk_kb invalid parameters");
5561 return;
5564 TAILQ_FOREACH(k, &kbl, entry) {
5565 if (k->cmd == NULL)
5566 continue;
5567 str[0] = '\0';
5569 /* sanity */
5570 if (gdk_keyval_name(k->key) == NULL)
5571 continue;
5573 strlcat(str, k->cmd, sizeof str);
5574 strlcat(str, ",", sizeof str);
5576 if (k->mask & GDK_SHIFT_MASK)
5577 strlcat(str, "S-", sizeof str);
5578 if (k->mask & GDK_CONTROL_MASK)
5579 strlcat(str, "C-", sizeof str);
5580 if (k->mask & GDK_MOD1_MASK)
5581 strlcat(str, "M1-", sizeof str);
5582 if (k->mask & GDK_MOD2_MASK)
5583 strlcat(str, "M2-", sizeof str);
5584 if (k->mask & GDK_MOD3_MASK)
5585 strlcat(str, "M3-", sizeof str);
5586 if (k->mask & GDK_MOD4_MASK)
5587 strlcat(str, "M4-", sizeof str);
5588 if (k->mask & GDK_MOD5_MASK)
5589 strlcat(str, "M5-", sizeof str);
5591 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5592 cb(s, str, cb_args);
5596 void
5597 init_keybindings(void)
5599 int i;
5600 struct key_binding *k;
5602 for (i = 0; i < LENGTH(keys); i++) {
5603 k = g_malloc0(sizeof *k);
5604 k->cmd = keys[i].cmd;
5605 k->mask = keys[i].mask;
5606 k->use_in_entry = keys[i].use_in_entry;
5607 k->key = keys[i].key;
5608 TAILQ_INSERT_HEAD(&kbl, k, entry);
5610 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5611 k->cmd ? k->cmd : "unnamed key");
5615 void
5616 keybinding_clearall(void)
5618 struct key_binding *k, *next;
5620 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5621 next = TAILQ_NEXT(k, entry);
5622 if (k->cmd == NULL)
5623 continue;
5625 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5626 k->cmd ? k->cmd : "unnamed key");
5627 TAILQ_REMOVE(&kbl, k, entry);
5628 g_free(k);
5633 keybinding_add(char *cmd, char *key, int use_in_entry)
5635 struct key_binding *k;
5636 guint keyval, mask = 0;
5637 int i;
5639 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5641 /* Keys which are to be used in entry have been prefixed with an
5642 * exclamation mark. */
5643 if (use_in_entry)
5644 key++;
5646 /* find modifier keys */
5647 if (strstr(key, "S-"))
5648 mask |= GDK_SHIFT_MASK;
5649 if (strstr(key, "C-"))
5650 mask |= GDK_CONTROL_MASK;
5651 if (strstr(key, "M1-"))
5652 mask |= GDK_MOD1_MASK;
5653 if (strstr(key, "M2-"))
5654 mask |= GDK_MOD2_MASK;
5655 if (strstr(key, "M3-"))
5656 mask |= GDK_MOD3_MASK;
5657 if (strstr(key, "M4-"))
5658 mask |= GDK_MOD4_MASK;
5659 if (strstr(key, "M5-"))
5660 mask |= GDK_MOD5_MASK;
5662 /* find keyname */
5663 for (i = strlen(key) - 1; i > 0; i--)
5664 if (key[i] == '-')
5665 key = &key[i + 1];
5667 /* validate keyname */
5668 keyval = gdk_keyval_from_name(key);
5669 if (keyval == GDK_VoidSymbol) {
5670 warnx("invalid keybinding name %s", key);
5671 return (1);
5673 /* must run this test too, gtk+ doesn't handle 10 for example */
5674 if (gdk_keyval_name(keyval) == NULL) {
5675 warnx("invalid keybinding name %s", key);
5676 return (1);
5679 /* Remove eventual dupes. */
5680 TAILQ_FOREACH(k, &kbl, entry)
5681 if (k->key == keyval && k->mask == mask) {
5682 TAILQ_REMOVE(&kbl, k, entry);
5683 g_free(k);
5684 break;
5687 /* add keyname */
5688 k = g_malloc0(sizeof *k);
5689 k->cmd = g_strdup(cmd);
5690 k->mask = mask;
5691 k->use_in_entry = use_in_entry;
5692 k->key = keyval;
5694 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5695 k->cmd,
5696 k->mask,
5697 k->use_in_entry,
5698 k->key);
5699 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5700 k->cmd, gdk_keyval_name(keyval));
5702 TAILQ_INSERT_HEAD(&kbl, k, entry);
5704 return (0);
5708 add_kb(struct settings *s, char *entry)
5710 char *kb, *key;
5712 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5714 /* clearall is special */
5715 if (!strcmp(entry, "clearall")) {
5716 keybinding_clearall();
5717 return (0);
5720 kb = strstr(entry, ",");
5721 if (kb == NULL)
5722 return (1);
5723 *kb = '\0';
5724 key = kb + 1;
5726 return (keybinding_add(entry, key, key[0] == '!'));
5729 struct cmd {
5730 char *cmd;
5731 int level;
5732 int (*func)(struct tab *, struct karg *);
5733 int arg;
5734 int type;
5735 } cmds[] = {
5736 { "command", 0, command, ':', 0 },
5737 { "search", 0, command, '/', 0 },
5738 { "searchb", 0, command, '?', 0 },
5739 { "togglesrc", 0, toggle_src, 0, 0 },
5741 /* yanking and pasting */
5742 { "yankuri", 0, yank_uri, 0, 0 },
5743 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5744 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5745 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5747 /* search */
5748 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5749 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5751 /* focus */
5752 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5753 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5755 /* hinting */
5756 { "hinting", 0, hint, 0, 0 },
5758 /* custom stylesheet */
5759 { "userstyle", 0, userstyle, 0, 0 },
5761 /* navigation */
5762 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5763 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5764 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5766 /* vertical movement */
5767 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5768 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5769 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5770 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5771 { "1", 0, move, XT_MOVE_TOP, 0 },
5772 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5773 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5774 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5775 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5776 /* horizontal movement */
5777 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5778 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5779 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5780 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5782 { "favorites", 0, xtp_page_fl, 0, 0 },
5783 { "fav", 0, xtp_page_fl, 0, 0 },
5784 { "favadd", 0, add_favorite, 0, 0 },
5786 { "qall", 0, quit, 0, 0 },
5787 { "quitall", 0, quit, 0, 0 },
5788 { "w", 0, save_tabs, 0, 0 },
5789 { "wq", 0, save_tabs_and_quit, 0, 0 },
5790 { "help", 0, help, 0, 0 },
5791 { "about", 0, about, 0, 0 },
5792 { "stats", 0, stats, 0, 0 },
5793 { "version", 0, about, 0, 0 },
5795 /* js command */
5796 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5797 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5798 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5799 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5800 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5801 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5802 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5803 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5804 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5805 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5806 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5808 /* cookie command */
5809 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5810 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5811 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5812 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5813 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5814 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5815 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5816 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5817 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5818 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5819 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5821 /* toplevel (domain) command */
5822 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5823 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5825 /* cookie jar */
5826 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5828 /* cert command */
5829 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5830 { "save", 1, cert_cmd, XT_SAVE, 0 },
5831 { "show", 1, cert_cmd, XT_SHOW, 0 },
5833 { "ca", 0, ca_cmd, 0, 0 },
5834 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5835 { "dl", 0, xtp_page_dl, 0, 0 },
5836 { "h", 0, xtp_page_hl, 0, 0 },
5837 { "history", 0, xtp_page_hl, 0, 0 },
5838 { "home", 0, go_home, 0, 0 },
5839 { "restart", 0, restart, 0, 0 },
5840 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5841 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5842 { "statustoggle", 0, statustoggle, 0, 0 },
5843 { "run_script", 0, run_page_script, 0, XT_USERARG },
5845 { "print", 0, print_page, 0, 0 },
5847 /* tabs */
5848 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5849 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5850 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5851 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5852 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5853 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5854 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5855 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5856 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5857 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5858 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5859 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5860 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5861 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5862 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5863 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5864 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5865 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5866 { "buffers", 0, buffers, 0, 0 },
5867 { "ls", 0, buffers, 0, 0 },
5868 { "tabs", 0, buffers, 0, 0 },
5870 /* command aliases (handy when -S flag is used) */
5871 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5872 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5873 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5874 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5876 /* settings */
5877 { "set", 0, set, 0, XT_USERARG },
5879 { "fullscreen", 0, fullscreen, 0, 0 },
5880 { "f", 0, fullscreen, 0, 0 },
5882 /* sessions */
5883 { "session", 0, session_cmd, XT_SHOW, 0 },
5884 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5885 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5886 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5887 { "show", 1, session_cmd, XT_SHOW, 0 },
5890 struct {
5891 int index;
5892 int len;
5893 gchar *list[256];
5894 } cmd_status = {-1, 0};
5896 gboolean
5897 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5900 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5901 btn_down = 0;
5903 return (FALSE);
5906 gboolean
5907 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5909 struct karg a;
5911 hide_oops(t);
5912 hide_buffers(t);
5914 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5915 btn_down = 1;
5916 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5917 /* go backward */
5918 a.i = XT_NAV_BACK;
5919 navaction(t, &a);
5921 return (TRUE);
5922 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5923 /* go forward */
5924 a.i = XT_NAV_FORWARD;
5925 navaction(t, &a);
5927 return (TRUE);
5930 return (FALSE);
5933 gboolean
5934 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5936 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5938 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5939 delete_tab(t);
5941 return (FALSE);
5945 * cancel, remove, etc. downloads
5947 void
5948 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5950 struct download find, *d = NULL;
5952 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5954 /* some commands require a valid download id */
5955 if (cmd != XT_XTP_DL_LIST) {
5956 /* lookup download in question */
5957 find.id = id;
5958 d = RB_FIND(download_list, &downloads, &find);
5960 if (d == NULL) {
5961 show_oops(t, "%s: no such download", __func__);
5962 return;
5966 /* decide what to do */
5967 switch (cmd) {
5968 case XT_XTP_DL_CANCEL:
5969 webkit_download_cancel(d->download);
5970 break;
5971 case XT_XTP_DL_REMOVE:
5972 webkit_download_cancel(d->download); /* just incase */
5973 g_object_unref(d->download);
5974 RB_REMOVE(download_list, &downloads, d);
5975 break;
5976 case XT_XTP_DL_LIST:
5977 /* Nothing */
5978 break;
5979 default:
5980 show_oops(t, "%s: unknown command", __func__);
5981 break;
5983 xtp_page_dl(t, NULL);
5987 * Actions on history, only does one thing for now, but
5988 * we provide the function for future actions
5990 void
5991 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5993 struct history *h, *next;
5994 int i = 1;
5996 switch (cmd) {
5997 case XT_XTP_HL_REMOVE:
5998 /* walk backwards, as listed in reverse */
5999 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6000 next = RB_PREV(history_list, &hl, h);
6001 if (id == i) {
6002 RB_REMOVE(history_list, &hl, h);
6003 g_free((gpointer) h->title);
6004 g_free((gpointer) h->uri);
6005 g_free(h);
6006 break;
6008 i++;
6010 break;
6011 case XT_XTP_HL_LIST:
6012 /* Nothing - just xtp_page_hl() below */
6013 break;
6014 default:
6015 show_oops(t, "%s: unknown command", __func__);
6016 break;
6019 xtp_page_hl(t, NULL);
6022 /* remove a favorite */
6023 void
6024 remove_favorite(struct tab *t, int index)
6026 char file[PATH_MAX], *title, *uri = NULL;
6027 char *new_favs, *tmp;
6028 FILE *f;
6029 int i;
6030 size_t len, lineno;
6032 /* open favorites */
6033 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6035 if ((f = fopen(file, "r")) == NULL) {
6036 show_oops(t, "%s: can't open favorites: %s",
6037 __func__, strerror(errno));
6038 return;
6041 /* build a string which will become the new favroites file */
6042 new_favs = g_strdup("");
6044 for (i = 1;;) {
6045 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6046 if (feof(f) || ferror(f))
6047 break;
6048 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6049 if (len == 0) {
6050 free(title);
6051 title = NULL;
6052 continue;
6055 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6056 if (feof(f) || ferror(f)) {
6057 show_oops(t, "%s: can't parse favorites %s",
6058 __func__, strerror(errno));
6059 goto clean;
6063 /* as long as this isn't the one we are deleting add to file */
6064 if (i != index) {
6065 tmp = new_favs;
6066 new_favs = g_strdup_printf("%s%s\n%s\n",
6067 new_favs, title, uri);
6068 g_free(tmp);
6071 free(uri);
6072 uri = NULL;
6073 free(title);
6074 title = NULL;
6075 i++;
6077 fclose(f);
6079 /* write back new favorites file */
6080 if ((f = fopen(file, "w")) == NULL) {
6081 show_oops(t, "%s: can't open favorites: %s",
6082 __func__, strerror(errno));
6083 goto clean;
6086 fwrite(new_favs, strlen(new_favs), 1, f);
6087 fclose(f);
6089 clean:
6090 if (uri)
6091 free(uri);
6092 if (title)
6093 free(title);
6095 g_free(new_favs);
6098 void
6099 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6101 switch (cmd) {
6102 case XT_XTP_FL_LIST:
6103 /* nothing, just the below call to xtp_page_fl() */
6104 break;
6105 case XT_XTP_FL_REMOVE:
6106 remove_favorite(t, arg);
6107 break;
6108 default:
6109 show_oops(t, "%s: invalid favorites command", __func__);
6110 break;
6113 xtp_page_fl(t, NULL);
6116 void
6117 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6119 switch (cmd) {
6120 case XT_XTP_CL_LIST:
6121 /* nothing, just xtp_page_cl() */
6122 break;
6123 case XT_XTP_CL_REMOVE:
6124 remove_cookie(arg);
6125 break;
6126 default:
6127 show_oops(t, "%s: unknown cookie xtp command", __func__);
6128 break;
6131 xtp_page_cl(t, NULL);
6134 /* link an XTP class to it's session key and handler function */
6135 struct xtp_despatch {
6136 uint8_t xtp_class;
6137 char **session_key;
6138 void (*handle_func)(struct tab *, uint8_t, int);
6141 struct xtp_despatch xtp_despatches[] = {
6142 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6143 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6144 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6145 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6146 { XT_XTP_INVALID, NULL, NULL }
6150 * is the url xtp protocol? (xxxt://)
6151 * if so, parse and despatch correct bahvior
6154 parse_xtp_url(struct tab *t, const char *url)
6156 char *dup = NULL, *p, *last;
6157 uint8_t n_tokens = 0;
6158 char *tokens[4] = {NULL, NULL, NULL, ""};
6159 struct xtp_despatch *dsp, *dsp_match = NULL;
6160 uint8_t req_class;
6161 int ret = FALSE;
6164 * tokens array meaning:
6165 * tokens[0] = class
6166 * tokens[1] = session key
6167 * tokens[2] = action
6168 * tokens[3] = optional argument
6171 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6173 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6174 goto clean;
6176 dup = g_strdup(url + strlen(XT_XTP_STR));
6178 /* split out the url */
6179 for ((p = strtok_r(dup, "/", &last)); p;
6180 (p = strtok_r(NULL, "/", &last))) {
6181 if (n_tokens < 4)
6182 tokens[n_tokens++] = p;
6185 /* should be atleast three fields 'class/seskey/command/arg' */
6186 if (n_tokens < 3)
6187 goto clean;
6189 dsp = xtp_despatches;
6190 req_class = atoi(tokens[0]);
6191 while (dsp->xtp_class) {
6192 if (dsp->xtp_class == req_class) {
6193 dsp_match = dsp;
6194 break;
6196 dsp++;
6199 /* did we find one atall? */
6200 if (dsp_match == NULL) {
6201 show_oops(t, "%s: no matching xtp despatch found", __func__);
6202 goto clean;
6205 /* check session key and call despatch function */
6206 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6207 ret = TRUE; /* all is well, this was a valid xtp request */
6208 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6211 clean:
6212 if (dup)
6213 g_free(dup);
6215 return (ret);
6220 void
6221 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6223 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6225 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6227 if (t == NULL) {
6228 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6229 return;
6232 if (uri == NULL) {
6233 show_oops(t, "activate_uri_entry_cb no uri");
6234 return;
6237 uri += strspn(uri, "\t ");
6239 /* if xxxt:// treat specially */
6240 if (parse_xtp_url(t, uri))
6241 return;
6243 /* otherwise continue to load page normally */
6244 load_uri(t, (gchar *)uri);
6245 focus_webview(t);
6248 void
6249 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6251 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6252 char *newuri = NULL;
6253 gchar *enc_search;
6255 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6257 if (t == NULL) {
6258 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6259 return;
6262 if (search_string == NULL) {
6263 show_oops(t, "no search_string");
6264 return;
6267 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6269 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6270 newuri = g_strdup_printf(search_string, enc_search);
6271 g_free(enc_search);
6273 marks_clear(t);
6274 webkit_web_view_load_uri(t->wv, newuri);
6275 focus_webview(t);
6277 if (newuri)
6278 g_free(newuri);
6281 void
6282 check_and_set_cookie(const gchar *uri, struct tab *t)
6284 struct domain *d = NULL;
6285 int es = 0;
6287 if (uri == NULL || t == NULL)
6288 return;
6290 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6291 es = 0;
6292 else
6293 es = 1;
6295 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6296 es ? "enable" : "disable", uri);
6298 g_object_set(G_OBJECT(t->settings),
6299 "enable-html5-local-storage", es, (char *)NULL);
6300 webkit_web_view_set_settings(t->wv, t->settings);
6303 void
6304 check_and_set_js(const gchar *uri, struct tab *t)
6306 struct domain *d = NULL;
6307 int es = 0;
6309 if (uri == NULL || t == NULL)
6310 return;
6312 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6313 es = 0;
6314 else
6315 es = 1;
6317 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6318 es ? "enable" : "disable", uri);
6320 g_object_set(G_OBJECT(t->settings),
6321 "enable-scripts", es, (char *)NULL);
6322 g_object_set(G_OBJECT(t->settings),
6323 "javascript-can-open-windows-automatically", es, (char *)NULL);
6324 webkit_web_view_set_settings(t->wv, t->settings);
6326 button_set_stockid(t->js_toggle,
6327 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6330 gboolean
6331 color_address_bar(gpointer p)
6333 GdkColor color;
6334 struct tab *tt, *t = p;
6335 gchar *col_str = XT_COLOR_YELLOW;
6337 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6339 /* make sure t still exists */
6340 if (t == NULL)
6341 goto done;
6342 TAILQ_FOREACH(tt, &tabs, entry)
6343 if (t == tt)
6344 break;
6345 if (t != tt)
6346 goto done;
6348 switch (load_compare_cert(t, NULL)) {
6349 case CERT_LOCAL:
6350 col_str = XT_COLOR_BLUE;
6351 break;
6352 case CERT_TRUSTED:
6353 col_str = XT_COLOR_GREEN;
6354 break;
6355 case CERT_UNTRUSTED:
6356 col_str = XT_COLOR_YELLOW;
6357 break;
6358 case CERT_BAD:
6359 col_str = XT_COLOR_RED;
6360 break;
6363 gdk_color_parse(col_str, &color);
6364 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6366 if (!strcmp(col_str, XT_COLOR_WHITE))
6367 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6368 else
6369 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6371 col_str = NULL;
6372 done:
6373 return (FALSE /* kill thread */);
6376 void
6377 show_ca_status(struct tab *t, const char *uri)
6379 GdkColor color;
6380 gchar *col_str = XT_COLOR_WHITE;
6382 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6383 ssl_strict_certs, ssl_ca_file, uri);
6385 if (t == NULL)
6386 return;
6388 if (uri == NULL)
6389 goto done;
6390 if (ssl_ca_file == NULL) {
6391 if (g_str_has_prefix(uri, "http://"))
6392 goto done;
6393 if (g_str_has_prefix(uri, "https://")) {
6394 col_str = XT_COLOR_RED;
6395 goto done;
6397 return;
6399 if (g_str_has_prefix(uri, "http://") ||
6400 !g_str_has_prefix(uri, "https://"))
6401 goto done;
6403 /* thread the coloring of the address bar */
6404 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6405 color_address_bar, t, NULL);
6406 return;
6408 done:
6409 if (col_str) {
6410 gdk_color_parse(col_str, &color);
6411 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6413 if (!strcmp(col_str, XT_COLOR_WHITE))
6414 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6415 else
6416 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6420 void
6421 free_favicon(struct tab *t)
6423 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6424 __func__, t->icon_download, t->icon_request);
6426 if (t->icon_request)
6427 g_object_unref(t->icon_request);
6428 if (t->icon_dest_uri)
6429 g_free(t->icon_dest_uri);
6431 t->icon_request = NULL;
6432 t->icon_dest_uri = NULL;
6435 void
6436 xt_icon_from_name(struct tab *t, gchar *name)
6438 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6439 GTK_ENTRY_ICON_PRIMARY, "text-html");
6440 if (show_url == 0)
6441 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6442 GTK_ENTRY_ICON_PRIMARY, "text-html");
6443 else
6444 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6445 GTK_ENTRY_ICON_PRIMARY, NULL);
6448 void
6449 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6451 GdkPixbuf *pb_scaled;
6453 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6454 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6455 GDK_INTERP_BILINEAR);
6456 else
6457 pb_scaled = pb;
6459 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6460 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6461 if (show_url == 0)
6462 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6463 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6464 else
6465 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6466 GTK_ENTRY_ICON_PRIMARY, NULL);
6468 if (pb_scaled != pb)
6469 g_object_unref(pb_scaled);
6472 void
6473 xt_icon_from_file(struct tab *t, char *file)
6475 GdkPixbuf *pb;
6477 if (g_str_has_prefix(file, "file://"))
6478 file += strlen("file://");
6480 pb = gdk_pixbuf_new_from_file(file, NULL);
6481 if (pb) {
6482 xt_icon_from_pixbuf(t, pb);
6483 g_object_unref(pb);
6484 } else
6485 xt_icon_from_name(t, "text-html");
6488 gboolean
6489 is_valid_icon(char *file)
6491 gboolean valid = 0;
6492 const char *mime_type;
6493 GFileInfo *fi;
6494 GFile *gf;
6496 gf = g_file_new_for_path(file);
6497 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6498 NULL, NULL);
6499 mime_type = g_file_info_get_content_type(fi);
6500 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6501 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6502 g_strcmp0(mime_type, "image/png") == 0 ||
6503 g_strcmp0(mime_type, "image/gif") == 0 ||
6504 g_strcmp0(mime_type, "application/octet-stream") == 0;
6505 g_object_unref(fi);
6506 g_object_unref(gf);
6508 return (valid);
6511 void
6512 set_favicon_from_file(struct tab *t, char *file)
6514 struct stat sb;
6516 if (t == NULL || file == NULL)
6517 return;
6519 if (g_str_has_prefix(file, "file://"))
6520 file += strlen("file://");
6521 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6523 if (!stat(file, &sb)) {
6524 if (sb.st_size == 0 || !is_valid_icon(file)) {
6525 /* corrupt icon so trash it */
6526 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6527 __func__, file);
6528 unlink(file);
6529 /* no need to set icon to default here */
6530 return;
6533 xt_icon_from_file(t, file);
6536 void
6537 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6538 WebKitWebView *wv)
6540 WebKitDownloadStatus status = webkit_download_get_status(download);
6541 struct tab *tt = NULL, *t = NULL;
6544 * find the webview instead of passing in the tab as it could have been
6545 * deleted from underneath us.
6547 TAILQ_FOREACH(tt, &tabs, entry) {
6548 if (tt->wv == wv) {
6549 t = tt;
6550 break;
6553 if (t == NULL)
6554 return;
6556 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6557 __func__, t->tab_id, status);
6559 switch (status) {
6560 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6561 /* -1 */
6562 t->icon_download = NULL;
6563 free_favicon(t);
6564 break;
6565 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6566 /* 0 */
6567 break;
6568 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6569 /* 1 */
6570 break;
6571 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6572 /* 2 */
6573 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6574 __func__, t->tab_id);
6575 t->icon_download = NULL;
6576 free_favicon(t);
6577 break;
6578 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6579 /* 3 */
6581 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6582 __func__, t->icon_dest_uri);
6583 set_favicon_from_file(t, t->icon_dest_uri);
6584 /* these will be freed post callback */
6585 t->icon_request = NULL;
6586 t->icon_download = NULL;
6587 break;
6588 default:
6589 break;
6593 void
6594 abort_favicon_download(struct tab *t)
6596 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6598 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6599 if (t->icon_download) {
6600 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6601 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6602 webkit_download_cancel(t->icon_download);
6603 t->icon_download = NULL;
6604 } else
6605 free_favicon(t);
6606 #endif
6608 xt_icon_from_name(t, "text-html");
6611 void
6612 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6614 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6616 if (uri == NULL || t == NULL)
6617 return;
6619 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6620 /* take icon from WebKitIconDatabase */
6621 GdkPixbuf *pb;
6623 pb = webkit_web_view_get_icon_pixbuf(wv);
6624 if (pb) {
6625 xt_icon_from_pixbuf(t, pb);
6626 g_object_unref(pb);
6627 } else
6628 xt_icon_from_name(t, "text-html");
6629 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6630 /* download icon to cache dir */
6631 gchar *name_hash, file[PATH_MAX];
6632 struct stat sb;
6634 if (t->icon_request) {
6635 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6636 return;
6639 /* check to see if we got the icon in cache */
6640 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6641 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6642 g_free(name_hash);
6644 if (!stat(file, &sb)) {
6645 if (sb.st_size > 0) {
6646 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6647 __func__, file);
6648 set_favicon_from_file(t, file);
6649 return;
6652 /* corrupt icon so trash it */
6653 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6654 __func__, file);
6655 unlink(file);
6658 /* create download for icon */
6659 t->icon_request = webkit_network_request_new(uri);
6660 if (t->icon_request == NULL) {
6661 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6662 __func__, uri);
6663 return;
6666 t->icon_download = webkit_download_new(t->icon_request);
6667 if (t->icon_download == NULL)
6668 return;
6670 /* we have to free icon_dest_uri later */
6671 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6672 webkit_download_set_destination_uri(t->icon_download,
6673 t->icon_dest_uri);
6675 if (webkit_download_get_status(t->icon_download) ==
6676 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6677 g_object_unref(t->icon_request);
6678 g_free(t->icon_dest_uri);
6679 t->icon_request = NULL;
6680 t->icon_dest_uri = NULL;
6681 return;
6684 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6685 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6687 webkit_download_start(t->icon_download);
6688 #endif
6691 void
6692 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6694 const gchar *uri = NULL, *title = NULL;
6695 struct history *h, find;
6696 struct karg a;
6697 GdkColor color;
6699 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6700 webkit_web_view_get_load_status(wview),
6701 get_uri(t) ? get_uri(t) : "NOTHING");
6703 if (t == NULL) {
6704 show_oops(NULL, "notify_load_status_cb invalid parameters");
6705 return;
6708 switch (webkit_web_view_get_load_status(wview)) {
6709 case WEBKIT_LOAD_PROVISIONAL:
6710 /* 0 */
6711 abort_favicon_download(t);
6712 #if GTK_CHECK_VERSION(2, 20, 0)
6713 gtk_widget_show(t->spinner);
6714 gtk_spinner_start(GTK_SPINNER(t->spinner));
6715 #endif
6716 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6718 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6720 /* assume we are a new address */
6721 gdk_color_parse("white", &color);
6722 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6723 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6725 /* take focus if we are visible */
6726 focus_webview(t);
6727 t->focus_wv = 1;
6729 break;
6731 case WEBKIT_LOAD_COMMITTED:
6732 /* 1 */
6733 uri = get_uri(t);
6734 if (uri == NULL)
6735 return;
6736 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6738 if (t->status) {
6739 g_free(t->status);
6740 t->status = NULL;
6742 set_status(t, (char *)uri, XT_STATUS_LOADING);
6744 /* check if js white listing is enabled */
6745 if (enable_cookie_whitelist)
6746 check_and_set_cookie(uri, t);
6747 if (enable_js_whitelist)
6748 check_and_set_js(uri, t);
6750 if (t->styled)
6751 apply_style(t);
6754 /* we know enough to autosave the session */
6755 if (session_autosave) {
6756 a.s = NULL;
6757 save_tabs(t, &a);
6760 show_ca_status(t, uri);
6761 break;
6763 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6764 /* 3 */
6765 break;
6767 case WEBKIT_LOAD_FINISHED:
6768 /* 2 */
6769 uri = get_uri(t);
6770 if (uri == NULL)
6771 return;
6773 if (!strncmp(uri, "http://", strlen("http://")) ||
6774 !strncmp(uri, "https://", strlen("https://")) ||
6775 !strncmp(uri, "file://", strlen("file://"))) {
6776 find.uri = uri;
6777 h = RB_FIND(history_list, &hl, &find);
6778 if (!h) {
6779 title = get_title(t, FALSE);
6780 h = g_malloc(sizeof *h);
6781 h->uri = g_strdup(uri);
6782 h->title = g_strdup(title);
6783 RB_INSERT(history_list, &hl, h);
6784 completion_add_uri(h->uri);
6785 update_history_tabs(NULL);
6789 set_status(t, (char *)uri, XT_STATUS_URI);
6790 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6791 case WEBKIT_LOAD_FAILED:
6792 /* 4 */
6793 #endif
6794 #if GTK_CHECK_VERSION(2, 20, 0)
6795 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6796 gtk_widget_hide(t->spinner);
6797 #endif
6798 default:
6799 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6800 break;
6803 if (t->item)
6804 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6805 else
6806 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6807 can_go_back_for_real(t));
6809 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6810 can_go_forward_for_real(t));
6813 #if 0
6814 gboolean
6815 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6816 gchar *uri, gpointer web_error,struct tab *t)
6819 * XXX this function is wrong
6820 * it overwrites perfectly good urls with garbage on load errors
6821 * those happen often when popups fail to resolve dns
6823 if (t->tmp_uri)
6824 g_free(t->tmp_uri);
6825 t->tmp_uri = g_strdup(uri);
6826 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6827 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6828 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6829 set_status(t, uri, XT_STATUS_NOTHING);
6831 return (FALSE);
6833 #endif
6835 void
6836 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6838 const gchar *title = NULL, *win_title = NULL;
6840 title = get_title(t, FALSE);
6841 win_title = get_title(t, TRUE);
6842 gtk_label_set_text(GTK_LABEL(t->label), title);
6843 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6844 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6845 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6848 void
6849 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6851 run_script(t, JS_HINTING);
6854 void
6855 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6857 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6858 progress == 100 ? 0 : (double)progress / 100);
6859 if (show_url == 0) {
6860 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6861 progress == 100 ? 0 : (double)progress / 100);
6864 update_statusbar_position(NULL, NULL);
6868 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6869 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6870 WebKitWebPolicyDecision *pd, struct tab *t)
6872 char *uri;
6873 WebKitWebNavigationReason reason;
6874 struct domain *d = NULL;
6876 if (t == NULL) {
6877 show_oops(NULL, "webview_npd_cb invalid parameters");
6878 return (FALSE);
6881 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6882 t->ctrl_click,
6883 webkit_network_request_get_uri(request));
6885 uri = (char *)webkit_network_request_get_uri(request);
6887 /* if this is an xtp url, we don't load anything else */
6888 if (parse_xtp_url(t, uri))
6889 return (TRUE);
6891 if (t->ctrl_click) {
6892 t->ctrl_click = 0;
6893 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6894 webkit_web_policy_decision_ignore(pd);
6895 return (TRUE); /* we made the decission */
6899 * This is a little hairy but it comes down to this:
6900 * when we run in whitelist mode we have to assist the browser in
6901 * opening the URL that it would have opened in a new tab.
6903 reason = webkit_web_navigation_action_get_reason(na);
6904 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6905 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6906 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6907 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6908 load_uri(t, uri);
6909 webkit_web_policy_decision_use(pd);
6910 return (TRUE); /* we made the decision */
6913 return (FALSE);
6916 WebKitWebView *
6917 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6919 struct tab *tt;
6920 struct domain *d = NULL;
6921 const gchar *uri;
6922 WebKitWebView *webview = NULL;
6924 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6925 webkit_web_view_get_uri(wv));
6927 if (tabless) {
6928 /* open in current tab */
6929 webview = t->wv;
6930 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6931 uri = webkit_web_view_get_uri(wv);
6932 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6933 return (NULL);
6935 tt = create_new_tab(NULL, NULL, 1, -1);
6936 webview = tt->wv;
6937 } else if (enable_scripts == 1) {
6938 tt = create_new_tab(NULL, NULL, 1, -1);
6939 webview = tt->wv;
6942 return (webview);
6945 gboolean
6946 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6948 const gchar *uri;
6949 struct domain *d = NULL;
6951 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6953 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6954 uri = webkit_web_view_get_uri(wv);
6955 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6956 return (FALSE);
6958 delete_tab(t);
6959 } else if (enable_scripts == 1)
6960 delete_tab(t);
6962 return (TRUE);
6966 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6968 /* we can not eat the event without throwing gtk off so defer it */
6970 /* catch middle click */
6971 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6972 t->ctrl_click = 1;
6973 goto done;
6976 /* catch ctrl click */
6977 if (e->type == GDK_BUTTON_RELEASE &&
6978 CLEAN(e->state) == GDK_CONTROL_MASK)
6979 t->ctrl_click = 1;
6980 else
6981 t->ctrl_click = 0;
6982 done:
6983 return (XT_CB_PASSTHROUGH);
6987 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6989 struct mime_type *m;
6991 m = find_mime_type(mime_type);
6992 if (m == NULL)
6993 return (1);
6994 if (m->mt_download)
6995 return (1);
6997 switch (fork()) {
6998 case -1:
6999 show_oops(t, "can't fork mime handler");
7000 return (1);
7001 /* NOTREACHED */
7002 case 0:
7003 break;
7004 default:
7005 return (0);
7008 /* child */
7009 execlp(m->mt_action, m->mt_action,
7010 webkit_network_request_get_uri(request), (void *)NULL);
7012 _exit(0);
7014 /* NOTREACHED */
7015 return (0);
7018 const gchar *
7019 get_mime_type(char *file)
7021 const char *mime_type;
7022 GFileInfo *fi;
7023 GFile *gf;
7025 if (g_str_has_prefix(file, "file://"))
7026 file += strlen("file://");
7028 gf = g_file_new_for_path(file);
7029 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7030 NULL, NULL);
7031 mime_type = g_file_info_get_content_type(fi);
7032 g_object_unref(fi);
7033 g_object_unref(gf);
7035 return (mime_type);
7039 run_download_mimehandler(char *mime_type, char *file)
7041 struct mime_type *m;
7043 m = find_mime_type(mime_type);
7044 if (m == NULL)
7045 return (1);
7047 switch (fork()) {
7048 case -1:
7049 show_oops(NULL, "can't fork download mime handler");
7050 return (1);
7051 /* NOTREACHED */
7052 case 0:
7053 break;
7054 default:
7055 return (0);
7058 /* child */
7059 if (g_str_has_prefix(file, "file://"))
7060 file += strlen("file://");
7061 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7063 _exit(0);
7065 /* NOTREACHED */
7066 return (0);
7069 void
7070 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7071 WebKitWebView *wv)
7073 WebKitDownloadStatus status;
7074 const gchar *file = NULL, *mime = NULL;
7076 if (download == NULL)
7077 return;
7078 status = webkit_download_get_status(download);
7079 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7080 return;
7082 file = webkit_download_get_destination_uri(download);
7083 if (file == NULL)
7084 return;
7085 mime = get_mime_type((char *)file);
7086 if (mime == NULL)
7087 return;
7089 run_download_mimehandler((char *)mime, (char *)file);
7093 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7094 WebKitNetworkRequest *request, char *mime_type,
7095 WebKitWebPolicyDecision *decision, struct tab *t)
7097 if (t == NULL) {
7098 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7099 return (FALSE);
7102 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7103 t->tab_id, mime_type);
7105 if (run_mimehandler(t, mime_type, request) == 0) {
7106 webkit_web_policy_decision_ignore(decision);
7107 focus_webview(t);
7108 return (TRUE);
7111 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7112 webkit_web_policy_decision_download(decision);
7113 return (TRUE);
7116 return (FALSE);
7120 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7121 struct tab *t)
7123 struct stat sb;
7124 const gchar *suggested_name;
7125 gchar *filename = NULL;
7126 char *uri = NULL;
7127 struct download *download_entry;
7128 int i, ret = TRUE;
7130 if (wk_download == NULL || t == NULL) {
7131 show_oops(NULL, "%s invalid parameters", __func__);
7132 return (FALSE);
7135 suggested_name = webkit_download_get_suggested_filename(wk_download);
7136 if (suggested_name == NULL)
7137 return (FALSE); /* abort download */
7139 i = 0;
7140 do {
7141 if (filename) {
7142 g_free(filename);
7143 filename = NULL;
7145 if (i) {
7146 g_free(uri);
7147 uri = NULL;
7148 filename = g_strdup_printf("%d%s", i, suggested_name);
7150 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7151 filename : suggested_name);
7152 i++;
7153 } while (!stat(uri + strlen("file://"), &sb));
7155 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7156 "local %s\n", __func__, t->tab_id, filename, uri);
7158 webkit_download_set_destination_uri(wk_download, uri);
7160 if (webkit_download_get_status(wk_download) ==
7161 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7162 show_oops(t, "%s: download failed to start", __func__);
7163 ret = FALSE;
7164 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7165 } else {
7166 /* connect "download first" mime handler */
7167 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7168 G_CALLBACK(download_status_changed_cb), NULL);
7170 download_entry = g_malloc(sizeof(struct download));
7171 download_entry->download = wk_download;
7172 download_entry->tab = t;
7173 download_entry->id = next_download_id++;
7174 RB_INSERT(download_list, &downloads, download_entry);
7175 /* get from history */
7176 g_object_ref(wk_download);
7177 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7178 show_oops(t, "Download of '%s' started...",
7179 basename((char *)webkit_download_get_destination_uri(wk_download)));
7182 if (uri)
7183 g_free(uri);
7185 if (filename)
7186 g_free(filename);
7188 /* sync other download manager tabs */
7189 update_download_tabs(NULL);
7192 * NOTE: never redirect/render the current tab before this
7193 * function returns. This will cause the download to never start.
7195 return (ret); /* start download */
7198 void
7199 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7201 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7203 if (t == NULL) {
7204 show_oops(NULL, "webview_hover_cb");
7205 return;
7208 if (uri)
7209 set_status(t, uri, XT_STATUS_LINK);
7210 else {
7211 if (t->status)
7212 set_status(t, t->status, XT_STATUS_NOTHING);
7217 mark(struct tab *t, struct karg *arg)
7219 char mark;
7220 int index;
7222 mark = arg->s[1];
7223 if ((index = marktoindex(mark)) == -1)
7224 return -1;
7226 if (arg->i == XT_MARK_SET)
7227 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7228 else if (arg->i == XT_MARK_GOTO) {
7229 if (t->mark[index] == XT_INVALID_MARK) {
7230 show_oops(t, "mark '%c' does not exist", mark);
7231 return -1;
7233 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7234 something changes the document size */
7235 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7238 return 0;
7241 void
7242 marks_clear(struct tab *t)
7244 int i;
7246 for (i = 0; i < LENGTH(t->mark); i++)
7247 t->mark[i] = XT_INVALID_MARK;
7251 qmarks_load(void)
7253 char file[PATH_MAX];
7254 char *line = NULL, *p, mark;
7255 int index, i;
7256 FILE *f;
7257 size_t linelen;
7259 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7260 if ((f = fopen(file, "r+")) == NULL) {
7261 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7262 return (1);
7265 for (i = 1; ; i++) {
7266 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7267 if (feof(f) || ferror(f))
7268 break;
7269 if (strlen(line) == 0 || line[0] == '#') {
7270 free(line);
7271 line = NULL;
7272 continue;
7275 p = strtok(line, " \t");
7277 if (p == NULL || strlen(p) != 1 ||
7278 (index = marktoindex(*p)) == -1) {
7279 warnx("corrupt quickmarks file, line %d", i);
7280 break;
7283 mark = *p;
7284 p = strtok(NULL, " \t");
7285 if (qmarks[index] != NULL)
7286 g_free(qmarks[index]);
7287 qmarks[index] = g_strdup(p);
7290 fclose(f);
7292 return (0);
7296 qmarks_save(void)
7298 char file[PATH_MAX];
7299 int i;
7300 FILE *f;
7302 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7303 if ((f = fopen(file, "r+")) == NULL) {
7304 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7305 return (1);
7308 for (i = 0; i < XT_NOMARKS; i++)
7309 if (qmarks[i] != NULL)
7310 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7312 fclose(f);
7314 return (0);
7318 qmark(struct tab *t, struct karg *arg)
7320 char mark;
7321 int index;
7323 mark = arg->s[strlen(arg->s)-1];
7324 index = marktoindex(mark);
7325 if (index == -1)
7326 return (-1);
7328 switch (arg->i) {
7329 case XT_QMARK_SET:
7330 if (qmarks[index] != NULL)
7331 g_free(qmarks[index]);
7333 qmarks_load(); /* sync if multiple instances */
7334 qmarks[index] = g_strdup(get_uri(t));
7335 qmarks_save();
7336 break;
7337 case XT_QMARK_OPEN:
7338 if (qmarks[index] != NULL)
7339 load_uri(t, qmarks[index]);
7340 else {
7341 show_oops(t, "quickmark \"%c\" does not exist",
7342 mark);
7343 return (-1);
7345 break;
7346 case XT_QMARK_TAB:
7347 if (qmarks[index] != NULL)
7348 create_new_tab(qmarks[index], NULL, 1, -1);
7349 else {
7350 show_oops(t, "quickmark \"%c\" does not exist",
7351 mark);
7352 return (-1);
7354 break;
7357 return (0);
7361 go_up(struct tab *t, struct karg *args)
7363 int levels;
7364 char *uri;
7365 char *tmp;
7367 levels = atoi(args->s);
7368 if (levels == 0)
7369 levels = 1;
7371 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7372 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7373 return 1;
7374 tmp += strlen(XT_PROTO_DELIM);
7376 /* if an uri starts with a slash, leave it alone (for file:///) */
7377 if (tmp[0] == '/')
7378 tmp++;
7380 while (levels--) {
7381 char *p;
7383 p = strrchr(tmp, '/');
7384 if (p != NULL)
7385 *p = '\0';
7386 else
7387 break;
7390 load_uri(t, uri);
7391 g_free(uri);
7393 return (0);
7397 gototab(struct tab *t, struct karg *args)
7399 int tab;
7400 struct karg arg = {0, NULL, -1};
7402 tab = atoi(args->s);
7404 arg.i = XT_TAB_NEXT;
7405 arg.precount = tab;
7407 movetab(t, &arg);
7409 return (0);
7413 zoom_amount(struct tab *t, struct karg *arg)
7415 struct karg narg = {0, NULL, -1};
7417 narg.i = atoi(arg->s);
7418 resizetab(t, &narg);
7420 return 0;
7424 flip_colon(struct tab *t, struct karg *arg)
7426 struct karg narg = {0, NULL, -1};
7427 char *p;
7429 if (t == NULL || arg == NULL)
7430 return (1);
7432 p = strstr(arg->s, ":");
7433 if (p == NULL)
7434 return (1);
7435 *p = '\0';
7437 narg.i = ':';
7438 narg.s = arg->s;
7439 command(t, &narg);
7441 return (0);
7444 /* buffer commands receive the regex that triggered them in arg.s */
7445 char bcmd[XT_BUFCMD_SZ];
7446 struct buffercmd {
7447 char *regex;
7448 int precount;
7449 #define XT_PRE_NO (0)
7450 #define XT_PRE_YES (1)
7451 #define XT_PRE_MAYBE (2)
7452 char *cmd;
7453 int (*func)(struct tab *, struct karg *);
7454 int arg;
7455 regex_t cregex;
7456 } buffercmds[] = {
7457 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7458 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7459 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7460 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7461 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7462 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7463 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7464 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7465 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7466 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7467 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7468 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7469 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7470 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7471 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7472 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7473 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7474 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7477 void
7478 buffercmd_init(void)
7480 int i;
7482 for (i = 0; i < LENGTH(buffercmds); i++)
7483 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7484 REG_EXTENDED | REG_NOSUB))
7485 startpage_add("invalid buffercmd regex %s",
7486 buffercmds[i].regex);
7489 void
7490 buffercmd_abort(struct tab *t)
7492 int i;
7494 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7495 for (i = 0; i < LENGTH(bcmd); i++)
7496 bcmd[i] = '\0';
7498 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7499 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7502 void
7503 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7505 struct karg arg = {0, NULL, -1};
7507 arg.i = cmd->arg;
7508 arg.s = g_strdup(bcmd);
7510 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7511 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7512 cmd->func(t, &arg);
7514 if (arg.s)
7515 g_free(arg.s);
7517 buffercmd_abort(t);
7520 gboolean
7521 buffercmd_addkey(struct tab *t, guint keyval)
7523 int i, c, match ;
7524 char s[XT_BUFCMD_SZ];
7526 if (keyval == GDK_Escape) {
7527 buffercmd_abort(t);
7528 return (XT_CB_HANDLED);
7531 /* key with modifier or non-ascii character */
7532 if (!isascii(keyval))
7533 return (XT_CB_PASSTHROUGH);
7535 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7536 "to buffer \"%s\"\n", keyval, bcmd);
7538 for (i = 0; i < LENGTH(bcmd); i++)
7539 if (bcmd[i] == '\0') {
7540 bcmd[i] = keyval;
7541 break;
7544 /* buffer full, ignore input */
7545 if (i >= LENGTH(bcmd) -1) {
7546 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7547 buffercmd_abort(t);
7548 return (XT_CB_HANDLED);
7551 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7553 /* find exact match */
7554 for (i = 0; i < LENGTH(buffercmds); i++)
7555 if (regexec(&buffercmds[i].cregex, bcmd,
7556 (size_t) 0, NULL, 0) == 0) {
7557 buffercmd_execute(t, &buffercmds[i]);
7558 goto done;
7561 /* find non exact matches to see if we need to abort ot not */
7562 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7563 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7564 c = -1;
7565 s[0] = '\0';
7566 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7567 if (isdigit(bcmd[0])) {
7568 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7569 continue;
7570 } else {
7571 c = 0;
7572 if (sscanf(bcmd, "%s", s) == 0)
7573 continue;
7575 } else if (buffercmds[i].precount == XT_PRE_YES) {
7576 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7577 continue;
7578 } else {
7579 if (sscanf(bcmd, "%s", s) == 0)
7580 continue;
7582 if (c == -1 && buffercmds[i].precount)
7583 continue;
7584 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7585 match++;
7587 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7588 i, match, buffercmds[i].cmd, c, s);
7590 if (match == 0) {
7591 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7592 buffercmd_abort(t);
7595 done:
7596 return (XT_CB_HANDLED);
7599 gboolean
7600 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7602 struct key_binding *k;
7604 /* handle keybindings if buffercmd is empty.
7605 if not empty, allow commands like C-n */
7606 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7607 TAILQ_FOREACH(k, &kbl, entry)
7608 if (e->keyval == k->key
7609 && (entry ? k->use_in_entry : 1)) {
7610 if (k->mask == 0) {
7611 if ((e->state & (CTRL | MOD1)) == 0)
7612 return (cmd_execute(t, k->cmd));
7613 } else if ((e->state & k->mask) == k->mask) {
7614 return (cmd_execute(t, k->cmd));
7618 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7619 return buffercmd_addkey(t, e->keyval);
7621 return (XT_CB_PASSTHROUGH);
7625 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7627 char s[2], buf[128];
7628 const char *errstr = NULL;
7630 /* don't use w directly; use t->whatever instead */
7632 if (t == NULL) {
7633 show_oops(NULL, "wv_keypress_after_cb");
7634 return (XT_CB_PASSTHROUGH);
7637 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7638 e->keyval, e->state, t);
7640 if (t->hints_on) {
7641 /* ESC */
7642 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7643 disable_hints(t);
7644 return (XT_CB_HANDLED);
7647 /* RETURN */
7648 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7649 if (errstr) {
7650 /* we have a string */
7651 } else {
7652 /* we have a number */
7653 snprintf(buf, sizeof buf,
7654 "vimprobable_fire(%s)", t->hint_num);
7655 run_script(t, buf);
7657 disable_hints(t);
7660 /* BACKSPACE */
7661 /* XXX unfuck this */
7662 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7663 if (t->hint_mode == XT_HINT_NUMERICAL) {
7664 /* last input was numerical */
7665 int l;
7666 l = strlen(t->hint_num);
7667 if (l > 0) {
7668 l--;
7669 if (l == 0) {
7670 disable_hints(t);
7671 enable_hints(t);
7672 } else {
7673 t->hint_num[l] = '\0';
7674 goto num;
7677 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7678 /* last input was alphanumerical */
7679 int l;
7680 l = strlen(t->hint_buf);
7681 if (l > 0) {
7682 l--;
7683 if (l == 0) {
7684 disable_hints(t);
7685 enable_hints(t);
7686 } else {
7687 t->hint_buf[l] = '\0';
7688 goto anum;
7691 } else {
7692 /* bogus */
7693 disable_hints(t);
7697 /* numerical input */
7698 if (CLEAN(e->state) == 0 &&
7699 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7700 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7701 snprintf(s, sizeof s, "%c", e->keyval);
7702 strlcat(t->hint_num, s, sizeof t->hint_num);
7703 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7704 t->hint_num);
7705 num:
7706 if (errstr) {
7707 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7708 "invalid link number\n");
7709 disable_hints(t);
7710 } else {
7711 snprintf(buf, sizeof buf,
7712 "vimprobable_update_hints(%s)",
7713 t->hint_num);
7714 t->hint_mode = XT_HINT_NUMERICAL;
7715 run_script(t, buf);
7718 /* empty the counter buffer */
7719 bzero(t->hint_buf, sizeof t->hint_buf);
7720 return (XT_CB_HANDLED);
7723 /* alphanumerical input */
7724 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7725 e->keyval <= GDK_z) ||
7726 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7727 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7728 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7729 e->keyval <= GDK_9) ||
7730 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7731 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7732 snprintf(s, sizeof s, "%c", e->keyval);
7733 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7734 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7735 " %s\n", t->hint_buf);
7736 anum:
7737 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7738 run_script(t, buf);
7740 snprintf(buf, sizeof buf,
7741 "vimprobable_show_hints('%s')", t->hint_buf);
7742 t->hint_mode = XT_HINT_ALPHANUM;
7743 run_script(t, buf);
7745 /* empty the counter buffer */
7746 bzero(t->hint_num, sizeof t->hint_num);
7747 return (XT_CB_HANDLED);
7750 return (XT_CB_HANDLED);
7751 } else {
7752 /* prefix input*/
7753 snprintf(s, sizeof s, "%c", e->keyval);
7754 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7755 cmd_prefix = 10 * cmd_prefix + atoi(s);
7758 return (handle_keypress(t, e, 0));
7762 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7764 hide_oops(t);
7766 /* Hide buffers, if they are visible, with escape. */
7767 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7768 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7769 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7770 hide_buffers(t);
7771 return (XT_CB_HANDLED);
7774 return (XT_CB_PASSTHROUGH);
7777 gboolean
7778 search_continue(struct tab *t)
7780 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7781 gboolean rv = FALSE;
7783 if (c[0] == ':')
7784 goto done;
7785 if (strlen(c) == 1) {
7786 webkit_web_view_unmark_text_matches(t->wv);
7787 goto done;
7790 if (c[0] == '/')
7791 t->search_forward = TRUE;
7792 else if (c[0] == '?')
7793 t->search_forward = FALSE;
7794 else
7795 goto done;
7797 rv = TRUE;
7798 done:
7799 return (rv);
7802 gboolean
7803 search_cb(struct tab *t)
7805 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7806 GdkColor color;
7808 if (search_continue(t) == FALSE)
7809 goto done;
7811 /* search */
7812 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7813 TRUE) == FALSE) {
7814 /* not found, mark red */
7815 gdk_color_parse(XT_COLOR_RED, &color);
7816 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7817 /* unmark and remove selection */
7818 webkit_web_view_unmark_text_matches(t->wv);
7819 /* my kingdom for a way to unselect text in webview */
7820 } else {
7821 /* found, highlight all */
7822 webkit_web_view_unmark_text_matches(t->wv);
7823 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7824 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7825 gdk_color_parse(XT_COLOR_WHITE, &color);
7826 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7828 done:
7829 t->search_id = 0;
7830 return (FALSE);
7834 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7836 const gchar *c = gtk_entry_get_text(w);
7838 if (t == NULL) {
7839 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7840 return (XT_CB_PASSTHROUGH);
7843 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7844 e->keyval, e->state, t);
7846 if (search_continue(t) == FALSE)
7847 goto done;
7849 /* if search length is > 4 then no longer play timeout games */
7850 if (strlen(c) > 4) {
7851 if (t->search_id) {
7852 g_source_remove(t->search_id);
7853 t->search_id = 0;
7855 search_cb(t);
7856 goto done;
7859 /* reestablish a new timer if the user types fast */
7860 if (t->search_id)
7861 g_source_remove(t->search_id);
7862 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7864 done:
7865 return (XT_CB_PASSTHROUGH);
7868 gboolean
7869 match_uri(const gchar *uri, const gchar *key) {
7870 gchar *voffset;
7871 size_t len;
7872 gboolean match = FALSE;
7874 len = strlen(key);
7876 if (!strncmp(key, uri, len))
7877 match = TRUE;
7878 else {
7879 voffset = strstr(uri, "/") + 2;
7880 if (!strncmp(key, voffset, len))
7881 match = TRUE;
7882 else if (g_str_has_prefix(voffset, "www.")) {
7883 voffset = voffset + strlen("www.");
7884 if (!strncmp(key, voffset, len))
7885 match = TRUE;
7889 return (match);
7892 gboolean
7893 match_session(const gchar *name, const gchar *key) {
7894 char *sub;
7896 sub = strcasestr(name, key);
7898 return sub == name;
7901 void
7902 cmd_getlist(int id, char *key)
7904 int i, dep, c = 0;
7905 struct history *h;
7906 struct session *s;
7908 if (id >= 0) {
7909 if (cmds[id].type & XT_URLARG) {
7910 RB_FOREACH_REVERSE(h, history_list, &hl)
7911 if (match_uri(h->uri, key)) {
7912 cmd_status.list[c] = (char *)h->uri;
7913 if (++c > 255)
7914 break;
7917 cmd_status.len = c;
7918 return;
7919 } else if (cmds[id].type & XT_SESSARG) {
7920 TAILQ_FOREACH(s, &sessions, entry) {
7921 if (match_session(s->name, key)) {
7922 cmd_status.list[c] = (char *)s->name;
7923 if (++c > 255)
7924 break;
7928 cmd_status.len = c;
7929 return;
7933 dep = (id == -1) ? 0 : cmds[id].level + 1;
7935 for (i = id + 1; i < LENGTH(cmds); i++) {
7936 if (cmds[i].level < dep)
7937 break;
7938 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7939 strlen(key)))
7940 cmd_status.list[c++] = cmds[i].cmd;
7944 cmd_status.len = c;
7947 char *
7948 cmd_getnext(int dir)
7950 cmd_status.index += dir;
7952 if (cmd_status.index < 0)
7953 cmd_status.index = cmd_status.len - 1;
7954 else if (cmd_status.index >= cmd_status.len)
7955 cmd_status.index = 0;
7957 return cmd_status.list[cmd_status.index];
7961 cmd_tokenize(char *s, char *tokens[])
7963 int i = 0;
7964 char *tok, *last;
7965 size_t len = strlen(s);
7966 bool blank;
7968 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7969 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7970 tok = strtok_r(NULL, " ", &last), i++)
7971 tokens[i] = tok;
7973 if (blank && i < 3)
7974 tokens[i++] = "";
7976 return (i);
7979 void
7980 cmd_complete(struct tab *t, char *str, int dir)
7982 GtkEntry *w = GTK_ENTRY(t->cmd);
7983 int i, j, levels, c = 0, dep = 0, parent = -1;
7984 int matchcount = 0;
7985 char *tok, *match, *s = g_strdup(str);
7986 char *tokens[3];
7987 char res[XT_MAX_URL_LENGTH + 32] = ":";
7988 char *sc = s;
7990 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7992 /* copy prefix*/
7993 for (i = 0; isdigit(s[i]); i++)
7994 res[i + 1] = s[i];
7996 for (; isspace(s[i]); i++)
7997 res[i + 1] = s[i];
7999 s += i;
8001 levels = cmd_tokenize(s, tokens);
8003 for (i = 0; i < levels - 1; i++) {
8004 tok = tokens[i];
8005 matchcount = 0;
8006 for (j = c; j < LENGTH(cmds); j++) {
8007 if (cmds[j].level < dep)
8008 break;
8009 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8010 strlen(tok))) {
8011 matchcount++;
8012 c = j + 1;
8013 if (strlen(tok) == strlen(cmds[j].cmd)) {
8014 matchcount = 1;
8015 break;
8020 if (matchcount == 1) {
8021 strlcat(res, tok, sizeof res);
8022 strlcat(res, " ", sizeof res);
8023 dep++;
8024 } else {
8025 g_free(sc);
8026 return;
8029 parent = c - 1;
8032 if (cmd_status.index == -1)
8033 cmd_getlist(parent, tokens[i]);
8035 if (cmd_status.len > 0) {
8036 match = cmd_getnext(dir);
8037 strlcat(res, match, sizeof res);
8038 gtk_entry_set_text(w, res);
8039 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8042 g_free(sc);
8045 gboolean
8046 cmd_execute(struct tab *t, char *str)
8048 struct cmd *cmd = NULL;
8049 char *tok, *last, *s = g_strdup(str), *sc;
8050 char prefixstr[4];
8051 int j, len, c = 0, dep = 0, matchcount = 0;
8052 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8053 struct karg arg = {0, NULL, -1};
8055 sc = s;
8057 /* copy prefix*/
8058 for (j = 0; j<3 && isdigit(s[j]); j++)
8059 prefixstr[j]=s[j];
8061 prefixstr[j]='\0';
8063 s += j;
8064 while (isspace(s[0]))
8065 s++;
8067 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8068 prefix = atoi(prefixstr);
8069 else
8070 s = sc;
8072 for (tok = strtok_r(s, " ", &last); tok;
8073 tok = strtok_r(NULL, " ", &last)) {
8074 matchcount = 0;
8075 for (j = c; j < LENGTH(cmds); j++) {
8076 if (cmds[j].level < dep)
8077 break;
8078 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8079 strlen(tok);
8080 if (cmds[j].level == dep &&
8081 !strncmp(tok, cmds[j].cmd, len)) {
8082 matchcount++;
8083 c = j + 1;
8084 cmd = &cmds[j];
8085 if (len == strlen(cmds[j].cmd)) {
8086 matchcount = 1;
8087 break;
8091 if (matchcount == 1) {
8092 if (cmd->type > 0)
8093 goto execute_cmd;
8094 dep++;
8095 } else {
8096 show_oops(t, "Invalid command: %s", str);
8097 goto done;
8100 execute_cmd:
8101 arg.i = cmd->arg;
8103 if (prefix != -1)
8104 arg.precount = prefix;
8105 else if (cmd_prefix > 0)
8106 arg.precount = cmd_prefix;
8108 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8109 show_oops(t, "No prefix allowed: %s", str);
8110 goto done;
8112 if (cmd->type > 1)
8113 arg.s = last ? g_strdup(last) : g_strdup("");
8114 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8115 arg.precount = atoi(arg.s);
8116 if (arg.precount <= 0) {
8117 if (arg.s[0] == '0')
8118 show_oops(t, "Zero count");
8119 else
8120 show_oops(t, "Trailing characters");
8121 goto done;
8125 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8126 __func__, arg.precount, arg.s);
8128 cmd->func(t, &arg);
8130 rv = XT_CB_HANDLED;
8131 done:
8132 if (j > 0)
8133 cmd_prefix = 0;
8134 g_free(sc);
8135 if (arg.s)
8136 g_free(arg.s);
8138 return (rv);
8142 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8144 if (t == NULL) {
8145 show_oops(NULL, "entry_key_cb invalid parameters");
8146 return (XT_CB_PASSTHROUGH);
8149 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8150 e->keyval, e->state, t);
8152 hide_oops(t);
8154 if (e->keyval == GDK_Escape) {
8155 /* don't use focus_webview(t) because we want to type :cmds */
8156 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8159 return (handle_keypress(t, e, 1));
8162 struct command_entry *
8163 history_prev(struct command_list *l, struct command_entry *at)
8165 if (at == NULL)
8166 at = TAILQ_LAST(l, command_list);
8167 else {
8168 at = TAILQ_PREV(at, command_list, entry);
8169 if (at == NULL)
8170 at = TAILQ_LAST(l, command_list);
8173 return (at);
8176 struct command_entry *
8177 history_next(struct command_list *l, struct command_entry *at)
8179 if (at == NULL)
8180 at = TAILQ_FIRST(l);
8181 else {
8182 at = TAILQ_NEXT(at, entry);
8183 if (at == NULL)
8184 at = TAILQ_FIRST(l);
8187 return (at);
8191 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8193 int rv = XT_CB_HANDLED;
8194 const gchar *c = gtk_entry_get_text(w);
8196 if (t == NULL) {
8197 show_oops(NULL, "cmd_keypress_cb parameters");
8198 return (XT_CB_PASSTHROUGH);
8201 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8202 e->keyval, e->state, t);
8204 /* sanity */
8205 if (c == NULL)
8206 e->keyval = GDK_Escape;
8207 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8208 e->keyval = GDK_Escape;
8210 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8211 e->keyval != GDK_ISO_Left_Tab)
8212 cmd_status.index = -1;
8214 switch (e->keyval) {
8215 case GDK_Tab:
8216 if (c[0] == ':')
8217 cmd_complete(t, (char *)&c[1], 1);
8218 goto done;
8219 case GDK_ISO_Left_Tab:
8220 if (c[0] == ':')
8221 cmd_complete(t, (char *)&c[1], -1);
8223 goto done;
8224 case GDK_Down:
8225 if (c[0] != ':') {
8226 if ((search_at = history_next(&shl, search_at))) {
8227 search_at->line[0] = c[0];
8228 gtk_entry_set_text(w, search_at->line);
8229 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8231 } else {
8232 if ((history_at = history_prev(&chl, history_at))) {
8233 history_at->line[0] = c[0];
8234 gtk_entry_set_text(w, history_at->line);
8235 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8239 goto done;
8240 case GDK_Up:
8241 if (c[0] != ':') {
8242 if ((search_at = history_next(&shl, search_at))) {
8243 search_at->line[0] = c[0];
8244 gtk_entry_set_text(w, search_at->line);
8245 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8247 } else {
8248 if ((history_at = history_next(&chl, history_at))) {
8249 history_at->line[0] = c[0];
8250 gtk_entry_set_text(w, history_at->line);
8251 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8255 goto done;
8256 case GDK_BackSpace:
8257 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8258 break;
8259 /* FALLTHROUGH */
8260 case GDK_Escape:
8261 hide_cmd(t);
8262 focus_webview(t);
8264 /* cancel search */
8265 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8266 webkit_web_view_unmark_text_matches(t->wv);
8267 goto done;
8270 rv = XT_CB_PASSTHROUGH;
8271 done:
8272 return (rv);
8275 void
8276 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8278 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8281 void
8282 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8284 /* popup menu enabled */
8285 t->popup = 1;
8289 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8291 if (t == NULL) {
8292 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8293 return (XT_CB_PASSTHROUGH);
8296 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8297 t->tab_id, t->popup);
8299 /* if popup is enabled don't lose focus */
8300 if (t->popup) {
8301 t->popup = 0;
8302 return (XT_CB_PASSTHROUGH);
8305 hide_cmd(t);
8306 hide_oops(t);
8308 if (show_url == 0 || t->focus_wv)
8309 focus_webview(t);
8310 else
8311 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8313 return (XT_CB_PASSTHROUGH);
8316 void
8317 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8319 char *s;
8320 const gchar *c = gtk_entry_get_text(entry);
8322 if (t == NULL) {
8323 show_oops(NULL, "cmd_activate_cb invalid parameters");
8324 return;
8327 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8329 hide_cmd(t);
8331 /* sanity */
8332 if (c == NULL)
8333 goto done;
8334 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8335 goto done;
8336 if (strlen(c) < 2)
8337 goto done;
8338 s = (char *)&c[1];
8340 if (c[0] == '/' || c[0] == '?') {
8341 /* see if there is a timer pending */
8342 if (t->search_id) {
8343 g_source_remove(t->search_id);
8344 t->search_id = 0;
8345 search_cb(t);
8348 if (t->search_text) {
8349 g_free(t->search_text);
8350 t->search_text = NULL;
8353 t->search_text = g_strdup(s);
8354 if (global_search)
8355 g_free(global_search);
8356 global_search = g_strdup(s);
8357 t->search_forward = c[0] == '/';
8359 history_add(&shl, search_file, s, &search_history_count);
8360 goto done;
8363 history_add(&chl, command_file, s, &cmd_history_count);
8364 cmd_execute(t, s);
8365 done:
8366 return;
8369 void
8370 backward_cb(GtkWidget *w, struct tab *t)
8372 struct karg a;
8374 if (t == NULL) {
8375 show_oops(NULL, "backward_cb invalid parameters");
8376 return;
8379 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8381 a.i = XT_NAV_BACK;
8382 navaction(t, &a);
8385 void
8386 forward_cb(GtkWidget *w, struct tab *t)
8388 struct karg a;
8390 if (t == NULL) {
8391 show_oops(NULL, "forward_cb invalid parameters");
8392 return;
8395 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8397 a.i = XT_NAV_FORWARD;
8398 navaction(t, &a);
8401 void
8402 home_cb(GtkWidget *w, struct tab *t)
8404 if (t == NULL) {
8405 show_oops(NULL, "home_cb invalid parameters");
8406 return;
8409 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8411 load_uri(t, home);
8414 void
8415 stop_cb(GtkWidget *w, struct tab *t)
8417 WebKitWebFrame *frame;
8419 if (t == NULL) {
8420 show_oops(NULL, "stop_cb invalid parameters");
8421 return;
8424 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8426 frame = webkit_web_view_get_main_frame(t->wv);
8427 if (frame == NULL) {
8428 show_oops(t, "stop_cb: no frame");
8429 return;
8432 webkit_web_frame_stop_loading(frame);
8433 abort_favicon_download(t);
8436 void
8437 setup_webkit(struct tab *t)
8439 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8440 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8441 FALSE, (char *)NULL);
8442 else
8443 warnx("webkit does not have \"enable-dns-prefetching\" property");
8444 g_object_set(G_OBJECT(t->settings),
8445 "user-agent", t->user_agent, (char *)NULL);
8446 g_object_set(G_OBJECT(t->settings),
8447 "enable-scripts", enable_scripts, (char *)NULL);
8448 g_object_set(G_OBJECT(t->settings),
8449 "enable-plugins", enable_plugins, (char *)NULL);
8450 g_object_set(G_OBJECT(t->settings),
8451 "javascript-can-open-windows-automatically", enable_scripts,
8452 (char *)NULL);
8453 g_object_set(G_OBJECT(t->settings),
8454 "enable-html5-database", FALSE, (char *)NULL);
8455 g_object_set(G_OBJECT(t->settings),
8456 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8457 g_object_set(G_OBJECT(t->settings),
8458 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8459 g_object_set(G_OBJECT(t->settings),
8460 "spell_checking_languages", spell_check_languages, (char *)NULL);
8461 g_object_set(G_OBJECT(t->wv),
8462 "full-content-zoom", TRUE, (char *)NULL);
8464 webkit_web_view_set_settings(t->wv, t->settings);
8467 gboolean
8468 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8470 struct tab *ti, *t = NULL;
8471 gdouble view_size, value, max;
8472 gchar *position;
8474 TAILQ_FOREACH(ti, &tabs, entry)
8475 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8476 t = ti;
8477 break;
8480 if (t == NULL)
8481 return FALSE;
8483 if (adjustment == NULL)
8484 adjustment = gtk_scrolled_window_get_vadjustment(
8485 GTK_SCROLLED_WINDOW(t->browser_win));
8487 view_size = gtk_adjustment_get_page_size(adjustment);
8488 value = gtk_adjustment_get_value(adjustment);
8489 max = gtk_adjustment_get_upper(adjustment) - view_size;
8491 if (max == 0)
8492 position = g_strdup("All");
8493 else if (value == max)
8494 position = g_strdup("Bot");
8495 else if (value == 0)
8496 position = g_strdup("Top");
8497 else
8498 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8500 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8501 g_free(position);
8503 return (TRUE);
8506 GtkWidget *
8507 create_browser(struct tab *t)
8509 GtkWidget *w;
8510 gchar *strval;
8511 GtkAdjustment *adjustment;
8513 if (t == NULL) {
8514 show_oops(NULL, "create_browser invalid parameters");
8515 return (NULL);
8518 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8519 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8520 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8521 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8523 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8524 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8525 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8527 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8528 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8530 /* set defaults */
8531 t->settings = webkit_web_settings_new();
8533 if (user_agent == NULL) {
8534 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8535 (char *)NULL);
8536 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8537 g_free(strval);
8538 } else
8539 t->user_agent = g_strdup(user_agent);
8541 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8543 adjustment =
8544 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8545 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8546 G_CALLBACK(update_statusbar_position), NULL);
8548 setup_webkit(t);
8550 return (w);
8553 GtkWidget *
8554 create_window(void)
8556 GtkWidget *w;
8558 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8559 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8560 gtk_widget_set_name(w, "xxxterm");
8561 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8562 g_signal_connect(G_OBJECT(w), "delete_event",
8563 G_CALLBACK (gtk_main_quit), NULL);
8565 return (w);
8568 GtkWidget *
8569 create_kiosk_toolbar(struct tab *t)
8571 GtkWidget *toolbar = NULL, *b;
8573 b = gtk_hbox_new(FALSE, 0);
8574 toolbar = b;
8575 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8577 /* backward button */
8578 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8579 gtk_widget_set_sensitive(t->backward, FALSE);
8580 g_signal_connect(G_OBJECT(t->backward), "clicked",
8581 G_CALLBACK(backward_cb), t);
8582 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8584 /* forward button */
8585 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8586 gtk_widget_set_sensitive(t->forward, FALSE);
8587 g_signal_connect(G_OBJECT(t->forward), "clicked",
8588 G_CALLBACK(forward_cb), t);
8589 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8591 /* home button */
8592 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8593 gtk_widget_set_sensitive(t->gohome, true);
8594 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8595 G_CALLBACK(home_cb), t);
8596 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8598 /* create widgets but don't use them */
8599 t->uri_entry = gtk_entry_new();
8600 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8601 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8602 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8604 return (toolbar);
8607 GtkWidget *
8608 create_toolbar(struct tab *t)
8610 GtkWidget *toolbar = NULL, *b, *eb1;
8612 b = gtk_hbox_new(FALSE, 0);
8613 toolbar = b;
8614 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8616 /* backward button */
8617 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8618 gtk_widget_set_sensitive(t->backward, FALSE);
8619 g_signal_connect(G_OBJECT(t->backward), "clicked",
8620 G_CALLBACK(backward_cb), t);
8621 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8623 /* forward button */
8624 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8625 gtk_widget_set_sensitive(t->forward, FALSE);
8626 g_signal_connect(G_OBJECT(t->forward), "clicked",
8627 G_CALLBACK(forward_cb), t);
8628 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8629 FALSE, 0);
8631 /* stop button */
8632 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8633 gtk_widget_set_sensitive(t->stop, FALSE);
8634 g_signal_connect(G_OBJECT(t->stop), "clicked",
8635 G_CALLBACK(stop_cb), t);
8636 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8637 FALSE, 0);
8639 /* JS button */
8640 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8641 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8642 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8643 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8644 G_CALLBACK(js_toggle_cb), t);
8645 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8647 t->uri_entry = gtk_entry_new();
8648 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8649 G_CALLBACK(activate_uri_entry_cb), t);
8650 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8651 G_CALLBACK(entry_key_cb), t);
8652 completion_add(t);
8653 eb1 = gtk_hbox_new(FALSE, 0);
8654 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8655 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8656 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8658 /* search entry */
8659 if (search_string) {
8660 GtkWidget *eb2;
8661 t->search_entry = gtk_entry_new();
8662 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8663 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8664 G_CALLBACK(activate_search_entry_cb), t);
8665 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8666 G_CALLBACK(entry_key_cb), t);
8667 gtk_widget_set_size_request(t->search_entry, -1, -1);
8668 eb2 = gtk_hbox_new(FALSE, 0);
8669 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8670 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8672 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8675 return (toolbar);
8678 GtkWidget *
8679 create_buffers(struct tab *t)
8681 GtkCellRenderer *renderer;
8682 GtkWidget *view;
8684 view = gtk_tree_view_new();
8686 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8688 renderer = gtk_cell_renderer_text_new();
8689 gtk_tree_view_insert_column_with_attributes
8690 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8692 renderer = gtk_cell_renderer_text_new();
8693 gtk_tree_view_insert_column_with_attributes
8694 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8695 NULL);
8697 gtk_tree_view_set_model
8698 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8700 return view;
8703 void
8704 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8705 GtkTreeViewColumn *col, struct tab *t)
8707 GtkTreeIter iter;
8708 guint id;
8710 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8712 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8713 path)) {
8714 gtk_tree_model_get
8715 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8716 set_current_tab(id - 1);
8719 hide_buffers(t);
8722 /* after tab reordering/creation/removal */
8723 void
8724 recalc_tabs(void)
8726 struct tab *t;
8727 int maxid = 0;
8729 TAILQ_FOREACH(t, &tabs, entry) {
8730 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8731 if (t->tab_id > maxid)
8732 maxid = t->tab_id;
8734 gtk_widget_show(t->tab_elems.sep);
8737 TAILQ_FOREACH(t, &tabs, entry) {
8738 if (t->tab_id == maxid) {
8739 gtk_widget_hide(t->tab_elems.sep);
8740 break;
8745 /* after active tab change */
8746 void
8747 recolor_compact_tabs(void)
8749 struct tab *t;
8750 int curid = 0;
8751 GdkColor color;
8753 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8754 TAILQ_FOREACH(t, &tabs, entry)
8755 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8756 &color);
8758 curid = gtk_notebook_get_current_page(notebook);
8759 TAILQ_FOREACH(t, &tabs, entry)
8760 if (t->tab_id == curid) {
8761 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8762 gtk_widget_modify_fg(t->tab_elems.label,
8763 GTK_STATE_NORMAL, &color);
8764 break;
8768 void
8769 set_current_tab(int page_num)
8771 buffercmd_abort(get_current_tab());
8772 gtk_notebook_set_current_page(notebook, page_num);
8773 recolor_compact_tabs();
8777 undo_close_tab_save(struct tab *t)
8779 int m, n;
8780 const gchar *uri;
8781 struct undo *u1, *u2;
8782 GList *items;
8783 WebKitWebHistoryItem *item;
8785 if ((uri = get_uri(t)) == NULL)
8786 return (1);
8788 u1 = g_malloc0(sizeof(struct undo));
8789 u1->uri = g_strdup(uri);
8791 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8793 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8794 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8795 u1->back = n;
8797 /* forward history */
8798 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8800 while (items) {
8801 item = items->data;
8802 u1->history = g_list_prepend(u1->history,
8803 webkit_web_history_item_copy(item));
8804 items = g_list_next(items);
8807 /* current item */
8808 if (m) {
8809 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8810 u1->history = g_list_prepend(u1->history,
8811 webkit_web_history_item_copy(item));
8814 /* back history */
8815 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8817 while (items) {
8818 item = items->data;
8819 u1->history = g_list_prepend(u1->history,
8820 webkit_web_history_item_copy(item));
8821 items = g_list_next(items);
8824 TAILQ_INSERT_HEAD(&undos, u1, entry);
8826 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8827 u2 = TAILQ_LAST(&undos, undo_tailq);
8828 TAILQ_REMOVE(&undos, u2, entry);
8829 g_free(u2->uri);
8830 g_list_free(u2->history);
8831 g_free(u2);
8832 } else
8833 undo_count++;
8835 return (0);
8838 void
8839 delete_tab(struct tab *t)
8841 struct karg a;
8843 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8845 if (t == NULL)
8846 return;
8848 buffercmd_abort(t);
8849 TAILQ_REMOVE(&tabs, t, entry);
8851 /* Halt all webkit activity. */
8852 abort_favicon_download(t);
8853 webkit_web_view_stop_loading(t->wv);
8855 /* Save the tab, so we can undo the close. */
8856 undo_close_tab_save(t);
8858 if (browser_mode == XT_BM_KIOSK) {
8859 gtk_widget_destroy(t->uri_entry);
8860 gtk_widget_destroy(t->stop);
8861 gtk_widget_destroy(t->js_toggle);
8864 gtk_widget_destroy(t->tab_elems.eventbox);
8865 gtk_widget_destroy(t->vbox);
8867 /* just in case */
8868 if (t->search_id)
8869 g_source_remove(t->search_id);
8871 g_free(t->user_agent);
8872 g_free(t->stylesheet);
8873 g_free(t->tmp_uri);
8874 g_free(t);
8876 if (TAILQ_EMPTY(&tabs)) {
8877 if (browser_mode == XT_BM_KIOSK)
8878 create_new_tab(home, NULL, 1, -1);
8879 else
8880 create_new_tab(NULL, NULL, 1, -1);
8883 /* recreate session */
8884 if (session_autosave) {
8885 a.s = NULL;
8886 save_tabs(t, &a);
8889 recalc_tabs();
8890 recolor_compact_tabs();
8893 void
8894 update_statusbar_zoom(struct tab *t)
8896 gfloat zoom;
8897 char s[16] = { '\0' };
8899 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8900 if ((zoom <= 0.99 || zoom >= 1.01))
8901 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8902 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8905 void
8906 setzoom_webkit(struct tab *t, int adjust)
8908 #define XT_ZOOMPERCENT 0.04
8910 gfloat zoom;
8912 if (t == NULL) {
8913 show_oops(NULL, "setzoom_webkit invalid parameters");
8914 return;
8917 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8918 if (adjust == XT_ZOOM_IN)
8919 zoom += XT_ZOOMPERCENT;
8920 else if (adjust == XT_ZOOM_OUT)
8921 zoom -= XT_ZOOMPERCENT;
8922 else if (adjust > 0)
8923 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8924 else {
8925 show_oops(t, "setzoom_webkit invalid zoom value");
8926 return;
8929 if (zoom < XT_ZOOMPERCENT)
8930 zoom = XT_ZOOMPERCENT;
8931 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8932 update_statusbar_zoom(t);
8935 gboolean
8936 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8938 struct tab *t = (struct tab *) data;
8940 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8942 switch (event->button) {
8943 case 1:
8944 set_current_tab(t->tab_id);
8945 break;
8946 case 2:
8947 delete_tab(t);
8948 break;
8951 return TRUE;
8954 void
8955 append_tab(struct tab *t)
8957 if (t == NULL)
8958 return;
8960 TAILQ_INSERT_TAIL(&tabs, t, entry);
8961 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8964 GtkWidget *
8965 create_sbe(int width)
8967 GtkWidget *sbe;
8969 sbe = gtk_entry_new();
8970 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8971 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8972 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8973 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8974 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8975 gtk_widget_set_size_request(sbe, width, -1);
8977 return sbe;
8980 struct tab *
8981 create_new_tab(char *title, struct undo *u, int focus, int position)
8983 struct tab *t;
8984 int load = 1, id;
8985 GtkWidget *b, *bb;
8986 WebKitWebHistoryItem *item;
8987 GList *items;
8988 GdkColor color;
8989 char *p;
8990 int sbe_p = 0, sbe_b = 0,
8991 sbe_z = 0;
8993 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8995 if (tabless && !TAILQ_EMPTY(&tabs)) {
8996 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8997 return (NULL);
9000 t = g_malloc0(sizeof *t);
9002 if (title == NULL) {
9003 title = "(untitled)";
9004 load = 0;
9007 t->vbox = gtk_vbox_new(FALSE, 0);
9009 /* label + button for tab */
9010 b = gtk_hbox_new(FALSE, 0);
9011 t->tab_content = b;
9013 #if GTK_CHECK_VERSION(2, 20, 0)
9014 t->spinner = gtk_spinner_new();
9015 #endif
9016 t->label = gtk_label_new(title);
9017 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9018 gtk_widget_set_size_request(t->label, 100, 0);
9019 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9020 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9021 gtk_widget_set_size_request(b, 130, 0);
9023 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9024 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9025 #if GTK_CHECK_VERSION(2, 20, 0)
9026 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9027 #endif
9029 /* toolbar */
9030 if (browser_mode == XT_BM_KIOSK) {
9031 t->toolbar = create_kiosk_toolbar(t);
9032 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9034 } else {
9035 t->toolbar = create_toolbar(t);
9036 if (fancy_bar)
9037 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9038 FALSE, 0);
9041 /* marks */
9042 marks_clear(t);
9044 /* browser */
9045 t->browser_win = create_browser(t);
9046 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9048 /* oops message for user feedback */
9049 t->oops = gtk_entry_new();
9050 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9051 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9052 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9053 gdk_color_parse(XT_COLOR_RED, &color);
9054 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9055 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9056 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9058 /* command entry */
9059 t->cmd = gtk_entry_new();
9060 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9061 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9062 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9063 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9065 /* status bar */
9066 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9068 t->sbe.statusbar = gtk_entry_new();
9069 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9070 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9071 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9072 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9074 /* create these widgets only if specified in statusbar_elems */
9076 t->sbe.position = create_sbe(40);
9077 t->sbe.zoom = create_sbe(40);
9078 t->sbe.buffercmd = create_sbe(60);
9080 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9082 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9083 TRUE, FALSE);
9085 /* gtk widgets cannot be added to a box twice. sbe_* variables
9086 make sure of this */
9087 for (p = statusbar_elems; *p != '\0'; p++) {
9088 switch (*p) {
9089 case '|':
9091 GtkWidget *sep = gtk_vseparator_new();
9093 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9094 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9095 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9096 FALSE, FALSE, FALSE);
9097 break;
9099 case 'P':
9100 if (sbe_p) {
9101 warnx("flag \"%c\" specified more than "
9102 "once in statusbar_elems\n", *p);
9103 break;
9105 sbe_p = 1;
9106 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9107 t->sbe.position, FALSE, FALSE, FALSE);
9108 break;
9109 case 'B':
9110 if (sbe_b) {
9111 warnx("flag \"%c\" specified more than "
9112 "once in statusbar_elems\n", *p);
9113 break;
9115 sbe_b = 1;
9116 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9117 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9118 break;
9119 case 'Z':
9120 if (sbe_z) {
9121 warnx("flag \"%c\" specified more than "
9122 "once in statusbar_elems\n", *p);
9123 break;
9125 sbe_z = 1;
9126 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9127 t->sbe.zoom, FALSE, FALSE, FALSE);
9128 break;
9129 default:
9130 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9131 break;
9135 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9137 /* buffer list */
9138 t->buffers = create_buffers(t);
9139 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9141 /* xtp meaning is normal by default */
9142 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9144 /* set empty favicon */
9145 xt_icon_from_name(t, "text-html");
9147 /* and show it all */
9148 gtk_widget_show_all(b);
9149 gtk_widget_show_all(t->vbox);
9151 /* compact tab bar */
9152 t->tab_elems.label = gtk_label_new(title);
9153 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9154 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9155 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9156 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9158 t->tab_elems.eventbox = gtk_event_box_new();
9159 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9160 t->tab_elems.sep = gtk_vseparator_new();
9162 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9163 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9164 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9165 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9166 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9167 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9169 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9170 TRUE, 0);
9171 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9172 FALSE, 0);
9173 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9174 t->tab_elems.box);
9176 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9177 TRUE, 0);
9178 gtk_widget_show_all(t->tab_elems.eventbox);
9180 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9181 append_tab(t);
9182 else {
9183 id = position >= 0 ? position :
9184 gtk_notebook_get_current_page(notebook) + 1;
9185 if (id > gtk_notebook_get_n_pages(notebook))
9186 append_tab(t);
9187 else {
9188 TAILQ_INSERT_TAIL(&tabs, t, entry);
9189 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9190 gtk_box_reorder_child(GTK_BOX(tab_bar),
9191 t->tab_elems.eventbox, id);
9192 recalc_tabs();
9196 #if GTK_CHECK_VERSION(2, 20, 0)
9197 /* turn spinner off if we are a new tab without uri */
9198 if (!load) {
9199 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9200 gtk_widget_hide(t->spinner);
9202 #endif
9203 /* make notebook tabs reorderable */
9204 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9206 /* compact tabs clickable */
9207 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9208 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9210 g_object_connect(G_OBJECT(t->cmd),
9211 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9212 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9213 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9214 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9215 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9216 (char *)NULL);
9218 /* reuse wv_button_cb to hide oops */
9219 g_object_connect(G_OBJECT(t->oops),
9220 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9221 (char *)NULL);
9223 g_signal_connect(t->buffers,
9224 "row-activated", G_CALLBACK(row_activated_cb), t);
9225 g_object_connect(G_OBJECT(t->buffers),
9226 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
9228 g_object_connect(G_OBJECT(t->wv),
9229 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9230 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9231 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9232 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9233 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9234 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9235 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9236 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9237 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9238 "signal::event", G_CALLBACK(webview_event_cb), t,
9239 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9240 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9241 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9242 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9243 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9244 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9245 (char *)NULL);
9246 g_signal_connect(t->wv,
9247 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9249 * XXX this puts invalid url in uri_entry and that is undesirable
9251 #if 0
9252 g_signal_connect(t->wv,
9253 "load-error", G_CALLBACK(notify_load_error_cb), t);
9254 #endif
9255 g_signal_connect(t->wv,
9256 "notify::title", G_CALLBACK(notify_title_cb), t);
9258 /* hijack the unused keys as if we were the browser */
9259 g_object_connect(G_OBJECT(t->toolbar),
9260 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9261 (char *)NULL);
9263 g_signal_connect(G_OBJECT(bb), "button_press_event",
9264 G_CALLBACK(tab_close_cb), t);
9266 /* setup history */
9267 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9268 /* restore the tab's history */
9269 if (u && u->history) {
9270 items = u->history;
9271 while (items) {
9272 item = items->data;
9273 webkit_web_back_forward_list_add_item(t->bfl, item);
9274 items = g_list_next(items);
9277 item = g_list_nth_data(u->history, u->back);
9278 if (item)
9279 webkit_web_view_go_to_back_forward_item(t->wv, item);
9281 g_list_free(items);
9282 g_list_free(u->history);
9283 } else
9284 webkit_web_back_forward_list_clear(t->bfl);
9286 /* hide stuff */
9287 hide_cmd(t);
9288 hide_oops(t);
9289 hide_buffers(t);
9290 url_set_visibility();
9291 statusbar_set_visibility();
9293 if (focus) {
9294 set_current_tab(t->tab_id);
9295 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9296 t->tab_id);
9299 if (load) {
9300 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9301 load_uri(t, title);
9302 } else {
9303 if (show_url == 1)
9304 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9305 else
9306 focus_webview(t);
9309 recolor_compact_tabs();
9310 setzoom_webkit(t, XT_ZOOM_NORMAL);
9311 return (t);
9314 void
9315 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9316 gpointer *udata)
9318 struct tab *t;
9319 const gchar *uri;
9321 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9323 if (gtk_notebook_get_current_page(notebook) == -1)
9324 recalc_tabs();
9326 TAILQ_FOREACH(t, &tabs, entry) {
9327 if (t->tab_id == pn) {
9328 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9329 "%d\n", pn);
9331 uri = get_title(t, TRUE);
9332 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9334 hide_cmd(t);
9335 hide_oops(t);
9337 if (t->focus_wv) {
9338 /* can't use focus_webview here */
9339 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9345 void
9346 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9347 gpointer *udata)
9349 struct tab *t = NULL, *tt;
9351 recalc_tabs();
9353 TAILQ_FOREACH(tt, &tabs, entry)
9354 if (tt->tab_id == pn) {
9355 t = tt;
9356 break;
9359 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9361 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9362 t->tab_id);
9365 void
9366 menuitem_response(struct tab *t)
9368 gtk_notebook_set_current_page(notebook, t->tab_id);
9371 gboolean
9372 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9374 GtkWidget *menu, *menu_items;
9375 GdkEventButton *bevent;
9376 const gchar *uri;
9377 struct tab *ti;
9379 if (event->type == GDK_BUTTON_PRESS) {
9380 bevent = (GdkEventButton *) event;
9381 menu = gtk_menu_new();
9383 TAILQ_FOREACH(ti, &tabs, entry) {
9384 if ((uri = get_uri(ti)) == NULL)
9385 /* XXX make sure there is something to print */
9386 /* XXX add gui pages in here to look purdy */
9387 uri = "(untitled)";
9388 menu_items = gtk_menu_item_new_with_label(uri);
9389 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9390 gtk_widget_show(menu_items);
9392 g_signal_connect_swapped((menu_items),
9393 "activate", G_CALLBACK(menuitem_response),
9394 (gpointer)ti);
9397 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9398 bevent->button, bevent->time);
9400 /* unref object so it'll free itself when popped down */
9401 #if !GTK_CHECK_VERSION(3, 0, 0)
9402 /* XXX does not need unref with gtk+3? */
9403 g_object_ref_sink(menu);
9404 g_object_unref(menu);
9405 #endif
9407 return (TRUE /* eat event */);
9410 return (FALSE /* propagate */);
9414 icon_size_map(int icon_size)
9416 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9417 icon_size > GTK_ICON_SIZE_DIALOG)
9418 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9420 return (icon_size);
9423 GtkWidget *
9424 create_button(char *name, char *stockid, int size)
9426 GtkWidget *button, *image;
9427 gchar *rcstring;
9428 int gtk_icon_size;
9430 rcstring = g_strdup_printf(
9431 "style \"%s-style\"\n"
9432 "{\n"
9433 " GtkWidget::focus-padding = 0\n"
9434 " GtkWidget::focus-line-width = 0\n"
9435 " xthickness = 0\n"
9436 " ythickness = 0\n"
9437 "}\n"
9438 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9439 gtk_rc_parse_string(rcstring);
9440 g_free(rcstring);
9441 button = gtk_button_new();
9442 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9443 gtk_icon_size = icon_size_map(size ? size : icon_size);
9445 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9446 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9447 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9448 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9449 gtk_widget_set_name(button, name);
9450 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9452 return (button);
9455 void
9456 button_set_stockid(GtkWidget *button, char *stockid)
9458 GtkWidget *image;
9460 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9461 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9462 gtk_button_set_image(GTK_BUTTON(button), image);
9465 void
9466 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9468 gchar *p = NULL;
9469 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9470 gint len;
9472 if (xterm_workaround == 0)
9473 return;
9476 * xterm doesn't play nice with clipboards because it clears the
9477 * primary when clicked. We rely on primary being set to properly
9478 * handle middle mouse button clicks (paste). So when someone clears
9479 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9480 * other application behavior (as in DON'T clear primary).
9483 p = gtk_clipboard_wait_for_text(primary);
9484 if (p == NULL) {
9485 if (gdk_property_get(gdk_get_default_root_window(),
9486 atom,
9487 gdk_atom_intern("STRING", FALSE),
9489 1024 * 1024 /* picked out of my butt */,
9490 FALSE,
9491 NULL,
9492 NULL,
9493 &len,
9494 (guchar **)&p)) {
9495 /* yes sir, we need to NUL the string */
9496 p[len] = '\0';
9497 gtk_clipboard_set_text(primary, p, -1);
9501 if (p)
9502 g_free(p);
9505 void
9506 create_canvas(void)
9508 GtkWidget *vbox;
9509 GList *l = NULL;
9510 GdkPixbuf *pb;
9511 char file[PATH_MAX];
9512 int i;
9514 vbox = gtk_vbox_new(FALSE, 0);
9515 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9516 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9517 #if !GTK_CHECK_VERSION(3, 0, 0)
9518 /* XXX seems to be needed with gtk+2 */
9519 gtk_notebook_set_tab_hborder(notebook, 0);
9520 gtk_notebook_set_tab_vborder(notebook, 0);
9521 #endif
9522 gtk_notebook_set_scrollable(notebook, TRUE);
9523 gtk_notebook_set_show_border(notebook, FALSE);
9524 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9526 abtn = gtk_button_new();
9527 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9528 gtk_widget_set_size_request(arrow, -1, -1);
9529 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9530 gtk_widget_set_size_request(abtn, -1, 20);
9532 #if GTK_CHECK_VERSION(2, 20, 0)
9533 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9534 #endif
9535 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9537 /* compact tab bar */
9538 tab_bar = gtk_hbox_new(TRUE, 0);
9540 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9541 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9542 gtk_widget_set_size_request(vbox, -1, -1);
9544 g_object_connect(G_OBJECT(notebook),
9545 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9546 (char *)NULL);
9547 g_object_connect(G_OBJECT(notebook),
9548 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9549 NULL, (char *)NULL);
9550 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9551 G_CALLBACK(arrow_cb), NULL);
9553 main_window = create_window();
9554 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9556 /* icons */
9557 for (i = 0; i < LENGTH(icons); i++) {
9558 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9559 pb = gdk_pixbuf_new_from_file(file, NULL);
9560 l = g_list_append(l, pb);
9562 gtk_window_set_default_icon_list(l);
9564 /* clipboard work around */
9565 if (xterm_workaround)
9566 g_signal_connect(
9567 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9568 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9570 gtk_widget_show_all(abtn);
9571 gtk_widget_show_all(main_window);
9572 notebook_tab_set_visibility();
9575 void
9576 set_hook(void **hook, char *name)
9578 if (hook == NULL)
9579 errx(1, "set_hook");
9581 if (*hook == NULL) {
9582 *hook = dlsym(RTLD_NEXT, name);
9583 if (*hook == NULL)
9584 errx(1, "can't hook %s", name);
9588 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9589 gboolean
9590 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9592 g_return_val_if_fail(cookie1, FALSE);
9593 g_return_val_if_fail(cookie2, FALSE);
9595 return (!strcmp (cookie1->name, cookie2->name) &&
9596 !strcmp (cookie1->value, cookie2->value) &&
9597 !strcmp (cookie1->path, cookie2->path) &&
9598 !strcmp (cookie1->domain, cookie2->domain));
9601 void
9602 transfer_cookies(void)
9604 GSList *cf;
9605 SoupCookie *sc, *pc;
9607 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9609 for (;cf; cf = cf->next) {
9610 pc = cf->data;
9611 sc = soup_cookie_copy(pc);
9612 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9615 soup_cookies_free(cf);
9618 void
9619 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9621 GSList *cf;
9622 SoupCookie *ci;
9624 print_cookie("soup_cookie_jar_delete_cookie", c);
9626 if (cookies_enabled == 0)
9627 return;
9629 if (jar == NULL || c == NULL)
9630 return;
9632 /* find and remove from persistent jar */
9633 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9635 for (;cf; cf = cf->next) {
9636 ci = cf->data;
9637 if (soup_cookie_equal(ci, c)) {
9638 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9639 break;
9643 soup_cookies_free(cf);
9645 /* delete from session jar */
9646 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9649 void
9650 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9652 struct domain *d = NULL;
9653 SoupCookie *c;
9654 FILE *r_cookie_f;
9656 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9657 jar, p_cookiejar, s_cookiejar);
9659 if (cookies_enabled == 0)
9660 return;
9662 /* see if we are up and running */
9663 if (p_cookiejar == NULL) {
9664 _soup_cookie_jar_add_cookie(jar, cookie);
9665 return;
9667 /* disallow p_cookiejar adds, shouldn't happen */
9668 if (jar == p_cookiejar)
9669 return;
9671 /* sanity */
9672 if (jar == NULL || cookie == NULL)
9673 return;
9675 if (enable_cookie_whitelist &&
9676 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9677 blocked_cookies++;
9678 DNPRINTF(XT_D_COOKIE,
9679 "soup_cookie_jar_add_cookie: reject %s\n",
9680 cookie->domain);
9681 if (save_rejected_cookies) {
9682 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9683 show_oops(NULL, "can't open reject cookie file");
9684 return;
9686 fseek(r_cookie_f, 0, SEEK_END);
9687 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9688 cookie->http_only ? "#HttpOnly_" : "",
9689 cookie->domain,
9690 *cookie->domain == '.' ? "TRUE" : "FALSE",
9691 cookie->path,
9692 cookie->secure ? "TRUE" : "FALSE",
9693 cookie->expires ?
9694 (gulong)soup_date_to_time_t(cookie->expires) :
9696 cookie->name,
9697 cookie->value);
9698 fflush(r_cookie_f);
9699 fclose(r_cookie_f);
9701 if (!allow_volatile_cookies)
9702 return;
9705 if (cookie->expires == NULL && session_timeout) {
9706 soup_cookie_set_expires(cookie,
9707 soup_date_new_from_now(session_timeout));
9708 print_cookie("modified add cookie", cookie);
9711 /* see if we are white listed for persistence */
9712 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9713 /* add to persistent jar */
9714 c = soup_cookie_copy(cookie);
9715 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9716 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9719 /* add to session jar */
9720 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9721 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9724 void
9725 setup_cookies(void)
9727 char file[PATH_MAX];
9729 set_hook((void *)&_soup_cookie_jar_add_cookie,
9730 "soup_cookie_jar_add_cookie");
9731 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9732 "soup_cookie_jar_delete_cookie");
9734 if (cookies_enabled == 0)
9735 return;
9738 * the following code is intricate due to overriding several libsoup
9739 * functions.
9740 * do not alter order of these operations.
9743 /* rejected cookies */
9744 if (save_rejected_cookies)
9745 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9746 XT_REJECT_FILE);
9748 /* persistent cookies */
9749 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9750 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9752 /* session cookies */
9753 s_cookiejar = soup_cookie_jar_new();
9754 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9755 cookie_policy, (void *)NULL);
9756 transfer_cookies();
9758 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9761 void
9762 setup_proxy(char *uri)
9764 if (proxy_uri) {
9765 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9766 soup_uri_free(proxy_uri);
9767 proxy_uri = NULL;
9769 if (http_proxy) {
9770 if (http_proxy != uri) {
9771 g_free(http_proxy);
9772 http_proxy = NULL;
9776 if (uri) {
9777 http_proxy = g_strdup(uri);
9778 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9779 proxy_uri = soup_uri_new(http_proxy);
9780 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9781 g_object_set(session, "proxy-uri", proxy_uri,
9782 (char *)NULL);
9787 set_http_proxy(char *proxy)
9789 SoupURI *uri;
9791 if (proxy == NULL)
9792 return (1);
9794 /* see if we need to clear it instead */
9795 if (strlen(proxy) == 0) {
9796 setup_proxy(NULL);
9797 return (0);
9800 uri = soup_uri_new(proxy);
9801 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9802 return (1);
9804 setup_proxy(proxy);
9806 soup_uri_free(uri);
9808 return (0);
9812 send_cmd_to_socket(char *cmd)
9814 int s, len, rv = 1;
9815 struct sockaddr_un sa;
9817 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9818 warnx("%s: socket", __func__);
9819 return (rv);
9822 sa.sun_family = AF_UNIX;
9823 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9824 work_dir, XT_SOCKET_FILE);
9825 len = SUN_LEN(&sa);
9827 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9828 warnx("%s: connect", __func__);
9829 goto done;
9832 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9833 warnx("%s: send", __func__);
9834 goto done;
9837 rv = 0;
9838 done:
9839 close(s);
9840 return (rv);
9843 gboolean
9844 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9846 int s, n;
9847 char str[XT_MAX_URL_LENGTH];
9848 socklen_t t = sizeof(struct sockaddr_un);
9849 struct sockaddr_un sa;
9850 struct passwd *p;
9851 uid_t uid;
9852 gid_t gid;
9853 struct tab *tt;
9854 gint fd = g_io_channel_unix_get_fd(source);
9856 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9857 warn("accept");
9858 return (FALSE);
9861 if (getpeereid(s, &uid, &gid) == -1) {
9862 warn("getpeereid");
9863 return (FALSE);
9865 if (uid != getuid() || gid != getgid()) {
9866 warnx("unauthorized user");
9867 return (FALSE);
9870 p = getpwuid(uid);
9871 if (p == NULL) {
9872 warnx("not a valid user");
9873 return (FALSE);
9876 n = recv(s, str, sizeof(str), 0);
9877 if (n <= 0)
9878 return (TRUE);
9880 tt = TAILQ_LAST(&tabs, tab_list);
9881 cmd_execute(tt, str);
9882 return (TRUE);
9886 is_running(void)
9888 int s, len, rv = 1;
9889 struct sockaddr_un sa;
9891 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9892 warn("is_running: socket");
9893 return (-1);
9896 sa.sun_family = AF_UNIX;
9897 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9898 work_dir, XT_SOCKET_FILE);
9899 len = SUN_LEN(&sa);
9901 /* connect to see if there is a listener */
9902 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9903 rv = 0; /* not running */
9904 else
9905 rv = 1; /* already running */
9907 close(s);
9909 return (rv);
9913 build_socket(void)
9915 int s, len;
9916 struct sockaddr_un sa;
9918 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9919 warn("build_socket: socket");
9920 return (-1);
9923 sa.sun_family = AF_UNIX;
9924 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9925 work_dir, XT_SOCKET_FILE);
9926 len = SUN_LEN(&sa);
9928 /* connect to see if there is a listener */
9929 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9930 /* no listener so we will */
9931 unlink(sa.sun_path);
9933 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9934 warn("build_socket: bind");
9935 goto done;
9938 if (listen(s, 1) == -1) {
9939 warn("build_socket: listen");
9940 goto done;
9943 return (s);
9946 done:
9947 close(s);
9948 return (-1);
9951 gboolean
9952 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9953 GtkTreeIter *iter, struct tab *t)
9955 gchar *value;
9957 gtk_tree_model_get(model, iter, 0, &value, -1);
9958 load_uri(t, value);
9959 g_free(value);
9961 return (FALSE);
9964 gboolean
9965 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9966 GtkTreeIter *iter, struct tab *t)
9968 gchar *value;
9970 gtk_tree_model_get(model, iter, 0, &value, -1);
9971 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9972 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9973 g_free(value);
9975 return (TRUE);
9978 void
9979 completion_add_uri(const gchar *uri)
9981 GtkTreeIter iter;
9983 /* add uri to list_store */
9984 gtk_list_store_append(completion_model, &iter);
9985 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9988 gboolean
9989 completion_match(GtkEntryCompletion *completion, const gchar *key,
9990 GtkTreeIter *iter, gpointer user_data)
9992 gchar *value;
9993 gboolean match = FALSE;
9995 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9996 -1);
9998 if (value == NULL)
9999 return FALSE;
10001 match = match_uri(value, key);
10003 g_free(value);
10004 return (match);
10007 void
10008 completion_add(struct tab *t)
10010 /* enable completion for tab */
10011 t->completion = gtk_entry_completion_new();
10012 gtk_entry_completion_set_text_column(t->completion, 0);
10013 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10014 gtk_entry_completion_set_model(t->completion,
10015 GTK_TREE_MODEL(completion_model));
10016 gtk_entry_completion_set_match_func(t->completion, completion_match,
10017 NULL, NULL);
10018 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10019 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10020 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10021 G_CALLBACK(completion_select_cb), t);
10022 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10023 G_CALLBACK(completion_hover_cb), t);
10026 void
10027 xxx_dir(char *dir)
10029 struct stat sb;
10031 if (stat(dir, &sb)) {
10032 if (mkdir(dir, S_IRWXU) == -1)
10033 err(1, "mkdir %s", dir);
10034 if (stat(dir, &sb))
10035 err(1, "stat %s", dir);
10037 if (S_ISDIR(sb.st_mode) == 0)
10038 errx(1, "%s not a dir", dir);
10039 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10040 warnx("fixing invalid permissions on %s", dir);
10041 if (chmod(dir, S_IRWXU) == -1)
10042 err(1, "chmod %s", dir);
10046 void
10047 usage(void)
10049 fprintf(stderr,
10050 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10051 exit(0);
10056 main(int argc, char *argv[])
10058 struct stat sb;
10059 int c, s, optn = 0, opte = 0, focus = 1;
10060 char conf[PATH_MAX] = { '\0' };
10061 char file[PATH_MAX];
10062 char *env_proxy = NULL;
10063 char *cmd = NULL;
10064 FILE *f = NULL;
10065 struct karg a;
10066 struct sigaction sact;
10067 GIOChannel *channel;
10068 struct rlimit rlp;
10070 start_argv = argv;
10072 /* prepare gtk */
10073 if (!g_thread_supported()) {
10074 g_thread_init(NULL);
10075 gdk_threads_init();
10077 gtk_init(&argc, &argv);
10079 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10081 RB_INIT(&hl);
10082 RB_INIT(&js_wl);
10083 RB_INIT(&downloads);
10085 TAILQ_INIT(&sessions);
10086 TAILQ_INIT(&tabs);
10087 TAILQ_INIT(&mtl);
10088 TAILQ_INIT(&aliases);
10089 TAILQ_INIT(&undos);
10090 TAILQ_INIT(&kbl);
10091 TAILQ_INIT(&spl);
10092 TAILQ_INIT(&chl);
10093 TAILQ_INIT(&shl);
10095 /* fiddle with ulimits */
10096 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10097 warn("getrlimit");
10098 else {
10099 /* just use them all */
10100 rlp.rlim_cur = rlp.rlim_max;
10101 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10102 warn("setrlimit");
10103 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10104 warn("getrlimit");
10105 else if (rlp.rlim_cur <= 256)
10106 startpage_add("%s requires at least 256 file "
10107 "descriptors, currently it has up to %d available",
10108 __progname, rlp.rlim_cur);
10111 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10112 switch (c) {
10113 case 'S':
10114 show_url = 0;
10115 break;
10116 case 'T':
10117 show_tabs = 0;
10118 break;
10119 case 'V':
10120 errx(0 , "Version: %s", version);
10121 break;
10122 case 'f':
10123 strlcpy(conf, optarg, sizeof(conf));
10124 break;
10125 case 's':
10126 strlcpy(named_session, optarg, sizeof(named_session));
10127 break;
10128 case 't':
10129 tabless = 1;
10130 break;
10131 case 'n':
10132 optn = 1;
10133 break;
10134 case 'e':
10135 opte = 1;
10136 break;
10137 default:
10138 usage();
10139 /* NOTREACHED */
10142 argc -= optind;
10143 argv += optind;
10145 init_keybindings();
10147 gnutls_global_init();
10149 /* generate session keys for xtp pages */
10150 generate_xtp_session_key(&dl_session_key);
10151 generate_xtp_session_key(&hl_session_key);
10152 generate_xtp_session_key(&cl_session_key);
10153 generate_xtp_session_key(&fl_session_key);
10155 /* signals */
10156 bzero(&sact, sizeof(sact));
10157 sigemptyset(&sact.sa_mask);
10158 sact.sa_handler = sigchild;
10159 sact.sa_flags = SA_NOCLDSTOP;
10160 sigaction(SIGCHLD, &sact, NULL);
10162 /* set download dir */
10163 pwd = getpwuid(getuid());
10164 if (pwd == NULL)
10165 errx(1, "invalid user %d", getuid());
10166 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10168 /* compile buffer command regexes */
10169 buffercmd_init();
10171 /* set default string settings */
10172 home = g_strdup("https://www.cyphertite.com");
10173 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10174 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10175 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10176 cmd_font_name = g_strdup("monospace normal 9");
10177 oops_font_name = g_strdup("monospace normal 9");
10178 statusbar_font_name = g_strdup("monospace normal 9");
10179 tabbar_font_name = g_strdup("monospace normal 9");
10180 statusbar_elems = g_strdup("BP");
10182 /* read config file */
10183 if (strlen(conf) == 0)
10184 snprintf(conf, sizeof conf, "%s/.%s",
10185 pwd->pw_dir, XT_CONF_FILE);
10186 config_parse(conf, 0);
10188 /* init fonts */
10189 cmd_font = pango_font_description_from_string(cmd_font_name);
10190 oops_font = pango_font_description_from_string(oops_font_name);
10191 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10192 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10194 /* working directory */
10195 if (strlen(work_dir) == 0)
10196 snprintf(work_dir, sizeof work_dir, "%s/%s",
10197 pwd->pw_dir, XT_DIR);
10198 xxx_dir(work_dir);
10200 /* icon cache dir */
10201 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10202 xxx_dir(cache_dir);
10204 /* certs dir */
10205 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10206 xxx_dir(certs_dir);
10208 /* sessions dir */
10209 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10210 work_dir, XT_SESSIONS_DIR);
10211 xxx_dir(sessions_dir);
10213 /* runtime settings that can override config file */
10214 if (runtime_settings[0] != '\0')
10215 config_parse(runtime_settings, 1);
10217 /* download dir */
10218 if (!strcmp(download_dir, pwd->pw_dir))
10219 strlcat(download_dir, "/downloads", sizeof download_dir);
10220 xxx_dir(download_dir);
10222 /* favorites file */
10223 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10224 if (stat(file, &sb)) {
10225 warnx("favorites file doesn't exist, creating it");
10226 if ((f = fopen(file, "w")) == NULL)
10227 err(1, "favorites");
10228 fclose(f);
10231 /* quickmarks file */
10232 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10233 if (stat(file, &sb)) {
10234 warnx("quickmarks file doesn't exist, creating it");
10235 if ((f = fopen(file, "w")) == NULL)
10236 err(1, "quickmarks");
10237 fclose(f);
10240 /* search history */
10241 if (history_autosave) {
10242 snprintf(search_file, sizeof search_file, "%s/%s",
10243 work_dir, XT_SEARCH_FILE);
10244 if (stat(search_file, &sb)) {
10245 warnx("search history file doesn't exist, creating it");
10246 if ((f = fopen(search_file, "w")) == NULL)
10247 err(1, "search_history");
10248 fclose(f);
10250 history_read(&shl, search_file, &search_history_count);
10253 /* command history */
10254 if (history_autosave) {
10255 snprintf(command_file, sizeof command_file, "%s/%s",
10256 work_dir, XT_COMMAND_FILE);
10257 if (stat(command_file, &sb)) {
10258 warnx("command history file doesn't exist, creating it");
10259 if ((f = fopen(command_file, "w")) == NULL)
10260 err(1, "command_history");
10261 fclose(f);
10263 history_read(&chl, command_file, &cmd_history_count);
10266 /* cookies */
10267 session = webkit_get_default_session();
10268 setup_cookies();
10270 /* certs */
10271 if (ssl_ca_file) {
10272 if (stat(ssl_ca_file, &sb)) {
10273 warnx("no CA file: %s", ssl_ca_file);
10274 g_free(ssl_ca_file);
10275 ssl_ca_file = NULL;
10276 } else
10277 g_object_set(session,
10278 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10279 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10280 (void *)NULL);
10283 /* guess_search regex */
10284 if (url_regex == NULL)
10285 url_regex = g_strdup(XT_URL_REGEX);
10286 if (url_regex)
10287 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10288 startpage_add("invalid url regex %s", url_regex);
10290 /* proxy */
10291 env_proxy = getenv("http_proxy");
10292 if (env_proxy)
10293 setup_proxy(env_proxy);
10294 else
10295 setup_proxy(http_proxy);
10297 if (opte) {
10298 send_cmd_to_socket(argv[0]);
10299 exit(0);
10302 /* set some connection parameters */
10303 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10304 g_object_set(session, "max-conns-per-host", max_host_connections,
10305 (char *)NULL);
10307 /* see if there is already an xxxterm running */
10308 if (single_instance && is_running()) {
10309 optn = 1;
10310 warnx("already running");
10313 if (optn) {
10314 while (argc) {
10315 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10316 send_cmd_to_socket(cmd);
10317 if (cmd)
10318 g_free(cmd);
10320 argc--;
10321 argv++;
10323 exit(0);
10326 /* uri completion */
10327 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10329 /* buffers */
10330 buffers_store = gtk_list_store_new
10331 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10333 qmarks_load();
10335 /* go graphical */
10336 create_canvas();
10337 notebook_tab_set_visibility();
10339 if (save_global_history)
10340 restore_global_history();
10342 /* restore session list */
10343 restore_sessions_list();
10345 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10346 restore_saved_tabs();
10347 else {
10348 a.s = named_session;
10349 a.i = XT_SES_DONOTHING;
10350 open_tabs(NULL, &a);
10353 /* see if we have an exception */
10354 if (!TAILQ_EMPTY(&spl)) {
10355 create_new_tab("about:startpage", NULL, focus, -1);
10356 focus = 0;
10359 while (argc) {
10360 create_new_tab(argv[0], NULL, focus, -1);
10361 focus = 0;
10363 argc--;
10364 argv++;
10367 if (TAILQ_EMPTY(&tabs))
10368 create_new_tab(home, NULL, 1, -1);
10370 if (enable_socket)
10371 if ((s = build_socket()) != -1) {
10372 channel = g_io_channel_unix_new(s);
10373 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10376 gtk_main();
10378 gnutls_global_deinit();
10380 if (url_regex)
10381 regfree(&url_re);
10383 return (0);