Abort commands when there are no submatches in regex; had to hand roll
[xxxterm.git] / xxxterm.c
blob6eeb630c80f83b78d018e62f370f072eb39929a6
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pthread.h>
35 #include <pwd.h>
36 #include <regex.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
49 #include <libutil.h>
50 #include "freebsd/util.h"
51 #include <sys/tree.h>
52 #else /* OpenBSD */
53 #include <util.h>
54 #include <sys/tree.h>
55 #endif
56 #include <sys/queue.h>
57 #include <sys/resource.h>
58 #include <sys/socket.h>
59 #include <sys/stat.h>
60 #include <sys/time.h>
61 #include <sys/un.h>
63 #include <gtk/gtk.h>
64 #include <gdk/gdkkeysyms.h>
66 #if GTK_CHECK_VERSION(3,0,0)
67 /* we still use GDK_* instead of GDK_KEY_* */
68 #include <gdk/gdkkeysyms-compat.h>
69 #endif
71 #include <webkit/webkit.h>
72 #include <libsoup/soup.h>
73 #include <gnutls/gnutls.h>
74 #include <JavaScriptCore/JavaScript.h>
75 #include <gnutls/x509.h>
77 #include "javascript.h"
80 javascript.h borrowed from vimprobable2 under the following license:
82 Copyright (c) 2009 Leon Winter
83 Copyright (c) 2009 Hannes Schueller
84 Copyright (c) 2009 Matto Fransen
86 Permission is hereby granted, free of charge, to any person obtaining a copy
87 of this software and associated documentation files (the "Software"), to deal
88 in the Software without restriction, including without limitation the rights
89 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90 copies of the Software, and to permit persons to whom the Software is
91 furnished to do so, subject to the following conditions:
93 The above copyright notice and this permission notice shall be included in
94 all copies or substantial portions of the Software.
96 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
102 THE SOFTWARE.
105 static char *version = "$xxxterm$";
107 /* hooked functions */
108 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
109 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
110 SoupCookie *);
112 /*#define XT_DEBUG*/
113 #ifdef XT_DEBUG
114 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
115 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
116 #define XT_D_MOVE 0x0001
117 #define XT_D_KEY 0x0002
118 #define XT_D_TAB 0x0004
119 #define XT_D_URL 0x0008
120 #define XT_D_CMD 0x0010
121 #define XT_D_NAV 0x0020
122 #define XT_D_DOWNLOAD 0x0040
123 #define XT_D_CONFIG 0x0080
124 #define XT_D_JS 0x0100
125 #define XT_D_FAVORITE 0x0200
126 #define XT_D_PRINTING 0x0400
127 #define XT_D_COOKIE 0x0800
128 #define XT_D_KEYBINDING 0x1000
129 #define XT_D_CLIP 0x2000
130 #define XT_D_BUFFERCMD 0x4000
131 u_int32_t swm_debug = 0
132 | XT_D_MOVE
133 | XT_D_KEY
134 | XT_D_TAB
135 | XT_D_URL
136 | XT_D_CMD
137 | XT_D_NAV
138 | XT_D_DOWNLOAD
139 | XT_D_CONFIG
140 | XT_D_JS
141 | XT_D_FAVORITE
142 | XT_D_PRINTING
143 | XT_D_COOKIE
144 | XT_D_KEYBINDING
145 | XT_D_CLIP
146 | XT_D_BUFFERCMD
148 #else
149 #define DPRINTF(x...)
150 #define DNPRINTF(n,x...)
151 #endif
153 #define LENGTH(x) (sizeof x / sizeof x[0])
154 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
155 ~(GDK_BUTTON1_MASK) & \
156 ~(GDK_BUTTON2_MASK) & \
157 ~(GDK_BUTTON3_MASK) & \
158 ~(GDK_BUTTON4_MASK) & \
159 ~(GDK_BUTTON5_MASK))
161 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
163 char *icons[] = {
164 "xxxtermicon16.png",
165 "xxxtermicon32.png",
166 "xxxtermicon48.png",
167 "xxxtermicon64.png",
168 "xxxtermicon128.png"
171 struct tab {
172 TAILQ_ENTRY(tab) entry;
173 GtkWidget *vbox;
174 GtkWidget *tab_content;
175 struct {
176 GtkWidget *label;
177 GtkWidget *eventbox;
178 GtkWidget *box;
179 GtkWidget *sep;
180 } tab_elems;
181 GtkWidget *label;
182 GtkWidget *spinner;
183 GtkWidget *uri_entry;
184 GtkWidget *search_entry;
185 GtkWidget *toolbar;
186 GtkWidget *browser_win;
187 GtkWidget *statusbar_box;
188 struct {
189 GtkWidget *statusbar;
190 GtkWidget *buffercmd;
191 GtkWidget *zoom;
192 GtkWidget *position;
193 } sbe;
194 GtkWidget *cmd;
195 GtkWidget *buffers;
196 GtkWidget *oops;
197 GtkWidget *backward;
198 GtkWidget *forward;
199 GtkWidget *stop;
200 GtkWidget *gohome;
201 GtkWidget *js_toggle;
202 GtkEntryCompletion *completion;
203 guint tab_id;
204 WebKitWebView *wv;
206 WebKitWebHistoryItem *item;
207 WebKitWebBackForwardList *bfl;
209 /* favicon */
210 WebKitNetworkRequest *icon_request;
211 WebKitDownload *icon_download;
212 gchar *icon_dest_uri;
214 /* adjustments for browser */
215 GtkScrollbar *sb_h;
216 GtkScrollbar *sb_v;
217 GtkAdjustment *adjust_h;
218 GtkAdjustment *adjust_v;
220 /* flags */
221 int focus_wv;
222 int ctrl_click;
223 gchar *status;
224 int xtp_meaning; /* identifies dls/favorites */
225 gchar *tmp_uri;
227 /* hints */
228 int hints_on;
229 int hint_mode;
230 #define XT_HINT_NONE (0)
231 #define XT_HINT_NUMERICAL (1)
232 #define XT_HINT_ALPHANUM (2)
233 char hint_buf[128];
234 char hint_num[128];
236 /* custom stylesheet */
237 int styled;
238 char *stylesheet;
240 /* search */
241 char *search_text;
242 int search_forward;
244 /* settings */
245 WebKitWebSettings *settings;
246 gchar *user_agent;
248 /* marks */
249 double mark[XT_NOMARKS];
251 TAILQ_HEAD(tab_list, tab);
253 struct history {
254 RB_ENTRY(history) entry;
255 const gchar *uri;
256 const gchar *title;
258 RB_HEAD(history_list, history);
260 struct download {
261 RB_ENTRY(download) entry;
262 int id;
263 WebKitDownload *download;
264 struct tab *tab;
266 RB_HEAD(download_list, download);
268 struct domain {
269 RB_ENTRY(domain) entry;
270 gchar *d;
271 int handy; /* app use */
273 RB_HEAD(domain_list, domain);
275 struct undo {
276 TAILQ_ENTRY(undo) entry;
277 gchar *uri;
278 GList *history;
279 int back; /* Keeps track of how many back
280 * history items there are. */
282 TAILQ_HEAD(undo_tailq, undo);
284 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
285 int next_download_id = 1;
287 struct karg {
288 int i;
289 char *s;
290 int precount;
293 /* defines */
294 #define XT_NAME ("XXXTerm")
295 #define XT_DIR (".xxxterm")
296 #define XT_CACHE_DIR ("cache")
297 #define XT_CERT_DIR ("certs/")
298 #define XT_SESSIONS_DIR ("sessions/")
299 #define XT_CONF_FILE ("xxxterm.conf")
300 #define XT_FAVS_FILE ("favorites")
301 #define XT_QMARKS_FILE ("quickmarks")
302 #define XT_SAVED_TABS_FILE ("main_session")
303 #define XT_RESTART_TABS_FILE ("restart_tabs")
304 #define XT_SOCKET_FILE ("socket")
305 #define XT_HISTORY_FILE ("history")
306 #define XT_REJECT_FILE ("rejected.txt")
307 #define XT_COOKIE_FILE ("cookies.txt")
308 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
309 #define XT_CB_HANDLED (TRUE)
310 #define XT_CB_PASSTHROUGH (FALSE)
311 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
312 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
313 #define XT_DLMAN_REFRESH "10"
314 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
315 "td{overflow: hidden;" \
316 " padding: 2px 2px 2px 2px;" \
317 " border: 1px solid black;" \
318 " vertical-align:top;" \
319 " word-wrap: break-word}\n" \
320 "tr:hover{background: #ffff99}\n" \
321 "th{background-color: #cccccc;" \
322 " border: 1px solid black}\n" \
323 "table{width: 100%%;" \
324 " border: 1px black solid;" \
325 " border-collapse:collapse}\n" \
326 ".progress-outer{" \
327 "border: 1px solid black;" \
328 " height: 8px;" \
329 " width: 90%%}\n" \
330 ".progress-inner{float: left;" \
331 " height: 8px;" \
332 " background: green}\n" \
333 ".dlstatus{font-size: small;" \
334 " text-align: center}\n" \
335 "</style>\n"
336 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
337 #define XT_MAX_UNDO_CLOSE_TAB (32)
338 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
339 #define XT_PRINT_EXTRA_MARGIN 10
340 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
342 /* colors */
343 #define XT_COLOR_RED "#cc0000"
344 #define XT_COLOR_YELLOW "#ffff66"
345 #define XT_COLOR_BLUE "lightblue"
346 #define XT_COLOR_GREEN "#99ff66"
347 #define XT_COLOR_WHITE "white"
348 #define XT_COLOR_BLACK "black"
350 #define XT_COLOR_CT_BACKGROUND "#000000"
351 #define XT_COLOR_CT_INACTIVE "#dddddd"
352 #define XT_COLOR_CT_ACTIVE "#bbbb00"
353 #define XT_COLOR_CT_SEPARATOR "#555555"
355 #define XT_COLOR_SB_SEPARATOR "#555555"
357 #define XT_PROTO_DELIM "://"
360 * xxxterm "protocol" (xtp)
361 * We use this for managing stuff like downloads and favorites. They
362 * make magical HTML pages in memory which have xxxt:// links in order
363 * to communicate with xxxterm's internals. These links take the format:
364 * xxxt://class/session_key/action/arg
366 * Don't begin xtp class/actions as 0. atoi returns that on error.
368 * Typically we have not put addition of items in this framework, as
369 * adding items is either done via an ex-command or via a keybinding instead.
372 #define XT_XTP_STR "xxxt://"
374 /* XTP classes (xxxt://<class>) */
375 #define XT_XTP_INVALID 0 /* invalid */
376 #define XT_XTP_DL 1 /* downloads */
377 #define XT_XTP_HL 2 /* history */
378 #define XT_XTP_CL 3 /* cookies */
379 #define XT_XTP_FL 4 /* favorites */
381 /* XTP download actions */
382 #define XT_XTP_DL_LIST 1
383 #define XT_XTP_DL_CANCEL 2
384 #define XT_XTP_DL_REMOVE 3
386 /* XTP history actions */
387 #define XT_XTP_HL_LIST 1
388 #define XT_XTP_HL_REMOVE 2
390 /* XTP cookie actions */
391 #define XT_XTP_CL_LIST 1
392 #define XT_XTP_CL_REMOVE 2
394 /* XTP cookie actions */
395 #define XT_XTP_FL_LIST 1
396 #define XT_XTP_FL_REMOVE 2
398 /* actions */
399 #define XT_MOVE_INVALID (0)
400 #define XT_MOVE_DOWN (1)
401 #define XT_MOVE_UP (2)
402 #define XT_MOVE_BOTTOM (3)
403 #define XT_MOVE_TOP (4)
404 #define XT_MOVE_PAGEDOWN (5)
405 #define XT_MOVE_PAGEUP (6)
406 #define XT_MOVE_HALFDOWN (7)
407 #define XT_MOVE_HALFUP (8)
408 #define XT_MOVE_LEFT (9)
409 #define XT_MOVE_FARLEFT (10)
410 #define XT_MOVE_RIGHT (11)
411 #define XT_MOVE_FARRIGHT (12)
412 #define XT_MOVE_PERCENT (13)
414 #define XT_QMARK_SET (0)
415 #define XT_QMARK_OPEN (1)
416 #define XT_QMARK_TAB (2)
418 #define XT_MARK_SET (0)
419 #define XT_MARK_GOTO (1)
421 #define XT_TAB_LAST (-4)
422 #define XT_TAB_FIRST (-3)
423 #define XT_TAB_PREV (-2)
424 #define XT_TAB_NEXT (-1)
425 #define XT_TAB_INVALID (0)
426 #define XT_TAB_NEW (1)
427 #define XT_TAB_DELETE (2)
428 #define XT_TAB_DELQUIT (3)
429 #define XT_TAB_OPEN (4)
430 #define XT_TAB_UNDO_CLOSE (5)
431 #define XT_TAB_SHOW (6)
432 #define XT_TAB_HIDE (7)
433 #define XT_TAB_NEXTSTYLE (8)
435 #define XT_NAV_INVALID (0)
436 #define XT_NAV_BACK (1)
437 #define XT_NAV_FORWARD (2)
438 #define XT_NAV_RELOAD (3)
440 #define XT_FOCUS_INVALID (0)
441 #define XT_FOCUS_URI (1)
442 #define XT_FOCUS_SEARCH (2)
444 #define XT_SEARCH_INVALID (0)
445 #define XT_SEARCH_NEXT (1)
446 #define XT_SEARCH_PREV (2)
448 #define XT_PASTE_CURRENT_TAB (0)
449 #define XT_PASTE_NEW_TAB (1)
451 #define XT_ZOOM_IN (-1)
452 #define XT_ZOOM_OUT (-2)
453 #define XT_ZOOM_NORMAL (100)
455 #define XT_URL_SHOW (1)
456 #define XT_URL_HIDE (2)
458 #define XT_WL_TOGGLE (1<<0)
459 #define XT_WL_ENABLE (1<<1)
460 #define XT_WL_DISABLE (1<<2)
461 #define XT_WL_FQDN (1<<3) /* default */
462 #define XT_WL_TOPLEVEL (1<<4)
463 #define XT_WL_PERSISTENT (1<<5)
464 #define XT_WL_SESSION (1<<6)
465 #define XT_WL_RELOAD (1<<7)
467 #define XT_SHOW (1<<7)
468 #define XT_DELETE (1<<8)
469 #define XT_SAVE (1<<9)
470 #define XT_OPEN (1<<10)
472 #define XT_CMD_OPEN (0)
473 #define XT_CMD_OPEN_CURRENT (1)
474 #define XT_CMD_TABNEW (2)
475 #define XT_CMD_TABNEW_CURRENT (3)
477 #define XT_STATUS_NOTHING (0)
478 #define XT_STATUS_LINK (1)
479 #define XT_STATUS_URI (2)
480 #define XT_STATUS_LOADING (3)
482 #define XT_SES_DONOTHING (0)
483 #define XT_SES_CLOSETABS (1)
485 #define XT_BM_NORMAL (0)
486 #define XT_BM_WHITELIST (1)
487 #define XT_BM_KIOSK (2)
489 #define XT_PREFIX (1<<0)
490 #define XT_USERARG (1<<1)
491 #define XT_URLARG (1<<2)
492 #define XT_INTARG (1<<3)
494 #define XT_TABS_NORMAL 0
495 #define XT_TABS_COMPACT 1
497 #define XT_BUFCMD_SZ (8)
499 /* mime types */
500 struct mime_type {
501 char *mt_type;
502 char *mt_action;
503 int mt_default;
504 int mt_download;
505 TAILQ_ENTRY(mime_type) entry;
507 TAILQ_HEAD(mime_type_list, mime_type);
509 /* uri aliases */
510 struct alias {
511 char *a_name;
512 char *a_uri;
513 TAILQ_ENTRY(alias) entry;
515 TAILQ_HEAD(alias_list, alias);
517 /* settings that require restart */
518 int tabless = 0; /* allow only 1 tab */
519 int enable_socket = 0;
520 int single_instance = 0; /* only allow one xxxterm to run */
521 int fancy_bar = 1; /* fancy toolbar */
522 int browser_mode = XT_BM_NORMAL;
523 int enable_localstorage = 0;
524 char *statusbar_elems = NULL;
526 /* runtime settings */
527 int show_tabs = 1; /* show tabs on notebook */
528 int tab_style = XT_TABS_NORMAL; /* tab bar style */
529 int show_url = 1; /* show url toolbar on notebook */
530 int show_statusbar = 0; /* vimperator style status bar */
531 int ctrl_click_focus = 0; /* ctrl click gets focus */
532 int cookies_enabled = 1; /* enable cookies */
533 int read_only_cookies = 0; /* enable to not write cookies */
534 int enable_scripts = 1;
535 int enable_plugins = 0;
536 gfloat default_zoom_level = 1.0;
537 char default_script[PATH_MAX];
538 int window_height = 768;
539 int window_width = 1024;
540 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
541 int refresh_interval = 10; /* download refresh interval */
542 int enable_cookie_whitelist = 0;
543 int enable_js_whitelist = 0;
544 int session_timeout = 3600; /* cookie session timeout */
545 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
546 char *ssl_ca_file = NULL;
547 char *resource_dir = NULL;
548 gboolean ssl_strict_certs = FALSE;
549 int append_next = 1; /* append tab after current tab */
550 char *home = NULL;
551 char *search_string = NULL;
552 char *http_proxy = NULL;
553 char download_dir[PATH_MAX];
554 char runtime_settings[PATH_MAX]; /* override of settings */
555 int allow_volatile_cookies = 0;
556 int save_global_history = 0; /* save global history to disk */
557 char *user_agent = NULL;
558 int save_rejected_cookies = 0;
559 int session_autosave = 0;
560 int guess_search = 0;
561 int dns_prefetch = FALSE;
562 gint max_connections = 25;
563 gint max_host_connections = 5;
564 gint enable_spell_checking = 0;
565 char *spell_check_languages = NULL;
566 int xterm_workaround = 0;
568 char *cmd_font_name = NULL;
569 char *oops_font_name = NULL;
570 char *statusbar_font_name = NULL;
571 char *tabbar_font_name = NULL;
572 PangoFontDescription *cmd_font;
573 PangoFontDescription *oops_font;
574 PangoFontDescription *statusbar_font;
575 PangoFontDescription *tabbar_font;
576 char *qmarks[XT_NOMARKS];
578 int btn_down; /* M1 down in any wv */
580 struct settings;
581 struct key_binding;
582 int set_browser_mode(struct settings *, char *);
583 int set_cookie_policy(struct settings *, char *);
584 int set_download_dir(struct settings *, char *);
585 int set_default_script(struct settings *, char *);
586 int set_runtime_dir(struct settings *, char *);
587 int set_tab_style(struct settings *, char *);
588 int set_work_dir(struct settings *, char *);
589 int add_alias(struct settings *, char *);
590 int add_mime_type(struct settings *, char *);
591 int add_cookie_wl(struct settings *, char *);
592 int add_js_wl(struct settings *, char *);
593 int add_kb(struct settings *, char *);
594 void button_set_stockid(GtkWidget *, char *);
595 GtkWidget * create_button(char *, char *, int);
597 char *get_browser_mode(struct settings *);
598 char *get_cookie_policy(struct settings *);
599 char *get_download_dir(struct settings *);
600 char *get_default_script(struct settings *);
601 char *get_runtime_dir(struct settings *);
602 char *get_tab_style(struct settings *);
603 char *get_work_dir(struct settings *);
605 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
606 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
607 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
608 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
609 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
611 void recalc_tabs(void);
612 void recolor_compact_tabs(void);
613 void set_current_tab(int page_num);
614 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
615 void marks_clear(struct tab *t);
617 struct special {
618 int (*set)(struct settings *, char *);
619 char *(*get)(struct settings *);
620 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
623 struct special s_browser_mode = {
624 set_browser_mode,
625 get_browser_mode,
626 NULL
629 struct special s_cookie = {
630 set_cookie_policy,
631 get_cookie_policy,
632 NULL
635 struct special s_alias = {
636 add_alias,
637 NULL,
638 walk_alias
641 struct special s_mime = {
642 add_mime_type,
643 NULL,
644 walk_mime_type
647 struct special s_js = {
648 add_js_wl,
649 NULL,
650 walk_js_wl
653 struct special s_kb = {
654 add_kb,
655 NULL,
656 walk_kb
659 struct special s_cookie_wl = {
660 add_cookie_wl,
661 NULL,
662 walk_cookie_wl
665 struct special s_default_script = {
666 set_default_script,
667 get_default_script,
668 NULL
671 struct special s_download_dir = {
672 set_download_dir,
673 get_download_dir,
674 NULL
677 struct special s_work_dir = {
678 set_work_dir,
679 get_work_dir,
680 NULL
683 struct special s_tab_style = {
684 set_tab_style,
685 get_tab_style,
686 NULL
689 struct settings {
690 char *name;
691 int type;
692 #define XT_S_INVALID (0)
693 #define XT_S_INT (1)
694 #define XT_S_STR (2)
695 #define XT_S_FLOAT (3)
696 uint32_t flags;
697 #define XT_SF_RESTART (1<<0)
698 #define XT_SF_RUNTIME (1<<1)
699 int *ival;
700 char **sval;
701 struct special *s;
702 gfloat *fval;
703 } rs[] = {
704 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
705 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
706 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
707 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
708 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
709 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
710 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
711 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
712 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
713 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
714 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
715 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
716 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
717 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
718 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
719 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
720 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
721 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
722 { "home", XT_S_STR, 0, NULL, &home, NULL },
723 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
724 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
725 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
726 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
727 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
728 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
729 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
730 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
731 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
732 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
733 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
734 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
735 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
736 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
737 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
738 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
739 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
740 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
741 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
742 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
743 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
744 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
745 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
746 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
747 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
748 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
750 /* font settings */
751 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
752 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
753 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
754 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
756 /* runtime settings */
757 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
758 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
759 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
760 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
761 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
764 int about(struct tab *, struct karg *);
765 int blank(struct tab *, struct karg *);
766 int ca_cmd(struct tab *, struct karg *);
767 int cookie_show_wl(struct tab *, struct karg *);
768 int js_show_wl(struct tab *, struct karg *);
769 int help(struct tab *, struct karg *);
770 int set(struct tab *, struct karg *);
771 int stats(struct tab *, struct karg *);
772 int marco(struct tab *, struct karg *);
773 const char * marco_message(int *);
774 int xtp_page_cl(struct tab *, struct karg *);
775 int xtp_page_dl(struct tab *, struct karg *);
776 int xtp_page_fl(struct tab *, struct karg *);
777 int xtp_page_hl(struct tab *, struct karg *);
778 void xt_icon_from_file(struct tab *, char *);
779 const gchar *get_uri(struct tab *);
780 const gchar *get_title(struct tab *, bool);
782 #define XT_URI_ABOUT ("about:")
783 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
784 #define XT_URI_ABOUT_ABOUT ("about")
785 #define XT_URI_ABOUT_BLANK ("blank")
786 #define XT_URI_ABOUT_CERTS ("certs")
787 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
788 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
789 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
790 #define XT_URI_ABOUT_FAVORITES ("favorites")
791 #define XT_URI_ABOUT_HELP ("help")
792 #define XT_URI_ABOUT_HISTORY ("history")
793 #define XT_URI_ABOUT_JSWL ("jswl")
794 #define XT_URI_ABOUT_SET ("set")
795 #define XT_URI_ABOUT_STATS ("stats")
796 #define XT_URI_ABOUT_MARCO ("marco")
798 struct about_type {
799 char *name;
800 int (*func)(struct tab *, struct karg *);
801 } about_list[] = {
802 { XT_URI_ABOUT_ABOUT, about },
803 { XT_URI_ABOUT_BLANK, blank },
804 { XT_URI_ABOUT_CERTS, ca_cmd },
805 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
806 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
807 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
808 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
809 { XT_URI_ABOUT_HELP, help },
810 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
811 { XT_URI_ABOUT_JSWL, js_show_wl },
812 { XT_URI_ABOUT_SET, set },
813 { XT_URI_ABOUT_STATS, stats },
814 { XT_URI_ABOUT_MARCO, marco },
817 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
818 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
819 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
820 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
821 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
822 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
823 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
825 /* globals */
826 extern char *__progname;
827 char **start_argv;
828 struct passwd *pwd;
829 GtkWidget *main_window;
830 GtkNotebook *notebook;
831 GtkWidget *tab_bar;
832 GtkWidget *arrow, *abtn;
833 struct tab_list tabs;
834 struct history_list hl;
835 struct download_list downloads;
836 struct domain_list c_wl;
837 struct domain_list js_wl;
838 struct undo_tailq undos;
839 struct keybinding_list kbl;
840 int undo_count;
841 int updating_dl_tabs = 0;
842 int updating_hl_tabs = 0;
843 int updating_cl_tabs = 0;
844 int updating_fl_tabs = 0;
845 char *global_search;
846 uint64_t blocked_cookies = 0;
847 char named_session[PATH_MAX];
848 int icon_size_map(int);
850 GtkListStore *completion_model;
851 void completion_add(struct tab *);
852 void completion_add_uri(const gchar *);
853 GtkListStore *buffers_store;
854 void xxx_dir(char *);
856 /* marks and quickmarks array storage.
857 * first a-z, then A-Z, then 0-9 */
858 char
859 indextomark(int i)
861 if (i < 0)
862 return 0;
864 if (i >= 0 && i <= 'z' - 'a')
865 return 'a' + i;
867 i -= 'z' - 'a' + 1;
868 if (i >= 0 && i <= 'Z' - 'A')
869 return 'A' + i;
871 i -= 'Z' - 'A' + 1;
872 if (i >= 10)
873 return 0;
875 return i + '0';
879 marktoindex(char m)
881 int ret = 0;
883 if (m >= 'a' && m <= 'z')
884 return ret + m - 'a';
886 ret += 'z' - 'a' + 1;
887 if (m >= 'A' && m <= 'Z')
888 return ret + m - 'A';
890 ret += 'Z' - 'A' + 1;
891 if (m >= '0' && m <= '9')
892 return ret + m - '0';
894 return -1;
898 void
899 sigchild(int sig)
901 int saved_errno, status;
902 pid_t pid;
904 saved_errno = errno;
906 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
907 if (pid == -1) {
908 if (errno == EINTR)
909 continue;
910 if (errno != ECHILD) {
912 clog_warn("sigchild: waitpid:");
915 break;
918 if (WIFEXITED(status)) {
919 if (WEXITSTATUS(status) != 0) {
921 clog_warnx("sigchild: child exit status: %d",
922 WEXITSTATUS(status));
925 } else {
927 clog_warnx("sigchild: child is terminated abnormally");
932 errno = saved_errno;
936 is_g_object_setting(GObject *o, char *str)
938 guint n_props = 0, i;
939 GParamSpec **proplist;
941 if (! G_IS_OBJECT(o))
942 return (0);
944 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
945 &n_props);
947 for (i=0; i < n_props; i++) {
948 if (! strcmp(proplist[i]->name, str))
949 return (1);
951 return (0);
954 gchar *
955 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
957 gchar *r;
959 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
960 "<head>\n"
961 "<title>%s</title>\n"
962 "%s"
963 "%s"
964 "</head>\n"
965 "<body>\n"
966 "<h1>%s</h1>\n"
967 "%s\n</body>\n"
968 "</html>",
969 title,
970 addstyles ? XT_PAGE_STYLE : "",
971 head,
972 title,
973 body);
975 return r;
979 * Display a web page from a HTML string in memory, rather than from a URL
981 void
982 load_webkit_string(struct tab *t, const char *str, gchar *title)
984 char file[PATH_MAX];
985 int i;
987 /* we set this to indicate we want to manually do navaction */
988 if (t->bfl)
989 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
991 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
992 if (title) {
993 /* set t->xtp_meaning */
994 for (i = 0; i < LENGTH(about_list); i++)
995 if (!strcmp(title, about_list[i].name)) {
996 t->xtp_meaning = i;
997 break;
1000 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1001 #if GTK_CHECK_VERSION(2, 20, 0)
1002 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1003 gtk_widget_hide(t->spinner);
1004 #endif
1005 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1006 xt_icon_from_file(t, file);
1010 struct tab *
1011 get_current_tab(void)
1013 struct tab *t;
1015 TAILQ_FOREACH(t, &tabs, entry) {
1016 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1017 return (t);
1020 warnx("%s: no current tab", __func__);
1022 return (NULL);
1025 void
1026 set_status(struct tab *t, gchar *s, int status)
1028 gchar *type = NULL;
1030 if (s == NULL)
1031 return;
1033 switch (status) {
1034 case XT_STATUS_LOADING:
1035 type = g_strdup_printf("Loading: %s", s);
1036 s = type;
1037 break;
1038 case XT_STATUS_LINK:
1039 type = g_strdup_printf("Link: %s", s);
1040 if (!t->status)
1041 t->status = g_strdup(gtk_entry_get_text(
1042 GTK_ENTRY(t->sbe.statusbar)));
1043 s = type;
1044 break;
1045 case XT_STATUS_URI:
1046 type = g_strdup_printf("%s", s);
1047 if (!t->status) {
1048 t->status = g_strdup(type);
1050 s = type;
1051 if (!t->status)
1052 t->status = g_strdup(s);
1053 break;
1054 case XT_STATUS_NOTHING:
1055 /* FALL THROUGH */
1056 default:
1057 break;
1059 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1060 if (type)
1061 g_free(type);
1064 void
1065 hide_cmd(struct tab *t)
1067 gtk_widget_hide(t->cmd);
1070 void
1071 show_cmd(struct tab *t)
1073 gtk_widget_hide(t->oops);
1074 gtk_widget_show(t->cmd);
1077 void
1078 hide_buffers(struct tab *t)
1080 gtk_widget_hide(t->buffers);
1081 gtk_list_store_clear(buffers_store);
1084 enum {
1085 COL_ID = 0,
1086 COL_TITLE,
1087 NUM_COLS
1091 sort_tabs_by_page_num(struct tab ***stabs)
1093 int num_tabs = 0;
1094 struct tab *t;
1096 num_tabs = gtk_notebook_get_n_pages(notebook);
1098 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1100 TAILQ_FOREACH(t, &tabs, entry)
1101 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1103 return (num_tabs);
1106 void
1107 buffers_make_list(void)
1109 int i, num_tabs;
1110 const gchar *title = NULL;
1111 GtkTreeIter iter;
1112 struct tab **stabs = NULL;
1114 num_tabs = sort_tabs_by_page_num(&stabs);
1116 for (i = 0; i < num_tabs; i++)
1117 if (stabs[i]) {
1118 gtk_list_store_append(buffers_store, &iter);
1119 title = get_title(stabs[i], FALSE);
1120 gtk_list_store_set(buffers_store, &iter,
1121 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1122 * rather than 0. */
1123 COL_TITLE, title,
1124 -1);
1127 g_free(stabs);
1130 void
1131 show_buffers(struct tab *t)
1133 buffers_make_list();
1134 gtk_widget_show(t->buffers);
1135 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1138 void
1139 toggle_buffers(struct tab *t)
1141 if (gtk_widget_get_visible(t->buffers))
1142 hide_buffers(t);
1143 else
1144 show_buffers(t);
1148 buffers(struct tab *t, struct karg *args)
1150 show_buffers(t);
1152 return (0);
1155 void
1156 hide_oops(struct tab *t)
1158 gtk_widget_hide(t->oops);
1161 void
1162 show_oops(struct tab *at, const char *fmt, ...)
1164 va_list ap;
1165 char *msg;
1166 struct tab *t = NULL;
1168 if (fmt == NULL)
1169 return;
1171 if (at == NULL) {
1172 if ((t = get_current_tab()) == NULL)
1173 return;
1174 } else
1175 t = at;
1177 va_start(ap, fmt);
1178 if (vasprintf(&msg, fmt, ap) == -1)
1179 errx(1, "show_oops failed");
1180 va_end(ap);
1182 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1183 gtk_widget_hide(t->cmd);
1184 gtk_widget_show(t->oops);
1187 char *
1188 get_as_string(struct settings *s)
1190 char *r = NULL;
1192 if (s == NULL)
1193 return (NULL);
1195 if (s->s) {
1196 if (s->s->get)
1197 r = s->s->get(s);
1198 else
1199 warnx("get_as_string skip %s\n", s->name);
1200 } else if (s->type == XT_S_INT)
1201 r = g_strdup_printf("%d", *s->ival);
1202 else if (s->type == XT_S_STR)
1203 r = g_strdup(*s->sval);
1204 else if (s->type == XT_S_FLOAT)
1205 r = g_strdup_printf("%f", *s->fval);
1206 else
1207 r = g_strdup_printf("INVALID TYPE");
1209 return (r);
1212 void
1213 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1215 int i;
1216 char *s;
1218 for (i = 0; i < LENGTH(rs); i++) {
1219 if (rs[i].s && rs[i].s->walk)
1220 rs[i].s->walk(&rs[i], cb, cb_args);
1221 else {
1222 s = get_as_string(&rs[i]);
1223 cb(&rs[i], s, cb_args);
1224 g_free(s);
1230 set_browser_mode(struct settings *s, char *val)
1232 if (!strcmp(val, "whitelist")) {
1233 browser_mode = XT_BM_WHITELIST;
1234 allow_volatile_cookies = 0;
1235 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1236 cookies_enabled = 1;
1237 enable_cookie_whitelist = 1;
1238 read_only_cookies = 0;
1239 save_rejected_cookies = 0;
1240 session_timeout = 3600;
1241 enable_scripts = 0;
1242 enable_js_whitelist = 1;
1243 enable_localstorage = 0;
1244 } else if (!strcmp(val, "normal")) {
1245 browser_mode = XT_BM_NORMAL;
1246 allow_volatile_cookies = 0;
1247 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1248 cookies_enabled = 1;
1249 enable_cookie_whitelist = 0;
1250 read_only_cookies = 0;
1251 save_rejected_cookies = 0;
1252 session_timeout = 3600;
1253 enable_scripts = 1;
1254 enable_js_whitelist = 0;
1255 enable_localstorage = 1;
1256 } else if (!strcmp(val, "kiosk")) {
1257 browser_mode = XT_BM_KIOSK;
1258 allow_volatile_cookies = 0;
1259 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1260 cookies_enabled = 1;
1261 enable_cookie_whitelist = 0;
1262 read_only_cookies = 0;
1263 save_rejected_cookies = 0;
1264 session_timeout = 3600;
1265 enable_scripts = 1;
1266 enable_js_whitelist = 0;
1267 enable_localstorage = 1;
1268 show_tabs = 0;
1269 tabless = 1;
1270 } else
1271 return (1);
1273 return (0);
1276 char *
1277 get_browser_mode(struct settings *s)
1279 char *r = NULL;
1281 if (browser_mode == XT_BM_WHITELIST)
1282 r = g_strdup("whitelist");
1283 else if (browser_mode == XT_BM_NORMAL)
1284 r = g_strdup("normal");
1285 else if (browser_mode == XT_BM_KIOSK)
1286 r = g_strdup("kiosk");
1287 else
1288 return (NULL);
1290 return (r);
1294 set_cookie_policy(struct settings *s, char *val)
1296 if (!strcmp(val, "no3rdparty"))
1297 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1298 else if (!strcmp(val, "accept"))
1299 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1300 else if (!strcmp(val, "reject"))
1301 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1302 else
1303 return (1);
1305 return (0);
1308 char *
1309 get_cookie_policy(struct settings *s)
1311 char *r = NULL;
1313 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1314 r = g_strdup("no3rdparty");
1315 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1316 r = g_strdup("accept");
1317 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1318 r = g_strdup("reject");
1319 else
1320 return (NULL);
1322 return (r);
1325 char *
1326 get_default_script(struct settings *s)
1328 if (default_script[0] == '\0')
1329 return (0);
1330 return (g_strdup(default_script));
1334 set_default_script(struct settings *s, char *val)
1336 if (val[0] == '~')
1337 snprintf(default_script, sizeof default_script, "%s/%s",
1338 pwd->pw_dir, &val[1]);
1339 else
1340 strlcpy(default_script, val, sizeof default_script);
1342 return (0);
1345 char *
1346 get_download_dir(struct settings *s)
1348 if (download_dir[0] == '\0')
1349 return (0);
1350 return (g_strdup(download_dir));
1354 set_download_dir(struct settings *s, char *val)
1356 if (val[0] == '~')
1357 snprintf(download_dir, sizeof download_dir, "%s/%s",
1358 pwd->pw_dir, &val[1]);
1359 else
1360 strlcpy(download_dir, val, sizeof download_dir);
1362 return (0);
1366 * Session IDs.
1367 * We use these to prevent people putting xxxt:// URLs on
1368 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1370 #define XT_XTP_SES_KEY_SZ 8
1371 #define XT_XTP_SES_KEY_HEX_FMT \
1372 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1373 char *dl_session_key; /* downloads */
1374 char *hl_session_key; /* history list */
1375 char *cl_session_key; /* cookie list */
1376 char *fl_session_key; /* favorites list */
1378 char work_dir[PATH_MAX];
1379 char certs_dir[PATH_MAX];
1380 char cache_dir[PATH_MAX];
1381 char sessions_dir[PATH_MAX];
1382 char cookie_file[PATH_MAX];
1383 SoupURI *proxy_uri = NULL;
1384 SoupSession *session;
1385 SoupCookieJar *s_cookiejar;
1386 SoupCookieJar *p_cookiejar;
1387 char rc_fname[PATH_MAX];
1389 struct mime_type_list mtl;
1390 struct alias_list aliases;
1392 /* protos */
1393 struct tab *create_new_tab(char *, struct undo *, int, int);
1394 void delete_tab(struct tab *);
1395 void setzoom_webkit(struct tab *, int);
1396 int run_script(struct tab *, char *);
1397 int download_rb_cmp(struct download *, struct download *);
1398 gboolean cmd_execute(struct tab *t, char *str);
1401 history_rb_cmp(struct history *h1, struct history *h2)
1403 return (strcmp(h1->uri, h2->uri));
1405 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1408 domain_rb_cmp(struct domain *d1, struct domain *d2)
1410 return (strcmp(d1->d, d2->d));
1412 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1414 char *
1415 get_work_dir(struct settings *s)
1417 if (work_dir[0] == '\0')
1418 return (0);
1419 return (g_strdup(work_dir));
1423 set_work_dir(struct settings *s, char *val)
1425 if (val[0] == '~')
1426 snprintf(work_dir, sizeof work_dir, "%s/%s",
1427 pwd->pw_dir, &val[1]);
1428 else
1429 strlcpy(work_dir, val, sizeof work_dir);
1431 return (0);
1434 char *
1435 get_tab_style(struct settings *s)
1437 if (tab_style == XT_TABS_NORMAL)
1438 return (g_strdup("normal"));
1439 else
1440 return (g_strdup("compact"));
1444 set_tab_style(struct settings *s, char *val)
1446 if (!strcmp(val, "normal"))
1447 tab_style = XT_TABS_NORMAL;
1448 else if (!strcmp(val, "compact"))
1449 tab_style = XT_TABS_COMPACT;
1450 else
1451 return (1);
1453 return (0);
1457 * generate a session key to secure xtp commands.
1458 * pass in a ptr to the key in question and it will
1459 * be modified in place.
1461 void
1462 generate_xtp_session_key(char **key)
1464 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1466 /* free old key */
1467 if (*key)
1468 g_free(*key);
1470 /* make a new one */
1471 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1472 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1473 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1474 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1476 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1480 * validate a xtp session key.
1481 * return 1 if OK
1484 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1486 if (strcmp(trusted, untrusted) != 0) {
1487 show_oops(t, "%s: xtp session key mismatch possible spoof",
1488 __func__);
1489 return (0);
1492 return (1);
1496 download_rb_cmp(struct download *e1, struct download *e2)
1498 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1500 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1502 struct valid_url_types {
1503 char *type;
1504 } vut[] = {
1505 { "http://" },
1506 { "https://" },
1507 { "ftp://" },
1508 { "file://" },
1509 { XT_XTP_STR },
1513 valid_url_type(char *url)
1515 int i;
1517 for (i = 0; i < LENGTH(vut); i++)
1518 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1519 return (0);
1521 return (1);
1524 void
1525 print_cookie(char *msg, SoupCookie *c)
1527 if (c == NULL)
1528 return;
1530 if (msg)
1531 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1532 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1533 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1534 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1535 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1536 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1537 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1538 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1539 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1540 DNPRINTF(XT_D_COOKIE, "====================================\n");
1543 void
1544 walk_alias(struct settings *s,
1545 void (*cb)(struct settings *, char *, void *), void *cb_args)
1547 struct alias *a;
1548 char *str;
1550 if (s == NULL || cb == NULL) {
1551 show_oops(NULL, "walk_alias invalid parameters");
1552 return;
1555 TAILQ_FOREACH(a, &aliases, entry) {
1556 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1557 cb(s, str, cb_args);
1558 g_free(str);
1562 char *
1563 match_alias(char *url_in)
1565 struct alias *a;
1566 char *arg;
1567 char *url_out = NULL, *search, *enc_arg;
1569 search = g_strdup(url_in);
1570 arg = search;
1571 if (strsep(&arg, " \t") == NULL) {
1572 show_oops(NULL, "match_alias: NULL URL");
1573 goto done;
1576 TAILQ_FOREACH(a, &aliases, entry) {
1577 if (!strcmp(search, a->a_name))
1578 break;
1581 if (a != NULL) {
1582 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1583 a->a_name);
1584 if (arg != NULL) {
1585 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1586 url_out = g_strdup_printf(a->a_uri, enc_arg);
1587 g_free(enc_arg);
1588 } else
1589 url_out = g_strdup_printf(a->a_uri, "");
1591 done:
1592 g_free(search);
1593 return (url_out);
1596 char *
1597 guess_url_type(char *url_in)
1599 struct stat sb;
1600 char *url_out = NULL, *enc_search = NULL;
1602 url_out = match_alias(url_in);
1603 if (url_out != NULL)
1604 return (url_out);
1606 if (guess_search) {
1608 * If there is no dot nor slash in the string and it isn't a
1609 * path to a local file and doesn't resolves to an IP, assume
1610 * that the user wants to search for the string.
1613 if (strchr(url_in, '.') == NULL &&
1614 strchr(url_in, '/') == NULL &&
1615 stat(url_in, &sb) != 0 &&
1616 gethostbyname(url_in) == NULL) {
1618 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1619 url_out = g_strdup_printf(search_string, enc_search);
1620 g_free(enc_search);
1621 return (url_out);
1625 /* XXX not sure about this heuristic */
1626 if (stat(url_in, &sb) == 0)
1627 url_out = g_strdup_printf("file://%s", url_in);
1628 else
1629 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1631 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1633 return (url_out);
1636 void
1637 load_uri(struct tab *t, gchar *uri)
1639 struct karg args;
1640 gchar *newuri = NULL;
1641 int i;
1643 if (uri == NULL)
1644 return;
1646 /* Strip leading spaces. */
1647 while (*uri && isspace(*uri))
1648 uri++;
1650 if (strlen(uri) == 0) {
1651 blank(t, NULL);
1652 return;
1655 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1657 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1658 for (i = 0; i < LENGTH(about_list); i++)
1659 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1660 bzero(&args, sizeof args);
1661 about_list[i].func(t, &args);
1662 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1663 FALSE);
1664 return;
1666 show_oops(t, "invalid about page");
1667 return;
1670 if (valid_url_type(uri)) {
1671 newuri = guess_url_type(uri);
1672 uri = newuri;
1675 set_status(t, (char *)uri, XT_STATUS_LOADING);
1676 marks_clear(t);
1677 webkit_web_view_load_uri(t->wv, uri);
1679 if (newuri)
1680 g_free(newuri);
1683 const gchar *
1684 get_uri(struct tab *t)
1686 const gchar *uri = NULL;
1688 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1689 return t->tmp_uri;
1690 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1691 uri = webkit_web_view_get_uri(t->wv);
1692 } else {
1693 /* use tmp_uri to make sure it is g_freed */
1694 if (t->tmp_uri)
1695 g_free(t->tmp_uri);
1696 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1697 about_list[t->xtp_meaning].name);
1698 uri = t->tmp_uri;
1700 return uri;
1703 const gchar *
1704 get_title(struct tab *t, bool window)
1706 const gchar *set = NULL, *title = NULL;
1707 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1709 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1710 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1711 goto notitle;
1713 title = webkit_web_view_get_title(t->wv);
1714 if ((set = title ? title : get_uri(t)))
1715 return set;
1717 notitle:
1718 set = window ? XT_NAME : "(untitled)";
1720 return set;
1724 add_alias(struct settings *s, char *line)
1726 char *l, *alias;
1727 struct alias *a = NULL;
1729 if (s == NULL || line == NULL) {
1730 show_oops(NULL, "add_alias invalid parameters");
1731 return (1);
1734 l = line;
1735 a = g_malloc(sizeof(*a));
1737 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1738 show_oops(NULL, "add_alias: incomplete alias definition");
1739 goto bad;
1741 if (strlen(alias) == 0 || strlen(l) == 0) {
1742 show_oops(NULL, "add_alias: invalid alias definition");
1743 goto bad;
1746 a->a_name = g_strdup(alias);
1747 a->a_uri = g_strdup(l);
1749 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1751 TAILQ_INSERT_TAIL(&aliases, a, entry);
1753 return (0);
1754 bad:
1755 if (a)
1756 g_free(a);
1757 return (1);
1761 add_mime_type(struct settings *s, char *line)
1763 char *mime_type;
1764 char *l;
1765 struct mime_type *m = NULL;
1766 int downloadfirst = 0;
1768 /* XXX this could be smarter */
1770 if (line == NULL || strlen(line) == 0) {
1771 show_oops(NULL, "add_mime_type invalid parameters");
1772 return (1);
1775 l = line;
1776 if (*l == '@') {
1777 downloadfirst = 1;
1778 l++;
1780 m = g_malloc(sizeof(*m));
1782 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1783 show_oops(NULL, "add_mime_type: invalid mime_type");
1784 goto bad;
1786 if (mime_type[strlen(mime_type) - 1] == '*') {
1787 mime_type[strlen(mime_type) - 1] = '\0';
1788 m->mt_default = 1;
1789 } else
1790 m->mt_default = 0;
1792 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1793 show_oops(NULL, "add_mime_type: invalid mime_type");
1794 goto bad;
1797 m->mt_type = g_strdup(mime_type);
1798 m->mt_action = g_strdup(l);
1799 m->mt_download = downloadfirst;
1801 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1802 m->mt_type, m->mt_action, m->mt_default);
1804 TAILQ_INSERT_TAIL(&mtl, m, entry);
1806 return (0);
1807 bad:
1808 if (m)
1809 g_free(m);
1810 return (1);
1813 struct mime_type *
1814 find_mime_type(char *mime_type)
1816 struct mime_type *m, *def = NULL, *rv = NULL;
1818 TAILQ_FOREACH(m, &mtl, entry) {
1819 if (m->mt_default &&
1820 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1821 def = m;
1823 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1824 rv = m;
1825 break;
1829 if (rv == NULL)
1830 rv = def;
1832 return (rv);
1835 void
1836 walk_mime_type(struct settings *s,
1837 void (*cb)(struct settings *, char *, void *), void *cb_args)
1839 struct mime_type *m;
1840 char *str;
1842 if (s == NULL || cb == NULL) {
1843 show_oops(NULL, "walk_mime_type invalid parameters");
1844 return;
1847 TAILQ_FOREACH(m, &mtl, entry) {
1848 str = g_strdup_printf("%s%s --> %s",
1849 m->mt_type,
1850 m->mt_default ? "*" : "",
1851 m->mt_action);
1852 cb(s, str, cb_args);
1853 g_free(str);
1857 void
1858 wl_add(char *str, struct domain_list *wl, int handy)
1860 struct domain *d;
1861 int add_dot = 0;
1863 if (str == NULL || wl == NULL || strlen(str) < 2)
1864 return;
1866 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1868 /* treat *.moo.com the same as .moo.com */
1869 if (str[0] == '*' && str[1] == '.')
1870 str = &str[1];
1871 else if (str[0] == '.')
1872 str = &str[0];
1873 else
1874 add_dot = 1;
1876 d = g_malloc(sizeof *d);
1877 if (add_dot)
1878 d->d = g_strdup_printf(".%s", str);
1879 else
1880 d->d = g_strdup(str);
1881 d->handy = handy;
1883 if (RB_INSERT(domain_list, wl, d))
1884 goto unwind;
1886 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1887 return;
1888 unwind:
1889 if (d) {
1890 if (d->d)
1891 g_free(d->d);
1892 g_free(d);
1897 add_cookie_wl(struct settings *s, char *entry)
1899 wl_add(entry, &c_wl, 1);
1900 return (0);
1903 void
1904 walk_cookie_wl(struct settings *s,
1905 void (*cb)(struct settings *, char *, void *), void *cb_args)
1907 struct domain *d;
1909 if (s == NULL || cb == NULL) {
1910 show_oops(NULL, "walk_cookie_wl invalid parameters");
1911 return;
1914 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1915 cb(s, d->d, cb_args);
1918 void
1919 walk_js_wl(struct settings *s,
1920 void (*cb)(struct settings *, char *, void *), void *cb_args)
1922 struct domain *d;
1924 if (s == NULL || cb == NULL) {
1925 show_oops(NULL, "walk_js_wl invalid parameters");
1926 return;
1929 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1930 cb(s, d->d, cb_args);
1934 add_js_wl(struct settings *s, char *entry)
1936 wl_add(entry, &js_wl, 1 /* persistent */);
1937 return (0);
1940 struct domain *
1941 wl_find(const gchar *search, struct domain_list *wl)
1943 int i;
1944 struct domain *d = NULL, dfind;
1945 gchar *s = NULL;
1947 if (search == NULL || wl == NULL)
1948 return (NULL);
1949 if (strlen(search) < 2)
1950 return (NULL);
1952 if (search[0] != '.')
1953 s = g_strdup_printf(".%s", search);
1954 else
1955 s = g_strdup(search);
1957 for (i = strlen(s) - 1; i >= 0; i--) {
1958 if (s[i] == '.') {
1959 dfind.d = &s[i];
1960 d = RB_FIND(domain_list, wl, &dfind);
1961 if (d)
1962 goto done;
1966 done:
1967 if (s)
1968 g_free(s);
1970 return (d);
1973 struct domain *
1974 wl_find_uri(const gchar *s, struct domain_list *wl)
1976 int i;
1977 char *ss;
1978 struct domain *r;
1980 if (s == NULL || wl == NULL)
1981 return (NULL);
1983 if (!strncmp(s, "http://", strlen("http://")))
1984 s = &s[strlen("http://")];
1985 else if (!strncmp(s, "https://", strlen("https://")))
1986 s = &s[strlen("https://")];
1988 if (strlen(s) < 2)
1989 return (NULL);
1991 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1992 /* chop string at first slash */
1993 if (s[i] == '/' || s[i] == '\0') {
1994 ss = g_strdup(s);
1995 ss[i] = '\0';
1996 r = wl_find(ss, wl);
1997 g_free(ss);
1998 return (r);
2001 return (NULL);
2005 settings_add(char *var, char *val)
2007 int i, rv, *p;
2008 gfloat *f;
2009 char **s;
2011 /* get settings */
2012 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2013 if (strcmp(var, rs[i].name))
2014 continue;
2016 if (rs[i].s) {
2017 if (rs[i].s->set(&rs[i], val))
2018 errx(1, "invalid value for %s: %s", var, val);
2019 rv = 1;
2020 break;
2021 } else
2022 switch (rs[i].type) {
2023 case XT_S_INT:
2024 p = rs[i].ival;
2025 *p = atoi(val);
2026 rv = 1;
2027 break;
2028 case XT_S_STR:
2029 s = rs[i].sval;
2030 if (s == NULL)
2031 errx(1, "invalid sval for %s",
2032 rs[i].name);
2033 if (*s)
2034 g_free(*s);
2035 *s = g_strdup(val);
2036 rv = 1;
2037 break;
2038 case XT_S_FLOAT:
2039 f = rs[i].fval;
2040 *f = atof(val);
2041 rv = 1;
2042 break;
2043 case XT_S_INVALID:
2044 default:
2045 errx(1, "invalid type for %s", var);
2047 break;
2049 return (rv);
2052 #define WS "\n= \t"
2053 void
2054 config_parse(char *filename, int runtime)
2056 FILE *config, *f;
2057 char *line, *cp, *var, *val;
2058 size_t len, lineno = 0;
2059 int handled;
2060 char file[PATH_MAX];
2061 struct stat sb;
2063 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2065 if (filename == NULL)
2066 return;
2068 if (runtime && runtime_settings[0] != '\0') {
2069 snprintf(file, sizeof file, "%s/%s",
2070 work_dir, runtime_settings);
2071 if (stat(file, &sb)) {
2072 warnx("runtime file doesn't exist, creating it");
2073 if ((f = fopen(file, "w")) == NULL)
2074 err(1, "runtime");
2075 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2076 fclose(f);
2078 } else
2079 strlcpy(file, filename, sizeof file);
2081 if ((config = fopen(file, "r")) == NULL) {
2082 warn("config_parse: cannot open %s", filename);
2083 return;
2086 for (;;) {
2087 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2088 if (feof(config) || ferror(config))
2089 break;
2091 cp = line;
2092 cp += (long)strspn(cp, WS);
2093 if (cp[0] == '\0') {
2094 /* empty line */
2095 free(line);
2096 continue;
2099 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2100 errx(1, "invalid config file entry: %s", line);
2102 cp += (long)strspn(cp, WS);
2104 if ((val = strsep(&cp, "\0")) == NULL)
2105 break;
2107 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2108 handled = settings_add(var, val);
2109 if (handled == 0)
2110 errx(1, "invalid conf file entry: %s=%s", var, val);
2112 free(line);
2115 fclose(config);
2118 char *
2119 js_ref_to_string(JSContextRef context, JSValueRef ref)
2121 char *s = NULL;
2122 size_t l;
2123 JSStringRef jsref;
2125 jsref = JSValueToStringCopy(context, ref, NULL);
2126 if (jsref == NULL)
2127 return (NULL);
2129 l = JSStringGetMaximumUTF8CStringSize(jsref);
2130 s = g_malloc(l);
2131 if (s)
2132 JSStringGetUTF8CString(jsref, s, l);
2133 JSStringRelease(jsref);
2135 return (s);
2138 void
2139 disable_hints(struct tab *t)
2141 bzero(t->hint_buf, sizeof t->hint_buf);
2142 bzero(t->hint_num, sizeof t->hint_num);
2143 run_script(t, "vimprobable_clear()");
2144 t->hints_on = 0;
2145 t->hint_mode = XT_HINT_NONE;
2148 void
2149 enable_hints(struct tab *t)
2151 bzero(t->hint_buf, sizeof t->hint_buf);
2152 run_script(t, "vimprobable_show_hints()");
2153 t->hints_on = 1;
2154 t->hint_mode = XT_HINT_NONE;
2157 #define XT_JS_OPEN ("open;")
2158 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2159 #define XT_JS_FIRE ("fire;")
2160 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2161 #define XT_JS_FOUND ("found;")
2162 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2165 run_script(struct tab *t, char *s)
2167 JSGlobalContextRef ctx;
2168 WebKitWebFrame *frame;
2169 JSStringRef str;
2170 JSValueRef val, exception;
2171 char *es, buf[128];
2173 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2174 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2176 frame = webkit_web_view_get_main_frame(t->wv);
2177 ctx = webkit_web_frame_get_global_context(frame);
2179 str = JSStringCreateWithUTF8CString(s);
2180 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2181 NULL, 0, &exception);
2182 JSStringRelease(str);
2184 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2185 if (val == NULL) {
2186 es = js_ref_to_string(ctx, exception);
2187 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2188 g_free(es);
2189 return (1);
2190 } else {
2191 es = js_ref_to_string(ctx, val);
2192 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2194 /* handle return value right here */
2195 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2196 disable_hints(t);
2197 marks_clear(t);
2198 load_uri(t, &es[XT_JS_OPEN_LEN]);
2201 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2202 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2203 &es[XT_JS_FIRE_LEN]);
2204 run_script(t, buf);
2205 disable_hints(t);
2208 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2209 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2210 disable_hints(t);
2213 g_free(es);
2216 return (0);
2220 hint(struct tab *t, struct karg *args)
2223 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2225 if (t->hints_on == 0)
2226 enable_hints(t);
2227 else
2228 disable_hints(t);
2230 return (0);
2233 void
2234 apply_style(struct tab *t)
2236 g_object_set(G_OBJECT(t->settings),
2237 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2241 userstyle(struct tab *t, struct karg *args)
2243 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2245 if (t->styled) {
2246 t->styled = 0;
2247 g_object_set(G_OBJECT(t->settings),
2248 "user-stylesheet-uri", NULL, (char *)NULL);
2249 } else {
2250 t->styled = 1;
2251 apply_style(t);
2253 return (0);
2257 * Doesn't work fully, due to the following bug:
2258 * https://bugs.webkit.org/show_bug.cgi?id=51747
2261 restore_global_history(void)
2263 char file[PATH_MAX];
2264 FILE *f;
2265 struct history *h;
2266 gchar *uri;
2267 gchar *title;
2268 const char delim[3] = {'\\', '\\', '\0'};
2270 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2272 if ((f = fopen(file, "r")) == NULL) {
2273 warnx("%s: fopen", __func__);
2274 return (1);
2277 for (;;) {
2278 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2279 if (feof(f) || ferror(f))
2280 break;
2282 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2283 if (feof(f) || ferror(f)) {
2284 free(uri);
2285 warnx("%s: broken history file\n", __func__);
2286 return (1);
2289 if (uri && strlen(uri) && title && strlen(title)) {
2290 webkit_web_history_item_new_with_data(uri, title);
2291 h = g_malloc(sizeof(struct history));
2292 h->uri = g_strdup(uri);
2293 h->title = g_strdup(title);
2294 RB_INSERT(history_list, &hl, h);
2295 completion_add_uri(h->uri);
2296 } else {
2297 warnx("%s: failed to restore history\n", __func__);
2298 free(uri);
2299 free(title);
2300 return (1);
2303 free(uri);
2304 free(title);
2305 uri = NULL;
2306 title = NULL;
2309 return (0);
2313 save_global_history_to_disk(struct tab *t)
2315 char file[PATH_MAX];
2316 FILE *f;
2317 struct history *h;
2319 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2321 if ((f = fopen(file, "w")) == NULL) {
2322 show_oops(t, "%s: global history file: %s",
2323 __func__, strerror(errno));
2324 return (1);
2327 RB_FOREACH_REVERSE(h, history_list, &hl) {
2328 if (h->uri && h->title)
2329 fprintf(f, "%s\n%s\n", h->uri, h->title);
2332 fclose(f);
2334 return (0);
2338 quit(struct tab *t, struct karg *args)
2340 if (save_global_history)
2341 save_global_history_to_disk(t);
2343 gtk_main_quit();
2345 return (1);
2349 open_tabs(struct tab *t, struct karg *a)
2351 char file[PATH_MAX];
2352 FILE *f = NULL;
2353 char *uri = NULL;
2354 int rv = 1;
2355 struct tab *ti, *tt;
2357 if (a == NULL)
2358 goto done;
2360 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2361 if ((f = fopen(file, "r")) == NULL)
2362 goto done;
2364 ti = TAILQ_LAST(&tabs, tab_list);
2366 for (;;) {
2367 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2368 if (feof(f) || ferror(f))
2369 break;
2371 /* retrieve session name */
2372 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2373 strlcpy(named_session,
2374 &uri[strlen(XT_SAVE_SESSION_ID)],
2375 sizeof named_session);
2376 continue;
2379 if (uri && strlen(uri))
2380 create_new_tab(uri, NULL, 1, -1);
2382 free(uri);
2383 uri = NULL;
2386 /* close open tabs */
2387 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2388 for (;;) {
2389 tt = TAILQ_FIRST(&tabs);
2390 if (tt != ti) {
2391 delete_tab(tt);
2392 continue;
2394 delete_tab(tt);
2395 break;
2397 recalc_tabs();
2400 rv = 0;
2401 done:
2402 if (f)
2403 fclose(f);
2405 return (rv);
2409 restore_saved_tabs(void)
2411 char file[PATH_MAX];
2412 int unlink_file = 0;
2413 struct stat sb;
2414 struct karg a;
2415 int rv = 0;
2417 snprintf(file, sizeof file, "%s/%s",
2418 sessions_dir, XT_RESTART_TABS_FILE);
2419 if (stat(file, &sb) == -1)
2420 a.s = XT_SAVED_TABS_FILE;
2421 else {
2422 unlink_file = 1;
2423 a.s = XT_RESTART_TABS_FILE;
2426 a.i = XT_SES_DONOTHING;
2427 rv = open_tabs(NULL, &a);
2429 if (unlink_file)
2430 unlink(file);
2432 return (rv);
2436 save_tabs(struct tab *t, struct karg *a)
2438 char file[PATH_MAX];
2439 FILE *f;
2440 int num_tabs = 0, i;
2441 struct tab **stabs = NULL;
2443 if (a == NULL)
2444 return (1);
2445 if (a->s == NULL)
2446 snprintf(file, sizeof file, "%s/%s",
2447 sessions_dir, named_session);
2448 else
2449 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2451 if ((f = fopen(file, "w")) == NULL) {
2452 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2453 return (1);
2456 /* save session name */
2457 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2459 /* Save tabs, in the order they are arranged in the notebook. */
2460 num_tabs = sort_tabs_by_page_num(&stabs);
2462 for (i = 0; i < num_tabs; i++)
2463 if (stabs[i] && get_uri(stabs[i]) != NULL)
2464 fprintf(f, "%s\n", get_uri(stabs[i]));
2466 g_free(stabs);
2468 /* try and make sure this gets to disk NOW. XXX Backup first? */
2469 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2470 show_oops(t, "May not have managed to save session: %s",
2471 strerror(errno));
2474 fclose(f);
2476 return (0);
2480 save_tabs_and_quit(struct tab *t, struct karg *args)
2482 struct karg a;
2484 a.s = NULL;
2485 save_tabs(t, &a);
2486 quit(t, NULL);
2488 return (1);
2492 run_page_script(struct tab *t, struct karg *args)
2494 const gchar *uri;
2495 char *tmp, script[PATH_MAX];
2497 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2498 if (tmp[0] == '\0') {
2499 show_oops(t, "no script specified");
2500 return (1);
2503 if ((uri = get_uri(t)) == NULL) {
2504 show_oops(t, "tab is empty, not running script");
2505 return (1);
2508 if (tmp[0] == '~')
2509 snprintf(script, sizeof script, "%s/%s",
2510 pwd->pw_dir, &tmp[1]);
2511 else
2512 strlcpy(script, tmp, sizeof script);
2514 switch (fork()) {
2515 case -1:
2516 show_oops(t, "can't fork to run script");
2517 return (1);
2518 /* NOTREACHED */
2519 case 0:
2520 break;
2521 default:
2522 return (0);
2525 /* child */
2526 execlp(script, script, uri, (void *)NULL);
2528 _exit(0);
2530 /* NOTREACHED */
2532 return (0);
2536 yank_uri(struct tab *t, struct karg *args)
2538 const gchar *uri;
2539 GtkClipboard *clipboard;
2541 if ((uri = get_uri(t)) == NULL)
2542 return (1);
2544 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2545 gtk_clipboard_set_text(clipboard, uri, -1);
2547 return (0);
2551 paste_uri(struct tab *t, struct karg *args)
2553 GtkClipboard *clipboard;
2554 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2555 gint len;
2556 gchar *p = NULL, *uri;
2558 /* try primary clipboard first */
2559 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2560 p = gtk_clipboard_wait_for_text(clipboard);
2562 /* if it failed get whatever text is in cut_buffer0 */
2563 if (p == NULL && xterm_workaround)
2564 if (gdk_property_get(gdk_get_default_root_window(),
2565 atom,
2566 gdk_atom_intern("STRING", FALSE),
2568 1024 * 1024 /* picked out of my butt */,
2569 FALSE,
2570 NULL,
2571 NULL,
2572 &len,
2573 (guchar **)&p)) {
2574 /* yes sir, we need to NUL the string */
2575 p[len] = '\0';
2578 if (p) {
2579 uri = p;
2580 while (*uri && isspace(*uri))
2581 uri++;
2582 if (strlen(uri) == 0) {
2583 show_oops(t, "empty paste buffer");
2584 goto done;
2586 if (guess_search == 0 && valid_url_type(uri)) {
2587 /* we can be clever and paste this in search box */
2588 show_oops(t, "not a valid URL");
2589 goto done;
2592 if (args->i == XT_PASTE_CURRENT_TAB)
2593 load_uri(t, uri);
2594 else if (args->i == XT_PASTE_NEW_TAB)
2595 create_new_tab(uri, NULL, 1, -1);
2598 done:
2599 if (p)
2600 g_free(p);
2602 return (0);
2605 gchar *
2606 find_domain(const gchar *s, int toplevel)
2608 SoupURI *uri;
2609 gchar *ret, *p;
2611 if (s == NULL)
2612 return (NULL);
2614 uri = soup_uri_new(s);
2616 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2617 return (NULL);
2620 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2621 if ((p = strrchr(uri->host, '.')) != NULL) {
2622 while(--p >= uri->host && *p != '.');
2623 p++;
2624 } else
2625 p = uri->host;
2626 } else
2627 p = uri->host;
2629 if (uri->port == 80)
2630 ret = g_strdup_printf(".%s", p);
2631 else
2632 ret = g_strdup_printf(".%s:%d", p, uri->port);
2634 soup_uri_free(uri);
2636 return ret;
2640 toggle_cwl(struct tab *t, struct karg *args)
2642 struct domain *d;
2643 const gchar *uri;
2644 char *dom = NULL;
2645 int es;
2647 if (args == NULL)
2648 return (1);
2650 uri = get_uri(t);
2651 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2653 if (uri == NULL || dom == NULL ||
2654 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2655 show_oops(t, "Can't toggle domain in cookie white list");
2656 goto done;
2658 d = wl_find(dom, &c_wl);
2660 if (d == NULL)
2661 es = 0;
2662 else
2663 es = 1;
2665 if (args->i & XT_WL_TOGGLE)
2666 es = !es;
2667 else if ((args->i & XT_WL_ENABLE) && es != 1)
2668 es = 1;
2669 else if ((args->i & XT_WL_DISABLE) && es != 0)
2670 es = 0;
2672 if (es)
2673 /* enable cookies for domain */
2674 wl_add(dom, &c_wl, 0);
2675 else
2676 /* disable cookies for domain */
2677 RB_REMOVE(domain_list, &c_wl, d);
2679 if (args->i & XT_WL_RELOAD)
2680 webkit_web_view_reload(t->wv);
2682 done:
2683 g_free(dom);
2684 return (0);
2688 toggle_js(struct tab *t, struct karg *args)
2690 int es;
2691 const gchar *uri;
2692 struct domain *d;
2693 char *dom = NULL;
2695 if (args == NULL)
2696 return (1);
2698 g_object_get(G_OBJECT(t->settings),
2699 "enable-scripts", &es, (char *)NULL);
2700 if (args->i & XT_WL_TOGGLE)
2701 es = !es;
2702 else if ((args->i & XT_WL_ENABLE) && es != 1)
2703 es = 1;
2704 else if ((args->i & XT_WL_DISABLE) && es != 0)
2705 es = 0;
2706 else
2707 return (1);
2709 uri = get_uri(t);
2710 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2712 if (uri == NULL || dom == NULL ||
2713 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2714 show_oops(t, "Can't toggle domain in JavaScript white list");
2715 goto done;
2718 if (es) {
2719 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2720 wl_add(dom, &js_wl, 0 /* session */);
2721 } else {
2722 d = wl_find(dom, &js_wl);
2723 if (d)
2724 RB_REMOVE(domain_list, &js_wl, d);
2725 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2727 g_object_set(G_OBJECT(t->settings),
2728 "enable-scripts", es, (char *)NULL);
2729 g_object_set(G_OBJECT(t->settings),
2730 "javascript-can-open-windows-automatically", es, (char *)NULL);
2731 webkit_web_view_set_settings(t->wv, t->settings);
2733 if (args->i & XT_WL_RELOAD)
2734 webkit_web_view_reload(t->wv);
2735 done:
2736 if (dom)
2737 g_free(dom);
2738 return (0);
2741 void
2742 js_toggle_cb(GtkWidget *w, struct tab *t)
2744 struct karg a;
2746 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2747 toggle_cwl(t, &a);
2749 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2750 toggle_js(t, &a);
2754 toggle_src(struct tab *t, struct karg *args)
2756 gboolean mode;
2758 if (t == NULL)
2759 return (0);
2761 mode = webkit_web_view_get_view_source_mode(t->wv);
2762 webkit_web_view_set_view_source_mode(t->wv, !mode);
2763 webkit_web_view_reload(t->wv);
2765 return (0);
2768 void
2769 focus_webview(struct tab *t)
2771 if (t == NULL)
2772 return;
2774 /* only grab focus if we are visible */
2775 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2776 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2780 focus(struct tab *t, struct karg *args)
2782 if (t == NULL || args == NULL)
2783 return (1);
2785 if (show_url == 0)
2786 return (0);
2788 if (args->i == XT_FOCUS_URI)
2789 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2790 else if (args->i == XT_FOCUS_SEARCH)
2791 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2793 return (0);
2797 stats(struct tab *t, struct karg *args)
2799 char *page, *body, *s, line[64 * 1024];
2800 uint64_t line_count = 0;
2801 FILE *r_cookie_f;
2803 if (t == NULL)
2804 show_oops(NULL, "stats invalid parameters");
2806 line[0] = '\0';
2807 if (save_rejected_cookies) {
2808 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2809 for (;;) {
2810 s = fgets(line, sizeof line, r_cookie_f);
2811 if (s == NULL || feof(r_cookie_f) ||
2812 ferror(r_cookie_f))
2813 break;
2814 line_count++;
2816 fclose(r_cookie_f);
2817 snprintf(line, sizeof line,
2818 "<br/>Cookies blocked(*) total: %llu", line_count);
2819 } else
2820 show_oops(t, "Can't open blocked cookies file: %s",
2821 strerror(errno));
2824 body = g_strdup_printf(
2825 "Cookies blocked(*) this session: %llu"
2826 "%s"
2827 "<p><small><b>*</b> results vary based on settings</small></p>",
2828 blocked_cookies,
2829 line);
2831 page = get_html_page("Statistics", body, "", 0);
2832 g_free(body);
2834 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2835 g_free(page);
2837 return (0);
2841 marco(struct tab *t, struct karg *args)
2843 char *page, line[64 * 1024];
2844 int len;
2846 if (t == NULL)
2847 show_oops(NULL, "marco invalid parameters");
2849 line[0] = '\0';
2850 snprintf(line, sizeof line, "%s", marco_message(&len));
2852 page = get_html_page("Marco Sez...", line, "", 0);
2854 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2855 g_free(page);
2857 return (0);
2861 blank(struct tab *t, struct karg *args)
2863 if (t == NULL)
2864 show_oops(NULL, "blank invalid parameters");
2866 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2868 return (0);
2871 about(struct tab *t, struct karg *args)
2873 char *page, *body;
2875 if (t == NULL)
2876 show_oops(NULL, "about invalid parameters");
2878 body = g_strdup_printf("<b>Version: %s</b><p>"
2879 "Authors:"
2880 "<ul>"
2881 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2882 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2883 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2884 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2885 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2886 "</ul>"
2887 "Copyrights and licenses can be found on the XXXTerm "
2888 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2889 version
2892 page = get_html_page("About", body, "", 0);
2893 g_free(body);
2895 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2896 g_free(page);
2898 return (0);
2902 help(struct tab *t, struct karg *args)
2904 char *page, *head, *body;
2906 if (t == NULL)
2907 show_oops(NULL, "help invalid parameters");
2909 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2910 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2911 "</head>\n";
2912 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2913 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2914 "cgi-bin/man-cgi?xxxterm</a>";
2916 page = get_html_page(XT_NAME, body, head, FALSE);
2918 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2919 g_free(page);
2921 return (0);
2925 * update all favorite tabs apart from one. Pass NULL if
2926 * you want to update all.
2928 void
2929 update_favorite_tabs(struct tab *apart_from)
2931 struct tab *t;
2932 if (!updating_fl_tabs) {
2933 updating_fl_tabs = 1; /* stop infinite recursion */
2934 TAILQ_FOREACH(t, &tabs, entry)
2935 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2936 && (t != apart_from))
2937 xtp_page_fl(t, NULL);
2938 updating_fl_tabs = 0;
2942 /* show a list of favorites (bookmarks) */
2944 xtp_page_fl(struct tab *t, struct karg *args)
2946 char file[PATH_MAX];
2947 FILE *f;
2948 char *uri = NULL, *title = NULL;
2949 size_t len, lineno = 0;
2950 int i, failed = 0;
2951 char *body, *tmp, *page = NULL;
2952 const char delim[3] = {'\\', '\\', '\0'};
2954 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2956 if (t == NULL)
2957 warn("%s: bad param", __func__);
2959 /* new session key */
2960 if (!updating_fl_tabs)
2961 generate_xtp_session_key(&fl_session_key);
2963 /* open favorites */
2964 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2965 if ((f = fopen(file, "r")) == NULL) {
2966 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2967 return (1);
2970 /* body */
2971 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2972 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2973 "<th style='width: 40px'>Rm</th></tr>\n");
2975 for (i = 1;;) {
2976 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2977 if (feof(f) || ferror(f))
2978 break;
2979 if (strlen(title) == 0 || title[0] == '#') {
2980 free(title);
2981 title = NULL;
2982 continue;
2985 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2986 if (feof(f) || ferror(f)) {
2987 show_oops(t, "favorites file corrupt");
2988 failed = 1;
2989 break;
2992 tmp = body;
2993 body = g_strdup_printf("%s<tr>"
2994 "<td>%d</td>"
2995 "<td><a href='%s'>%s</a></td>"
2996 "<td style='text-align: center'>"
2997 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2998 "</tr>\n",
2999 body, i, uri, title,
3000 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3002 g_free(tmp);
3004 free(uri);
3005 uri = NULL;
3006 free(title);
3007 title = NULL;
3008 i++;
3010 fclose(f);
3012 /* if none, say so */
3013 if (i == 1) {
3014 tmp = body;
3015 body = g_strdup_printf("%s<tr>"
3016 "<td colspan='3' style='text-align: center'>"
3017 "No favorites - To add one use the 'favadd' command."
3018 "</td></tr>", body);
3019 g_free(tmp);
3022 tmp = body;
3023 body = g_strdup_printf("%s</table>", body);
3024 g_free(tmp);
3026 if (uri)
3027 free(uri);
3028 if (title)
3029 free(title);
3031 /* render */
3032 if (!failed) {
3033 page = get_html_page("Favorites", body, "", 1);
3034 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3035 g_free(page);
3038 update_favorite_tabs(t);
3040 if (body)
3041 g_free(body);
3043 return (failed);
3046 void
3047 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3048 size_t cert_count, char *title)
3050 gnutls_datum_t cinfo;
3051 char *tmp, *body;
3052 int i;
3054 body = g_strdup("");
3056 for (i = 0; i < cert_count; i++) {
3057 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3058 &cinfo))
3059 return;
3061 tmp = body;
3062 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3063 body, i, cinfo.data);
3064 gnutls_free(cinfo.data);
3065 g_free(tmp);
3068 tmp = get_html_page(title, body, "", 0);
3069 g_free(body);
3071 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3072 g_free(tmp);
3076 ca_cmd(struct tab *t, struct karg *args)
3078 FILE *f = NULL;
3079 int rv = 1, certs = 0, certs_read;
3080 struct stat sb;
3081 gnutls_datum_t dt;
3082 gnutls_x509_crt_t *c = NULL;
3083 char *certs_buf = NULL, *s;
3085 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3086 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3087 return (1);
3090 if (fstat(fileno(f), &sb) == -1) {
3091 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3092 goto done;
3095 certs_buf = g_malloc(sb.st_size + 1);
3096 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3097 show_oops(t, "Can't read CA file: %s", strerror(errno));
3098 goto done;
3100 certs_buf[sb.st_size] = '\0';
3102 s = certs_buf;
3103 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3104 certs++;
3105 s += strlen("BEGIN CERTIFICATE");
3108 bzero(&dt, sizeof dt);
3109 dt.data = (unsigned char *)certs_buf;
3110 dt.size = sb.st_size;
3111 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3112 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3113 GNUTLS_X509_FMT_PEM, 0);
3114 if (certs_read <= 0) {
3115 show_oops(t, "No cert(s) available");
3116 goto done;
3118 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3119 done:
3120 if (c)
3121 g_free(c);
3122 if (certs_buf)
3123 g_free(certs_buf);
3124 if (f)
3125 fclose(f);
3127 return (rv);
3131 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3132 size_t domain_sz)
3134 SoupURI *su = NULL;
3135 struct addrinfo hints, *res = NULL, *ai;
3136 int rv = -1, s = -1, on, error;
3137 char port[8];
3139 if (uri && !g_str_has_prefix(uri, "https://")) {
3140 show_oops(t, "invalid URI");
3141 goto done;
3144 su = soup_uri_new(uri);
3145 if (su == NULL) {
3146 show_oops(t, "invalid soup URI");
3147 goto done;
3149 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3150 show_oops(t, "invalid HTTPS URI");
3151 goto done;
3154 snprintf(port, sizeof port, "%d", su->port);
3155 bzero(&hints, sizeof(struct addrinfo));
3156 hints.ai_flags = AI_CANONNAME;
3157 hints.ai_family = AF_UNSPEC;
3158 hints.ai_socktype = SOCK_STREAM;
3160 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3161 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3162 goto done;
3165 for (ai = res; ai; ai = ai->ai_next) {
3166 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3167 continue;
3169 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3170 if (s == -1) {
3171 show_oops(t, "socket failed: %s", strerror(errno));
3172 goto done;
3174 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3175 sizeof(on)) == -1) {
3176 show_oops(t, "setsockopt failed: %s", strerror(errno));
3177 goto done;
3179 if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
3180 show_oops(t, "connect failed: %s", strerror(errno));
3181 goto done;
3184 break;
3187 if (domain)
3188 strlcpy(domain, su->host, domain_sz);
3189 rv = s;
3190 done:
3191 if (su)
3192 soup_uri_free(su);
3193 if (res)
3194 freeaddrinfo(res);
3195 if (rv == -1 && s != -1)
3196 close(s);
3198 return (rv);
3202 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3204 if (gsession)
3205 gnutls_deinit(gsession);
3206 if (xcred)
3207 gnutls_certificate_free_credentials(xcred);
3209 return (0);
3213 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3214 gnutls_certificate_credentials_t *xc)
3216 gnutls_certificate_credentials_t xcred;
3217 gnutls_session_t gsession;
3218 int rv = 1;
3220 if (gs == NULL || xc == NULL)
3221 goto done;
3223 *gs = NULL;
3224 *xc = NULL;
3226 gnutls_certificate_allocate_credentials(&xcred);
3227 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3228 GNUTLS_X509_FMT_PEM);
3230 gnutls_init(&gsession, GNUTLS_CLIENT);
3231 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3232 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3233 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3234 if ((rv = gnutls_handshake(gsession)) < 0) {
3235 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3237 gnutls_error_is_fatal(rv),
3238 gnutls_strerror_name(rv));
3239 stop_tls(gsession, xcred);
3240 goto done;
3243 gnutls_credentials_type_t cred;
3244 cred = gnutls_auth_get_type(gsession);
3245 if (cred != GNUTLS_CRD_CERTIFICATE) {
3246 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3247 stop_tls(gsession, xcred);
3248 goto done;
3251 *gs = gsession;
3252 *xc = xcred;
3253 rv = 0;
3254 done:
3255 return (rv);
3259 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3260 size_t *cert_count)
3262 unsigned int len;
3263 const gnutls_datum_t *cl;
3264 gnutls_x509_crt_t *all_certs;
3265 int i, rv = 1;
3267 if (certs == NULL || cert_count == NULL)
3268 goto done;
3269 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3270 goto done;
3271 cl = gnutls_certificate_get_peers(gsession, &len);
3272 if (len == 0)
3273 goto done;
3275 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3276 for (i = 0; i < len; i++) {
3277 gnutls_x509_crt_init(&all_certs[i]);
3278 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3279 GNUTLS_X509_FMT_PEM < 0)) {
3280 g_free(all_certs);
3281 goto done;
3285 *certs = all_certs;
3286 *cert_count = len;
3287 rv = 0;
3288 done:
3289 return (rv);
3292 void
3293 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3295 int i;
3297 for (i = 0; i < cert_count; i++)
3298 gnutls_x509_crt_deinit(certs[i]);
3299 g_free(certs);
3302 void
3303 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3305 GdkColor c_text, c_base;
3307 gdk_color_parse(text, &c_text);
3308 gdk_color_parse(base, &c_base);
3310 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3311 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3312 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3313 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3315 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3316 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3317 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3318 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3321 void
3322 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3323 size_t cert_count, char *domain)
3325 size_t cert_buf_sz;
3326 char cert_buf[64 * 1024], file[PATH_MAX];
3327 int i;
3328 FILE *f;
3329 GdkColor color;
3331 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3332 return;
3334 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3335 if ((f = fopen(file, "w")) == NULL) {
3336 show_oops(t, "Can't create cert file %s %s",
3337 file, strerror(errno));
3338 return;
3341 for (i = 0; i < cert_count; i++) {
3342 cert_buf_sz = sizeof cert_buf;
3343 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3344 cert_buf, &cert_buf_sz)) {
3345 show_oops(t, "gnutls_x509_crt_export failed");
3346 goto done;
3348 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3349 show_oops(t, "Can't write certs: %s", strerror(errno));
3350 goto done;
3354 /* not the best spot but oh well */
3355 gdk_color_parse("lightblue", &color);
3356 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3357 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3358 done:
3359 fclose(f);
3362 enum cert_trust {
3363 CERT_LOCAL,
3364 CERT_TRUSTED,
3365 CERT_UNTRUSTED,
3366 CERT_BAD
3369 enum cert_trust
3370 load_compare_cert(struct tab *t, struct karg *args)
3372 const gchar *uri;
3373 char domain[8182], file[PATH_MAX];
3374 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3375 int s = -1, i, error;
3376 FILE *f = NULL;
3377 size_t cert_buf_sz, cert_count;
3378 enum cert_trust rv = CERT_UNTRUSTED;
3379 char serr[80];
3380 gnutls_session_t gsession;
3381 gnutls_x509_crt_t *certs;
3382 gnutls_certificate_credentials_t xcred;
3384 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3386 if (t == NULL)
3387 return (rv);
3389 if ((uri = get_uri(t)) == NULL)
3390 return (rv);
3391 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3393 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3394 return (rv);
3395 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3397 /* go ssl/tls */
3398 if (start_tls(t, s, &gsession, &xcred))
3399 goto done;
3400 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3402 /* verify certs in case cert file doesn't exist */
3403 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3404 GNUTLS_E_SUCCESS) {
3405 show_oops(t, "Invalid certificates");
3406 goto done;
3409 /* get certs */
3410 if (get_connection_certs(gsession, &certs, &cert_count)) {
3411 show_oops(t, "Can't get connection certificates");
3412 goto done;
3415 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3416 if ((f = fopen(file, "r")) == NULL) {
3417 if (!error)
3418 rv = CERT_TRUSTED;
3419 goto freeit;
3422 for (i = 0; i < cert_count; i++) {
3423 cert_buf_sz = sizeof cert_buf;
3424 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3425 cert_buf, &cert_buf_sz)) {
3426 goto freeit;
3428 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3429 rv = CERT_BAD; /* critical */
3430 goto freeit;
3432 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3433 rv = CERT_BAD; /* critical */
3434 goto freeit;
3436 rv = CERT_LOCAL;
3439 freeit:
3440 if (f)
3441 fclose(f);
3442 free_connection_certs(certs, cert_count);
3443 done:
3444 /* we close the socket first for speed */
3445 if (s != -1)
3446 close(s);
3448 /* only complain if we didn't save it locally */
3449 if (error && rv != CERT_LOCAL) {
3450 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3451 if (error & GNUTLS_CERT_INVALID)
3452 strlcat(serr, "invalid, ", sizeof serr);
3453 if (error & GNUTLS_CERT_REVOKED)
3454 strlcat(serr, "revoked, ", sizeof serr);
3455 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3456 strlcat(serr, "signer not found, ", sizeof serr);
3457 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3458 strlcat(serr, "not signed by CA, ", sizeof serr);
3459 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3460 strlcat(serr, "insecure algorithm, ", sizeof serr);
3461 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3462 strlcat(serr, "not activated, ", sizeof serr);
3463 if (error & GNUTLS_CERT_EXPIRED)
3464 strlcat(serr, "expired, ", sizeof serr);
3465 for (i = strlen(serr) - 1; i > 0; i--)
3466 if (serr[i] == ',') {
3467 serr[i] = '\0';
3468 break;
3470 show_oops(t, serr);
3473 stop_tls(gsession, xcred);
3475 return (rv);
3479 cert_cmd(struct tab *t, struct karg *args)
3481 const gchar *uri;
3482 char domain[8182];
3483 int s = -1;
3484 size_t cert_count;
3485 gnutls_session_t gsession;
3486 gnutls_x509_crt_t *certs;
3487 gnutls_certificate_credentials_t xcred;
3489 if (t == NULL)
3490 return (1);
3492 if (ssl_ca_file == NULL) {
3493 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3494 return (1);
3497 if ((uri = get_uri(t)) == NULL) {
3498 show_oops(t, "Invalid URI");
3499 return (1);
3502 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3503 show_oops(t, "Invalid certificate URI: %s", uri);
3504 return (1);
3507 /* go ssl/tls */
3508 if (start_tls(t, s, &gsession, &xcred))
3509 goto done;
3511 /* get certs */
3512 if (get_connection_certs(gsession, &certs, &cert_count)) {
3513 show_oops(t, "get_connection_certs failed");
3514 goto done;
3517 if (args->i & XT_SHOW)
3518 show_certs(t, certs, cert_count, "Certificate Chain");
3519 else if (args->i & XT_SAVE)
3520 save_certs(t, certs, cert_count, domain);
3522 free_connection_certs(certs, cert_count);
3523 done:
3524 /* we close the socket first for speed */
3525 if (s != -1)
3526 close(s);
3527 stop_tls(gsession, xcred);
3529 return (0);
3533 remove_cookie(int index)
3535 int i, rv = 1;
3536 GSList *cf;
3537 SoupCookie *c;
3539 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3541 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3543 for (i = 1; cf; cf = cf->next, i++) {
3544 if (i != index)
3545 continue;
3546 c = cf->data;
3547 print_cookie("remove cookie", c);
3548 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3549 rv = 0;
3550 break;
3553 soup_cookies_free(cf);
3555 return (rv);
3559 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3561 struct domain *d;
3562 char *tmp, *body;
3564 body = g_strdup("");
3566 /* p list */
3567 if (args->i & XT_WL_PERSISTENT) {
3568 tmp = body;
3569 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3570 g_free(tmp);
3571 RB_FOREACH(d, domain_list, wl) {
3572 if (d->handy == 0)
3573 continue;
3574 tmp = body;
3575 body = g_strdup_printf("%s%s<br/>", body, d->d);
3576 g_free(tmp);
3580 /* s list */
3581 if (args->i & XT_WL_SESSION) {
3582 tmp = body;
3583 body = g_strdup_printf("%s<h2>Session</h2>", body);
3584 g_free(tmp);
3585 RB_FOREACH(d, domain_list, wl) {
3586 if (d->handy == 1)
3587 continue;
3588 tmp = body;
3589 body = g_strdup_printf("%s%s<br/>", body, d->d);
3590 g_free(tmp);
3594 tmp = get_html_page(title, body, "", 0);
3595 g_free(body);
3596 if (wl == &js_wl)
3597 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3598 else
3599 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3600 g_free(tmp);
3601 return (0);
3605 wl_save(struct tab *t, struct karg *args, int js)
3607 char file[PATH_MAX];
3608 FILE *f;
3609 char *line = NULL, *lt = NULL, *dom = NULL;
3610 size_t linelen;
3611 const gchar *uri;
3612 struct karg a;
3613 struct domain *d;
3614 GSList *cf;
3615 SoupCookie *ci, *c;
3617 if (t == NULL || args == NULL)
3618 return (1);
3620 if (runtime_settings[0] == '\0')
3621 return (1);
3623 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3624 if ((f = fopen(file, "r+")) == NULL)
3625 return (1);
3627 uri = get_uri(t);
3628 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3629 if (uri == NULL || dom == NULL ||
3630 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3631 show_oops(t, "Can't add domain to %s white list",
3632 js ? "JavaScript" : "cookie");
3633 goto done;
3636 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3638 while (!feof(f)) {
3639 line = fparseln(f, &linelen, NULL, NULL, 0);
3640 if (line == NULL)
3641 continue;
3642 if (!strcmp(line, lt))
3643 goto done;
3644 free(line);
3645 line = NULL;
3648 fprintf(f, "%s\n", lt);
3650 a.i = XT_WL_ENABLE;
3651 a.i |= args->i;
3652 if (js) {
3653 d = wl_find(dom, &js_wl);
3654 if (!d) {
3655 settings_add("js_wl", dom);
3656 d = wl_find(dom, &js_wl);
3658 toggle_js(t, &a);
3659 } else {
3660 d = wl_find(dom, &c_wl);
3661 if (!d) {
3662 settings_add("cookie_wl", dom);
3663 d = wl_find(dom, &c_wl);
3665 toggle_cwl(t, &a);
3667 /* find and add to persistent jar */
3668 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3669 for (;cf; cf = cf->next) {
3670 ci = cf->data;
3671 if (!strcmp(dom, ci->domain) ||
3672 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3673 c = soup_cookie_copy(ci);
3674 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3677 soup_cookies_free(cf);
3679 if (d)
3680 d->handy = 1;
3682 done:
3683 if (line)
3684 free(line);
3685 if (dom)
3686 g_free(dom);
3687 if (lt)
3688 g_free(lt);
3689 fclose(f);
3691 return (0);
3695 js_show_wl(struct tab *t, struct karg *args)
3697 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3698 wl_show(t, args, "JavaScript White List", &js_wl);
3700 return (0);
3704 cookie_show_wl(struct tab *t, struct karg *args)
3706 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3707 wl_show(t, args, "Cookie White List", &c_wl);
3709 return (0);
3713 cookie_cmd(struct tab *t, struct karg *args)
3715 if (args->i & XT_SHOW)
3716 wl_show(t, args, "Cookie White List", &c_wl);
3717 else if (args->i & XT_WL_TOGGLE) {
3718 args->i |= XT_WL_RELOAD;
3719 toggle_cwl(t, args);
3720 } else if (args->i & XT_SAVE) {
3721 args->i |= XT_WL_RELOAD;
3722 wl_save(t, args, 0);
3723 } else if (args->i & XT_DELETE)
3724 show_oops(t, "'cookie delete' currently unimplemented");
3726 return (0);
3730 js_cmd(struct tab *t, struct karg *args)
3732 if (args->i & XT_SHOW)
3733 wl_show(t, args, "JavaScript White List", &js_wl);
3734 else if (args->i & XT_SAVE) {
3735 args->i |= XT_WL_RELOAD;
3736 wl_save(t, args, 1);
3737 } else if (args->i & XT_WL_TOGGLE) {
3738 args->i |= XT_WL_RELOAD;
3739 toggle_js(t, args);
3740 } else if (args->i & XT_DELETE)
3741 show_oops(t, "'js delete' currently unimplemented");
3743 return (0);
3747 toplevel_cmd(struct tab *t, struct karg *args)
3749 js_toggle_cb(t->js_toggle, t);
3751 return (0);
3755 add_favorite(struct tab *t, struct karg *args)
3757 char file[PATH_MAX];
3758 FILE *f;
3759 char *line = NULL;
3760 size_t urilen, linelen;
3761 const gchar *uri, *title;
3763 if (t == NULL)
3764 return (1);
3766 /* don't allow adding of xtp pages to favorites */
3767 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3768 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3769 return (1);
3772 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3773 if ((f = fopen(file, "r+")) == NULL) {
3774 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3775 return (1);
3778 title = get_title(t, FALSE);
3779 uri = get_uri(t);
3781 if (title == NULL || uri == NULL) {
3782 show_oops(t, "can't add page to favorites");
3783 goto done;
3786 urilen = strlen(uri);
3788 for (;;) {
3789 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3790 if (feof(f) || ferror(f))
3791 break;
3793 if (linelen == urilen && !strcmp(line, uri))
3794 goto done;
3796 free(line);
3797 line = NULL;
3800 fprintf(f, "\n%s\n%s", title, uri);
3801 done:
3802 if (line)
3803 free(line);
3804 fclose(f);
3806 update_favorite_tabs(NULL);
3808 return (0);
3812 navaction(struct tab *t, struct karg *args)
3814 WebKitWebHistoryItem *item;
3816 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3817 t->tab_id, args->i);
3819 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3821 if (t->item) {
3822 if (args->i == XT_NAV_BACK)
3823 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3824 else
3825 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3826 if (item == NULL)
3827 return (XT_CB_PASSTHROUGH);
3828 webkit_web_view_go_to_back_forward_item(t->wv, item);
3829 t->item = NULL;
3830 return (XT_CB_PASSTHROUGH);
3833 switch (args->i) {
3834 case XT_NAV_BACK:
3835 marks_clear(t);
3836 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3837 if (item)
3838 webkit_web_view_go_to_back_forward_item(t->wv, item);
3839 break;
3840 case XT_NAV_FORWARD:
3841 marks_clear(t);
3842 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3843 if (item)
3844 webkit_web_view_go_to_back_forward_item(t->wv, item);
3845 break;
3846 case XT_NAV_RELOAD:
3847 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3848 if (item)
3849 webkit_web_view_go_to_back_forward_item(t->wv, item);
3850 break;
3852 return (XT_CB_PASSTHROUGH);
3856 move(struct tab *t, struct karg *args)
3858 GtkAdjustment *adjust;
3859 double pi, si, pos, ps, upper, lower, max;
3860 double percent;
3862 switch (args->i) {
3863 case XT_MOVE_DOWN:
3864 case XT_MOVE_UP:
3865 case XT_MOVE_BOTTOM:
3866 case XT_MOVE_TOP:
3867 case XT_MOVE_PAGEDOWN:
3868 case XT_MOVE_PAGEUP:
3869 case XT_MOVE_HALFDOWN:
3870 case XT_MOVE_HALFUP:
3871 case XT_MOVE_PERCENT:
3872 adjust = t->adjust_v;
3873 break;
3874 default:
3875 adjust = t->adjust_h;
3876 break;
3879 pos = gtk_adjustment_get_value(adjust);
3880 ps = gtk_adjustment_get_page_size(adjust);
3881 upper = gtk_adjustment_get_upper(adjust);
3882 lower = gtk_adjustment_get_lower(adjust);
3883 si = gtk_adjustment_get_step_increment(adjust);
3884 pi = gtk_adjustment_get_page_increment(adjust);
3885 max = upper - ps;
3887 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3888 "max %f si %f pi %f\n",
3889 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3890 pos, ps, upper, lower, max, si, pi);
3892 switch (args->i) {
3893 case XT_MOVE_DOWN:
3894 case XT_MOVE_RIGHT:
3895 pos += si;
3896 gtk_adjustment_set_value(adjust, MIN(pos, max));
3897 break;
3898 case XT_MOVE_UP:
3899 case XT_MOVE_LEFT:
3900 pos -= si;
3901 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3902 break;
3903 case XT_MOVE_BOTTOM:
3904 case XT_MOVE_FARRIGHT:
3905 gtk_adjustment_set_value(adjust, max);
3906 break;
3907 case XT_MOVE_TOP:
3908 case XT_MOVE_FARLEFT:
3909 gtk_adjustment_set_value(adjust, lower);
3910 break;
3911 case XT_MOVE_PAGEDOWN:
3912 pos += pi;
3913 gtk_adjustment_set_value(adjust, MIN(pos, max));
3914 break;
3915 case XT_MOVE_PAGEUP:
3916 pos -= pi;
3917 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3918 break;
3919 case XT_MOVE_HALFDOWN:
3920 pos += pi / 2;
3921 gtk_adjustment_set_value(adjust, MIN(pos, max));
3922 break;
3923 case XT_MOVE_HALFUP:
3924 pos -= pi / 2;
3925 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3926 break;
3927 case XT_MOVE_PERCENT:
3928 percent = atoi(args->s) / 100.0;
3929 pos = max * percent;
3930 if (pos < 0.0 || pos > max)
3931 break;
3932 gtk_adjustment_set_value(adjust, pos);
3933 break;
3934 default:
3935 return (XT_CB_PASSTHROUGH);
3938 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3940 return (XT_CB_HANDLED);
3943 void
3944 url_set_visibility(void)
3946 struct tab *t;
3948 TAILQ_FOREACH(t, &tabs, entry)
3949 if (show_url == 0) {
3950 gtk_widget_hide(t->toolbar);
3951 focus_webview(t);
3952 } else
3953 gtk_widget_show(t->toolbar);
3956 void
3957 notebook_tab_set_visibility(void)
3959 if (show_tabs == 0) {
3960 gtk_widget_hide(tab_bar);
3961 gtk_notebook_set_show_tabs(notebook, FALSE);
3962 } else {
3963 if (tab_style == XT_TABS_NORMAL) {
3964 gtk_widget_hide(tab_bar);
3965 gtk_notebook_set_show_tabs(notebook, TRUE);
3966 } else if (tab_style == XT_TABS_COMPACT) {
3967 gtk_widget_show(tab_bar);
3968 gtk_notebook_set_show_tabs(notebook, FALSE);
3973 void
3974 statusbar_set_visibility(void)
3976 struct tab *t;
3978 TAILQ_FOREACH(t, &tabs, entry)
3979 if (show_statusbar == 0) {
3980 gtk_widget_hide(t->statusbar_box);
3981 focus_webview(t);
3982 } else
3983 gtk_widget_show(t->statusbar_box);
3986 void
3987 url_set(struct tab *t, int enable_url_entry)
3989 GdkPixbuf *pixbuf;
3990 int progress;
3992 show_url = enable_url_entry;
3994 if (enable_url_entry) {
3995 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
3996 GTK_ENTRY_ICON_PRIMARY, NULL);
3997 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
3998 } else {
3999 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4000 GTK_ENTRY_ICON_PRIMARY);
4001 progress =
4002 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4003 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4004 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4005 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4006 progress);
4011 fullscreen(struct tab *t, struct karg *args)
4013 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4015 if (t == NULL)
4016 return (XT_CB_PASSTHROUGH);
4018 if (show_url == 0) {
4019 url_set(t, 1);
4020 show_tabs = 1;
4021 } else {
4022 url_set(t, 0);
4023 show_tabs = 0;
4026 url_set_visibility();
4027 notebook_tab_set_visibility();
4029 return (XT_CB_HANDLED);
4033 statustoggle(struct tab *t, struct karg *args)
4035 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4037 if (show_statusbar == 1) {
4038 show_statusbar = 0;
4039 statusbar_set_visibility();
4040 } else if (show_statusbar == 0) {
4041 show_statusbar = 1;
4042 statusbar_set_visibility();
4044 return (XT_CB_HANDLED);
4048 urlaction(struct tab *t, struct karg *args)
4050 int rv = XT_CB_HANDLED;
4052 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4054 if (t == NULL)
4055 return (XT_CB_PASSTHROUGH);
4057 switch (args->i) {
4058 case XT_URL_SHOW:
4059 if (show_url == 0) {
4060 url_set(t, 1);
4061 url_set_visibility();
4063 break;
4064 case XT_URL_HIDE:
4065 if (show_url == 1) {
4066 url_set(t, 0);
4067 url_set_visibility();
4069 break;
4071 return (rv);
4075 tabaction(struct tab *t, struct karg *args)
4077 int rv = XT_CB_HANDLED;
4078 char *url = args->s;
4079 struct undo *u;
4080 struct tab *tt;
4082 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4084 if (t == NULL)
4085 return (XT_CB_PASSTHROUGH);
4087 switch (args->i) {
4088 case XT_TAB_NEW:
4089 if (strlen(url) > 0)
4090 create_new_tab(url, NULL, 1, args->precount);
4091 else
4092 create_new_tab(NULL, NULL, 1, args->precount);
4093 break;
4094 case XT_TAB_DELETE:
4095 if (args->precount < 0)
4096 delete_tab(t);
4097 else
4098 TAILQ_FOREACH(tt, &tabs, entry)
4099 if (tt->tab_id == args->precount - 1) {
4100 delete_tab(tt);
4101 break;
4103 break;
4104 case XT_TAB_DELQUIT:
4105 if (gtk_notebook_get_n_pages(notebook) > 1)
4106 delete_tab(t);
4107 else
4108 quit(t, args);
4109 break;
4110 case XT_TAB_OPEN:
4111 if (strlen(url) > 0)
4113 else {
4114 rv = XT_CB_PASSTHROUGH;
4115 goto done;
4117 load_uri(t, url);
4118 break;
4119 case XT_TAB_SHOW:
4120 if (show_tabs == 0) {
4121 show_tabs = 1;
4122 notebook_tab_set_visibility();
4124 break;
4125 case XT_TAB_HIDE:
4126 if (show_tabs == 1) {
4127 show_tabs = 0;
4128 notebook_tab_set_visibility();
4130 break;
4131 case XT_TAB_NEXTSTYLE:
4132 if (tab_style == XT_TABS_NORMAL) {
4133 tab_style = XT_TABS_COMPACT;
4134 recolor_compact_tabs();
4136 else
4137 tab_style = XT_TABS_NORMAL;
4138 notebook_tab_set_visibility();
4139 break;
4140 case XT_TAB_UNDO_CLOSE:
4141 if (undo_count == 0) {
4142 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4143 __func__);
4144 goto done;
4145 } else {
4146 undo_count--;
4147 u = TAILQ_FIRST(&undos);
4148 create_new_tab(u->uri, u, 1, -1);
4150 TAILQ_REMOVE(&undos, u, entry);
4151 g_free(u->uri);
4152 /* u->history is freed in create_new_tab() */
4153 g_free(u);
4155 break;
4156 default:
4157 rv = XT_CB_PASSTHROUGH;
4158 goto done;
4161 done:
4162 if (args->s) {
4163 g_free(args->s);
4164 args->s = NULL;
4167 return (rv);
4171 resizetab(struct tab *t, struct karg *args)
4173 if (t == NULL || args == NULL) {
4174 show_oops(NULL, "resizetab invalid parameters");
4175 return (XT_CB_PASSTHROUGH);
4178 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4179 t->tab_id, args->i);
4181 setzoom_webkit(t, args->i);
4183 return (XT_CB_HANDLED);
4187 movetab(struct tab *t, struct karg *args)
4189 int n, dest;
4191 if (t == NULL || args == NULL) {
4192 show_oops(NULL, "movetab invalid parameters");
4193 return (XT_CB_PASSTHROUGH);
4196 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4197 t->tab_id, args->i);
4199 if (args->i >= XT_TAB_INVALID)
4200 return (XT_CB_PASSTHROUGH);
4202 if (TAILQ_EMPTY(&tabs))
4203 return (XT_CB_PASSTHROUGH);
4205 n = gtk_notebook_get_n_pages(notebook);
4206 dest = gtk_notebook_get_current_page(notebook);
4208 switch (args->i) {
4209 case XT_TAB_NEXT:
4210 if (args->precount < 0)
4211 dest = dest == n - 1 ? 0 : dest + 1;
4212 else
4213 dest = args->precount - 1;
4215 break;
4216 case XT_TAB_PREV:
4217 if (args->precount < 0)
4218 dest -= 1;
4219 else
4220 dest -= args->precount % n;
4222 if (dest < 0)
4223 dest += n;
4225 break;
4226 case XT_TAB_FIRST:
4227 dest = 0;
4228 break;
4229 case XT_TAB_LAST:
4230 dest = n - 1;
4231 break;
4232 default:
4233 return (XT_CB_PASSTHROUGH);
4236 if (dest < 0 || dest >= n)
4237 return (XT_CB_PASSTHROUGH);
4238 if (t->tab_id == dest) {
4239 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4240 return (XT_CB_HANDLED);
4243 set_current_tab(dest);
4245 return (XT_CB_HANDLED);
4248 int cmd_prefix = 0;
4251 command(struct tab *t, struct karg *args)
4253 char *s = NULL, *ss = NULL;
4254 GdkColor color;
4255 const gchar *uri;
4257 if (t == NULL || args == NULL) {
4258 show_oops(NULL, "command invalid parameters");
4259 return (XT_CB_PASSTHROUGH);
4262 switch (args->i) {
4263 case '/':
4264 s = "/";
4265 break;
4266 case '?':
4267 s = "?";
4268 break;
4269 case ':':
4270 if (cmd_prefix == 0)
4271 s = ":";
4272 else {
4273 ss = g_strdup_printf(":%d", cmd_prefix);
4274 s = ss;
4275 cmd_prefix = 0;
4277 break;
4278 case XT_CMD_OPEN:
4279 s = ":open ";
4280 break;
4281 case XT_CMD_TABNEW:
4282 s = ":tabnew ";
4283 break;
4284 case XT_CMD_OPEN_CURRENT:
4285 s = ":open ";
4286 /* FALL THROUGH */
4287 case XT_CMD_TABNEW_CURRENT:
4288 if (!s) /* FALL THROUGH? */
4289 s = ":tabnew ";
4290 if ((uri = get_uri(t)) != NULL) {
4291 ss = g_strdup_printf("%s%s", s, uri);
4292 s = ss;
4294 break;
4295 default:
4296 show_oops(t, "command: invalid opcode %d", args->i);
4297 return (XT_CB_PASSTHROUGH);
4300 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4302 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4303 gdk_color_parse(XT_COLOR_WHITE, &color);
4304 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4305 show_cmd(t);
4306 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4307 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4309 if (ss)
4310 g_free(ss);
4312 return (XT_CB_HANDLED);
4316 * Return a new string with a download row (in html)
4317 * appended. Old string is freed.
4319 char *
4320 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4323 WebKitDownloadStatus stat;
4324 char *status_html = NULL, *cmd_html = NULL, *new_html;
4325 gdouble progress;
4326 char cur_sz[FMT_SCALED_STRSIZE];
4327 char tot_sz[FMT_SCALED_STRSIZE];
4328 char *xtp_prefix;
4330 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4332 /* All actions wil take this form:
4333 * xxxt://class/seskey
4335 xtp_prefix = g_strdup_printf("%s%d/%s/",
4336 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4338 stat = webkit_download_get_status(dl->download);
4340 switch (stat) {
4341 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4342 status_html = g_strdup_printf("Finished");
4343 cmd_html = g_strdup_printf(
4344 "<a href='%s%d/%d'>Remove</a>",
4345 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4346 break;
4347 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4348 /* gather size info */
4349 progress = 100 * webkit_download_get_progress(dl->download);
4351 fmt_scaled(
4352 webkit_download_get_current_size(dl->download), cur_sz);
4353 fmt_scaled(
4354 webkit_download_get_total_size(dl->download), tot_sz);
4356 status_html = g_strdup_printf(
4357 "<div style='width: 100%%' align='center'>"
4358 "<div class='progress-outer'>"
4359 "<div class='progress-inner' style='width: %.2f%%'>"
4360 "</div></div></div>"
4361 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4362 progress, cur_sz, tot_sz, progress);
4364 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4365 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4367 break;
4368 /* LLL */
4369 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4370 status_html = g_strdup_printf("Cancelled");
4371 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4372 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4373 break;
4374 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4375 status_html = g_strdup_printf("Error!");
4376 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4377 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4378 break;
4379 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4380 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4381 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4382 status_html = g_strdup_printf("Starting");
4383 break;
4384 default:
4385 show_oops(t, "%s: unknown download status", __func__);
4388 new_html = g_strdup_printf(
4389 "%s\n<tr><td>%s</td><td>%s</td>"
4390 "<td style='text-align:center'>%s</td></tr>\n",
4391 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4392 status_html, cmd_html);
4393 g_free(html);
4395 if (status_html)
4396 g_free(status_html);
4398 if (cmd_html)
4399 g_free(cmd_html);
4401 g_free(xtp_prefix);
4403 return new_html;
4407 * update all download tabs apart from one. Pass NULL if
4408 * you want to update all.
4410 void
4411 update_download_tabs(struct tab *apart_from)
4413 struct tab *t;
4414 if (!updating_dl_tabs) {
4415 updating_dl_tabs = 1; /* stop infinite recursion */
4416 TAILQ_FOREACH(t, &tabs, entry)
4417 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4418 && (t != apart_from))
4419 xtp_page_dl(t, NULL);
4420 updating_dl_tabs = 0;
4425 * update all cookie tabs apart from one. Pass NULL if
4426 * you want to update all.
4428 void
4429 update_cookie_tabs(struct tab *apart_from)
4431 struct tab *t;
4432 if (!updating_cl_tabs) {
4433 updating_cl_tabs = 1; /* stop infinite recursion */
4434 TAILQ_FOREACH(t, &tabs, entry)
4435 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4436 && (t != apart_from))
4437 xtp_page_cl(t, NULL);
4438 updating_cl_tabs = 0;
4443 * update all history tabs apart from one. Pass NULL if
4444 * you want to update all.
4446 void
4447 update_history_tabs(struct tab *apart_from)
4449 struct tab *t;
4451 if (!updating_hl_tabs) {
4452 updating_hl_tabs = 1; /* stop infinite recursion */
4453 TAILQ_FOREACH(t, &tabs, entry)
4454 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4455 && (t != apart_from))
4456 xtp_page_hl(t, NULL);
4457 updating_hl_tabs = 0;
4461 /* cookie management XTP page */
4463 xtp_page_cl(struct tab *t, struct karg *args)
4465 char *body, *page, *tmp;
4466 int i = 1; /* all ids start 1 */
4467 GSList *sc, *pc, *pc_start;
4468 SoupCookie *c;
4469 char *type, *table_headers, *last_domain;
4471 DNPRINTF(XT_D_CMD, "%s", __func__);
4473 if (t == NULL) {
4474 show_oops(NULL, "%s invalid parameters", __func__);
4475 return (1);
4478 /* Generate a new session key */
4479 if (!updating_cl_tabs)
4480 generate_xtp_session_key(&cl_session_key);
4482 /* table headers */
4483 table_headers = g_strdup_printf("<table><tr>"
4484 "<th>Type</th>"
4485 "<th>Name</th>"
4486 "<th style='width:200px'>Value</th>"
4487 "<th>Path</th>"
4488 "<th>Expires</th>"
4489 "<th>Secure</th>"
4490 "<th>HTTP<br />only</th>"
4491 "<th style='width:40px'>Rm</th></tr>\n");
4493 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4494 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4495 pc_start = pc;
4497 body = NULL;
4498 last_domain = strdup("");
4499 for (; sc; sc = sc->next) {
4500 c = sc->data;
4502 if (strcmp(last_domain, c->domain) != 0) {
4503 /* new domain */
4504 free(last_domain);
4505 last_domain = strdup(c->domain);
4507 if (body != NULL) {
4508 tmp = body;
4509 body = g_strdup_printf("%s</table>"
4510 "<h2>%s</h2>%s\n",
4511 body, c->domain, table_headers);
4512 g_free(tmp);
4513 } else {
4514 /* first domain */
4515 body = g_strdup_printf("<h2>%s</h2>%s\n",
4516 c->domain, table_headers);
4520 type = "Session";
4521 for (pc = pc_start; pc; pc = pc->next)
4522 if (soup_cookie_equal(pc->data, c)) {
4523 type = "Session + Persistent";
4524 break;
4527 tmp = body;
4528 body = g_strdup_printf(
4529 "%s\n<tr>"
4530 "<td>%s</td>"
4531 "<td style='word-wrap:normal'>%s</td>"
4532 "<td>"
4533 " <textarea rows='4'>%s</textarea>"
4534 "</td>"
4535 "<td>%s</td>"
4536 "<td>%s</td>"
4537 "<td>%d</td>"
4538 "<td>%d</td>"
4539 "<td style='text-align:center'>"
4540 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4541 body,
4542 type,
4543 c->name,
4544 c->value,
4545 c->path,
4546 c->expires ?
4547 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4548 c->secure,
4549 c->http_only,
4551 XT_XTP_STR,
4552 XT_XTP_CL,
4553 cl_session_key,
4554 XT_XTP_CL_REMOVE,
4558 g_free(tmp);
4559 i++;
4562 soup_cookies_free(sc);
4563 soup_cookies_free(pc);
4565 /* small message if there are none */
4566 if (i == 1) {
4567 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4568 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4570 tmp = body;
4571 body = g_strdup_printf("%s</table>", body);
4572 g_free(tmp);
4574 page = get_html_page("Cookie Jar", body, "", TRUE);
4575 g_free(body);
4576 g_free(table_headers);
4577 g_free(last_domain);
4579 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4580 update_cookie_tabs(t);
4582 g_free(page);
4584 return (0);
4588 xtp_page_hl(struct tab *t, struct karg *args)
4590 char *body, *page, *tmp;
4591 struct history *h;
4592 int i = 1; /* all ids start 1 */
4594 DNPRINTF(XT_D_CMD, "%s", __func__);
4596 if (t == NULL) {
4597 show_oops(NULL, "%s invalid parameters", __func__);
4598 return (1);
4601 /* Generate a new session key */
4602 if (!updating_hl_tabs)
4603 generate_xtp_session_key(&hl_session_key);
4605 /* body */
4606 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4607 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4609 RB_FOREACH_REVERSE(h, history_list, &hl) {
4610 tmp = body;
4611 body = g_strdup_printf(
4612 "%s\n<tr>"
4613 "<td><a href='%s'>%s</a></td>"
4614 "<td>%s</td>"
4615 "<td style='text-align: center'>"
4616 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4617 body, h->uri, h->uri, h->title,
4618 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4619 XT_XTP_HL_REMOVE, i);
4621 g_free(tmp);
4622 i++;
4625 /* small message if there are none */
4626 if (i == 1) {
4627 tmp = body;
4628 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4629 "colspan='3'>No History</td></tr>\n", body);
4630 g_free(tmp);
4633 tmp = body;
4634 body = g_strdup_printf("%s</table>", body);
4635 g_free(tmp);
4637 page = get_html_page("History", body, "", TRUE);
4638 g_free(body);
4641 * update all history manager tabs as the xtp session
4642 * key has now changed. No need to update the current tab.
4643 * Already did that above.
4645 update_history_tabs(t);
4647 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4648 g_free(page);
4650 return (0);
4654 * Generate a web page detailing the status of any downloads
4657 xtp_page_dl(struct tab *t, struct karg *args)
4659 struct download *dl;
4660 char *body, *page, *tmp;
4661 char *ref;
4662 int n_dl = 1;
4664 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4666 if (t == NULL) {
4667 show_oops(NULL, "%s invalid parameters", __func__);
4668 return (1);
4672 * Generate a new session key for next page instance.
4673 * This only happens for the top level call to xtp_page_dl()
4674 * in which case updating_dl_tabs is 0.
4676 if (!updating_dl_tabs)
4677 generate_xtp_session_key(&dl_session_key);
4679 /* header - with refresh so as to update */
4680 if (refresh_interval >= 1)
4681 ref = g_strdup_printf(
4682 "<meta http-equiv='refresh' content='%u"
4683 ";url=%s%d/%s/%d' />\n",
4684 refresh_interval,
4685 XT_XTP_STR,
4686 XT_XTP_DL,
4687 dl_session_key,
4688 XT_XTP_DL_LIST);
4689 else
4690 ref = g_strdup("");
4692 body = g_strdup_printf("<div align='center'>"
4693 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4694 "</p><table><tr><th style='width: 60%%'>"
4695 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4696 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4698 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4699 body = xtp_page_dl_row(t, body, dl);
4700 n_dl++;
4703 /* message if no downloads in list */
4704 if (n_dl == 1) {
4705 tmp = body;
4706 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4707 " style='text-align: center'>"
4708 "No downloads</td></tr>\n", body);
4709 g_free(tmp);
4712 tmp = body;
4713 body = g_strdup_printf("%s</table></div>", body);
4714 g_free(tmp);
4716 page = get_html_page("Downloads", body, ref, 1);
4717 g_free(ref);
4718 g_free(body);
4721 * update all download manager tabs as the xtp session
4722 * key has now changed. No need to update the current tab.
4723 * Already did that above.
4725 update_download_tabs(t);
4727 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4728 g_free(page);
4730 return (0);
4734 search(struct tab *t, struct karg *args)
4736 gboolean d;
4738 if (t == NULL || args == NULL) {
4739 show_oops(NULL, "search invalid parameters");
4740 return (1);
4742 if (t->search_text == NULL) {
4743 if (global_search == NULL)
4744 return (XT_CB_PASSTHROUGH);
4745 else {
4746 t->search_text = g_strdup(global_search);
4747 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4748 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4752 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4753 t->tab_id, args->i, t->search_forward, t->search_text);
4755 switch (args->i) {
4756 case XT_SEARCH_NEXT:
4757 d = t->search_forward;
4758 break;
4759 case XT_SEARCH_PREV:
4760 d = !t->search_forward;
4761 break;
4762 default:
4763 return (XT_CB_PASSTHROUGH);
4766 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4768 return (XT_CB_HANDLED);
4771 struct settings_args {
4772 char **body;
4773 int i;
4776 void
4777 print_setting(struct settings *s, char *val, void *cb_args)
4779 char *tmp, *color;
4780 struct settings_args *sa = cb_args;
4782 if (sa == NULL)
4783 return;
4785 if (s->flags & XT_SF_RUNTIME)
4786 color = "#22cc22";
4787 else
4788 color = "#cccccc";
4790 tmp = *sa->body;
4791 *sa->body = g_strdup_printf(
4792 "%s\n<tr>"
4793 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4794 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4795 *sa->body,
4796 color,
4797 s->name,
4798 color,
4801 g_free(tmp);
4802 sa->i++;
4806 set(struct tab *t, struct karg *args)
4808 char *body, *page, *tmp;
4809 int i = 1;
4810 struct settings_args sa;
4812 bzero(&sa, sizeof sa);
4813 sa.body = &body;
4815 /* body */
4816 body = g_strdup_printf("<div align='center'><table><tr>"
4817 "<th align='left'>Setting</th>"
4818 "<th align='left'>Value</th></tr>\n");
4820 settings_walk(print_setting, &sa);
4821 i = sa.i;
4823 /* small message if there are none */
4824 if (i == 1) {
4825 tmp = body;
4826 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4827 "colspan='2'>No settings</td></tr>\n", body);
4828 g_free(tmp);
4831 tmp = body;
4832 body = g_strdup_printf("%s</table></div>", body);
4833 g_free(tmp);
4835 page = get_html_page("Settings", body, "", 0);
4837 g_free(body);
4839 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4841 g_free(page);
4843 return (XT_CB_PASSTHROUGH);
4847 session_save(struct tab *t, char *filename)
4849 struct karg a;
4850 int rv = 1;
4852 if (strlen(filename) == 0)
4853 goto done;
4855 if (filename[0] == '.' || filename[0] == '/')
4856 goto done;
4858 a.s = filename;
4859 if (save_tabs(t, &a))
4860 goto done;
4861 strlcpy(named_session, filename, sizeof named_session);
4863 rv = 0;
4864 done:
4865 return (rv);
4869 session_open(struct tab *t, char *filename)
4871 struct karg a;
4872 int rv = 1;
4874 if (strlen(filename) == 0)
4875 goto done;
4877 if (filename[0] == '.' || filename[0] == '/')
4878 goto done;
4880 a.s = filename;
4881 a.i = XT_SES_CLOSETABS;
4882 if (open_tabs(t, &a))
4883 goto done;
4885 strlcpy(named_session, filename, sizeof named_session);
4887 rv = 0;
4888 done:
4889 return (rv);
4893 session_delete(struct tab *t, char *filename)
4895 char file[PATH_MAX];
4896 int rv = 1;
4898 if (strlen(filename) == 0)
4899 goto done;
4901 if (filename[0] == '.' || filename[0] == '/')
4902 goto done;
4904 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4905 if (unlink(file))
4906 goto done;
4908 if (!strcmp(filename, named_session))
4909 strlcpy(named_session, XT_SAVED_TABS_FILE,
4910 sizeof named_session);
4912 rv = 0;
4913 done:
4914 return (rv);
4918 session_cmd(struct tab *t, struct karg *args)
4920 char *filename = args->s;
4922 if (t == NULL)
4923 return (1);
4925 if (args->i & XT_SHOW)
4926 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4927 XT_SAVED_TABS_FILE : named_session);
4928 else if (args->i & XT_SAVE) {
4929 if (session_save(t, filename)) {
4930 show_oops(t, "Can't save session: %s",
4931 filename ? filename : "INVALID");
4932 goto done;
4934 } else if (args->i & XT_OPEN) {
4935 if (session_open(t, filename)) {
4936 show_oops(t, "Can't open session: %s",
4937 filename ? filename : "INVALID");
4938 goto done;
4940 } else if (args->i & XT_DELETE) {
4941 if (session_delete(t, filename)) {
4942 show_oops(t, "Can't delete session: %s",
4943 filename ? filename : "INVALID");
4944 goto done;
4947 done:
4948 return (XT_CB_PASSTHROUGH);
4952 * Make a hardcopy of the page
4955 print_page(struct tab *t, struct karg *args)
4957 WebKitWebFrame *frame;
4958 GtkPageSetup *ps;
4959 GtkPrintOperation *op;
4960 GtkPrintOperationAction action;
4961 GtkPrintOperationResult print_res;
4962 GError *g_err = NULL;
4963 int marg_l, marg_r, marg_t, marg_b;
4965 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4967 ps = gtk_page_setup_new();
4968 op = gtk_print_operation_new();
4969 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4970 frame = webkit_web_view_get_main_frame(t->wv);
4972 /* the default margins are too small, so we will bump them */
4973 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4974 XT_PRINT_EXTRA_MARGIN;
4975 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4976 XT_PRINT_EXTRA_MARGIN;
4977 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4978 XT_PRINT_EXTRA_MARGIN;
4979 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4980 XT_PRINT_EXTRA_MARGIN;
4982 /* set margins */
4983 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4984 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4985 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4986 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4988 gtk_print_operation_set_default_page_setup(op, ps);
4990 /* this appears to free 'op' and 'ps' */
4991 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4993 /* check it worked */
4994 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4995 show_oops(NULL, "can't print: %s", g_err->message);
4996 g_error_free (g_err);
4997 return (1);
5000 return (0);
5004 go_home(struct tab *t, struct karg *args)
5006 load_uri(t, home);
5007 return (0);
5011 restart(struct tab *t, struct karg *args)
5013 struct karg a;
5015 a.s = XT_RESTART_TABS_FILE;
5016 save_tabs(t, &a);
5017 execvp(start_argv[0], start_argv);
5018 /* NOTREACHED */
5020 return (0);
5023 #define CTRL GDK_CONTROL_MASK
5024 #define MOD1 GDK_MOD1_MASK
5025 #define SHFT GDK_SHIFT_MASK
5027 /* inherent to GTK not all keys will be caught at all times */
5028 /* XXX sort key bindings */
5029 struct key_binding {
5030 char *cmd;
5031 guint mask;
5032 guint use_in_entry;
5033 guint key;
5034 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5035 } keys[] = {
5036 { "cookiejar", MOD1, 0, GDK_j },
5037 { "downloadmgr", MOD1, 0, GDK_d },
5038 { "history", MOD1, 0, GDK_h },
5039 { "print", CTRL, 0, GDK_p },
5040 { "search", 0, 0, GDK_slash },
5041 { "searchb", 0, 0, GDK_question },
5042 { "statustoggle", CTRL, 0, GDK_n },
5043 { "command", 0, 0, GDK_colon },
5044 { "qa", CTRL, 0, GDK_q },
5045 { "restart", MOD1, 0, GDK_q },
5046 { "js toggle", CTRL, 0, GDK_j },
5047 { "cookie toggle", MOD1, 0, GDK_c },
5048 { "togglesrc", CTRL, 0, GDK_s },
5049 { "yankuri", 0, 0, GDK_y },
5050 { "pasteuricur", 0, 0, GDK_p },
5051 { "pasteurinew", 0, 0, GDK_P },
5052 { "toplevel toggle", 0, 0, GDK_F4 },
5053 { "help", 0, 0, GDK_F1 },
5054 { "run_script", MOD1, 0, GDK_r },
5056 /* search */
5057 { "searchnext", 0, 0, GDK_n },
5058 { "searchprevious", 0, 0, GDK_N },
5060 /* focus */
5061 { "focusaddress", 0, 0, GDK_F6 },
5062 { "focussearch", 0, 0, GDK_F7 },
5064 /* hinting */
5065 { "hinting", 0, 0, GDK_f },
5067 /* custom stylesheet */
5068 { "userstyle", 0, 0, GDK_i },
5070 /* navigation */
5071 { "goback", 0, 0, GDK_BackSpace },
5072 { "goback", MOD1, 0, GDK_Left },
5073 { "goforward", SHFT, 0, GDK_BackSpace },
5074 { "goforward", MOD1, 0, GDK_Right },
5075 { "reload", 0, 0, GDK_F5 },
5076 { "reload", CTRL, 0, GDK_r },
5077 { "reload", CTRL, 0, GDK_l },
5078 { "favorites", MOD1, 1, GDK_f },
5080 /* vertical movement */
5081 { "scrolldown", 0, 0, GDK_j },
5082 { "scrolldown", 0, 0, GDK_Down },
5083 { "scrollup", 0, 0, GDK_Up },
5084 { "scrollup", 0, 0, GDK_k },
5085 { "scrollbottom", 0, 0, GDK_G },
5086 { "scrollbottom", 0, 0, GDK_End },
5087 { "scrolltop", 0, 0, GDK_Home },
5088 { "scrollpagedown", 0, 0, GDK_space },
5089 { "scrollpagedown", CTRL, 0, GDK_f },
5090 { "scrollhalfdown", CTRL, 0, GDK_d },
5091 { "scrollpagedown", 0, 0, GDK_Page_Down },
5092 { "scrollpageup", 0, 0, GDK_Page_Up },
5093 { "scrollpageup", CTRL, 0, GDK_b },
5094 { "scrollhalfup", CTRL, 0, GDK_u },
5095 /* horizontal movement */
5096 { "scrollright", 0, 0, GDK_l },
5097 { "scrollright", 0, 0, GDK_Right },
5098 { "scrollleft", 0, 0, GDK_Left },
5099 { "scrollleft", 0, 0, GDK_h },
5100 { "scrollfarright", 0, 0, GDK_dollar },
5101 { "scrollfarleft", 0, 0, GDK_0 },
5103 /* tabs */
5104 { "tabnew", CTRL, 0, GDK_t },
5105 { "999tabnew", CTRL, 0, GDK_T },
5106 { "tabclose", CTRL, 1, GDK_w },
5107 { "tabundoclose", 0, 0, GDK_U },
5108 { "tabnext 1", CTRL, 0, GDK_1 },
5109 { "tabnext 2", CTRL, 0, GDK_2 },
5110 { "tabnext 3", CTRL, 0, GDK_3 },
5111 { "tabnext 4", CTRL, 0, GDK_4 },
5112 { "tabnext 5", CTRL, 0, GDK_5 },
5113 { "tabnext 6", CTRL, 0, GDK_6 },
5114 { "tabnext 7", CTRL, 0, GDK_7 },
5115 { "tabnext 8", CTRL, 0, GDK_8 },
5116 { "tabnext 9", CTRL, 0, GDK_9 },
5117 { "tabfirst", CTRL, 0, GDK_less },
5118 { "tablast", CTRL, 0, GDK_greater },
5119 { "tabprevious", CTRL, 0, GDK_Left },
5120 { "tabnext", CTRL, 0, GDK_Right },
5121 { "focusout", CTRL, 0, GDK_minus },
5122 { "focusin", CTRL, 0, GDK_plus },
5123 { "focusin", CTRL, 0, GDK_equal },
5124 { "focusreset", CTRL, 0, GDK_0 },
5126 /* command aliases (handy when -S flag is used) */
5127 { "promptopen", 0, 0, GDK_F9 },
5128 { "promptopencurrent", 0, 0, GDK_F10 },
5129 { "prompttabnew", 0, 0, GDK_F11 },
5130 { "prompttabnewcurrent",0, 0, GDK_F12 },
5132 TAILQ_HEAD(keybinding_list, key_binding);
5134 void
5135 walk_kb(struct settings *s,
5136 void (*cb)(struct settings *, char *, void *), void *cb_args)
5138 struct key_binding *k;
5139 char str[1024];
5141 if (s == NULL || cb == NULL) {
5142 show_oops(NULL, "walk_kb invalid parameters");
5143 return;
5146 TAILQ_FOREACH(k, &kbl, entry) {
5147 if (k->cmd == NULL)
5148 continue;
5149 str[0] = '\0';
5151 /* sanity */
5152 if (gdk_keyval_name(k->key) == NULL)
5153 continue;
5155 strlcat(str, k->cmd, sizeof str);
5156 strlcat(str, ",", sizeof str);
5158 if (k->mask & GDK_SHIFT_MASK)
5159 strlcat(str, "S-", sizeof str);
5160 if (k->mask & GDK_CONTROL_MASK)
5161 strlcat(str, "C-", sizeof str);
5162 if (k->mask & GDK_MOD1_MASK)
5163 strlcat(str, "M1-", sizeof str);
5164 if (k->mask & GDK_MOD2_MASK)
5165 strlcat(str, "M2-", sizeof str);
5166 if (k->mask & GDK_MOD3_MASK)
5167 strlcat(str, "M3-", sizeof str);
5168 if (k->mask & GDK_MOD4_MASK)
5169 strlcat(str, "M4-", sizeof str);
5170 if (k->mask & GDK_MOD5_MASK)
5171 strlcat(str, "M5-", sizeof str);
5173 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5174 cb(s, str, cb_args);
5178 void
5179 init_keybindings(void)
5181 int i;
5182 struct key_binding *k;
5184 for (i = 0; i < LENGTH(keys); i++) {
5185 k = g_malloc0(sizeof *k);
5186 k->cmd = keys[i].cmd;
5187 k->mask = keys[i].mask;
5188 k->use_in_entry = keys[i].use_in_entry;
5189 k->key = keys[i].key;
5190 TAILQ_INSERT_HEAD(&kbl, k, entry);
5192 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5193 k->cmd ? k->cmd : "unnamed key");
5197 void
5198 keybinding_clearall(void)
5200 struct key_binding *k, *next;
5202 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5203 next = TAILQ_NEXT(k, entry);
5204 if (k->cmd == NULL)
5205 continue;
5207 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5208 k->cmd ? k->cmd : "unnamed key");
5209 TAILQ_REMOVE(&kbl, k, entry);
5210 g_free(k);
5215 keybinding_add(char *cmd, char *key, int use_in_entry)
5217 struct key_binding *k;
5218 guint keyval, mask = 0;
5219 int i;
5221 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5223 /* Keys which are to be used in entry have been prefixed with an
5224 * exclamation mark. */
5225 if (use_in_entry)
5226 key++;
5228 /* find modifier keys */
5229 if (strstr(key, "S-"))
5230 mask |= GDK_SHIFT_MASK;
5231 if (strstr(key, "C-"))
5232 mask |= GDK_CONTROL_MASK;
5233 if (strstr(key, "M1-"))
5234 mask |= GDK_MOD1_MASK;
5235 if (strstr(key, "M2-"))
5236 mask |= GDK_MOD2_MASK;
5237 if (strstr(key, "M3-"))
5238 mask |= GDK_MOD3_MASK;
5239 if (strstr(key, "M4-"))
5240 mask |= GDK_MOD4_MASK;
5241 if (strstr(key, "M5-"))
5242 mask |= GDK_MOD5_MASK;
5244 /* find keyname */
5245 for (i = strlen(key) - 1; i > 0; i--)
5246 if (key[i] == '-')
5247 key = &key[i + 1];
5249 /* validate keyname */
5250 keyval = gdk_keyval_from_name(key);
5251 if (keyval == GDK_VoidSymbol) {
5252 warnx("invalid keybinding name %s", key);
5253 return (1);
5255 /* must run this test too, gtk+ doesn't handle 10 for example */
5256 if (gdk_keyval_name(keyval) == NULL) {
5257 warnx("invalid keybinding name %s", key);
5258 return (1);
5261 /* Remove eventual dupes. */
5262 TAILQ_FOREACH(k, &kbl, entry)
5263 if (k->key == keyval && k->mask == mask) {
5264 TAILQ_REMOVE(&kbl, k, entry);
5265 g_free(k);
5266 break;
5269 /* add keyname */
5270 k = g_malloc0(sizeof *k);
5271 k->cmd = g_strdup(cmd);
5272 k->mask = mask;
5273 k->use_in_entry = use_in_entry;
5274 k->key = keyval;
5276 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5277 k->cmd,
5278 k->mask,
5279 k->use_in_entry,
5280 k->key);
5281 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5282 k->cmd, gdk_keyval_name(keyval));
5284 TAILQ_INSERT_HEAD(&kbl, k, entry);
5286 return (0);
5290 add_kb(struct settings *s, char *entry)
5292 char *kb, *key;
5294 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5296 /* clearall is special */
5297 if (!strcmp(entry, "clearall")) {
5298 keybinding_clearall();
5299 return (0);
5302 kb = strstr(entry, ",");
5303 if (kb == NULL)
5304 return (1);
5305 *kb = '\0';
5306 key = kb + 1;
5308 return (keybinding_add(entry, key, key[0] == '!'));
5311 struct cmd {
5312 char *cmd;
5313 int level;
5314 int (*func)(struct tab *, struct karg *);
5315 int arg;
5316 int type;
5317 } cmds[] = {
5318 { "command", 0, command, ':', 0 },
5319 { "search", 0, command, '/', 0 },
5320 { "searchb", 0, command, '?', 0 },
5321 { "togglesrc", 0, toggle_src, 0, 0 },
5323 /* yanking and pasting */
5324 { "yankuri", 0, yank_uri, 0, 0 },
5325 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5326 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5327 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5329 /* search */
5330 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5331 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5333 /* focus */
5334 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5335 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5337 /* hinting */
5338 { "hinting", 0, hint, 0, 0 },
5340 /* custom stylesheet */
5341 { "userstyle", 0, userstyle, 0, 0 },
5343 /* navigation */
5344 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5345 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5346 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5348 /* vertical movement */
5349 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5350 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5351 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5352 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5353 { "1", 0, move, XT_MOVE_TOP, 0 },
5354 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5355 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5356 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5357 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5358 /* horizontal movement */
5359 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5360 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5361 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5362 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5364 { "favorites", 0, xtp_page_fl, 0, 0 },
5365 { "fav", 0, xtp_page_fl, 0, 0 },
5366 { "favadd", 0, add_favorite, 0, 0 },
5368 { "qall", 0, quit, 0, 0 },
5369 { "quitall", 0, quit, 0, 0 },
5370 { "w", 0, save_tabs, 0, 0 },
5371 { "wq", 0, save_tabs_and_quit, 0, 0 },
5372 { "help", 0, help, 0, 0 },
5373 { "about", 0, about, 0, 0 },
5374 { "stats", 0, stats, 0, 0 },
5375 { "version", 0, about, 0, 0 },
5377 /* js command */
5378 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5379 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5380 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5381 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5382 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5383 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5384 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5385 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5386 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5387 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5388 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5390 /* cookie command */
5391 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5392 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5393 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5394 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5395 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5396 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5397 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5398 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5399 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5400 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5401 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5403 /* toplevel (domain) command */
5404 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5405 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5407 /* cookie jar */
5408 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5410 /* cert command */
5411 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5412 { "save", 1, cert_cmd, XT_SAVE, 0 },
5413 { "show", 1, cert_cmd, XT_SHOW, 0 },
5415 { "ca", 0, ca_cmd, 0, 0 },
5416 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5417 { "dl", 0, xtp_page_dl, 0, 0 },
5418 { "h", 0, xtp_page_hl, 0, 0 },
5419 { "history", 0, xtp_page_hl, 0, 0 },
5420 { "home", 0, go_home, 0, 0 },
5421 { "restart", 0, restart, 0, 0 },
5422 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5423 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5424 { "statustoggle", 0, statustoggle, 0, 0 },
5425 { "run_script", 0, run_page_script, 0, XT_USERARG },
5427 { "print", 0, print_page, 0, 0 },
5429 /* tabs */
5430 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5431 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5432 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5433 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5434 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5435 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5436 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5437 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5438 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5439 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5440 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5441 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5442 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5443 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5444 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5445 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5446 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5447 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5448 { "buffers", 0, buffers, 0, 0 },
5449 { "ls", 0, buffers, 0, 0 },
5450 { "tabs", 0, buffers, 0, 0 },
5452 /* command aliases (handy when -S flag is used) */
5453 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5454 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5455 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5456 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5458 /* settings */
5459 { "set", 0, set, 0, 0 },
5460 { "fullscreen", 0, fullscreen, 0, 0 },
5461 { "f", 0, fullscreen, 0, 0 },
5463 /* sessions */
5464 { "session", 0, session_cmd, XT_SHOW, 0 },
5465 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5466 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5467 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5468 { "show", 1, session_cmd, XT_SHOW, 0 },
5471 struct {
5472 int index;
5473 int len;
5474 gchar *list[256];
5475 } cmd_status = {-1, 0};
5477 gboolean
5478 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5481 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5482 btn_down = 0;
5484 return (FALSE);
5487 gboolean
5488 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5490 struct karg a;
5492 hide_oops(t);
5493 hide_buffers(t);
5495 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5496 btn_down = 1;
5497 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5498 /* go backward */
5499 a.i = XT_NAV_BACK;
5500 navaction(t, &a);
5502 return (TRUE);
5503 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5504 /* go forward */
5505 a.i = XT_NAV_FORWARD;
5506 navaction(t, &a);
5508 return (TRUE);
5511 return (FALSE);
5514 gboolean
5515 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5517 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5519 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5520 delete_tab(t);
5522 return (FALSE);
5526 * cancel, remove, etc. downloads
5528 void
5529 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5531 struct download find, *d = NULL;
5533 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5535 /* some commands require a valid download id */
5536 if (cmd != XT_XTP_DL_LIST) {
5537 /* lookup download in question */
5538 find.id = id;
5539 d = RB_FIND(download_list, &downloads, &find);
5541 if (d == NULL) {
5542 show_oops(t, "%s: no such download", __func__);
5543 return;
5547 /* decide what to do */
5548 switch (cmd) {
5549 case XT_XTP_DL_CANCEL:
5550 webkit_download_cancel(d->download);
5551 break;
5552 case XT_XTP_DL_REMOVE:
5553 webkit_download_cancel(d->download); /* just incase */
5554 g_object_unref(d->download);
5555 RB_REMOVE(download_list, &downloads, d);
5556 break;
5557 case XT_XTP_DL_LIST:
5558 /* Nothing */
5559 break;
5560 default:
5561 show_oops(t, "%s: unknown command", __func__);
5562 break;
5564 xtp_page_dl(t, NULL);
5568 * Actions on history, only does one thing for now, but
5569 * we provide the function for future actions
5571 void
5572 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5574 struct history *h, *next;
5575 int i = 1;
5577 switch (cmd) {
5578 case XT_XTP_HL_REMOVE:
5579 /* walk backwards, as listed in reverse */
5580 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5581 next = RB_PREV(history_list, &hl, h);
5582 if (id == i) {
5583 RB_REMOVE(history_list, &hl, h);
5584 g_free((gpointer) h->title);
5585 g_free((gpointer) h->uri);
5586 g_free(h);
5587 break;
5589 i++;
5591 break;
5592 case XT_XTP_HL_LIST:
5593 /* Nothing - just xtp_page_hl() below */
5594 break;
5595 default:
5596 show_oops(t, "%s: unknown command", __func__);
5597 break;
5600 xtp_page_hl(t, NULL);
5603 /* remove a favorite */
5604 void
5605 remove_favorite(struct tab *t, int index)
5607 char file[PATH_MAX], *title, *uri = NULL;
5608 char *new_favs, *tmp;
5609 FILE *f;
5610 int i;
5611 size_t len, lineno;
5613 /* open favorites */
5614 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5616 if ((f = fopen(file, "r")) == NULL) {
5617 show_oops(t, "%s: can't open favorites: %s",
5618 __func__, strerror(errno));
5619 return;
5622 /* build a string which will become the new favroites file */
5623 new_favs = g_strdup("");
5625 for (i = 1;;) {
5626 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5627 if (feof(f) || ferror(f))
5628 break;
5629 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5630 if (len == 0) {
5631 free(title);
5632 title = NULL;
5633 continue;
5636 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5637 if (feof(f) || ferror(f)) {
5638 show_oops(t, "%s: can't parse favorites %s",
5639 __func__, strerror(errno));
5640 goto clean;
5644 /* as long as this isn't the one we are deleting add to file */
5645 if (i != index) {
5646 tmp = new_favs;
5647 new_favs = g_strdup_printf("%s%s\n%s\n",
5648 new_favs, title, uri);
5649 g_free(tmp);
5652 free(uri);
5653 uri = NULL;
5654 free(title);
5655 title = NULL;
5656 i++;
5658 fclose(f);
5660 /* write back new favorites file */
5661 if ((f = fopen(file, "w")) == NULL) {
5662 show_oops(t, "%s: can't open favorites: %s",
5663 __func__, strerror(errno));
5664 goto clean;
5667 fwrite(new_favs, strlen(new_favs), 1, f);
5668 fclose(f);
5670 clean:
5671 if (uri)
5672 free(uri);
5673 if (title)
5674 free(title);
5676 g_free(new_favs);
5679 void
5680 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5682 switch (cmd) {
5683 case XT_XTP_FL_LIST:
5684 /* nothing, just the below call to xtp_page_fl() */
5685 break;
5686 case XT_XTP_FL_REMOVE:
5687 remove_favorite(t, arg);
5688 break;
5689 default:
5690 show_oops(t, "%s: invalid favorites command", __func__);
5691 break;
5694 xtp_page_fl(t, NULL);
5697 void
5698 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5700 switch (cmd) {
5701 case XT_XTP_CL_LIST:
5702 /* nothing, just xtp_page_cl() */
5703 break;
5704 case XT_XTP_CL_REMOVE:
5705 remove_cookie(arg);
5706 break;
5707 default:
5708 show_oops(t, "%s: unknown cookie xtp command", __func__);
5709 break;
5712 xtp_page_cl(t, NULL);
5715 /* link an XTP class to it's session key and handler function */
5716 struct xtp_despatch {
5717 uint8_t xtp_class;
5718 char **session_key;
5719 void (*handle_func)(struct tab *, uint8_t, int);
5722 struct xtp_despatch xtp_despatches[] = {
5723 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5724 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5725 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5726 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5727 { XT_XTP_INVALID, NULL, NULL }
5731 * is the url xtp protocol? (xxxt://)
5732 * if so, parse and despatch correct bahvior
5735 parse_xtp_url(struct tab *t, const char *url)
5737 char *dup = NULL, *p, *last;
5738 uint8_t n_tokens = 0;
5739 char *tokens[4] = {NULL, NULL, NULL, ""};
5740 struct xtp_despatch *dsp, *dsp_match = NULL;
5741 uint8_t req_class;
5742 int ret = FALSE;
5745 * tokens array meaning:
5746 * tokens[0] = class
5747 * tokens[1] = session key
5748 * tokens[2] = action
5749 * tokens[3] = optional argument
5752 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5754 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5755 goto clean;
5757 dup = g_strdup(url + strlen(XT_XTP_STR));
5759 /* split out the url */
5760 for ((p = strtok_r(dup, "/", &last)); p;
5761 (p = strtok_r(NULL, "/", &last))) {
5762 if (n_tokens < 4)
5763 tokens[n_tokens++] = p;
5766 /* should be atleast three fields 'class/seskey/command/arg' */
5767 if (n_tokens < 3)
5768 goto clean;
5770 dsp = xtp_despatches;
5771 req_class = atoi(tokens[0]);
5772 while (dsp->xtp_class) {
5773 if (dsp->xtp_class == req_class) {
5774 dsp_match = dsp;
5775 break;
5777 dsp++;
5780 /* did we find one atall? */
5781 if (dsp_match == NULL) {
5782 show_oops(t, "%s: no matching xtp despatch found", __func__);
5783 goto clean;
5786 /* check session key and call despatch function */
5787 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5788 ret = TRUE; /* all is well, this was a valid xtp request */
5789 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5792 clean:
5793 if (dup)
5794 g_free(dup);
5796 return (ret);
5801 void
5802 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5804 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5806 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5808 if (t == NULL) {
5809 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5810 return;
5813 if (uri == NULL) {
5814 show_oops(t, "activate_uri_entry_cb no uri");
5815 return;
5818 uri += strspn(uri, "\t ");
5820 /* if xxxt:// treat specially */
5821 if (parse_xtp_url(t, uri))
5822 return;
5824 /* otherwise continue to load page normally */
5825 load_uri(t, (gchar *)uri);
5826 focus_webview(t);
5829 void
5830 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5832 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5833 char *newuri = NULL;
5834 gchar *enc_search;
5836 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5838 if (t == NULL) {
5839 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5840 return;
5843 if (search_string == NULL) {
5844 show_oops(t, "no search_string");
5845 return;
5848 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5850 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5851 newuri = g_strdup_printf(search_string, enc_search);
5852 g_free(enc_search);
5854 marks_clear(t);
5855 webkit_web_view_load_uri(t->wv, newuri);
5856 focus_webview(t);
5858 if (newuri)
5859 g_free(newuri);
5862 void
5863 check_and_set_js(const gchar *uri, struct tab *t)
5865 struct domain *d = NULL;
5866 int es = 0;
5868 if (uri == NULL || t == NULL)
5869 return;
5871 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5872 es = 0;
5873 else
5874 es = 1;
5876 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5877 es ? "enable" : "disable", uri);
5879 g_object_set(G_OBJECT(t->settings),
5880 "enable-scripts", es, (char *)NULL);
5881 g_object_set(G_OBJECT(t->settings),
5882 "javascript-can-open-windows-automatically", es, (char *)NULL);
5883 webkit_web_view_set_settings(t->wv, t->settings);
5885 button_set_stockid(t->js_toggle,
5886 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5889 gboolean
5890 color_address_bar(gpointer p)
5892 GdkColor color;
5893 struct tab *tt, *t = p;
5894 gchar *col_str = XT_COLOR_YELLOW;
5896 DNPRINTF(XT_D_URL, "%s:\n", __func__);
5898 /* make sure t still exists */
5899 if (t == NULL)
5900 goto done;
5901 TAILQ_FOREACH(tt, &tabs, entry)
5902 if (t == tt)
5903 break;
5904 if (t != tt)
5905 goto done;
5907 switch (load_compare_cert(t, NULL)) {
5908 case CERT_LOCAL:
5909 col_str = XT_COLOR_BLUE;
5910 break;
5911 case CERT_TRUSTED:
5912 col_str = XT_COLOR_GREEN;
5913 break;
5914 case CERT_UNTRUSTED:
5915 col_str = XT_COLOR_YELLOW;
5916 break;
5917 case CERT_BAD:
5918 col_str = XT_COLOR_RED;
5919 break;
5922 gdk_color_parse(col_str, &color);
5923 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5925 if (!strcmp(col_str, XT_COLOR_WHITE))
5926 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5927 else
5928 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5930 col_str = NULL;
5931 done:
5932 return (FALSE /* kill thread */);
5935 void
5936 show_ca_status(struct tab *t, const char *uri)
5938 GdkColor color;
5939 gchar *col_str = XT_COLOR_WHITE;
5941 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5942 ssl_strict_certs, ssl_ca_file, uri);
5944 if (t == NULL)
5945 return;
5947 if (uri == NULL)
5948 goto done;
5949 if (ssl_ca_file == NULL) {
5950 if (g_str_has_prefix(uri, "http://"))
5951 goto done;
5952 if (g_str_has_prefix(uri, "https://")) {
5953 col_str = XT_COLOR_RED;
5954 goto done;
5956 return;
5958 if (g_str_has_prefix(uri, "http://") ||
5959 !g_str_has_prefix(uri, "https://"))
5960 goto done;
5962 /* thread the coloring of the address bar */
5963 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
5964 color_address_bar, t, NULL);
5965 return;
5967 done:
5968 if (col_str) {
5969 gdk_color_parse(col_str, &color);
5970 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5972 if (!strcmp(col_str, XT_COLOR_WHITE))
5973 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5974 else
5975 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5979 void
5980 free_favicon(struct tab *t)
5982 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5983 __func__, t->icon_download, t->icon_request);
5985 if (t->icon_request)
5986 g_object_unref(t->icon_request);
5987 if (t->icon_dest_uri)
5988 g_free(t->icon_dest_uri);
5990 t->icon_request = NULL;
5991 t->icon_dest_uri = NULL;
5994 void
5995 xt_icon_from_name(struct tab *t, gchar *name)
5997 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5998 GTK_ENTRY_ICON_PRIMARY, "text-html");
5999 if (show_url == 0)
6000 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6001 GTK_ENTRY_ICON_PRIMARY, "text-html");
6002 else
6003 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6004 GTK_ENTRY_ICON_PRIMARY, NULL);
6007 void
6008 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6010 GdkPixbuf *pb_scaled;
6012 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6013 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6014 GDK_INTERP_BILINEAR);
6015 else
6016 pb_scaled = pb;
6018 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6019 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6020 if (show_url == 0)
6021 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6022 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6023 else
6024 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6025 GTK_ENTRY_ICON_PRIMARY, NULL);
6027 if (pb_scaled != pb)
6028 g_object_unref(pb_scaled);
6031 void
6032 xt_icon_from_file(struct tab *t, char *file)
6034 GdkPixbuf *pb;
6036 if (g_str_has_prefix(file, "file://"))
6037 file += strlen("file://");
6039 pb = gdk_pixbuf_new_from_file(file, NULL);
6040 if (pb) {
6041 xt_icon_from_pixbuf(t, pb);
6042 g_object_unref(pb);
6043 } else
6044 xt_icon_from_name(t, "text-html");
6047 gboolean
6048 is_valid_icon(char *file)
6050 gboolean valid = 0;
6051 const char *mime_type;
6052 GFileInfo *fi;
6053 GFile *gf;
6055 gf = g_file_new_for_path(file);
6056 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6057 NULL, NULL);
6058 mime_type = g_file_info_get_content_type(fi);
6059 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6060 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6061 g_strcmp0(mime_type, "image/png") == 0 ||
6062 g_strcmp0(mime_type, "image/gif") == 0 ||
6063 g_strcmp0(mime_type, "application/octet-stream") == 0;
6064 g_object_unref(fi);
6065 g_object_unref(gf);
6067 return (valid);
6070 void
6071 set_favicon_from_file(struct tab *t, char *file)
6073 struct stat sb;
6075 if (t == NULL || file == NULL)
6076 return;
6078 if (g_str_has_prefix(file, "file://"))
6079 file += strlen("file://");
6080 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6082 if (!stat(file, &sb)) {
6083 if (sb.st_size == 0 || !is_valid_icon(file)) {
6084 /* corrupt icon so trash it */
6085 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6086 __func__, file);
6087 unlink(file);
6088 /* no need to set icon to default here */
6089 return;
6092 xt_icon_from_file(t, file);
6095 void
6096 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6097 WebKitWebView *wv)
6099 WebKitDownloadStatus status = webkit_download_get_status(download);
6100 struct tab *tt = NULL, *t = NULL;
6103 * find the webview instead of passing in the tab as it could have been
6104 * deleted from underneath us.
6106 TAILQ_FOREACH(tt, &tabs, entry) {
6107 if (tt->wv == wv) {
6108 t = tt;
6109 break;
6112 if (t == NULL)
6113 return;
6115 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6116 __func__, t->tab_id, status);
6118 switch (status) {
6119 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6120 /* -1 */
6121 t->icon_download = NULL;
6122 free_favicon(t);
6123 break;
6124 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6125 /* 0 */
6126 break;
6127 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6128 /* 1 */
6129 break;
6130 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6131 /* 2 */
6132 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6133 __func__, t->tab_id);
6134 t->icon_download = NULL;
6135 free_favicon(t);
6136 break;
6137 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6138 /* 3 */
6140 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6141 __func__, t->icon_dest_uri);
6142 set_favicon_from_file(t, t->icon_dest_uri);
6143 /* these will be freed post callback */
6144 t->icon_request = NULL;
6145 t->icon_download = NULL;
6146 break;
6147 default:
6148 break;
6152 void
6153 abort_favicon_download(struct tab *t)
6155 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6157 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6158 if (t->icon_download) {
6159 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6160 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6161 webkit_download_cancel(t->icon_download);
6162 t->icon_download = NULL;
6163 } else
6164 free_favicon(t);
6165 #endif
6167 xt_icon_from_name(t, "text-html");
6170 void
6171 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6173 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6175 if (uri == NULL || t == NULL)
6176 return;
6178 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6179 /* take icon from WebKitIconDatabase */
6180 GdkPixbuf *pb;
6182 pb = webkit_web_view_get_icon_pixbuf(wv);
6183 if (pb) {
6184 xt_icon_from_pixbuf(t, pb);
6185 g_object_unref(pb);
6186 } else
6187 xt_icon_from_name(t, "text-html");
6188 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6189 /* download icon to cache dir */
6190 gchar *name_hash, file[PATH_MAX];
6191 struct stat sb;
6193 if (t->icon_request) {
6194 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6195 return;
6198 /* check to see if we got the icon in cache */
6199 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6200 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6201 g_free(name_hash);
6203 if (!stat(file, &sb)) {
6204 if (sb.st_size > 0) {
6205 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6206 __func__, file);
6207 set_favicon_from_file(t, file);
6208 return;
6211 /* corrupt icon so trash it */
6212 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6213 __func__, file);
6214 unlink(file);
6217 /* create download for icon */
6218 t->icon_request = webkit_network_request_new(uri);
6219 if (t->icon_request == NULL) {
6220 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6221 __func__, uri);
6222 return;
6225 t->icon_download = webkit_download_new(t->icon_request);
6226 if (t->icon_download == NULL)
6227 return;
6229 /* we have to free icon_dest_uri later */
6230 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6231 webkit_download_set_destination_uri(t->icon_download,
6232 t->icon_dest_uri);
6234 if (webkit_download_get_status(t->icon_download) ==
6235 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6236 g_object_unref(t->icon_request);
6237 g_free(t->icon_dest_uri);
6238 t->icon_request = NULL;
6239 t->icon_dest_uri = NULL;
6240 return;
6243 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6244 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6246 webkit_download_start(t->icon_download);
6247 #endif
6250 void
6251 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6253 const gchar *uri = NULL, *title = NULL;
6254 struct history *h, find;
6255 struct karg a;
6256 GdkColor color;
6258 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6259 webkit_web_view_get_load_status(wview),
6260 get_uri(t) ? get_uri(t) : "NOTHING");
6262 if (t == NULL) {
6263 show_oops(NULL, "notify_load_status_cb invalid parameters");
6264 return;
6267 switch (webkit_web_view_get_load_status(wview)) {
6268 case WEBKIT_LOAD_PROVISIONAL:
6269 /* 0 */
6270 abort_favicon_download(t);
6271 #if GTK_CHECK_VERSION(2, 20, 0)
6272 gtk_widget_show(t->spinner);
6273 gtk_spinner_start(GTK_SPINNER(t->spinner));
6274 #endif
6275 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6277 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6279 /* assume we are a new address */
6280 gdk_color_parse("white", &color);
6281 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6282 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6284 /* take focus if we are visible */
6285 focus_webview(t);
6286 t->focus_wv = 1;
6288 break;
6290 case WEBKIT_LOAD_COMMITTED:
6291 /* 1 */
6292 uri = get_uri(t);
6293 if (uri == NULL)
6294 return;
6295 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6297 if (t->status) {
6298 g_free(t->status);
6299 t->status = NULL;
6301 set_status(t, (char *)uri, XT_STATUS_LOADING);
6303 /* check if js white listing is enabled */
6304 if (enable_js_whitelist) {
6305 check_and_set_js(uri, t);
6308 if (t->styled)
6309 apply_style(t);
6312 /* we know enough to autosave the session */
6313 if (session_autosave) {
6314 a.s = NULL;
6315 save_tabs(t, &a);
6318 show_ca_status(t, uri);
6319 break;
6321 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6322 /* 3 */
6323 break;
6325 case WEBKIT_LOAD_FINISHED:
6326 /* 2 */
6327 uri = get_uri(t);
6328 if (uri == NULL)
6329 return;
6331 if (!strncmp(uri, "http://", strlen("http://")) ||
6332 !strncmp(uri, "https://", strlen("https://")) ||
6333 !strncmp(uri, "file://", strlen("file://"))) {
6334 find.uri = uri;
6335 h = RB_FIND(history_list, &hl, &find);
6336 if (!h) {
6337 title = get_title(t, FALSE);
6338 h = g_malloc(sizeof *h);
6339 h->uri = g_strdup(uri);
6340 h->title = g_strdup(title);
6341 RB_INSERT(history_list, &hl, h);
6342 completion_add_uri(h->uri);
6343 update_history_tabs(NULL);
6347 set_status(t, (char *)uri, XT_STATUS_URI);
6348 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6349 case WEBKIT_LOAD_FAILED:
6350 /* 4 */
6351 #endif
6352 #if GTK_CHECK_VERSION(2, 20, 0)
6353 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6354 gtk_widget_hide(t->spinner);
6355 #endif
6356 default:
6357 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6358 break;
6361 if (t->item)
6362 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6363 else
6364 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6365 webkit_web_view_can_go_back(wview));
6367 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6368 webkit_web_view_can_go_forward(wview));
6371 #if 0
6372 gboolean
6373 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6374 gchar *uri, gpointer web_error,struct tab *t)
6377 * XXX this function is wrong
6378 * it overwrites perfectly good urls with garbage on load errors
6379 * those happen often when popups fail to resolve dns
6381 if (t->tmp_uri)
6382 g_free(t->tmp_uri);
6383 t->tmp_uri = g_strdup(uri);
6384 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6385 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6386 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6387 set_status(t, uri, XT_STATUS_NOTHING);
6389 return (FALSE);
6391 #endif
6393 void
6394 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6396 const gchar *title = NULL, *win_title = NULL;
6398 title = get_title(t, FALSE);
6399 win_title = get_title(t, TRUE);
6400 gtk_label_set_text(GTK_LABEL(t->label), title);
6401 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6402 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6403 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6406 void
6407 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6409 run_script(t, JS_HINTING);
6412 void
6413 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6415 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6416 progress == 100 ? 0 : (double)progress / 100);
6417 if (show_url == 0) {
6418 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6419 progress == 100 ? 0 : (double)progress / 100);
6422 update_statusbar_position(NULL, NULL);
6426 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6427 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6428 WebKitWebPolicyDecision *pd, struct tab *t)
6430 char *uri;
6431 WebKitWebNavigationReason reason;
6432 struct domain *d = NULL;
6434 if (t == NULL) {
6435 show_oops(NULL, "webview_npd_cb invalid parameters");
6436 return (FALSE);
6439 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6440 t->ctrl_click,
6441 webkit_network_request_get_uri(request));
6443 uri = (char *)webkit_network_request_get_uri(request);
6445 /* if this is an xtp url, we don't load anything else */
6446 if (parse_xtp_url(t, uri))
6447 return (TRUE);
6449 if (t->ctrl_click) {
6450 t->ctrl_click = 0;
6451 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6452 webkit_web_policy_decision_ignore(pd);
6453 return (TRUE); /* we made the decission */
6457 * This is a little hairy but it comes down to this:
6458 * when we run in whitelist mode we have to assist the browser in
6459 * opening the URL that it would have opened in a new tab.
6461 reason = webkit_web_navigation_action_get_reason(na);
6462 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6463 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6464 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6465 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6466 load_uri(t, uri);
6467 webkit_web_policy_decision_use(pd);
6468 return (TRUE); /* we made the decision */
6471 return (FALSE);
6474 WebKitWebView *
6475 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6477 struct tab *tt;
6478 struct domain *d = NULL;
6479 const gchar *uri;
6480 WebKitWebView *webview = NULL;
6482 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6483 webkit_web_view_get_uri(wv));
6485 if (tabless) {
6486 /* open in current tab */
6487 webview = t->wv;
6488 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6489 uri = webkit_web_view_get_uri(wv);
6490 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6491 return (NULL);
6493 tt = create_new_tab(NULL, NULL, 1, -1);
6494 webview = tt->wv;
6495 } else if (enable_scripts == 1) {
6496 tt = create_new_tab(NULL, NULL, 1, -1);
6497 webview = tt->wv;
6500 return (webview);
6503 gboolean
6504 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6506 const gchar *uri;
6507 struct domain *d = NULL;
6509 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6511 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6512 uri = webkit_web_view_get_uri(wv);
6513 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6514 return (FALSE);
6516 delete_tab(t);
6517 } else if (enable_scripts == 1)
6518 delete_tab(t);
6520 return (TRUE);
6524 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6526 /* we can not eat the event without throwing gtk off so defer it */
6528 /* catch middle click */
6529 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6530 t->ctrl_click = 1;
6531 goto done;
6534 /* catch ctrl click */
6535 if (e->type == GDK_BUTTON_RELEASE &&
6536 CLEAN(e->state) == GDK_CONTROL_MASK)
6537 t->ctrl_click = 1;
6538 else
6539 t->ctrl_click = 0;
6540 done:
6541 return (XT_CB_PASSTHROUGH);
6545 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6547 struct mime_type *m;
6549 m = find_mime_type(mime_type);
6550 if (m == NULL)
6551 return (1);
6552 if (m->mt_download)
6553 return (1);
6555 switch (fork()) {
6556 case -1:
6557 show_oops(t, "can't fork mime handler");
6558 return (1);
6559 /* NOTREACHED */
6560 case 0:
6561 break;
6562 default:
6563 return (0);
6566 /* child */
6567 execlp(m->mt_action, m->mt_action,
6568 webkit_network_request_get_uri(request), (void *)NULL);
6570 _exit(0);
6572 /* NOTREACHED */
6573 return (0);
6576 const gchar *
6577 get_mime_type(char *file)
6579 const char *mime_type;
6580 GFileInfo *fi;
6581 GFile *gf;
6583 if (g_str_has_prefix(file, "file://"))
6584 file += strlen("file://");
6586 gf = g_file_new_for_path(file);
6587 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6588 NULL, NULL);
6589 mime_type = g_file_info_get_content_type(fi);
6590 g_object_unref(fi);
6591 g_object_unref(gf);
6593 return (mime_type);
6597 run_download_mimehandler(char *mime_type, char *file)
6599 struct mime_type *m;
6601 m = find_mime_type(mime_type);
6602 if (m == NULL)
6603 return (1);
6605 switch (fork()) {
6606 case -1:
6607 show_oops(NULL, "can't fork download mime handler");
6608 return (1);
6609 /* NOTREACHED */
6610 case 0:
6611 break;
6612 default:
6613 return (0);
6616 /* child */
6617 if (g_str_has_prefix(file, "file://"))
6618 file += strlen("file://");
6619 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6621 _exit(0);
6623 /* NOTREACHED */
6624 return (0);
6627 void
6628 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6629 WebKitWebView *wv)
6631 WebKitDownloadStatus status;
6632 const gchar *file = NULL, *mime = NULL;
6634 if (download == NULL)
6635 return;
6636 status = webkit_download_get_status(download);
6637 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6638 return;
6640 file = webkit_download_get_destination_uri(download);
6641 if (file == NULL)
6642 return;
6643 mime = get_mime_type((char *)file);
6644 if (mime == NULL)
6645 return;
6647 run_download_mimehandler((char *)mime, (char *)file);
6651 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6652 WebKitNetworkRequest *request, char *mime_type,
6653 WebKitWebPolicyDecision *decision, struct tab *t)
6655 if (t == NULL) {
6656 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6657 return (FALSE);
6660 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6661 t->tab_id, mime_type);
6663 if (run_mimehandler(t, mime_type, request) == 0) {
6664 webkit_web_policy_decision_ignore(decision);
6665 focus_webview(t);
6666 return (TRUE);
6669 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6670 webkit_web_policy_decision_download(decision);
6671 return (TRUE);
6674 return (FALSE);
6678 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6679 struct tab *t)
6681 struct stat sb;
6682 const gchar *suggested_name;
6683 gchar *filename = NULL;
6684 char *uri = NULL;
6685 struct download *download_entry;
6686 int i, ret = TRUE;
6688 if (wk_download == NULL || t == NULL) {
6689 show_oops(NULL, "%s invalid parameters", __func__);
6690 return (FALSE);
6693 suggested_name = webkit_download_get_suggested_filename(wk_download);
6694 if (suggested_name == NULL)
6695 return (FALSE); /* abort download */
6697 i = 0;
6698 do {
6699 if (filename) {
6700 g_free(filename);
6701 filename = NULL;
6703 if (i) {
6704 g_free(uri);
6705 uri = NULL;
6706 filename = g_strdup_printf("%d%s", i, suggested_name);
6708 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6709 filename : suggested_name);
6710 i++;
6711 } while (!stat(uri + strlen("file://"), &sb));
6713 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6714 "local %s\n", __func__, t->tab_id, filename, uri);
6716 webkit_download_set_destination_uri(wk_download, uri);
6718 if (webkit_download_get_status(wk_download) ==
6719 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6720 show_oops(t, "%s: download failed to start", __func__);
6721 ret = FALSE;
6722 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6723 } else {
6724 /* connect "download first" mime handler */
6725 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6726 G_CALLBACK(download_status_changed_cb), NULL);
6728 download_entry = g_malloc(sizeof(struct download));
6729 download_entry->download = wk_download;
6730 download_entry->tab = t;
6731 download_entry->id = next_download_id++;
6732 RB_INSERT(download_list, &downloads, download_entry);
6733 /* get from history */
6734 g_object_ref(wk_download);
6735 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6736 show_oops(t, "Download of '%s' started...",
6737 basename((char *)webkit_download_get_destination_uri(wk_download)));
6740 if (uri)
6741 g_free(uri);
6743 if (filename)
6744 g_free(filename);
6746 /* sync other download manager tabs */
6747 update_download_tabs(NULL);
6750 * NOTE: never redirect/render the current tab before this
6751 * function returns. This will cause the download to never start.
6753 return (ret); /* start download */
6756 void
6757 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6759 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6761 if (t == NULL) {
6762 show_oops(NULL, "webview_hover_cb");
6763 return;
6766 if (uri)
6767 set_status(t, uri, XT_STATUS_LINK);
6768 else {
6769 if (t->status)
6770 set_status(t, t->status, XT_STATUS_NOTHING);
6775 mark(struct tab *t, struct karg *arg)
6777 char mark;
6778 int index;
6780 mark = arg->s[1];
6781 if ((index = marktoindex(mark)) == -1)
6782 return -1;
6784 if (arg->i == XT_MARK_SET)
6785 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6786 else if (arg->i == XT_MARK_GOTO) {
6787 if (t->mark[index] == XT_INVALID_MARK) {
6788 show_oops(t, "mark '%c' does not exist", mark);
6789 return -1;
6791 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6792 something changes the document size */
6793 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6796 return 0;
6799 void
6800 marks_clear(struct tab *t)
6802 int i;
6804 for (i = 0; i < LENGTH(t->mark); i++)
6805 t->mark[i] = XT_INVALID_MARK;
6809 qmarks_load(void)
6811 char file[PATH_MAX];
6812 char *line = NULL, *p, mark;
6813 int index, i;
6814 FILE *f;
6815 size_t linelen;
6817 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6818 if ((f = fopen(file, "r+")) == NULL) {
6819 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6820 return (1);
6823 for (i = 1; ; i++) {
6824 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
6825 if (feof(f) || ferror(f))
6826 break;
6827 if (strlen(line) == 0 || line[0] == '#') {
6828 free(line);
6829 line = NULL;
6830 continue;
6833 p = strtok(line, " \t");
6835 if (p == NULL || strlen(p) != 1 ||
6836 (index = marktoindex(*p)) == -1) {
6837 warnx("corrupt quickmarks file, line %d", i);
6838 break;
6841 mark = *p;
6842 p = strtok(NULL, " \t");
6843 if (qmarks[index] != NULL)
6844 g_free(qmarks[index]);
6845 qmarks[index] = g_strdup(p);
6848 fclose(f);
6850 return (0);
6854 qmarks_save(void)
6856 char file[PATH_MAX];
6857 int i;
6858 FILE *f;
6860 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6861 if ((f = fopen(file, "r+")) == NULL) {
6862 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6863 return (1);
6866 for (i = 0; i < XT_NOMARKS; i++)
6867 if (qmarks[i] != NULL)
6868 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
6870 fclose(f);
6872 return (0);
6876 qmark(struct tab *t, struct karg *arg)
6878 char mark;
6879 int index;
6881 mark = arg->s[strlen(arg->s)-1];
6882 index = marktoindex(mark);
6883 if (index == -1)
6884 return (-1);
6886 switch (arg->i) {
6887 case XT_QMARK_SET:
6888 if (qmarks[index] != NULL)
6889 g_free(qmarks[index]);
6891 qmarks_load(); /* sync if multiple instances */
6892 qmarks[index] = g_strdup(get_uri(t));
6893 qmarks_save();
6894 break;
6895 case XT_QMARK_OPEN:
6896 if (qmarks[index] != NULL)
6897 load_uri(t, qmarks[index]);
6898 else {
6899 show_oops(t, "quickmark \"%c\" does not exist",
6900 mark);
6901 return (-1);
6903 break;
6904 case XT_QMARK_TAB:
6905 if (qmarks[index] != NULL)
6906 create_new_tab(qmarks[index], NULL, 1, -1);
6907 else {
6908 show_oops(t, "quickmark \"%c\" does not exist",
6909 mark);
6910 return (-1);
6912 break;
6915 return (0);
6919 go_up(struct tab *t, struct karg *args)
6921 int levels;
6922 char *uri;
6923 char *tmp;
6925 levels = atoi(args->s);
6926 if (levels == 0)
6927 levels = 1;
6929 uri = g_strdup(webkit_web_view_get_uri(t->wv));
6930 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
6931 return 1;
6932 tmp += strlen(XT_PROTO_DELIM);
6934 /* if an uri starts with a slash, leave it alone (for file:///) */
6935 if (tmp[0] == '/')
6936 tmp++;
6938 while (levels--) {
6939 char *p;
6941 p = strrchr(tmp, '/');
6942 if (p != NULL)
6943 *p = '\0';
6944 else
6945 break;
6948 load_uri(t, uri);
6949 g_free(uri);
6951 return (0);
6955 gototab(struct tab *t, struct karg *args)
6957 int tab;
6958 struct karg arg = {0, NULL, -1};
6960 tab = atoi(args->s);
6962 arg.i = XT_TAB_NEXT;
6963 arg.precount = tab;
6965 movetab(t, &arg);
6967 return (0);
6971 zoom_amount(struct tab *t, struct karg *arg)
6973 struct karg narg = {0, NULL, -1};
6975 narg.i = atoi(arg->s);
6976 resizetab(t, &narg);
6978 return 0;
6982 flip_colon(struct tab *t, struct karg *arg)
6984 struct karg narg = {0, NULL, -1};
6985 char *p;
6987 if (t == NULL || arg == NULL)
6988 return (1);
6990 p = strstr(arg->s, ":");
6991 if (p == NULL)
6992 return (1);
6993 *p = '\0';
6995 narg.i = ':';
6996 narg.s = arg->s;
6997 command(t, &narg);
6999 return (0);
7002 /* buffer commands receive the regex that triggered them in arg.s */
7003 char bcmd[XT_BUFCMD_SZ];
7004 struct buffercmd {
7005 char *regex;
7006 int precount;
7007 #define XT_PRE_NO (0)
7008 #define XT_PRE_YES (1)
7009 #define XT_PRE_MAYBE (2)
7010 char *cmd;
7011 int (*func)(struct tab *, struct karg *);
7012 int arg;
7013 regex_t cregex;
7014 } buffercmds[] = {
7015 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7016 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7017 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7018 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7019 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7020 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7021 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7022 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7023 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7024 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7025 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7026 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7027 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7028 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7029 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7030 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7031 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7032 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7035 void
7036 buffercmd_init(void)
7038 int i;
7040 for (i = 0; i < LENGTH(buffercmds); i++)
7041 regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7042 REG_EXTENDED);
7045 void
7046 buffercmd_abort(struct tab *t)
7048 int i;
7050 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7051 for (i = 0; i < LENGTH(bcmd); i++)
7052 bcmd[i] = '\0';
7054 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7055 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7058 void
7059 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7061 struct karg arg = {0, NULL, -1};
7063 arg.i = cmd->arg;
7064 arg.s = g_strdup(bcmd);
7066 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7067 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7068 cmd->func(t, &arg);
7070 if (arg.s)
7071 g_free(arg.s);
7073 buffercmd_abort(t);
7076 gboolean
7077 buffercmd_addkey(struct tab *t, guint keyval)
7079 int i, c, match ;
7080 char s[XT_BUFCMD_SZ];
7082 if (keyval == GDK_Escape) {
7083 buffercmd_abort(t);
7084 return (XT_CB_HANDLED);
7087 /* key with modifier or non-ascii character */
7088 if (!isascii(keyval))
7089 return (XT_CB_PASSTHROUGH);
7091 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7092 "to buffer \"%s\"\n", keyval, bcmd);
7094 for (i = 0; i < LENGTH(bcmd); i++)
7095 if (bcmd[i] == '\0') {
7096 bcmd[i] = keyval;
7097 break;
7100 /* buffer full, ignore input */
7101 if (i >= LENGTH(bcmd) -1) {
7102 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7103 buffercmd_abort(t);
7104 return (XT_CB_HANDLED);
7107 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7109 /* find exact match */
7110 for (i = 0; i < LENGTH(buffercmds); i++)
7111 if (regexec(&buffercmds[i].cregex, bcmd,
7112 (size_t) 0, NULL, 0) == 0) {
7113 buffercmd_execute(t, &buffercmds[i]);
7114 goto done;
7117 /* find non exact matches to see if we need to abort ot not */
7118 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7119 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7120 c = -1;
7121 s[0] = '\0';
7122 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7123 if (isdigit(bcmd[0])) {
7124 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7125 continue;
7126 } else {
7127 c = 0;
7128 if (sscanf(bcmd, "%s", s) == 0)
7129 continue;
7131 } else if (buffercmds[i].precount == XT_PRE_YES) {
7132 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7133 continue;
7134 } else {
7135 if (sscanf(bcmd, "%s", s) == 0)
7136 continue;
7138 if (c == -1 && buffercmds[i].precount)
7139 continue;
7140 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7141 match++;
7143 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7144 i, match, buffercmds[i].cmd, c, s);
7146 if (match == 0) {
7147 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7148 buffercmd_abort(t);
7151 done:
7152 return (XT_CB_HANDLED);
7155 gboolean
7156 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7158 struct key_binding *k;
7160 /* handle keybindings if buffercmd is empty.
7161 if not empty, allow commands like C-n */
7162 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7163 TAILQ_FOREACH(k, &kbl, entry)
7164 if (e->keyval == k->key
7165 && (entry ? k->use_in_entry : 1)) {
7166 if (k->mask == 0) {
7167 if ((e->state & (CTRL | MOD1)) == 0)
7168 return (cmd_execute(t, k->cmd));
7169 } else if ((e->state & k->mask) == k->mask) {
7170 return (cmd_execute(t, k->cmd));
7174 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7175 return buffercmd_addkey(t, e->keyval);
7177 return (XT_CB_PASSTHROUGH);
7181 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7183 char s[2], buf[128];
7184 const char *errstr = NULL;
7186 /* don't use w directly; use t->whatever instead */
7188 if (t == NULL) {
7189 show_oops(NULL, "wv_keypress_after_cb");
7190 return (XT_CB_PASSTHROUGH);
7193 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7194 e->keyval, e->state, t);
7196 if (t->hints_on) {
7197 /* ESC */
7198 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7199 disable_hints(t);
7200 return (XT_CB_HANDLED);
7203 /* RETURN */
7204 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7205 if (errstr) {
7206 /* we have a string */
7207 } else {
7208 /* we have a number */
7209 snprintf(buf, sizeof buf,
7210 "vimprobable_fire(%s)", t->hint_num);
7211 run_script(t, buf);
7213 disable_hints(t);
7216 /* BACKSPACE */
7217 /* XXX unfuck this */
7218 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7219 if (t->hint_mode == XT_HINT_NUMERICAL) {
7220 /* last input was numerical */
7221 int l;
7222 l = strlen(t->hint_num);
7223 if (l > 0) {
7224 l--;
7225 if (l == 0) {
7226 disable_hints(t);
7227 enable_hints(t);
7228 } else {
7229 t->hint_num[l] = '\0';
7230 goto num;
7233 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7234 /* last input was alphanumerical */
7235 int l;
7236 l = strlen(t->hint_buf);
7237 if (l > 0) {
7238 l--;
7239 if (l == 0) {
7240 disable_hints(t);
7241 enable_hints(t);
7242 } else {
7243 t->hint_buf[l] = '\0';
7244 goto anum;
7247 } else {
7248 /* bogus */
7249 disable_hints(t);
7253 /* numerical input */
7254 if (CLEAN(e->state) == 0 &&
7255 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7256 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7257 snprintf(s, sizeof s, "%c", e->keyval);
7258 strlcat(t->hint_num, s, sizeof t->hint_num);
7259 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7260 t->hint_num);
7261 num:
7262 if (errstr) {
7263 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7264 "invalid link number\n");
7265 disable_hints(t);
7266 } else {
7267 snprintf(buf, sizeof buf,
7268 "vimprobable_update_hints(%s)",
7269 t->hint_num);
7270 t->hint_mode = XT_HINT_NUMERICAL;
7271 run_script(t, buf);
7274 /* empty the counter buffer */
7275 bzero(t->hint_buf, sizeof t->hint_buf);
7276 return (XT_CB_HANDLED);
7279 /* alphanumerical input */
7280 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7281 e->keyval <= GDK_z) ||
7282 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7283 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7284 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7285 e->keyval <= GDK_9) ||
7286 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7287 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7288 snprintf(s, sizeof s, "%c", e->keyval);
7289 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7290 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7291 " %s\n", t->hint_buf);
7292 anum:
7293 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7294 run_script(t, buf);
7296 snprintf(buf, sizeof buf,
7297 "vimprobable_show_hints('%s')", t->hint_buf);
7298 t->hint_mode = XT_HINT_ALPHANUM;
7299 run_script(t, buf);
7301 /* empty the counter buffer */
7302 bzero(t->hint_num, sizeof t->hint_num);
7303 return (XT_CB_HANDLED);
7306 return (XT_CB_HANDLED);
7307 } else {
7308 /* prefix input*/
7309 snprintf(s, sizeof s, "%c", e->keyval);
7310 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7311 cmd_prefix = 10 * cmd_prefix + atoi(s);
7314 return (handle_keypress(t, e, 0));
7318 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7320 hide_oops(t);
7322 /* Hide buffers, if they are visible, with escape. */
7323 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7324 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7325 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7326 hide_buffers(t);
7327 return (XT_CB_HANDLED);
7330 return (XT_CB_PASSTHROUGH);
7334 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7336 const gchar *c = gtk_entry_get_text(w);
7337 GdkColor color;
7338 int forward = TRUE;
7340 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7341 e->keyval, e->state, t);
7343 if (t == NULL) {
7344 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7345 return (XT_CB_PASSTHROUGH);
7348 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7349 e->keyval, e->state, t);
7351 if (c[0] == ':')
7352 goto done;
7353 if (strlen(c) == 1) {
7354 webkit_web_view_unmark_text_matches(t->wv);
7355 goto done;
7358 if (c[0] == '/')
7359 forward = TRUE;
7360 else if (c[0] == '?')
7361 forward = FALSE;
7362 else
7363 goto done;
7365 /* search */
7366 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
7367 FALSE) {
7368 /* not found, mark red */
7369 gdk_color_parse(XT_COLOR_RED, &color);
7370 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7371 /* unmark and remove selection */
7372 webkit_web_view_unmark_text_matches(t->wv);
7373 /* my kingdom for a way to unselect text in webview */
7374 } else {
7375 /* found, highlight all */
7376 webkit_web_view_unmark_text_matches(t->wv);
7377 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7378 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7379 gdk_color_parse(XT_COLOR_WHITE, &color);
7380 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7382 done:
7383 return (XT_CB_PASSTHROUGH);
7386 gboolean
7387 match_uri(const gchar *uri, const gchar *key) {
7388 gchar *voffset;
7389 size_t len;
7390 gboolean match = FALSE;
7392 len = strlen(key);
7394 if (!strncmp(key, uri, len))
7395 match = TRUE;
7396 else {
7397 voffset = strstr(uri, "/") + 2;
7398 if (!strncmp(key, voffset, len))
7399 match = TRUE;
7400 else if (g_str_has_prefix(voffset, "www.")) {
7401 voffset = voffset + strlen("www.");
7402 if (!strncmp(key, voffset, len))
7403 match = TRUE;
7407 return (match);
7410 void
7411 cmd_getlist(int id, char *key)
7413 int i, dep, c = 0;
7414 struct history *h;
7416 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7417 RB_FOREACH_REVERSE(h, history_list, &hl)
7418 if (match_uri(h->uri, key)) {
7419 cmd_status.list[c] = (char *)h->uri;
7420 if (++c > 255)
7421 break;
7424 cmd_status.len = c;
7425 return;
7428 dep = (id == -1) ? 0 : cmds[id].level + 1;
7430 for (i = id + 1; i < LENGTH(cmds); i++) {
7431 if (cmds[i].level < dep)
7432 break;
7433 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7434 strlen(key)))
7435 cmd_status.list[c++] = cmds[i].cmd;
7439 cmd_status.len = c;
7442 char *
7443 cmd_getnext(int dir)
7445 cmd_status.index += dir;
7447 if (cmd_status.index < 0)
7448 cmd_status.index = cmd_status.len - 1;
7449 else if (cmd_status.index >= cmd_status.len)
7450 cmd_status.index = 0;
7452 return cmd_status.list[cmd_status.index];
7456 cmd_tokenize(char *s, char *tokens[])
7458 int i = 0;
7459 char *tok, *last;
7460 size_t len = strlen(s);
7461 bool blank;
7463 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7464 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7465 tok = strtok_r(NULL, " ", &last), i++)
7466 tokens[i] = tok;
7468 if (blank && i < 3)
7469 tokens[i++] = "";
7471 return (i);
7474 void
7475 cmd_complete(struct tab *t, char *str, int dir)
7477 GtkEntry *w = GTK_ENTRY(t->cmd);
7478 int i, j, levels, c = 0, dep = 0, parent = -1;
7479 int matchcount = 0;
7480 char *tok, *match, *s = g_strdup(str);
7481 char *tokens[3];
7482 char res[XT_MAX_URL_LENGTH + 32] = ":";
7483 char *sc = s;
7485 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7487 /* copy prefix*/
7488 for (i = 0; isdigit(s[i]); i++)
7489 res[i + 1] = s[i];
7491 for (; isspace(s[i]); i++)
7492 res[i + 1] = s[i];
7494 s += i;
7496 levels = cmd_tokenize(s, tokens);
7498 for (i = 0; i < levels - 1; i++) {
7499 tok = tokens[i];
7500 matchcount = 0;
7501 for (j = c; j < LENGTH(cmds); j++) {
7502 if (cmds[j].level < dep)
7503 break;
7504 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7505 strlen(tok))) {
7506 matchcount++;
7507 c = j + 1;
7508 if (strlen(tok) == strlen(cmds[j].cmd)) {
7509 matchcount = 1;
7510 break;
7515 if (matchcount == 1) {
7516 strlcat(res, tok, sizeof res);
7517 strlcat(res, " ", sizeof res);
7518 dep++;
7519 } else {
7520 g_free(sc);
7521 return;
7524 parent = c - 1;
7527 if (cmd_status.index == -1)
7528 cmd_getlist(parent, tokens[i]);
7530 if (cmd_status.len > 0) {
7531 match = cmd_getnext(dir);
7532 strlcat(res, match, sizeof res);
7533 gtk_entry_set_text(w, res);
7534 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7537 g_free(sc);
7540 gboolean
7541 cmd_execute(struct tab *t, char *str)
7543 struct cmd *cmd = NULL;
7544 char *tok, *last, *s = g_strdup(str), *sc;
7545 char prefixstr[4];
7546 int j, len, c = 0, dep = 0, matchcount = 0;
7547 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7548 struct karg arg = {0, NULL, -1};
7550 sc = s;
7552 /* copy prefix*/
7553 for (j = 0; j<3 && isdigit(s[j]); j++)
7554 prefixstr[j]=s[j];
7556 prefixstr[j]='\0';
7558 s += j;
7559 while (isspace(s[0]))
7560 s++;
7562 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7563 prefix = atoi(prefixstr);
7564 else
7565 s = sc;
7567 for (tok = strtok_r(s, " ", &last); tok;
7568 tok = strtok_r(NULL, " ", &last)) {
7569 matchcount = 0;
7570 for (j = c; j < LENGTH(cmds); j++) {
7571 if (cmds[j].level < dep)
7572 break;
7573 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7574 strlen(tok);
7575 if (cmds[j].level == dep &&
7576 !strncmp(tok, cmds[j].cmd, len)) {
7577 matchcount++;
7578 c = j + 1;
7579 cmd = &cmds[j];
7580 if (len == strlen(cmds[j].cmd)) {
7581 matchcount = 1;
7582 break;
7586 if (matchcount == 1) {
7587 if (cmd->type > 0)
7588 goto execute_cmd;
7589 dep++;
7590 } else {
7591 show_oops(t, "Invalid command: %s", str);
7592 goto done;
7595 execute_cmd:
7596 arg.i = cmd->arg;
7598 if (prefix != -1)
7599 arg.precount = prefix;
7600 else if (cmd_prefix > 0)
7601 arg.precount = cmd_prefix;
7603 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7604 show_oops(t, "No prefix allowed: %s", str);
7605 goto done;
7607 if (cmd->type > 1)
7608 arg.s = last ? g_strdup(last) : g_strdup("");
7609 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7610 arg.precount = atoi(arg.s);
7611 if (arg.precount <= 0) {
7612 if (arg.s[0] == '0')
7613 show_oops(t, "Zero count");
7614 else
7615 show_oops(t, "Trailing characters");
7616 goto done;
7620 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7621 __func__, arg.precount, arg.s);
7623 cmd->func(t, &arg);
7625 rv = XT_CB_HANDLED;
7626 done:
7627 if (j > 0)
7628 cmd_prefix = 0;
7629 g_free(sc);
7630 if (arg.s)
7631 g_free(arg.s);
7633 return (rv);
7637 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7639 if (t == NULL) {
7640 show_oops(NULL, "entry_key_cb invalid parameters");
7641 return (XT_CB_PASSTHROUGH);
7644 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7645 e->keyval, e->state, t);
7647 hide_oops(t);
7649 if (e->keyval == GDK_Escape) {
7650 /* don't use focus_webview(t) because we want to type :cmds */
7651 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7654 return (handle_keypress(t, e, 1));
7658 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7660 int rv = XT_CB_HANDLED;
7661 const gchar *c = gtk_entry_get_text(w);
7663 if (t == NULL) {
7664 show_oops(NULL, "cmd_keypress_cb parameters");
7665 return (XT_CB_PASSTHROUGH);
7668 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7669 e->keyval, e->state, t);
7671 /* sanity */
7672 if (c == NULL)
7673 e->keyval = GDK_Escape;
7674 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7675 e->keyval = GDK_Escape;
7677 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7678 e->keyval != GDK_ISO_Left_Tab)
7679 cmd_status.index = -1;
7681 switch (e->keyval) {
7682 case GDK_Tab:
7683 if (c[0] == ':')
7684 cmd_complete(t, (char *)&c[1], 1);
7685 goto done;
7686 case GDK_ISO_Left_Tab:
7687 if (c[0] == ':')
7688 cmd_complete(t, (char *)&c[1], -1);
7690 goto done;
7691 case GDK_BackSpace:
7692 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7693 break;
7694 /* FALLTHROUGH */
7695 case GDK_Escape:
7696 hide_cmd(t);
7697 focus_webview(t);
7699 /* cancel search */
7700 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7701 webkit_web_view_unmark_text_matches(t->wv);
7702 goto done;
7705 rv = XT_CB_PASSTHROUGH;
7706 done:
7707 return (rv);
7711 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7713 if (t == NULL) {
7714 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7715 return (XT_CB_PASSTHROUGH);
7717 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7719 hide_cmd(t);
7720 hide_oops(t);
7722 if (show_url == 0 || t->focus_wv)
7723 focus_webview(t);
7724 else
7725 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7727 return (XT_CB_PASSTHROUGH);
7730 void
7731 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7733 char *s;
7734 const gchar *c = gtk_entry_get_text(entry);
7736 if (t == NULL) {
7737 show_oops(NULL, "cmd_activate_cb invalid parameters");
7738 return;
7741 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7743 hide_cmd(t);
7745 /* sanity */
7746 if (c == NULL)
7747 goto done;
7748 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7749 goto done;
7750 if (strlen(c) < 2)
7751 goto done;
7752 s = (char *)&c[1];
7754 if (c[0] == '/' || c[0] == '?') {
7755 if (t->search_text) {
7756 g_free(t->search_text);
7757 t->search_text = NULL;
7760 t->search_text = g_strdup(s);
7761 if (global_search)
7762 g_free(global_search);
7763 global_search = g_strdup(s);
7764 t->search_forward = c[0] == '/';
7766 goto done;
7769 cmd_execute(t, s);
7771 done:
7772 return;
7775 void
7776 backward_cb(GtkWidget *w, struct tab *t)
7778 struct karg a;
7780 if (t == NULL) {
7781 show_oops(NULL, "backward_cb invalid parameters");
7782 return;
7785 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7787 a.i = XT_NAV_BACK;
7788 navaction(t, &a);
7791 void
7792 forward_cb(GtkWidget *w, struct tab *t)
7794 struct karg a;
7796 if (t == NULL) {
7797 show_oops(NULL, "forward_cb invalid parameters");
7798 return;
7801 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7803 a.i = XT_NAV_FORWARD;
7804 navaction(t, &a);
7807 void
7808 home_cb(GtkWidget *w, struct tab *t)
7810 if (t == NULL) {
7811 show_oops(NULL, "home_cb invalid parameters");
7812 return;
7815 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7817 load_uri(t, home);
7820 void
7821 stop_cb(GtkWidget *w, struct tab *t)
7823 WebKitWebFrame *frame;
7825 if (t == NULL) {
7826 show_oops(NULL, "stop_cb invalid parameters");
7827 return;
7830 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7832 frame = webkit_web_view_get_main_frame(t->wv);
7833 if (frame == NULL) {
7834 show_oops(t, "stop_cb: no frame");
7835 return;
7838 webkit_web_frame_stop_loading(frame);
7839 abort_favicon_download(t);
7842 void
7843 setup_webkit(struct tab *t)
7845 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7846 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7847 FALSE, (char *)NULL);
7848 else
7849 warnx("webkit does not have \"enable-dns-prefetching\" property");
7850 g_object_set(G_OBJECT(t->settings),
7851 "user-agent", t->user_agent, (char *)NULL);
7852 g_object_set(G_OBJECT(t->settings),
7853 "enable-scripts", enable_scripts, (char *)NULL);
7854 g_object_set(G_OBJECT(t->settings),
7855 "enable-plugins", enable_plugins, (char *)NULL);
7856 g_object_set(G_OBJECT(t->settings),
7857 "javascript-can-open-windows-automatically", enable_scripts,
7858 (char *)NULL);
7859 g_object_set(G_OBJECT(t->settings),
7860 "enable-html5-database", FALSE, (char *)NULL);
7861 g_object_set(G_OBJECT(t->settings),
7862 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7863 g_object_set(G_OBJECT(t->settings),
7864 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7865 g_object_set(G_OBJECT(t->settings),
7866 "spell_checking_languages", spell_check_languages, (char *)NULL);
7867 g_object_set(G_OBJECT(t->wv),
7868 "full-content-zoom", TRUE, (char *)NULL);
7870 webkit_web_view_set_settings(t->wv, t->settings);
7873 gboolean
7874 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7876 struct tab *ti, *t = NULL;
7877 gdouble view_size, value, max;
7878 gchar *position;
7880 TAILQ_FOREACH(ti, &tabs, entry)
7881 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7882 t = ti;
7883 break;
7886 if (t == NULL)
7887 return FALSE;
7889 if (adjustment == NULL)
7890 adjustment = gtk_scrolled_window_get_vadjustment(
7891 GTK_SCROLLED_WINDOW(t->browser_win));
7893 view_size = gtk_adjustment_get_page_size(adjustment);
7894 value = gtk_adjustment_get_value(adjustment);
7895 max = gtk_adjustment_get_upper(adjustment) - view_size;
7897 if (max == 0)
7898 position = g_strdup("All");
7899 else if (value == max)
7900 position = g_strdup("Bot");
7901 else if (value == 0)
7902 position = g_strdup("Top");
7903 else
7904 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7906 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7907 g_free(position);
7909 return (TRUE);
7912 GtkWidget *
7913 create_browser(struct tab *t)
7915 GtkWidget *w;
7916 gchar *strval;
7917 GtkAdjustment *adjustment;
7919 if (t == NULL) {
7920 show_oops(NULL, "create_browser invalid parameters");
7921 return (NULL);
7924 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7925 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7926 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7927 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7929 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7930 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7931 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7933 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7934 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7936 /* set defaults */
7937 t->settings = webkit_web_settings_new();
7939 if (user_agent == NULL) {
7940 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7941 (char *)NULL);
7942 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7943 g_free(strval);
7944 } else
7945 t->user_agent = g_strdup(user_agent);
7947 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7949 adjustment =
7950 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7951 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7952 G_CALLBACK(update_statusbar_position), NULL);
7954 setup_webkit(t);
7956 return (w);
7959 GtkWidget *
7960 create_window(void)
7962 GtkWidget *w;
7964 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7965 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7966 gtk_widget_set_name(w, "xxxterm");
7967 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7968 g_signal_connect(G_OBJECT(w), "delete_event",
7969 G_CALLBACK (gtk_main_quit), NULL);
7971 return (w);
7974 GtkWidget *
7975 create_kiosk_toolbar(struct tab *t)
7977 GtkWidget *toolbar = NULL, *b;
7979 b = gtk_hbox_new(FALSE, 0);
7980 toolbar = b;
7981 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7983 /* backward button */
7984 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7985 gtk_widget_set_sensitive(t->backward, FALSE);
7986 g_signal_connect(G_OBJECT(t->backward), "clicked",
7987 G_CALLBACK(backward_cb), t);
7988 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7990 /* forward button */
7991 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7992 gtk_widget_set_sensitive(t->forward, FALSE);
7993 g_signal_connect(G_OBJECT(t->forward), "clicked",
7994 G_CALLBACK(forward_cb), t);
7995 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7997 /* home button */
7998 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7999 gtk_widget_set_sensitive(t->gohome, true);
8000 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8001 G_CALLBACK(home_cb), t);
8002 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8004 /* create widgets but don't use them */
8005 t->uri_entry = gtk_entry_new();
8006 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8007 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8008 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8010 return (toolbar);
8013 GtkWidget *
8014 create_toolbar(struct tab *t)
8016 GtkWidget *toolbar = NULL, *b, *eb1;
8018 b = gtk_hbox_new(FALSE, 0);
8019 toolbar = b;
8020 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8022 /* backward button */
8023 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8024 gtk_widget_set_sensitive(t->backward, FALSE);
8025 g_signal_connect(G_OBJECT(t->backward), "clicked",
8026 G_CALLBACK(backward_cb), t);
8027 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8029 /* forward button */
8030 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8031 gtk_widget_set_sensitive(t->forward, FALSE);
8032 g_signal_connect(G_OBJECT(t->forward), "clicked",
8033 G_CALLBACK(forward_cb), t);
8034 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8035 FALSE, 0);
8037 /* stop button */
8038 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8039 gtk_widget_set_sensitive(t->stop, FALSE);
8040 g_signal_connect(G_OBJECT(t->stop), "clicked",
8041 G_CALLBACK(stop_cb), t);
8042 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8043 FALSE, 0);
8045 /* JS button */
8046 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8047 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8048 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8049 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8050 G_CALLBACK(js_toggle_cb), t);
8051 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8053 t->uri_entry = gtk_entry_new();
8054 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8055 G_CALLBACK(activate_uri_entry_cb), t);
8056 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8057 G_CALLBACK(entry_key_cb), t);
8058 completion_add(t);
8059 eb1 = gtk_hbox_new(FALSE, 0);
8060 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8061 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8062 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8064 /* search entry */
8065 if (search_string) {
8066 GtkWidget *eb2;
8067 t->search_entry = gtk_entry_new();
8068 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8069 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8070 G_CALLBACK(activate_search_entry_cb), t);
8071 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8072 G_CALLBACK(entry_key_cb), t);
8073 gtk_widget_set_size_request(t->search_entry, -1, -1);
8074 eb2 = gtk_hbox_new(FALSE, 0);
8075 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8076 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8078 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8081 return (toolbar);
8084 GtkWidget *
8085 create_buffers(struct tab *t)
8087 GtkCellRenderer *renderer;
8088 GtkWidget *view;
8090 view = gtk_tree_view_new();
8092 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8094 renderer = gtk_cell_renderer_text_new();
8095 gtk_tree_view_insert_column_with_attributes
8096 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8098 renderer = gtk_cell_renderer_text_new();
8099 gtk_tree_view_insert_column_with_attributes
8100 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8101 NULL);
8103 gtk_tree_view_set_model
8104 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8106 return view;
8109 void
8110 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8111 GtkTreeViewColumn *col, struct tab *t)
8113 GtkTreeIter iter;
8114 guint id;
8116 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8118 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8119 path)) {
8120 gtk_tree_model_get
8121 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8122 set_current_tab(id - 1);
8125 hide_buffers(t);
8128 /* after tab reordering/creation/removal */
8129 void
8130 recalc_tabs(void)
8132 struct tab *t;
8133 int maxid = 0;
8135 TAILQ_FOREACH(t, &tabs, entry) {
8136 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8137 if (t->tab_id > maxid)
8138 maxid = t->tab_id;
8140 gtk_widget_show(t->tab_elems.sep);
8143 TAILQ_FOREACH(t, &tabs, entry) {
8144 if (t->tab_id == maxid) {
8145 gtk_widget_hide(t->tab_elems.sep);
8146 break;
8151 /* after active tab change */
8152 void
8153 recolor_compact_tabs(void)
8155 struct tab *t;
8156 int curid = 0;
8157 GdkColor color;
8159 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8160 TAILQ_FOREACH(t, &tabs, entry)
8161 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8162 &color);
8164 curid = gtk_notebook_get_current_page(notebook);
8165 TAILQ_FOREACH(t, &tabs, entry)
8166 if (t->tab_id == curid) {
8167 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8168 gtk_widget_modify_fg(t->tab_elems.label,
8169 GTK_STATE_NORMAL, &color);
8170 break;
8174 void
8175 set_current_tab(int page_num)
8177 buffercmd_abort(get_current_tab());
8178 gtk_notebook_set_current_page(notebook, page_num);
8179 recolor_compact_tabs();
8183 undo_close_tab_save(struct tab *t)
8185 int m, n;
8186 const gchar *uri;
8187 struct undo *u1, *u2;
8188 GList *items;
8189 WebKitWebHistoryItem *item;
8191 if ((uri = get_uri(t)) == NULL)
8192 return (1);
8194 u1 = g_malloc0(sizeof(struct undo));
8195 u1->uri = g_strdup(uri);
8197 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8199 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8200 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8201 u1->back = n;
8203 /* forward history */
8204 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8206 while (items) {
8207 item = items->data;
8208 u1->history = g_list_prepend(u1->history,
8209 webkit_web_history_item_copy(item));
8210 items = g_list_next(items);
8213 /* current item */
8214 if (m) {
8215 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8216 u1->history = g_list_prepend(u1->history,
8217 webkit_web_history_item_copy(item));
8220 /* back history */
8221 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8223 while (items) {
8224 item = items->data;
8225 u1->history = g_list_prepend(u1->history,
8226 webkit_web_history_item_copy(item));
8227 items = g_list_next(items);
8230 TAILQ_INSERT_HEAD(&undos, u1, entry);
8232 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8233 u2 = TAILQ_LAST(&undos, undo_tailq);
8234 TAILQ_REMOVE(&undos, u2, entry);
8235 g_free(u2->uri);
8236 g_list_free(u2->history);
8237 g_free(u2);
8238 } else
8239 undo_count++;
8241 return (0);
8244 void
8245 delete_tab(struct tab *t)
8247 struct karg a;
8249 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8251 if (t == NULL)
8252 return;
8254 buffercmd_abort(t);
8255 TAILQ_REMOVE(&tabs, t, entry);
8257 /* Halt all webkit activity. */
8258 abort_favicon_download(t);
8259 webkit_web_view_stop_loading(t->wv);
8261 /* Save the tab, so we can undo the close. */
8262 undo_close_tab_save(t);
8264 if (browser_mode == XT_BM_KIOSK) {
8265 gtk_widget_destroy(t->uri_entry);
8266 gtk_widget_destroy(t->stop);
8267 gtk_widget_destroy(t->js_toggle);
8270 gtk_widget_destroy(t->tab_elems.eventbox);
8271 gtk_widget_destroy(t->vbox);
8273 g_free(t->user_agent);
8274 g_free(t->stylesheet);
8275 g_free(t->tmp_uri);
8276 g_free(t);
8278 if (TAILQ_EMPTY(&tabs)) {
8279 if (browser_mode == XT_BM_KIOSK)
8280 create_new_tab(home, NULL, 1, -1);
8281 else
8282 create_new_tab(NULL, NULL, 1, -1);
8285 /* recreate session */
8286 if (session_autosave) {
8287 a.s = NULL;
8288 save_tabs(t, &a);
8291 recalc_tabs();
8292 recolor_compact_tabs();
8295 void
8296 update_statusbar_zoom(struct tab *t)
8298 gfloat zoom;
8299 char s[16] = { '\0' };
8301 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8302 if ((zoom <= 0.99 || zoom >= 1.01))
8303 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8304 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8307 void
8308 setzoom_webkit(struct tab *t, int adjust)
8310 #define XT_ZOOMPERCENT 0.04
8312 gfloat zoom;
8314 if (t == NULL) {
8315 show_oops(NULL, "setzoom_webkit invalid parameters");
8316 return;
8319 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8320 if (adjust == XT_ZOOM_IN)
8321 zoom += XT_ZOOMPERCENT;
8322 else if (adjust == XT_ZOOM_OUT)
8323 zoom -= XT_ZOOMPERCENT;
8324 else if (adjust > 0)
8325 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8326 else {
8327 show_oops(t, "setzoom_webkit invalid zoom value");
8328 return;
8331 if (zoom < XT_ZOOMPERCENT)
8332 zoom = XT_ZOOMPERCENT;
8333 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8334 update_statusbar_zoom(t);
8337 gboolean
8338 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8340 struct tab *t = (struct tab *) data;
8342 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8344 switch (event->button) {
8345 case 1:
8346 set_current_tab(t->tab_id);
8347 break;
8348 case 2:
8349 delete_tab(t);
8350 break;
8353 return TRUE;
8356 void
8357 append_tab(struct tab *t)
8359 if (t == NULL)
8360 return;
8362 TAILQ_INSERT_TAIL(&tabs, t, entry);
8363 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8366 GtkWidget *
8367 create_sbe(int width)
8369 GtkWidget *sbe;
8371 sbe = gtk_entry_new();
8372 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8373 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8374 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8375 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8376 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8377 gtk_widget_set_size_request(sbe, width, -1);
8379 return sbe;
8382 struct tab *
8383 create_new_tab(char *title, struct undo *u, int focus, int position)
8385 struct tab *t;
8386 int load = 1, id;
8387 GtkWidget *b, *bb;
8388 WebKitWebHistoryItem *item;
8389 GList *items;
8390 GdkColor color;
8391 char *p;
8392 int sbe_p = 0, sbe_b = 0,
8393 sbe_z = 0;
8395 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8397 if (tabless && !TAILQ_EMPTY(&tabs)) {
8398 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8399 return (NULL);
8402 t = g_malloc0(sizeof *t);
8404 if (title == NULL) {
8405 title = "(untitled)";
8406 load = 0;
8409 t->vbox = gtk_vbox_new(FALSE, 0);
8411 /* label + button for tab */
8412 b = gtk_hbox_new(FALSE, 0);
8413 t->tab_content = b;
8415 #if GTK_CHECK_VERSION(2, 20, 0)
8416 t->spinner = gtk_spinner_new();
8417 #endif
8418 t->label = gtk_label_new(title);
8419 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8420 gtk_widget_set_size_request(t->label, 100, 0);
8421 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8422 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8423 gtk_widget_set_size_request(b, 130, 0);
8425 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8426 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8427 #if GTK_CHECK_VERSION(2, 20, 0)
8428 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8429 #endif
8431 /* toolbar */
8432 if (browser_mode == XT_BM_KIOSK) {
8433 t->toolbar = create_kiosk_toolbar(t);
8434 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8436 } else {
8437 t->toolbar = create_toolbar(t);
8438 if (fancy_bar)
8439 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8440 FALSE, 0);
8443 /* marks */
8444 marks_clear(t);
8446 /* browser */
8447 t->browser_win = create_browser(t);
8448 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8450 /* oops message for user feedback */
8451 t->oops = gtk_entry_new();
8452 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8453 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8454 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8455 gdk_color_parse(XT_COLOR_RED, &color);
8456 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8457 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8458 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8460 /* command entry */
8461 t->cmd = gtk_entry_new();
8462 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8463 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8464 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8465 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8467 /* status bar */
8468 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8470 t->sbe.statusbar = gtk_entry_new();
8471 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8472 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8473 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8474 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8476 /* create these widgets only if specified in statusbar_elems */
8478 t->sbe.position = create_sbe(40);
8479 t->sbe.zoom = create_sbe(40);
8480 t->sbe.buffercmd = create_sbe(60);
8482 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8484 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8485 TRUE, FALSE);
8487 /* gtk widgets cannot be added to a box twice. sbe_* variables
8488 make sure of this */
8489 for (p = statusbar_elems; *p != '\0'; p++) {
8490 switch (*p) {
8491 case '|':
8493 GtkWidget *sep = gtk_vseparator_new();
8495 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8496 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8497 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8498 FALSE, FALSE, FALSE);
8499 break;
8501 case 'P':
8502 if (sbe_p) {
8503 warnx("flag \"%c\" specified more than "
8504 "once in statusbar_elems\n", *p);
8505 break;
8507 sbe_p = 1;
8508 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8509 t->sbe.position, FALSE, FALSE, FALSE);
8510 break;
8511 case 'B':
8512 if (sbe_b) {
8513 warnx("flag \"%c\" specified more than "
8514 "once in statusbar_elems\n", *p);
8515 break;
8517 sbe_b = 1;
8518 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8519 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8520 break;
8521 case 'Z':
8522 if (sbe_z) {
8523 warnx("flag \"%c\" specified more than "
8524 "once in statusbar_elems\n", *p);
8525 break;
8527 sbe_z = 1;
8528 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8529 t->sbe.zoom, FALSE, FALSE, FALSE);
8530 break;
8531 default:
8532 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8533 break;
8537 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8539 /* buffer list */
8540 t->buffers = create_buffers(t);
8541 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8543 /* xtp meaning is normal by default */
8544 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8546 /* set empty favicon */
8547 xt_icon_from_name(t, "text-html");
8549 /* and show it all */
8550 gtk_widget_show_all(b);
8551 gtk_widget_show_all(t->vbox);
8553 /* compact tab bar */
8554 t->tab_elems.label = gtk_label_new(title);
8555 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8556 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8557 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8558 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8560 t->tab_elems.eventbox = gtk_event_box_new();
8561 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8562 t->tab_elems.sep = gtk_vseparator_new();
8564 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8565 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8566 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8567 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8568 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8569 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8571 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8572 TRUE, 0);
8573 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8574 FALSE, 0);
8575 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8576 t->tab_elems.box);
8578 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8579 TRUE, 0);
8580 gtk_widget_show_all(t->tab_elems.eventbox);
8582 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8583 append_tab(t);
8584 else {
8585 id = position >= 0 ? position :
8586 gtk_notebook_get_current_page(notebook) + 1;
8587 if (id > gtk_notebook_get_n_pages(notebook))
8588 append_tab(t);
8589 else {
8590 TAILQ_INSERT_TAIL(&tabs, t, entry);
8591 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8592 gtk_box_reorder_child(GTK_BOX(tab_bar),
8593 t->tab_elems.eventbox, id);
8594 recalc_tabs();
8598 #if GTK_CHECK_VERSION(2, 20, 0)
8599 /* turn spinner off if we are a new tab without uri */
8600 if (!load) {
8601 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8602 gtk_widget_hide(t->spinner);
8604 #endif
8605 /* make notebook tabs reorderable */
8606 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8608 /* compact tabs clickable */
8609 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8610 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8612 g_object_connect(G_OBJECT(t->cmd),
8613 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8614 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8615 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8616 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8617 (char *)NULL);
8619 /* reuse wv_button_cb to hide oops */
8620 g_object_connect(G_OBJECT(t->oops),
8621 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8622 (char *)NULL);
8624 g_signal_connect(t->buffers,
8625 "row-activated", G_CALLBACK(row_activated_cb), t);
8626 g_object_connect(G_OBJECT(t->buffers),
8627 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8629 g_object_connect(G_OBJECT(t->wv),
8630 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8631 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8632 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8633 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8634 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8635 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8636 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8637 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8638 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8639 "signal::event", G_CALLBACK(webview_event_cb), t,
8640 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8641 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8642 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8643 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8644 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8645 (char *)NULL);
8646 g_signal_connect(t->wv,
8647 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8649 * XXX this puts invalid url in uri_entry and that is undesirable
8651 #if 0
8652 g_signal_connect(t->wv,
8653 "load-error", G_CALLBACK(notify_load_error_cb), t);
8654 #endif
8655 g_signal_connect(t->wv,
8656 "notify::title", G_CALLBACK(notify_title_cb), t);
8658 /* hijack the unused keys as if we were the browser */
8659 g_object_connect(G_OBJECT(t->toolbar),
8660 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8661 (char *)NULL);
8663 g_signal_connect(G_OBJECT(bb), "button_press_event",
8664 G_CALLBACK(tab_close_cb), t);
8666 /* hide stuff */
8667 hide_cmd(t);
8668 hide_oops(t);
8669 hide_buffers(t);
8670 url_set_visibility();
8671 statusbar_set_visibility();
8673 if (focus) {
8674 set_current_tab(t->tab_id);
8675 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8676 t->tab_id);
8679 if (load) {
8680 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8681 load_uri(t, title);
8682 } else {
8683 if (show_url == 1)
8684 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8685 else
8686 focus_webview(t);
8689 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8690 /* restore the tab's history */
8691 if (u && u->history) {
8692 items = u->history;
8693 while (items) {
8694 item = items->data;
8695 webkit_web_back_forward_list_add_item(t->bfl, item);
8696 items = g_list_next(items);
8699 item = g_list_nth_data(u->history, u->back);
8700 if (item)
8701 webkit_web_view_go_to_back_forward_item(t->wv, item);
8703 g_list_free(items);
8704 g_list_free(u->history);
8705 } else
8706 webkit_web_back_forward_list_clear(t->bfl);
8708 recolor_compact_tabs();
8709 setzoom_webkit(t, XT_ZOOM_NORMAL);
8710 return (t);
8713 void
8714 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8715 gpointer *udata)
8717 struct tab *t;
8718 const gchar *uri;
8720 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8722 if (gtk_notebook_get_current_page(notebook) == -1)
8723 recalc_tabs();
8725 TAILQ_FOREACH(t, &tabs, entry) {
8726 if (t->tab_id == pn) {
8727 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8728 "%d\n", pn);
8730 uri = get_title(t, TRUE);
8731 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8733 hide_cmd(t);
8734 hide_oops(t);
8736 if (t->focus_wv) {
8737 /* can't use focus_webview here */
8738 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8744 void
8745 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8746 gpointer *udata)
8748 struct tab *t = NULL, *tt;
8750 recalc_tabs();
8752 TAILQ_FOREACH(tt, &tabs, entry)
8753 if (tt->tab_id == pn) {
8754 t = tt;
8755 break;
8758 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8760 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8761 t->tab_id);
8764 void
8765 menuitem_response(struct tab *t)
8767 gtk_notebook_set_current_page(notebook, t->tab_id);
8770 gboolean
8771 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8773 GtkWidget *menu, *menu_items;
8774 GdkEventButton *bevent;
8775 const gchar *uri;
8776 struct tab *ti;
8778 if (event->type == GDK_BUTTON_PRESS) {
8779 bevent = (GdkEventButton *) event;
8780 menu = gtk_menu_new();
8782 TAILQ_FOREACH(ti, &tabs, entry) {
8783 if ((uri = get_uri(ti)) == NULL)
8784 /* XXX make sure there is something to print */
8785 /* XXX add gui pages in here to look purdy */
8786 uri = "(untitled)";
8787 menu_items = gtk_menu_item_new_with_label(uri);
8788 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8789 gtk_widget_show(menu_items);
8791 g_signal_connect_swapped((menu_items),
8792 "activate", G_CALLBACK(menuitem_response),
8793 (gpointer)ti);
8796 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8797 bevent->button, bevent->time);
8799 /* unref object so it'll free itself when popped down */
8800 #if !GTK_CHECK_VERSION(3, 0, 0)
8801 /* XXX does not need unref with gtk+3? */
8802 g_object_ref_sink(menu);
8803 g_object_unref(menu);
8804 #endif
8806 return (TRUE /* eat event */);
8809 return (FALSE /* propagate */);
8813 icon_size_map(int icon_size)
8815 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8816 icon_size > GTK_ICON_SIZE_DIALOG)
8817 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8819 return (icon_size);
8822 GtkWidget *
8823 create_button(char *name, char *stockid, int size)
8825 GtkWidget *button, *image;
8826 gchar *rcstring;
8827 int gtk_icon_size;
8829 rcstring = g_strdup_printf(
8830 "style \"%s-style\"\n"
8831 "{\n"
8832 " GtkWidget::focus-padding = 0\n"
8833 " GtkWidget::focus-line-width = 0\n"
8834 " xthickness = 0\n"
8835 " ythickness = 0\n"
8836 "}\n"
8837 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8838 gtk_rc_parse_string(rcstring);
8839 g_free(rcstring);
8840 button = gtk_button_new();
8841 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8842 gtk_icon_size = icon_size_map(size ? size : icon_size);
8844 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8845 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8846 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8847 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8848 gtk_widget_set_name(button, name);
8849 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8851 return (button);
8854 void
8855 button_set_stockid(GtkWidget *button, char *stockid)
8857 GtkWidget *image;
8859 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8860 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8861 gtk_button_set_image(GTK_BUTTON(button), image);
8864 void
8865 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8867 gchar *p = NULL;
8868 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
8869 gint len;
8871 if (xterm_workaround == 0)
8872 return;
8875 * xterm doesn't play nice with clipboards because it clears the
8876 * primary when clicked. We rely on primary being set to properly
8877 * handle middle mouse button clicks (paste). So when someone clears
8878 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
8879 * other application behavior (as in DON'T clear primary).
8882 p = gtk_clipboard_wait_for_text(primary);
8883 if (p == NULL) {
8884 if (gdk_property_get(gdk_get_default_root_window(),
8885 atom,
8886 gdk_atom_intern("STRING", FALSE),
8888 1024 * 1024 /* picked out of my butt */,
8889 FALSE,
8890 NULL,
8891 NULL,
8892 &len,
8893 (guchar **)&p)) {
8894 /* yes sir, we need to NUL the string */
8895 p[len] = '\0';
8896 gtk_clipboard_set_text(primary, p, -1);
8900 if (p)
8901 g_free(p);
8904 void
8905 create_canvas(void)
8907 GtkWidget *vbox;
8908 GList *l = NULL;
8909 GdkPixbuf *pb;
8910 char file[PATH_MAX];
8911 int i;
8913 vbox = gtk_vbox_new(FALSE, 0);
8914 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8915 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8916 #if !GTK_CHECK_VERSION(3, 0, 0)
8917 /* XXX seems to be needed with gtk+2 */
8918 gtk_notebook_set_tab_hborder(notebook, 0);
8919 gtk_notebook_set_tab_vborder(notebook, 0);
8920 #endif
8921 gtk_notebook_set_scrollable(notebook, TRUE);
8922 gtk_notebook_set_show_border(notebook, FALSE);
8923 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8925 abtn = gtk_button_new();
8926 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8927 gtk_widget_set_size_request(arrow, -1, -1);
8928 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8929 gtk_widget_set_size_request(abtn, -1, 20);
8931 #if GTK_CHECK_VERSION(2, 20, 0)
8932 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8933 #endif
8934 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8936 /* compact tab bar */
8937 tab_bar = gtk_hbox_new(TRUE, 0);
8939 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8940 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8941 gtk_widget_set_size_request(vbox, -1, -1);
8943 g_object_connect(G_OBJECT(notebook),
8944 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8945 (char *)NULL);
8946 g_object_connect(G_OBJECT(notebook),
8947 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
8948 NULL, (char *)NULL);
8949 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8950 G_CALLBACK(arrow_cb), NULL);
8952 main_window = create_window();
8953 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8955 /* icons */
8956 for (i = 0; i < LENGTH(icons); i++) {
8957 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8958 pb = gdk_pixbuf_new_from_file(file, NULL);
8959 l = g_list_append(l, pb);
8961 gtk_window_set_default_icon_list(l);
8963 /* clipboard work around */
8964 if (xterm_workaround)
8965 g_signal_connect(
8966 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8967 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8969 gtk_widget_show_all(abtn);
8970 gtk_widget_show_all(main_window);
8971 notebook_tab_set_visibility();
8974 void
8975 set_hook(void **hook, char *name)
8977 if (hook == NULL)
8978 errx(1, "set_hook");
8980 if (*hook == NULL) {
8981 *hook = dlsym(RTLD_NEXT, name);
8982 if (*hook == NULL)
8983 errx(1, "can't hook %s", name);
8987 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8988 gboolean
8989 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8991 g_return_val_if_fail(cookie1, FALSE);
8992 g_return_val_if_fail(cookie2, FALSE);
8994 return (!strcmp (cookie1->name, cookie2->name) &&
8995 !strcmp (cookie1->value, cookie2->value) &&
8996 !strcmp (cookie1->path, cookie2->path) &&
8997 !strcmp (cookie1->domain, cookie2->domain));
9000 void
9001 transfer_cookies(void)
9003 GSList *cf;
9004 SoupCookie *sc, *pc;
9006 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9008 for (;cf; cf = cf->next) {
9009 pc = cf->data;
9010 sc = soup_cookie_copy(pc);
9011 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9014 soup_cookies_free(cf);
9017 void
9018 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9020 GSList *cf;
9021 SoupCookie *ci;
9023 print_cookie("soup_cookie_jar_delete_cookie", c);
9025 if (cookies_enabled == 0)
9026 return;
9028 if (jar == NULL || c == NULL)
9029 return;
9031 /* find and remove from persistent jar */
9032 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9034 for (;cf; cf = cf->next) {
9035 ci = cf->data;
9036 if (soup_cookie_equal(ci, c)) {
9037 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9038 break;
9042 soup_cookies_free(cf);
9044 /* delete from session jar */
9045 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9048 void
9049 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9051 struct domain *d = NULL;
9052 SoupCookie *c;
9053 FILE *r_cookie_f;
9055 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9056 jar, p_cookiejar, s_cookiejar);
9058 if (cookies_enabled == 0)
9059 return;
9061 /* see if we are up and running */
9062 if (p_cookiejar == NULL) {
9063 _soup_cookie_jar_add_cookie(jar, cookie);
9064 return;
9066 /* disallow p_cookiejar adds, shouldn't happen */
9067 if (jar == p_cookiejar)
9068 return;
9070 /* sanity */
9071 if (jar == NULL || cookie == NULL)
9072 return;
9074 if (enable_cookie_whitelist &&
9075 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9076 blocked_cookies++;
9077 DNPRINTF(XT_D_COOKIE,
9078 "soup_cookie_jar_add_cookie: reject %s\n",
9079 cookie->domain);
9080 if (save_rejected_cookies) {
9081 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9082 show_oops(NULL, "can't open reject cookie file");
9083 return;
9085 fseek(r_cookie_f, 0, SEEK_END);
9086 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9087 cookie->http_only ? "#HttpOnly_" : "",
9088 cookie->domain,
9089 *cookie->domain == '.' ? "TRUE" : "FALSE",
9090 cookie->path,
9091 cookie->secure ? "TRUE" : "FALSE",
9092 cookie->expires ?
9093 (gulong)soup_date_to_time_t(cookie->expires) :
9095 cookie->name,
9096 cookie->value);
9097 fflush(r_cookie_f);
9098 fclose(r_cookie_f);
9100 if (!allow_volatile_cookies)
9101 return;
9104 if (cookie->expires == NULL && session_timeout) {
9105 soup_cookie_set_expires(cookie,
9106 soup_date_new_from_now(session_timeout));
9107 print_cookie("modified add cookie", cookie);
9110 /* see if we are white listed for persistence */
9111 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9112 /* add to persistent jar */
9113 c = soup_cookie_copy(cookie);
9114 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9115 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9118 /* add to session jar */
9119 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9120 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9123 void
9124 setup_cookies(void)
9126 char file[PATH_MAX];
9128 set_hook((void *)&_soup_cookie_jar_add_cookie,
9129 "soup_cookie_jar_add_cookie");
9130 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9131 "soup_cookie_jar_delete_cookie");
9133 if (cookies_enabled == 0)
9134 return;
9137 * the following code is intricate due to overriding several libsoup
9138 * functions.
9139 * do not alter order of these operations.
9142 /* rejected cookies */
9143 if (save_rejected_cookies)
9144 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9145 XT_REJECT_FILE);
9147 /* persistent cookies */
9148 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9149 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9151 /* session cookies */
9152 s_cookiejar = soup_cookie_jar_new();
9153 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9154 cookie_policy, (void *)NULL);
9155 transfer_cookies();
9157 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9160 void
9161 setup_proxy(char *uri)
9163 if (proxy_uri) {
9164 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9165 soup_uri_free(proxy_uri);
9166 proxy_uri = NULL;
9168 if (http_proxy) {
9169 if (http_proxy != uri) {
9170 g_free(http_proxy);
9171 http_proxy = NULL;
9175 if (uri) {
9176 http_proxy = g_strdup(uri);
9177 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9178 proxy_uri = soup_uri_new(http_proxy);
9179 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
9184 send_cmd_to_socket(char *cmd)
9186 int s, len, rv = 1;
9187 struct sockaddr_un sa;
9189 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9190 warnx("%s: socket", __func__);
9191 return (rv);
9194 sa.sun_family = AF_UNIX;
9195 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9196 work_dir, XT_SOCKET_FILE);
9197 len = SUN_LEN(&sa);
9199 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9200 warnx("%s: connect", __func__);
9201 goto done;
9204 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9205 warnx("%s: send", __func__);
9206 goto done;
9209 rv = 0;
9210 done:
9211 close(s);
9212 return (rv);
9215 gboolean
9216 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9218 int s, n;
9219 char str[XT_MAX_URL_LENGTH];
9220 socklen_t t = sizeof(struct sockaddr_un);
9221 struct sockaddr_un sa;
9222 struct passwd *p;
9223 uid_t uid;
9224 gid_t gid;
9225 struct tab *tt;
9226 gint fd = g_io_channel_unix_get_fd(source);
9228 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9229 warn("accept");
9230 return (FALSE);
9233 if (getpeereid(s, &uid, &gid) == -1) {
9234 warn("getpeereid");
9235 return (FALSE);
9237 if (uid != getuid() || gid != getgid()) {
9238 warnx("unauthorized user");
9239 return (FALSE);
9242 p = getpwuid(uid);
9243 if (p == NULL) {
9244 warnx("not a valid user");
9245 return (FALSE);
9248 n = recv(s, str, sizeof(str), 0);
9249 if (n <= 0)
9250 return (TRUE);
9252 tt = TAILQ_LAST(&tabs, tab_list);
9253 cmd_execute(tt, str);
9254 return (TRUE);
9258 is_running(void)
9260 int s, len, rv = 1;
9261 struct sockaddr_un sa;
9263 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9264 warn("is_running: socket");
9265 return (-1);
9268 sa.sun_family = AF_UNIX;
9269 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9270 work_dir, XT_SOCKET_FILE);
9271 len = SUN_LEN(&sa);
9273 /* connect to see if there is a listener */
9274 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9275 rv = 0; /* not running */
9276 else
9277 rv = 1; /* already running */
9279 close(s);
9281 return (rv);
9285 build_socket(void)
9287 int s, len;
9288 struct sockaddr_un sa;
9290 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9291 warn("build_socket: socket");
9292 return (-1);
9295 sa.sun_family = AF_UNIX;
9296 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9297 work_dir, XT_SOCKET_FILE);
9298 len = SUN_LEN(&sa);
9300 /* connect to see if there is a listener */
9301 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9302 /* no listener so we will */
9303 unlink(sa.sun_path);
9305 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9306 warn("build_socket: bind");
9307 goto done;
9310 if (listen(s, 1) == -1) {
9311 warn("build_socket: listen");
9312 goto done;
9315 return (s);
9318 done:
9319 close(s);
9320 return (-1);
9323 gboolean
9324 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9325 GtkTreeIter *iter, struct tab *t)
9327 gchar *value;
9329 gtk_tree_model_get(model, iter, 0, &value, -1);
9330 load_uri(t, value);
9331 g_free(value);
9333 return (FALSE);
9336 gboolean
9337 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9338 GtkTreeIter *iter, struct tab *t)
9340 gchar *value;
9342 gtk_tree_model_get(model, iter, 0, &value, -1);
9343 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9344 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9345 g_free(value);
9347 return (TRUE);
9350 void
9351 completion_add_uri(const gchar *uri)
9353 GtkTreeIter iter;
9355 /* add uri to list_store */
9356 gtk_list_store_append(completion_model, &iter);
9357 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9360 gboolean
9361 completion_match(GtkEntryCompletion *completion, const gchar *key,
9362 GtkTreeIter *iter, gpointer user_data)
9364 gchar *value;
9365 gboolean match = FALSE;
9367 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9368 -1);
9370 if (value == NULL)
9371 return FALSE;
9373 match = match_uri(value, key);
9375 g_free(value);
9376 return (match);
9379 void
9380 completion_add(struct tab *t)
9382 /* enable completion for tab */
9383 t->completion = gtk_entry_completion_new();
9384 gtk_entry_completion_set_text_column(t->completion, 0);
9385 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9386 gtk_entry_completion_set_model(t->completion,
9387 GTK_TREE_MODEL(completion_model));
9388 gtk_entry_completion_set_match_func(t->completion, completion_match,
9389 NULL, NULL);
9390 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9391 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9392 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9393 G_CALLBACK(completion_select_cb), t);
9394 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9395 G_CALLBACK(completion_hover_cb), t);
9398 void
9399 xxx_dir(char *dir)
9401 struct stat sb;
9403 if (stat(dir, &sb)) {
9404 if (mkdir(dir, S_IRWXU) == -1)
9405 err(1, "mkdir %s", dir);
9406 if (stat(dir, &sb))
9407 err(1, "stat %s", dir);
9409 if (S_ISDIR(sb.st_mode) == 0)
9410 errx(1, "%s not a dir", dir);
9411 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9412 warnx("fixing invalid permissions on %s", dir);
9413 if (chmod(dir, S_IRWXU) == -1)
9414 err(1, "chmod %s", dir);
9418 void
9419 usage(void)
9421 fprintf(stderr,
9422 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9423 exit(0);
9428 main(int argc, char *argv[])
9430 struct stat sb;
9431 int c, s, optn = 0, opte = 0, focus = 1;
9432 char conf[PATH_MAX] = { '\0' };
9433 char file[PATH_MAX];
9434 char *env_proxy = NULL;
9435 char *cmd = NULL;
9436 FILE *f = NULL;
9437 struct karg a;
9438 struct sigaction sact;
9439 GIOChannel *channel;
9440 struct rlimit rlp;
9442 start_argv = argv;
9444 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9446 /* fiddle with ulimits */
9447 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9448 warn("getrlimit");
9449 else {
9450 /* just use them all */
9451 rlp.rlim_cur = rlp.rlim_max;
9452 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9453 warn("setrlimit");
9454 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9455 warn("getrlimit");
9456 else if (rlp.rlim_cur <= 256)
9457 warnx("%s requires at least 256 file descriptors",
9458 __progname);
9461 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9462 switch (c) {
9463 case 'S':
9464 show_url = 0;
9465 break;
9466 case 'T':
9467 show_tabs = 0;
9468 break;
9469 case 'V':
9470 errx(0 , "Version: %s", version);
9471 break;
9472 case 'f':
9473 strlcpy(conf, optarg, sizeof(conf));
9474 break;
9475 case 's':
9476 strlcpy(named_session, optarg, sizeof(named_session));
9477 break;
9478 case 't':
9479 tabless = 1;
9480 break;
9481 case 'n':
9482 optn = 1;
9483 break;
9484 case 'e':
9485 opte = 1;
9486 break;
9487 default:
9488 usage();
9489 /* NOTREACHED */
9492 argc -= optind;
9493 argv += optind;
9495 RB_INIT(&hl);
9496 RB_INIT(&js_wl);
9497 RB_INIT(&downloads);
9499 TAILQ_INIT(&tabs);
9500 TAILQ_INIT(&mtl);
9501 TAILQ_INIT(&aliases);
9502 TAILQ_INIT(&undos);
9503 TAILQ_INIT(&kbl);
9505 init_keybindings();
9507 gnutls_global_init();
9509 /* generate session keys for xtp pages */
9510 generate_xtp_session_key(&dl_session_key);
9511 generate_xtp_session_key(&hl_session_key);
9512 generate_xtp_session_key(&cl_session_key);
9513 generate_xtp_session_key(&fl_session_key);
9515 /* prepare gtk */
9516 if (!g_thread_supported()) {
9517 g_thread_init(NULL);
9518 gdk_threads_init();
9519 gdk_threads_enter();
9521 gtk_init(&argc, &argv);
9523 /* signals */
9524 bzero(&sact, sizeof(sact));
9525 sigemptyset(&sact.sa_mask);
9526 sact.sa_handler = sigchild;
9527 sact.sa_flags = SA_NOCLDSTOP;
9528 sigaction(SIGCHLD, &sact, NULL);
9530 /* set download dir */
9531 pwd = getpwuid(getuid());
9532 if (pwd == NULL)
9533 errx(1, "invalid user %d", getuid());
9534 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9536 /* compile buffer command regexes */
9537 buffercmd_init();
9539 /* set default string settings */
9540 home = g_strdup("https://www.cyphertite.com");
9541 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9542 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9543 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9544 cmd_font_name = g_strdup("monospace normal 9");
9545 oops_font_name = g_strdup("monospace normal 9");
9546 statusbar_font_name = g_strdup("monospace normal 9");
9547 tabbar_font_name = g_strdup("monospace normal 9");
9548 statusbar_elems = g_strdup("BP");
9550 /* read config file */
9551 if (strlen(conf) == 0)
9552 snprintf(conf, sizeof conf, "%s/.%s",
9553 pwd->pw_dir, XT_CONF_FILE);
9554 config_parse(conf, 0);
9556 /* init fonts */
9557 cmd_font = pango_font_description_from_string(cmd_font_name);
9558 oops_font = pango_font_description_from_string(oops_font_name);
9559 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9560 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9562 /* working directory */
9563 if (strlen(work_dir) == 0)
9564 snprintf(work_dir, sizeof work_dir, "%s/%s",
9565 pwd->pw_dir, XT_DIR);
9566 xxx_dir(work_dir);
9568 /* icon cache dir */
9569 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9570 xxx_dir(cache_dir);
9572 /* certs dir */
9573 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9574 xxx_dir(certs_dir);
9576 /* sessions dir */
9577 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9578 work_dir, XT_SESSIONS_DIR);
9579 xxx_dir(sessions_dir);
9581 /* runtime settings that can override config file */
9582 if (runtime_settings[0] != '\0')
9583 config_parse(runtime_settings, 1);
9585 /* download dir */
9586 if (!strcmp(download_dir, pwd->pw_dir))
9587 strlcat(download_dir, "/downloads", sizeof download_dir);
9588 xxx_dir(download_dir);
9590 /* favorites file */
9591 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9592 if (stat(file, &sb)) {
9593 warnx("favorites file doesn't exist, creating it");
9594 if ((f = fopen(file, "w")) == NULL)
9595 err(1, "favorites");
9596 fclose(f);
9599 /* quickmarks file */
9600 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9601 if (stat(file, &sb)) {
9602 warnx("quickmarks file doesn't exist, creating it");
9603 if ((f = fopen(file, "w")) == NULL)
9604 err(1, "quickmarks");
9605 fclose(f);
9608 /* cookies */
9609 session = webkit_get_default_session();
9610 setup_cookies();
9612 /* certs */
9613 if (ssl_ca_file) {
9614 if (stat(ssl_ca_file, &sb)) {
9615 warnx("no CA file: %s", ssl_ca_file);
9616 g_free(ssl_ca_file);
9617 ssl_ca_file = NULL;
9618 } else
9619 g_object_set(session,
9620 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9621 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9622 (void *)NULL);
9625 /* proxy */
9626 env_proxy = getenv("http_proxy");
9627 if (env_proxy)
9628 setup_proxy(env_proxy);
9629 else
9630 setup_proxy(http_proxy);
9632 if (opte) {
9633 send_cmd_to_socket(argv[0]);
9634 exit(0);
9637 /* set some connection parameters */
9638 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9639 g_object_set(session, "max-conns-per-host", max_host_connections,
9640 (char *)NULL);
9642 /* see if there is already an xxxterm running */
9643 if (single_instance && is_running()) {
9644 optn = 1;
9645 warnx("already running");
9648 if (optn) {
9649 while (argc) {
9650 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9651 send_cmd_to_socket(cmd);
9652 if (cmd)
9653 g_free(cmd);
9655 argc--;
9656 argv++;
9658 exit(0);
9661 /* uri completion */
9662 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9664 /* buffers */
9665 buffers_store = gtk_list_store_new
9666 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9668 qmarks_load();
9670 /* go graphical */
9671 create_canvas();
9672 notebook_tab_set_visibility();
9674 if (save_global_history)
9675 restore_global_history();
9677 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9678 restore_saved_tabs();
9679 else {
9680 a.s = named_session;
9681 a.i = XT_SES_DONOTHING;
9682 open_tabs(NULL, &a);
9685 while (argc) {
9686 create_new_tab(argv[0], NULL, focus, -1);
9687 focus = 0;
9689 argc--;
9690 argv++;
9693 if (TAILQ_EMPTY(&tabs))
9694 create_new_tab(home, NULL, 1, -1);
9696 if (enable_socket)
9697 if ((s = build_socket()) != -1) {
9698 channel = g_io_channel_unix_new(s);
9699 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9702 gtk_main();
9704 if (!g_thread_supported()) {
9705 gdk_threads_leave();
9708 gnutls_global_deinit();
9710 return (0);