this webkit bug has been fixed
[xxxterm.git] / xxxterm.c
blob04a319c10fb2a104b040e96272550523093138fe
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 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
32 #include <ctype.h>
33 #include <dlfcn.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <libgen.h>
37 #include <pthread.h>
38 #include <pwd.h>
39 #include <regex.h>
40 #include <signal.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #if defined(__linux__)
49 #include "linux/util.h"
50 #include "linux/tree.h"
51 #elif defined(__FreeBSD__)
52 #include <libutil.h>
53 #include "freebsd/util.h"
54 #include <sys/tree.h>
55 #else /* OpenBSD */
56 #include <util.h>
57 #include <sys/tree.h>
58 #endif
59 #include <sys/queue.h>
60 #include <sys/resource.h>
61 #include <sys/socket.h>
62 #include <sys/stat.h>
63 #include <sys/time.h>
64 #include <sys/un.h>
66 #include <gtk/gtk.h>
67 #include <gdk/gdkkeysyms.h>
69 #if GTK_CHECK_VERSION(3,0,0)
70 /* we still use GDK_* instead of GDK_KEY_* */
71 #include <gdk/gdkkeysyms-compat.h>
72 #endif
74 #include <webkit/webkit.h>
75 #include <libsoup/soup.h>
76 #include <gnutls/gnutls.h>
77 #include <JavaScriptCore/JavaScript.h>
78 #include <gnutls/x509.h>
80 #include "javascript.h"
83 javascript.h borrowed from vimprobable2 under the following license:
85 Copyright (c) 2009 Leon Winter
86 Copyright (c) 2009 Hannes Schueller
87 Copyright (c) 2009 Matto Fransen
89 Permission is hereby granted, free of charge, to any person obtaining a copy
90 of this software and associated documentation files (the "Software"), to deal
91 in the Software without restriction, including without limitation the rights
92 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
93 copies of the Software, and to permit persons to whom the Software is
94 furnished to do so, subject to the following conditions:
96 The above copyright notice and this permission notice shall be included in
97 all copies or substantial portions of the Software.
99 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
100 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
101 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
102 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
103 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
104 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
105 THE SOFTWARE.
108 static char *version = "$xxxterm$";
110 /* hooked functions */
111 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
112 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
113 SoupCookie *);
115 /*#define XT_DEBUG*/
116 #ifdef XT_DEBUG
117 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
118 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
119 #define XT_D_MOVE 0x0001
120 #define XT_D_KEY 0x0002
121 #define XT_D_TAB 0x0004
122 #define XT_D_URL 0x0008
123 #define XT_D_CMD 0x0010
124 #define XT_D_NAV 0x0020
125 #define XT_D_DOWNLOAD 0x0040
126 #define XT_D_CONFIG 0x0080
127 #define XT_D_JS 0x0100
128 #define XT_D_FAVORITE 0x0200
129 #define XT_D_PRINTING 0x0400
130 #define XT_D_COOKIE 0x0800
131 #define XT_D_KEYBINDING 0x1000
132 #define XT_D_CLIP 0x2000
133 #define XT_D_BUFFERCMD 0x4000
134 u_int32_t swm_debug = 0
135 | XT_D_MOVE
136 | XT_D_KEY
137 | XT_D_TAB
138 | XT_D_URL
139 | XT_D_CMD
140 | XT_D_NAV
141 | XT_D_DOWNLOAD
142 | XT_D_CONFIG
143 | XT_D_JS
144 | XT_D_FAVORITE
145 | XT_D_PRINTING
146 | XT_D_COOKIE
147 | XT_D_KEYBINDING
148 | XT_D_CLIP
149 | XT_D_BUFFERCMD
151 #else
152 #define DPRINTF(x...)
153 #define DNPRINTF(n,x...)
154 #endif
156 #define LENGTH(x) (sizeof x / sizeof x[0])
157 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
158 ~(GDK_BUTTON1_MASK) & \
159 ~(GDK_BUTTON2_MASK) & \
160 ~(GDK_BUTTON3_MASK) & \
161 ~(GDK_BUTTON4_MASK) & \
162 ~(GDK_BUTTON5_MASK))
164 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
166 char *icons[] = {
167 "xxxtermicon16.png",
168 "xxxtermicon32.png",
169 "xxxtermicon48.png",
170 "xxxtermicon64.png",
171 "xxxtermicon128.png"
174 struct tab {
175 TAILQ_ENTRY(tab) entry;
176 GtkWidget *vbox;
177 GtkWidget *tab_content;
178 struct {
179 GtkWidget *label;
180 GtkWidget *eventbox;
181 GtkWidget *box;
182 GtkWidget *sep;
183 } tab_elems;
184 GtkWidget *label;
185 GtkWidget *spinner;
186 GtkWidget *uri_entry;
187 GtkWidget *search_entry;
188 GtkWidget *toolbar;
189 GtkWidget *browser_win;
190 GtkWidget *statusbar_box;
191 struct {
192 GtkWidget *statusbar;
193 GtkWidget *buffercmd;
194 GtkWidget *zoom;
195 GtkWidget *position;
196 } sbe;
197 GtkWidget *cmd;
198 GtkWidget *buffers;
199 GtkWidget *oops;
200 GtkWidget *backward;
201 GtkWidget *forward;
202 GtkWidget *stop;
203 GtkWidget *gohome;
204 GtkWidget *js_toggle;
205 GtkEntryCompletion *completion;
206 guint tab_id;
207 WebKitWebView *wv;
209 WebKitWebHistoryItem *item;
210 WebKitWebBackForwardList *bfl;
212 /* favicon */
213 WebKitNetworkRequest *icon_request;
214 WebKitDownload *icon_download;
215 gchar *icon_dest_uri;
217 /* adjustments for browser */
218 GtkScrollbar *sb_h;
219 GtkScrollbar *sb_v;
220 GtkAdjustment *adjust_h;
221 GtkAdjustment *adjust_v;
223 /* flags */
224 int focus_wv;
225 int ctrl_click;
226 gchar *status;
227 int xtp_meaning; /* identifies dls/favorites */
228 gchar *tmp_uri;
230 /* hints */
231 int hints_on;
232 int hint_mode;
233 #define XT_HINT_NONE (0)
234 #define XT_HINT_NUMERICAL (1)
235 #define XT_HINT_ALPHANUM (2)
236 char hint_buf[128];
237 char hint_num[128];
239 /* custom stylesheet */
240 int styled;
241 char *stylesheet;
243 /* search */
244 char *search_text;
245 int search_forward;
247 /* settings */
248 WebKitWebSettings *settings;
249 gchar *user_agent;
251 /* marks */
252 double mark[XT_NOMARKS];
254 TAILQ_HEAD(tab_list, tab);
256 struct history {
257 RB_ENTRY(history) entry;
258 const gchar *uri;
259 const gchar *title;
261 RB_HEAD(history_list, history);
263 struct download {
264 RB_ENTRY(download) entry;
265 int id;
266 WebKitDownload *download;
267 struct tab *tab;
269 RB_HEAD(download_list, download);
271 struct domain {
272 RB_ENTRY(domain) entry;
273 gchar *d;
274 int handy; /* app use */
276 RB_HEAD(domain_list, domain);
278 struct undo {
279 TAILQ_ENTRY(undo) entry;
280 gchar *uri;
281 GList *history;
282 int back; /* Keeps track of how many back
283 * history items there are. */
285 TAILQ_HEAD(undo_tailq, undo);
287 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
288 int next_download_id = 1;
290 struct karg {
291 int i;
292 char *s;
293 int p;
296 /* defines */
297 #define XT_NAME ("XXXTerm")
298 #define XT_DIR (".xxxterm")
299 #define XT_CACHE_DIR ("cache")
300 #define XT_CERT_DIR ("certs/")
301 #define XT_SESSIONS_DIR ("sessions/")
302 #define XT_CONF_FILE ("xxxterm.conf")
303 #define XT_FAVS_FILE ("favorites")
304 #define XT_QMARKS_FILE ("quickmarks")
305 #define XT_SAVED_TABS_FILE ("main_session")
306 #define XT_RESTART_TABS_FILE ("restart_tabs")
307 #define XT_SOCKET_FILE ("socket")
308 #define XT_HISTORY_FILE ("history")
309 #define XT_REJECT_FILE ("rejected.txt")
310 #define XT_COOKIE_FILE ("cookies.txt")
311 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
312 #define XT_CB_HANDLED (TRUE)
313 #define XT_CB_PASSTHROUGH (FALSE)
314 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
315 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
316 #define XT_DLMAN_REFRESH "10"
317 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
318 "td{overflow: hidden;" \
319 " padding: 2px 2px 2px 2px;" \
320 " border: 1px solid black;" \
321 " vertical-align:top;" \
322 " word-wrap: break-word}\n" \
323 "tr:hover{background: #ffff99}\n" \
324 "th{background-color: #cccccc;" \
325 " border: 1px solid black}\n" \
326 "table{width: 100%%;" \
327 " border: 1px black solid;" \
328 " border-collapse:collapse}\n" \
329 ".progress-outer{" \
330 "border: 1px solid black;" \
331 " height: 8px;" \
332 " width: 90%%}\n" \
333 ".progress-inner{float: left;" \
334 " height: 8px;" \
335 " background: green}\n" \
336 ".dlstatus{font-size: small;" \
337 " text-align: center}\n" \
338 "</style>\n"
339 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
340 #define XT_MAX_UNDO_CLOSE_TAB (32)
341 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
342 #define XT_PRINT_EXTRA_MARGIN 10
343 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
345 /* colors */
346 #define XT_COLOR_RED "#cc0000"
347 #define XT_COLOR_YELLOW "#ffff66"
348 #define XT_COLOR_BLUE "lightblue"
349 #define XT_COLOR_GREEN "#99ff66"
350 #define XT_COLOR_WHITE "white"
351 #define XT_COLOR_BLACK "black"
353 #define XT_COLOR_CT_BACKGROUND "#000000"
354 #define XT_COLOR_CT_INACTIVE "#dddddd"
355 #define XT_COLOR_CT_ACTIVE "#bbbb00"
356 #define XT_COLOR_CT_SEPARATOR "#555555"
358 #define XT_COLOR_SB_SEPARATOR "#555555"
360 #define XT_PROTO_DELIM "://"
363 * xxxterm "protocol" (xtp)
364 * We use this for managing stuff like downloads and favorites. They
365 * make magical HTML pages in memory which have xxxt:// links in order
366 * to communicate with xxxterm's internals. These links take the format:
367 * xxxt://class/session_key/action/arg
369 * Don't begin xtp class/actions as 0. atoi returns that on error.
371 * Typically we have not put addition of items in this framework, as
372 * adding items is either done via an ex-command or via a keybinding instead.
375 #define XT_XTP_STR "xxxt://"
377 /* XTP classes (xxxt://<class>) */
378 #define XT_XTP_INVALID 0 /* invalid */
379 #define XT_XTP_DL 1 /* downloads */
380 #define XT_XTP_HL 2 /* history */
381 #define XT_XTP_CL 3 /* cookies */
382 #define XT_XTP_FL 4 /* favorites */
384 /* XTP download actions */
385 #define XT_XTP_DL_LIST 1
386 #define XT_XTP_DL_CANCEL 2
387 #define XT_XTP_DL_REMOVE 3
389 /* XTP history actions */
390 #define XT_XTP_HL_LIST 1
391 #define XT_XTP_HL_REMOVE 2
393 /* XTP cookie actions */
394 #define XT_XTP_CL_LIST 1
395 #define XT_XTP_CL_REMOVE 2
397 /* XTP cookie actions */
398 #define XT_XTP_FL_LIST 1
399 #define XT_XTP_FL_REMOVE 2
401 /* actions */
402 #define XT_MOVE_INVALID (0)
403 #define XT_MOVE_DOWN (1)
404 #define XT_MOVE_UP (2)
405 #define XT_MOVE_BOTTOM (3)
406 #define XT_MOVE_TOP (4)
407 #define XT_MOVE_PAGEDOWN (5)
408 #define XT_MOVE_PAGEUP (6)
409 #define XT_MOVE_HALFDOWN (7)
410 #define XT_MOVE_HALFUP (8)
411 #define XT_MOVE_LEFT (9)
412 #define XT_MOVE_FARLEFT (10)
413 #define XT_MOVE_RIGHT (11)
414 #define XT_MOVE_FARRIGHT (12)
415 #define XT_MOVE_PERCENT (13)
417 #define XT_QMARK_SET (0)
418 #define XT_QMARK_OPEN (1)
419 #define XT_QMARK_TAB (2)
421 #define XT_MARK_SET (0)
422 #define XT_MARK_GOTO (1)
424 #define XT_TAB_LAST (-4)
425 #define XT_TAB_FIRST (-3)
426 #define XT_TAB_PREV (-2)
427 #define XT_TAB_NEXT (-1)
428 #define XT_TAB_INVALID (0)
429 #define XT_TAB_NEW (1)
430 #define XT_TAB_DELETE (2)
431 #define XT_TAB_DELQUIT (3)
432 #define XT_TAB_OPEN (4)
433 #define XT_TAB_UNDO_CLOSE (5)
434 #define XT_TAB_SHOW (6)
435 #define XT_TAB_HIDE (7)
436 #define XT_TAB_NEXTSTYLE (8)
438 #define XT_NAV_INVALID (0)
439 #define XT_NAV_BACK (1)
440 #define XT_NAV_FORWARD (2)
441 #define XT_NAV_RELOAD (3)
442 #define XT_NAV_RELOAD_CACHE (4)
444 #define XT_FOCUS_INVALID (0)
445 #define XT_FOCUS_URI (1)
446 #define XT_FOCUS_SEARCH (2)
448 #define XT_SEARCH_INVALID (0)
449 #define XT_SEARCH_NEXT (1)
450 #define XT_SEARCH_PREV (2)
452 #define XT_PASTE_CURRENT_TAB (0)
453 #define XT_PASTE_NEW_TAB (1)
455 #define XT_ZOOM_IN (-1)
456 #define XT_ZOOM_OUT (-2)
457 #define XT_ZOOM_NORMAL (100)
459 #define XT_URL_SHOW (1)
460 #define XT_URL_HIDE (2)
462 #define XT_WL_TOGGLE (1<<0)
463 #define XT_WL_ENABLE (1<<1)
464 #define XT_WL_DISABLE (1<<2)
465 #define XT_WL_FQDN (1<<3) /* default */
466 #define XT_WL_TOPLEVEL (1<<4)
467 #define XT_WL_PERSISTENT (1<<5)
468 #define XT_WL_SESSION (1<<6)
469 #define XT_WL_RELOAD (1<<7)
471 #define XT_SHOW (1<<7)
472 #define XT_DELETE (1<<8)
473 #define XT_SAVE (1<<9)
474 #define XT_OPEN (1<<10)
476 #define XT_CMD_OPEN (0)
477 #define XT_CMD_OPEN_CURRENT (1)
478 #define XT_CMD_TABNEW (2)
479 #define XT_CMD_TABNEW_CURRENT (3)
481 #define XT_STATUS_NOTHING (0)
482 #define XT_STATUS_LINK (1)
483 #define XT_STATUS_URI (2)
484 #define XT_STATUS_LOADING (3)
486 #define XT_SES_DONOTHING (0)
487 #define XT_SES_CLOSETABS (1)
489 #define XT_BM_NORMAL (0)
490 #define XT_BM_WHITELIST (1)
491 #define XT_BM_KIOSK (2)
493 #define XT_PREFIX (1<<0)
494 #define XT_USERARG (1<<1)
495 #define XT_URLARG (1<<2)
496 #define XT_INTARG (1<<3)
498 #define XT_TABS_NORMAL 0
499 #define XT_TABS_COMPACT 1
501 /* mime types */
502 struct mime_type {
503 char *mt_type;
504 char *mt_action;
505 int mt_default;
506 int mt_download;
507 TAILQ_ENTRY(mime_type) entry;
509 TAILQ_HEAD(mime_type_list, mime_type);
511 /* uri aliases */
512 struct alias {
513 char *a_name;
514 char *a_uri;
515 TAILQ_ENTRY(alias) entry;
517 TAILQ_HEAD(alias_list, alias);
519 /* settings that require restart */
520 int tabless = 0; /* allow only 1 tab */
521 int enable_socket = 0;
522 int single_instance = 0; /* only allow one xxxterm to run */
523 int fancy_bar = 1; /* fancy toolbar */
524 int browser_mode = XT_BM_NORMAL;
525 int enable_localstorage = 0;
526 char *statusbar_elems = NULL;
528 /* runtime settings */
529 int show_tabs = 1; /* show tabs on notebook */
530 int tab_style = XT_TABS_NORMAL; /* tab bar style */
531 int show_url = 1; /* show url toolbar on notebook */
532 int show_statusbar = 0; /* vimperator style status bar */
533 int ctrl_click_focus = 0; /* ctrl click gets focus */
534 int cookies_enabled = 1; /* enable cookies */
535 int read_only_cookies = 0; /* enable to not write cookies */
536 int enable_scripts = 1;
537 int enable_plugins = 0;
538 gfloat default_zoom_level = 1.0;
539 char default_script[PATH_MAX];
540 int window_height = 768;
541 int window_width = 1024;
542 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
543 int refresh_interval = 10; /* download refresh interval */
544 int enable_cookie_whitelist = 0;
545 int enable_js_whitelist = 0;
546 int session_timeout = 3600; /* cookie session timeout */
547 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
548 char *ssl_ca_file = NULL;
549 char *resource_dir = NULL;
550 gboolean ssl_strict_certs = FALSE;
551 int append_next = 1; /* append tab after current tab */
552 char *home = NULL;
553 char *search_string = NULL;
554 char *http_proxy = NULL;
555 char download_dir[PATH_MAX];
556 char runtime_settings[PATH_MAX]; /* override of settings */
557 int allow_volatile_cookies = 0;
558 int save_global_history = 0; /* save global history to disk */
559 char *user_agent = NULL;
560 int save_rejected_cookies = 0;
561 int session_autosave = 0;
562 int guess_search = 0;
563 int dns_prefetch = FALSE;
564 gint max_connections = 25;
565 gint max_host_connections = 5;
566 gint enable_spell_checking = 0;
567 char *spell_check_languages = NULL;
569 char *cmd_font_name = NULL;
570 char *oops_font_name = NULL;
571 char *statusbar_font_name = NULL;
572 char *tabbar_font_name = NULL;
573 PangoFontDescription *cmd_font;
574 PangoFontDescription *oops_font;
575 PangoFontDescription *statusbar_font;
576 PangoFontDescription *tabbar_font;
577 char *qmarks[XT_NOMARKS];
579 int btn_down; /* M1 down in any wv */
581 struct settings;
582 struct key_binding;
583 int set_browser_mode(struct settings *, char *);
584 int set_cookie_policy(struct settings *, char *);
585 int set_download_dir(struct settings *, char *);
586 int set_default_script(struct settings *, char *);
587 int set_runtime_dir(struct settings *, char *);
588 int set_tab_style(struct settings *, char *);
589 int set_work_dir(struct settings *, char *);
590 int add_alias(struct settings *, char *);
591 int add_mime_type(struct settings *, char *);
592 int add_cookie_wl(struct settings *, char *);
593 int add_js_wl(struct settings *, char *);
594 int add_kb(struct settings *, char *);
595 void button_set_stockid(GtkWidget *, char *);
596 GtkWidget * create_button(char *, char *, int);
598 char *get_browser_mode(struct settings *);
599 char *get_cookie_policy(struct settings *);
600 char *get_download_dir(struct settings *);
601 char *get_default_script(struct settings *);
602 char *get_runtime_dir(struct settings *);
603 char *get_tab_style(struct settings *);
604 char *get_work_dir(struct settings *);
606 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
607 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
608 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
609 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
610 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
612 void recalc_tabs(void);
613 void recolor_compact_tabs(void);
614 void set_current_tab(int page_num);
615 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
616 void marks_clear(struct tab *t);
618 struct special {
619 int (*set)(struct settings *, char *);
620 char *(*get)(struct settings *);
621 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
624 struct special s_browser_mode = {
625 set_browser_mode,
626 get_browser_mode,
627 NULL
630 struct special s_cookie = {
631 set_cookie_policy,
632 get_cookie_policy,
633 NULL
636 struct special s_alias = {
637 add_alias,
638 NULL,
639 walk_alias
642 struct special s_mime = {
643 add_mime_type,
644 NULL,
645 walk_mime_type
648 struct special s_js = {
649 add_js_wl,
650 NULL,
651 walk_js_wl
654 struct special s_kb = {
655 add_kb,
656 NULL,
657 walk_kb
660 struct special s_cookie_wl = {
661 add_cookie_wl,
662 NULL,
663 walk_cookie_wl
666 struct special s_default_script = {
667 set_default_script,
668 get_default_script,
669 NULL
672 struct special s_download_dir = {
673 set_download_dir,
674 get_download_dir,
675 NULL
678 struct special s_work_dir = {
679 set_work_dir,
680 get_work_dir,
681 NULL
684 struct special s_tab_style = {
685 set_tab_style,
686 get_tab_style,
687 NULL
690 struct settings {
691 char *name;
692 int type;
693 #define XT_S_INVALID (0)
694 #define XT_S_INT (1)
695 #define XT_S_STR (2)
696 #define XT_S_FLOAT (3)
697 uint32_t flags;
698 #define XT_SF_RESTART (1<<0)
699 #define XT_SF_RUNTIME (1<<1)
700 int *ival;
701 char **sval;
702 struct special *s;
703 gfloat *fval;
704 } rs[] = {
705 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
706 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
707 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
708 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
709 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
710 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
711 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
712 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
713 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
714 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
715 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
716 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
717 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
718 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
719 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
720 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
721 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
722 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
723 { "home", XT_S_STR, 0, NULL, &home, NULL },
724 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
725 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
726 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
727 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
728 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
729 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
730 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
731 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
732 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
733 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
734 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
735 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
736 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
737 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
738 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
739 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
740 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
741 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
742 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
743 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
744 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
745 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
746 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
747 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
748 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
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)
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 (len == 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(const gchar *uri, char *domain, size_t domain_sz)
3133 SoupURI *su = NULL;
3134 struct addrinfo hints, *res = NULL, *ai;
3135 int s = -1, on;
3136 char port[8];
3138 if (uri && !g_str_has_prefix(uri, "https://"))
3139 goto done;
3141 su = soup_uri_new(uri);
3142 if (su == NULL)
3143 goto done;
3144 if (!SOUP_URI_VALID_FOR_HTTP(su))
3145 goto done;
3147 snprintf(port, sizeof port, "%d", su->port);
3148 bzero(&hints, sizeof(struct addrinfo));
3149 hints.ai_flags = AI_CANONNAME;
3150 hints.ai_family = AF_UNSPEC;
3151 hints.ai_socktype = SOCK_STREAM;
3153 if (getaddrinfo(su->host, port, &hints, &res))
3154 goto done;
3156 for (ai = res; ai; ai = ai->ai_next) {
3157 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3158 continue;
3160 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3161 if (s < 0)
3162 goto done;
3163 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3164 sizeof(on)) == -1)
3165 goto done;
3167 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
3168 goto done;
3171 if (domain)
3172 strlcpy(domain, su->host, domain_sz);
3173 done:
3174 if (su)
3175 soup_uri_free(su);
3176 if (res)
3177 freeaddrinfo(res);
3179 return (s);
3183 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3185 if (gsession)
3186 gnutls_deinit(gsession);
3187 if (xcred)
3188 gnutls_certificate_free_credentials(xcred);
3190 return (0);
3194 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3195 gnutls_certificate_credentials_t *xc)
3197 gnutls_certificate_credentials_t xcred;
3198 gnutls_session_t gsession;
3199 int rv = 1;
3201 if (gs == NULL || xc == NULL)
3202 goto done;
3204 *gs = NULL;
3205 *xc = NULL;
3207 gnutls_certificate_allocate_credentials(&xcred);
3208 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3209 GNUTLS_X509_FMT_PEM);
3210 gnutls_init(&gsession, GNUTLS_CLIENT);
3211 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3212 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3213 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3214 if ((rv = gnutls_handshake(gsession)) < 0) {
3215 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3217 gnutls_error_is_fatal(rv),
3218 gnutls_strerror_name(rv));
3219 stop_tls(gsession, xcred);
3220 goto done;
3223 gnutls_credentials_type_t cred;
3224 cred = gnutls_auth_get_type(gsession);
3225 if (cred != GNUTLS_CRD_CERTIFICATE) {
3226 stop_tls(gsession, xcred);
3227 goto done;
3230 *gs = gsession;
3231 *xc = xcred;
3232 rv = 0;
3233 done:
3234 return (rv);
3238 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3239 size_t *cert_count)
3241 unsigned int len;
3242 const gnutls_datum_t *cl;
3243 gnutls_x509_crt_t *all_certs;
3244 int i, rv = 1;
3246 if (certs == NULL || cert_count == NULL)
3247 goto done;
3248 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3249 goto done;
3250 cl = gnutls_certificate_get_peers(gsession, &len);
3251 if (len == 0)
3252 goto done;
3254 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3255 for (i = 0; i < len; i++) {
3256 gnutls_x509_crt_init(&all_certs[i]);
3257 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3258 GNUTLS_X509_FMT_PEM < 0)) {
3259 g_free(all_certs);
3260 goto done;
3264 *certs = all_certs;
3265 *cert_count = len;
3266 rv = 0;
3267 done:
3268 return (rv);
3271 void
3272 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3274 int i;
3276 for (i = 0; i < cert_count; i++)
3277 gnutls_x509_crt_deinit(certs[i]);
3278 g_free(certs);
3281 void
3282 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3284 GdkColor c_text, c_base;
3286 gdk_color_parse(text, &c_text);
3287 gdk_color_parse(base, &c_base);
3289 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3290 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3291 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3292 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3294 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3295 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3296 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3297 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3300 void
3301 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3302 size_t cert_count, char *domain)
3304 size_t cert_buf_sz;
3305 char cert_buf[64 * 1024], file[PATH_MAX];
3306 int i;
3307 FILE *f;
3308 GdkColor color;
3310 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3311 return;
3313 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3314 if ((f = fopen(file, "w")) == NULL) {
3315 show_oops(t, "Can't create cert file %s %s",
3316 file, strerror(errno));
3317 return;
3320 for (i = 0; i < cert_count; i++) {
3321 cert_buf_sz = sizeof cert_buf;
3322 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3323 cert_buf, &cert_buf_sz)) {
3324 show_oops(t, "gnutls_x509_crt_export failed");
3325 goto done;
3327 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3328 show_oops(t, "Can't write certs: %s", strerror(errno));
3329 goto done;
3333 /* not the best spot but oh well */
3334 gdk_color_parse("lightblue", &color);
3335 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3336 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3337 done:
3338 fclose(f);
3342 load_compare_cert(struct tab *t, struct karg *args)
3344 const gchar *uri;
3345 char domain[8182], file[PATH_MAX];
3346 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3347 int s = -1, rv = 1, i;
3348 size_t cert_count;
3349 FILE *f = NULL;
3350 size_t cert_buf_sz;
3351 gnutls_session_t gsession;
3352 gnutls_x509_crt_t *certs;
3353 gnutls_certificate_credentials_t xcred;
3355 if (t == NULL)
3356 return (1);
3358 if ((uri = get_uri(t)) == NULL)
3359 return (1);
3361 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3362 return (1);
3364 /* go ssl/tls */
3365 if (start_tls(t, s, &gsession, &xcred)) {
3366 show_oops(t, "Start TLS failed");
3367 goto done;
3370 /* get certs */
3371 if (get_connection_certs(gsession, &certs, &cert_count)) {
3372 show_oops(t, "Can't get connection certificates");
3373 goto done;
3376 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3377 if ((f = fopen(file, "r")) == NULL)
3378 goto freeit;
3380 for (i = 0; i < cert_count; i++) {
3381 cert_buf_sz = sizeof cert_buf;
3382 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3383 cert_buf, &cert_buf_sz)) {
3384 goto freeit;
3386 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3387 rv = -1; /* critical */
3388 goto freeit;
3390 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3391 rv = -1; /* critical */
3392 goto freeit;
3396 rv = 0;
3397 freeit:
3398 if (f)
3399 fclose(f);
3400 free_connection_certs(certs, cert_count);
3401 done:
3402 /* we close the socket first for speed */
3403 if (s != -1)
3404 close(s);
3405 stop_tls(gsession, xcred);
3407 return (rv);
3411 cert_cmd(struct tab *t, struct karg *args)
3413 const gchar *uri;
3414 char domain[8182];
3415 int s = -1;
3416 size_t cert_count;
3417 gnutls_session_t gsession;
3418 gnutls_x509_crt_t *certs;
3419 gnutls_certificate_credentials_t xcred;
3421 if (t == NULL)
3422 return (1);
3424 if (ssl_ca_file == NULL) {
3425 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3426 return (1);
3429 if ((uri = get_uri(t)) == NULL) {
3430 show_oops(t, "Invalid URI");
3431 return (1);
3434 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3435 show_oops(t, "Invalid certificate URI: %s", uri);
3436 return (1);
3439 /* go ssl/tls */
3440 if (start_tls(t, s, &gsession, &xcred)) {
3441 show_oops(t, "Start TLS failed");
3442 goto done;
3445 /* get certs */
3446 if (get_connection_certs(gsession, &certs, &cert_count)) {
3447 show_oops(t, "get_connection_certs failed");
3448 goto done;
3451 if (args->i & XT_SHOW)
3452 show_certs(t, certs, cert_count, "Certificate Chain");
3453 else if (args->i & XT_SAVE)
3454 save_certs(t, certs, cert_count, domain);
3456 free_connection_certs(certs, cert_count);
3457 done:
3458 /* we close the socket first for speed */
3459 if (s != -1)
3460 close(s);
3461 stop_tls(gsession, xcred);
3463 return (0);
3467 remove_cookie(int index)
3469 int i, rv = 1;
3470 GSList *cf;
3471 SoupCookie *c;
3473 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3475 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3477 for (i = 1; cf; cf = cf->next, i++) {
3478 if (i != index)
3479 continue;
3480 c = cf->data;
3481 print_cookie("remove cookie", c);
3482 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3483 rv = 0;
3484 break;
3487 soup_cookies_free(cf);
3489 return (rv);
3493 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3495 struct domain *d;
3496 char *tmp, *body;
3498 body = g_strdup("");
3500 /* p list */
3501 if (args->i & XT_WL_PERSISTENT) {
3502 tmp = body;
3503 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3504 g_free(tmp);
3505 RB_FOREACH(d, domain_list, wl) {
3506 if (d->handy == 0)
3507 continue;
3508 tmp = body;
3509 body = g_strdup_printf("%s%s<br/>", body, d->d);
3510 g_free(tmp);
3514 /* s list */
3515 if (args->i & XT_WL_SESSION) {
3516 tmp = body;
3517 body = g_strdup_printf("%s<h2>Session</h2>", body);
3518 g_free(tmp);
3519 RB_FOREACH(d, domain_list, wl) {
3520 if (d->handy == 1)
3521 continue;
3522 tmp = body;
3523 body = g_strdup_printf("%s%s<br/>", body, d->d);
3524 g_free(tmp);
3528 tmp = get_html_page(title, body, "", 0);
3529 g_free(body);
3530 if (wl == &js_wl)
3531 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3532 else
3533 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3534 g_free(tmp);
3535 return (0);
3539 wl_save(struct tab *t, struct karg *args, int js)
3541 char file[PATH_MAX];
3542 FILE *f;
3543 char *line = NULL, *lt = NULL, *dom = NULL;
3544 size_t linelen;
3545 const gchar *uri;
3546 struct karg a;
3547 struct domain *d;
3548 GSList *cf;
3549 SoupCookie *ci, *c;
3551 if (t == NULL || args == NULL)
3552 return (1);
3554 if (runtime_settings[0] == '\0')
3555 return (1);
3557 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3558 if ((f = fopen(file, "r+")) == NULL)
3559 return (1);
3561 uri = get_uri(t);
3562 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3563 if (uri == NULL || dom == NULL ||
3564 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3565 show_oops(t, "Can't add domain to %s white list",
3566 js ? "JavaScript" : "cookie");
3567 goto done;
3570 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3572 while (!feof(f)) {
3573 line = fparseln(f, &linelen, NULL, NULL, 0);
3574 if (line == NULL)
3575 continue;
3576 if (!strcmp(line, lt))
3577 goto done;
3578 free(line);
3579 line = NULL;
3582 fprintf(f, "%s\n", lt);
3584 a.i = XT_WL_ENABLE;
3585 a.i |= args->i;
3586 if (js) {
3587 d = wl_find(dom, &js_wl);
3588 if (!d) {
3589 settings_add("js_wl", dom);
3590 d = wl_find(dom, &js_wl);
3592 toggle_js(t, &a);
3593 } else {
3594 d = wl_find(dom, &c_wl);
3595 if (!d) {
3596 settings_add("cookie_wl", dom);
3597 d = wl_find(dom, &c_wl);
3599 toggle_cwl(t, &a);
3601 /* find and add to persistent jar */
3602 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3603 for (;cf; cf = cf->next) {
3604 ci = cf->data;
3605 if (!strcmp(dom, ci->domain) ||
3606 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3607 c = soup_cookie_copy(ci);
3608 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3611 soup_cookies_free(cf);
3613 if (d)
3614 d->handy = 1;
3616 done:
3617 if (line)
3618 free(line);
3619 if (dom)
3620 g_free(dom);
3621 if (lt)
3622 g_free(lt);
3623 fclose(f);
3625 return (0);
3629 js_show_wl(struct tab *t, struct karg *args)
3631 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3632 wl_show(t, args, "JavaScript White List", &js_wl);
3634 return (0);
3638 cookie_show_wl(struct tab *t, struct karg *args)
3640 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3641 wl_show(t, args, "Cookie White List", &c_wl);
3643 return (0);
3647 cookie_cmd(struct tab *t, struct karg *args)
3649 if (args->i & XT_SHOW)
3650 wl_show(t, args, "Cookie White List", &c_wl);
3651 else if (args->i & XT_WL_TOGGLE) {
3652 args->i |= XT_WL_RELOAD;
3653 toggle_cwl(t, args);
3654 } else if (args->i & XT_SAVE) {
3655 args->i |= XT_WL_RELOAD;
3656 wl_save(t, args, 0);
3657 } else if (args->i & XT_DELETE)
3658 show_oops(t, "'cookie delete' currently unimplemented");
3660 return (0);
3664 js_cmd(struct tab *t, struct karg *args)
3666 if (args->i & XT_SHOW)
3667 wl_show(t, args, "JavaScript White List", &js_wl);
3668 else if (args->i & XT_SAVE) {
3669 args->i |= XT_WL_RELOAD;
3670 wl_save(t, args, 1);
3671 } else if (args->i & XT_WL_TOGGLE) {
3672 args->i |= XT_WL_RELOAD;
3673 toggle_js(t, args);
3674 } else if (args->i & XT_DELETE)
3675 show_oops(t, "'js delete' currently unimplemented");
3677 return (0);
3681 toplevel_cmd(struct tab *t, struct karg *args)
3683 js_toggle_cb(t->js_toggle, t);
3685 return (0);
3689 add_favorite(struct tab *t, struct karg *args)
3691 char file[PATH_MAX];
3692 FILE *f;
3693 char *line = NULL;
3694 size_t urilen, linelen;
3695 const gchar *uri, *title;
3697 if (t == NULL)
3698 return (1);
3700 /* don't allow adding of xtp pages to favorites */
3701 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3702 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3703 return (1);
3706 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3707 if ((f = fopen(file, "r+")) == NULL) {
3708 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3709 return (1);
3712 title = get_title(t, FALSE);
3713 uri = get_uri(t);
3715 if (title == NULL || uri == NULL) {
3716 show_oops(t, "can't add page to favorites");
3717 goto done;
3720 urilen = strlen(uri);
3722 for (;;) {
3723 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3724 if (feof(f) || ferror(f))
3725 break;
3727 if (linelen == urilen && !strcmp(line, uri))
3728 goto done;
3730 free(line);
3731 line = NULL;
3734 fprintf(f, "\n%s\n%s", title, uri);
3735 done:
3736 if (line)
3737 free(line);
3738 fclose(f);
3740 update_favorite_tabs(NULL);
3742 return (0);
3746 navaction(struct tab *t, struct karg *args)
3748 WebKitWebHistoryItem *item;
3750 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3751 t->tab_id, args->i);
3753 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3755 if (t->item) {
3756 if (args->i == XT_NAV_BACK)
3757 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3758 else
3759 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3760 if (item == NULL)
3761 return (XT_CB_PASSTHROUGH);
3762 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3763 t->item = NULL;
3764 return (XT_CB_PASSTHROUGH);
3767 switch (args->i) {
3768 case XT_NAV_BACK:
3769 marks_clear(t);
3770 webkit_web_view_go_back(t->wv);
3771 break;
3772 case XT_NAV_FORWARD:
3773 marks_clear(t);
3774 webkit_web_view_go_forward(t->wv);
3775 break;
3776 case XT_NAV_RELOAD:
3777 webkit_web_view_reload(t->wv);
3778 break;
3779 case XT_NAV_RELOAD_CACHE:
3780 webkit_web_view_reload_bypass_cache(t->wv);
3781 break;
3783 return (XT_CB_PASSTHROUGH);
3787 move(struct tab *t, struct karg *args)
3789 GtkAdjustment *adjust;
3790 double pi, si, pos, ps, upper, lower, max;
3792 switch (args->i) {
3793 case XT_MOVE_DOWN:
3794 case XT_MOVE_UP:
3795 case XT_MOVE_BOTTOM:
3796 case XT_MOVE_TOP:
3797 case XT_MOVE_PAGEDOWN:
3798 case XT_MOVE_PAGEUP:
3799 case XT_MOVE_HALFDOWN:
3800 case XT_MOVE_HALFUP:
3801 case XT_MOVE_PERCENT:
3802 adjust = t->adjust_v;
3803 break;
3804 default:
3805 adjust = t->adjust_h;
3806 break;
3809 pos = gtk_adjustment_get_value(adjust);
3810 ps = gtk_adjustment_get_page_size(adjust);
3811 upper = gtk_adjustment_get_upper(adjust);
3812 lower = gtk_adjustment_get_lower(adjust);
3813 si = gtk_adjustment_get_step_increment(adjust);
3814 pi = gtk_adjustment_get_page_increment(adjust);
3815 max = upper - ps;
3817 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3818 "max %f si %f pi %f\n",
3819 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3820 pos, ps, upper, lower, max, si, pi);
3822 switch (args->i) {
3823 case XT_MOVE_DOWN:
3824 case XT_MOVE_RIGHT:
3825 pos += si;
3826 gtk_adjustment_set_value(adjust, MIN(pos, max));
3827 break;
3828 case XT_MOVE_UP:
3829 case XT_MOVE_LEFT:
3830 pos -= si;
3831 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3832 break;
3833 case XT_MOVE_BOTTOM:
3834 case XT_MOVE_FARRIGHT:
3835 gtk_adjustment_set_value(adjust, max);
3836 break;
3837 case XT_MOVE_TOP:
3838 case XT_MOVE_FARLEFT:
3839 gtk_adjustment_set_value(adjust, lower);
3840 break;
3841 case XT_MOVE_PAGEDOWN:
3842 pos += pi;
3843 gtk_adjustment_set_value(adjust, MIN(pos, max));
3844 break;
3845 case XT_MOVE_PAGEUP:
3846 pos -= pi;
3847 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3848 break;
3849 case XT_MOVE_HALFDOWN:
3850 pos += pi / 2;
3851 gtk_adjustment_set_value(adjust, MIN(pos, max));
3852 break;
3853 case XT_MOVE_HALFUP:
3854 pos -= pi / 2;
3855 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3856 break;
3857 case XT_MOVE_PERCENT:
3859 double percent;
3861 percent = atoi(args->s) / 100.0;
3862 pos = max * percent;
3863 if (pos < 0.0 || pos > max)
3864 break;
3866 gtk_adjustment_set_value(adjust, pos);
3867 break;
3869 default:
3870 return (XT_CB_PASSTHROUGH);
3873 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3875 return (XT_CB_HANDLED);
3878 void
3879 url_set_visibility(void)
3881 struct tab *t;
3883 TAILQ_FOREACH(t, &tabs, entry)
3884 if (show_url == 0) {
3885 gtk_widget_hide(t->toolbar);
3886 focus_webview(t);
3887 } else
3888 gtk_widget_show(t->toolbar);
3891 void
3892 notebook_tab_set_visibility()
3894 if (show_tabs == 0) {
3895 gtk_widget_hide(tab_bar);
3896 gtk_notebook_set_show_tabs(notebook, FALSE);
3897 } else {
3898 if (tab_style == XT_TABS_NORMAL) {
3899 gtk_widget_hide(tab_bar);
3900 gtk_notebook_set_show_tabs(notebook, TRUE);
3901 } else if (tab_style == XT_TABS_COMPACT) {
3902 gtk_widget_show(tab_bar);
3903 gtk_notebook_set_show_tabs(notebook, FALSE);
3908 void
3909 statusbar_set_visibility(void)
3911 struct tab *t;
3913 TAILQ_FOREACH(t, &tabs, entry)
3914 if (show_statusbar == 0) {
3915 gtk_widget_hide(t->statusbar_box);
3916 focus_webview(t);
3917 } else
3918 gtk_widget_show(t->statusbar_box);
3921 void
3922 url_set(struct tab *t, int enable_url_entry)
3924 GdkPixbuf *pixbuf;
3925 int progress;
3927 show_url = enable_url_entry;
3929 if (enable_url_entry) {
3930 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
3931 GTK_ENTRY_ICON_PRIMARY, NULL);
3932 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
3933 } else {
3934 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3935 GTK_ENTRY_ICON_PRIMARY);
3936 progress =
3937 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3938 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
3939 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3940 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
3941 progress);
3946 fullscreen(struct tab *t, struct karg *args)
3948 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3950 if (t == NULL)
3951 return (XT_CB_PASSTHROUGH);
3953 if (show_url == 0) {
3954 url_set(t, 1);
3955 show_tabs = 1;
3956 } else {
3957 url_set(t, 0);
3958 show_tabs = 0;
3961 url_set_visibility();
3962 notebook_tab_set_visibility();
3964 return (XT_CB_HANDLED);
3968 statustoggle(struct tab *t, struct karg *args)
3970 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3972 if (show_statusbar == 1) {
3973 show_statusbar = 0;
3974 statusbar_set_visibility();
3975 } else if (show_statusbar == 0) {
3976 show_statusbar = 1;
3977 statusbar_set_visibility();
3979 return (XT_CB_HANDLED);
3983 urlaction(struct tab *t, struct karg *args)
3985 int rv = XT_CB_HANDLED;
3987 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3989 if (t == NULL)
3990 return (XT_CB_PASSTHROUGH);
3992 switch (args->i) {
3993 case XT_URL_SHOW:
3994 if (show_url == 0) {
3995 url_set(t, 1);
3996 url_set_visibility();
3998 break;
3999 case XT_URL_HIDE:
4000 if (show_url == 1) {
4001 url_set(t, 0);
4002 url_set_visibility();
4004 break;
4006 return (rv);
4010 tabaction(struct tab *t, struct karg *args)
4012 int rv = XT_CB_HANDLED;
4013 char *url = args->s;
4014 struct undo *u;
4015 struct tab *tt;
4017 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4019 if (t == NULL)
4020 return (XT_CB_PASSTHROUGH);
4022 switch (args->i) {
4023 case XT_TAB_NEW:
4024 if (strlen(url) > 0)
4025 create_new_tab(url, NULL, 1, args->p);
4026 else
4027 create_new_tab(NULL, NULL, 1, args->p);
4028 break;
4029 case XT_TAB_DELETE:
4030 if (args->p < 0)
4031 delete_tab(t);
4032 else
4033 TAILQ_FOREACH(tt, &tabs, entry)
4034 if (tt->tab_id == args->p - 1) {
4035 delete_tab(tt);
4036 break;
4038 break;
4039 case XT_TAB_DELQUIT:
4040 if (gtk_notebook_get_n_pages(notebook) > 1)
4041 delete_tab(t);
4042 else
4043 quit(t, args);
4044 break;
4045 case XT_TAB_OPEN:
4046 if (strlen(url) > 0)
4048 else {
4049 rv = XT_CB_PASSTHROUGH;
4050 goto done;
4052 load_uri(t, url);
4053 break;
4054 case XT_TAB_SHOW:
4055 if (show_tabs == 0) {
4056 show_tabs = 1;
4057 notebook_tab_set_visibility();
4059 break;
4060 case XT_TAB_HIDE:
4061 if (show_tabs == 1) {
4062 show_tabs = 0;
4063 notebook_tab_set_visibility();
4065 break;
4066 case XT_TAB_NEXTSTYLE:
4067 if (tab_style == XT_TABS_NORMAL) {
4068 tab_style = XT_TABS_COMPACT;
4069 recolor_compact_tabs();
4071 else
4072 tab_style = XT_TABS_NORMAL;
4073 notebook_tab_set_visibility();
4074 break;
4075 case XT_TAB_UNDO_CLOSE:
4076 if (undo_count == 0) {
4077 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4078 __func__);
4079 goto done;
4080 } else {
4081 undo_count--;
4082 u = TAILQ_FIRST(&undos);
4083 create_new_tab(u->uri, u, 1, -1);
4085 TAILQ_REMOVE(&undos, u, entry);
4086 g_free(u->uri);
4087 /* u->history is freed in create_new_tab() */
4088 g_free(u);
4090 break;
4091 default:
4092 rv = XT_CB_PASSTHROUGH;
4093 goto done;
4096 done:
4097 if (args->s) {
4098 g_free(args->s);
4099 args->s = NULL;
4102 return (rv);
4106 resizetab(struct tab *t, struct karg *args)
4108 if (t == NULL || args == NULL) {
4109 show_oops(NULL, "resizetab invalid parameters");
4110 return (XT_CB_PASSTHROUGH);
4113 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4114 t->tab_id, args->i);
4116 setzoom_webkit(t, args->i);
4118 return (XT_CB_HANDLED);
4122 movetab(struct tab *t, struct karg *args)
4124 int n, dest;
4126 if (t == NULL || args == NULL) {
4127 show_oops(NULL, "movetab invalid parameters");
4128 return (XT_CB_PASSTHROUGH);
4131 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4132 t->tab_id, args->i);
4134 if (args->i >= XT_TAB_INVALID)
4135 return (XT_CB_PASSTHROUGH);
4137 if (TAILQ_EMPTY(&tabs))
4138 return (XT_CB_PASSTHROUGH);
4140 n = gtk_notebook_get_n_pages(notebook);
4141 dest = gtk_notebook_get_current_page(notebook);
4143 switch (args->i) {
4144 case XT_TAB_NEXT:
4145 if (args->p < 0)
4146 dest = dest == n - 1 ? 0 : dest + 1;
4147 else
4148 dest = args->p - 1;
4150 break;
4151 case XT_TAB_PREV:
4152 if (args->p < 0)
4153 dest -= 1;
4154 else
4155 dest -= args->p % n;
4157 if (dest < 0)
4158 dest += n;
4160 break;
4161 case XT_TAB_FIRST:
4162 dest = 0;
4163 break;
4164 case XT_TAB_LAST:
4165 dest = n - 1;
4166 break;
4167 default:
4168 return (XT_CB_PASSTHROUGH);
4171 if (dest < 0 || dest >= n)
4172 return (XT_CB_PASSTHROUGH);
4173 if (t->tab_id == dest) {
4174 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4175 return (XT_CB_HANDLED);
4178 set_current_tab(dest);
4180 return (XT_CB_HANDLED);
4183 int cmd_prefix = 0;
4186 command(struct tab *t, struct karg *args)
4188 char *s = NULL, *ss = NULL;
4189 GdkColor color;
4190 const gchar *uri;
4192 if (t == NULL || args == NULL) {
4193 show_oops(NULL, "command invalid parameters");
4194 return (XT_CB_PASSTHROUGH);
4197 switch (args->i) {
4198 case '/':
4199 s = "/";
4200 break;
4201 case '?':
4202 s = "?";
4203 break;
4204 case ':':
4205 if (cmd_prefix == 0)
4206 s = ":";
4207 else {
4208 ss = g_strdup_printf(":%d", cmd_prefix);
4209 s = ss;
4210 cmd_prefix = 0;
4212 break;
4213 case XT_CMD_OPEN:
4214 s = ":open ";
4215 break;
4216 case XT_CMD_TABNEW:
4217 s = ":tabnew ";
4218 break;
4219 case XT_CMD_OPEN_CURRENT:
4220 s = ":open ";
4221 /* FALL THROUGH */
4222 case XT_CMD_TABNEW_CURRENT:
4223 if (!s) /* FALL THROUGH? */
4224 s = ":tabnew ";
4225 if ((uri = get_uri(t)) != NULL) {
4226 ss = g_strdup_printf("%s%s", s, uri);
4227 s = ss;
4229 break;
4230 default:
4231 show_oops(t, "command: invalid opcode %d", args->i);
4232 return (XT_CB_PASSTHROUGH);
4235 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4237 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4238 gdk_color_parse(XT_COLOR_WHITE, &color);
4239 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4240 show_cmd(t);
4241 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4242 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4244 if (ss)
4245 g_free(ss);
4247 return (XT_CB_HANDLED);
4251 * Return a new string with a download row (in html)
4252 * appended. Old string is freed.
4254 char *
4255 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4258 WebKitDownloadStatus stat;
4259 char *status_html = NULL, *cmd_html = NULL, *new_html;
4260 gdouble progress;
4261 char cur_sz[FMT_SCALED_STRSIZE];
4262 char tot_sz[FMT_SCALED_STRSIZE];
4263 char *xtp_prefix;
4265 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4267 /* All actions wil take this form:
4268 * xxxt://class/seskey
4270 xtp_prefix = g_strdup_printf("%s%d/%s/",
4271 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4273 stat = webkit_download_get_status(dl->download);
4275 switch (stat) {
4276 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4277 status_html = g_strdup_printf("Finished");
4278 cmd_html = g_strdup_printf(
4279 "<a href='%s%d/%d'>Remove</a>",
4280 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4281 break;
4282 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4283 /* gather size info */
4284 progress = 100 * webkit_download_get_progress(dl->download);
4286 fmt_scaled(
4287 webkit_download_get_current_size(dl->download), cur_sz);
4288 fmt_scaled(
4289 webkit_download_get_total_size(dl->download), tot_sz);
4291 status_html = g_strdup_printf(
4292 "<div style='width: 100%%' align='center'>"
4293 "<div class='progress-outer'>"
4294 "<div class='progress-inner' style='width: %.2f%%'>"
4295 "</div></div></div>"
4296 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4297 progress, cur_sz, tot_sz, progress);
4299 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4300 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4302 break;
4303 /* LLL */
4304 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4305 status_html = g_strdup_printf("Cancelled");
4306 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4307 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4308 break;
4309 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4310 status_html = g_strdup_printf("Error!");
4311 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4312 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4313 break;
4314 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4315 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4316 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4317 status_html = g_strdup_printf("Starting");
4318 break;
4319 default:
4320 show_oops(t, "%s: unknown download status", __func__);
4323 new_html = g_strdup_printf(
4324 "%s\n<tr><td>%s</td><td>%s</td>"
4325 "<td style='text-align:center'>%s</td></tr>\n",
4326 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4327 status_html, cmd_html);
4328 g_free(html);
4330 if (status_html)
4331 g_free(status_html);
4333 if (cmd_html)
4334 g_free(cmd_html);
4336 g_free(xtp_prefix);
4338 return new_html;
4342 * update all download tabs apart from one. Pass NULL if
4343 * you want to update all.
4345 void
4346 update_download_tabs(struct tab *apart_from)
4348 struct tab *t;
4349 if (!updating_dl_tabs) {
4350 updating_dl_tabs = 1; /* stop infinite recursion */
4351 TAILQ_FOREACH(t, &tabs, entry)
4352 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4353 && (t != apart_from))
4354 xtp_page_dl(t, NULL);
4355 updating_dl_tabs = 0;
4360 * update all cookie tabs apart from one. Pass NULL if
4361 * you want to update all.
4363 void
4364 update_cookie_tabs(struct tab *apart_from)
4366 struct tab *t;
4367 if (!updating_cl_tabs) {
4368 updating_cl_tabs = 1; /* stop infinite recursion */
4369 TAILQ_FOREACH(t, &tabs, entry)
4370 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4371 && (t != apart_from))
4372 xtp_page_cl(t, NULL);
4373 updating_cl_tabs = 0;
4378 * update all history tabs apart from one. Pass NULL if
4379 * you want to update all.
4381 void
4382 update_history_tabs(struct tab *apart_from)
4384 struct tab *t;
4386 if (!updating_hl_tabs) {
4387 updating_hl_tabs = 1; /* stop infinite recursion */
4388 TAILQ_FOREACH(t, &tabs, entry)
4389 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4390 && (t != apart_from))
4391 xtp_page_hl(t, NULL);
4392 updating_hl_tabs = 0;
4396 /* cookie management XTP page */
4398 xtp_page_cl(struct tab *t, struct karg *args)
4400 char *body, *page, *tmp;
4401 int i = 1; /* all ids start 1 */
4402 GSList *sc, *pc, *pc_start;
4403 SoupCookie *c;
4404 char *type, *table_headers, *last_domain;
4406 DNPRINTF(XT_D_CMD, "%s", __func__);
4408 if (t == NULL) {
4409 show_oops(NULL, "%s invalid parameters", __func__);
4410 return (1);
4413 /* Generate a new session key */
4414 if (!updating_cl_tabs)
4415 generate_xtp_session_key(&cl_session_key);
4417 /* table headers */
4418 table_headers = g_strdup_printf("<table><tr>"
4419 "<th>Type</th>"
4420 "<th>Name</th>"
4421 "<th style='width:200px'>Value</th>"
4422 "<th>Path</th>"
4423 "<th>Expires</th>"
4424 "<th>Secure</th>"
4425 "<th>HTTP<br />only</th>"
4426 "<th style='width:40px'>Rm</th></tr>\n");
4428 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4429 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4430 pc_start = pc;
4432 body = NULL;
4433 last_domain = strdup("");
4434 for (; sc; sc = sc->next) {
4435 c = sc->data;
4437 if (strcmp(last_domain, c->domain) != 0) {
4438 /* new domain */
4439 free(last_domain);
4440 last_domain = strdup(c->domain);
4442 if (body != NULL) {
4443 tmp = body;
4444 body = g_strdup_printf("%s</table>"
4445 "<h2>%s</h2>%s\n",
4446 body, c->domain, table_headers);
4447 g_free(tmp);
4448 } else {
4449 /* first domain */
4450 body = g_strdup_printf("<h2>%s</h2>%s\n",
4451 c->domain, table_headers);
4455 type = "Session";
4456 for (pc = pc_start; pc; pc = pc->next)
4457 if (soup_cookie_equal(pc->data, c)) {
4458 type = "Session + Persistent";
4459 break;
4462 tmp = body;
4463 body = g_strdup_printf(
4464 "%s\n<tr>"
4465 "<td>%s</td>"
4466 "<td style='word-wrap:normal'>%s</td>"
4467 "<td>"
4468 " <textarea rows='4'>%s</textarea>"
4469 "</td>"
4470 "<td>%s</td>"
4471 "<td>%s</td>"
4472 "<td>%d</td>"
4473 "<td>%d</td>"
4474 "<td style='text-align:center'>"
4475 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4476 body,
4477 type,
4478 c->name,
4479 c->value,
4480 c->path,
4481 c->expires ?
4482 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4483 c->secure,
4484 c->http_only,
4486 XT_XTP_STR,
4487 XT_XTP_CL,
4488 cl_session_key,
4489 XT_XTP_CL_REMOVE,
4493 g_free(tmp);
4494 i++;
4497 soup_cookies_free(sc);
4498 soup_cookies_free(pc);
4500 /* small message if there are none */
4501 if (i == 1) {
4502 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4503 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4505 tmp = body;
4506 body = g_strdup_printf("%s</table>", body);
4507 g_free(tmp);
4509 page = get_html_page("Cookie Jar", body, "", TRUE);
4510 g_free(body);
4511 g_free(table_headers);
4512 g_free(last_domain);
4514 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4515 update_cookie_tabs(t);
4517 g_free(page);
4519 return (0);
4523 xtp_page_hl(struct tab *t, struct karg *args)
4525 char *body, *page, *tmp;
4526 struct history *h;
4527 int i = 1; /* all ids start 1 */
4529 DNPRINTF(XT_D_CMD, "%s", __func__);
4531 if (t == NULL) {
4532 show_oops(NULL, "%s invalid parameters", __func__);
4533 return (1);
4536 /* Generate a new session key */
4537 if (!updating_hl_tabs)
4538 generate_xtp_session_key(&hl_session_key);
4540 /* body */
4541 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4542 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4544 RB_FOREACH_REVERSE(h, history_list, &hl) {
4545 tmp = body;
4546 body = g_strdup_printf(
4547 "%s\n<tr>"
4548 "<td><a href='%s'>%s</a></td>"
4549 "<td>%s</td>"
4550 "<td style='text-align: center'>"
4551 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4552 body, h->uri, h->uri, h->title,
4553 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4554 XT_XTP_HL_REMOVE, i);
4556 g_free(tmp);
4557 i++;
4560 /* small message if there are none */
4561 if (i == 1) {
4562 tmp = body;
4563 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4564 "colspan='3'>No History</td></tr>\n", body);
4565 g_free(tmp);
4568 tmp = body;
4569 body = g_strdup_printf("%s</table>", body);
4570 g_free(tmp);
4572 page = get_html_page("History", body, "", TRUE);
4573 g_free(body);
4576 * update all history manager tabs as the xtp session
4577 * key has now changed. No need to update the current tab.
4578 * Already did that above.
4580 update_history_tabs(t);
4582 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4583 g_free(page);
4585 return (0);
4589 * Generate a web page detailing the status of any downloads
4592 xtp_page_dl(struct tab *t, struct karg *args)
4594 struct download *dl;
4595 char *body, *page, *tmp;
4596 char *ref;
4597 int n_dl = 1;
4599 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4601 if (t == NULL) {
4602 show_oops(NULL, "%s invalid parameters", __func__);
4603 return (1);
4607 * Generate a new session key for next page instance.
4608 * This only happens for the top level call to xtp_page_dl()
4609 * in which case updating_dl_tabs is 0.
4611 if (!updating_dl_tabs)
4612 generate_xtp_session_key(&dl_session_key);
4614 /* header - with refresh so as to update */
4615 if (refresh_interval >= 1)
4616 ref = g_strdup_printf(
4617 "<meta http-equiv='refresh' content='%u"
4618 ";url=%s%d/%s/%d' />\n",
4619 refresh_interval,
4620 XT_XTP_STR,
4621 XT_XTP_DL,
4622 dl_session_key,
4623 XT_XTP_DL_LIST);
4624 else
4625 ref = g_strdup("");
4627 body = g_strdup_printf("<div align='center'>"
4628 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4629 "</p><table><tr><th style='width: 60%%'>"
4630 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4631 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4633 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4634 body = xtp_page_dl_row(t, body, dl);
4635 n_dl++;
4638 /* message if no downloads in list */
4639 if (n_dl == 1) {
4640 tmp = body;
4641 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4642 " style='text-align: center'>"
4643 "No downloads</td></tr>\n", body);
4644 g_free(tmp);
4647 tmp = body;
4648 body = g_strdup_printf("%s</table></div>", body);
4649 g_free(tmp);
4651 page = get_html_page("Downloads", body, ref, 1);
4652 g_free(ref);
4653 g_free(body);
4656 * update all download manager tabs as the xtp session
4657 * key has now changed. No need to update the current tab.
4658 * Already did that above.
4660 update_download_tabs(t);
4662 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4663 g_free(page);
4665 return (0);
4669 search(struct tab *t, struct karg *args)
4671 gboolean d;
4673 if (t == NULL || args == NULL) {
4674 show_oops(NULL, "search invalid parameters");
4675 return (1);
4677 if (t->search_text == NULL) {
4678 if (global_search == NULL)
4679 return (XT_CB_PASSTHROUGH);
4680 else {
4681 t->search_text = g_strdup(global_search);
4682 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4683 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4687 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4688 t->tab_id, args->i, t->search_forward, t->search_text);
4690 switch (args->i) {
4691 case XT_SEARCH_NEXT:
4692 d = t->search_forward;
4693 break;
4694 case XT_SEARCH_PREV:
4695 d = !t->search_forward;
4696 break;
4697 default:
4698 return (XT_CB_PASSTHROUGH);
4701 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4703 return (XT_CB_HANDLED);
4706 struct settings_args {
4707 char **body;
4708 int i;
4711 void
4712 print_setting(struct settings *s, char *val, void *cb_args)
4714 char *tmp, *color;
4715 struct settings_args *sa = cb_args;
4717 if (sa == NULL)
4718 return;
4720 if (s->flags & XT_SF_RUNTIME)
4721 color = "#22cc22";
4722 else
4723 color = "#cccccc";
4725 tmp = *sa->body;
4726 *sa->body = g_strdup_printf(
4727 "%s\n<tr>"
4728 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4729 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4730 *sa->body,
4731 color,
4732 s->name,
4733 color,
4736 g_free(tmp);
4737 sa->i++;
4741 set(struct tab *t, struct karg *args)
4743 char *body, *page, *tmp;
4744 int i = 1;
4745 struct settings_args sa;
4747 bzero(&sa, sizeof sa);
4748 sa.body = &body;
4750 /* body */
4751 body = g_strdup_printf("<div align='center'><table><tr>"
4752 "<th align='left'>Setting</th>"
4753 "<th align='left'>Value</th></tr>\n");
4755 settings_walk(print_setting, &sa);
4756 i = sa.i;
4758 /* small message if there are none */
4759 if (i == 1) {
4760 tmp = body;
4761 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4762 "colspan='2'>No settings</td></tr>\n", body);
4763 g_free(tmp);
4766 tmp = body;
4767 body = g_strdup_printf("%s</table></div>", body);
4768 g_free(tmp);
4770 page = get_html_page("Settings", body, "", 0);
4772 g_free(body);
4774 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4776 g_free(page);
4778 return (XT_CB_PASSTHROUGH);
4782 session_save(struct tab *t, char *filename)
4784 struct karg a;
4785 int rv = 1;
4787 if (strlen(filename) == 0)
4788 goto done;
4790 if (filename[0] == '.' || filename[0] == '/')
4791 goto done;
4793 a.s = filename;
4794 if (save_tabs(t, &a))
4795 goto done;
4796 strlcpy(named_session, filename, sizeof named_session);
4798 rv = 0;
4799 done:
4800 return (rv);
4804 session_open(struct tab *t, char *filename)
4806 struct karg a;
4807 int rv = 1;
4809 if (strlen(filename) == 0)
4810 goto done;
4812 if (filename[0] == '.' || filename[0] == '/')
4813 goto done;
4815 a.s = filename;
4816 a.i = XT_SES_CLOSETABS;
4817 if (open_tabs(t, &a))
4818 goto done;
4820 strlcpy(named_session, filename, sizeof named_session);
4822 rv = 0;
4823 done:
4824 return (rv);
4828 session_delete(struct tab *t, char *filename)
4830 char file[PATH_MAX];
4831 int rv = 1;
4833 if (strlen(filename) == 0)
4834 goto done;
4836 if (filename[0] == '.' || filename[0] == '/')
4837 goto done;
4839 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4840 if (unlink(file))
4841 goto done;
4843 if (!strcmp(filename, named_session))
4844 strlcpy(named_session, XT_SAVED_TABS_FILE,
4845 sizeof named_session);
4847 rv = 0;
4848 done:
4849 return (rv);
4853 session_cmd(struct tab *t, struct karg *args)
4855 char *filename = args->s;
4857 if (t == NULL)
4858 return (1);
4860 if (args->i & XT_SHOW)
4861 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4862 XT_SAVED_TABS_FILE : named_session);
4863 else if (args->i & XT_SAVE) {
4864 if (session_save(t, filename)) {
4865 show_oops(t, "Can't save session: %s",
4866 filename ? filename : "INVALID");
4867 goto done;
4869 } else if (args->i & XT_OPEN) {
4870 if (session_open(t, filename)) {
4871 show_oops(t, "Can't open session: %s",
4872 filename ? filename : "INVALID");
4873 goto done;
4875 } else if (args->i & XT_DELETE) {
4876 if (session_delete(t, filename)) {
4877 show_oops(t, "Can't delete session: %s",
4878 filename ? filename : "INVALID");
4879 goto done;
4882 done:
4883 return (XT_CB_PASSTHROUGH);
4887 * Make a hardcopy of the page
4890 print_page(struct tab *t, struct karg *args)
4892 WebKitWebFrame *frame;
4893 GtkPageSetup *ps;
4894 GtkPrintOperation *op;
4895 GtkPrintOperationAction action;
4896 GtkPrintOperationResult print_res;
4897 GError *g_err = NULL;
4898 int marg_l, marg_r, marg_t, marg_b;
4900 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4902 ps = gtk_page_setup_new();
4903 op = gtk_print_operation_new();
4904 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4905 frame = webkit_web_view_get_main_frame(t->wv);
4907 /* the default margins are too small, so we will bump them */
4908 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4909 XT_PRINT_EXTRA_MARGIN;
4910 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4911 XT_PRINT_EXTRA_MARGIN;
4912 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4913 XT_PRINT_EXTRA_MARGIN;
4914 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4915 XT_PRINT_EXTRA_MARGIN;
4917 /* set margins */
4918 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4919 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4920 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4921 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4923 gtk_print_operation_set_default_page_setup(op, ps);
4925 /* this appears to free 'op' and 'ps' */
4926 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4928 /* check it worked */
4929 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4930 show_oops(NULL, "can't print: %s", g_err->message);
4931 g_error_free (g_err);
4932 return (1);
4935 return (0);
4939 go_home(struct tab *t, struct karg *args)
4941 load_uri(t, home);
4942 return (0);
4946 restart(struct tab *t, struct karg *args)
4948 struct karg a;
4950 a.s = XT_RESTART_TABS_FILE;
4951 save_tabs(t, &a);
4952 execvp(start_argv[0], start_argv);
4953 /* NOTREACHED */
4955 return (0);
4958 #define CTRL GDK_CONTROL_MASK
4959 #define MOD1 GDK_MOD1_MASK
4960 #define SHFT GDK_SHIFT_MASK
4962 /* inherent to GTK not all keys will be caught at all times */
4963 /* XXX sort key bindings */
4964 struct key_binding {
4965 char *cmd;
4966 guint mask;
4967 guint use_in_entry;
4968 guint key;
4969 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4970 } keys[] = {
4971 { "cookiejar", MOD1, 0, GDK_j },
4972 { "downloadmgr", MOD1, 0, GDK_d },
4973 { "history", MOD1, 0, GDK_h },
4974 { "print", CTRL, 0, GDK_p },
4975 { "search", 0, 0, GDK_slash },
4976 { "searchb", 0, 0, GDK_question },
4977 { "statustoggle", CTRL, 0, GDK_n },
4978 { "command", 0, 0, GDK_colon },
4979 { "qa", CTRL, 0, GDK_q },
4980 { "restart", MOD1, 0, GDK_q },
4981 { "js toggle", CTRL, 0, GDK_j },
4982 { "cookie toggle", MOD1, 0, GDK_c },
4983 { "togglesrc", CTRL, 0, GDK_s },
4984 { "yankuri", 0, 0, GDK_y },
4985 { "pasteuricur", 0, 0, GDK_p },
4986 { "pasteurinew", 0, 0, GDK_P },
4987 { "toplevel toggle", 0, 0, GDK_F4 },
4988 { "help", 0, 0, GDK_F1 },
4989 { "run_script", MOD1, 0, GDK_r },
4991 /* search */
4992 { "searchnext", 0, 0, GDK_n },
4993 { "searchprevious", 0, 0, GDK_N },
4995 /* focus */
4996 { "focusaddress", 0, 0, GDK_F6 },
4997 { "focussearch", 0, 0, GDK_F7 },
4999 /* hinting */
5000 { "hinting", 0, 0, GDK_f },
5002 /* custom stylesheet */
5003 { "userstyle", 0, 0, GDK_i },
5005 /* navigation */
5006 { "goback", 0, 0, GDK_BackSpace },
5007 { "goback", MOD1, 0, GDK_Left },
5008 { "goforward", SHFT, 0, GDK_BackSpace },
5009 { "goforward", MOD1, 0, GDK_Right },
5010 { "reload", 0, 0, GDK_F5 },
5011 { "reload", CTRL, 0, GDK_r },
5012 { "reloadforce", CTRL, 0, GDK_R },
5013 { "reload", CTRL, 0, GDK_l },
5014 { "favorites", MOD1, 1, GDK_f },
5016 /* vertical movement */
5017 { "scrolldown", 0, 0, GDK_j },
5018 { "scrolldown", 0, 0, GDK_Down },
5019 { "scrollup", 0, 0, GDK_Up },
5020 { "scrollup", 0, 0, GDK_k },
5021 { "scrollbottom", 0, 0, GDK_G },
5022 { "scrollbottom", 0, 0, GDK_End },
5023 { "scrolltop", 0, 0, GDK_Home },
5024 { "scrollpagedown", 0, 0, GDK_space },
5025 { "scrollpagedown", CTRL, 0, GDK_f },
5026 { "scrollhalfdown", CTRL, 0, GDK_d },
5027 { "scrollpagedown", 0, 0, GDK_Page_Down },
5028 { "scrollpageup", 0, 0, GDK_Page_Up },
5029 { "scrollpageup", CTRL, 0, GDK_b },
5030 { "scrollhalfup", CTRL, 0, GDK_u },
5031 /* horizontal movement */
5032 { "scrollright", 0, 0, GDK_l },
5033 { "scrollright", 0, 0, GDK_Right },
5034 { "scrollleft", 0, 0, GDK_Left },
5035 { "scrollleft", 0, 0, GDK_h },
5036 { "scrollfarright", 0, 0, GDK_dollar },
5037 { "scrollfarleft", 0, 0, GDK_0 },
5039 /* tabs */
5040 { "tabnew", CTRL, 0, GDK_t },
5041 { "999tabnew", CTRL, 0, GDK_T },
5042 { "tabclose", CTRL, 1, GDK_w },
5043 { "tabundoclose", 0, 0, GDK_U },
5044 { "tabnext 1", CTRL, 0, GDK_1 },
5045 { "tabnext 2", CTRL, 0, GDK_2 },
5046 { "tabnext 3", CTRL, 0, GDK_3 },
5047 { "tabnext 4", CTRL, 0, GDK_4 },
5048 { "tabnext 5", CTRL, 0, GDK_5 },
5049 { "tabnext 6", CTRL, 0, GDK_6 },
5050 { "tabnext 7", CTRL, 0, GDK_7 },
5051 { "tabnext 8", CTRL, 0, GDK_8 },
5052 { "tabnext 9", CTRL, 0, GDK_9 },
5053 { "tabfirst", CTRL, 0, GDK_less },
5054 { "tablast", CTRL, 0, GDK_greater },
5055 { "tabprevious", CTRL, 0, GDK_Left },
5056 { "tabnext", CTRL, 0, GDK_Right },
5057 { "focusout", CTRL, 0, GDK_minus },
5058 { "focusin", CTRL, 0, GDK_plus },
5059 { "focusin", CTRL, 0, GDK_equal },
5060 { "focusreset", CTRL, 0, GDK_0 },
5062 /* command aliases (handy when -S flag is used) */
5063 { "promptopen", 0, 0, GDK_F9 },
5064 { "promptopencurrent", 0, 0, GDK_F10 },
5065 { "prompttabnew", 0, 0, GDK_F11 },
5066 { "prompttabnewcurrent",0, 0, GDK_F12 },
5068 TAILQ_HEAD(keybinding_list, key_binding);
5070 void
5071 walk_kb(struct settings *s,
5072 void (*cb)(struct settings *, char *, void *), void *cb_args)
5074 struct key_binding *k;
5075 char str[1024];
5077 if (s == NULL || cb == NULL) {
5078 show_oops(NULL, "walk_kb invalid parameters");
5079 return;
5082 TAILQ_FOREACH(k, &kbl, entry) {
5083 if (k->cmd == NULL)
5084 continue;
5085 str[0] = '\0';
5087 /* sanity */
5088 if (gdk_keyval_name(k->key) == NULL)
5089 continue;
5091 strlcat(str, k->cmd, sizeof str);
5092 strlcat(str, ",", sizeof str);
5094 if (k->mask & GDK_SHIFT_MASK)
5095 strlcat(str, "S-", sizeof str);
5096 if (k->mask & GDK_CONTROL_MASK)
5097 strlcat(str, "C-", sizeof str);
5098 if (k->mask & GDK_MOD1_MASK)
5099 strlcat(str, "M1-", sizeof str);
5100 if (k->mask & GDK_MOD2_MASK)
5101 strlcat(str, "M2-", sizeof str);
5102 if (k->mask & GDK_MOD3_MASK)
5103 strlcat(str, "M3-", sizeof str);
5104 if (k->mask & GDK_MOD4_MASK)
5105 strlcat(str, "M4-", sizeof str);
5106 if (k->mask & GDK_MOD5_MASK)
5107 strlcat(str, "M5-", sizeof str);
5109 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5110 cb(s, str, cb_args);
5114 void
5115 init_keybindings(void)
5117 int i;
5118 struct key_binding *k;
5120 for (i = 0; i < LENGTH(keys); i++) {
5121 k = g_malloc0(sizeof *k);
5122 k->cmd = keys[i].cmd;
5123 k->mask = keys[i].mask;
5124 k->use_in_entry = keys[i].use_in_entry;
5125 k->key = keys[i].key;
5126 TAILQ_INSERT_HEAD(&kbl, k, entry);
5128 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5129 k->cmd ? k->cmd : "unnamed key");
5133 void
5134 keybinding_clearall(void)
5136 struct key_binding *k, *next;
5138 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5139 next = TAILQ_NEXT(k, entry);
5140 if (k->cmd == NULL)
5141 continue;
5143 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5144 k->cmd ? k->cmd : "unnamed key");
5145 TAILQ_REMOVE(&kbl, k, entry);
5146 g_free(k);
5151 keybinding_add(char *cmd, char *key, int use_in_entry)
5153 struct key_binding *k;
5154 guint keyval, mask = 0;
5155 int i;
5157 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5159 /* Keys which are to be used in entry have been prefixed with an
5160 * exclamation mark. */
5161 if (use_in_entry)
5162 key++;
5164 /* find modifier keys */
5165 if (strstr(key, "S-"))
5166 mask |= GDK_SHIFT_MASK;
5167 if (strstr(key, "C-"))
5168 mask |= GDK_CONTROL_MASK;
5169 if (strstr(key, "M1-"))
5170 mask |= GDK_MOD1_MASK;
5171 if (strstr(key, "M2-"))
5172 mask |= GDK_MOD2_MASK;
5173 if (strstr(key, "M3-"))
5174 mask |= GDK_MOD3_MASK;
5175 if (strstr(key, "M4-"))
5176 mask |= GDK_MOD4_MASK;
5177 if (strstr(key, "M5-"))
5178 mask |= GDK_MOD5_MASK;
5180 /* find keyname */
5181 for (i = strlen(key) - 1; i > 0; i--)
5182 if (key[i] == '-')
5183 key = &key[i + 1];
5185 /* validate keyname */
5186 keyval = gdk_keyval_from_name(key);
5187 if (keyval == GDK_VoidSymbol) {
5188 warnx("invalid keybinding name %s", key);
5189 return (1);
5191 /* must run this test too, gtk+ doesn't handle 10 for example */
5192 if (gdk_keyval_name(keyval) == NULL) {
5193 warnx("invalid keybinding name %s", key);
5194 return (1);
5197 /* Remove eventual dupes. */
5198 TAILQ_FOREACH(k, &kbl, entry)
5199 if (k->key == keyval && k->mask == mask) {
5200 TAILQ_REMOVE(&kbl, k, entry);
5201 g_free(k);
5202 break;
5205 /* add keyname */
5206 k = g_malloc0(sizeof *k);
5207 k->cmd = g_strdup(cmd);
5208 k->mask = mask;
5209 k->use_in_entry = use_in_entry;
5210 k->key = keyval;
5212 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5213 k->cmd,
5214 k->mask,
5215 k->use_in_entry,
5216 k->key);
5217 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5218 k->cmd, gdk_keyval_name(keyval));
5220 TAILQ_INSERT_HEAD(&kbl, k, entry);
5222 return (0);
5226 add_kb(struct settings *s, char *entry)
5228 char *kb, *key;
5230 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5232 /* clearall is special */
5233 if (!strcmp(entry, "clearall")) {
5234 keybinding_clearall();
5235 return (0);
5238 kb = strstr(entry, ",");
5239 if (kb == NULL)
5240 return (1);
5241 *kb = '\0';
5242 key = kb + 1;
5244 return (keybinding_add(entry, key, key[0] == '!'));
5247 struct cmd {
5248 char *cmd;
5249 int level;
5250 int (*func)(struct tab *, struct karg *);
5251 int arg;
5252 int type;
5253 } cmds[] = {
5254 { "command", 0, command, ':', 0 },
5255 { "search", 0, command, '/', 0 },
5256 { "searchb", 0, command, '?', 0 },
5257 { "togglesrc", 0, toggle_src, 0, 0 },
5259 /* yanking and pasting */
5260 { "yankuri", 0, yank_uri, 0, 0 },
5261 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5262 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5263 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5265 /* search */
5266 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5267 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5269 /* focus */
5270 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5271 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5273 /* hinting */
5274 { "hinting", 0, hint, 0, 0 },
5276 /* custom stylesheet */
5277 { "userstyle", 0, userstyle, 0, 0 },
5279 /* navigation */
5280 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5281 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5282 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5283 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
5285 /* vertical movement */
5286 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5287 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5288 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5289 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5290 { "1", 0, move, XT_MOVE_TOP, 0 },
5291 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5292 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5293 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5294 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5295 /* horizontal movement */
5296 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5297 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5298 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5299 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5301 { "favorites", 0, xtp_page_fl, 0, 0 },
5302 { "fav", 0, xtp_page_fl, 0, 0 },
5303 { "favadd", 0, add_favorite, 0, 0 },
5305 { "qall", 0, quit, 0, 0 },
5306 { "quitall", 0, quit, 0, 0 },
5307 { "w", 0, save_tabs, 0, 0 },
5308 { "wq", 0, save_tabs_and_quit, 0, 0 },
5309 { "help", 0, help, 0, 0 },
5310 { "about", 0, about, 0, 0 },
5311 { "stats", 0, stats, 0, 0 },
5312 { "version", 0, about, 0, 0 },
5314 /* js command */
5315 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5316 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5317 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5318 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5319 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5320 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5321 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5322 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5323 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5324 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5325 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5327 /* cookie command */
5328 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5329 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5330 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5331 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5332 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5333 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5334 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5335 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5336 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5337 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5338 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5340 /* toplevel (domain) command */
5341 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5342 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5344 /* cookie jar */
5345 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5347 /* cert command */
5348 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5349 { "save", 1, cert_cmd, XT_SAVE, 0 },
5350 { "show", 1, cert_cmd, XT_SHOW, 0 },
5352 { "ca", 0, ca_cmd, 0, 0 },
5353 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5354 { "dl", 0, xtp_page_dl, 0, 0 },
5355 { "h", 0, xtp_page_hl, 0, 0 },
5356 { "history", 0, xtp_page_hl, 0, 0 },
5357 { "home", 0, go_home, 0, 0 },
5358 { "restart", 0, restart, 0, 0 },
5359 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5360 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5361 { "statustoggle", 0, statustoggle, 0, 0 },
5362 { "run_script", 0, run_page_script, 0, XT_USERARG },
5364 { "print", 0, print_page, 0, 0 },
5366 /* tabs */
5367 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5368 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5369 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5370 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5371 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5372 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5373 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5374 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5375 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5376 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5377 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5378 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5379 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5380 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5381 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5382 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5383 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5384 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5385 { "buffers", 0, buffers, 0, 0 },
5386 { "ls", 0, buffers, 0, 0 },
5387 { "tabs", 0, buffers, 0, 0 },
5389 /* command aliases (handy when -S flag is used) */
5390 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5391 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5392 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5393 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5395 /* settings */
5396 { "set", 0, set, 0, 0 },
5397 { "fullscreen", 0, fullscreen, 0, 0 },
5398 { "f", 0, fullscreen, 0, 0 },
5400 /* sessions */
5401 { "session", 0, session_cmd, XT_SHOW, 0 },
5402 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5403 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5404 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5405 { "show", 1, session_cmd, XT_SHOW, 0 },
5408 struct {
5409 int index;
5410 int len;
5411 gchar *list[256];
5412 } cmd_status = {-1, 0};
5414 gboolean
5415 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5418 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5419 btn_down = 0;
5421 return (FALSE);
5424 gboolean
5425 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5427 struct karg a;
5429 hide_oops(t);
5430 hide_buffers(t);
5432 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5433 btn_down = 1;
5434 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5435 /* go backward */
5436 a.i = XT_NAV_BACK;
5437 navaction(t, &a);
5439 return (TRUE);
5440 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5441 /* go forward */
5442 a.i = XT_NAV_FORWARD;
5443 navaction(t, &a);
5445 return (TRUE);
5448 return (FALSE);
5451 gboolean
5452 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5454 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5456 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5457 delete_tab(t);
5459 return (FALSE);
5463 * cancel, remove, etc. downloads
5465 void
5466 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5468 struct download find, *d = NULL;
5470 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5472 /* some commands require a valid download id */
5473 if (cmd != XT_XTP_DL_LIST) {
5474 /* lookup download in question */
5475 find.id = id;
5476 d = RB_FIND(download_list, &downloads, &find);
5478 if (d == NULL) {
5479 show_oops(t, "%s: no such download", __func__);
5480 return;
5484 /* decide what to do */
5485 switch (cmd) {
5486 case XT_XTP_DL_CANCEL:
5487 webkit_download_cancel(d->download);
5488 break;
5489 case XT_XTP_DL_REMOVE:
5490 webkit_download_cancel(d->download); /* just incase */
5491 g_object_unref(d->download);
5492 RB_REMOVE(download_list, &downloads, d);
5493 break;
5494 case XT_XTP_DL_LIST:
5495 /* Nothing */
5496 break;
5497 default:
5498 show_oops(t, "%s: unknown command", __func__);
5499 break;
5501 xtp_page_dl(t, NULL);
5505 * Actions on history, only does one thing for now, but
5506 * we provide the function for future actions
5508 void
5509 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5511 struct history *h, *next;
5512 int i = 1;
5514 switch (cmd) {
5515 case XT_XTP_HL_REMOVE:
5516 /* walk backwards, as listed in reverse */
5517 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5518 next = RB_PREV(history_list, &hl, h);
5519 if (id == i) {
5520 RB_REMOVE(history_list, &hl, h);
5521 g_free((gpointer) h->title);
5522 g_free((gpointer) h->uri);
5523 g_free(h);
5524 break;
5526 i++;
5528 break;
5529 case XT_XTP_HL_LIST:
5530 /* Nothing - just xtp_page_hl() below */
5531 break;
5532 default:
5533 show_oops(t, "%s: unknown command", __func__);
5534 break;
5537 xtp_page_hl(t, NULL);
5540 /* remove a favorite */
5541 void
5542 remove_favorite(struct tab *t, int index)
5544 char file[PATH_MAX], *title, *uri = NULL;
5545 char *new_favs, *tmp;
5546 FILE *f;
5547 int i;
5548 size_t len, lineno;
5550 /* open favorites */
5551 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5553 if ((f = fopen(file, "r")) == NULL) {
5554 show_oops(t, "%s: can't open favorites: %s",
5555 __func__, strerror(errno));
5556 return;
5559 /* build a string which will become the new favroites file */
5560 new_favs = g_strdup("");
5562 for (i = 1;;) {
5563 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5564 if (feof(f) || ferror(f))
5565 break;
5566 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5567 if (len == 0) {
5568 free(title);
5569 title = NULL;
5570 continue;
5573 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5574 if (feof(f) || ferror(f)) {
5575 show_oops(t, "%s: can't parse favorites %s",
5576 __func__, strerror(errno));
5577 goto clean;
5581 /* as long as this isn't the one we are deleting add to file */
5582 if (i != index) {
5583 tmp = new_favs;
5584 new_favs = g_strdup_printf("%s%s\n%s\n",
5585 new_favs, title, uri);
5586 g_free(tmp);
5589 free(uri);
5590 uri = NULL;
5591 free(title);
5592 title = NULL;
5593 i++;
5595 fclose(f);
5597 /* write back new favorites file */
5598 if ((f = fopen(file, "w")) == NULL) {
5599 show_oops(t, "%s: can't open favorites: %s",
5600 __func__, strerror(errno));
5601 goto clean;
5604 fwrite(new_favs, strlen(new_favs), 1, f);
5605 fclose(f);
5607 clean:
5608 if (uri)
5609 free(uri);
5610 if (title)
5611 free(title);
5613 g_free(new_favs);
5616 void
5617 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5619 switch (cmd) {
5620 case XT_XTP_FL_LIST:
5621 /* nothing, just the below call to xtp_page_fl() */
5622 break;
5623 case XT_XTP_FL_REMOVE:
5624 remove_favorite(t, arg);
5625 break;
5626 default:
5627 show_oops(t, "%s: invalid favorites command", __func__);
5628 break;
5631 xtp_page_fl(t, NULL);
5634 void
5635 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5637 switch (cmd) {
5638 case XT_XTP_CL_LIST:
5639 /* nothing, just xtp_page_cl() */
5640 break;
5641 case XT_XTP_CL_REMOVE:
5642 remove_cookie(arg);
5643 break;
5644 default:
5645 show_oops(t, "%s: unknown cookie xtp command", __func__);
5646 break;
5649 xtp_page_cl(t, NULL);
5652 /* link an XTP class to it's session key and handler function */
5653 struct xtp_despatch {
5654 uint8_t xtp_class;
5655 char **session_key;
5656 void (*handle_func)(struct tab *, uint8_t, int);
5659 struct xtp_despatch xtp_despatches[] = {
5660 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5661 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5662 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5663 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5664 { XT_XTP_INVALID, NULL, NULL }
5668 * is the url xtp protocol? (xxxt://)
5669 * if so, parse and despatch correct bahvior
5672 parse_xtp_url(struct tab *t, const char *url)
5674 char *dup = NULL, *p, *last;
5675 uint8_t n_tokens = 0;
5676 char *tokens[4] = {NULL, NULL, NULL, ""};
5677 struct xtp_despatch *dsp, *dsp_match = NULL;
5678 uint8_t req_class;
5679 int ret = FALSE;
5682 * tokens array meaning:
5683 * tokens[0] = class
5684 * tokens[1] = session key
5685 * tokens[2] = action
5686 * tokens[3] = optional argument
5689 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5691 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5692 goto clean;
5694 dup = g_strdup(url + strlen(XT_XTP_STR));
5696 /* split out the url */
5697 for ((p = strtok_r(dup, "/", &last)); p;
5698 (p = strtok_r(NULL, "/", &last))) {
5699 if (n_tokens < 4)
5700 tokens[n_tokens++] = p;
5703 /* should be atleast three fields 'class/seskey/command/arg' */
5704 if (n_tokens < 3)
5705 goto clean;
5707 dsp = xtp_despatches;
5708 req_class = atoi(tokens[0]);
5709 while (dsp->xtp_class) {
5710 if (dsp->xtp_class == req_class) {
5711 dsp_match = dsp;
5712 break;
5714 dsp++;
5717 /* did we find one atall? */
5718 if (dsp_match == NULL) {
5719 show_oops(t, "%s: no matching xtp despatch found", __func__);
5720 goto clean;
5723 /* check session key and call despatch function */
5724 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5725 ret = TRUE; /* all is well, this was a valid xtp request */
5726 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5729 clean:
5730 if (dup)
5731 g_free(dup);
5733 return (ret);
5738 void
5739 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5741 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5743 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5745 if (t == NULL) {
5746 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5747 return;
5750 if (uri == NULL) {
5751 show_oops(t, "activate_uri_entry_cb no uri");
5752 return;
5755 uri += strspn(uri, "\t ");
5757 /* if xxxt:// treat specially */
5758 if (parse_xtp_url(t, uri))
5759 return;
5761 /* otherwise continue to load page normally */
5762 load_uri(t, (gchar *)uri);
5763 focus_webview(t);
5766 void
5767 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5769 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5770 char *newuri = NULL;
5771 gchar *enc_search;
5773 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5775 if (t == NULL) {
5776 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5777 return;
5780 if (search_string == NULL) {
5781 show_oops(t, "no search_string");
5782 return;
5785 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5787 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5788 newuri = g_strdup_printf(search_string, enc_search);
5789 g_free(enc_search);
5791 marks_clear(t);
5792 webkit_web_view_load_uri(t->wv, newuri);
5793 focus_webview(t);
5795 if (newuri)
5796 g_free(newuri);
5799 void
5800 check_and_set_js(const gchar *uri, struct tab *t)
5802 struct domain *d = NULL;
5803 int es = 0;
5805 if (uri == NULL || t == NULL)
5806 return;
5808 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5809 es = 0;
5810 else
5811 es = 1;
5813 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5814 es ? "enable" : "disable", uri);
5816 g_object_set(G_OBJECT(t->settings),
5817 "enable-scripts", es, (char *)NULL);
5818 g_object_set(G_OBJECT(t->settings),
5819 "javascript-can-open-windows-automatically", es, (char *)NULL);
5820 webkit_web_view_set_settings(t->wv, t->settings);
5822 button_set_stockid(t->js_toggle,
5823 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5826 void
5827 show_ca_status(struct tab *t, const char *uri)
5829 WebKitWebFrame *frame;
5830 WebKitWebDataSource *source;
5831 WebKitNetworkRequest *request;
5832 SoupMessage *message;
5833 GdkColor color;
5834 gchar *col_str = XT_COLOR_WHITE;
5835 int r;
5837 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5838 ssl_strict_certs, ssl_ca_file, uri);
5840 if (uri == NULL)
5841 goto done;
5842 if (ssl_ca_file == NULL) {
5843 if (g_str_has_prefix(uri, "http://"))
5844 goto done;
5845 if (g_str_has_prefix(uri, "https://")) {
5846 col_str = XT_COLOR_RED;
5847 goto done;
5849 return;
5851 if (g_str_has_prefix(uri, "http://") ||
5852 !g_str_has_prefix(uri, "https://"))
5853 goto done;
5855 frame = webkit_web_view_get_main_frame(t->wv);
5856 source = webkit_web_frame_get_data_source(frame);
5857 request = webkit_web_data_source_get_request(source);
5858 message = webkit_network_request_get_message(request);
5860 if (message && (soup_message_get_flags(message) &
5861 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5862 col_str = XT_COLOR_GREEN;
5863 goto done;
5864 } else {
5865 r = load_compare_cert(t, NULL);
5866 if (r == 0)
5867 col_str = XT_COLOR_BLUE;
5868 else if (r == 1)
5869 col_str = XT_COLOR_YELLOW;
5870 else
5871 col_str = XT_COLOR_RED;
5872 goto done;
5874 done:
5875 if (col_str) {
5876 gdk_color_parse(col_str, &color);
5877 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5879 if (!strcmp(col_str, XT_COLOR_WHITE))
5880 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5881 else
5882 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5886 void
5887 free_favicon(struct tab *t)
5889 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5890 __func__, t->icon_download, t->icon_request);
5892 if (t->icon_request)
5893 g_object_unref(t->icon_request);
5894 if (t->icon_dest_uri)
5895 g_free(t->icon_dest_uri);
5897 t->icon_request = NULL;
5898 t->icon_dest_uri = NULL;
5901 void
5902 xt_icon_from_name(struct tab *t, gchar *name)
5904 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5905 GTK_ENTRY_ICON_PRIMARY, "text-html");
5906 if (show_url == 0)
5907 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5908 GTK_ENTRY_ICON_PRIMARY, "text-html");
5909 else
5910 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5911 GTK_ENTRY_ICON_PRIMARY, NULL);
5914 void
5915 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5917 GdkPixbuf *pb_scaled;
5919 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5920 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
5921 GDK_INTERP_BILINEAR);
5922 else
5923 pb_scaled = pb;
5925 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5926 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5927 if (show_url == 0)
5928 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
5929 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5930 else
5931 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5932 GTK_ENTRY_ICON_PRIMARY, NULL);
5934 if (pb_scaled != pb)
5935 g_object_unref(pb_scaled);
5938 void
5939 xt_icon_from_file(struct tab *t, char *file)
5941 GdkPixbuf *pb;
5943 if (g_str_has_prefix(file, "file://"))
5944 file += strlen("file://");
5946 pb = gdk_pixbuf_new_from_file(file, NULL);
5947 if (pb) {
5948 xt_icon_from_pixbuf(t, pb);
5949 g_object_unref(pb);
5950 } else
5951 xt_icon_from_name(t, "text-html");
5954 gboolean
5955 is_valid_icon(char *file)
5957 gboolean valid = 0;
5958 const char *mime_type;
5959 GFileInfo *fi;
5960 GFile *gf;
5962 gf = g_file_new_for_path(file);
5963 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5964 NULL, NULL);
5965 mime_type = g_file_info_get_content_type(fi);
5966 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5967 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5968 g_strcmp0(mime_type, "image/png") == 0 ||
5969 g_strcmp0(mime_type, "image/gif") == 0 ||
5970 g_strcmp0(mime_type, "application/octet-stream") == 0;
5971 g_object_unref(fi);
5972 g_object_unref(gf);
5974 return (valid);
5977 void
5978 set_favicon_from_file(struct tab *t, char *file)
5980 struct stat sb;
5982 if (t == NULL || file == NULL)
5983 return;
5985 if (g_str_has_prefix(file, "file://"))
5986 file += strlen("file://");
5987 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5989 if (!stat(file, &sb)) {
5990 if (sb.st_size == 0 || !is_valid_icon(file)) {
5991 /* corrupt icon so trash it */
5992 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5993 __func__, file);
5994 unlink(file);
5995 /* no need to set icon to default here */
5996 return;
5999 xt_icon_from_file(t, file);
6002 void
6003 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6004 WebKitWebView *wv)
6006 WebKitDownloadStatus status = webkit_download_get_status(download);
6007 struct tab *tt = NULL, *t = NULL;
6010 * find the webview instead of passing in the tab as it could have been
6011 * deleted from underneath us.
6013 TAILQ_FOREACH(tt, &tabs, entry) {
6014 if (tt->wv == wv) {
6015 t = tt;
6016 break;
6019 if (t == NULL)
6020 return;
6022 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6023 __func__, t->tab_id, status);
6025 switch (status) {
6026 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6027 /* -1 */
6028 t->icon_download = NULL;
6029 free_favicon(t);
6030 break;
6031 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6032 /* 0 */
6033 break;
6034 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6035 /* 1 */
6036 break;
6037 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6038 /* 2 */
6039 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6040 __func__, t->tab_id);
6041 t->icon_download = NULL;
6042 free_favicon(t);
6043 break;
6044 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6045 /* 3 */
6047 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6048 __func__, t->icon_dest_uri);
6049 set_favicon_from_file(t, t->icon_dest_uri);
6050 /* these will be freed post callback */
6051 t->icon_request = NULL;
6052 t->icon_download = NULL;
6053 break;
6054 default:
6055 break;
6059 void
6060 abort_favicon_download(struct tab *t)
6062 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6064 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6065 if (t->icon_download) {
6066 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6067 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6068 webkit_download_cancel(t->icon_download);
6069 t->icon_download = NULL;
6070 } else
6071 free_favicon(t);
6072 #endif
6074 xt_icon_from_name(t, "text-html");
6077 void
6078 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6080 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6082 if (uri == NULL || t == NULL)
6083 return;
6085 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6086 /* take icon from WebKitIconDatabase */
6087 GdkPixbuf *pb;
6089 pb = webkit_web_view_get_icon_pixbuf(wv);
6090 if (pb) {
6091 xt_icon_from_pixbuf(t, pb);
6092 g_object_unref(pb);
6093 } else
6094 xt_icon_from_name(t, "text-html");
6095 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6096 /* download icon to cache dir */
6097 gchar *name_hash, file[PATH_MAX];
6098 struct stat sb;
6100 if (t->icon_request) {
6101 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6102 return;
6105 /* check to see if we got the icon in cache */
6106 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6107 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6108 g_free(name_hash);
6110 if (!stat(file, &sb)) {
6111 if (sb.st_size > 0) {
6112 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6113 __func__, file);
6114 set_favicon_from_file(t, file);
6115 return;
6118 /* corrupt icon so trash it */
6119 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6120 __func__, file);
6121 unlink(file);
6124 /* create download for icon */
6125 t->icon_request = webkit_network_request_new(uri);
6126 if (t->icon_request == NULL) {
6127 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6128 __func__, uri);
6129 return;
6132 t->icon_download = webkit_download_new(t->icon_request);
6133 if (t->icon_download == NULL)
6134 return;
6136 /* we have to free icon_dest_uri later */
6137 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6138 webkit_download_set_destination_uri(t->icon_download,
6139 t->icon_dest_uri);
6141 if (webkit_download_get_status(t->icon_download) ==
6142 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6143 g_object_unref(t->icon_request);
6144 g_free(t->icon_dest_uri);
6145 t->icon_request = NULL;
6146 t->icon_dest_uri = NULL;
6147 return;
6150 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6151 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6153 webkit_download_start(t->icon_download);
6154 #endif
6157 void
6158 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6160 const gchar *uri = NULL, *title = NULL;
6161 struct history *h, find;
6162 struct karg a;
6164 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6165 webkit_web_view_get_load_status(wview),
6166 get_uri(t) ? get_uri(t) : "NOTHING");
6168 if (t == NULL) {
6169 show_oops(NULL, "notify_load_status_cb invalid parameters");
6170 return;
6173 switch (webkit_web_view_get_load_status(wview)) {
6174 case WEBKIT_LOAD_PROVISIONAL:
6175 /* 0 */
6176 abort_favicon_download(t);
6177 #if GTK_CHECK_VERSION(2, 20, 0)
6178 gtk_widget_show(t->spinner);
6179 gtk_spinner_start(GTK_SPINNER(t->spinner));
6180 #endif
6181 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6183 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6185 /* take focus if we are visible */
6186 focus_webview(t);
6187 t->focus_wv = 1;
6189 break;
6191 case WEBKIT_LOAD_COMMITTED:
6192 /* 1 */
6193 uri = get_uri(t);
6194 if (uri == NULL)
6195 return;
6196 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6198 if (t->status) {
6199 g_free(t->status);
6200 t->status = NULL;
6202 set_status(t, (char *)uri, XT_STATUS_LOADING);
6204 /* check if js white listing is enabled */
6205 if (enable_js_whitelist) {
6206 check_and_set_js(uri, t);
6209 if (t->styled)
6210 apply_style(t);
6212 show_ca_status(t, uri);
6214 /* we know enough to autosave the session */
6215 if (session_autosave) {
6216 a.s = NULL;
6217 save_tabs(t, &a);
6219 break;
6221 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6222 /* 3 */
6223 break;
6225 case WEBKIT_LOAD_FINISHED:
6226 /* 2 */
6227 uri = get_uri(t);
6228 if (uri == NULL)
6229 return;
6231 if (!strncmp(uri, "http://", strlen("http://")) ||
6232 !strncmp(uri, "https://", strlen("https://")) ||
6233 !strncmp(uri, "file://", strlen("file://"))) {
6234 find.uri = uri;
6235 h = RB_FIND(history_list, &hl, &find);
6236 if (!h) {
6237 title = get_title(t, FALSE);
6238 h = g_malloc(sizeof *h);
6239 h->uri = g_strdup(uri);
6240 h->title = g_strdup(title);
6241 RB_INSERT(history_list, &hl, h);
6242 completion_add_uri(h->uri);
6243 update_history_tabs(NULL);
6247 set_status(t, (char *)uri, XT_STATUS_URI);
6248 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6249 case WEBKIT_LOAD_FAILED:
6250 /* 4 */
6251 #endif
6252 #if GTK_CHECK_VERSION(2, 20, 0)
6253 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6254 gtk_widget_hide(t->spinner);
6255 #endif
6256 default:
6257 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6258 break;
6261 if (t->item)
6262 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6263 else
6264 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6265 webkit_web_view_can_go_back(wview));
6267 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6268 webkit_web_view_can_go_forward(wview));
6271 gboolean
6272 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6273 gchar *uri, gpointer web_error,struct tab *t)
6275 if (t->tmp_uri)
6276 g_free(t->tmp_uri);
6277 t->tmp_uri = g_strdup(uri);
6278 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6279 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6280 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6281 set_status(t, uri, XT_STATUS_NOTHING);
6283 return (FALSE);
6286 void
6287 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6289 const gchar *title = NULL, *win_title = NULL;
6291 title = get_title(t, FALSE);
6292 win_title = get_title(t, TRUE);
6293 gtk_label_set_text(GTK_LABEL(t->label), title);
6294 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6295 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6296 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6299 void
6300 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6302 run_script(t, JS_HINTING);
6305 void
6306 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6308 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6309 progress == 100 ? 0 : (double)progress / 100);
6310 if (show_url == 0) {
6311 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6312 progress == 100 ? 0 : (double)progress / 100);
6315 update_statusbar_position(NULL, NULL);
6319 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6320 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6321 WebKitWebPolicyDecision *pd, struct tab *t)
6323 char *uri;
6324 WebKitWebNavigationReason reason;
6325 struct domain *d = NULL;
6327 if (t == NULL) {
6328 show_oops(NULL, "webview_npd_cb invalid parameters");
6329 return (FALSE);
6332 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6333 t->ctrl_click,
6334 webkit_network_request_get_uri(request));
6336 uri = (char *)webkit_network_request_get_uri(request);
6338 /* if this is an xtp url, we don't load anything else */
6339 if (parse_xtp_url(t, uri))
6340 return (TRUE);
6342 if (t->ctrl_click) {
6343 t->ctrl_click = 0;
6344 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6345 webkit_web_policy_decision_ignore(pd);
6346 return (TRUE); /* we made the decission */
6350 * This is a little hairy but it comes down to this:
6351 * when we run in whitelist mode we have to assist the browser in
6352 * opening the URL that it would have opened in a new tab.
6354 reason = webkit_web_navigation_action_get_reason(na);
6355 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6356 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6357 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6358 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6359 load_uri(t, uri);
6360 webkit_web_policy_decision_use(pd);
6361 return (TRUE); /* we made the decision */
6364 return (FALSE);
6367 WebKitWebView *
6368 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6370 struct tab *tt;
6371 struct domain *d = NULL;
6372 const gchar *uri;
6373 WebKitWebView *webview = NULL;
6375 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6376 webkit_web_view_get_uri(wv));
6378 if (tabless) {
6379 /* open in current tab */
6380 webview = t->wv;
6381 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6382 uri = webkit_web_view_get_uri(wv);
6383 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6384 return (NULL);
6386 tt = create_new_tab(NULL, NULL, 1, -1);
6387 webview = tt->wv;
6388 } else if (enable_scripts == 1) {
6389 tt = create_new_tab(NULL, NULL, 1, -1);
6390 webview = tt->wv;
6393 return (webview);
6396 gboolean
6397 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6399 const gchar *uri;
6400 struct domain *d = NULL;
6402 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6404 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6405 uri = webkit_web_view_get_uri(wv);
6406 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6407 return (FALSE);
6409 delete_tab(t);
6410 } else if (enable_scripts == 1)
6411 delete_tab(t);
6413 return (TRUE);
6417 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6419 /* we can not eat the event without throwing gtk off so defer it */
6421 /* catch middle click */
6422 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6423 t->ctrl_click = 1;
6424 goto done;
6427 /* catch ctrl click */
6428 if (e->type == GDK_BUTTON_RELEASE &&
6429 CLEAN(e->state) == GDK_CONTROL_MASK)
6430 t->ctrl_click = 1;
6431 else
6432 t->ctrl_click = 0;
6433 done:
6434 return (XT_CB_PASSTHROUGH);
6438 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6440 struct mime_type *m;
6442 m = find_mime_type(mime_type);
6443 if (m == NULL)
6444 return (1);
6445 if (m->mt_download)
6446 return (1);
6448 switch (fork()) {
6449 case -1:
6450 show_oops(t, "can't fork mime handler");
6451 return (1);
6452 /* NOTREACHED */
6453 case 0:
6454 break;
6455 default:
6456 return (0);
6459 /* child */
6460 execlp(m->mt_action, m->mt_action,
6461 webkit_network_request_get_uri(request), (void *)NULL);
6463 _exit(0);
6465 /* NOTREACHED */
6466 return (0);
6469 const gchar *
6470 get_mime_type(char *file)
6472 const char *mime_type;
6473 GFileInfo *fi;
6474 GFile *gf;
6476 if (g_str_has_prefix(file, "file://"))
6477 file += strlen("file://");
6479 gf = g_file_new_for_path(file);
6480 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6481 NULL, NULL);
6482 mime_type = g_file_info_get_content_type(fi);
6483 g_object_unref(fi);
6484 g_object_unref(gf);
6486 return (mime_type);
6490 run_download_mimehandler(char *mime_type, char *file)
6492 struct mime_type *m;
6494 m = find_mime_type(mime_type);
6495 if (m == NULL)
6496 return (1);
6498 switch (fork()) {
6499 case -1:
6500 show_oops(NULL, "can't fork download mime handler");
6501 return (1);
6502 /* NOTREACHED */
6503 case 0:
6504 break;
6505 default:
6506 return (0);
6509 /* child */
6510 if (g_str_has_prefix(file, "file://"))
6511 file += strlen("file://");
6512 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6514 _exit(0);
6516 /* NOTREACHED */
6517 return (0);
6520 void
6521 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6522 WebKitWebView *wv)
6524 WebKitDownloadStatus status;
6525 const gchar *file = NULL, *mime = NULL;
6527 if (download == NULL)
6528 return;
6529 status = webkit_download_get_status(download);
6530 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6531 return;
6533 file = webkit_download_get_destination_uri(download);
6534 if (file == NULL)
6535 return;
6536 mime = get_mime_type((char *)file);
6537 if (mime == NULL)
6538 return;
6540 run_download_mimehandler((char *)mime, (char *)file);
6544 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6545 WebKitNetworkRequest *request, char *mime_type,
6546 WebKitWebPolicyDecision *decision, struct tab *t)
6548 if (t == NULL) {
6549 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6550 return (FALSE);
6553 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6554 t->tab_id, mime_type);
6556 if (run_mimehandler(t, mime_type, request) == 0) {
6557 webkit_web_policy_decision_ignore(decision);
6558 focus_webview(t);
6559 return (TRUE);
6562 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6563 webkit_web_policy_decision_download(decision);
6564 return (TRUE);
6567 return (FALSE);
6571 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6572 struct tab *t)
6574 struct stat sb;
6575 const gchar *suggested_name;
6576 gchar *filename = NULL;
6577 char *uri = NULL;
6578 struct download *download_entry;
6579 int i, ret = TRUE;
6581 if (wk_download == NULL || t == NULL) {
6582 show_oops(NULL, "%s invalid parameters", __func__);
6583 return (FALSE);
6586 suggested_name = webkit_download_get_suggested_filename(wk_download);
6587 if (suggested_name == NULL)
6588 return (FALSE); /* abort download */
6590 i = 0;
6591 do {
6592 if (filename) {
6593 g_free(filename);
6594 filename = NULL;
6596 if (i) {
6597 g_free(uri);
6598 uri = NULL;
6599 filename = g_strdup_printf("%d%s", i, suggested_name);
6601 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6602 filename : suggested_name);
6603 i++;
6604 } while (!stat(uri + strlen("file://"), &sb));
6606 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6607 "local %s\n", __func__, t->tab_id, filename, uri);
6609 webkit_download_set_destination_uri(wk_download, uri);
6611 if (webkit_download_get_status(wk_download) ==
6612 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6613 show_oops(t, "%s: download failed to start", __func__);
6614 ret = FALSE;
6615 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6616 } else {
6617 /* connect "download first" mime handler */
6618 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6619 G_CALLBACK(download_status_changed_cb), NULL);
6621 download_entry = g_malloc(sizeof(struct download));
6622 download_entry->download = wk_download;
6623 download_entry->tab = t;
6624 download_entry->id = next_download_id++;
6625 RB_INSERT(download_list, &downloads, download_entry);
6626 /* get from history */
6627 g_object_ref(wk_download);
6628 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6629 show_oops(t, "Download of '%s' started...",
6630 basename((char *)webkit_download_get_destination_uri(wk_download)));
6633 if (uri)
6634 g_free(uri);
6636 if (filename)
6637 g_free(filename);
6639 /* sync other download manager tabs */
6640 update_download_tabs(NULL);
6643 * NOTE: never redirect/render the current tab before this
6644 * function returns. This will cause the download to never start.
6646 return (ret); /* start download */
6649 void
6650 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6652 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6654 if (t == NULL) {
6655 show_oops(NULL, "webview_hover_cb");
6656 return;
6659 if (uri)
6660 set_status(t, uri, XT_STATUS_LINK);
6661 else {
6662 if (t->status)
6663 set_status(t, t->status, XT_STATUS_NOTHING);
6668 mark(struct tab *t, struct karg *arg)
6670 char mark;
6671 int index;
6673 mark = arg->s[1];
6674 if ((index = marktoindex(mark)) == -1)
6675 return -1;
6677 if (arg->i == XT_MARK_SET)
6678 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6679 else if (arg->i == XT_MARK_GOTO) {
6680 if (t->mark[index] == XT_INVALID_MARK) {
6681 show_oops(t, "mark '%c' does not exist", mark);
6682 return -1;
6684 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6685 something changes the document size */
6686 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6689 return 0;
6692 void
6693 marks_clear(struct tab *t)
6695 int i;
6697 for (i = 0; i < LENGTH(t->mark); i++)
6698 t->mark[i] = XT_INVALID_MARK;
6702 qmarks_load()
6704 char file[PATH_MAX];
6705 char *line = NULL, *p, mark;
6706 int index, i;
6707 FILE *f;
6708 size_t linelen;
6710 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6711 if ((f = fopen(file, "r+")) == NULL) {
6712 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6713 return (1);
6716 for (i = 0; ; i++) {
6717 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
6718 if (feof(f) || ferror(f))
6719 break;
6720 p = strtok(line, " \t");
6722 if (p == NULL || strlen(p) != 1 || (index = marktoindex(*p)) == -1) {
6723 warnx("corrupt quickmarks file, line %d", i);
6724 break;
6727 mark = *p;
6728 p = strtok(NULL, " \t");
6729 if (qmarks[index] != NULL)
6730 g_free(qmarks[index]);
6731 qmarks[index] = g_strdup(p);
6734 fclose(f);
6736 return (0);
6740 qmarks_save()
6742 char file[PATH_MAX];
6743 int i;
6744 FILE *f;
6746 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6747 if ((f = fopen(file, "r+")) == NULL) {
6748 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6749 return (1);
6752 for (i = 0; i < XT_NOMARKS; i++)
6753 if (qmarks[i] != NULL)
6754 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
6756 fclose(f);
6758 return (0);
6762 qmark(struct tab *t, struct karg *arg)
6764 char mark;
6765 int index;
6767 mark = arg->s[strlen(arg->s)-1];
6768 index = marktoindex(mark);
6769 if (index == -1)
6770 return (-1);
6772 switch (arg->i) {
6773 case XT_QMARK_SET:
6774 if (qmarks[index] != NULL)
6775 g_free(qmarks[index]);
6777 qmarks_load(); /* sync if multiple instances */
6778 qmarks[index] = g_strdup(get_uri(t));
6779 qmarks_save();
6780 break;
6781 case XT_QMARK_OPEN:
6782 if (qmarks[index] != NULL)
6783 load_uri(t, qmarks[index]);
6784 else
6785 return (-1);
6786 break;
6787 case XT_QMARK_TAB:
6788 if (qmarks[index] != NULL)
6789 create_new_tab(qmarks[index], NULL, 1, -1);
6790 else
6791 return (-1);
6792 break;
6795 return (0);
6799 go_up(struct tab *t, struct karg *args)
6801 int levels;
6802 char *uri;
6803 char *tmp;
6805 levels = atoi(args->s);
6806 if (levels == 0)
6807 levels = 1;
6809 uri = g_strdup(webkit_web_view_get_uri(t->wv));
6810 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
6811 return 1;
6812 tmp += strlen(XT_PROTO_DELIM);
6814 /* if an uri starts with a slash, leave it alone (for file:///) */
6815 if (tmp[0] == '/')
6816 tmp++;
6818 while (levels--) {
6819 char *p;
6821 p = strrchr(tmp, '/');
6822 if (p != NULL)
6823 *p = '\0';
6824 else
6825 break;
6828 load_uri(t, uri);
6829 g_free(uri);
6831 return 0;
6835 gototab(struct tab *t, struct karg *args)
6837 int tab;
6838 struct karg arg = {0, NULL, -1};
6840 tab = atoi(args->s);
6842 arg.i = XT_TAB_NEXT;
6843 arg.p = tab;
6845 movetab(t, &arg);
6847 return 0;
6851 zoom_amount(struct tab *t, struct karg *arg)
6853 struct karg narg = {0, NULL, -1};
6855 narg.i = atoi(arg->s);
6856 resizetab(t, &narg);
6858 return 0;
6861 /* buffer commands receive the regex that triggered them in arg.s */
6862 char bcmd[8];
6863 struct buffercmd {
6864 char *regex;
6865 int (*func)(struct tab *, struct karg *);
6866 int arg;
6867 regex_t cregex;
6868 } buffercmds[] = {
6869 { "^[0-9]*gu$", go_up, 0 },
6870 { "^gg$", move, XT_MOVE_TOP },
6871 { "^gG$", move, XT_MOVE_BOTTOM },
6872 { "^[0-9]+%$", move, XT_MOVE_PERCENT },
6873 { "^gh$", go_home, 0 },
6874 { "^m[a-zA-Z0-9]$", mark, XT_MARK_SET },
6875 { "^[`'][a-zA-Z0-9]$", mark, XT_MARK_GOTO },
6876 { "^[0-9]+t$", gototab, 0 },
6877 { "^M[a-zA-Z0-9]$", qmark, XT_QMARK_SET },
6878 { "^go[a-zA-Z0-9]$", qmark, XT_QMARK_OPEN },
6879 { "^gn[a-zA-Z0-9]$", qmark, XT_QMARK_TAB },
6880 { "^ZR$", restart, 0 },
6881 { "^ZZ$", quit, 0 },
6882 { "^zi$", resizetab, XT_ZOOM_IN },
6883 { "^zo$", resizetab, XT_ZOOM_OUT },
6884 { "^z0$", resizetab, XT_ZOOM_NORMAL },
6885 { "^[0-9]+Z$", zoom_amount, 0 },
6888 void
6889 buffercmd_init()
6891 int i;
6893 for (i = 0; i < LENGTH(buffercmds); i++)
6894 regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
6895 REG_EXTENDED);
6898 void
6899 buffercmd_abort(struct tab *t)
6901 int i;
6903 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
6904 for (i = 0; i < LENGTH(bcmd); i++)
6905 bcmd[i] = '\0';
6907 cmd_prefix = 0; /* clear prefix for non-buffer commands */
6908 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6911 void
6912 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
6914 struct karg arg = {0, NULL, -1};
6916 arg.i = cmd->arg;
6917 arg.s = g_strdup(bcmd);
6919 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
6920 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
6921 cmd->func(t, &arg);
6923 if (arg.s)
6924 g_free(arg.s);
6926 buffercmd_abort(t);
6929 gboolean
6930 buffercmd_addkey(struct tab *t, guint keyval)
6932 int i;
6934 if (keyval == GDK_Escape) {
6935 buffercmd_abort(t);
6936 return (XT_CB_HANDLED);
6939 /* key with modifier or non-ascii character */
6940 if (!isascii(keyval))
6941 return (XT_CB_PASSTHROUGH);
6943 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
6944 "to buffer \"%s\"\n", keyval, bcmd);
6946 for (i = 0; i < LENGTH(bcmd); i++)
6947 if (bcmd[i] == '\0') {
6948 bcmd[i] = keyval;
6949 break;
6952 /* buffer full, ignore input */
6953 if (i == LENGTH(bcmd)) {
6954 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
6955 return (XT_CB_HANDLED);
6958 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6960 for (i = 0; i < LENGTH(buffercmds); i++)
6961 if (regexec(&buffercmds[i].cregex, bcmd,
6962 (size_t) 0, NULL, 0) == 0) {
6963 buffercmd_execute(t, &buffercmds[i]);
6964 break;
6967 return (XT_CB_HANDLED);
6970 gboolean
6971 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6973 struct key_binding *k;
6975 /* handle keybindings if buffercmd is empty.
6976 if not empty, allow commands like C-n */
6977 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
6978 TAILQ_FOREACH(k, &kbl, entry)
6979 if (e->keyval == k->key
6980 && (entry ? k->use_in_entry : 1)) {
6981 if (k->mask == 0) {
6982 if ((e->state & (CTRL | MOD1)) == 0)
6983 return (cmd_execute(t, k->cmd));
6984 } else if ((e->state & k->mask) == k->mask) {
6985 return (cmd_execute(t, k->cmd));
6989 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
6990 return buffercmd_addkey(t, e->keyval);
6992 return (XT_CB_PASSTHROUGH);
6996 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6998 char s[2], buf[128];
6999 const char *errstr = NULL;
7001 /* don't use w directly; use t->whatever instead */
7003 if (t == NULL) {
7004 show_oops(NULL, "wv_keypress_after_cb");
7005 return (XT_CB_PASSTHROUGH);
7008 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7009 e->keyval, e->state, t);
7011 if (t->hints_on) {
7012 /* ESC */
7013 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7014 disable_hints(t);
7015 return (XT_CB_HANDLED);
7018 /* RETURN */
7019 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7020 if (errstr) {
7021 /* we have a string */
7022 } else {
7023 /* we have a number */
7024 snprintf(buf, sizeof buf,
7025 "vimprobable_fire(%s)", t->hint_num);
7026 run_script(t, buf);
7028 disable_hints(t);
7031 /* BACKSPACE */
7032 /* XXX unfuck this */
7033 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7034 if (t->hint_mode == XT_HINT_NUMERICAL) {
7035 /* last input was numerical */
7036 int l;
7037 l = strlen(t->hint_num);
7038 if (l > 0) {
7039 l--;
7040 if (l == 0) {
7041 disable_hints(t);
7042 enable_hints(t);
7043 } else {
7044 t->hint_num[l] = '\0';
7045 goto num;
7048 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7049 /* last input was alphanumerical */
7050 int l;
7051 l = strlen(t->hint_buf);
7052 if (l > 0) {
7053 l--;
7054 if (l == 0) {
7055 disable_hints(t);
7056 enable_hints(t);
7057 } else {
7058 t->hint_buf[l] = '\0';
7059 goto anum;
7062 } else {
7063 /* bogus */
7064 disable_hints(t);
7068 /* numerical input */
7069 if (CLEAN(e->state) == 0 &&
7070 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7071 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7072 snprintf(s, sizeof s, "%c", e->keyval);
7073 strlcat(t->hint_num, s, sizeof t->hint_num);
7074 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7075 t->hint_num);
7076 num:
7077 if (errstr) {
7078 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7079 "invalid link number\n");
7080 disable_hints(t);
7081 } else {
7082 snprintf(buf, sizeof buf,
7083 "vimprobable_update_hints(%s)",
7084 t->hint_num);
7085 t->hint_mode = XT_HINT_NUMERICAL;
7086 run_script(t, buf);
7089 /* empty the counter buffer */
7090 bzero(t->hint_buf, sizeof t->hint_buf);
7091 return (XT_CB_HANDLED);
7094 /* alphanumerical input */
7095 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7096 e->keyval <= GDK_z) ||
7097 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7098 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7099 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7100 e->keyval <= GDK_9) ||
7101 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7102 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7103 snprintf(s, sizeof s, "%c", e->keyval);
7104 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7105 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7106 " %s\n", t->hint_buf);
7107 anum:
7108 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7109 run_script(t, buf);
7111 snprintf(buf, sizeof buf,
7112 "vimprobable_show_hints('%s')", t->hint_buf);
7113 t->hint_mode = XT_HINT_ALPHANUM;
7114 run_script(t, buf);
7116 /* empty the counter buffer */
7117 bzero(t->hint_num, sizeof t->hint_num);
7118 return (XT_CB_HANDLED);
7121 return (XT_CB_HANDLED);
7122 } else {
7123 /* prefix input*/
7124 snprintf(s, sizeof s, "%c", e->keyval);
7125 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7126 cmd_prefix = 10 * cmd_prefix + atoi(s);
7129 return (handle_keypress(t, e, 0));
7133 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7135 hide_oops(t);
7137 /* Hide buffers, if they are visible, with escape. */
7138 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7139 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7140 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7141 hide_buffers(t);
7142 return (XT_CB_HANDLED);
7145 return (XT_CB_PASSTHROUGH);
7149 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7151 const gchar *c = gtk_entry_get_text(w);
7152 GdkColor color;
7153 int forward = TRUE;
7155 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7156 e->keyval, e->state, t);
7158 if (t == NULL) {
7159 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7160 return (XT_CB_PASSTHROUGH);
7163 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7164 e->keyval, e->state, t);
7166 if (c[0] == ':')
7167 goto done;
7168 if (strlen(c) == 1) {
7169 webkit_web_view_unmark_text_matches(t->wv);
7170 goto done;
7173 if (c[0] == '/')
7174 forward = TRUE;
7175 else if (c[0] == '?')
7176 forward = FALSE;
7177 else
7178 goto done;
7180 /* search */
7181 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
7182 FALSE) {
7183 /* not found, mark red */
7184 gdk_color_parse(XT_COLOR_RED, &color);
7185 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7186 /* unmark and remove selection */
7187 webkit_web_view_unmark_text_matches(t->wv);
7188 /* my kingdom for a way to unselect text in webview */
7189 } else {
7190 /* found, highlight all */
7191 webkit_web_view_unmark_text_matches(t->wv);
7192 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7193 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7194 gdk_color_parse(XT_COLOR_WHITE, &color);
7195 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7197 done:
7198 return (XT_CB_PASSTHROUGH);
7201 gboolean
7202 match_uri(const gchar *uri, const gchar *key) {
7203 gchar *voffset;
7204 size_t len;
7205 gboolean match = FALSE;
7207 len = strlen(key);
7209 if (!strncmp(key, uri, len))
7210 match = TRUE;
7211 else {
7212 voffset = strstr(uri, "/") + 2;
7213 if (!strncmp(key, voffset, len))
7214 match = TRUE;
7215 else if (g_str_has_prefix(voffset, "www.")) {
7216 voffset = voffset + strlen("www.");
7217 if (!strncmp(key, voffset, len))
7218 match = TRUE;
7222 return (match);
7225 void
7226 cmd_getlist(int id, char *key)
7228 int i, dep, c = 0;
7229 struct history *h;
7231 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7232 RB_FOREACH_REVERSE(h, history_list, &hl)
7233 if (match_uri(h->uri, key)) {
7234 cmd_status.list[c] = (char *)h->uri;
7235 if (++c > 255)
7236 break;
7239 cmd_status.len = c;
7240 return;
7243 dep = (id == -1) ? 0 : cmds[id].level + 1;
7245 for (i = id + 1; i < LENGTH(cmds); i++) {
7246 if (cmds[i].level < dep)
7247 break;
7248 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7249 strlen(key)))
7250 cmd_status.list[c++] = cmds[i].cmd;
7254 cmd_status.len = c;
7257 char *
7258 cmd_getnext(int dir)
7260 cmd_status.index += dir;
7262 if (cmd_status.index < 0)
7263 cmd_status.index = cmd_status.len - 1;
7264 else if (cmd_status.index >= cmd_status.len)
7265 cmd_status.index = 0;
7267 return cmd_status.list[cmd_status.index];
7271 cmd_tokenize(char *s, char *tokens[])
7273 int i = 0;
7274 char *tok, *last;
7275 size_t len = strlen(s);
7276 bool blank;
7278 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7279 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7280 tok = strtok_r(NULL, " ", &last), i++)
7281 tokens[i] = tok;
7283 if (blank && i < 3)
7284 tokens[i++] = "";
7286 return (i);
7289 void
7290 cmd_complete(struct tab *t, char *str, int dir)
7292 GtkEntry *w = GTK_ENTRY(t->cmd);
7293 int i, j, levels, c = 0, dep = 0, parent = -1;
7294 int matchcount = 0;
7295 char *tok, *match, *s = g_strdup(str);
7296 char *tokens[3];
7297 char res[XT_MAX_URL_LENGTH + 32] = ":";
7298 char *sc = s;
7300 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7302 /* copy prefix*/
7303 for (i = 0; isdigit(s[i]); i++)
7304 res[i + 1] = s[i];
7306 for (; isspace(s[i]); i++)
7307 res[i + 1] = s[i];
7309 s += i;
7311 levels = cmd_tokenize(s, tokens);
7313 for (i = 0; i < levels - 1; i++) {
7314 tok = tokens[i];
7315 matchcount = 0;
7316 for (j = c; j < LENGTH(cmds); j++) {
7317 if (cmds[j].level < dep)
7318 break;
7319 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7320 strlen(tok))) {
7321 matchcount++;
7322 c = j + 1;
7323 if (strlen(tok) == strlen(cmds[j].cmd)) {
7324 matchcount = 1;
7325 break;
7330 if (matchcount == 1) {
7331 strlcat(res, tok, sizeof res);
7332 strlcat(res, " ", sizeof res);
7333 dep++;
7334 } else {
7335 g_free(sc);
7336 return;
7339 parent = c - 1;
7342 if (cmd_status.index == -1)
7343 cmd_getlist(parent, tokens[i]);
7345 if (cmd_status.len > 0) {
7346 match = cmd_getnext(dir);
7347 strlcat(res, match, sizeof res);
7348 gtk_entry_set_text(w, res);
7349 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7352 g_free(sc);
7355 gboolean
7356 cmd_execute(struct tab *t, char *str)
7358 struct cmd *cmd = NULL;
7359 char *tok, *last, *s = g_strdup(str), *sc;
7360 char prefixstr[4];
7361 int j, len, c = 0, dep = 0, matchcount = 0;
7362 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7363 struct karg arg = {0, NULL, -1};
7365 sc = s;
7367 /* copy prefix*/
7368 for (j = 0; j<3 && isdigit(s[j]); j++)
7369 prefixstr[j]=s[j];
7371 prefixstr[j]='\0';
7373 s += j;
7374 while (isspace(s[0]))
7375 s++;
7377 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7378 prefix = atoi(prefixstr);
7379 else
7380 s = sc;
7382 for (tok = strtok_r(s, " ", &last); tok;
7383 tok = strtok_r(NULL, " ", &last)) {
7384 matchcount = 0;
7385 for (j = c; j < LENGTH(cmds); j++) {
7386 if (cmds[j].level < dep)
7387 break;
7388 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7389 strlen(tok);
7390 if (cmds[j].level == dep &&
7391 !strncmp(tok, cmds[j].cmd, len)) {
7392 matchcount++;
7393 c = j + 1;
7394 cmd = &cmds[j];
7395 if (len == strlen(cmds[j].cmd)) {
7396 matchcount = 1;
7397 break;
7401 if (matchcount == 1) {
7402 if (cmd->type > 0)
7403 goto execute_cmd;
7404 dep++;
7405 } else {
7406 show_oops(t, "Invalid command: %s", str);
7407 goto done;
7410 execute_cmd:
7411 arg.i = cmd->arg;
7413 if (prefix != -1)
7414 arg.p = prefix;
7415 else if (cmd_prefix > 0)
7416 arg.p = cmd_prefix;
7418 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
7419 show_oops(t, "No prefix allowed: %s", str);
7420 goto done;
7422 if (cmd->type > 1)
7423 arg.s = last ? g_strdup(last) : g_strdup("");
7424 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7425 arg.p = atoi(arg.s);
7426 if (arg.p <= 0) {
7427 if (arg.s[0]=='0')
7428 show_oops(t, "Zero count");
7429 else
7430 show_oops(t, "Trailing characters");
7431 goto done;
7435 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
7437 cmd->func(t, &arg);
7439 rv = XT_CB_HANDLED;
7440 done:
7441 if (j > 0)
7442 cmd_prefix = 0;
7443 g_free(sc);
7444 if (arg.s)
7445 g_free(arg.s);
7447 return (rv);
7451 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7453 if (t == NULL) {
7454 show_oops(NULL, "entry_key_cb invalid parameters");
7455 return (XT_CB_PASSTHROUGH);
7458 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7459 e->keyval, e->state, t);
7461 hide_oops(t);
7463 if (e->keyval == GDK_Escape) {
7464 /* don't use focus_webview(t) because we want to type :cmds */
7465 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7468 return (handle_keypress(t, e, 1));
7472 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7474 int rv = XT_CB_HANDLED;
7475 const gchar *c = gtk_entry_get_text(w);
7477 if (t == NULL) {
7478 show_oops(NULL, "cmd_keypress_cb parameters");
7479 return (XT_CB_PASSTHROUGH);
7482 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7483 e->keyval, e->state, t);
7485 /* sanity */
7486 if (c == NULL)
7487 e->keyval = GDK_Escape;
7488 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7489 e->keyval = GDK_Escape;
7491 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7492 e->keyval != GDK_ISO_Left_Tab)
7493 cmd_status.index = -1;
7495 switch (e->keyval) {
7496 case GDK_Tab:
7497 if (c[0] == ':')
7498 cmd_complete(t, (char *)&c[1], 1);
7499 goto done;
7500 case GDK_ISO_Left_Tab:
7501 if (c[0] == ':')
7502 cmd_complete(t, (char *)&c[1], -1);
7504 goto done;
7505 case GDK_BackSpace:
7506 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7507 break;
7508 /* FALLTHROUGH */
7509 case GDK_Escape:
7510 hide_cmd(t);
7511 focus_webview(t);
7513 /* cancel search */
7514 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7515 webkit_web_view_unmark_text_matches(t->wv);
7516 goto done;
7519 rv = XT_CB_PASSTHROUGH;
7520 done:
7521 return (rv);
7525 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7527 if (t == NULL) {
7528 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7529 return (XT_CB_PASSTHROUGH);
7531 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7533 hide_cmd(t);
7534 hide_oops(t);
7536 if (show_url == 0 || t->focus_wv)
7537 focus_webview(t);
7538 else
7539 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7541 return (XT_CB_PASSTHROUGH);
7544 void
7545 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7547 char *s;
7548 const gchar *c = gtk_entry_get_text(entry);
7550 if (t == NULL) {
7551 show_oops(NULL, "cmd_activate_cb invalid parameters");
7552 return;
7555 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7557 hide_cmd(t);
7559 /* sanity */
7560 if (c == NULL)
7561 goto done;
7562 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7563 goto done;
7564 if (strlen(c) < 2)
7565 goto done;
7566 s = (char *)&c[1];
7568 if (c[0] == '/' || c[0] == '?') {
7569 if (t->search_text) {
7570 g_free(t->search_text);
7571 t->search_text = NULL;
7574 t->search_text = g_strdup(s);
7575 if (global_search)
7576 g_free(global_search);
7577 global_search = g_strdup(s);
7578 t->search_forward = c[0] == '/';
7580 goto done;
7583 cmd_execute(t, s);
7585 done:
7586 return;
7589 void
7590 backward_cb(GtkWidget *w, struct tab *t)
7592 struct karg a;
7594 if (t == NULL) {
7595 show_oops(NULL, "backward_cb invalid parameters");
7596 return;
7599 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7601 a.i = XT_NAV_BACK;
7602 navaction(t, &a);
7605 void
7606 forward_cb(GtkWidget *w, struct tab *t)
7608 struct karg a;
7610 if (t == NULL) {
7611 show_oops(NULL, "forward_cb invalid parameters");
7612 return;
7615 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7617 a.i = XT_NAV_FORWARD;
7618 navaction(t, &a);
7621 void
7622 home_cb(GtkWidget *w, struct tab *t)
7624 if (t == NULL) {
7625 show_oops(NULL, "home_cb invalid parameters");
7626 return;
7629 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7631 load_uri(t, home);
7634 void
7635 stop_cb(GtkWidget *w, struct tab *t)
7637 WebKitWebFrame *frame;
7639 if (t == NULL) {
7640 show_oops(NULL, "stop_cb invalid parameters");
7641 return;
7644 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7646 frame = webkit_web_view_get_main_frame(t->wv);
7647 if (frame == NULL) {
7648 show_oops(t, "stop_cb: no frame");
7649 return;
7652 webkit_web_frame_stop_loading(frame);
7653 abort_favicon_download(t);
7656 void
7657 setup_webkit(struct tab *t)
7659 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7660 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7661 FALSE, (char *)NULL);
7662 else
7663 warnx("webkit does not have \"enable-dns-prefetching\" property");
7664 g_object_set(G_OBJECT(t->settings),
7665 "user-agent", t->user_agent, (char *)NULL);
7666 g_object_set(G_OBJECT(t->settings),
7667 "enable-scripts", enable_scripts, (char *)NULL);
7668 g_object_set(G_OBJECT(t->settings),
7669 "enable-plugins", enable_plugins, (char *)NULL);
7670 g_object_set(G_OBJECT(t->settings),
7671 "javascript-can-open-windows-automatically", enable_scripts,
7672 (char *)NULL);
7673 g_object_set(G_OBJECT(t->settings),
7674 "enable-html5-database", FALSE, (char *)NULL);
7675 g_object_set(G_OBJECT(t->settings),
7676 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7677 g_object_set(G_OBJECT(t->settings),
7678 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7679 g_object_set(G_OBJECT(t->settings),
7680 "spell_checking_languages", spell_check_languages, (char *)NULL);
7681 g_object_set(G_OBJECT(t->wv),
7682 "full-content-zoom", TRUE, (char *)NULL);
7684 webkit_web_view_set_settings(t->wv, t->settings);
7687 gboolean
7688 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7690 struct tab *ti, *t = NULL;
7691 gdouble view_size, value, max;
7692 gchar *position;
7694 TAILQ_FOREACH(ti, &tabs, entry)
7695 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7696 t = ti;
7697 break;
7700 if (t == NULL)
7701 return FALSE;
7703 if (adjustment == NULL)
7704 adjustment = gtk_scrolled_window_get_vadjustment(
7705 GTK_SCROLLED_WINDOW(t->browser_win));
7707 view_size = gtk_adjustment_get_page_size(adjustment);
7708 value = gtk_adjustment_get_value(adjustment);
7709 max = gtk_adjustment_get_upper(adjustment) - view_size;
7711 if (max == 0)
7712 position = g_strdup("All");
7713 else if (value == max)
7714 position = g_strdup("Bot");
7715 else if (value == 0)
7716 position = g_strdup("Top");
7717 else
7718 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7720 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7721 g_free(position);
7723 return (TRUE);
7726 GtkWidget *
7727 create_browser(struct tab *t)
7729 GtkWidget *w;
7730 gchar *strval;
7731 GtkAdjustment *adjustment;
7733 if (t == NULL) {
7734 show_oops(NULL, "create_browser invalid parameters");
7735 return (NULL);
7738 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7739 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7740 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7741 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7743 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7744 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7745 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7747 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7748 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7750 /* set defaults */
7751 t->settings = webkit_web_settings_new();
7753 if (user_agent == NULL) {
7754 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7755 (char *)NULL);
7756 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7757 g_free(strval);
7758 } else
7759 t->user_agent = g_strdup(user_agent);
7761 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7763 adjustment =
7764 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7765 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7766 G_CALLBACK(update_statusbar_position), NULL);
7768 setup_webkit(t);
7770 return (w);
7773 GtkWidget *
7774 create_window(void)
7776 GtkWidget *w;
7778 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7779 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7780 gtk_widget_set_name(w, "xxxterm");
7781 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7782 g_signal_connect(G_OBJECT(w), "delete_event",
7783 G_CALLBACK (gtk_main_quit), NULL);
7785 return (w);
7788 GtkWidget *
7789 create_kiosk_toolbar(struct tab *t)
7791 GtkWidget *toolbar = NULL, *b;
7793 b = gtk_hbox_new(FALSE, 0);
7794 toolbar = b;
7795 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7797 /* backward button */
7798 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7799 gtk_widget_set_sensitive(t->backward, FALSE);
7800 g_signal_connect(G_OBJECT(t->backward), "clicked",
7801 G_CALLBACK(backward_cb), t);
7802 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7804 /* forward button */
7805 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7806 gtk_widget_set_sensitive(t->forward, FALSE);
7807 g_signal_connect(G_OBJECT(t->forward), "clicked",
7808 G_CALLBACK(forward_cb), t);
7809 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7811 /* home button */
7812 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7813 gtk_widget_set_sensitive(t->gohome, true);
7814 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7815 G_CALLBACK(home_cb), t);
7816 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7818 /* create widgets but don't use them */
7819 t->uri_entry = gtk_entry_new();
7820 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7821 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7822 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7824 return (toolbar);
7827 GtkWidget *
7828 create_toolbar(struct tab *t)
7830 GtkWidget *toolbar = NULL, *b, *eb1;
7832 b = gtk_hbox_new(FALSE, 0);
7833 toolbar = b;
7834 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7836 if (fancy_bar) {
7837 /* backward button */
7838 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7839 gtk_widget_set_sensitive(t->backward, FALSE);
7840 g_signal_connect(G_OBJECT(t->backward), "clicked",
7841 G_CALLBACK(backward_cb), t);
7842 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7844 /* forward button */
7845 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7846 gtk_widget_set_sensitive(t->forward, FALSE);
7847 g_signal_connect(G_OBJECT(t->forward), "clicked",
7848 G_CALLBACK(forward_cb), t);
7849 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7850 FALSE, 0);
7852 /* stop button */
7853 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7854 gtk_widget_set_sensitive(t->stop, FALSE);
7855 g_signal_connect(G_OBJECT(t->stop), "clicked",
7856 G_CALLBACK(stop_cb), t);
7857 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7858 FALSE, 0);
7860 /* JS button */
7861 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7862 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7863 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7864 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7865 G_CALLBACK(js_toggle_cb), t);
7866 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7869 t->uri_entry = gtk_entry_new();
7870 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7871 G_CALLBACK(activate_uri_entry_cb), t);
7872 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7873 G_CALLBACK(entry_key_cb), t);
7874 completion_add(t);
7875 eb1 = gtk_hbox_new(FALSE, 0);
7876 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7877 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7878 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7880 /* search entry */
7881 if (fancy_bar && search_string) {
7882 GtkWidget *eb2;
7883 t->search_entry = gtk_entry_new();
7884 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7885 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7886 G_CALLBACK(activate_search_entry_cb), t);
7887 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7888 G_CALLBACK(entry_key_cb), t);
7889 gtk_widget_set_size_request(t->search_entry, -1, -1);
7890 eb2 = gtk_hbox_new(FALSE, 0);
7891 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7892 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7894 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7896 return (toolbar);
7899 GtkWidget *
7900 create_buffers(struct tab *t)
7902 GtkCellRenderer *renderer;
7903 GtkWidget *view;
7905 view = gtk_tree_view_new();
7907 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7909 renderer = gtk_cell_renderer_text_new();
7910 gtk_tree_view_insert_column_with_attributes
7911 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7913 renderer = gtk_cell_renderer_text_new();
7914 gtk_tree_view_insert_column_with_attributes
7915 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
7916 NULL);
7918 gtk_tree_view_set_model
7919 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7921 return view;
7924 void
7925 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7926 GtkTreeViewColumn *col, struct tab *t)
7928 GtkTreeIter iter;
7929 guint id;
7931 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7933 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
7934 path)) {
7935 gtk_tree_model_get
7936 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7937 set_current_tab(id - 1);
7940 hide_buffers(t);
7943 /* after tab reordering/creation/removal */
7944 void
7945 recalc_tabs(void)
7947 struct tab *t;
7948 int maxid = 0;
7950 TAILQ_FOREACH(t, &tabs, entry) {
7951 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7952 if (t->tab_id > maxid)
7953 maxid = t->tab_id;
7955 gtk_widget_show(t->tab_elems.sep);
7958 TAILQ_FOREACH(t, &tabs, entry) {
7959 if (t->tab_id == maxid) {
7960 gtk_widget_hide(t->tab_elems.sep);
7961 break;
7966 /* after active tab change */
7967 void
7968 recolor_compact_tabs(void)
7970 struct tab *t;
7971 int curid = 0;
7972 GdkColor color;
7974 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7975 TAILQ_FOREACH(t, &tabs, entry)
7976 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
7977 &color);
7979 curid = gtk_notebook_get_current_page(notebook);
7980 TAILQ_FOREACH(t, &tabs, entry)
7981 if (t->tab_id == curid) {
7982 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7983 gtk_widget_modify_fg(t->tab_elems.label,
7984 GTK_STATE_NORMAL, &color);
7985 break;
7989 void
7990 set_current_tab(int page_num)
7992 buffercmd_abort(get_current_tab());
7993 gtk_notebook_set_current_page(notebook, page_num);
7994 recolor_compact_tabs();
7998 undo_close_tab_save(struct tab *t)
8000 int m, n;
8001 const gchar *uri;
8002 struct undo *u1, *u2;
8003 GList *items;
8004 WebKitWebHistoryItem *item;
8006 if ((uri = get_uri(t)) == NULL)
8007 return (1);
8009 u1 = g_malloc0(sizeof(struct undo));
8010 u1->uri = g_strdup(uri);
8012 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8014 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8015 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8016 u1->back = n;
8018 /* forward history */
8019 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8021 while (items) {
8022 item = items->data;
8023 u1->history = g_list_prepend(u1->history,
8024 webkit_web_history_item_copy(item));
8025 items = g_list_next(items);
8028 /* current item */
8029 if (m) {
8030 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8031 u1->history = g_list_prepend(u1->history,
8032 webkit_web_history_item_copy(item));
8035 /* back history */
8036 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8038 while (items) {
8039 item = items->data;
8040 u1->history = g_list_prepend(u1->history,
8041 webkit_web_history_item_copy(item));
8042 items = g_list_next(items);
8045 TAILQ_INSERT_HEAD(&undos, u1, entry);
8047 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8048 u2 = TAILQ_LAST(&undos, undo_tailq);
8049 TAILQ_REMOVE(&undos, u2, entry);
8050 g_free(u2->uri);
8051 g_list_free(u2->history);
8052 g_free(u2);
8053 } else
8054 undo_count++;
8056 return (0);
8059 void
8060 delete_tab(struct tab *t)
8062 struct karg a;
8064 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8066 if (t == NULL)
8067 return;
8069 TAILQ_REMOVE(&tabs, t, entry);
8071 /* Halt all webkit activity. */
8072 abort_favicon_download(t);
8073 webkit_web_view_stop_loading(t->wv);
8075 /* Save the tab, so we can undo the close. */
8076 undo_close_tab_save(t);
8078 if (browser_mode == XT_BM_KIOSK) {
8079 gtk_widget_destroy(t->uri_entry);
8080 gtk_widget_destroy(t->stop);
8081 gtk_widget_destroy(t->js_toggle);
8084 gtk_widget_destroy(t->tab_elems.eventbox);
8085 gtk_widget_destroy(t->vbox);
8087 g_free(t->user_agent);
8088 g_free(t->stylesheet);
8089 g_free(t->tmp_uri);
8090 g_free(t);
8092 if (TAILQ_EMPTY(&tabs)) {
8093 if (browser_mode == XT_BM_KIOSK)
8094 create_new_tab(home, NULL, 1, -1);
8095 else
8096 create_new_tab(NULL, NULL, 1, -1);
8099 /* recreate session */
8100 if (session_autosave) {
8101 a.s = NULL;
8102 save_tabs(t, &a);
8105 recalc_tabs();
8106 recolor_compact_tabs();
8109 void
8110 update_statusbar_zoom(struct tab *t)
8112 gfloat zoom;
8113 char s[16] = { '\0' };
8115 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8116 if ((zoom <= 0.99 || zoom >= 1.01))
8117 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8118 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8121 void
8122 setzoom_webkit(struct tab *t, int adjust)
8124 #define XT_ZOOMPERCENT 0.04
8126 gfloat zoom;
8128 if (t == NULL) {
8129 show_oops(NULL, "setzoom_webkit invalid parameters");
8130 return;
8133 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8134 if (adjust == XT_ZOOM_IN)
8135 zoom += XT_ZOOMPERCENT;
8136 else if (adjust == XT_ZOOM_OUT)
8137 zoom -= XT_ZOOMPERCENT;
8138 else if (adjust > 0)
8139 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8140 else {
8141 show_oops(t, "setzoom_webkit invalid zoom value");
8142 return;
8145 if (zoom < XT_ZOOMPERCENT)
8146 zoom = XT_ZOOMPERCENT;
8147 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8148 update_statusbar_zoom(t);
8151 gboolean
8152 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8154 struct tab *t = (struct tab *) data;
8156 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8158 switch (event->button) {
8159 case 1:
8160 set_current_tab(t->tab_id);
8161 break;
8162 case 2:
8163 delete_tab(t);
8164 break;
8167 return TRUE;
8170 void
8171 append_tab(struct tab *t)
8173 if (t == NULL)
8174 return;
8176 TAILQ_INSERT_TAIL(&tabs, t, entry);
8177 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8180 struct tab *
8181 create_new_tab(char *title, struct undo *u, int focus, int position)
8183 struct tab *t;
8184 int load = 1, id;
8185 GtkWidget *b, *bb;
8186 WebKitWebHistoryItem *item;
8187 GList *items;
8188 GdkColor color;
8189 char *p;
8190 int sbe_p = 0, sbe_b = 0,
8191 sbe_z = 0;
8193 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8195 if (tabless && !TAILQ_EMPTY(&tabs)) {
8196 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8197 return (NULL);
8200 t = g_malloc0(sizeof *t);
8202 if (title == NULL) {
8203 title = "(untitled)";
8204 load = 0;
8207 t->vbox = gtk_vbox_new(FALSE, 0);
8209 /* label + button for tab */
8210 b = gtk_hbox_new(FALSE, 0);
8211 t->tab_content = b;
8213 #if GTK_CHECK_VERSION(2, 20, 0)
8214 t->spinner = gtk_spinner_new();
8215 #endif
8216 t->label = gtk_label_new(title);
8217 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8218 gtk_widget_set_size_request(t->label, 100, 0);
8219 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8220 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8221 gtk_widget_set_size_request(b, 130, 0);
8223 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8224 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8225 #if GTK_CHECK_VERSION(2, 20, 0)
8226 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8227 #endif
8229 /* toolbar */
8230 if (browser_mode == XT_BM_KIOSK)
8231 t->toolbar = create_kiosk_toolbar(t);
8232 else
8233 t->toolbar = create_toolbar(t);
8235 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
8237 /* marks */
8238 marks_clear(t);
8240 /* browser */
8241 t->browser_win = create_browser(t);
8242 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8244 /* oops message for user feedback */
8245 t->oops = gtk_entry_new();
8246 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8247 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8248 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8249 gdk_color_parse(XT_COLOR_RED, &color);
8250 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8251 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8252 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8254 /* command entry */
8255 t->cmd = gtk_entry_new();
8256 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8257 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8258 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8259 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8261 /* status bar */
8262 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8264 t->sbe.statusbar = gtk_entry_new();
8265 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8266 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8267 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8268 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8270 /* XXX create these widgets only if specified in statusbar_elems */
8271 t->sbe.position = gtk_entry_new();
8272 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.position), NULL);
8273 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.position), FALSE);
8274 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.position), FALSE);
8275 gtk_widget_modify_font(GTK_WIDGET(t->sbe.position), statusbar_font);
8276 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.position), 1.0);
8277 gtk_widget_set_size_request(t->sbe.position, 40, -1);
8279 t->sbe.zoom = gtk_entry_new();
8280 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.zoom), NULL);
8281 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.zoom), FALSE);
8282 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.zoom), FALSE);
8283 gtk_widget_modify_font(GTK_WIDGET(t->sbe.zoom), statusbar_font);
8284 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.zoom), 1.0);
8285 gtk_widget_set_size_request(t->sbe.zoom, 40, -1);
8287 t->sbe.buffercmd = gtk_entry_new();
8288 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.buffercmd), NULL);
8289 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.buffercmd), FALSE);
8290 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.buffercmd), FALSE);
8291 gtk_widget_modify_font(GTK_WIDGET(t->sbe.buffercmd), statusbar_font);
8292 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.buffercmd), 1.0);
8293 gtk_widget_set_size_request(t->sbe.buffercmd, 60, -1);
8295 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8297 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8298 TRUE, FALSE);
8300 /* gtk widgets cannot be added to a box twice. sbe_* variables
8301 make sure of this */
8302 for (p = statusbar_elems; *p != '\0'; p++) {
8303 switch (*p) {
8304 case '|':
8306 GtkWidget *sep = gtk_vseparator_new();
8308 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8309 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8310 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8311 FALSE, FALSE, FALSE);
8312 break;
8314 case 'P':
8315 if (sbe_p) {
8316 warnx("flag \"%c\" specified more than "
8317 "once in statusbar_elems\n", *p);
8318 break;
8320 sbe_p = 1;
8321 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8322 t->sbe.position, FALSE, FALSE, FALSE);
8323 break;
8324 case 'B':
8325 if (sbe_b) {
8326 warnx("flag \"%c\" specified more than "
8327 "once in statusbar_elems\n", *p);
8328 break;
8330 sbe_b = 1;
8331 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8332 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8333 break;
8334 case 'Z':
8335 if (sbe_z) {
8336 warnx("flag \"%c\" specified more than "
8337 "once in statusbar_elems\n", *p);
8338 break;
8340 sbe_z = 1;
8341 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8342 t->sbe.zoom, FALSE, FALSE, FALSE);
8343 break;
8344 default:
8345 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8346 break;
8350 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8352 /* buffer list */
8353 t->buffers = create_buffers(t);
8354 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8356 /* xtp meaning is normal by default */
8357 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8359 /* set empty favicon */
8360 xt_icon_from_name(t, "text-html");
8362 /* and show it all */
8363 gtk_widget_show_all(b);
8364 gtk_widget_show_all(t->vbox);
8366 /* compact tab bar */
8367 t->tab_elems.label = gtk_label_new(title);
8368 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8369 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8370 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8371 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8373 t->tab_elems.eventbox = gtk_event_box_new();
8374 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8375 t->tab_elems.sep = gtk_vseparator_new();
8377 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8378 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8379 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8380 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8381 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8382 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8384 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8385 TRUE, 0);
8386 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8387 FALSE, 0);
8388 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8389 t->tab_elems.box);
8391 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8392 TRUE, 0);
8393 gtk_widget_show_all(t->tab_elems.eventbox);
8395 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8396 append_tab(t);
8397 else {
8398 id = position >= 0 ? position :
8399 gtk_notebook_get_current_page(notebook) + 1;
8400 if (id > gtk_notebook_get_n_pages(notebook))
8401 append_tab(t);
8402 else {
8403 TAILQ_INSERT_TAIL(&tabs, t, entry);
8404 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8405 gtk_box_reorder_child(GTK_BOX(tab_bar),
8406 t->tab_elems.eventbox, id);
8407 recalc_tabs();
8411 #if GTK_CHECK_VERSION(2, 20, 0)
8412 /* turn spinner off if we are a new tab without uri */
8413 if (!load) {
8414 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8415 gtk_widget_hide(t->spinner);
8417 #endif
8418 /* make notebook tabs reorderable */
8419 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8421 /* compact tabs clickable */
8422 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8423 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8425 g_object_connect(G_OBJECT(t->cmd),
8426 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8427 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8428 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8429 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8430 (char *)NULL);
8432 /* reuse wv_button_cb to hide oops */
8433 g_object_connect(G_OBJECT(t->oops),
8434 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8435 (char *)NULL);
8437 g_signal_connect(t->buffers,
8438 "row-activated", G_CALLBACK(row_activated_cb), t);
8439 g_object_connect(G_OBJECT(t->buffers),
8440 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8442 g_object_connect(G_OBJECT(t->wv),
8443 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8444 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8445 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8446 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8447 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8448 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8449 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8450 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8451 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8452 "signal::event", G_CALLBACK(webview_event_cb), t,
8453 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8454 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8455 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8456 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8457 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8458 (char *)NULL);
8459 g_signal_connect(t->wv,
8460 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8461 g_signal_connect(t->wv,
8462 "load-error", G_CALLBACK(notify_load_error_cb), t);
8463 g_signal_connect(t->wv,
8464 "notify::title", G_CALLBACK(notify_title_cb), t);
8466 /* hijack the unused keys as if we were the browser */
8467 g_object_connect(G_OBJECT(t->toolbar),
8468 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8469 (char *)NULL);
8471 g_signal_connect(G_OBJECT(bb), "button_press_event",
8472 G_CALLBACK(tab_close_cb), t);
8474 /* hide stuff */
8475 hide_cmd(t);
8476 hide_oops(t);
8477 hide_buffers(t);
8478 url_set_visibility();
8479 statusbar_set_visibility();
8481 if (focus) {
8482 set_current_tab(t->tab_id);
8483 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8484 t->tab_id);
8487 if (load) {
8488 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8489 load_uri(t, title);
8490 } else {
8491 if (show_url == 1)
8492 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8493 else
8494 focus_webview(t);
8497 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8498 /* restore the tab's history */
8499 if (u && u->history) {
8500 items = u->history;
8501 while (items) {
8502 item = items->data;
8503 webkit_web_back_forward_list_add_item(t->bfl, item);
8504 items = g_list_next(items);
8507 item = g_list_nth_data(u->history, u->back);
8508 if (item)
8509 webkit_web_view_go_to_back_forward_item(t->wv, item);
8511 g_list_free(items);
8512 g_list_free(u->history);
8513 } else
8514 webkit_web_back_forward_list_clear(t->bfl);
8516 recolor_compact_tabs();
8517 return (t);
8520 void
8521 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8522 gpointer *udata)
8524 struct tab *t;
8525 const gchar *uri;
8527 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8529 if (gtk_notebook_get_current_page(notebook) == -1)
8530 recalc_tabs();
8532 TAILQ_FOREACH(t, &tabs, entry) {
8533 if (t->tab_id == pn) {
8534 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8535 "%d\n", pn);
8537 uri = get_title(t, TRUE);
8538 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8540 hide_cmd(t);
8541 hide_oops(t);
8543 if (t->focus_wv) {
8544 /* can't use focus_webview here */
8545 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8551 void
8552 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8553 gpointer *udata)
8555 struct tab *t = NULL, *tt;
8557 recalc_tabs();
8559 TAILQ_FOREACH(tt, &tabs, entry)
8560 if (tt->tab_id == pn) {
8561 t = tt;
8562 break;
8565 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8567 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8568 t->tab_id);
8571 void
8572 menuitem_response(struct tab *t)
8574 gtk_notebook_set_current_page(notebook, t->tab_id);
8577 gboolean
8578 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8580 GtkWidget *menu, *menu_items;
8581 GdkEventButton *bevent;
8582 const gchar *uri;
8583 struct tab *ti;
8585 if (event->type == GDK_BUTTON_PRESS) {
8586 bevent = (GdkEventButton *) event;
8587 menu = gtk_menu_new();
8589 TAILQ_FOREACH(ti, &tabs, entry) {
8590 if ((uri = get_uri(ti)) == NULL)
8591 /* XXX make sure there is something to print */
8592 /* XXX add gui pages in here to look purdy */
8593 uri = "(untitled)";
8594 menu_items = gtk_menu_item_new_with_label(uri);
8595 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8596 gtk_widget_show(menu_items);
8598 g_signal_connect_swapped((menu_items),
8599 "activate", G_CALLBACK(menuitem_response),
8600 (gpointer)ti);
8603 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8604 bevent->button, bevent->time);
8606 /* unref object so it'll free itself when popped down */
8607 #if !GTK_CHECK_VERSION(3, 0, 0)
8608 /* XXX does not need unref with gtk+3? */
8609 g_object_ref_sink(menu);
8610 g_object_unref(menu);
8611 #endif
8613 return (TRUE /* eat event */);
8616 return (FALSE /* propagate */);
8620 icon_size_map(int icon_size)
8622 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8623 icon_size > GTK_ICON_SIZE_DIALOG)
8624 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8626 return (icon_size);
8629 GtkWidget *
8630 create_button(char *name, char *stockid, int size)
8632 GtkWidget *button, *image;
8633 gchar *rcstring;
8634 int gtk_icon_size;
8636 rcstring = g_strdup_printf(
8637 "style \"%s-style\"\n"
8638 "{\n"
8639 " GtkWidget::focus-padding = 0\n"
8640 " GtkWidget::focus-line-width = 0\n"
8641 " xthickness = 0\n"
8642 " ythickness = 0\n"
8643 "}\n"
8644 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8645 gtk_rc_parse_string(rcstring);
8646 g_free(rcstring);
8647 button = gtk_button_new();
8648 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8649 gtk_icon_size = icon_size_map(size ? size : icon_size);
8651 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8652 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8653 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8654 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8655 gtk_widget_set_name(button, name);
8656 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8658 return (button);
8661 void
8662 button_set_stockid(GtkWidget *button, char *stockid)
8664 GtkWidget *image;
8666 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8667 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8668 gtk_button_set_image(GTK_BUTTON(button), image);
8671 void
8672 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8674 gchar *p = NULL;
8675 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
8676 gint len;
8679 * xterm doesn't play nice with clipboards because it clears the
8680 * primary when clicked. We rely on primary being set to properly
8681 * handle middle mouse button clicks (paste). So when someone clears
8682 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
8683 * other application behavior (as in DON'T clear primary).
8686 p = gtk_clipboard_wait_for_text(primary);
8687 if (p == NULL) {
8688 if (gdk_property_get(gdk_get_default_root_window(),
8689 atom,
8690 gdk_atom_intern("STRING", FALSE),
8692 1024 * 1024 /* picked out of my butt */,
8693 FALSE,
8694 NULL,
8695 NULL,
8696 &len,
8697 (guchar **)&p)) {
8698 /* yes sir, we need to NUL the string */
8699 p[len] = '\0';
8700 gtk_clipboard_set_text(primary, p, -1);
8704 if (p)
8705 g_free(p);
8708 void
8709 create_canvas(void)
8711 GtkWidget *vbox;
8712 GList *l = NULL;
8713 GdkPixbuf *pb;
8714 char file[PATH_MAX];
8715 int i;
8717 vbox = gtk_vbox_new(FALSE, 0);
8718 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8719 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8720 #if !GTK_CHECK_VERSION(3, 0, 0)
8721 /* XXX seems to be needed with gtk+2 */
8722 gtk_notebook_set_tab_hborder(notebook, 0);
8723 gtk_notebook_set_tab_vborder(notebook, 0);
8724 #endif
8725 gtk_notebook_set_scrollable(notebook, TRUE);
8726 gtk_notebook_set_show_border(notebook, FALSE);
8727 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8729 abtn = gtk_button_new();
8730 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8731 gtk_widget_set_size_request(arrow, -1, -1);
8732 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8733 gtk_widget_set_size_request(abtn, -1, 20);
8735 #if GTK_CHECK_VERSION(2, 20, 0)
8736 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8737 #endif
8738 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8740 /* compact tab bar */
8741 tab_bar = gtk_hbox_new(TRUE, 0);
8743 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8744 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8745 gtk_widget_set_size_request(vbox, -1, -1);
8747 g_object_connect(G_OBJECT(notebook),
8748 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8749 (char *)NULL);
8750 g_object_connect(G_OBJECT(notebook),
8751 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
8752 NULL, (char *)NULL);
8753 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8754 G_CALLBACK(arrow_cb), NULL);
8756 main_window = create_window();
8757 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8759 /* icons */
8760 for (i = 0; i < LENGTH(icons); i++) {
8761 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8762 pb = gdk_pixbuf_new_from_file(file, NULL);
8763 l = g_list_append(l, pb);
8765 gtk_window_set_default_icon_list(l);
8767 /* clipboard */
8768 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8769 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8771 gtk_widget_show_all(abtn);
8772 gtk_widget_show_all(main_window);
8773 notebook_tab_set_visibility();
8776 void
8777 set_hook(void **hook, char *name)
8779 if (hook == NULL)
8780 errx(1, "set_hook");
8782 if (*hook == NULL) {
8783 *hook = dlsym(RTLD_NEXT, name);
8784 if (*hook == NULL)
8785 errx(1, "can't hook %s", name);
8789 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8790 gboolean
8791 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8793 g_return_val_if_fail(cookie1, FALSE);
8794 g_return_val_if_fail(cookie2, FALSE);
8796 return (!strcmp (cookie1->name, cookie2->name) &&
8797 !strcmp (cookie1->value, cookie2->value) &&
8798 !strcmp (cookie1->path, cookie2->path) &&
8799 !strcmp (cookie1->domain, cookie2->domain));
8802 void
8803 transfer_cookies(void)
8805 GSList *cf;
8806 SoupCookie *sc, *pc;
8808 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8810 for (;cf; cf = cf->next) {
8811 pc = cf->data;
8812 sc = soup_cookie_copy(pc);
8813 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8816 soup_cookies_free(cf);
8819 void
8820 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8822 GSList *cf;
8823 SoupCookie *ci;
8825 print_cookie("soup_cookie_jar_delete_cookie", c);
8827 if (cookies_enabled == 0)
8828 return;
8830 if (jar == NULL || c == NULL)
8831 return;
8833 /* find and remove from persistent jar */
8834 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8836 for (;cf; cf = cf->next) {
8837 ci = cf->data;
8838 if (soup_cookie_equal(ci, c)) {
8839 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8840 break;
8844 soup_cookies_free(cf);
8846 /* delete from session jar */
8847 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8850 void
8851 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8853 struct domain *d = NULL;
8854 SoupCookie *c;
8855 FILE *r_cookie_f;
8857 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8858 jar, p_cookiejar, s_cookiejar);
8860 if (cookies_enabled == 0)
8861 return;
8863 /* see if we are up and running */
8864 if (p_cookiejar == NULL) {
8865 _soup_cookie_jar_add_cookie(jar, cookie);
8866 return;
8868 /* disallow p_cookiejar adds, shouldn't happen */
8869 if (jar == p_cookiejar)
8870 return;
8872 /* sanity */
8873 if (jar == NULL || cookie == NULL)
8874 return;
8876 if (enable_cookie_whitelist &&
8877 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8878 blocked_cookies++;
8879 DNPRINTF(XT_D_COOKIE,
8880 "soup_cookie_jar_add_cookie: reject %s\n",
8881 cookie->domain);
8882 if (save_rejected_cookies) {
8883 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8884 show_oops(NULL, "can't open reject cookie file");
8885 return;
8887 fseek(r_cookie_f, 0, SEEK_END);
8888 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8889 cookie->http_only ? "#HttpOnly_" : "",
8890 cookie->domain,
8891 *cookie->domain == '.' ? "TRUE" : "FALSE",
8892 cookie->path,
8893 cookie->secure ? "TRUE" : "FALSE",
8894 cookie->expires ?
8895 (gulong)soup_date_to_time_t(cookie->expires) :
8897 cookie->name,
8898 cookie->value);
8899 fflush(r_cookie_f);
8900 fclose(r_cookie_f);
8902 if (!allow_volatile_cookies)
8903 return;
8906 if (cookie->expires == NULL && session_timeout) {
8907 soup_cookie_set_expires(cookie,
8908 soup_date_new_from_now(session_timeout));
8909 print_cookie("modified add cookie", cookie);
8912 /* see if we are white listed for persistence */
8913 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8914 /* add to persistent jar */
8915 c = soup_cookie_copy(cookie);
8916 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8917 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8920 /* add to session jar */
8921 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8922 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8925 void
8926 setup_cookies(void)
8928 char file[PATH_MAX];
8930 set_hook((void *)&_soup_cookie_jar_add_cookie,
8931 "soup_cookie_jar_add_cookie");
8932 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8933 "soup_cookie_jar_delete_cookie");
8935 if (cookies_enabled == 0)
8936 return;
8939 * the following code is intricate due to overriding several libsoup
8940 * functions.
8941 * do not alter order of these operations.
8944 /* rejected cookies */
8945 if (save_rejected_cookies)
8946 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
8947 XT_REJECT_FILE);
8949 /* persistent cookies */
8950 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8951 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8953 /* session cookies */
8954 s_cookiejar = soup_cookie_jar_new();
8955 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8956 cookie_policy, (void *)NULL);
8957 transfer_cookies();
8959 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8962 void
8963 setup_proxy(char *uri)
8965 if (proxy_uri) {
8966 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8967 soup_uri_free(proxy_uri);
8968 proxy_uri = NULL;
8970 if (http_proxy) {
8971 if (http_proxy != uri) {
8972 g_free(http_proxy);
8973 http_proxy = NULL;
8977 if (uri) {
8978 http_proxy = g_strdup(uri);
8979 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8980 proxy_uri = soup_uri_new(http_proxy);
8981 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8986 send_cmd_to_socket(char *cmd)
8988 int s, len, rv = 1;
8989 struct sockaddr_un sa;
8991 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8992 warnx("%s: socket", __func__);
8993 return (rv);
8996 sa.sun_family = AF_UNIX;
8997 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8998 work_dir, XT_SOCKET_FILE);
8999 len = SUN_LEN(&sa);
9001 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9002 warnx("%s: connect", __func__);
9003 goto done;
9006 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9007 warnx("%s: send", __func__);
9008 goto done;
9011 rv = 0;
9012 done:
9013 close(s);
9014 return (rv);
9017 gboolean
9018 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9020 int s, n;
9021 char str[XT_MAX_URL_LENGTH];
9022 socklen_t t = sizeof(struct sockaddr_un);
9023 struct sockaddr_un sa;
9024 struct passwd *p;
9025 uid_t uid;
9026 gid_t gid;
9027 struct tab *tt;
9028 gint fd = g_io_channel_unix_get_fd(source);
9030 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9031 warn("accept");
9032 return (FALSE);
9035 if (getpeereid(s, &uid, &gid) == -1) {
9036 warn("getpeereid");
9037 return (FALSE);
9039 if (uid != getuid() || gid != getgid()) {
9040 warnx("unauthorized user");
9041 return (FALSE);
9044 p = getpwuid(uid);
9045 if (p == NULL) {
9046 warnx("not a valid user");
9047 return (FALSE);
9050 n = recv(s, str, sizeof(str), 0);
9051 if (n <= 0)
9052 return (TRUE);
9054 tt = TAILQ_LAST(&tabs, tab_list);
9055 cmd_execute(tt, str);
9056 return (TRUE);
9060 is_running(void)
9062 int s, len, rv = 1;
9063 struct sockaddr_un sa;
9065 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9066 warn("is_running: socket");
9067 return (-1);
9070 sa.sun_family = AF_UNIX;
9071 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9072 work_dir, XT_SOCKET_FILE);
9073 len = SUN_LEN(&sa);
9075 /* connect to see if there is a listener */
9076 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9077 rv = 0; /* not running */
9078 else
9079 rv = 1; /* already running */
9081 close(s);
9083 return (rv);
9087 build_socket(void)
9089 int s, len;
9090 struct sockaddr_un sa;
9092 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9093 warn("build_socket: socket");
9094 return (-1);
9097 sa.sun_family = AF_UNIX;
9098 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9099 work_dir, XT_SOCKET_FILE);
9100 len = SUN_LEN(&sa);
9102 /* connect to see if there is a listener */
9103 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9104 /* no listener so we will */
9105 unlink(sa.sun_path);
9107 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9108 warn("build_socket: bind");
9109 goto done;
9112 if (listen(s, 1) == -1) {
9113 warn("build_socket: listen");
9114 goto done;
9117 return (s);
9120 done:
9121 close(s);
9122 return (-1);
9125 gboolean
9126 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9127 GtkTreeIter *iter, struct tab *t)
9129 gchar *value;
9131 gtk_tree_model_get(model, iter, 0, &value, -1);
9132 load_uri(t, value);
9133 g_free(value);
9135 return (FALSE);
9138 gboolean
9139 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9140 GtkTreeIter *iter, struct tab *t)
9142 gchar *value;
9144 gtk_tree_model_get(model, iter, 0, &value, -1);
9145 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9146 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9147 g_free(value);
9149 return (TRUE);
9152 void
9153 completion_add_uri(const gchar *uri)
9155 GtkTreeIter iter;
9157 /* add uri to list_store */
9158 gtk_list_store_append(completion_model, &iter);
9159 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9162 gboolean
9163 completion_match(GtkEntryCompletion *completion, const gchar *key,
9164 GtkTreeIter *iter, gpointer user_data)
9166 gchar *value;
9167 gboolean match = FALSE;
9169 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9170 -1);
9172 if (value == NULL)
9173 return FALSE;
9175 match = match_uri(value, key);
9177 g_free(value);
9178 return (match);
9181 void
9182 completion_add(struct tab *t)
9184 /* enable completion for tab */
9185 t->completion = gtk_entry_completion_new();
9186 gtk_entry_completion_set_text_column(t->completion, 0);
9187 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9188 gtk_entry_completion_set_model(t->completion,
9189 GTK_TREE_MODEL(completion_model));
9190 gtk_entry_completion_set_match_func(t->completion, completion_match,
9191 NULL, NULL);
9192 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9193 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9194 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9195 G_CALLBACK(completion_select_cb), t);
9196 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9197 G_CALLBACK(completion_hover_cb), t);
9200 void
9201 xxx_dir(char *dir)
9203 struct stat sb;
9205 if (stat(dir, &sb)) {
9206 if (mkdir(dir, S_IRWXU) == -1)
9207 err(1, "mkdir %s", dir);
9208 if (stat(dir, &sb))
9209 err(1, "stat %s", dir);
9211 if (S_ISDIR(sb.st_mode) == 0)
9212 errx(1, "%s not a dir", dir);
9213 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9214 warnx("fixing invalid permissions on %s", dir);
9215 if (chmod(dir, S_IRWXU) == -1)
9216 err(1, "chmod %s", dir);
9220 void
9221 usage(void)
9223 fprintf(stderr,
9224 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9225 exit(0);
9230 main(int argc, char *argv[])
9232 struct stat sb;
9233 int c, s, optn = 0, opte = 0, focus = 1;
9234 char conf[PATH_MAX] = { '\0' };
9235 char file[PATH_MAX];
9236 char *env_proxy = NULL;
9237 FILE *f = NULL;
9238 struct karg a;
9239 struct sigaction sact;
9240 GIOChannel *channel;
9241 struct rlimit rlp;
9243 start_argv = argv;
9245 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9247 /* fiddle with ulimits */
9248 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9249 warn("getrlimit");
9250 else {
9251 /* just use them all */
9252 rlp.rlim_cur = rlp.rlim_max;
9253 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9254 warn("setrlimit");
9255 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9256 warn("getrlimit");
9257 else if (rlp.rlim_cur <= 256)
9258 warnx("%s requires at least 256 file descriptors",
9259 __progname);
9262 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9263 switch (c) {
9264 case 'S':
9265 show_url = 0;
9266 break;
9267 case 'T':
9268 show_tabs = 0;
9269 break;
9270 case 'V':
9271 errx(0 , "Version: %s", version);
9272 break;
9273 case 'f':
9274 strlcpy(conf, optarg, sizeof(conf));
9275 break;
9276 case 's':
9277 strlcpy(named_session, optarg, sizeof(named_session));
9278 break;
9279 case 't':
9280 tabless = 1;
9281 break;
9282 case 'n':
9283 optn = 1;
9284 break;
9285 case 'e':
9286 opte = 1;
9287 break;
9288 default:
9289 usage();
9290 /* NOTREACHED */
9293 argc -= optind;
9294 argv += optind;
9296 RB_INIT(&hl);
9297 RB_INIT(&js_wl);
9298 RB_INIT(&downloads);
9300 TAILQ_INIT(&tabs);
9301 TAILQ_INIT(&mtl);
9302 TAILQ_INIT(&aliases);
9303 TAILQ_INIT(&undos);
9304 TAILQ_INIT(&kbl);
9306 init_keybindings();
9308 gnutls_global_init();
9310 /* generate session keys for xtp pages */
9311 generate_xtp_session_key(&dl_session_key);
9312 generate_xtp_session_key(&hl_session_key);
9313 generate_xtp_session_key(&cl_session_key);
9314 generate_xtp_session_key(&fl_session_key);
9316 /* prepare gtk */
9317 gtk_init(&argc, &argv);
9318 if (!g_thread_supported())
9319 g_thread_init(NULL);
9321 /* signals */
9322 bzero(&sact, sizeof(sact));
9323 sigemptyset(&sact.sa_mask);
9324 sact.sa_handler = sigchild;
9325 sact.sa_flags = SA_NOCLDSTOP;
9326 sigaction(SIGCHLD, &sact, NULL);
9328 /* set download dir */
9329 pwd = getpwuid(getuid());
9330 if (pwd == NULL)
9331 errx(1, "invalid user %d", getuid());
9332 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9334 /* compile buffer command regexes */
9335 buffercmd_init();
9337 /* set default string settings */
9338 home = g_strdup("https://www.cyphertite.com");
9339 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9340 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9341 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9342 cmd_font_name = g_strdup("monospace normal 9");
9343 oops_font_name = g_strdup("monospace normal 9");
9344 statusbar_font_name = g_strdup("monospace normal 9");
9345 tabbar_font_name = g_strdup("monospace normal 9");
9346 statusbar_elems = g_strdup("BP");
9348 /* read config file */
9349 if (strlen(conf) == 0)
9350 snprintf(conf, sizeof conf, "%s/.%s",
9351 pwd->pw_dir, XT_CONF_FILE);
9352 config_parse(conf, 0);
9354 /* init fonts */
9355 cmd_font = pango_font_description_from_string(cmd_font_name);
9356 oops_font = pango_font_description_from_string(oops_font_name);
9357 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9358 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9360 /* working directory */
9361 if (strlen(work_dir) == 0)
9362 snprintf(work_dir, sizeof work_dir, "%s/%s",
9363 pwd->pw_dir, XT_DIR);
9364 xxx_dir(work_dir);
9366 /* icon cache dir */
9367 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9368 xxx_dir(cache_dir);
9370 /* certs dir */
9371 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9372 xxx_dir(certs_dir);
9374 /* sessions dir */
9375 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9376 work_dir, XT_SESSIONS_DIR);
9377 xxx_dir(sessions_dir);
9379 /* runtime settings that can override config file */
9380 if (runtime_settings[0] != '\0')
9381 config_parse(runtime_settings, 1);
9383 /* download dir */
9384 if (!strcmp(download_dir, pwd->pw_dir))
9385 strlcat(download_dir, "/downloads", sizeof download_dir);
9386 xxx_dir(download_dir);
9388 /* favorites file */
9389 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9390 if (stat(file, &sb)) {
9391 warnx("favorites file doesn't exist, creating it");
9392 if ((f = fopen(file, "w")) == NULL)
9393 err(1, "favorites");
9394 fclose(f);
9397 /* quickmarks file */
9398 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9399 if (stat(file, &sb)) {
9400 warnx("quickmarks file doesn't exist, creating it");
9401 if ((f = fopen(file, "w")) == NULL)
9402 err(1, "quickmarks");
9403 fclose(f);
9406 /* cookies */
9407 session = webkit_get_default_session();
9408 setup_cookies();
9410 /* certs */
9411 if (ssl_ca_file) {
9412 if (stat(ssl_ca_file, &sb)) {
9413 warnx("no CA file: %s", ssl_ca_file);
9414 g_free(ssl_ca_file);
9415 ssl_ca_file = NULL;
9416 } else
9417 g_object_set(session,
9418 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9419 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9420 (void *)NULL);
9423 /* proxy */
9424 env_proxy = getenv("http_proxy");
9425 if (env_proxy)
9426 setup_proxy(env_proxy);
9427 else
9428 setup_proxy(http_proxy);
9430 if (opte) {
9431 send_cmd_to_socket(argv[0]);
9432 exit(0);
9435 /* set some connection parameters */
9436 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9437 g_object_set(session, "max-conns-per-host", max_host_connections,
9438 (char *)NULL);
9440 /* see if there is already an xxxterm running */
9441 if (single_instance && is_running()) {
9442 optn = 1;
9443 warnx("already running");
9446 char *cmd = NULL;
9447 if (optn) {
9448 while (argc) {
9449 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9450 send_cmd_to_socket(cmd);
9451 if (cmd)
9452 g_free(cmd);
9454 argc--;
9455 argv++;
9457 exit(0);
9460 /* uri completion */
9461 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9463 /* buffers */
9464 buffers_store = gtk_list_store_new
9465 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9467 qmarks_load();
9469 /* go graphical */
9470 create_canvas();
9471 notebook_tab_set_visibility();
9473 if (save_global_history)
9474 restore_global_history();
9476 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9477 restore_saved_tabs();
9478 else {
9479 a.s = named_session;
9480 a.i = XT_SES_DONOTHING;
9481 open_tabs(NULL, &a);
9484 while (argc) {
9485 create_new_tab(argv[0], NULL, focus, -1);
9486 focus = 0;
9488 argc--;
9489 argv++;
9492 if (TAILQ_EMPTY(&tabs))
9493 create_new_tab(home, NULL, 1, -1);
9495 if (enable_socket)
9496 if ((s = build_socket()) != -1) {
9497 channel = g_io_channel_unix_new(s);
9498 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9501 gtk_main();
9503 gnutls_global_deinit();
9505 return (0);