Add xxxterm.core to CLEANFILES in Makefile
[xxxterm/127001.git] / xxxterm.c
blob42ae8d85d6274e33a95dc5dacce18349dabb33b4
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pwd.h>
35 #include <regex.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <dirent.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #include <bsd/stdlib.h>
49 #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)
518 #define XT_SETARG (1<<5)
520 #define XT_TABS_NORMAL 0
521 #define XT_TABS_COMPACT 1
523 #define XT_BUFCMD_SZ (8)
525 /* mime types */
526 struct mime_type {
527 char *mt_type;
528 char *mt_action;
529 int mt_default;
530 int mt_download;
531 TAILQ_ENTRY(mime_type) entry;
533 TAILQ_HEAD(mime_type_list, mime_type);
535 /* uri aliases */
536 struct alias {
537 char *a_name;
538 char *a_uri;
539 TAILQ_ENTRY(alias) entry;
541 TAILQ_HEAD(alias_list, alias);
543 /* settings that require restart */
544 int tabless = 0; /* allow only 1 tab */
545 int enable_socket = 0;
546 int single_instance = 0; /* only allow one xxxterm to run */
547 int fancy_bar = 1; /* fancy toolbar */
548 int browser_mode = XT_BM_NORMAL;
549 int enable_localstorage = 1;
550 char *statusbar_elems = NULL;
552 /* runtime settings */
553 int show_tabs = 1; /* show tabs on notebook */
554 int tab_style = XT_TABS_NORMAL; /* tab bar style */
555 int show_url = 1; /* show url toolbar on notebook */
556 int show_statusbar = 0; /* vimperator style status bar */
557 int ctrl_click_focus = 0; /* ctrl click gets focus */
558 int cookies_enabled = 1; /* enable cookies */
559 int read_only_cookies = 0; /* enable to not write cookies */
560 int enable_scripts = 1;
561 int enable_plugins = 0;
562 gfloat default_zoom_level = 1.0;
563 char default_script[PATH_MAX];
564 int window_height = 768;
565 int window_width = 1024;
566 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
567 int refresh_interval = 10; /* download refresh interval */
568 int enable_cookie_whitelist = 0;
569 int enable_js_whitelist = 0;
570 int session_timeout = 3600; /* cookie session timeout */
571 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
572 char *ssl_ca_file = NULL;
573 char *resource_dir = NULL;
574 gboolean ssl_strict_certs = FALSE;
575 int append_next = 1; /* append tab after current tab */
576 char *home = NULL;
577 char *search_string = NULL;
578 char *http_proxy = NULL;
579 char download_dir[PATH_MAX];
580 char runtime_settings[PATH_MAX]; /* override of settings */
581 int allow_volatile_cookies = 0;
582 int save_global_history = 0; /* save global history to disk */
583 char *user_agent = NULL;
584 int save_rejected_cookies = 0;
585 int session_autosave = 0;
586 int guess_search = 0;
587 int dns_prefetch = FALSE;
588 gint max_connections = 25;
589 gint max_host_connections = 5;
590 gint enable_spell_checking = 0;
591 char *spell_check_languages = NULL;
592 int xterm_workaround = 0;
593 char *url_regex = NULL;
594 int history_autosave = 0;
595 char search_file[PATH_MAX];
596 char command_file[PATH_MAX];
598 char *cmd_font_name = NULL;
599 char *oops_font_name = NULL;
600 char *statusbar_font_name = NULL;
601 char *tabbar_font_name = NULL;
602 PangoFontDescription *cmd_font;
603 PangoFontDescription *oops_font;
604 PangoFontDescription *statusbar_font;
605 PangoFontDescription *tabbar_font;
606 char *qmarks[XT_NOMARKS];
608 int btn_down; /* M1 down in any wv */
609 regex_t url_re; /* guess_search regex */
611 struct settings;
612 struct key_binding;
613 int set_browser_mode(struct settings *, char *);
614 int set_cookie_policy(struct settings *, char *);
615 int set_download_dir(struct settings *, char *);
616 int set_default_script(struct settings *, char *);
617 int set_runtime_dir(struct settings *, char *);
618 int set_tab_style(struct settings *, char *);
619 int set_work_dir(struct settings *, char *);
620 int add_alias(struct settings *, char *);
621 int add_mime_type(struct settings *, char *);
622 int add_cookie_wl(struct settings *, char *);
623 int add_js_wl(struct settings *, char *);
624 int add_kb(struct settings *, char *);
625 void button_set_stockid(GtkWidget *, char *);
626 GtkWidget * create_button(char *, char *, int);
628 char *get_browser_mode(struct settings *);
629 char *get_cookie_policy(struct settings *);
630 char *get_download_dir(struct settings *);
631 char *get_default_script(struct settings *);
632 char *get_runtime_dir(struct settings *);
633 char *get_tab_style(struct settings *);
634 char *get_work_dir(struct settings *);
635 void startpage_add(const char *, ...);
637 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
638 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
639 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
640 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
641 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
643 void recalc_tabs(void);
644 void recolor_compact_tabs(void);
645 void set_current_tab(int page_num);
646 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
647 void marks_clear(struct tab *t);
649 int set_http_proxy(char *);
651 struct special {
652 int (*set)(struct settings *, char *);
653 char *(*get)(struct settings *);
654 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
657 struct special s_browser_mode = {
658 set_browser_mode,
659 get_browser_mode,
660 NULL
663 struct special s_cookie = {
664 set_cookie_policy,
665 get_cookie_policy,
666 NULL
669 struct special s_alias = {
670 add_alias,
671 NULL,
672 walk_alias
675 struct special s_mime = {
676 add_mime_type,
677 NULL,
678 walk_mime_type
681 struct special s_js = {
682 add_js_wl,
683 NULL,
684 walk_js_wl
687 struct special s_kb = {
688 add_kb,
689 NULL,
690 walk_kb
693 struct special s_cookie_wl = {
694 add_cookie_wl,
695 NULL,
696 walk_cookie_wl
699 struct special s_default_script = {
700 set_default_script,
701 get_default_script,
702 NULL
705 struct special s_download_dir = {
706 set_download_dir,
707 get_download_dir,
708 NULL
711 struct special s_work_dir = {
712 set_work_dir,
713 get_work_dir,
714 NULL
717 struct special s_tab_style = {
718 set_tab_style,
719 get_tab_style,
720 NULL
723 struct settings {
724 char *name;
725 int type;
726 #define XT_S_INVALID (0)
727 #define XT_S_INT (1)
728 #define XT_S_STR (2)
729 #define XT_S_FLOAT (3)
730 uint32_t flags;
731 #define XT_SF_RESTART (1<<0)
732 #define XT_SF_RUNTIME (1<<1)
733 int *ival;
734 char **sval;
735 struct special *s;
736 gfloat *fval;
737 int (*activate)(char *);
738 } rs[] = {
739 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
740 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
741 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
742 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
743 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
744 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
745 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
746 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
747 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
748 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
749 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
750 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
751 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
752 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
753 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
754 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
755 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
756 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
757 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
758 { "home", XT_S_STR, 0, NULL, &home, NULL },
759 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
760 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
761 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
762 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
763 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
764 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
765 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
766 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
767 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
768 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
769 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
770 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
771 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
772 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
773 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
774 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
775 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
776 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
777 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
778 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
779 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
780 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
781 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
782 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
783 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
784 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
785 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
787 /* font settings */
788 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
789 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
790 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
791 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
793 /* runtime settings */
794 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
795 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
796 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
797 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
798 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
801 int about(struct tab *, struct karg *);
802 int blank(struct tab *, struct karg *);
803 int ca_cmd(struct tab *, struct karg *);
804 int cookie_show_wl(struct tab *, struct karg *);
805 int js_show_wl(struct tab *, struct karg *);
806 int help(struct tab *, struct karg *);
807 int set(struct tab *, struct karg *);
808 int stats(struct tab *, struct karg *);
809 int marco(struct tab *, struct karg *);
810 int startpage(struct tab *, struct karg *);
811 const char * marco_message(int *);
812 int xtp_page_cl(struct tab *, struct karg *);
813 int xtp_page_dl(struct tab *, struct karg *);
814 int xtp_page_fl(struct tab *, struct karg *);
815 int xtp_page_hl(struct tab *, struct karg *);
816 void xt_icon_from_file(struct tab *, char *);
817 const gchar *get_uri(struct tab *);
818 const gchar *get_title(struct tab *, bool);
820 #define XT_URI_ABOUT ("about:")
821 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
822 #define XT_URI_ABOUT_ABOUT ("about")
823 #define XT_URI_ABOUT_BLANK ("blank")
824 #define XT_URI_ABOUT_CERTS ("certs")
825 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
826 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
827 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
828 #define XT_URI_ABOUT_FAVORITES ("favorites")
829 #define XT_URI_ABOUT_HELP ("help")
830 #define XT_URI_ABOUT_HISTORY ("history")
831 #define XT_URI_ABOUT_JSWL ("jswl")
832 #define XT_URI_ABOUT_SET ("set")
833 #define XT_URI_ABOUT_STATS ("stats")
834 #define XT_URI_ABOUT_MARCO ("marco")
835 #define XT_URI_ABOUT_STARTPAGE ("startpage")
837 struct about_type {
838 char *name;
839 int (*func)(struct tab *, struct karg *);
840 } about_list[] = {
841 { XT_URI_ABOUT_ABOUT, about },
842 { XT_URI_ABOUT_BLANK, blank },
843 { XT_URI_ABOUT_CERTS, ca_cmd },
844 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
845 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
846 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
847 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
848 { XT_URI_ABOUT_HELP, help },
849 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
850 { XT_URI_ABOUT_JSWL, js_show_wl },
851 { XT_URI_ABOUT_SET, set },
852 { XT_URI_ABOUT_STATS, stats },
853 { XT_URI_ABOUT_MARCO, marco },
854 { XT_URI_ABOUT_STARTPAGE, startpage },
857 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
858 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
859 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
860 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
861 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
862 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
863 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
865 /* globals */
866 extern char *__progname;
867 char **start_argv;
868 struct passwd *pwd;
869 GtkWidget *main_window;
870 GtkNotebook *notebook;
871 GtkWidget *tab_bar;
872 GtkWidget *arrow, *abtn;
873 struct tab_list tabs;
874 struct history_list hl;
875 struct session_list sessions;
876 struct download_list downloads;
877 struct domain_list c_wl;
878 struct domain_list js_wl;
879 struct undo_tailq undos;
880 struct keybinding_list kbl;
881 struct sp_list spl;
882 struct command_list chl;
883 struct command_list shl;
884 struct command_entry *history_at;
885 struct command_entry *search_at;
886 int undo_count;
887 int updating_dl_tabs = 0;
888 int updating_hl_tabs = 0;
889 int updating_cl_tabs = 0;
890 int updating_fl_tabs = 0;
891 int cmd_history_count = 0;
892 int search_history_count = 0;
893 char *global_search;
894 uint64_t blocked_cookies = 0;
895 char named_session[PATH_MAX];
896 GtkListStore *completion_model;
897 GtkListStore *buffers_store;
899 void xxx_dir(char *);
900 int icon_size_map(int);
901 void completion_add(struct tab *);
902 void completion_add_uri(const gchar *);
903 void show_oops(struct tab *, const char *, ...);
905 void
906 history_delete(struct command_list *l, int *counter)
908 struct command_entry *c;
910 if (l == NULL || counter == NULL)
911 return;
913 c = TAILQ_LAST(l, command_list);
914 if (c == NULL)
915 return;
917 TAILQ_REMOVE(l, c, entry);
918 *counter -= 1;
919 g_free(c->line);
920 g_free(c);
923 void
924 history_add(struct command_list *list, char *file, char *l, int *counter)
926 struct command_entry *c;
927 FILE *f;
929 if (list == NULL || l == NULL || counter == NULL)
930 return;
932 /* don't add the same line */
933 c = TAILQ_FIRST(list);
934 if (c)
935 if (!strcmp(c->line + 1 /* skip space */, l))
936 return;
938 c = g_malloc0(sizeof *c);
939 c->line = g_strdup_printf(" %s", l);
941 *counter += 1;
942 TAILQ_INSERT_HEAD(list, c, entry);
944 if (*counter > 1000)
945 history_delete(list, counter);
947 if (history_autosave && file) {
948 f = fopen(file, "w");
949 if (f == NULL) {
950 show_oops(NULL, "couldn't write history %s", file);
951 return;
954 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
955 c->line[0] = ' ';
956 fprintf(f, "%s\n", c->line);
959 fclose(f);
964 history_read(struct command_list *list, char *file, int *counter)
966 FILE *f;
967 char *s, line[65536];
969 if (list == NULL || file == NULL)
970 return (1);
972 f = fopen(file, "r");
973 if (f == NULL) {
974 startpage_add("couldn't open history file %s", file);
975 return (1);
978 for (;;) {
979 s = fgets(line, sizeof line, f);
980 if (s == NULL || feof(f) || ferror(f))
981 break;
982 if ((s = strchr(line, '\n')) == NULL) {
983 startpage_add("invalid history file %s", file);
984 fclose(f);
985 return (1);
987 *s = '\0';
989 history_add(list, NULL, line + 1, counter);
992 fclose(f);
994 return (0);
997 /* marks and quickmarks array storage.
998 * first a-z, then A-Z, then 0-9 */
999 char
1000 indextomark(int i)
1002 if (i < 0)
1003 return 0;
1005 if (i >= 0 && i <= 'z' - 'a')
1006 return 'a' + i;
1008 i -= 'z' - 'a' + 1;
1009 if (i >= 0 && i <= 'Z' - 'A')
1010 return 'A' + i;
1012 i -= 'Z' - 'A' + 1;
1013 if (i >= 10)
1014 return 0;
1016 return i + '0';
1020 marktoindex(char m)
1022 int ret = 0;
1024 if (m >= 'a' && m <= 'z')
1025 return ret + m - 'a';
1027 ret += 'z' - 'a' + 1;
1028 if (m >= 'A' && m <= 'Z')
1029 return ret + m - 'A';
1031 ret += 'Z' - 'A' + 1;
1032 if (m >= '0' && m <= '9')
1033 return ret + m - '0';
1035 return -1;
1039 void
1040 sigchild(int sig)
1042 int saved_errno, status;
1043 pid_t pid;
1045 saved_errno = errno;
1047 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1048 if (pid == -1) {
1049 if (errno == EINTR)
1050 continue;
1051 if (errno != ECHILD) {
1053 clog_warn("sigchild: waitpid:");
1056 break;
1059 if (WIFEXITED(status)) {
1060 if (WEXITSTATUS(status) != 0) {
1062 clog_warnx("sigchild: child exit status: %d",
1063 WEXITSTATUS(status));
1066 } else {
1068 clog_warnx("sigchild: child is terminated abnormally");
1073 errno = saved_errno;
1077 is_g_object_setting(GObject *o, char *str)
1079 guint n_props = 0, i;
1080 GParamSpec **proplist;
1082 if (! G_IS_OBJECT(o))
1083 return (0);
1085 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1086 &n_props);
1088 for (i=0; i < n_props; i++) {
1089 if (! strcmp(proplist[i]->name, str))
1090 return (1);
1092 return (0);
1095 gchar *
1096 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1098 gchar *r;
1100 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1101 "<head>\n"
1102 "<title>%s</title>\n"
1103 "%s"
1104 "%s"
1105 "</head>\n"
1106 "<body>\n"
1107 "<h1>%s</h1>\n"
1108 "%s\n</body>\n"
1109 "</html>",
1110 title,
1111 addstyles ? XT_PAGE_STYLE : "",
1112 head,
1113 title,
1114 body);
1116 return r;
1120 * Display a web page from a HTML string in memory, rather than from a URL
1122 void
1123 load_webkit_string(struct tab *t, const char *str, gchar *title)
1125 char file[PATH_MAX];
1126 int i;
1128 /* we set this to indicate we want to manually do navaction */
1129 if (t->bfl)
1130 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1132 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1133 if (title) {
1134 /* set t->xtp_meaning */
1135 for (i = 0; i < LENGTH(about_list); i++)
1136 if (!strcmp(title, about_list[i].name)) {
1137 t->xtp_meaning = i;
1138 break;
1141 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1142 #if GTK_CHECK_VERSION(2, 20, 0)
1143 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1144 gtk_widget_hide(t->spinner);
1145 #endif
1146 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1147 xt_icon_from_file(t, file);
1151 struct tab *
1152 get_current_tab(void)
1154 struct tab *t;
1156 TAILQ_FOREACH(t, &tabs, entry) {
1157 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1158 return (t);
1161 warnx("%s: no current tab", __func__);
1163 return (NULL);
1166 void
1167 set_status(struct tab *t, gchar *s, int status)
1169 gchar *type = NULL;
1171 if (s == NULL)
1172 return;
1174 switch (status) {
1175 case XT_STATUS_LOADING:
1176 type = g_strdup_printf("Loading: %s", s);
1177 s = type;
1178 break;
1179 case XT_STATUS_LINK:
1180 type = g_strdup_printf("Link: %s", s);
1181 if (!t->status)
1182 t->status = g_strdup(gtk_entry_get_text(
1183 GTK_ENTRY(t->sbe.statusbar)));
1184 s = type;
1185 break;
1186 case XT_STATUS_URI:
1187 type = g_strdup_printf("%s", s);
1188 if (!t->status) {
1189 t->status = g_strdup(type);
1191 s = type;
1192 if (!t->status)
1193 t->status = g_strdup(s);
1194 break;
1195 case XT_STATUS_NOTHING:
1196 /* FALL THROUGH */
1197 default:
1198 break;
1200 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1201 if (type)
1202 g_free(type);
1205 void
1206 hide_cmd(struct tab *t)
1208 history_at = NULL; /* just in case */
1209 search_at = NULL; /* just in case */
1210 gtk_widget_hide(t->cmd);
1213 void
1214 show_cmd(struct tab *t)
1216 history_at = NULL;
1217 search_at = NULL;
1218 gtk_widget_hide(t->oops);
1219 gtk_widget_show(t->cmd);
1222 void
1223 hide_buffers(struct tab *t)
1225 gtk_widget_hide(t->buffers);
1226 gtk_list_store_clear(buffers_store);
1229 enum {
1230 COL_ID = 0,
1231 COL_TITLE,
1232 NUM_COLS
1236 sort_tabs_by_page_num(struct tab ***stabs)
1238 int num_tabs = 0;
1239 struct tab *t;
1241 num_tabs = gtk_notebook_get_n_pages(notebook);
1243 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1245 TAILQ_FOREACH(t, &tabs, entry)
1246 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1248 return (num_tabs);
1251 void
1252 buffers_make_list(void)
1254 int i, num_tabs;
1255 const gchar *title = NULL;
1256 GtkTreeIter iter;
1257 struct tab **stabs = NULL;
1259 num_tabs = sort_tabs_by_page_num(&stabs);
1261 for (i = 0; i < num_tabs; i++)
1262 if (stabs[i]) {
1263 gtk_list_store_append(buffers_store, &iter);
1264 title = get_title(stabs[i], FALSE);
1265 gtk_list_store_set(buffers_store, &iter,
1266 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1267 * rather than 0. */
1268 COL_TITLE, title,
1269 -1);
1272 g_free(stabs);
1275 void
1276 show_buffers(struct tab *t)
1278 buffers_make_list();
1279 gtk_widget_show(t->buffers);
1280 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1283 void
1284 toggle_buffers(struct tab *t)
1286 if (gtk_widget_get_visible(t->buffers))
1287 hide_buffers(t);
1288 else
1289 show_buffers(t);
1293 buffers(struct tab *t, struct karg *args)
1295 show_buffers(t);
1297 return (0);
1300 void
1301 hide_oops(struct tab *t)
1303 gtk_widget_hide(t->oops);
1306 void
1307 show_oops(struct tab *at, const char *fmt, ...)
1309 va_list ap;
1310 char *msg = NULL;
1311 struct tab *t = NULL;
1313 if (fmt == NULL)
1314 return;
1316 if (at == NULL) {
1317 if ((t = get_current_tab()) == NULL)
1318 return;
1319 } else
1320 t = at;
1322 va_start(ap, fmt);
1323 if (vasprintf(&msg, fmt, ap) == -1)
1324 errx(1, "show_oops failed");
1325 va_end(ap);
1327 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1328 gtk_widget_hide(t->cmd);
1329 gtk_widget_show(t->oops);
1331 if (msg)
1332 free(msg);
1335 char *
1336 get_as_string(struct settings *s)
1338 char *r = NULL;
1340 if (s == NULL)
1341 return (NULL);
1343 if (s->s) {
1344 if (s->s->get)
1345 r = s->s->get(s);
1346 else
1347 warnx("get_as_string skip %s\n", s->name);
1348 } else if (s->type == XT_S_INT)
1349 r = g_strdup_printf("%d", *s->ival);
1350 else if (s->type == XT_S_STR)
1351 r = g_strdup(*s->sval);
1352 else if (s->type == XT_S_FLOAT)
1353 r = g_strdup_printf("%f", *s->fval);
1354 else
1355 r = g_strdup_printf("INVALID TYPE");
1357 return (r);
1360 void
1361 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1363 int i;
1364 char *s;
1366 for (i = 0; i < LENGTH(rs); i++) {
1367 if (rs[i].s && rs[i].s->walk)
1368 rs[i].s->walk(&rs[i], cb, cb_args);
1369 else {
1370 s = get_as_string(&rs[i]);
1371 cb(&rs[i], s, cb_args);
1372 g_free(s);
1378 set_browser_mode(struct settings *s, char *val)
1380 if (!strcmp(val, "whitelist")) {
1381 browser_mode = XT_BM_WHITELIST;
1382 allow_volatile_cookies = 0;
1383 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1384 cookies_enabled = 1;
1385 enable_cookie_whitelist = 1;
1386 read_only_cookies = 0;
1387 save_rejected_cookies = 0;
1388 session_timeout = 3600;
1389 enable_scripts = 0;
1390 enable_js_whitelist = 1;
1391 enable_localstorage = 0;
1392 } else if (!strcmp(val, "normal")) {
1393 browser_mode = XT_BM_NORMAL;
1394 allow_volatile_cookies = 0;
1395 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1396 cookies_enabled = 1;
1397 enable_cookie_whitelist = 0;
1398 read_only_cookies = 0;
1399 save_rejected_cookies = 0;
1400 session_timeout = 3600;
1401 enable_scripts = 1;
1402 enable_js_whitelist = 0;
1403 enable_localstorage = 1;
1404 } else if (!strcmp(val, "kiosk")) {
1405 browser_mode = XT_BM_KIOSK;
1406 allow_volatile_cookies = 0;
1407 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1408 cookies_enabled = 1;
1409 enable_cookie_whitelist = 0;
1410 read_only_cookies = 0;
1411 save_rejected_cookies = 0;
1412 session_timeout = 3600;
1413 enable_scripts = 1;
1414 enable_js_whitelist = 0;
1415 enable_localstorage = 1;
1416 show_tabs = 0;
1417 tabless = 1;
1418 } else
1419 return (1);
1421 return (0);
1424 char *
1425 get_browser_mode(struct settings *s)
1427 char *r = NULL;
1429 if (browser_mode == XT_BM_WHITELIST)
1430 r = g_strdup("whitelist");
1431 else if (browser_mode == XT_BM_NORMAL)
1432 r = g_strdup("normal");
1433 else if (browser_mode == XT_BM_KIOSK)
1434 r = g_strdup("kiosk");
1435 else
1436 return (NULL);
1438 return (r);
1442 set_cookie_policy(struct settings *s, char *val)
1444 if (!strcmp(val, "no3rdparty"))
1445 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1446 else if (!strcmp(val, "accept"))
1447 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1448 else if (!strcmp(val, "reject"))
1449 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1450 else
1451 return (1);
1453 return (0);
1456 char *
1457 get_cookie_policy(struct settings *s)
1459 char *r = NULL;
1461 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1462 r = g_strdup("no3rdparty");
1463 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1464 r = g_strdup("accept");
1465 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1466 r = g_strdup("reject");
1467 else
1468 return (NULL);
1470 return (r);
1473 char *
1474 get_default_script(struct settings *s)
1476 if (default_script[0] == '\0')
1477 return (0);
1478 return (g_strdup(default_script));
1482 set_default_script(struct settings *s, char *val)
1484 if (val[0] == '~')
1485 snprintf(default_script, sizeof default_script, "%s/%s",
1486 pwd->pw_dir, &val[1]);
1487 else
1488 strlcpy(default_script, val, sizeof default_script);
1490 return (0);
1493 char *
1494 get_download_dir(struct settings *s)
1496 if (download_dir[0] == '\0')
1497 return (0);
1498 return (g_strdup(download_dir));
1502 set_download_dir(struct settings *s, char *val)
1504 if (val[0] == '~')
1505 snprintf(download_dir, sizeof download_dir, "%s/%s",
1506 pwd->pw_dir, &val[1]);
1507 else
1508 strlcpy(download_dir, val, sizeof download_dir);
1510 return (0);
1514 * Session IDs.
1515 * We use these to prevent people putting xxxt:// URLs on
1516 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1518 #define XT_XTP_SES_KEY_SZ 8
1519 #define XT_XTP_SES_KEY_HEX_FMT \
1520 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1521 char *dl_session_key; /* downloads */
1522 char *hl_session_key; /* history list */
1523 char *cl_session_key; /* cookie list */
1524 char *fl_session_key; /* favorites list */
1526 char work_dir[PATH_MAX];
1527 char certs_dir[PATH_MAX];
1528 char cache_dir[PATH_MAX];
1529 char sessions_dir[PATH_MAX];
1530 char cookie_file[PATH_MAX];
1531 SoupURI *proxy_uri = NULL;
1532 SoupSession *session;
1533 SoupCookieJar *s_cookiejar;
1534 SoupCookieJar *p_cookiejar;
1535 char rc_fname[PATH_MAX];
1537 struct mime_type_list mtl;
1538 struct alias_list aliases;
1540 /* protos */
1541 struct tab *create_new_tab(char *, struct undo *, int, int);
1542 void delete_tab(struct tab *);
1543 void setzoom_webkit(struct tab *, int);
1544 int run_script(struct tab *, char *);
1545 int download_rb_cmp(struct download *, struct download *);
1546 gboolean cmd_execute(struct tab *t, char *str);
1549 history_rb_cmp(struct history *h1, struct history *h2)
1551 return (strcmp(h1->uri, h2->uri));
1553 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1556 domain_rb_cmp(struct domain *d1, struct domain *d2)
1558 return (strcmp(d1->d, d2->d));
1560 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1562 char *
1563 get_work_dir(struct settings *s)
1565 if (work_dir[0] == '\0')
1566 return (0);
1567 return (g_strdup(work_dir));
1571 set_work_dir(struct settings *s, char *val)
1573 if (val[0] == '~')
1574 snprintf(work_dir, sizeof work_dir, "%s/%s",
1575 pwd->pw_dir, &val[1]);
1576 else
1577 strlcpy(work_dir, val, sizeof work_dir);
1579 return (0);
1582 char *
1583 get_tab_style(struct settings *s)
1585 if (tab_style == XT_TABS_NORMAL)
1586 return (g_strdup("normal"));
1587 else
1588 return (g_strdup("compact"));
1592 set_tab_style(struct settings *s, char *val)
1594 if (!strcmp(val, "normal"))
1595 tab_style = XT_TABS_NORMAL;
1596 else if (!strcmp(val, "compact"))
1597 tab_style = XT_TABS_COMPACT;
1598 else
1599 return (1);
1601 return (0);
1605 * generate a session key to secure xtp commands.
1606 * pass in a ptr to the key in question and it will
1607 * be modified in place.
1609 void
1610 generate_xtp_session_key(char **key)
1612 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1614 /* free old key */
1615 if (*key)
1616 g_free(*key);
1618 /* make a new one */
1619 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1620 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1621 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1622 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1624 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1628 * validate a xtp session key.
1629 * return 1 if OK
1632 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1634 if (strcmp(trusted, untrusted) != 0) {
1635 show_oops(t, "%s: xtp session key mismatch possible spoof",
1636 __func__);
1637 return (0);
1640 return (1);
1644 download_rb_cmp(struct download *e1, struct download *e2)
1646 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1648 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1650 struct valid_url_types {
1651 char *type;
1652 } vut[] = {
1653 { "http://" },
1654 { "https://" },
1655 { "ftp://" },
1656 { "file://" },
1657 { XT_XTP_STR },
1661 valid_url_type(char *url)
1663 int i;
1665 for (i = 0; i < LENGTH(vut); i++)
1666 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1667 return (0);
1669 return (1);
1672 void
1673 print_cookie(char *msg, SoupCookie *c)
1675 if (c == NULL)
1676 return;
1678 if (msg)
1679 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1680 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1681 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1682 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1683 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1684 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1685 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1686 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1687 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1688 DNPRINTF(XT_D_COOKIE, "====================================\n");
1691 void
1692 walk_alias(struct settings *s,
1693 void (*cb)(struct settings *, char *, void *), void *cb_args)
1695 struct alias *a;
1696 char *str;
1698 if (s == NULL || cb == NULL) {
1699 show_oops(NULL, "walk_alias invalid parameters");
1700 return;
1703 TAILQ_FOREACH(a, &aliases, entry) {
1704 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1705 cb(s, str, cb_args);
1706 g_free(str);
1710 char *
1711 match_alias(char *url_in)
1713 struct alias *a;
1714 char *arg;
1715 char *url_out = NULL, *search, *enc_arg;
1717 search = g_strdup(url_in);
1718 arg = search;
1719 if (strsep(&arg, " \t") == NULL) {
1720 show_oops(NULL, "match_alias: NULL URL");
1721 goto done;
1724 TAILQ_FOREACH(a, &aliases, entry) {
1725 if (!strcmp(search, a->a_name))
1726 break;
1729 if (a != NULL) {
1730 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1731 a->a_name);
1732 if (arg != NULL) {
1733 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1734 url_out = g_strdup_printf(a->a_uri, enc_arg);
1735 g_free(enc_arg);
1736 } else
1737 url_out = g_strdup_printf(a->a_uri, "");
1739 done:
1740 g_free(search);
1741 return (url_out);
1744 char *
1745 guess_url_type(char *url_in)
1747 struct stat sb;
1748 char *url_out = NULL, *enc_search = NULL;
1749 int i;
1751 /* substitute aliases */
1752 url_out = match_alias(url_in);
1753 if (url_out != NULL)
1754 return (url_out);
1756 /* see if we are an about page */
1757 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1758 for (i = 0; i < LENGTH(about_list); i++)
1759 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1760 about_list[i].name)) {
1761 url_out = g_strdup(url_in);
1762 goto done;
1765 if (guess_search && url_regex &&
1766 !(g_str_has_prefix(url_in, "http://") ||
1767 g_str_has_prefix(url_in, "https://"))) {
1768 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1769 /* invalid URI so search instead */
1770 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1771 url_out = g_strdup_printf(search_string, enc_search);
1772 g_free(enc_search);
1773 goto done;
1777 /* XXX not sure about this heuristic */
1778 if (stat(url_in, &sb) == 0)
1779 url_out = g_strdup_printf("file://%s", url_in);
1780 else
1781 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1782 done:
1783 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1785 return (url_out);
1788 void
1789 load_uri(struct tab *t, gchar *uri)
1791 struct karg args;
1792 gchar *newuri = NULL;
1793 int i;
1795 if (uri == NULL)
1796 return;
1798 /* Strip leading spaces. */
1799 while (*uri && isspace(*uri))
1800 uri++;
1802 if (strlen(uri) == 0) {
1803 blank(t, NULL);
1804 return;
1807 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1809 if (valid_url_type(uri)) {
1810 newuri = guess_url_type(uri);
1811 uri = newuri;
1814 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1815 for (i = 0; i < LENGTH(about_list); i++)
1816 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1817 bzero(&args, sizeof args);
1818 about_list[i].func(t, &args);
1819 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1820 FALSE);
1821 goto done;
1823 show_oops(t, "invalid about page");
1824 goto done;
1827 set_status(t, (char *)uri, XT_STATUS_LOADING);
1828 marks_clear(t);
1829 webkit_web_view_load_uri(t->wv, uri);
1830 done:
1831 if (newuri)
1832 g_free(newuri);
1835 const gchar *
1836 get_uri(struct tab *t)
1838 const gchar *uri = NULL;
1840 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1841 return t->tmp_uri;
1842 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1843 uri = webkit_web_view_get_uri(t->wv);
1844 } else {
1845 /* use tmp_uri to make sure it is g_freed */
1846 if (t->tmp_uri)
1847 g_free(t->tmp_uri);
1848 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1849 about_list[t->xtp_meaning].name);
1850 uri = t->tmp_uri;
1852 return uri;
1855 const gchar *
1856 get_title(struct tab *t, bool window)
1858 const gchar *set = NULL, *title = NULL;
1859 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1861 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1862 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1863 goto notitle;
1865 title = webkit_web_view_get_title(t->wv);
1866 if ((set = title ? title : get_uri(t)))
1867 return set;
1869 notitle:
1870 set = window ? XT_NAME : "(untitled)";
1872 return set;
1876 add_alias(struct settings *s, char *line)
1878 char *l, *alias;
1879 struct alias *a = NULL;
1881 if (s == NULL || line == NULL) {
1882 show_oops(NULL, "add_alias invalid parameters");
1883 return (1);
1886 l = line;
1887 a = g_malloc(sizeof(*a));
1889 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1890 show_oops(NULL, "add_alias: incomplete alias definition");
1891 goto bad;
1893 if (strlen(alias) == 0 || strlen(l) == 0) {
1894 show_oops(NULL, "add_alias: invalid alias definition");
1895 goto bad;
1898 a->a_name = g_strdup(alias);
1899 a->a_uri = g_strdup(l);
1901 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1903 TAILQ_INSERT_TAIL(&aliases, a, entry);
1905 return (0);
1906 bad:
1907 if (a)
1908 g_free(a);
1909 return (1);
1913 add_mime_type(struct settings *s, char *line)
1915 char *mime_type;
1916 char *l;
1917 struct mime_type *m = NULL;
1918 int downloadfirst = 0;
1920 /* XXX this could be smarter */
1922 if (line == NULL || strlen(line) == 0) {
1923 show_oops(NULL, "add_mime_type invalid parameters");
1924 return (1);
1927 l = line;
1928 if (*l == '@') {
1929 downloadfirst = 1;
1930 l++;
1932 m = g_malloc(sizeof(*m));
1934 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1935 show_oops(NULL, "add_mime_type: invalid mime_type");
1936 goto bad;
1938 if (mime_type[strlen(mime_type) - 1] == '*') {
1939 mime_type[strlen(mime_type) - 1] = '\0';
1940 m->mt_default = 1;
1941 } else
1942 m->mt_default = 0;
1944 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1945 show_oops(NULL, "add_mime_type: invalid mime_type");
1946 goto bad;
1949 m->mt_type = g_strdup(mime_type);
1950 m->mt_action = g_strdup(l);
1951 m->mt_download = downloadfirst;
1953 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1954 m->mt_type, m->mt_action, m->mt_default);
1956 TAILQ_INSERT_TAIL(&mtl, m, entry);
1958 return (0);
1959 bad:
1960 if (m)
1961 g_free(m);
1962 return (1);
1965 struct mime_type *
1966 find_mime_type(char *mime_type)
1968 struct mime_type *m, *def = NULL, *rv = NULL;
1970 TAILQ_FOREACH(m, &mtl, entry) {
1971 if (m->mt_default &&
1972 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1973 def = m;
1975 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1976 rv = m;
1977 break;
1981 if (rv == NULL)
1982 rv = def;
1984 return (rv);
1987 void
1988 walk_mime_type(struct settings *s,
1989 void (*cb)(struct settings *, char *, void *), void *cb_args)
1991 struct mime_type *m;
1992 char *str;
1994 if (s == NULL || cb == NULL) {
1995 show_oops(NULL, "walk_mime_type invalid parameters");
1996 return;
1999 TAILQ_FOREACH(m, &mtl, entry) {
2000 str = g_strdup_printf("%s%s --> %s",
2001 m->mt_type,
2002 m->mt_default ? "*" : "",
2003 m->mt_action);
2004 cb(s, str, cb_args);
2005 g_free(str);
2009 void
2010 wl_add(char *str, struct domain_list *wl, int handy)
2012 struct domain *d;
2013 int add_dot = 0;
2014 char *p;
2016 if (str == NULL || wl == NULL || strlen(str) < 2)
2017 return;
2019 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2021 /* treat *.moo.com the same as .moo.com */
2022 if (str[0] == '*' && str[1] == '.')
2023 str = &str[1];
2024 else if (str[0] == '.')
2025 str = &str[0];
2026 else
2027 add_dot = 1;
2029 /* slice off port number */
2030 p = g_strrstr(str, ":");
2031 if (p)
2032 *p = '\0';
2034 d = g_malloc(sizeof *d);
2035 if (add_dot)
2036 d->d = g_strdup_printf(".%s", str);
2037 else
2038 d->d = g_strdup(str);
2039 d->handy = handy;
2041 if (RB_INSERT(domain_list, wl, d))
2042 goto unwind;
2044 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2045 return;
2046 unwind:
2047 if (d) {
2048 if (d->d)
2049 g_free(d->d);
2050 g_free(d);
2055 add_cookie_wl(struct settings *s, char *entry)
2057 wl_add(entry, &c_wl, 1);
2058 return (0);
2061 void
2062 walk_cookie_wl(struct settings *s,
2063 void (*cb)(struct settings *, char *, void *), void *cb_args)
2065 struct domain *d;
2067 if (s == NULL || cb == NULL) {
2068 show_oops(NULL, "walk_cookie_wl invalid parameters");
2069 return;
2072 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2073 cb(s, d->d, cb_args);
2076 void
2077 walk_js_wl(struct settings *s,
2078 void (*cb)(struct settings *, char *, void *), void *cb_args)
2080 struct domain *d;
2082 if (s == NULL || cb == NULL) {
2083 show_oops(NULL, "walk_js_wl invalid parameters");
2084 return;
2087 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2088 cb(s, d->d, cb_args);
2092 add_js_wl(struct settings *s, char *entry)
2094 wl_add(entry, &js_wl, 1 /* persistent */);
2095 return (0);
2098 struct domain *
2099 wl_find(const gchar *search, struct domain_list *wl)
2101 int i;
2102 struct domain *d = NULL, dfind;
2103 gchar *s = NULL;
2105 if (search == NULL || wl == NULL)
2106 return (NULL);
2107 if (strlen(search) < 2)
2108 return (NULL);
2110 if (search[0] != '.')
2111 s = g_strdup_printf(".%s", search);
2112 else
2113 s = g_strdup(search);
2115 for (i = strlen(s) - 1; i >= 0; i--) {
2116 if (s[i] == '.') {
2117 dfind.d = &s[i];
2118 d = RB_FIND(domain_list, wl, &dfind);
2119 if (d)
2120 goto done;
2124 done:
2125 if (s)
2126 g_free(s);
2128 return (d);
2131 struct domain *
2132 wl_find_uri(const gchar *s, struct domain_list *wl)
2134 int i;
2135 char *ss;
2136 struct domain *r;
2138 if (s == NULL || wl == NULL)
2139 return (NULL);
2141 if (!strncmp(s, "http://", strlen("http://")))
2142 s = &s[strlen("http://")];
2143 else if (!strncmp(s, "https://", strlen("https://")))
2144 s = &s[strlen("https://")];
2146 if (strlen(s) < 2)
2147 return (NULL);
2149 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2150 /* chop string at first slash */
2151 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2152 ss = g_strdup(s);
2153 ss[i] = '\0';
2154 r = wl_find(ss, wl);
2155 g_free(ss);
2156 return (r);
2159 return (NULL);
2163 settings_add(char *var, char *val)
2165 int i, rv, *p;
2166 gfloat *f;
2167 char **s;
2169 /* get settings */
2170 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2171 if (strcmp(var, rs[i].name))
2172 continue;
2174 if (rs[i].s) {
2175 if (rs[i].s->set(&rs[i], val))
2176 errx(1, "invalid value for %s: %s", var, val);
2177 rv = 1;
2178 break;
2179 } else
2180 switch (rs[i].type) {
2181 case XT_S_INT:
2182 p = rs[i].ival;
2183 *p = atoi(val);
2184 rv = 1;
2185 break;
2186 case XT_S_STR:
2187 s = rs[i].sval;
2188 if (s == NULL)
2189 errx(1, "invalid sval for %s",
2190 rs[i].name);
2191 if (*s)
2192 g_free(*s);
2193 *s = g_strdup(val);
2194 rv = 1;
2195 break;
2196 case XT_S_FLOAT:
2197 f = rs[i].fval;
2198 *f = atof(val);
2199 rv = 1;
2200 break;
2201 case XT_S_INVALID:
2202 default:
2203 errx(1, "invalid type for %s", var);
2205 break;
2207 return (rv);
2210 #define WS "\n= \t"
2211 void
2212 config_parse(char *filename, int runtime)
2214 FILE *config, *f;
2215 char *line, *cp, *var, *val;
2216 size_t len, lineno = 0;
2217 int handled;
2218 char file[PATH_MAX];
2219 struct stat sb;
2221 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2223 if (filename == NULL)
2224 return;
2226 if (runtime && runtime_settings[0] != '\0') {
2227 snprintf(file, sizeof file, "%s/%s",
2228 work_dir, runtime_settings);
2229 if (stat(file, &sb)) {
2230 warnx("runtime file doesn't exist, creating it");
2231 if ((f = fopen(file, "w")) == NULL)
2232 err(1, "runtime");
2233 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2234 fclose(f);
2236 } else
2237 strlcpy(file, filename, sizeof file);
2239 if ((config = fopen(file, "r")) == NULL) {
2240 warn("config_parse: cannot open %s", filename);
2241 return;
2244 for (;;) {
2245 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2246 if (feof(config) || ferror(config))
2247 break;
2249 cp = line;
2250 cp += (long)strspn(cp, WS);
2251 if (cp[0] == '\0') {
2252 /* empty line */
2253 free(line);
2254 continue;
2257 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2258 startpage_add("invalid configuration file entry: %s",
2259 line);
2261 cp += (long)strspn(cp, WS);
2263 if ((val = strsep(&cp, "\0")) == NULL)
2264 break;
2266 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2267 handled = settings_add(var, val);
2268 if (handled == 0)
2269 startpage_add("invalid configuration file entry: %s=%s",
2270 var, val);
2272 free(line);
2275 fclose(config);
2278 char *
2279 js_ref_to_string(JSContextRef context, JSValueRef ref)
2281 char *s = NULL;
2282 size_t l;
2283 JSStringRef jsref;
2285 jsref = JSValueToStringCopy(context, ref, NULL);
2286 if (jsref == NULL)
2287 return (NULL);
2289 l = JSStringGetMaximumUTF8CStringSize(jsref);
2290 s = g_malloc(l);
2291 if (s)
2292 JSStringGetUTF8CString(jsref, s, l);
2293 JSStringRelease(jsref);
2295 return (s);
2298 void
2299 disable_hints(struct tab *t)
2301 bzero(t->hint_buf, sizeof t->hint_buf);
2302 bzero(t->hint_num, sizeof t->hint_num);
2303 run_script(t, "vimprobable_clear()");
2304 t->hints_on = 0;
2305 t->hint_mode = XT_HINT_NONE;
2308 void
2309 enable_hints(struct tab *t)
2311 bzero(t->hint_buf, sizeof t->hint_buf);
2312 run_script(t, "vimprobable_show_hints()");
2313 t->hints_on = 1;
2314 t->hint_mode = XT_HINT_NONE;
2317 #define XT_JS_OPEN ("open;")
2318 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2319 #define XT_JS_FIRE ("fire;")
2320 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2321 #define XT_JS_FOUND ("found;")
2322 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2325 run_script(struct tab *t, char *s)
2327 JSGlobalContextRef ctx;
2328 WebKitWebFrame *frame;
2329 JSStringRef str;
2330 JSValueRef val, exception;
2331 char *es, buf[128];
2333 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2334 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2336 frame = webkit_web_view_get_main_frame(t->wv);
2337 ctx = webkit_web_frame_get_global_context(frame);
2339 str = JSStringCreateWithUTF8CString(s);
2340 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2341 NULL, 0, &exception);
2342 JSStringRelease(str);
2344 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2345 if (val == NULL) {
2346 es = js_ref_to_string(ctx, exception);
2347 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2348 g_free(es);
2349 return (1);
2350 } else {
2351 es = js_ref_to_string(ctx, val);
2352 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2354 /* handle return value right here */
2355 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2356 disable_hints(t);
2357 marks_clear(t);
2358 load_uri(t, &es[XT_JS_OPEN_LEN]);
2361 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2362 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2363 &es[XT_JS_FIRE_LEN]);
2364 run_script(t, buf);
2365 disable_hints(t);
2368 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2369 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2370 disable_hints(t);
2373 g_free(es);
2376 return (0);
2380 hint(struct tab *t, struct karg *args)
2383 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2385 if (t->hints_on == 0)
2386 enable_hints(t);
2387 else
2388 disable_hints(t);
2390 return (0);
2393 void
2394 apply_style(struct tab *t)
2396 g_object_set(G_OBJECT(t->settings),
2397 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2401 userstyle(struct tab *t, struct karg *args)
2403 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2405 if (t->styled) {
2406 t->styled = 0;
2407 g_object_set(G_OBJECT(t->settings),
2408 "user-stylesheet-uri", NULL, (char *)NULL);
2409 } else {
2410 t->styled = 1;
2411 apply_style(t);
2413 return (0);
2417 * Doesn't work fully, due to the following bug:
2418 * https://bugs.webkit.org/show_bug.cgi?id=51747
2421 restore_global_history(void)
2423 char file[PATH_MAX];
2424 FILE *f;
2425 struct history *h;
2426 gchar *uri;
2427 gchar *title;
2428 const char delim[3] = {'\\', '\\', '\0'};
2430 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2432 if ((f = fopen(file, "r")) == NULL) {
2433 warnx("%s: fopen", __func__);
2434 return (1);
2437 for (;;) {
2438 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2439 if (feof(f) || ferror(f))
2440 break;
2442 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2443 if (feof(f) || ferror(f)) {
2444 free(uri);
2445 warnx("%s: broken history file\n", __func__);
2446 return (1);
2449 if (uri && strlen(uri) && title && strlen(title)) {
2450 webkit_web_history_item_new_with_data(uri, title);
2451 h = g_malloc(sizeof(struct history));
2452 h->uri = g_strdup(uri);
2453 h->title = g_strdup(title);
2454 RB_INSERT(history_list, &hl, h);
2455 completion_add_uri(h->uri);
2456 } else {
2457 warnx("%s: failed to restore history\n", __func__);
2458 free(uri);
2459 free(title);
2460 return (1);
2463 free(uri);
2464 free(title);
2465 uri = NULL;
2466 title = NULL;
2469 return (0);
2473 save_global_history_to_disk(struct tab *t)
2475 char file[PATH_MAX];
2476 FILE *f;
2477 struct history *h;
2479 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2481 if ((f = fopen(file, "w")) == NULL) {
2482 show_oops(t, "%s: global history file: %s",
2483 __func__, strerror(errno));
2484 return (1);
2487 RB_FOREACH_REVERSE(h, history_list, &hl) {
2488 if (h->uri && h->title)
2489 fprintf(f, "%s\n%s\n", h->uri, h->title);
2492 fclose(f);
2494 return (0);
2498 quit(struct tab *t, struct karg *args)
2500 if (save_global_history)
2501 save_global_history_to_disk(t);
2503 gtk_main_quit();
2505 return (1);
2508 void
2509 restore_sessions_list(void)
2511 DIR *sdir = NULL;
2512 struct dirent *dp = NULL;
2513 struct session *s;
2515 sdir = opendir(sessions_dir);
2516 if (sdir) {
2517 while ((dp = readdir(sdir)) != NULL)
2518 if (dp->d_type == DT_REG) {
2519 s = g_malloc(sizeof(struct session));
2520 s->name = g_strdup(dp->d_name);
2521 TAILQ_INSERT_TAIL(&sessions, s, entry);
2523 closedir(sdir);
2528 open_tabs(struct tab *t, struct karg *a)
2530 char file[PATH_MAX];
2531 FILE *f = NULL;
2532 char *uri = NULL;
2533 int rv = 1;
2534 struct tab *ti, *tt;
2536 if (a == NULL)
2537 goto done;
2539 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2540 if ((f = fopen(file, "r")) == NULL)
2541 goto done;
2543 ti = TAILQ_LAST(&tabs, tab_list);
2545 for (;;) {
2546 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2547 if (feof(f) || ferror(f))
2548 break;
2550 /* retrieve session name */
2551 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2552 strlcpy(named_session,
2553 &uri[strlen(XT_SAVE_SESSION_ID)],
2554 sizeof named_session);
2555 continue;
2558 if (uri && strlen(uri))
2559 create_new_tab(uri, NULL, 1, -1);
2561 free(uri);
2562 uri = NULL;
2565 /* close open tabs */
2566 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2567 for (;;) {
2568 tt = TAILQ_FIRST(&tabs);
2569 if (tt != ti) {
2570 delete_tab(tt);
2571 continue;
2573 delete_tab(tt);
2574 break;
2576 recalc_tabs();
2579 rv = 0;
2580 done:
2581 if (f)
2582 fclose(f);
2584 return (rv);
2588 restore_saved_tabs(void)
2590 char file[PATH_MAX];
2591 int unlink_file = 0;
2592 struct stat sb;
2593 struct karg a;
2594 int rv = 0;
2596 snprintf(file, sizeof file, "%s/%s",
2597 sessions_dir, XT_RESTART_TABS_FILE);
2598 if (stat(file, &sb) == -1)
2599 a.s = XT_SAVED_TABS_FILE;
2600 else {
2601 unlink_file = 1;
2602 a.s = XT_RESTART_TABS_FILE;
2605 a.i = XT_SES_DONOTHING;
2606 rv = open_tabs(NULL, &a);
2608 if (unlink_file)
2609 unlink(file);
2611 return (rv);
2615 save_tabs(struct tab *t, struct karg *a)
2617 char file[PATH_MAX];
2618 FILE *f;
2619 int num_tabs = 0, i;
2620 struct tab **stabs = NULL;
2622 if (a == NULL)
2623 return (1);
2624 if (a->s == NULL)
2625 snprintf(file, sizeof file, "%s/%s",
2626 sessions_dir, named_session);
2627 else
2628 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2630 if ((f = fopen(file, "w")) == NULL) {
2631 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2632 return (1);
2635 /* save session name */
2636 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2638 /* Save tabs, in the order they are arranged in the notebook. */
2639 num_tabs = sort_tabs_by_page_num(&stabs);
2641 for (i = 0; i < num_tabs; i++)
2642 if (stabs[i]) {
2643 if (get_uri(stabs[i]) != NULL)
2644 fprintf(f, "%s\n", get_uri(stabs[i]));
2645 else if (gtk_entry_get_text(GTK_ENTRY(
2646 stabs[i]->uri_entry)))
2647 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2648 stabs[i]->uri_entry)));
2651 g_free(stabs);
2653 /* try and make sure this gets to disk NOW. XXX Backup first? */
2654 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2655 show_oops(t, "May not have managed to save session: %s",
2656 strerror(errno));
2659 fclose(f);
2661 return (0);
2665 save_tabs_and_quit(struct tab *t, struct karg *args)
2667 struct karg a;
2669 a.s = NULL;
2670 save_tabs(t, &a);
2671 quit(t, NULL);
2673 return (1);
2677 run_page_script(struct tab *t, struct karg *args)
2679 const gchar *uri;
2680 char *tmp, script[PATH_MAX];
2682 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2683 if (tmp[0] == '\0') {
2684 show_oops(t, "no script specified");
2685 return (1);
2688 if ((uri = get_uri(t)) == NULL) {
2689 show_oops(t, "tab is empty, not running script");
2690 return (1);
2693 if (tmp[0] == '~')
2694 snprintf(script, sizeof script, "%s/%s",
2695 pwd->pw_dir, &tmp[1]);
2696 else
2697 strlcpy(script, tmp, sizeof script);
2699 switch (fork()) {
2700 case -1:
2701 show_oops(t, "can't fork to run script");
2702 return (1);
2703 /* NOTREACHED */
2704 case 0:
2705 break;
2706 default:
2707 return (0);
2710 /* child */
2711 execlp(script, script, uri, (void *)NULL);
2713 _exit(0);
2715 /* NOTREACHED */
2717 return (0);
2721 yank_uri(struct tab *t, struct karg *args)
2723 const gchar *uri;
2724 GtkClipboard *clipboard;
2726 if ((uri = get_uri(t)) == NULL)
2727 return (1);
2729 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2730 gtk_clipboard_set_text(clipboard, uri, -1);
2732 return (0);
2736 paste_uri(struct tab *t, struct karg *args)
2738 GtkClipboard *clipboard;
2739 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2740 gint len;
2741 gchar *p = NULL, *uri;
2743 /* try primary clipboard first */
2744 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2745 p = gtk_clipboard_wait_for_text(clipboard);
2747 /* if it failed get whatever text is in cut_buffer0 */
2748 if (p == NULL && xterm_workaround)
2749 if (gdk_property_get(gdk_get_default_root_window(),
2750 atom,
2751 gdk_atom_intern("STRING", FALSE),
2753 1024 * 1024 /* picked out of my butt */,
2754 FALSE,
2755 NULL,
2756 NULL,
2757 &len,
2758 (guchar **)&p)) {
2759 /* yes sir, we need to NUL the string */
2760 p[len] = '\0';
2763 if (p) {
2764 uri = p;
2765 while (*uri && isspace(*uri))
2766 uri++;
2767 if (strlen(uri) == 0) {
2768 show_oops(t, "empty paste buffer");
2769 goto done;
2771 if (guess_search == 0 && valid_url_type(uri)) {
2772 /* we can be clever and paste this in search box */
2773 show_oops(t, "not a valid URL");
2774 goto done;
2777 if (args->i == XT_PASTE_CURRENT_TAB)
2778 load_uri(t, uri);
2779 else if (args->i == XT_PASTE_NEW_TAB)
2780 create_new_tab(uri, NULL, 1, -1);
2783 done:
2784 if (p)
2785 g_free(p);
2787 return (0);
2790 gchar *
2791 find_domain(const gchar *s, int toplevel)
2793 SoupURI *uri;
2794 gchar *ret, *p;
2796 if (s == NULL)
2797 return (NULL);
2799 uri = soup_uri_new(s);
2801 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2802 return (NULL);
2805 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2806 if ((p = strrchr(uri->host, '.')) != NULL) {
2807 while(--p >= uri->host && *p != '.');
2808 p++;
2809 } else
2810 p = uri->host;
2811 } else
2812 p = uri->host;
2814 ret = g_strdup_printf(".%s", p);
2816 soup_uri_free(uri);
2818 return ret;
2822 toggle_cwl(struct tab *t, struct karg *args)
2824 struct domain *d;
2825 const gchar *uri;
2826 char *dom = NULL;
2827 int es;
2829 if (args == NULL)
2830 return (1);
2832 uri = get_uri(t);
2833 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2835 if (uri == NULL || dom == NULL ||
2836 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2837 show_oops(t, "Can't toggle domain in cookie white list");
2838 goto done;
2840 d = wl_find(dom, &c_wl);
2842 if (d == NULL)
2843 es = 0;
2844 else
2845 es = 1;
2847 if (args->i & XT_WL_TOGGLE)
2848 es = !es;
2849 else if ((args->i & XT_WL_ENABLE) && es != 1)
2850 es = 1;
2851 else if ((args->i & XT_WL_DISABLE) && es != 0)
2852 es = 0;
2854 if (es)
2855 /* enable cookies for domain */
2856 wl_add(dom, &c_wl, 0);
2857 else
2858 /* disable cookies for domain */
2859 RB_REMOVE(domain_list, &c_wl, d);
2861 if (args->i & XT_WL_RELOAD)
2862 webkit_web_view_reload(t->wv);
2864 done:
2865 g_free(dom);
2866 return (0);
2870 toggle_js(struct tab *t, struct karg *args)
2872 int es;
2873 const gchar *uri;
2874 struct domain *d;
2875 char *dom = NULL;
2877 if (args == NULL)
2878 return (1);
2880 g_object_get(G_OBJECT(t->settings),
2881 "enable-scripts", &es, (char *)NULL);
2882 if (args->i & XT_WL_TOGGLE)
2883 es = !es;
2884 else if ((args->i & XT_WL_ENABLE) && es != 1)
2885 es = 1;
2886 else if ((args->i & XT_WL_DISABLE) && es != 0)
2887 es = 0;
2888 else
2889 return (1);
2891 uri = get_uri(t);
2892 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2894 if (uri == NULL || dom == NULL ||
2895 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2896 show_oops(t, "Can't toggle domain in JavaScript white list");
2897 goto done;
2900 if (es) {
2901 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2902 wl_add(dom, &js_wl, 0 /* session */);
2903 } else {
2904 d = wl_find(dom, &js_wl);
2905 if (d)
2906 RB_REMOVE(domain_list, &js_wl, d);
2907 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2909 g_object_set(G_OBJECT(t->settings),
2910 "enable-scripts", es, (char *)NULL);
2911 g_object_set(G_OBJECT(t->settings),
2912 "javascript-can-open-windows-automatically", es, (char *)NULL);
2913 webkit_web_view_set_settings(t->wv, t->settings);
2915 if (args->i & XT_WL_RELOAD)
2916 webkit_web_view_reload(t->wv);
2917 done:
2918 if (dom)
2919 g_free(dom);
2920 return (0);
2923 void
2924 js_toggle_cb(GtkWidget *w, struct tab *t)
2926 struct karg a;
2928 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2929 toggle_cwl(t, &a);
2931 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2932 toggle_js(t, &a);
2936 toggle_src(struct tab *t, struct karg *args)
2938 gboolean mode;
2940 if (t == NULL)
2941 return (0);
2943 mode = webkit_web_view_get_view_source_mode(t->wv);
2944 webkit_web_view_set_view_source_mode(t->wv, !mode);
2945 webkit_web_view_reload(t->wv);
2947 return (0);
2950 void
2951 focus_webview(struct tab *t)
2953 if (t == NULL)
2954 return;
2956 /* only grab focus if we are visible */
2957 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2958 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2962 focus(struct tab *t, struct karg *args)
2964 if (t == NULL || args == NULL)
2965 return (1);
2967 if (show_url == 0)
2968 return (0);
2970 if (args->i == XT_FOCUS_URI)
2971 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2972 else if (args->i == XT_FOCUS_SEARCH)
2973 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2975 return (0);
2979 stats(struct tab *t, struct karg *args)
2981 char *page, *body, *s, line[64 * 1024];
2982 uint64_t line_count = 0;
2983 FILE *r_cookie_f;
2985 if (t == NULL)
2986 show_oops(NULL, "stats invalid parameters");
2988 line[0] = '\0';
2989 if (save_rejected_cookies) {
2990 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2991 for (;;) {
2992 s = fgets(line, sizeof line, r_cookie_f);
2993 if (s == NULL || feof(r_cookie_f) ||
2994 ferror(r_cookie_f))
2995 break;
2996 line_count++;
2998 fclose(r_cookie_f);
2999 snprintf(line, sizeof line,
3000 "<br/>Cookies blocked(*) total: %llu", line_count);
3001 } else
3002 show_oops(t, "Can't open blocked cookies file: %s",
3003 strerror(errno));
3006 body = g_strdup_printf(
3007 "Cookies blocked(*) this session: %llu"
3008 "%s"
3009 "<p><small><b>*</b> results vary based on settings</small></p>",
3010 blocked_cookies,
3011 line);
3013 page = get_html_page("Statistics", body, "", 0);
3014 g_free(body);
3016 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3017 g_free(page);
3019 return (0);
3023 marco(struct tab *t, struct karg *args)
3025 char *page, line[64 * 1024];
3026 int len;
3028 if (t == NULL)
3029 show_oops(NULL, "marco invalid parameters");
3031 line[0] = '\0';
3032 snprintf(line, sizeof line, "%s", marco_message(&len));
3034 page = get_html_page("Marco Sez...", line, "", 0);
3036 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3037 g_free(page);
3039 return (0);
3043 blank(struct tab *t, struct karg *args)
3045 if (t == NULL)
3046 show_oops(NULL, "blank invalid parameters");
3048 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3050 return (0);
3054 about(struct tab *t, struct karg *args)
3056 char *page, *body;
3058 if (t == NULL)
3059 show_oops(NULL, "about invalid parameters");
3061 body = g_strdup_printf("<b>Version: %s</b><p>"
3062 "Authors:"
3063 "<ul>"
3064 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3065 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3066 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3067 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3068 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3069 "</ul>"
3070 "Copyrights and licenses can be found on the XXXTerm "
3071 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
3072 version
3075 page = get_html_page("About", body, "", 0);
3076 g_free(body);
3078 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3079 g_free(page);
3081 return (0);
3085 help(struct tab *t, struct karg *args)
3087 char *page, *head, *body;
3089 if (t == NULL)
3090 show_oops(NULL, "help invalid parameters");
3092 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3093 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3094 "</head>\n";
3095 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3096 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3097 "cgi-bin/man-cgi?xxxterm</a>";
3099 page = get_html_page(XT_NAME, body, head, FALSE);
3101 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3102 g_free(page);
3104 return (0);
3108 startpage(struct tab *t, struct karg *args)
3110 char *page, *body, *b;
3111 struct sp *s;
3113 if (t == NULL)
3114 show_oops(NULL, "startpage invalid parameters");
3116 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3118 TAILQ_FOREACH(s, &spl, entry) {
3119 b = body;
3120 body = g_strdup_printf("%s%s<br>", body, s->line);
3121 g_free(b);
3124 page = get_html_page("Startup Exception", body, "", 0);
3125 g_free(body);
3127 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3128 g_free(page);
3130 return (0);
3133 void
3134 startpage_add(const char *fmt, ...)
3136 va_list ap;
3137 char *msg;
3138 struct sp *s;
3140 if (fmt == NULL)
3141 return;
3143 va_start(ap, fmt);
3144 if (vasprintf(&msg, fmt, ap) == -1)
3145 errx(1, "startpage_add failed");
3146 va_end(ap);
3148 s = g_malloc0(sizeof *s);
3149 s->line = msg;
3151 TAILQ_INSERT_TAIL(&spl, s, entry);
3155 * update all favorite tabs apart from one. Pass NULL if
3156 * you want to update all.
3158 void
3159 update_favorite_tabs(struct tab *apart_from)
3161 struct tab *t;
3162 if (!updating_fl_tabs) {
3163 updating_fl_tabs = 1; /* stop infinite recursion */
3164 TAILQ_FOREACH(t, &tabs, entry)
3165 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3166 && (t != apart_from))
3167 xtp_page_fl(t, NULL);
3168 updating_fl_tabs = 0;
3172 /* show a list of favorites (bookmarks) */
3174 xtp_page_fl(struct tab *t, struct karg *args)
3176 char file[PATH_MAX];
3177 FILE *f;
3178 char *uri = NULL, *title = NULL;
3179 size_t len, lineno = 0;
3180 int i, failed = 0;
3181 char *body, *tmp, *page = NULL;
3182 const char delim[3] = {'\\', '\\', '\0'};
3184 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3186 if (t == NULL)
3187 warn("%s: bad param", __func__);
3189 /* new session key */
3190 if (!updating_fl_tabs)
3191 generate_xtp_session_key(&fl_session_key);
3193 /* open favorites */
3194 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3195 if ((f = fopen(file, "r")) == NULL) {
3196 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3197 return (1);
3200 /* body */
3201 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3202 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3203 "<th style='width: 40px'>Rm</th></tr>\n");
3205 for (i = 1;;) {
3206 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3207 if (feof(f) || ferror(f))
3208 break;
3209 if (strlen(title) == 0 || title[0] == '#') {
3210 free(title);
3211 title = NULL;
3212 continue;
3215 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3216 if (feof(f) || ferror(f)) {
3217 show_oops(t, "favorites file corrupt");
3218 failed = 1;
3219 break;
3222 tmp = body;
3223 body = g_strdup_printf("%s<tr>"
3224 "<td>%d</td>"
3225 "<td><a href='%s'>%s</a></td>"
3226 "<td style='text-align: center'>"
3227 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3228 "</tr>\n",
3229 body, i, uri, title,
3230 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3232 g_free(tmp);
3234 free(uri);
3235 uri = NULL;
3236 free(title);
3237 title = NULL;
3238 i++;
3240 fclose(f);
3242 /* if none, say so */
3243 if (i == 1) {
3244 tmp = body;
3245 body = g_strdup_printf("%s<tr>"
3246 "<td colspan='3' style='text-align: center'>"
3247 "No favorites - To add one use the 'favadd' command."
3248 "</td></tr>", body);
3249 g_free(tmp);
3252 tmp = body;
3253 body = g_strdup_printf("%s</table>", body);
3254 g_free(tmp);
3256 if (uri)
3257 free(uri);
3258 if (title)
3259 free(title);
3261 /* render */
3262 if (!failed) {
3263 page = get_html_page("Favorites", body, "", 1);
3264 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3265 g_free(page);
3268 update_favorite_tabs(t);
3270 if (body)
3271 g_free(body);
3273 return (failed);
3276 void
3277 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3278 size_t cert_count, char *title)
3280 gnutls_datum_t cinfo;
3281 char *tmp, *body;
3282 int i;
3284 body = g_strdup("");
3286 for (i = 0; i < cert_count; i++) {
3287 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3288 &cinfo))
3289 return;
3291 tmp = body;
3292 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3293 body, i, cinfo.data);
3294 gnutls_free(cinfo.data);
3295 g_free(tmp);
3298 tmp = get_html_page(title, body, "", 0);
3299 g_free(body);
3301 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3302 g_free(tmp);
3306 ca_cmd(struct tab *t, struct karg *args)
3308 FILE *f = NULL;
3309 int rv = 1, certs = 0, certs_read;
3310 struct stat sb;
3311 gnutls_datum_t dt;
3312 gnutls_x509_crt_t *c = NULL;
3313 char *certs_buf = NULL, *s;
3315 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3316 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3317 return (1);
3320 if (fstat(fileno(f), &sb) == -1) {
3321 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3322 goto done;
3325 certs_buf = g_malloc(sb.st_size + 1);
3326 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3327 show_oops(t, "Can't read CA file: %s", strerror(errno));
3328 goto done;
3330 certs_buf[sb.st_size] = '\0';
3332 s = certs_buf;
3333 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3334 certs++;
3335 s += strlen("BEGIN CERTIFICATE");
3338 bzero(&dt, sizeof dt);
3339 dt.data = (unsigned char *)certs_buf;
3340 dt.size = sb.st_size;
3341 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3342 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3343 GNUTLS_X509_FMT_PEM, 0);
3344 if (certs_read <= 0) {
3345 show_oops(t, "No cert(s) available");
3346 goto done;
3348 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3349 done:
3350 if (c)
3351 g_free(c);
3352 if (certs_buf)
3353 g_free(certs_buf);
3354 if (f)
3355 fclose(f);
3357 return (rv);
3361 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3362 size_t domain_sz)
3364 SoupURI *su = NULL;
3365 struct addrinfo hints, *res = NULL, *ai;
3366 int rv = -1, s = -1, on, error;
3367 char port[8];
3369 if (uri && !g_str_has_prefix(uri, "https://")) {
3370 show_oops(t, "invalid URI");
3371 goto done;
3374 su = soup_uri_new(uri);
3375 if (su == NULL) {
3376 show_oops(t, "invalid soup URI");
3377 goto done;
3379 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3380 show_oops(t, "invalid HTTPS URI");
3381 goto done;
3384 snprintf(port, sizeof port, "%d", su->port);
3385 bzero(&hints, sizeof(struct addrinfo));
3386 hints.ai_flags = AI_CANONNAME;
3387 hints.ai_family = AF_UNSPEC;
3388 hints.ai_socktype = SOCK_STREAM;
3390 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3391 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3392 goto done;
3395 for (ai = res; ai; ai = ai->ai_next) {
3396 if (s != -1) {
3397 close(s);
3398 s = -1;
3401 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3402 continue;
3403 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3404 if (s == -1)
3405 continue;
3406 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3407 sizeof(on)) == -1)
3408 continue;
3409 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3410 break;
3412 if (s == -1) {
3413 show_oops(t, "could not obtain certificates from: %s",
3414 su->host);
3415 goto done;
3418 if (domain)
3419 strlcpy(domain, su->host, domain_sz);
3420 rv = s;
3421 done:
3422 if (su)
3423 soup_uri_free(su);
3424 if (res)
3425 freeaddrinfo(res);
3426 if (rv == -1 && s != -1)
3427 close(s);
3429 return (rv);
3433 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3435 if (gsession)
3436 gnutls_deinit(gsession);
3437 if (xcred)
3438 gnutls_certificate_free_credentials(xcred);
3440 return (0);
3444 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3445 gnutls_certificate_credentials_t *xc)
3447 gnutls_certificate_credentials_t xcred;
3448 gnutls_session_t gsession;
3449 int rv = 1;
3451 if (gs == NULL || xc == NULL)
3452 goto done;
3454 *gs = NULL;
3455 *xc = NULL;
3457 gnutls_certificate_allocate_credentials(&xcred);
3458 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3459 GNUTLS_X509_FMT_PEM);
3461 gnutls_init(&gsession, GNUTLS_CLIENT);
3462 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3463 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3464 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3465 if ((rv = gnutls_handshake(gsession)) < 0) {
3466 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3468 gnutls_error_is_fatal(rv),
3469 gnutls_strerror_name(rv));
3470 stop_tls(gsession, xcred);
3471 goto done;
3474 gnutls_credentials_type_t cred;
3475 cred = gnutls_auth_get_type(gsession);
3476 if (cred != GNUTLS_CRD_CERTIFICATE) {
3477 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3478 stop_tls(gsession, xcred);
3479 goto done;
3482 *gs = gsession;
3483 *xc = xcred;
3484 rv = 0;
3485 done:
3486 return (rv);
3490 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3491 size_t *cert_count)
3493 unsigned int len;
3494 const gnutls_datum_t *cl;
3495 gnutls_x509_crt_t *all_certs;
3496 int i, rv = 1;
3498 if (certs == NULL || cert_count == NULL)
3499 goto done;
3500 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3501 goto done;
3502 cl = gnutls_certificate_get_peers(gsession, &len);
3503 if (len == 0)
3504 goto done;
3506 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3507 for (i = 0; i < len; i++) {
3508 gnutls_x509_crt_init(&all_certs[i]);
3509 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3510 GNUTLS_X509_FMT_PEM < 0)) {
3511 g_free(all_certs);
3512 goto done;
3516 *certs = all_certs;
3517 *cert_count = len;
3518 rv = 0;
3519 done:
3520 return (rv);
3523 void
3524 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3526 int i;
3528 for (i = 0; i < cert_count; i++)
3529 gnutls_x509_crt_deinit(certs[i]);
3530 g_free(certs);
3533 void
3534 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3536 GdkColor c_text, c_base;
3538 gdk_color_parse(text, &c_text);
3539 gdk_color_parse(base, &c_base);
3541 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3542 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3543 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3544 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3546 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3547 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3548 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3549 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3552 void
3553 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3554 size_t cert_count, char *domain)
3556 size_t cert_buf_sz;
3557 char cert_buf[64 * 1024], file[PATH_MAX];
3558 int i;
3559 FILE *f;
3560 GdkColor color;
3562 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3563 return;
3565 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3566 if ((f = fopen(file, "w")) == NULL) {
3567 show_oops(t, "Can't create cert file %s %s",
3568 file, strerror(errno));
3569 return;
3572 for (i = 0; i < cert_count; i++) {
3573 cert_buf_sz = sizeof cert_buf;
3574 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3575 cert_buf, &cert_buf_sz)) {
3576 show_oops(t, "gnutls_x509_crt_export failed");
3577 goto done;
3579 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3580 show_oops(t, "Can't write certs: %s", strerror(errno));
3581 goto done;
3585 /* not the best spot but oh well */
3586 gdk_color_parse("lightblue", &color);
3587 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3588 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3589 done:
3590 fclose(f);
3593 enum cert_trust {
3594 CERT_LOCAL,
3595 CERT_TRUSTED,
3596 CERT_UNTRUSTED,
3597 CERT_BAD
3600 enum cert_trust
3601 load_compare_cert(struct tab *t, struct karg *args)
3603 const gchar *uri;
3604 char domain[8182], file[PATH_MAX];
3605 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3606 int s = -1, i;
3607 unsigned int error;
3608 FILE *f = NULL;
3609 size_t cert_buf_sz, cert_count;
3610 enum cert_trust rv = CERT_UNTRUSTED;
3611 char serr[80];
3612 gnutls_session_t gsession;
3613 gnutls_x509_crt_t *certs;
3614 gnutls_certificate_credentials_t xcred;
3616 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3618 if (t == NULL)
3619 return (rv);
3621 if ((uri = get_uri(t)) == NULL)
3622 return (rv);
3623 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3625 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3626 return (rv);
3627 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3629 /* go ssl/tls */
3630 if (start_tls(t, s, &gsession, &xcred))
3631 goto done;
3632 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3634 /* verify certs in case cert file doesn't exist */
3635 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3636 GNUTLS_E_SUCCESS) {
3637 show_oops(t, "Invalid certificates");
3638 goto done;
3641 /* get certs */
3642 if (get_connection_certs(gsession, &certs, &cert_count)) {
3643 show_oops(t, "Can't get connection certificates");
3644 goto done;
3647 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3648 if ((f = fopen(file, "r")) == NULL) {
3649 if (!error)
3650 rv = CERT_TRUSTED;
3651 goto freeit;
3654 for (i = 0; i < cert_count; i++) {
3655 cert_buf_sz = sizeof cert_buf;
3656 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3657 cert_buf, &cert_buf_sz)) {
3658 goto freeit;
3660 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3661 rv = CERT_BAD; /* critical */
3662 goto freeit;
3664 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3665 rv = CERT_BAD; /* critical */
3666 goto freeit;
3668 rv = CERT_LOCAL;
3671 freeit:
3672 if (f)
3673 fclose(f);
3674 free_connection_certs(certs, cert_count);
3675 done:
3676 /* we close the socket first for speed */
3677 if (s != -1)
3678 close(s);
3680 /* only complain if we didn't save it locally */
3681 if (error && rv != CERT_LOCAL) {
3682 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3683 if (error & GNUTLS_CERT_INVALID)
3684 strlcat(serr, "invalid, ", sizeof serr);
3685 if (error & GNUTLS_CERT_REVOKED)
3686 strlcat(serr, "revoked, ", sizeof serr);
3687 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3688 strlcat(serr, "signer not found, ", sizeof serr);
3689 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3690 strlcat(serr, "not signed by CA, ", sizeof serr);
3691 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3692 strlcat(serr, "insecure algorithm, ", sizeof serr);
3693 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3694 strlcat(serr, "not activated, ", sizeof serr);
3695 if (error & GNUTLS_CERT_EXPIRED)
3696 strlcat(serr, "expired, ", sizeof serr);
3697 for (i = strlen(serr) - 1; i > 0; i--)
3698 if (serr[i] == ',') {
3699 serr[i] = '\0';
3700 break;
3702 show_oops(t, serr);
3705 stop_tls(gsession, xcred);
3707 return (rv);
3711 cert_cmd(struct tab *t, struct karg *args)
3713 const gchar *uri;
3714 char domain[8182];
3715 int s = -1;
3716 size_t cert_count;
3717 gnutls_session_t gsession;
3718 gnutls_x509_crt_t *certs;
3719 gnutls_certificate_credentials_t xcred;
3721 if (t == NULL)
3722 return (1);
3724 if (ssl_ca_file == NULL) {
3725 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3726 return (1);
3729 if ((uri = get_uri(t)) == NULL) {
3730 show_oops(t, "Invalid URI");
3731 return (1);
3734 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3735 show_oops(t, "Invalid certificate URI: %s", uri);
3736 return (1);
3739 /* go ssl/tls */
3740 if (start_tls(t, s, &gsession, &xcred))
3741 goto done;
3743 /* get certs */
3744 if (get_connection_certs(gsession, &certs, &cert_count)) {
3745 show_oops(t, "get_connection_certs failed");
3746 goto done;
3749 if (args->i & XT_SHOW)
3750 show_certs(t, certs, cert_count, "Certificate Chain");
3751 else if (args->i & XT_SAVE)
3752 save_certs(t, certs, cert_count, domain);
3754 free_connection_certs(certs, cert_count);
3755 done:
3756 /* we close the socket first for speed */
3757 if (s != -1)
3758 close(s);
3759 stop_tls(gsession, xcred);
3761 return (0);
3765 remove_cookie(int index)
3767 int i, rv = 1;
3768 GSList *cf;
3769 SoupCookie *c;
3771 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3773 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3775 for (i = 1; cf; cf = cf->next, i++) {
3776 if (i != index)
3777 continue;
3778 c = cf->data;
3779 print_cookie("remove cookie", c);
3780 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3781 rv = 0;
3782 break;
3785 soup_cookies_free(cf);
3787 return (rv);
3791 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3793 struct domain *d;
3794 char *tmp, *body;
3796 body = g_strdup("");
3798 /* p list */
3799 if (args->i & XT_WL_PERSISTENT) {
3800 tmp = body;
3801 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3802 g_free(tmp);
3803 RB_FOREACH(d, domain_list, wl) {
3804 if (d->handy == 0)
3805 continue;
3806 tmp = body;
3807 body = g_strdup_printf("%s%s<br/>", body, d->d);
3808 g_free(tmp);
3812 /* s list */
3813 if (args->i & XT_WL_SESSION) {
3814 tmp = body;
3815 body = g_strdup_printf("%s<h2>Session</h2>", body);
3816 g_free(tmp);
3817 RB_FOREACH(d, domain_list, wl) {
3818 if (d->handy == 1)
3819 continue;
3820 tmp = body;
3821 body = g_strdup_printf("%s%s<br/>", body, d->d);
3822 g_free(tmp);
3826 tmp = get_html_page(title, body, "", 0);
3827 g_free(body);
3828 if (wl == &js_wl)
3829 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3830 else
3831 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3832 g_free(tmp);
3833 return (0);
3837 wl_save(struct tab *t, struct karg *args, int js)
3839 char file[PATH_MAX];
3840 FILE *f;
3841 char *line = NULL, *lt = NULL, *dom = NULL;
3842 size_t linelen;
3843 const gchar *uri;
3844 struct karg a;
3845 struct domain *d;
3846 GSList *cf;
3847 SoupCookie *ci, *c;
3849 if (t == NULL || args == NULL)
3850 return (1);
3852 if (runtime_settings[0] == '\0')
3853 return (1);
3855 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3856 if ((f = fopen(file, "r+")) == NULL)
3857 return (1);
3859 uri = get_uri(t);
3860 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3861 if (uri == NULL || dom == NULL ||
3862 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3863 show_oops(t, "Can't add domain to %s white list",
3864 js ? "JavaScript" : "cookie");
3865 goto done;
3868 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3870 while (!feof(f)) {
3871 line = fparseln(f, &linelen, NULL, NULL, 0);
3872 if (line == NULL)
3873 continue;
3874 if (!strcmp(line, lt))
3875 goto done;
3876 free(line);
3877 line = NULL;
3880 fprintf(f, "%s\n", lt);
3882 a.i = XT_WL_ENABLE;
3883 a.i |= args->i;
3884 if (js) {
3885 d = wl_find(dom, &js_wl);
3886 if (!d) {
3887 settings_add("js_wl", dom);
3888 d = wl_find(dom, &js_wl);
3890 toggle_js(t, &a);
3891 } else {
3892 d = wl_find(dom, &c_wl);
3893 if (!d) {
3894 settings_add("cookie_wl", dom);
3895 d = wl_find(dom, &c_wl);
3897 toggle_cwl(t, &a);
3899 /* find and add to persistent jar */
3900 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3901 for (;cf; cf = cf->next) {
3902 ci = cf->data;
3903 if (!strcmp(dom, ci->domain) ||
3904 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3905 c = soup_cookie_copy(ci);
3906 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3909 soup_cookies_free(cf);
3911 if (d)
3912 d->handy = 1;
3914 done:
3915 if (line)
3916 free(line);
3917 if (dom)
3918 g_free(dom);
3919 if (lt)
3920 g_free(lt);
3921 fclose(f);
3923 return (0);
3927 js_show_wl(struct tab *t, struct karg *args)
3929 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3930 wl_show(t, args, "JavaScript White List", &js_wl);
3932 return (0);
3936 cookie_show_wl(struct tab *t, struct karg *args)
3938 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3939 wl_show(t, args, "Cookie White List", &c_wl);
3941 return (0);
3945 cookie_cmd(struct tab *t, struct karg *args)
3947 if (args->i & XT_SHOW)
3948 wl_show(t, args, "Cookie White List", &c_wl);
3949 else if (args->i & XT_WL_TOGGLE) {
3950 args->i |= XT_WL_RELOAD;
3951 toggle_cwl(t, args);
3952 } else if (args->i & XT_SAVE) {
3953 args->i |= XT_WL_RELOAD;
3954 wl_save(t, args, 0);
3955 } else if (args->i & XT_DELETE)
3956 show_oops(t, "'cookie delete' currently unimplemented");
3958 return (0);
3962 js_cmd(struct tab *t, struct karg *args)
3964 if (args->i & XT_SHOW)
3965 wl_show(t, args, "JavaScript White List", &js_wl);
3966 else if (args->i & XT_SAVE) {
3967 args->i |= XT_WL_RELOAD;
3968 wl_save(t, args, 1);
3969 } else if (args->i & XT_WL_TOGGLE) {
3970 args->i |= XT_WL_RELOAD;
3971 toggle_js(t, args);
3972 } else if (args->i & XT_DELETE)
3973 show_oops(t, "'js delete' currently unimplemented");
3975 return (0);
3979 toplevel_cmd(struct tab *t, struct karg *args)
3981 js_toggle_cb(t->js_toggle, t);
3983 return (0);
3987 add_favorite(struct tab *t, struct karg *args)
3989 char file[PATH_MAX];
3990 FILE *f;
3991 char *line = NULL;
3992 size_t urilen, linelen;
3993 const gchar *uri, *title;
3995 if (t == NULL)
3996 return (1);
3998 /* don't allow adding of xtp pages to favorites */
3999 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4000 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4001 return (1);
4004 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4005 if ((f = fopen(file, "r+")) == NULL) {
4006 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4007 return (1);
4010 title = get_title(t, FALSE);
4011 uri = get_uri(t);
4013 if (title == NULL || uri == NULL) {
4014 show_oops(t, "can't add page to favorites");
4015 goto done;
4018 urilen = strlen(uri);
4020 for (;;) {
4021 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4022 if (feof(f) || ferror(f))
4023 break;
4025 if (linelen == urilen && !strcmp(line, uri))
4026 goto done;
4028 free(line);
4029 line = NULL;
4032 fprintf(f, "\n%s\n%s", title, uri);
4033 done:
4034 if (line)
4035 free(line);
4036 fclose(f);
4038 update_favorite_tabs(NULL);
4040 return (0);
4044 can_go_back_for_real(struct tab *t)
4046 int i;
4047 WebKitWebHistoryItem *item;
4049 /* rely on webkit to make sure we can go backward when on an about page */
4050 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4051 return (webkit_web_view_can_go_forward(t->wv));
4054 /* the back/forwars list is stupid so help determine if we can go back */
4055 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4056 item != NULL;
4057 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4058 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4059 return (TRUE);
4062 return (FALSE);
4066 can_go_forward_for_real(struct tab *t)
4068 int i;
4069 WebKitWebHistoryItem *item;
4071 /* rely on webkit to make sure we can go forward when on an about page */
4072 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4073 return (webkit_web_view_can_go_forward(t->wv));
4075 /* the back/forwars list is stupid so help selecting a different item */
4076 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4077 item != NULL;
4078 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4079 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4080 return (TRUE);
4083 return (FALSE);
4086 void
4087 go_back_for_real(struct tab *t)
4089 int i;
4090 WebKitWebHistoryItem *item;
4092 /* the back/forwars list is stupid so help selecting a different item */
4093 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4094 item != NULL;
4095 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4096 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t))) {
4097 webkit_web_view_go_to_back_forward_item(t->wv, item);
4098 break;
4103 void
4104 go_forward_for_real(struct tab *t)
4106 int i;
4107 WebKitWebHistoryItem *item;
4109 /* the back/forwars list is stupid so help selecting a different item */
4110 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4111 item != NULL;
4112 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4113 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t))) {
4114 webkit_web_view_go_to_back_forward_item(t->wv, item);
4115 break;
4121 navaction(struct tab *t, struct karg *args)
4123 WebKitWebHistoryItem *item;
4124 WebKitWebFrame *frame;
4126 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4127 t->tab_id, args->i);
4129 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4130 if (t->item) {
4131 if (args->i == XT_NAV_BACK)
4132 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4133 else
4134 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4135 if (item == NULL)
4136 return (XT_CB_PASSTHROUGH);
4137 webkit_web_view_go_to_back_forward_item(t->wv, item);
4138 t->item = NULL;
4139 return (XT_CB_PASSTHROUGH);
4142 switch (args->i) {
4143 case XT_NAV_BACK:
4144 marks_clear(t);
4145 go_back_for_real(t);
4146 break;
4147 case XT_NAV_FORWARD:
4148 marks_clear(t);
4149 go_forward_for_real(t);
4150 break;
4151 case XT_NAV_RELOAD:
4152 frame = webkit_web_view_get_main_frame(t->wv);
4153 webkit_web_frame_reload(frame);
4154 break;
4156 return (XT_CB_PASSTHROUGH);
4160 move(struct tab *t, struct karg *args)
4162 GtkAdjustment *adjust;
4163 double pi, si, pos, ps, upper, lower, max;
4164 double percent;
4166 switch (args->i) {
4167 case XT_MOVE_DOWN:
4168 case XT_MOVE_UP:
4169 case XT_MOVE_BOTTOM:
4170 case XT_MOVE_TOP:
4171 case XT_MOVE_PAGEDOWN:
4172 case XT_MOVE_PAGEUP:
4173 case XT_MOVE_HALFDOWN:
4174 case XT_MOVE_HALFUP:
4175 case XT_MOVE_PERCENT:
4176 adjust = t->adjust_v;
4177 break;
4178 default:
4179 adjust = t->adjust_h;
4180 break;
4183 pos = gtk_adjustment_get_value(adjust);
4184 ps = gtk_adjustment_get_page_size(adjust);
4185 upper = gtk_adjustment_get_upper(adjust);
4186 lower = gtk_adjustment_get_lower(adjust);
4187 si = gtk_adjustment_get_step_increment(adjust);
4188 pi = gtk_adjustment_get_page_increment(adjust);
4189 max = upper - ps;
4191 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4192 "max %f si %f pi %f\n",
4193 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4194 pos, ps, upper, lower, max, si, pi);
4196 switch (args->i) {
4197 case XT_MOVE_DOWN:
4198 case XT_MOVE_RIGHT:
4199 pos += si;
4200 gtk_adjustment_set_value(adjust, MIN(pos, max));
4201 break;
4202 case XT_MOVE_UP:
4203 case XT_MOVE_LEFT:
4204 pos -= si;
4205 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4206 break;
4207 case XT_MOVE_BOTTOM:
4208 case XT_MOVE_FARRIGHT:
4209 gtk_adjustment_set_value(adjust, max);
4210 break;
4211 case XT_MOVE_TOP:
4212 case XT_MOVE_FARLEFT:
4213 gtk_adjustment_set_value(adjust, lower);
4214 break;
4215 case XT_MOVE_PAGEDOWN:
4216 pos += pi;
4217 gtk_adjustment_set_value(adjust, MIN(pos, max));
4218 break;
4219 case XT_MOVE_PAGEUP:
4220 pos -= pi;
4221 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4222 break;
4223 case XT_MOVE_HALFDOWN:
4224 pos += pi / 2;
4225 gtk_adjustment_set_value(adjust, MIN(pos, max));
4226 break;
4227 case XT_MOVE_HALFUP:
4228 pos -= pi / 2;
4229 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4230 break;
4231 case XT_MOVE_PERCENT:
4232 percent = atoi(args->s) / 100.0;
4233 pos = max * percent;
4234 if (pos < 0.0 || pos > max)
4235 break;
4236 gtk_adjustment_set_value(adjust, pos);
4237 break;
4238 default:
4239 return (XT_CB_PASSTHROUGH);
4242 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4244 return (XT_CB_HANDLED);
4247 void
4248 url_set_visibility(void)
4250 struct tab *t;
4252 TAILQ_FOREACH(t, &tabs, entry)
4253 if (show_url == 0) {
4254 gtk_widget_hide(t->toolbar);
4255 focus_webview(t);
4256 } else
4257 gtk_widget_show(t->toolbar);
4260 void
4261 notebook_tab_set_visibility(void)
4263 if (show_tabs == 0) {
4264 gtk_widget_hide(tab_bar);
4265 gtk_notebook_set_show_tabs(notebook, FALSE);
4266 } else {
4267 if (tab_style == XT_TABS_NORMAL) {
4268 gtk_widget_hide(tab_bar);
4269 gtk_notebook_set_show_tabs(notebook, TRUE);
4270 } else if (tab_style == XT_TABS_COMPACT) {
4271 gtk_widget_show(tab_bar);
4272 gtk_notebook_set_show_tabs(notebook, FALSE);
4277 void
4278 statusbar_set_visibility(void)
4280 struct tab *t;
4282 TAILQ_FOREACH(t, &tabs, entry)
4283 if (show_statusbar == 0) {
4284 gtk_widget_hide(t->statusbar_box);
4285 focus_webview(t);
4286 } else
4287 gtk_widget_show(t->statusbar_box);
4290 void
4291 url_set(struct tab *t, int enable_url_entry)
4293 GdkPixbuf *pixbuf;
4294 int progress;
4296 show_url = enable_url_entry;
4298 if (enable_url_entry) {
4299 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4300 GTK_ENTRY_ICON_PRIMARY, NULL);
4301 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4302 } else {
4303 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4304 GTK_ENTRY_ICON_PRIMARY);
4305 progress =
4306 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4307 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4308 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4309 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4310 progress);
4315 fullscreen(struct tab *t, struct karg *args)
4317 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4319 if (t == NULL)
4320 return (XT_CB_PASSTHROUGH);
4322 if (show_url == 0) {
4323 url_set(t, 1);
4324 show_tabs = 1;
4325 } else {
4326 url_set(t, 0);
4327 show_tabs = 0;
4330 url_set_visibility();
4331 notebook_tab_set_visibility();
4333 return (XT_CB_HANDLED);
4337 statustoggle(struct tab *t, struct karg *args)
4339 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4341 if (show_statusbar == 1) {
4342 show_statusbar = 0;
4343 statusbar_set_visibility();
4344 } else if (show_statusbar == 0) {
4345 show_statusbar = 1;
4346 statusbar_set_visibility();
4348 return (XT_CB_HANDLED);
4352 urlaction(struct tab *t, struct karg *args)
4354 int rv = XT_CB_HANDLED;
4356 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4358 if (t == NULL)
4359 return (XT_CB_PASSTHROUGH);
4361 switch (args->i) {
4362 case XT_URL_SHOW:
4363 if (show_url == 0) {
4364 url_set(t, 1);
4365 url_set_visibility();
4367 break;
4368 case XT_URL_HIDE:
4369 if (show_url == 1) {
4370 url_set(t, 0);
4371 url_set_visibility();
4373 break;
4375 return (rv);
4379 tabaction(struct tab *t, struct karg *args)
4381 int rv = XT_CB_HANDLED;
4382 char *url = args->s;
4383 struct undo *u;
4384 struct tab *tt;
4386 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4388 if (t == NULL)
4389 return (XT_CB_PASSTHROUGH);
4391 switch (args->i) {
4392 case XT_TAB_NEW:
4393 if (strlen(url) > 0)
4394 create_new_tab(url, NULL, 1, args->precount);
4395 else
4396 create_new_tab(NULL, NULL, 1, args->precount);
4397 break;
4398 case XT_TAB_DELETE:
4399 if (args->precount < 0)
4400 delete_tab(t);
4401 else
4402 TAILQ_FOREACH(tt, &tabs, entry)
4403 if (tt->tab_id == args->precount - 1) {
4404 delete_tab(tt);
4405 break;
4407 break;
4408 case XT_TAB_DELQUIT:
4409 if (gtk_notebook_get_n_pages(notebook) > 1)
4410 delete_tab(t);
4411 else
4412 quit(t, args);
4413 break;
4414 case XT_TAB_OPEN:
4415 if (strlen(url) > 0)
4417 else {
4418 rv = XT_CB_PASSTHROUGH;
4419 goto done;
4421 load_uri(t, url);
4422 break;
4423 case XT_TAB_SHOW:
4424 if (show_tabs == 0) {
4425 show_tabs = 1;
4426 notebook_tab_set_visibility();
4428 break;
4429 case XT_TAB_HIDE:
4430 if (show_tabs == 1) {
4431 show_tabs = 0;
4432 notebook_tab_set_visibility();
4434 break;
4435 case XT_TAB_NEXTSTYLE:
4436 if (tab_style == XT_TABS_NORMAL) {
4437 tab_style = XT_TABS_COMPACT;
4438 recolor_compact_tabs();
4440 else
4441 tab_style = XT_TABS_NORMAL;
4442 notebook_tab_set_visibility();
4443 break;
4444 case XT_TAB_UNDO_CLOSE:
4445 if (undo_count == 0) {
4446 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4447 __func__);
4448 goto done;
4449 } else {
4450 undo_count--;
4451 u = TAILQ_FIRST(&undos);
4452 create_new_tab(u->uri, u, 1, -1);
4454 TAILQ_REMOVE(&undos, u, entry);
4455 g_free(u->uri);
4456 /* u->history is freed in create_new_tab() */
4457 g_free(u);
4459 break;
4460 default:
4461 rv = XT_CB_PASSTHROUGH;
4462 goto done;
4465 done:
4466 if (args->s) {
4467 g_free(args->s);
4468 args->s = NULL;
4471 return (rv);
4475 resizetab(struct tab *t, struct karg *args)
4477 if (t == NULL || args == NULL) {
4478 show_oops(NULL, "resizetab invalid parameters");
4479 return (XT_CB_PASSTHROUGH);
4482 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4483 t->tab_id, args->i);
4485 setzoom_webkit(t, args->i);
4487 return (XT_CB_HANDLED);
4491 movetab(struct tab *t, struct karg *args)
4493 int n, dest;
4495 if (t == NULL || args == NULL) {
4496 show_oops(NULL, "movetab invalid parameters");
4497 return (XT_CB_PASSTHROUGH);
4500 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4501 t->tab_id, args->i);
4503 if (args->i >= XT_TAB_INVALID)
4504 return (XT_CB_PASSTHROUGH);
4506 if (TAILQ_EMPTY(&tabs))
4507 return (XT_CB_PASSTHROUGH);
4509 n = gtk_notebook_get_n_pages(notebook);
4510 dest = gtk_notebook_get_current_page(notebook);
4512 switch (args->i) {
4513 case XT_TAB_NEXT:
4514 if (args->precount < 0)
4515 dest = dest == n - 1 ? 0 : dest + 1;
4516 else
4517 dest = args->precount - 1;
4519 break;
4520 case XT_TAB_PREV:
4521 if (args->precount < 0)
4522 dest -= 1;
4523 else
4524 dest -= args->precount % n;
4526 if (dest < 0)
4527 dest += n;
4529 break;
4530 case XT_TAB_FIRST:
4531 dest = 0;
4532 break;
4533 case XT_TAB_LAST:
4534 dest = n - 1;
4535 break;
4536 default:
4537 return (XT_CB_PASSTHROUGH);
4540 if (dest < 0 || dest >= n)
4541 return (XT_CB_PASSTHROUGH);
4542 if (t->tab_id == dest) {
4543 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4544 return (XT_CB_HANDLED);
4547 set_current_tab(dest);
4549 return (XT_CB_HANDLED);
4552 int cmd_prefix = 0;
4555 command(struct tab *t, struct karg *args)
4557 char *s = NULL, *ss = NULL;
4558 GdkColor color;
4559 const gchar *uri;
4561 if (t == NULL || args == NULL) {
4562 show_oops(NULL, "command invalid parameters");
4563 return (XT_CB_PASSTHROUGH);
4566 switch (args->i) {
4567 case '/':
4568 s = "/";
4569 break;
4570 case '?':
4571 s = "?";
4572 break;
4573 case ':':
4574 if (cmd_prefix == 0)
4575 s = ":";
4576 else {
4577 ss = g_strdup_printf(":%d", cmd_prefix);
4578 s = ss;
4579 cmd_prefix = 0;
4581 break;
4582 case XT_CMD_OPEN:
4583 s = ":open ";
4584 break;
4585 case XT_CMD_TABNEW:
4586 s = ":tabnew ";
4587 break;
4588 case XT_CMD_OPEN_CURRENT:
4589 s = ":open ";
4590 /* FALL THROUGH */
4591 case XT_CMD_TABNEW_CURRENT:
4592 if (!s) /* FALL THROUGH? */
4593 s = ":tabnew ";
4594 if ((uri = get_uri(t)) != NULL) {
4595 ss = g_strdup_printf("%s%s", s, uri);
4596 s = ss;
4598 break;
4599 default:
4600 show_oops(t, "command: invalid opcode %d", args->i);
4601 return (XT_CB_PASSTHROUGH);
4604 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4606 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4607 gdk_color_parse(XT_COLOR_WHITE, &color);
4608 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4609 show_cmd(t);
4610 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4611 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4613 if (ss)
4614 g_free(ss);
4616 return (XT_CB_HANDLED);
4620 * Return a new string with a download row (in html)
4621 * appended. Old string is freed.
4623 char *
4624 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4627 WebKitDownloadStatus stat;
4628 char *status_html = NULL, *cmd_html = NULL, *new_html;
4629 gdouble progress;
4630 char cur_sz[FMT_SCALED_STRSIZE];
4631 char tot_sz[FMT_SCALED_STRSIZE];
4632 char *xtp_prefix;
4634 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4636 /* All actions wil take this form:
4637 * xxxt://class/seskey
4639 xtp_prefix = g_strdup_printf("%s%d/%s/",
4640 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4642 stat = webkit_download_get_status(dl->download);
4644 switch (stat) {
4645 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4646 status_html = g_strdup_printf("Finished");
4647 cmd_html = g_strdup_printf(
4648 "<a href='%s%d/%d'>Remove</a>",
4649 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4650 break;
4651 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4652 /* gather size info */
4653 progress = 100 * webkit_download_get_progress(dl->download);
4655 fmt_scaled(
4656 webkit_download_get_current_size(dl->download), cur_sz);
4657 fmt_scaled(
4658 webkit_download_get_total_size(dl->download), tot_sz);
4660 status_html = g_strdup_printf(
4661 "<div style='width: 100%%' align='center'>"
4662 "<div class='progress-outer'>"
4663 "<div class='progress-inner' style='width: %.2f%%'>"
4664 "</div></div></div>"
4665 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4666 progress, cur_sz, tot_sz, progress);
4668 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4669 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4671 break;
4672 /* LLL */
4673 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4674 status_html = g_strdup_printf("Cancelled");
4675 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4676 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4677 break;
4678 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4679 status_html = g_strdup_printf("Error!");
4680 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4681 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4682 break;
4683 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4684 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4685 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4686 status_html = g_strdup_printf("Starting");
4687 break;
4688 default:
4689 show_oops(t, "%s: unknown download status", __func__);
4692 new_html = g_strdup_printf(
4693 "%s\n<tr><td>%s</td><td>%s</td>"
4694 "<td style='text-align:center'>%s</td></tr>\n",
4695 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4696 status_html, cmd_html);
4697 g_free(html);
4699 if (status_html)
4700 g_free(status_html);
4702 if (cmd_html)
4703 g_free(cmd_html);
4705 g_free(xtp_prefix);
4707 return new_html;
4711 * update all download tabs apart from one. Pass NULL if
4712 * you want to update all.
4714 void
4715 update_download_tabs(struct tab *apart_from)
4717 struct tab *t;
4718 if (!updating_dl_tabs) {
4719 updating_dl_tabs = 1; /* stop infinite recursion */
4720 TAILQ_FOREACH(t, &tabs, entry)
4721 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4722 && (t != apart_from))
4723 xtp_page_dl(t, NULL);
4724 updating_dl_tabs = 0;
4729 * update all cookie tabs apart from one. Pass NULL if
4730 * you want to update all.
4732 void
4733 update_cookie_tabs(struct tab *apart_from)
4735 struct tab *t;
4736 if (!updating_cl_tabs) {
4737 updating_cl_tabs = 1; /* stop infinite recursion */
4738 TAILQ_FOREACH(t, &tabs, entry)
4739 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4740 && (t != apart_from))
4741 xtp_page_cl(t, NULL);
4742 updating_cl_tabs = 0;
4747 * update all history tabs apart from one. Pass NULL if
4748 * you want to update all.
4750 void
4751 update_history_tabs(struct tab *apart_from)
4753 struct tab *t;
4755 if (!updating_hl_tabs) {
4756 updating_hl_tabs = 1; /* stop infinite recursion */
4757 TAILQ_FOREACH(t, &tabs, entry)
4758 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4759 && (t != apart_from))
4760 xtp_page_hl(t, NULL);
4761 updating_hl_tabs = 0;
4765 /* cookie management XTP page */
4767 xtp_page_cl(struct tab *t, struct karg *args)
4769 char *body, *page, *tmp;
4770 int i = 1; /* all ids start 1 */
4771 GSList *sc, *pc, *pc_start;
4772 SoupCookie *c;
4773 char *type, *table_headers, *last_domain;
4775 DNPRINTF(XT_D_CMD, "%s", __func__);
4777 if (t == NULL) {
4778 show_oops(NULL, "%s invalid parameters", __func__);
4779 return (1);
4782 /* Generate a new session key */
4783 if (!updating_cl_tabs)
4784 generate_xtp_session_key(&cl_session_key);
4786 /* table headers */
4787 table_headers = g_strdup_printf("<table><tr>"
4788 "<th>Type</th>"
4789 "<th>Name</th>"
4790 "<th style='width:200px'>Value</th>"
4791 "<th>Path</th>"
4792 "<th>Expires</th>"
4793 "<th>Secure</th>"
4794 "<th>HTTP<br />only</th>"
4795 "<th style='width:40px'>Rm</th></tr>\n");
4797 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4798 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4799 pc_start = pc;
4801 body = NULL;
4802 last_domain = strdup("");
4803 for (; sc; sc = sc->next) {
4804 c = sc->data;
4806 if (strcmp(last_domain, c->domain) != 0) {
4807 /* new domain */
4808 free(last_domain);
4809 last_domain = strdup(c->domain);
4811 if (body != NULL) {
4812 tmp = body;
4813 body = g_strdup_printf("%s</table>"
4814 "<h2>%s</h2>%s\n",
4815 body, c->domain, table_headers);
4816 g_free(tmp);
4817 } else {
4818 /* first domain */
4819 body = g_strdup_printf("<h2>%s</h2>%s\n",
4820 c->domain, table_headers);
4824 type = "Session";
4825 for (pc = pc_start; pc; pc = pc->next)
4826 if (soup_cookie_equal(pc->data, c)) {
4827 type = "Session + Persistent";
4828 break;
4831 tmp = body;
4832 body = g_strdup_printf(
4833 "%s\n<tr>"
4834 "<td>%s</td>"
4835 "<td style='word-wrap:normal'>%s</td>"
4836 "<td>"
4837 " <textarea rows='4'>%s</textarea>"
4838 "</td>"
4839 "<td>%s</td>"
4840 "<td>%s</td>"
4841 "<td>%d</td>"
4842 "<td>%d</td>"
4843 "<td style='text-align:center'>"
4844 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4845 body,
4846 type,
4847 c->name,
4848 c->value,
4849 c->path,
4850 c->expires ?
4851 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4852 c->secure,
4853 c->http_only,
4855 XT_XTP_STR,
4856 XT_XTP_CL,
4857 cl_session_key,
4858 XT_XTP_CL_REMOVE,
4862 g_free(tmp);
4863 i++;
4866 soup_cookies_free(sc);
4867 soup_cookies_free(pc);
4869 /* small message if there are none */
4870 if (i == 1) {
4871 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4872 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4874 tmp = body;
4875 body = g_strdup_printf("%s</table>", body);
4876 g_free(tmp);
4878 page = get_html_page("Cookie Jar", body, "", TRUE);
4879 g_free(body);
4880 g_free(table_headers);
4881 g_free(last_domain);
4883 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4884 update_cookie_tabs(t);
4886 g_free(page);
4888 return (0);
4892 xtp_page_hl(struct tab *t, struct karg *args)
4894 char *body, *page, *tmp;
4895 struct history *h;
4896 int i = 1; /* all ids start 1 */
4898 DNPRINTF(XT_D_CMD, "%s", __func__);
4900 if (t == NULL) {
4901 show_oops(NULL, "%s invalid parameters", __func__);
4902 return (1);
4905 /* Generate a new session key */
4906 if (!updating_hl_tabs)
4907 generate_xtp_session_key(&hl_session_key);
4909 /* body */
4910 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4911 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4913 RB_FOREACH_REVERSE(h, history_list, &hl) {
4914 tmp = body;
4915 body = g_strdup_printf(
4916 "%s\n<tr>"
4917 "<td><a href='%s'>%s</a></td>"
4918 "<td>%s</td>"
4919 "<td style='text-align: center'>"
4920 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4921 body, h->uri, h->uri, h->title,
4922 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4923 XT_XTP_HL_REMOVE, i);
4925 g_free(tmp);
4926 i++;
4929 /* small message if there are none */
4930 if (i == 1) {
4931 tmp = body;
4932 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4933 "colspan='3'>No History</td></tr>\n", body);
4934 g_free(tmp);
4937 tmp = body;
4938 body = g_strdup_printf("%s</table>", body);
4939 g_free(tmp);
4941 page = get_html_page("History", body, "", TRUE);
4942 g_free(body);
4945 * update all history manager tabs as the xtp session
4946 * key has now changed. No need to update the current tab.
4947 * Already did that above.
4949 update_history_tabs(t);
4951 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4952 g_free(page);
4954 return (0);
4958 * Generate a web page detailing the status of any downloads
4961 xtp_page_dl(struct tab *t, struct karg *args)
4963 struct download *dl;
4964 char *body, *page, *tmp;
4965 char *ref;
4966 int n_dl = 1;
4968 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4970 if (t == NULL) {
4971 show_oops(NULL, "%s invalid parameters", __func__);
4972 return (1);
4976 * Generate a new session key for next page instance.
4977 * This only happens for the top level call to xtp_page_dl()
4978 * in which case updating_dl_tabs is 0.
4980 if (!updating_dl_tabs)
4981 generate_xtp_session_key(&dl_session_key);
4983 /* header - with refresh so as to update */
4984 if (refresh_interval >= 1)
4985 ref = g_strdup_printf(
4986 "<meta http-equiv='refresh' content='%u"
4987 ";url=%s%d/%s/%d' />\n",
4988 refresh_interval,
4989 XT_XTP_STR,
4990 XT_XTP_DL,
4991 dl_session_key,
4992 XT_XTP_DL_LIST);
4993 else
4994 ref = g_strdup("");
4996 body = g_strdup_printf("<div align='center'>"
4997 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4998 "</p><table><tr><th style='width: 60%%'>"
4999 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5000 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5002 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5003 body = xtp_page_dl_row(t, body, dl);
5004 n_dl++;
5007 /* message if no downloads in list */
5008 if (n_dl == 1) {
5009 tmp = body;
5010 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5011 " style='text-align: center'>"
5012 "No downloads</td></tr>\n", body);
5013 g_free(tmp);
5016 tmp = body;
5017 body = g_strdup_printf("%s</table></div>", body);
5018 g_free(tmp);
5020 page = get_html_page("Downloads", body, ref, 1);
5021 g_free(ref);
5022 g_free(body);
5025 * update all download manager tabs as the xtp session
5026 * key has now changed. No need to update the current tab.
5027 * Already did that above.
5029 update_download_tabs(t);
5031 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5032 g_free(page);
5034 return (0);
5038 search(struct tab *t, struct karg *args)
5040 gboolean d;
5042 if (t == NULL || args == NULL) {
5043 show_oops(NULL, "search invalid parameters");
5044 return (1);
5046 if (t->search_text == NULL) {
5047 if (global_search == NULL)
5048 return (XT_CB_PASSTHROUGH);
5049 else {
5050 t->search_text = g_strdup(global_search);
5051 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5052 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5056 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5057 t->tab_id, args->i, t->search_forward, t->search_text);
5059 switch (args->i) {
5060 case XT_SEARCH_NEXT:
5061 d = t->search_forward;
5062 break;
5063 case XT_SEARCH_PREV:
5064 d = !t->search_forward;
5065 break;
5066 default:
5067 return (XT_CB_PASSTHROUGH);
5070 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5072 return (XT_CB_HANDLED);
5075 struct settings_args {
5076 char **body;
5077 int i;
5080 void
5081 print_setting(struct settings *s, char *val, void *cb_args)
5083 char *tmp, *color;
5084 struct settings_args *sa = cb_args;
5086 if (sa == NULL)
5087 return;
5089 if (s->flags & XT_SF_RUNTIME)
5090 color = "#22cc22";
5091 else
5092 color = "#cccccc";
5094 tmp = *sa->body;
5095 *sa->body = g_strdup_printf(
5096 "%s\n<tr>"
5097 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5098 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5099 *sa->body,
5100 color,
5101 s->name,
5102 color,
5105 g_free(tmp);
5106 sa->i++;
5110 set_show(struct tab *t, struct karg *args)
5112 char *body, *page, *tmp;
5113 int i = 1;
5114 struct settings_args sa;
5116 bzero(&sa, sizeof sa);
5117 sa.body = &body;
5119 /* body */
5120 body = g_strdup_printf("<div align='center'><table><tr>"
5121 "<th align='left'>Setting</th>"
5122 "<th align='left'>Value</th></tr>\n");
5124 settings_walk(print_setting, &sa);
5125 i = sa.i;
5127 /* small message if there are none */
5128 if (i == 1) {
5129 tmp = body;
5130 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5131 "colspan='2'>No settings</td></tr>\n", body);
5132 g_free(tmp);
5135 tmp = body;
5136 body = g_strdup_printf("%s</table></div>", body);
5137 g_free(tmp);
5139 page = get_html_page("Settings", body, "", 0);
5141 g_free(body);
5143 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5145 g_free(page);
5147 return (XT_CB_PASSTHROUGH);
5151 set(struct tab *t, struct karg *args)
5153 char *p, *val;
5154 int i;
5156 if (args == NULL || args->s == NULL)
5157 return (set_show(t, args));
5159 /* strip spaces */
5160 p = g_strstrip(args->s);
5162 if (strlen(p) == 0)
5163 return (set_show(t, args));
5165 /* we got some sort of string */
5166 val = g_strrstr(p, "=");
5167 if (val) {
5168 *val++ = '\0';
5169 val = g_strchomp(val);
5170 p = g_strchomp(p);
5172 for (i = 0; i < LENGTH(rs); i++) {
5173 if (strcmp(rs[i].name, p))
5174 continue;
5176 if (rs[i].activate) {
5177 if (rs[i].activate(val))
5178 show_oops(t, "%s invalid value %s",
5179 p, val);
5180 else
5181 show_oops(t, ":set %s = %s", p, val);
5182 goto done;
5183 } else {
5184 show_oops(t, "not a runtime option: %s", p);
5185 goto done;
5188 show_oops(t, "unknown option: %s", p);
5189 } else {
5190 p = g_strchomp(p);
5192 for (i = 0; i < LENGTH(rs); i++) {
5193 if (strcmp(rs[i].name, p))
5194 continue;
5196 /* XXX this could use some cleanup */
5197 switch (rs[i].type) {
5198 case XT_S_INT:
5199 if (rs[i].ival)
5200 show_oops(t, "%s = %d",
5201 rs[i].name, *rs[i].ival);
5202 else if (rs[i].s && rs[i].s->get)
5203 show_oops(t, "%s = %s",
5204 rs[i].name,
5205 rs[i].s->get(&rs[i]));
5206 else if (rs[i].s && rs[i].s->get == NULL)
5207 show_oops(t, "%s = ...", rs[i].name);
5208 else
5209 show_oops(t, "%s = ", rs[i].name);
5210 break;
5211 case XT_S_FLOAT:
5212 if (rs[i].fval)
5213 show_oops(t, "%s = %f",
5214 rs[i].name, *rs[i].fval);
5215 else if (rs[i].s && rs[i].s->get)
5216 show_oops(t, "%s = %s",
5217 rs[i].name,
5218 rs[i].s->get(&rs[i]));
5219 else if (rs[i].s && rs[i].s->get == NULL)
5220 show_oops(t, "%s = ...", rs[i].name);
5221 else
5222 show_oops(t, "%s = ", rs[i].name);
5223 break;
5224 case XT_S_STR:
5225 if (rs[i].sval && *rs[i].sval)
5226 show_oops(t, "%s = %s",
5227 rs[i].name, *rs[i].sval);
5228 else if (rs[i].s && rs[i].s->get)
5229 show_oops(t, "%s = %s",
5230 rs[i].name,
5231 rs[i].s->get(&rs[i]));
5232 else if (rs[i].s && rs[i].s->get == NULL)
5233 show_oops(t, "%s = ...", rs[i].name);
5234 else
5235 show_oops(t, "%s = ", rs[i].name);
5236 break;
5237 default:
5238 show_oops(t, "unknown type for %s", rs[i].name);
5239 goto done;
5242 goto done;
5244 show_oops(t, "unknown option: %s", p);
5246 done:
5247 return (XT_CB_PASSTHROUGH);
5251 session_save(struct tab *t, char *filename)
5253 struct karg a;
5254 int rv = 1;
5255 struct session *s;
5257 if (strlen(filename) == 0)
5258 goto done;
5260 if (filename[0] == '.' || filename[0] == '/')
5261 goto done;
5263 a.s = filename;
5264 if (save_tabs(t, &a))
5265 goto done;
5266 strlcpy(named_session, filename, sizeof named_session);
5268 /* add the new session to the list of sessions */
5269 s = g_malloc(sizeof(struct session));
5270 s->name = g_strdup(filename);
5271 TAILQ_INSERT_TAIL(&sessions, s, entry);
5273 rv = 0;
5274 done:
5275 return (rv);
5279 session_open(struct tab *t, char *filename)
5281 struct karg a;
5282 int rv = 1;
5284 if (strlen(filename) == 0)
5285 goto done;
5287 if (filename[0] == '.' || filename[0] == '/')
5288 goto done;
5290 a.s = filename;
5291 a.i = XT_SES_CLOSETABS;
5292 if (open_tabs(t, &a))
5293 goto done;
5295 strlcpy(named_session, filename, sizeof named_session);
5297 rv = 0;
5298 done:
5299 return (rv);
5303 session_delete(struct tab *t, char *filename)
5305 char file[PATH_MAX];
5306 int rv = 1;
5307 struct session *s;
5309 if (strlen(filename) == 0)
5310 goto done;
5312 if (filename[0] == '.' || filename[0] == '/')
5313 goto done;
5315 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5316 if (unlink(file))
5317 goto done;
5319 if (!strcmp(filename, named_session))
5320 strlcpy(named_session, XT_SAVED_TABS_FILE,
5321 sizeof named_session);
5323 /* remove session from sessions list */
5324 TAILQ_FOREACH(s, &sessions, entry) {
5325 if (!strcmp(s->name, filename))
5326 break;
5328 TAILQ_REMOVE(&sessions, s, entry);
5329 g_free((gpointer) s->name);
5330 g_free(s);
5332 rv = 0;
5333 done:
5334 return (rv);
5338 session_cmd(struct tab *t, struct karg *args)
5340 char *filename = args->s;
5342 if (t == NULL)
5343 return (1);
5345 if (args->i & XT_SHOW)
5346 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5347 XT_SAVED_TABS_FILE : named_session);
5348 else if (args->i & XT_SAVE) {
5349 if (session_save(t, filename)) {
5350 show_oops(t, "Can't save session: %s",
5351 filename ? filename : "INVALID");
5352 goto done;
5354 } else if (args->i & XT_OPEN) {
5355 if (session_open(t, filename)) {
5356 show_oops(t, "Can't open session: %s",
5357 filename ? filename : "INVALID");
5358 goto done;
5360 } else if (args->i & XT_DELETE) {
5361 if (session_delete(t, filename)) {
5362 show_oops(t, "Can't delete session: %s",
5363 filename ? filename : "INVALID");
5364 goto done;
5367 done:
5368 return (XT_CB_PASSTHROUGH);
5372 * Make a hardcopy of the page
5375 print_page(struct tab *t, struct karg *args)
5377 WebKitWebFrame *frame;
5378 GtkPageSetup *ps;
5379 GtkPrintOperation *op;
5380 GtkPrintOperationAction action;
5381 GtkPrintOperationResult print_res;
5382 GError *g_err = NULL;
5383 int marg_l, marg_r, marg_t, marg_b;
5385 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5387 ps = gtk_page_setup_new();
5388 op = gtk_print_operation_new();
5389 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5390 frame = webkit_web_view_get_main_frame(t->wv);
5392 /* the default margins are too small, so we will bump them */
5393 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5394 XT_PRINT_EXTRA_MARGIN;
5395 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5396 XT_PRINT_EXTRA_MARGIN;
5397 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5398 XT_PRINT_EXTRA_MARGIN;
5399 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5400 XT_PRINT_EXTRA_MARGIN;
5402 /* set margins */
5403 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5404 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5405 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5406 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5408 gtk_print_operation_set_default_page_setup(op, ps);
5410 /* this appears to free 'op' and 'ps' */
5411 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5413 /* check it worked */
5414 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5415 show_oops(NULL, "can't print: %s", g_err->message);
5416 g_error_free (g_err);
5417 return (1);
5420 return (0);
5424 go_home(struct tab *t, struct karg *args)
5426 load_uri(t, home);
5427 return (0);
5431 restart(struct tab *t, struct karg *args)
5433 struct karg a;
5435 a.s = XT_RESTART_TABS_FILE;
5436 save_tabs(t, &a);
5437 execvp(start_argv[0], start_argv);
5438 /* NOTREACHED */
5440 return (0);
5443 #define CTRL GDK_CONTROL_MASK
5444 #define MOD1 GDK_MOD1_MASK
5445 #define SHFT GDK_SHIFT_MASK
5447 /* inherent to GTK not all keys will be caught at all times */
5448 /* XXX sort key bindings */
5449 struct key_binding {
5450 char *cmd;
5451 guint mask;
5452 guint use_in_entry;
5453 guint key;
5454 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5455 } keys[] = {
5456 { "cookiejar", MOD1, 0, GDK_j },
5457 { "downloadmgr", MOD1, 0, GDK_d },
5458 { "history", MOD1, 0, GDK_h },
5459 { "print", CTRL, 0, GDK_p },
5460 { "search", 0, 0, GDK_slash },
5461 { "searchb", 0, 0, GDK_question },
5462 { "statustoggle", CTRL, 0, GDK_n },
5463 { "command", 0, 0, GDK_colon },
5464 { "qa", CTRL, 0, GDK_q },
5465 { "restart", MOD1, 0, GDK_q },
5466 { "js toggle", CTRL, 0, GDK_j },
5467 { "cookie toggle", MOD1, 0, GDK_c },
5468 { "togglesrc", CTRL, 0, GDK_s },
5469 { "yankuri", 0, 0, GDK_y },
5470 { "pasteuricur", 0, 0, GDK_p },
5471 { "pasteurinew", 0, 0, GDK_P },
5472 { "toplevel toggle", 0, 0, GDK_F4 },
5473 { "help", 0, 0, GDK_F1 },
5474 { "run_script", MOD1, 0, GDK_r },
5476 /* search */
5477 { "searchnext", 0, 0, GDK_n },
5478 { "searchprevious", 0, 0, GDK_N },
5480 /* focus */
5481 { "focusaddress", 0, 0, GDK_F6 },
5482 { "focussearch", 0, 0, GDK_F7 },
5484 /* hinting */
5485 { "hinting", 0, 0, GDK_f },
5487 /* custom stylesheet */
5488 { "userstyle", 0, 0, GDK_i },
5490 /* navigation */
5491 { "goback", 0, 0, GDK_BackSpace },
5492 { "goback", MOD1, 0, GDK_Left },
5493 { "goforward", SHFT, 0, GDK_BackSpace },
5494 { "goforward", MOD1, 0, GDK_Right },
5495 { "reload", 0, 0, GDK_F5 },
5496 { "reload", CTRL, 0, GDK_r },
5497 { "reload", CTRL, 0, GDK_l },
5498 { "favorites", MOD1, 1, GDK_f },
5500 /* vertical movement */
5501 { "scrolldown", 0, 0, GDK_j },
5502 { "scrolldown", 0, 0, GDK_Down },
5503 { "scrollup", 0, 0, GDK_Up },
5504 { "scrollup", 0, 0, GDK_k },
5505 { "scrollbottom", 0, 0, GDK_G },
5506 { "scrollbottom", 0, 0, GDK_End },
5507 { "scrolltop", 0, 0, GDK_Home },
5508 { "scrollpagedown", 0, 0, GDK_space },
5509 { "scrollpagedown", CTRL, 0, GDK_f },
5510 { "scrollhalfdown", CTRL, 0, GDK_d },
5511 { "scrollpagedown", 0, 0, GDK_Page_Down },
5512 { "scrollpageup", 0, 0, GDK_Page_Up },
5513 { "scrollpageup", CTRL, 0, GDK_b },
5514 { "scrollhalfup", CTRL, 0, GDK_u },
5515 /* horizontal movement */
5516 { "scrollright", 0, 0, GDK_l },
5517 { "scrollright", 0, 0, GDK_Right },
5518 { "scrollleft", 0, 0, GDK_Left },
5519 { "scrollleft", 0, 0, GDK_h },
5520 { "scrollfarright", 0, 0, GDK_dollar },
5521 { "scrollfarleft", 0, 0, GDK_0 },
5523 /* tabs */
5524 { "tabnew", CTRL, 0, GDK_t },
5525 { "999tabnew", CTRL, 0, GDK_T },
5526 { "tabclose", CTRL, 1, GDK_w },
5527 { "tabundoclose", 0, 0, GDK_U },
5528 { "tabnext 1", CTRL, 0, GDK_1 },
5529 { "tabnext 2", CTRL, 0, GDK_2 },
5530 { "tabnext 3", CTRL, 0, GDK_3 },
5531 { "tabnext 4", CTRL, 0, GDK_4 },
5532 { "tabnext 5", CTRL, 0, GDK_5 },
5533 { "tabnext 6", CTRL, 0, GDK_6 },
5534 { "tabnext 7", CTRL, 0, GDK_7 },
5535 { "tabnext 8", CTRL, 0, GDK_8 },
5536 { "tabnext 9", CTRL, 0, GDK_9 },
5537 { "tabfirst", CTRL, 0, GDK_less },
5538 { "tablast", CTRL, 0, GDK_greater },
5539 { "tabprevious", CTRL, 0, GDK_Left },
5540 { "tabnext", CTRL, 0, GDK_Right },
5541 { "focusout", CTRL, 0, GDK_minus },
5542 { "focusin", CTRL, 0, GDK_plus },
5543 { "focusin", CTRL, 0, GDK_equal },
5544 { "focusreset", CTRL, 0, GDK_0 },
5546 /* command aliases (handy when -S flag is used) */
5547 { "promptopen", 0, 0, GDK_F9 },
5548 { "promptopencurrent", 0, 0, GDK_F10 },
5549 { "prompttabnew", 0, 0, GDK_F11 },
5550 { "prompttabnewcurrent",0, 0, GDK_F12 },
5552 TAILQ_HEAD(keybinding_list, key_binding);
5554 void
5555 walk_kb(struct settings *s,
5556 void (*cb)(struct settings *, char *, void *), void *cb_args)
5558 struct key_binding *k;
5559 char str[1024];
5561 if (s == NULL || cb == NULL) {
5562 show_oops(NULL, "walk_kb invalid parameters");
5563 return;
5566 TAILQ_FOREACH(k, &kbl, entry) {
5567 if (k->cmd == NULL)
5568 continue;
5569 str[0] = '\0';
5571 /* sanity */
5572 if (gdk_keyval_name(k->key) == NULL)
5573 continue;
5575 strlcat(str, k->cmd, sizeof str);
5576 strlcat(str, ",", sizeof str);
5578 if (k->mask & GDK_SHIFT_MASK)
5579 strlcat(str, "S-", sizeof str);
5580 if (k->mask & GDK_CONTROL_MASK)
5581 strlcat(str, "C-", sizeof str);
5582 if (k->mask & GDK_MOD1_MASK)
5583 strlcat(str, "M1-", sizeof str);
5584 if (k->mask & GDK_MOD2_MASK)
5585 strlcat(str, "M2-", sizeof str);
5586 if (k->mask & GDK_MOD3_MASK)
5587 strlcat(str, "M3-", sizeof str);
5588 if (k->mask & GDK_MOD4_MASK)
5589 strlcat(str, "M4-", sizeof str);
5590 if (k->mask & GDK_MOD5_MASK)
5591 strlcat(str, "M5-", sizeof str);
5593 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5594 cb(s, str, cb_args);
5598 void
5599 init_keybindings(void)
5601 int i;
5602 struct key_binding *k;
5604 for (i = 0; i < LENGTH(keys); i++) {
5605 k = g_malloc0(sizeof *k);
5606 k->cmd = keys[i].cmd;
5607 k->mask = keys[i].mask;
5608 k->use_in_entry = keys[i].use_in_entry;
5609 k->key = keys[i].key;
5610 TAILQ_INSERT_HEAD(&kbl, k, entry);
5612 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5613 k->cmd ? k->cmd : "unnamed key");
5617 void
5618 keybinding_clearall(void)
5620 struct key_binding *k, *next;
5622 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5623 next = TAILQ_NEXT(k, entry);
5624 if (k->cmd == NULL)
5625 continue;
5627 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5628 k->cmd ? k->cmd : "unnamed key");
5629 TAILQ_REMOVE(&kbl, k, entry);
5630 g_free(k);
5635 keybinding_add(char *cmd, char *key, int use_in_entry)
5637 struct key_binding *k;
5638 guint keyval, mask = 0;
5639 int i;
5641 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5643 /* Keys which are to be used in entry have been prefixed with an
5644 * exclamation mark. */
5645 if (use_in_entry)
5646 key++;
5648 /* find modifier keys */
5649 if (strstr(key, "S-"))
5650 mask |= GDK_SHIFT_MASK;
5651 if (strstr(key, "C-"))
5652 mask |= GDK_CONTROL_MASK;
5653 if (strstr(key, "M1-"))
5654 mask |= GDK_MOD1_MASK;
5655 if (strstr(key, "M2-"))
5656 mask |= GDK_MOD2_MASK;
5657 if (strstr(key, "M3-"))
5658 mask |= GDK_MOD3_MASK;
5659 if (strstr(key, "M4-"))
5660 mask |= GDK_MOD4_MASK;
5661 if (strstr(key, "M5-"))
5662 mask |= GDK_MOD5_MASK;
5664 /* find keyname */
5665 for (i = strlen(key) - 1; i > 0; i--)
5666 if (key[i] == '-')
5667 key = &key[i + 1];
5669 /* validate keyname */
5670 keyval = gdk_keyval_from_name(key);
5671 if (keyval == GDK_VoidSymbol) {
5672 warnx("invalid keybinding name %s", key);
5673 return (1);
5675 /* must run this test too, gtk+ doesn't handle 10 for example */
5676 if (gdk_keyval_name(keyval) == NULL) {
5677 warnx("invalid keybinding name %s", key);
5678 return (1);
5681 /* Remove eventual dupes. */
5682 TAILQ_FOREACH(k, &kbl, entry)
5683 if (k->key == keyval && k->mask == mask) {
5684 TAILQ_REMOVE(&kbl, k, entry);
5685 g_free(k);
5686 break;
5689 /* add keyname */
5690 k = g_malloc0(sizeof *k);
5691 k->cmd = g_strdup(cmd);
5692 k->mask = mask;
5693 k->use_in_entry = use_in_entry;
5694 k->key = keyval;
5696 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5697 k->cmd,
5698 k->mask,
5699 k->use_in_entry,
5700 k->key);
5701 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5702 k->cmd, gdk_keyval_name(keyval));
5704 TAILQ_INSERT_HEAD(&kbl, k, entry);
5706 return (0);
5710 add_kb(struct settings *s, char *entry)
5712 char *kb, *key;
5714 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5716 /* clearall is special */
5717 if (!strcmp(entry, "clearall")) {
5718 keybinding_clearall();
5719 return (0);
5722 kb = strstr(entry, ",");
5723 if (kb == NULL)
5724 return (1);
5725 *kb = '\0';
5726 key = kb + 1;
5728 return (keybinding_add(entry, key, key[0] == '!'));
5731 struct cmd {
5732 char *cmd;
5733 int level;
5734 int (*func)(struct tab *, struct karg *);
5735 int arg;
5736 int type;
5737 } cmds[] = {
5738 { "command", 0, command, ':', 0 },
5739 { "search", 0, command, '/', 0 },
5740 { "searchb", 0, command, '?', 0 },
5741 { "togglesrc", 0, toggle_src, 0, 0 },
5743 /* yanking and pasting */
5744 { "yankuri", 0, yank_uri, 0, 0 },
5745 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5746 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5747 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5749 /* search */
5750 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5751 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5753 /* focus */
5754 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5755 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5757 /* hinting */
5758 { "hinting", 0, hint, 0, 0 },
5760 /* custom stylesheet */
5761 { "userstyle", 0, userstyle, 0, 0 },
5763 /* navigation */
5764 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5765 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5766 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5768 /* vertical movement */
5769 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5770 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5771 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5772 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5773 { "1", 0, move, XT_MOVE_TOP, 0 },
5774 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5775 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5776 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5777 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5778 /* horizontal movement */
5779 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5780 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5781 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5782 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5784 { "favorites", 0, xtp_page_fl, 0, 0 },
5785 { "fav", 0, xtp_page_fl, 0, 0 },
5786 { "favadd", 0, add_favorite, 0, 0 },
5788 { "qall", 0, quit, 0, 0 },
5789 { "quitall", 0, quit, 0, 0 },
5790 { "w", 0, save_tabs, 0, 0 },
5791 { "wq", 0, save_tabs_and_quit, 0, 0 },
5792 { "help", 0, help, 0, 0 },
5793 { "about", 0, about, 0, 0 },
5794 { "stats", 0, stats, 0, 0 },
5795 { "version", 0, about, 0, 0 },
5797 /* js command */
5798 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5799 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5800 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5801 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5802 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5803 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5804 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5805 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5806 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5807 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5808 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5810 /* cookie command */
5811 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5812 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5813 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5814 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5815 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5816 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5817 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5818 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5819 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5820 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5821 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5823 /* toplevel (domain) command */
5824 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5825 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5827 /* cookie jar */
5828 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5830 /* cert command */
5831 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5832 { "save", 1, cert_cmd, XT_SAVE, 0 },
5833 { "show", 1, cert_cmd, XT_SHOW, 0 },
5835 { "ca", 0, ca_cmd, 0, 0 },
5836 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5837 { "dl", 0, xtp_page_dl, 0, 0 },
5838 { "h", 0, xtp_page_hl, 0, 0 },
5839 { "history", 0, xtp_page_hl, 0, 0 },
5840 { "home", 0, go_home, 0, 0 },
5841 { "restart", 0, restart, 0, 0 },
5842 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5843 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5844 { "statustoggle", 0, statustoggle, 0, 0 },
5845 { "run_script", 0, run_page_script, 0, XT_USERARG },
5847 { "print", 0, print_page, 0, 0 },
5849 /* tabs */
5850 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5851 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5852 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5853 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5854 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5855 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5856 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5857 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5858 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5859 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5860 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5861 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5862 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5863 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5864 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5865 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5866 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5867 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5868 { "buffers", 0, buffers, 0, 0 },
5869 { "ls", 0, buffers, 0, 0 },
5870 { "tabs", 0, buffers, 0, 0 },
5872 /* command aliases (handy when -S flag is used) */
5873 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5874 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5875 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5876 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5878 /* settings */
5879 { "set", 0, set, 0, XT_SETARG },
5881 { "fullscreen", 0, fullscreen, 0, 0 },
5882 { "f", 0, fullscreen, 0, 0 },
5884 /* sessions */
5885 { "session", 0, session_cmd, XT_SHOW, 0 },
5886 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5887 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5888 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5889 { "show", 1, session_cmd, XT_SHOW, 0 },
5892 struct {
5893 int index;
5894 int len;
5895 gchar *list[256];
5896 } cmd_status = {-1, 0};
5898 gboolean
5899 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5902 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5903 btn_down = 0;
5905 return (FALSE);
5908 gboolean
5909 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5911 struct karg a;
5913 hide_oops(t);
5914 hide_buffers(t);
5916 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5917 btn_down = 1;
5918 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5919 /* go backward */
5920 a.i = XT_NAV_BACK;
5921 navaction(t, &a);
5923 return (TRUE);
5924 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5925 /* go forward */
5926 a.i = XT_NAV_FORWARD;
5927 navaction(t, &a);
5929 return (TRUE);
5932 return (FALSE);
5935 gboolean
5936 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5938 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5940 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5941 delete_tab(t);
5943 return (FALSE);
5947 * cancel, remove, etc. downloads
5949 void
5950 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5952 struct download find, *d = NULL;
5954 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5956 /* some commands require a valid download id */
5957 if (cmd != XT_XTP_DL_LIST) {
5958 /* lookup download in question */
5959 find.id = id;
5960 d = RB_FIND(download_list, &downloads, &find);
5962 if (d == NULL) {
5963 show_oops(t, "%s: no such download", __func__);
5964 return;
5968 /* decide what to do */
5969 switch (cmd) {
5970 case XT_XTP_DL_CANCEL:
5971 webkit_download_cancel(d->download);
5972 break;
5973 case XT_XTP_DL_REMOVE:
5974 webkit_download_cancel(d->download); /* just incase */
5975 g_object_unref(d->download);
5976 RB_REMOVE(download_list, &downloads, d);
5977 break;
5978 case XT_XTP_DL_LIST:
5979 /* Nothing */
5980 break;
5981 default:
5982 show_oops(t, "%s: unknown command", __func__);
5983 break;
5985 xtp_page_dl(t, NULL);
5989 * Actions on history, only does one thing for now, but
5990 * we provide the function for future actions
5992 void
5993 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5995 struct history *h, *next;
5996 int i = 1;
5998 switch (cmd) {
5999 case XT_XTP_HL_REMOVE:
6000 /* walk backwards, as listed in reverse */
6001 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6002 next = RB_PREV(history_list, &hl, h);
6003 if (id == i) {
6004 RB_REMOVE(history_list, &hl, h);
6005 g_free((gpointer) h->title);
6006 g_free((gpointer) h->uri);
6007 g_free(h);
6008 break;
6010 i++;
6012 break;
6013 case XT_XTP_HL_LIST:
6014 /* Nothing - just xtp_page_hl() below */
6015 break;
6016 default:
6017 show_oops(t, "%s: unknown command", __func__);
6018 break;
6021 xtp_page_hl(t, NULL);
6024 /* remove a favorite */
6025 void
6026 remove_favorite(struct tab *t, int index)
6028 char file[PATH_MAX], *title, *uri = NULL;
6029 char *new_favs, *tmp;
6030 FILE *f;
6031 int i;
6032 size_t len, lineno;
6034 /* open favorites */
6035 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6037 if ((f = fopen(file, "r")) == NULL) {
6038 show_oops(t, "%s: can't open favorites: %s",
6039 __func__, strerror(errno));
6040 return;
6043 /* build a string which will become the new favroites file */
6044 new_favs = g_strdup("");
6046 for (i = 1;;) {
6047 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6048 if (feof(f) || ferror(f))
6049 break;
6050 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6051 if (len == 0) {
6052 free(title);
6053 title = NULL;
6054 continue;
6057 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6058 if (feof(f) || ferror(f)) {
6059 show_oops(t, "%s: can't parse favorites %s",
6060 __func__, strerror(errno));
6061 goto clean;
6065 /* as long as this isn't the one we are deleting add to file */
6066 if (i != index) {
6067 tmp = new_favs;
6068 new_favs = g_strdup_printf("%s%s\n%s\n",
6069 new_favs, title, uri);
6070 g_free(tmp);
6073 free(uri);
6074 uri = NULL;
6075 free(title);
6076 title = NULL;
6077 i++;
6079 fclose(f);
6081 /* write back new favorites file */
6082 if ((f = fopen(file, "w")) == NULL) {
6083 show_oops(t, "%s: can't open favorites: %s",
6084 __func__, strerror(errno));
6085 goto clean;
6088 fwrite(new_favs, strlen(new_favs), 1, f);
6089 fclose(f);
6091 clean:
6092 if (uri)
6093 free(uri);
6094 if (title)
6095 free(title);
6097 g_free(new_favs);
6100 void
6101 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6103 switch (cmd) {
6104 case XT_XTP_FL_LIST:
6105 /* nothing, just the below call to xtp_page_fl() */
6106 break;
6107 case XT_XTP_FL_REMOVE:
6108 remove_favorite(t, arg);
6109 break;
6110 default:
6111 show_oops(t, "%s: invalid favorites command", __func__);
6112 break;
6115 xtp_page_fl(t, NULL);
6118 void
6119 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6121 switch (cmd) {
6122 case XT_XTP_CL_LIST:
6123 /* nothing, just xtp_page_cl() */
6124 break;
6125 case XT_XTP_CL_REMOVE:
6126 remove_cookie(arg);
6127 break;
6128 default:
6129 show_oops(t, "%s: unknown cookie xtp command", __func__);
6130 break;
6133 xtp_page_cl(t, NULL);
6136 /* link an XTP class to it's session key and handler function */
6137 struct xtp_despatch {
6138 uint8_t xtp_class;
6139 char **session_key;
6140 void (*handle_func)(struct tab *, uint8_t, int);
6143 struct xtp_despatch xtp_despatches[] = {
6144 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6145 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6146 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6147 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6148 { XT_XTP_INVALID, NULL, NULL }
6152 * is the url xtp protocol? (xxxt://)
6153 * if so, parse and despatch correct bahvior
6156 parse_xtp_url(struct tab *t, const char *url)
6158 char *dup = NULL, *p, *last;
6159 uint8_t n_tokens = 0;
6160 char *tokens[4] = {NULL, NULL, NULL, ""};
6161 struct xtp_despatch *dsp, *dsp_match = NULL;
6162 uint8_t req_class;
6163 int ret = FALSE;
6166 * tokens array meaning:
6167 * tokens[0] = class
6168 * tokens[1] = session key
6169 * tokens[2] = action
6170 * tokens[3] = optional argument
6173 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6175 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6176 goto clean;
6178 dup = g_strdup(url + strlen(XT_XTP_STR));
6180 /* split out the url */
6181 for ((p = strtok_r(dup, "/", &last)); p;
6182 (p = strtok_r(NULL, "/", &last))) {
6183 if (n_tokens < 4)
6184 tokens[n_tokens++] = p;
6187 /* should be atleast three fields 'class/seskey/command/arg' */
6188 if (n_tokens < 3)
6189 goto clean;
6191 dsp = xtp_despatches;
6192 req_class = atoi(tokens[0]);
6193 while (dsp->xtp_class) {
6194 if (dsp->xtp_class == req_class) {
6195 dsp_match = dsp;
6196 break;
6198 dsp++;
6201 /* did we find one atall? */
6202 if (dsp_match == NULL) {
6203 show_oops(t, "%s: no matching xtp despatch found", __func__);
6204 goto clean;
6207 /* check session key and call despatch function */
6208 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6209 ret = TRUE; /* all is well, this was a valid xtp request */
6210 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6213 clean:
6214 if (dup)
6215 g_free(dup);
6217 return (ret);
6222 void
6223 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6225 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6227 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6229 if (t == NULL) {
6230 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6231 return;
6234 if (uri == NULL) {
6235 show_oops(t, "activate_uri_entry_cb no uri");
6236 return;
6239 uri += strspn(uri, "\t ");
6241 /* if xxxt:// treat specially */
6242 if (parse_xtp_url(t, uri))
6243 return;
6245 /* otherwise continue to load page normally */
6246 load_uri(t, (gchar *)uri);
6247 focus_webview(t);
6250 void
6251 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6253 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6254 char *newuri = NULL;
6255 gchar *enc_search;
6257 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6259 if (t == NULL) {
6260 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6261 return;
6264 if (search_string == NULL) {
6265 show_oops(t, "no search_string");
6266 return;
6269 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6271 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6272 newuri = g_strdup_printf(search_string, enc_search);
6273 g_free(enc_search);
6275 marks_clear(t);
6276 webkit_web_view_load_uri(t->wv, newuri);
6277 focus_webview(t);
6279 if (newuri)
6280 g_free(newuri);
6283 void
6284 check_and_set_cookie(const gchar *uri, struct tab *t)
6286 struct domain *d = NULL;
6287 int es = 0;
6289 if (uri == NULL || t == NULL)
6290 return;
6292 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6293 es = 0;
6294 else
6295 es = 1;
6297 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6298 es ? "enable" : "disable", uri);
6300 g_object_set(G_OBJECT(t->settings),
6301 "enable-html5-local-storage", es, (char *)NULL);
6302 webkit_web_view_set_settings(t->wv, t->settings);
6305 void
6306 check_and_set_js(const gchar *uri, struct tab *t)
6308 struct domain *d = NULL;
6309 int es = 0;
6311 if (uri == NULL || t == NULL)
6312 return;
6314 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6315 es = 0;
6316 else
6317 es = 1;
6319 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6320 es ? "enable" : "disable", uri);
6322 g_object_set(G_OBJECT(t->settings),
6323 "enable-scripts", es, (char *)NULL);
6324 g_object_set(G_OBJECT(t->settings),
6325 "javascript-can-open-windows-automatically", es, (char *)NULL);
6326 webkit_web_view_set_settings(t->wv, t->settings);
6328 button_set_stockid(t->js_toggle,
6329 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6332 gboolean
6333 color_address_bar(gpointer p)
6335 GdkColor color;
6336 struct tab *tt, *t = p;
6337 gchar *col_str = XT_COLOR_YELLOW;
6339 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6341 /* make sure t still exists */
6342 if (t == NULL)
6343 goto done;
6344 TAILQ_FOREACH(tt, &tabs, entry)
6345 if (t == tt)
6346 break;
6347 if (t != tt)
6348 goto done;
6350 switch (load_compare_cert(t, NULL)) {
6351 case CERT_LOCAL:
6352 col_str = XT_COLOR_BLUE;
6353 break;
6354 case CERT_TRUSTED:
6355 col_str = XT_COLOR_GREEN;
6356 break;
6357 case CERT_UNTRUSTED:
6358 col_str = XT_COLOR_YELLOW;
6359 break;
6360 case CERT_BAD:
6361 col_str = XT_COLOR_RED;
6362 break;
6365 gdk_color_parse(col_str, &color);
6366 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6368 if (!strcmp(col_str, XT_COLOR_WHITE))
6369 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6370 else
6371 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6373 col_str = NULL;
6374 done:
6375 return (FALSE /* kill thread */);
6378 void
6379 show_ca_status(struct tab *t, const char *uri)
6381 GdkColor color;
6382 gchar *col_str = XT_COLOR_WHITE;
6384 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6385 ssl_strict_certs, ssl_ca_file, uri);
6387 if (t == NULL)
6388 return;
6390 if (uri == NULL)
6391 goto done;
6392 if (ssl_ca_file == NULL) {
6393 if (g_str_has_prefix(uri, "http://"))
6394 goto done;
6395 if (g_str_has_prefix(uri, "https://")) {
6396 col_str = XT_COLOR_RED;
6397 goto done;
6399 return;
6401 if (g_str_has_prefix(uri, "http://") ||
6402 !g_str_has_prefix(uri, "https://"))
6403 goto done;
6405 /* thread the coloring of the address bar */
6406 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6407 color_address_bar, t, NULL);
6408 return;
6410 done:
6411 if (col_str) {
6412 gdk_color_parse(col_str, &color);
6413 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6415 if (!strcmp(col_str, XT_COLOR_WHITE))
6416 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6417 else
6418 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6422 void
6423 free_favicon(struct tab *t)
6425 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6426 __func__, t->icon_download, t->icon_request);
6428 if (t->icon_request)
6429 g_object_unref(t->icon_request);
6430 if (t->icon_dest_uri)
6431 g_free(t->icon_dest_uri);
6433 t->icon_request = NULL;
6434 t->icon_dest_uri = NULL;
6437 void
6438 xt_icon_from_name(struct tab *t, gchar *name)
6440 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6441 GTK_ENTRY_ICON_PRIMARY, "text-html");
6442 if (show_url == 0)
6443 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6444 GTK_ENTRY_ICON_PRIMARY, "text-html");
6445 else
6446 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6447 GTK_ENTRY_ICON_PRIMARY, NULL);
6450 void
6451 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6453 GdkPixbuf *pb_scaled;
6455 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6456 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6457 GDK_INTERP_BILINEAR);
6458 else
6459 pb_scaled = pb;
6461 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6462 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6463 if (show_url == 0)
6464 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6465 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6466 else
6467 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6468 GTK_ENTRY_ICON_PRIMARY, NULL);
6470 if (pb_scaled != pb)
6471 g_object_unref(pb_scaled);
6474 void
6475 xt_icon_from_file(struct tab *t, char *file)
6477 GdkPixbuf *pb;
6479 if (g_str_has_prefix(file, "file://"))
6480 file += strlen("file://");
6482 pb = gdk_pixbuf_new_from_file(file, NULL);
6483 if (pb) {
6484 xt_icon_from_pixbuf(t, pb);
6485 g_object_unref(pb);
6486 } else
6487 xt_icon_from_name(t, "text-html");
6490 gboolean
6491 is_valid_icon(char *file)
6493 gboolean valid = 0;
6494 const char *mime_type;
6495 GFileInfo *fi;
6496 GFile *gf;
6498 gf = g_file_new_for_path(file);
6499 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6500 NULL, NULL);
6501 mime_type = g_file_info_get_content_type(fi);
6502 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6503 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6504 g_strcmp0(mime_type, "image/png") == 0 ||
6505 g_strcmp0(mime_type, "image/gif") == 0 ||
6506 g_strcmp0(mime_type, "application/octet-stream") == 0;
6507 g_object_unref(fi);
6508 g_object_unref(gf);
6510 return (valid);
6513 void
6514 set_favicon_from_file(struct tab *t, char *file)
6516 struct stat sb;
6518 if (t == NULL || file == NULL)
6519 return;
6521 if (g_str_has_prefix(file, "file://"))
6522 file += strlen("file://");
6523 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6525 if (!stat(file, &sb)) {
6526 if (sb.st_size == 0 || !is_valid_icon(file)) {
6527 /* corrupt icon so trash it */
6528 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6529 __func__, file);
6530 unlink(file);
6531 /* no need to set icon to default here */
6532 return;
6535 xt_icon_from_file(t, file);
6538 void
6539 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6540 WebKitWebView *wv)
6542 WebKitDownloadStatus status = webkit_download_get_status(download);
6543 struct tab *tt = NULL, *t = NULL;
6546 * find the webview instead of passing in the tab as it could have been
6547 * deleted from underneath us.
6549 TAILQ_FOREACH(tt, &tabs, entry) {
6550 if (tt->wv == wv) {
6551 t = tt;
6552 break;
6555 if (t == NULL)
6556 return;
6558 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6559 __func__, t->tab_id, status);
6561 switch (status) {
6562 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6563 /* -1 */
6564 t->icon_download = NULL;
6565 free_favicon(t);
6566 break;
6567 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6568 /* 0 */
6569 break;
6570 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6571 /* 1 */
6572 break;
6573 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6574 /* 2 */
6575 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6576 __func__, t->tab_id);
6577 t->icon_download = NULL;
6578 free_favicon(t);
6579 break;
6580 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6581 /* 3 */
6583 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6584 __func__, t->icon_dest_uri);
6585 set_favicon_from_file(t, t->icon_dest_uri);
6586 /* these will be freed post callback */
6587 t->icon_request = NULL;
6588 t->icon_download = NULL;
6589 break;
6590 default:
6591 break;
6595 void
6596 abort_favicon_download(struct tab *t)
6598 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6600 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6601 if (t->icon_download) {
6602 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6603 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6604 webkit_download_cancel(t->icon_download);
6605 t->icon_download = NULL;
6606 } else
6607 free_favicon(t);
6608 #endif
6610 xt_icon_from_name(t, "text-html");
6613 void
6614 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6616 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6618 if (uri == NULL || t == NULL)
6619 return;
6621 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6622 /* take icon from WebKitIconDatabase */
6623 GdkPixbuf *pb;
6625 pb = webkit_web_view_get_icon_pixbuf(wv);
6626 if (pb) {
6627 xt_icon_from_pixbuf(t, pb);
6628 g_object_unref(pb);
6629 } else
6630 xt_icon_from_name(t, "text-html");
6631 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6632 /* download icon to cache dir */
6633 gchar *name_hash, file[PATH_MAX];
6634 struct stat sb;
6636 if (t->icon_request) {
6637 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6638 return;
6641 /* check to see if we got the icon in cache */
6642 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6643 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6644 g_free(name_hash);
6646 if (!stat(file, &sb)) {
6647 if (sb.st_size > 0) {
6648 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6649 __func__, file);
6650 set_favicon_from_file(t, file);
6651 return;
6654 /* corrupt icon so trash it */
6655 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6656 __func__, file);
6657 unlink(file);
6660 /* create download for icon */
6661 t->icon_request = webkit_network_request_new(uri);
6662 if (t->icon_request == NULL) {
6663 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6664 __func__, uri);
6665 return;
6668 t->icon_download = webkit_download_new(t->icon_request);
6669 if (t->icon_download == NULL)
6670 return;
6672 /* we have to free icon_dest_uri later */
6673 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6674 webkit_download_set_destination_uri(t->icon_download,
6675 t->icon_dest_uri);
6677 if (webkit_download_get_status(t->icon_download) ==
6678 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6679 g_object_unref(t->icon_request);
6680 g_free(t->icon_dest_uri);
6681 t->icon_request = NULL;
6682 t->icon_dest_uri = NULL;
6683 return;
6686 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6687 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6689 webkit_download_start(t->icon_download);
6690 #endif
6693 void
6694 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6696 const gchar *uri = NULL, *title = NULL;
6697 struct history *h, find;
6698 struct karg a;
6699 GdkColor color;
6701 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6702 webkit_web_view_get_load_status(wview),
6703 get_uri(t) ? get_uri(t) : "NOTHING");
6705 if (t == NULL) {
6706 show_oops(NULL, "notify_load_status_cb invalid parameters");
6707 return;
6710 switch (webkit_web_view_get_load_status(wview)) {
6711 case WEBKIT_LOAD_PROVISIONAL:
6712 /* 0 */
6713 abort_favicon_download(t);
6714 #if GTK_CHECK_VERSION(2, 20, 0)
6715 gtk_widget_show(t->spinner);
6716 gtk_spinner_start(GTK_SPINNER(t->spinner));
6717 #endif
6718 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6720 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6722 /* assume we are a new address */
6723 gdk_color_parse("white", &color);
6724 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6725 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6727 /* take focus if we are visible */
6728 focus_webview(t);
6729 t->focus_wv = 1;
6731 break;
6733 case WEBKIT_LOAD_COMMITTED:
6734 /* 1 */
6735 uri = get_uri(t);
6736 if (uri == NULL)
6737 return;
6738 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6740 if (t->status) {
6741 g_free(t->status);
6742 t->status = NULL;
6744 set_status(t, (char *)uri, XT_STATUS_LOADING);
6746 /* check if js white listing is enabled */
6747 if (enable_cookie_whitelist)
6748 check_and_set_cookie(uri, t);
6749 if (enable_js_whitelist)
6750 check_and_set_js(uri, t);
6752 if (t->styled)
6753 apply_style(t);
6756 /* we know enough to autosave the session */
6757 if (session_autosave) {
6758 a.s = NULL;
6759 save_tabs(t, &a);
6762 show_ca_status(t, uri);
6763 break;
6765 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6766 /* 3 */
6767 break;
6769 case WEBKIT_LOAD_FINISHED:
6770 /* 2 */
6771 uri = get_uri(t);
6772 if (uri == NULL)
6773 return;
6775 if (!strncmp(uri, "http://", strlen("http://")) ||
6776 !strncmp(uri, "https://", strlen("https://")) ||
6777 !strncmp(uri, "file://", strlen("file://"))) {
6778 find.uri = uri;
6779 h = RB_FIND(history_list, &hl, &find);
6780 if (!h) {
6781 title = get_title(t, FALSE);
6782 h = g_malloc(sizeof *h);
6783 h->uri = g_strdup(uri);
6784 h->title = g_strdup(title);
6785 RB_INSERT(history_list, &hl, h);
6786 completion_add_uri(h->uri);
6787 update_history_tabs(NULL);
6791 set_status(t, (char *)uri, XT_STATUS_URI);
6792 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6793 case WEBKIT_LOAD_FAILED:
6794 /* 4 */
6795 #endif
6796 #if GTK_CHECK_VERSION(2, 20, 0)
6797 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6798 gtk_widget_hide(t->spinner);
6799 #endif
6800 default:
6801 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6802 break;
6805 if (t->item)
6806 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6807 else
6808 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6809 can_go_back_for_real(t));
6811 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6812 can_go_forward_for_real(t));
6815 #if 0
6816 gboolean
6817 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6818 gchar *uri, gpointer web_error,struct tab *t)
6821 * XXX this function is wrong
6822 * it overwrites perfectly good urls with garbage on load errors
6823 * those happen often when popups fail to resolve dns
6825 if (t->tmp_uri)
6826 g_free(t->tmp_uri);
6827 t->tmp_uri = g_strdup(uri);
6828 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6829 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6830 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6831 set_status(t, uri, XT_STATUS_NOTHING);
6833 return (FALSE);
6835 #endif
6837 void
6838 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6840 const gchar *title = NULL, *win_title = NULL;
6842 title = get_title(t, FALSE);
6843 win_title = get_title(t, TRUE);
6844 gtk_label_set_text(GTK_LABEL(t->label), title);
6845 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6846 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6847 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6850 void
6851 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6853 run_script(t, JS_HINTING);
6856 void
6857 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6859 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6860 progress == 100 ? 0 : (double)progress / 100);
6861 if (show_url == 0) {
6862 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6863 progress == 100 ? 0 : (double)progress / 100);
6866 update_statusbar_position(NULL, NULL);
6870 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6871 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6872 WebKitWebPolicyDecision *pd, struct tab *t)
6874 char *uri;
6875 WebKitWebNavigationReason reason;
6876 struct domain *d = NULL;
6878 if (t == NULL) {
6879 show_oops(NULL, "webview_npd_cb invalid parameters");
6880 return (FALSE);
6883 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6884 t->ctrl_click,
6885 webkit_network_request_get_uri(request));
6887 uri = (char *)webkit_network_request_get_uri(request);
6889 /* if this is an xtp url, we don't load anything else */
6890 if (parse_xtp_url(t, uri))
6891 return (TRUE);
6893 if (t->ctrl_click) {
6894 t->ctrl_click = 0;
6895 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6896 webkit_web_policy_decision_ignore(pd);
6897 return (TRUE); /* we made the decission */
6901 * This is a little hairy but it comes down to this:
6902 * when we run in whitelist mode we have to assist the browser in
6903 * opening the URL that it would have opened in a new tab.
6905 reason = webkit_web_navigation_action_get_reason(na);
6906 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6907 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6908 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6909 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6910 load_uri(t, uri);
6911 webkit_web_policy_decision_use(pd);
6912 return (TRUE); /* we made the decision */
6915 return (FALSE);
6918 WebKitWebView *
6919 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6921 struct tab *tt;
6922 struct domain *d = NULL;
6923 const gchar *uri;
6924 WebKitWebView *webview = NULL;
6926 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6927 webkit_web_view_get_uri(wv));
6929 if (tabless) {
6930 /* open in current tab */
6931 webview = t->wv;
6932 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6933 uri = webkit_web_view_get_uri(wv);
6934 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6935 return (NULL);
6937 tt = create_new_tab(NULL, NULL, 1, -1);
6938 webview = tt->wv;
6939 } else if (enable_scripts == 1) {
6940 tt = create_new_tab(NULL, NULL, 1, -1);
6941 webview = tt->wv;
6944 return (webview);
6947 gboolean
6948 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6950 const gchar *uri;
6951 struct domain *d = NULL;
6953 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6955 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6956 uri = webkit_web_view_get_uri(wv);
6957 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6958 return (FALSE);
6960 delete_tab(t);
6961 } else if (enable_scripts == 1)
6962 delete_tab(t);
6964 return (TRUE);
6968 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6970 /* we can not eat the event without throwing gtk off so defer it */
6972 /* catch middle click */
6973 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6974 t->ctrl_click = 1;
6975 goto done;
6978 /* catch ctrl click */
6979 if (e->type == GDK_BUTTON_RELEASE &&
6980 CLEAN(e->state) == GDK_CONTROL_MASK)
6981 t->ctrl_click = 1;
6982 else
6983 t->ctrl_click = 0;
6984 done:
6985 return (XT_CB_PASSTHROUGH);
6989 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6991 struct mime_type *m;
6993 m = find_mime_type(mime_type);
6994 if (m == NULL)
6995 return (1);
6996 if (m->mt_download)
6997 return (1);
6999 switch (fork()) {
7000 case -1:
7001 show_oops(t, "can't fork mime handler");
7002 return (1);
7003 /* NOTREACHED */
7004 case 0:
7005 break;
7006 default:
7007 return (0);
7010 /* child */
7011 execlp(m->mt_action, m->mt_action,
7012 webkit_network_request_get_uri(request), (void *)NULL);
7014 _exit(0);
7016 /* NOTREACHED */
7017 return (0);
7020 const gchar *
7021 get_mime_type(char *file)
7023 const char *mime_type;
7024 GFileInfo *fi;
7025 GFile *gf;
7027 if (g_str_has_prefix(file, "file://"))
7028 file += strlen("file://");
7030 gf = g_file_new_for_path(file);
7031 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7032 NULL, NULL);
7033 mime_type = g_file_info_get_content_type(fi);
7034 g_object_unref(fi);
7035 g_object_unref(gf);
7037 return (mime_type);
7041 run_download_mimehandler(char *mime_type, char *file)
7043 struct mime_type *m;
7045 m = find_mime_type(mime_type);
7046 if (m == NULL)
7047 return (1);
7049 switch (fork()) {
7050 case -1:
7051 show_oops(NULL, "can't fork download mime handler");
7052 return (1);
7053 /* NOTREACHED */
7054 case 0:
7055 break;
7056 default:
7057 return (0);
7060 /* child */
7061 if (g_str_has_prefix(file, "file://"))
7062 file += strlen("file://");
7063 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7065 _exit(0);
7067 /* NOTREACHED */
7068 return (0);
7071 void
7072 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7073 WebKitWebView *wv)
7075 WebKitDownloadStatus status;
7076 const gchar *file = NULL, *mime = NULL;
7078 if (download == NULL)
7079 return;
7080 status = webkit_download_get_status(download);
7081 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7082 return;
7084 file = webkit_download_get_destination_uri(download);
7085 if (file == NULL)
7086 return;
7087 mime = get_mime_type((char *)file);
7088 if (mime == NULL)
7089 return;
7091 run_download_mimehandler((char *)mime, (char *)file);
7095 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7096 WebKitNetworkRequest *request, char *mime_type,
7097 WebKitWebPolicyDecision *decision, struct tab *t)
7099 if (t == NULL) {
7100 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7101 return (FALSE);
7104 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7105 t->tab_id, mime_type);
7107 if (run_mimehandler(t, mime_type, request) == 0) {
7108 webkit_web_policy_decision_ignore(decision);
7109 focus_webview(t);
7110 return (TRUE);
7113 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7114 webkit_web_policy_decision_download(decision);
7115 return (TRUE);
7118 return (FALSE);
7122 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7123 struct tab *t)
7125 struct stat sb;
7126 const gchar *suggested_name;
7127 gchar *filename = NULL;
7128 char *uri = NULL;
7129 struct download *download_entry;
7130 int i, ret = TRUE;
7132 if (wk_download == NULL || t == NULL) {
7133 show_oops(NULL, "%s invalid parameters", __func__);
7134 return (FALSE);
7137 suggested_name = webkit_download_get_suggested_filename(wk_download);
7138 if (suggested_name == NULL)
7139 return (FALSE); /* abort download */
7141 i = 0;
7142 do {
7143 if (filename) {
7144 g_free(filename);
7145 filename = NULL;
7147 if (i) {
7148 g_free(uri);
7149 uri = NULL;
7150 filename = g_strdup_printf("%d%s", i, suggested_name);
7152 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7153 filename : suggested_name);
7154 i++;
7155 } while (!stat(uri + strlen("file://"), &sb));
7157 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7158 "local %s\n", __func__, t->tab_id, filename, uri);
7160 webkit_download_set_destination_uri(wk_download, uri);
7162 if (webkit_download_get_status(wk_download) ==
7163 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7164 show_oops(t, "%s: download failed to start", __func__);
7165 ret = FALSE;
7166 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7167 } else {
7168 /* connect "download first" mime handler */
7169 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7170 G_CALLBACK(download_status_changed_cb), NULL);
7172 download_entry = g_malloc(sizeof(struct download));
7173 download_entry->download = wk_download;
7174 download_entry->tab = t;
7175 download_entry->id = next_download_id++;
7176 RB_INSERT(download_list, &downloads, download_entry);
7177 /* get from history */
7178 g_object_ref(wk_download);
7179 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7180 show_oops(t, "Download of '%s' started...",
7181 basename((char *)webkit_download_get_destination_uri(wk_download)));
7184 if (uri)
7185 g_free(uri);
7187 if (filename)
7188 g_free(filename);
7190 /* sync other download manager tabs */
7191 update_download_tabs(NULL);
7194 * NOTE: never redirect/render the current tab before this
7195 * function returns. This will cause the download to never start.
7197 return (ret); /* start download */
7200 void
7201 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7203 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7205 if (t == NULL) {
7206 show_oops(NULL, "webview_hover_cb");
7207 return;
7210 if (uri)
7211 set_status(t, uri, XT_STATUS_LINK);
7212 else {
7213 if (t->status)
7214 set_status(t, t->status, XT_STATUS_NOTHING);
7219 mark(struct tab *t, struct karg *arg)
7221 char mark;
7222 int index;
7224 mark = arg->s[1];
7225 if ((index = marktoindex(mark)) == -1)
7226 return -1;
7228 if (arg->i == XT_MARK_SET)
7229 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7230 else if (arg->i == XT_MARK_GOTO) {
7231 if (t->mark[index] == XT_INVALID_MARK) {
7232 show_oops(t, "mark '%c' does not exist", mark);
7233 return -1;
7235 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7236 something changes the document size */
7237 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7240 return 0;
7243 void
7244 marks_clear(struct tab *t)
7246 int i;
7248 for (i = 0; i < LENGTH(t->mark); i++)
7249 t->mark[i] = XT_INVALID_MARK;
7253 qmarks_load(void)
7255 char file[PATH_MAX];
7256 char *line = NULL, *p;
7257 int index, i;
7258 FILE *f;
7259 size_t linelen;
7261 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7262 if ((f = fopen(file, "r+")) == NULL) {
7263 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7264 return (1);
7267 for (i = 1; ; i++) {
7268 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7269 if (feof(f) || ferror(f))
7270 break;
7271 if (strlen(line) == 0 || line[0] == '#') {
7272 free(line);
7273 line = NULL;
7274 continue;
7277 p = strtok(line, " \t");
7279 if (p == NULL || strlen(p) != 1 ||
7280 (index = marktoindex(*p)) == -1) {
7281 warnx("corrupt quickmarks file, line %d", i);
7282 break;
7285 p = strtok(NULL, " \t");
7286 if (qmarks[index] != NULL)
7287 g_free(qmarks[index]);
7288 qmarks[index] = g_strdup(p);
7291 fclose(f);
7293 return (0);
7297 qmarks_save(void)
7299 char file[PATH_MAX];
7300 int i;
7301 FILE *f;
7303 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7304 if ((f = fopen(file, "r+")) == NULL) {
7305 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7306 return (1);
7309 for (i = 0; i < XT_NOMARKS; i++)
7310 if (qmarks[i] != NULL)
7311 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7313 fclose(f);
7315 return (0);
7319 qmark(struct tab *t, struct karg *arg)
7321 char mark;
7322 int index;
7324 mark = arg->s[strlen(arg->s)-1];
7325 index = marktoindex(mark);
7326 if (index == -1)
7327 return (-1);
7329 switch (arg->i) {
7330 case XT_QMARK_SET:
7331 if (qmarks[index] != NULL)
7332 g_free(qmarks[index]);
7334 qmarks_load(); /* sync if multiple instances */
7335 qmarks[index] = g_strdup(get_uri(t));
7336 qmarks_save();
7337 break;
7338 case XT_QMARK_OPEN:
7339 if (qmarks[index] != NULL)
7340 load_uri(t, qmarks[index]);
7341 else {
7342 show_oops(t, "quickmark \"%c\" does not exist",
7343 mark);
7344 return (-1);
7346 break;
7347 case XT_QMARK_TAB:
7348 if (qmarks[index] != NULL)
7349 create_new_tab(qmarks[index], NULL, 1, -1);
7350 else {
7351 show_oops(t, "quickmark \"%c\" does not exist",
7352 mark);
7353 return (-1);
7355 break;
7358 return (0);
7362 go_up(struct tab *t, struct karg *args)
7364 int levels;
7365 char *uri;
7366 char *tmp;
7368 levels = atoi(args->s);
7369 if (levels == 0)
7370 levels = 1;
7372 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7373 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7374 return 1;
7375 tmp += strlen(XT_PROTO_DELIM);
7377 /* if an uri starts with a slash, leave it alone (for file:///) */
7378 if (tmp[0] == '/')
7379 tmp++;
7381 while (levels--) {
7382 char *p;
7384 p = strrchr(tmp, '/');
7385 if (p != NULL)
7386 *p = '\0';
7387 else
7388 break;
7391 load_uri(t, uri);
7392 g_free(uri);
7394 return (0);
7398 gototab(struct tab *t, struct karg *args)
7400 int tab;
7401 struct karg arg = {0, NULL, -1};
7403 tab = atoi(args->s);
7405 arg.i = XT_TAB_NEXT;
7406 arg.precount = tab;
7408 movetab(t, &arg);
7410 return (0);
7414 zoom_amount(struct tab *t, struct karg *arg)
7416 struct karg narg = {0, NULL, -1};
7418 narg.i = atoi(arg->s);
7419 resizetab(t, &narg);
7421 return 0;
7425 flip_colon(struct tab *t, struct karg *arg)
7427 struct karg narg = {0, NULL, -1};
7428 char *p;
7430 if (t == NULL || arg == NULL)
7431 return (1);
7433 p = strstr(arg->s, ":");
7434 if (p == NULL)
7435 return (1);
7436 *p = '\0';
7438 narg.i = ':';
7439 narg.s = arg->s;
7440 command(t, &narg);
7442 return (0);
7445 /* buffer commands receive the regex that triggered them in arg.s */
7446 char bcmd[XT_BUFCMD_SZ];
7447 struct buffercmd {
7448 char *regex;
7449 int precount;
7450 #define XT_PRE_NO (0)
7451 #define XT_PRE_YES (1)
7452 #define XT_PRE_MAYBE (2)
7453 char *cmd;
7454 int (*func)(struct tab *, struct karg *);
7455 int arg;
7456 regex_t cregex;
7457 } buffercmds[] = {
7458 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7459 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7460 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7461 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7462 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7463 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7464 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7465 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7466 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7467 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7468 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7469 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7470 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7471 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7472 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7473 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7474 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7475 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7478 void
7479 buffercmd_init(void)
7481 int i;
7483 for (i = 0; i < LENGTH(buffercmds); i++)
7484 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7485 REG_EXTENDED | REG_NOSUB))
7486 startpage_add("invalid buffercmd regex %s",
7487 buffercmds[i].regex);
7490 void
7491 buffercmd_abort(struct tab *t)
7493 int i;
7495 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7496 for (i = 0; i < LENGTH(bcmd); i++)
7497 bcmd[i] = '\0';
7499 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7500 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7503 void
7504 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7506 struct karg arg = {0, NULL, -1};
7508 arg.i = cmd->arg;
7509 arg.s = g_strdup(bcmd);
7511 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7512 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7513 cmd->func(t, &arg);
7515 if (arg.s)
7516 g_free(arg.s);
7518 buffercmd_abort(t);
7521 gboolean
7522 buffercmd_addkey(struct tab *t, guint keyval)
7524 int i, c, match ;
7525 char s[XT_BUFCMD_SZ];
7527 if (keyval == GDK_Escape) {
7528 buffercmd_abort(t);
7529 return (XT_CB_HANDLED);
7532 /* key with modifier or non-ascii character */
7533 if (!isascii(keyval))
7534 return (XT_CB_PASSTHROUGH);
7536 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7537 "to buffer \"%s\"\n", keyval, bcmd);
7539 for (i = 0; i < LENGTH(bcmd); i++)
7540 if (bcmd[i] == '\0') {
7541 bcmd[i] = keyval;
7542 break;
7545 /* buffer full, ignore input */
7546 if (i >= LENGTH(bcmd) -1) {
7547 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7548 buffercmd_abort(t);
7549 return (XT_CB_HANDLED);
7552 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7554 /* find exact match */
7555 for (i = 0; i < LENGTH(buffercmds); i++)
7556 if (regexec(&buffercmds[i].cregex, bcmd,
7557 (size_t) 0, NULL, 0) == 0) {
7558 buffercmd_execute(t, &buffercmds[i]);
7559 goto done;
7562 /* find non exact matches to see if we need to abort ot not */
7563 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7564 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7565 c = -1;
7566 s[0] = '\0';
7567 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7568 if (isdigit(bcmd[0])) {
7569 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7570 continue;
7571 } else {
7572 c = 0;
7573 if (sscanf(bcmd, "%s", s) == 0)
7574 continue;
7576 } else if (buffercmds[i].precount == XT_PRE_YES) {
7577 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7578 continue;
7579 } else {
7580 if (sscanf(bcmd, "%s", s) == 0)
7581 continue;
7583 if (c == -1 && buffercmds[i].precount)
7584 continue;
7585 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7586 match++;
7588 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7589 i, match, buffercmds[i].cmd, c, s);
7591 if (match == 0) {
7592 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7593 buffercmd_abort(t);
7596 done:
7597 return (XT_CB_HANDLED);
7600 gboolean
7601 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7603 struct key_binding *k;
7605 /* handle keybindings if buffercmd is empty.
7606 if not empty, allow commands like C-n */
7607 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7608 TAILQ_FOREACH(k, &kbl, entry)
7609 if (e->keyval == k->key
7610 && (entry ? k->use_in_entry : 1)) {
7611 if (k->mask == 0) {
7612 if ((e->state & (CTRL | MOD1)) == 0)
7613 return (cmd_execute(t, k->cmd));
7614 } else if ((e->state & k->mask) == k->mask) {
7615 return (cmd_execute(t, k->cmd));
7619 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7620 return buffercmd_addkey(t, e->keyval);
7622 return (XT_CB_PASSTHROUGH);
7626 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7628 char s[2], buf[128];
7629 const char *errstr = NULL;
7631 /* don't use w directly; use t->whatever instead */
7633 if (t == NULL) {
7634 show_oops(NULL, "wv_keypress_after_cb");
7635 return (XT_CB_PASSTHROUGH);
7638 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7639 e->keyval, e->state, t);
7641 if (t->hints_on) {
7642 /* ESC */
7643 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7644 disable_hints(t);
7645 return (XT_CB_HANDLED);
7648 /* RETURN */
7649 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7650 if (errstr) {
7651 /* we have a string */
7652 } else {
7653 /* we have a number */
7654 snprintf(buf, sizeof buf,
7655 "vimprobable_fire(%s)", t->hint_num);
7656 run_script(t, buf);
7658 disable_hints(t);
7661 /* BACKSPACE */
7662 /* XXX unfuck this */
7663 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7664 if (t->hint_mode == XT_HINT_NUMERICAL) {
7665 /* last input was numerical */
7666 int l;
7667 l = strlen(t->hint_num);
7668 if (l > 0) {
7669 l--;
7670 if (l == 0) {
7671 disable_hints(t);
7672 enable_hints(t);
7673 } else {
7674 t->hint_num[l] = '\0';
7675 goto num;
7678 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7679 /* last input was alphanumerical */
7680 int l;
7681 l = strlen(t->hint_buf);
7682 if (l > 0) {
7683 l--;
7684 if (l == 0) {
7685 disable_hints(t);
7686 enable_hints(t);
7687 } else {
7688 t->hint_buf[l] = '\0';
7689 goto anum;
7692 } else {
7693 /* bogus */
7694 disable_hints(t);
7698 /* numerical input */
7699 if (CLEAN(e->state) == 0 &&
7700 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7701 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7702 snprintf(s, sizeof s, "%c", e->keyval);
7703 strlcat(t->hint_num, s, sizeof t->hint_num);
7704 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7705 t->hint_num);
7706 num:
7707 if (errstr) {
7708 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7709 "invalid link number\n");
7710 disable_hints(t);
7711 } else {
7712 snprintf(buf, sizeof buf,
7713 "vimprobable_update_hints(%s)",
7714 t->hint_num);
7715 t->hint_mode = XT_HINT_NUMERICAL;
7716 run_script(t, buf);
7719 /* empty the counter buffer */
7720 bzero(t->hint_buf, sizeof t->hint_buf);
7721 return (XT_CB_HANDLED);
7724 /* alphanumerical input */
7725 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7726 e->keyval <= GDK_z) ||
7727 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7728 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7729 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7730 e->keyval <= GDK_9) ||
7731 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7732 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7733 snprintf(s, sizeof s, "%c", e->keyval);
7734 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7735 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7736 " %s\n", t->hint_buf);
7737 anum:
7738 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7739 run_script(t, buf);
7741 snprintf(buf, sizeof buf,
7742 "vimprobable_show_hints('%s')", t->hint_buf);
7743 t->hint_mode = XT_HINT_ALPHANUM;
7744 run_script(t, buf);
7746 /* empty the counter buffer */
7747 bzero(t->hint_num, sizeof t->hint_num);
7748 return (XT_CB_HANDLED);
7751 return (XT_CB_HANDLED);
7752 } else {
7753 /* prefix input*/
7754 snprintf(s, sizeof s, "%c", e->keyval);
7755 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7756 cmd_prefix = 10 * cmd_prefix + atoi(s);
7759 return (handle_keypress(t, e, 0));
7763 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7765 hide_oops(t);
7767 /* Hide buffers, if they are visible, with escape. */
7768 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7769 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7770 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7771 hide_buffers(t);
7772 return (XT_CB_HANDLED);
7775 return (XT_CB_PASSTHROUGH);
7778 gboolean
7779 search_continue(struct tab *t)
7781 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7782 gboolean rv = FALSE;
7784 if (c[0] == ':')
7785 goto done;
7786 if (strlen(c) == 1) {
7787 webkit_web_view_unmark_text_matches(t->wv);
7788 goto done;
7791 if (c[0] == '/')
7792 t->search_forward = TRUE;
7793 else if (c[0] == '?')
7794 t->search_forward = FALSE;
7795 else
7796 goto done;
7798 rv = TRUE;
7799 done:
7800 return (rv);
7803 gboolean
7804 search_cb(struct tab *t)
7806 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7807 GdkColor color;
7809 if (search_continue(t) == FALSE)
7810 goto done;
7812 /* search */
7813 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7814 TRUE) == FALSE) {
7815 /* not found, mark red */
7816 gdk_color_parse(XT_COLOR_RED, &color);
7817 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7818 /* unmark and remove selection */
7819 webkit_web_view_unmark_text_matches(t->wv);
7820 /* my kingdom for a way to unselect text in webview */
7821 } else {
7822 /* found, highlight all */
7823 webkit_web_view_unmark_text_matches(t->wv);
7824 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7825 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7826 gdk_color_parse(XT_COLOR_WHITE, &color);
7827 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7829 done:
7830 t->search_id = 0;
7831 return (FALSE);
7835 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7837 const gchar *c = gtk_entry_get_text(w);
7839 if (t == NULL) {
7840 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7841 return (XT_CB_PASSTHROUGH);
7844 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7845 e->keyval, e->state, t);
7847 if (search_continue(t) == FALSE)
7848 goto done;
7850 /* if search length is > 4 then no longer play timeout games */
7851 if (strlen(c) > 4) {
7852 if (t->search_id) {
7853 g_source_remove(t->search_id);
7854 t->search_id = 0;
7856 search_cb(t);
7857 goto done;
7860 /* reestablish a new timer if the user types fast */
7861 if (t->search_id)
7862 g_source_remove(t->search_id);
7863 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7865 done:
7866 return (XT_CB_PASSTHROUGH);
7869 gboolean
7870 match_uri(const gchar *uri, const gchar *key) {
7871 gchar *voffset;
7872 size_t len;
7873 gboolean match = FALSE;
7875 len = strlen(key);
7877 if (!strncmp(key, uri, len))
7878 match = TRUE;
7879 else {
7880 voffset = strstr(uri, "/") + 2;
7881 if (!strncmp(key, voffset, len))
7882 match = TRUE;
7883 else if (g_str_has_prefix(voffset, "www.")) {
7884 voffset = voffset + strlen("www.");
7885 if (!strncmp(key, voffset, len))
7886 match = TRUE;
7890 return (match);
7893 gboolean
7894 match_session(const gchar *name, const gchar *key) {
7895 char *sub;
7897 sub = strcasestr(name, key);
7899 return sub == name;
7902 void
7903 cmd_getlist(int id, char *key)
7905 int i, dep, c = 0;
7906 struct history *h;
7907 struct session *s;
7909 if (id >= 0) {
7910 if (cmds[id].type & XT_URLARG) {
7911 RB_FOREACH_REVERSE(h, history_list, &hl)
7912 if (match_uri(h->uri, key)) {
7913 cmd_status.list[c] = (char *)h->uri;
7914 if (++c > 255)
7915 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;
7926 cmd_status.len = c;
7927 return;
7928 } else if (cmds[id].type & XT_SETARG) {
7929 for (i = 0; i < LENGTH(rs); i++)
7930 if(!strncmp(key, rs[i].name, strlen(key)))
7931 cmd_status.list[c++] = rs[i].name;
7932 cmd_status.len = c;
7933 return;
7937 dep = (id == -1) ? 0 : cmds[id].level + 1;
7939 for (i = id + 1; i < LENGTH(cmds); i++) {
7940 if (cmds[i].level < dep)
7941 break;
7942 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7943 strlen(key)))
7944 cmd_status.list[c++] = cmds[i].cmd;
7948 cmd_status.len = c;
7951 char *
7952 cmd_getnext(int dir)
7954 cmd_status.index += dir;
7956 if (cmd_status.index < 0)
7957 cmd_status.index = cmd_status.len - 1;
7958 else if (cmd_status.index >= cmd_status.len)
7959 cmd_status.index = 0;
7961 return cmd_status.list[cmd_status.index];
7965 cmd_tokenize(char *s, char *tokens[])
7967 int i = 0;
7968 char *tok, *last;
7969 size_t len = strlen(s);
7970 bool blank;
7972 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7973 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7974 tok = strtok_r(NULL, " ", &last), i++)
7975 tokens[i] = tok;
7977 if (blank && i < 3)
7978 tokens[i++] = "";
7980 return (i);
7983 void
7984 cmd_complete(struct tab *t, char *str, int dir)
7986 GtkEntry *w = GTK_ENTRY(t->cmd);
7987 int i, j, levels, c = 0, dep = 0, parent = -1;
7988 int matchcount = 0;
7989 char *tok, *match, *s = g_strdup(str);
7990 char *tokens[3];
7991 char res[XT_MAX_URL_LENGTH + 32] = ":";
7992 char *sc = s;
7994 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7996 /* copy prefix*/
7997 for (i = 0; isdigit(s[i]); i++)
7998 res[i + 1] = s[i];
8000 for (; isspace(s[i]); i++)
8001 res[i + 1] = s[i];
8003 s += i;
8005 levels = cmd_tokenize(s, tokens);
8007 for (i = 0; i < levels - 1; i++) {
8008 tok = tokens[i];
8009 matchcount = 0;
8010 for (j = c; j < LENGTH(cmds); j++) {
8011 if (cmds[j].level < dep)
8012 break;
8013 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8014 strlen(tok))) {
8015 matchcount++;
8016 c = j + 1;
8017 if (strlen(tok) == strlen(cmds[j].cmd)) {
8018 matchcount = 1;
8019 break;
8024 if (matchcount == 1) {
8025 strlcat(res, tok, sizeof res);
8026 strlcat(res, " ", sizeof res);
8027 dep++;
8028 } else {
8029 g_free(sc);
8030 return;
8033 parent = c - 1;
8036 if (cmd_status.index == -1)
8037 cmd_getlist(parent, tokens[i]);
8039 if (cmd_status.len > 0) {
8040 match = cmd_getnext(dir);
8041 strlcat(res, match, sizeof res);
8042 gtk_entry_set_text(w, res);
8043 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8046 g_free(sc);
8049 gboolean
8050 cmd_execute(struct tab *t, char *str)
8052 struct cmd *cmd = NULL;
8053 char *tok, *last, *s = g_strdup(str), *sc;
8054 char prefixstr[4];
8055 int j, len, c = 0, dep = 0, matchcount = 0;
8056 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8057 struct karg arg = {0, NULL, -1};
8059 sc = s;
8061 /* copy prefix*/
8062 for (j = 0; j<3 && isdigit(s[j]); j++)
8063 prefixstr[j]=s[j];
8065 prefixstr[j]='\0';
8067 s += j;
8068 while (isspace(s[0]))
8069 s++;
8071 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8072 prefix = atoi(prefixstr);
8073 else
8074 s = sc;
8076 for (tok = strtok_r(s, " ", &last); tok;
8077 tok = strtok_r(NULL, " ", &last)) {
8078 matchcount = 0;
8079 for (j = c; j < LENGTH(cmds); j++) {
8080 if (cmds[j].level < dep)
8081 break;
8082 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8083 strlen(tok);
8084 if (cmds[j].level == dep &&
8085 !strncmp(tok, cmds[j].cmd, len)) {
8086 matchcount++;
8087 c = j + 1;
8088 cmd = &cmds[j];
8089 if (len == strlen(cmds[j].cmd)) {
8090 matchcount = 1;
8091 break;
8095 if (matchcount == 1) {
8096 if (cmd->type > 0)
8097 goto execute_cmd;
8098 dep++;
8099 } else {
8100 show_oops(t, "Invalid command: %s", str);
8101 goto done;
8104 execute_cmd:
8105 arg.i = cmd->arg;
8107 if (prefix != -1)
8108 arg.precount = prefix;
8109 else if (cmd_prefix > 0)
8110 arg.precount = cmd_prefix;
8112 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8113 show_oops(t, "No prefix allowed: %s", str);
8114 goto done;
8116 if (cmd->type > 1)
8117 arg.s = last ? g_strdup(last) : g_strdup("");
8118 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8119 arg.precount = atoi(arg.s);
8120 if (arg.precount <= 0) {
8121 if (arg.s[0] == '0')
8122 show_oops(t, "Zero count");
8123 else
8124 show_oops(t, "Trailing characters");
8125 goto done;
8129 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8130 __func__, arg.precount, arg.s);
8132 cmd->func(t, &arg);
8134 rv = XT_CB_HANDLED;
8135 done:
8136 if (j > 0)
8137 cmd_prefix = 0;
8138 g_free(sc);
8139 if (arg.s)
8140 g_free(arg.s);
8142 return (rv);
8146 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8148 if (t == NULL) {
8149 show_oops(NULL, "entry_key_cb invalid parameters");
8150 return (XT_CB_PASSTHROUGH);
8153 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8154 e->keyval, e->state, t);
8156 hide_oops(t);
8158 if (e->keyval == GDK_Escape) {
8159 /* don't use focus_webview(t) because we want to type :cmds */
8160 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8163 return (handle_keypress(t, e, 1));
8166 struct command_entry *
8167 history_prev(struct command_list *l, struct command_entry *at)
8169 if (at == NULL)
8170 at = TAILQ_LAST(l, command_list);
8171 else {
8172 at = TAILQ_PREV(at, command_list, entry);
8173 if (at == NULL)
8174 at = TAILQ_LAST(l, command_list);
8177 return (at);
8180 struct command_entry *
8181 history_next(struct command_list *l, struct command_entry *at)
8183 if (at == NULL)
8184 at = TAILQ_FIRST(l);
8185 else {
8186 at = TAILQ_NEXT(at, entry);
8187 if (at == NULL)
8188 at = TAILQ_FIRST(l);
8191 return (at);
8195 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8197 int rv = XT_CB_HANDLED;
8198 const gchar *c = gtk_entry_get_text(w);
8200 if (t == NULL) {
8201 show_oops(NULL, "cmd_keypress_cb parameters");
8202 return (XT_CB_PASSTHROUGH);
8205 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8206 e->keyval, e->state, t);
8208 /* sanity */
8209 if (c == NULL)
8210 e->keyval = GDK_Escape;
8211 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8212 e->keyval = GDK_Escape;
8214 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8215 e->keyval != GDK_ISO_Left_Tab)
8216 cmd_status.index = -1;
8218 switch (e->keyval) {
8219 case GDK_Tab:
8220 if (c[0] == ':')
8221 cmd_complete(t, (char *)&c[1], 1);
8222 goto done;
8223 case GDK_ISO_Left_Tab:
8224 if (c[0] == ':')
8225 cmd_complete(t, (char *)&c[1], -1);
8227 goto done;
8228 case GDK_Down:
8229 if (c[0] != ':') {
8230 if ((search_at = history_next(&shl, search_at))) {
8231 search_at->line[0] = c[0];
8232 gtk_entry_set_text(w, search_at->line);
8233 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8235 } else {
8236 if ((history_at = history_prev(&chl, history_at))) {
8237 history_at->line[0] = c[0];
8238 gtk_entry_set_text(w, history_at->line);
8239 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8243 goto done;
8244 case GDK_Up:
8245 if (c[0] != ':') {
8246 if ((search_at = history_next(&shl, search_at))) {
8247 search_at->line[0] = c[0];
8248 gtk_entry_set_text(w, search_at->line);
8249 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8251 } else {
8252 if ((history_at = history_next(&chl, history_at))) {
8253 history_at->line[0] = c[0];
8254 gtk_entry_set_text(w, history_at->line);
8255 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8259 goto done;
8260 case GDK_BackSpace:
8261 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8262 break;
8263 /* FALLTHROUGH */
8264 case GDK_Escape:
8265 hide_cmd(t);
8266 focus_webview(t);
8268 /* cancel search */
8269 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8270 webkit_web_view_unmark_text_matches(t->wv);
8271 goto done;
8274 rv = XT_CB_PASSTHROUGH;
8275 done:
8276 return (rv);
8279 void
8280 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8282 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8285 void
8286 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8288 /* popup menu enabled */
8289 t->popup = 1;
8293 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8295 if (t == NULL) {
8296 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8297 return (XT_CB_PASSTHROUGH);
8300 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8301 t->tab_id, t->popup);
8303 /* if popup is enabled don't lose focus */
8304 if (t->popup) {
8305 t->popup = 0;
8306 return (XT_CB_PASSTHROUGH);
8309 hide_cmd(t);
8310 hide_oops(t);
8312 if (show_url == 0 || t->focus_wv)
8313 focus_webview(t);
8314 else
8315 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8317 return (XT_CB_PASSTHROUGH);
8320 void
8321 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8323 char *s;
8324 const gchar *c = gtk_entry_get_text(entry);
8326 if (t == NULL) {
8327 show_oops(NULL, "cmd_activate_cb invalid parameters");
8328 return;
8331 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8333 hide_cmd(t);
8335 /* sanity */
8336 if (c == NULL)
8337 goto done;
8338 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8339 goto done;
8340 if (strlen(c) < 2)
8341 goto done;
8342 s = (char *)&c[1];
8344 if (c[0] == '/' || c[0] == '?') {
8345 /* see if there is a timer pending */
8346 if (t->search_id) {
8347 g_source_remove(t->search_id);
8348 t->search_id = 0;
8349 search_cb(t);
8352 if (t->search_text) {
8353 g_free(t->search_text);
8354 t->search_text = NULL;
8357 t->search_text = g_strdup(s);
8358 if (global_search)
8359 g_free(global_search);
8360 global_search = g_strdup(s);
8361 t->search_forward = c[0] == '/';
8363 history_add(&shl, search_file, s, &search_history_count);
8364 goto done;
8367 history_add(&chl, command_file, s, &cmd_history_count);
8368 cmd_execute(t, s);
8369 done:
8370 return;
8373 void
8374 backward_cb(GtkWidget *w, struct tab *t)
8376 struct karg a;
8378 if (t == NULL) {
8379 show_oops(NULL, "backward_cb invalid parameters");
8380 return;
8383 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8385 a.i = XT_NAV_BACK;
8386 navaction(t, &a);
8389 void
8390 forward_cb(GtkWidget *w, struct tab *t)
8392 struct karg a;
8394 if (t == NULL) {
8395 show_oops(NULL, "forward_cb invalid parameters");
8396 return;
8399 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8401 a.i = XT_NAV_FORWARD;
8402 navaction(t, &a);
8405 void
8406 home_cb(GtkWidget *w, struct tab *t)
8408 if (t == NULL) {
8409 show_oops(NULL, "home_cb invalid parameters");
8410 return;
8413 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8415 load_uri(t, home);
8418 void
8419 stop_cb(GtkWidget *w, struct tab *t)
8421 WebKitWebFrame *frame;
8423 if (t == NULL) {
8424 show_oops(NULL, "stop_cb invalid parameters");
8425 return;
8428 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8430 frame = webkit_web_view_get_main_frame(t->wv);
8431 if (frame == NULL) {
8432 show_oops(t, "stop_cb: no frame");
8433 return;
8436 webkit_web_frame_stop_loading(frame);
8437 abort_favicon_download(t);
8440 void
8441 setup_webkit(struct tab *t)
8443 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8444 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8445 FALSE, (char *)NULL);
8446 else
8447 warnx("webkit does not have \"enable-dns-prefetching\" property");
8448 g_object_set(G_OBJECT(t->settings),
8449 "user-agent", t->user_agent, (char *)NULL);
8450 g_object_set(G_OBJECT(t->settings),
8451 "enable-scripts", enable_scripts, (char *)NULL);
8452 g_object_set(G_OBJECT(t->settings),
8453 "enable-plugins", enable_plugins, (char *)NULL);
8454 g_object_set(G_OBJECT(t->settings),
8455 "javascript-can-open-windows-automatically", enable_scripts,
8456 (char *)NULL);
8457 g_object_set(G_OBJECT(t->settings),
8458 "enable-html5-database", FALSE, (char *)NULL);
8459 g_object_set(G_OBJECT(t->settings),
8460 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8461 g_object_set(G_OBJECT(t->settings),
8462 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8463 g_object_set(G_OBJECT(t->settings),
8464 "spell_checking_languages", spell_check_languages, (char *)NULL);
8465 g_object_set(G_OBJECT(t->wv),
8466 "full-content-zoom", TRUE, (char *)NULL);
8468 webkit_web_view_set_settings(t->wv, t->settings);
8471 gboolean
8472 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8474 struct tab *ti, *t = NULL;
8475 gdouble view_size, value, max;
8476 gchar *position;
8478 TAILQ_FOREACH(ti, &tabs, entry)
8479 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8480 t = ti;
8481 break;
8484 if (t == NULL)
8485 return FALSE;
8487 if (adjustment == NULL)
8488 adjustment = gtk_scrolled_window_get_vadjustment(
8489 GTK_SCROLLED_WINDOW(t->browser_win));
8491 view_size = gtk_adjustment_get_page_size(adjustment);
8492 value = gtk_adjustment_get_value(adjustment);
8493 max = gtk_adjustment_get_upper(adjustment) - view_size;
8495 if (max == 0)
8496 position = g_strdup("All");
8497 else if (value == max)
8498 position = g_strdup("Bot");
8499 else if (value == 0)
8500 position = g_strdup("Top");
8501 else
8502 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8504 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8505 g_free(position);
8507 return (TRUE);
8510 GtkWidget *
8511 create_browser(struct tab *t)
8513 GtkWidget *w;
8514 gchar *strval;
8515 GtkAdjustment *adjustment;
8517 if (t == NULL) {
8518 show_oops(NULL, "create_browser invalid parameters");
8519 return (NULL);
8522 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8523 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8524 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8525 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8527 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8528 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8529 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8531 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8532 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8534 /* set defaults */
8535 t->settings = webkit_web_settings_new();
8537 if (user_agent == NULL) {
8538 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8539 (char *)NULL);
8540 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8541 g_free(strval);
8542 } else
8543 t->user_agent = g_strdup(user_agent);
8545 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8547 adjustment =
8548 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8549 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8550 G_CALLBACK(update_statusbar_position), NULL);
8552 setup_webkit(t);
8554 return (w);
8557 GtkWidget *
8558 create_window(void)
8560 GtkWidget *w;
8562 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8563 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8564 gtk_widget_set_name(w, "xxxterm");
8565 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8566 g_signal_connect(G_OBJECT(w), "delete_event",
8567 G_CALLBACK (gtk_main_quit), NULL);
8569 return (w);
8572 GtkWidget *
8573 create_kiosk_toolbar(struct tab *t)
8575 GtkWidget *toolbar = NULL, *b;
8577 b = gtk_hbox_new(FALSE, 0);
8578 toolbar = b;
8579 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8581 /* backward button */
8582 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8583 gtk_widget_set_sensitive(t->backward, FALSE);
8584 g_signal_connect(G_OBJECT(t->backward), "clicked",
8585 G_CALLBACK(backward_cb), t);
8586 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8588 /* forward button */
8589 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8590 gtk_widget_set_sensitive(t->forward, FALSE);
8591 g_signal_connect(G_OBJECT(t->forward), "clicked",
8592 G_CALLBACK(forward_cb), t);
8593 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8595 /* home button */
8596 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8597 gtk_widget_set_sensitive(t->gohome, true);
8598 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8599 G_CALLBACK(home_cb), t);
8600 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8602 /* create widgets but don't use them */
8603 t->uri_entry = gtk_entry_new();
8604 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8605 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8606 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8608 return (toolbar);
8611 GtkWidget *
8612 create_toolbar(struct tab *t)
8614 GtkWidget *toolbar = NULL, *b, *eb1;
8616 b = gtk_hbox_new(FALSE, 0);
8617 toolbar = b;
8618 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8620 /* backward button */
8621 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8622 gtk_widget_set_sensitive(t->backward, FALSE);
8623 g_signal_connect(G_OBJECT(t->backward), "clicked",
8624 G_CALLBACK(backward_cb), t);
8625 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8627 /* forward button */
8628 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8629 gtk_widget_set_sensitive(t->forward, FALSE);
8630 g_signal_connect(G_OBJECT(t->forward), "clicked",
8631 G_CALLBACK(forward_cb), t);
8632 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8633 FALSE, 0);
8635 /* stop button */
8636 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8637 gtk_widget_set_sensitive(t->stop, FALSE);
8638 g_signal_connect(G_OBJECT(t->stop), "clicked",
8639 G_CALLBACK(stop_cb), t);
8640 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8641 FALSE, 0);
8643 /* JS button */
8644 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8645 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8646 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8647 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8648 G_CALLBACK(js_toggle_cb), t);
8649 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8651 t->uri_entry = gtk_entry_new();
8652 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8653 G_CALLBACK(activate_uri_entry_cb), t);
8654 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8655 G_CALLBACK(entry_key_cb), t);
8656 completion_add(t);
8657 eb1 = gtk_hbox_new(FALSE, 0);
8658 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8659 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8660 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8662 /* search entry */
8663 if (search_string) {
8664 GtkWidget *eb2;
8665 t->search_entry = gtk_entry_new();
8666 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8667 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8668 G_CALLBACK(activate_search_entry_cb), t);
8669 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8670 G_CALLBACK(entry_key_cb), t);
8671 gtk_widget_set_size_request(t->search_entry, -1, -1);
8672 eb2 = gtk_hbox_new(FALSE, 0);
8673 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8674 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8676 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8679 return (toolbar);
8682 GtkWidget *
8683 create_buffers(struct tab *t)
8685 GtkCellRenderer *renderer;
8686 GtkWidget *view;
8688 view = gtk_tree_view_new();
8690 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8692 renderer = gtk_cell_renderer_text_new();
8693 gtk_tree_view_insert_column_with_attributes
8694 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8696 renderer = gtk_cell_renderer_text_new();
8697 gtk_tree_view_insert_column_with_attributes
8698 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8699 (char *)NULL);
8701 gtk_tree_view_set_model
8702 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8704 return view;
8707 void
8708 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8709 GtkTreeViewColumn *col, struct tab *t)
8711 GtkTreeIter iter;
8712 guint id;
8714 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8716 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8717 path)) {
8718 gtk_tree_model_get
8719 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8720 set_current_tab(id - 1);
8723 hide_buffers(t);
8726 /* after tab reordering/creation/removal */
8727 void
8728 recalc_tabs(void)
8730 struct tab *t;
8731 int maxid = 0;
8733 TAILQ_FOREACH(t, &tabs, entry) {
8734 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8735 if (t->tab_id > maxid)
8736 maxid = t->tab_id;
8738 gtk_widget_show(t->tab_elems.sep);
8741 TAILQ_FOREACH(t, &tabs, entry) {
8742 if (t->tab_id == maxid) {
8743 gtk_widget_hide(t->tab_elems.sep);
8744 break;
8749 /* after active tab change */
8750 void
8751 recolor_compact_tabs(void)
8753 struct tab *t;
8754 int curid = 0;
8755 GdkColor color;
8757 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8758 TAILQ_FOREACH(t, &tabs, entry)
8759 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8760 &color);
8762 curid = gtk_notebook_get_current_page(notebook);
8763 TAILQ_FOREACH(t, &tabs, entry)
8764 if (t->tab_id == curid) {
8765 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8766 gtk_widget_modify_fg(t->tab_elems.label,
8767 GTK_STATE_NORMAL, &color);
8768 break;
8772 void
8773 set_current_tab(int page_num)
8775 buffercmd_abort(get_current_tab());
8776 gtk_notebook_set_current_page(notebook, page_num);
8777 recolor_compact_tabs();
8781 undo_close_tab_save(struct tab *t)
8783 int m, n;
8784 const gchar *uri;
8785 struct undo *u1, *u2;
8786 GList *items;
8787 WebKitWebHistoryItem *item;
8789 if ((uri = get_uri(t)) == NULL)
8790 return (1);
8792 u1 = g_malloc0(sizeof(struct undo));
8793 u1->uri = g_strdup(uri);
8795 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8797 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8798 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8799 u1->back = n;
8801 /* forward history */
8802 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8804 while (items) {
8805 item = items->data;
8806 u1->history = g_list_prepend(u1->history,
8807 webkit_web_history_item_copy(item));
8808 items = g_list_next(items);
8811 /* current item */
8812 if (m) {
8813 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8814 u1->history = g_list_prepend(u1->history,
8815 webkit_web_history_item_copy(item));
8818 /* back history */
8819 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8821 while (items) {
8822 item = items->data;
8823 u1->history = g_list_prepend(u1->history,
8824 webkit_web_history_item_copy(item));
8825 items = g_list_next(items);
8828 TAILQ_INSERT_HEAD(&undos, u1, entry);
8830 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8831 u2 = TAILQ_LAST(&undos, undo_tailq);
8832 TAILQ_REMOVE(&undos, u2, entry);
8833 g_free(u2->uri);
8834 g_list_free(u2->history);
8835 g_free(u2);
8836 } else
8837 undo_count++;
8839 return (0);
8842 void
8843 delete_tab(struct tab *t)
8845 struct karg a;
8847 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8849 if (t == NULL)
8850 return;
8852 buffercmd_abort(t);
8853 TAILQ_REMOVE(&tabs, t, entry);
8855 /* Halt all webkit activity. */
8856 abort_favicon_download(t);
8857 webkit_web_view_stop_loading(t->wv);
8859 /* Save the tab, so we can undo the close. */
8860 undo_close_tab_save(t);
8862 if (browser_mode == XT_BM_KIOSK) {
8863 gtk_widget_destroy(t->uri_entry);
8864 gtk_widget_destroy(t->stop);
8865 gtk_widget_destroy(t->js_toggle);
8868 gtk_widget_destroy(t->tab_elems.eventbox);
8869 gtk_widget_destroy(t->vbox);
8871 /* just in case */
8872 if (t->search_id)
8873 g_source_remove(t->search_id);
8875 g_free(t->user_agent);
8876 g_free(t->stylesheet);
8877 g_free(t->tmp_uri);
8878 g_free(t);
8880 if (TAILQ_EMPTY(&tabs)) {
8881 if (browser_mode == XT_BM_KIOSK)
8882 create_new_tab(home, NULL, 1, -1);
8883 else
8884 create_new_tab(NULL, NULL, 1, -1);
8887 /* recreate session */
8888 if (session_autosave) {
8889 a.s = NULL;
8890 save_tabs(t, &a);
8893 recalc_tabs();
8894 recolor_compact_tabs();
8897 void
8898 update_statusbar_zoom(struct tab *t)
8900 gfloat zoom;
8901 char s[16] = { '\0' };
8903 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8904 if ((zoom <= 0.99 || zoom >= 1.01))
8905 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8906 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8909 void
8910 setzoom_webkit(struct tab *t, int adjust)
8912 #define XT_ZOOMPERCENT 0.04
8914 gfloat zoom;
8916 if (t == NULL) {
8917 show_oops(NULL, "setzoom_webkit invalid parameters");
8918 return;
8921 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8922 if (adjust == XT_ZOOM_IN)
8923 zoom += XT_ZOOMPERCENT;
8924 else if (adjust == XT_ZOOM_OUT)
8925 zoom -= XT_ZOOMPERCENT;
8926 else if (adjust > 0)
8927 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8928 else {
8929 show_oops(t, "setzoom_webkit invalid zoom value");
8930 return;
8933 if (zoom < XT_ZOOMPERCENT)
8934 zoom = XT_ZOOMPERCENT;
8935 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8936 update_statusbar_zoom(t);
8939 gboolean
8940 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8942 struct tab *t = (struct tab *) data;
8944 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8946 switch (event->button) {
8947 case 1:
8948 set_current_tab(t->tab_id);
8949 break;
8950 case 2:
8951 delete_tab(t);
8952 break;
8955 return TRUE;
8958 void
8959 append_tab(struct tab *t)
8961 if (t == NULL)
8962 return;
8964 TAILQ_INSERT_TAIL(&tabs, t, entry);
8965 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8968 GtkWidget *
8969 create_sbe(int width)
8971 GtkWidget *sbe;
8973 sbe = gtk_entry_new();
8974 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8975 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8976 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8977 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8978 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8979 gtk_widget_set_size_request(sbe, width, -1);
8981 return sbe;
8984 struct tab *
8985 create_new_tab(char *title, struct undo *u, int focus, int position)
8987 struct tab *t;
8988 int load = 1, id;
8989 GtkWidget *b, *bb;
8990 WebKitWebHistoryItem *item;
8991 GList *items;
8992 GdkColor color;
8993 char *p;
8994 int sbe_p = 0, sbe_b = 0,
8995 sbe_z = 0;
8997 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8999 if (tabless && !TAILQ_EMPTY(&tabs)) {
9000 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9001 return (NULL);
9004 t = g_malloc0(sizeof *t);
9006 if (title == NULL) {
9007 title = "(untitled)";
9008 load = 0;
9011 t->vbox = gtk_vbox_new(FALSE, 0);
9013 /* label + button for tab */
9014 b = gtk_hbox_new(FALSE, 0);
9015 t->tab_content = b;
9017 #if GTK_CHECK_VERSION(2, 20, 0)
9018 t->spinner = gtk_spinner_new();
9019 #endif
9020 t->label = gtk_label_new(title);
9021 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9022 gtk_widget_set_size_request(t->label, 100, 0);
9023 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9024 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9025 gtk_widget_set_size_request(b, 130, 0);
9027 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9028 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9029 #if GTK_CHECK_VERSION(2, 20, 0)
9030 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9031 #endif
9033 /* toolbar */
9034 if (browser_mode == XT_BM_KIOSK) {
9035 t->toolbar = create_kiosk_toolbar(t);
9036 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9038 } else {
9039 t->toolbar = create_toolbar(t);
9040 if (fancy_bar)
9041 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9042 FALSE, 0);
9045 /* marks */
9046 marks_clear(t);
9048 /* browser */
9049 t->browser_win = create_browser(t);
9050 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9052 /* oops message for user feedback */
9053 t->oops = gtk_entry_new();
9054 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9055 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9056 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9057 gdk_color_parse(XT_COLOR_RED, &color);
9058 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9059 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9060 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9062 /* command entry */
9063 t->cmd = gtk_entry_new();
9064 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9065 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9066 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9067 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9069 /* status bar */
9070 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9072 t->sbe.statusbar = gtk_entry_new();
9073 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9074 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9075 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9076 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9078 /* create these widgets only if specified in statusbar_elems */
9080 t->sbe.position = create_sbe(40);
9081 t->sbe.zoom = create_sbe(40);
9082 t->sbe.buffercmd = create_sbe(60);
9084 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9086 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9087 TRUE, FALSE);
9089 /* gtk widgets cannot be added to a box twice. sbe_* variables
9090 make sure of this */
9091 for (p = statusbar_elems; *p != '\0'; p++) {
9092 switch (*p) {
9093 case '|':
9095 GtkWidget *sep = gtk_vseparator_new();
9097 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9098 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9099 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9100 FALSE, FALSE, FALSE);
9101 break;
9103 case 'P':
9104 if (sbe_p) {
9105 warnx("flag \"%c\" specified more than "
9106 "once in statusbar_elems\n", *p);
9107 break;
9109 sbe_p = 1;
9110 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9111 t->sbe.position, FALSE, FALSE, FALSE);
9112 break;
9113 case 'B':
9114 if (sbe_b) {
9115 warnx("flag \"%c\" specified more than "
9116 "once in statusbar_elems\n", *p);
9117 break;
9119 sbe_b = 1;
9120 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9121 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9122 break;
9123 case 'Z':
9124 if (sbe_z) {
9125 warnx("flag \"%c\" specified more than "
9126 "once in statusbar_elems\n", *p);
9127 break;
9129 sbe_z = 1;
9130 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9131 t->sbe.zoom, FALSE, FALSE, FALSE);
9132 break;
9133 default:
9134 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9135 break;
9139 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9141 /* buffer list */
9142 t->buffers = create_buffers(t);
9143 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9145 /* xtp meaning is normal by default */
9146 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9148 /* set empty favicon */
9149 xt_icon_from_name(t, "text-html");
9151 /* and show it all */
9152 gtk_widget_show_all(b);
9153 gtk_widget_show_all(t->vbox);
9155 /* compact tab bar */
9156 t->tab_elems.label = gtk_label_new(title);
9157 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9158 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9159 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9160 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9162 t->tab_elems.eventbox = gtk_event_box_new();
9163 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9164 t->tab_elems.sep = gtk_vseparator_new();
9166 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9167 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9168 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9169 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9170 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9171 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9173 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9174 TRUE, 0);
9175 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9176 FALSE, 0);
9177 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9178 t->tab_elems.box);
9180 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9181 TRUE, 0);
9182 gtk_widget_show_all(t->tab_elems.eventbox);
9184 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9185 append_tab(t);
9186 else {
9187 id = position >= 0 ? position :
9188 gtk_notebook_get_current_page(notebook) + 1;
9189 if (id > gtk_notebook_get_n_pages(notebook))
9190 append_tab(t);
9191 else {
9192 TAILQ_INSERT_TAIL(&tabs, t, entry);
9193 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9194 gtk_box_reorder_child(GTK_BOX(tab_bar),
9195 t->tab_elems.eventbox, id);
9196 recalc_tabs();
9200 #if GTK_CHECK_VERSION(2, 20, 0)
9201 /* turn spinner off if we are a new tab without uri */
9202 if (!load) {
9203 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9204 gtk_widget_hide(t->spinner);
9206 #endif
9207 /* make notebook tabs reorderable */
9208 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9210 /* compact tabs clickable */
9211 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9212 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9214 g_object_connect(G_OBJECT(t->cmd),
9215 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9216 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9217 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9218 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9219 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9220 (char *)NULL);
9222 /* reuse wv_button_cb to hide oops */
9223 g_object_connect(G_OBJECT(t->oops),
9224 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9225 (char *)NULL);
9227 g_signal_connect(t->buffers,
9228 "row-activated", G_CALLBACK(row_activated_cb), t);
9229 g_object_connect(G_OBJECT(t->buffers),
9230 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9232 g_object_connect(G_OBJECT(t->wv),
9233 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9234 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9235 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9236 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9237 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9238 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9239 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9240 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9241 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9242 "signal::event", G_CALLBACK(webview_event_cb), t,
9243 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9244 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9245 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9246 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9247 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9248 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9249 (char *)NULL);
9250 g_signal_connect(t->wv,
9251 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9253 * XXX this puts invalid url in uri_entry and that is undesirable
9255 #if 0
9256 g_signal_connect(t->wv,
9257 "load-error", G_CALLBACK(notify_load_error_cb), t);
9258 #endif
9259 g_signal_connect(t->wv,
9260 "notify::title", G_CALLBACK(notify_title_cb), t);
9262 /* hijack the unused keys as if we were the browser */
9263 g_object_connect(G_OBJECT(t->toolbar),
9264 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9265 (char *)NULL);
9267 g_signal_connect(G_OBJECT(bb), "button_press_event",
9268 G_CALLBACK(tab_close_cb), t);
9270 /* setup history */
9271 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9272 /* restore the tab's history */
9273 if (u && u->history) {
9274 items = u->history;
9275 while (items) {
9276 item = items->data;
9277 webkit_web_back_forward_list_add_item(t->bfl, item);
9278 items = g_list_next(items);
9281 item = g_list_nth_data(u->history, u->back);
9282 if (item)
9283 webkit_web_view_go_to_back_forward_item(t->wv, item);
9285 g_list_free(items);
9286 g_list_free(u->history);
9287 } else
9288 webkit_web_back_forward_list_clear(t->bfl);
9290 /* hide stuff */
9291 hide_cmd(t);
9292 hide_oops(t);
9293 hide_buffers(t);
9294 url_set_visibility();
9295 statusbar_set_visibility();
9297 if (focus) {
9298 set_current_tab(t->tab_id);
9299 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9300 t->tab_id);
9303 if (load) {
9304 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9305 load_uri(t, title);
9306 } else {
9307 if (show_url == 1)
9308 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9309 else
9310 focus_webview(t);
9313 recolor_compact_tabs();
9314 setzoom_webkit(t, XT_ZOOM_NORMAL);
9315 return (t);
9318 void
9319 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9320 gpointer *udata)
9322 struct tab *t;
9323 const gchar *uri;
9325 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9327 if (gtk_notebook_get_current_page(notebook) == -1)
9328 recalc_tabs();
9330 TAILQ_FOREACH(t, &tabs, entry) {
9331 if (t->tab_id == pn) {
9332 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9333 "%d\n", pn);
9335 uri = get_title(t, TRUE);
9336 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9338 hide_cmd(t);
9339 hide_oops(t);
9341 if (t->focus_wv) {
9342 /* can't use focus_webview here */
9343 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9349 void
9350 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9351 gpointer *udata)
9353 struct tab *t = NULL, *tt;
9355 recalc_tabs();
9357 TAILQ_FOREACH(tt, &tabs, entry)
9358 if (tt->tab_id == pn) {
9359 t = tt;
9360 break;
9363 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9365 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9366 t->tab_id);
9369 void
9370 menuitem_response(struct tab *t)
9372 gtk_notebook_set_current_page(notebook, t->tab_id);
9375 gboolean
9376 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9378 GtkWidget *menu, *menu_items;
9379 GdkEventButton *bevent;
9380 const gchar *uri;
9381 struct tab *ti;
9383 if (event->type == GDK_BUTTON_PRESS) {
9384 bevent = (GdkEventButton *) event;
9385 menu = gtk_menu_new();
9387 TAILQ_FOREACH(ti, &tabs, entry) {
9388 if ((uri = get_uri(ti)) == NULL)
9389 /* XXX make sure there is something to print */
9390 /* XXX add gui pages in here to look purdy */
9391 uri = "(untitled)";
9392 menu_items = gtk_menu_item_new_with_label(uri);
9393 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9394 gtk_widget_show(menu_items);
9396 g_signal_connect_swapped((menu_items),
9397 "activate", G_CALLBACK(menuitem_response),
9398 (gpointer)ti);
9401 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9402 bevent->button, bevent->time);
9404 /* unref object so it'll free itself when popped down */
9405 #if !GTK_CHECK_VERSION(3, 0, 0)
9406 /* XXX does not need unref with gtk+3? */
9407 g_object_ref_sink(menu);
9408 g_object_unref(menu);
9409 #endif
9411 return (TRUE /* eat event */);
9414 return (FALSE /* propagate */);
9418 icon_size_map(int icon_size)
9420 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9421 icon_size > GTK_ICON_SIZE_DIALOG)
9422 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9424 return (icon_size);
9427 GtkWidget *
9428 create_button(char *name, char *stockid, int size)
9430 GtkWidget *button, *image;
9431 gchar *rcstring;
9432 int gtk_icon_size;
9434 rcstring = g_strdup_printf(
9435 "style \"%s-style\"\n"
9436 "{\n"
9437 " GtkWidget::focus-padding = 0\n"
9438 " GtkWidget::focus-line-width = 0\n"
9439 " xthickness = 0\n"
9440 " ythickness = 0\n"
9441 "}\n"
9442 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9443 gtk_rc_parse_string(rcstring);
9444 g_free(rcstring);
9445 button = gtk_button_new();
9446 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9447 gtk_icon_size = icon_size_map(size ? size : icon_size);
9449 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9450 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9451 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9452 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9453 gtk_widget_set_name(button, name);
9454 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9456 return (button);
9459 void
9460 button_set_stockid(GtkWidget *button, char *stockid)
9462 GtkWidget *image;
9464 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9465 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9466 gtk_button_set_image(GTK_BUTTON(button), image);
9469 void
9470 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9472 gchar *p = NULL;
9473 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9474 gint len;
9476 if (xterm_workaround == 0)
9477 return;
9480 * xterm doesn't play nice with clipboards because it clears the
9481 * primary when clicked. We rely on primary being set to properly
9482 * handle middle mouse button clicks (paste). So when someone clears
9483 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9484 * other application behavior (as in DON'T clear primary).
9487 p = gtk_clipboard_wait_for_text(primary);
9488 if (p == NULL) {
9489 if (gdk_property_get(gdk_get_default_root_window(),
9490 atom,
9491 gdk_atom_intern("STRING", FALSE),
9493 1024 * 1024 /* picked out of my butt */,
9494 FALSE,
9495 NULL,
9496 NULL,
9497 &len,
9498 (guchar **)&p)) {
9499 /* yes sir, we need to NUL the string */
9500 p[len] = '\0';
9501 gtk_clipboard_set_text(primary, p, -1);
9505 if (p)
9506 g_free(p);
9509 void
9510 create_canvas(void)
9512 GtkWidget *vbox;
9513 GList *l = NULL;
9514 GdkPixbuf *pb;
9515 char file[PATH_MAX];
9516 int i;
9518 vbox = gtk_vbox_new(FALSE, 0);
9519 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9520 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9521 #if !GTK_CHECK_VERSION(3, 0, 0)
9522 /* XXX seems to be needed with gtk+2 */
9523 gtk_notebook_set_tab_hborder(notebook, 0);
9524 gtk_notebook_set_tab_vborder(notebook, 0);
9525 #endif
9526 gtk_notebook_set_scrollable(notebook, TRUE);
9527 gtk_notebook_set_show_border(notebook, FALSE);
9528 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9530 abtn = gtk_button_new();
9531 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9532 gtk_widget_set_size_request(arrow, -1, -1);
9533 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9534 gtk_widget_set_size_request(abtn, -1, 20);
9536 #if GTK_CHECK_VERSION(2, 20, 0)
9537 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9538 #endif
9539 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9541 /* compact tab bar */
9542 tab_bar = gtk_hbox_new(TRUE, 0);
9544 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9545 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9546 gtk_widget_set_size_request(vbox, -1, -1);
9548 g_object_connect(G_OBJECT(notebook),
9549 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9550 (char *)NULL);
9551 g_object_connect(G_OBJECT(notebook),
9552 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9553 NULL, (char *)NULL);
9554 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9555 G_CALLBACK(arrow_cb), NULL);
9557 main_window = create_window();
9558 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9560 /* icons */
9561 for (i = 0; i < LENGTH(icons); i++) {
9562 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9563 pb = gdk_pixbuf_new_from_file(file, NULL);
9564 l = g_list_append(l, pb);
9566 gtk_window_set_default_icon_list(l);
9568 /* clipboard work around */
9569 if (xterm_workaround)
9570 g_signal_connect(
9571 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9572 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9574 gtk_widget_show_all(abtn);
9575 gtk_widget_show_all(main_window);
9576 notebook_tab_set_visibility();
9579 void
9580 set_hook(void **hook, char *name)
9582 if (hook == NULL)
9583 errx(1, "set_hook");
9585 if (*hook == NULL) {
9586 *hook = dlsym(RTLD_NEXT, name);
9587 if (*hook == NULL)
9588 errx(1, "can't hook %s", name);
9592 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9593 gboolean
9594 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9596 g_return_val_if_fail(cookie1, FALSE);
9597 g_return_val_if_fail(cookie2, FALSE);
9599 return (!strcmp (cookie1->name, cookie2->name) &&
9600 !strcmp (cookie1->value, cookie2->value) &&
9601 !strcmp (cookie1->path, cookie2->path) &&
9602 !strcmp (cookie1->domain, cookie2->domain));
9605 void
9606 transfer_cookies(void)
9608 GSList *cf;
9609 SoupCookie *sc, *pc;
9611 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9613 for (;cf; cf = cf->next) {
9614 pc = cf->data;
9615 sc = soup_cookie_copy(pc);
9616 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9619 soup_cookies_free(cf);
9622 void
9623 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9625 GSList *cf;
9626 SoupCookie *ci;
9628 print_cookie("soup_cookie_jar_delete_cookie", c);
9630 if (cookies_enabled == 0)
9631 return;
9633 if (jar == NULL || c == NULL)
9634 return;
9636 /* find and remove from persistent jar */
9637 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9639 for (;cf; cf = cf->next) {
9640 ci = cf->data;
9641 if (soup_cookie_equal(ci, c)) {
9642 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9643 break;
9647 soup_cookies_free(cf);
9649 /* delete from session jar */
9650 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9653 void
9654 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9656 struct domain *d = NULL;
9657 SoupCookie *c;
9658 FILE *r_cookie_f;
9660 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9661 jar, p_cookiejar, s_cookiejar);
9663 if (cookies_enabled == 0)
9664 return;
9666 /* see if we are up and running */
9667 if (p_cookiejar == NULL) {
9668 _soup_cookie_jar_add_cookie(jar, cookie);
9669 return;
9671 /* disallow p_cookiejar adds, shouldn't happen */
9672 if (jar == p_cookiejar)
9673 return;
9675 /* sanity */
9676 if (jar == NULL || cookie == NULL)
9677 return;
9679 if (enable_cookie_whitelist &&
9680 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9681 blocked_cookies++;
9682 DNPRINTF(XT_D_COOKIE,
9683 "soup_cookie_jar_add_cookie: reject %s\n",
9684 cookie->domain);
9685 if (save_rejected_cookies) {
9686 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9687 show_oops(NULL, "can't open reject cookie file");
9688 return;
9690 fseek(r_cookie_f, 0, SEEK_END);
9691 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9692 cookie->http_only ? "#HttpOnly_" : "",
9693 cookie->domain,
9694 *cookie->domain == '.' ? "TRUE" : "FALSE",
9695 cookie->path,
9696 cookie->secure ? "TRUE" : "FALSE",
9697 cookie->expires ?
9698 (gulong)soup_date_to_time_t(cookie->expires) :
9700 cookie->name,
9701 cookie->value);
9702 fflush(r_cookie_f);
9703 fclose(r_cookie_f);
9705 if (!allow_volatile_cookies)
9706 return;
9709 if (cookie->expires == NULL && session_timeout) {
9710 soup_cookie_set_expires(cookie,
9711 soup_date_new_from_now(session_timeout));
9712 print_cookie("modified add cookie", cookie);
9715 /* see if we are white listed for persistence */
9716 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9717 /* add to persistent jar */
9718 c = soup_cookie_copy(cookie);
9719 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9720 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9723 /* add to session jar */
9724 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9725 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9728 void
9729 setup_cookies(void)
9731 char file[PATH_MAX];
9733 set_hook((void *)&_soup_cookie_jar_add_cookie,
9734 "soup_cookie_jar_add_cookie");
9735 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9736 "soup_cookie_jar_delete_cookie");
9738 if (cookies_enabled == 0)
9739 return;
9742 * the following code is intricate due to overriding several libsoup
9743 * functions.
9744 * do not alter order of these operations.
9747 /* rejected cookies */
9748 if (save_rejected_cookies)
9749 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9750 XT_REJECT_FILE);
9752 /* persistent cookies */
9753 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9754 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9756 /* session cookies */
9757 s_cookiejar = soup_cookie_jar_new();
9758 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9759 cookie_policy, (void *)NULL);
9760 transfer_cookies();
9762 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9765 void
9766 setup_proxy(char *uri)
9768 if (proxy_uri) {
9769 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9770 soup_uri_free(proxy_uri);
9771 proxy_uri = NULL;
9773 if (http_proxy) {
9774 if (http_proxy != uri) {
9775 g_free(http_proxy);
9776 http_proxy = NULL;
9780 if (uri) {
9781 http_proxy = g_strdup(uri);
9782 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9783 proxy_uri = soup_uri_new(http_proxy);
9784 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9785 g_object_set(session, "proxy-uri", proxy_uri,
9786 (char *)NULL);
9791 set_http_proxy(char *proxy)
9793 SoupURI *uri;
9795 if (proxy == NULL)
9796 return (1);
9798 /* see if we need to clear it instead */
9799 if (strlen(proxy) == 0) {
9800 setup_proxy(NULL);
9801 return (0);
9804 uri = soup_uri_new(proxy);
9805 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9806 return (1);
9808 setup_proxy(proxy);
9810 soup_uri_free(uri);
9812 return (0);
9816 send_cmd_to_socket(char *cmd)
9818 int s, len, rv = 1;
9819 struct sockaddr_un sa;
9821 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9822 warnx("%s: socket", __func__);
9823 return (rv);
9826 sa.sun_family = AF_UNIX;
9827 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9828 work_dir, XT_SOCKET_FILE);
9829 len = SUN_LEN(&sa);
9831 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9832 warnx("%s: connect", __func__);
9833 goto done;
9836 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9837 warnx("%s: send", __func__);
9838 goto done;
9841 rv = 0;
9842 done:
9843 close(s);
9844 return (rv);
9847 gboolean
9848 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9850 int s, n;
9851 char str[XT_MAX_URL_LENGTH];
9852 socklen_t t = sizeof(struct sockaddr_un);
9853 struct sockaddr_un sa;
9854 struct passwd *p;
9855 uid_t uid;
9856 gid_t gid;
9857 struct tab *tt;
9858 gint fd = g_io_channel_unix_get_fd(source);
9860 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9861 warn("accept");
9862 return (FALSE);
9865 if (getpeereid(s, &uid, &gid) == -1) {
9866 warn("getpeereid");
9867 return (FALSE);
9869 if (uid != getuid() || gid != getgid()) {
9870 warnx("unauthorized user");
9871 return (FALSE);
9874 p = getpwuid(uid);
9875 if (p == NULL) {
9876 warnx("not a valid user");
9877 return (FALSE);
9880 n = recv(s, str, sizeof(str), 0);
9881 if (n <= 0)
9882 return (TRUE);
9884 tt = TAILQ_LAST(&tabs, tab_list);
9885 cmd_execute(tt, str);
9886 return (TRUE);
9890 is_running(void)
9892 int s, len, rv = 1;
9893 struct sockaddr_un sa;
9895 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9896 warn("is_running: socket");
9897 return (-1);
9900 sa.sun_family = AF_UNIX;
9901 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9902 work_dir, XT_SOCKET_FILE);
9903 len = SUN_LEN(&sa);
9905 /* connect to see if there is a listener */
9906 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9907 rv = 0; /* not running */
9908 else
9909 rv = 1; /* already running */
9911 close(s);
9913 return (rv);
9917 build_socket(void)
9919 int s, len;
9920 struct sockaddr_un sa;
9922 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9923 warn("build_socket: socket");
9924 return (-1);
9927 sa.sun_family = AF_UNIX;
9928 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9929 work_dir, XT_SOCKET_FILE);
9930 len = SUN_LEN(&sa);
9932 /* connect to see if there is a listener */
9933 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9934 /* no listener so we will */
9935 unlink(sa.sun_path);
9937 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9938 warn("build_socket: bind");
9939 goto done;
9942 if (listen(s, 1) == -1) {
9943 warn("build_socket: listen");
9944 goto done;
9947 return (s);
9950 done:
9951 close(s);
9952 return (-1);
9955 gboolean
9956 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9957 GtkTreeIter *iter, struct tab *t)
9959 gchar *value;
9961 gtk_tree_model_get(model, iter, 0, &value, -1);
9962 load_uri(t, value);
9963 g_free(value);
9965 return (FALSE);
9968 gboolean
9969 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9970 GtkTreeIter *iter, struct tab *t)
9972 gchar *value;
9974 gtk_tree_model_get(model, iter, 0, &value, -1);
9975 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9976 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9977 g_free(value);
9979 return (TRUE);
9982 void
9983 completion_add_uri(const gchar *uri)
9985 GtkTreeIter iter;
9987 /* add uri to list_store */
9988 gtk_list_store_append(completion_model, &iter);
9989 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9992 gboolean
9993 completion_match(GtkEntryCompletion *completion, const gchar *key,
9994 GtkTreeIter *iter, gpointer user_data)
9996 gchar *value;
9997 gboolean match = FALSE;
9999 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10000 -1);
10002 if (value == NULL)
10003 return FALSE;
10005 match = match_uri(value, key);
10007 g_free(value);
10008 return (match);
10011 void
10012 completion_add(struct tab *t)
10014 /* enable completion for tab */
10015 t->completion = gtk_entry_completion_new();
10016 gtk_entry_completion_set_text_column(t->completion, 0);
10017 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10018 gtk_entry_completion_set_model(t->completion,
10019 GTK_TREE_MODEL(completion_model));
10020 gtk_entry_completion_set_match_func(t->completion, completion_match,
10021 NULL, NULL);
10022 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10023 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10024 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10025 G_CALLBACK(completion_select_cb), t);
10026 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10027 G_CALLBACK(completion_hover_cb), t);
10030 void
10031 xxx_dir(char *dir)
10033 struct stat sb;
10035 if (stat(dir, &sb)) {
10036 if (mkdir(dir, S_IRWXU) == -1)
10037 err(1, "mkdir %s", dir);
10038 if (stat(dir, &sb))
10039 err(1, "stat %s", dir);
10041 if (S_ISDIR(sb.st_mode) == 0)
10042 errx(1, "%s not a dir", dir);
10043 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10044 warnx("fixing invalid permissions on %s", dir);
10045 if (chmod(dir, S_IRWXU) == -1)
10046 err(1, "chmod %s", dir);
10050 void
10051 usage(void)
10053 fprintf(stderr,
10054 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10055 exit(0);
10060 main(int argc, char *argv[])
10062 struct stat sb;
10063 int c, s, optn = 0, opte = 0, focus = 1;
10064 char conf[PATH_MAX] = { '\0' };
10065 char file[PATH_MAX];
10066 char *env_proxy = NULL;
10067 char *cmd = NULL;
10068 FILE *f = NULL;
10069 struct karg a;
10070 struct sigaction sact;
10071 GIOChannel *channel;
10072 struct rlimit rlp;
10074 start_argv = argv;
10076 /* prepare gtk */
10077 gtk_init(&argc, &argv);
10078 g_thread_init(NULL);
10080 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10082 RB_INIT(&hl);
10083 RB_INIT(&js_wl);
10084 RB_INIT(&downloads);
10086 TAILQ_INIT(&sessions);
10087 TAILQ_INIT(&tabs);
10088 TAILQ_INIT(&mtl);
10089 TAILQ_INIT(&aliases);
10090 TAILQ_INIT(&undos);
10091 TAILQ_INIT(&kbl);
10092 TAILQ_INIT(&spl);
10093 TAILQ_INIT(&chl);
10094 TAILQ_INIT(&shl);
10096 /* fiddle with ulimits */
10097 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10098 warn("getrlimit");
10099 else {
10100 /* just use them all */
10101 rlp.rlim_cur = rlp.rlim_max;
10102 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10103 warn("setrlimit");
10104 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10105 warn("getrlimit");
10106 else if (rlp.rlim_cur <= 256)
10107 startpage_add("%s requires at least 256 file "
10108 "descriptors, currently it has up to %d available",
10109 __progname, rlp.rlim_cur);
10112 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10113 switch (c) {
10114 case 'S':
10115 show_url = 0;
10116 break;
10117 case 'T':
10118 show_tabs = 0;
10119 break;
10120 case 'V':
10121 errx(0 , "Version: %s", version);
10122 break;
10123 case 'f':
10124 strlcpy(conf, optarg, sizeof(conf));
10125 break;
10126 case 's':
10127 strlcpy(named_session, optarg, sizeof(named_session));
10128 break;
10129 case 't':
10130 tabless = 1;
10131 break;
10132 case 'n':
10133 optn = 1;
10134 break;
10135 case 'e':
10136 opte = 1;
10137 break;
10138 default:
10139 usage();
10140 /* NOTREACHED */
10143 argc -= optind;
10144 argv += optind;
10146 init_keybindings();
10148 gnutls_global_init();
10150 /* generate session keys for xtp pages */
10151 generate_xtp_session_key(&dl_session_key);
10152 generate_xtp_session_key(&hl_session_key);
10153 generate_xtp_session_key(&cl_session_key);
10154 generate_xtp_session_key(&fl_session_key);
10156 /* signals */
10157 bzero(&sact, sizeof(sact));
10158 sigemptyset(&sact.sa_mask);
10159 sact.sa_handler = sigchild;
10160 sact.sa_flags = SA_NOCLDSTOP;
10161 sigaction(SIGCHLD, &sact, NULL);
10163 /* set download dir */
10164 pwd = getpwuid(getuid());
10165 if (pwd == NULL)
10166 errx(1, "invalid user %d", getuid());
10167 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10169 /* compile buffer command regexes */
10170 buffercmd_init();
10172 /* set default string settings */
10173 home = g_strdup("https://www.cyphertite.com");
10174 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10175 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10176 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10177 cmd_font_name = g_strdup("monospace normal 9");
10178 oops_font_name = g_strdup("monospace normal 9");
10179 statusbar_font_name = g_strdup("monospace normal 9");
10180 tabbar_font_name = g_strdup("monospace normal 9");
10181 statusbar_elems = g_strdup("BP");
10183 /* read config file */
10184 if (strlen(conf) == 0)
10185 snprintf(conf, sizeof conf, "%s/.%s",
10186 pwd->pw_dir, XT_CONF_FILE);
10187 config_parse(conf, 0);
10189 /* init fonts */
10190 cmd_font = pango_font_description_from_string(cmd_font_name);
10191 oops_font = pango_font_description_from_string(oops_font_name);
10192 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10193 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10195 /* working directory */
10196 if (strlen(work_dir) == 0)
10197 snprintf(work_dir, sizeof work_dir, "%s/%s",
10198 pwd->pw_dir, XT_DIR);
10199 xxx_dir(work_dir);
10201 /* icon cache dir */
10202 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10203 xxx_dir(cache_dir);
10205 /* certs dir */
10206 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10207 xxx_dir(certs_dir);
10209 /* sessions dir */
10210 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10211 work_dir, XT_SESSIONS_DIR);
10212 xxx_dir(sessions_dir);
10214 /* runtime settings that can override config file */
10215 if (runtime_settings[0] != '\0')
10216 config_parse(runtime_settings, 1);
10218 /* download dir */
10219 if (!strcmp(download_dir, pwd->pw_dir))
10220 strlcat(download_dir, "/downloads", sizeof download_dir);
10221 xxx_dir(download_dir);
10223 /* favorites file */
10224 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10225 if (stat(file, &sb)) {
10226 warnx("favorites file doesn't exist, creating it");
10227 if ((f = fopen(file, "w")) == NULL)
10228 err(1, "favorites");
10229 fclose(f);
10232 /* quickmarks file */
10233 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10234 if (stat(file, &sb)) {
10235 warnx("quickmarks file doesn't exist, creating it");
10236 if ((f = fopen(file, "w")) == NULL)
10237 err(1, "quickmarks");
10238 fclose(f);
10241 /* search history */
10242 if (history_autosave) {
10243 snprintf(search_file, sizeof search_file, "%s/%s",
10244 work_dir, XT_SEARCH_FILE);
10245 if (stat(search_file, &sb)) {
10246 warnx("search history file doesn't exist, creating it");
10247 if ((f = fopen(search_file, "w")) == NULL)
10248 err(1, "search_history");
10249 fclose(f);
10251 history_read(&shl, search_file, &search_history_count);
10254 /* command history */
10255 if (history_autosave) {
10256 snprintf(command_file, sizeof command_file, "%s/%s",
10257 work_dir, XT_COMMAND_FILE);
10258 if (stat(command_file, &sb)) {
10259 warnx("command history file doesn't exist, creating it");
10260 if ((f = fopen(command_file, "w")) == NULL)
10261 err(1, "command_history");
10262 fclose(f);
10264 history_read(&chl, command_file, &cmd_history_count);
10267 /* cookies */
10268 session = webkit_get_default_session();
10269 setup_cookies();
10271 /* certs */
10272 if (ssl_ca_file) {
10273 if (stat(ssl_ca_file, &sb)) {
10274 warnx("no CA file: %s", ssl_ca_file);
10275 g_free(ssl_ca_file);
10276 ssl_ca_file = NULL;
10277 } else
10278 g_object_set(session,
10279 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10280 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10281 (void *)NULL);
10284 /* guess_search regex */
10285 if (url_regex == NULL)
10286 url_regex = g_strdup(XT_URL_REGEX);
10287 if (url_regex)
10288 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10289 startpage_add("invalid url regex %s", url_regex);
10291 /* proxy */
10292 env_proxy = getenv("http_proxy");
10293 if (env_proxy)
10294 setup_proxy(env_proxy);
10295 else
10296 setup_proxy(http_proxy);
10298 if (opte) {
10299 send_cmd_to_socket(argv[0]);
10300 exit(0);
10303 /* set some connection parameters */
10304 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10305 g_object_set(session, "max-conns-per-host", max_host_connections,
10306 (char *)NULL);
10308 /* see if there is already an xxxterm running */
10309 if (single_instance && is_running()) {
10310 optn = 1;
10311 warnx("already running");
10314 if (optn) {
10315 while (argc) {
10316 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10317 send_cmd_to_socket(cmd);
10318 if (cmd)
10319 g_free(cmd);
10321 argc--;
10322 argv++;
10324 exit(0);
10327 /* uri completion */
10328 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10330 /* buffers */
10331 buffers_store = gtk_list_store_new
10332 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10334 qmarks_load();
10336 /* go graphical */
10337 create_canvas();
10338 notebook_tab_set_visibility();
10340 if (save_global_history)
10341 restore_global_history();
10343 /* restore session list */
10344 restore_sessions_list();
10346 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10347 restore_saved_tabs();
10348 else {
10349 a.s = named_session;
10350 a.i = XT_SES_DONOTHING;
10351 open_tabs(NULL, &a);
10354 /* see if we have an exception */
10355 if (!TAILQ_EMPTY(&spl)) {
10356 create_new_tab("about:startpage", NULL, focus, -1);
10357 focus = 0;
10360 while (argc) {
10361 create_new_tab(argv[0], NULL, focus, -1);
10362 focus = 0;
10364 argc--;
10365 argv++;
10368 if (TAILQ_EMPTY(&tabs))
10369 create_new_tab(home, NULL, 1, -1);
10371 if (enable_socket)
10372 if ((s = build_socket()) != -1) {
10373 channel = g_io_channel_unix_new(s);
10374 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10377 gtk_main();
10379 gnutls_global_deinit();
10381 if (url_regex)
10382 regfree(&url_re);
10384 return (0);