allow right click in command entry without losing focus
[xxxterm.git] / xxxterm.c
blob28f69bd6ec1a830891875fff8cd3e2c96ac3a086
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pthread.h>
35 #include <pwd.h>
36 #include <regex.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
49 #include <libutil.h>
50 #include "freebsd/util.h"
51 #include <sys/tree.h>
52 #else /* OpenBSD */
53 #include <util.h>
54 #include <sys/tree.h>
55 #endif
56 #include <sys/queue.h>
57 #include <sys/resource.h>
58 #include <sys/socket.h>
59 #include <sys/stat.h>
60 #include <sys/time.h>
61 #include <sys/un.h>
63 #include <gtk/gtk.h>
64 #include <gdk/gdkkeysyms.h>
66 #if GTK_CHECK_VERSION(3,0,0)
67 /* we still use GDK_* instead of GDK_KEY_* */
68 #include <gdk/gdkkeysyms-compat.h>
69 #endif
71 #include <webkit/webkit.h>
72 #include <libsoup/soup.h>
73 #include <gnutls/gnutls.h>
74 #include <JavaScriptCore/JavaScript.h>
75 #include <gnutls/x509.h>
77 #include "javascript.h"
80 javascript.h borrowed from vimprobable2 under the following license:
82 Copyright (c) 2009 Leon Winter
83 Copyright (c) 2009 Hannes Schueller
84 Copyright (c) 2009 Matto Fransen
86 Permission is hereby granted, free of charge, to any person obtaining a copy
87 of this software and associated documentation files (the "Software"), to deal
88 in the Software without restriction, including without limitation the rights
89 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90 copies of the Software, and to permit persons to whom the Software is
91 furnished to do so, subject to the following conditions:
93 The above copyright notice and this permission notice shall be included in
94 all copies or substantial portions of the Software.
96 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
102 THE SOFTWARE.
105 static char *version = "$xxxterm$";
107 /* hooked functions */
108 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
109 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
110 SoupCookie *);
112 /*#define XT_DEBUG*/
113 #ifdef XT_DEBUG
114 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
115 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
116 #define XT_D_MOVE 0x0001
117 #define XT_D_KEY 0x0002
118 #define XT_D_TAB 0x0004
119 #define XT_D_URL 0x0008
120 #define XT_D_CMD 0x0010
121 #define XT_D_NAV 0x0020
122 #define XT_D_DOWNLOAD 0x0040
123 #define XT_D_CONFIG 0x0080
124 #define XT_D_JS 0x0100
125 #define XT_D_FAVORITE 0x0200
126 #define XT_D_PRINTING 0x0400
127 #define XT_D_COOKIE 0x0800
128 #define XT_D_KEYBINDING 0x1000
129 #define XT_D_CLIP 0x2000
130 #define XT_D_BUFFERCMD 0x4000
131 u_int32_t swm_debug = 0
132 | XT_D_MOVE
133 | XT_D_KEY
134 | XT_D_TAB
135 | XT_D_URL
136 | XT_D_CMD
137 | XT_D_NAV
138 | XT_D_DOWNLOAD
139 | XT_D_CONFIG
140 | XT_D_JS
141 | XT_D_FAVORITE
142 | XT_D_PRINTING
143 | XT_D_COOKIE
144 | XT_D_KEYBINDING
145 | XT_D_CLIP
146 | XT_D_BUFFERCMD
148 #else
149 #define DPRINTF(x...)
150 #define DNPRINTF(n,x...)
151 #endif
153 #define LENGTH(x) (sizeof x / sizeof x[0])
154 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
155 ~(GDK_BUTTON1_MASK) & \
156 ~(GDK_BUTTON2_MASK) & \
157 ~(GDK_BUTTON3_MASK) & \
158 ~(GDK_BUTTON4_MASK) & \
159 ~(GDK_BUTTON5_MASK))
161 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
163 char *icons[] = {
164 "xxxtermicon16.png",
165 "xxxtermicon32.png",
166 "xxxtermicon48.png",
167 "xxxtermicon64.png",
168 "xxxtermicon128.png"
171 struct tab {
172 TAILQ_ENTRY(tab) entry;
173 GtkWidget *vbox;
174 GtkWidget *tab_content;
175 struct {
176 GtkWidget *label;
177 GtkWidget *eventbox;
178 GtkWidget *box;
179 GtkWidget *sep;
180 } tab_elems;
181 GtkWidget *label;
182 GtkWidget *spinner;
183 GtkWidget *uri_entry;
184 GtkWidget *search_entry;
185 GtkWidget *toolbar;
186 GtkWidget *browser_win;
187 GtkWidget *statusbar_box;
188 struct {
189 GtkWidget *statusbar;
190 GtkWidget *buffercmd;
191 GtkWidget *zoom;
192 GtkWidget *position;
193 } sbe;
194 GtkWidget *cmd;
195 GtkWidget *buffers;
196 GtkWidget *oops;
197 GtkWidget *backward;
198 GtkWidget *forward;
199 GtkWidget *stop;
200 GtkWidget *gohome;
201 GtkWidget *js_toggle;
202 GtkEntryCompletion *completion;
203 guint tab_id;
204 WebKitWebView *wv;
206 WebKitWebHistoryItem *item;
207 WebKitWebBackForwardList *bfl;
209 /* favicon */
210 WebKitNetworkRequest *icon_request;
211 WebKitDownload *icon_download;
212 gchar *icon_dest_uri;
214 /* adjustments for browser */
215 GtkScrollbar *sb_h;
216 GtkScrollbar *sb_v;
217 GtkAdjustment *adjust_h;
218 GtkAdjustment *adjust_v;
220 /* flags */
221 int focus_wv;
222 int ctrl_click;
223 gchar *status;
224 int xtp_meaning; /* identifies dls/favorites */
225 gchar *tmp_uri;
226 int popup; /* 1 if cmd_entry has popup visible */
228 /* hints */
229 int hints_on;
230 int hint_mode;
231 #define XT_HINT_NONE (0)
232 #define XT_HINT_NUMERICAL (1)
233 #define XT_HINT_ALPHANUM (2)
234 char hint_buf[128];
235 char hint_num[128];
237 /* custom stylesheet */
238 int styled;
239 char *stylesheet;
241 /* search */
242 char *search_text;
243 int search_forward;
244 guint search_id;
246 /* settings */
247 WebKitWebSettings *settings;
248 gchar *user_agent;
250 /* marks */
251 double mark[XT_NOMARKS];
253 TAILQ_HEAD(tab_list, tab);
255 struct history {
256 RB_ENTRY(history) entry;
257 const gchar *uri;
258 const gchar *title;
260 RB_HEAD(history_list, history);
262 struct download {
263 RB_ENTRY(download) entry;
264 int id;
265 WebKitDownload *download;
266 struct tab *tab;
268 RB_HEAD(download_list, download);
270 struct domain {
271 RB_ENTRY(domain) entry;
272 gchar *d;
273 int handy; /* app use */
275 RB_HEAD(domain_list, domain);
277 struct undo {
278 TAILQ_ENTRY(undo) entry;
279 gchar *uri;
280 GList *history;
281 int back; /* Keeps track of how many back
282 * history items there are. */
284 TAILQ_HEAD(undo_tailq, undo);
286 struct sp {
287 char *line;
288 TAILQ_ENTRY(sp) entry;
290 TAILQ_HEAD(sp_list, sp);
292 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
293 int next_download_id = 1;
295 struct karg {
296 int i;
297 char *s;
298 int precount;
301 /* defines */
302 #define XT_NAME ("XXXTerm")
303 #define XT_DIR (".xxxterm")
304 #define XT_CACHE_DIR ("cache")
305 #define XT_CERT_DIR ("certs/")
306 #define XT_SESSIONS_DIR ("sessions/")
307 #define XT_CONF_FILE ("xxxterm.conf")
308 #define XT_FAVS_FILE ("favorites")
309 #define XT_QMARKS_FILE ("quickmarks")
310 #define XT_SAVED_TABS_FILE ("main_session")
311 #define XT_RESTART_TABS_FILE ("restart_tabs")
312 #define XT_SOCKET_FILE ("socket")
313 #define XT_HISTORY_FILE ("history")
314 #define XT_REJECT_FILE ("rejected.txt")
315 #define XT_COOKIE_FILE ("cookies.txt")
316 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
317 #define XT_CB_HANDLED (TRUE)
318 #define XT_CB_PASSTHROUGH (FALSE)
319 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
320 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
321 #define XT_DLMAN_REFRESH "10"
322 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
323 "td{overflow: hidden;" \
324 " padding: 2px 2px 2px 2px;" \
325 " border: 1px solid black;" \
326 " vertical-align:top;" \
327 " word-wrap: break-word}\n" \
328 "tr:hover{background: #ffff99}\n" \
329 "th{background-color: #cccccc;" \
330 " border: 1px solid black}\n" \
331 "table{width: 100%%;" \
332 " border: 1px black solid;" \
333 " border-collapse:collapse}\n" \
334 ".progress-outer{" \
335 "border: 1px solid black;" \
336 " height: 8px;" \
337 " width: 90%%}\n" \
338 ".progress-inner{float: left;" \
339 " height: 8px;" \
340 " background: green}\n" \
341 ".dlstatus{font-size: small;" \
342 " text-align: center}\n" \
343 "</style>\n"
344 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
345 #define XT_MAX_UNDO_CLOSE_TAB (32)
346 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
347 #define XT_PRINT_EXTRA_MARGIN 10
348 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
349 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
351 /* colors */
352 #define XT_COLOR_RED "#cc0000"
353 #define XT_COLOR_YELLOW "#ffff66"
354 #define XT_COLOR_BLUE "lightblue"
355 #define XT_COLOR_GREEN "#99ff66"
356 #define XT_COLOR_WHITE "white"
357 #define XT_COLOR_BLACK "black"
359 #define XT_COLOR_CT_BACKGROUND "#000000"
360 #define XT_COLOR_CT_INACTIVE "#dddddd"
361 #define XT_COLOR_CT_ACTIVE "#bbbb00"
362 #define XT_COLOR_CT_SEPARATOR "#555555"
364 #define XT_COLOR_SB_SEPARATOR "#555555"
366 #define XT_PROTO_DELIM "://"
369 * xxxterm "protocol" (xtp)
370 * We use this for managing stuff like downloads and favorites. They
371 * make magical HTML pages in memory which have xxxt:// links in order
372 * to communicate with xxxterm's internals. These links take the format:
373 * xxxt://class/session_key/action/arg
375 * Don't begin xtp class/actions as 0. atoi returns that on error.
377 * Typically we have not put addition of items in this framework, as
378 * adding items is either done via an ex-command or via a keybinding instead.
381 #define XT_XTP_STR "xxxt://"
383 /* XTP classes (xxxt://<class>) */
384 #define XT_XTP_INVALID 0 /* invalid */
385 #define XT_XTP_DL 1 /* downloads */
386 #define XT_XTP_HL 2 /* history */
387 #define XT_XTP_CL 3 /* cookies */
388 #define XT_XTP_FL 4 /* favorites */
390 /* XTP download actions */
391 #define XT_XTP_DL_LIST 1
392 #define XT_XTP_DL_CANCEL 2
393 #define XT_XTP_DL_REMOVE 3
395 /* XTP history actions */
396 #define XT_XTP_HL_LIST 1
397 #define XT_XTP_HL_REMOVE 2
399 /* XTP cookie actions */
400 #define XT_XTP_CL_LIST 1
401 #define XT_XTP_CL_REMOVE 2
403 /* XTP cookie actions */
404 #define XT_XTP_FL_LIST 1
405 #define XT_XTP_FL_REMOVE 2
407 /* actions */
408 #define XT_MOVE_INVALID (0)
409 #define XT_MOVE_DOWN (1)
410 #define XT_MOVE_UP (2)
411 #define XT_MOVE_BOTTOM (3)
412 #define XT_MOVE_TOP (4)
413 #define XT_MOVE_PAGEDOWN (5)
414 #define XT_MOVE_PAGEUP (6)
415 #define XT_MOVE_HALFDOWN (7)
416 #define XT_MOVE_HALFUP (8)
417 #define XT_MOVE_LEFT (9)
418 #define XT_MOVE_FARLEFT (10)
419 #define XT_MOVE_RIGHT (11)
420 #define XT_MOVE_FARRIGHT (12)
421 #define XT_MOVE_PERCENT (13)
423 #define XT_QMARK_SET (0)
424 #define XT_QMARK_OPEN (1)
425 #define XT_QMARK_TAB (2)
427 #define XT_MARK_SET (0)
428 #define XT_MARK_GOTO (1)
430 #define XT_TAB_LAST (-4)
431 #define XT_TAB_FIRST (-3)
432 #define XT_TAB_PREV (-2)
433 #define XT_TAB_NEXT (-1)
434 #define XT_TAB_INVALID (0)
435 #define XT_TAB_NEW (1)
436 #define XT_TAB_DELETE (2)
437 #define XT_TAB_DELQUIT (3)
438 #define XT_TAB_OPEN (4)
439 #define XT_TAB_UNDO_CLOSE (5)
440 #define XT_TAB_SHOW (6)
441 #define XT_TAB_HIDE (7)
442 #define XT_TAB_NEXTSTYLE (8)
444 #define XT_NAV_INVALID (0)
445 #define XT_NAV_BACK (1)
446 #define XT_NAV_FORWARD (2)
447 #define XT_NAV_RELOAD (3)
449 #define XT_FOCUS_INVALID (0)
450 #define XT_FOCUS_URI (1)
451 #define XT_FOCUS_SEARCH (2)
453 #define XT_SEARCH_INVALID (0)
454 #define XT_SEARCH_NEXT (1)
455 #define XT_SEARCH_PREV (2)
457 #define XT_PASTE_CURRENT_TAB (0)
458 #define XT_PASTE_NEW_TAB (1)
460 #define XT_ZOOM_IN (-1)
461 #define XT_ZOOM_OUT (-2)
462 #define XT_ZOOM_NORMAL (100)
464 #define XT_URL_SHOW (1)
465 #define XT_URL_HIDE (2)
467 #define XT_WL_TOGGLE (1<<0)
468 #define XT_WL_ENABLE (1<<1)
469 #define XT_WL_DISABLE (1<<2)
470 #define XT_WL_FQDN (1<<3) /* default */
471 #define XT_WL_TOPLEVEL (1<<4)
472 #define XT_WL_PERSISTENT (1<<5)
473 #define XT_WL_SESSION (1<<6)
474 #define XT_WL_RELOAD (1<<7)
476 #define XT_SHOW (1<<7)
477 #define XT_DELETE (1<<8)
478 #define XT_SAVE (1<<9)
479 #define XT_OPEN (1<<10)
481 #define XT_CMD_OPEN (0)
482 #define XT_CMD_OPEN_CURRENT (1)
483 #define XT_CMD_TABNEW (2)
484 #define XT_CMD_TABNEW_CURRENT (3)
486 #define XT_STATUS_NOTHING (0)
487 #define XT_STATUS_LINK (1)
488 #define XT_STATUS_URI (2)
489 #define XT_STATUS_LOADING (3)
491 #define XT_SES_DONOTHING (0)
492 #define XT_SES_CLOSETABS (1)
494 #define XT_BM_NORMAL (0)
495 #define XT_BM_WHITELIST (1)
496 #define XT_BM_KIOSK (2)
498 #define XT_PREFIX (1<<0)
499 #define XT_USERARG (1<<1)
500 #define XT_URLARG (1<<2)
501 #define XT_INTARG (1<<3)
503 #define XT_TABS_NORMAL 0
504 #define XT_TABS_COMPACT 1
506 #define XT_BUFCMD_SZ (8)
508 /* mime types */
509 struct mime_type {
510 char *mt_type;
511 char *mt_action;
512 int mt_default;
513 int mt_download;
514 TAILQ_ENTRY(mime_type) entry;
516 TAILQ_HEAD(mime_type_list, mime_type);
518 /* uri aliases */
519 struct alias {
520 char *a_name;
521 char *a_uri;
522 TAILQ_ENTRY(alias) entry;
524 TAILQ_HEAD(alias_list, alias);
526 /* settings that require restart */
527 int tabless = 0; /* allow only 1 tab */
528 int enable_socket = 0;
529 int single_instance = 0; /* only allow one xxxterm to run */
530 int fancy_bar = 1; /* fancy toolbar */
531 int browser_mode = XT_BM_NORMAL;
532 int enable_localstorage = 0;
533 char *statusbar_elems = NULL;
535 /* runtime settings */
536 int show_tabs = 1; /* show tabs on notebook */
537 int tab_style = XT_TABS_NORMAL; /* tab bar style */
538 int show_url = 1; /* show url toolbar on notebook */
539 int show_statusbar = 0; /* vimperator style status bar */
540 int ctrl_click_focus = 0; /* ctrl click gets focus */
541 int cookies_enabled = 1; /* enable cookies */
542 int read_only_cookies = 0; /* enable to not write cookies */
543 int enable_scripts = 1;
544 int enable_plugins = 0;
545 gfloat default_zoom_level = 1.0;
546 char default_script[PATH_MAX];
547 int window_height = 768;
548 int window_width = 1024;
549 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
550 int refresh_interval = 10; /* download refresh interval */
551 int enable_cookie_whitelist = 0;
552 int enable_js_whitelist = 0;
553 int session_timeout = 3600; /* cookie session timeout */
554 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
555 char *ssl_ca_file = NULL;
556 char *resource_dir = NULL;
557 gboolean ssl_strict_certs = FALSE;
558 int append_next = 1; /* append tab after current tab */
559 char *home = NULL;
560 char *search_string = NULL;
561 char *http_proxy = NULL;
562 char download_dir[PATH_MAX];
563 char runtime_settings[PATH_MAX]; /* override of settings */
564 int allow_volatile_cookies = 0;
565 int save_global_history = 0; /* save global history to disk */
566 char *user_agent = NULL;
567 int save_rejected_cookies = 0;
568 int session_autosave = 0;
569 int guess_search = 0;
570 int dns_prefetch = FALSE;
571 gint max_connections = 25;
572 gint max_host_connections = 5;
573 gint enable_spell_checking = 0;
574 char *spell_check_languages = NULL;
575 int xterm_workaround = 0;
576 char *url_regex = NULL;
578 char *cmd_font_name = NULL;
579 char *oops_font_name = NULL;
580 char *statusbar_font_name = NULL;
581 char *tabbar_font_name = NULL;
582 PangoFontDescription *cmd_font;
583 PangoFontDescription *oops_font;
584 PangoFontDescription *statusbar_font;
585 PangoFontDescription *tabbar_font;
586 char *qmarks[XT_NOMARKS];
588 int btn_down; /* M1 down in any wv */
589 regex_t url_re; /* guess_search regex */
591 struct settings;
592 struct key_binding;
593 int set_browser_mode(struct settings *, char *);
594 int set_cookie_policy(struct settings *, char *);
595 int set_download_dir(struct settings *, char *);
596 int set_default_script(struct settings *, char *);
597 int set_runtime_dir(struct settings *, char *);
598 int set_tab_style(struct settings *, char *);
599 int set_work_dir(struct settings *, char *);
600 int add_alias(struct settings *, char *);
601 int add_mime_type(struct settings *, char *);
602 int add_cookie_wl(struct settings *, char *);
603 int add_js_wl(struct settings *, char *);
604 int add_kb(struct settings *, char *);
605 void button_set_stockid(GtkWidget *, char *);
606 GtkWidget * create_button(char *, char *, int);
608 char *get_browser_mode(struct settings *);
609 char *get_cookie_policy(struct settings *);
610 char *get_download_dir(struct settings *);
611 char *get_default_script(struct settings *);
612 char *get_runtime_dir(struct settings *);
613 char *get_tab_style(struct settings *);
614 char *get_work_dir(struct settings *);
615 void startpage_add(const char *, ...);
617 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
618 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
619 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
620 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
621 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
623 void recalc_tabs(void);
624 void recolor_compact_tabs(void);
625 void set_current_tab(int page_num);
626 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
627 void marks_clear(struct tab *t);
629 int set_http_proxy(char *);
631 struct special {
632 int (*set)(struct settings *, char *);
633 char *(*get)(struct settings *);
634 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
637 struct special s_browser_mode = {
638 set_browser_mode,
639 get_browser_mode,
640 NULL
643 struct special s_cookie = {
644 set_cookie_policy,
645 get_cookie_policy,
646 NULL
649 struct special s_alias = {
650 add_alias,
651 NULL,
652 walk_alias
655 struct special s_mime = {
656 add_mime_type,
657 NULL,
658 walk_mime_type
661 struct special s_js = {
662 add_js_wl,
663 NULL,
664 walk_js_wl
667 struct special s_kb = {
668 add_kb,
669 NULL,
670 walk_kb
673 struct special s_cookie_wl = {
674 add_cookie_wl,
675 NULL,
676 walk_cookie_wl
679 struct special s_default_script = {
680 set_default_script,
681 get_default_script,
682 NULL
685 struct special s_download_dir = {
686 set_download_dir,
687 get_download_dir,
688 NULL
691 struct special s_work_dir = {
692 set_work_dir,
693 get_work_dir,
694 NULL
697 struct special s_tab_style = {
698 set_tab_style,
699 get_tab_style,
700 NULL
703 struct settings {
704 char *name;
705 int type;
706 #define XT_S_INVALID (0)
707 #define XT_S_INT (1)
708 #define XT_S_STR (2)
709 #define XT_S_FLOAT (3)
710 uint32_t flags;
711 #define XT_SF_RESTART (1<<0)
712 #define XT_SF_RUNTIME (1<<1)
713 int *ival;
714 char **sval;
715 struct special *s;
716 gfloat *fval;
717 int (*activate)(char *);
718 } rs[] = {
719 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
720 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
721 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
722 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
723 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
724 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
725 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
726 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
727 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
728 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
729 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
730 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
731 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
732 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
733 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
734 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
735 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
736 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
737 { "home", XT_S_STR, 0, NULL, &home, NULL },
738 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
739 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
740 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
741 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
742 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
743 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
744 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
745 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
746 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
747 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
748 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
749 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
750 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
751 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
752 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
753 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
754 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
755 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
756 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
757 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
758 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
759 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
760 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
761 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
762 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
763 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
764 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
766 /* font settings */
767 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
768 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
769 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
770 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
772 /* runtime settings */
773 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
774 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
775 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
776 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
777 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
780 int about(struct tab *, struct karg *);
781 int blank(struct tab *, struct karg *);
782 int ca_cmd(struct tab *, struct karg *);
783 int cookie_show_wl(struct tab *, struct karg *);
784 int js_show_wl(struct tab *, struct karg *);
785 int help(struct tab *, struct karg *);
786 int set(struct tab *, struct karg *);
787 int stats(struct tab *, struct karg *);
788 int marco(struct tab *, struct karg *);
789 int startpage(struct tab *, struct karg *);
790 const char * marco_message(int *);
791 int xtp_page_cl(struct tab *, struct karg *);
792 int xtp_page_dl(struct tab *, struct karg *);
793 int xtp_page_fl(struct tab *, struct karg *);
794 int xtp_page_hl(struct tab *, struct karg *);
795 void xt_icon_from_file(struct tab *, char *);
796 const gchar *get_uri(struct tab *);
797 const gchar *get_title(struct tab *, bool);
799 #define XT_URI_ABOUT ("about:")
800 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
801 #define XT_URI_ABOUT_ABOUT ("about")
802 #define XT_URI_ABOUT_BLANK ("blank")
803 #define XT_URI_ABOUT_CERTS ("certs")
804 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
805 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
806 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
807 #define XT_URI_ABOUT_FAVORITES ("favorites")
808 #define XT_URI_ABOUT_HELP ("help")
809 #define XT_URI_ABOUT_HISTORY ("history")
810 #define XT_URI_ABOUT_JSWL ("jswl")
811 #define XT_URI_ABOUT_SET ("set")
812 #define XT_URI_ABOUT_STATS ("stats")
813 #define XT_URI_ABOUT_MARCO ("marco")
814 #define XT_URI_ABOUT_STARTPAGE ("startpage")
816 struct about_type {
817 char *name;
818 int (*func)(struct tab *, struct karg *);
819 } about_list[] = {
820 { XT_URI_ABOUT_ABOUT, about },
821 { XT_URI_ABOUT_BLANK, blank },
822 { XT_URI_ABOUT_CERTS, ca_cmd },
823 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
824 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
825 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
826 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
827 { XT_URI_ABOUT_HELP, help },
828 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
829 { XT_URI_ABOUT_JSWL, js_show_wl },
830 { XT_URI_ABOUT_SET, set },
831 { XT_URI_ABOUT_STATS, stats },
832 { XT_URI_ABOUT_MARCO, marco },
833 { XT_URI_ABOUT_STARTPAGE, startpage },
836 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
837 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
838 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
839 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
840 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
841 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
842 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
844 /* globals */
845 extern char *__progname;
846 char **start_argv;
847 struct passwd *pwd;
848 GtkWidget *main_window;
849 GtkNotebook *notebook;
850 GtkWidget *tab_bar;
851 GtkWidget *arrow, *abtn;
852 struct tab_list tabs;
853 struct history_list hl;
854 struct download_list downloads;
855 struct domain_list c_wl;
856 struct domain_list js_wl;
857 struct undo_tailq undos;
858 struct keybinding_list kbl;
859 struct sp_list spl;
860 int undo_count;
861 int updating_dl_tabs = 0;
862 int updating_hl_tabs = 0;
863 int updating_cl_tabs = 0;
864 int updating_fl_tabs = 0;
865 char *global_search;
866 uint64_t blocked_cookies = 0;
867 char named_session[PATH_MAX];
868 int icon_size_map(int);
870 GtkListStore *completion_model;
871 void completion_add(struct tab *);
872 void completion_add_uri(const gchar *);
873 GtkListStore *buffers_store;
874 void xxx_dir(char *);
876 /* marks and quickmarks array storage.
877 * first a-z, then A-Z, then 0-9 */
878 char
879 indextomark(int i)
881 if (i < 0)
882 return 0;
884 if (i >= 0 && i <= 'z' - 'a')
885 return 'a' + i;
887 i -= 'z' - 'a' + 1;
888 if (i >= 0 && i <= 'Z' - 'A')
889 return 'A' + i;
891 i -= 'Z' - 'A' + 1;
892 if (i >= 10)
893 return 0;
895 return i + '0';
899 marktoindex(char m)
901 int ret = 0;
903 if (m >= 'a' && m <= 'z')
904 return ret + m - 'a';
906 ret += 'z' - 'a' + 1;
907 if (m >= 'A' && m <= 'Z')
908 return ret + m - 'A';
910 ret += 'Z' - 'A' + 1;
911 if (m >= '0' && m <= '9')
912 return ret + m - '0';
914 return -1;
918 void
919 sigchild(int sig)
921 int saved_errno, status;
922 pid_t pid;
924 saved_errno = errno;
926 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
927 if (pid == -1) {
928 if (errno == EINTR)
929 continue;
930 if (errno != ECHILD) {
932 clog_warn("sigchild: waitpid:");
935 break;
938 if (WIFEXITED(status)) {
939 if (WEXITSTATUS(status) != 0) {
941 clog_warnx("sigchild: child exit status: %d",
942 WEXITSTATUS(status));
945 } else {
947 clog_warnx("sigchild: child is terminated abnormally");
952 errno = saved_errno;
956 is_g_object_setting(GObject *o, char *str)
958 guint n_props = 0, i;
959 GParamSpec **proplist;
961 if (! G_IS_OBJECT(o))
962 return (0);
964 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
965 &n_props);
967 for (i=0; i < n_props; i++) {
968 if (! strcmp(proplist[i]->name, str))
969 return (1);
971 return (0);
974 gchar *
975 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
977 gchar *r;
979 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
980 "<head>\n"
981 "<title>%s</title>\n"
982 "%s"
983 "%s"
984 "</head>\n"
985 "<body>\n"
986 "<h1>%s</h1>\n"
987 "%s\n</body>\n"
988 "</html>",
989 title,
990 addstyles ? XT_PAGE_STYLE : "",
991 head,
992 title,
993 body);
995 return r;
999 * Display a web page from a HTML string in memory, rather than from a URL
1001 void
1002 load_webkit_string(struct tab *t, const char *str, gchar *title)
1004 char file[PATH_MAX];
1005 int i;
1007 /* we set this to indicate we want to manually do navaction */
1008 if (t->bfl)
1009 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1011 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1012 if (title) {
1013 /* set t->xtp_meaning */
1014 for (i = 0; i < LENGTH(about_list); i++)
1015 if (!strcmp(title, about_list[i].name)) {
1016 t->xtp_meaning = i;
1017 break;
1020 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1021 #if GTK_CHECK_VERSION(2, 20, 0)
1022 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1023 gtk_widget_hide(t->spinner);
1024 #endif
1025 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1026 xt_icon_from_file(t, file);
1030 struct tab *
1031 get_current_tab(void)
1033 struct tab *t;
1035 TAILQ_FOREACH(t, &tabs, entry) {
1036 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1037 return (t);
1040 warnx("%s: no current tab", __func__);
1042 return (NULL);
1045 void
1046 set_status(struct tab *t, gchar *s, int status)
1048 gchar *type = NULL;
1050 if (s == NULL)
1051 return;
1053 switch (status) {
1054 case XT_STATUS_LOADING:
1055 type = g_strdup_printf("Loading: %s", s);
1056 s = type;
1057 break;
1058 case XT_STATUS_LINK:
1059 type = g_strdup_printf("Link: %s", s);
1060 if (!t->status)
1061 t->status = g_strdup(gtk_entry_get_text(
1062 GTK_ENTRY(t->sbe.statusbar)));
1063 s = type;
1064 break;
1065 case XT_STATUS_URI:
1066 type = g_strdup_printf("%s", s);
1067 if (!t->status) {
1068 t->status = g_strdup(type);
1070 s = type;
1071 if (!t->status)
1072 t->status = g_strdup(s);
1073 break;
1074 case XT_STATUS_NOTHING:
1075 /* FALL THROUGH */
1076 default:
1077 break;
1079 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1080 if (type)
1081 g_free(type);
1084 void
1085 hide_cmd(struct tab *t)
1087 gtk_widget_hide(t->cmd);
1090 void
1091 show_cmd(struct tab *t)
1093 gtk_widget_hide(t->oops);
1094 gtk_widget_show(t->cmd);
1097 void
1098 hide_buffers(struct tab *t)
1100 gtk_widget_hide(t->buffers);
1101 gtk_list_store_clear(buffers_store);
1104 enum {
1105 COL_ID = 0,
1106 COL_TITLE,
1107 NUM_COLS
1111 sort_tabs_by_page_num(struct tab ***stabs)
1113 int num_tabs = 0;
1114 struct tab *t;
1116 num_tabs = gtk_notebook_get_n_pages(notebook);
1118 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1120 TAILQ_FOREACH(t, &tabs, entry)
1121 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1123 return (num_tabs);
1126 void
1127 buffers_make_list(void)
1129 int i, num_tabs;
1130 const gchar *title = NULL;
1131 GtkTreeIter iter;
1132 struct tab **stabs = NULL;
1134 num_tabs = sort_tabs_by_page_num(&stabs);
1136 for (i = 0; i < num_tabs; i++)
1137 if (stabs[i]) {
1138 gtk_list_store_append(buffers_store, &iter);
1139 title = get_title(stabs[i], FALSE);
1140 gtk_list_store_set(buffers_store, &iter,
1141 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1142 * rather than 0. */
1143 COL_TITLE, title,
1144 -1);
1147 g_free(stabs);
1150 void
1151 show_buffers(struct tab *t)
1153 buffers_make_list();
1154 gtk_widget_show(t->buffers);
1155 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1158 void
1159 toggle_buffers(struct tab *t)
1161 if (gtk_widget_get_visible(t->buffers))
1162 hide_buffers(t);
1163 else
1164 show_buffers(t);
1168 buffers(struct tab *t, struct karg *args)
1170 show_buffers(t);
1172 return (0);
1175 void
1176 hide_oops(struct tab *t)
1178 gtk_widget_hide(t->oops);
1181 void
1182 show_oops(struct tab *at, const char *fmt, ...)
1184 va_list ap;
1185 char *msg = NULL;
1186 struct tab *t = NULL;
1188 if (fmt == NULL)
1189 return;
1191 if (at == NULL) {
1192 if ((t = get_current_tab()) == NULL)
1193 return;
1194 } else
1195 t = at;
1197 va_start(ap, fmt);
1198 if (vasprintf(&msg, fmt, ap) == -1)
1199 errx(1, "show_oops failed");
1200 va_end(ap);
1202 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1203 gtk_widget_hide(t->cmd);
1204 gtk_widget_show(t->oops);
1206 if (msg)
1207 free(msg);
1210 char *
1211 get_as_string(struct settings *s)
1213 char *r = NULL;
1215 if (s == NULL)
1216 return (NULL);
1218 if (s->s) {
1219 if (s->s->get)
1220 r = s->s->get(s);
1221 else
1222 warnx("get_as_string skip %s\n", s->name);
1223 } else if (s->type == XT_S_INT)
1224 r = g_strdup_printf("%d", *s->ival);
1225 else if (s->type == XT_S_STR)
1226 r = g_strdup(*s->sval);
1227 else if (s->type == XT_S_FLOAT)
1228 r = g_strdup_printf("%f", *s->fval);
1229 else
1230 r = g_strdup_printf("INVALID TYPE");
1232 return (r);
1235 void
1236 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1238 int i;
1239 char *s;
1241 for (i = 0; i < LENGTH(rs); i++) {
1242 if (rs[i].s && rs[i].s->walk)
1243 rs[i].s->walk(&rs[i], cb, cb_args);
1244 else {
1245 s = get_as_string(&rs[i]);
1246 cb(&rs[i], s, cb_args);
1247 g_free(s);
1253 set_browser_mode(struct settings *s, char *val)
1255 if (!strcmp(val, "whitelist")) {
1256 browser_mode = XT_BM_WHITELIST;
1257 allow_volatile_cookies = 0;
1258 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1259 cookies_enabled = 1;
1260 enable_cookie_whitelist = 1;
1261 read_only_cookies = 0;
1262 save_rejected_cookies = 0;
1263 session_timeout = 3600;
1264 enable_scripts = 0;
1265 enable_js_whitelist = 1;
1266 enable_localstorage = 0;
1267 } else if (!strcmp(val, "normal")) {
1268 browser_mode = XT_BM_NORMAL;
1269 allow_volatile_cookies = 0;
1270 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1271 cookies_enabled = 1;
1272 enable_cookie_whitelist = 0;
1273 read_only_cookies = 0;
1274 save_rejected_cookies = 0;
1275 session_timeout = 3600;
1276 enable_scripts = 1;
1277 enable_js_whitelist = 0;
1278 enable_localstorage = 1;
1279 } else if (!strcmp(val, "kiosk")) {
1280 browser_mode = XT_BM_KIOSK;
1281 allow_volatile_cookies = 0;
1282 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1283 cookies_enabled = 1;
1284 enable_cookie_whitelist = 0;
1285 read_only_cookies = 0;
1286 save_rejected_cookies = 0;
1287 session_timeout = 3600;
1288 enable_scripts = 1;
1289 enable_js_whitelist = 0;
1290 enable_localstorage = 1;
1291 show_tabs = 0;
1292 tabless = 1;
1293 } else
1294 return (1);
1296 return (0);
1299 char *
1300 get_browser_mode(struct settings *s)
1302 char *r = NULL;
1304 if (browser_mode == XT_BM_WHITELIST)
1305 r = g_strdup("whitelist");
1306 else if (browser_mode == XT_BM_NORMAL)
1307 r = g_strdup("normal");
1308 else if (browser_mode == XT_BM_KIOSK)
1309 r = g_strdup("kiosk");
1310 else
1311 return (NULL);
1313 return (r);
1317 set_cookie_policy(struct settings *s, char *val)
1319 if (!strcmp(val, "no3rdparty"))
1320 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1321 else if (!strcmp(val, "accept"))
1322 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1323 else if (!strcmp(val, "reject"))
1324 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1325 else
1326 return (1);
1328 return (0);
1331 char *
1332 get_cookie_policy(struct settings *s)
1334 char *r = NULL;
1336 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1337 r = g_strdup("no3rdparty");
1338 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1339 r = g_strdup("accept");
1340 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1341 r = g_strdup("reject");
1342 else
1343 return (NULL);
1345 return (r);
1348 char *
1349 get_default_script(struct settings *s)
1351 if (default_script[0] == '\0')
1352 return (0);
1353 return (g_strdup(default_script));
1357 set_default_script(struct settings *s, char *val)
1359 if (val[0] == '~')
1360 snprintf(default_script, sizeof default_script, "%s/%s",
1361 pwd->pw_dir, &val[1]);
1362 else
1363 strlcpy(default_script, val, sizeof default_script);
1365 return (0);
1368 char *
1369 get_download_dir(struct settings *s)
1371 if (download_dir[0] == '\0')
1372 return (0);
1373 return (g_strdup(download_dir));
1377 set_download_dir(struct settings *s, char *val)
1379 if (val[0] == '~')
1380 snprintf(download_dir, sizeof download_dir, "%s/%s",
1381 pwd->pw_dir, &val[1]);
1382 else
1383 strlcpy(download_dir, val, sizeof download_dir);
1385 return (0);
1389 * Session IDs.
1390 * We use these to prevent people putting xxxt:// URLs on
1391 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1393 #define XT_XTP_SES_KEY_SZ 8
1394 #define XT_XTP_SES_KEY_HEX_FMT \
1395 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1396 char *dl_session_key; /* downloads */
1397 char *hl_session_key; /* history list */
1398 char *cl_session_key; /* cookie list */
1399 char *fl_session_key; /* favorites list */
1401 char work_dir[PATH_MAX];
1402 char certs_dir[PATH_MAX];
1403 char cache_dir[PATH_MAX];
1404 char sessions_dir[PATH_MAX];
1405 char cookie_file[PATH_MAX];
1406 SoupURI *proxy_uri = NULL;
1407 SoupSession *session;
1408 SoupCookieJar *s_cookiejar;
1409 SoupCookieJar *p_cookiejar;
1410 char rc_fname[PATH_MAX];
1412 struct mime_type_list mtl;
1413 struct alias_list aliases;
1415 /* protos */
1416 struct tab *create_new_tab(char *, struct undo *, int, int);
1417 void delete_tab(struct tab *);
1418 void setzoom_webkit(struct tab *, int);
1419 int run_script(struct tab *, char *);
1420 int download_rb_cmp(struct download *, struct download *);
1421 gboolean cmd_execute(struct tab *t, char *str);
1424 history_rb_cmp(struct history *h1, struct history *h2)
1426 return (strcmp(h1->uri, h2->uri));
1428 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1431 domain_rb_cmp(struct domain *d1, struct domain *d2)
1433 return (strcmp(d1->d, d2->d));
1435 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1437 char *
1438 get_work_dir(struct settings *s)
1440 if (work_dir[0] == '\0')
1441 return (0);
1442 return (g_strdup(work_dir));
1446 set_work_dir(struct settings *s, char *val)
1448 if (val[0] == '~')
1449 snprintf(work_dir, sizeof work_dir, "%s/%s",
1450 pwd->pw_dir, &val[1]);
1451 else
1452 strlcpy(work_dir, val, sizeof work_dir);
1454 return (0);
1457 char *
1458 get_tab_style(struct settings *s)
1460 if (tab_style == XT_TABS_NORMAL)
1461 return (g_strdup("normal"));
1462 else
1463 return (g_strdup("compact"));
1467 set_tab_style(struct settings *s, char *val)
1469 if (!strcmp(val, "normal"))
1470 tab_style = XT_TABS_NORMAL;
1471 else if (!strcmp(val, "compact"))
1472 tab_style = XT_TABS_COMPACT;
1473 else
1474 return (1);
1476 return (0);
1480 * generate a session key to secure xtp commands.
1481 * pass in a ptr to the key in question and it will
1482 * be modified in place.
1484 void
1485 generate_xtp_session_key(char **key)
1487 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1489 /* free old key */
1490 if (*key)
1491 g_free(*key);
1493 /* make a new one */
1494 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1495 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1496 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1497 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1499 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1503 * validate a xtp session key.
1504 * return 1 if OK
1507 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1509 if (strcmp(trusted, untrusted) != 0) {
1510 show_oops(t, "%s: xtp session key mismatch possible spoof",
1511 __func__);
1512 return (0);
1515 return (1);
1519 download_rb_cmp(struct download *e1, struct download *e2)
1521 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1523 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1525 struct valid_url_types {
1526 char *type;
1527 } vut[] = {
1528 { "http://" },
1529 { "https://" },
1530 { "ftp://" },
1531 { "file://" },
1532 { XT_XTP_STR },
1536 valid_url_type(char *url)
1538 int i;
1540 for (i = 0; i < LENGTH(vut); i++)
1541 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1542 return (0);
1544 return (1);
1547 void
1548 print_cookie(char *msg, SoupCookie *c)
1550 if (c == NULL)
1551 return;
1553 if (msg)
1554 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1555 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1556 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1557 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1558 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1559 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1560 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1561 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1562 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1563 DNPRINTF(XT_D_COOKIE, "====================================\n");
1566 void
1567 walk_alias(struct settings *s,
1568 void (*cb)(struct settings *, char *, void *), void *cb_args)
1570 struct alias *a;
1571 char *str;
1573 if (s == NULL || cb == NULL) {
1574 show_oops(NULL, "walk_alias invalid parameters");
1575 return;
1578 TAILQ_FOREACH(a, &aliases, entry) {
1579 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1580 cb(s, str, cb_args);
1581 g_free(str);
1585 char *
1586 match_alias(char *url_in)
1588 struct alias *a;
1589 char *arg;
1590 char *url_out = NULL, *search, *enc_arg;
1592 search = g_strdup(url_in);
1593 arg = search;
1594 if (strsep(&arg, " \t") == NULL) {
1595 show_oops(NULL, "match_alias: NULL URL");
1596 goto done;
1599 TAILQ_FOREACH(a, &aliases, entry) {
1600 if (!strcmp(search, a->a_name))
1601 break;
1604 if (a != NULL) {
1605 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1606 a->a_name);
1607 if (arg != NULL) {
1608 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1609 url_out = g_strdup_printf(a->a_uri, enc_arg);
1610 g_free(enc_arg);
1611 } else
1612 url_out = g_strdup_printf(a->a_uri, "");
1614 done:
1615 g_free(search);
1616 return (url_out);
1619 char *
1620 guess_url_type(char *url_in)
1622 struct stat sb;
1623 char *url_out = NULL, *enc_search = NULL;
1625 url_out = match_alias(url_in);
1626 if (url_out != NULL)
1627 return (url_out);
1629 if (guess_search && url_regex &&
1630 !(g_str_has_prefix(url_in, "http://") ||
1631 g_str_has_prefix(url_in, "https://"))) {
1632 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1633 /* invalid URI so search instead */
1634 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1635 url_out = g_strdup_printf(search_string, enc_search);
1636 g_free(enc_search);
1637 goto done;
1641 /* XXX not sure about this heuristic */
1642 if (stat(url_in, &sb) == 0)
1643 url_out = g_strdup_printf("file://%s", url_in);
1644 else
1645 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1646 done:
1647 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1649 return (url_out);
1652 void
1653 load_uri(struct tab *t, gchar *uri)
1655 struct karg args;
1656 gchar *newuri = NULL;
1657 int i;
1659 if (uri == NULL)
1660 return;
1662 /* Strip leading spaces. */
1663 while (*uri && isspace(*uri))
1664 uri++;
1666 if (strlen(uri) == 0) {
1667 blank(t, NULL);
1668 return;
1671 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1673 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1674 for (i = 0; i < LENGTH(about_list); i++)
1675 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1676 bzero(&args, sizeof args);
1677 about_list[i].func(t, &args);
1678 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1679 FALSE);
1680 return;
1682 show_oops(t, "invalid about page");
1683 return;
1686 if (valid_url_type(uri)) {
1687 newuri = guess_url_type(uri);
1688 uri = newuri;
1691 set_status(t, (char *)uri, XT_STATUS_LOADING);
1692 marks_clear(t);
1693 webkit_web_view_load_uri(t->wv, uri);
1695 if (newuri)
1696 g_free(newuri);
1699 const gchar *
1700 get_uri(struct tab *t)
1702 const gchar *uri = NULL;
1704 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1705 return t->tmp_uri;
1706 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1707 uri = webkit_web_view_get_uri(t->wv);
1708 } else {
1709 /* use tmp_uri to make sure it is g_freed */
1710 if (t->tmp_uri)
1711 g_free(t->tmp_uri);
1712 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1713 about_list[t->xtp_meaning].name);
1714 uri = t->tmp_uri;
1716 return uri;
1719 const gchar *
1720 get_title(struct tab *t, bool window)
1722 const gchar *set = NULL, *title = NULL;
1723 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1725 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1726 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1727 goto notitle;
1729 title = webkit_web_view_get_title(t->wv);
1730 if ((set = title ? title : get_uri(t)))
1731 return set;
1733 notitle:
1734 set = window ? XT_NAME : "(untitled)";
1736 return set;
1740 add_alias(struct settings *s, char *line)
1742 char *l, *alias;
1743 struct alias *a = NULL;
1745 if (s == NULL || line == NULL) {
1746 show_oops(NULL, "add_alias invalid parameters");
1747 return (1);
1750 l = line;
1751 a = g_malloc(sizeof(*a));
1753 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1754 show_oops(NULL, "add_alias: incomplete alias definition");
1755 goto bad;
1757 if (strlen(alias) == 0 || strlen(l) == 0) {
1758 show_oops(NULL, "add_alias: invalid alias definition");
1759 goto bad;
1762 a->a_name = g_strdup(alias);
1763 a->a_uri = g_strdup(l);
1765 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1767 TAILQ_INSERT_TAIL(&aliases, a, entry);
1769 return (0);
1770 bad:
1771 if (a)
1772 g_free(a);
1773 return (1);
1777 add_mime_type(struct settings *s, char *line)
1779 char *mime_type;
1780 char *l;
1781 struct mime_type *m = NULL;
1782 int downloadfirst = 0;
1784 /* XXX this could be smarter */
1786 if (line == NULL || strlen(line) == 0) {
1787 show_oops(NULL, "add_mime_type invalid parameters");
1788 return (1);
1791 l = line;
1792 if (*l == '@') {
1793 downloadfirst = 1;
1794 l++;
1796 m = g_malloc(sizeof(*m));
1798 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1799 show_oops(NULL, "add_mime_type: invalid mime_type");
1800 goto bad;
1802 if (mime_type[strlen(mime_type) - 1] == '*') {
1803 mime_type[strlen(mime_type) - 1] = '\0';
1804 m->mt_default = 1;
1805 } else
1806 m->mt_default = 0;
1808 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1809 show_oops(NULL, "add_mime_type: invalid mime_type");
1810 goto bad;
1813 m->mt_type = g_strdup(mime_type);
1814 m->mt_action = g_strdup(l);
1815 m->mt_download = downloadfirst;
1817 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1818 m->mt_type, m->mt_action, m->mt_default);
1820 TAILQ_INSERT_TAIL(&mtl, m, entry);
1822 return (0);
1823 bad:
1824 if (m)
1825 g_free(m);
1826 return (1);
1829 struct mime_type *
1830 find_mime_type(char *mime_type)
1832 struct mime_type *m, *def = NULL, *rv = NULL;
1834 TAILQ_FOREACH(m, &mtl, entry) {
1835 if (m->mt_default &&
1836 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1837 def = m;
1839 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1840 rv = m;
1841 break;
1845 if (rv == NULL)
1846 rv = def;
1848 return (rv);
1851 void
1852 walk_mime_type(struct settings *s,
1853 void (*cb)(struct settings *, char *, void *), void *cb_args)
1855 struct mime_type *m;
1856 char *str;
1858 if (s == NULL || cb == NULL) {
1859 show_oops(NULL, "walk_mime_type invalid parameters");
1860 return;
1863 TAILQ_FOREACH(m, &mtl, entry) {
1864 str = g_strdup_printf("%s%s --> %s",
1865 m->mt_type,
1866 m->mt_default ? "*" : "",
1867 m->mt_action);
1868 cb(s, str, cb_args);
1869 g_free(str);
1873 void
1874 wl_add(char *str, struct domain_list *wl, int handy)
1876 struct domain *d;
1877 int add_dot = 0;
1878 char *p;
1880 if (str == NULL || wl == NULL || strlen(str) < 2)
1881 return;
1883 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1885 /* treat *.moo.com the same as .moo.com */
1886 if (str[0] == '*' && str[1] == '.')
1887 str = &str[1];
1888 else if (str[0] == '.')
1889 str = &str[0];
1890 else
1891 add_dot = 1;
1893 /* slice off port number */
1894 p = g_strrstr(str, ":");
1895 if (p)
1896 *p = '\0';
1898 d = g_malloc(sizeof *d);
1899 if (add_dot)
1900 d->d = g_strdup_printf(".%s", str);
1901 else
1902 d->d = g_strdup(str);
1903 d->handy = handy;
1905 if (RB_INSERT(domain_list, wl, d))
1906 goto unwind;
1908 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1909 return;
1910 unwind:
1911 if (d) {
1912 if (d->d)
1913 g_free(d->d);
1914 g_free(d);
1919 add_cookie_wl(struct settings *s, char *entry)
1921 wl_add(entry, &c_wl, 1);
1922 return (0);
1925 void
1926 walk_cookie_wl(struct settings *s,
1927 void (*cb)(struct settings *, char *, void *), void *cb_args)
1929 struct domain *d;
1931 if (s == NULL || cb == NULL) {
1932 show_oops(NULL, "walk_cookie_wl invalid parameters");
1933 return;
1936 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1937 cb(s, d->d, cb_args);
1940 void
1941 walk_js_wl(struct settings *s,
1942 void (*cb)(struct settings *, char *, void *), void *cb_args)
1944 struct domain *d;
1946 if (s == NULL || cb == NULL) {
1947 show_oops(NULL, "walk_js_wl invalid parameters");
1948 return;
1951 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1952 cb(s, d->d, cb_args);
1956 add_js_wl(struct settings *s, char *entry)
1958 wl_add(entry, &js_wl, 1 /* persistent */);
1959 return (0);
1962 struct domain *
1963 wl_find(const gchar *search, struct domain_list *wl)
1965 int i;
1966 struct domain *d = NULL, dfind;
1967 gchar *s = NULL;
1969 if (search == NULL || wl == NULL)
1970 return (NULL);
1971 if (strlen(search) < 2)
1972 return (NULL);
1974 if (search[0] != '.')
1975 s = g_strdup_printf(".%s", search);
1976 else
1977 s = g_strdup(search);
1979 for (i = strlen(s) - 1; i >= 0; i--) {
1980 if (s[i] == '.') {
1981 dfind.d = &s[i];
1982 d = RB_FIND(domain_list, wl, &dfind);
1983 if (d)
1984 goto done;
1988 done:
1989 if (s)
1990 g_free(s);
1992 return (d);
1995 struct domain *
1996 wl_find_uri(const gchar *s, struct domain_list *wl)
1998 int i;
1999 char *ss;
2000 struct domain *r;
2002 if (s == NULL || wl == NULL)
2003 return (NULL);
2005 if (!strncmp(s, "http://", strlen("http://")))
2006 s = &s[strlen("http://")];
2007 else if (!strncmp(s, "https://", strlen("https://")))
2008 s = &s[strlen("https://")];
2010 if (strlen(s) < 2)
2011 return (NULL);
2013 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2014 /* chop string at first slash */
2015 if (s[i] == '/' || s[i] == '\0') {
2016 ss = g_strdup(s);
2017 ss[i] = '\0';
2018 r = wl_find(ss, wl);
2019 g_free(ss);
2020 return (r);
2023 return (NULL);
2027 settings_add(char *var, char *val)
2029 int i, rv, *p;
2030 gfloat *f;
2031 char **s;
2033 /* get settings */
2034 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2035 if (strcmp(var, rs[i].name))
2036 continue;
2038 if (rs[i].s) {
2039 if (rs[i].s->set(&rs[i], val))
2040 errx(1, "invalid value for %s: %s", var, val);
2041 rv = 1;
2042 break;
2043 } else
2044 switch (rs[i].type) {
2045 case XT_S_INT:
2046 p = rs[i].ival;
2047 *p = atoi(val);
2048 rv = 1;
2049 break;
2050 case XT_S_STR:
2051 s = rs[i].sval;
2052 if (s == NULL)
2053 errx(1, "invalid sval for %s",
2054 rs[i].name);
2055 if (*s)
2056 g_free(*s);
2057 *s = g_strdup(val);
2058 rv = 1;
2059 break;
2060 case XT_S_FLOAT:
2061 f = rs[i].fval;
2062 *f = atof(val);
2063 rv = 1;
2064 break;
2065 case XT_S_INVALID:
2066 default:
2067 errx(1, "invalid type for %s", var);
2069 break;
2071 return (rv);
2074 #define WS "\n= \t"
2075 void
2076 config_parse(char *filename, int runtime)
2078 FILE *config, *f;
2079 char *line, *cp, *var, *val;
2080 size_t len, lineno = 0;
2081 int handled;
2082 char file[PATH_MAX];
2083 struct stat sb;
2085 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2087 if (filename == NULL)
2088 return;
2090 if (runtime && runtime_settings[0] != '\0') {
2091 snprintf(file, sizeof file, "%s/%s",
2092 work_dir, runtime_settings);
2093 if (stat(file, &sb)) {
2094 warnx("runtime file doesn't exist, creating it");
2095 if ((f = fopen(file, "w")) == NULL)
2096 err(1, "runtime");
2097 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2098 fclose(f);
2100 } else
2101 strlcpy(file, filename, sizeof file);
2103 if ((config = fopen(file, "r")) == NULL) {
2104 warn("config_parse: cannot open %s", filename);
2105 return;
2108 for (;;) {
2109 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2110 if (feof(config) || ferror(config))
2111 break;
2113 cp = line;
2114 cp += (long)strspn(cp, WS);
2115 if (cp[0] == '\0') {
2116 /* empty line */
2117 free(line);
2118 continue;
2121 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2122 startpage_add("invalid configuration file entry: %s",
2123 line);
2125 cp += (long)strspn(cp, WS);
2127 if ((val = strsep(&cp, "\0")) == NULL)
2128 break;
2130 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2131 handled = settings_add(var, val);
2132 if (handled == 0)
2133 startpage_add("invalid configuration file entry: %s=%s",
2134 var, val);
2136 free(line);
2139 fclose(config);
2142 char *
2143 js_ref_to_string(JSContextRef context, JSValueRef ref)
2145 char *s = NULL;
2146 size_t l;
2147 JSStringRef jsref;
2149 jsref = JSValueToStringCopy(context, ref, NULL);
2150 if (jsref == NULL)
2151 return (NULL);
2153 l = JSStringGetMaximumUTF8CStringSize(jsref);
2154 s = g_malloc(l);
2155 if (s)
2156 JSStringGetUTF8CString(jsref, s, l);
2157 JSStringRelease(jsref);
2159 return (s);
2162 void
2163 disable_hints(struct tab *t)
2165 bzero(t->hint_buf, sizeof t->hint_buf);
2166 bzero(t->hint_num, sizeof t->hint_num);
2167 run_script(t, "vimprobable_clear()");
2168 t->hints_on = 0;
2169 t->hint_mode = XT_HINT_NONE;
2172 void
2173 enable_hints(struct tab *t)
2175 bzero(t->hint_buf, sizeof t->hint_buf);
2176 run_script(t, "vimprobable_show_hints()");
2177 t->hints_on = 1;
2178 t->hint_mode = XT_HINT_NONE;
2181 #define XT_JS_OPEN ("open;")
2182 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2183 #define XT_JS_FIRE ("fire;")
2184 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2185 #define XT_JS_FOUND ("found;")
2186 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2189 run_script(struct tab *t, char *s)
2191 JSGlobalContextRef ctx;
2192 WebKitWebFrame *frame;
2193 JSStringRef str;
2194 JSValueRef val, exception;
2195 char *es, buf[128];
2197 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2198 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2200 frame = webkit_web_view_get_main_frame(t->wv);
2201 ctx = webkit_web_frame_get_global_context(frame);
2203 str = JSStringCreateWithUTF8CString(s);
2204 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2205 NULL, 0, &exception);
2206 JSStringRelease(str);
2208 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2209 if (val == NULL) {
2210 es = js_ref_to_string(ctx, exception);
2211 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2212 g_free(es);
2213 return (1);
2214 } else {
2215 es = js_ref_to_string(ctx, val);
2216 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2218 /* handle return value right here */
2219 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2220 disable_hints(t);
2221 marks_clear(t);
2222 load_uri(t, &es[XT_JS_OPEN_LEN]);
2225 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2226 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2227 &es[XT_JS_FIRE_LEN]);
2228 run_script(t, buf);
2229 disable_hints(t);
2232 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2233 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2234 disable_hints(t);
2237 g_free(es);
2240 return (0);
2244 hint(struct tab *t, struct karg *args)
2247 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2249 if (t->hints_on == 0)
2250 enable_hints(t);
2251 else
2252 disable_hints(t);
2254 return (0);
2257 void
2258 apply_style(struct tab *t)
2260 g_object_set(G_OBJECT(t->settings),
2261 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2265 userstyle(struct tab *t, struct karg *args)
2267 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2269 if (t->styled) {
2270 t->styled = 0;
2271 g_object_set(G_OBJECT(t->settings),
2272 "user-stylesheet-uri", NULL, (char *)NULL);
2273 } else {
2274 t->styled = 1;
2275 apply_style(t);
2277 return (0);
2281 * Doesn't work fully, due to the following bug:
2282 * https://bugs.webkit.org/show_bug.cgi?id=51747
2285 restore_global_history(void)
2287 char file[PATH_MAX];
2288 FILE *f;
2289 struct history *h;
2290 gchar *uri;
2291 gchar *title;
2292 const char delim[3] = {'\\', '\\', '\0'};
2294 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2296 if ((f = fopen(file, "r")) == NULL) {
2297 warnx("%s: fopen", __func__);
2298 return (1);
2301 for (;;) {
2302 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2303 if (feof(f) || ferror(f))
2304 break;
2306 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2307 if (feof(f) || ferror(f)) {
2308 free(uri);
2309 warnx("%s: broken history file\n", __func__);
2310 return (1);
2313 if (uri && strlen(uri) && title && strlen(title)) {
2314 webkit_web_history_item_new_with_data(uri, title);
2315 h = g_malloc(sizeof(struct history));
2316 h->uri = g_strdup(uri);
2317 h->title = g_strdup(title);
2318 RB_INSERT(history_list, &hl, h);
2319 completion_add_uri(h->uri);
2320 } else {
2321 warnx("%s: failed to restore history\n", __func__);
2322 free(uri);
2323 free(title);
2324 return (1);
2327 free(uri);
2328 free(title);
2329 uri = NULL;
2330 title = NULL;
2333 return (0);
2337 save_global_history_to_disk(struct tab *t)
2339 char file[PATH_MAX];
2340 FILE *f;
2341 struct history *h;
2343 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2345 if ((f = fopen(file, "w")) == NULL) {
2346 show_oops(t, "%s: global history file: %s",
2347 __func__, strerror(errno));
2348 return (1);
2351 RB_FOREACH_REVERSE(h, history_list, &hl) {
2352 if (h->uri && h->title)
2353 fprintf(f, "%s\n%s\n", h->uri, h->title);
2356 fclose(f);
2358 return (0);
2362 quit(struct tab *t, struct karg *args)
2364 if (save_global_history)
2365 save_global_history_to_disk(t);
2367 gtk_main_quit();
2369 return (1);
2373 open_tabs(struct tab *t, struct karg *a)
2375 char file[PATH_MAX];
2376 FILE *f = NULL;
2377 char *uri = NULL;
2378 int rv = 1;
2379 struct tab *ti, *tt;
2381 if (a == NULL)
2382 goto done;
2384 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2385 if ((f = fopen(file, "r")) == NULL)
2386 goto done;
2388 ti = TAILQ_LAST(&tabs, tab_list);
2390 for (;;) {
2391 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2392 if (feof(f) || ferror(f))
2393 break;
2395 /* retrieve session name */
2396 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2397 strlcpy(named_session,
2398 &uri[strlen(XT_SAVE_SESSION_ID)],
2399 sizeof named_session);
2400 continue;
2403 if (uri && strlen(uri))
2404 create_new_tab(uri, NULL, 1, -1);
2406 free(uri);
2407 uri = NULL;
2410 /* close open tabs */
2411 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2412 for (;;) {
2413 tt = TAILQ_FIRST(&tabs);
2414 if (tt != ti) {
2415 delete_tab(tt);
2416 continue;
2418 delete_tab(tt);
2419 break;
2421 recalc_tabs();
2424 rv = 0;
2425 done:
2426 if (f)
2427 fclose(f);
2429 return (rv);
2433 restore_saved_tabs(void)
2435 char file[PATH_MAX];
2436 int unlink_file = 0;
2437 struct stat sb;
2438 struct karg a;
2439 int rv = 0;
2441 snprintf(file, sizeof file, "%s/%s",
2442 sessions_dir, XT_RESTART_TABS_FILE);
2443 if (stat(file, &sb) == -1)
2444 a.s = XT_SAVED_TABS_FILE;
2445 else {
2446 unlink_file = 1;
2447 a.s = XT_RESTART_TABS_FILE;
2450 a.i = XT_SES_DONOTHING;
2451 rv = open_tabs(NULL, &a);
2453 if (unlink_file)
2454 unlink(file);
2456 return (rv);
2460 save_tabs(struct tab *t, struct karg *a)
2462 char file[PATH_MAX];
2463 FILE *f;
2464 int num_tabs = 0, i;
2465 struct tab **stabs = NULL;
2467 if (a == NULL)
2468 return (1);
2469 if (a->s == NULL)
2470 snprintf(file, sizeof file, "%s/%s",
2471 sessions_dir, named_session);
2472 else
2473 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2475 if ((f = fopen(file, "w")) == NULL) {
2476 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2477 return (1);
2480 /* save session name */
2481 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2483 /* Save tabs, in the order they are arranged in the notebook. */
2484 num_tabs = sort_tabs_by_page_num(&stabs);
2486 for (i = 0; i < num_tabs; i++)
2487 if (stabs[i]) {
2488 if (get_uri(stabs[i]) != NULL)
2489 fprintf(f, "%s\n", get_uri(stabs[i]));
2490 else if (gtk_entry_get_text(GTK_ENTRY(
2491 stabs[i]->uri_entry)))
2492 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2493 stabs[i]->uri_entry)));
2496 g_free(stabs);
2498 /* try and make sure this gets to disk NOW. XXX Backup first? */
2499 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2500 show_oops(t, "May not have managed to save session: %s",
2501 strerror(errno));
2504 fclose(f);
2506 return (0);
2510 save_tabs_and_quit(struct tab *t, struct karg *args)
2512 struct karg a;
2514 a.s = NULL;
2515 save_tabs(t, &a);
2516 quit(t, NULL);
2518 return (1);
2522 run_page_script(struct tab *t, struct karg *args)
2524 const gchar *uri;
2525 char *tmp, script[PATH_MAX];
2527 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2528 if (tmp[0] == '\0') {
2529 show_oops(t, "no script specified");
2530 return (1);
2533 if ((uri = get_uri(t)) == NULL) {
2534 show_oops(t, "tab is empty, not running script");
2535 return (1);
2538 if (tmp[0] == '~')
2539 snprintf(script, sizeof script, "%s/%s",
2540 pwd->pw_dir, &tmp[1]);
2541 else
2542 strlcpy(script, tmp, sizeof script);
2544 switch (fork()) {
2545 case -1:
2546 show_oops(t, "can't fork to run script");
2547 return (1);
2548 /* NOTREACHED */
2549 case 0:
2550 break;
2551 default:
2552 return (0);
2555 /* child */
2556 execlp(script, script, uri, (void *)NULL);
2558 _exit(0);
2560 /* NOTREACHED */
2562 return (0);
2566 yank_uri(struct tab *t, struct karg *args)
2568 const gchar *uri;
2569 GtkClipboard *clipboard;
2571 if ((uri = get_uri(t)) == NULL)
2572 return (1);
2574 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2575 gtk_clipboard_set_text(clipboard, uri, -1);
2577 return (0);
2581 paste_uri(struct tab *t, struct karg *args)
2583 GtkClipboard *clipboard;
2584 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2585 gint len;
2586 gchar *p = NULL, *uri;
2588 /* try primary clipboard first */
2589 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2590 p = gtk_clipboard_wait_for_text(clipboard);
2592 /* if it failed get whatever text is in cut_buffer0 */
2593 if (p == NULL && xterm_workaround)
2594 if (gdk_property_get(gdk_get_default_root_window(),
2595 atom,
2596 gdk_atom_intern("STRING", FALSE),
2598 1024 * 1024 /* picked out of my butt */,
2599 FALSE,
2600 NULL,
2601 NULL,
2602 &len,
2603 (guchar **)&p)) {
2604 /* yes sir, we need to NUL the string */
2605 p[len] = '\0';
2608 if (p) {
2609 uri = p;
2610 while (*uri && isspace(*uri))
2611 uri++;
2612 if (strlen(uri) == 0) {
2613 show_oops(t, "empty paste buffer");
2614 goto done;
2616 if (guess_search == 0 && valid_url_type(uri)) {
2617 /* we can be clever and paste this in search box */
2618 show_oops(t, "not a valid URL");
2619 goto done;
2622 if (args->i == XT_PASTE_CURRENT_TAB)
2623 load_uri(t, uri);
2624 else if (args->i == XT_PASTE_NEW_TAB)
2625 create_new_tab(uri, NULL, 1, -1);
2628 done:
2629 if (p)
2630 g_free(p);
2632 return (0);
2635 gchar *
2636 find_domain(const gchar *s, int toplevel)
2638 SoupURI *uri;
2639 gchar *ret, *p;
2641 if (s == NULL)
2642 return (NULL);
2644 uri = soup_uri_new(s);
2646 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2647 return (NULL);
2650 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2651 if ((p = strrchr(uri->host, '.')) != NULL) {
2652 while(--p >= uri->host && *p != '.');
2653 p++;
2654 } else
2655 p = uri->host;
2656 } else
2657 p = uri->host;
2659 if (uri->port == 80)
2660 ret = g_strdup_printf(".%s", p);
2661 else
2662 ret = g_strdup_printf(".%s:%d", p, uri->port);
2664 soup_uri_free(uri);
2666 return ret;
2670 toggle_cwl(struct tab *t, struct karg *args)
2672 struct domain *d;
2673 const gchar *uri;
2674 char *dom = NULL;
2675 int es;
2677 if (args == NULL)
2678 return (1);
2680 uri = get_uri(t);
2681 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2683 if (uri == NULL || dom == NULL ||
2684 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2685 show_oops(t, "Can't toggle domain in cookie white list");
2686 goto done;
2688 d = wl_find(dom, &c_wl);
2690 if (d == NULL)
2691 es = 0;
2692 else
2693 es = 1;
2695 if (args->i & XT_WL_TOGGLE)
2696 es = !es;
2697 else if ((args->i & XT_WL_ENABLE) && es != 1)
2698 es = 1;
2699 else if ((args->i & XT_WL_DISABLE) && es != 0)
2700 es = 0;
2702 if (es)
2703 /* enable cookies for domain */
2704 wl_add(dom, &c_wl, 0);
2705 else
2706 /* disable cookies for domain */
2707 RB_REMOVE(domain_list, &c_wl, d);
2709 if (args->i & XT_WL_RELOAD)
2710 webkit_web_view_reload(t->wv);
2712 done:
2713 g_free(dom);
2714 return (0);
2718 toggle_js(struct tab *t, struct karg *args)
2720 int es;
2721 const gchar *uri;
2722 struct domain *d;
2723 char *dom = NULL;
2725 if (args == NULL)
2726 return (1);
2728 g_object_get(G_OBJECT(t->settings),
2729 "enable-scripts", &es, (char *)NULL);
2730 if (args->i & XT_WL_TOGGLE)
2731 es = !es;
2732 else if ((args->i & XT_WL_ENABLE) && es != 1)
2733 es = 1;
2734 else if ((args->i & XT_WL_DISABLE) && es != 0)
2735 es = 0;
2736 else
2737 return (1);
2739 uri = get_uri(t);
2740 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2742 if (uri == NULL || dom == NULL ||
2743 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2744 show_oops(t, "Can't toggle domain in JavaScript white list");
2745 goto done;
2748 if (es) {
2749 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2750 wl_add(dom, &js_wl, 0 /* session */);
2751 } else {
2752 d = wl_find(dom, &js_wl);
2753 if (d)
2754 RB_REMOVE(domain_list, &js_wl, d);
2755 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2757 g_object_set(G_OBJECT(t->settings),
2758 "enable-scripts", es, (char *)NULL);
2759 g_object_set(G_OBJECT(t->settings),
2760 "javascript-can-open-windows-automatically", es, (char *)NULL);
2761 webkit_web_view_set_settings(t->wv, t->settings);
2763 if (args->i & XT_WL_RELOAD)
2764 webkit_web_view_reload(t->wv);
2765 done:
2766 if (dom)
2767 g_free(dom);
2768 return (0);
2771 void
2772 js_toggle_cb(GtkWidget *w, struct tab *t)
2774 struct karg a;
2776 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2777 toggle_cwl(t, &a);
2779 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2780 toggle_js(t, &a);
2784 toggle_src(struct tab *t, struct karg *args)
2786 gboolean mode;
2788 if (t == NULL)
2789 return (0);
2791 mode = webkit_web_view_get_view_source_mode(t->wv);
2792 webkit_web_view_set_view_source_mode(t->wv, !mode);
2793 webkit_web_view_reload(t->wv);
2795 return (0);
2798 void
2799 focus_webview(struct tab *t)
2801 if (t == NULL)
2802 return;
2804 /* only grab focus if we are visible */
2805 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2806 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2810 focus(struct tab *t, struct karg *args)
2812 if (t == NULL || args == NULL)
2813 return (1);
2815 if (show_url == 0)
2816 return (0);
2818 if (args->i == XT_FOCUS_URI)
2819 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2820 else if (args->i == XT_FOCUS_SEARCH)
2821 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2823 return (0);
2827 stats(struct tab *t, struct karg *args)
2829 char *page, *body, *s, line[64 * 1024];
2830 uint64_t line_count = 0;
2831 FILE *r_cookie_f;
2833 if (t == NULL)
2834 show_oops(NULL, "stats invalid parameters");
2836 line[0] = '\0';
2837 if (save_rejected_cookies) {
2838 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2839 for (;;) {
2840 s = fgets(line, sizeof line, r_cookie_f);
2841 if (s == NULL || feof(r_cookie_f) ||
2842 ferror(r_cookie_f))
2843 break;
2844 line_count++;
2846 fclose(r_cookie_f);
2847 snprintf(line, sizeof line,
2848 "<br/>Cookies blocked(*) total: %llu", line_count);
2849 } else
2850 show_oops(t, "Can't open blocked cookies file: %s",
2851 strerror(errno));
2854 body = g_strdup_printf(
2855 "Cookies blocked(*) this session: %llu"
2856 "%s"
2857 "<p><small><b>*</b> results vary based on settings</small></p>",
2858 blocked_cookies,
2859 line);
2861 page = get_html_page("Statistics", body, "", 0);
2862 g_free(body);
2864 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2865 g_free(page);
2867 return (0);
2871 marco(struct tab *t, struct karg *args)
2873 char *page, line[64 * 1024];
2874 int len;
2876 if (t == NULL)
2877 show_oops(NULL, "marco invalid parameters");
2879 line[0] = '\0';
2880 snprintf(line, sizeof line, "%s", marco_message(&len));
2882 page = get_html_page("Marco Sez...", line, "", 0);
2884 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2885 g_free(page);
2887 return (0);
2891 blank(struct tab *t, struct karg *args)
2893 if (t == NULL)
2894 show_oops(NULL, "blank invalid parameters");
2896 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2898 return (0);
2902 about(struct tab *t, struct karg *args)
2904 char *page, *body;
2906 if (t == NULL)
2907 show_oops(NULL, "about invalid parameters");
2909 body = g_strdup_printf("<b>Version: %s</b><p>"
2910 "Authors:"
2911 "<ul>"
2912 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2913 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2914 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2915 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2916 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2917 "</ul>"
2918 "Copyrights and licenses can be found on the XXXTerm "
2919 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2920 version
2923 page = get_html_page("About", body, "", 0);
2924 g_free(body);
2926 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2927 g_free(page);
2929 return (0);
2933 help(struct tab *t, struct karg *args)
2935 char *page, *head, *body;
2937 if (t == NULL)
2938 show_oops(NULL, "help invalid parameters");
2940 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2941 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2942 "</head>\n";
2943 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2944 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2945 "cgi-bin/man-cgi?xxxterm</a>";
2947 page = get_html_page(XT_NAME, body, head, FALSE);
2949 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2950 g_free(page);
2952 return (0);
2956 startpage(struct tab *t, struct karg *args)
2958 char *page, *body, *b;
2959 struct sp *s;
2961 if (t == NULL)
2962 show_oops(NULL, "startpage invalid parameters");
2964 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
2966 TAILQ_FOREACH(s, &spl, entry) {
2967 b = body;
2968 body = g_strdup_printf("%s%s<br>", body, s->line);
2969 g_free(b);
2972 page = get_html_page("Startup Exception", body, "", 0);
2973 g_free(body);
2975 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
2976 g_free(page);
2978 return (0);
2981 void
2982 startpage_add(const char *fmt, ...)
2984 va_list ap;
2985 char *msg;
2986 struct sp *s;
2988 if (fmt == NULL)
2989 return;
2991 va_start(ap, fmt);
2992 if (vasprintf(&msg, fmt, ap) == -1)
2993 errx(1, "startpage_add failed");
2994 va_end(ap);
2996 s = g_malloc0(sizeof *s);
2997 s->line = msg;
2999 TAILQ_INSERT_TAIL(&spl, s, entry);
3003 * update all favorite tabs apart from one. Pass NULL if
3004 * you want to update all.
3006 void
3007 update_favorite_tabs(struct tab *apart_from)
3009 struct tab *t;
3010 if (!updating_fl_tabs) {
3011 updating_fl_tabs = 1; /* stop infinite recursion */
3012 TAILQ_FOREACH(t, &tabs, entry)
3013 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3014 && (t != apart_from))
3015 xtp_page_fl(t, NULL);
3016 updating_fl_tabs = 0;
3020 /* show a list of favorites (bookmarks) */
3022 xtp_page_fl(struct tab *t, struct karg *args)
3024 char file[PATH_MAX];
3025 FILE *f;
3026 char *uri = NULL, *title = NULL;
3027 size_t len, lineno = 0;
3028 int i, failed = 0;
3029 char *body, *tmp, *page = NULL;
3030 const char delim[3] = {'\\', '\\', '\0'};
3032 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3034 if (t == NULL)
3035 warn("%s: bad param", __func__);
3037 /* new session key */
3038 if (!updating_fl_tabs)
3039 generate_xtp_session_key(&fl_session_key);
3041 /* open favorites */
3042 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3043 if ((f = fopen(file, "r")) == NULL) {
3044 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3045 return (1);
3048 /* body */
3049 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3050 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3051 "<th style='width: 40px'>Rm</th></tr>\n");
3053 for (i = 1;;) {
3054 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3055 if (feof(f) || ferror(f))
3056 break;
3057 if (strlen(title) == 0 || title[0] == '#') {
3058 free(title);
3059 title = NULL;
3060 continue;
3063 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3064 if (feof(f) || ferror(f)) {
3065 show_oops(t, "favorites file corrupt");
3066 failed = 1;
3067 break;
3070 tmp = body;
3071 body = g_strdup_printf("%s<tr>"
3072 "<td>%d</td>"
3073 "<td><a href='%s'>%s</a></td>"
3074 "<td style='text-align: center'>"
3075 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3076 "</tr>\n",
3077 body, i, uri, title,
3078 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3080 g_free(tmp);
3082 free(uri);
3083 uri = NULL;
3084 free(title);
3085 title = NULL;
3086 i++;
3088 fclose(f);
3090 /* if none, say so */
3091 if (i == 1) {
3092 tmp = body;
3093 body = g_strdup_printf("%s<tr>"
3094 "<td colspan='3' style='text-align: center'>"
3095 "No favorites - To add one use the 'favadd' command."
3096 "</td></tr>", body);
3097 g_free(tmp);
3100 tmp = body;
3101 body = g_strdup_printf("%s</table>", body);
3102 g_free(tmp);
3104 if (uri)
3105 free(uri);
3106 if (title)
3107 free(title);
3109 /* render */
3110 if (!failed) {
3111 page = get_html_page("Favorites", body, "", 1);
3112 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3113 g_free(page);
3116 update_favorite_tabs(t);
3118 if (body)
3119 g_free(body);
3121 return (failed);
3124 void
3125 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3126 size_t cert_count, char *title)
3128 gnutls_datum_t cinfo;
3129 char *tmp, *body;
3130 int i;
3132 body = g_strdup("");
3134 for (i = 0; i < cert_count; i++) {
3135 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3136 &cinfo))
3137 return;
3139 tmp = body;
3140 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3141 body, i, cinfo.data);
3142 gnutls_free(cinfo.data);
3143 g_free(tmp);
3146 tmp = get_html_page(title, body, "", 0);
3147 g_free(body);
3149 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3150 g_free(tmp);
3154 ca_cmd(struct tab *t, struct karg *args)
3156 FILE *f = NULL;
3157 int rv = 1, certs = 0, certs_read;
3158 struct stat sb;
3159 gnutls_datum_t dt;
3160 gnutls_x509_crt_t *c = NULL;
3161 char *certs_buf = NULL, *s;
3163 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3164 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3165 return (1);
3168 if (fstat(fileno(f), &sb) == -1) {
3169 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3170 goto done;
3173 certs_buf = g_malloc(sb.st_size + 1);
3174 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3175 show_oops(t, "Can't read CA file: %s", strerror(errno));
3176 goto done;
3178 certs_buf[sb.st_size] = '\0';
3180 s = certs_buf;
3181 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3182 certs++;
3183 s += strlen("BEGIN CERTIFICATE");
3186 bzero(&dt, sizeof dt);
3187 dt.data = (unsigned char *)certs_buf;
3188 dt.size = sb.st_size;
3189 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3190 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3191 GNUTLS_X509_FMT_PEM, 0);
3192 if (certs_read <= 0) {
3193 show_oops(t, "No cert(s) available");
3194 goto done;
3196 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3197 done:
3198 if (c)
3199 g_free(c);
3200 if (certs_buf)
3201 g_free(certs_buf);
3202 if (f)
3203 fclose(f);
3205 return (rv);
3209 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3210 size_t domain_sz)
3212 SoupURI *su = NULL;
3213 struct addrinfo hints, *res = NULL, *ai;
3214 int rv = -1, s = -1, on, error;
3215 char port[8];
3217 if (uri && !g_str_has_prefix(uri, "https://")) {
3218 show_oops(t, "invalid URI");
3219 goto done;
3222 su = soup_uri_new(uri);
3223 if (su == NULL) {
3224 show_oops(t, "invalid soup URI");
3225 goto done;
3227 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3228 show_oops(t, "invalid HTTPS URI");
3229 goto done;
3232 snprintf(port, sizeof port, "%d", su->port);
3233 bzero(&hints, sizeof(struct addrinfo));
3234 hints.ai_flags = AI_CANONNAME;
3235 hints.ai_family = AF_UNSPEC;
3236 hints.ai_socktype = SOCK_STREAM;
3238 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3239 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3240 goto done;
3243 for (ai = res; ai; ai = ai->ai_next) {
3244 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3245 continue;
3247 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3248 if (s == -1) {
3249 show_oops(t, "socket failed: %s", strerror(errno));
3250 goto done;
3252 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3253 sizeof(on)) == -1) {
3254 show_oops(t, "setsockopt failed: %s", strerror(errno));
3255 goto done;
3257 if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
3258 show_oops(t, "connect failed: %s", strerror(errno));
3259 goto done;
3262 break;
3265 if (domain)
3266 strlcpy(domain, su->host, domain_sz);
3267 rv = s;
3268 done:
3269 if (su)
3270 soup_uri_free(su);
3271 if (res)
3272 freeaddrinfo(res);
3273 if (rv == -1 && s != -1)
3274 close(s);
3276 return (rv);
3280 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3282 if (gsession)
3283 gnutls_deinit(gsession);
3284 if (xcred)
3285 gnutls_certificate_free_credentials(xcred);
3287 return (0);
3291 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3292 gnutls_certificate_credentials_t *xc)
3294 gnutls_certificate_credentials_t xcred;
3295 gnutls_session_t gsession;
3296 int rv = 1;
3298 if (gs == NULL || xc == NULL)
3299 goto done;
3301 *gs = NULL;
3302 *xc = NULL;
3304 gnutls_certificate_allocate_credentials(&xcred);
3305 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3306 GNUTLS_X509_FMT_PEM);
3308 gnutls_init(&gsession, GNUTLS_CLIENT);
3309 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3310 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3311 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3312 if ((rv = gnutls_handshake(gsession)) < 0) {
3313 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3315 gnutls_error_is_fatal(rv),
3316 gnutls_strerror_name(rv));
3317 stop_tls(gsession, xcred);
3318 goto done;
3321 gnutls_credentials_type_t cred;
3322 cred = gnutls_auth_get_type(gsession);
3323 if (cred != GNUTLS_CRD_CERTIFICATE) {
3324 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3325 stop_tls(gsession, xcred);
3326 goto done;
3329 *gs = gsession;
3330 *xc = xcred;
3331 rv = 0;
3332 done:
3333 return (rv);
3337 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3338 size_t *cert_count)
3340 unsigned int len;
3341 const gnutls_datum_t *cl;
3342 gnutls_x509_crt_t *all_certs;
3343 int i, rv = 1;
3345 if (certs == NULL || cert_count == NULL)
3346 goto done;
3347 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3348 goto done;
3349 cl = gnutls_certificate_get_peers(gsession, &len);
3350 if (len == 0)
3351 goto done;
3353 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3354 for (i = 0; i < len; i++) {
3355 gnutls_x509_crt_init(&all_certs[i]);
3356 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3357 GNUTLS_X509_FMT_PEM < 0)) {
3358 g_free(all_certs);
3359 goto done;
3363 *certs = all_certs;
3364 *cert_count = len;
3365 rv = 0;
3366 done:
3367 return (rv);
3370 void
3371 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3373 int i;
3375 for (i = 0; i < cert_count; i++)
3376 gnutls_x509_crt_deinit(certs[i]);
3377 g_free(certs);
3380 void
3381 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3383 GdkColor c_text, c_base;
3385 gdk_color_parse(text, &c_text);
3386 gdk_color_parse(base, &c_base);
3388 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3389 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3390 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3391 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3393 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3394 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3395 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3396 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3399 void
3400 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3401 size_t cert_count, char *domain)
3403 size_t cert_buf_sz;
3404 char cert_buf[64 * 1024], file[PATH_MAX];
3405 int i;
3406 FILE *f;
3407 GdkColor color;
3409 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3410 return;
3412 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3413 if ((f = fopen(file, "w")) == NULL) {
3414 show_oops(t, "Can't create cert file %s %s",
3415 file, strerror(errno));
3416 return;
3419 for (i = 0; i < cert_count; i++) {
3420 cert_buf_sz = sizeof cert_buf;
3421 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3422 cert_buf, &cert_buf_sz)) {
3423 show_oops(t, "gnutls_x509_crt_export failed");
3424 goto done;
3426 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3427 show_oops(t, "Can't write certs: %s", strerror(errno));
3428 goto done;
3432 /* not the best spot but oh well */
3433 gdk_color_parse("lightblue", &color);
3434 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3435 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3436 done:
3437 fclose(f);
3440 enum cert_trust {
3441 CERT_LOCAL,
3442 CERT_TRUSTED,
3443 CERT_UNTRUSTED,
3444 CERT_BAD
3447 enum cert_trust
3448 load_compare_cert(struct tab *t, struct karg *args)
3450 const gchar *uri;
3451 char domain[8182], file[PATH_MAX];
3452 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3453 int s = -1, i, error;
3454 FILE *f = NULL;
3455 size_t cert_buf_sz, cert_count;
3456 enum cert_trust rv = CERT_UNTRUSTED;
3457 char serr[80];
3458 gnutls_session_t gsession;
3459 gnutls_x509_crt_t *certs;
3460 gnutls_certificate_credentials_t xcred;
3462 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3464 if (t == NULL)
3465 return (rv);
3467 if ((uri = get_uri(t)) == NULL)
3468 return (rv);
3469 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3471 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3472 return (rv);
3473 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3475 /* go ssl/tls */
3476 if (start_tls(t, s, &gsession, &xcred))
3477 goto done;
3478 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3480 /* verify certs in case cert file doesn't exist */
3481 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3482 GNUTLS_E_SUCCESS) {
3483 show_oops(t, "Invalid certificates");
3484 goto done;
3487 /* get certs */
3488 if (get_connection_certs(gsession, &certs, &cert_count)) {
3489 show_oops(t, "Can't get connection certificates");
3490 goto done;
3493 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3494 if ((f = fopen(file, "r")) == NULL) {
3495 if (!error)
3496 rv = CERT_TRUSTED;
3497 goto freeit;
3500 for (i = 0; i < cert_count; i++) {
3501 cert_buf_sz = sizeof cert_buf;
3502 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3503 cert_buf, &cert_buf_sz)) {
3504 goto freeit;
3506 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3507 rv = CERT_BAD; /* critical */
3508 goto freeit;
3510 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3511 rv = CERT_BAD; /* critical */
3512 goto freeit;
3514 rv = CERT_LOCAL;
3517 freeit:
3518 if (f)
3519 fclose(f);
3520 free_connection_certs(certs, cert_count);
3521 done:
3522 /* we close the socket first for speed */
3523 if (s != -1)
3524 close(s);
3526 /* only complain if we didn't save it locally */
3527 if (error && rv != CERT_LOCAL) {
3528 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3529 if (error & GNUTLS_CERT_INVALID)
3530 strlcat(serr, "invalid, ", sizeof serr);
3531 if (error & GNUTLS_CERT_REVOKED)
3532 strlcat(serr, "revoked, ", sizeof serr);
3533 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3534 strlcat(serr, "signer not found, ", sizeof serr);
3535 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3536 strlcat(serr, "not signed by CA, ", sizeof serr);
3537 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3538 strlcat(serr, "insecure algorithm, ", sizeof serr);
3539 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3540 strlcat(serr, "not activated, ", sizeof serr);
3541 if (error & GNUTLS_CERT_EXPIRED)
3542 strlcat(serr, "expired, ", sizeof serr);
3543 for (i = strlen(serr) - 1; i > 0; i--)
3544 if (serr[i] == ',') {
3545 serr[i] = '\0';
3546 break;
3548 show_oops(t, serr);
3551 stop_tls(gsession, xcred);
3553 return (rv);
3557 cert_cmd(struct tab *t, struct karg *args)
3559 const gchar *uri;
3560 char domain[8182];
3561 int s = -1;
3562 size_t cert_count;
3563 gnutls_session_t gsession;
3564 gnutls_x509_crt_t *certs;
3565 gnutls_certificate_credentials_t xcred;
3567 if (t == NULL)
3568 return (1);
3570 if (ssl_ca_file == NULL) {
3571 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3572 return (1);
3575 if ((uri = get_uri(t)) == NULL) {
3576 show_oops(t, "Invalid URI");
3577 return (1);
3580 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3581 show_oops(t, "Invalid certificate URI: %s", uri);
3582 return (1);
3585 /* go ssl/tls */
3586 if (start_tls(t, s, &gsession, &xcred))
3587 goto done;
3589 /* get certs */
3590 if (get_connection_certs(gsession, &certs, &cert_count)) {
3591 show_oops(t, "get_connection_certs failed");
3592 goto done;
3595 if (args->i & XT_SHOW)
3596 show_certs(t, certs, cert_count, "Certificate Chain");
3597 else if (args->i & XT_SAVE)
3598 save_certs(t, certs, cert_count, domain);
3600 free_connection_certs(certs, cert_count);
3601 done:
3602 /* we close the socket first for speed */
3603 if (s != -1)
3604 close(s);
3605 stop_tls(gsession, xcred);
3607 return (0);
3611 remove_cookie(int index)
3613 int i, rv = 1;
3614 GSList *cf;
3615 SoupCookie *c;
3617 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3619 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3621 for (i = 1; cf; cf = cf->next, i++) {
3622 if (i != index)
3623 continue;
3624 c = cf->data;
3625 print_cookie("remove cookie", c);
3626 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3627 rv = 0;
3628 break;
3631 soup_cookies_free(cf);
3633 return (rv);
3637 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3639 struct domain *d;
3640 char *tmp, *body;
3642 body = g_strdup("");
3644 /* p list */
3645 if (args->i & XT_WL_PERSISTENT) {
3646 tmp = body;
3647 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3648 g_free(tmp);
3649 RB_FOREACH(d, domain_list, wl) {
3650 if (d->handy == 0)
3651 continue;
3652 tmp = body;
3653 body = g_strdup_printf("%s%s<br/>", body, d->d);
3654 g_free(tmp);
3658 /* s list */
3659 if (args->i & XT_WL_SESSION) {
3660 tmp = body;
3661 body = g_strdup_printf("%s<h2>Session</h2>", body);
3662 g_free(tmp);
3663 RB_FOREACH(d, domain_list, wl) {
3664 if (d->handy == 1)
3665 continue;
3666 tmp = body;
3667 body = g_strdup_printf("%s%s<br/>", body, d->d);
3668 g_free(tmp);
3672 tmp = get_html_page(title, body, "", 0);
3673 g_free(body);
3674 if (wl == &js_wl)
3675 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3676 else
3677 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3678 g_free(tmp);
3679 return (0);
3683 wl_save(struct tab *t, struct karg *args, int js)
3685 char file[PATH_MAX];
3686 FILE *f;
3687 char *line = NULL, *lt = NULL, *dom = NULL;
3688 size_t linelen;
3689 const gchar *uri;
3690 struct karg a;
3691 struct domain *d;
3692 GSList *cf;
3693 SoupCookie *ci, *c;
3694 char *p;
3696 if (t == NULL || args == NULL)
3697 return (1);
3699 if (runtime_settings[0] == '\0')
3700 return (1);
3702 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3703 if ((f = fopen(file, "r+")) == NULL)
3704 return (1);
3706 uri = get_uri(t);
3707 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3708 if (uri == NULL || dom == NULL ||
3709 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3710 show_oops(t, "Can't add domain to %s white list",
3711 js ? "JavaScript" : "cookie");
3712 goto done;
3715 /* we don't want to save :port number */
3716 p = g_strrstr(dom, ":");
3717 if (p)
3718 *p = '\0';
3720 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3722 while (!feof(f)) {
3723 line = fparseln(f, &linelen, NULL, NULL, 0);
3724 if (line == NULL)
3725 continue;
3726 if (!strcmp(line, lt))
3727 goto done;
3728 free(line);
3729 line = NULL;
3732 fprintf(f, "%s\n", lt);
3734 a.i = XT_WL_ENABLE;
3735 a.i |= args->i;
3736 if (js) {
3737 d = wl_find(dom, &js_wl);
3738 if (!d) {
3739 settings_add("js_wl", dom);
3740 d = wl_find(dom, &js_wl);
3742 toggle_js(t, &a);
3743 } else {
3744 d = wl_find(dom, &c_wl);
3745 if (!d) {
3746 settings_add("cookie_wl", dom);
3747 d = wl_find(dom, &c_wl);
3749 toggle_cwl(t, &a);
3751 /* find and add to persistent jar */
3752 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3753 for (;cf; cf = cf->next) {
3754 ci = cf->data;
3755 if (!strcmp(dom, ci->domain) ||
3756 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3757 c = soup_cookie_copy(ci);
3758 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3761 soup_cookies_free(cf);
3763 if (d)
3764 d->handy = 1;
3766 done:
3767 if (line)
3768 free(line);
3769 if (dom)
3770 g_free(dom);
3771 if (lt)
3772 g_free(lt);
3773 fclose(f);
3775 return (0);
3779 js_show_wl(struct tab *t, struct karg *args)
3781 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3782 wl_show(t, args, "JavaScript White List", &js_wl);
3784 return (0);
3788 cookie_show_wl(struct tab *t, struct karg *args)
3790 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3791 wl_show(t, args, "Cookie White List", &c_wl);
3793 return (0);
3797 cookie_cmd(struct tab *t, struct karg *args)
3799 if (args->i & XT_SHOW)
3800 wl_show(t, args, "Cookie White List", &c_wl);
3801 else if (args->i & XT_WL_TOGGLE) {
3802 args->i |= XT_WL_RELOAD;
3803 toggle_cwl(t, args);
3804 } else if (args->i & XT_SAVE) {
3805 args->i |= XT_WL_RELOAD;
3806 wl_save(t, args, 0);
3807 } else if (args->i & XT_DELETE)
3808 show_oops(t, "'cookie delete' currently unimplemented");
3810 return (0);
3814 js_cmd(struct tab *t, struct karg *args)
3816 if (args->i & XT_SHOW)
3817 wl_show(t, args, "JavaScript White List", &js_wl);
3818 else if (args->i & XT_SAVE) {
3819 args->i |= XT_WL_RELOAD;
3820 wl_save(t, args, 1);
3821 } else if (args->i & XT_WL_TOGGLE) {
3822 args->i |= XT_WL_RELOAD;
3823 toggle_js(t, args);
3824 } else if (args->i & XT_DELETE)
3825 show_oops(t, "'js delete' currently unimplemented");
3827 return (0);
3831 toplevel_cmd(struct tab *t, struct karg *args)
3833 js_toggle_cb(t->js_toggle, t);
3835 return (0);
3839 add_favorite(struct tab *t, struct karg *args)
3841 char file[PATH_MAX];
3842 FILE *f;
3843 char *line = NULL;
3844 size_t urilen, linelen;
3845 const gchar *uri, *title;
3847 if (t == NULL)
3848 return (1);
3850 /* don't allow adding of xtp pages to favorites */
3851 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3852 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3853 return (1);
3856 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3857 if ((f = fopen(file, "r+")) == NULL) {
3858 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3859 return (1);
3862 title = get_title(t, FALSE);
3863 uri = get_uri(t);
3865 if (title == NULL || uri == NULL) {
3866 show_oops(t, "can't add page to favorites");
3867 goto done;
3870 urilen = strlen(uri);
3872 for (;;) {
3873 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3874 if (feof(f) || ferror(f))
3875 break;
3877 if (linelen == urilen && !strcmp(line, uri))
3878 goto done;
3880 free(line);
3881 line = NULL;
3884 fprintf(f, "\n%s\n%s", title, uri);
3885 done:
3886 if (line)
3887 free(line);
3888 fclose(f);
3890 update_favorite_tabs(NULL);
3892 return (0);
3896 navaction(struct tab *t, struct karg *args)
3898 WebKitWebHistoryItem *item;
3900 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3901 t->tab_id, args->i);
3903 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3905 if (t->item) {
3906 if (args->i == XT_NAV_BACK)
3907 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3908 else
3909 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3910 if (item == NULL)
3911 return (XT_CB_PASSTHROUGH);
3912 webkit_web_view_go_to_back_forward_item(t->wv, item);
3913 t->item = NULL;
3914 return (XT_CB_PASSTHROUGH);
3917 switch (args->i) {
3918 case XT_NAV_BACK:
3919 marks_clear(t);
3920 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3921 if (item)
3922 webkit_web_view_go_to_back_forward_item(t->wv, item);
3923 break;
3924 case XT_NAV_FORWARD:
3925 marks_clear(t);
3926 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3927 if (item)
3928 webkit_web_view_go_to_back_forward_item(t->wv, item);
3929 break;
3930 case XT_NAV_RELOAD:
3931 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3932 if (item)
3933 webkit_web_view_go_to_back_forward_item(t->wv, item);
3934 break;
3936 return (XT_CB_PASSTHROUGH);
3940 move(struct tab *t, struct karg *args)
3942 GtkAdjustment *adjust;
3943 double pi, si, pos, ps, upper, lower, max;
3944 double percent;
3946 switch (args->i) {
3947 case XT_MOVE_DOWN:
3948 case XT_MOVE_UP:
3949 case XT_MOVE_BOTTOM:
3950 case XT_MOVE_TOP:
3951 case XT_MOVE_PAGEDOWN:
3952 case XT_MOVE_PAGEUP:
3953 case XT_MOVE_HALFDOWN:
3954 case XT_MOVE_HALFUP:
3955 case XT_MOVE_PERCENT:
3956 adjust = t->adjust_v;
3957 break;
3958 default:
3959 adjust = t->adjust_h;
3960 break;
3963 pos = gtk_adjustment_get_value(adjust);
3964 ps = gtk_adjustment_get_page_size(adjust);
3965 upper = gtk_adjustment_get_upper(adjust);
3966 lower = gtk_adjustment_get_lower(adjust);
3967 si = gtk_adjustment_get_step_increment(adjust);
3968 pi = gtk_adjustment_get_page_increment(adjust);
3969 max = upper - ps;
3971 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3972 "max %f si %f pi %f\n",
3973 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3974 pos, ps, upper, lower, max, si, pi);
3976 switch (args->i) {
3977 case XT_MOVE_DOWN:
3978 case XT_MOVE_RIGHT:
3979 pos += si;
3980 gtk_adjustment_set_value(adjust, MIN(pos, max));
3981 break;
3982 case XT_MOVE_UP:
3983 case XT_MOVE_LEFT:
3984 pos -= si;
3985 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3986 break;
3987 case XT_MOVE_BOTTOM:
3988 case XT_MOVE_FARRIGHT:
3989 gtk_adjustment_set_value(adjust, max);
3990 break;
3991 case XT_MOVE_TOP:
3992 case XT_MOVE_FARLEFT:
3993 gtk_adjustment_set_value(adjust, lower);
3994 break;
3995 case XT_MOVE_PAGEDOWN:
3996 pos += pi;
3997 gtk_adjustment_set_value(adjust, MIN(pos, max));
3998 break;
3999 case XT_MOVE_PAGEUP:
4000 pos -= pi;
4001 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4002 break;
4003 case XT_MOVE_HALFDOWN:
4004 pos += pi / 2;
4005 gtk_adjustment_set_value(adjust, MIN(pos, max));
4006 break;
4007 case XT_MOVE_HALFUP:
4008 pos -= pi / 2;
4009 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4010 break;
4011 case XT_MOVE_PERCENT:
4012 percent = atoi(args->s) / 100.0;
4013 pos = max * percent;
4014 if (pos < 0.0 || pos > max)
4015 break;
4016 gtk_adjustment_set_value(adjust, pos);
4017 break;
4018 default:
4019 return (XT_CB_PASSTHROUGH);
4022 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4024 return (XT_CB_HANDLED);
4027 void
4028 url_set_visibility(void)
4030 struct tab *t;
4032 TAILQ_FOREACH(t, &tabs, entry)
4033 if (show_url == 0) {
4034 gtk_widget_hide(t->toolbar);
4035 focus_webview(t);
4036 } else
4037 gtk_widget_show(t->toolbar);
4040 void
4041 notebook_tab_set_visibility(void)
4043 if (show_tabs == 0) {
4044 gtk_widget_hide(tab_bar);
4045 gtk_notebook_set_show_tabs(notebook, FALSE);
4046 } else {
4047 if (tab_style == XT_TABS_NORMAL) {
4048 gtk_widget_hide(tab_bar);
4049 gtk_notebook_set_show_tabs(notebook, TRUE);
4050 } else if (tab_style == XT_TABS_COMPACT) {
4051 gtk_widget_show(tab_bar);
4052 gtk_notebook_set_show_tabs(notebook, FALSE);
4057 void
4058 statusbar_set_visibility(void)
4060 struct tab *t;
4062 TAILQ_FOREACH(t, &tabs, entry)
4063 if (show_statusbar == 0) {
4064 gtk_widget_hide(t->statusbar_box);
4065 focus_webview(t);
4066 } else
4067 gtk_widget_show(t->statusbar_box);
4070 void
4071 url_set(struct tab *t, int enable_url_entry)
4073 GdkPixbuf *pixbuf;
4074 int progress;
4076 show_url = enable_url_entry;
4078 if (enable_url_entry) {
4079 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4080 GTK_ENTRY_ICON_PRIMARY, NULL);
4081 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4082 } else {
4083 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4084 GTK_ENTRY_ICON_PRIMARY);
4085 progress =
4086 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4087 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4088 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4089 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4090 progress);
4095 fullscreen(struct tab *t, struct karg *args)
4097 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4099 if (t == NULL)
4100 return (XT_CB_PASSTHROUGH);
4102 if (show_url == 0) {
4103 url_set(t, 1);
4104 show_tabs = 1;
4105 } else {
4106 url_set(t, 0);
4107 show_tabs = 0;
4110 url_set_visibility();
4111 notebook_tab_set_visibility();
4113 return (XT_CB_HANDLED);
4117 statustoggle(struct tab *t, struct karg *args)
4119 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4121 if (show_statusbar == 1) {
4122 show_statusbar = 0;
4123 statusbar_set_visibility();
4124 } else if (show_statusbar == 0) {
4125 show_statusbar = 1;
4126 statusbar_set_visibility();
4128 return (XT_CB_HANDLED);
4132 urlaction(struct tab *t, struct karg *args)
4134 int rv = XT_CB_HANDLED;
4136 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4138 if (t == NULL)
4139 return (XT_CB_PASSTHROUGH);
4141 switch (args->i) {
4142 case XT_URL_SHOW:
4143 if (show_url == 0) {
4144 url_set(t, 1);
4145 url_set_visibility();
4147 break;
4148 case XT_URL_HIDE:
4149 if (show_url == 1) {
4150 url_set(t, 0);
4151 url_set_visibility();
4153 break;
4155 return (rv);
4159 tabaction(struct tab *t, struct karg *args)
4161 int rv = XT_CB_HANDLED;
4162 char *url = args->s;
4163 struct undo *u;
4164 struct tab *tt;
4166 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4168 if (t == NULL)
4169 return (XT_CB_PASSTHROUGH);
4171 switch (args->i) {
4172 case XT_TAB_NEW:
4173 if (strlen(url) > 0)
4174 create_new_tab(url, NULL, 1, args->precount);
4175 else
4176 create_new_tab(NULL, NULL, 1, args->precount);
4177 break;
4178 case XT_TAB_DELETE:
4179 if (args->precount < 0)
4180 delete_tab(t);
4181 else
4182 TAILQ_FOREACH(tt, &tabs, entry)
4183 if (tt->tab_id == args->precount - 1) {
4184 delete_tab(tt);
4185 break;
4187 break;
4188 case XT_TAB_DELQUIT:
4189 if (gtk_notebook_get_n_pages(notebook) > 1)
4190 delete_tab(t);
4191 else
4192 quit(t, args);
4193 break;
4194 case XT_TAB_OPEN:
4195 if (strlen(url) > 0)
4197 else {
4198 rv = XT_CB_PASSTHROUGH;
4199 goto done;
4201 load_uri(t, url);
4202 break;
4203 case XT_TAB_SHOW:
4204 if (show_tabs == 0) {
4205 show_tabs = 1;
4206 notebook_tab_set_visibility();
4208 break;
4209 case XT_TAB_HIDE:
4210 if (show_tabs == 1) {
4211 show_tabs = 0;
4212 notebook_tab_set_visibility();
4214 break;
4215 case XT_TAB_NEXTSTYLE:
4216 if (tab_style == XT_TABS_NORMAL) {
4217 tab_style = XT_TABS_COMPACT;
4218 recolor_compact_tabs();
4220 else
4221 tab_style = XT_TABS_NORMAL;
4222 notebook_tab_set_visibility();
4223 break;
4224 case XT_TAB_UNDO_CLOSE:
4225 if (undo_count == 0) {
4226 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4227 __func__);
4228 goto done;
4229 } else {
4230 undo_count--;
4231 u = TAILQ_FIRST(&undos);
4232 create_new_tab(u->uri, u, 1, -1);
4234 TAILQ_REMOVE(&undos, u, entry);
4235 g_free(u->uri);
4236 /* u->history is freed in create_new_tab() */
4237 g_free(u);
4239 break;
4240 default:
4241 rv = XT_CB_PASSTHROUGH;
4242 goto done;
4245 done:
4246 if (args->s) {
4247 g_free(args->s);
4248 args->s = NULL;
4251 return (rv);
4255 resizetab(struct tab *t, struct karg *args)
4257 if (t == NULL || args == NULL) {
4258 show_oops(NULL, "resizetab invalid parameters");
4259 return (XT_CB_PASSTHROUGH);
4262 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4263 t->tab_id, args->i);
4265 setzoom_webkit(t, args->i);
4267 return (XT_CB_HANDLED);
4271 movetab(struct tab *t, struct karg *args)
4273 int n, dest;
4275 if (t == NULL || args == NULL) {
4276 show_oops(NULL, "movetab invalid parameters");
4277 return (XT_CB_PASSTHROUGH);
4280 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4281 t->tab_id, args->i);
4283 if (args->i >= XT_TAB_INVALID)
4284 return (XT_CB_PASSTHROUGH);
4286 if (TAILQ_EMPTY(&tabs))
4287 return (XT_CB_PASSTHROUGH);
4289 n = gtk_notebook_get_n_pages(notebook);
4290 dest = gtk_notebook_get_current_page(notebook);
4292 switch (args->i) {
4293 case XT_TAB_NEXT:
4294 if (args->precount < 0)
4295 dest = dest == n - 1 ? 0 : dest + 1;
4296 else
4297 dest = args->precount - 1;
4299 break;
4300 case XT_TAB_PREV:
4301 if (args->precount < 0)
4302 dest -= 1;
4303 else
4304 dest -= args->precount % n;
4306 if (dest < 0)
4307 dest += n;
4309 break;
4310 case XT_TAB_FIRST:
4311 dest = 0;
4312 break;
4313 case XT_TAB_LAST:
4314 dest = n - 1;
4315 break;
4316 default:
4317 return (XT_CB_PASSTHROUGH);
4320 if (dest < 0 || dest >= n)
4321 return (XT_CB_PASSTHROUGH);
4322 if (t->tab_id == dest) {
4323 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4324 return (XT_CB_HANDLED);
4327 set_current_tab(dest);
4329 return (XT_CB_HANDLED);
4332 int cmd_prefix = 0;
4335 command(struct tab *t, struct karg *args)
4337 char *s = NULL, *ss = NULL;
4338 GdkColor color;
4339 const gchar *uri;
4341 if (t == NULL || args == NULL) {
4342 show_oops(NULL, "command invalid parameters");
4343 return (XT_CB_PASSTHROUGH);
4346 switch (args->i) {
4347 case '/':
4348 s = "/";
4349 break;
4350 case '?':
4351 s = "?";
4352 break;
4353 case ':':
4354 if (cmd_prefix == 0)
4355 s = ":";
4356 else {
4357 ss = g_strdup_printf(":%d", cmd_prefix);
4358 s = ss;
4359 cmd_prefix = 0;
4361 break;
4362 case XT_CMD_OPEN:
4363 s = ":open ";
4364 break;
4365 case XT_CMD_TABNEW:
4366 s = ":tabnew ";
4367 break;
4368 case XT_CMD_OPEN_CURRENT:
4369 s = ":open ";
4370 /* FALL THROUGH */
4371 case XT_CMD_TABNEW_CURRENT:
4372 if (!s) /* FALL THROUGH? */
4373 s = ":tabnew ";
4374 if ((uri = get_uri(t)) != NULL) {
4375 ss = g_strdup_printf("%s%s", s, uri);
4376 s = ss;
4378 break;
4379 default:
4380 show_oops(t, "command: invalid opcode %d", args->i);
4381 return (XT_CB_PASSTHROUGH);
4384 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4386 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4387 gdk_color_parse(XT_COLOR_WHITE, &color);
4388 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4389 show_cmd(t);
4390 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4391 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4393 if (ss)
4394 g_free(ss);
4396 return (XT_CB_HANDLED);
4400 * Return a new string with a download row (in html)
4401 * appended. Old string is freed.
4403 char *
4404 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4407 WebKitDownloadStatus stat;
4408 char *status_html = NULL, *cmd_html = NULL, *new_html;
4409 gdouble progress;
4410 char cur_sz[FMT_SCALED_STRSIZE];
4411 char tot_sz[FMT_SCALED_STRSIZE];
4412 char *xtp_prefix;
4414 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4416 /* All actions wil take this form:
4417 * xxxt://class/seskey
4419 xtp_prefix = g_strdup_printf("%s%d/%s/",
4420 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4422 stat = webkit_download_get_status(dl->download);
4424 switch (stat) {
4425 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4426 status_html = g_strdup_printf("Finished");
4427 cmd_html = g_strdup_printf(
4428 "<a href='%s%d/%d'>Remove</a>",
4429 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4430 break;
4431 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4432 /* gather size info */
4433 progress = 100 * webkit_download_get_progress(dl->download);
4435 fmt_scaled(
4436 webkit_download_get_current_size(dl->download), cur_sz);
4437 fmt_scaled(
4438 webkit_download_get_total_size(dl->download), tot_sz);
4440 status_html = g_strdup_printf(
4441 "<div style='width: 100%%' align='center'>"
4442 "<div class='progress-outer'>"
4443 "<div class='progress-inner' style='width: %.2f%%'>"
4444 "</div></div></div>"
4445 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4446 progress, cur_sz, tot_sz, progress);
4448 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4449 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4451 break;
4452 /* LLL */
4453 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4454 status_html = g_strdup_printf("Cancelled");
4455 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4456 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4457 break;
4458 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4459 status_html = g_strdup_printf("Error!");
4460 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4461 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4462 break;
4463 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4464 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4465 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4466 status_html = g_strdup_printf("Starting");
4467 break;
4468 default:
4469 show_oops(t, "%s: unknown download status", __func__);
4472 new_html = g_strdup_printf(
4473 "%s\n<tr><td>%s</td><td>%s</td>"
4474 "<td style='text-align:center'>%s</td></tr>\n",
4475 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4476 status_html, cmd_html);
4477 g_free(html);
4479 if (status_html)
4480 g_free(status_html);
4482 if (cmd_html)
4483 g_free(cmd_html);
4485 g_free(xtp_prefix);
4487 return new_html;
4491 * update all download tabs apart from one. Pass NULL if
4492 * you want to update all.
4494 void
4495 update_download_tabs(struct tab *apart_from)
4497 struct tab *t;
4498 if (!updating_dl_tabs) {
4499 updating_dl_tabs = 1; /* stop infinite recursion */
4500 TAILQ_FOREACH(t, &tabs, entry)
4501 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4502 && (t != apart_from))
4503 xtp_page_dl(t, NULL);
4504 updating_dl_tabs = 0;
4509 * update all cookie tabs apart from one. Pass NULL if
4510 * you want to update all.
4512 void
4513 update_cookie_tabs(struct tab *apart_from)
4515 struct tab *t;
4516 if (!updating_cl_tabs) {
4517 updating_cl_tabs = 1; /* stop infinite recursion */
4518 TAILQ_FOREACH(t, &tabs, entry)
4519 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4520 && (t != apart_from))
4521 xtp_page_cl(t, NULL);
4522 updating_cl_tabs = 0;
4527 * update all history tabs apart from one. Pass NULL if
4528 * you want to update all.
4530 void
4531 update_history_tabs(struct tab *apart_from)
4533 struct tab *t;
4535 if (!updating_hl_tabs) {
4536 updating_hl_tabs = 1; /* stop infinite recursion */
4537 TAILQ_FOREACH(t, &tabs, entry)
4538 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4539 && (t != apart_from))
4540 xtp_page_hl(t, NULL);
4541 updating_hl_tabs = 0;
4545 /* cookie management XTP page */
4547 xtp_page_cl(struct tab *t, struct karg *args)
4549 char *body, *page, *tmp;
4550 int i = 1; /* all ids start 1 */
4551 GSList *sc, *pc, *pc_start;
4552 SoupCookie *c;
4553 char *type, *table_headers, *last_domain;
4555 DNPRINTF(XT_D_CMD, "%s", __func__);
4557 if (t == NULL) {
4558 show_oops(NULL, "%s invalid parameters", __func__);
4559 return (1);
4562 /* Generate a new session key */
4563 if (!updating_cl_tabs)
4564 generate_xtp_session_key(&cl_session_key);
4566 /* table headers */
4567 table_headers = g_strdup_printf("<table><tr>"
4568 "<th>Type</th>"
4569 "<th>Name</th>"
4570 "<th style='width:200px'>Value</th>"
4571 "<th>Path</th>"
4572 "<th>Expires</th>"
4573 "<th>Secure</th>"
4574 "<th>HTTP<br />only</th>"
4575 "<th style='width:40px'>Rm</th></tr>\n");
4577 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4578 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4579 pc_start = pc;
4581 body = NULL;
4582 last_domain = strdup("");
4583 for (; sc; sc = sc->next) {
4584 c = sc->data;
4586 if (strcmp(last_domain, c->domain) != 0) {
4587 /* new domain */
4588 free(last_domain);
4589 last_domain = strdup(c->domain);
4591 if (body != NULL) {
4592 tmp = body;
4593 body = g_strdup_printf("%s</table>"
4594 "<h2>%s</h2>%s\n",
4595 body, c->domain, table_headers);
4596 g_free(tmp);
4597 } else {
4598 /* first domain */
4599 body = g_strdup_printf("<h2>%s</h2>%s\n",
4600 c->domain, table_headers);
4604 type = "Session";
4605 for (pc = pc_start; pc; pc = pc->next)
4606 if (soup_cookie_equal(pc->data, c)) {
4607 type = "Session + Persistent";
4608 break;
4611 tmp = body;
4612 body = g_strdup_printf(
4613 "%s\n<tr>"
4614 "<td>%s</td>"
4615 "<td style='word-wrap:normal'>%s</td>"
4616 "<td>"
4617 " <textarea rows='4'>%s</textarea>"
4618 "</td>"
4619 "<td>%s</td>"
4620 "<td>%s</td>"
4621 "<td>%d</td>"
4622 "<td>%d</td>"
4623 "<td style='text-align:center'>"
4624 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4625 body,
4626 type,
4627 c->name,
4628 c->value,
4629 c->path,
4630 c->expires ?
4631 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4632 c->secure,
4633 c->http_only,
4635 XT_XTP_STR,
4636 XT_XTP_CL,
4637 cl_session_key,
4638 XT_XTP_CL_REMOVE,
4642 g_free(tmp);
4643 i++;
4646 soup_cookies_free(sc);
4647 soup_cookies_free(pc);
4649 /* small message if there are none */
4650 if (i == 1) {
4651 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4652 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4654 tmp = body;
4655 body = g_strdup_printf("%s</table>", body);
4656 g_free(tmp);
4658 page = get_html_page("Cookie Jar", body, "", TRUE);
4659 g_free(body);
4660 g_free(table_headers);
4661 g_free(last_domain);
4663 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4664 update_cookie_tabs(t);
4666 g_free(page);
4668 return (0);
4672 xtp_page_hl(struct tab *t, struct karg *args)
4674 char *body, *page, *tmp;
4675 struct history *h;
4676 int i = 1; /* all ids start 1 */
4678 DNPRINTF(XT_D_CMD, "%s", __func__);
4680 if (t == NULL) {
4681 show_oops(NULL, "%s invalid parameters", __func__);
4682 return (1);
4685 /* Generate a new session key */
4686 if (!updating_hl_tabs)
4687 generate_xtp_session_key(&hl_session_key);
4689 /* body */
4690 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4691 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4693 RB_FOREACH_REVERSE(h, history_list, &hl) {
4694 tmp = body;
4695 body = g_strdup_printf(
4696 "%s\n<tr>"
4697 "<td><a href='%s'>%s</a></td>"
4698 "<td>%s</td>"
4699 "<td style='text-align: center'>"
4700 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4701 body, h->uri, h->uri, h->title,
4702 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4703 XT_XTP_HL_REMOVE, i);
4705 g_free(tmp);
4706 i++;
4709 /* small message if there are none */
4710 if (i == 1) {
4711 tmp = body;
4712 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4713 "colspan='3'>No History</td></tr>\n", body);
4714 g_free(tmp);
4717 tmp = body;
4718 body = g_strdup_printf("%s</table>", body);
4719 g_free(tmp);
4721 page = get_html_page("History", body, "", TRUE);
4722 g_free(body);
4725 * update all history manager tabs as the xtp session
4726 * key has now changed. No need to update the current tab.
4727 * Already did that above.
4729 update_history_tabs(t);
4731 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4732 g_free(page);
4734 return (0);
4738 * Generate a web page detailing the status of any downloads
4741 xtp_page_dl(struct tab *t, struct karg *args)
4743 struct download *dl;
4744 char *body, *page, *tmp;
4745 char *ref;
4746 int n_dl = 1;
4748 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4750 if (t == NULL) {
4751 show_oops(NULL, "%s invalid parameters", __func__);
4752 return (1);
4756 * Generate a new session key for next page instance.
4757 * This only happens for the top level call to xtp_page_dl()
4758 * in which case updating_dl_tabs is 0.
4760 if (!updating_dl_tabs)
4761 generate_xtp_session_key(&dl_session_key);
4763 /* header - with refresh so as to update */
4764 if (refresh_interval >= 1)
4765 ref = g_strdup_printf(
4766 "<meta http-equiv='refresh' content='%u"
4767 ";url=%s%d/%s/%d' />\n",
4768 refresh_interval,
4769 XT_XTP_STR,
4770 XT_XTP_DL,
4771 dl_session_key,
4772 XT_XTP_DL_LIST);
4773 else
4774 ref = g_strdup("");
4776 body = g_strdup_printf("<div align='center'>"
4777 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4778 "</p><table><tr><th style='width: 60%%'>"
4779 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4780 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4782 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4783 body = xtp_page_dl_row(t, body, dl);
4784 n_dl++;
4787 /* message if no downloads in list */
4788 if (n_dl == 1) {
4789 tmp = body;
4790 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4791 " style='text-align: center'>"
4792 "No downloads</td></tr>\n", body);
4793 g_free(tmp);
4796 tmp = body;
4797 body = g_strdup_printf("%s</table></div>", body);
4798 g_free(tmp);
4800 page = get_html_page("Downloads", body, ref, 1);
4801 g_free(ref);
4802 g_free(body);
4805 * update all download manager tabs as the xtp session
4806 * key has now changed. No need to update the current tab.
4807 * Already did that above.
4809 update_download_tabs(t);
4811 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4812 g_free(page);
4814 return (0);
4818 search(struct tab *t, struct karg *args)
4820 gboolean d;
4822 if (t == NULL || args == NULL) {
4823 show_oops(NULL, "search invalid parameters");
4824 return (1);
4826 if (t->search_text == NULL) {
4827 if (global_search == NULL)
4828 return (XT_CB_PASSTHROUGH);
4829 else {
4830 t->search_text = g_strdup(global_search);
4831 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4832 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4836 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4837 t->tab_id, args->i, t->search_forward, t->search_text);
4839 switch (args->i) {
4840 case XT_SEARCH_NEXT:
4841 d = t->search_forward;
4842 break;
4843 case XT_SEARCH_PREV:
4844 d = !t->search_forward;
4845 break;
4846 default:
4847 return (XT_CB_PASSTHROUGH);
4850 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4852 return (XT_CB_HANDLED);
4855 struct settings_args {
4856 char **body;
4857 int i;
4860 void
4861 print_setting(struct settings *s, char *val, void *cb_args)
4863 char *tmp, *color;
4864 struct settings_args *sa = cb_args;
4866 if (sa == NULL)
4867 return;
4869 if (s->flags & XT_SF_RUNTIME)
4870 color = "#22cc22";
4871 else
4872 color = "#cccccc";
4874 tmp = *sa->body;
4875 *sa->body = g_strdup_printf(
4876 "%s\n<tr>"
4877 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4878 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4879 *sa->body,
4880 color,
4881 s->name,
4882 color,
4885 g_free(tmp);
4886 sa->i++;
4890 set_show(struct tab *t, struct karg *args)
4892 char *body, *page, *tmp;
4893 int i = 1;
4894 struct settings_args sa;
4896 bzero(&sa, sizeof sa);
4897 sa.body = &body;
4899 /* body */
4900 body = g_strdup_printf("<div align='center'><table><tr>"
4901 "<th align='left'>Setting</th>"
4902 "<th align='left'>Value</th></tr>\n");
4904 settings_walk(print_setting, &sa);
4905 i = sa.i;
4907 /* small message if there are none */
4908 if (i == 1) {
4909 tmp = body;
4910 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4911 "colspan='2'>No settings</td></tr>\n", body);
4912 g_free(tmp);
4915 tmp = body;
4916 body = g_strdup_printf("%s</table></div>", body);
4917 g_free(tmp);
4919 page = get_html_page("Settings", body, "", 0);
4921 g_free(body);
4923 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4925 g_free(page);
4927 return (XT_CB_PASSTHROUGH);
4931 set(struct tab *t, struct karg *args)
4933 char *p, *val;
4934 int i;
4936 if (args == NULL || args->s == NULL)
4937 return (set_show(t, args));
4939 /* strip spaces */
4940 p = g_strstrip(args->s);
4942 if (strlen(p) == 0)
4943 return (set_show(t, args));
4945 /* we got some sort of string */
4946 val = g_strrstr(p, "=");
4947 if (val) {
4948 *val++ = '\0';
4949 val = g_strchomp(val);
4950 p = g_strchomp(p);
4952 for (i = 0; i < LENGTH(rs); i++) {
4953 if (strcmp(rs[i].name, p))
4954 continue;
4956 if (rs[i].activate) {
4957 if (rs[i].activate(val))
4958 show_oops(t, "%s invalid value %s",
4959 p, val);
4960 else
4961 show_oops(t, ":set %s = %s", p, val);
4962 goto done;
4963 } else {
4964 show_oops(t, "not a runtime option: %s", p);
4965 goto done;
4968 show_oops(t, "unknown option: %s", p);
4969 } else {
4970 p = g_strchomp(p);
4972 for (i = 0; i < LENGTH(rs); i++) {
4973 if (strcmp(rs[i].name, p))
4974 continue;
4976 /* XXX this could use some cleanup */
4977 switch (rs[i].type) {
4978 case XT_S_INT:
4979 if (rs[i].ival)
4980 show_oops(t, "%s = %d",
4981 rs[i].name, *rs[i].ival);
4982 else if (rs[i].s && rs[i].s->get)
4983 show_oops(t, "%s = %s",
4984 rs[i].name,
4985 rs[i].s->get(&rs[i]));
4986 else if (rs[i].s && rs[i].s->get == NULL)
4987 show_oops(t, "%s = ...", rs[i].name);
4988 else
4989 show_oops(t, "%s = ", rs[i].name);
4990 break;
4991 case XT_S_FLOAT:
4992 if (rs[i].fval)
4993 show_oops(t, "%s = %f",
4994 rs[i].name, *rs[i].fval);
4995 else if (rs[i].s && rs[i].s->get)
4996 show_oops(t, "%s = %s",
4997 rs[i].name,
4998 rs[i].s->get(&rs[i]));
4999 else if (rs[i].s && rs[i].s->get == NULL)
5000 show_oops(t, "%s = ...", rs[i].name);
5001 else
5002 show_oops(t, "%s = ", rs[i].name);
5003 break;
5004 case XT_S_STR:
5005 if (rs[i].sval && *rs[i].sval)
5006 show_oops(t, "%s = %s",
5007 rs[i].name, *rs[i].sval);
5008 else if (rs[i].s && rs[i].s->get)
5009 show_oops(t, "%s = %s",
5010 rs[i].name,
5011 rs[i].s->get(&rs[i]));
5012 else if (rs[i].s && rs[i].s->get == NULL)
5013 show_oops(t, "%s = ...", rs[i].name);
5014 else
5015 show_oops(t, "%s = ", rs[i].name);
5016 break;
5017 default:
5018 show_oops(t, "unknown type for %s", rs[i].name);
5019 goto done;
5022 goto done;
5024 show_oops(t, "unknown option: %s", p);
5026 done:
5027 return (XT_CB_PASSTHROUGH);
5031 session_save(struct tab *t, char *filename)
5033 struct karg a;
5034 int rv = 1;
5036 if (strlen(filename) == 0)
5037 goto done;
5039 if (filename[0] == '.' || filename[0] == '/')
5040 goto done;
5042 a.s = filename;
5043 if (save_tabs(t, &a))
5044 goto done;
5045 strlcpy(named_session, filename, sizeof named_session);
5047 rv = 0;
5048 done:
5049 return (rv);
5053 session_open(struct tab *t, char *filename)
5055 struct karg a;
5056 int rv = 1;
5058 if (strlen(filename) == 0)
5059 goto done;
5061 if (filename[0] == '.' || filename[0] == '/')
5062 goto done;
5064 a.s = filename;
5065 a.i = XT_SES_CLOSETABS;
5066 if (open_tabs(t, &a))
5067 goto done;
5069 strlcpy(named_session, filename, sizeof named_session);
5071 rv = 0;
5072 done:
5073 return (rv);
5077 session_delete(struct tab *t, char *filename)
5079 char file[PATH_MAX];
5080 int rv = 1;
5082 if (strlen(filename) == 0)
5083 goto done;
5085 if (filename[0] == '.' || filename[0] == '/')
5086 goto done;
5088 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5089 if (unlink(file))
5090 goto done;
5092 if (!strcmp(filename, named_session))
5093 strlcpy(named_session, XT_SAVED_TABS_FILE,
5094 sizeof named_session);
5096 rv = 0;
5097 done:
5098 return (rv);
5102 session_cmd(struct tab *t, struct karg *args)
5104 char *filename = args->s;
5106 if (t == NULL)
5107 return (1);
5109 if (args->i & XT_SHOW)
5110 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5111 XT_SAVED_TABS_FILE : named_session);
5112 else if (args->i & XT_SAVE) {
5113 if (session_save(t, filename)) {
5114 show_oops(t, "Can't save session: %s",
5115 filename ? filename : "INVALID");
5116 goto done;
5118 } else if (args->i & XT_OPEN) {
5119 if (session_open(t, filename)) {
5120 show_oops(t, "Can't open session: %s",
5121 filename ? filename : "INVALID");
5122 goto done;
5124 } else if (args->i & XT_DELETE) {
5125 if (session_delete(t, filename)) {
5126 show_oops(t, "Can't delete session: %s",
5127 filename ? filename : "INVALID");
5128 goto done;
5131 done:
5132 return (XT_CB_PASSTHROUGH);
5136 * Make a hardcopy of the page
5139 print_page(struct tab *t, struct karg *args)
5141 WebKitWebFrame *frame;
5142 GtkPageSetup *ps;
5143 GtkPrintOperation *op;
5144 GtkPrintOperationAction action;
5145 GtkPrintOperationResult print_res;
5146 GError *g_err = NULL;
5147 int marg_l, marg_r, marg_t, marg_b;
5149 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5151 ps = gtk_page_setup_new();
5152 op = gtk_print_operation_new();
5153 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5154 frame = webkit_web_view_get_main_frame(t->wv);
5156 /* the default margins are too small, so we will bump them */
5157 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5158 XT_PRINT_EXTRA_MARGIN;
5159 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5160 XT_PRINT_EXTRA_MARGIN;
5161 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5162 XT_PRINT_EXTRA_MARGIN;
5163 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5164 XT_PRINT_EXTRA_MARGIN;
5166 /* set margins */
5167 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5168 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5169 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5170 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5172 gtk_print_operation_set_default_page_setup(op, ps);
5174 /* this appears to free 'op' and 'ps' */
5175 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5177 /* check it worked */
5178 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5179 show_oops(NULL, "can't print: %s", g_err->message);
5180 g_error_free (g_err);
5181 return (1);
5184 return (0);
5188 go_home(struct tab *t, struct karg *args)
5190 load_uri(t, home);
5191 return (0);
5195 restart(struct tab *t, struct karg *args)
5197 struct karg a;
5199 a.s = XT_RESTART_TABS_FILE;
5200 save_tabs(t, &a);
5201 execvp(start_argv[0], start_argv);
5202 /* NOTREACHED */
5204 return (0);
5207 #define CTRL GDK_CONTROL_MASK
5208 #define MOD1 GDK_MOD1_MASK
5209 #define SHFT GDK_SHIFT_MASK
5211 /* inherent to GTK not all keys will be caught at all times */
5212 /* XXX sort key bindings */
5213 struct key_binding {
5214 char *cmd;
5215 guint mask;
5216 guint use_in_entry;
5217 guint key;
5218 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5219 } keys[] = {
5220 { "cookiejar", MOD1, 0, GDK_j },
5221 { "downloadmgr", MOD1, 0, GDK_d },
5222 { "history", MOD1, 0, GDK_h },
5223 { "print", CTRL, 0, GDK_p },
5224 { "search", 0, 0, GDK_slash },
5225 { "searchb", 0, 0, GDK_question },
5226 { "statustoggle", CTRL, 0, GDK_n },
5227 { "command", 0, 0, GDK_colon },
5228 { "qa", CTRL, 0, GDK_q },
5229 { "restart", MOD1, 0, GDK_q },
5230 { "js toggle", CTRL, 0, GDK_j },
5231 { "cookie toggle", MOD1, 0, GDK_c },
5232 { "togglesrc", CTRL, 0, GDK_s },
5233 { "yankuri", 0, 0, GDK_y },
5234 { "pasteuricur", 0, 0, GDK_p },
5235 { "pasteurinew", 0, 0, GDK_P },
5236 { "toplevel toggle", 0, 0, GDK_F4 },
5237 { "help", 0, 0, GDK_F1 },
5238 { "run_script", MOD1, 0, GDK_r },
5240 /* search */
5241 { "searchnext", 0, 0, GDK_n },
5242 { "searchprevious", 0, 0, GDK_N },
5244 /* focus */
5245 { "focusaddress", 0, 0, GDK_F6 },
5246 { "focussearch", 0, 0, GDK_F7 },
5248 /* hinting */
5249 { "hinting", 0, 0, GDK_f },
5251 /* custom stylesheet */
5252 { "userstyle", 0, 0, GDK_i },
5254 /* navigation */
5255 { "goback", 0, 0, GDK_BackSpace },
5256 { "goback", MOD1, 0, GDK_Left },
5257 { "goforward", SHFT, 0, GDK_BackSpace },
5258 { "goforward", MOD1, 0, GDK_Right },
5259 { "reload", 0, 0, GDK_F5 },
5260 { "reload", CTRL, 0, GDK_r },
5261 { "reload", CTRL, 0, GDK_l },
5262 { "favorites", MOD1, 1, GDK_f },
5264 /* vertical movement */
5265 { "scrolldown", 0, 0, GDK_j },
5266 { "scrolldown", 0, 0, GDK_Down },
5267 { "scrollup", 0, 0, GDK_Up },
5268 { "scrollup", 0, 0, GDK_k },
5269 { "scrollbottom", 0, 0, GDK_G },
5270 { "scrollbottom", 0, 0, GDK_End },
5271 { "scrolltop", 0, 0, GDK_Home },
5272 { "scrollpagedown", 0, 0, GDK_space },
5273 { "scrollpagedown", CTRL, 0, GDK_f },
5274 { "scrollhalfdown", CTRL, 0, GDK_d },
5275 { "scrollpagedown", 0, 0, GDK_Page_Down },
5276 { "scrollpageup", 0, 0, GDK_Page_Up },
5277 { "scrollpageup", CTRL, 0, GDK_b },
5278 { "scrollhalfup", CTRL, 0, GDK_u },
5279 /* horizontal movement */
5280 { "scrollright", 0, 0, GDK_l },
5281 { "scrollright", 0, 0, GDK_Right },
5282 { "scrollleft", 0, 0, GDK_Left },
5283 { "scrollleft", 0, 0, GDK_h },
5284 { "scrollfarright", 0, 0, GDK_dollar },
5285 { "scrollfarleft", 0, 0, GDK_0 },
5287 /* tabs */
5288 { "tabnew", CTRL, 0, GDK_t },
5289 { "999tabnew", CTRL, 0, GDK_T },
5290 { "tabclose", CTRL, 1, GDK_w },
5291 { "tabundoclose", 0, 0, GDK_U },
5292 { "tabnext 1", CTRL, 0, GDK_1 },
5293 { "tabnext 2", CTRL, 0, GDK_2 },
5294 { "tabnext 3", CTRL, 0, GDK_3 },
5295 { "tabnext 4", CTRL, 0, GDK_4 },
5296 { "tabnext 5", CTRL, 0, GDK_5 },
5297 { "tabnext 6", CTRL, 0, GDK_6 },
5298 { "tabnext 7", CTRL, 0, GDK_7 },
5299 { "tabnext 8", CTRL, 0, GDK_8 },
5300 { "tabnext 9", CTRL, 0, GDK_9 },
5301 { "tabfirst", CTRL, 0, GDK_less },
5302 { "tablast", CTRL, 0, GDK_greater },
5303 { "tabprevious", CTRL, 0, GDK_Left },
5304 { "tabnext", CTRL, 0, GDK_Right },
5305 { "focusout", CTRL, 0, GDK_minus },
5306 { "focusin", CTRL, 0, GDK_plus },
5307 { "focusin", CTRL, 0, GDK_equal },
5308 { "focusreset", CTRL, 0, GDK_0 },
5310 /* command aliases (handy when -S flag is used) */
5311 { "promptopen", 0, 0, GDK_F9 },
5312 { "promptopencurrent", 0, 0, GDK_F10 },
5313 { "prompttabnew", 0, 0, GDK_F11 },
5314 { "prompttabnewcurrent",0, 0, GDK_F12 },
5316 TAILQ_HEAD(keybinding_list, key_binding);
5318 void
5319 walk_kb(struct settings *s,
5320 void (*cb)(struct settings *, char *, void *), void *cb_args)
5322 struct key_binding *k;
5323 char str[1024];
5325 if (s == NULL || cb == NULL) {
5326 show_oops(NULL, "walk_kb invalid parameters");
5327 return;
5330 TAILQ_FOREACH(k, &kbl, entry) {
5331 if (k->cmd == NULL)
5332 continue;
5333 str[0] = '\0';
5335 /* sanity */
5336 if (gdk_keyval_name(k->key) == NULL)
5337 continue;
5339 strlcat(str, k->cmd, sizeof str);
5340 strlcat(str, ",", sizeof str);
5342 if (k->mask & GDK_SHIFT_MASK)
5343 strlcat(str, "S-", sizeof str);
5344 if (k->mask & GDK_CONTROL_MASK)
5345 strlcat(str, "C-", sizeof str);
5346 if (k->mask & GDK_MOD1_MASK)
5347 strlcat(str, "M1-", sizeof str);
5348 if (k->mask & GDK_MOD2_MASK)
5349 strlcat(str, "M2-", sizeof str);
5350 if (k->mask & GDK_MOD3_MASK)
5351 strlcat(str, "M3-", sizeof str);
5352 if (k->mask & GDK_MOD4_MASK)
5353 strlcat(str, "M4-", sizeof str);
5354 if (k->mask & GDK_MOD5_MASK)
5355 strlcat(str, "M5-", sizeof str);
5357 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5358 cb(s, str, cb_args);
5362 void
5363 init_keybindings(void)
5365 int i;
5366 struct key_binding *k;
5368 for (i = 0; i < LENGTH(keys); i++) {
5369 k = g_malloc0(sizeof *k);
5370 k->cmd = keys[i].cmd;
5371 k->mask = keys[i].mask;
5372 k->use_in_entry = keys[i].use_in_entry;
5373 k->key = keys[i].key;
5374 TAILQ_INSERT_HEAD(&kbl, k, entry);
5376 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5377 k->cmd ? k->cmd : "unnamed key");
5381 void
5382 keybinding_clearall(void)
5384 struct key_binding *k, *next;
5386 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5387 next = TAILQ_NEXT(k, entry);
5388 if (k->cmd == NULL)
5389 continue;
5391 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5392 k->cmd ? k->cmd : "unnamed key");
5393 TAILQ_REMOVE(&kbl, k, entry);
5394 g_free(k);
5399 keybinding_add(char *cmd, char *key, int use_in_entry)
5401 struct key_binding *k;
5402 guint keyval, mask = 0;
5403 int i;
5405 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5407 /* Keys which are to be used in entry have been prefixed with an
5408 * exclamation mark. */
5409 if (use_in_entry)
5410 key++;
5412 /* find modifier keys */
5413 if (strstr(key, "S-"))
5414 mask |= GDK_SHIFT_MASK;
5415 if (strstr(key, "C-"))
5416 mask |= GDK_CONTROL_MASK;
5417 if (strstr(key, "M1-"))
5418 mask |= GDK_MOD1_MASK;
5419 if (strstr(key, "M2-"))
5420 mask |= GDK_MOD2_MASK;
5421 if (strstr(key, "M3-"))
5422 mask |= GDK_MOD3_MASK;
5423 if (strstr(key, "M4-"))
5424 mask |= GDK_MOD4_MASK;
5425 if (strstr(key, "M5-"))
5426 mask |= GDK_MOD5_MASK;
5428 /* find keyname */
5429 for (i = strlen(key) - 1; i > 0; i--)
5430 if (key[i] == '-')
5431 key = &key[i + 1];
5433 /* validate keyname */
5434 keyval = gdk_keyval_from_name(key);
5435 if (keyval == GDK_VoidSymbol) {
5436 warnx("invalid keybinding name %s", key);
5437 return (1);
5439 /* must run this test too, gtk+ doesn't handle 10 for example */
5440 if (gdk_keyval_name(keyval) == NULL) {
5441 warnx("invalid keybinding name %s", key);
5442 return (1);
5445 /* Remove eventual dupes. */
5446 TAILQ_FOREACH(k, &kbl, entry)
5447 if (k->key == keyval && k->mask == mask) {
5448 TAILQ_REMOVE(&kbl, k, entry);
5449 g_free(k);
5450 break;
5453 /* add keyname */
5454 k = g_malloc0(sizeof *k);
5455 k->cmd = g_strdup(cmd);
5456 k->mask = mask;
5457 k->use_in_entry = use_in_entry;
5458 k->key = keyval;
5460 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5461 k->cmd,
5462 k->mask,
5463 k->use_in_entry,
5464 k->key);
5465 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5466 k->cmd, gdk_keyval_name(keyval));
5468 TAILQ_INSERT_HEAD(&kbl, k, entry);
5470 return (0);
5474 add_kb(struct settings *s, char *entry)
5476 char *kb, *key;
5478 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5480 /* clearall is special */
5481 if (!strcmp(entry, "clearall")) {
5482 keybinding_clearall();
5483 return (0);
5486 kb = strstr(entry, ",");
5487 if (kb == NULL)
5488 return (1);
5489 *kb = '\0';
5490 key = kb + 1;
5492 return (keybinding_add(entry, key, key[0] == '!'));
5495 struct cmd {
5496 char *cmd;
5497 int level;
5498 int (*func)(struct tab *, struct karg *);
5499 int arg;
5500 int type;
5501 } cmds[] = {
5502 { "command", 0, command, ':', 0 },
5503 { "search", 0, command, '/', 0 },
5504 { "searchb", 0, command, '?', 0 },
5505 { "togglesrc", 0, toggle_src, 0, 0 },
5507 /* yanking and pasting */
5508 { "yankuri", 0, yank_uri, 0, 0 },
5509 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5510 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5511 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5513 /* search */
5514 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5515 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5517 /* focus */
5518 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5519 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5521 /* hinting */
5522 { "hinting", 0, hint, 0, 0 },
5524 /* custom stylesheet */
5525 { "userstyle", 0, userstyle, 0, 0 },
5527 /* navigation */
5528 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5529 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5530 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5532 /* vertical movement */
5533 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5534 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5535 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5536 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5537 { "1", 0, move, XT_MOVE_TOP, 0 },
5538 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5539 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5540 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5541 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5542 /* horizontal movement */
5543 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5544 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5545 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5546 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5548 { "favorites", 0, xtp_page_fl, 0, 0 },
5549 { "fav", 0, xtp_page_fl, 0, 0 },
5550 { "favadd", 0, add_favorite, 0, 0 },
5552 { "qall", 0, quit, 0, 0 },
5553 { "quitall", 0, quit, 0, 0 },
5554 { "w", 0, save_tabs, 0, 0 },
5555 { "wq", 0, save_tabs_and_quit, 0, 0 },
5556 { "help", 0, help, 0, 0 },
5557 { "about", 0, about, 0, 0 },
5558 { "stats", 0, stats, 0, 0 },
5559 { "version", 0, about, 0, 0 },
5561 /* js command */
5562 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5563 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5564 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5565 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5566 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5567 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5568 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5569 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5570 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5571 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5572 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5574 /* cookie command */
5575 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5576 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5577 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5578 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5579 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5580 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5581 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5582 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5583 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5584 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5585 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5587 /* toplevel (domain) command */
5588 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5589 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5591 /* cookie jar */
5592 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5594 /* cert command */
5595 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5596 { "save", 1, cert_cmd, XT_SAVE, 0 },
5597 { "show", 1, cert_cmd, XT_SHOW, 0 },
5599 { "ca", 0, ca_cmd, 0, 0 },
5600 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5601 { "dl", 0, xtp_page_dl, 0, 0 },
5602 { "h", 0, xtp_page_hl, 0, 0 },
5603 { "history", 0, xtp_page_hl, 0, 0 },
5604 { "home", 0, go_home, 0, 0 },
5605 { "restart", 0, restart, 0, 0 },
5606 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5607 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5608 { "statustoggle", 0, statustoggle, 0, 0 },
5609 { "run_script", 0, run_page_script, 0, XT_USERARG },
5611 { "print", 0, print_page, 0, 0 },
5613 /* tabs */
5614 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5615 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5616 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5617 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5618 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5619 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5620 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5621 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5622 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5623 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5624 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5625 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5626 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5627 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5628 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5629 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5630 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5631 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5632 { "buffers", 0, buffers, 0, 0 },
5633 { "ls", 0, buffers, 0, 0 },
5634 { "tabs", 0, buffers, 0, 0 },
5636 /* command aliases (handy when -S flag is used) */
5637 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5638 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5639 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5640 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5642 /* settings */
5643 { "set", 0, set, 0, XT_USERARG },
5645 { "fullscreen", 0, fullscreen, 0, 0 },
5646 { "f", 0, fullscreen, 0, 0 },
5648 /* sessions */
5649 { "session", 0, session_cmd, XT_SHOW, 0 },
5650 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5651 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5652 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5653 { "show", 1, session_cmd, XT_SHOW, 0 },
5656 struct {
5657 int index;
5658 int len;
5659 gchar *list[256];
5660 } cmd_status = {-1, 0};
5662 gboolean
5663 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5666 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5667 btn_down = 0;
5669 return (FALSE);
5672 gboolean
5673 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5675 struct karg a;
5677 hide_oops(t);
5678 hide_buffers(t);
5680 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5681 btn_down = 1;
5682 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5683 /* go backward */
5684 a.i = XT_NAV_BACK;
5685 navaction(t, &a);
5687 return (TRUE);
5688 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5689 /* go forward */
5690 a.i = XT_NAV_FORWARD;
5691 navaction(t, &a);
5693 return (TRUE);
5696 return (FALSE);
5699 gboolean
5700 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5702 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5704 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5705 delete_tab(t);
5707 return (FALSE);
5711 * cancel, remove, etc. downloads
5713 void
5714 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5716 struct download find, *d = NULL;
5718 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5720 /* some commands require a valid download id */
5721 if (cmd != XT_XTP_DL_LIST) {
5722 /* lookup download in question */
5723 find.id = id;
5724 d = RB_FIND(download_list, &downloads, &find);
5726 if (d == NULL) {
5727 show_oops(t, "%s: no such download", __func__);
5728 return;
5732 /* decide what to do */
5733 switch (cmd) {
5734 case XT_XTP_DL_CANCEL:
5735 webkit_download_cancel(d->download);
5736 break;
5737 case XT_XTP_DL_REMOVE:
5738 webkit_download_cancel(d->download); /* just incase */
5739 g_object_unref(d->download);
5740 RB_REMOVE(download_list, &downloads, d);
5741 break;
5742 case XT_XTP_DL_LIST:
5743 /* Nothing */
5744 break;
5745 default:
5746 show_oops(t, "%s: unknown command", __func__);
5747 break;
5749 xtp_page_dl(t, NULL);
5753 * Actions on history, only does one thing for now, but
5754 * we provide the function for future actions
5756 void
5757 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5759 struct history *h, *next;
5760 int i = 1;
5762 switch (cmd) {
5763 case XT_XTP_HL_REMOVE:
5764 /* walk backwards, as listed in reverse */
5765 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5766 next = RB_PREV(history_list, &hl, h);
5767 if (id == i) {
5768 RB_REMOVE(history_list, &hl, h);
5769 g_free((gpointer) h->title);
5770 g_free((gpointer) h->uri);
5771 g_free(h);
5772 break;
5774 i++;
5776 break;
5777 case XT_XTP_HL_LIST:
5778 /* Nothing - just xtp_page_hl() below */
5779 break;
5780 default:
5781 show_oops(t, "%s: unknown command", __func__);
5782 break;
5785 xtp_page_hl(t, NULL);
5788 /* remove a favorite */
5789 void
5790 remove_favorite(struct tab *t, int index)
5792 char file[PATH_MAX], *title, *uri = NULL;
5793 char *new_favs, *tmp;
5794 FILE *f;
5795 int i;
5796 size_t len, lineno;
5798 /* open favorites */
5799 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5801 if ((f = fopen(file, "r")) == NULL) {
5802 show_oops(t, "%s: can't open favorites: %s",
5803 __func__, strerror(errno));
5804 return;
5807 /* build a string which will become the new favroites file */
5808 new_favs = g_strdup("");
5810 for (i = 1;;) {
5811 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5812 if (feof(f) || ferror(f))
5813 break;
5814 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5815 if (len == 0) {
5816 free(title);
5817 title = NULL;
5818 continue;
5821 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5822 if (feof(f) || ferror(f)) {
5823 show_oops(t, "%s: can't parse favorites %s",
5824 __func__, strerror(errno));
5825 goto clean;
5829 /* as long as this isn't the one we are deleting add to file */
5830 if (i != index) {
5831 tmp = new_favs;
5832 new_favs = g_strdup_printf("%s%s\n%s\n",
5833 new_favs, title, uri);
5834 g_free(tmp);
5837 free(uri);
5838 uri = NULL;
5839 free(title);
5840 title = NULL;
5841 i++;
5843 fclose(f);
5845 /* write back new favorites file */
5846 if ((f = fopen(file, "w")) == NULL) {
5847 show_oops(t, "%s: can't open favorites: %s",
5848 __func__, strerror(errno));
5849 goto clean;
5852 fwrite(new_favs, strlen(new_favs), 1, f);
5853 fclose(f);
5855 clean:
5856 if (uri)
5857 free(uri);
5858 if (title)
5859 free(title);
5861 g_free(new_favs);
5864 void
5865 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5867 switch (cmd) {
5868 case XT_XTP_FL_LIST:
5869 /* nothing, just the below call to xtp_page_fl() */
5870 break;
5871 case XT_XTP_FL_REMOVE:
5872 remove_favorite(t, arg);
5873 break;
5874 default:
5875 show_oops(t, "%s: invalid favorites command", __func__);
5876 break;
5879 xtp_page_fl(t, NULL);
5882 void
5883 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5885 switch (cmd) {
5886 case XT_XTP_CL_LIST:
5887 /* nothing, just xtp_page_cl() */
5888 break;
5889 case XT_XTP_CL_REMOVE:
5890 remove_cookie(arg);
5891 break;
5892 default:
5893 show_oops(t, "%s: unknown cookie xtp command", __func__);
5894 break;
5897 xtp_page_cl(t, NULL);
5900 /* link an XTP class to it's session key and handler function */
5901 struct xtp_despatch {
5902 uint8_t xtp_class;
5903 char **session_key;
5904 void (*handle_func)(struct tab *, uint8_t, int);
5907 struct xtp_despatch xtp_despatches[] = {
5908 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5909 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5910 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5911 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5912 { XT_XTP_INVALID, NULL, NULL }
5916 * is the url xtp protocol? (xxxt://)
5917 * if so, parse and despatch correct bahvior
5920 parse_xtp_url(struct tab *t, const char *url)
5922 char *dup = NULL, *p, *last;
5923 uint8_t n_tokens = 0;
5924 char *tokens[4] = {NULL, NULL, NULL, ""};
5925 struct xtp_despatch *dsp, *dsp_match = NULL;
5926 uint8_t req_class;
5927 int ret = FALSE;
5930 * tokens array meaning:
5931 * tokens[0] = class
5932 * tokens[1] = session key
5933 * tokens[2] = action
5934 * tokens[3] = optional argument
5937 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5939 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5940 goto clean;
5942 dup = g_strdup(url + strlen(XT_XTP_STR));
5944 /* split out the url */
5945 for ((p = strtok_r(dup, "/", &last)); p;
5946 (p = strtok_r(NULL, "/", &last))) {
5947 if (n_tokens < 4)
5948 tokens[n_tokens++] = p;
5951 /* should be atleast three fields 'class/seskey/command/arg' */
5952 if (n_tokens < 3)
5953 goto clean;
5955 dsp = xtp_despatches;
5956 req_class = atoi(tokens[0]);
5957 while (dsp->xtp_class) {
5958 if (dsp->xtp_class == req_class) {
5959 dsp_match = dsp;
5960 break;
5962 dsp++;
5965 /* did we find one atall? */
5966 if (dsp_match == NULL) {
5967 show_oops(t, "%s: no matching xtp despatch found", __func__);
5968 goto clean;
5971 /* check session key and call despatch function */
5972 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5973 ret = TRUE; /* all is well, this was a valid xtp request */
5974 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5977 clean:
5978 if (dup)
5979 g_free(dup);
5981 return (ret);
5986 void
5987 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5989 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5991 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5993 if (t == NULL) {
5994 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5995 return;
5998 if (uri == NULL) {
5999 show_oops(t, "activate_uri_entry_cb no uri");
6000 return;
6003 uri += strspn(uri, "\t ");
6005 /* if xxxt:// treat specially */
6006 if (parse_xtp_url(t, uri))
6007 return;
6009 /* otherwise continue to load page normally */
6010 load_uri(t, (gchar *)uri);
6011 focus_webview(t);
6014 void
6015 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6017 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6018 char *newuri = NULL;
6019 gchar *enc_search;
6021 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6023 if (t == NULL) {
6024 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6025 return;
6028 if (search_string == NULL) {
6029 show_oops(t, "no search_string");
6030 return;
6033 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6035 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6036 newuri = g_strdup_printf(search_string, enc_search);
6037 g_free(enc_search);
6039 marks_clear(t);
6040 webkit_web_view_load_uri(t->wv, newuri);
6041 focus_webview(t);
6043 if (newuri)
6044 g_free(newuri);
6047 void
6048 check_and_set_cookie(const gchar *uri, struct tab *t)
6050 struct domain *d = NULL;
6051 int es = 0;
6053 if (uri == NULL || t == NULL)
6054 return;
6056 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6057 es = 0;
6058 else
6059 es = 1;
6061 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6062 es ? "enable" : "disable", uri);
6064 g_object_set(G_OBJECT(t->settings),
6065 "enable-html5-local-storage", es, (char *)NULL);
6066 webkit_web_view_set_settings(t->wv, t->settings);
6069 void
6070 check_and_set_js(const gchar *uri, struct tab *t)
6072 struct domain *d = NULL;
6073 int es = 0;
6075 if (uri == NULL || t == NULL)
6076 return;
6078 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6079 es = 0;
6080 else
6081 es = 1;
6083 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6084 es ? "enable" : "disable", uri);
6086 g_object_set(G_OBJECT(t->settings),
6087 "enable-scripts", es, (char *)NULL);
6088 g_object_set(G_OBJECT(t->settings),
6089 "javascript-can-open-windows-automatically", es, (char *)NULL);
6090 webkit_web_view_set_settings(t->wv, t->settings);
6092 button_set_stockid(t->js_toggle,
6093 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6096 gboolean
6097 color_address_bar(gpointer p)
6099 GdkColor color;
6100 struct tab *tt, *t = p;
6101 gchar *col_str = XT_COLOR_YELLOW;
6103 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6105 /* make sure t still exists */
6106 if (t == NULL)
6107 goto done;
6108 TAILQ_FOREACH(tt, &tabs, entry)
6109 if (t == tt)
6110 break;
6111 if (t != tt)
6112 goto done;
6114 switch (load_compare_cert(t, NULL)) {
6115 case CERT_LOCAL:
6116 col_str = XT_COLOR_BLUE;
6117 break;
6118 case CERT_TRUSTED:
6119 col_str = XT_COLOR_GREEN;
6120 break;
6121 case CERT_UNTRUSTED:
6122 col_str = XT_COLOR_YELLOW;
6123 break;
6124 case CERT_BAD:
6125 col_str = XT_COLOR_RED;
6126 break;
6129 gdk_color_parse(col_str, &color);
6130 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6132 if (!strcmp(col_str, XT_COLOR_WHITE))
6133 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6134 else
6135 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6137 col_str = NULL;
6138 done:
6139 return (FALSE /* kill thread */);
6142 void
6143 show_ca_status(struct tab *t, const char *uri)
6145 GdkColor color;
6146 gchar *col_str = XT_COLOR_WHITE;
6148 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6149 ssl_strict_certs, ssl_ca_file, uri);
6151 if (t == NULL)
6152 return;
6154 if (uri == NULL)
6155 goto done;
6156 if (ssl_ca_file == NULL) {
6157 if (g_str_has_prefix(uri, "http://"))
6158 goto done;
6159 if (g_str_has_prefix(uri, "https://")) {
6160 col_str = XT_COLOR_RED;
6161 goto done;
6163 return;
6165 if (g_str_has_prefix(uri, "http://") ||
6166 !g_str_has_prefix(uri, "https://"))
6167 goto done;
6169 /* thread the coloring of the address bar */
6170 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6171 color_address_bar, t, NULL);
6172 return;
6174 done:
6175 if (col_str) {
6176 gdk_color_parse(col_str, &color);
6177 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6179 if (!strcmp(col_str, XT_COLOR_WHITE))
6180 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6181 else
6182 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6186 void
6187 free_favicon(struct tab *t)
6189 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6190 __func__, t->icon_download, t->icon_request);
6192 if (t->icon_request)
6193 g_object_unref(t->icon_request);
6194 if (t->icon_dest_uri)
6195 g_free(t->icon_dest_uri);
6197 t->icon_request = NULL;
6198 t->icon_dest_uri = NULL;
6201 void
6202 xt_icon_from_name(struct tab *t, gchar *name)
6204 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6205 GTK_ENTRY_ICON_PRIMARY, "text-html");
6206 if (show_url == 0)
6207 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6208 GTK_ENTRY_ICON_PRIMARY, "text-html");
6209 else
6210 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6211 GTK_ENTRY_ICON_PRIMARY, NULL);
6214 void
6215 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6217 GdkPixbuf *pb_scaled;
6219 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6220 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6221 GDK_INTERP_BILINEAR);
6222 else
6223 pb_scaled = pb;
6225 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6226 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6227 if (show_url == 0)
6228 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6229 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6230 else
6231 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6232 GTK_ENTRY_ICON_PRIMARY, NULL);
6234 if (pb_scaled != pb)
6235 g_object_unref(pb_scaled);
6238 void
6239 xt_icon_from_file(struct tab *t, char *file)
6241 GdkPixbuf *pb;
6243 if (g_str_has_prefix(file, "file://"))
6244 file += strlen("file://");
6246 pb = gdk_pixbuf_new_from_file(file, NULL);
6247 if (pb) {
6248 xt_icon_from_pixbuf(t, pb);
6249 g_object_unref(pb);
6250 } else
6251 xt_icon_from_name(t, "text-html");
6254 gboolean
6255 is_valid_icon(char *file)
6257 gboolean valid = 0;
6258 const char *mime_type;
6259 GFileInfo *fi;
6260 GFile *gf;
6262 gf = g_file_new_for_path(file);
6263 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6264 NULL, NULL);
6265 mime_type = g_file_info_get_content_type(fi);
6266 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6267 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6268 g_strcmp0(mime_type, "image/png") == 0 ||
6269 g_strcmp0(mime_type, "image/gif") == 0 ||
6270 g_strcmp0(mime_type, "application/octet-stream") == 0;
6271 g_object_unref(fi);
6272 g_object_unref(gf);
6274 return (valid);
6277 void
6278 set_favicon_from_file(struct tab *t, char *file)
6280 struct stat sb;
6282 if (t == NULL || file == NULL)
6283 return;
6285 if (g_str_has_prefix(file, "file://"))
6286 file += strlen("file://");
6287 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6289 if (!stat(file, &sb)) {
6290 if (sb.st_size == 0 || !is_valid_icon(file)) {
6291 /* corrupt icon so trash it */
6292 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6293 __func__, file);
6294 unlink(file);
6295 /* no need to set icon to default here */
6296 return;
6299 xt_icon_from_file(t, file);
6302 void
6303 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6304 WebKitWebView *wv)
6306 WebKitDownloadStatus status = webkit_download_get_status(download);
6307 struct tab *tt = NULL, *t = NULL;
6310 * find the webview instead of passing in the tab as it could have been
6311 * deleted from underneath us.
6313 TAILQ_FOREACH(tt, &tabs, entry) {
6314 if (tt->wv == wv) {
6315 t = tt;
6316 break;
6319 if (t == NULL)
6320 return;
6322 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6323 __func__, t->tab_id, status);
6325 switch (status) {
6326 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6327 /* -1 */
6328 t->icon_download = NULL;
6329 free_favicon(t);
6330 break;
6331 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6332 /* 0 */
6333 break;
6334 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6335 /* 1 */
6336 break;
6337 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6338 /* 2 */
6339 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6340 __func__, t->tab_id);
6341 t->icon_download = NULL;
6342 free_favicon(t);
6343 break;
6344 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6345 /* 3 */
6347 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6348 __func__, t->icon_dest_uri);
6349 set_favicon_from_file(t, t->icon_dest_uri);
6350 /* these will be freed post callback */
6351 t->icon_request = NULL;
6352 t->icon_download = NULL;
6353 break;
6354 default:
6355 break;
6359 void
6360 abort_favicon_download(struct tab *t)
6362 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6364 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6365 if (t->icon_download) {
6366 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6367 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6368 webkit_download_cancel(t->icon_download);
6369 t->icon_download = NULL;
6370 } else
6371 free_favicon(t);
6372 #endif
6374 xt_icon_from_name(t, "text-html");
6377 void
6378 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6380 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6382 if (uri == NULL || t == NULL)
6383 return;
6385 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6386 /* take icon from WebKitIconDatabase */
6387 GdkPixbuf *pb;
6389 pb = webkit_web_view_get_icon_pixbuf(wv);
6390 if (pb) {
6391 xt_icon_from_pixbuf(t, pb);
6392 g_object_unref(pb);
6393 } else
6394 xt_icon_from_name(t, "text-html");
6395 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6396 /* download icon to cache dir */
6397 gchar *name_hash, file[PATH_MAX];
6398 struct stat sb;
6400 if (t->icon_request) {
6401 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6402 return;
6405 /* check to see if we got the icon in cache */
6406 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6407 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6408 g_free(name_hash);
6410 if (!stat(file, &sb)) {
6411 if (sb.st_size > 0) {
6412 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6413 __func__, file);
6414 set_favicon_from_file(t, file);
6415 return;
6418 /* corrupt icon so trash it */
6419 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6420 __func__, file);
6421 unlink(file);
6424 /* create download for icon */
6425 t->icon_request = webkit_network_request_new(uri);
6426 if (t->icon_request == NULL) {
6427 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6428 __func__, uri);
6429 return;
6432 t->icon_download = webkit_download_new(t->icon_request);
6433 if (t->icon_download == NULL)
6434 return;
6436 /* we have to free icon_dest_uri later */
6437 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6438 webkit_download_set_destination_uri(t->icon_download,
6439 t->icon_dest_uri);
6441 if (webkit_download_get_status(t->icon_download) ==
6442 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6443 g_object_unref(t->icon_request);
6444 g_free(t->icon_dest_uri);
6445 t->icon_request = NULL;
6446 t->icon_dest_uri = NULL;
6447 return;
6450 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6451 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6453 webkit_download_start(t->icon_download);
6454 #endif
6457 void
6458 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6460 const gchar *uri = NULL, *title = NULL;
6461 struct history *h, find;
6462 struct karg a;
6463 GdkColor color;
6465 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6466 webkit_web_view_get_load_status(wview),
6467 get_uri(t) ? get_uri(t) : "NOTHING");
6469 if (t == NULL) {
6470 show_oops(NULL, "notify_load_status_cb invalid parameters");
6471 return;
6474 switch (webkit_web_view_get_load_status(wview)) {
6475 case WEBKIT_LOAD_PROVISIONAL:
6476 /* 0 */
6477 abort_favicon_download(t);
6478 #if GTK_CHECK_VERSION(2, 20, 0)
6479 gtk_widget_show(t->spinner);
6480 gtk_spinner_start(GTK_SPINNER(t->spinner));
6481 #endif
6482 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6484 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6486 /* assume we are a new address */
6487 gdk_color_parse("white", &color);
6488 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6489 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6491 /* take focus if we are visible */
6492 focus_webview(t);
6493 t->focus_wv = 1;
6495 break;
6497 case WEBKIT_LOAD_COMMITTED:
6498 /* 1 */
6499 uri = get_uri(t);
6500 if (uri == NULL)
6501 return;
6502 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6504 if (t->status) {
6505 g_free(t->status);
6506 t->status = NULL;
6508 set_status(t, (char *)uri, XT_STATUS_LOADING);
6510 /* check if js white listing is enabled */
6511 if (enable_cookie_whitelist)
6512 check_and_set_cookie(uri, t);
6513 if (enable_js_whitelist)
6514 check_and_set_js(uri, t);
6516 if (t->styled)
6517 apply_style(t);
6520 /* we know enough to autosave the session */
6521 if (session_autosave) {
6522 a.s = NULL;
6523 save_tabs(t, &a);
6526 show_ca_status(t, uri);
6527 break;
6529 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6530 /* 3 */
6531 break;
6533 case WEBKIT_LOAD_FINISHED:
6534 /* 2 */
6535 uri = get_uri(t);
6536 if (uri == NULL)
6537 return;
6539 if (!strncmp(uri, "http://", strlen("http://")) ||
6540 !strncmp(uri, "https://", strlen("https://")) ||
6541 !strncmp(uri, "file://", strlen("file://"))) {
6542 find.uri = uri;
6543 h = RB_FIND(history_list, &hl, &find);
6544 if (!h) {
6545 title = get_title(t, FALSE);
6546 h = g_malloc(sizeof *h);
6547 h->uri = g_strdup(uri);
6548 h->title = g_strdup(title);
6549 RB_INSERT(history_list, &hl, h);
6550 completion_add_uri(h->uri);
6551 update_history_tabs(NULL);
6555 set_status(t, (char *)uri, XT_STATUS_URI);
6556 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6557 case WEBKIT_LOAD_FAILED:
6558 /* 4 */
6559 #endif
6560 #if GTK_CHECK_VERSION(2, 20, 0)
6561 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6562 gtk_widget_hide(t->spinner);
6563 #endif
6564 default:
6565 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6566 break;
6569 if (t->item)
6570 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6571 else
6572 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6573 webkit_web_view_can_go_back(wview));
6575 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6576 webkit_web_view_can_go_forward(wview));
6579 #if 0
6580 gboolean
6581 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6582 gchar *uri, gpointer web_error,struct tab *t)
6585 * XXX this function is wrong
6586 * it overwrites perfectly good urls with garbage on load errors
6587 * those happen often when popups fail to resolve dns
6589 if (t->tmp_uri)
6590 g_free(t->tmp_uri);
6591 t->tmp_uri = g_strdup(uri);
6592 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6593 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6594 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6595 set_status(t, uri, XT_STATUS_NOTHING);
6597 return (FALSE);
6599 #endif
6601 void
6602 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6604 const gchar *title = NULL, *win_title = NULL;
6606 title = get_title(t, FALSE);
6607 win_title = get_title(t, TRUE);
6608 gtk_label_set_text(GTK_LABEL(t->label), title);
6609 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6610 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6611 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6614 void
6615 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6617 run_script(t, JS_HINTING);
6620 void
6621 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6623 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6624 progress == 100 ? 0 : (double)progress / 100);
6625 if (show_url == 0) {
6626 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6627 progress == 100 ? 0 : (double)progress / 100);
6630 update_statusbar_position(NULL, NULL);
6634 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6635 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6636 WebKitWebPolicyDecision *pd, struct tab *t)
6638 char *uri;
6639 WebKitWebNavigationReason reason;
6640 struct domain *d = NULL;
6642 if (t == NULL) {
6643 show_oops(NULL, "webview_npd_cb invalid parameters");
6644 return (FALSE);
6647 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6648 t->ctrl_click,
6649 webkit_network_request_get_uri(request));
6651 uri = (char *)webkit_network_request_get_uri(request);
6653 /* if this is an xtp url, we don't load anything else */
6654 if (parse_xtp_url(t, uri))
6655 return (TRUE);
6657 if (t->ctrl_click) {
6658 t->ctrl_click = 0;
6659 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6660 webkit_web_policy_decision_ignore(pd);
6661 return (TRUE); /* we made the decission */
6665 * This is a little hairy but it comes down to this:
6666 * when we run in whitelist mode we have to assist the browser in
6667 * opening the URL that it would have opened in a new tab.
6669 reason = webkit_web_navigation_action_get_reason(na);
6670 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6671 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6672 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6673 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6674 load_uri(t, uri);
6675 webkit_web_policy_decision_use(pd);
6676 return (TRUE); /* we made the decision */
6679 return (FALSE);
6682 WebKitWebView *
6683 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6685 struct tab *tt;
6686 struct domain *d = NULL;
6687 const gchar *uri;
6688 WebKitWebView *webview = NULL;
6690 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6691 webkit_web_view_get_uri(wv));
6693 if (tabless) {
6694 /* open in current tab */
6695 webview = t->wv;
6696 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6697 uri = webkit_web_view_get_uri(wv);
6698 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6699 return (NULL);
6701 tt = create_new_tab(NULL, NULL, 1, -1);
6702 webview = tt->wv;
6703 } else if (enable_scripts == 1) {
6704 tt = create_new_tab(NULL, NULL, 1, -1);
6705 webview = tt->wv;
6708 return (webview);
6711 gboolean
6712 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6714 const gchar *uri;
6715 struct domain *d = NULL;
6717 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6719 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6720 uri = webkit_web_view_get_uri(wv);
6721 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6722 return (FALSE);
6724 delete_tab(t);
6725 } else if (enable_scripts == 1)
6726 delete_tab(t);
6728 return (TRUE);
6732 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6734 /* we can not eat the event without throwing gtk off so defer it */
6736 /* catch middle click */
6737 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6738 t->ctrl_click = 1;
6739 goto done;
6742 /* catch ctrl click */
6743 if (e->type == GDK_BUTTON_RELEASE &&
6744 CLEAN(e->state) == GDK_CONTROL_MASK)
6745 t->ctrl_click = 1;
6746 else
6747 t->ctrl_click = 0;
6748 done:
6749 return (XT_CB_PASSTHROUGH);
6753 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6755 struct mime_type *m;
6757 m = find_mime_type(mime_type);
6758 if (m == NULL)
6759 return (1);
6760 if (m->mt_download)
6761 return (1);
6763 switch (fork()) {
6764 case -1:
6765 show_oops(t, "can't fork mime handler");
6766 return (1);
6767 /* NOTREACHED */
6768 case 0:
6769 break;
6770 default:
6771 return (0);
6774 /* child */
6775 execlp(m->mt_action, m->mt_action,
6776 webkit_network_request_get_uri(request), (void *)NULL);
6778 _exit(0);
6780 /* NOTREACHED */
6781 return (0);
6784 const gchar *
6785 get_mime_type(char *file)
6787 const char *mime_type;
6788 GFileInfo *fi;
6789 GFile *gf;
6791 if (g_str_has_prefix(file, "file://"))
6792 file += strlen("file://");
6794 gf = g_file_new_for_path(file);
6795 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6796 NULL, NULL);
6797 mime_type = g_file_info_get_content_type(fi);
6798 g_object_unref(fi);
6799 g_object_unref(gf);
6801 return (mime_type);
6805 run_download_mimehandler(char *mime_type, char *file)
6807 struct mime_type *m;
6809 m = find_mime_type(mime_type);
6810 if (m == NULL)
6811 return (1);
6813 switch (fork()) {
6814 case -1:
6815 show_oops(NULL, "can't fork download mime handler");
6816 return (1);
6817 /* NOTREACHED */
6818 case 0:
6819 break;
6820 default:
6821 return (0);
6824 /* child */
6825 if (g_str_has_prefix(file, "file://"))
6826 file += strlen("file://");
6827 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6829 _exit(0);
6831 /* NOTREACHED */
6832 return (0);
6835 void
6836 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6837 WebKitWebView *wv)
6839 WebKitDownloadStatus status;
6840 const gchar *file = NULL, *mime = NULL;
6842 if (download == NULL)
6843 return;
6844 status = webkit_download_get_status(download);
6845 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6846 return;
6848 file = webkit_download_get_destination_uri(download);
6849 if (file == NULL)
6850 return;
6851 mime = get_mime_type((char *)file);
6852 if (mime == NULL)
6853 return;
6855 run_download_mimehandler((char *)mime, (char *)file);
6859 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6860 WebKitNetworkRequest *request, char *mime_type,
6861 WebKitWebPolicyDecision *decision, struct tab *t)
6863 if (t == NULL) {
6864 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6865 return (FALSE);
6868 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6869 t->tab_id, mime_type);
6871 if (run_mimehandler(t, mime_type, request) == 0) {
6872 webkit_web_policy_decision_ignore(decision);
6873 focus_webview(t);
6874 return (TRUE);
6877 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6878 webkit_web_policy_decision_download(decision);
6879 return (TRUE);
6882 return (FALSE);
6886 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6887 struct tab *t)
6889 struct stat sb;
6890 const gchar *suggested_name;
6891 gchar *filename = NULL;
6892 char *uri = NULL;
6893 struct download *download_entry;
6894 int i, ret = TRUE;
6896 if (wk_download == NULL || t == NULL) {
6897 show_oops(NULL, "%s invalid parameters", __func__);
6898 return (FALSE);
6901 suggested_name = webkit_download_get_suggested_filename(wk_download);
6902 if (suggested_name == NULL)
6903 return (FALSE); /* abort download */
6905 i = 0;
6906 do {
6907 if (filename) {
6908 g_free(filename);
6909 filename = NULL;
6911 if (i) {
6912 g_free(uri);
6913 uri = NULL;
6914 filename = g_strdup_printf("%d%s", i, suggested_name);
6916 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6917 filename : suggested_name);
6918 i++;
6919 } while (!stat(uri + strlen("file://"), &sb));
6921 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6922 "local %s\n", __func__, t->tab_id, filename, uri);
6924 webkit_download_set_destination_uri(wk_download, uri);
6926 if (webkit_download_get_status(wk_download) ==
6927 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6928 show_oops(t, "%s: download failed to start", __func__);
6929 ret = FALSE;
6930 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6931 } else {
6932 /* connect "download first" mime handler */
6933 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6934 G_CALLBACK(download_status_changed_cb), NULL);
6936 download_entry = g_malloc(sizeof(struct download));
6937 download_entry->download = wk_download;
6938 download_entry->tab = t;
6939 download_entry->id = next_download_id++;
6940 RB_INSERT(download_list, &downloads, download_entry);
6941 /* get from history */
6942 g_object_ref(wk_download);
6943 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6944 show_oops(t, "Download of '%s' started...",
6945 basename((char *)webkit_download_get_destination_uri(wk_download)));
6948 if (uri)
6949 g_free(uri);
6951 if (filename)
6952 g_free(filename);
6954 /* sync other download manager tabs */
6955 update_download_tabs(NULL);
6958 * NOTE: never redirect/render the current tab before this
6959 * function returns. This will cause the download to never start.
6961 return (ret); /* start download */
6964 void
6965 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6967 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6969 if (t == NULL) {
6970 show_oops(NULL, "webview_hover_cb");
6971 return;
6974 if (uri)
6975 set_status(t, uri, XT_STATUS_LINK);
6976 else {
6977 if (t->status)
6978 set_status(t, t->status, XT_STATUS_NOTHING);
6983 mark(struct tab *t, struct karg *arg)
6985 char mark;
6986 int index;
6988 mark = arg->s[1];
6989 if ((index = marktoindex(mark)) == -1)
6990 return -1;
6992 if (arg->i == XT_MARK_SET)
6993 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6994 else if (arg->i == XT_MARK_GOTO) {
6995 if (t->mark[index] == XT_INVALID_MARK) {
6996 show_oops(t, "mark '%c' does not exist", mark);
6997 return -1;
6999 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7000 something changes the document size */
7001 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7004 return 0;
7007 void
7008 marks_clear(struct tab *t)
7010 int i;
7012 for (i = 0; i < LENGTH(t->mark); i++)
7013 t->mark[i] = XT_INVALID_MARK;
7017 qmarks_load(void)
7019 char file[PATH_MAX];
7020 char *line = NULL, *p, mark;
7021 int index, i;
7022 FILE *f;
7023 size_t linelen;
7025 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7026 if ((f = fopen(file, "r+")) == NULL) {
7027 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7028 return (1);
7031 for (i = 1; ; i++) {
7032 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7033 if (feof(f) || ferror(f))
7034 break;
7035 if (strlen(line) == 0 || line[0] == '#') {
7036 free(line);
7037 line = NULL;
7038 continue;
7041 p = strtok(line, " \t");
7043 if (p == NULL || strlen(p) != 1 ||
7044 (index = marktoindex(*p)) == -1) {
7045 warnx("corrupt quickmarks file, line %d", i);
7046 break;
7049 mark = *p;
7050 p = strtok(NULL, " \t");
7051 if (qmarks[index] != NULL)
7052 g_free(qmarks[index]);
7053 qmarks[index] = g_strdup(p);
7056 fclose(f);
7058 return (0);
7062 qmarks_save(void)
7064 char file[PATH_MAX];
7065 int i;
7066 FILE *f;
7068 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7069 if ((f = fopen(file, "r+")) == NULL) {
7070 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7071 return (1);
7074 for (i = 0; i < XT_NOMARKS; i++)
7075 if (qmarks[i] != NULL)
7076 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7078 fclose(f);
7080 return (0);
7084 qmark(struct tab *t, struct karg *arg)
7086 char mark;
7087 int index;
7089 mark = arg->s[strlen(arg->s)-1];
7090 index = marktoindex(mark);
7091 if (index == -1)
7092 return (-1);
7094 switch (arg->i) {
7095 case XT_QMARK_SET:
7096 if (qmarks[index] != NULL)
7097 g_free(qmarks[index]);
7099 qmarks_load(); /* sync if multiple instances */
7100 qmarks[index] = g_strdup(get_uri(t));
7101 qmarks_save();
7102 break;
7103 case XT_QMARK_OPEN:
7104 if (qmarks[index] != NULL)
7105 load_uri(t, qmarks[index]);
7106 else {
7107 show_oops(t, "quickmark \"%c\" does not exist",
7108 mark);
7109 return (-1);
7111 break;
7112 case XT_QMARK_TAB:
7113 if (qmarks[index] != NULL)
7114 create_new_tab(qmarks[index], NULL, 1, -1);
7115 else {
7116 show_oops(t, "quickmark \"%c\" does not exist",
7117 mark);
7118 return (-1);
7120 break;
7123 return (0);
7127 go_up(struct tab *t, struct karg *args)
7129 int levels;
7130 char *uri;
7131 char *tmp;
7133 levels = atoi(args->s);
7134 if (levels == 0)
7135 levels = 1;
7137 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7138 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7139 return 1;
7140 tmp += strlen(XT_PROTO_DELIM);
7142 /* if an uri starts with a slash, leave it alone (for file:///) */
7143 if (tmp[0] == '/')
7144 tmp++;
7146 while (levels--) {
7147 char *p;
7149 p = strrchr(tmp, '/');
7150 if (p != NULL)
7151 *p = '\0';
7152 else
7153 break;
7156 load_uri(t, uri);
7157 g_free(uri);
7159 return (0);
7163 gototab(struct tab *t, struct karg *args)
7165 int tab;
7166 struct karg arg = {0, NULL, -1};
7168 tab = atoi(args->s);
7170 arg.i = XT_TAB_NEXT;
7171 arg.precount = tab;
7173 movetab(t, &arg);
7175 return (0);
7179 zoom_amount(struct tab *t, struct karg *arg)
7181 struct karg narg = {0, NULL, -1};
7183 narg.i = atoi(arg->s);
7184 resizetab(t, &narg);
7186 return 0;
7190 flip_colon(struct tab *t, struct karg *arg)
7192 struct karg narg = {0, NULL, -1};
7193 char *p;
7195 if (t == NULL || arg == NULL)
7196 return (1);
7198 p = strstr(arg->s, ":");
7199 if (p == NULL)
7200 return (1);
7201 *p = '\0';
7203 narg.i = ':';
7204 narg.s = arg->s;
7205 command(t, &narg);
7207 return (0);
7210 /* buffer commands receive the regex that triggered them in arg.s */
7211 char bcmd[XT_BUFCMD_SZ];
7212 struct buffercmd {
7213 char *regex;
7214 int precount;
7215 #define XT_PRE_NO (0)
7216 #define XT_PRE_YES (1)
7217 #define XT_PRE_MAYBE (2)
7218 char *cmd;
7219 int (*func)(struct tab *, struct karg *);
7220 int arg;
7221 regex_t cregex;
7222 } buffercmds[] = {
7223 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7224 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7225 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7226 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7227 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7228 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7229 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7230 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7231 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7232 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7233 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7234 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7235 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7236 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7237 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7238 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7239 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7240 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7243 void
7244 buffercmd_init(void)
7246 int i;
7248 for (i = 0; i < LENGTH(buffercmds); i++)
7249 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7250 REG_EXTENDED | REG_NOSUB))
7251 startpage_add("invalid buffercmd regex %s",
7252 buffercmds[i].regex);
7255 void
7256 buffercmd_abort(struct tab *t)
7258 int i;
7260 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7261 for (i = 0; i < LENGTH(bcmd); i++)
7262 bcmd[i] = '\0';
7264 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7265 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7268 void
7269 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7271 struct karg arg = {0, NULL, -1};
7273 arg.i = cmd->arg;
7274 arg.s = g_strdup(bcmd);
7276 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7277 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7278 cmd->func(t, &arg);
7280 if (arg.s)
7281 g_free(arg.s);
7283 buffercmd_abort(t);
7286 gboolean
7287 buffercmd_addkey(struct tab *t, guint keyval)
7289 int i, c, match ;
7290 char s[XT_BUFCMD_SZ];
7292 if (keyval == GDK_Escape) {
7293 buffercmd_abort(t);
7294 return (XT_CB_HANDLED);
7297 /* key with modifier or non-ascii character */
7298 if (!isascii(keyval))
7299 return (XT_CB_PASSTHROUGH);
7301 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7302 "to buffer \"%s\"\n", keyval, bcmd);
7304 for (i = 0; i < LENGTH(bcmd); i++)
7305 if (bcmd[i] == '\0') {
7306 bcmd[i] = keyval;
7307 break;
7310 /* buffer full, ignore input */
7311 if (i >= LENGTH(bcmd) -1) {
7312 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7313 buffercmd_abort(t);
7314 return (XT_CB_HANDLED);
7317 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7319 /* find exact match */
7320 for (i = 0; i < LENGTH(buffercmds); i++)
7321 if (regexec(&buffercmds[i].cregex, bcmd,
7322 (size_t) 0, NULL, 0) == 0) {
7323 buffercmd_execute(t, &buffercmds[i]);
7324 goto done;
7327 /* find non exact matches to see if we need to abort ot not */
7328 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7329 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7330 c = -1;
7331 s[0] = '\0';
7332 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7333 if (isdigit(bcmd[0])) {
7334 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7335 continue;
7336 } else {
7337 c = 0;
7338 if (sscanf(bcmd, "%s", s) == 0)
7339 continue;
7341 } else if (buffercmds[i].precount == XT_PRE_YES) {
7342 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7343 continue;
7344 } else {
7345 if (sscanf(bcmd, "%s", s) == 0)
7346 continue;
7348 if (c == -1 && buffercmds[i].precount)
7349 continue;
7350 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7351 match++;
7353 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7354 i, match, buffercmds[i].cmd, c, s);
7356 if (match == 0) {
7357 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7358 buffercmd_abort(t);
7361 done:
7362 return (XT_CB_HANDLED);
7365 gboolean
7366 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7368 struct key_binding *k;
7370 /* handle keybindings if buffercmd is empty.
7371 if not empty, allow commands like C-n */
7372 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7373 TAILQ_FOREACH(k, &kbl, entry)
7374 if (e->keyval == k->key
7375 && (entry ? k->use_in_entry : 1)) {
7376 if (k->mask == 0) {
7377 if ((e->state & (CTRL | MOD1)) == 0)
7378 return (cmd_execute(t, k->cmd));
7379 } else if ((e->state & k->mask) == k->mask) {
7380 return (cmd_execute(t, k->cmd));
7384 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7385 return buffercmd_addkey(t, e->keyval);
7387 return (XT_CB_PASSTHROUGH);
7391 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7393 char s[2], buf[128];
7394 const char *errstr = NULL;
7396 /* don't use w directly; use t->whatever instead */
7398 if (t == NULL) {
7399 show_oops(NULL, "wv_keypress_after_cb");
7400 return (XT_CB_PASSTHROUGH);
7403 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7404 e->keyval, e->state, t);
7406 if (t->hints_on) {
7407 /* ESC */
7408 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7409 disable_hints(t);
7410 return (XT_CB_HANDLED);
7413 /* RETURN */
7414 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7415 if (errstr) {
7416 /* we have a string */
7417 } else {
7418 /* we have a number */
7419 snprintf(buf, sizeof buf,
7420 "vimprobable_fire(%s)", t->hint_num);
7421 run_script(t, buf);
7423 disable_hints(t);
7426 /* BACKSPACE */
7427 /* XXX unfuck this */
7428 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7429 if (t->hint_mode == XT_HINT_NUMERICAL) {
7430 /* last input was numerical */
7431 int l;
7432 l = strlen(t->hint_num);
7433 if (l > 0) {
7434 l--;
7435 if (l == 0) {
7436 disable_hints(t);
7437 enable_hints(t);
7438 } else {
7439 t->hint_num[l] = '\0';
7440 goto num;
7443 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7444 /* last input was alphanumerical */
7445 int l;
7446 l = strlen(t->hint_buf);
7447 if (l > 0) {
7448 l--;
7449 if (l == 0) {
7450 disable_hints(t);
7451 enable_hints(t);
7452 } else {
7453 t->hint_buf[l] = '\0';
7454 goto anum;
7457 } else {
7458 /* bogus */
7459 disable_hints(t);
7463 /* numerical input */
7464 if (CLEAN(e->state) == 0 &&
7465 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7466 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7467 snprintf(s, sizeof s, "%c", e->keyval);
7468 strlcat(t->hint_num, s, sizeof t->hint_num);
7469 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7470 t->hint_num);
7471 num:
7472 if (errstr) {
7473 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7474 "invalid link number\n");
7475 disable_hints(t);
7476 } else {
7477 snprintf(buf, sizeof buf,
7478 "vimprobable_update_hints(%s)",
7479 t->hint_num);
7480 t->hint_mode = XT_HINT_NUMERICAL;
7481 run_script(t, buf);
7484 /* empty the counter buffer */
7485 bzero(t->hint_buf, sizeof t->hint_buf);
7486 return (XT_CB_HANDLED);
7489 /* alphanumerical input */
7490 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7491 e->keyval <= GDK_z) ||
7492 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7493 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7494 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7495 e->keyval <= GDK_9) ||
7496 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7497 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7498 snprintf(s, sizeof s, "%c", e->keyval);
7499 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7500 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7501 " %s\n", t->hint_buf);
7502 anum:
7503 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7504 run_script(t, buf);
7506 snprintf(buf, sizeof buf,
7507 "vimprobable_show_hints('%s')", t->hint_buf);
7508 t->hint_mode = XT_HINT_ALPHANUM;
7509 run_script(t, buf);
7511 /* empty the counter buffer */
7512 bzero(t->hint_num, sizeof t->hint_num);
7513 return (XT_CB_HANDLED);
7516 return (XT_CB_HANDLED);
7517 } else {
7518 /* prefix input*/
7519 snprintf(s, sizeof s, "%c", e->keyval);
7520 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7521 cmd_prefix = 10 * cmd_prefix + atoi(s);
7524 return (handle_keypress(t, e, 0));
7528 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7530 hide_oops(t);
7532 /* Hide buffers, if they are visible, with escape. */
7533 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7534 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7535 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7536 hide_buffers(t);
7537 return (XT_CB_HANDLED);
7540 return (XT_CB_PASSTHROUGH);
7543 gboolean
7544 search_continue(struct tab *t)
7546 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7547 gboolean rv = FALSE;
7549 if (c[0] == ':')
7550 goto done;
7551 if (strlen(c) == 1) {
7552 webkit_web_view_unmark_text_matches(t->wv);
7553 goto done;
7556 if (c[0] == '/')
7557 t->search_forward = TRUE;
7558 else if (c[0] == '?')
7559 t->search_forward = FALSE;
7560 else
7561 goto done;
7563 rv = TRUE;
7564 done:
7565 return (rv);
7568 gboolean
7569 search_cb(struct tab *t)
7571 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7572 GdkColor color;
7574 if (search_continue(t) == FALSE)
7575 goto done;
7577 /* search */
7578 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7579 TRUE) == FALSE) {
7580 /* not found, mark red */
7581 gdk_color_parse(XT_COLOR_RED, &color);
7582 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7583 /* unmark and remove selection */
7584 webkit_web_view_unmark_text_matches(t->wv);
7585 /* my kingdom for a way to unselect text in webview */
7586 } else {
7587 /* found, highlight all */
7588 webkit_web_view_unmark_text_matches(t->wv);
7589 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7590 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7591 gdk_color_parse(XT_COLOR_WHITE, &color);
7592 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7594 done:
7595 t->search_id = 0;
7596 return (FALSE);
7600 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7602 const gchar *c = gtk_entry_get_text(w);
7604 if (t == NULL) {
7605 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7606 return (XT_CB_PASSTHROUGH);
7609 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7610 e->keyval, e->state, t);
7612 if (search_continue(t) == FALSE)
7613 goto done;
7615 /* if search length is > 4 then no longer play timeout games */
7616 if (strlen(c) > 4) {
7617 if (t->search_id) {
7618 g_source_remove(t->search_id);
7619 t->search_id = 0;
7621 search_cb(t);
7622 goto done;
7625 /* reestablish a new timer if the user types fast */
7626 if (t->search_id)
7627 g_source_remove(t->search_id);
7628 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7630 done:
7631 return (XT_CB_PASSTHROUGH);
7634 gboolean
7635 match_uri(const gchar *uri, const gchar *key) {
7636 gchar *voffset;
7637 size_t len;
7638 gboolean match = FALSE;
7640 len = strlen(key);
7642 if (!strncmp(key, uri, len))
7643 match = TRUE;
7644 else {
7645 voffset = strstr(uri, "/") + 2;
7646 if (!strncmp(key, voffset, len))
7647 match = TRUE;
7648 else if (g_str_has_prefix(voffset, "www.")) {
7649 voffset = voffset + strlen("www.");
7650 if (!strncmp(key, voffset, len))
7651 match = TRUE;
7655 return (match);
7658 void
7659 cmd_getlist(int id, char *key)
7661 int i, dep, c = 0;
7662 struct history *h;
7664 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7665 RB_FOREACH_REVERSE(h, history_list, &hl)
7666 if (match_uri(h->uri, key)) {
7667 cmd_status.list[c] = (char *)h->uri;
7668 if (++c > 255)
7669 break;
7672 cmd_status.len = c;
7673 return;
7676 dep = (id == -1) ? 0 : cmds[id].level + 1;
7678 for (i = id + 1; i < LENGTH(cmds); i++) {
7679 if (cmds[i].level < dep)
7680 break;
7681 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7682 strlen(key)))
7683 cmd_status.list[c++] = cmds[i].cmd;
7687 cmd_status.len = c;
7690 char *
7691 cmd_getnext(int dir)
7693 cmd_status.index += dir;
7695 if (cmd_status.index < 0)
7696 cmd_status.index = cmd_status.len - 1;
7697 else if (cmd_status.index >= cmd_status.len)
7698 cmd_status.index = 0;
7700 return cmd_status.list[cmd_status.index];
7704 cmd_tokenize(char *s, char *tokens[])
7706 int i = 0;
7707 char *tok, *last;
7708 size_t len = strlen(s);
7709 bool blank;
7711 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7712 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7713 tok = strtok_r(NULL, " ", &last), i++)
7714 tokens[i] = tok;
7716 if (blank && i < 3)
7717 tokens[i++] = "";
7719 return (i);
7722 void
7723 cmd_complete(struct tab *t, char *str, int dir)
7725 GtkEntry *w = GTK_ENTRY(t->cmd);
7726 int i, j, levels, c = 0, dep = 0, parent = -1;
7727 int matchcount = 0;
7728 char *tok, *match, *s = g_strdup(str);
7729 char *tokens[3];
7730 char res[XT_MAX_URL_LENGTH + 32] = ":";
7731 char *sc = s;
7733 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7735 /* copy prefix*/
7736 for (i = 0; isdigit(s[i]); i++)
7737 res[i + 1] = s[i];
7739 for (; isspace(s[i]); i++)
7740 res[i + 1] = s[i];
7742 s += i;
7744 levels = cmd_tokenize(s, tokens);
7746 for (i = 0; i < levels - 1; i++) {
7747 tok = tokens[i];
7748 matchcount = 0;
7749 for (j = c; j < LENGTH(cmds); j++) {
7750 if (cmds[j].level < dep)
7751 break;
7752 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7753 strlen(tok))) {
7754 matchcount++;
7755 c = j + 1;
7756 if (strlen(tok) == strlen(cmds[j].cmd)) {
7757 matchcount = 1;
7758 break;
7763 if (matchcount == 1) {
7764 strlcat(res, tok, sizeof res);
7765 strlcat(res, " ", sizeof res);
7766 dep++;
7767 } else {
7768 g_free(sc);
7769 return;
7772 parent = c - 1;
7775 if (cmd_status.index == -1)
7776 cmd_getlist(parent, tokens[i]);
7778 if (cmd_status.len > 0) {
7779 match = cmd_getnext(dir);
7780 strlcat(res, match, sizeof res);
7781 gtk_entry_set_text(w, res);
7782 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7785 g_free(sc);
7788 gboolean
7789 cmd_execute(struct tab *t, char *str)
7791 struct cmd *cmd = NULL;
7792 char *tok, *last, *s = g_strdup(str), *sc;
7793 char prefixstr[4];
7794 int j, len, c = 0, dep = 0, matchcount = 0;
7795 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7796 struct karg arg = {0, NULL, -1};
7798 sc = s;
7800 /* copy prefix*/
7801 for (j = 0; j<3 && isdigit(s[j]); j++)
7802 prefixstr[j]=s[j];
7804 prefixstr[j]='\0';
7806 s += j;
7807 while (isspace(s[0]))
7808 s++;
7810 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7811 prefix = atoi(prefixstr);
7812 else
7813 s = sc;
7815 for (tok = strtok_r(s, " ", &last); tok;
7816 tok = strtok_r(NULL, " ", &last)) {
7817 matchcount = 0;
7818 for (j = c; j < LENGTH(cmds); j++) {
7819 if (cmds[j].level < dep)
7820 break;
7821 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7822 strlen(tok);
7823 if (cmds[j].level == dep &&
7824 !strncmp(tok, cmds[j].cmd, len)) {
7825 matchcount++;
7826 c = j + 1;
7827 cmd = &cmds[j];
7828 if (len == strlen(cmds[j].cmd)) {
7829 matchcount = 1;
7830 break;
7834 if (matchcount == 1) {
7835 if (cmd->type > 0)
7836 goto execute_cmd;
7837 dep++;
7838 } else {
7839 show_oops(t, "Invalid command: %s", str);
7840 goto done;
7843 execute_cmd:
7844 arg.i = cmd->arg;
7846 if (prefix != -1)
7847 arg.precount = prefix;
7848 else if (cmd_prefix > 0)
7849 arg.precount = cmd_prefix;
7851 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7852 show_oops(t, "No prefix allowed: %s", str);
7853 goto done;
7855 if (cmd->type > 1)
7856 arg.s = last ? g_strdup(last) : g_strdup("");
7857 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7858 arg.precount = atoi(arg.s);
7859 if (arg.precount <= 0) {
7860 if (arg.s[0] == '0')
7861 show_oops(t, "Zero count");
7862 else
7863 show_oops(t, "Trailing characters");
7864 goto done;
7868 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7869 __func__, arg.precount, arg.s);
7871 cmd->func(t, &arg);
7873 rv = XT_CB_HANDLED;
7874 done:
7875 if (j > 0)
7876 cmd_prefix = 0;
7877 g_free(sc);
7878 if (arg.s)
7879 g_free(arg.s);
7881 return (rv);
7885 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7887 if (t == NULL) {
7888 show_oops(NULL, "entry_key_cb invalid parameters");
7889 return (XT_CB_PASSTHROUGH);
7892 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7893 e->keyval, e->state, t);
7895 hide_oops(t);
7897 if (e->keyval == GDK_Escape) {
7898 /* don't use focus_webview(t) because we want to type :cmds */
7899 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7902 return (handle_keypress(t, e, 1));
7906 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7908 int rv = XT_CB_HANDLED;
7909 const gchar *c = gtk_entry_get_text(w);
7911 if (t == NULL) {
7912 show_oops(NULL, "cmd_keypress_cb parameters");
7913 return (XT_CB_PASSTHROUGH);
7916 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7917 e->keyval, e->state, t);
7919 /* sanity */
7920 if (c == NULL)
7921 e->keyval = GDK_Escape;
7922 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7923 e->keyval = GDK_Escape;
7925 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7926 e->keyval != GDK_ISO_Left_Tab)
7927 cmd_status.index = -1;
7929 switch (e->keyval) {
7930 case GDK_Tab:
7931 if (c[0] == ':')
7932 cmd_complete(t, (char *)&c[1], 1);
7933 goto done;
7934 case GDK_ISO_Left_Tab:
7935 if (c[0] == ':')
7936 cmd_complete(t, (char *)&c[1], -1);
7938 goto done;
7939 case GDK_BackSpace:
7940 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7941 break;
7942 /* FALLTHROUGH */
7943 case GDK_Escape:
7944 hide_cmd(t);
7945 focus_webview(t);
7947 /* cancel search */
7948 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7949 webkit_web_view_unmark_text_matches(t->wv);
7950 goto done;
7953 rv = XT_CB_PASSTHROUGH;
7954 done:
7955 return (rv);
7958 void
7959 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
7961 /* popup menu enabled */
7962 t->popup = 1;
7966 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7968 if (t == NULL) {
7969 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7970 return (XT_CB_PASSTHROUGH);
7973 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
7974 t->tab_id, t->popup);
7976 /* if popup is enabled don't lose focus */
7977 if (t->popup) {
7978 t->popup = 0;
7979 return (XT_CB_PASSTHROUGH);
7982 hide_cmd(t);
7983 hide_oops(t);
7985 if (show_url == 0 || t->focus_wv)
7986 focus_webview(t);
7987 else
7988 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7990 return (XT_CB_PASSTHROUGH);
7993 void
7994 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7996 char *s;
7997 const gchar *c = gtk_entry_get_text(entry);
7999 if (t == NULL) {
8000 show_oops(NULL, "cmd_activate_cb invalid parameters");
8001 return;
8004 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8006 hide_cmd(t);
8008 /* sanity */
8009 if (c == NULL)
8010 goto done;
8011 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8012 goto done;
8013 if (strlen(c) < 2)
8014 goto done;
8015 s = (char *)&c[1];
8017 if (c[0] == '/' || c[0] == '?') {
8018 /* see if there is a timer pending */
8019 if (t->search_id) {
8020 g_source_remove(t->search_id);
8021 t->search_id = 0;
8022 search_cb(t);
8025 if (t->search_text) {
8026 g_free(t->search_text);
8027 t->search_text = NULL;
8030 t->search_text = g_strdup(s);
8031 if (global_search)
8032 g_free(global_search);
8033 global_search = g_strdup(s);
8034 t->search_forward = c[0] == '/';
8036 goto done;
8039 cmd_execute(t, s);
8041 done:
8042 return;
8045 void
8046 backward_cb(GtkWidget *w, struct tab *t)
8048 struct karg a;
8050 if (t == NULL) {
8051 show_oops(NULL, "backward_cb invalid parameters");
8052 return;
8055 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8057 a.i = XT_NAV_BACK;
8058 navaction(t, &a);
8061 void
8062 forward_cb(GtkWidget *w, struct tab *t)
8064 struct karg a;
8066 if (t == NULL) {
8067 show_oops(NULL, "forward_cb invalid parameters");
8068 return;
8071 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8073 a.i = XT_NAV_FORWARD;
8074 navaction(t, &a);
8077 void
8078 home_cb(GtkWidget *w, struct tab *t)
8080 if (t == NULL) {
8081 show_oops(NULL, "home_cb invalid parameters");
8082 return;
8085 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8087 load_uri(t, home);
8090 void
8091 stop_cb(GtkWidget *w, struct tab *t)
8093 WebKitWebFrame *frame;
8095 if (t == NULL) {
8096 show_oops(NULL, "stop_cb invalid parameters");
8097 return;
8100 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8102 frame = webkit_web_view_get_main_frame(t->wv);
8103 if (frame == NULL) {
8104 show_oops(t, "stop_cb: no frame");
8105 return;
8108 webkit_web_frame_stop_loading(frame);
8109 abort_favicon_download(t);
8112 void
8113 setup_webkit(struct tab *t)
8115 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8116 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8117 FALSE, (char *)NULL);
8118 else
8119 warnx("webkit does not have \"enable-dns-prefetching\" property");
8120 g_object_set(G_OBJECT(t->settings),
8121 "user-agent", t->user_agent, (char *)NULL);
8122 g_object_set(G_OBJECT(t->settings),
8123 "enable-scripts", enable_scripts, (char *)NULL);
8124 g_object_set(G_OBJECT(t->settings),
8125 "enable-plugins", enable_plugins, (char *)NULL);
8126 g_object_set(G_OBJECT(t->settings),
8127 "javascript-can-open-windows-automatically", enable_scripts,
8128 (char *)NULL);
8129 g_object_set(G_OBJECT(t->settings),
8130 "enable-html5-database", FALSE, (char *)NULL);
8131 g_object_set(G_OBJECT(t->settings),
8132 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8133 g_object_set(G_OBJECT(t->settings),
8134 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8135 g_object_set(G_OBJECT(t->settings),
8136 "spell_checking_languages", spell_check_languages, (char *)NULL);
8137 g_object_set(G_OBJECT(t->wv),
8138 "full-content-zoom", TRUE, (char *)NULL);
8140 webkit_web_view_set_settings(t->wv, t->settings);
8143 gboolean
8144 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8146 struct tab *ti, *t = NULL;
8147 gdouble view_size, value, max;
8148 gchar *position;
8150 TAILQ_FOREACH(ti, &tabs, entry)
8151 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8152 t = ti;
8153 break;
8156 if (t == NULL)
8157 return FALSE;
8159 if (adjustment == NULL)
8160 adjustment = gtk_scrolled_window_get_vadjustment(
8161 GTK_SCROLLED_WINDOW(t->browser_win));
8163 view_size = gtk_adjustment_get_page_size(adjustment);
8164 value = gtk_adjustment_get_value(adjustment);
8165 max = gtk_adjustment_get_upper(adjustment) - view_size;
8167 if (max == 0)
8168 position = g_strdup("All");
8169 else if (value == max)
8170 position = g_strdup("Bot");
8171 else if (value == 0)
8172 position = g_strdup("Top");
8173 else
8174 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8176 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8177 g_free(position);
8179 return (TRUE);
8182 GtkWidget *
8183 create_browser(struct tab *t)
8185 GtkWidget *w;
8186 gchar *strval;
8187 GtkAdjustment *adjustment;
8189 if (t == NULL) {
8190 show_oops(NULL, "create_browser invalid parameters");
8191 return (NULL);
8194 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8195 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8196 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8197 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8199 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8200 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8201 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8203 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8204 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8206 /* set defaults */
8207 t->settings = webkit_web_settings_new();
8209 if (user_agent == NULL) {
8210 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8211 (char *)NULL);
8212 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8213 g_free(strval);
8214 } else
8215 t->user_agent = g_strdup(user_agent);
8217 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8219 adjustment =
8220 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8221 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8222 G_CALLBACK(update_statusbar_position), NULL);
8224 setup_webkit(t);
8226 return (w);
8229 GtkWidget *
8230 create_window(void)
8232 GtkWidget *w;
8234 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8235 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8236 gtk_widget_set_name(w, "xxxterm");
8237 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8238 g_signal_connect(G_OBJECT(w), "delete_event",
8239 G_CALLBACK (gtk_main_quit), NULL);
8241 return (w);
8244 GtkWidget *
8245 create_kiosk_toolbar(struct tab *t)
8247 GtkWidget *toolbar = NULL, *b;
8249 b = gtk_hbox_new(FALSE, 0);
8250 toolbar = b;
8251 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8253 /* backward button */
8254 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8255 gtk_widget_set_sensitive(t->backward, FALSE);
8256 g_signal_connect(G_OBJECT(t->backward), "clicked",
8257 G_CALLBACK(backward_cb), t);
8258 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8260 /* forward button */
8261 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8262 gtk_widget_set_sensitive(t->forward, FALSE);
8263 g_signal_connect(G_OBJECT(t->forward), "clicked",
8264 G_CALLBACK(forward_cb), t);
8265 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8267 /* home button */
8268 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8269 gtk_widget_set_sensitive(t->gohome, true);
8270 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8271 G_CALLBACK(home_cb), t);
8272 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8274 /* create widgets but don't use them */
8275 t->uri_entry = gtk_entry_new();
8276 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8277 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8278 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8280 return (toolbar);
8283 GtkWidget *
8284 create_toolbar(struct tab *t)
8286 GtkWidget *toolbar = NULL, *b, *eb1;
8288 b = gtk_hbox_new(FALSE, 0);
8289 toolbar = b;
8290 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8292 /* backward button */
8293 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8294 gtk_widget_set_sensitive(t->backward, FALSE);
8295 g_signal_connect(G_OBJECT(t->backward), "clicked",
8296 G_CALLBACK(backward_cb), t);
8297 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8299 /* forward button */
8300 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8301 gtk_widget_set_sensitive(t->forward, FALSE);
8302 g_signal_connect(G_OBJECT(t->forward), "clicked",
8303 G_CALLBACK(forward_cb), t);
8304 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8305 FALSE, 0);
8307 /* stop button */
8308 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8309 gtk_widget_set_sensitive(t->stop, FALSE);
8310 g_signal_connect(G_OBJECT(t->stop), "clicked",
8311 G_CALLBACK(stop_cb), t);
8312 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8313 FALSE, 0);
8315 /* JS button */
8316 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8317 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8318 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8319 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8320 G_CALLBACK(js_toggle_cb), t);
8321 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8323 t->uri_entry = gtk_entry_new();
8324 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8325 G_CALLBACK(activate_uri_entry_cb), t);
8326 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8327 G_CALLBACK(entry_key_cb), t);
8328 completion_add(t);
8329 eb1 = gtk_hbox_new(FALSE, 0);
8330 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8331 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8332 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8334 /* search entry */
8335 if (search_string) {
8336 GtkWidget *eb2;
8337 t->search_entry = gtk_entry_new();
8338 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8339 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8340 G_CALLBACK(activate_search_entry_cb), t);
8341 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8342 G_CALLBACK(entry_key_cb), t);
8343 gtk_widget_set_size_request(t->search_entry, -1, -1);
8344 eb2 = gtk_hbox_new(FALSE, 0);
8345 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8346 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8348 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8351 return (toolbar);
8354 GtkWidget *
8355 create_buffers(struct tab *t)
8357 GtkCellRenderer *renderer;
8358 GtkWidget *view;
8360 view = gtk_tree_view_new();
8362 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8364 renderer = gtk_cell_renderer_text_new();
8365 gtk_tree_view_insert_column_with_attributes
8366 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8368 renderer = gtk_cell_renderer_text_new();
8369 gtk_tree_view_insert_column_with_attributes
8370 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8371 NULL);
8373 gtk_tree_view_set_model
8374 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8376 return view;
8379 void
8380 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8381 GtkTreeViewColumn *col, struct tab *t)
8383 GtkTreeIter iter;
8384 guint id;
8386 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8388 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8389 path)) {
8390 gtk_tree_model_get
8391 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8392 set_current_tab(id - 1);
8395 hide_buffers(t);
8398 /* after tab reordering/creation/removal */
8399 void
8400 recalc_tabs(void)
8402 struct tab *t;
8403 int maxid = 0;
8405 TAILQ_FOREACH(t, &tabs, entry) {
8406 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8407 if (t->tab_id > maxid)
8408 maxid = t->tab_id;
8410 gtk_widget_show(t->tab_elems.sep);
8413 TAILQ_FOREACH(t, &tabs, entry) {
8414 if (t->tab_id == maxid) {
8415 gtk_widget_hide(t->tab_elems.sep);
8416 break;
8421 /* after active tab change */
8422 void
8423 recolor_compact_tabs(void)
8425 struct tab *t;
8426 int curid = 0;
8427 GdkColor color;
8429 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8430 TAILQ_FOREACH(t, &tabs, entry)
8431 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8432 &color);
8434 curid = gtk_notebook_get_current_page(notebook);
8435 TAILQ_FOREACH(t, &tabs, entry)
8436 if (t->tab_id == curid) {
8437 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8438 gtk_widget_modify_fg(t->tab_elems.label,
8439 GTK_STATE_NORMAL, &color);
8440 break;
8444 void
8445 set_current_tab(int page_num)
8447 buffercmd_abort(get_current_tab());
8448 gtk_notebook_set_current_page(notebook, page_num);
8449 recolor_compact_tabs();
8453 undo_close_tab_save(struct tab *t)
8455 int m, n;
8456 const gchar *uri;
8457 struct undo *u1, *u2;
8458 GList *items;
8459 WebKitWebHistoryItem *item;
8461 if ((uri = get_uri(t)) == NULL)
8462 return (1);
8464 u1 = g_malloc0(sizeof(struct undo));
8465 u1->uri = g_strdup(uri);
8467 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8469 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8470 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8471 u1->back = n;
8473 /* forward history */
8474 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8476 while (items) {
8477 item = items->data;
8478 u1->history = g_list_prepend(u1->history,
8479 webkit_web_history_item_copy(item));
8480 items = g_list_next(items);
8483 /* current item */
8484 if (m) {
8485 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8486 u1->history = g_list_prepend(u1->history,
8487 webkit_web_history_item_copy(item));
8490 /* back history */
8491 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8493 while (items) {
8494 item = items->data;
8495 u1->history = g_list_prepend(u1->history,
8496 webkit_web_history_item_copy(item));
8497 items = g_list_next(items);
8500 TAILQ_INSERT_HEAD(&undos, u1, entry);
8502 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8503 u2 = TAILQ_LAST(&undos, undo_tailq);
8504 TAILQ_REMOVE(&undos, u2, entry);
8505 g_free(u2->uri);
8506 g_list_free(u2->history);
8507 g_free(u2);
8508 } else
8509 undo_count++;
8511 return (0);
8514 void
8515 delete_tab(struct tab *t)
8517 struct karg a;
8519 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8521 if (t == NULL)
8522 return;
8524 buffercmd_abort(t);
8525 TAILQ_REMOVE(&tabs, t, entry);
8527 /* Halt all webkit activity. */
8528 abort_favicon_download(t);
8529 webkit_web_view_stop_loading(t->wv);
8531 /* Save the tab, so we can undo the close. */
8532 undo_close_tab_save(t);
8534 if (browser_mode == XT_BM_KIOSK) {
8535 gtk_widget_destroy(t->uri_entry);
8536 gtk_widget_destroy(t->stop);
8537 gtk_widget_destroy(t->js_toggle);
8540 gtk_widget_destroy(t->tab_elems.eventbox);
8541 gtk_widget_destroy(t->vbox);
8543 /* just in case */
8544 if (t->search_id)
8545 g_source_remove(t->search_id);
8547 g_free(t->user_agent);
8548 g_free(t->stylesheet);
8549 g_free(t->tmp_uri);
8550 g_free(t);
8552 if (TAILQ_EMPTY(&tabs)) {
8553 if (browser_mode == XT_BM_KIOSK)
8554 create_new_tab(home, NULL, 1, -1);
8555 else
8556 create_new_tab(NULL, NULL, 1, -1);
8559 /* recreate session */
8560 if (session_autosave) {
8561 a.s = NULL;
8562 save_tabs(t, &a);
8565 recalc_tabs();
8566 recolor_compact_tabs();
8569 void
8570 update_statusbar_zoom(struct tab *t)
8572 gfloat zoom;
8573 char s[16] = { '\0' };
8575 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8576 if ((zoom <= 0.99 || zoom >= 1.01))
8577 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8578 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8581 void
8582 setzoom_webkit(struct tab *t, int adjust)
8584 #define XT_ZOOMPERCENT 0.04
8586 gfloat zoom;
8588 if (t == NULL) {
8589 show_oops(NULL, "setzoom_webkit invalid parameters");
8590 return;
8593 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8594 if (adjust == XT_ZOOM_IN)
8595 zoom += XT_ZOOMPERCENT;
8596 else if (adjust == XT_ZOOM_OUT)
8597 zoom -= XT_ZOOMPERCENT;
8598 else if (adjust > 0)
8599 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8600 else {
8601 show_oops(t, "setzoom_webkit invalid zoom value");
8602 return;
8605 if (zoom < XT_ZOOMPERCENT)
8606 zoom = XT_ZOOMPERCENT;
8607 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8608 update_statusbar_zoom(t);
8611 gboolean
8612 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8614 struct tab *t = (struct tab *) data;
8616 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8618 switch (event->button) {
8619 case 1:
8620 set_current_tab(t->tab_id);
8621 break;
8622 case 2:
8623 delete_tab(t);
8624 break;
8627 return TRUE;
8630 void
8631 append_tab(struct tab *t)
8633 if (t == NULL)
8634 return;
8636 TAILQ_INSERT_TAIL(&tabs, t, entry);
8637 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8640 GtkWidget *
8641 create_sbe(int width)
8643 GtkWidget *sbe;
8645 sbe = gtk_entry_new();
8646 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8647 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8648 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8649 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8650 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8651 gtk_widget_set_size_request(sbe, width, -1);
8653 return sbe;
8656 struct tab *
8657 create_new_tab(char *title, struct undo *u, int focus, int position)
8659 struct tab *t;
8660 int load = 1, id;
8661 GtkWidget *b, *bb;
8662 WebKitWebHistoryItem *item;
8663 GList *items;
8664 GdkColor color;
8665 char *p;
8666 int sbe_p = 0, sbe_b = 0,
8667 sbe_z = 0;
8669 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8671 if (tabless && !TAILQ_EMPTY(&tabs)) {
8672 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8673 return (NULL);
8676 t = g_malloc0(sizeof *t);
8678 if (title == NULL) {
8679 title = "(untitled)";
8680 load = 0;
8683 t->vbox = gtk_vbox_new(FALSE, 0);
8685 /* label + button for tab */
8686 b = gtk_hbox_new(FALSE, 0);
8687 t->tab_content = b;
8689 #if GTK_CHECK_VERSION(2, 20, 0)
8690 t->spinner = gtk_spinner_new();
8691 #endif
8692 t->label = gtk_label_new(title);
8693 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8694 gtk_widget_set_size_request(t->label, 100, 0);
8695 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8696 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8697 gtk_widget_set_size_request(b, 130, 0);
8699 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8700 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8701 #if GTK_CHECK_VERSION(2, 20, 0)
8702 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8703 #endif
8705 /* toolbar */
8706 if (browser_mode == XT_BM_KIOSK) {
8707 t->toolbar = create_kiosk_toolbar(t);
8708 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8710 } else {
8711 t->toolbar = create_toolbar(t);
8712 if (fancy_bar)
8713 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8714 FALSE, 0);
8717 /* marks */
8718 marks_clear(t);
8720 /* browser */
8721 t->browser_win = create_browser(t);
8722 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8724 /* oops message for user feedback */
8725 t->oops = gtk_entry_new();
8726 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8727 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8728 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8729 gdk_color_parse(XT_COLOR_RED, &color);
8730 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8731 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8732 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8734 /* command entry */
8735 t->cmd = gtk_entry_new();
8736 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8737 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8738 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8739 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8741 /* status bar */
8742 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8744 t->sbe.statusbar = gtk_entry_new();
8745 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8746 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8747 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8748 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8750 /* create these widgets only if specified in statusbar_elems */
8752 t->sbe.position = create_sbe(40);
8753 t->sbe.zoom = create_sbe(40);
8754 t->sbe.buffercmd = create_sbe(60);
8756 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8758 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8759 TRUE, FALSE);
8761 /* gtk widgets cannot be added to a box twice. sbe_* variables
8762 make sure of this */
8763 for (p = statusbar_elems; *p != '\0'; p++) {
8764 switch (*p) {
8765 case '|':
8767 GtkWidget *sep = gtk_vseparator_new();
8769 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8770 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8771 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8772 FALSE, FALSE, FALSE);
8773 break;
8775 case 'P':
8776 if (sbe_p) {
8777 warnx("flag \"%c\" specified more than "
8778 "once in statusbar_elems\n", *p);
8779 break;
8781 sbe_p = 1;
8782 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8783 t->sbe.position, FALSE, FALSE, FALSE);
8784 break;
8785 case 'B':
8786 if (sbe_b) {
8787 warnx("flag \"%c\" specified more than "
8788 "once in statusbar_elems\n", *p);
8789 break;
8791 sbe_b = 1;
8792 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8793 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8794 break;
8795 case 'Z':
8796 if (sbe_z) {
8797 warnx("flag \"%c\" specified more than "
8798 "once in statusbar_elems\n", *p);
8799 break;
8801 sbe_z = 1;
8802 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8803 t->sbe.zoom, FALSE, FALSE, FALSE);
8804 break;
8805 default:
8806 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8807 break;
8811 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8813 /* buffer list */
8814 t->buffers = create_buffers(t);
8815 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8817 /* xtp meaning is normal by default */
8818 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8820 /* set empty favicon */
8821 xt_icon_from_name(t, "text-html");
8823 /* and show it all */
8824 gtk_widget_show_all(b);
8825 gtk_widget_show_all(t->vbox);
8827 /* compact tab bar */
8828 t->tab_elems.label = gtk_label_new(title);
8829 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8830 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8831 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8832 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8834 t->tab_elems.eventbox = gtk_event_box_new();
8835 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8836 t->tab_elems.sep = gtk_vseparator_new();
8838 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8839 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8840 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8841 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8842 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8843 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8845 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8846 TRUE, 0);
8847 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8848 FALSE, 0);
8849 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8850 t->tab_elems.box);
8852 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8853 TRUE, 0);
8854 gtk_widget_show_all(t->tab_elems.eventbox);
8856 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8857 append_tab(t);
8858 else {
8859 id = position >= 0 ? position :
8860 gtk_notebook_get_current_page(notebook) + 1;
8861 if (id > gtk_notebook_get_n_pages(notebook))
8862 append_tab(t);
8863 else {
8864 TAILQ_INSERT_TAIL(&tabs, t, entry);
8865 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8866 gtk_box_reorder_child(GTK_BOX(tab_bar),
8867 t->tab_elems.eventbox, id);
8868 recalc_tabs();
8872 #if GTK_CHECK_VERSION(2, 20, 0)
8873 /* turn spinner off if we are a new tab without uri */
8874 if (!load) {
8875 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8876 gtk_widget_hide(t->spinner);
8878 #endif
8879 /* make notebook tabs reorderable */
8880 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8882 /* compact tabs clickable */
8883 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8884 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8886 g_object_connect(G_OBJECT(t->cmd),
8887 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8888 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8889 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8890 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8891 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
8892 (char *)NULL);
8894 /* reuse wv_button_cb to hide oops */
8895 g_object_connect(G_OBJECT(t->oops),
8896 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8897 (char *)NULL);
8899 g_signal_connect(t->buffers,
8900 "row-activated", G_CALLBACK(row_activated_cb), t);
8901 g_object_connect(G_OBJECT(t->buffers),
8902 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8904 g_object_connect(G_OBJECT(t->wv),
8905 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8906 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8907 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8908 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8909 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8910 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8911 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8912 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8913 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8914 "signal::event", G_CALLBACK(webview_event_cb), t,
8915 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8916 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8917 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8918 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8919 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8920 (char *)NULL);
8921 g_signal_connect(t->wv,
8922 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8924 * XXX this puts invalid url in uri_entry and that is undesirable
8926 #if 0
8927 g_signal_connect(t->wv,
8928 "load-error", G_CALLBACK(notify_load_error_cb), t);
8929 #endif
8930 g_signal_connect(t->wv,
8931 "notify::title", G_CALLBACK(notify_title_cb), t);
8933 /* hijack the unused keys as if we were the browser */
8934 g_object_connect(G_OBJECT(t->toolbar),
8935 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8936 (char *)NULL);
8938 g_signal_connect(G_OBJECT(bb), "button_press_event",
8939 G_CALLBACK(tab_close_cb), t);
8941 /* hide stuff */
8942 hide_cmd(t);
8943 hide_oops(t);
8944 hide_buffers(t);
8945 url_set_visibility();
8946 statusbar_set_visibility();
8948 if (focus) {
8949 set_current_tab(t->tab_id);
8950 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8951 t->tab_id);
8954 if (load) {
8955 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8956 load_uri(t, title);
8957 } else {
8958 if (show_url == 1)
8959 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8960 else
8961 focus_webview(t);
8964 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8965 /* restore the tab's history */
8966 if (u && u->history) {
8967 items = u->history;
8968 while (items) {
8969 item = items->data;
8970 webkit_web_back_forward_list_add_item(t->bfl, item);
8971 items = g_list_next(items);
8974 item = g_list_nth_data(u->history, u->back);
8975 if (item)
8976 webkit_web_view_go_to_back_forward_item(t->wv, item);
8978 g_list_free(items);
8979 g_list_free(u->history);
8980 } else
8981 webkit_web_back_forward_list_clear(t->bfl);
8983 recolor_compact_tabs();
8984 setzoom_webkit(t, XT_ZOOM_NORMAL);
8985 return (t);
8988 void
8989 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8990 gpointer *udata)
8992 struct tab *t;
8993 const gchar *uri;
8995 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8997 if (gtk_notebook_get_current_page(notebook) == -1)
8998 recalc_tabs();
9000 TAILQ_FOREACH(t, &tabs, entry) {
9001 if (t->tab_id == pn) {
9002 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9003 "%d\n", pn);
9005 uri = get_title(t, TRUE);
9006 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9008 hide_cmd(t);
9009 hide_oops(t);
9011 if (t->focus_wv) {
9012 /* can't use focus_webview here */
9013 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9019 void
9020 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9021 gpointer *udata)
9023 struct tab *t = NULL, *tt;
9025 recalc_tabs();
9027 TAILQ_FOREACH(tt, &tabs, entry)
9028 if (tt->tab_id == pn) {
9029 t = tt;
9030 break;
9033 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9035 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9036 t->tab_id);
9039 void
9040 menuitem_response(struct tab *t)
9042 gtk_notebook_set_current_page(notebook, t->tab_id);
9045 gboolean
9046 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9048 GtkWidget *menu, *menu_items;
9049 GdkEventButton *bevent;
9050 const gchar *uri;
9051 struct tab *ti;
9053 if (event->type == GDK_BUTTON_PRESS) {
9054 bevent = (GdkEventButton *) event;
9055 menu = gtk_menu_new();
9057 TAILQ_FOREACH(ti, &tabs, entry) {
9058 if ((uri = get_uri(ti)) == NULL)
9059 /* XXX make sure there is something to print */
9060 /* XXX add gui pages in here to look purdy */
9061 uri = "(untitled)";
9062 menu_items = gtk_menu_item_new_with_label(uri);
9063 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9064 gtk_widget_show(menu_items);
9066 g_signal_connect_swapped((menu_items),
9067 "activate", G_CALLBACK(menuitem_response),
9068 (gpointer)ti);
9071 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9072 bevent->button, bevent->time);
9074 /* unref object so it'll free itself when popped down */
9075 #if !GTK_CHECK_VERSION(3, 0, 0)
9076 /* XXX does not need unref with gtk+3? */
9077 g_object_ref_sink(menu);
9078 g_object_unref(menu);
9079 #endif
9081 return (TRUE /* eat event */);
9084 return (FALSE /* propagate */);
9088 icon_size_map(int icon_size)
9090 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9091 icon_size > GTK_ICON_SIZE_DIALOG)
9092 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9094 return (icon_size);
9097 GtkWidget *
9098 create_button(char *name, char *stockid, int size)
9100 GtkWidget *button, *image;
9101 gchar *rcstring;
9102 int gtk_icon_size;
9104 rcstring = g_strdup_printf(
9105 "style \"%s-style\"\n"
9106 "{\n"
9107 " GtkWidget::focus-padding = 0\n"
9108 " GtkWidget::focus-line-width = 0\n"
9109 " xthickness = 0\n"
9110 " ythickness = 0\n"
9111 "}\n"
9112 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9113 gtk_rc_parse_string(rcstring);
9114 g_free(rcstring);
9115 button = gtk_button_new();
9116 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9117 gtk_icon_size = icon_size_map(size ? size : icon_size);
9119 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9120 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9121 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9122 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9123 gtk_widget_set_name(button, name);
9124 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9126 return (button);
9129 void
9130 button_set_stockid(GtkWidget *button, char *stockid)
9132 GtkWidget *image;
9134 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9135 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9136 gtk_button_set_image(GTK_BUTTON(button), image);
9139 void
9140 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9142 gchar *p = NULL;
9143 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9144 gint len;
9146 if (xterm_workaround == 0)
9147 return;
9150 * xterm doesn't play nice with clipboards because it clears the
9151 * primary when clicked. We rely on primary being set to properly
9152 * handle middle mouse button clicks (paste). So when someone clears
9153 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9154 * other application behavior (as in DON'T clear primary).
9157 p = gtk_clipboard_wait_for_text(primary);
9158 if (p == NULL) {
9159 if (gdk_property_get(gdk_get_default_root_window(),
9160 atom,
9161 gdk_atom_intern("STRING", FALSE),
9163 1024 * 1024 /* picked out of my butt */,
9164 FALSE,
9165 NULL,
9166 NULL,
9167 &len,
9168 (guchar **)&p)) {
9169 /* yes sir, we need to NUL the string */
9170 p[len] = '\0';
9171 gtk_clipboard_set_text(primary, p, -1);
9175 if (p)
9176 g_free(p);
9179 void
9180 create_canvas(void)
9182 GtkWidget *vbox;
9183 GList *l = NULL;
9184 GdkPixbuf *pb;
9185 char file[PATH_MAX];
9186 int i;
9188 vbox = gtk_vbox_new(FALSE, 0);
9189 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9190 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9191 #if !GTK_CHECK_VERSION(3, 0, 0)
9192 /* XXX seems to be needed with gtk+2 */
9193 gtk_notebook_set_tab_hborder(notebook, 0);
9194 gtk_notebook_set_tab_vborder(notebook, 0);
9195 #endif
9196 gtk_notebook_set_scrollable(notebook, TRUE);
9197 gtk_notebook_set_show_border(notebook, FALSE);
9198 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9200 abtn = gtk_button_new();
9201 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9202 gtk_widget_set_size_request(arrow, -1, -1);
9203 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9204 gtk_widget_set_size_request(abtn, -1, 20);
9206 #if GTK_CHECK_VERSION(2, 20, 0)
9207 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9208 #endif
9209 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9211 /* compact tab bar */
9212 tab_bar = gtk_hbox_new(TRUE, 0);
9214 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9215 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9216 gtk_widget_set_size_request(vbox, -1, -1);
9218 g_object_connect(G_OBJECT(notebook),
9219 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9220 (char *)NULL);
9221 g_object_connect(G_OBJECT(notebook),
9222 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9223 NULL, (char *)NULL);
9224 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9225 G_CALLBACK(arrow_cb), NULL);
9227 main_window = create_window();
9228 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9230 /* icons */
9231 for (i = 0; i < LENGTH(icons); i++) {
9232 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9233 pb = gdk_pixbuf_new_from_file(file, NULL);
9234 l = g_list_append(l, pb);
9236 gtk_window_set_default_icon_list(l);
9238 /* clipboard work around */
9239 if (xterm_workaround)
9240 g_signal_connect(
9241 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9242 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9244 gtk_widget_show_all(abtn);
9245 gtk_widget_show_all(main_window);
9246 notebook_tab_set_visibility();
9249 void
9250 set_hook(void **hook, char *name)
9252 if (hook == NULL)
9253 errx(1, "set_hook");
9255 if (*hook == NULL) {
9256 *hook = dlsym(RTLD_NEXT, name);
9257 if (*hook == NULL)
9258 errx(1, "can't hook %s", name);
9262 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9263 gboolean
9264 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9266 g_return_val_if_fail(cookie1, FALSE);
9267 g_return_val_if_fail(cookie2, FALSE);
9269 return (!strcmp (cookie1->name, cookie2->name) &&
9270 !strcmp (cookie1->value, cookie2->value) &&
9271 !strcmp (cookie1->path, cookie2->path) &&
9272 !strcmp (cookie1->domain, cookie2->domain));
9275 void
9276 transfer_cookies(void)
9278 GSList *cf;
9279 SoupCookie *sc, *pc;
9281 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9283 for (;cf; cf = cf->next) {
9284 pc = cf->data;
9285 sc = soup_cookie_copy(pc);
9286 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9289 soup_cookies_free(cf);
9292 void
9293 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9295 GSList *cf;
9296 SoupCookie *ci;
9298 print_cookie("soup_cookie_jar_delete_cookie", c);
9300 if (cookies_enabled == 0)
9301 return;
9303 if (jar == NULL || c == NULL)
9304 return;
9306 /* find and remove from persistent jar */
9307 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9309 for (;cf; cf = cf->next) {
9310 ci = cf->data;
9311 if (soup_cookie_equal(ci, c)) {
9312 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9313 break;
9317 soup_cookies_free(cf);
9319 /* delete from session jar */
9320 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9323 void
9324 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9326 struct domain *d = NULL;
9327 SoupCookie *c;
9328 FILE *r_cookie_f;
9330 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9331 jar, p_cookiejar, s_cookiejar);
9333 if (cookies_enabled == 0)
9334 return;
9336 /* see if we are up and running */
9337 if (p_cookiejar == NULL) {
9338 _soup_cookie_jar_add_cookie(jar, cookie);
9339 return;
9341 /* disallow p_cookiejar adds, shouldn't happen */
9342 if (jar == p_cookiejar)
9343 return;
9345 /* sanity */
9346 if (jar == NULL || cookie == NULL)
9347 return;
9349 if (enable_cookie_whitelist &&
9350 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9351 blocked_cookies++;
9352 DNPRINTF(XT_D_COOKIE,
9353 "soup_cookie_jar_add_cookie: reject %s\n",
9354 cookie->domain);
9355 if (save_rejected_cookies) {
9356 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9357 show_oops(NULL, "can't open reject cookie file");
9358 return;
9360 fseek(r_cookie_f, 0, SEEK_END);
9361 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9362 cookie->http_only ? "#HttpOnly_" : "",
9363 cookie->domain,
9364 *cookie->domain == '.' ? "TRUE" : "FALSE",
9365 cookie->path,
9366 cookie->secure ? "TRUE" : "FALSE",
9367 cookie->expires ?
9368 (gulong)soup_date_to_time_t(cookie->expires) :
9370 cookie->name,
9371 cookie->value);
9372 fflush(r_cookie_f);
9373 fclose(r_cookie_f);
9375 if (!allow_volatile_cookies)
9376 return;
9379 if (cookie->expires == NULL && session_timeout) {
9380 soup_cookie_set_expires(cookie,
9381 soup_date_new_from_now(session_timeout));
9382 print_cookie("modified add cookie", cookie);
9385 /* see if we are white listed for persistence */
9386 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9387 /* add to persistent jar */
9388 c = soup_cookie_copy(cookie);
9389 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9390 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9393 /* add to session jar */
9394 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9395 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9398 void
9399 setup_cookies(void)
9401 char file[PATH_MAX];
9403 set_hook((void *)&_soup_cookie_jar_add_cookie,
9404 "soup_cookie_jar_add_cookie");
9405 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9406 "soup_cookie_jar_delete_cookie");
9408 if (cookies_enabled == 0)
9409 return;
9412 * the following code is intricate due to overriding several libsoup
9413 * functions.
9414 * do not alter order of these operations.
9417 /* rejected cookies */
9418 if (save_rejected_cookies)
9419 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9420 XT_REJECT_FILE);
9422 /* persistent cookies */
9423 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9424 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9426 /* session cookies */
9427 s_cookiejar = soup_cookie_jar_new();
9428 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9429 cookie_policy, (void *)NULL);
9430 transfer_cookies();
9432 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9435 void
9436 setup_proxy(char *uri)
9438 SoupURI *suri;
9440 if (proxy_uri) {
9441 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9442 soup_uri_free(proxy_uri);
9443 proxy_uri = NULL;
9445 if (http_proxy) {
9446 if (http_proxy != uri) {
9447 g_free(http_proxy);
9448 http_proxy = NULL;
9452 if (uri) {
9453 http_proxy = g_strdup(uri);
9454 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9455 suri = soup_uri_new(http_proxy);
9456 if (!(suri == NULL || !SOUP_URI_VALID_FOR_HTTP(suri)))
9457 g_object_set(session, "proxy-uri", proxy_uri,
9458 (char *)NULL);
9459 if (suri)
9460 soup_uri_free(suri);
9465 set_http_proxy(char *proxy)
9467 SoupURI *uri;
9469 if (proxy == NULL)
9470 return (1);
9472 /* see if we need to clear it instead */
9473 if (strlen(proxy) == 0) {
9474 setup_proxy(NULL);
9475 return (0);
9478 uri = soup_uri_new(proxy);
9479 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9480 return (1);
9482 setup_proxy(proxy);
9484 soup_uri_free(uri);
9486 return (0);
9490 send_cmd_to_socket(char *cmd)
9492 int s, len, rv = 1;
9493 struct sockaddr_un sa;
9495 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9496 warnx("%s: socket", __func__);
9497 return (rv);
9500 sa.sun_family = AF_UNIX;
9501 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9502 work_dir, XT_SOCKET_FILE);
9503 len = SUN_LEN(&sa);
9505 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9506 warnx("%s: connect", __func__);
9507 goto done;
9510 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9511 warnx("%s: send", __func__);
9512 goto done;
9515 rv = 0;
9516 done:
9517 close(s);
9518 return (rv);
9521 gboolean
9522 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9524 int s, n;
9525 char str[XT_MAX_URL_LENGTH];
9526 socklen_t t = sizeof(struct sockaddr_un);
9527 struct sockaddr_un sa;
9528 struct passwd *p;
9529 uid_t uid;
9530 gid_t gid;
9531 struct tab *tt;
9532 gint fd = g_io_channel_unix_get_fd(source);
9534 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9535 warn("accept");
9536 return (FALSE);
9539 if (getpeereid(s, &uid, &gid) == -1) {
9540 warn("getpeereid");
9541 return (FALSE);
9543 if (uid != getuid() || gid != getgid()) {
9544 warnx("unauthorized user");
9545 return (FALSE);
9548 p = getpwuid(uid);
9549 if (p == NULL) {
9550 warnx("not a valid user");
9551 return (FALSE);
9554 n = recv(s, str, sizeof(str), 0);
9555 if (n <= 0)
9556 return (TRUE);
9558 tt = TAILQ_LAST(&tabs, tab_list);
9559 cmd_execute(tt, str);
9560 return (TRUE);
9564 is_running(void)
9566 int s, len, rv = 1;
9567 struct sockaddr_un sa;
9569 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9570 warn("is_running: socket");
9571 return (-1);
9574 sa.sun_family = AF_UNIX;
9575 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9576 work_dir, XT_SOCKET_FILE);
9577 len = SUN_LEN(&sa);
9579 /* connect to see if there is a listener */
9580 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9581 rv = 0; /* not running */
9582 else
9583 rv = 1; /* already running */
9585 close(s);
9587 return (rv);
9591 build_socket(void)
9593 int s, len;
9594 struct sockaddr_un sa;
9596 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9597 warn("build_socket: socket");
9598 return (-1);
9601 sa.sun_family = AF_UNIX;
9602 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9603 work_dir, XT_SOCKET_FILE);
9604 len = SUN_LEN(&sa);
9606 /* connect to see if there is a listener */
9607 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9608 /* no listener so we will */
9609 unlink(sa.sun_path);
9611 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9612 warn("build_socket: bind");
9613 goto done;
9616 if (listen(s, 1) == -1) {
9617 warn("build_socket: listen");
9618 goto done;
9621 return (s);
9624 done:
9625 close(s);
9626 return (-1);
9629 gboolean
9630 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9631 GtkTreeIter *iter, struct tab *t)
9633 gchar *value;
9635 gtk_tree_model_get(model, iter, 0, &value, -1);
9636 load_uri(t, value);
9637 g_free(value);
9639 return (FALSE);
9642 gboolean
9643 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9644 GtkTreeIter *iter, struct tab *t)
9646 gchar *value;
9648 gtk_tree_model_get(model, iter, 0, &value, -1);
9649 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9650 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9651 g_free(value);
9653 return (TRUE);
9656 void
9657 completion_add_uri(const gchar *uri)
9659 GtkTreeIter iter;
9661 /* add uri to list_store */
9662 gtk_list_store_append(completion_model, &iter);
9663 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9666 gboolean
9667 completion_match(GtkEntryCompletion *completion, const gchar *key,
9668 GtkTreeIter *iter, gpointer user_data)
9670 gchar *value;
9671 gboolean match = FALSE;
9673 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9674 -1);
9676 if (value == NULL)
9677 return FALSE;
9679 match = match_uri(value, key);
9681 g_free(value);
9682 return (match);
9685 void
9686 completion_add(struct tab *t)
9688 /* enable completion for tab */
9689 t->completion = gtk_entry_completion_new();
9690 gtk_entry_completion_set_text_column(t->completion, 0);
9691 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9692 gtk_entry_completion_set_model(t->completion,
9693 GTK_TREE_MODEL(completion_model));
9694 gtk_entry_completion_set_match_func(t->completion, completion_match,
9695 NULL, NULL);
9696 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9697 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9698 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9699 G_CALLBACK(completion_select_cb), t);
9700 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9701 G_CALLBACK(completion_hover_cb), t);
9704 void
9705 xxx_dir(char *dir)
9707 struct stat sb;
9709 if (stat(dir, &sb)) {
9710 if (mkdir(dir, S_IRWXU) == -1)
9711 err(1, "mkdir %s", dir);
9712 if (stat(dir, &sb))
9713 err(1, "stat %s", dir);
9715 if (S_ISDIR(sb.st_mode) == 0)
9716 errx(1, "%s not a dir", dir);
9717 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9718 warnx("fixing invalid permissions on %s", dir);
9719 if (chmod(dir, S_IRWXU) == -1)
9720 err(1, "chmod %s", dir);
9724 void
9725 usage(void)
9727 fprintf(stderr,
9728 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9729 exit(0);
9734 main(int argc, char *argv[])
9736 struct stat sb;
9737 int c, s, optn = 0, opte = 0, focus = 1;
9738 char conf[PATH_MAX] = { '\0' };
9739 char file[PATH_MAX];
9740 char *env_proxy = NULL;
9741 char *cmd = NULL;
9742 FILE *f = NULL;
9743 struct karg a;
9744 struct sigaction sact;
9745 GIOChannel *channel;
9746 struct rlimit rlp;
9748 start_argv = argv;
9750 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9752 RB_INIT(&hl);
9753 RB_INIT(&js_wl);
9754 RB_INIT(&downloads);
9756 TAILQ_INIT(&tabs);
9757 TAILQ_INIT(&mtl);
9758 TAILQ_INIT(&aliases);
9759 TAILQ_INIT(&undos);
9760 TAILQ_INIT(&kbl);
9761 TAILQ_INIT(&spl);
9763 /* fiddle with ulimits */
9764 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9765 warn("getrlimit");
9766 else {
9767 /* just use them all */
9768 rlp.rlim_cur = rlp.rlim_max;
9769 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9770 warn("setrlimit");
9771 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9772 warn("getrlimit");
9773 else if (rlp.rlim_cur <= 256)
9774 startpage_add("%s requires at least 256 file "
9775 "descriptors, currently it has up to %d available",
9776 __progname, rlp.rlim_cur);
9779 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9780 switch (c) {
9781 case 'S':
9782 show_url = 0;
9783 break;
9784 case 'T':
9785 show_tabs = 0;
9786 break;
9787 case 'V':
9788 errx(0 , "Version: %s", version);
9789 break;
9790 case 'f':
9791 strlcpy(conf, optarg, sizeof(conf));
9792 break;
9793 case 's':
9794 strlcpy(named_session, optarg, sizeof(named_session));
9795 break;
9796 case 't':
9797 tabless = 1;
9798 break;
9799 case 'n':
9800 optn = 1;
9801 break;
9802 case 'e':
9803 opte = 1;
9804 break;
9805 default:
9806 usage();
9807 /* NOTREACHED */
9810 argc -= optind;
9811 argv += optind;
9813 init_keybindings();
9815 gnutls_global_init();
9817 /* generate session keys for xtp pages */
9818 generate_xtp_session_key(&dl_session_key);
9819 generate_xtp_session_key(&hl_session_key);
9820 generate_xtp_session_key(&cl_session_key);
9821 generate_xtp_session_key(&fl_session_key);
9823 /* prepare gtk */
9824 if (!g_thread_supported()) {
9825 g_thread_init(NULL);
9826 gdk_threads_init();
9827 gdk_threads_enter();
9829 gtk_init(&argc, &argv);
9831 /* signals */
9832 bzero(&sact, sizeof(sact));
9833 sigemptyset(&sact.sa_mask);
9834 sact.sa_handler = sigchild;
9835 sact.sa_flags = SA_NOCLDSTOP;
9836 sigaction(SIGCHLD, &sact, NULL);
9838 /* set download dir */
9839 pwd = getpwuid(getuid());
9840 if (pwd == NULL)
9841 errx(1, "invalid user %d", getuid());
9842 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9844 /* compile buffer command regexes */
9845 buffercmd_init();
9847 /* set default string settings */
9848 home = g_strdup("https://www.cyphertite.com");
9849 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9850 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9851 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9852 cmd_font_name = g_strdup("monospace normal 9");
9853 oops_font_name = g_strdup("monospace normal 9");
9854 statusbar_font_name = g_strdup("monospace normal 9");
9855 tabbar_font_name = g_strdup("monospace normal 9");
9856 statusbar_elems = g_strdup("BP");
9858 /* read config file */
9859 if (strlen(conf) == 0)
9860 snprintf(conf, sizeof conf, "%s/.%s",
9861 pwd->pw_dir, XT_CONF_FILE);
9862 config_parse(conf, 0);
9864 /* init fonts */
9865 cmd_font = pango_font_description_from_string(cmd_font_name);
9866 oops_font = pango_font_description_from_string(oops_font_name);
9867 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9868 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9870 /* working directory */
9871 if (strlen(work_dir) == 0)
9872 snprintf(work_dir, sizeof work_dir, "%s/%s",
9873 pwd->pw_dir, XT_DIR);
9874 xxx_dir(work_dir);
9876 /* icon cache dir */
9877 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9878 xxx_dir(cache_dir);
9880 /* certs dir */
9881 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9882 xxx_dir(certs_dir);
9884 /* sessions dir */
9885 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9886 work_dir, XT_SESSIONS_DIR);
9887 xxx_dir(sessions_dir);
9889 /* runtime settings that can override config file */
9890 if (runtime_settings[0] != '\0')
9891 config_parse(runtime_settings, 1);
9893 /* download dir */
9894 if (!strcmp(download_dir, pwd->pw_dir))
9895 strlcat(download_dir, "/downloads", sizeof download_dir);
9896 xxx_dir(download_dir);
9898 /* favorites file */
9899 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9900 if (stat(file, &sb)) {
9901 warnx("favorites file doesn't exist, creating it");
9902 if ((f = fopen(file, "w")) == NULL)
9903 err(1, "favorites");
9904 fclose(f);
9907 /* quickmarks file */
9908 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9909 if (stat(file, &sb)) {
9910 warnx("quickmarks file doesn't exist, creating it");
9911 if ((f = fopen(file, "w")) == NULL)
9912 err(1, "quickmarks");
9913 fclose(f);
9916 /* cookies */
9917 session = webkit_get_default_session();
9918 setup_cookies();
9920 /* certs */
9921 if (ssl_ca_file) {
9922 if (stat(ssl_ca_file, &sb)) {
9923 warnx("no CA file: %s", ssl_ca_file);
9924 g_free(ssl_ca_file);
9925 ssl_ca_file = NULL;
9926 } else
9927 g_object_set(session,
9928 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9929 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9930 (void *)NULL);
9933 /* guess_search regex */
9934 if (url_regex == NULL)
9935 url_regex = g_strdup(XT_URL_REGEX);
9936 if (url_regex)
9937 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
9938 startpage_add("invalid url regex %s", url_regex);
9940 /* proxy */
9941 env_proxy = getenv("http_proxy");
9942 if (env_proxy)
9943 setup_proxy(env_proxy);
9944 else
9945 setup_proxy(http_proxy);
9947 if (opte) {
9948 send_cmd_to_socket(argv[0]);
9949 exit(0);
9952 /* set some connection parameters */
9953 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9954 g_object_set(session, "max-conns-per-host", max_host_connections,
9955 (char *)NULL);
9957 /* see if there is already an xxxterm running */
9958 if (single_instance && is_running()) {
9959 optn = 1;
9960 warnx("already running");
9963 if (optn) {
9964 while (argc) {
9965 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9966 send_cmd_to_socket(cmd);
9967 if (cmd)
9968 g_free(cmd);
9970 argc--;
9971 argv++;
9973 exit(0);
9976 /* uri completion */
9977 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9979 /* buffers */
9980 buffers_store = gtk_list_store_new
9981 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9983 qmarks_load();
9985 /* go graphical */
9986 create_canvas();
9987 notebook_tab_set_visibility();
9989 if (save_global_history)
9990 restore_global_history();
9992 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9993 restore_saved_tabs();
9994 else {
9995 a.s = named_session;
9996 a.i = XT_SES_DONOTHING;
9997 open_tabs(NULL, &a);
10000 /* see if we have an exception */
10001 if (!TAILQ_EMPTY(&spl)) {
10002 create_new_tab("about:startpage", NULL, focus, -1);
10003 focus = 0;
10006 while (argc) {
10007 create_new_tab(argv[0], NULL, focus, -1);
10008 focus = 0;
10010 argc--;
10011 argv++;
10014 if (TAILQ_EMPTY(&tabs))
10015 create_new_tab(home, NULL, 1, -1);
10017 if (enable_socket)
10018 if ((s = build_socket()) != -1) {
10019 channel = g_io_channel_unix_new(s);
10020 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10023 gtk_main();
10025 if (!g_thread_supported()) {
10026 gdk_threads_leave();
10029 gnutls_global_deinit();
10031 if (url_regex)
10032 regfree(&url_re);
10034 return (0);