add a regex to determine if something is a url (to be used in
[xxxterm.git] / xxxterm.c
blob444f455b7e02e89da6df53a4e004b43077f64c59
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pthread.h>
35 #include <pwd.h>
36 #include <regex.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
49 #include <libutil.h>
50 #include "freebsd/util.h"
51 #include <sys/tree.h>
52 #else /* OpenBSD */
53 #include <util.h>
54 #include <sys/tree.h>
55 #endif
56 #include <sys/queue.h>
57 #include <sys/resource.h>
58 #include <sys/socket.h>
59 #include <sys/stat.h>
60 #include <sys/time.h>
61 #include <sys/un.h>
63 #include <gtk/gtk.h>
64 #include <gdk/gdkkeysyms.h>
66 #if GTK_CHECK_VERSION(3,0,0)
67 /* we still use GDK_* instead of GDK_KEY_* */
68 #include <gdk/gdkkeysyms-compat.h>
69 #endif
71 #include <webkit/webkit.h>
72 #include <libsoup/soup.h>
73 #include <gnutls/gnutls.h>
74 #include <JavaScriptCore/JavaScript.h>
75 #include <gnutls/x509.h>
77 #include "javascript.h"
80 javascript.h borrowed from vimprobable2 under the following license:
82 Copyright (c) 2009 Leon Winter
83 Copyright (c) 2009 Hannes Schueller
84 Copyright (c) 2009 Matto Fransen
86 Permission is hereby granted, free of charge, to any person obtaining a copy
87 of this software and associated documentation files (the "Software"), to deal
88 in the Software without restriction, including without limitation the rights
89 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90 copies of the Software, and to permit persons to whom the Software is
91 furnished to do so, subject to the following conditions:
93 The above copyright notice and this permission notice shall be included in
94 all copies or substantial portions of the Software.
96 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
102 THE SOFTWARE.
105 static char *version = "$xxxterm$";
107 /* hooked functions */
108 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
109 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
110 SoupCookie *);
112 /*#define XT_DEBUG*/
113 #ifdef XT_DEBUG
114 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
115 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
116 #define XT_D_MOVE 0x0001
117 #define XT_D_KEY 0x0002
118 #define XT_D_TAB 0x0004
119 #define XT_D_URL 0x0008
120 #define XT_D_CMD 0x0010
121 #define XT_D_NAV 0x0020
122 #define XT_D_DOWNLOAD 0x0040
123 #define XT_D_CONFIG 0x0080
124 #define XT_D_JS 0x0100
125 #define XT_D_FAVORITE 0x0200
126 #define XT_D_PRINTING 0x0400
127 #define XT_D_COOKIE 0x0800
128 #define XT_D_KEYBINDING 0x1000
129 #define XT_D_CLIP 0x2000
130 #define XT_D_BUFFERCMD 0x4000
131 u_int32_t swm_debug = 0
132 | XT_D_MOVE
133 | XT_D_KEY
134 | XT_D_TAB
135 | XT_D_URL
136 | XT_D_CMD
137 | XT_D_NAV
138 | XT_D_DOWNLOAD
139 | XT_D_CONFIG
140 | XT_D_JS
141 | XT_D_FAVORITE
142 | XT_D_PRINTING
143 | XT_D_COOKIE
144 | XT_D_KEYBINDING
145 | XT_D_CLIP
146 | XT_D_BUFFERCMD
148 #else
149 #define DPRINTF(x...)
150 #define DNPRINTF(n,x...)
151 #endif
153 #define LENGTH(x) (sizeof x / sizeof x[0])
154 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
155 ~(GDK_BUTTON1_MASK) & \
156 ~(GDK_BUTTON2_MASK) & \
157 ~(GDK_BUTTON3_MASK) & \
158 ~(GDK_BUTTON4_MASK) & \
159 ~(GDK_BUTTON5_MASK))
161 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
163 char *icons[] = {
164 "xxxtermicon16.png",
165 "xxxtermicon32.png",
166 "xxxtermicon48.png",
167 "xxxtermicon64.png",
168 "xxxtermicon128.png"
171 struct tab {
172 TAILQ_ENTRY(tab) entry;
173 GtkWidget *vbox;
174 GtkWidget *tab_content;
175 struct {
176 GtkWidget *label;
177 GtkWidget *eventbox;
178 GtkWidget *box;
179 GtkWidget *sep;
180 } tab_elems;
181 GtkWidget *label;
182 GtkWidget *spinner;
183 GtkWidget *uri_entry;
184 GtkWidget *search_entry;
185 GtkWidget *toolbar;
186 GtkWidget *browser_win;
187 GtkWidget *statusbar_box;
188 struct {
189 GtkWidget *statusbar;
190 GtkWidget *buffercmd;
191 GtkWidget *zoom;
192 GtkWidget *position;
193 } sbe;
194 GtkWidget *cmd;
195 GtkWidget *buffers;
196 GtkWidget *oops;
197 GtkWidget *backward;
198 GtkWidget *forward;
199 GtkWidget *stop;
200 GtkWidget *gohome;
201 GtkWidget *js_toggle;
202 GtkEntryCompletion *completion;
203 guint tab_id;
204 WebKitWebView *wv;
206 WebKitWebHistoryItem *item;
207 WebKitWebBackForwardList *bfl;
209 /* favicon */
210 WebKitNetworkRequest *icon_request;
211 WebKitDownload *icon_download;
212 gchar *icon_dest_uri;
214 /* adjustments for browser */
215 GtkScrollbar *sb_h;
216 GtkScrollbar *sb_v;
217 GtkAdjustment *adjust_h;
218 GtkAdjustment *adjust_v;
220 /* flags */
221 int focus_wv;
222 int ctrl_click;
223 gchar *status;
224 int xtp_meaning; /* identifies dls/favorites */
225 gchar *tmp_uri;
227 /* hints */
228 int hints_on;
229 int hint_mode;
230 #define XT_HINT_NONE (0)
231 #define XT_HINT_NUMERICAL (1)
232 #define XT_HINT_ALPHANUM (2)
233 char hint_buf[128];
234 char hint_num[128];
236 /* custom stylesheet */
237 int styled;
238 char *stylesheet;
240 /* search */
241 char *search_text;
242 int search_forward;
243 guint search_id;
245 /* settings */
246 WebKitWebSettings *settings;
247 gchar *user_agent;
249 /* marks */
250 double mark[XT_NOMARKS];
252 TAILQ_HEAD(tab_list, tab);
254 struct history {
255 RB_ENTRY(history) entry;
256 const gchar *uri;
257 const gchar *title;
259 RB_HEAD(history_list, history);
261 struct download {
262 RB_ENTRY(download) entry;
263 int id;
264 WebKitDownload *download;
265 struct tab *tab;
267 RB_HEAD(download_list, download);
269 struct domain {
270 RB_ENTRY(domain) entry;
271 gchar *d;
272 int handy; /* app use */
274 RB_HEAD(domain_list, domain);
276 struct undo {
277 TAILQ_ENTRY(undo) entry;
278 gchar *uri;
279 GList *history;
280 int back; /* Keeps track of how many back
281 * history items there are. */
283 TAILQ_HEAD(undo_tailq, undo);
285 struct sp {
286 char *line;
287 TAILQ_ENTRY(sp) entry;
289 TAILQ_HEAD(sp_list, sp);
291 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
292 int next_download_id = 1;
294 struct karg {
295 int i;
296 char *s;
297 int precount;
300 /* defines */
301 #define XT_NAME ("XXXTerm")
302 #define XT_DIR (".xxxterm")
303 #define XT_CACHE_DIR ("cache")
304 #define XT_CERT_DIR ("certs/")
305 #define XT_SESSIONS_DIR ("sessions/")
306 #define XT_CONF_FILE ("xxxterm.conf")
307 #define XT_FAVS_FILE ("favorites")
308 #define XT_QMARKS_FILE ("quickmarks")
309 #define XT_SAVED_TABS_FILE ("main_session")
310 #define XT_RESTART_TABS_FILE ("restart_tabs")
311 #define XT_SOCKET_FILE ("socket")
312 #define XT_HISTORY_FILE ("history")
313 #define XT_REJECT_FILE ("rejected.txt")
314 #define XT_COOKIE_FILE ("cookies.txt")
315 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
316 #define XT_CB_HANDLED (TRUE)
317 #define XT_CB_PASSTHROUGH (FALSE)
318 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
319 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
320 #define XT_DLMAN_REFRESH "10"
321 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
322 "td{overflow: hidden;" \
323 " padding: 2px 2px 2px 2px;" \
324 " border: 1px solid black;" \
325 " vertical-align:top;" \
326 " word-wrap: break-word}\n" \
327 "tr:hover{background: #ffff99}\n" \
328 "th{background-color: #cccccc;" \
329 " border: 1px solid black}\n" \
330 "table{width: 100%%;" \
331 " border: 1px black solid;" \
332 " border-collapse:collapse}\n" \
333 ".progress-outer{" \
334 "border: 1px solid black;" \
335 " height: 8px;" \
336 " width: 90%%}\n" \
337 ".progress-inner{float: left;" \
338 " height: 8px;" \
339 " background: green}\n" \
340 ".dlstatus{font-size: small;" \
341 " text-align: center}\n" \
342 "</style>\n"
343 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
344 #define XT_MAX_UNDO_CLOSE_TAB (32)
345 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
346 #define XT_PRINT_EXTRA_MARGIN 10
347 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
348 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
350 /* colors */
351 #define XT_COLOR_RED "#cc0000"
352 #define XT_COLOR_YELLOW "#ffff66"
353 #define XT_COLOR_BLUE "lightblue"
354 #define XT_COLOR_GREEN "#99ff66"
355 #define XT_COLOR_WHITE "white"
356 #define XT_COLOR_BLACK "black"
358 #define XT_COLOR_CT_BACKGROUND "#000000"
359 #define XT_COLOR_CT_INACTIVE "#dddddd"
360 #define XT_COLOR_CT_ACTIVE "#bbbb00"
361 #define XT_COLOR_CT_SEPARATOR "#555555"
363 #define XT_COLOR_SB_SEPARATOR "#555555"
365 #define XT_PROTO_DELIM "://"
368 * xxxterm "protocol" (xtp)
369 * We use this for managing stuff like downloads and favorites. They
370 * make magical HTML pages in memory which have xxxt:// links in order
371 * to communicate with xxxterm's internals. These links take the format:
372 * xxxt://class/session_key/action/arg
374 * Don't begin xtp class/actions as 0. atoi returns that on error.
376 * Typically we have not put addition of items in this framework, as
377 * adding items is either done via an ex-command or via a keybinding instead.
380 #define XT_XTP_STR "xxxt://"
382 /* XTP classes (xxxt://<class>) */
383 #define XT_XTP_INVALID 0 /* invalid */
384 #define XT_XTP_DL 1 /* downloads */
385 #define XT_XTP_HL 2 /* history */
386 #define XT_XTP_CL 3 /* cookies */
387 #define XT_XTP_FL 4 /* favorites */
389 /* XTP download actions */
390 #define XT_XTP_DL_LIST 1
391 #define XT_XTP_DL_CANCEL 2
392 #define XT_XTP_DL_REMOVE 3
394 /* XTP history actions */
395 #define XT_XTP_HL_LIST 1
396 #define XT_XTP_HL_REMOVE 2
398 /* XTP cookie actions */
399 #define XT_XTP_CL_LIST 1
400 #define XT_XTP_CL_REMOVE 2
402 /* XTP cookie actions */
403 #define XT_XTP_FL_LIST 1
404 #define XT_XTP_FL_REMOVE 2
406 /* actions */
407 #define XT_MOVE_INVALID (0)
408 #define XT_MOVE_DOWN (1)
409 #define XT_MOVE_UP (2)
410 #define XT_MOVE_BOTTOM (3)
411 #define XT_MOVE_TOP (4)
412 #define XT_MOVE_PAGEDOWN (5)
413 #define XT_MOVE_PAGEUP (6)
414 #define XT_MOVE_HALFDOWN (7)
415 #define XT_MOVE_HALFUP (8)
416 #define XT_MOVE_LEFT (9)
417 #define XT_MOVE_FARLEFT (10)
418 #define XT_MOVE_RIGHT (11)
419 #define XT_MOVE_FARRIGHT (12)
420 #define XT_MOVE_PERCENT (13)
422 #define XT_QMARK_SET (0)
423 #define XT_QMARK_OPEN (1)
424 #define XT_QMARK_TAB (2)
426 #define XT_MARK_SET (0)
427 #define XT_MARK_GOTO (1)
429 #define XT_TAB_LAST (-4)
430 #define XT_TAB_FIRST (-3)
431 #define XT_TAB_PREV (-2)
432 #define XT_TAB_NEXT (-1)
433 #define XT_TAB_INVALID (0)
434 #define XT_TAB_NEW (1)
435 #define XT_TAB_DELETE (2)
436 #define XT_TAB_DELQUIT (3)
437 #define XT_TAB_OPEN (4)
438 #define XT_TAB_UNDO_CLOSE (5)
439 #define XT_TAB_SHOW (6)
440 #define XT_TAB_HIDE (7)
441 #define XT_TAB_NEXTSTYLE (8)
443 #define XT_NAV_INVALID (0)
444 #define XT_NAV_BACK (1)
445 #define XT_NAV_FORWARD (2)
446 #define XT_NAV_RELOAD (3)
448 #define XT_FOCUS_INVALID (0)
449 #define XT_FOCUS_URI (1)
450 #define XT_FOCUS_SEARCH (2)
452 #define XT_SEARCH_INVALID (0)
453 #define XT_SEARCH_NEXT (1)
454 #define XT_SEARCH_PREV (2)
456 #define XT_PASTE_CURRENT_TAB (0)
457 #define XT_PASTE_NEW_TAB (1)
459 #define XT_ZOOM_IN (-1)
460 #define XT_ZOOM_OUT (-2)
461 #define XT_ZOOM_NORMAL (100)
463 #define XT_URL_SHOW (1)
464 #define XT_URL_HIDE (2)
466 #define XT_WL_TOGGLE (1<<0)
467 #define XT_WL_ENABLE (1<<1)
468 #define XT_WL_DISABLE (1<<2)
469 #define XT_WL_FQDN (1<<3) /* default */
470 #define XT_WL_TOPLEVEL (1<<4)
471 #define XT_WL_PERSISTENT (1<<5)
472 #define XT_WL_SESSION (1<<6)
473 #define XT_WL_RELOAD (1<<7)
475 #define XT_SHOW (1<<7)
476 #define XT_DELETE (1<<8)
477 #define XT_SAVE (1<<9)
478 #define XT_OPEN (1<<10)
480 #define XT_CMD_OPEN (0)
481 #define XT_CMD_OPEN_CURRENT (1)
482 #define XT_CMD_TABNEW (2)
483 #define XT_CMD_TABNEW_CURRENT (3)
485 #define XT_STATUS_NOTHING (0)
486 #define XT_STATUS_LINK (1)
487 #define XT_STATUS_URI (2)
488 #define XT_STATUS_LOADING (3)
490 #define XT_SES_DONOTHING (0)
491 #define XT_SES_CLOSETABS (1)
493 #define XT_BM_NORMAL (0)
494 #define XT_BM_WHITELIST (1)
495 #define XT_BM_KIOSK (2)
497 #define XT_PREFIX (1<<0)
498 #define XT_USERARG (1<<1)
499 #define XT_URLARG (1<<2)
500 #define XT_INTARG (1<<3)
502 #define XT_TABS_NORMAL 0
503 #define XT_TABS_COMPACT 1
505 #define XT_BUFCMD_SZ (8)
507 /* mime types */
508 struct mime_type {
509 char *mt_type;
510 char *mt_action;
511 int mt_default;
512 int mt_download;
513 TAILQ_ENTRY(mime_type) entry;
515 TAILQ_HEAD(mime_type_list, mime_type);
517 /* uri aliases */
518 struct alias {
519 char *a_name;
520 char *a_uri;
521 TAILQ_ENTRY(alias) entry;
523 TAILQ_HEAD(alias_list, alias);
525 /* settings that require restart */
526 int tabless = 0; /* allow only 1 tab */
527 int enable_socket = 0;
528 int single_instance = 0; /* only allow one xxxterm to run */
529 int fancy_bar = 1; /* fancy toolbar */
530 int browser_mode = XT_BM_NORMAL;
531 int enable_localstorage = 0;
532 char *statusbar_elems = NULL;
534 /* runtime settings */
535 int show_tabs = 1; /* show tabs on notebook */
536 int tab_style = XT_TABS_NORMAL; /* tab bar style */
537 int show_url = 1; /* show url toolbar on notebook */
538 int show_statusbar = 0; /* vimperator style status bar */
539 int ctrl_click_focus = 0; /* ctrl click gets focus */
540 int cookies_enabled = 1; /* enable cookies */
541 int read_only_cookies = 0; /* enable to not write cookies */
542 int enable_scripts = 1;
543 int enable_plugins = 0;
544 gfloat default_zoom_level = 1.0;
545 char default_script[PATH_MAX];
546 int window_height = 768;
547 int window_width = 1024;
548 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
549 int refresh_interval = 10; /* download refresh interval */
550 int enable_cookie_whitelist = 0;
551 int enable_js_whitelist = 0;
552 int session_timeout = 3600; /* cookie session timeout */
553 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
554 char *ssl_ca_file = NULL;
555 char *resource_dir = NULL;
556 gboolean ssl_strict_certs = FALSE;
557 int append_next = 1; /* append tab after current tab */
558 char *home = NULL;
559 char *search_string = NULL;
560 char *http_proxy = NULL;
561 char download_dir[PATH_MAX];
562 char runtime_settings[PATH_MAX]; /* override of settings */
563 int allow_volatile_cookies = 0;
564 int save_global_history = 0; /* save global history to disk */
565 char *user_agent = NULL;
566 int save_rejected_cookies = 0;
567 int session_autosave = 0;
568 int guess_search = 0;
569 int dns_prefetch = FALSE;
570 gint max_connections = 25;
571 gint max_host_connections = 5;
572 gint enable_spell_checking = 0;
573 char *spell_check_languages = NULL;
574 int xterm_workaround = 0;
575 char *url_regex = NULL;
577 char *cmd_font_name = NULL;
578 char *oops_font_name = NULL;
579 char *statusbar_font_name = NULL;
580 char *tabbar_font_name = NULL;
581 PangoFontDescription *cmd_font;
582 PangoFontDescription *oops_font;
583 PangoFontDescription *statusbar_font;
584 PangoFontDescription *tabbar_font;
585 char *qmarks[XT_NOMARKS];
587 int btn_down; /* M1 down in any wv */
588 regex_t url_re; /* guess_search regex */
590 struct settings;
591 struct key_binding;
592 int set_browser_mode(struct settings *, char *);
593 int set_cookie_policy(struct settings *, char *);
594 int set_download_dir(struct settings *, char *);
595 int set_default_script(struct settings *, char *);
596 int set_runtime_dir(struct settings *, char *);
597 int set_tab_style(struct settings *, char *);
598 int set_work_dir(struct settings *, char *);
599 int add_alias(struct settings *, char *);
600 int add_mime_type(struct settings *, char *);
601 int add_cookie_wl(struct settings *, char *);
602 int add_js_wl(struct settings *, char *);
603 int add_kb(struct settings *, char *);
604 void button_set_stockid(GtkWidget *, char *);
605 GtkWidget * create_button(char *, char *, int);
607 char *get_browser_mode(struct settings *);
608 char *get_cookie_policy(struct settings *);
609 char *get_download_dir(struct settings *);
610 char *get_default_script(struct settings *);
611 char *get_runtime_dir(struct settings *);
612 char *get_tab_style(struct settings *);
613 char *get_work_dir(struct settings *);
614 void startpage_add(const char *, ...);
616 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
617 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
618 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
619 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
620 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
622 void recalc_tabs(void);
623 void recolor_compact_tabs(void);
624 void set_current_tab(int page_num);
625 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
626 void marks_clear(struct tab *t);
628 int set_http_proxy(char *);
630 struct special {
631 int (*set)(struct settings *, char *);
632 char *(*get)(struct settings *);
633 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
636 struct special s_browser_mode = {
637 set_browser_mode,
638 get_browser_mode,
639 NULL
642 struct special s_cookie = {
643 set_cookie_policy,
644 get_cookie_policy,
645 NULL
648 struct special s_alias = {
649 add_alias,
650 NULL,
651 walk_alias
654 struct special s_mime = {
655 add_mime_type,
656 NULL,
657 walk_mime_type
660 struct special s_js = {
661 add_js_wl,
662 NULL,
663 walk_js_wl
666 struct special s_kb = {
667 add_kb,
668 NULL,
669 walk_kb
672 struct special s_cookie_wl = {
673 add_cookie_wl,
674 NULL,
675 walk_cookie_wl
678 struct special s_default_script = {
679 set_default_script,
680 get_default_script,
681 NULL
684 struct special s_download_dir = {
685 set_download_dir,
686 get_download_dir,
687 NULL
690 struct special s_work_dir = {
691 set_work_dir,
692 get_work_dir,
693 NULL
696 struct special s_tab_style = {
697 set_tab_style,
698 get_tab_style,
699 NULL
702 struct settings {
703 char *name;
704 int type;
705 #define XT_S_INVALID (0)
706 #define XT_S_INT (1)
707 #define XT_S_STR (2)
708 #define XT_S_FLOAT (3)
709 uint32_t flags;
710 #define XT_SF_RESTART (1<<0)
711 #define XT_SF_RUNTIME (1<<1)
712 int *ival;
713 char **sval;
714 struct special *s;
715 gfloat *fval;
716 int (*activate)(char *);
717 } rs[] = {
718 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
719 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
720 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
721 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
722 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
723 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
724 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
725 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
726 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
727 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
728 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
729 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
730 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
731 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
732 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
733 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
734 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
735 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
736 { "home", XT_S_STR, 0, NULL, &home, NULL },
737 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
738 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
739 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
740 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
741 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
742 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
743 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
744 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
745 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
746 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
747 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
748 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
749 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
750 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
751 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
752 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
753 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
754 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
755 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
756 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
757 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
758 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
759 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
760 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
761 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
762 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
763 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
765 /* font settings */
766 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
767 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
768 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
769 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
771 /* runtime settings */
772 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
773 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
774 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
775 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
776 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
779 int about(struct tab *, struct karg *);
780 int blank(struct tab *, struct karg *);
781 int ca_cmd(struct tab *, struct karg *);
782 int cookie_show_wl(struct tab *, struct karg *);
783 int js_show_wl(struct tab *, struct karg *);
784 int help(struct tab *, struct karg *);
785 int set(struct tab *, struct karg *);
786 int stats(struct tab *, struct karg *);
787 int marco(struct tab *, struct karg *);
788 int startpage(struct tab *, struct karg *);
789 const char * marco_message(int *);
790 int xtp_page_cl(struct tab *, struct karg *);
791 int xtp_page_dl(struct tab *, struct karg *);
792 int xtp_page_fl(struct tab *, struct karg *);
793 int xtp_page_hl(struct tab *, struct karg *);
794 void xt_icon_from_file(struct tab *, char *);
795 const gchar *get_uri(struct tab *);
796 const gchar *get_title(struct tab *, bool);
798 #define XT_URI_ABOUT ("about:")
799 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
800 #define XT_URI_ABOUT_ABOUT ("about")
801 #define XT_URI_ABOUT_BLANK ("blank")
802 #define XT_URI_ABOUT_CERTS ("certs")
803 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
804 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
805 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
806 #define XT_URI_ABOUT_FAVORITES ("favorites")
807 #define XT_URI_ABOUT_HELP ("help")
808 #define XT_URI_ABOUT_HISTORY ("history")
809 #define XT_URI_ABOUT_JSWL ("jswl")
810 #define XT_URI_ABOUT_SET ("set")
811 #define XT_URI_ABOUT_STATS ("stats")
812 #define XT_URI_ABOUT_MARCO ("marco")
813 #define XT_URI_ABOUT_STARTPAGE ("startpage")
815 struct about_type {
816 char *name;
817 int (*func)(struct tab *, struct karg *);
818 } about_list[] = {
819 { XT_URI_ABOUT_ABOUT, about },
820 { XT_URI_ABOUT_BLANK, blank },
821 { XT_URI_ABOUT_CERTS, ca_cmd },
822 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
823 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
824 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
825 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
826 { XT_URI_ABOUT_HELP, help },
827 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
828 { XT_URI_ABOUT_JSWL, js_show_wl },
829 { XT_URI_ABOUT_SET, set },
830 { XT_URI_ABOUT_STATS, stats },
831 { XT_URI_ABOUT_MARCO, marco },
832 { XT_URI_ABOUT_STARTPAGE, startpage },
835 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
836 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
837 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
838 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
839 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
840 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
841 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
843 /* globals */
844 extern char *__progname;
845 char **start_argv;
846 struct passwd *pwd;
847 GtkWidget *main_window;
848 GtkNotebook *notebook;
849 GtkWidget *tab_bar;
850 GtkWidget *arrow, *abtn;
851 struct tab_list tabs;
852 struct history_list hl;
853 struct download_list downloads;
854 struct domain_list c_wl;
855 struct domain_list js_wl;
856 struct undo_tailq undos;
857 struct keybinding_list kbl;
858 struct sp_list spl;
859 int undo_count;
860 int updating_dl_tabs = 0;
861 int updating_hl_tabs = 0;
862 int updating_cl_tabs = 0;
863 int updating_fl_tabs = 0;
864 char *global_search;
865 uint64_t blocked_cookies = 0;
866 char named_session[PATH_MAX];
867 int icon_size_map(int);
869 GtkListStore *completion_model;
870 void completion_add(struct tab *);
871 void completion_add_uri(const gchar *);
872 GtkListStore *buffers_store;
873 void xxx_dir(char *);
875 /* marks and quickmarks array storage.
876 * first a-z, then A-Z, then 0-9 */
877 char
878 indextomark(int i)
880 if (i < 0)
881 return 0;
883 if (i >= 0 && i <= 'z' - 'a')
884 return 'a' + i;
886 i -= 'z' - 'a' + 1;
887 if (i >= 0 && i <= 'Z' - 'A')
888 return 'A' + i;
890 i -= 'Z' - 'A' + 1;
891 if (i >= 10)
892 return 0;
894 return i + '0';
898 marktoindex(char m)
900 int ret = 0;
902 if (m >= 'a' && m <= 'z')
903 return ret + m - 'a';
905 ret += 'z' - 'a' + 1;
906 if (m >= 'A' && m <= 'Z')
907 return ret + m - 'A';
909 ret += 'Z' - 'A' + 1;
910 if (m >= '0' && m <= '9')
911 return ret + m - '0';
913 return -1;
917 void
918 sigchild(int sig)
920 int saved_errno, status;
921 pid_t pid;
923 saved_errno = errno;
925 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
926 if (pid == -1) {
927 if (errno == EINTR)
928 continue;
929 if (errno != ECHILD) {
931 clog_warn("sigchild: waitpid:");
934 break;
937 if (WIFEXITED(status)) {
938 if (WEXITSTATUS(status) != 0) {
940 clog_warnx("sigchild: child exit status: %d",
941 WEXITSTATUS(status));
944 } else {
946 clog_warnx("sigchild: child is terminated abnormally");
951 errno = saved_errno;
955 is_g_object_setting(GObject *o, char *str)
957 guint n_props = 0, i;
958 GParamSpec **proplist;
960 if (! G_IS_OBJECT(o))
961 return (0);
963 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
964 &n_props);
966 for (i=0; i < n_props; i++) {
967 if (! strcmp(proplist[i]->name, str))
968 return (1);
970 return (0);
973 gchar *
974 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
976 gchar *r;
978 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
979 "<head>\n"
980 "<title>%s</title>\n"
981 "%s"
982 "%s"
983 "</head>\n"
984 "<body>\n"
985 "<h1>%s</h1>\n"
986 "%s\n</body>\n"
987 "</html>",
988 title,
989 addstyles ? XT_PAGE_STYLE : "",
990 head,
991 title,
992 body);
994 return r;
998 * Display a web page from a HTML string in memory, rather than from a URL
1000 void
1001 load_webkit_string(struct tab *t, const char *str, gchar *title)
1003 char file[PATH_MAX];
1004 int i;
1006 /* we set this to indicate we want to manually do navaction */
1007 if (t->bfl)
1008 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1010 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1011 if (title) {
1012 /* set t->xtp_meaning */
1013 for (i = 0; i < LENGTH(about_list); i++)
1014 if (!strcmp(title, about_list[i].name)) {
1015 t->xtp_meaning = i;
1016 break;
1019 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1020 #if GTK_CHECK_VERSION(2, 20, 0)
1021 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1022 gtk_widget_hide(t->spinner);
1023 #endif
1024 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1025 xt_icon_from_file(t, file);
1029 struct tab *
1030 get_current_tab(void)
1032 struct tab *t;
1034 TAILQ_FOREACH(t, &tabs, entry) {
1035 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1036 return (t);
1039 warnx("%s: no current tab", __func__);
1041 return (NULL);
1044 void
1045 set_status(struct tab *t, gchar *s, int status)
1047 gchar *type = NULL;
1049 if (s == NULL)
1050 return;
1052 switch (status) {
1053 case XT_STATUS_LOADING:
1054 type = g_strdup_printf("Loading: %s", s);
1055 s = type;
1056 break;
1057 case XT_STATUS_LINK:
1058 type = g_strdup_printf("Link: %s", s);
1059 if (!t->status)
1060 t->status = g_strdup(gtk_entry_get_text(
1061 GTK_ENTRY(t->sbe.statusbar)));
1062 s = type;
1063 break;
1064 case XT_STATUS_URI:
1065 type = g_strdup_printf("%s", s);
1066 if (!t->status) {
1067 t->status = g_strdup(type);
1069 s = type;
1070 if (!t->status)
1071 t->status = g_strdup(s);
1072 break;
1073 case XT_STATUS_NOTHING:
1074 /* FALL THROUGH */
1075 default:
1076 break;
1078 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1079 if (type)
1080 g_free(type);
1083 void
1084 hide_cmd(struct tab *t)
1086 gtk_widget_hide(t->cmd);
1089 void
1090 show_cmd(struct tab *t)
1092 gtk_widget_hide(t->oops);
1093 gtk_widget_show(t->cmd);
1096 void
1097 hide_buffers(struct tab *t)
1099 gtk_widget_hide(t->buffers);
1100 gtk_list_store_clear(buffers_store);
1103 enum {
1104 COL_ID = 0,
1105 COL_TITLE,
1106 NUM_COLS
1110 sort_tabs_by_page_num(struct tab ***stabs)
1112 int num_tabs = 0;
1113 struct tab *t;
1115 num_tabs = gtk_notebook_get_n_pages(notebook);
1117 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1119 TAILQ_FOREACH(t, &tabs, entry)
1120 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1122 return (num_tabs);
1125 void
1126 buffers_make_list(void)
1128 int i, num_tabs;
1129 const gchar *title = NULL;
1130 GtkTreeIter iter;
1131 struct tab **stabs = NULL;
1133 num_tabs = sort_tabs_by_page_num(&stabs);
1135 for (i = 0; i < num_tabs; i++)
1136 if (stabs[i]) {
1137 gtk_list_store_append(buffers_store, &iter);
1138 title = get_title(stabs[i], FALSE);
1139 gtk_list_store_set(buffers_store, &iter,
1140 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1141 * rather than 0. */
1142 COL_TITLE, title,
1143 -1);
1146 g_free(stabs);
1149 void
1150 show_buffers(struct tab *t)
1152 buffers_make_list();
1153 gtk_widget_show(t->buffers);
1154 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1157 void
1158 toggle_buffers(struct tab *t)
1160 if (gtk_widget_get_visible(t->buffers))
1161 hide_buffers(t);
1162 else
1163 show_buffers(t);
1167 buffers(struct tab *t, struct karg *args)
1169 show_buffers(t);
1171 return (0);
1174 void
1175 hide_oops(struct tab *t)
1177 gtk_widget_hide(t->oops);
1180 void
1181 show_oops(struct tab *at, const char *fmt, ...)
1183 va_list ap;
1184 char *msg = NULL;
1185 struct tab *t = NULL;
1187 if (fmt == NULL)
1188 return;
1190 if (at == NULL) {
1191 if ((t = get_current_tab()) == NULL)
1192 return;
1193 } else
1194 t = at;
1196 va_start(ap, fmt);
1197 if (vasprintf(&msg, fmt, ap) == -1)
1198 errx(1, "show_oops failed");
1199 va_end(ap);
1201 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1202 gtk_widget_hide(t->cmd);
1203 gtk_widget_show(t->oops);
1205 if (msg)
1206 free(msg);
1209 char *
1210 get_as_string(struct settings *s)
1212 char *r = NULL;
1214 if (s == NULL)
1215 return (NULL);
1217 if (s->s) {
1218 if (s->s->get)
1219 r = s->s->get(s);
1220 else
1221 warnx("get_as_string skip %s\n", s->name);
1222 } else if (s->type == XT_S_INT)
1223 r = g_strdup_printf("%d", *s->ival);
1224 else if (s->type == XT_S_STR)
1225 r = g_strdup(*s->sval);
1226 else if (s->type == XT_S_FLOAT)
1227 r = g_strdup_printf("%f", *s->fval);
1228 else
1229 r = g_strdup_printf("INVALID TYPE");
1231 return (r);
1234 void
1235 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1237 int i;
1238 char *s;
1240 for (i = 0; i < LENGTH(rs); i++) {
1241 if (rs[i].s && rs[i].s->walk)
1242 rs[i].s->walk(&rs[i], cb, cb_args);
1243 else {
1244 s = get_as_string(&rs[i]);
1245 cb(&rs[i], s, cb_args);
1246 g_free(s);
1252 set_browser_mode(struct settings *s, char *val)
1254 if (!strcmp(val, "whitelist")) {
1255 browser_mode = XT_BM_WHITELIST;
1256 allow_volatile_cookies = 0;
1257 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1258 cookies_enabled = 1;
1259 enable_cookie_whitelist = 1;
1260 read_only_cookies = 0;
1261 save_rejected_cookies = 0;
1262 session_timeout = 3600;
1263 enable_scripts = 0;
1264 enable_js_whitelist = 1;
1265 enable_localstorage = 0;
1266 } else if (!strcmp(val, "normal")) {
1267 browser_mode = XT_BM_NORMAL;
1268 allow_volatile_cookies = 0;
1269 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1270 cookies_enabled = 1;
1271 enable_cookie_whitelist = 0;
1272 read_only_cookies = 0;
1273 save_rejected_cookies = 0;
1274 session_timeout = 3600;
1275 enable_scripts = 1;
1276 enable_js_whitelist = 0;
1277 enable_localstorage = 1;
1278 } else if (!strcmp(val, "kiosk")) {
1279 browser_mode = XT_BM_KIOSK;
1280 allow_volatile_cookies = 0;
1281 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1282 cookies_enabled = 1;
1283 enable_cookie_whitelist = 0;
1284 read_only_cookies = 0;
1285 save_rejected_cookies = 0;
1286 session_timeout = 3600;
1287 enable_scripts = 1;
1288 enable_js_whitelist = 0;
1289 enable_localstorage = 1;
1290 show_tabs = 0;
1291 tabless = 1;
1292 } else
1293 return (1);
1295 return (0);
1298 char *
1299 get_browser_mode(struct settings *s)
1301 char *r = NULL;
1303 if (browser_mode == XT_BM_WHITELIST)
1304 r = g_strdup("whitelist");
1305 else if (browser_mode == XT_BM_NORMAL)
1306 r = g_strdup("normal");
1307 else if (browser_mode == XT_BM_KIOSK)
1308 r = g_strdup("kiosk");
1309 else
1310 return (NULL);
1312 return (r);
1316 set_cookie_policy(struct settings *s, char *val)
1318 if (!strcmp(val, "no3rdparty"))
1319 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1320 else if (!strcmp(val, "accept"))
1321 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1322 else if (!strcmp(val, "reject"))
1323 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1324 else
1325 return (1);
1327 return (0);
1330 char *
1331 get_cookie_policy(struct settings *s)
1333 char *r = NULL;
1335 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1336 r = g_strdup("no3rdparty");
1337 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1338 r = g_strdup("accept");
1339 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1340 r = g_strdup("reject");
1341 else
1342 return (NULL);
1344 return (r);
1347 char *
1348 get_default_script(struct settings *s)
1350 if (default_script[0] == '\0')
1351 return (0);
1352 return (g_strdup(default_script));
1356 set_default_script(struct settings *s, char *val)
1358 if (val[0] == '~')
1359 snprintf(default_script, sizeof default_script, "%s/%s",
1360 pwd->pw_dir, &val[1]);
1361 else
1362 strlcpy(default_script, val, sizeof default_script);
1364 return (0);
1367 char *
1368 get_download_dir(struct settings *s)
1370 if (download_dir[0] == '\0')
1371 return (0);
1372 return (g_strdup(download_dir));
1376 set_download_dir(struct settings *s, char *val)
1378 if (val[0] == '~')
1379 snprintf(download_dir, sizeof download_dir, "%s/%s",
1380 pwd->pw_dir, &val[1]);
1381 else
1382 strlcpy(download_dir, val, sizeof download_dir);
1384 return (0);
1388 * Session IDs.
1389 * We use these to prevent people putting xxxt:// URLs on
1390 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1392 #define XT_XTP_SES_KEY_SZ 8
1393 #define XT_XTP_SES_KEY_HEX_FMT \
1394 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1395 char *dl_session_key; /* downloads */
1396 char *hl_session_key; /* history list */
1397 char *cl_session_key; /* cookie list */
1398 char *fl_session_key; /* favorites list */
1400 char work_dir[PATH_MAX];
1401 char certs_dir[PATH_MAX];
1402 char cache_dir[PATH_MAX];
1403 char sessions_dir[PATH_MAX];
1404 char cookie_file[PATH_MAX];
1405 SoupURI *proxy_uri = NULL;
1406 SoupSession *session;
1407 SoupCookieJar *s_cookiejar;
1408 SoupCookieJar *p_cookiejar;
1409 char rc_fname[PATH_MAX];
1411 struct mime_type_list mtl;
1412 struct alias_list aliases;
1414 /* protos */
1415 struct tab *create_new_tab(char *, struct undo *, int, int);
1416 void delete_tab(struct tab *);
1417 void setzoom_webkit(struct tab *, int);
1418 int run_script(struct tab *, char *);
1419 int download_rb_cmp(struct download *, struct download *);
1420 gboolean cmd_execute(struct tab *t, char *str);
1423 history_rb_cmp(struct history *h1, struct history *h2)
1425 return (strcmp(h1->uri, h2->uri));
1427 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1430 domain_rb_cmp(struct domain *d1, struct domain *d2)
1432 return (strcmp(d1->d, d2->d));
1434 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1436 char *
1437 get_work_dir(struct settings *s)
1439 if (work_dir[0] == '\0')
1440 return (0);
1441 return (g_strdup(work_dir));
1445 set_work_dir(struct settings *s, char *val)
1447 if (val[0] == '~')
1448 snprintf(work_dir, sizeof work_dir, "%s/%s",
1449 pwd->pw_dir, &val[1]);
1450 else
1451 strlcpy(work_dir, val, sizeof work_dir);
1453 return (0);
1456 char *
1457 get_tab_style(struct settings *s)
1459 if (tab_style == XT_TABS_NORMAL)
1460 return (g_strdup("normal"));
1461 else
1462 return (g_strdup("compact"));
1466 set_tab_style(struct settings *s, char *val)
1468 if (!strcmp(val, "normal"))
1469 tab_style = XT_TABS_NORMAL;
1470 else if (!strcmp(val, "compact"))
1471 tab_style = XT_TABS_COMPACT;
1472 else
1473 return (1);
1475 return (0);
1479 * generate a session key to secure xtp commands.
1480 * pass in a ptr to the key in question and it will
1481 * be modified in place.
1483 void
1484 generate_xtp_session_key(char **key)
1486 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1488 /* free old key */
1489 if (*key)
1490 g_free(*key);
1492 /* make a new one */
1493 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1494 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1495 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1496 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1498 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1502 * validate a xtp session key.
1503 * return 1 if OK
1506 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1508 if (strcmp(trusted, untrusted) != 0) {
1509 show_oops(t, "%s: xtp session key mismatch possible spoof",
1510 __func__);
1511 return (0);
1514 return (1);
1518 download_rb_cmp(struct download *e1, struct download *e2)
1520 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1522 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1524 struct valid_url_types {
1525 char *type;
1526 } vut[] = {
1527 { "http://" },
1528 { "https://" },
1529 { "ftp://" },
1530 { "file://" },
1531 { XT_XTP_STR },
1535 valid_url_type(char *url)
1537 int i;
1539 for (i = 0; i < LENGTH(vut); i++)
1540 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1541 return (0);
1543 return (1);
1546 void
1547 print_cookie(char *msg, SoupCookie *c)
1549 if (c == NULL)
1550 return;
1552 if (msg)
1553 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1554 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1555 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1556 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1557 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1558 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1559 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1560 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1561 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1562 DNPRINTF(XT_D_COOKIE, "====================================\n");
1565 void
1566 walk_alias(struct settings *s,
1567 void (*cb)(struct settings *, char *, void *), void *cb_args)
1569 struct alias *a;
1570 char *str;
1572 if (s == NULL || cb == NULL) {
1573 show_oops(NULL, "walk_alias invalid parameters");
1574 return;
1577 TAILQ_FOREACH(a, &aliases, entry) {
1578 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1579 cb(s, str, cb_args);
1580 g_free(str);
1584 char *
1585 match_alias(char *url_in)
1587 struct alias *a;
1588 char *arg;
1589 char *url_out = NULL, *search, *enc_arg;
1591 search = g_strdup(url_in);
1592 arg = search;
1593 if (strsep(&arg, " \t") == NULL) {
1594 show_oops(NULL, "match_alias: NULL URL");
1595 goto done;
1598 TAILQ_FOREACH(a, &aliases, entry) {
1599 if (!strcmp(search, a->a_name))
1600 break;
1603 if (a != NULL) {
1604 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1605 a->a_name);
1606 if (arg != NULL) {
1607 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1608 url_out = g_strdup_printf(a->a_uri, enc_arg);
1609 g_free(enc_arg);
1610 } else
1611 url_out = g_strdup_printf(a->a_uri, "");
1613 done:
1614 g_free(search);
1615 return (url_out);
1618 char *
1619 guess_url_type(char *url_in)
1621 struct stat sb;
1622 char *url_out = NULL, *enc_search = NULL;
1624 url_out = match_alias(url_in);
1625 if (url_out != NULL)
1626 return (url_out);
1628 if (guess_search && url_regex &&
1629 !(g_str_has_prefix(url_in, "http://") ||
1630 g_str_has_prefix(url_in, "https://"))) {
1631 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1632 /* invalid URI so search instead */
1633 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1634 url_out = g_strdup_printf(search_string, enc_search);
1635 g_free(enc_search);
1636 goto done;
1640 /* XXX not sure about this heuristic */
1641 if (stat(url_in, &sb) == 0)
1642 url_out = g_strdup_printf("file://%s", url_in);
1643 else
1644 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1645 done:
1646 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1648 return (url_out);
1651 void
1652 load_uri(struct tab *t, gchar *uri)
1654 struct karg args;
1655 gchar *newuri = NULL;
1656 int i;
1658 if (uri == NULL)
1659 return;
1661 /* Strip leading spaces. */
1662 while (*uri && isspace(*uri))
1663 uri++;
1665 if (strlen(uri) == 0) {
1666 blank(t, NULL);
1667 return;
1670 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1672 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1673 for (i = 0; i < LENGTH(about_list); i++)
1674 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1675 bzero(&args, sizeof args);
1676 about_list[i].func(t, &args);
1677 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1678 FALSE);
1679 return;
1681 show_oops(t, "invalid about page");
1682 return;
1685 if (valid_url_type(uri)) {
1686 newuri = guess_url_type(uri);
1687 uri = newuri;
1690 set_status(t, (char *)uri, XT_STATUS_LOADING);
1691 marks_clear(t);
1692 webkit_web_view_load_uri(t->wv, uri);
1694 if (newuri)
1695 g_free(newuri);
1698 const gchar *
1699 get_uri(struct tab *t)
1701 const gchar *uri = NULL;
1703 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1704 return t->tmp_uri;
1705 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1706 uri = webkit_web_view_get_uri(t->wv);
1707 } else {
1708 /* use tmp_uri to make sure it is g_freed */
1709 if (t->tmp_uri)
1710 g_free(t->tmp_uri);
1711 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1712 about_list[t->xtp_meaning].name);
1713 uri = t->tmp_uri;
1715 return uri;
1718 const gchar *
1719 get_title(struct tab *t, bool window)
1721 const gchar *set = NULL, *title = NULL;
1722 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1724 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1725 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1726 goto notitle;
1728 title = webkit_web_view_get_title(t->wv);
1729 if ((set = title ? title : get_uri(t)))
1730 return set;
1732 notitle:
1733 set = window ? XT_NAME : "(untitled)";
1735 return set;
1739 add_alias(struct settings *s, char *line)
1741 char *l, *alias;
1742 struct alias *a = NULL;
1744 if (s == NULL || line == NULL) {
1745 show_oops(NULL, "add_alias invalid parameters");
1746 return (1);
1749 l = line;
1750 a = g_malloc(sizeof(*a));
1752 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1753 show_oops(NULL, "add_alias: incomplete alias definition");
1754 goto bad;
1756 if (strlen(alias) == 0 || strlen(l) == 0) {
1757 show_oops(NULL, "add_alias: invalid alias definition");
1758 goto bad;
1761 a->a_name = g_strdup(alias);
1762 a->a_uri = g_strdup(l);
1764 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1766 TAILQ_INSERT_TAIL(&aliases, a, entry);
1768 return (0);
1769 bad:
1770 if (a)
1771 g_free(a);
1772 return (1);
1776 add_mime_type(struct settings *s, char *line)
1778 char *mime_type;
1779 char *l;
1780 struct mime_type *m = NULL;
1781 int downloadfirst = 0;
1783 /* XXX this could be smarter */
1785 if (line == NULL || strlen(line) == 0) {
1786 show_oops(NULL, "add_mime_type invalid parameters");
1787 return (1);
1790 l = line;
1791 if (*l == '@') {
1792 downloadfirst = 1;
1793 l++;
1795 m = g_malloc(sizeof(*m));
1797 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1798 show_oops(NULL, "add_mime_type: invalid mime_type");
1799 goto bad;
1801 if (mime_type[strlen(mime_type) - 1] == '*') {
1802 mime_type[strlen(mime_type) - 1] = '\0';
1803 m->mt_default = 1;
1804 } else
1805 m->mt_default = 0;
1807 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1808 show_oops(NULL, "add_mime_type: invalid mime_type");
1809 goto bad;
1812 m->mt_type = g_strdup(mime_type);
1813 m->mt_action = g_strdup(l);
1814 m->mt_download = downloadfirst;
1816 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1817 m->mt_type, m->mt_action, m->mt_default);
1819 TAILQ_INSERT_TAIL(&mtl, m, entry);
1821 return (0);
1822 bad:
1823 if (m)
1824 g_free(m);
1825 return (1);
1828 struct mime_type *
1829 find_mime_type(char *mime_type)
1831 struct mime_type *m, *def = NULL, *rv = NULL;
1833 TAILQ_FOREACH(m, &mtl, entry) {
1834 if (m->mt_default &&
1835 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1836 def = m;
1838 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1839 rv = m;
1840 break;
1844 if (rv == NULL)
1845 rv = def;
1847 return (rv);
1850 void
1851 walk_mime_type(struct settings *s,
1852 void (*cb)(struct settings *, char *, void *), void *cb_args)
1854 struct mime_type *m;
1855 char *str;
1857 if (s == NULL || cb == NULL) {
1858 show_oops(NULL, "walk_mime_type invalid parameters");
1859 return;
1862 TAILQ_FOREACH(m, &mtl, entry) {
1863 str = g_strdup_printf("%s%s --> %s",
1864 m->mt_type,
1865 m->mt_default ? "*" : "",
1866 m->mt_action);
1867 cb(s, str, cb_args);
1868 g_free(str);
1872 void
1873 wl_add(char *str, struct domain_list *wl, int handy)
1875 struct domain *d;
1876 int add_dot = 0;
1877 char *p;
1879 if (str == NULL || wl == NULL || strlen(str) < 2)
1880 return;
1882 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1884 /* treat *.moo.com the same as .moo.com */
1885 if (str[0] == '*' && str[1] == '.')
1886 str = &str[1];
1887 else if (str[0] == '.')
1888 str = &str[0];
1889 else
1890 add_dot = 1;
1892 /* slice off port number */
1893 p = g_strrstr(str, ":");
1894 if (p)
1895 *p = '\0';
1897 d = g_malloc(sizeof *d);
1898 if (add_dot)
1899 d->d = g_strdup_printf(".%s", str);
1900 else
1901 d->d = g_strdup(str);
1902 d->handy = handy;
1904 if (RB_INSERT(domain_list, wl, d))
1905 goto unwind;
1907 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1908 return;
1909 unwind:
1910 if (d) {
1911 if (d->d)
1912 g_free(d->d);
1913 g_free(d);
1918 add_cookie_wl(struct settings *s, char *entry)
1920 wl_add(entry, &c_wl, 1);
1921 return (0);
1924 void
1925 walk_cookie_wl(struct settings *s,
1926 void (*cb)(struct settings *, char *, void *), void *cb_args)
1928 struct domain *d;
1930 if (s == NULL || cb == NULL) {
1931 show_oops(NULL, "walk_cookie_wl invalid parameters");
1932 return;
1935 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1936 cb(s, d->d, cb_args);
1939 void
1940 walk_js_wl(struct settings *s,
1941 void (*cb)(struct settings *, char *, void *), void *cb_args)
1943 struct domain *d;
1945 if (s == NULL || cb == NULL) {
1946 show_oops(NULL, "walk_js_wl invalid parameters");
1947 return;
1950 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1951 cb(s, d->d, cb_args);
1955 add_js_wl(struct settings *s, char *entry)
1957 wl_add(entry, &js_wl, 1 /* persistent */);
1958 return (0);
1961 struct domain *
1962 wl_find(const gchar *search, struct domain_list *wl)
1964 int i;
1965 struct domain *d = NULL, dfind;
1966 gchar *s = NULL;
1968 if (search == NULL || wl == NULL)
1969 return (NULL);
1970 if (strlen(search) < 2)
1971 return (NULL);
1973 if (search[0] != '.')
1974 s = g_strdup_printf(".%s", search);
1975 else
1976 s = g_strdup(search);
1978 for (i = strlen(s) - 1; i >= 0; i--) {
1979 if (s[i] == '.') {
1980 dfind.d = &s[i];
1981 d = RB_FIND(domain_list, wl, &dfind);
1982 if (d)
1983 goto done;
1987 done:
1988 if (s)
1989 g_free(s);
1991 return (d);
1994 struct domain *
1995 wl_find_uri(const gchar *s, struct domain_list *wl)
1997 int i;
1998 char *ss;
1999 struct domain *r;
2001 if (s == NULL || wl == NULL)
2002 return (NULL);
2004 if (!strncmp(s, "http://", strlen("http://")))
2005 s = &s[strlen("http://")];
2006 else if (!strncmp(s, "https://", strlen("https://")))
2007 s = &s[strlen("https://")];
2009 if (strlen(s) < 2)
2010 return (NULL);
2012 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2013 /* chop string at first slash */
2014 if (s[i] == '/' || s[i] == '\0') {
2015 ss = g_strdup(s);
2016 ss[i] = '\0';
2017 r = wl_find(ss, wl);
2018 g_free(ss);
2019 return (r);
2022 return (NULL);
2026 settings_add(char *var, char *val)
2028 int i, rv, *p;
2029 gfloat *f;
2030 char **s;
2032 /* get settings */
2033 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2034 if (strcmp(var, rs[i].name))
2035 continue;
2037 if (rs[i].s) {
2038 if (rs[i].s->set(&rs[i], val))
2039 errx(1, "invalid value for %s: %s", var, val);
2040 rv = 1;
2041 break;
2042 } else
2043 switch (rs[i].type) {
2044 case XT_S_INT:
2045 p = rs[i].ival;
2046 *p = atoi(val);
2047 rv = 1;
2048 break;
2049 case XT_S_STR:
2050 s = rs[i].sval;
2051 if (s == NULL)
2052 errx(1, "invalid sval for %s",
2053 rs[i].name);
2054 if (*s)
2055 g_free(*s);
2056 *s = g_strdup(val);
2057 rv = 1;
2058 break;
2059 case XT_S_FLOAT:
2060 f = rs[i].fval;
2061 *f = atof(val);
2062 rv = 1;
2063 break;
2064 case XT_S_INVALID:
2065 default:
2066 errx(1, "invalid type for %s", var);
2068 break;
2070 return (rv);
2073 #define WS "\n= \t"
2074 void
2075 config_parse(char *filename, int runtime)
2077 FILE *config, *f;
2078 char *line, *cp, *var, *val;
2079 size_t len, lineno = 0;
2080 int handled;
2081 char file[PATH_MAX];
2082 struct stat sb;
2084 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2086 if (filename == NULL)
2087 return;
2089 if (runtime && runtime_settings[0] != '\0') {
2090 snprintf(file, sizeof file, "%s/%s",
2091 work_dir, runtime_settings);
2092 if (stat(file, &sb)) {
2093 warnx("runtime file doesn't exist, creating it");
2094 if ((f = fopen(file, "w")) == NULL)
2095 err(1, "runtime");
2096 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2097 fclose(f);
2099 } else
2100 strlcpy(file, filename, sizeof file);
2102 if ((config = fopen(file, "r")) == NULL) {
2103 warn("config_parse: cannot open %s", filename);
2104 return;
2107 for (;;) {
2108 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2109 if (feof(config) || ferror(config))
2110 break;
2112 cp = line;
2113 cp += (long)strspn(cp, WS);
2114 if (cp[0] == '\0') {
2115 /* empty line */
2116 free(line);
2117 continue;
2120 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2121 startpage_add("invalid configuration file entry: %s",
2122 line);
2124 cp += (long)strspn(cp, WS);
2126 if ((val = strsep(&cp, "\0")) == NULL)
2127 break;
2129 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2130 handled = settings_add(var, val);
2131 if (handled == 0)
2132 startpage_add("invalid configuration file entry: %s=%s",
2133 var, val);
2135 free(line);
2138 fclose(config);
2141 char *
2142 js_ref_to_string(JSContextRef context, JSValueRef ref)
2144 char *s = NULL;
2145 size_t l;
2146 JSStringRef jsref;
2148 jsref = JSValueToStringCopy(context, ref, NULL);
2149 if (jsref == NULL)
2150 return (NULL);
2152 l = JSStringGetMaximumUTF8CStringSize(jsref);
2153 s = g_malloc(l);
2154 if (s)
2155 JSStringGetUTF8CString(jsref, s, l);
2156 JSStringRelease(jsref);
2158 return (s);
2161 void
2162 disable_hints(struct tab *t)
2164 bzero(t->hint_buf, sizeof t->hint_buf);
2165 bzero(t->hint_num, sizeof t->hint_num);
2166 run_script(t, "vimprobable_clear()");
2167 t->hints_on = 0;
2168 t->hint_mode = XT_HINT_NONE;
2171 void
2172 enable_hints(struct tab *t)
2174 bzero(t->hint_buf, sizeof t->hint_buf);
2175 run_script(t, "vimprobable_show_hints()");
2176 t->hints_on = 1;
2177 t->hint_mode = XT_HINT_NONE;
2180 #define XT_JS_OPEN ("open;")
2181 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2182 #define XT_JS_FIRE ("fire;")
2183 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2184 #define XT_JS_FOUND ("found;")
2185 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2188 run_script(struct tab *t, char *s)
2190 JSGlobalContextRef ctx;
2191 WebKitWebFrame *frame;
2192 JSStringRef str;
2193 JSValueRef val, exception;
2194 char *es, buf[128];
2196 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2197 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2199 frame = webkit_web_view_get_main_frame(t->wv);
2200 ctx = webkit_web_frame_get_global_context(frame);
2202 str = JSStringCreateWithUTF8CString(s);
2203 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2204 NULL, 0, &exception);
2205 JSStringRelease(str);
2207 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2208 if (val == NULL) {
2209 es = js_ref_to_string(ctx, exception);
2210 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2211 g_free(es);
2212 return (1);
2213 } else {
2214 es = js_ref_to_string(ctx, val);
2215 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2217 /* handle return value right here */
2218 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2219 disable_hints(t);
2220 marks_clear(t);
2221 load_uri(t, &es[XT_JS_OPEN_LEN]);
2224 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2225 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2226 &es[XT_JS_FIRE_LEN]);
2227 run_script(t, buf);
2228 disable_hints(t);
2231 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2232 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2233 disable_hints(t);
2236 g_free(es);
2239 return (0);
2243 hint(struct tab *t, struct karg *args)
2246 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2248 if (t->hints_on == 0)
2249 enable_hints(t);
2250 else
2251 disable_hints(t);
2253 return (0);
2256 void
2257 apply_style(struct tab *t)
2259 g_object_set(G_OBJECT(t->settings),
2260 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2264 userstyle(struct tab *t, struct karg *args)
2266 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2268 if (t->styled) {
2269 t->styled = 0;
2270 g_object_set(G_OBJECT(t->settings),
2271 "user-stylesheet-uri", NULL, (char *)NULL);
2272 } else {
2273 t->styled = 1;
2274 apply_style(t);
2276 return (0);
2280 * Doesn't work fully, due to the following bug:
2281 * https://bugs.webkit.org/show_bug.cgi?id=51747
2284 restore_global_history(void)
2286 char file[PATH_MAX];
2287 FILE *f;
2288 struct history *h;
2289 gchar *uri;
2290 gchar *title;
2291 const char delim[3] = {'\\', '\\', '\0'};
2293 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2295 if ((f = fopen(file, "r")) == NULL) {
2296 warnx("%s: fopen", __func__);
2297 return (1);
2300 for (;;) {
2301 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2302 if (feof(f) || ferror(f))
2303 break;
2305 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2306 if (feof(f) || ferror(f)) {
2307 free(uri);
2308 warnx("%s: broken history file\n", __func__);
2309 return (1);
2312 if (uri && strlen(uri) && title && strlen(title)) {
2313 webkit_web_history_item_new_with_data(uri, title);
2314 h = g_malloc(sizeof(struct history));
2315 h->uri = g_strdup(uri);
2316 h->title = g_strdup(title);
2317 RB_INSERT(history_list, &hl, h);
2318 completion_add_uri(h->uri);
2319 } else {
2320 warnx("%s: failed to restore history\n", __func__);
2321 free(uri);
2322 free(title);
2323 return (1);
2326 free(uri);
2327 free(title);
2328 uri = NULL;
2329 title = NULL;
2332 return (0);
2336 save_global_history_to_disk(struct tab *t)
2338 char file[PATH_MAX];
2339 FILE *f;
2340 struct history *h;
2342 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2344 if ((f = fopen(file, "w")) == NULL) {
2345 show_oops(t, "%s: global history file: %s",
2346 __func__, strerror(errno));
2347 return (1);
2350 RB_FOREACH_REVERSE(h, history_list, &hl) {
2351 if (h->uri && h->title)
2352 fprintf(f, "%s\n%s\n", h->uri, h->title);
2355 fclose(f);
2357 return (0);
2361 quit(struct tab *t, struct karg *args)
2363 if (save_global_history)
2364 save_global_history_to_disk(t);
2366 gtk_main_quit();
2368 return (1);
2372 open_tabs(struct tab *t, struct karg *a)
2374 char file[PATH_MAX];
2375 FILE *f = NULL;
2376 char *uri = NULL;
2377 int rv = 1;
2378 struct tab *ti, *tt;
2380 if (a == NULL)
2381 goto done;
2383 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2384 if ((f = fopen(file, "r")) == NULL)
2385 goto done;
2387 ti = TAILQ_LAST(&tabs, tab_list);
2389 for (;;) {
2390 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2391 if (feof(f) || ferror(f))
2392 break;
2394 /* retrieve session name */
2395 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2396 strlcpy(named_session,
2397 &uri[strlen(XT_SAVE_SESSION_ID)],
2398 sizeof named_session);
2399 continue;
2402 if (uri && strlen(uri))
2403 create_new_tab(uri, NULL, 1, -1);
2405 free(uri);
2406 uri = NULL;
2409 /* close open tabs */
2410 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2411 for (;;) {
2412 tt = TAILQ_FIRST(&tabs);
2413 if (tt != ti) {
2414 delete_tab(tt);
2415 continue;
2417 delete_tab(tt);
2418 break;
2420 recalc_tabs();
2423 rv = 0;
2424 done:
2425 if (f)
2426 fclose(f);
2428 return (rv);
2432 restore_saved_tabs(void)
2434 char file[PATH_MAX];
2435 int unlink_file = 0;
2436 struct stat sb;
2437 struct karg a;
2438 int rv = 0;
2440 snprintf(file, sizeof file, "%s/%s",
2441 sessions_dir, XT_RESTART_TABS_FILE);
2442 if (stat(file, &sb) == -1)
2443 a.s = XT_SAVED_TABS_FILE;
2444 else {
2445 unlink_file = 1;
2446 a.s = XT_RESTART_TABS_FILE;
2449 a.i = XT_SES_DONOTHING;
2450 rv = open_tabs(NULL, &a);
2452 if (unlink_file)
2453 unlink(file);
2455 return (rv);
2459 save_tabs(struct tab *t, struct karg *a)
2461 char file[PATH_MAX];
2462 FILE *f;
2463 int num_tabs = 0, i;
2464 struct tab **stabs = NULL;
2466 if (a == NULL)
2467 return (1);
2468 if (a->s == NULL)
2469 snprintf(file, sizeof file, "%s/%s",
2470 sessions_dir, named_session);
2471 else
2472 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2474 if ((f = fopen(file, "w")) == NULL) {
2475 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2476 return (1);
2479 /* save session name */
2480 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2482 /* Save tabs, in the order they are arranged in the notebook. */
2483 num_tabs = sort_tabs_by_page_num(&stabs);
2485 for (i = 0; i < num_tabs; i++)
2486 if (stabs[i]) {
2487 if (get_uri(stabs[i]) != NULL)
2488 fprintf(f, "%s\n", get_uri(stabs[i]));
2489 else if (gtk_entry_get_text(GTK_ENTRY(
2490 stabs[i]->uri_entry)))
2491 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2492 stabs[i]->uri_entry)));
2495 g_free(stabs);
2497 /* try and make sure this gets to disk NOW. XXX Backup first? */
2498 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2499 show_oops(t, "May not have managed to save session: %s",
2500 strerror(errno));
2503 fclose(f);
2505 return (0);
2509 save_tabs_and_quit(struct tab *t, struct karg *args)
2511 struct karg a;
2513 a.s = NULL;
2514 save_tabs(t, &a);
2515 quit(t, NULL);
2517 return (1);
2521 run_page_script(struct tab *t, struct karg *args)
2523 const gchar *uri;
2524 char *tmp, script[PATH_MAX];
2526 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2527 if (tmp[0] == '\0') {
2528 show_oops(t, "no script specified");
2529 return (1);
2532 if ((uri = get_uri(t)) == NULL) {
2533 show_oops(t, "tab is empty, not running script");
2534 return (1);
2537 if (tmp[0] == '~')
2538 snprintf(script, sizeof script, "%s/%s",
2539 pwd->pw_dir, &tmp[1]);
2540 else
2541 strlcpy(script, tmp, sizeof script);
2543 switch (fork()) {
2544 case -1:
2545 show_oops(t, "can't fork to run script");
2546 return (1);
2547 /* NOTREACHED */
2548 case 0:
2549 break;
2550 default:
2551 return (0);
2554 /* child */
2555 execlp(script, script, uri, (void *)NULL);
2557 _exit(0);
2559 /* NOTREACHED */
2561 return (0);
2565 yank_uri(struct tab *t, struct karg *args)
2567 const gchar *uri;
2568 GtkClipboard *clipboard;
2570 if ((uri = get_uri(t)) == NULL)
2571 return (1);
2573 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2574 gtk_clipboard_set_text(clipboard, uri, -1);
2576 return (0);
2580 paste_uri(struct tab *t, struct karg *args)
2582 GtkClipboard *clipboard;
2583 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2584 gint len;
2585 gchar *p = NULL, *uri;
2587 /* try primary clipboard first */
2588 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2589 p = gtk_clipboard_wait_for_text(clipboard);
2591 /* if it failed get whatever text is in cut_buffer0 */
2592 if (p == NULL && xterm_workaround)
2593 if (gdk_property_get(gdk_get_default_root_window(),
2594 atom,
2595 gdk_atom_intern("STRING", FALSE),
2597 1024 * 1024 /* picked out of my butt */,
2598 FALSE,
2599 NULL,
2600 NULL,
2601 &len,
2602 (guchar **)&p)) {
2603 /* yes sir, we need to NUL the string */
2604 p[len] = '\0';
2607 if (p) {
2608 uri = p;
2609 while (*uri && isspace(*uri))
2610 uri++;
2611 if (strlen(uri) == 0) {
2612 show_oops(t, "empty paste buffer");
2613 goto done;
2615 if (guess_search == 0 && valid_url_type(uri)) {
2616 /* we can be clever and paste this in search box */
2617 show_oops(t, "not a valid URL");
2618 goto done;
2621 if (args->i == XT_PASTE_CURRENT_TAB)
2622 load_uri(t, uri);
2623 else if (args->i == XT_PASTE_NEW_TAB)
2624 create_new_tab(uri, NULL, 1, -1);
2627 done:
2628 if (p)
2629 g_free(p);
2631 return (0);
2634 gchar *
2635 find_domain(const gchar *s, int toplevel)
2637 SoupURI *uri;
2638 gchar *ret, *p;
2640 if (s == NULL)
2641 return (NULL);
2643 uri = soup_uri_new(s);
2645 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2646 return (NULL);
2649 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2650 if ((p = strrchr(uri->host, '.')) != NULL) {
2651 while(--p >= uri->host && *p != '.');
2652 p++;
2653 } else
2654 p = uri->host;
2655 } else
2656 p = uri->host;
2658 if (uri->port == 80)
2659 ret = g_strdup_printf(".%s", p);
2660 else
2661 ret = g_strdup_printf(".%s:%d", p, uri->port);
2663 soup_uri_free(uri);
2665 return ret;
2669 toggle_cwl(struct tab *t, struct karg *args)
2671 struct domain *d;
2672 const gchar *uri;
2673 char *dom = NULL;
2674 int es;
2676 if (args == NULL)
2677 return (1);
2679 uri = get_uri(t);
2680 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2682 if (uri == NULL || dom == NULL ||
2683 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2684 show_oops(t, "Can't toggle domain in cookie white list");
2685 goto done;
2687 d = wl_find(dom, &c_wl);
2689 if (d == NULL)
2690 es = 0;
2691 else
2692 es = 1;
2694 if (args->i & XT_WL_TOGGLE)
2695 es = !es;
2696 else if ((args->i & XT_WL_ENABLE) && es != 1)
2697 es = 1;
2698 else if ((args->i & XT_WL_DISABLE) && es != 0)
2699 es = 0;
2701 if (es)
2702 /* enable cookies for domain */
2703 wl_add(dom, &c_wl, 0);
2704 else
2705 /* disable cookies for domain */
2706 RB_REMOVE(domain_list, &c_wl, d);
2708 if (args->i & XT_WL_RELOAD)
2709 webkit_web_view_reload(t->wv);
2711 done:
2712 g_free(dom);
2713 return (0);
2717 toggle_js(struct tab *t, struct karg *args)
2719 int es;
2720 const gchar *uri;
2721 struct domain *d;
2722 char *dom = NULL;
2724 if (args == NULL)
2725 return (1);
2727 g_object_get(G_OBJECT(t->settings),
2728 "enable-scripts", &es, (char *)NULL);
2729 if (args->i & XT_WL_TOGGLE)
2730 es = !es;
2731 else if ((args->i & XT_WL_ENABLE) && es != 1)
2732 es = 1;
2733 else if ((args->i & XT_WL_DISABLE) && es != 0)
2734 es = 0;
2735 else
2736 return (1);
2738 uri = get_uri(t);
2739 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2741 if (uri == NULL || dom == NULL ||
2742 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2743 show_oops(t, "Can't toggle domain in JavaScript white list");
2744 goto done;
2747 if (es) {
2748 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2749 wl_add(dom, &js_wl, 0 /* session */);
2750 } else {
2751 d = wl_find(dom, &js_wl);
2752 if (d)
2753 RB_REMOVE(domain_list, &js_wl, d);
2754 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2756 g_object_set(G_OBJECT(t->settings),
2757 "enable-scripts", es, (char *)NULL);
2758 g_object_set(G_OBJECT(t->settings),
2759 "javascript-can-open-windows-automatically", es, (char *)NULL);
2760 webkit_web_view_set_settings(t->wv, t->settings);
2762 if (args->i & XT_WL_RELOAD)
2763 webkit_web_view_reload(t->wv);
2764 done:
2765 if (dom)
2766 g_free(dom);
2767 return (0);
2770 void
2771 js_toggle_cb(GtkWidget *w, struct tab *t)
2773 struct karg a;
2775 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2776 toggle_cwl(t, &a);
2778 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2779 toggle_js(t, &a);
2783 toggle_src(struct tab *t, struct karg *args)
2785 gboolean mode;
2787 if (t == NULL)
2788 return (0);
2790 mode = webkit_web_view_get_view_source_mode(t->wv);
2791 webkit_web_view_set_view_source_mode(t->wv, !mode);
2792 webkit_web_view_reload(t->wv);
2794 return (0);
2797 void
2798 focus_webview(struct tab *t)
2800 if (t == NULL)
2801 return;
2803 /* only grab focus if we are visible */
2804 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2805 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2809 focus(struct tab *t, struct karg *args)
2811 if (t == NULL || args == NULL)
2812 return (1);
2814 if (show_url == 0)
2815 return (0);
2817 if (args->i == XT_FOCUS_URI)
2818 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2819 else if (args->i == XT_FOCUS_SEARCH)
2820 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2822 return (0);
2826 stats(struct tab *t, struct karg *args)
2828 char *page, *body, *s, line[64 * 1024];
2829 uint64_t line_count = 0;
2830 FILE *r_cookie_f;
2832 if (t == NULL)
2833 show_oops(NULL, "stats invalid parameters");
2835 line[0] = '\0';
2836 if (save_rejected_cookies) {
2837 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2838 for (;;) {
2839 s = fgets(line, sizeof line, r_cookie_f);
2840 if (s == NULL || feof(r_cookie_f) ||
2841 ferror(r_cookie_f))
2842 break;
2843 line_count++;
2845 fclose(r_cookie_f);
2846 snprintf(line, sizeof line,
2847 "<br/>Cookies blocked(*) total: %llu", line_count);
2848 } else
2849 show_oops(t, "Can't open blocked cookies file: %s",
2850 strerror(errno));
2853 body = g_strdup_printf(
2854 "Cookies blocked(*) this session: %llu"
2855 "%s"
2856 "<p><small><b>*</b> results vary based on settings</small></p>",
2857 blocked_cookies,
2858 line);
2860 page = get_html_page("Statistics", body, "", 0);
2861 g_free(body);
2863 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2864 g_free(page);
2866 return (0);
2870 marco(struct tab *t, struct karg *args)
2872 char *page, line[64 * 1024];
2873 int len;
2875 if (t == NULL)
2876 show_oops(NULL, "marco invalid parameters");
2878 line[0] = '\0';
2879 snprintf(line, sizeof line, "%s", marco_message(&len));
2881 page = get_html_page("Marco Sez...", line, "", 0);
2883 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2884 g_free(page);
2886 return (0);
2890 blank(struct tab *t, struct karg *args)
2892 if (t == NULL)
2893 show_oops(NULL, "blank invalid parameters");
2895 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2897 return (0);
2901 about(struct tab *t, struct karg *args)
2903 char *page, *body;
2905 if (t == NULL)
2906 show_oops(NULL, "about invalid parameters");
2908 body = g_strdup_printf("<b>Version: %s</b><p>"
2909 "Authors:"
2910 "<ul>"
2911 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2912 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2913 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2914 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2915 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2916 "</ul>"
2917 "Copyrights and licenses can be found on the XXXTerm "
2918 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2919 version
2922 page = get_html_page("About", body, "", 0);
2923 g_free(body);
2925 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2926 g_free(page);
2928 return (0);
2932 help(struct tab *t, struct karg *args)
2934 char *page, *head, *body;
2936 if (t == NULL)
2937 show_oops(NULL, "help invalid parameters");
2939 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2940 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2941 "</head>\n";
2942 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2943 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2944 "cgi-bin/man-cgi?xxxterm</a>";
2946 page = get_html_page(XT_NAME, body, head, FALSE);
2948 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2949 g_free(page);
2951 return (0);
2955 startpage(struct tab *t, struct karg *args)
2957 char *page, *body, *b;
2958 struct sp *s;
2960 if (t == NULL)
2961 show_oops(NULL, "startpage invalid parameters");
2963 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
2965 TAILQ_FOREACH(s, &spl, entry) {
2966 b = body;
2967 body = g_strdup_printf("%s%s<br>", body, s->line);
2968 g_free(b);
2971 page = get_html_page("Startup Exception", body, "", 0);
2972 g_free(body);
2974 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
2975 g_free(page);
2977 return (0);
2980 void
2981 startpage_add(const char *fmt, ...)
2983 va_list ap;
2984 char *msg;
2985 struct sp *s;
2987 if (fmt == NULL)
2988 return;
2990 va_start(ap, fmt);
2991 if (vasprintf(&msg, fmt, ap) == -1)
2992 errx(1, "startpage_add failed");
2993 va_end(ap);
2995 s = g_malloc0(sizeof *s);
2996 s->line = msg;
2998 TAILQ_INSERT_TAIL(&spl, s, entry);
3002 * update all favorite tabs apart from one. Pass NULL if
3003 * you want to update all.
3005 void
3006 update_favorite_tabs(struct tab *apart_from)
3008 struct tab *t;
3009 if (!updating_fl_tabs) {
3010 updating_fl_tabs = 1; /* stop infinite recursion */
3011 TAILQ_FOREACH(t, &tabs, entry)
3012 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3013 && (t != apart_from))
3014 xtp_page_fl(t, NULL);
3015 updating_fl_tabs = 0;
3019 /* show a list of favorites (bookmarks) */
3021 xtp_page_fl(struct tab *t, struct karg *args)
3023 char file[PATH_MAX];
3024 FILE *f;
3025 char *uri = NULL, *title = NULL;
3026 size_t len, lineno = 0;
3027 int i, failed = 0;
3028 char *body, *tmp, *page = NULL;
3029 const char delim[3] = {'\\', '\\', '\0'};
3031 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3033 if (t == NULL)
3034 warn("%s: bad param", __func__);
3036 /* new session key */
3037 if (!updating_fl_tabs)
3038 generate_xtp_session_key(&fl_session_key);
3040 /* open favorites */
3041 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3042 if ((f = fopen(file, "r")) == NULL) {
3043 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3044 return (1);
3047 /* body */
3048 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3049 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3050 "<th style='width: 40px'>Rm</th></tr>\n");
3052 for (i = 1;;) {
3053 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3054 if (feof(f) || ferror(f))
3055 break;
3056 if (strlen(title) == 0 || title[0] == '#') {
3057 free(title);
3058 title = NULL;
3059 continue;
3062 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3063 if (feof(f) || ferror(f)) {
3064 show_oops(t, "favorites file corrupt");
3065 failed = 1;
3066 break;
3069 tmp = body;
3070 body = g_strdup_printf("%s<tr>"
3071 "<td>%d</td>"
3072 "<td><a href='%s'>%s</a></td>"
3073 "<td style='text-align: center'>"
3074 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3075 "</tr>\n",
3076 body, i, uri, title,
3077 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3079 g_free(tmp);
3081 free(uri);
3082 uri = NULL;
3083 free(title);
3084 title = NULL;
3085 i++;
3087 fclose(f);
3089 /* if none, say so */
3090 if (i == 1) {
3091 tmp = body;
3092 body = g_strdup_printf("%s<tr>"
3093 "<td colspan='3' style='text-align: center'>"
3094 "No favorites - To add one use the 'favadd' command."
3095 "</td></tr>", body);
3096 g_free(tmp);
3099 tmp = body;
3100 body = g_strdup_printf("%s</table>", body);
3101 g_free(tmp);
3103 if (uri)
3104 free(uri);
3105 if (title)
3106 free(title);
3108 /* render */
3109 if (!failed) {
3110 page = get_html_page("Favorites", body, "", 1);
3111 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3112 g_free(page);
3115 update_favorite_tabs(t);
3117 if (body)
3118 g_free(body);
3120 return (failed);
3123 void
3124 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3125 size_t cert_count, char *title)
3127 gnutls_datum_t cinfo;
3128 char *tmp, *body;
3129 int i;
3131 body = g_strdup("");
3133 for (i = 0; i < cert_count; i++) {
3134 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3135 &cinfo))
3136 return;
3138 tmp = body;
3139 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3140 body, i, cinfo.data);
3141 gnutls_free(cinfo.data);
3142 g_free(tmp);
3145 tmp = get_html_page(title, body, "", 0);
3146 g_free(body);
3148 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3149 g_free(tmp);
3153 ca_cmd(struct tab *t, struct karg *args)
3155 FILE *f = NULL;
3156 int rv = 1, certs = 0, certs_read;
3157 struct stat sb;
3158 gnutls_datum_t dt;
3159 gnutls_x509_crt_t *c = NULL;
3160 char *certs_buf = NULL, *s;
3162 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3163 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3164 return (1);
3167 if (fstat(fileno(f), &sb) == -1) {
3168 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3169 goto done;
3172 certs_buf = g_malloc(sb.st_size + 1);
3173 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3174 show_oops(t, "Can't read CA file: %s", strerror(errno));
3175 goto done;
3177 certs_buf[sb.st_size] = '\0';
3179 s = certs_buf;
3180 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3181 certs++;
3182 s += strlen("BEGIN CERTIFICATE");
3185 bzero(&dt, sizeof dt);
3186 dt.data = (unsigned char *)certs_buf;
3187 dt.size = sb.st_size;
3188 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3189 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3190 GNUTLS_X509_FMT_PEM, 0);
3191 if (certs_read <= 0) {
3192 show_oops(t, "No cert(s) available");
3193 goto done;
3195 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3196 done:
3197 if (c)
3198 g_free(c);
3199 if (certs_buf)
3200 g_free(certs_buf);
3201 if (f)
3202 fclose(f);
3204 return (rv);
3208 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3209 size_t domain_sz)
3211 SoupURI *su = NULL;
3212 struct addrinfo hints, *res = NULL, *ai;
3213 int rv = -1, s = -1, on, error;
3214 char port[8];
3216 if (uri && !g_str_has_prefix(uri, "https://")) {
3217 show_oops(t, "invalid URI");
3218 goto done;
3221 su = soup_uri_new(uri);
3222 if (su == NULL) {
3223 show_oops(t, "invalid soup URI");
3224 goto done;
3226 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3227 show_oops(t, "invalid HTTPS URI");
3228 goto done;
3231 snprintf(port, sizeof port, "%d", su->port);
3232 bzero(&hints, sizeof(struct addrinfo));
3233 hints.ai_flags = AI_CANONNAME;
3234 hints.ai_family = AF_UNSPEC;
3235 hints.ai_socktype = SOCK_STREAM;
3237 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3238 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3239 goto done;
3242 for (ai = res; ai; ai = ai->ai_next) {
3243 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3244 continue;
3246 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3247 if (s == -1) {
3248 show_oops(t, "socket failed: %s", strerror(errno));
3249 goto done;
3251 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3252 sizeof(on)) == -1) {
3253 show_oops(t, "setsockopt failed: %s", strerror(errno));
3254 goto done;
3256 if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
3257 show_oops(t, "connect failed: %s", strerror(errno));
3258 goto done;
3261 break;
3264 if (domain)
3265 strlcpy(domain, su->host, domain_sz);
3266 rv = s;
3267 done:
3268 if (su)
3269 soup_uri_free(su);
3270 if (res)
3271 freeaddrinfo(res);
3272 if (rv == -1 && s != -1)
3273 close(s);
3275 return (rv);
3279 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3281 if (gsession)
3282 gnutls_deinit(gsession);
3283 if (xcred)
3284 gnutls_certificate_free_credentials(xcred);
3286 return (0);
3290 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3291 gnutls_certificate_credentials_t *xc)
3293 gnutls_certificate_credentials_t xcred;
3294 gnutls_session_t gsession;
3295 int rv = 1;
3297 if (gs == NULL || xc == NULL)
3298 goto done;
3300 *gs = NULL;
3301 *xc = NULL;
3303 gnutls_certificate_allocate_credentials(&xcred);
3304 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3305 GNUTLS_X509_FMT_PEM);
3307 gnutls_init(&gsession, GNUTLS_CLIENT);
3308 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3309 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3310 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3311 if ((rv = gnutls_handshake(gsession)) < 0) {
3312 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3314 gnutls_error_is_fatal(rv),
3315 gnutls_strerror_name(rv));
3316 stop_tls(gsession, xcred);
3317 goto done;
3320 gnutls_credentials_type_t cred;
3321 cred = gnutls_auth_get_type(gsession);
3322 if (cred != GNUTLS_CRD_CERTIFICATE) {
3323 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3324 stop_tls(gsession, xcred);
3325 goto done;
3328 *gs = gsession;
3329 *xc = xcred;
3330 rv = 0;
3331 done:
3332 return (rv);
3336 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3337 size_t *cert_count)
3339 unsigned int len;
3340 const gnutls_datum_t *cl;
3341 gnutls_x509_crt_t *all_certs;
3342 int i, rv = 1;
3344 if (certs == NULL || cert_count == NULL)
3345 goto done;
3346 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3347 goto done;
3348 cl = gnutls_certificate_get_peers(gsession, &len);
3349 if (len == 0)
3350 goto done;
3352 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3353 for (i = 0; i < len; i++) {
3354 gnutls_x509_crt_init(&all_certs[i]);
3355 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3356 GNUTLS_X509_FMT_PEM < 0)) {
3357 g_free(all_certs);
3358 goto done;
3362 *certs = all_certs;
3363 *cert_count = len;
3364 rv = 0;
3365 done:
3366 return (rv);
3369 void
3370 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3372 int i;
3374 for (i = 0; i < cert_count; i++)
3375 gnutls_x509_crt_deinit(certs[i]);
3376 g_free(certs);
3379 void
3380 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3382 GdkColor c_text, c_base;
3384 gdk_color_parse(text, &c_text);
3385 gdk_color_parse(base, &c_base);
3387 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3388 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3389 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3390 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3392 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3393 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3394 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3395 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3398 void
3399 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3400 size_t cert_count, char *domain)
3402 size_t cert_buf_sz;
3403 char cert_buf[64 * 1024], file[PATH_MAX];
3404 int i;
3405 FILE *f;
3406 GdkColor color;
3408 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3409 return;
3411 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3412 if ((f = fopen(file, "w")) == NULL) {
3413 show_oops(t, "Can't create cert file %s %s",
3414 file, strerror(errno));
3415 return;
3418 for (i = 0; i < cert_count; i++) {
3419 cert_buf_sz = sizeof cert_buf;
3420 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3421 cert_buf, &cert_buf_sz)) {
3422 show_oops(t, "gnutls_x509_crt_export failed");
3423 goto done;
3425 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3426 show_oops(t, "Can't write certs: %s", strerror(errno));
3427 goto done;
3431 /* not the best spot but oh well */
3432 gdk_color_parse("lightblue", &color);
3433 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3434 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3435 done:
3436 fclose(f);
3439 enum cert_trust {
3440 CERT_LOCAL,
3441 CERT_TRUSTED,
3442 CERT_UNTRUSTED,
3443 CERT_BAD
3446 enum cert_trust
3447 load_compare_cert(struct tab *t, struct karg *args)
3449 const gchar *uri;
3450 char domain[8182], file[PATH_MAX];
3451 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3452 int s = -1, i, error;
3453 FILE *f = NULL;
3454 size_t cert_buf_sz, cert_count;
3455 enum cert_trust rv = CERT_UNTRUSTED;
3456 char serr[80];
3457 gnutls_session_t gsession;
3458 gnutls_x509_crt_t *certs;
3459 gnutls_certificate_credentials_t xcred;
3461 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3463 if (t == NULL)
3464 return (rv);
3466 if ((uri = get_uri(t)) == NULL)
3467 return (rv);
3468 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3470 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3471 return (rv);
3472 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3474 /* go ssl/tls */
3475 if (start_tls(t, s, &gsession, &xcred))
3476 goto done;
3477 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3479 /* verify certs in case cert file doesn't exist */
3480 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3481 GNUTLS_E_SUCCESS) {
3482 show_oops(t, "Invalid certificates");
3483 goto done;
3486 /* get certs */
3487 if (get_connection_certs(gsession, &certs, &cert_count)) {
3488 show_oops(t, "Can't get connection certificates");
3489 goto done;
3492 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3493 if ((f = fopen(file, "r")) == NULL) {
3494 if (!error)
3495 rv = CERT_TRUSTED;
3496 goto freeit;
3499 for (i = 0; i < cert_count; i++) {
3500 cert_buf_sz = sizeof cert_buf;
3501 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3502 cert_buf, &cert_buf_sz)) {
3503 goto freeit;
3505 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3506 rv = CERT_BAD; /* critical */
3507 goto freeit;
3509 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3510 rv = CERT_BAD; /* critical */
3511 goto freeit;
3513 rv = CERT_LOCAL;
3516 freeit:
3517 if (f)
3518 fclose(f);
3519 free_connection_certs(certs, cert_count);
3520 done:
3521 /* we close the socket first for speed */
3522 if (s != -1)
3523 close(s);
3525 /* only complain if we didn't save it locally */
3526 if (error && rv != CERT_LOCAL) {
3527 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3528 if (error & GNUTLS_CERT_INVALID)
3529 strlcat(serr, "invalid, ", sizeof serr);
3530 if (error & GNUTLS_CERT_REVOKED)
3531 strlcat(serr, "revoked, ", sizeof serr);
3532 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3533 strlcat(serr, "signer not found, ", sizeof serr);
3534 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3535 strlcat(serr, "not signed by CA, ", sizeof serr);
3536 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3537 strlcat(serr, "insecure algorithm, ", sizeof serr);
3538 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3539 strlcat(serr, "not activated, ", sizeof serr);
3540 if (error & GNUTLS_CERT_EXPIRED)
3541 strlcat(serr, "expired, ", sizeof serr);
3542 for (i = strlen(serr) - 1; i > 0; i--)
3543 if (serr[i] == ',') {
3544 serr[i] = '\0';
3545 break;
3547 show_oops(t, serr);
3550 stop_tls(gsession, xcred);
3552 return (rv);
3556 cert_cmd(struct tab *t, struct karg *args)
3558 const gchar *uri;
3559 char domain[8182];
3560 int s = -1;
3561 size_t cert_count;
3562 gnutls_session_t gsession;
3563 gnutls_x509_crt_t *certs;
3564 gnutls_certificate_credentials_t xcred;
3566 if (t == NULL)
3567 return (1);
3569 if (ssl_ca_file == NULL) {
3570 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3571 return (1);
3574 if ((uri = get_uri(t)) == NULL) {
3575 show_oops(t, "Invalid URI");
3576 return (1);
3579 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3580 show_oops(t, "Invalid certificate URI: %s", uri);
3581 return (1);
3584 /* go ssl/tls */
3585 if (start_tls(t, s, &gsession, &xcred))
3586 goto done;
3588 /* get certs */
3589 if (get_connection_certs(gsession, &certs, &cert_count)) {
3590 show_oops(t, "get_connection_certs failed");
3591 goto done;
3594 if (args->i & XT_SHOW)
3595 show_certs(t, certs, cert_count, "Certificate Chain");
3596 else if (args->i & XT_SAVE)
3597 save_certs(t, certs, cert_count, domain);
3599 free_connection_certs(certs, cert_count);
3600 done:
3601 /* we close the socket first for speed */
3602 if (s != -1)
3603 close(s);
3604 stop_tls(gsession, xcred);
3606 return (0);
3610 remove_cookie(int index)
3612 int i, rv = 1;
3613 GSList *cf;
3614 SoupCookie *c;
3616 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3618 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3620 for (i = 1; cf; cf = cf->next, i++) {
3621 if (i != index)
3622 continue;
3623 c = cf->data;
3624 print_cookie("remove cookie", c);
3625 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3626 rv = 0;
3627 break;
3630 soup_cookies_free(cf);
3632 return (rv);
3636 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3638 struct domain *d;
3639 char *tmp, *body;
3641 body = g_strdup("");
3643 /* p list */
3644 if (args->i & XT_WL_PERSISTENT) {
3645 tmp = body;
3646 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3647 g_free(tmp);
3648 RB_FOREACH(d, domain_list, wl) {
3649 if (d->handy == 0)
3650 continue;
3651 tmp = body;
3652 body = g_strdup_printf("%s%s<br/>", body, d->d);
3653 g_free(tmp);
3657 /* s list */
3658 if (args->i & XT_WL_SESSION) {
3659 tmp = body;
3660 body = g_strdup_printf("%s<h2>Session</h2>", body);
3661 g_free(tmp);
3662 RB_FOREACH(d, domain_list, wl) {
3663 if (d->handy == 1)
3664 continue;
3665 tmp = body;
3666 body = g_strdup_printf("%s%s<br/>", body, d->d);
3667 g_free(tmp);
3671 tmp = get_html_page(title, body, "", 0);
3672 g_free(body);
3673 if (wl == &js_wl)
3674 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3675 else
3676 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3677 g_free(tmp);
3678 return (0);
3682 wl_save(struct tab *t, struct karg *args, int js)
3684 char file[PATH_MAX];
3685 FILE *f;
3686 char *line = NULL, *lt = NULL, *dom = NULL;
3687 size_t linelen;
3688 const gchar *uri;
3689 struct karg a;
3690 struct domain *d;
3691 GSList *cf;
3692 SoupCookie *ci, *c;
3693 char *p;
3695 if (t == NULL || args == NULL)
3696 return (1);
3698 if (runtime_settings[0] == '\0')
3699 return (1);
3701 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3702 if ((f = fopen(file, "r+")) == NULL)
3703 return (1);
3705 uri = get_uri(t);
3706 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3707 if (uri == NULL || dom == NULL ||
3708 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3709 show_oops(t, "Can't add domain to %s white list",
3710 js ? "JavaScript" : "cookie");
3711 goto done;
3714 /* we don't want to save :port number */
3715 p = g_strrstr(dom, ":");
3716 if (p)
3717 *p = '\0';
3719 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3721 while (!feof(f)) {
3722 line = fparseln(f, &linelen, NULL, NULL, 0);
3723 if (line == NULL)
3724 continue;
3725 if (!strcmp(line, lt))
3726 goto done;
3727 free(line);
3728 line = NULL;
3731 fprintf(f, "%s\n", lt);
3733 a.i = XT_WL_ENABLE;
3734 a.i |= args->i;
3735 if (js) {
3736 d = wl_find(dom, &js_wl);
3737 if (!d) {
3738 settings_add("js_wl", dom);
3739 d = wl_find(dom, &js_wl);
3741 toggle_js(t, &a);
3742 } else {
3743 d = wl_find(dom, &c_wl);
3744 if (!d) {
3745 settings_add("cookie_wl", dom);
3746 d = wl_find(dom, &c_wl);
3748 toggle_cwl(t, &a);
3750 /* find and add to persistent jar */
3751 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3752 for (;cf; cf = cf->next) {
3753 ci = cf->data;
3754 if (!strcmp(dom, ci->domain) ||
3755 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3756 c = soup_cookie_copy(ci);
3757 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3760 soup_cookies_free(cf);
3762 if (d)
3763 d->handy = 1;
3765 done:
3766 if (line)
3767 free(line);
3768 if (dom)
3769 g_free(dom);
3770 if (lt)
3771 g_free(lt);
3772 fclose(f);
3774 return (0);
3778 js_show_wl(struct tab *t, struct karg *args)
3780 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3781 wl_show(t, args, "JavaScript White List", &js_wl);
3783 return (0);
3787 cookie_show_wl(struct tab *t, struct karg *args)
3789 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3790 wl_show(t, args, "Cookie White List", &c_wl);
3792 return (0);
3796 cookie_cmd(struct tab *t, struct karg *args)
3798 if (args->i & XT_SHOW)
3799 wl_show(t, args, "Cookie White List", &c_wl);
3800 else if (args->i & XT_WL_TOGGLE) {
3801 args->i |= XT_WL_RELOAD;
3802 toggle_cwl(t, args);
3803 } else if (args->i & XT_SAVE) {
3804 args->i |= XT_WL_RELOAD;
3805 wl_save(t, args, 0);
3806 } else if (args->i & XT_DELETE)
3807 show_oops(t, "'cookie delete' currently unimplemented");
3809 return (0);
3813 js_cmd(struct tab *t, struct karg *args)
3815 if (args->i & XT_SHOW)
3816 wl_show(t, args, "JavaScript White List", &js_wl);
3817 else if (args->i & XT_SAVE) {
3818 args->i |= XT_WL_RELOAD;
3819 wl_save(t, args, 1);
3820 } else if (args->i & XT_WL_TOGGLE) {
3821 args->i |= XT_WL_RELOAD;
3822 toggle_js(t, args);
3823 } else if (args->i & XT_DELETE)
3824 show_oops(t, "'js delete' currently unimplemented");
3826 return (0);
3830 toplevel_cmd(struct tab *t, struct karg *args)
3832 js_toggle_cb(t->js_toggle, t);
3834 return (0);
3838 add_favorite(struct tab *t, struct karg *args)
3840 char file[PATH_MAX];
3841 FILE *f;
3842 char *line = NULL;
3843 size_t urilen, linelen;
3844 const gchar *uri, *title;
3846 if (t == NULL)
3847 return (1);
3849 /* don't allow adding of xtp pages to favorites */
3850 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3851 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3852 return (1);
3855 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3856 if ((f = fopen(file, "r+")) == NULL) {
3857 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3858 return (1);
3861 title = get_title(t, FALSE);
3862 uri = get_uri(t);
3864 if (title == NULL || uri == NULL) {
3865 show_oops(t, "can't add page to favorites");
3866 goto done;
3869 urilen = strlen(uri);
3871 for (;;) {
3872 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3873 if (feof(f) || ferror(f))
3874 break;
3876 if (linelen == urilen && !strcmp(line, uri))
3877 goto done;
3879 free(line);
3880 line = NULL;
3883 fprintf(f, "\n%s\n%s", title, uri);
3884 done:
3885 if (line)
3886 free(line);
3887 fclose(f);
3889 update_favorite_tabs(NULL);
3891 return (0);
3895 navaction(struct tab *t, struct karg *args)
3897 WebKitWebHistoryItem *item;
3899 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3900 t->tab_id, args->i);
3902 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3904 if (t->item) {
3905 if (args->i == XT_NAV_BACK)
3906 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3907 else
3908 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3909 if (item == NULL)
3910 return (XT_CB_PASSTHROUGH);
3911 webkit_web_view_go_to_back_forward_item(t->wv, item);
3912 t->item = NULL;
3913 return (XT_CB_PASSTHROUGH);
3916 switch (args->i) {
3917 case XT_NAV_BACK:
3918 marks_clear(t);
3919 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3920 if (item)
3921 webkit_web_view_go_to_back_forward_item(t->wv, item);
3922 break;
3923 case XT_NAV_FORWARD:
3924 marks_clear(t);
3925 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3926 if (item)
3927 webkit_web_view_go_to_back_forward_item(t->wv, item);
3928 break;
3929 case XT_NAV_RELOAD:
3930 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3931 if (item)
3932 webkit_web_view_go_to_back_forward_item(t->wv, item);
3933 break;
3935 return (XT_CB_PASSTHROUGH);
3939 move(struct tab *t, struct karg *args)
3941 GtkAdjustment *adjust;
3942 double pi, si, pos, ps, upper, lower, max;
3943 double percent;
3945 switch (args->i) {
3946 case XT_MOVE_DOWN:
3947 case XT_MOVE_UP:
3948 case XT_MOVE_BOTTOM:
3949 case XT_MOVE_TOP:
3950 case XT_MOVE_PAGEDOWN:
3951 case XT_MOVE_PAGEUP:
3952 case XT_MOVE_HALFDOWN:
3953 case XT_MOVE_HALFUP:
3954 case XT_MOVE_PERCENT:
3955 adjust = t->adjust_v;
3956 break;
3957 default:
3958 adjust = t->adjust_h;
3959 break;
3962 pos = gtk_adjustment_get_value(adjust);
3963 ps = gtk_adjustment_get_page_size(adjust);
3964 upper = gtk_adjustment_get_upper(adjust);
3965 lower = gtk_adjustment_get_lower(adjust);
3966 si = gtk_adjustment_get_step_increment(adjust);
3967 pi = gtk_adjustment_get_page_increment(adjust);
3968 max = upper - ps;
3970 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3971 "max %f si %f pi %f\n",
3972 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3973 pos, ps, upper, lower, max, si, pi);
3975 switch (args->i) {
3976 case XT_MOVE_DOWN:
3977 case XT_MOVE_RIGHT:
3978 pos += si;
3979 gtk_adjustment_set_value(adjust, MIN(pos, max));
3980 break;
3981 case XT_MOVE_UP:
3982 case XT_MOVE_LEFT:
3983 pos -= si;
3984 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3985 break;
3986 case XT_MOVE_BOTTOM:
3987 case XT_MOVE_FARRIGHT:
3988 gtk_adjustment_set_value(adjust, max);
3989 break;
3990 case XT_MOVE_TOP:
3991 case XT_MOVE_FARLEFT:
3992 gtk_adjustment_set_value(adjust, lower);
3993 break;
3994 case XT_MOVE_PAGEDOWN:
3995 pos += pi;
3996 gtk_adjustment_set_value(adjust, MIN(pos, max));
3997 break;
3998 case XT_MOVE_PAGEUP:
3999 pos -= pi;
4000 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4001 break;
4002 case XT_MOVE_HALFDOWN:
4003 pos += pi / 2;
4004 gtk_adjustment_set_value(adjust, MIN(pos, max));
4005 break;
4006 case XT_MOVE_HALFUP:
4007 pos -= pi / 2;
4008 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4009 break;
4010 case XT_MOVE_PERCENT:
4011 percent = atoi(args->s) / 100.0;
4012 pos = max * percent;
4013 if (pos < 0.0 || pos > max)
4014 break;
4015 gtk_adjustment_set_value(adjust, pos);
4016 break;
4017 default:
4018 return (XT_CB_PASSTHROUGH);
4021 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4023 return (XT_CB_HANDLED);
4026 void
4027 url_set_visibility(void)
4029 struct tab *t;
4031 TAILQ_FOREACH(t, &tabs, entry)
4032 if (show_url == 0) {
4033 gtk_widget_hide(t->toolbar);
4034 focus_webview(t);
4035 } else
4036 gtk_widget_show(t->toolbar);
4039 void
4040 notebook_tab_set_visibility(void)
4042 if (show_tabs == 0) {
4043 gtk_widget_hide(tab_bar);
4044 gtk_notebook_set_show_tabs(notebook, FALSE);
4045 } else {
4046 if (tab_style == XT_TABS_NORMAL) {
4047 gtk_widget_hide(tab_bar);
4048 gtk_notebook_set_show_tabs(notebook, TRUE);
4049 } else if (tab_style == XT_TABS_COMPACT) {
4050 gtk_widget_show(tab_bar);
4051 gtk_notebook_set_show_tabs(notebook, FALSE);
4056 void
4057 statusbar_set_visibility(void)
4059 struct tab *t;
4061 TAILQ_FOREACH(t, &tabs, entry)
4062 if (show_statusbar == 0) {
4063 gtk_widget_hide(t->statusbar_box);
4064 focus_webview(t);
4065 } else
4066 gtk_widget_show(t->statusbar_box);
4069 void
4070 url_set(struct tab *t, int enable_url_entry)
4072 GdkPixbuf *pixbuf;
4073 int progress;
4075 show_url = enable_url_entry;
4077 if (enable_url_entry) {
4078 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4079 GTK_ENTRY_ICON_PRIMARY, NULL);
4080 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4081 } else {
4082 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4083 GTK_ENTRY_ICON_PRIMARY);
4084 progress =
4085 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4086 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4087 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4088 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4089 progress);
4094 fullscreen(struct tab *t, struct karg *args)
4096 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4098 if (t == NULL)
4099 return (XT_CB_PASSTHROUGH);
4101 if (show_url == 0) {
4102 url_set(t, 1);
4103 show_tabs = 1;
4104 } else {
4105 url_set(t, 0);
4106 show_tabs = 0;
4109 url_set_visibility();
4110 notebook_tab_set_visibility();
4112 return (XT_CB_HANDLED);
4116 statustoggle(struct tab *t, struct karg *args)
4118 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4120 if (show_statusbar == 1) {
4121 show_statusbar = 0;
4122 statusbar_set_visibility();
4123 } else if (show_statusbar == 0) {
4124 show_statusbar = 1;
4125 statusbar_set_visibility();
4127 return (XT_CB_HANDLED);
4131 urlaction(struct tab *t, struct karg *args)
4133 int rv = XT_CB_HANDLED;
4135 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4137 if (t == NULL)
4138 return (XT_CB_PASSTHROUGH);
4140 switch (args->i) {
4141 case XT_URL_SHOW:
4142 if (show_url == 0) {
4143 url_set(t, 1);
4144 url_set_visibility();
4146 break;
4147 case XT_URL_HIDE:
4148 if (show_url == 1) {
4149 url_set(t, 0);
4150 url_set_visibility();
4152 break;
4154 return (rv);
4158 tabaction(struct tab *t, struct karg *args)
4160 int rv = XT_CB_HANDLED;
4161 char *url = args->s;
4162 struct undo *u;
4163 struct tab *tt;
4165 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4167 if (t == NULL)
4168 return (XT_CB_PASSTHROUGH);
4170 switch (args->i) {
4171 case XT_TAB_NEW:
4172 if (strlen(url) > 0)
4173 create_new_tab(url, NULL, 1, args->precount);
4174 else
4175 create_new_tab(NULL, NULL, 1, args->precount);
4176 break;
4177 case XT_TAB_DELETE:
4178 if (args->precount < 0)
4179 delete_tab(t);
4180 else
4181 TAILQ_FOREACH(tt, &tabs, entry)
4182 if (tt->tab_id == args->precount - 1) {
4183 delete_tab(tt);
4184 break;
4186 break;
4187 case XT_TAB_DELQUIT:
4188 if (gtk_notebook_get_n_pages(notebook) > 1)
4189 delete_tab(t);
4190 else
4191 quit(t, args);
4192 break;
4193 case XT_TAB_OPEN:
4194 if (strlen(url) > 0)
4196 else {
4197 rv = XT_CB_PASSTHROUGH;
4198 goto done;
4200 load_uri(t, url);
4201 break;
4202 case XT_TAB_SHOW:
4203 if (show_tabs == 0) {
4204 show_tabs = 1;
4205 notebook_tab_set_visibility();
4207 break;
4208 case XT_TAB_HIDE:
4209 if (show_tabs == 1) {
4210 show_tabs = 0;
4211 notebook_tab_set_visibility();
4213 break;
4214 case XT_TAB_NEXTSTYLE:
4215 if (tab_style == XT_TABS_NORMAL) {
4216 tab_style = XT_TABS_COMPACT;
4217 recolor_compact_tabs();
4219 else
4220 tab_style = XT_TABS_NORMAL;
4221 notebook_tab_set_visibility();
4222 break;
4223 case XT_TAB_UNDO_CLOSE:
4224 if (undo_count == 0) {
4225 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4226 __func__);
4227 goto done;
4228 } else {
4229 undo_count--;
4230 u = TAILQ_FIRST(&undos);
4231 create_new_tab(u->uri, u, 1, -1);
4233 TAILQ_REMOVE(&undos, u, entry);
4234 g_free(u->uri);
4235 /* u->history is freed in create_new_tab() */
4236 g_free(u);
4238 break;
4239 default:
4240 rv = XT_CB_PASSTHROUGH;
4241 goto done;
4244 done:
4245 if (args->s) {
4246 g_free(args->s);
4247 args->s = NULL;
4250 return (rv);
4254 resizetab(struct tab *t, struct karg *args)
4256 if (t == NULL || args == NULL) {
4257 show_oops(NULL, "resizetab invalid parameters");
4258 return (XT_CB_PASSTHROUGH);
4261 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4262 t->tab_id, args->i);
4264 setzoom_webkit(t, args->i);
4266 return (XT_CB_HANDLED);
4270 movetab(struct tab *t, struct karg *args)
4272 int n, dest;
4274 if (t == NULL || args == NULL) {
4275 show_oops(NULL, "movetab invalid parameters");
4276 return (XT_CB_PASSTHROUGH);
4279 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4280 t->tab_id, args->i);
4282 if (args->i >= XT_TAB_INVALID)
4283 return (XT_CB_PASSTHROUGH);
4285 if (TAILQ_EMPTY(&tabs))
4286 return (XT_CB_PASSTHROUGH);
4288 n = gtk_notebook_get_n_pages(notebook);
4289 dest = gtk_notebook_get_current_page(notebook);
4291 switch (args->i) {
4292 case XT_TAB_NEXT:
4293 if (args->precount < 0)
4294 dest = dest == n - 1 ? 0 : dest + 1;
4295 else
4296 dest = args->precount - 1;
4298 break;
4299 case XT_TAB_PREV:
4300 if (args->precount < 0)
4301 dest -= 1;
4302 else
4303 dest -= args->precount % n;
4305 if (dest < 0)
4306 dest += n;
4308 break;
4309 case XT_TAB_FIRST:
4310 dest = 0;
4311 break;
4312 case XT_TAB_LAST:
4313 dest = n - 1;
4314 break;
4315 default:
4316 return (XT_CB_PASSTHROUGH);
4319 if (dest < 0 || dest >= n)
4320 return (XT_CB_PASSTHROUGH);
4321 if (t->tab_id == dest) {
4322 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4323 return (XT_CB_HANDLED);
4326 set_current_tab(dest);
4328 return (XT_CB_HANDLED);
4331 int cmd_prefix = 0;
4334 command(struct tab *t, struct karg *args)
4336 char *s = NULL, *ss = NULL;
4337 GdkColor color;
4338 const gchar *uri;
4340 if (t == NULL || args == NULL) {
4341 show_oops(NULL, "command invalid parameters");
4342 return (XT_CB_PASSTHROUGH);
4345 switch (args->i) {
4346 case '/':
4347 s = "/";
4348 break;
4349 case '?':
4350 s = "?";
4351 break;
4352 case ':':
4353 if (cmd_prefix == 0)
4354 s = ":";
4355 else {
4356 ss = g_strdup_printf(":%d", cmd_prefix);
4357 s = ss;
4358 cmd_prefix = 0;
4360 break;
4361 case XT_CMD_OPEN:
4362 s = ":open ";
4363 break;
4364 case XT_CMD_TABNEW:
4365 s = ":tabnew ";
4366 break;
4367 case XT_CMD_OPEN_CURRENT:
4368 s = ":open ";
4369 /* FALL THROUGH */
4370 case XT_CMD_TABNEW_CURRENT:
4371 if (!s) /* FALL THROUGH? */
4372 s = ":tabnew ";
4373 if ((uri = get_uri(t)) != NULL) {
4374 ss = g_strdup_printf("%s%s", s, uri);
4375 s = ss;
4377 break;
4378 default:
4379 show_oops(t, "command: invalid opcode %d", args->i);
4380 return (XT_CB_PASSTHROUGH);
4383 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4385 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4386 gdk_color_parse(XT_COLOR_WHITE, &color);
4387 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4388 show_cmd(t);
4389 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4390 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4392 if (ss)
4393 g_free(ss);
4395 return (XT_CB_HANDLED);
4399 * Return a new string with a download row (in html)
4400 * appended. Old string is freed.
4402 char *
4403 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4406 WebKitDownloadStatus stat;
4407 char *status_html = NULL, *cmd_html = NULL, *new_html;
4408 gdouble progress;
4409 char cur_sz[FMT_SCALED_STRSIZE];
4410 char tot_sz[FMT_SCALED_STRSIZE];
4411 char *xtp_prefix;
4413 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4415 /* All actions wil take this form:
4416 * xxxt://class/seskey
4418 xtp_prefix = g_strdup_printf("%s%d/%s/",
4419 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4421 stat = webkit_download_get_status(dl->download);
4423 switch (stat) {
4424 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4425 status_html = g_strdup_printf("Finished");
4426 cmd_html = g_strdup_printf(
4427 "<a href='%s%d/%d'>Remove</a>",
4428 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4429 break;
4430 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4431 /* gather size info */
4432 progress = 100 * webkit_download_get_progress(dl->download);
4434 fmt_scaled(
4435 webkit_download_get_current_size(dl->download), cur_sz);
4436 fmt_scaled(
4437 webkit_download_get_total_size(dl->download), tot_sz);
4439 status_html = g_strdup_printf(
4440 "<div style='width: 100%%' align='center'>"
4441 "<div class='progress-outer'>"
4442 "<div class='progress-inner' style='width: %.2f%%'>"
4443 "</div></div></div>"
4444 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4445 progress, cur_sz, tot_sz, progress);
4447 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4448 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4450 break;
4451 /* LLL */
4452 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4453 status_html = g_strdup_printf("Cancelled");
4454 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4455 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4456 break;
4457 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4458 status_html = g_strdup_printf("Error!");
4459 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4460 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4461 break;
4462 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4463 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4464 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4465 status_html = g_strdup_printf("Starting");
4466 break;
4467 default:
4468 show_oops(t, "%s: unknown download status", __func__);
4471 new_html = g_strdup_printf(
4472 "%s\n<tr><td>%s</td><td>%s</td>"
4473 "<td style='text-align:center'>%s</td></tr>\n",
4474 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4475 status_html, cmd_html);
4476 g_free(html);
4478 if (status_html)
4479 g_free(status_html);
4481 if (cmd_html)
4482 g_free(cmd_html);
4484 g_free(xtp_prefix);
4486 return new_html;
4490 * update all download tabs apart from one. Pass NULL if
4491 * you want to update all.
4493 void
4494 update_download_tabs(struct tab *apart_from)
4496 struct tab *t;
4497 if (!updating_dl_tabs) {
4498 updating_dl_tabs = 1; /* stop infinite recursion */
4499 TAILQ_FOREACH(t, &tabs, entry)
4500 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4501 && (t != apart_from))
4502 xtp_page_dl(t, NULL);
4503 updating_dl_tabs = 0;
4508 * update all cookie tabs apart from one. Pass NULL if
4509 * you want to update all.
4511 void
4512 update_cookie_tabs(struct tab *apart_from)
4514 struct tab *t;
4515 if (!updating_cl_tabs) {
4516 updating_cl_tabs = 1; /* stop infinite recursion */
4517 TAILQ_FOREACH(t, &tabs, entry)
4518 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4519 && (t != apart_from))
4520 xtp_page_cl(t, NULL);
4521 updating_cl_tabs = 0;
4526 * update all history tabs apart from one. Pass NULL if
4527 * you want to update all.
4529 void
4530 update_history_tabs(struct tab *apart_from)
4532 struct tab *t;
4534 if (!updating_hl_tabs) {
4535 updating_hl_tabs = 1; /* stop infinite recursion */
4536 TAILQ_FOREACH(t, &tabs, entry)
4537 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4538 && (t != apart_from))
4539 xtp_page_hl(t, NULL);
4540 updating_hl_tabs = 0;
4544 /* cookie management XTP page */
4546 xtp_page_cl(struct tab *t, struct karg *args)
4548 char *body, *page, *tmp;
4549 int i = 1; /* all ids start 1 */
4550 GSList *sc, *pc, *pc_start;
4551 SoupCookie *c;
4552 char *type, *table_headers, *last_domain;
4554 DNPRINTF(XT_D_CMD, "%s", __func__);
4556 if (t == NULL) {
4557 show_oops(NULL, "%s invalid parameters", __func__);
4558 return (1);
4561 /* Generate a new session key */
4562 if (!updating_cl_tabs)
4563 generate_xtp_session_key(&cl_session_key);
4565 /* table headers */
4566 table_headers = g_strdup_printf("<table><tr>"
4567 "<th>Type</th>"
4568 "<th>Name</th>"
4569 "<th style='width:200px'>Value</th>"
4570 "<th>Path</th>"
4571 "<th>Expires</th>"
4572 "<th>Secure</th>"
4573 "<th>HTTP<br />only</th>"
4574 "<th style='width:40px'>Rm</th></tr>\n");
4576 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4577 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4578 pc_start = pc;
4580 body = NULL;
4581 last_domain = strdup("");
4582 for (; sc; sc = sc->next) {
4583 c = sc->data;
4585 if (strcmp(last_domain, c->domain) != 0) {
4586 /* new domain */
4587 free(last_domain);
4588 last_domain = strdup(c->domain);
4590 if (body != NULL) {
4591 tmp = body;
4592 body = g_strdup_printf("%s</table>"
4593 "<h2>%s</h2>%s\n",
4594 body, c->domain, table_headers);
4595 g_free(tmp);
4596 } else {
4597 /* first domain */
4598 body = g_strdup_printf("<h2>%s</h2>%s\n",
4599 c->domain, table_headers);
4603 type = "Session";
4604 for (pc = pc_start; pc; pc = pc->next)
4605 if (soup_cookie_equal(pc->data, c)) {
4606 type = "Session + Persistent";
4607 break;
4610 tmp = body;
4611 body = g_strdup_printf(
4612 "%s\n<tr>"
4613 "<td>%s</td>"
4614 "<td style='word-wrap:normal'>%s</td>"
4615 "<td>"
4616 " <textarea rows='4'>%s</textarea>"
4617 "</td>"
4618 "<td>%s</td>"
4619 "<td>%s</td>"
4620 "<td>%d</td>"
4621 "<td>%d</td>"
4622 "<td style='text-align:center'>"
4623 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4624 body,
4625 type,
4626 c->name,
4627 c->value,
4628 c->path,
4629 c->expires ?
4630 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4631 c->secure,
4632 c->http_only,
4634 XT_XTP_STR,
4635 XT_XTP_CL,
4636 cl_session_key,
4637 XT_XTP_CL_REMOVE,
4641 g_free(tmp);
4642 i++;
4645 soup_cookies_free(sc);
4646 soup_cookies_free(pc);
4648 /* small message if there are none */
4649 if (i == 1) {
4650 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4651 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4653 tmp = body;
4654 body = g_strdup_printf("%s</table>", body);
4655 g_free(tmp);
4657 page = get_html_page("Cookie Jar", body, "", TRUE);
4658 g_free(body);
4659 g_free(table_headers);
4660 g_free(last_domain);
4662 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4663 update_cookie_tabs(t);
4665 g_free(page);
4667 return (0);
4671 xtp_page_hl(struct tab *t, struct karg *args)
4673 char *body, *page, *tmp;
4674 struct history *h;
4675 int i = 1; /* all ids start 1 */
4677 DNPRINTF(XT_D_CMD, "%s", __func__);
4679 if (t == NULL) {
4680 show_oops(NULL, "%s invalid parameters", __func__);
4681 return (1);
4684 /* Generate a new session key */
4685 if (!updating_hl_tabs)
4686 generate_xtp_session_key(&hl_session_key);
4688 /* body */
4689 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4690 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4692 RB_FOREACH_REVERSE(h, history_list, &hl) {
4693 tmp = body;
4694 body = g_strdup_printf(
4695 "%s\n<tr>"
4696 "<td><a href='%s'>%s</a></td>"
4697 "<td>%s</td>"
4698 "<td style='text-align: center'>"
4699 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4700 body, h->uri, h->uri, h->title,
4701 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4702 XT_XTP_HL_REMOVE, i);
4704 g_free(tmp);
4705 i++;
4708 /* small message if there are none */
4709 if (i == 1) {
4710 tmp = body;
4711 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4712 "colspan='3'>No History</td></tr>\n", body);
4713 g_free(tmp);
4716 tmp = body;
4717 body = g_strdup_printf("%s</table>", body);
4718 g_free(tmp);
4720 page = get_html_page("History", body, "", TRUE);
4721 g_free(body);
4724 * update all history manager tabs as the xtp session
4725 * key has now changed. No need to update the current tab.
4726 * Already did that above.
4728 update_history_tabs(t);
4730 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4731 g_free(page);
4733 return (0);
4737 * Generate a web page detailing the status of any downloads
4740 xtp_page_dl(struct tab *t, struct karg *args)
4742 struct download *dl;
4743 char *body, *page, *tmp;
4744 char *ref;
4745 int n_dl = 1;
4747 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4749 if (t == NULL) {
4750 show_oops(NULL, "%s invalid parameters", __func__);
4751 return (1);
4755 * Generate a new session key for next page instance.
4756 * This only happens for the top level call to xtp_page_dl()
4757 * in which case updating_dl_tabs is 0.
4759 if (!updating_dl_tabs)
4760 generate_xtp_session_key(&dl_session_key);
4762 /* header - with refresh so as to update */
4763 if (refresh_interval >= 1)
4764 ref = g_strdup_printf(
4765 "<meta http-equiv='refresh' content='%u"
4766 ";url=%s%d/%s/%d' />\n",
4767 refresh_interval,
4768 XT_XTP_STR,
4769 XT_XTP_DL,
4770 dl_session_key,
4771 XT_XTP_DL_LIST);
4772 else
4773 ref = g_strdup("");
4775 body = g_strdup_printf("<div align='center'>"
4776 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4777 "</p><table><tr><th style='width: 60%%'>"
4778 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4779 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4781 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4782 body = xtp_page_dl_row(t, body, dl);
4783 n_dl++;
4786 /* message if no downloads in list */
4787 if (n_dl == 1) {
4788 tmp = body;
4789 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4790 " style='text-align: center'>"
4791 "No downloads</td></tr>\n", body);
4792 g_free(tmp);
4795 tmp = body;
4796 body = g_strdup_printf("%s</table></div>", body);
4797 g_free(tmp);
4799 page = get_html_page("Downloads", body, ref, 1);
4800 g_free(ref);
4801 g_free(body);
4804 * update all download manager tabs as the xtp session
4805 * key has now changed. No need to update the current tab.
4806 * Already did that above.
4808 update_download_tabs(t);
4810 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4811 g_free(page);
4813 return (0);
4817 search(struct tab *t, struct karg *args)
4819 gboolean d;
4821 if (t == NULL || args == NULL) {
4822 show_oops(NULL, "search invalid parameters");
4823 return (1);
4825 if (t->search_text == NULL) {
4826 if (global_search == NULL)
4827 return (XT_CB_PASSTHROUGH);
4828 else {
4829 t->search_text = g_strdup(global_search);
4830 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4831 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4835 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4836 t->tab_id, args->i, t->search_forward, t->search_text);
4838 switch (args->i) {
4839 case XT_SEARCH_NEXT:
4840 d = t->search_forward;
4841 break;
4842 case XT_SEARCH_PREV:
4843 d = !t->search_forward;
4844 break;
4845 default:
4846 return (XT_CB_PASSTHROUGH);
4849 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4851 return (XT_CB_HANDLED);
4854 struct settings_args {
4855 char **body;
4856 int i;
4859 void
4860 print_setting(struct settings *s, char *val, void *cb_args)
4862 char *tmp, *color;
4863 struct settings_args *sa = cb_args;
4865 if (sa == NULL)
4866 return;
4868 if (s->flags & XT_SF_RUNTIME)
4869 color = "#22cc22";
4870 else
4871 color = "#cccccc";
4873 tmp = *sa->body;
4874 *sa->body = g_strdup_printf(
4875 "%s\n<tr>"
4876 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4877 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4878 *sa->body,
4879 color,
4880 s->name,
4881 color,
4884 g_free(tmp);
4885 sa->i++;
4889 set_show(struct tab *t, struct karg *args)
4891 char *body, *page, *tmp;
4892 int i = 1;
4893 struct settings_args sa;
4895 bzero(&sa, sizeof sa);
4896 sa.body = &body;
4898 /* body */
4899 body = g_strdup_printf("<div align='center'><table><tr>"
4900 "<th align='left'>Setting</th>"
4901 "<th align='left'>Value</th></tr>\n");
4903 settings_walk(print_setting, &sa);
4904 i = sa.i;
4906 /* small message if there are none */
4907 if (i == 1) {
4908 tmp = body;
4909 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4910 "colspan='2'>No settings</td></tr>\n", body);
4911 g_free(tmp);
4914 tmp = body;
4915 body = g_strdup_printf("%s</table></div>", body);
4916 g_free(tmp);
4918 page = get_html_page("Settings", body, "", 0);
4920 g_free(body);
4922 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4924 g_free(page);
4926 return (XT_CB_PASSTHROUGH);
4930 set(struct tab *t, struct karg *args)
4932 char *p, *val;
4933 int i;
4935 if (args == NULL || args->s == NULL)
4936 return (set_show(t, args));
4938 /* strip spaces */
4939 p = g_strstrip(args->s);
4941 if (strlen(p) == 0)
4942 return (set_show(t, args));
4944 /* we got some sort of string */
4945 val = g_strrstr(p, "=");
4946 if (val) {
4947 *val++ = '\0';
4948 val = g_strchomp(val);
4949 p = g_strchomp(p);
4951 for (i = 0; i < LENGTH(rs); i++) {
4952 if (strcmp(rs[i].name, p))
4953 continue;
4955 if (rs[i].activate) {
4956 if (rs[i].activate(val))
4957 show_oops(t, "%s invalid value %s",
4958 p, val);
4959 else
4960 show_oops(t, ":set %s = %s", p, val);
4961 goto done;
4962 } else {
4963 show_oops(t, "not a runtime option: %s", p);
4964 goto done;
4967 show_oops(t, "unknown option: %s", p);
4968 } else {
4969 p = g_strchomp(p);
4971 for (i = 0; i < LENGTH(rs); i++) {
4972 if (strcmp(rs[i].name, p))
4973 continue;
4975 /* XXX this could use some cleanup */
4976 switch (rs[i].type) {
4977 case XT_S_INT:
4978 if (rs[i].ival)
4979 show_oops(t, "%s = %d",
4980 rs[i].name, *rs[i].ival);
4981 else if (rs[i].s && rs[i].s->get)
4982 show_oops(t, "%s = %s",
4983 rs[i].name,
4984 rs[i].s->get(&rs[i]));
4985 else if (rs[i].s && rs[i].s->get == NULL)
4986 show_oops(t, "%s = ...", rs[i].name);
4987 else
4988 show_oops(t, "%s = ", rs[i].name);
4989 break;
4990 case XT_S_FLOAT:
4991 if (rs[i].fval)
4992 show_oops(t, "%s = %f",
4993 rs[i].name, *rs[i].fval);
4994 else if (rs[i].s && rs[i].s->get)
4995 show_oops(t, "%s = %s",
4996 rs[i].name,
4997 rs[i].s->get(&rs[i]));
4998 else if (rs[i].s && rs[i].s->get == NULL)
4999 show_oops(t, "%s = ...", rs[i].name);
5000 else
5001 show_oops(t, "%s = ", rs[i].name);
5002 break;
5003 case XT_S_STR:
5004 if (rs[i].sval && *rs[i].sval)
5005 show_oops(t, "%s = %s",
5006 rs[i].name, *rs[i].sval);
5007 else if (rs[i].s && rs[i].s->get)
5008 show_oops(t, "%s = %s",
5009 rs[i].name,
5010 rs[i].s->get(&rs[i]));
5011 else if (rs[i].s && rs[i].s->get == NULL)
5012 show_oops(t, "%s = ...", rs[i].name);
5013 else
5014 show_oops(t, "%s = ", rs[i].name);
5015 break;
5016 default:
5017 show_oops(t, "unknown type for %s", rs[i].name);
5018 goto done;
5021 goto done;
5023 show_oops(t, "unknown option: %s", p);
5025 done:
5026 return (XT_CB_PASSTHROUGH);
5030 session_save(struct tab *t, char *filename)
5032 struct karg a;
5033 int rv = 1;
5035 if (strlen(filename) == 0)
5036 goto done;
5038 if (filename[0] == '.' || filename[0] == '/')
5039 goto done;
5041 a.s = filename;
5042 if (save_tabs(t, &a))
5043 goto done;
5044 strlcpy(named_session, filename, sizeof named_session);
5046 rv = 0;
5047 done:
5048 return (rv);
5052 session_open(struct tab *t, char *filename)
5054 struct karg a;
5055 int rv = 1;
5057 if (strlen(filename) == 0)
5058 goto done;
5060 if (filename[0] == '.' || filename[0] == '/')
5061 goto done;
5063 a.s = filename;
5064 a.i = XT_SES_CLOSETABS;
5065 if (open_tabs(t, &a))
5066 goto done;
5068 strlcpy(named_session, filename, sizeof named_session);
5070 rv = 0;
5071 done:
5072 return (rv);
5076 session_delete(struct tab *t, char *filename)
5078 char file[PATH_MAX];
5079 int rv = 1;
5081 if (strlen(filename) == 0)
5082 goto done;
5084 if (filename[0] == '.' || filename[0] == '/')
5085 goto done;
5087 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5088 if (unlink(file))
5089 goto done;
5091 if (!strcmp(filename, named_session))
5092 strlcpy(named_session, XT_SAVED_TABS_FILE,
5093 sizeof named_session);
5095 rv = 0;
5096 done:
5097 return (rv);
5101 session_cmd(struct tab *t, struct karg *args)
5103 char *filename = args->s;
5105 if (t == NULL)
5106 return (1);
5108 if (args->i & XT_SHOW)
5109 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5110 XT_SAVED_TABS_FILE : named_session);
5111 else if (args->i & XT_SAVE) {
5112 if (session_save(t, filename)) {
5113 show_oops(t, "Can't save session: %s",
5114 filename ? filename : "INVALID");
5115 goto done;
5117 } else if (args->i & XT_OPEN) {
5118 if (session_open(t, filename)) {
5119 show_oops(t, "Can't open session: %s",
5120 filename ? filename : "INVALID");
5121 goto done;
5123 } else if (args->i & XT_DELETE) {
5124 if (session_delete(t, filename)) {
5125 show_oops(t, "Can't delete session: %s",
5126 filename ? filename : "INVALID");
5127 goto done;
5130 done:
5131 return (XT_CB_PASSTHROUGH);
5135 * Make a hardcopy of the page
5138 print_page(struct tab *t, struct karg *args)
5140 WebKitWebFrame *frame;
5141 GtkPageSetup *ps;
5142 GtkPrintOperation *op;
5143 GtkPrintOperationAction action;
5144 GtkPrintOperationResult print_res;
5145 GError *g_err = NULL;
5146 int marg_l, marg_r, marg_t, marg_b;
5148 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5150 ps = gtk_page_setup_new();
5151 op = gtk_print_operation_new();
5152 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5153 frame = webkit_web_view_get_main_frame(t->wv);
5155 /* the default margins are too small, so we will bump them */
5156 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5157 XT_PRINT_EXTRA_MARGIN;
5158 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5159 XT_PRINT_EXTRA_MARGIN;
5160 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5161 XT_PRINT_EXTRA_MARGIN;
5162 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5163 XT_PRINT_EXTRA_MARGIN;
5165 /* set margins */
5166 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5167 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5168 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5169 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5171 gtk_print_operation_set_default_page_setup(op, ps);
5173 /* this appears to free 'op' and 'ps' */
5174 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5176 /* check it worked */
5177 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5178 show_oops(NULL, "can't print: %s", g_err->message);
5179 g_error_free (g_err);
5180 return (1);
5183 return (0);
5187 go_home(struct tab *t, struct karg *args)
5189 load_uri(t, home);
5190 return (0);
5194 restart(struct tab *t, struct karg *args)
5196 struct karg a;
5198 a.s = XT_RESTART_TABS_FILE;
5199 save_tabs(t, &a);
5200 execvp(start_argv[0], start_argv);
5201 /* NOTREACHED */
5203 return (0);
5206 #define CTRL GDK_CONTROL_MASK
5207 #define MOD1 GDK_MOD1_MASK
5208 #define SHFT GDK_SHIFT_MASK
5210 /* inherent to GTK not all keys will be caught at all times */
5211 /* XXX sort key bindings */
5212 struct key_binding {
5213 char *cmd;
5214 guint mask;
5215 guint use_in_entry;
5216 guint key;
5217 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5218 } keys[] = {
5219 { "cookiejar", MOD1, 0, GDK_j },
5220 { "downloadmgr", MOD1, 0, GDK_d },
5221 { "history", MOD1, 0, GDK_h },
5222 { "print", CTRL, 0, GDK_p },
5223 { "search", 0, 0, GDK_slash },
5224 { "searchb", 0, 0, GDK_question },
5225 { "statustoggle", CTRL, 0, GDK_n },
5226 { "command", 0, 0, GDK_colon },
5227 { "qa", CTRL, 0, GDK_q },
5228 { "restart", MOD1, 0, GDK_q },
5229 { "js toggle", CTRL, 0, GDK_j },
5230 { "cookie toggle", MOD1, 0, GDK_c },
5231 { "togglesrc", CTRL, 0, GDK_s },
5232 { "yankuri", 0, 0, GDK_y },
5233 { "pasteuricur", 0, 0, GDK_p },
5234 { "pasteurinew", 0, 0, GDK_P },
5235 { "toplevel toggle", 0, 0, GDK_F4 },
5236 { "help", 0, 0, GDK_F1 },
5237 { "run_script", MOD1, 0, GDK_r },
5239 /* search */
5240 { "searchnext", 0, 0, GDK_n },
5241 { "searchprevious", 0, 0, GDK_N },
5243 /* focus */
5244 { "focusaddress", 0, 0, GDK_F6 },
5245 { "focussearch", 0, 0, GDK_F7 },
5247 /* hinting */
5248 { "hinting", 0, 0, GDK_f },
5250 /* custom stylesheet */
5251 { "userstyle", 0, 0, GDK_i },
5253 /* navigation */
5254 { "goback", 0, 0, GDK_BackSpace },
5255 { "goback", MOD1, 0, GDK_Left },
5256 { "goforward", SHFT, 0, GDK_BackSpace },
5257 { "goforward", MOD1, 0, GDK_Right },
5258 { "reload", 0, 0, GDK_F5 },
5259 { "reload", CTRL, 0, GDK_r },
5260 { "reload", CTRL, 0, GDK_l },
5261 { "favorites", MOD1, 1, GDK_f },
5263 /* vertical movement */
5264 { "scrolldown", 0, 0, GDK_j },
5265 { "scrolldown", 0, 0, GDK_Down },
5266 { "scrollup", 0, 0, GDK_Up },
5267 { "scrollup", 0, 0, GDK_k },
5268 { "scrollbottom", 0, 0, GDK_G },
5269 { "scrollbottom", 0, 0, GDK_End },
5270 { "scrolltop", 0, 0, GDK_Home },
5271 { "scrollpagedown", 0, 0, GDK_space },
5272 { "scrollpagedown", CTRL, 0, GDK_f },
5273 { "scrollhalfdown", CTRL, 0, GDK_d },
5274 { "scrollpagedown", 0, 0, GDK_Page_Down },
5275 { "scrollpageup", 0, 0, GDK_Page_Up },
5276 { "scrollpageup", CTRL, 0, GDK_b },
5277 { "scrollhalfup", CTRL, 0, GDK_u },
5278 /* horizontal movement */
5279 { "scrollright", 0, 0, GDK_l },
5280 { "scrollright", 0, 0, GDK_Right },
5281 { "scrollleft", 0, 0, GDK_Left },
5282 { "scrollleft", 0, 0, GDK_h },
5283 { "scrollfarright", 0, 0, GDK_dollar },
5284 { "scrollfarleft", 0, 0, GDK_0 },
5286 /* tabs */
5287 { "tabnew", CTRL, 0, GDK_t },
5288 { "999tabnew", CTRL, 0, GDK_T },
5289 { "tabclose", CTRL, 1, GDK_w },
5290 { "tabundoclose", 0, 0, GDK_U },
5291 { "tabnext 1", CTRL, 0, GDK_1 },
5292 { "tabnext 2", CTRL, 0, GDK_2 },
5293 { "tabnext 3", CTRL, 0, GDK_3 },
5294 { "tabnext 4", CTRL, 0, GDK_4 },
5295 { "tabnext 5", CTRL, 0, GDK_5 },
5296 { "tabnext 6", CTRL, 0, GDK_6 },
5297 { "tabnext 7", CTRL, 0, GDK_7 },
5298 { "tabnext 8", CTRL, 0, GDK_8 },
5299 { "tabnext 9", CTRL, 0, GDK_9 },
5300 { "tabfirst", CTRL, 0, GDK_less },
5301 { "tablast", CTRL, 0, GDK_greater },
5302 { "tabprevious", CTRL, 0, GDK_Left },
5303 { "tabnext", CTRL, 0, GDK_Right },
5304 { "focusout", CTRL, 0, GDK_minus },
5305 { "focusin", CTRL, 0, GDK_plus },
5306 { "focusin", CTRL, 0, GDK_equal },
5307 { "focusreset", CTRL, 0, GDK_0 },
5309 /* command aliases (handy when -S flag is used) */
5310 { "promptopen", 0, 0, GDK_F9 },
5311 { "promptopencurrent", 0, 0, GDK_F10 },
5312 { "prompttabnew", 0, 0, GDK_F11 },
5313 { "prompttabnewcurrent",0, 0, GDK_F12 },
5315 TAILQ_HEAD(keybinding_list, key_binding);
5317 void
5318 walk_kb(struct settings *s,
5319 void (*cb)(struct settings *, char *, void *), void *cb_args)
5321 struct key_binding *k;
5322 char str[1024];
5324 if (s == NULL || cb == NULL) {
5325 show_oops(NULL, "walk_kb invalid parameters");
5326 return;
5329 TAILQ_FOREACH(k, &kbl, entry) {
5330 if (k->cmd == NULL)
5331 continue;
5332 str[0] = '\0';
5334 /* sanity */
5335 if (gdk_keyval_name(k->key) == NULL)
5336 continue;
5338 strlcat(str, k->cmd, sizeof str);
5339 strlcat(str, ",", sizeof str);
5341 if (k->mask & GDK_SHIFT_MASK)
5342 strlcat(str, "S-", sizeof str);
5343 if (k->mask & GDK_CONTROL_MASK)
5344 strlcat(str, "C-", sizeof str);
5345 if (k->mask & GDK_MOD1_MASK)
5346 strlcat(str, "M1-", sizeof str);
5347 if (k->mask & GDK_MOD2_MASK)
5348 strlcat(str, "M2-", sizeof str);
5349 if (k->mask & GDK_MOD3_MASK)
5350 strlcat(str, "M3-", sizeof str);
5351 if (k->mask & GDK_MOD4_MASK)
5352 strlcat(str, "M4-", sizeof str);
5353 if (k->mask & GDK_MOD5_MASK)
5354 strlcat(str, "M5-", sizeof str);
5356 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5357 cb(s, str, cb_args);
5361 void
5362 init_keybindings(void)
5364 int i;
5365 struct key_binding *k;
5367 for (i = 0; i < LENGTH(keys); i++) {
5368 k = g_malloc0(sizeof *k);
5369 k->cmd = keys[i].cmd;
5370 k->mask = keys[i].mask;
5371 k->use_in_entry = keys[i].use_in_entry;
5372 k->key = keys[i].key;
5373 TAILQ_INSERT_HEAD(&kbl, k, entry);
5375 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5376 k->cmd ? k->cmd : "unnamed key");
5380 void
5381 keybinding_clearall(void)
5383 struct key_binding *k, *next;
5385 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5386 next = TAILQ_NEXT(k, entry);
5387 if (k->cmd == NULL)
5388 continue;
5390 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5391 k->cmd ? k->cmd : "unnamed key");
5392 TAILQ_REMOVE(&kbl, k, entry);
5393 g_free(k);
5398 keybinding_add(char *cmd, char *key, int use_in_entry)
5400 struct key_binding *k;
5401 guint keyval, mask = 0;
5402 int i;
5404 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5406 /* Keys which are to be used in entry have been prefixed with an
5407 * exclamation mark. */
5408 if (use_in_entry)
5409 key++;
5411 /* find modifier keys */
5412 if (strstr(key, "S-"))
5413 mask |= GDK_SHIFT_MASK;
5414 if (strstr(key, "C-"))
5415 mask |= GDK_CONTROL_MASK;
5416 if (strstr(key, "M1-"))
5417 mask |= GDK_MOD1_MASK;
5418 if (strstr(key, "M2-"))
5419 mask |= GDK_MOD2_MASK;
5420 if (strstr(key, "M3-"))
5421 mask |= GDK_MOD3_MASK;
5422 if (strstr(key, "M4-"))
5423 mask |= GDK_MOD4_MASK;
5424 if (strstr(key, "M5-"))
5425 mask |= GDK_MOD5_MASK;
5427 /* find keyname */
5428 for (i = strlen(key) - 1; i > 0; i--)
5429 if (key[i] == '-')
5430 key = &key[i + 1];
5432 /* validate keyname */
5433 keyval = gdk_keyval_from_name(key);
5434 if (keyval == GDK_VoidSymbol) {
5435 warnx("invalid keybinding name %s", key);
5436 return (1);
5438 /* must run this test too, gtk+ doesn't handle 10 for example */
5439 if (gdk_keyval_name(keyval) == NULL) {
5440 warnx("invalid keybinding name %s", key);
5441 return (1);
5444 /* Remove eventual dupes. */
5445 TAILQ_FOREACH(k, &kbl, entry)
5446 if (k->key == keyval && k->mask == mask) {
5447 TAILQ_REMOVE(&kbl, k, entry);
5448 g_free(k);
5449 break;
5452 /* add keyname */
5453 k = g_malloc0(sizeof *k);
5454 k->cmd = g_strdup(cmd);
5455 k->mask = mask;
5456 k->use_in_entry = use_in_entry;
5457 k->key = keyval;
5459 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5460 k->cmd,
5461 k->mask,
5462 k->use_in_entry,
5463 k->key);
5464 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5465 k->cmd, gdk_keyval_name(keyval));
5467 TAILQ_INSERT_HEAD(&kbl, k, entry);
5469 return (0);
5473 add_kb(struct settings *s, char *entry)
5475 char *kb, *key;
5477 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5479 /* clearall is special */
5480 if (!strcmp(entry, "clearall")) {
5481 keybinding_clearall();
5482 return (0);
5485 kb = strstr(entry, ",");
5486 if (kb == NULL)
5487 return (1);
5488 *kb = '\0';
5489 key = kb + 1;
5491 return (keybinding_add(entry, key, key[0] == '!'));
5494 struct cmd {
5495 char *cmd;
5496 int level;
5497 int (*func)(struct tab *, struct karg *);
5498 int arg;
5499 int type;
5500 } cmds[] = {
5501 { "command", 0, command, ':', 0 },
5502 { "search", 0, command, '/', 0 },
5503 { "searchb", 0, command, '?', 0 },
5504 { "togglesrc", 0, toggle_src, 0, 0 },
5506 /* yanking and pasting */
5507 { "yankuri", 0, yank_uri, 0, 0 },
5508 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5509 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5510 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5512 /* search */
5513 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5514 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5516 /* focus */
5517 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5518 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5520 /* hinting */
5521 { "hinting", 0, hint, 0, 0 },
5523 /* custom stylesheet */
5524 { "userstyle", 0, userstyle, 0, 0 },
5526 /* navigation */
5527 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5528 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5529 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5531 /* vertical movement */
5532 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5533 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5534 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5535 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5536 { "1", 0, move, XT_MOVE_TOP, 0 },
5537 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5538 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5539 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5540 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5541 /* horizontal movement */
5542 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5543 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5544 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5545 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5547 { "favorites", 0, xtp_page_fl, 0, 0 },
5548 { "fav", 0, xtp_page_fl, 0, 0 },
5549 { "favadd", 0, add_favorite, 0, 0 },
5551 { "qall", 0, quit, 0, 0 },
5552 { "quitall", 0, quit, 0, 0 },
5553 { "w", 0, save_tabs, 0, 0 },
5554 { "wq", 0, save_tabs_and_quit, 0, 0 },
5555 { "help", 0, help, 0, 0 },
5556 { "about", 0, about, 0, 0 },
5557 { "stats", 0, stats, 0, 0 },
5558 { "version", 0, about, 0, 0 },
5560 /* js command */
5561 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5562 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5563 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5564 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5565 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5566 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5567 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5568 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5569 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5570 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5571 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5573 /* cookie command */
5574 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5575 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5576 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5577 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5578 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5579 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5580 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5581 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5582 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5583 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5584 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5586 /* toplevel (domain) command */
5587 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5588 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5590 /* cookie jar */
5591 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5593 /* cert command */
5594 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5595 { "save", 1, cert_cmd, XT_SAVE, 0 },
5596 { "show", 1, cert_cmd, XT_SHOW, 0 },
5598 { "ca", 0, ca_cmd, 0, 0 },
5599 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5600 { "dl", 0, xtp_page_dl, 0, 0 },
5601 { "h", 0, xtp_page_hl, 0, 0 },
5602 { "history", 0, xtp_page_hl, 0, 0 },
5603 { "home", 0, go_home, 0, 0 },
5604 { "restart", 0, restart, 0, 0 },
5605 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5606 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5607 { "statustoggle", 0, statustoggle, 0, 0 },
5608 { "run_script", 0, run_page_script, 0, XT_USERARG },
5610 { "print", 0, print_page, 0, 0 },
5612 /* tabs */
5613 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5614 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5615 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5616 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5617 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5618 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5619 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5620 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5621 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5622 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5623 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5624 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5625 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5626 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5627 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5628 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5629 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5630 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5631 { "buffers", 0, buffers, 0, 0 },
5632 { "ls", 0, buffers, 0, 0 },
5633 { "tabs", 0, buffers, 0, 0 },
5635 /* command aliases (handy when -S flag is used) */
5636 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5637 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5638 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5639 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5641 /* settings */
5642 { "set", 0, set, 0, XT_USERARG },
5644 { "fullscreen", 0, fullscreen, 0, 0 },
5645 { "f", 0, fullscreen, 0, 0 },
5647 /* sessions */
5648 { "session", 0, session_cmd, XT_SHOW, 0 },
5649 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5650 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5651 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5652 { "show", 1, session_cmd, XT_SHOW, 0 },
5655 struct {
5656 int index;
5657 int len;
5658 gchar *list[256];
5659 } cmd_status = {-1, 0};
5661 gboolean
5662 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5665 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5666 btn_down = 0;
5668 return (FALSE);
5671 gboolean
5672 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5674 struct karg a;
5676 hide_oops(t);
5677 hide_buffers(t);
5679 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5680 btn_down = 1;
5681 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5682 /* go backward */
5683 a.i = XT_NAV_BACK;
5684 navaction(t, &a);
5686 return (TRUE);
5687 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5688 /* go forward */
5689 a.i = XT_NAV_FORWARD;
5690 navaction(t, &a);
5692 return (TRUE);
5695 return (FALSE);
5698 gboolean
5699 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5701 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5703 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5704 delete_tab(t);
5706 return (FALSE);
5710 * cancel, remove, etc. downloads
5712 void
5713 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5715 struct download find, *d = NULL;
5717 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5719 /* some commands require a valid download id */
5720 if (cmd != XT_XTP_DL_LIST) {
5721 /* lookup download in question */
5722 find.id = id;
5723 d = RB_FIND(download_list, &downloads, &find);
5725 if (d == NULL) {
5726 show_oops(t, "%s: no such download", __func__);
5727 return;
5731 /* decide what to do */
5732 switch (cmd) {
5733 case XT_XTP_DL_CANCEL:
5734 webkit_download_cancel(d->download);
5735 break;
5736 case XT_XTP_DL_REMOVE:
5737 webkit_download_cancel(d->download); /* just incase */
5738 g_object_unref(d->download);
5739 RB_REMOVE(download_list, &downloads, d);
5740 break;
5741 case XT_XTP_DL_LIST:
5742 /* Nothing */
5743 break;
5744 default:
5745 show_oops(t, "%s: unknown command", __func__);
5746 break;
5748 xtp_page_dl(t, NULL);
5752 * Actions on history, only does one thing for now, but
5753 * we provide the function for future actions
5755 void
5756 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5758 struct history *h, *next;
5759 int i = 1;
5761 switch (cmd) {
5762 case XT_XTP_HL_REMOVE:
5763 /* walk backwards, as listed in reverse */
5764 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5765 next = RB_PREV(history_list, &hl, h);
5766 if (id == i) {
5767 RB_REMOVE(history_list, &hl, h);
5768 g_free((gpointer) h->title);
5769 g_free((gpointer) h->uri);
5770 g_free(h);
5771 break;
5773 i++;
5775 break;
5776 case XT_XTP_HL_LIST:
5777 /* Nothing - just xtp_page_hl() below */
5778 break;
5779 default:
5780 show_oops(t, "%s: unknown command", __func__);
5781 break;
5784 xtp_page_hl(t, NULL);
5787 /* remove a favorite */
5788 void
5789 remove_favorite(struct tab *t, int index)
5791 char file[PATH_MAX], *title, *uri = NULL;
5792 char *new_favs, *tmp;
5793 FILE *f;
5794 int i;
5795 size_t len, lineno;
5797 /* open favorites */
5798 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5800 if ((f = fopen(file, "r")) == NULL) {
5801 show_oops(t, "%s: can't open favorites: %s",
5802 __func__, strerror(errno));
5803 return;
5806 /* build a string which will become the new favroites file */
5807 new_favs = g_strdup("");
5809 for (i = 1;;) {
5810 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5811 if (feof(f) || ferror(f))
5812 break;
5813 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5814 if (len == 0) {
5815 free(title);
5816 title = NULL;
5817 continue;
5820 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5821 if (feof(f) || ferror(f)) {
5822 show_oops(t, "%s: can't parse favorites %s",
5823 __func__, strerror(errno));
5824 goto clean;
5828 /* as long as this isn't the one we are deleting add to file */
5829 if (i != index) {
5830 tmp = new_favs;
5831 new_favs = g_strdup_printf("%s%s\n%s\n",
5832 new_favs, title, uri);
5833 g_free(tmp);
5836 free(uri);
5837 uri = NULL;
5838 free(title);
5839 title = NULL;
5840 i++;
5842 fclose(f);
5844 /* write back new favorites file */
5845 if ((f = fopen(file, "w")) == NULL) {
5846 show_oops(t, "%s: can't open favorites: %s",
5847 __func__, strerror(errno));
5848 goto clean;
5851 fwrite(new_favs, strlen(new_favs), 1, f);
5852 fclose(f);
5854 clean:
5855 if (uri)
5856 free(uri);
5857 if (title)
5858 free(title);
5860 g_free(new_favs);
5863 void
5864 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5866 switch (cmd) {
5867 case XT_XTP_FL_LIST:
5868 /* nothing, just the below call to xtp_page_fl() */
5869 break;
5870 case XT_XTP_FL_REMOVE:
5871 remove_favorite(t, arg);
5872 break;
5873 default:
5874 show_oops(t, "%s: invalid favorites command", __func__);
5875 break;
5878 xtp_page_fl(t, NULL);
5881 void
5882 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5884 switch (cmd) {
5885 case XT_XTP_CL_LIST:
5886 /* nothing, just xtp_page_cl() */
5887 break;
5888 case XT_XTP_CL_REMOVE:
5889 remove_cookie(arg);
5890 break;
5891 default:
5892 show_oops(t, "%s: unknown cookie xtp command", __func__);
5893 break;
5896 xtp_page_cl(t, NULL);
5899 /* link an XTP class to it's session key and handler function */
5900 struct xtp_despatch {
5901 uint8_t xtp_class;
5902 char **session_key;
5903 void (*handle_func)(struct tab *, uint8_t, int);
5906 struct xtp_despatch xtp_despatches[] = {
5907 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5908 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5909 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5910 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5911 { XT_XTP_INVALID, NULL, NULL }
5915 * is the url xtp protocol? (xxxt://)
5916 * if so, parse and despatch correct bahvior
5919 parse_xtp_url(struct tab *t, const char *url)
5921 char *dup = NULL, *p, *last;
5922 uint8_t n_tokens = 0;
5923 char *tokens[4] = {NULL, NULL, NULL, ""};
5924 struct xtp_despatch *dsp, *dsp_match = NULL;
5925 uint8_t req_class;
5926 int ret = FALSE;
5929 * tokens array meaning:
5930 * tokens[0] = class
5931 * tokens[1] = session key
5932 * tokens[2] = action
5933 * tokens[3] = optional argument
5936 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5938 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5939 goto clean;
5941 dup = g_strdup(url + strlen(XT_XTP_STR));
5943 /* split out the url */
5944 for ((p = strtok_r(dup, "/", &last)); p;
5945 (p = strtok_r(NULL, "/", &last))) {
5946 if (n_tokens < 4)
5947 tokens[n_tokens++] = p;
5950 /* should be atleast three fields 'class/seskey/command/arg' */
5951 if (n_tokens < 3)
5952 goto clean;
5954 dsp = xtp_despatches;
5955 req_class = atoi(tokens[0]);
5956 while (dsp->xtp_class) {
5957 if (dsp->xtp_class == req_class) {
5958 dsp_match = dsp;
5959 break;
5961 dsp++;
5964 /* did we find one atall? */
5965 if (dsp_match == NULL) {
5966 show_oops(t, "%s: no matching xtp despatch found", __func__);
5967 goto clean;
5970 /* check session key and call despatch function */
5971 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5972 ret = TRUE; /* all is well, this was a valid xtp request */
5973 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5976 clean:
5977 if (dup)
5978 g_free(dup);
5980 return (ret);
5985 void
5986 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5988 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5990 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5992 if (t == NULL) {
5993 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5994 return;
5997 if (uri == NULL) {
5998 show_oops(t, "activate_uri_entry_cb no uri");
5999 return;
6002 uri += strspn(uri, "\t ");
6004 /* if xxxt:// treat specially */
6005 if (parse_xtp_url(t, uri))
6006 return;
6008 /* otherwise continue to load page normally */
6009 load_uri(t, (gchar *)uri);
6010 focus_webview(t);
6013 void
6014 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6016 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6017 char *newuri = NULL;
6018 gchar *enc_search;
6020 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6022 if (t == NULL) {
6023 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6024 return;
6027 if (search_string == NULL) {
6028 show_oops(t, "no search_string");
6029 return;
6032 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6034 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6035 newuri = g_strdup_printf(search_string, enc_search);
6036 g_free(enc_search);
6038 marks_clear(t);
6039 webkit_web_view_load_uri(t->wv, newuri);
6040 focus_webview(t);
6042 if (newuri)
6043 g_free(newuri);
6046 void
6047 check_and_set_cookie(const gchar *uri, struct tab *t)
6049 struct domain *d = NULL;
6050 int es = 0;
6052 if (uri == NULL || t == NULL)
6053 return;
6055 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6056 es = 0;
6057 else
6058 es = 1;
6060 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6061 es ? "enable" : "disable", uri);
6063 g_object_set(G_OBJECT(t->settings),
6064 "enable-html5-local-storage", es, (char *)NULL);
6065 webkit_web_view_set_settings(t->wv, t->settings);
6068 void
6069 check_and_set_js(const gchar *uri, struct tab *t)
6071 struct domain *d = NULL;
6072 int es = 0;
6074 if (uri == NULL || t == NULL)
6075 return;
6077 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6078 es = 0;
6079 else
6080 es = 1;
6082 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6083 es ? "enable" : "disable", uri);
6085 g_object_set(G_OBJECT(t->settings),
6086 "enable-scripts", es, (char *)NULL);
6087 g_object_set(G_OBJECT(t->settings),
6088 "javascript-can-open-windows-automatically", es, (char *)NULL);
6089 webkit_web_view_set_settings(t->wv, t->settings);
6091 button_set_stockid(t->js_toggle,
6092 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6095 gboolean
6096 color_address_bar(gpointer p)
6098 GdkColor color;
6099 struct tab *tt, *t = p;
6100 gchar *col_str = XT_COLOR_YELLOW;
6102 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6104 /* make sure t still exists */
6105 if (t == NULL)
6106 goto done;
6107 TAILQ_FOREACH(tt, &tabs, entry)
6108 if (t == tt)
6109 break;
6110 if (t != tt)
6111 goto done;
6113 switch (load_compare_cert(t, NULL)) {
6114 case CERT_LOCAL:
6115 col_str = XT_COLOR_BLUE;
6116 break;
6117 case CERT_TRUSTED:
6118 col_str = XT_COLOR_GREEN;
6119 break;
6120 case CERT_UNTRUSTED:
6121 col_str = XT_COLOR_YELLOW;
6122 break;
6123 case CERT_BAD:
6124 col_str = XT_COLOR_RED;
6125 break;
6128 gdk_color_parse(col_str, &color);
6129 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6131 if (!strcmp(col_str, XT_COLOR_WHITE))
6132 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6133 else
6134 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6136 col_str = NULL;
6137 done:
6138 return (FALSE /* kill thread */);
6141 void
6142 show_ca_status(struct tab *t, const char *uri)
6144 GdkColor color;
6145 gchar *col_str = XT_COLOR_WHITE;
6147 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6148 ssl_strict_certs, ssl_ca_file, uri);
6150 if (t == NULL)
6151 return;
6153 if (uri == NULL)
6154 goto done;
6155 if (ssl_ca_file == NULL) {
6156 if (g_str_has_prefix(uri, "http://"))
6157 goto done;
6158 if (g_str_has_prefix(uri, "https://")) {
6159 col_str = XT_COLOR_RED;
6160 goto done;
6162 return;
6164 if (g_str_has_prefix(uri, "http://") ||
6165 !g_str_has_prefix(uri, "https://"))
6166 goto done;
6168 /* thread the coloring of the address bar */
6169 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
6170 color_address_bar, t, NULL);
6171 return;
6173 done:
6174 if (col_str) {
6175 gdk_color_parse(col_str, &color);
6176 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6178 if (!strcmp(col_str, XT_COLOR_WHITE))
6179 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6180 else
6181 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6185 void
6186 free_favicon(struct tab *t)
6188 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6189 __func__, t->icon_download, t->icon_request);
6191 if (t->icon_request)
6192 g_object_unref(t->icon_request);
6193 if (t->icon_dest_uri)
6194 g_free(t->icon_dest_uri);
6196 t->icon_request = NULL;
6197 t->icon_dest_uri = NULL;
6200 void
6201 xt_icon_from_name(struct tab *t, gchar *name)
6203 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6204 GTK_ENTRY_ICON_PRIMARY, "text-html");
6205 if (show_url == 0)
6206 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6207 GTK_ENTRY_ICON_PRIMARY, "text-html");
6208 else
6209 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6210 GTK_ENTRY_ICON_PRIMARY, NULL);
6213 void
6214 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6216 GdkPixbuf *pb_scaled;
6218 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6219 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6220 GDK_INTERP_BILINEAR);
6221 else
6222 pb_scaled = pb;
6224 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6225 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6226 if (show_url == 0)
6227 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6228 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6229 else
6230 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6231 GTK_ENTRY_ICON_PRIMARY, NULL);
6233 if (pb_scaled != pb)
6234 g_object_unref(pb_scaled);
6237 void
6238 xt_icon_from_file(struct tab *t, char *file)
6240 GdkPixbuf *pb;
6242 if (g_str_has_prefix(file, "file://"))
6243 file += strlen("file://");
6245 pb = gdk_pixbuf_new_from_file(file, NULL);
6246 if (pb) {
6247 xt_icon_from_pixbuf(t, pb);
6248 g_object_unref(pb);
6249 } else
6250 xt_icon_from_name(t, "text-html");
6253 gboolean
6254 is_valid_icon(char *file)
6256 gboolean valid = 0;
6257 const char *mime_type;
6258 GFileInfo *fi;
6259 GFile *gf;
6261 gf = g_file_new_for_path(file);
6262 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6263 NULL, NULL);
6264 mime_type = g_file_info_get_content_type(fi);
6265 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6266 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6267 g_strcmp0(mime_type, "image/png") == 0 ||
6268 g_strcmp0(mime_type, "image/gif") == 0 ||
6269 g_strcmp0(mime_type, "application/octet-stream") == 0;
6270 g_object_unref(fi);
6271 g_object_unref(gf);
6273 return (valid);
6276 void
6277 set_favicon_from_file(struct tab *t, char *file)
6279 struct stat sb;
6281 if (t == NULL || file == NULL)
6282 return;
6284 if (g_str_has_prefix(file, "file://"))
6285 file += strlen("file://");
6286 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6288 if (!stat(file, &sb)) {
6289 if (sb.st_size == 0 || !is_valid_icon(file)) {
6290 /* corrupt icon so trash it */
6291 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6292 __func__, file);
6293 unlink(file);
6294 /* no need to set icon to default here */
6295 return;
6298 xt_icon_from_file(t, file);
6301 void
6302 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6303 WebKitWebView *wv)
6305 WebKitDownloadStatus status = webkit_download_get_status(download);
6306 struct tab *tt = NULL, *t = NULL;
6309 * find the webview instead of passing in the tab as it could have been
6310 * deleted from underneath us.
6312 TAILQ_FOREACH(tt, &tabs, entry) {
6313 if (tt->wv == wv) {
6314 t = tt;
6315 break;
6318 if (t == NULL)
6319 return;
6321 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6322 __func__, t->tab_id, status);
6324 switch (status) {
6325 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6326 /* -1 */
6327 t->icon_download = NULL;
6328 free_favicon(t);
6329 break;
6330 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6331 /* 0 */
6332 break;
6333 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6334 /* 1 */
6335 break;
6336 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6337 /* 2 */
6338 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6339 __func__, t->tab_id);
6340 t->icon_download = NULL;
6341 free_favicon(t);
6342 break;
6343 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6344 /* 3 */
6346 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6347 __func__, t->icon_dest_uri);
6348 set_favicon_from_file(t, t->icon_dest_uri);
6349 /* these will be freed post callback */
6350 t->icon_request = NULL;
6351 t->icon_download = NULL;
6352 break;
6353 default:
6354 break;
6358 void
6359 abort_favicon_download(struct tab *t)
6361 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6363 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6364 if (t->icon_download) {
6365 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6366 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6367 webkit_download_cancel(t->icon_download);
6368 t->icon_download = NULL;
6369 } else
6370 free_favicon(t);
6371 #endif
6373 xt_icon_from_name(t, "text-html");
6376 void
6377 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6379 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6381 if (uri == NULL || t == NULL)
6382 return;
6384 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6385 /* take icon from WebKitIconDatabase */
6386 GdkPixbuf *pb;
6388 pb = webkit_web_view_get_icon_pixbuf(wv);
6389 if (pb) {
6390 xt_icon_from_pixbuf(t, pb);
6391 g_object_unref(pb);
6392 } else
6393 xt_icon_from_name(t, "text-html");
6394 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6395 /* download icon to cache dir */
6396 gchar *name_hash, file[PATH_MAX];
6397 struct stat sb;
6399 if (t->icon_request) {
6400 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6401 return;
6404 /* check to see if we got the icon in cache */
6405 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6406 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6407 g_free(name_hash);
6409 if (!stat(file, &sb)) {
6410 if (sb.st_size > 0) {
6411 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6412 __func__, file);
6413 set_favicon_from_file(t, file);
6414 return;
6417 /* corrupt icon so trash it */
6418 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6419 __func__, file);
6420 unlink(file);
6423 /* create download for icon */
6424 t->icon_request = webkit_network_request_new(uri);
6425 if (t->icon_request == NULL) {
6426 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6427 __func__, uri);
6428 return;
6431 t->icon_download = webkit_download_new(t->icon_request);
6432 if (t->icon_download == NULL)
6433 return;
6435 /* we have to free icon_dest_uri later */
6436 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6437 webkit_download_set_destination_uri(t->icon_download,
6438 t->icon_dest_uri);
6440 if (webkit_download_get_status(t->icon_download) ==
6441 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6442 g_object_unref(t->icon_request);
6443 g_free(t->icon_dest_uri);
6444 t->icon_request = NULL;
6445 t->icon_dest_uri = NULL;
6446 return;
6449 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6450 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6452 webkit_download_start(t->icon_download);
6453 #endif
6456 void
6457 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6459 const gchar *uri = NULL, *title = NULL;
6460 struct history *h, find;
6461 struct karg a;
6462 GdkColor color;
6464 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6465 webkit_web_view_get_load_status(wview),
6466 get_uri(t) ? get_uri(t) : "NOTHING");
6468 if (t == NULL) {
6469 show_oops(NULL, "notify_load_status_cb invalid parameters");
6470 return;
6473 switch (webkit_web_view_get_load_status(wview)) {
6474 case WEBKIT_LOAD_PROVISIONAL:
6475 /* 0 */
6476 abort_favicon_download(t);
6477 #if GTK_CHECK_VERSION(2, 20, 0)
6478 gtk_widget_show(t->spinner);
6479 gtk_spinner_start(GTK_SPINNER(t->spinner));
6480 #endif
6481 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6483 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6485 /* assume we are a new address */
6486 gdk_color_parse("white", &color);
6487 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6488 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6490 /* take focus if we are visible */
6491 focus_webview(t);
6492 t->focus_wv = 1;
6494 break;
6496 case WEBKIT_LOAD_COMMITTED:
6497 /* 1 */
6498 uri = get_uri(t);
6499 if (uri == NULL)
6500 return;
6501 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6503 if (t->status) {
6504 g_free(t->status);
6505 t->status = NULL;
6507 set_status(t, (char *)uri, XT_STATUS_LOADING);
6509 /* check if js white listing is enabled */
6510 if (enable_cookie_whitelist)
6511 check_and_set_cookie(uri, t);
6512 if (enable_js_whitelist)
6513 check_and_set_js(uri, t);
6515 if (t->styled)
6516 apply_style(t);
6519 /* we know enough to autosave the session */
6520 if (session_autosave) {
6521 a.s = NULL;
6522 save_tabs(t, &a);
6525 show_ca_status(t, uri);
6526 break;
6528 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6529 /* 3 */
6530 break;
6532 case WEBKIT_LOAD_FINISHED:
6533 /* 2 */
6534 uri = get_uri(t);
6535 if (uri == NULL)
6536 return;
6538 if (!strncmp(uri, "http://", strlen("http://")) ||
6539 !strncmp(uri, "https://", strlen("https://")) ||
6540 !strncmp(uri, "file://", strlen("file://"))) {
6541 find.uri = uri;
6542 h = RB_FIND(history_list, &hl, &find);
6543 if (!h) {
6544 title = get_title(t, FALSE);
6545 h = g_malloc(sizeof *h);
6546 h->uri = g_strdup(uri);
6547 h->title = g_strdup(title);
6548 RB_INSERT(history_list, &hl, h);
6549 completion_add_uri(h->uri);
6550 update_history_tabs(NULL);
6554 set_status(t, (char *)uri, XT_STATUS_URI);
6555 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6556 case WEBKIT_LOAD_FAILED:
6557 /* 4 */
6558 #endif
6559 #if GTK_CHECK_VERSION(2, 20, 0)
6560 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6561 gtk_widget_hide(t->spinner);
6562 #endif
6563 default:
6564 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6565 break;
6568 if (t->item)
6569 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6570 else
6571 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6572 webkit_web_view_can_go_back(wview));
6574 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6575 webkit_web_view_can_go_forward(wview));
6578 #if 0
6579 gboolean
6580 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6581 gchar *uri, gpointer web_error,struct tab *t)
6584 * XXX this function is wrong
6585 * it overwrites perfectly good urls with garbage on load errors
6586 * those happen often when popups fail to resolve dns
6588 if (t->tmp_uri)
6589 g_free(t->tmp_uri);
6590 t->tmp_uri = g_strdup(uri);
6591 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6592 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6593 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6594 set_status(t, uri, XT_STATUS_NOTHING);
6596 return (FALSE);
6598 #endif
6600 void
6601 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6603 const gchar *title = NULL, *win_title = NULL;
6605 title = get_title(t, FALSE);
6606 win_title = get_title(t, TRUE);
6607 gtk_label_set_text(GTK_LABEL(t->label), title);
6608 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6609 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6610 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6613 void
6614 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6616 run_script(t, JS_HINTING);
6619 void
6620 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6622 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6623 progress == 100 ? 0 : (double)progress / 100);
6624 if (show_url == 0) {
6625 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6626 progress == 100 ? 0 : (double)progress / 100);
6629 update_statusbar_position(NULL, NULL);
6633 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6634 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6635 WebKitWebPolicyDecision *pd, struct tab *t)
6637 char *uri;
6638 WebKitWebNavigationReason reason;
6639 struct domain *d = NULL;
6641 if (t == NULL) {
6642 show_oops(NULL, "webview_npd_cb invalid parameters");
6643 return (FALSE);
6646 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6647 t->ctrl_click,
6648 webkit_network_request_get_uri(request));
6650 uri = (char *)webkit_network_request_get_uri(request);
6652 /* if this is an xtp url, we don't load anything else */
6653 if (parse_xtp_url(t, uri))
6654 return (TRUE);
6656 if (t->ctrl_click) {
6657 t->ctrl_click = 0;
6658 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6659 webkit_web_policy_decision_ignore(pd);
6660 return (TRUE); /* we made the decission */
6664 * This is a little hairy but it comes down to this:
6665 * when we run in whitelist mode we have to assist the browser in
6666 * opening the URL that it would have opened in a new tab.
6668 reason = webkit_web_navigation_action_get_reason(na);
6669 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6670 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6671 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6672 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6673 load_uri(t, uri);
6674 webkit_web_policy_decision_use(pd);
6675 return (TRUE); /* we made the decision */
6678 return (FALSE);
6681 WebKitWebView *
6682 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6684 struct tab *tt;
6685 struct domain *d = NULL;
6686 const gchar *uri;
6687 WebKitWebView *webview = NULL;
6689 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6690 webkit_web_view_get_uri(wv));
6692 if (tabless) {
6693 /* open in current tab */
6694 webview = t->wv;
6695 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6696 uri = webkit_web_view_get_uri(wv);
6697 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6698 return (NULL);
6700 tt = create_new_tab(NULL, NULL, 1, -1);
6701 webview = tt->wv;
6702 } else if (enable_scripts == 1) {
6703 tt = create_new_tab(NULL, NULL, 1, -1);
6704 webview = tt->wv;
6707 return (webview);
6710 gboolean
6711 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6713 const gchar *uri;
6714 struct domain *d = NULL;
6716 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6718 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6719 uri = webkit_web_view_get_uri(wv);
6720 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6721 return (FALSE);
6723 delete_tab(t);
6724 } else if (enable_scripts == 1)
6725 delete_tab(t);
6727 return (TRUE);
6731 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6733 /* we can not eat the event without throwing gtk off so defer it */
6735 /* catch middle click */
6736 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6737 t->ctrl_click = 1;
6738 goto done;
6741 /* catch ctrl click */
6742 if (e->type == GDK_BUTTON_RELEASE &&
6743 CLEAN(e->state) == GDK_CONTROL_MASK)
6744 t->ctrl_click = 1;
6745 else
6746 t->ctrl_click = 0;
6747 done:
6748 return (XT_CB_PASSTHROUGH);
6752 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6754 struct mime_type *m;
6756 m = find_mime_type(mime_type);
6757 if (m == NULL)
6758 return (1);
6759 if (m->mt_download)
6760 return (1);
6762 switch (fork()) {
6763 case -1:
6764 show_oops(t, "can't fork mime handler");
6765 return (1);
6766 /* NOTREACHED */
6767 case 0:
6768 break;
6769 default:
6770 return (0);
6773 /* child */
6774 execlp(m->mt_action, m->mt_action,
6775 webkit_network_request_get_uri(request), (void *)NULL);
6777 _exit(0);
6779 /* NOTREACHED */
6780 return (0);
6783 const gchar *
6784 get_mime_type(char *file)
6786 const char *mime_type;
6787 GFileInfo *fi;
6788 GFile *gf;
6790 if (g_str_has_prefix(file, "file://"))
6791 file += strlen("file://");
6793 gf = g_file_new_for_path(file);
6794 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6795 NULL, NULL);
6796 mime_type = g_file_info_get_content_type(fi);
6797 g_object_unref(fi);
6798 g_object_unref(gf);
6800 return (mime_type);
6804 run_download_mimehandler(char *mime_type, char *file)
6806 struct mime_type *m;
6808 m = find_mime_type(mime_type);
6809 if (m == NULL)
6810 return (1);
6812 switch (fork()) {
6813 case -1:
6814 show_oops(NULL, "can't fork download mime handler");
6815 return (1);
6816 /* NOTREACHED */
6817 case 0:
6818 break;
6819 default:
6820 return (0);
6823 /* child */
6824 if (g_str_has_prefix(file, "file://"))
6825 file += strlen("file://");
6826 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6828 _exit(0);
6830 /* NOTREACHED */
6831 return (0);
6834 void
6835 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6836 WebKitWebView *wv)
6838 WebKitDownloadStatus status;
6839 const gchar *file = NULL, *mime = NULL;
6841 if (download == NULL)
6842 return;
6843 status = webkit_download_get_status(download);
6844 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6845 return;
6847 file = webkit_download_get_destination_uri(download);
6848 if (file == NULL)
6849 return;
6850 mime = get_mime_type((char *)file);
6851 if (mime == NULL)
6852 return;
6854 run_download_mimehandler((char *)mime, (char *)file);
6858 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6859 WebKitNetworkRequest *request, char *mime_type,
6860 WebKitWebPolicyDecision *decision, struct tab *t)
6862 if (t == NULL) {
6863 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6864 return (FALSE);
6867 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6868 t->tab_id, mime_type);
6870 if (run_mimehandler(t, mime_type, request) == 0) {
6871 webkit_web_policy_decision_ignore(decision);
6872 focus_webview(t);
6873 return (TRUE);
6876 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6877 webkit_web_policy_decision_download(decision);
6878 return (TRUE);
6881 return (FALSE);
6885 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6886 struct tab *t)
6888 struct stat sb;
6889 const gchar *suggested_name;
6890 gchar *filename = NULL;
6891 char *uri = NULL;
6892 struct download *download_entry;
6893 int i, ret = TRUE;
6895 if (wk_download == NULL || t == NULL) {
6896 show_oops(NULL, "%s invalid parameters", __func__);
6897 return (FALSE);
6900 suggested_name = webkit_download_get_suggested_filename(wk_download);
6901 if (suggested_name == NULL)
6902 return (FALSE); /* abort download */
6904 i = 0;
6905 do {
6906 if (filename) {
6907 g_free(filename);
6908 filename = NULL;
6910 if (i) {
6911 g_free(uri);
6912 uri = NULL;
6913 filename = g_strdup_printf("%d%s", i, suggested_name);
6915 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6916 filename : suggested_name);
6917 i++;
6918 } while (!stat(uri + strlen("file://"), &sb));
6920 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6921 "local %s\n", __func__, t->tab_id, filename, uri);
6923 webkit_download_set_destination_uri(wk_download, uri);
6925 if (webkit_download_get_status(wk_download) ==
6926 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6927 show_oops(t, "%s: download failed to start", __func__);
6928 ret = FALSE;
6929 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6930 } else {
6931 /* connect "download first" mime handler */
6932 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6933 G_CALLBACK(download_status_changed_cb), NULL);
6935 download_entry = g_malloc(sizeof(struct download));
6936 download_entry->download = wk_download;
6937 download_entry->tab = t;
6938 download_entry->id = next_download_id++;
6939 RB_INSERT(download_list, &downloads, download_entry);
6940 /* get from history */
6941 g_object_ref(wk_download);
6942 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6943 show_oops(t, "Download of '%s' started...",
6944 basename((char *)webkit_download_get_destination_uri(wk_download)));
6947 if (uri)
6948 g_free(uri);
6950 if (filename)
6951 g_free(filename);
6953 /* sync other download manager tabs */
6954 update_download_tabs(NULL);
6957 * NOTE: never redirect/render the current tab before this
6958 * function returns. This will cause the download to never start.
6960 return (ret); /* start download */
6963 void
6964 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6966 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6968 if (t == NULL) {
6969 show_oops(NULL, "webview_hover_cb");
6970 return;
6973 if (uri)
6974 set_status(t, uri, XT_STATUS_LINK);
6975 else {
6976 if (t->status)
6977 set_status(t, t->status, XT_STATUS_NOTHING);
6982 mark(struct tab *t, struct karg *arg)
6984 char mark;
6985 int index;
6987 mark = arg->s[1];
6988 if ((index = marktoindex(mark)) == -1)
6989 return -1;
6991 if (arg->i == XT_MARK_SET)
6992 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6993 else if (arg->i == XT_MARK_GOTO) {
6994 if (t->mark[index] == XT_INVALID_MARK) {
6995 show_oops(t, "mark '%c' does not exist", mark);
6996 return -1;
6998 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6999 something changes the document size */
7000 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7003 return 0;
7006 void
7007 marks_clear(struct tab *t)
7009 int i;
7011 for (i = 0; i < LENGTH(t->mark); i++)
7012 t->mark[i] = XT_INVALID_MARK;
7016 qmarks_load(void)
7018 char file[PATH_MAX];
7019 char *line = NULL, *p, mark;
7020 int index, i;
7021 FILE *f;
7022 size_t linelen;
7024 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7025 if ((f = fopen(file, "r+")) == NULL) {
7026 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7027 return (1);
7030 for (i = 1; ; i++) {
7031 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7032 if (feof(f) || ferror(f))
7033 break;
7034 if (strlen(line) == 0 || line[0] == '#') {
7035 free(line);
7036 line = NULL;
7037 continue;
7040 p = strtok(line, " \t");
7042 if (p == NULL || strlen(p) != 1 ||
7043 (index = marktoindex(*p)) == -1) {
7044 warnx("corrupt quickmarks file, line %d", i);
7045 break;
7048 mark = *p;
7049 p = strtok(NULL, " \t");
7050 if (qmarks[index] != NULL)
7051 g_free(qmarks[index]);
7052 qmarks[index] = g_strdup(p);
7055 fclose(f);
7057 return (0);
7061 qmarks_save(void)
7063 char file[PATH_MAX];
7064 int i;
7065 FILE *f;
7067 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7068 if ((f = fopen(file, "r+")) == NULL) {
7069 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7070 return (1);
7073 for (i = 0; i < XT_NOMARKS; i++)
7074 if (qmarks[i] != NULL)
7075 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7077 fclose(f);
7079 return (0);
7083 qmark(struct tab *t, struct karg *arg)
7085 char mark;
7086 int index;
7088 mark = arg->s[strlen(arg->s)-1];
7089 index = marktoindex(mark);
7090 if (index == -1)
7091 return (-1);
7093 switch (arg->i) {
7094 case XT_QMARK_SET:
7095 if (qmarks[index] != NULL)
7096 g_free(qmarks[index]);
7098 qmarks_load(); /* sync if multiple instances */
7099 qmarks[index] = g_strdup(get_uri(t));
7100 qmarks_save();
7101 break;
7102 case XT_QMARK_OPEN:
7103 if (qmarks[index] != NULL)
7104 load_uri(t, qmarks[index]);
7105 else {
7106 show_oops(t, "quickmark \"%c\" does not exist",
7107 mark);
7108 return (-1);
7110 break;
7111 case XT_QMARK_TAB:
7112 if (qmarks[index] != NULL)
7113 create_new_tab(qmarks[index], NULL, 1, -1);
7114 else {
7115 show_oops(t, "quickmark \"%c\" does not exist",
7116 mark);
7117 return (-1);
7119 break;
7122 return (0);
7126 go_up(struct tab *t, struct karg *args)
7128 int levels;
7129 char *uri;
7130 char *tmp;
7132 levels = atoi(args->s);
7133 if (levels == 0)
7134 levels = 1;
7136 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7137 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7138 return 1;
7139 tmp += strlen(XT_PROTO_DELIM);
7141 /* if an uri starts with a slash, leave it alone (for file:///) */
7142 if (tmp[0] == '/')
7143 tmp++;
7145 while (levels--) {
7146 char *p;
7148 p = strrchr(tmp, '/');
7149 if (p != NULL)
7150 *p = '\0';
7151 else
7152 break;
7155 load_uri(t, uri);
7156 g_free(uri);
7158 return (0);
7162 gototab(struct tab *t, struct karg *args)
7164 int tab;
7165 struct karg arg = {0, NULL, -1};
7167 tab = atoi(args->s);
7169 arg.i = XT_TAB_NEXT;
7170 arg.precount = tab;
7172 movetab(t, &arg);
7174 return (0);
7178 zoom_amount(struct tab *t, struct karg *arg)
7180 struct karg narg = {0, NULL, -1};
7182 narg.i = atoi(arg->s);
7183 resizetab(t, &narg);
7185 return 0;
7189 flip_colon(struct tab *t, struct karg *arg)
7191 struct karg narg = {0, NULL, -1};
7192 char *p;
7194 if (t == NULL || arg == NULL)
7195 return (1);
7197 p = strstr(arg->s, ":");
7198 if (p == NULL)
7199 return (1);
7200 *p = '\0';
7202 narg.i = ':';
7203 narg.s = arg->s;
7204 command(t, &narg);
7206 return (0);
7209 /* buffer commands receive the regex that triggered them in arg.s */
7210 char bcmd[XT_BUFCMD_SZ];
7211 struct buffercmd {
7212 char *regex;
7213 int precount;
7214 #define XT_PRE_NO (0)
7215 #define XT_PRE_YES (1)
7216 #define XT_PRE_MAYBE (2)
7217 char *cmd;
7218 int (*func)(struct tab *, struct karg *);
7219 int arg;
7220 regex_t cregex;
7221 } buffercmds[] = {
7222 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7223 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7224 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7225 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7226 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7227 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7228 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7229 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7230 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7231 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7232 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7233 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7234 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7235 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7236 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7237 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7238 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7239 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7242 void
7243 buffercmd_init(void)
7245 int i;
7247 for (i = 0; i < LENGTH(buffercmds); i++)
7248 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7249 REG_EXTENDED | REG_NOSUB))
7250 startpage_add("invalid buffercmd regex %s",
7251 buffercmds[i].regex);
7254 void
7255 buffercmd_abort(struct tab *t)
7257 int i;
7259 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7260 for (i = 0; i < LENGTH(bcmd); i++)
7261 bcmd[i] = '\0';
7263 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7264 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7267 void
7268 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7270 struct karg arg = {0, NULL, -1};
7272 arg.i = cmd->arg;
7273 arg.s = g_strdup(bcmd);
7275 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7276 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7277 cmd->func(t, &arg);
7279 if (arg.s)
7280 g_free(arg.s);
7282 buffercmd_abort(t);
7285 gboolean
7286 buffercmd_addkey(struct tab *t, guint keyval)
7288 int i, c, match ;
7289 char s[XT_BUFCMD_SZ];
7291 if (keyval == GDK_Escape) {
7292 buffercmd_abort(t);
7293 return (XT_CB_HANDLED);
7296 /* key with modifier or non-ascii character */
7297 if (!isascii(keyval))
7298 return (XT_CB_PASSTHROUGH);
7300 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7301 "to buffer \"%s\"\n", keyval, bcmd);
7303 for (i = 0; i < LENGTH(bcmd); i++)
7304 if (bcmd[i] == '\0') {
7305 bcmd[i] = keyval;
7306 break;
7309 /* buffer full, ignore input */
7310 if (i >= LENGTH(bcmd) -1) {
7311 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7312 buffercmd_abort(t);
7313 return (XT_CB_HANDLED);
7316 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7318 /* find exact match */
7319 for (i = 0; i < LENGTH(buffercmds); i++)
7320 if (regexec(&buffercmds[i].cregex, bcmd,
7321 (size_t) 0, NULL, 0) == 0) {
7322 buffercmd_execute(t, &buffercmds[i]);
7323 goto done;
7326 /* find non exact matches to see if we need to abort ot not */
7327 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7328 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7329 c = -1;
7330 s[0] = '\0';
7331 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7332 if (isdigit(bcmd[0])) {
7333 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7334 continue;
7335 } else {
7336 c = 0;
7337 if (sscanf(bcmd, "%s", s) == 0)
7338 continue;
7340 } else if (buffercmds[i].precount == XT_PRE_YES) {
7341 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7342 continue;
7343 } else {
7344 if (sscanf(bcmd, "%s", s) == 0)
7345 continue;
7347 if (c == -1 && buffercmds[i].precount)
7348 continue;
7349 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7350 match++;
7352 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7353 i, match, buffercmds[i].cmd, c, s);
7355 if (match == 0) {
7356 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7357 buffercmd_abort(t);
7360 done:
7361 return (XT_CB_HANDLED);
7364 gboolean
7365 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7367 struct key_binding *k;
7369 /* handle keybindings if buffercmd is empty.
7370 if not empty, allow commands like C-n */
7371 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7372 TAILQ_FOREACH(k, &kbl, entry)
7373 if (e->keyval == k->key
7374 && (entry ? k->use_in_entry : 1)) {
7375 if (k->mask == 0) {
7376 if ((e->state & (CTRL | MOD1)) == 0)
7377 return (cmd_execute(t, k->cmd));
7378 } else if ((e->state & k->mask) == k->mask) {
7379 return (cmd_execute(t, k->cmd));
7383 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7384 return buffercmd_addkey(t, e->keyval);
7386 return (XT_CB_PASSTHROUGH);
7390 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7392 char s[2], buf[128];
7393 const char *errstr = NULL;
7395 /* don't use w directly; use t->whatever instead */
7397 if (t == NULL) {
7398 show_oops(NULL, "wv_keypress_after_cb");
7399 return (XT_CB_PASSTHROUGH);
7402 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7403 e->keyval, e->state, t);
7405 if (t->hints_on) {
7406 /* ESC */
7407 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7408 disable_hints(t);
7409 return (XT_CB_HANDLED);
7412 /* RETURN */
7413 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7414 if (errstr) {
7415 /* we have a string */
7416 } else {
7417 /* we have a number */
7418 snprintf(buf, sizeof buf,
7419 "vimprobable_fire(%s)", t->hint_num);
7420 run_script(t, buf);
7422 disable_hints(t);
7425 /* BACKSPACE */
7426 /* XXX unfuck this */
7427 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7428 if (t->hint_mode == XT_HINT_NUMERICAL) {
7429 /* last input was numerical */
7430 int l;
7431 l = strlen(t->hint_num);
7432 if (l > 0) {
7433 l--;
7434 if (l == 0) {
7435 disable_hints(t);
7436 enable_hints(t);
7437 } else {
7438 t->hint_num[l] = '\0';
7439 goto num;
7442 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7443 /* last input was alphanumerical */
7444 int l;
7445 l = strlen(t->hint_buf);
7446 if (l > 0) {
7447 l--;
7448 if (l == 0) {
7449 disable_hints(t);
7450 enable_hints(t);
7451 } else {
7452 t->hint_buf[l] = '\0';
7453 goto anum;
7456 } else {
7457 /* bogus */
7458 disable_hints(t);
7462 /* numerical input */
7463 if (CLEAN(e->state) == 0 &&
7464 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7465 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7466 snprintf(s, sizeof s, "%c", e->keyval);
7467 strlcat(t->hint_num, s, sizeof t->hint_num);
7468 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7469 t->hint_num);
7470 num:
7471 if (errstr) {
7472 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7473 "invalid link number\n");
7474 disable_hints(t);
7475 } else {
7476 snprintf(buf, sizeof buf,
7477 "vimprobable_update_hints(%s)",
7478 t->hint_num);
7479 t->hint_mode = XT_HINT_NUMERICAL;
7480 run_script(t, buf);
7483 /* empty the counter buffer */
7484 bzero(t->hint_buf, sizeof t->hint_buf);
7485 return (XT_CB_HANDLED);
7488 /* alphanumerical input */
7489 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7490 e->keyval <= GDK_z) ||
7491 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7492 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7493 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7494 e->keyval <= GDK_9) ||
7495 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7496 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7497 snprintf(s, sizeof s, "%c", e->keyval);
7498 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7499 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7500 " %s\n", t->hint_buf);
7501 anum:
7502 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7503 run_script(t, buf);
7505 snprintf(buf, sizeof buf,
7506 "vimprobable_show_hints('%s')", t->hint_buf);
7507 t->hint_mode = XT_HINT_ALPHANUM;
7508 run_script(t, buf);
7510 /* empty the counter buffer */
7511 bzero(t->hint_num, sizeof t->hint_num);
7512 return (XT_CB_HANDLED);
7515 return (XT_CB_HANDLED);
7516 } else {
7517 /* prefix input*/
7518 snprintf(s, sizeof s, "%c", e->keyval);
7519 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7520 cmd_prefix = 10 * cmd_prefix + atoi(s);
7523 return (handle_keypress(t, e, 0));
7527 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7529 hide_oops(t);
7531 /* Hide buffers, if they are visible, with escape. */
7532 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7533 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7534 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7535 hide_buffers(t);
7536 return (XT_CB_HANDLED);
7539 return (XT_CB_PASSTHROUGH);
7542 gboolean
7543 search_continue(struct tab *t)
7545 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7546 gboolean rv = FALSE;
7548 if (c[0] == ':')
7549 goto done;
7550 if (strlen(c) == 1) {
7551 webkit_web_view_unmark_text_matches(t->wv);
7552 goto done;
7555 if (c[0] == '/')
7556 t->search_forward = TRUE;
7557 else if (c[0] == '?')
7558 t->search_forward = FALSE;
7559 else
7560 goto done;
7562 rv = TRUE;
7563 done:
7564 return (rv);
7567 gboolean
7568 search_cb(struct tab *t)
7570 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7571 GdkColor color;
7573 if (search_continue(t) == FALSE)
7574 goto done;
7576 /* search */
7577 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7578 TRUE) == FALSE) {
7579 /* not found, mark red */
7580 gdk_color_parse(XT_COLOR_RED, &color);
7581 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7582 /* unmark and remove selection */
7583 webkit_web_view_unmark_text_matches(t->wv);
7584 /* my kingdom for a way to unselect text in webview */
7585 } else {
7586 /* found, highlight all */
7587 webkit_web_view_unmark_text_matches(t->wv);
7588 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7589 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7590 gdk_color_parse(XT_COLOR_WHITE, &color);
7591 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7593 done:
7594 t->search_id = 0;
7595 return (FALSE);
7599 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7601 const gchar *c = gtk_entry_get_text(w);
7603 if (t == NULL) {
7604 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7605 return (XT_CB_PASSTHROUGH);
7608 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7609 e->keyval, e->state, t);
7611 if (search_continue(t) == FALSE)
7612 goto done;
7614 /* if search length is > 4 then no longer play timeout games */
7615 if (strlen(c) > 4) {
7616 if (t->search_id) {
7617 g_source_remove(t->search_id);
7618 t->search_id = 0;
7620 search_cb(t);
7621 goto done;
7624 /* reestablish a new timer if the user types fast */
7625 if (t->search_id)
7626 g_source_remove(t->search_id);
7627 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7629 done:
7630 return (XT_CB_PASSTHROUGH);
7633 gboolean
7634 match_uri(const gchar *uri, const gchar *key) {
7635 gchar *voffset;
7636 size_t len;
7637 gboolean match = FALSE;
7639 len = strlen(key);
7641 if (!strncmp(key, uri, len))
7642 match = TRUE;
7643 else {
7644 voffset = strstr(uri, "/") + 2;
7645 if (!strncmp(key, voffset, len))
7646 match = TRUE;
7647 else if (g_str_has_prefix(voffset, "www.")) {
7648 voffset = voffset + strlen("www.");
7649 if (!strncmp(key, voffset, len))
7650 match = TRUE;
7654 return (match);
7657 void
7658 cmd_getlist(int id, char *key)
7660 int i, dep, c = 0;
7661 struct history *h;
7663 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7664 RB_FOREACH_REVERSE(h, history_list, &hl)
7665 if (match_uri(h->uri, key)) {
7666 cmd_status.list[c] = (char *)h->uri;
7667 if (++c > 255)
7668 break;
7671 cmd_status.len = c;
7672 return;
7675 dep = (id == -1) ? 0 : cmds[id].level + 1;
7677 for (i = id + 1; i < LENGTH(cmds); i++) {
7678 if (cmds[i].level < dep)
7679 break;
7680 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7681 strlen(key)))
7682 cmd_status.list[c++] = cmds[i].cmd;
7686 cmd_status.len = c;
7689 char *
7690 cmd_getnext(int dir)
7692 cmd_status.index += dir;
7694 if (cmd_status.index < 0)
7695 cmd_status.index = cmd_status.len - 1;
7696 else if (cmd_status.index >= cmd_status.len)
7697 cmd_status.index = 0;
7699 return cmd_status.list[cmd_status.index];
7703 cmd_tokenize(char *s, char *tokens[])
7705 int i = 0;
7706 char *tok, *last;
7707 size_t len = strlen(s);
7708 bool blank;
7710 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7711 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7712 tok = strtok_r(NULL, " ", &last), i++)
7713 tokens[i] = tok;
7715 if (blank && i < 3)
7716 tokens[i++] = "";
7718 return (i);
7721 void
7722 cmd_complete(struct tab *t, char *str, int dir)
7724 GtkEntry *w = GTK_ENTRY(t->cmd);
7725 int i, j, levels, c = 0, dep = 0, parent = -1;
7726 int matchcount = 0;
7727 char *tok, *match, *s = g_strdup(str);
7728 char *tokens[3];
7729 char res[XT_MAX_URL_LENGTH + 32] = ":";
7730 char *sc = s;
7732 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7734 /* copy prefix*/
7735 for (i = 0; isdigit(s[i]); i++)
7736 res[i + 1] = s[i];
7738 for (; isspace(s[i]); i++)
7739 res[i + 1] = s[i];
7741 s += i;
7743 levels = cmd_tokenize(s, tokens);
7745 for (i = 0; i < levels - 1; i++) {
7746 tok = tokens[i];
7747 matchcount = 0;
7748 for (j = c; j < LENGTH(cmds); j++) {
7749 if (cmds[j].level < dep)
7750 break;
7751 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7752 strlen(tok))) {
7753 matchcount++;
7754 c = j + 1;
7755 if (strlen(tok) == strlen(cmds[j].cmd)) {
7756 matchcount = 1;
7757 break;
7762 if (matchcount == 1) {
7763 strlcat(res, tok, sizeof res);
7764 strlcat(res, " ", sizeof res);
7765 dep++;
7766 } else {
7767 g_free(sc);
7768 return;
7771 parent = c - 1;
7774 if (cmd_status.index == -1)
7775 cmd_getlist(parent, tokens[i]);
7777 if (cmd_status.len > 0) {
7778 match = cmd_getnext(dir);
7779 strlcat(res, match, sizeof res);
7780 gtk_entry_set_text(w, res);
7781 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7784 g_free(sc);
7787 gboolean
7788 cmd_execute(struct tab *t, char *str)
7790 struct cmd *cmd = NULL;
7791 char *tok, *last, *s = g_strdup(str), *sc;
7792 char prefixstr[4];
7793 int j, len, c = 0, dep = 0, matchcount = 0;
7794 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7795 struct karg arg = {0, NULL, -1};
7797 sc = s;
7799 /* copy prefix*/
7800 for (j = 0; j<3 && isdigit(s[j]); j++)
7801 prefixstr[j]=s[j];
7803 prefixstr[j]='\0';
7805 s += j;
7806 while (isspace(s[0]))
7807 s++;
7809 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7810 prefix = atoi(prefixstr);
7811 else
7812 s = sc;
7814 for (tok = strtok_r(s, " ", &last); tok;
7815 tok = strtok_r(NULL, " ", &last)) {
7816 matchcount = 0;
7817 for (j = c; j < LENGTH(cmds); j++) {
7818 if (cmds[j].level < dep)
7819 break;
7820 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7821 strlen(tok);
7822 if (cmds[j].level == dep &&
7823 !strncmp(tok, cmds[j].cmd, len)) {
7824 matchcount++;
7825 c = j + 1;
7826 cmd = &cmds[j];
7827 if (len == strlen(cmds[j].cmd)) {
7828 matchcount = 1;
7829 break;
7833 if (matchcount == 1) {
7834 if (cmd->type > 0)
7835 goto execute_cmd;
7836 dep++;
7837 } else {
7838 show_oops(t, "Invalid command: %s", str);
7839 goto done;
7842 execute_cmd:
7843 arg.i = cmd->arg;
7845 if (prefix != -1)
7846 arg.precount = prefix;
7847 else if (cmd_prefix > 0)
7848 arg.precount = cmd_prefix;
7850 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7851 show_oops(t, "No prefix allowed: %s", str);
7852 goto done;
7854 if (cmd->type > 1)
7855 arg.s = last ? g_strdup(last) : g_strdup("");
7856 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7857 arg.precount = atoi(arg.s);
7858 if (arg.precount <= 0) {
7859 if (arg.s[0] == '0')
7860 show_oops(t, "Zero count");
7861 else
7862 show_oops(t, "Trailing characters");
7863 goto done;
7867 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7868 __func__, arg.precount, arg.s);
7870 cmd->func(t, &arg);
7872 rv = XT_CB_HANDLED;
7873 done:
7874 if (j > 0)
7875 cmd_prefix = 0;
7876 g_free(sc);
7877 if (arg.s)
7878 g_free(arg.s);
7880 return (rv);
7884 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7886 if (t == NULL) {
7887 show_oops(NULL, "entry_key_cb invalid parameters");
7888 return (XT_CB_PASSTHROUGH);
7891 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7892 e->keyval, e->state, t);
7894 hide_oops(t);
7896 if (e->keyval == GDK_Escape) {
7897 /* don't use focus_webview(t) because we want to type :cmds */
7898 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7901 return (handle_keypress(t, e, 1));
7905 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7907 int rv = XT_CB_HANDLED;
7908 const gchar *c = gtk_entry_get_text(w);
7910 if (t == NULL) {
7911 show_oops(NULL, "cmd_keypress_cb parameters");
7912 return (XT_CB_PASSTHROUGH);
7915 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7916 e->keyval, e->state, t);
7918 /* sanity */
7919 if (c == NULL)
7920 e->keyval = GDK_Escape;
7921 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7922 e->keyval = GDK_Escape;
7924 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7925 e->keyval != GDK_ISO_Left_Tab)
7926 cmd_status.index = -1;
7928 switch (e->keyval) {
7929 case GDK_Tab:
7930 if (c[0] == ':')
7931 cmd_complete(t, (char *)&c[1], 1);
7932 goto done;
7933 case GDK_ISO_Left_Tab:
7934 if (c[0] == ':')
7935 cmd_complete(t, (char *)&c[1], -1);
7937 goto done;
7938 case GDK_BackSpace:
7939 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7940 break;
7941 /* FALLTHROUGH */
7942 case GDK_Escape:
7943 hide_cmd(t);
7944 focus_webview(t);
7946 /* cancel search */
7947 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7948 webkit_web_view_unmark_text_matches(t->wv);
7949 goto done;
7952 rv = XT_CB_PASSTHROUGH;
7953 done:
7954 return (rv);
7958 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7960 if (t == NULL) {
7961 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7962 return (XT_CB_PASSTHROUGH);
7964 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7966 hide_cmd(t);
7967 hide_oops(t);
7969 if (show_url == 0 || t->focus_wv)
7970 focus_webview(t);
7971 else
7972 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7974 return (XT_CB_PASSTHROUGH);
7977 void
7978 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7980 char *s;
7981 const gchar *c = gtk_entry_get_text(entry);
7983 if (t == NULL) {
7984 show_oops(NULL, "cmd_activate_cb invalid parameters");
7985 return;
7988 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7990 hide_cmd(t);
7992 /* sanity */
7993 if (c == NULL)
7994 goto done;
7995 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7996 goto done;
7997 if (strlen(c) < 2)
7998 goto done;
7999 s = (char *)&c[1];
8001 if (c[0] == '/' || c[0] == '?') {
8002 /* see if there is a timer pending */
8003 if (t->search_id) {
8004 g_source_remove(t->search_id);
8005 t->search_id = 0;
8006 search_cb(t);
8009 if (t->search_text) {
8010 g_free(t->search_text);
8011 t->search_text = NULL;
8014 t->search_text = g_strdup(s);
8015 if (global_search)
8016 g_free(global_search);
8017 global_search = g_strdup(s);
8018 t->search_forward = c[0] == '/';
8020 goto done;
8023 cmd_execute(t, s);
8025 done:
8026 return;
8029 void
8030 backward_cb(GtkWidget *w, struct tab *t)
8032 struct karg a;
8034 if (t == NULL) {
8035 show_oops(NULL, "backward_cb invalid parameters");
8036 return;
8039 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8041 a.i = XT_NAV_BACK;
8042 navaction(t, &a);
8045 void
8046 forward_cb(GtkWidget *w, struct tab *t)
8048 struct karg a;
8050 if (t == NULL) {
8051 show_oops(NULL, "forward_cb invalid parameters");
8052 return;
8055 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8057 a.i = XT_NAV_FORWARD;
8058 navaction(t, &a);
8061 void
8062 home_cb(GtkWidget *w, struct tab *t)
8064 if (t == NULL) {
8065 show_oops(NULL, "home_cb invalid parameters");
8066 return;
8069 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8071 load_uri(t, home);
8074 void
8075 stop_cb(GtkWidget *w, struct tab *t)
8077 WebKitWebFrame *frame;
8079 if (t == NULL) {
8080 show_oops(NULL, "stop_cb invalid parameters");
8081 return;
8084 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8086 frame = webkit_web_view_get_main_frame(t->wv);
8087 if (frame == NULL) {
8088 show_oops(t, "stop_cb: no frame");
8089 return;
8092 webkit_web_frame_stop_loading(frame);
8093 abort_favicon_download(t);
8096 void
8097 setup_webkit(struct tab *t)
8099 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8100 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8101 FALSE, (char *)NULL);
8102 else
8103 warnx("webkit does not have \"enable-dns-prefetching\" property");
8104 g_object_set(G_OBJECT(t->settings),
8105 "user-agent", t->user_agent, (char *)NULL);
8106 g_object_set(G_OBJECT(t->settings),
8107 "enable-scripts", enable_scripts, (char *)NULL);
8108 g_object_set(G_OBJECT(t->settings),
8109 "enable-plugins", enable_plugins, (char *)NULL);
8110 g_object_set(G_OBJECT(t->settings),
8111 "javascript-can-open-windows-automatically", enable_scripts,
8112 (char *)NULL);
8113 g_object_set(G_OBJECT(t->settings),
8114 "enable-html5-database", FALSE, (char *)NULL);
8115 g_object_set(G_OBJECT(t->settings),
8116 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8117 g_object_set(G_OBJECT(t->settings),
8118 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8119 g_object_set(G_OBJECT(t->settings),
8120 "spell_checking_languages", spell_check_languages, (char *)NULL);
8121 g_object_set(G_OBJECT(t->wv),
8122 "full-content-zoom", TRUE, (char *)NULL);
8124 webkit_web_view_set_settings(t->wv, t->settings);
8127 gboolean
8128 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8130 struct tab *ti, *t = NULL;
8131 gdouble view_size, value, max;
8132 gchar *position;
8134 TAILQ_FOREACH(ti, &tabs, entry)
8135 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8136 t = ti;
8137 break;
8140 if (t == NULL)
8141 return FALSE;
8143 if (adjustment == NULL)
8144 adjustment = gtk_scrolled_window_get_vadjustment(
8145 GTK_SCROLLED_WINDOW(t->browser_win));
8147 view_size = gtk_adjustment_get_page_size(adjustment);
8148 value = gtk_adjustment_get_value(adjustment);
8149 max = gtk_adjustment_get_upper(adjustment) - view_size;
8151 if (max == 0)
8152 position = g_strdup("All");
8153 else if (value == max)
8154 position = g_strdup("Bot");
8155 else if (value == 0)
8156 position = g_strdup("Top");
8157 else
8158 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8160 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8161 g_free(position);
8163 return (TRUE);
8166 GtkWidget *
8167 create_browser(struct tab *t)
8169 GtkWidget *w;
8170 gchar *strval;
8171 GtkAdjustment *adjustment;
8173 if (t == NULL) {
8174 show_oops(NULL, "create_browser invalid parameters");
8175 return (NULL);
8178 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8179 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8180 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8181 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8183 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8184 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8185 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8187 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8188 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8190 /* set defaults */
8191 t->settings = webkit_web_settings_new();
8193 if (user_agent == NULL) {
8194 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8195 (char *)NULL);
8196 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8197 g_free(strval);
8198 } else
8199 t->user_agent = g_strdup(user_agent);
8201 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8203 adjustment =
8204 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8205 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8206 G_CALLBACK(update_statusbar_position), NULL);
8208 setup_webkit(t);
8210 return (w);
8213 GtkWidget *
8214 create_window(void)
8216 GtkWidget *w;
8218 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8219 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8220 gtk_widget_set_name(w, "xxxterm");
8221 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8222 g_signal_connect(G_OBJECT(w), "delete_event",
8223 G_CALLBACK (gtk_main_quit), NULL);
8225 return (w);
8228 GtkWidget *
8229 create_kiosk_toolbar(struct tab *t)
8231 GtkWidget *toolbar = NULL, *b;
8233 b = gtk_hbox_new(FALSE, 0);
8234 toolbar = b;
8235 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8237 /* backward button */
8238 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8239 gtk_widget_set_sensitive(t->backward, FALSE);
8240 g_signal_connect(G_OBJECT(t->backward), "clicked",
8241 G_CALLBACK(backward_cb), t);
8242 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8244 /* forward button */
8245 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8246 gtk_widget_set_sensitive(t->forward, FALSE);
8247 g_signal_connect(G_OBJECT(t->forward), "clicked",
8248 G_CALLBACK(forward_cb), t);
8249 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8251 /* home button */
8252 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8253 gtk_widget_set_sensitive(t->gohome, true);
8254 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8255 G_CALLBACK(home_cb), t);
8256 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8258 /* create widgets but don't use them */
8259 t->uri_entry = gtk_entry_new();
8260 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8261 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8262 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8264 return (toolbar);
8267 GtkWidget *
8268 create_toolbar(struct tab *t)
8270 GtkWidget *toolbar = NULL, *b, *eb1;
8272 b = gtk_hbox_new(FALSE, 0);
8273 toolbar = b;
8274 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8276 /* backward button */
8277 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8278 gtk_widget_set_sensitive(t->backward, FALSE);
8279 g_signal_connect(G_OBJECT(t->backward), "clicked",
8280 G_CALLBACK(backward_cb), t);
8281 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8283 /* forward button */
8284 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8285 gtk_widget_set_sensitive(t->forward, FALSE);
8286 g_signal_connect(G_OBJECT(t->forward), "clicked",
8287 G_CALLBACK(forward_cb), t);
8288 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8289 FALSE, 0);
8291 /* stop button */
8292 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8293 gtk_widget_set_sensitive(t->stop, FALSE);
8294 g_signal_connect(G_OBJECT(t->stop), "clicked",
8295 G_CALLBACK(stop_cb), t);
8296 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8297 FALSE, 0);
8299 /* JS button */
8300 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8301 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8302 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8303 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8304 G_CALLBACK(js_toggle_cb), t);
8305 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8307 t->uri_entry = gtk_entry_new();
8308 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8309 G_CALLBACK(activate_uri_entry_cb), t);
8310 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8311 G_CALLBACK(entry_key_cb), t);
8312 completion_add(t);
8313 eb1 = gtk_hbox_new(FALSE, 0);
8314 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8315 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8316 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8318 /* search entry */
8319 if (search_string) {
8320 GtkWidget *eb2;
8321 t->search_entry = gtk_entry_new();
8322 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8323 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8324 G_CALLBACK(activate_search_entry_cb), t);
8325 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8326 G_CALLBACK(entry_key_cb), t);
8327 gtk_widget_set_size_request(t->search_entry, -1, -1);
8328 eb2 = gtk_hbox_new(FALSE, 0);
8329 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8330 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8332 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8335 return (toolbar);
8338 GtkWidget *
8339 create_buffers(struct tab *t)
8341 GtkCellRenderer *renderer;
8342 GtkWidget *view;
8344 view = gtk_tree_view_new();
8346 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8348 renderer = gtk_cell_renderer_text_new();
8349 gtk_tree_view_insert_column_with_attributes
8350 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
8352 renderer = gtk_cell_renderer_text_new();
8353 gtk_tree_view_insert_column_with_attributes
8354 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8355 NULL);
8357 gtk_tree_view_set_model
8358 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8360 return view;
8363 void
8364 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8365 GtkTreeViewColumn *col, struct tab *t)
8367 GtkTreeIter iter;
8368 guint id;
8370 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8372 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8373 path)) {
8374 gtk_tree_model_get
8375 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8376 set_current_tab(id - 1);
8379 hide_buffers(t);
8382 /* after tab reordering/creation/removal */
8383 void
8384 recalc_tabs(void)
8386 struct tab *t;
8387 int maxid = 0;
8389 TAILQ_FOREACH(t, &tabs, entry) {
8390 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8391 if (t->tab_id > maxid)
8392 maxid = t->tab_id;
8394 gtk_widget_show(t->tab_elems.sep);
8397 TAILQ_FOREACH(t, &tabs, entry) {
8398 if (t->tab_id == maxid) {
8399 gtk_widget_hide(t->tab_elems.sep);
8400 break;
8405 /* after active tab change */
8406 void
8407 recolor_compact_tabs(void)
8409 struct tab *t;
8410 int curid = 0;
8411 GdkColor color;
8413 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8414 TAILQ_FOREACH(t, &tabs, entry)
8415 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8416 &color);
8418 curid = gtk_notebook_get_current_page(notebook);
8419 TAILQ_FOREACH(t, &tabs, entry)
8420 if (t->tab_id == curid) {
8421 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8422 gtk_widget_modify_fg(t->tab_elems.label,
8423 GTK_STATE_NORMAL, &color);
8424 break;
8428 void
8429 set_current_tab(int page_num)
8431 buffercmd_abort(get_current_tab());
8432 gtk_notebook_set_current_page(notebook, page_num);
8433 recolor_compact_tabs();
8437 undo_close_tab_save(struct tab *t)
8439 int m, n;
8440 const gchar *uri;
8441 struct undo *u1, *u2;
8442 GList *items;
8443 WebKitWebHistoryItem *item;
8445 if ((uri = get_uri(t)) == NULL)
8446 return (1);
8448 u1 = g_malloc0(sizeof(struct undo));
8449 u1->uri = g_strdup(uri);
8451 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8453 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8454 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8455 u1->back = n;
8457 /* forward history */
8458 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8460 while (items) {
8461 item = items->data;
8462 u1->history = g_list_prepend(u1->history,
8463 webkit_web_history_item_copy(item));
8464 items = g_list_next(items);
8467 /* current item */
8468 if (m) {
8469 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8470 u1->history = g_list_prepend(u1->history,
8471 webkit_web_history_item_copy(item));
8474 /* back history */
8475 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8477 while (items) {
8478 item = items->data;
8479 u1->history = g_list_prepend(u1->history,
8480 webkit_web_history_item_copy(item));
8481 items = g_list_next(items);
8484 TAILQ_INSERT_HEAD(&undos, u1, entry);
8486 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8487 u2 = TAILQ_LAST(&undos, undo_tailq);
8488 TAILQ_REMOVE(&undos, u2, entry);
8489 g_free(u2->uri);
8490 g_list_free(u2->history);
8491 g_free(u2);
8492 } else
8493 undo_count++;
8495 return (0);
8498 void
8499 delete_tab(struct tab *t)
8501 struct karg a;
8503 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8505 if (t == NULL)
8506 return;
8508 buffercmd_abort(t);
8509 TAILQ_REMOVE(&tabs, t, entry);
8511 /* Halt all webkit activity. */
8512 abort_favicon_download(t);
8513 webkit_web_view_stop_loading(t->wv);
8515 /* Save the tab, so we can undo the close. */
8516 undo_close_tab_save(t);
8518 if (browser_mode == XT_BM_KIOSK) {
8519 gtk_widget_destroy(t->uri_entry);
8520 gtk_widget_destroy(t->stop);
8521 gtk_widget_destroy(t->js_toggle);
8524 gtk_widget_destroy(t->tab_elems.eventbox);
8525 gtk_widget_destroy(t->vbox);
8527 /* just in case */
8528 if (t->search_id)
8529 g_source_remove(t->search_id);
8531 g_free(t->user_agent);
8532 g_free(t->stylesheet);
8533 g_free(t->tmp_uri);
8534 g_free(t);
8536 if (TAILQ_EMPTY(&tabs)) {
8537 if (browser_mode == XT_BM_KIOSK)
8538 create_new_tab(home, NULL, 1, -1);
8539 else
8540 create_new_tab(NULL, NULL, 1, -1);
8543 /* recreate session */
8544 if (session_autosave) {
8545 a.s = NULL;
8546 save_tabs(t, &a);
8549 recalc_tabs();
8550 recolor_compact_tabs();
8553 void
8554 update_statusbar_zoom(struct tab *t)
8556 gfloat zoom;
8557 char s[16] = { '\0' };
8559 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8560 if ((zoom <= 0.99 || zoom >= 1.01))
8561 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8562 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8565 void
8566 setzoom_webkit(struct tab *t, int adjust)
8568 #define XT_ZOOMPERCENT 0.04
8570 gfloat zoom;
8572 if (t == NULL) {
8573 show_oops(NULL, "setzoom_webkit invalid parameters");
8574 return;
8577 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8578 if (adjust == XT_ZOOM_IN)
8579 zoom += XT_ZOOMPERCENT;
8580 else if (adjust == XT_ZOOM_OUT)
8581 zoom -= XT_ZOOMPERCENT;
8582 else if (adjust > 0)
8583 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8584 else {
8585 show_oops(t, "setzoom_webkit invalid zoom value");
8586 return;
8589 if (zoom < XT_ZOOMPERCENT)
8590 zoom = XT_ZOOMPERCENT;
8591 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8592 update_statusbar_zoom(t);
8595 gboolean
8596 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8598 struct tab *t = (struct tab *) data;
8600 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8602 switch (event->button) {
8603 case 1:
8604 set_current_tab(t->tab_id);
8605 break;
8606 case 2:
8607 delete_tab(t);
8608 break;
8611 return TRUE;
8614 void
8615 append_tab(struct tab *t)
8617 if (t == NULL)
8618 return;
8620 TAILQ_INSERT_TAIL(&tabs, t, entry);
8621 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8624 GtkWidget *
8625 create_sbe(int width)
8627 GtkWidget *sbe;
8629 sbe = gtk_entry_new();
8630 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8631 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8632 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8633 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8634 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8635 gtk_widget_set_size_request(sbe, width, -1);
8637 return sbe;
8640 struct tab *
8641 create_new_tab(char *title, struct undo *u, int focus, int position)
8643 struct tab *t;
8644 int load = 1, id;
8645 GtkWidget *b, *bb;
8646 WebKitWebHistoryItem *item;
8647 GList *items;
8648 GdkColor color;
8649 char *p;
8650 int sbe_p = 0, sbe_b = 0,
8651 sbe_z = 0;
8653 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8655 if (tabless && !TAILQ_EMPTY(&tabs)) {
8656 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8657 return (NULL);
8660 t = g_malloc0(sizeof *t);
8662 if (title == NULL) {
8663 title = "(untitled)";
8664 load = 0;
8667 t->vbox = gtk_vbox_new(FALSE, 0);
8669 /* label + button for tab */
8670 b = gtk_hbox_new(FALSE, 0);
8671 t->tab_content = b;
8673 #if GTK_CHECK_VERSION(2, 20, 0)
8674 t->spinner = gtk_spinner_new();
8675 #endif
8676 t->label = gtk_label_new(title);
8677 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8678 gtk_widget_set_size_request(t->label, 100, 0);
8679 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8680 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8681 gtk_widget_set_size_request(b, 130, 0);
8683 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8684 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8685 #if GTK_CHECK_VERSION(2, 20, 0)
8686 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8687 #endif
8689 /* toolbar */
8690 if (browser_mode == XT_BM_KIOSK) {
8691 t->toolbar = create_kiosk_toolbar(t);
8692 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8694 } else {
8695 t->toolbar = create_toolbar(t);
8696 if (fancy_bar)
8697 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8698 FALSE, 0);
8701 /* marks */
8702 marks_clear(t);
8704 /* browser */
8705 t->browser_win = create_browser(t);
8706 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8708 /* oops message for user feedback */
8709 t->oops = gtk_entry_new();
8710 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8711 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8712 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8713 gdk_color_parse(XT_COLOR_RED, &color);
8714 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8715 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8716 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8718 /* command entry */
8719 t->cmd = gtk_entry_new();
8720 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8721 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8722 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8723 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8725 /* status bar */
8726 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8728 t->sbe.statusbar = gtk_entry_new();
8729 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8730 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8731 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8732 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8734 /* create these widgets only if specified in statusbar_elems */
8736 t->sbe.position = create_sbe(40);
8737 t->sbe.zoom = create_sbe(40);
8738 t->sbe.buffercmd = create_sbe(60);
8740 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8742 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8743 TRUE, FALSE);
8745 /* gtk widgets cannot be added to a box twice. sbe_* variables
8746 make sure of this */
8747 for (p = statusbar_elems; *p != '\0'; p++) {
8748 switch (*p) {
8749 case '|':
8751 GtkWidget *sep = gtk_vseparator_new();
8753 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8754 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8755 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8756 FALSE, FALSE, FALSE);
8757 break;
8759 case 'P':
8760 if (sbe_p) {
8761 warnx("flag \"%c\" specified more than "
8762 "once in statusbar_elems\n", *p);
8763 break;
8765 sbe_p = 1;
8766 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8767 t->sbe.position, FALSE, FALSE, FALSE);
8768 break;
8769 case 'B':
8770 if (sbe_b) {
8771 warnx("flag \"%c\" specified more than "
8772 "once in statusbar_elems\n", *p);
8773 break;
8775 sbe_b = 1;
8776 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8777 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8778 break;
8779 case 'Z':
8780 if (sbe_z) {
8781 warnx("flag \"%c\" specified more than "
8782 "once in statusbar_elems\n", *p);
8783 break;
8785 sbe_z = 1;
8786 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8787 t->sbe.zoom, FALSE, FALSE, FALSE);
8788 break;
8789 default:
8790 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8791 break;
8795 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8797 /* buffer list */
8798 t->buffers = create_buffers(t);
8799 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8801 /* xtp meaning is normal by default */
8802 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8804 /* set empty favicon */
8805 xt_icon_from_name(t, "text-html");
8807 /* and show it all */
8808 gtk_widget_show_all(b);
8809 gtk_widget_show_all(t->vbox);
8811 /* compact tab bar */
8812 t->tab_elems.label = gtk_label_new(title);
8813 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8814 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8815 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8816 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8818 t->tab_elems.eventbox = gtk_event_box_new();
8819 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8820 t->tab_elems.sep = gtk_vseparator_new();
8822 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8823 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8824 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8825 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8826 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8827 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8829 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8830 TRUE, 0);
8831 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8832 FALSE, 0);
8833 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8834 t->tab_elems.box);
8836 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8837 TRUE, 0);
8838 gtk_widget_show_all(t->tab_elems.eventbox);
8840 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8841 append_tab(t);
8842 else {
8843 id = position >= 0 ? position :
8844 gtk_notebook_get_current_page(notebook) + 1;
8845 if (id > gtk_notebook_get_n_pages(notebook))
8846 append_tab(t);
8847 else {
8848 TAILQ_INSERT_TAIL(&tabs, t, entry);
8849 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8850 gtk_box_reorder_child(GTK_BOX(tab_bar),
8851 t->tab_elems.eventbox, id);
8852 recalc_tabs();
8856 #if GTK_CHECK_VERSION(2, 20, 0)
8857 /* turn spinner off if we are a new tab without uri */
8858 if (!load) {
8859 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8860 gtk_widget_hide(t->spinner);
8862 #endif
8863 /* make notebook tabs reorderable */
8864 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8866 /* compact tabs clickable */
8867 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8868 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8870 g_object_connect(G_OBJECT(t->cmd),
8871 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8872 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8873 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8874 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8875 (char *)NULL);
8877 /* reuse wv_button_cb to hide oops */
8878 g_object_connect(G_OBJECT(t->oops),
8879 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8880 (char *)NULL);
8882 g_signal_connect(t->buffers,
8883 "row-activated", G_CALLBACK(row_activated_cb), t);
8884 g_object_connect(G_OBJECT(t->buffers),
8885 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8887 g_object_connect(G_OBJECT(t->wv),
8888 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8889 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8890 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8891 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8892 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8893 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8894 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8895 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8896 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8897 "signal::event", G_CALLBACK(webview_event_cb), t,
8898 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8899 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8900 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8901 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8902 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8903 (char *)NULL);
8904 g_signal_connect(t->wv,
8905 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8907 * XXX this puts invalid url in uri_entry and that is undesirable
8909 #if 0
8910 g_signal_connect(t->wv,
8911 "load-error", G_CALLBACK(notify_load_error_cb), t);
8912 #endif
8913 g_signal_connect(t->wv,
8914 "notify::title", G_CALLBACK(notify_title_cb), t);
8916 /* hijack the unused keys as if we were the browser */
8917 g_object_connect(G_OBJECT(t->toolbar),
8918 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8919 (char *)NULL);
8921 g_signal_connect(G_OBJECT(bb), "button_press_event",
8922 G_CALLBACK(tab_close_cb), t);
8924 /* hide stuff */
8925 hide_cmd(t);
8926 hide_oops(t);
8927 hide_buffers(t);
8928 url_set_visibility();
8929 statusbar_set_visibility();
8931 if (focus) {
8932 set_current_tab(t->tab_id);
8933 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8934 t->tab_id);
8937 if (load) {
8938 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8939 load_uri(t, title);
8940 } else {
8941 if (show_url == 1)
8942 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8943 else
8944 focus_webview(t);
8947 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8948 /* restore the tab's history */
8949 if (u && u->history) {
8950 items = u->history;
8951 while (items) {
8952 item = items->data;
8953 webkit_web_back_forward_list_add_item(t->bfl, item);
8954 items = g_list_next(items);
8957 item = g_list_nth_data(u->history, u->back);
8958 if (item)
8959 webkit_web_view_go_to_back_forward_item(t->wv, item);
8961 g_list_free(items);
8962 g_list_free(u->history);
8963 } else
8964 webkit_web_back_forward_list_clear(t->bfl);
8966 recolor_compact_tabs();
8967 setzoom_webkit(t, XT_ZOOM_NORMAL);
8968 return (t);
8971 void
8972 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8973 gpointer *udata)
8975 struct tab *t;
8976 const gchar *uri;
8978 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8980 if (gtk_notebook_get_current_page(notebook) == -1)
8981 recalc_tabs();
8983 TAILQ_FOREACH(t, &tabs, entry) {
8984 if (t->tab_id == pn) {
8985 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8986 "%d\n", pn);
8988 uri = get_title(t, TRUE);
8989 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8991 hide_cmd(t);
8992 hide_oops(t);
8994 if (t->focus_wv) {
8995 /* can't use focus_webview here */
8996 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9002 void
9003 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9004 gpointer *udata)
9006 struct tab *t = NULL, *tt;
9008 recalc_tabs();
9010 TAILQ_FOREACH(tt, &tabs, entry)
9011 if (tt->tab_id == pn) {
9012 t = tt;
9013 break;
9016 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9018 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9019 t->tab_id);
9022 void
9023 menuitem_response(struct tab *t)
9025 gtk_notebook_set_current_page(notebook, t->tab_id);
9028 gboolean
9029 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9031 GtkWidget *menu, *menu_items;
9032 GdkEventButton *bevent;
9033 const gchar *uri;
9034 struct tab *ti;
9036 if (event->type == GDK_BUTTON_PRESS) {
9037 bevent = (GdkEventButton *) event;
9038 menu = gtk_menu_new();
9040 TAILQ_FOREACH(ti, &tabs, entry) {
9041 if ((uri = get_uri(ti)) == NULL)
9042 /* XXX make sure there is something to print */
9043 /* XXX add gui pages in here to look purdy */
9044 uri = "(untitled)";
9045 menu_items = gtk_menu_item_new_with_label(uri);
9046 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9047 gtk_widget_show(menu_items);
9049 g_signal_connect_swapped((menu_items),
9050 "activate", G_CALLBACK(menuitem_response),
9051 (gpointer)ti);
9054 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9055 bevent->button, bevent->time);
9057 /* unref object so it'll free itself when popped down */
9058 #if !GTK_CHECK_VERSION(3, 0, 0)
9059 /* XXX does not need unref with gtk+3? */
9060 g_object_ref_sink(menu);
9061 g_object_unref(menu);
9062 #endif
9064 return (TRUE /* eat event */);
9067 return (FALSE /* propagate */);
9071 icon_size_map(int icon_size)
9073 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9074 icon_size > GTK_ICON_SIZE_DIALOG)
9075 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9077 return (icon_size);
9080 GtkWidget *
9081 create_button(char *name, char *stockid, int size)
9083 GtkWidget *button, *image;
9084 gchar *rcstring;
9085 int gtk_icon_size;
9087 rcstring = g_strdup_printf(
9088 "style \"%s-style\"\n"
9089 "{\n"
9090 " GtkWidget::focus-padding = 0\n"
9091 " GtkWidget::focus-line-width = 0\n"
9092 " xthickness = 0\n"
9093 " ythickness = 0\n"
9094 "}\n"
9095 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9096 gtk_rc_parse_string(rcstring);
9097 g_free(rcstring);
9098 button = gtk_button_new();
9099 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9100 gtk_icon_size = icon_size_map(size ? size : icon_size);
9102 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9103 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9104 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9105 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9106 gtk_widget_set_name(button, name);
9107 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9109 return (button);
9112 void
9113 button_set_stockid(GtkWidget *button, char *stockid)
9115 GtkWidget *image;
9117 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9118 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9119 gtk_button_set_image(GTK_BUTTON(button), image);
9122 void
9123 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9125 gchar *p = NULL;
9126 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9127 gint len;
9129 if (xterm_workaround == 0)
9130 return;
9133 * xterm doesn't play nice with clipboards because it clears the
9134 * primary when clicked. We rely on primary being set to properly
9135 * handle middle mouse button clicks (paste). So when someone clears
9136 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9137 * other application behavior (as in DON'T clear primary).
9140 p = gtk_clipboard_wait_for_text(primary);
9141 if (p == NULL) {
9142 if (gdk_property_get(gdk_get_default_root_window(),
9143 atom,
9144 gdk_atom_intern("STRING", FALSE),
9146 1024 * 1024 /* picked out of my butt */,
9147 FALSE,
9148 NULL,
9149 NULL,
9150 &len,
9151 (guchar **)&p)) {
9152 /* yes sir, we need to NUL the string */
9153 p[len] = '\0';
9154 gtk_clipboard_set_text(primary, p, -1);
9158 if (p)
9159 g_free(p);
9162 void
9163 create_canvas(void)
9165 GtkWidget *vbox;
9166 GList *l = NULL;
9167 GdkPixbuf *pb;
9168 char file[PATH_MAX];
9169 int i;
9171 vbox = gtk_vbox_new(FALSE, 0);
9172 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9173 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9174 #if !GTK_CHECK_VERSION(3, 0, 0)
9175 /* XXX seems to be needed with gtk+2 */
9176 gtk_notebook_set_tab_hborder(notebook, 0);
9177 gtk_notebook_set_tab_vborder(notebook, 0);
9178 #endif
9179 gtk_notebook_set_scrollable(notebook, TRUE);
9180 gtk_notebook_set_show_border(notebook, FALSE);
9181 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9183 abtn = gtk_button_new();
9184 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9185 gtk_widget_set_size_request(arrow, -1, -1);
9186 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9187 gtk_widget_set_size_request(abtn, -1, 20);
9189 #if GTK_CHECK_VERSION(2, 20, 0)
9190 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9191 #endif
9192 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9194 /* compact tab bar */
9195 tab_bar = gtk_hbox_new(TRUE, 0);
9197 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9198 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9199 gtk_widget_set_size_request(vbox, -1, -1);
9201 g_object_connect(G_OBJECT(notebook),
9202 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9203 (char *)NULL);
9204 g_object_connect(G_OBJECT(notebook),
9205 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9206 NULL, (char *)NULL);
9207 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9208 G_CALLBACK(arrow_cb), NULL);
9210 main_window = create_window();
9211 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9213 /* icons */
9214 for (i = 0; i < LENGTH(icons); i++) {
9215 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9216 pb = gdk_pixbuf_new_from_file(file, NULL);
9217 l = g_list_append(l, pb);
9219 gtk_window_set_default_icon_list(l);
9221 /* clipboard work around */
9222 if (xterm_workaround)
9223 g_signal_connect(
9224 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9225 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9227 gtk_widget_show_all(abtn);
9228 gtk_widget_show_all(main_window);
9229 notebook_tab_set_visibility();
9232 void
9233 set_hook(void **hook, char *name)
9235 if (hook == NULL)
9236 errx(1, "set_hook");
9238 if (*hook == NULL) {
9239 *hook = dlsym(RTLD_NEXT, name);
9240 if (*hook == NULL)
9241 errx(1, "can't hook %s", name);
9245 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9246 gboolean
9247 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9249 g_return_val_if_fail(cookie1, FALSE);
9250 g_return_val_if_fail(cookie2, FALSE);
9252 return (!strcmp (cookie1->name, cookie2->name) &&
9253 !strcmp (cookie1->value, cookie2->value) &&
9254 !strcmp (cookie1->path, cookie2->path) &&
9255 !strcmp (cookie1->domain, cookie2->domain));
9258 void
9259 transfer_cookies(void)
9261 GSList *cf;
9262 SoupCookie *sc, *pc;
9264 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9266 for (;cf; cf = cf->next) {
9267 pc = cf->data;
9268 sc = soup_cookie_copy(pc);
9269 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9272 soup_cookies_free(cf);
9275 void
9276 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9278 GSList *cf;
9279 SoupCookie *ci;
9281 print_cookie("soup_cookie_jar_delete_cookie", c);
9283 if (cookies_enabled == 0)
9284 return;
9286 if (jar == NULL || c == NULL)
9287 return;
9289 /* find and remove from persistent jar */
9290 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9292 for (;cf; cf = cf->next) {
9293 ci = cf->data;
9294 if (soup_cookie_equal(ci, c)) {
9295 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9296 break;
9300 soup_cookies_free(cf);
9302 /* delete from session jar */
9303 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9306 void
9307 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9309 struct domain *d = NULL;
9310 SoupCookie *c;
9311 FILE *r_cookie_f;
9313 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9314 jar, p_cookiejar, s_cookiejar);
9316 if (cookies_enabled == 0)
9317 return;
9319 /* see if we are up and running */
9320 if (p_cookiejar == NULL) {
9321 _soup_cookie_jar_add_cookie(jar, cookie);
9322 return;
9324 /* disallow p_cookiejar adds, shouldn't happen */
9325 if (jar == p_cookiejar)
9326 return;
9328 /* sanity */
9329 if (jar == NULL || cookie == NULL)
9330 return;
9332 if (enable_cookie_whitelist &&
9333 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9334 blocked_cookies++;
9335 DNPRINTF(XT_D_COOKIE,
9336 "soup_cookie_jar_add_cookie: reject %s\n",
9337 cookie->domain);
9338 if (save_rejected_cookies) {
9339 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9340 show_oops(NULL, "can't open reject cookie file");
9341 return;
9343 fseek(r_cookie_f, 0, SEEK_END);
9344 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9345 cookie->http_only ? "#HttpOnly_" : "",
9346 cookie->domain,
9347 *cookie->domain == '.' ? "TRUE" : "FALSE",
9348 cookie->path,
9349 cookie->secure ? "TRUE" : "FALSE",
9350 cookie->expires ?
9351 (gulong)soup_date_to_time_t(cookie->expires) :
9353 cookie->name,
9354 cookie->value);
9355 fflush(r_cookie_f);
9356 fclose(r_cookie_f);
9358 if (!allow_volatile_cookies)
9359 return;
9362 if (cookie->expires == NULL && session_timeout) {
9363 soup_cookie_set_expires(cookie,
9364 soup_date_new_from_now(session_timeout));
9365 print_cookie("modified add cookie", cookie);
9368 /* see if we are white listed for persistence */
9369 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9370 /* add to persistent jar */
9371 c = soup_cookie_copy(cookie);
9372 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9373 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9376 /* add to session jar */
9377 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9378 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9381 void
9382 setup_cookies(void)
9384 char file[PATH_MAX];
9386 set_hook((void *)&_soup_cookie_jar_add_cookie,
9387 "soup_cookie_jar_add_cookie");
9388 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9389 "soup_cookie_jar_delete_cookie");
9391 if (cookies_enabled == 0)
9392 return;
9395 * the following code is intricate due to overriding several libsoup
9396 * functions.
9397 * do not alter order of these operations.
9400 /* rejected cookies */
9401 if (save_rejected_cookies)
9402 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9403 XT_REJECT_FILE);
9405 /* persistent cookies */
9406 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9407 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9409 /* session cookies */
9410 s_cookiejar = soup_cookie_jar_new();
9411 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9412 cookie_policy, (void *)NULL);
9413 transfer_cookies();
9415 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9418 void
9419 setup_proxy(char *uri)
9421 SoupURI *suri;
9423 if (proxy_uri) {
9424 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9425 soup_uri_free(proxy_uri);
9426 proxy_uri = NULL;
9428 if (http_proxy) {
9429 if (http_proxy != uri) {
9430 g_free(http_proxy);
9431 http_proxy = NULL;
9435 if (uri) {
9436 http_proxy = g_strdup(uri);
9437 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9438 suri = soup_uri_new(http_proxy);
9439 if (!(suri == NULL || !SOUP_URI_VALID_FOR_HTTP(suri)))
9440 g_object_set(session, "proxy-uri", proxy_uri,
9441 (char *)NULL);
9442 if (suri)
9443 soup_uri_free(suri);
9448 set_http_proxy(char *proxy)
9450 SoupURI *uri;
9452 if (proxy == NULL)
9453 return (1);
9455 /* see if we need to clear it instead */
9456 if (strlen(proxy) == 0) {
9457 setup_proxy(NULL);
9458 return (0);
9461 uri = soup_uri_new(proxy);
9462 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9463 return (1);
9465 setup_proxy(proxy);
9467 soup_uri_free(uri);
9469 return (0);
9473 send_cmd_to_socket(char *cmd)
9475 int s, len, rv = 1;
9476 struct sockaddr_un sa;
9478 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9479 warnx("%s: socket", __func__);
9480 return (rv);
9483 sa.sun_family = AF_UNIX;
9484 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9485 work_dir, XT_SOCKET_FILE);
9486 len = SUN_LEN(&sa);
9488 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9489 warnx("%s: connect", __func__);
9490 goto done;
9493 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9494 warnx("%s: send", __func__);
9495 goto done;
9498 rv = 0;
9499 done:
9500 close(s);
9501 return (rv);
9504 gboolean
9505 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9507 int s, n;
9508 char str[XT_MAX_URL_LENGTH];
9509 socklen_t t = sizeof(struct sockaddr_un);
9510 struct sockaddr_un sa;
9511 struct passwd *p;
9512 uid_t uid;
9513 gid_t gid;
9514 struct tab *tt;
9515 gint fd = g_io_channel_unix_get_fd(source);
9517 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9518 warn("accept");
9519 return (FALSE);
9522 if (getpeereid(s, &uid, &gid) == -1) {
9523 warn("getpeereid");
9524 return (FALSE);
9526 if (uid != getuid() || gid != getgid()) {
9527 warnx("unauthorized user");
9528 return (FALSE);
9531 p = getpwuid(uid);
9532 if (p == NULL) {
9533 warnx("not a valid user");
9534 return (FALSE);
9537 n = recv(s, str, sizeof(str), 0);
9538 if (n <= 0)
9539 return (TRUE);
9541 tt = TAILQ_LAST(&tabs, tab_list);
9542 cmd_execute(tt, str);
9543 return (TRUE);
9547 is_running(void)
9549 int s, len, rv = 1;
9550 struct sockaddr_un sa;
9552 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9553 warn("is_running: socket");
9554 return (-1);
9557 sa.sun_family = AF_UNIX;
9558 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9559 work_dir, XT_SOCKET_FILE);
9560 len = SUN_LEN(&sa);
9562 /* connect to see if there is a listener */
9563 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9564 rv = 0; /* not running */
9565 else
9566 rv = 1; /* already running */
9568 close(s);
9570 return (rv);
9574 build_socket(void)
9576 int s, len;
9577 struct sockaddr_un sa;
9579 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9580 warn("build_socket: socket");
9581 return (-1);
9584 sa.sun_family = AF_UNIX;
9585 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9586 work_dir, XT_SOCKET_FILE);
9587 len = SUN_LEN(&sa);
9589 /* connect to see if there is a listener */
9590 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9591 /* no listener so we will */
9592 unlink(sa.sun_path);
9594 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9595 warn("build_socket: bind");
9596 goto done;
9599 if (listen(s, 1) == -1) {
9600 warn("build_socket: listen");
9601 goto done;
9604 return (s);
9607 done:
9608 close(s);
9609 return (-1);
9612 gboolean
9613 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9614 GtkTreeIter *iter, struct tab *t)
9616 gchar *value;
9618 gtk_tree_model_get(model, iter, 0, &value, -1);
9619 load_uri(t, value);
9620 g_free(value);
9622 return (FALSE);
9625 gboolean
9626 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9627 GtkTreeIter *iter, struct tab *t)
9629 gchar *value;
9631 gtk_tree_model_get(model, iter, 0, &value, -1);
9632 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9633 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9634 g_free(value);
9636 return (TRUE);
9639 void
9640 completion_add_uri(const gchar *uri)
9642 GtkTreeIter iter;
9644 /* add uri to list_store */
9645 gtk_list_store_append(completion_model, &iter);
9646 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9649 gboolean
9650 completion_match(GtkEntryCompletion *completion, const gchar *key,
9651 GtkTreeIter *iter, gpointer user_data)
9653 gchar *value;
9654 gboolean match = FALSE;
9656 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9657 -1);
9659 if (value == NULL)
9660 return FALSE;
9662 match = match_uri(value, key);
9664 g_free(value);
9665 return (match);
9668 void
9669 completion_add(struct tab *t)
9671 /* enable completion for tab */
9672 t->completion = gtk_entry_completion_new();
9673 gtk_entry_completion_set_text_column(t->completion, 0);
9674 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9675 gtk_entry_completion_set_model(t->completion,
9676 GTK_TREE_MODEL(completion_model));
9677 gtk_entry_completion_set_match_func(t->completion, completion_match,
9678 NULL, NULL);
9679 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9680 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9681 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9682 G_CALLBACK(completion_select_cb), t);
9683 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9684 G_CALLBACK(completion_hover_cb), t);
9687 void
9688 xxx_dir(char *dir)
9690 struct stat sb;
9692 if (stat(dir, &sb)) {
9693 if (mkdir(dir, S_IRWXU) == -1)
9694 err(1, "mkdir %s", dir);
9695 if (stat(dir, &sb))
9696 err(1, "stat %s", dir);
9698 if (S_ISDIR(sb.st_mode) == 0)
9699 errx(1, "%s not a dir", dir);
9700 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9701 warnx("fixing invalid permissions on %s", dir);
9702 if (chmod(dir, S_IRWXU) == -1)
9703 err(1, "chmod %s", dir);
9707 void
9708 usage(void)
9710 fprintf(stderr,
9711 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9712 exit(0);
9717 main(int argc, char *argv[])
9719 struct stat sb;
9720 int c, s, optn = 0, opte = 0, focus = 1;
9721 char conf[PATH_MAX] = { '\0' };
9722 char file[PATH_MAX];
9723 char *env_proxy = NULL;
9724 char *cmd = NULL;
9725 FILE *f = NULL;
9726 struct karg a;
9727 struct sigaction sact;
9728 GIOChannel *channel;
9729 struct rlimit rlp;
9731 start_argv = argv;
9733 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9735 RB_INIT(&hl);
9736 RB_INIT(&js_wl);
9737 RB_INIT(&downloads);
9739 TAILQ_INIT(&tabs);
9740 TAILQ_INIT(&mtl);
9741 TAILQ_INIT(&aliases);
9742 TAILQ_INIT(&undos);
9743 TAILQ_INIT(&kbl);
9744 TAILQ_INIT(&spl);
9746 /* fiddle with ulimits */
9747 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9748 warn("getrlimit");
9749 else {
9750 /* just use them all */
9751 rlp.rlim_cur = rlp.rlim_max;
9752 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9753 warn("setrlimit");
9754 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9755 warn("getrlimit");
9756 else if (rlp.rlim_cur <= 256)
9757 startpage_add("%s requires at least 256 file "
9758 "descriptors, currently it has up to %d available",
9759 __progname, rlp.rlim_cur);
9762 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9763 switch (c) {
9764 case 'S':
9765 show_url = 0;
9766 break;
9767 case 'T':
9768 show_tabs = 0;
9769 break;
9770 case 'V':
9771 errx(0 , "Version: %s", version);
9772 break;
9773 case 'f':
9774 strlcpy(conf, optarg, sizeof(conf));
9775 break;
9776 case 's':
9777 strlcpy(named_session, optarg, sizeof(named_session));
9778 break;
9779 case 't':
9780 tabless = 1;
9781 break;
9782 case 'n':
9783 optn = 1;
9784 break;
9785 case 'e':
9786 opte = 1;
9787 break;
9788 default:
9789 usage();
9790 /* NOTREACHED */
9793 argc -= optind;
9794 argv += optind;
9796 init_keybindings();
9798 gnutls_global_init();
9800 /* generate session keys for xtp pages */
9801 generate_xtp_session_key(&dl_session_key);
9802 generate_xtp_session_key(&hl_session_key);
9803 generate_xtp_session_key(&cl_session_key);
9804 generate_xtp_session_key(&fl_session_key);
9806 /* prepare gtk */
9807 if (!g_thread_supported()) {
9808 g_thread_init(NULL);
9809 gdk_threads_init();
9810 gdk_threads_enter();
9812 gtk_init(&argc, &argv);
9814 /* signals */
9815 bzero(&sact, sizeof(sact));
9816 sigemptyset(&sact.sa_mask);
9817 sact.sa_handler = sigchild;
9818 sact.sa_flags = SA_NOCLDSTOP;
9819 sigaction(SIGCHLD, &sact, NULL);
9821 /* set download dir */
9822 pwd = getpwuid(getuid());
9823 if (pwd == NULL)
9824 errx(1, "invalid user %d", getuid());
9825 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9827 /* compile buffer command regexes */
9828 buffercmd_init();
9830 /* set default string settings */
9831 home = g_strdup("https://www.cyphertite.com");
9832 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9833 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9834 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9835 cmd_font_name = g_strdup("monospace normal 9");
9836 oops_font_name = g_strdup("monospace normal 9");
9837 statusbar_font_name = g_strdup("monospace normal 9");
9838 tabbar_font_name = g_strdup("monospace normal 9");
9839 statusbar_elems = g_strdup("BP");
9841 /* read config file */
9842 if (strlen(conf) == 0)
9843 snprintf(conf, sizeof conf, "%s/.%s",
9844 pwd->pw_dir, XT_CONF_FILE);
9845 config_parse(conf, 0);
9847 /* init fonts */
9848 cmd_font = pango_font_description_from_string(cmd_font_name);
9849 oops_font = pango_font_description_from_string(oops_font_name);
9850 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9851 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9853 /* working directory */
9854 if (strlen(work_dir) == 0)
9855 snprintf(work_dir, sizeof work_dir, "%s/%s",
9856 pwd->pw_dir, XT_DIR);
9857 xxx_dir(work_dir);
9859 /* icon cache dir */
9860 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9861 xxx_dir(cache_dir);
9863 /* certs dir */
9864 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9865 xxx_dir(certs_dir);
9867 /* sessions dir */
9868 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9869 work_dir, XT_SESSIONS_DIR);
9870 xxx_dir(sessions_dir);
9872 /* runtime settings that can override config file */
9873 if (runtime_settings[0] != '\0')
9874 config_parse(runtime_settings, 1);
9876 /* download dir */
9877 if (!strcmp(download_dir, pwd->pw_dir))
9878 strlcat(download_dir, "/downloads", sizeof download_dir);
9879 xxx_dir(download_dir);
9881 /* favorites file */
9882 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9883 if (stat(file, &sb)) {
9884 warnx("favorites file doesn't exist, creating it");
9885 if ((f = fopen(file, "w")) == NULL)
9886 err(1, "favorites");
9887 fclose(f);
9890 /* quickmarks file */
9891 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9892 if (stat(file, &sb)) {
9893 warnx("quickmarks file doesn't exist, creating it");
9894 if ((f = fopen(file, "w")) == NULL)
9895 err(1, "quickmarks");
9896 fclose(f);
9899 /* cookies */
9900 session = webkit_get_default_session();
9901 setup_cookies();
9903 /* certs */
9904 if (ssl_ca_file) {
9905 if (stat(ssl_ca_file, &sb)) {
9906 warnx("no CA file: %s", ssl_ca_file);
9907 g_free(ssl_ca_file);
9908 ssl_ca_file = NULL;
9909 } else
9910 g_object_set(session,
9911 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9912 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9913 (void *)NULL);
9916 /* guess_search regex */
9917 if (url_regex == NULL)
9918 url_regex = g_strdup(XT_URL_REGEX);
9919 if (url_regex)
9920 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
9921 startpage_add("invalid url regex %s", url_regex);
9923 /* proxy */
9924 env_proxy = getenv("http_proxy");
9925 if (env_proxy)
9926 setup_proxy(env_proxy);
9927 else
9928 setup_proxy(http_proxy);
9930 if (opte) {
9931 send_cmd_to_socket(argv[0]);
9932 exit(0);
9935 /* set some connection parameters */
9936 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9937 g_object_set(session, "max-conns-per-host", max_host_connections,
9938 (char *)NULL);
9940 /* see if there is already an xxxterm running */
9941 if (single_instance && is_running()) {
9942 optn = 1;
9943 warnx("already running");
9946 if (optn) {
9947 while (argc) {
9948 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9949 send_cmd_to_socket(cmd);
9950 if (cmd)
9951 g_free(cmd);
9953 argc--;
9954 argv++;
9956 exit(0);
9959 /* uri completion */
9960 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9962 /* buffers */
9963 buffers_store = gtk_list_store_new
9964 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9966 qmarks_load();
9968 /* go graphical */
9969 create_canvas();
9970 notebook_tab_set_visibility();
9972 if (save_global_history)
9973 restore_global_history();
9975 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9976 restore_saved_tabs();
9977 else {
9978 a.s = named_session;
9979 a.i = XT_SES_DONOTHING;
9980 open_tabs(NULL, &a);
9983 /* see if we have an exception */
9984 if (!TAILQ_EMPTY(&spl)) {
9985 create_new_tab("about:startpage", NULL, focus, -1);
9986 focus = 0;
9989 while (argc) {
9990 create_new_tab(argv[0], NULL, focus, -1);
9991 focus = 0;
9993 argc--;
9994 argv++;
9997 if (TAILQ_EMPTY(&tabs))
9998 create_new_tab(home, NULL, 1, -1);
10000 if (enable_socket)
10001 if ((s = build_socket()) != -1) {
10002 channel = g_io_channel_unix_new(s);
10003 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10006 gtk_main();
10008 if (!g_thread_supported()) {
10009 gdk_threads_leave();
10012 gnutls_global_deinit();
10014 if (url_regex)
10015 regfree(&url_re);
10017 return (0);