shut up gcc 4.6.1 warnings
[xxxterm.git] / xxxterm.c
blob8d5ab2405e640c1945f9155b462685af5cca61ab
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pwd.h>
35 #include <regex.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <dirent.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #include <bsd/stdlib.h>
49 # if !defined(sane_libbsd_headers)
50 void arc4random_buf(void *, size_t);
51 # endif
52 #elif defined(__FreeBSD__)
53 #include <libutil.h>
54 #include "freebsd/util.h"
55 #include <sys/tree.h>
56 #else /* OpenBSD */
57 #include <util.h>
58 #include <sys/tree.h>
59 #endif
60 #include <sys/queue.h>
61 #include <sys/resource.h>
62 #include <sys/socket.h>
63 #include <sys/stat.h>
64 #include <sys/time.h>
65 #include <sys/un.h>
67 #include <gtk/gtk.h>
68 #include <gdk/gdkkeysyms.h>
70 #if GTK_CHECK_VERSION(3,0,0)
71 /* we still use GDK_* instead of GDK_KEY_* */
72 #include <gdk/gdkkeysyms-compat.h>
73 #endif
75 #include <webkit/webkit.h>
76 #include <libsoup/soup.h>
77 #include <JavaScriptCore/JavaScript.h>
78 #include <gnutls/gnutls.h>
79 #include <gnutls/x509.h>
81 #include "version.h"
82 #include "javascript.h"
84 /* comment if you don't want to use threads */
85 #define USE_THREADS
87 #ifdef USE_THREADS
88 #include <gcrypt.h>
89 #include <pthread.h>
90 GCRY_THREAD_OPTION_PTHREAD_IMPL;
91 #endif
95 javascript.h borrowed from vimprobable2 under the following license:
97 Copyright (c) 2009 Leon Winter
98 Copyright (c) 2009 Hannes Schueller
99 Copyright (c) 2009 Matto Fransen
101 Permission is hereby granted, free of charge, to any person obtaining a copy
102 of this software and associated documentation files (the "Software"), to deal
103 in the Software without restriction, including without limitation the rights
104 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
105 copies of the Software, and to permit persons to whom the Software is
106 furnished to do so, subject to the following conditions:
108 The above copyright notice and this permission notice shall be included in
109 all copies or substantial portions of the Software.
111 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
112 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
113 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
114 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
115 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
116 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
117 THE SOFTWARE.
120 static char *version = XXXTERM_VERSION;
122 /* hooked functions */
123 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
124 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
125 SoupCookie *);
127 /*#define XT_DEBUG*/
128 #ifdef XT_DEBUG
129 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
130 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
131 #define XT_D_MOVE 0x0001
132 #define XT_D_KEY 0x0002
133 #define XT_D_TAB 0x0004
134 #define XT_D_URL 0x0008
135 #define XT_D_CMD 0x0010
136 #define XT_D_NAV 0x0020
137 #define XT_D_DOWNLOAD 0x0040
138 #define XT_D_CONFIG 0x0080
139 #define XT_D_JS 0x0100
140 #define XT_D_FAVORITE 0x0200
141 #define XT_D_PRINTING 0x0400
142 #define XT_D_COOKIE 0x0800
143 #define XT_D_KEYBINDING 0x1000
144 #define XT_D_CLIP 0x2000
145 #define XT_D_BUFFERCMD 0x4000
146 u_int32_t swm_debug = 0
147 | XT_D_MOVE
148 | XT_D_KEY
149 | XT_D_TAB
150 | XT_D_URL
151 | XT_D_CMD
152 | XT_D_NAV
153 | XT_D_DOWNLOAD
154 | XT_D_CONFIG
155 | XT_D_JS
156 | XT_D_FAVORITE
157 | XT_D_PRINTING
158 | XT_D_COOKIE
159 | XT_D_KEYBINDING
160 | XT_D_CLIP
161 | XT_D_BUFFERCMD
163 #else
164 #define DPRINTF(x...)
165 #define DNPRINTF(n,x...)
166 #endif
168 #define LENGTH(x) (sizeof x / sizeof x[0])
169 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
170 ~(GDK_BUTTON1_MASK) & \
171 ~(GDK_BUTTON2_MASK) & \
172 ~(GDK_BUTTON3_MASK) & \
173 ~(GDK_BUTTON4_MASK) & \
174 ~(GDK_BUTTON5_MASK))
176 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
178 char *icons[] = {
179 "xxxtermicon16.png",
180 "xxxtermicon32.png",
181 "xxxtermicon48.png",
182 "xxxtermicon64.png",
183 "xxxtermicon128.png"
186 struct tab {
187 TAILQ_ENTRY(tab) entry;
188 GtkWidget *vbox;
189 GtkWidget *tab_content;
190 struct {
191 GtkWidget *label;
192 GtkWidget *eventbox;
193 GtkWidget *box;
194 GtkWidget *sep;
195 } tab_elems;
196 GtkWidget *label;
197 GtkWidget *spinner;
198 GtkWidget *uri_entry;
199 GtkWidget *search_entry;
200 GtkWidget *toolbar;
201 GtkWidget *browser_win;
202 GtkWidget *statusbar_box;
203 struct {
204 GtkWidget *statusbar;
205 GtkWidget *buffercmd;
206 GtkWidget *zoom;
207 GtkWidget *position;
208 } sbe;
209 GtkWidget *cmd;
210 GtkWidget *buffers;
211 GtkWidget *oops;
212 GtkWidget *backward;
213 GtkWidget *forward;
214 GtkWidget *stop;
215 GtkWidget *gohome;
216 GtkWidget *js_toggle;
217 GtkEntryCompletion *completion;
218 guint tab_id;
219 WebKitWebView *wv;
221 WebKitWebHistoryItem *item;
222 WebKitWebBackForwardList *bfl;
224 /* favicon */
225 WebKitNetworkRequest *icon_request;
226 WebKitDownload *icon_download;
227 gchar *icon_dest_uri;
229 /* adjustments for browser */
230 GtkScrollbar *sb_h;
231 GtkScrollbar *sb_v;
232 GtkAdjustment *adjust_h;
233 GtkAdjustment *adjust_v;
235 /* flags */
236 int focus_wv;
237 int ctrl_click;
238 gchar *status;
239 int xtp_meaning; /* identifies dls/favorites */
240 gchar *tmp_uri;
241 int popup; /* 1 if cmd_entry has popup visible */
242 #ifdef USE_THREADS
243 /* https thread stuff */
244 GThread *thread;
245 #endif
246 /* hints */
247 int hints_on;
248 int hint_mode;
249 #define XT_HINT_NONE (0)
250 #define XT_HINT_NUMERICAL (1)
251 #define XT_HINT_ALPHANUM (2)
252 char hint_buf[128];
253 char hint_num[128];
255 /* custom stylesheet */
256 int styled;
257 char *stylesheet;
259 /* search */
260 char *search_text;
261 int search_forward;
262 guint search_id;
264 /* settings */
265 WebKitWebSettings *settings;
266 gchar *user_agent;
268 /* marks */
269 double mark[XT_NOMARKS];
271 TAILQ_HEAD(tab_list, tab);
273 struct history {
274 RB_ENTRY(history) entry;
275 const gchar *uri;
276 const gchar *title;
278 RB_HEAD(history_list, history);
280 struct session {
281 TAILQ_ENTRY(session) entry;
282 const gchar *name;
284 TAILQ_HEAD(session_list, session);
286 struct download {
287 RB_ENTRY(download) entry;
288 int id;
289 WebKitDownload *download;
290 struct tab *tab;
292 RB_HEAD(download_list, download);
294 struct domain {
295 RB_ENTRY(domain) entry;
296 gchar *d;
297 int handy; /* app use */
299 RB_HEAD(domain_list, domain);
301 struct undo {
302 TAILQ_ENTRY(undo) entry;
303 gchar *uri;
304 GList *history;
305 int back; /* Keeps track of how many back
306 * history items there are. */
308 TAILQ_HEAD(undo_tailq, undo);
310 struct sp {
311 char *line;
312 TAILQ_ENTRY(sp) entry;
314 TAILQ_HEAD(sp_list, sp);
316 struct command_entry {
317 char *line;
318 TAILQ_ENTRY(command_entry) entry;
320 TAILQ_HEAD(command_list, command_entry);
322 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
323 int next_download_id = 1;
325 struct karg {
326 int i;
327 char *s;
328 int precount;
331 /* defines */
332 #define XT_NAME ("XXXTerm")
333 #define XT_DIR (".xxxterm")
334 #define XT_CACHE_DIR ("cache")
335 #define XT_CERT_DIR ("certs/")
336 #define XT_SESSIONS_DIR ("sessions/")
337 #define XT_CONF_FILE ("xxxterm.conf")
338 #define XT_FAVS_FILE ("favorites")
339 #define XT_QMARKS_FILE ("quickmarks")
340 #define XT_SAVED_TABS_FILE ("main_session")
341 #define XT_RESTART_TABS_FILE ("restart_tabs")
342 #define XT_SOCKET_FILE ("socket")
343 #define XT_HISTORY_FILE ("history")
344 #define XT_REJECT_FILE ("rejected.txt")
345 #define XT_COOKIE_FILE ("cookies.txt")
346 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
347 #define XT_SEARCH_FILE ("search_history")
348 #define XT_COMMAND_FILE ("command_history")
349 #define XT_CB_HANDLED (TRUE)
350 #define XT_CB_PASSTHROUGH (FALSE)
351 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
352 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
353 #define XT_DLMAN_REFRESH "10"
354 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
355 "td{overflow: hidden;" \
356 " padding: 2px 2px 2px 2px;" \
357 " border: 1px solid black;" \
358 " vertical-align:top;" \
359 " word-wrap: break-word}\n" \
360 "tr:hover{background: #ffff99}\n" \
361 "th{background-color: #cccccc;" \
362 " border: 1px solid black}\n" \
363 "table{width: 100%%;" \
364 " border: 1px black solid;" \
365 " border-collapse:collapse}\n" \
366 ".progress-outer{" \
367 "border: 1px solid black;" \
368 " height: 8px;" \
369 " width: 90%%}\n" \
370 ".progress-inner{float: left;" \
371 " height: 8px;" \
372 " background: green}\n" \
373 ".dlstatus{font-size: small;" \
374 " text-align: center}\n" \
375 "</style>\n"
376 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
377 #define XT_MAX_UNDO_CLOSE_TAB (32)
378 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
379 #define XT_PRINT_EXTRA_MARGIN 10
380 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
381 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
383 /* colors */
384 #define XT_COLOR_RED "#cc0000"
385 #define XT_COLOR_YELLOW "#ffff66"
386 #define XT_COLOR_BLUE "lightblue"
387 #define XT_COLOR_GREEN "#99ff66"
388 #define XT_COLOR_WHITE "white"
389 #define XT_COLOR_BLACK "black"
391 #define XT_COLOR_CT_BACKGROUND "#000000"
392 #define XT_COLOR_CT_INACTIVE "#dddddd"
393 #define XT_COLOR_CT_ACTIVE "#bbbb00"
394 #define XT_COLOR_CT_SEPARATOR "#555555"
396 #define XT_COLOR_SB_SEPARATOR "#555555"
398 #define XT_PROTO_DELIM "://"
401 * xxxterm "protocol" (xtp)
402 * We use this for managing stuff like downloads and favorites. They
403 * make magical HTML pages in memory which have xxxt:// links in order
404 * to communicate with xxxterm's internals. These links take the format:
405 * xxxt://class/session_key/action/arg
407 * Don't begin xtp class/actions as 0. atoi returns that on error.
409 * Typically we have not put addition of items in this framework, as
410 * adding items is either done via an ex-command or via a keybinding instead.
413 #define XT_XTP_STR "xxxt://"
415 /* XTP classes (xxxt://<class>) */
416 #define XT_XTP_INVALID 0 /* invalid */
417 #define XT_XTP_DL 1 /* downloads */
418 #define XT_XTP_HL 2 /* history */
419 #define XT_XTP_CL 3 /* cookies */
420 #define XT_XTP_FL 4 /* favorites */
422 /* XTP download actions */
423 #define XT_XTP_DL_LIST 1
424 #define XT_XTP_DL_CANCEL 2
425 #define XT_XTP_DL_REMOVE 3
427 /* XTP history actions */
428 #define XT_XTP_HL_LIST 1
429 #define XT_XTP_HL_REMOVE 2
431 /* XTP cookie actions */
432 #define XT_XTP_CL_LIST 1
433 #define XT_XTP_CL_REMOVE 2
435 /* XTP cookie actions */
436 #define XT_XTP_FL_LIST 1
437 #define XT_XTP_FL_REMOVE 2
439 /* actions */
440 #define XT_MOVE_INVALID (0)
441 #define XT_MOVE_DOWN (1)
442 #define XT_MOVE_UP (2)
443 #define XT_MOVE_BOTTOM (3)
444 #define XT_MOVE_TOP (4)
445 #define XT_MOVE_PAGEDOWN (5)
446 #define XT_MOVE_PAGEUP (6)
447 #define XT_MOVE_HALFDOWN (7)
448 #define XT_MOVE_HALFUP (8)
449 #define XT_MOVE_LEFT (9)
450 #define XT_MOVE_FARLEFT (10)
451 #define XT_MOVE_RIGHT (11)
452 #define XT_MOVE_FARRIGHT (12)
453 #define XT_MOVE_PERCENT (13)
455 #define XT_QMARK_SET (0)
456 #define XT_QMARK_OPEN (1)
457 #define XT_QMARK_TAB (2)
459 #define XT_MARK_SET (0)
460 #define XT_MARK_GOTO (1)
462 #define XT_TAB_LAST (-4)
463 #define XT_TAB_FIRST (-3)
464 #define XT_TAB_PREV (-2)
465 #define XT_TAB_NEXT (-1)
466 #define XT_TAB_INVALID (0)
467 #define XT_TAB_NEW (1)
468 #define XT_TAB_DELETE (2)
469 #define XT_TAB_DELQUIT (3)
470 #define XT_TAB_OPEN (4)
471 #define XT_TAB_UNDO_CLOSE (5)
472 #define XT_TAB_SHOW (6)
473 #define XT_TAB_HIDE (7)
474 #define XT_TAB_NEXTSTYLE (8)
476 #define XT_NAV_INVALID (0)
477 #define XT_NAV_BACK (1)
478 #define XT_NAV_FORWARD (2)
479 #define XT_NAV_RELOAD (3)
481 #define XT_FOCUS_INVALID (0)
482 #define XT_FOCUS_URI (1)
483 #define XT_FOCUS_SEARCH (2)
485 #define XT_SEARCH_INVALID (0)
486 #define XT_SEARCH_NEXT (1)
487 #define XT_SEARCH_PREV (2)
489 #define XT_PASTE_CURRENT_TAB (0)
490 #define XT_PASTE_NEW_TAB (1)
492 #define XT_ZOOM_IN (-1)
493 #define XT_ZOOM_OUT (-2)
494 #define XT_ZOOM_NORMAL (100)
496 #define XT_URL_SHOW (1)
497 #define XT_URL_HIDE (2)
499 #define XT_WL_TOGGLE (1<<0)
500 #define XT_WL_ENABLE (1<<1)
501 #define XT_WL_DISABLE (1<<2)
502 #define XT_WL_FQDN (1<<3) /* default */
503 #define XT_WL_TOPLEVEL (1<<4)
504 #define XT_WL_PERSISTENT (1<<5)
505 #define XT_WL_SESSION (1<<6)
506 #define XT_WL_RELOAD (1<<7)
508 #define XT_SHOW (1<<7)
509 #define XT_DELETE (1<<8)
510 #define XT_SAVE (1<<9)
511 #define XT_OPEN (1<<10)
513 #define XT_CMD_OPEN (0)
514 #define XT_CMD_OPEN_CURRENT (1)
515 #define XT_CMD_TABNEW (2)
516 #define XT_CMD_TABNEW_CURRENT (3)
518 #define XT_STATUS_NOTHING (0)
519 #define XT_STATUS_LINK (1)
520 #define XT_STATUS_URI (2)
521 #define XT_STATUS_LOADING (3)
523 #define XT_SES_DONOTHING (0)
524 #define XT_SES_CLOSETABS (1)
526 #define XT_BM_NORMAL (0)
527 #define XT_BM_WHITELIST (1)
528 #define XT_BM_KIOSK (2)
530 #define XT_PREFIX (1<<0)
531 #define XT_USERARG (1<<1)
532 #define XT_URLARG (1<<2)
533 #define XT_INTARG (1<<3)
534 #define XT_SESSARG (1<<4)
535 #define XT_SETARG (1<<5)
537 #define XT_TABS_NORMAL 0
538 #define XT_TABS_COMPACT 1
540 #define XT_BUFCMD_SZ (8)
542 /* mime types */
543 struct mime_type {
544 char *mt_type;
545 char *mt_action;
546 int mt_default;
547 int mt_download;
548 TAILQ_ENTRY(mime_type) entry;
550 TAILQ_HEAD(mime_type_list, mime_type);
552 /* uri aliases */
553 struct alias {
554 char *a_name;
555 char *a_uri;
556 TAILQ_ENTRY(alias) entry;
558 TAILQ_HEAD(alias_list, alias);
560 /* settings that require restart */
561 int tabless = 0; /* allow only 1 tab */
562 int enable_socket = 0;
563 int single_instance = 0; /* only allow one xxxterm to run */
564 int fancy_bar = 1; /* fancy toolbar */
565 int browser_mode = XT_BM_NORMAL;
566 int enable_localstorage = 1;
567 char *statusbar_elems = NULL;
569 /* runtime settings */
570 int show_tabs = 1; /* show tabs on notebook */
571 int tab_style = XT_TABS_NORMAL; /* tab bar style */
572 int show_url = 1; /* show url toolbar on notebook */
573 int show_statusbar = 0; /* vimperator style status bar */
574 int ctrl_click_focus = 0; /* ctrl click gets focus */
575 int cookies_enabled = 1; /* enable cookies */
576 int read_only_cookies = 0; /* enable to not write cookies */
577 int enable_scripts = 1;
578 int enable_plugins = 0;
579 gfloat default_zoom_level = 1.0;
580 char default_script[PATH_MAX];
581 int window_height = 768;
582 int window_width = 1024;
583 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
584 int refresh_interval = 10; /* download refresh interval */
585 int enable_cookie_whitelist = 0;
586 int enable_js_whitelist = 0;
587 int session_timeout = 3600; /* cookie session timeout */
588 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
589 char *ssl_ca_file = NULL;
590 char *resource_dir = NULL;
591 gboolean ssl_strict_certs = FALSE;
592 int append_next = 1; /* append tab after current tab */
593 char *home = NULL;
594 char *search_string = NULL;
595 char *http_proxy = NULL;
596 char download_dir[PATH_MAX];
597 char runtime_settings[PATH_MAX]; /* override of settings */
598 int allow_volatile_cookies = 0;
599 int save_global_history = 0; /* save global history to disk */
600 char *user_agent = NULL;
601 int save_rejected_cookies = 0;
602 int session_autosave = 0;
603 int guess_search = 0;
604 int dns_prefetch = FALSE;
605 gint max_connections = 25;
606 gint max_host_connections = 5;
607 gint enable_spell_checking = 0;
608 char *spell_check_languages = NULL;
609 int xterm_workaround = 0;
610 char *url_regex = NULL;
611 int history_autosave = 0;
612 char search_file[PATH_MAX];
613 char command_file[PATH_MAX];
614 char *encoding = NULL;
616 char *cmd_font_name = NULL;
617 char *oops_font_name = NULL;
618 char *statusbar_font_name = NULL;
619 char *tabbar_font_name = NULL;
620 PangoFontDescription *cmd_font;
621 PangoFontDescription *oops_font;
622 PangoFontDescription *statusbar_font;
623 PangoFontDescription *tabbar_font;
624 char *qmarks[XT_NOMARKS];
626 int btn_down; /* M1 down in any wv */
627 regex_t url_re; /* guess_search regex */
629 struct settings;
630 struct key_binding;
631 int set_browser_mode(struct settings *, char *);
632 int set_cookie_policy(struct settings *, char *);
633 int set_download_dir(struct settings *, char *);
634 int set_default_script(struct settings *, char *);
635 int set_runtime_dir(struct settings *, char *);
636 int set_tab_style(struct settings *, char *);
637 int set_work_dir(struct settings *, char *);
638 int add_alias(struct settings *, char *);
639 int add_mime_type(struct settings *, char *);
640 int add_cookie_wl(struct settings *, char *);
641 int add_js_wl(struct settings *, char *);
642 int add_kb(struct settings *, char *);
643 void button_set_stockid(GtkWidget *, char *);
644 GtkWidget * create_button(char *, char *, int);
646 char *get_browser_mode(struct settings *);
647 char *get_cookie_policy(struct settings *);
648 char *get_download_dir(struct settings *);
649 char *get_default_script(struct settings *);
650 char *get_runtime_dir(struct settings *);
651 char *get_tab_style(struct settings *);
652 char *get_work_dir(struct settings *);
653 void startpage_add(const char *, ...);
655 void walk_alias(struct settings *, void (*)(struct settings *,
656 char *, void *), void *);
657 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
658 char *, void *), void *);
659 void walk_js_wl(struct settings *, void (*)(struct settings *,
660 char *, void *), void *);
661 void walk_kb(struct settings *, void (*)(struct settings *, char *,
662 void *), void *);
663 void walk_mime_type(struct settings *, void (*)(struct settings *,
664 char *, void *), void *);
666 void recalc_tabs(void);
667 void recolor_compact_tabs(void);
668 void set_current_tab(int page_num);
669 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
670 void marks_clear(struct tab *t);
672 int set_http_proxy(char *);
674 struct special {
675 int (*set)(struct settings *, char *);
676 char *(*get)(struct settings *);
677 void (*walk)(struct settings *,
678 void (*cb)(struct settings *, char *, void *),
679 void *);
682 struct special s_browser_mode = {
683 set_browser_mode,
684 get_browser_mode,
685 NULL
688 struct special s_cookie = {
689 set_cookie_policy,
690 get_cookie_policy,
691 NULL
694 struct special s_alias = {
695 add_alias,
696 NULL,
697 walk_alias
700 struct special s_mime = {
701 add_mime_type,
702 NULL,
703 walk_mime_type
706 struct special s_js = {
707 add_js_wl,
708 NULL,
709 walk_js_wl
712 struct special s_kb = {
713 add_kb,
714 NULL,
715 walk_kb
718 struct special s_cookie_wl = {
719 add_cookie_wl,
720 NULL,
721 walk_cookie_wl
724 struct special s_default_script = {
725 set_default_script,
726 get_default_script,
727 NULL
730 struct special s_download_dir = {
731 set_download_dir,
732 get_download_dir,
733 NULL
736 struct special s_work_dir = {
737 set_work_dir,
738 get_work_dir,
739 NULL
742 struct special s_tab_style = {
743 set_tab_style,
744 get_tab_style,
745 NULL
748 struct settings {
749 char *name;
750 int type;
751 #define XT_S_INVALID (0)
752 #define XT_S_INT (1)
753 #define XT_S_STR (2)
754 #define XT_S_FLOAT (3)
755 uint32_t flags;
756 #define XT_SF_RESTART (1<<0)
757 #define XT_SF_RUNTIME (1<<1)
758 int *ival;
759 char **sval;
760 struct special *s;
761 gfloat *fval;
762 int (*activate)(char *);
763 } rs[] = {
764 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
765 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
766 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
767 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
768 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
769 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
770 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
771 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
772 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
773 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
774 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
775 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
776 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
777 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
778 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
779 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
780 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
781 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
782 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
783 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
784 { "home", XT_S_STR, 0, NULL, &home, NULL },
785 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
786 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
787 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
788 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
789 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
790 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
791 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
792 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
793 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
794 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
795 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
796 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
797 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
798 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
799 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
800 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
801 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
802 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
803 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
804 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
805 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
806 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
807 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
808 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
809 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
810 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
811 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
813 /* font settings */
814 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
815 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
816 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
817 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
819 /* runtime settings */
820 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
821 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
822 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
823 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
824 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
827 int about(struct tab *, struct karg *);
828 int blank(struct tab *, struct karg *);
829 int ca_cmd(struct tab *, struct karg *);
830 int cookie_show_wl(struct tab *, struct karg *);
831 int js_show_wl(struct tab *, struct karg *);
832 int help(struct tab *, struct karg *);
833 int set(struct tab *, struct karg *);
834 int stats(struct tab *, struct karg *);
835 int marco(struct tab *, struct karg *);
836 int startpage(struct tab *, struct karg *);
837 const char * marco_message(int *);
838 int xtp_page_cl(struct tab *, struct karg *);
839 int xtp_page_dl(struct tab *, struct karg *);
840 int xtp_page_fl(struct tab *, struct karg *);
841 int xtp_page_hl(struct tab *, struct karg *);
842 void xt_icon_from_file(struct tab *, char *);
843 const gchar *get_uri(struct tab *);
844 const gchar *get_title(struct tab *, bool);
846 #define XT_URI_ABOUT ("about:")
847 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
848 #define XT_URI_ABOUT_ABOUT ("about")
849 #define XT_URI_ABOUT_BLANK ("blank")
850 #define XT_URI_ABOUT_CERTS ("certs")
851 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
852 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
853 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
854 #define XT_URI_ABOUT_FAVORITES ("favorites")
855 #define XT_URI_ABOUT_HELP ("help")
856 #define XT_URI_ABOUT_HISTORY ("history")
857 #define XT_URI_ABOUT_JSWL ("jswl")
858 #define XT_URI_ABOUT_SET ("set")
859 #define XT_URI_ABOUT_STATS ("stats")
860 #define XT_URI_ABOUT_MARCO ("marco")
861 #define XT_URI_ABOUT_STARTPAGE ("startpage")
863 struct about_type {
864 char *name;
865 int (*func)(struct tab *, struct karg *);
866 } about_list[] = {
867 { XT_URI_ABOUT_ABOUT, about },
868 { XT_URI_ABOUT_BLANK, blank },
869 { XT_URI_ABOUT_CERTS, ca_cmd },
870 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
871 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
872 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
873 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
874 { XT_URI_ABOUT_HELP, help },
875 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
876 { XT_URI_ABOUT_JSWL, js_show_wl },
877 { XT_URI_ABOUT_SET, set },
878 { XT_URI_ABOUT_STATS, stats },
879 { XT_URI_ABOUT_MARCO, marco },
880 { XT_URI_ABOUT_STARTPAGE, startpage },
883 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
884 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
885 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
886 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
887 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
888 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
889 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
891 /* globals */
892 extern char *__progname;
893 char **start_argv;
894 struct passwd *pwd;
895 GtkWidget *main_window;
896 GtkNotebook *notebook;
897 GtkWidget *tab_bar;
898 GtkWidget *arrow, *abtn;
899 struct tab_list tabs;
900 struct history_list hl;
901 struct session_list sessions;
902 struct download_list downloads;
903 struct domain_list c_wl;
904 struct domain_list js_wl;
905 struct undo_tailq undos;
906 struct keybinding_list kbl;
907 struct sp_list spl;
908 struct command_list chl;
909 struct command_list shl;
910 struct command_entry *history_at;
911 struct command_entry *search_at;
912 int undo_count;
913 int updating_dl_tabs = 0;
914 int updating_hl_tabs = 0;
915 int updating_cl_tabs = 0;
916 int updating_fl_tabs = 0;
917 int cmd_history_count = 0;
918 int search_history_count = 0;
919 char *global_search;
920 long long unsigned int blocked_cookies = 0;
921 char named_session[PATH_MAX];
922 GtkListStore *completion_model;
923 GtkListStore *buffers_store;
925 void xxx_dir(char *);
926 int icon_size_map(int);
927 void completion_add(struct tab *);
928 void completion_add_uri(const gchar *);
929 void show_oops(struct tab *, const char *, ...);
931 void
932 history_delete(struct command_list *l, int *counter)
934 struct command_entry *c;
936 if (l == NULL || counter == NULL)
937 return;
939 c = TAILQ_LAST(l, command_list);
940 if (c == NULL)
941 return;
943 TAILQ_REMOVE(l, c, entry);
944 *counter -= 1;
945 g_free(c->line);
946 g_free(c);
949 void
950 history_add(struct command_list *list, char *file, char *l, int *counter)
952 struct command_entry *c;
953 FILE *f;
955 if (list == NULL || l == NULL || counter == NULL)
956 return;
958 /* don't add the same line */
959 c = TAILQ_FIRST(list);
960 if (c)
961 if (!strcmp(c->line + 1 /* skip space */, l))
962 return;
964 c = g_malloc0(sizeof *c);
965 c->line = g_strdup_printf(" %s", l);
967 *counter += 1;
968 TAILQ_INSERT_HEAD(list, c, entry);
970 if (*counter > 1000)
971 history_delete(list, counter);
973 if (history_autosave && file) {
974 f = fopen(file, "w");
975 if (f == NULL) {
976 show_oops(NULL, "couldn't write history %s", file);
977 return;
980 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
981 c->line[0] = ' ';
982 fprintf(f, "%s\n", c->line);
985 fclose(f);
990 history_read(struct command_list *list, char *file, int *counter)
992 FILE *f;
993 char *s, line[65536];
995 if (list == NULL || file == NULL)
996 return (1);
998 f = fopen(file, "r");
999 if (f == NULL) {
1000 startpage_add("couldn't open history file %s", file);
1001 return (1);
1004 for (;;) {
1005 s = fgets(line, sizeof line, f);
1006 if (s == NULL || feof(f) || ferror(f))
1007 break;
1008 if ((s = strchr(line, '\n')) == NULL) {
1009 startpage_add("invalid history file %s", file);
1010 fclose(f);
1011 return (1);
1013 *s = '\0';
1015 history_add(list, NULL, line + 1, counter);
1018 fclose(f);
1020 return (0);
1023 /* marks and quickmarks array storage.
1024 * first a-z, then A-Z, then 0-9 */
1025 char
1026 indextomark(int i)
1028 if (i < 0)
1029 return (0);
1031 if (i >= 0 && i <= 'z' - 'a')
1032 return 'a' + i;
1034 i -= 'z' - 'a' + 1;
1035 if (i >= 0 && i <= 'Z' - 'A')
1036 return 'A' + i;
1038 i -= 'Z' - 'A' + 1;
1039 if (i >= 10)
1040 return (0);
1042 return i + '0';
1046 marktoindex(char m)
1048 int ret = 0;
1050 if (m >= 'a' && m <= 'z')
1051 return ret + m - 'a';
1053 ret += 'z' - 'a' + 1;
1054 if (m >= 'A' && m <= 'Z')
1055 return ret + m - 'A';
1057 ret += 'Z' - 'A' + 1;
1058 if (m >= '0' && m <= '9')
1059 return ret + m - '0';
1061 return (-1);
1065 void
1066 sigchild(int sig)
1068 int saved_errno, status;
1069 pid_t pid;
1071 saved_errno = errno;
1073 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1074 if (pid == -1) {
1075 if (errno == EINTR)
1076 continue;
1077 if (errno != ECHILD) {
1079 clog_warn("sigchild: waitpid:");
1082 break;
1085 if (WIFEXITED(status)) {
1086 if (WEXITSTATUS(status) != 0) {
1088 clog_warnx("sigchild: child exit status: %d",
1089 WEXITSTATUS(status));
1092 } else {
1094 clog_warnx("sigchild: child is terminated abnormally");
1099 errno = saved_errno;
1103 is_g_object_setting(GObject *o, char *str)
1105 guint n_props = 0, i;
1106 GParamSpec **proplist;
1108 if (! G_IS_OBJECT(o))
1109 return (0);
1111 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1112 &n_props);
1114 for (i=0; i < n_props; i++) {
1115 if (! strcmp(proplist[i]->name, str))
1116 return (1);
1118 return (0);
1121 gchar *
1122 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1124 gchar *r;
1126 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1127 "<head>\n"
1128 "<title>%s</title>\n"
1129 "%s"
1130 "%s"
1131 "</head>\n"
1132 "<body>\n"
1133 "<h1>%s</h1>\n"
1134 "%s\n</body>\n"
1135 "</html>",
1136 title,
1137 addstyles ? XT_PAGE_STYLE : "",
1138 head,
1139 title,
1140 body);
1142 return r;
1146 * Display a web page from a HTML string in memory, rather than from a URL
1148 void
1149 load_webkit_string(struct tab *t, const char *str, gchar *title)
1151 char file[PATH_MAX];
1152 int i;
1154 /* we set this to indicate we want to manually do navaction */
1155 if (t->bfl)
1156 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1158 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1159 if (title) {
1160 /* set t->xtp_meaning */
1161 for (i = 0; i < LENGTH(about_list); i++)
1162 if (!strcmp(title, about_list[i].name)) {
1163 t->xtp_meaning = i;
1164 break;
1167 webkit_web_view_load_string(t->wv, str, NULL, encoding,
1168 "file://");
1169 #if GTK_CHECK_VERSION(2, 20, 0)
1170 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1171 gtk_widget_hide(t->spinner);
1172 #endif
1173 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1174 xt_icon_from_file(t, file);
1178 struct tab *
1179 get_current_tab(void)
1181 struct tab *t;
1183 TAILQ_FOREACH(t, &tabs, entry) {
1184 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1185 return (t);
1188 warnx("%s: no current tab", __func__);
1190 return (NULL);
1193 void
1194 set_status(struct tab *t, gchar *s, int status)
1196 gchar *type = NULL;
1198 if (s == NULL)
1199 return;
1201 switch (status) {
1202 case XT_STATUS_LOADING:
1203 type = g_strdup_printf("Loading: %s", s);
1204 s = type;
1205 break;
1206 case XT_STATUS_LINK:
1207 type = g_strdup_printf("Link: %s", s);
1208 if (!t->status)
1209 t->status = g_strdup(gtk_entry_get_text(
1210 GTK_ENTRY(t->sbe.statusbar)));
1211 s = type;
1212 break;
1213 case XT_STATUS_URI:
1214 type = g_strdup_printf("%s", s);
1215 if (!t->status) {
1216 t->status = g_strdup(type);
1218 s = type;
1219 if (!t->status)
1220 t->status = g_strdup(s);
1221 break;
1222 case XT_STATUS_NOTHING:
1223 /* FALL THROUGH */
1224 default:
1225 break;
1227 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1228 if (type)
1229 g_free(type);
1232 void
1233 hide_cmd(struct tab *t)
1235 history_at = NULL; /* just in case */
1236 search_at = NULL; /* just in case */
1237 gtk_widget_hide(t->cmd);
1240 void
1241 show_cmd(struct tab *t)
1243 history_at = NULL;
1244 search_at = NULL;
1245 gtk_widget_hide(t->oops);
1246 gtk_widget_show(t->cmd);
1249 void
1250 hide_buffers(struct tab *t)
1252 gtk_widget_hide(t->buffers);
1253 gtk_list_store_clear(buffers_store);
1256 enum {
1257 COL_ID = 0,
1258 COL_TITLE,
1259 NUM_COLS
1263 sort_tabs_by_page_num(struct tab ***stabs)
1265 int num_tabs = 0;
1266 struct tab *t;
1268 num_tabs = gtk_notebook_get_n_pages(notebook);
1270 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1272 TAILQ_FOREACH(t, &tabs, entry)
1273 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1275 return (num_tabs);
1278 void
1279 buffers_make_list(void)
1281 int i, num_tabs;
1282 const gchar *title = NULL;
1283 GtkTreeIter iter;
1284 struct tab **stabs = NULL;
1286 num_tabs = sort_tabs_by_page_num(&stabs);
1288 for (i = 0; i < num_tabs; i++)
1289 if (stabs[i]) {
1290 gtk_list_store_append(buffers_store, &iter);
1291 title = get_title(stabs[i], FALSE);
1292 gtk_list_store_set(buffers_store, &iter,
1293 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1294 * rather than 0. */
1295 COL_TITLE, title,
1296 -1);
1299 g_free(stabs);
1302 void
1303 show_buffers(struct tab *t)
1305 buffers_make_list();
1306 gtk_widget_show(t->buffers);
1307 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1310 void
1311 toggle_buffers(struct tab *t)
1313 if (gtk_widget_get_visible(t->buffers))
1314 hide_buffers(t);
1315 else
1316 show_buffers(t);
1320 buffers(struct tab *t, struct karg *args)
1322 show_buffers(t);
1324 return (0);
1327 void
1328 hide_oops(struct tab *t)
1330 gtk_widget_hide(t->oops);
1333 void
1334 show_oops(struct tab *at, const char *fmt, ...)
1336 va_list ap;
1337 char *msg = NULL;
1338 struct tab *t = NULL;
1340 if (fmt == NULL)
1341 return;
1343 if (at == NULL) {
1344 if ((t = get_current_tab()) == NULL)
1345 return;
1346 } else
1347 t = at;
1349 va_start(ap, fmt);
1350 if (vasprintf(&msg, fmt, ap) == -1)
1351 errx(1, "show_oops failed");
1352 va_end(ap);
1354 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1355 gtk_widget_hide(t->cmd);
1356 gtk_widget_show(t->oops);
1358 if (msg)
1359 free(msg);
1362 char *
1363 get_as_string(struct settings *s)
1365 char *r = NULL;
1367 if (s == NULL)
1368 return (NULL);
1370 if (s->s) {
1371 if (s->s->get)
1372 r = s->s->get(s);
1373 else
1374 warnx("get_as_string skip %s\n", s->name);
1375 } else if (s->type == XT_S_INT)
1376 r = g_strdup_printf("%d", *s->ival);
1377 else if (s->type == XT_S_STR)
1378 r = g_strdup(*s->sval);
1379 else if (s->type == XT_S_FLOAT)
1380 r = g_strdup_printf("%f", *s->fval);
1381 else
1382 r = g_strdup_printf("INVALID TYPE");
1384 return (r);
1387 void
1388 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1390 int i;
1391 char *s;
1393 for (i = 0; i < LENGTH(rs); i++) {
1394 if (rs[i].s && rs[i].s->walk)
1395 rs[i].s->walk(&rs[i], cb, cb_args);
1396 else {
1397 s = get_as_string(&rs[i]);
1398 cb(&rs[i], s, cb_args);
1399 g_free(s);
1405 set_browser_mode(struct settings *s, char *val)
1407 if (!strcmp(val, "whitelist")) {
1408 browser_mode = XT_BM_WHITELIST;
1409 allow_volatile_cookies = 0;
1410 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1411 cookies_enabled = 1;
1412 enable_cookie_whitelist = 1;
1413 read_only_cookies = 0;
1414 save_rejected_cookies = 0;
1415 session_timeout = 3600;
1416 enable_scripts = 0;
1417 enable_js_whitelist = 1;
1418 enable_localstorage = 0;
1419 } else if (!strcmp(val, "normal")) {
1420 browser_mode = XT_BM_NORMAL;
1421 allow_volatile_cookies = 0;
1422 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1423 cookies_enabled = 1;
1424 enable_cookie_whitelist = 0;
1425 read_only_cookies = 0;
1426 save_rejected_cookies = 0;
1427 session_timeout = 3600;
1428 enable_scripts = 1;
1429 enable_js_whitelist = 0;
1430 enable_localstorage = 1;
1431 } else if (!strcmp(val, "kiosk")) {
1432 browser_mode = XT_BM_KIOSK;
1433 allow_volatile_cookies = 0;
1434 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1435 cookies_enabled = 1;
1436 enable_cookie_whitelist = 0;
1437 read_only_cookies = 0;
1438 save_rejected_cookies = 0;
1439 session_timeout = 3600;
1440 enable_scripts = 1;
1441 enable_js_whitelist = 0;
1442 enable_localstorage = 1;
1443 show_tabs = 0;
1444 tabless = 1;
1445 } else
1446 return (1);
1448 return (0);
1451 char *
1452 get_browser_mode(struct settings *s)
1454 char *r = NULL;
1456 if (browser_mode == XT_BM_WHITELIST)
1457 r = g_strdup("whitelist");
1458 else if (browser_mode == XT_BM_NORMAL)
1459 r = g_strdup("normal");
1460 else if (browser_mode == XT_BM_KIOSK)
1461 r = g_strdup("kiosk");
1462 else
1463 return (NULL);
1465 return (r);
1469 set_cookie_policy(struct settings *s, char *val)
1471 if (!strcmp(val, "no3rdparty"))
1472 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1473 else if (!strcmp(val, "accept"))
1474 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1475 else if (!strcmp(val, "reject"))
1476 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1477 else
1478 return (1);
1480 return (0);
1483 char *
1484 get_cookie_policy(struct settings *s)
1486 char *r = NULL;
1488 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1489 r = g_strdup("no3rdparty");
1490 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1491 r = g_strdup("accept");
1492 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1493 r = g_strdup("reject");
1494 else
1495 return (NULL);
1497 return (r);
1500 char *
1501 get_default_script(struct settings *s)
1503 if (default_script[0] == '\0')
1504 return (0);
1505 return (g_strdup(default_script));
1509 set_default_script(struct settings *s, char *val)
1511 if (val[0] == '~')
1512 snprintf(default_script, sizeof default_script, "%s/%s",
1513 pwd->pw_dir, &val[1]);
1514 else
1515 strlcpy(default_script, val, sizeof default_script);
1517 return (0);
1520 char *
1521 get_download_dir(struct settings *s)
1523 if (download_dir[0] == '\0')
1524 return (0);
1525 return (g_strdup(download_dir));
1529 set_download_dir(struct settings *s, char *val)
1531 if (val[0] == '~')
1532 snprintf(download_dir, sizeof download_dir, "%s/%s",
1533 pwd->pw_dir, &val[1]);
1534 else
1535 strlcpy(download_dir, val, sizeof download_dir);
1537 return (0);
1541 * Session IDs.
1542 * We use these to prevent people putting xxxt:// URLs on
1543 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1545 #define XT_XTP_SES_KEY_SZ 8
1546 #define XT_XTP_SES_KEY_HEX_FMT \
1547 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1548 char *dl_session_key; /* downloads */
1549 char *hl_session_key; /* history list */
1550 char *cl_session_key; /* cookie list */
1551 char *fl_session_key; /* favorites list */
1553 char work_dir[PATH_MAX];
1554 char certs_dir[PATH_MAX];
1555 char cache_dir[PATH_MAX];
1556 char sessions_dir[PATH_MAX];
1557 char cookie_file[PATH_MAX];
1558 SoupURI *proxy_uri = NULL;
1559 SoupSession *session;
1560 SoupCookieJar *s_cookiejar;
1561 SoupCookieJar *p_cookiejar;
1562 char rc_fname[PATH_MAX];
1564 struct mime_type_list mtl;
1565 struct alias_list aliases;
1567 /* protos */
1568 struct tab *create_new_tab(char *, struct undo *, int, int);
1569 void delete_tab(struct tab *);
1570 void setzoom_webkit(struct tab *, int);
1571 int run_script(struct tab *, char *);
1572 int download_rb_cmp(struct download *, struct download *);
1573 gboolean cmd_execute(struct tab *t, char *str);
1576 history_rb_cmp(struct history *h1, struct history *h2)
1578 return (strcmp(h1->uri, h2->uri));
1580 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1583 domain_rb_cmp(struct domain *d1, struct domain *d2)
1585 return (strcmp(d1->d, d2->d));
1587 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1589 char *
1590 get_work_dir(struct settings *s)
1592 if (work_dir[0] == '\0')
1593 return (0);
1594 return (g_strdup(work_dir));
1598 set_work_dir(struct settings *s, char *val)
1600 if (val[0] == '~')
1601 snprintf(work_dir, sizeof work_dir, "%s/%s",
1602 pwd->pw_dir, &val[1]);
1603 else
1604 strlcpy(work_dir, val, sizeof work_dir);
1606 return (0);
1609 char *
1610 get_tab_style(struct settings *s)
1612 if (tab_style == XT_TABS_NORMAL)
1613 return (g_strdup("normal"));
1614 else
1615 return (g_strdup("compact"));
1619 set_tab_style(struct settings *s, char *val)
1621 if (!strcmp(val, "normal"))
1622 tab_style = XT_TABS_NORMAL;
1623 else if (!strcmp(val, "compact"))
1624 tab_style = XT_TABS_COMPACT;
1625 else
1626 return (1);
1628 return (0);
1632 * generate a session key to secure xtp commands.
1633 * pass in a ptr to the key in question and it will
1634 * be modified in place.
1636 void
1637 generate_xtp_session_key(char **key)
1639 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1641 /* free old key */
1642 if (*key)
1643 g_free(*key);
1645 /* make a new one */
1646 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1647 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1648 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1649 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1651 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1655 * validate a xtp session key.
1656 * return (1) if OK
1659 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1661 if (strcmp(trusted, untrusted) != 0) {
1662 show_oops(t, "%s: xtp session key mismatch possible spoof",
1663 __func__);
1664 return (0);
1667 return (1);
1671 download_rb_cmp(struct download *e1, struct download *e2)
1673 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1675 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1677 struct valid_url_types {
1678 char *type;
1679 } vut[] = {
1680 { "http://" },
1681 { "https://" },
1682 { "ftp://" },
1683 { "file://" },
1684 { XT_XTP_STR },
1688 valid_url_type(char *url)
1690 int i;
1692 for (i = 0; i < LENGTH(vut); i++)
1693 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1694 return (0);
1696 return (1);
1699 void
1700 print_cookie(char *msg, SoupCookie *c)
1702 if (c == NULL)
1703 return;
1705 if (msg)
1706 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1707 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1708 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1709 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1710 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1711 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1712 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1713 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1714 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1715 DNPRINTF(XT_D_COOKIE, "====================================\n");
1718 void
1719 walk_alias(struct settings *s,
1720 void (*cb)(struct settings *, char *, void *), void *cb_args)
1722 struct alias *a;
1723 char *str;
1725 if (s == NULL || cb == NULL) {
1726 show_oops(NULL, "walk_alias invalid parameters");
1727 return;
1730 TAILQ_FOREACH(a, &aliases, entry) {
1731 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1732 cb(s, str, cb_args);
1733 g_free(str);
1737 char *
1738 match_alias(char *url_in)
1740 struct alias *a;
1741 char *arg;
1742 char *url_out = NULL, *search, *enc_arg;
1744 search = g_strdup(url_in);
1745 arg = search;
1746 if (strsep(&arg, " \t") == NULL) {
1747 show_oops(NULL, "match_alias: NULL URL");
1748 goto done;
1751 TAILQ_FOREACH(a, &aliases, entry) {
1752 if (!strcmp(search, a->a_name))
1753 break;
1756 if (a != NULL) {
1757 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1758 a->a_name);
1759 if (arg != NULL) {
1760 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1761 url_out = g_strdup_printf(a->a_uri, enc_arg);
1762 g_free(enc_arg);
1763 } else
1764 url_out = g_strdup_printf(a->a_uri, "");
1766 done:
1767 g_free(search);
1768 return (url_out);
1771 char *
1772 guess_url_type(char *url_in)
1774 struct stat sb;
1775 char *url_out = NULL, *enc_search = NULL;
1776 int i;
1778 /* substitute aliases */
1779 url_out = match_alias(url_in);
1780 if (url_out != NULL)
1781 return (url_out);
1783 /* see if we are an about page */
1784 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1785 for (i = 0; i < LENGTH(about_list); i++)
1786 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1787 about_list[i].name)) {
1788 url_out = g_strdup(url_in);
1789 goto done;
1792 if (guess_search && url_regex &&
1793 !(g_str_has_prefix(url_in, "http://") ||
1794 g_str_has_prefix(url_in, "https://"))) {
1795 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1796 /* invalid URI so search instead */
1797 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1798 url_out = g_strdup_printf(search_string, enc_search);
1799 g_free(enc_search);
1800 goto done;
1804 /* XXX not sure about this heuristic */
1805 if (stat(url_in, &sb) == 0)
1806 url_out = g_strdup_printf("file://%s", url_in);
1807 else
1808 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1809 done:
1810 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1812 return (url_out);
1815 void
1816 load_uri(struct tab *t, gchar *uri)
1818 struct karg args;
1819 gchar *newuri = NULL;
1820 int i;
1822 if (uri == NULL)
1823 return;
1825 /* Strip leading spaces. */
1826 while (*uri && isspace(*uri))
1827 uri++;
1829 if (strlen(uri) == 0) {
1830 blank(t, NULL);
1831 return;
1834 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1836 if (valid_url_type(uri)) {
1837 newuri = guess_url_type(uri);
1838 uri = newuri;
1841 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1842 for (i = 0; i < LENGTH(about_list); i++)
1843 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1844 bzero(&args, sizeof args);
1845 about_list[i].func(t, &args);
1846 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1847 FALSE);
1848 goto done;
1850 show_oops(t, "invalid about page");
1851 goto done;
1854 set_status(t, (char *)uri, XT_STATUS_LOADING);
1855 marks_clear(t);
1856 webkit_web_view_load_uri(t->wv, uri);
1857 done:
1858 if (newuri)
1859 g_free(newuri);
1862 const gchar *
1863 get_uri(struct tab *t)
1865 const gchar *uri = NULL;
1867 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1868 return NULL;
1869 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1870 uri = webkit_web_view_get_uri(t->wv);
1871 } else {
1872 /* use tmp_uri to make sure it is g_freed */
1873 if (t->tmp_uri)
1874 g_free(t->tmp_uri);
1875 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1876 about_list[t->xtp_meaning].name);
1877 uri = t->tmp_uri;
1879 return uri;
1882 const gchar *
1883 get_title(struct tab *t, bool window)
1885 const gchar *set = NULL, *title = NULL;
1886 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1888 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1889 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1890 goto notitle;
1892 title = webkit_web_view_get_title(t->wv);
1893 if ((set = title ? title : get_uri(t)))
1894 return set;
1896 notitle:
1897 set = window ? XT_NAME : "(untitled)";
1899 return set;
1903 add_alias(struct settings *s, char *line)
1905 char *l, *alias;
1906 struct alias *a = NULL;
1908 if (s == NULL || line == NULL) {
1909 show_oops(NULL, "add_alias invalid parameters");
1910 return (1);
1913 l = line;
1914 a = g_malloc(sizeof(*a));
1916 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1917 show_oops(NULL, "add_alias: incomplete alias definition");
1918 goto bad;
1920 if (strlen(alias) == 0 || strlen(l) == 0) {
1921 show_oops(NULL, "add_alias: invalid alias definition");
1922 goto bad;
1925 a->a_name = g_strdup(alias);
1926 a->a_uri = g_strdup(l);
1928 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1930 TAILQ_INSERT_TAIL(&aliases, a, entry);
1932 return (0);
1933 bad:
1934 if (a)
1935 g_free(a);
1936 return (1);
1940 add_mime_type(struct settings *s, char *line)
1942 char *mime_type;
1943 char *l;
1944 struct mime_type *m = NULL;
1945 int downloadfirst = 0;
1947 /* XXX this could be smarter */
1949 if (line == NULL || strlen(line) == 0) {
1950 show_oops(NULL, "add_mime_type invalid parameters");
1951 return (1);
1954 l = line;
1955 if (*l == '@') {
1956 downloadfirst = 1;
1957 l++;
1959 m = g_malloc(sizeof(*m));
1961 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1962 show_oops(NULL, "add_mime_type: invalid mime_type");
1963 goto bad;
1965 if (mime_type[strlen(mime_type) - 1] == '*') {
1966 mime_type[strlen(mime_type) - 1] = '\0';
1967 m->mt_default = 1;
1968 } else
1969 m->mt_default = 0;
1971 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1972 show_oops(NULL, "add_mime_type: invalid mime_type");
1973 goto bad;
1976 m->mt_type = g_strdup(mime_type);
1977 m->mt_action = g_strdup(l);
1978 m->mt_download = downloadfirst;
1980 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1981 m->mt_type, m->mt_action, m->mt_default);
1983 TAILQ_INSERT_TAIL(&mtl, m, entry);
1985 return (0);
1986 bad:
1987 if (m)
1988 g_free(m);
1989 return (1);
1992 struct mime_type *
1993 find_mime_type(char *mime_type)
1995 struct mime_type *m, *def = NULL, *rv = NULL;
1997 TAILQ_FOREACH(m, &mtl, entry) {
1998 if (m->mt_default &&
1999 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
2000 def = m;
2002 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
2003 rv = m;
2004 break;
2008 if (rv == NULL)
2009 rv = def;
2011 return (rv);
2014 void
2015 walk_mime_type(struct settings *s,
2016 void (*cb)(struct settings *, char *, void *), void *cb_args)
2018 struct mime_type *m;
2019 char *str;
2021 if (s == NULL || cb == NULL) {
2022 show_oops(NULL, "walk_mime_type invalid parameters");
2023 return;
2026 TAILQ_FOREACH(m, &mtl, entry) {
2027 str = g_strdup_printf("%s%s --> %s",
2028 m->mt_type,
2029 m->mt_default ? "*" : "",
2030 m->mt_action);
2031 cb(s, str, cb_args);
2032 g_free(str);
2036 void
2037 wl_add(char *str, struct domain_list *wl, int handy)
2039 struct domain *d;
2040 int add_dot = 0;
2041 char *p;
2043 if (str == NULL || wl == NULL || strlen(str) < 2)
2044 return;
2046 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2048 /* treat *.moo.com the same as .moo.com */
2049 if (str[0] == '*' && str[1] == '.')
2050 str = &str[1];
2051 else if (str[0] == '.')
2052 str = &str[0];
2053 else
2054 add_dot = 1;
2056 /* slice off port number */
2057 p = g_strrstr(str, ":");
2058 if (p)
2059 *p = '\0';
2061 d = g_malloc(sizeof *d);
2062 if (add_dot)
2063 d->d = g_strdup_printf(".%s", str);
2064 else
2065 d->d = g_strdup(str);
2066 d->handy = handy;
2068 if (RB_INSERT(domain_list, wl, d))
2069 goto unwind;
2071 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2072 return;
2073 unwind:
2074 if (d) {
2075 if (d->d)
2076 g_free(d->d);
2077 g_free(d);
2082 add_cookie_wl(struct settings *s, char *entry)
2084 wl_add(entry, &c_wl, 1);
2085 return (0);
2088 void
2089 walk_cookie_wl(struct settings *s,
2090 void (*cb)(struct settings *, char *, void *), void *cb_args)
2092 struct domain *d;
2094 if (s == NULL || cb == NULL) {
2095 show_oops(NULL, "walk_cookie_wl invalid parameters");
2096 return;
2099 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2100 cb(s, d->d, cb_args);
2103 void
2104 walk_js_wl(struct settings *s,
2105 void (*cb)(struct settings *, char *, void *), void *cb_args)
2107 struct domain *d;
2109 if (s == NULL || cb == NULL) {
2110 show_oops(NULL, "walk_js_wl invalid parameters");
2111 return;
2114 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2115 cb(s, d->d, cb_args);
2119 add_js_wl(struct settings *s, char *entry)
2121 wl_add(entry, &js_wl, 1 /* persistent */);
2122 return (0);
2125 struct domain *
2126 wl_find(const gchar *search, struct domain_list *wl)
2128 int i;
2129 struct domain *d = NULL, dfind;
2130 gchar *s = NULL;
2132 if (search == NULL || wl == NULL)
2133 return (NULL);
2134 if (strlen(search) < 2)
2135 return (NULL);
2137 if (search[0] != '.')
2138 s = g_strdup_printf(".%s", search);
2139 else
2140 s = g_strdup(search);
2142 for (i = strlen(s) - 1; i >= 0; i--) {
2143 if (s[i] == '.') {
2144 dfind.d = &s[i];
2145 d = RB_FIND(domain_list, wl, &dfind);
2146 if (d)
2147 goto done;
2151 done:
2152 if (s)
2153 g_free(s);
2155 return (d);
2158 struct domain *
2159 wl_find_uri(const gchar *s, struct domain_list *wl)
2161 int i;
2162 char *ss;
2163 struct domain *r;
2165 if (s == NULL || wl == NULL)
2166 return (NULL);
2168 if (!strncmp(s, "http://", strlen("http://")))
2169 s = &s[strlen("http://")];
2170 else if (!strncmp(s, "https://", strlen("https://")))
2171 s = &s[strlen("https://")];
2173 if (strlen(s) < 2)
2174 return (NULL);
2176 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2177 /* chop string at first slash */
2178 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2179 ss = g_strdup(s);
2180 ss[i] = '\0';
2181 r = wl_find(ss, wl);
2182 g_free(ss);
2183 return (r);
2186 return (NULL);
2190 settings_add(char *var, char *val)
2192 int i, rv, *p;
2193 gfloat *f;
2194 char **s;
2196 /* get settings */
2197 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2198 if (strcmp(var, rs[i].name))
2199 continue;
2201 if (rs[i].s) {
2202 if (rs[i].s->set(&rs[i], val))
2203 errx(1, "invalid value for %s: %s", var, val);
2204 rv = 1;
2205 break;
2206 } else
2207 switch (rs[i].type) {
2208 case XT_S_INT:
2209 p = rs[i].ival;
2210 *p = atoi(val);
2211 rv = 1;
2212 break;
2213 case XT_S_STR:
2214 s = rs[i].sval;
2215 if (s == NULL)
2216 errx(1, "invalid sval for %s",
2217 rs[i].name);
2218 if (*s)
2219 g_free(*s);
2220 *s = g_strdup(val);
2221 rv = 1;
2222 break;
2223 case XT_S_FLOAT:
2224 f = rs[i].fval;
2225 *f = atof(val);
2226 rv = 1;
2227 break;
2228 case XT_S_INVALID:
2229 default:
2230 errx(1, "invalid type for %s", var);
2232 break;
2234 return (rv);
2237 #define WS "\n= \t"
2238 void
2239 config_parse(char *filename, int runtime)
2241 FILE *config, *f;
2242 char *line, *cp, *var, *val;
2243 size_t len, lineno = 0;
2244 int handled;
2245 char file[PATH_MAX];
2246 struct stat sb;
2248 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2250 if (filename == NULL)
2251 return;
2253 if (runtime && runtime_settings[0] != '\0') {
2254 snprintf(file, sizeof file, "%s/%s",
2255 work_dir, runtime_settings);
2256 if (stat(file, &sb)) {
2257 warnx("runtime file doesn't exist, creating it");
2258 if ((f = fopen(file, "w")) == NULL)
2259 err(1, "runtime");
2260 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2261 fclose(f);
2263 } else
2264 strlcpy(file, filename, sizeof file);
2266 if ((config = fopen(file, "r")) == NULL) {
2267 warn("config_parse: cannot open %s", filename);
2268 return;
2271 for (;;) {
2272 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2273 if (feof(config) || ferror(config))
2274 break;
2276 cp = line;
2277 cp += (long)strspn(cp, WS);
2278 if (cp[0] == '\0') {
2279 /* empty line */
2280 free(line);
2281 continue;
2284 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2285 startpage_add("invalid configuration file entry: %s",
2286 line);
2287 else {
2288 cp += (long)strspn(cp, WS);
2290 if ((val = strsep(&cp, "\0")) == NULL)
2291 break;
2293 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",
2294 var, val);
2295 handled = settings_add(var, val);
2297 if (handled == 0)
2298 startpage_add("invalid configuration file entry"
2299 ": %s=%s", var, val);
2302 free(line);
2305 fclose(config);
2308 char *
2309 js_ref_to_string(JSContextRef context, JSValueRef ref)
2311 char *s = NULL;
2312 size_t l;
2313 JSStringRef jsref;
2315 jsref = JSValueToStringCopy(context, ref, NULL);
2316 if (jsref == NULL)
2317 return (NULL);
2319 l = JSStringGetMaximumUTF8CStringSize(jsref);
2320 s = g_malloc(l);
2321 if (s)
2322 JSStringGetUTF8CString(jsref, s, l);
2323 JSStringRelease(jsref);
2325 return (s);
2328 void
2329 disable_hints(struct tab *t)
2331 bzero(t->hint_buf, sizeof t->hint_buf);
2332 bzero(t->hint_num, sizeof t->hint_num);
2333 run_script(t, "vimprobable_clear()");
2334 t->hints_on = 0;
2335 t->hint_mode = XT_HINT_NONE;
2338 void
2339 enable_hints(struct tab *t)
2341 bzero(t->hint_buf, sizeof t->hint_buf);
2342 run_script(t, "vimprobable_show_hints()");
2343 t->hints_on = 1;
2344 t->hint_mode = XT_HINT_NONE;
2347 #define XT_JS_OPEN ("open;")
2348 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2349 #define XT_JS_FIRE ("fire;")
2350 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2351 #define XT_JS_FOUND ("found;")
2352 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2355 run_script(struct tab *t, char *s)
2357 JSGlobalContextRef ctx;
2358 WebKitWebFrame *frame;
2359 JSStringRef str;
2360 JSValueRef val, exception;
2361 char *es, buf[128];
2363 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2364 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2366 frame = webkit_web_view_get_main_frame(t->wv);
2367 ctx = webkit_web_frame_get_global_context(frame);
2369 str = JSStringCreateWithUTF8CString(s);
2370 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2371 NULL, 0, &exception);
2372 JSStringRelease(str);
2374 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2375 if (val == NULL) {
2376 es = js_ref_to_string(ctx, exception);
2377 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2378 g_free(es);
2379 return (1);
2380 } else {
2381 es = js_ref_to_string(ctx, val);
2382 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2384 /* handle return value right here */
2385 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2386 disable_hints(t);
2387 marks_clear(t);
2388 load_uri(t, &es[XT_JS_OPEN_LEN]);
2391 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2392 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2393 &es[XT_JS_FIRE_LEN]);
2394 run_script(t, buf);
2395 disable_hints(t);
2398 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2399 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2400 disable_hints(t);
2403 g_free(es);
2406 return (0);
2410 hint(struct tab *t, struct karg *args)
2413 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2415 if (t->hints_on == 0)
2416 enable_hints(t);
2417 else
2418 disable_hints(t);
2420 return (0);
2423 void
2424 apply_style(struct tab *t)
2426 g_object_set(G_OBJECT(t->settings),
2427 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2431 userstyle(struct tab *t, struct karg *args)
2433 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2435 if (t->styled) {
2436 t->styled = 0;
2437 g_object_set(G_OBJECT(t->settings),
2438 "user-stylesheet-uri", NULL, (char *)NULL);
2439 } else {
2440 t->styled = 1;
2441 apply_style(t);
2443 return (0);
2447 * Doesn't work fully, due to the following bug:
2448 * https://bugs.webkit.org/show_bug.cgi?id=51747
2451 restore_global_history(void)
2453 char file[PATH_MAX];
2454 FILE *f;
2455 struct history *h;
2456 gchar *uri;
2457 gchar *title;
2458 const char delim[3] = {'\\', '\\', '\0'};
2460 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2462 if ((f = fopen(file, "r")) == NULL) {
2463 warnx("%s: fopen", __func__);
2464 return (1);
2467 for (;;) {
2468 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2469 if (feof(f) || ferror(f))
2470 break;
2472 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2473 if (feof(f) || ferror(f)) {
2474 free(uri);
2475 warnx("%s: broken history file\n", __func__);
2476 return (1);
2479 if (uri && strlen(uri) && title && strlen(title)) {
2480 webkit_web_history_item_new_with_data(uri, title);
2481 h = g_malloc(sizeof(struct history));
2482 h->uri = g_strdup(uri);
2483 h->title = g_strdup(title);
2484 RB_INSERT(history_list, &hl, h);
2485 completion_add_uri(h->uri);
2486 } else {
2487 warnx("%s: failed to restore history\n", __func__);
2488 free(uri);
2489 free(title);
2490 return (1);
2493 free(uri);
2494 free(title);
2495 uri = NULL;
2496 title = NULL;
2499 return (0);
2503 save_global_history_to_disk(struct tab *t)
2505 char file[PATH_MAX];
2506 FILE *f;
2507 struct history *h;
2509 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2511 if ((f = fopen(file, "w")) == NULL) {
2512 show_oops(t, "%s: global history file: %s",
2513 __func__, strerror(errno));
2514 return (1);
2517 RB_FOREACH_REVERSE(h, history_list, &hl) {
2518 if (h->uri && h->title)
2519 fprintf(f, "%s\n%s\n", h->uri, h->title);
2522 fclose(f);
2524 return (0);
2528 quit(struct tab *t, struct karg *args)
2530 if (save_global_history)
2531 save_global_history_to_disk(t);
2533 gtk_main_quit();
2535 return (1);
2538 void
2539 restore_sessions_list(void)
2541 DIR *sdir = NULL;
2542 struct dirent *dp = NULL;
2543 struct session *s;
2545 sdir = opendir(sessions_dir);
2546 if (sdir) {
2547 while ((dp = readdir(sdir)) != NULL)
2548 if (dp->d_type == DT_REG) {
2549 s = g_malloc(sizeof(struct session));
2550 s->name = g_strdup(dp->d_name);
2551 TAILQ_INSERT_TAIL(&sessions, s, entry);
2553 closedir(sdir);
2558 open_tabs(struct tab *t, struct karg *a)
2560 char file[PATH_MAX];
2561 FILE *f = NULL;
2562 char *uri = NULL;
2563 int rv = 1;
2564 struct tab *ti, *tt;
2566 if (a == NULL)
2567 goto done;
2569 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2570 if ((f = fopen(file, "r")) == NULL)
2571 goto done;
2573 ti = TAILQ_LAST(&tabs, tab_list);
2575 for (;;) {
2576 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2577 if (feof(f) || ferror(f))
2578 break;
2580 /* retrieve session name */
2581 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2582 strlcpy(named_session,
2583 &uri[strlen(XT_SAVE_SESSION_ID)],
2584 sizeof named_session);
2585 continue;
2588 if (uri && strlen(uri))
2589 create_new_tab(uri, NULL, 1, -1);
2591 free(uri);
2592 uri = NULL;
2595 /* close open tabs */
2596 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2597 for (;;) {
2598 tt = TAILQ_FIRST(&tabs);
2599 if (tt != ti) {
2600 delete_tab(tt);
2601 continue;
2603 delete_tab(tt);
2604 break;
2606 recalc_tabs();
2609 rv = 0;
2610 done:
2611 if (f)
2612 fclose(f);
2614 return (rv);
2618 restore_saved_tabs(void)
2620 char file[PATH_MAX];
2621 int unlink_file = 0;
2622 struct stat sb;
2623 struct karg a;
2624 int rv = 0;
2626 snprintf(file, sizeof file, "%s/%s",
2627 sessions_dir, XT_RESTART_TABS_FILE);
2628 if (stat(file, &sb) == -1)
2629 a.s = XT_SAVED_TABS_FILE;
2630 else {
2631 unlink_file = 1;
2632 a.s = XT_RESTART_TABS_FILE;
2635 a.i = XT_SES_DONOTHING;
2636 rv = open_tabs(NULL, &a);
2638 if (unlink_file)
2639 unlink(file);
2641 return (rv);
2645 save_tabs(struct tab *t, struct karg *a)
2647 char file[PATH_MAX];
2648 FILE *f;
2649 int num_tabs = 0, i;
2650 struct tab **stabs = NULL;
2652 if (a == NULL)
2653 return (1);
2654 if (a->s == NULL)
2655 snprintf(file, sizeof file, "%s/%s",
2656 sessions_dir, named_session);
2657 else
2658 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2660 if ((f = fopen(file, "w")) == NULL) {
2661 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2662 return (1);
2665 /* save session name */
2666 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2668 /* Save tabs, in the order they are arranged in the notebook. */
2669 num_tabs = sort_tabs_by_page_num(&stabs);
2671 for (i = 0; i < num_tabs; i++)
2672 if (stabs[i]) {
2673 if (get_uri(stabs[i]) != NULL)
2674 fprintf(f, "%s\n", get_uri(stabs[i]));
2675 else if (gtk_entry_get_text(GTK_ENTRY(
2676 stabs[i]->uri_entry)))
2677 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2678 stabs[i]->uri_entry)));
2681 g_free(stabs);
2683 /* try and make sure this gets to disk NOW. XXX Backup first? */
2684 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2685 show_oops(t, "May not have managed to save session: %s",
2686 strerror(errno));
2689 fclose(f);
2691 return (0);
2695 save_tabs_and_quit(struct tab *t, struct karg *args)
2697 struct karg a;
2699 a.s = NULL;
2700 save_tabs(t, &a);
2701 quit(t, NULL);
2703 return (1);
2707 run_page_script(struct tab *t, struct karg *args)
2709 const gchar *uri;
2710 char *tmp, script[PATH_MAX];
2712 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2713 if (tmp[0] == '\0') {
2714 show_oops(t, "no script specified");
2715 return (1);
2718 if ((uri = get_uri(t)) == NULL) {
2719 show_oops(t, "tab is empty, not running script");
2720 return (1);
2723 if (tmp[0] == '~')
2724 snprintf(script, sizeof script, "%s/%s",
2725 pwd->pw_dir, &tmp[1]);
2726 else
2727 strlcpy(script, tmp, sizeof script);
2729 switch (fork()) {
2730 case -1:
2731 show_oops(t, "can't fork to run script");
2732 return (1);
2733 /* NOTREACHED */
2734 case 0:
2735 break;
2736 default:
2737 return (0);
2740 /* child */
2741 execlp(script, script, uri, (void *)NULL);
2743 _exit(0);
2745 /* NOTREACHED */
2747 return (0);
2751 yank_uri(struct tab *t, struct karg *args)
2753 const gchar *uri;
2754 GtkClipboard *clipboard;
2756 if ((uri = get_uri(t)) == NULL)
2757 return (1);
2759 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2760 gtk_clipboard_set_text(clipboard, uri, -1);
2762 return (0);
2766 paste_uri(struct tab *t, struct karg *args)
2768 GtkClipboard *clipboard;
2769 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2770 gint len;
2771 gchar *p = NULL, *uri;
2773 /* try primary clipboard first */
2774 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2775 p = gtk_clipboard_wait_for_text(clipboard);
2777 /* if it failed get whatever text is in cut_buffer0 */
2778 if (p == NULL && xterm_workaround)
2779 if (gdk_property_get(gdk_get_default_root_window(),
2780 atom,
2781 gdk_atom_intern("STRING", FALSE),
2783 1024 * 1024 /* picked out of my butt */,
2784 FALSE,
2785 NULL,
2786 NULL,
2787 &len,
2788 (guchar **)&p)) {
2789 /* yes sir, we need to NUL the string */
2790 p[len] = '\0';
2793 if (p) {
2794 uri = p;
2795 while (*uri && isspace(*uri))
2796 uri++;
2797 if (strlen(uri) == 0) {
2798 show_oops(t, "empty paste buffer");
2799 goto done;
2801 if (guess_search == 0 && valid_url_type(uri)) {
2802 /* we can be clever and paste this in search box */
2803 show_oops(t, "not a valid URL");
2804 goto done;
2807 if (args->i == XT_PASTE_CURRENT_TAB)
2808 load_uri(t, uri);
2809 else if (args->i == XT_PASTE_NEW_TAB)
2810 create_new_tab(uri, NULL, 1, -1);
2813 done:
2814 if (p)
2815 g_free(p);
2817 return (0);
2820 gchar *
2821 find_domain(const gchar *s, int toplevel)
2823 SoupURI *uri;
2824 gchar *ret, *p;
2826 if (s == NULL)
2827 return (NULL);
2829 uri = soup_uri_new(s);
2831 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2832 return (NULL);
2835 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2836 if ((p = strrchr(uri->host, '.')) != NULL) {
2837 while(--p >= uri->host && *p != '.');
2838 p++;
2839 } else
2840 p = uri->host;
2841 } else
2842 p = uri->host;
2844 ret = g_strdup_printf(".%s", p);
2846 soup_uri_free(uri);
2848 return ret;
2852 toggle_cwl(struct tab *t, struct karg *args)
2854 struct domain *d;
2855 const gchar *uri;
2856 char *dom = NULL;
2857 int es;
2859 if (args == NULL)
2860 return (1);
2862 uri = get_uri(t);
2863 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2865 if (uri == NULL || dom == NULL ||
2866 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2867 show_oops(t, "Can't toggle domain in cookie white list");
2868 goto done;
2870 d = wl_find(dom, &c_wl);
2872 if (d == NULL)
2873 es = 0;
2874 else
2875 es = 1;
2877 if (args->i & XT_WL_TOGGLE)
2878 es = !es;
2879 else if ((args->i & XT_WL_ENABLE) && es != 1)
2880 es = 1;
2881 else if ((args->i & XT_WL_DISABLE) && es != 0)
2882 es = 0;
2884 if (es)
2885 /* enable cookies for domain */
2886 wl_add(dom, &c_wl, 0);
2887 else
2888 /* disable cookies for domain */
2889 RB_REMOVE(domain_list, &c_wl, d);
2891 if (args->i & XT_WL_RELOAD)
2892 webkit_web_view_reload(t->wv);
2894 done:
2895 g_free(dom);
2896 return (0);
2900 toggle_js(struct tab *t, struct karg *args)
2902 int es;
2903 const gchar *uri;
2904 struct domain *d;
2905 char *dom = NULL;
2907 if (args == NULL)
2908 return (1);
2910 g_object_get(G_OBJECT(t->settings),
2911 "enable-scripts", &es, (char *)NULL);
2912 if (args->i & XT_WL_TOGGLE)
2913 es = !es;
2914 else if ((args->i & XT_WL_ENABLE) && es != 1)
2915 es = 1;
2916 else if ((args->i & XT_WL_DISABLE) && es != 0)
2917 es = 0;
2918 else
2919 return (1);
2921 uri = get_uri(t);
2922 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2924 if (uri == NULL || dom == NULL ||
2925 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2926 show_oops(t, "Can't toggle domain in JavaScript white list");
2927 goto done;
2930 if (es) {
2931 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2932 wl_add(dom, &js_wl, 0 /* session */);
2933 } else {
2934 d = wl_find(dom, &js_wl);
2935 if (d)
2936 RB_REMOVE(domain_list, &js_wl, d);
2937 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2939 g_object_set(G_OBJECT(t->settings),
2940 "enable-scripts", es, (char *)NULL);
2941 g_object_set(G_OBJECT(t->settings),
2942 "javascript-can-open-windows-automatically", es, (char *)NULL);
2943 webkit_web_view_set_settings(t->wv, t->settings);
2945 if (args->i & XT_WL_RELOAD)
2946 webkit_web_view_reload(t->wv);
2947 done:
2948 if (dom)
2949 g_free(dom);
2950 return (0);
2953 void
2954 js_toggle_cb(GtkWidget *w, struct tab *t)
2956 struct karg a;
2958 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2959 toggle_cwl(t, &a);
2961 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2962 toggle_js(t, &a);
2966 toggle_src(struct tab *t, struct karg *args)
2968 gboolean mode;
2970 if (t == NULL)
2971 return (0);
2973 mode = webkit_web_view_get_view_source_mode(t->wv);
2974 webkit_web_view_set_view_source_mode(t->wv, !mode);
2975 webkit_web_view_reload(t->wv);
2977 return (0);
2980 void
2981 focus_webview(struct tab *t)
2983 if (t == NULL)
2984 return;
2986 /* only grab focus if we are visible */
2987 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2988 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2992 focus(struct tab *t, struct karg *args)
2994 if (t == NULL || args == NULL)
2995 return (1);
2997 if (show_url == 0)
2998 return (0);
3000 if (args->i == XT_FOCUS_URI)
3001 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
3002 else if (args->i == XT_FOCUS_SEARCH)
3003 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
3005 return (0);
3009 stats(struct tab *t, struct karg *args)
3011 char *page, *body, *s, line[64 * 1024];
3012 long long unsigned int line_count = 0;
3013 FILE *r_cookie_f;
3015 if (t == NULL)
3016 show_oops(NULL, "stats invalid parameters");
3018 line[0] = '\0';
3019 if (save_rejected_cookies) {
3020 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3021 for (;;) {
3022 s = fgets(line, sizeof line, r_cookie_f);
3023 if (s == NULL || feof(r_cookie_f) ||
3024 ferror(r_cookie_f))
3025 break;
3026 line_count++;
3028 fclose(r_cookie_f);
3029 snprintf(line, sizeof line,
3030 "<br/>Cookies blocked(*) total: %llu", line_count);
3031 } else
3032 show_oops(t, "Can't open blocked cookies file: %s",
3033 strerror(errno));
3036 body = g_strdup_printf(
3037 "Cookies blocked(*) this session: %llu"
3038 "%s"
3039 "<p><small><b>*</b> results vary based on settings</small></p>",
3040 blocked_cookies,
3041 line);
3043 page = get_html_page("Statistics", body, "", 0);
3044 g_free(body);
3046 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3047 g_free(page);
3049 return (0);
3053 marco(struct tab *t, struct karg *args)
3055 char *page, line[64 * 1024];
3056 int len;
3058 if (t == NULL)
3059 show_oops(NULL, "marco invalid parameters");
3061 line[0] = '\0';
3062 snprintf(line, sizeof line, "%s", marco_message(&len));
3064 page = get_html_page("Marco Sez...", line, "", 0);
3066 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3067 g_free(page);
3069 return (0);
3073 blank(struct tab *t, struct karg *args)
3075 if (t == NULL)
3076 show_oops(NULL, "blank invalid parameters");
3078 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3080 return (0);
3084 about(struct tab *t, struct karg *args)
3086 char *page, *body;
3088 if (t == NULL)
3089 show_oops(NULL, "about invalid parameters");
3091 body = g_strdup_printf("<b>Version: %s</b>"
3092 #ifdef XXXTERM_BUILDSTR
3093 "<br><b>Build: %s</b>"
3094 #endif
3095 "<p>"
3096 "Authors:"
3097 "<ul>"
3098 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3099 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3100 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3101 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3102 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3103 "</ul>"
3104 "Copyrights and licenses can be found on the XXXTerm "
3105 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3106 "</p>",
3107 #ifdef XXXTERM_BUILDSTR
3108 version, XXXTERM_BUILDSTR
3109 #else
3110 version
3111 #endif
3114 page = get_html_page("About", body, "", 0);
3115 g_free(body);
3117 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3118 g_free(page);
3120 return (0);
3124 help(struct tab *t, struct karg *args)
3126 char *page, *head, *body;
3128 if (t == NULL)
3129 show_oops(NULL, "help invalid parameters");
3131 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3132 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3133 "</head>\n";
3134 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3135 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3136 "cgi-bin/man-cgi?xxxterm</a>";
3138 page = get_html_page(XT_NAME, body, head, FALSE);
3140 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3141 g_free(page);
3143 return (0);
3147 startpage(struct tab *t, struct karg *args)
3149 char *page, *body, *b;
3150 struct sp *s;
3152 if (t == NULL)
3153 show_oops(NULL, "startpage invalid parameters");
3155 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3157 TAILQ_FOREACH(s, &spl, entry) {
3158 b = body;
3159 body = g_strdup_printf("%s%s<br>", body, s->line);
3160 g_free(b);
3163 page = get_html_page("Startup Exception", body, "", 0);
3164 g_free(body);
3166 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3167 g_free(page);
3169 return (0);
3172 void
3173 startpage_add(const char *fmt, ...)
3175 va_list ap;
3176 char *msg;
3177 struct sp *s;
3179 if (fmt == NULL)
3180 return;
3182 va_start(ap, fmt);
3183 if (vasprintf(&msg, fmt, ap) == -1)
3184 errx(1, "startpage_add failed");
3185 va_end(ap);
3187 s = g_malloc0(sizeof *s);
3188 s->line = msg;
3190 TAILQ_INSERT_TAIL(&spl, s, entry);
3194 * update all favorite tabs apart from one. Pass NULL if
3195 * you want to update all.
3197 void
3198 update_favorite_tabs(struct tab *apart_from)
3200 struct tab *t;
3201 if (!updating_fl_tabs) {
3202 updating_fl_tabs = 1; /* stop infinite recursion */
3203 TAILQ_FOREACH(t, &tabs, entry)
3204 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3205 && (t != apart_from))
3206 xtp_page_fl(t, NULL);
3207 updating_fl_tabs = 0;
3211 /* show a list of favorites (bookmarks) */
3213 xtp_page_fl(struct tab *t, struct karg *args)
3215 char file[PATH_MAX];
3216 FILE *f;
3217 char *uri = NULL, *title = NULL;
3218 size_t len, lineno = 0;
3219 int i, failed = 0;
3220 char *body, *tmp, *page = NULL;
3221 const char delim[3] = {'\\', '\\', '\0'};
3223 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3225 if (t == NULL)
3226 warn("%s: bad param", __func__);
3228 /* new session key */
3229 if (!updating_fl_tabs)
3230 generate_xtp_session_key(&fl_session_key);
3232 /* open favorites */
3233 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3234 if ((f = fopen(file, "r")) == NULL) {
3235 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3236 return (1);
3239 /* body */
3240 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3241 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3242 "<th style='width: 40px'>Rm</th></tr>\n");
3244 for (i = 1;;) {
3245 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3246 break;
3247 if (strlen(title) == 0 || title[0] == '#') {
3248 free(title);
3249 title = NULL;
3250 continue;
3253 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3254 if (feof(f) || ferror(f)) {
3255 show_oops(t, "favorites file corrupt");
3256 failed = 1;
3257 break;
3260 tmp = body;
3261 body = g_strdup_printf("%s<tr>"
3262 "<td>%d</td>"
3263 "<td><a href='%s'>%s</a></td>"
3264 "<td style='text-align: center'>"
3265 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3266 "</tr>\n",
3267 body, i, uri, title,
3268 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3270 g_free(tmp);
3272 free(uri);
3273 uri = NULL;
3274 free(title);
3275 title = NULL;
3276 i++;
3278 fclose(f);
3280 /* if none, say so */
3281 if (i == 1) {
3282 tmp = body;
3283 body = g_strdup_printf("%s<tr>"
3284 "<td colspan='3' style='text-align: center'>"
3285 "No favorites - To add one use the 'favadd' command."
3286 "</td></tr>", body);
3287 g_free(tmp);
3290 tmp = body;
3291 body = g_strdup_printf("%s</table>", body);
3292 g_free(tmp);
3294 if (uri)
3295 free(uri);
3296 if (title)
3297 free(title);
3299 /* render */
3300 if (!failed) {
3301 page = get_html_page("Favorites", body, "", 1);
3302 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3303 g_free(page);
3306 update_favorite_tabs(t);
3308 if (body)
3309 g_free(body);
3311 return (failed);
3314 void
3315 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3316 size_t cert_count, char *title)
3318 gnutls_datum_t cinfo;
3319 char *tmp, *body;
3320 int i;
3322 body = g_strdup("");
3324 for (i = 0; i < cert_count; i++) {
3325 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3326 &cinfo))
3327 return;
3329 tmp = body;
3330 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3331 body, i, cinfo.data);
3332 gnutls_free(cinfo.data);
3333 g_free(tmp);
3336 tmp = get_html_page(title, body, "", 0);
3337 g_free(body);
3339 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3340 g_free(tmp);
3344 ca_cmd(struct tab *t, struct karg *args)
3346 FILE *f = NULL;
3347 int rv = 1, certs = 0, certs_read;
3348 struct stat sb;
3349 gnutls_datum_t dt;
3350 gnutls_x509_crt_t *c = NULL;
3351 char *certs_buf = NULL, *s;
3353 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3354 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3355 return (1);
3358 if (fstat(fileno(f), &sb) == -1) {
3359 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3360 goto done;
3363 certs_buf = g_malloc(sb.st_size + 1);
3364 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3365 show_oops(t, "Can't read CA file: %s", strerror(errno));
3366 goto done;
3368 certs_buf[sb.st_size] = '\0';
3370 s = certs_buf;
3371 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3372 certs++;
3373 s += strlen("BEGIN CERTIFICATE");
3376 bzero(&dt, sizeof dt);
3377 dt.data = (unsigned char *)certs_buf;
3378 dt.size = sb.st_size;
3379 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3380 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3381 GNUTLS_X509_FMT_PEM, 0);
3382 if (certs_read <= 0) {
3383 show_oops(t, "No cert(s) available");
3384 goto done;
3386 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3387 done:
3388 if (c)
3389 g_free(c);
3390 if (certs_buf)
3391 g_free(certs_buf);
3392 if (f)
3393 fclose(f);
3395 return (rv);
3399 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
3400 size_t domain_sz)
3402 SoupURI *su = NULL;
3403 struct addrinfo hints, *res = NULL, *ai;
3404 int rv = -1, s = -1, on, error;
3405 char port[8];
3406 static gchar myerror[256]; /* this is not thread safe */
3408 myerror[0] = '\0';
3409 *error_str = myerror;
3410 if (uri && !g_str_has_prefix(uri, "https://")) {
3411 *error_str = "invalid URI";
3412 goto done;
3415 su = soup_uri_new(uri);
3416 if (su == NULL) {
3417 *error_str = "invalid soup URI";
3418 goto done;
3420 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3421 *error_str = "invalid HTTPS URI";
3422 goto done;
3425 snprintf(port, sizeof port, "%d", su->port);
3426 bzero(&hints, sizeof(struct addrinfo));
3427 hints.ai_flags = AI_CANONNAME;
3428 hints.ai_family = AF_UNSPEC;
3429 hints.ai_socktype = SOCK_STREAM;
3431 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3432 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
3433 gai_strerror(errno));
3434 goto done;
3437 for (ai = res; ai; ai = ai->ai_next) {
3438 if (s != -1) {
3439 close(s);
3440 s = -1;
3443 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3444 continue;
3445 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3446 if (s == -1)
3447 continue;
3448 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3449 sizeof(on)) == -1)
3450 continue;
3451 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3452 break;
3454 if (s == -1) {
3455 snprintf(myerror, sizeof myerror,
3456 "could not obtain certificates from: %s",
3457 su->host);
3458 goto done;
3461 if (domain)
3462 strlcpy(domain, su->host, domain_sz);
3463 rv = s;
3464 done:
3465 if (su)
3466 soup_uri_free(su);
3467 if (res)
3468 freeaddrinfo(res);
3469 if (rv == -1 && s != -1)
3470 close(s);
3472 return (rv);
3476 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3478 if (gsession)
3479 gnutls_deinit(gsession);
3480 if (xcred)
3481 gnutls_certificate_free_credentials(xcred);
3483 return (0);
3487 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
3488 gnutls_certificate_credentials_t *xc)
3490 gnutls_certificate_credentials_t xcred;
3491 gnutls_session_t gsession;
3492 int rv = 1;
3493 static gchar myerror[1024]; /* this is not thread safe */
3495 if (gs == NULL || xc == NULL)
3496 goto done;
3498 myerror[0] = '\0';
3499 *gs = NULL;
3500 *xc = NULL;
3502 gnutls_certificate_allocate_credentials(&xcred);
3503 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3504 GNUTLS_X509_FMT_PEM);
3506 gnutls_init(&gsession, GNUTLS_CLIENT);
3507 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3508 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3509 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3510 if ((rv = gnutls_handshake(gsession)) < 0) {
3511 snprintf(myerror, sizeof myerror,
3512 "gnutls_handshake failed %d fatal %d %s",
3514 gnutls_error_is_fatal(rv),
3515 gnutls_strerror_name(rv));
3516 stop_tls(gsession, xcred);
3517 goto done;
3520 gnutls_credentials_type_t cred;
3521 cred = gnutls_auth_get_type(gsession);
3522 if (cred != GNUTLS_CRD_CERTIFICATE) {
3523 snprintf(myerror, sizeof myerror,
3524 "gnutls_auth_get_type failed %d",
3525 (int)cred);
3526 stop_tls(gsession, xcred);
3527 goto done;
3530 *gs = gsession;
3531 *xc = xcred;
3532 rv = 0;
3533 done:
3534 *error_str = myerror;
3535 return (rv);
3539 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3540 size_t *cert_count)
3542 unsigned int len;
3543 const gnutls_datum_t *cl;
3544 gnutls_x509_crt_t *all_certs;
3545 int i, rv = 1;
3547 if (certs == NULL || cert_count == NULL)
3548 goto done;
3549 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3550 goto done;
3551 cl = gnutls_certificate_get_peers(gsession, &len);
3552 if (len == 0)
3553 goto done;
3555 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3556 for (i = 0; i < len; i++) {
3557 gnutls_x509_crt_init(&all_certs[i]);
3558 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3559 GNUTLS_X509_FMT_PEM < 0)) {
3560 g_free(all_certs);
3561 goto done;
3565 *certs = all_certs;
3566 *cert_count = len;
3567 rv = 0;
3568 done:
3569 return (rv);
3572 void
3573 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3575 int i;
3577 for (i = 0; i < cert_count; i++)
3578 gnutls_x509_crt_deinit(certs[i]);
3579 g_free(certs);
3582 void
3583 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3585 GdkColor c_text, c_base;
3587 gdk_color_parse(text, &c_text);
3588 gdk_color_parse(base, &c_base);
3590 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3591 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3592 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3593 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3595 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3596 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3597 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3598 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3601 void
3602 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3603 size_t cert_count, char *domain)
3605 size_t cert_buf_sz;
3606 char cert_buf[64 * 1024], file[PATH_MAX];
3607 int i;
3608 FILE *f;
3609 GdkColor color;
3611 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3612 return;
3614 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3615 if ((f = fopen(file, "w")) == NULL) {
3616 show_oops(t, "Can't create cert file %s %s",
3617 file, strerror(errno));
3618 return;
3621 for (i = 0; i < cert_count; i++) {
3622 cert_buf_sz = sizeof cert_buf;
3623 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3624 cert_buf, &cert_buf_sz)) {
3625 show_oops(t, "gnutls_x509_crt_export failed");
3626 goto done;
3628 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3629 show_oops(t, "Can't write certs: %s", strerror(errno));
3630 goto done;
3634 /* not the best spot but oh well */
3635 gdk_color_parse("lightblue", &color);
3636 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3637 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3638 done:
3639 fclose(f);
3642 enum cert_trust {
3643 CERT_LOCAL,
3644 CERT_TRUSTED,
3645 CERT_UNTRUSTED,
3646 CERT_BAD
3649 enum cert_trust
3650 load_compare_cert(const gchar *uri, const gchar **error_str)
3652 char domain[8182], file[PATH_MAX];
3653 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3654 int s = -1, i;
3655 unsigned int error = 0;
3656 FILE *f = NULL;
3657 size_t cert_buf_sz, cert_count;
3658 enum cert_trust rv = CERT_UNTRUSTED;
3659 static gchar serr[80]; /* this isn't thread safe */
3660 gnutls_session_t gsession;
3661 gnutls_x509_crt_t *certs;
3662 gnutls_certificate_credentials_t xcred;
3664 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3666 serr[0] = '\0';
3667 *error_str = serr;
3668 if ((s = connect_socket_from_uri(uri, error_str, domain,
3669 sizeof domain)) == -1)
3670 return (rv);
3672 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3674 /* go ssl/tls */
3675 if (start_tls(error_str, s, &gsession, &xcred))
3676 goto done;
3677 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3679 /* verify certs in case cert file doesn't exist */
3680 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3681 GNUTLS_E_SUCCESS) {
3682 *error_str = "Invalid certificates";
3683 goto done;
3686 /* get certs */
3687 if (get_connection_certs(gsession, &certs, &cert_count)) {
3688 *error_str = "Can't get connection certificates";
3689 goto done;
3692 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3693 if ((f = fopen(file, "r")) == NULL) {
3694 if (!error)
3695 rv = CERT_TRUSTED;
3696 goto freeit;
3699 for (i = 0; i < cert_count; i++) {
3700 cert_buf_sz = sizeof cert_buf;
3701 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3702 cert_buf, &cert_buf_sz)) {
3703 goto freeit;
3705 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3706 rv = CERT_BAD; /* critical */
3707 goto freeit;
3709 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3710 rv = CERT_BAD; /* critical */
3711 goto freeit;
3713 rv = CERT_LOCAL;
3716 freeit:
3717 if (f)
3718 fclose(f);
3719 free_connection_certs(certs, cert_count);
3720 done:
3721 /* we close the socket first for speed */
3722 if (s != -1)
3723 close(s);
3725 /* only complain if we didn't save it locally */
3726 if (error && rv != CERT_LOCAL) {
3727 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3728 if (error & GNUTLS_CERT_INVALID)
3729 strlcat(serr, "invalid, ", sizeof serr);
3730 if (error & GNUTLS_CERT_REVOKED)
3731 strlcat(serr, "revoked, ", sizeof serr);
3732 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3733 strlcat(serr, "signer not found, ", sizeof serr);
3734 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3735 strlcat(serr, "not signed by CA, ", sizeof serr);
3736 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3737 strlcat(serr, "insecure algorithm, ", sizeof serr);
3738 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3739 strlcat(serr, "not activated, ", sizeof serr);
3740 if (error & GNUTLS_CERT_EXPIRED)
3741 strlcat(serr, "expired, ", sizeof serr);
3742 for (i = strlen(serr) - 1; i > 0; i--)
3743 if (serr[i] == ',') {
3744 serr[i] = '\0';
3745 break;
3747 *error_str = serr;
3750 stop_tls(gsession, xcred);
3752 return (rv);
3756 cert_cmd(struct tab *t, struct karg *args)
3758 const gchar *uri, *error_str = NULL;
3759 char domain[8182];
3760 int s = -1;
3761 size_t cert_count;
3762 gnutls_session_t gsession;
3763 gnutls_x509_crt_t *certs;
3764 gnutls_certificate_credentials_t xcred;
3766 if (t == NULL)
3767 return (1);
3769 if (ssl_ca_file == NULL) {
3770 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3771 return (1);
3774 if ((uri = get_uri(t)) == NULL) {
3775 show_oops(t, "Invalid URI");
3776 return (1);
3779 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3780 sizeof domain)) == -1) {
3781 show_oops(t, "%s", error_str);
3782 return (1);
3785 /* go ssl/tls */
3786 if (start_tls(&error_str, s, &gsession, &xcred))
3787 goto done;
3789 /* get certs */
3790 if (get_connection_certs(gsession, &certs, &cert_count)) {
3791 show_oops(t, "get_connection_certs failed");
3792 goto done;
3795 if (args->i & XT_SHOW)
3796 show_certs(t, certs, cert_count, "Certificate Chain");
3797 else if (args->i & XT_SAVE)
3798 save_certs(t, certs, cert_count, domain);
3800 free_connection_certs(certs, cert_count);
3801 done:
3802 /* we close the socket first for speed */
3803 if (s != -1)
3804 close(s);
3805 stop_tls(gsession, xcred);
3806 if (error_str && strlen(error_str))
3807 show_oops(t, "%s", error_str);
3808 return (0);
3812 remove_cookie(int index)
3814 int i, rv = 1;
3815 GSList *cf;
3816 SoupCookie *c;
3818 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3820 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3822 for (i = 1; cf; cf = cf->next, i++) {
3823 if (i != index)
3824 continue;
3825 c = cf->data;
3826 print_cookie("remove cookie", c);
3827 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3828 rv = 0;
3829 break;
3832 soup_cookies_free(cf);
3834 return (rv);
3838 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3840 struct domain *d;
3841 char *tmp, *body;
3843 body = g_strdup("");
3845 /* p list */
3846 if (args->i & XT_WL_PERSISTENT) {
3847 tmp = body;
3848 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3849 g_free(tmp);
3850 RB_FOREACH(d, domain_list, wl) {
3851 if (d->handy == 0)
3852 continue;
3853 tmp = body;
3854 body = g_strdup_printf("%s%s<br/>", body, d->d);
3855 g_free(tmp);
3859 /* s list */
3860 if (args->i & XT_WL_SESSION) {
3861 tmp = body;
3862 body = g_strdup_printf("%s<h2>Session</h2>", body);
3863 g_free(tmp);
3864 RB_FOREACH(d, domain_list, wl) {
3865 if (d->handy == 1)
3866 continue;
3867 tmp = body;
3868 body = g_strdup_printf("%s%s<br/>", body, d->d);
3869 g_free(tmp);
3873 tmp = get_html_page(title, body, "", 0);
3874 g_free(body);
3875 if (wl == &js_wl)
3876 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3877 else
3878 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3879 g_free(tmp);
3880 return (0);
3884 wl_save(struct tab *t, struct karg *args, int js)
3886 char file[PATH_MAX];
3887 FILE *f;
3888 char *line = NULL, *lt = NULL, *dom = NULL;
3889 size_t linelen;
3890 const gchar *uri;
3891 struct karg a;
3892 struct domain *d;
3893 GSList *cf;
3894 SoupCookie *ci, *c;
3896 if (t == NULL || args == NULL)
3897 return (1);
3899 if (runtime_settings[0] == '\0')
3900 return (1);
3902 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3903 if ((f = fopen(file, "r+")) == NULL)
3904 return (1);
3906 uri = get_uri(t);
3907 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3908 if (uri == NULL || dom == NULL ||
3909 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3910 show_oops(t, "Can't add domain to %s white list",
3911 js ? "JavaScript" : "cookie");
3912 goto done;
3915 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3917 while (!feof(f)) {
3918 line = fparseln(f, &linelen, NULL, NULL, 0);
3919 if (line == NULL)
3920 continue;
3921 if (!strcmp(line, lt))
3922 goto done;
3923 free(line);
3924 line = NULL;
3927 fprintf(f, "%s\n", lt);
3929 a.i = XT_WL_ENABLE;
3930 a.i |= args->i;
3931 if (js) {
3932 d = wl_find(dom, &js_wl);
3933 if (!d) {
3934 settings_add("js_wl", dom);
3935 d = wl_find(dom, &js_wl);
3937 toggle_js(t, &a);
3938 } else {
3939 d = wl_find(dom, &c_wl);
3940 if (!d) {
3941 settings_add("cookie_wl", dom);
3942 d = wl_find(dom, &c_wl);
3944 toggle_cwl(t, &a);
3946 /* find and add to persistent jar */
3947 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3948 for (;cf; cf = cf->next) {
3949 ci = cf->data;
3950 if (!strcmp(dom, ci->domain) ||
3951 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3952 c = soup_cookie_copy(ci);
3953 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3956 soup_cookies_free(cf);
3958 if (d)
3959 d->handy = 1;
3961 done:
3962 if (line)
3963 free(line);
3964 if (dom)
3965 g_free(dom);
3966 if (lt)
3967 g_free(lt);
3968 fclose(f);
3970 return (0);
3974 js_show_wl(struct tab *t, struct karg *args)
3976 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3977 wl_show(t, args, "JavaScript White List", &js_wl);
3979 return (0);
3983 cookie_show_wl(struct tab *t, struct karg *args)
3985 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3986 wl_show(t, args, "Cookie White List", &c_wl);
3988 return (0);
3992 cookie_cmd(struct tab *t, struct karg *args)
3994 if (args->i & XT_SHOW)
3995 wl_show(t, args, "Cookie White List", &c_wl);
3996 else if (args->i & XT_WL_TOGGLE) {
3997 args->i |= XT_WL_RELOAD;
3998 toggle_cwl(t, args);
3999 } else if (args->i & XT_SAVE) {
4000 args->i |= XT_WL_RELOAD;
4001 wl_save(t, args, 0);
4002 } else if (args->i & XT_DELETE)
4003 show_oops(t, "'cookie delete' currently unimplemented");
4005 return (0);
4009 js_cmd(struct tab *t, struct karg *args)
4011 if (args->i & XT_SHOW)
4012 wl_show(t, args, "JavaScript White List", &js_wl);
4013 else if (args->i & XT_SAVE) {
4014 args->i |= XT_WL_RELOAD;
4015 wl_save(t, args, 1);
4016 } else if (args->i & XT_WL_TOGGLE) {
4017 args->i |= XT_WL_RELOAD;
4018 toggle_js(t, args);
4019 } else if (args->i & XT_DELETE)
4020 show_oops(t, "'js delete' currently unimplemented");
4022 return (0);
4026 toplevel_cmd(struct tab *t, struct karg *args)
4028 js_toggle_cb(t->js_toggle, t);
4030 return (0);
4034 add_favorite(struct tab *t, struct karg *args)
4036 char file[PATH_MAX];
4037 FILE *f;
4038 char *line = NULL;
4039 size_t urilen, linelen;
4040 const gchar *uri, *title;
4042 if (t == NULL)
4043 return (1);
4045 /* don't allow adding of xtp pages to favorites */
4046 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4047 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4048 return (1);
4051 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4052 if ((f = fopen(file, "r+")) == NULL) {
4053 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4054 return (1);
4057 title = get_title(t, FALSE);
4058 uri = get_uri(t);
4060 if (title == NULL || uri == NULL) {
4061 show_oops(t, "can't add page to favorites");
4062 goto done;
4065 urilen = strlen(uri);
4067 for (;;) {
4068 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4069 if (feof(f) || ferror(f))
4070 break;
4072 if (linelen == urilen && !strcmp(line, uri))
4073 goto done;
4075 free(line);
4076 line = NULL;
4079 fprintf(f, "\n%s\n%s", title, uri);
4080 done:
4081 if (line)
4082 free(line);
4083 fclose(f);
4085 update_favorite_tabs(NULL);
4087 return (0);
4091 can_go_back_for_real(struct tab *t)
4093 int i;
4094 WebKitWebHistoryItem *item;
4095 const gchar *uri;
4097 if (t == NULL)
4098 return (FALSE);
4100 /* rely on webkit to make sure we can go backward when on an about page */
4101 uri = get_uri(t);
4102 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4103 return (webkit_web_view_can_go_back(t->wv));
4105 /* the back/forwars list is stupid so help determine if we can go back */
4106 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4107 item != NULL;
4108 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4109 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4110 return (TRUE);
4113 return (FALSE);
4117 can_go_forward_for_real(struct tab *t)
4119 int i;
4120 WebKitWebHistoryItem *item;
4121 const gchar *uri;
4123 if (t == NULL)
4124 return (FALSE);
4126 /* rely on webkit to make sure we can go forward when on an about page */
4127 uri = get_uri(t);
4128 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4129 return (webkit_web_view_can_go_forward(t->wv));
4131 /* the back/forwars list is stupid so help selecting a different item */
4132 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4133 item != NULL;
4134 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4135 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4136 return (TRUE);
4139 return (FALSE);
4142 void
4143 go_back_for_real(struct tab *t)
4145 int i;
4146 WebKitWebHistoryItem *item;
4147 const gchar *uri;
4149 if (t == NULL)
4150 return;
4152 uri = get_uri(t);
4153 if (uri == NULL) {
4154 webkit_web_view_go_back(t->wv);
4155 return;
4157 /* the back/forwars list is stupid so help selecting a different item */
4158 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4159 item != NULL;
4160 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4161 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4162 webkit_web_view_go_to_back_forward_item(t->wv, item);
4163 break;
4168 void
4169 go_forward_for_real(struct tab *t)
4171 int i;
4172 WebKitWebHistoryItem *item;
4173 const gchar *uri;
4175 if (t == NULL)
4176 return;
4178 uri = get_uri(t);
4179 if (uri == NULL) {
4180 webkit_web_view_go_forward(t->wv);
4181 return;
4183 /* the back/forwars list is stupid so help selecting a different item */
4184 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4185 item != NULL;
4186 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4187 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4188 webkit_web_view_go_to_back_forward_item(t->wv, item);
4189 break;
4195 navaction(struct tab *t, struct karg *args)
4197 WebKitWebHistoryItem *item;
4198 WebKitWebFrame *frame;
4200 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4201 t->tab_id, args->i);
4203 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4204 if (t->item) {
4205 if (args->i == XT_NAV_BACK)
4206 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4207 else
4208 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4209 if (item == NULL)
4210 return (XT_CB_PASSTHROUGH);
4211 webkit_web_view_go_to_back_forward_item(t->wv, item);
4212 t->item = NULL;
4213 return (XT_CB_PASSTHROUGH);
4216 switch (args->i) {
4217 case XT_NAV_BACK:
4218 marks_clear(t);
4219 go_back_for_real(t);
4220 break;
4221 case XT_NAV_FORWARD:
4222 marks_clear(t);
4223 go_forward_for_real(t);
4224 break;
4225 case XT_NAV_RELOAD:
4226 frame = webkit_web_view_get_main_frame(t->wv);
4227 webkit_web_frame_reload(frame);
4228 break;
4230 return (XT_CB_PASSTHROUGH);
4234 move(struct tab *t, struct karg *args)
4236 GtkAdjustment *adjust;
4237 double pi, si, pos, ps, upper, lower, max;
4238 double percent;
4240 switch (args->i) {
4241 case XT_MOVE_DOWN:
4242 case XT_MOVE_UP:
4243 case XT_MOVE_BOTTOM:
4244 case XT_MOVE_TOP:
4245 case XT_MOVE_PAGEDOWN:
4246 case XT_MOVE_PAGEUP:
4247 case XT_MOVE_HALFDOWN:
4248 case XT_MOVE_HALFUP:
4249 case XT_MOVE_PERCENT:
4250 adjust = t->adjust_v;
4251 break;
4252 default:
4253 adjust = t->adjust_h;
4254 break;
4257 pos = gtk_adjustment_get_value(adjust);
4258 ps = gtk_adjustment_get_page_size(adjust);
4259 upper = gtk_adjustment_get_upper(adjust);
4260 lower = gtk_adjustment_get_lower(adjust);
4261 si = gtk_adjustment_get_step_increment(adjust);
4262 pi = gtk_adjustment_get_page_increment(adjust);
4263 max = upper - ps;
4265 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4266 "max %f si %f pi %f\n",
4267 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4268 pos, ps, upper, lower, max, si, pi);
4270 switch (args->i) {
4271 case XT_MOVE_DOWN:
4272 case XT_MOVE_RIGHT:
4273 pos += si;
4274 gtk_adjustment_set_value(adjust, MIN(pos, max));
4275 break;
4276 case XT_MOVE_UP:
4277 case XT_MOVE_LEFT:
4278 pos -= si;
4279 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4280 break;
4281 case XT_MOVE_BOTTOM:
4282 case XT_MOVE_FARRIGHT:
4283 gtk_adjustment_set_value(adjust, max);
4284 break;
4285 case XT_MOVE_TOP:
4286 case XT_MOVE_FARLEFT:
4287 gtk_adjustment_set_value(adjust, lower);
4288 break;
4289 case XT_MOVE_PAGEDOWN:
4290 pos += pi;
4291 gtk_adjustment_set_value(adjust, MIN(pos, max));
4292 break;
4293 case XT_MOVE_PAGEUP:
4294 pos -= pi;
4295 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4296 break;
4297 case XT_MOVE_HALFDOWN:
4298 pos += pi / 2;
4299 gtk_adjustment_set_value(adjust, MIN(pos, max));
4300 break;
4301 case XT_MOVE_HALFUP:
4302 pos -= pi / 2;
4303 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4304 break;
4305 case XT_MOVE_PERCENT:
4306 percent = atoi(args->s) / 100.0;
4307 pos = max * percent;
4308 if (pos < 0.0 || pos > max)
4309 break;
4310 gtk_adjustment_set_value(adjust, pos);
4311 break;
4312 default:
4313 return (XT_CB_PASSTHROUGH);
4316 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4318 return (XT_CB_HANDLED);
4321 void
4322 url_set_visibility(void)
4324 struct tab *t;
4326 TAILQ_FOREACH(t, &tabs, entry)
4327 if (show_url == 0) {
4328 gtk_widget_hide(t->toolbar);
4329 focus_webview(t);
4330 } else
4331 gtk_widget_show(t->toolbar);
4334 void
4335 notebook_tab_set_visibility(void)
4337 if (show_tabs == 0) {
4338 gtk_widget_hide(tab_bar);
4339 gtk_notebook_set_show_tabs(notebook, FALSE);
4340 } else {
4341 if (tab_style == XT_TABS_NORMAL) {
4342 gtk_widget_hide(tab_bar);
4343 gtk_notebook_set_show_tabs(notebook, TRUE);
4344 } else if (tab_style == XT_TABS_COMPACT) {
4345 gtk_widget_show(tab_bar);
4346 gtk_notebook_set_show_tabs(notebook, FALSE);
4351 void
4352 statusbar_set_visibility(void)
4354 struct tab *t;
4356 TAILQ_FOREACH(t, &tabs, entry)
4357 if (show_statusbar == 0) {
4358 gtk_widget_hide(t->statusbar_box);
4359 focus_webview(t);
4360 } else
4361 gtk_widget_show(t->statusbar_box);
4364 void
4365 url_set(struct tab *t, int enable_url_entry)
4367 GdkPixbuf *pixbuf;
4368 int progress;
4370 show_url = enable_url_entry;
4372 if (enable_url_entry) {
4373 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4374 GTK_ENTRY_ICON_PRIMARY, NULL);
4375 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4376 } else {
4377 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4378 GTK_ENTRY_ICON_PRIMARY);
4379 progress =
4380 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4381 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4382 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4383 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4384 progress);
4389 fullscreen(struct tab *t, struct karg *args)
4391 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4393 if (t == NULL)
4394 return (XT_CB_PASSTHROUGH);
4396 if (show_url == 0) {
4397 url_set(t, 1);
4398 show_tabs = 1;
4399 } else {
4400 url_set(t, 0);
4401 show_tabs = 0;
4404 url_set_visibility();
4405 notebook_tab_set_visibility();
4407 return (XT_CB_HANDLED);
4411 statustoggle(struct tab *t, struct karg *args)
4413 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4415 if (show_statusbar == 1) {
4416 show_statusbar = 0;
4417 statusbar_set_visibility();
4418 } else if (show_statusbar == 0) {
4419 show_statusbar = 1;
4420 statusbar_set_visibility();
4422 return (XT_CB_HANDLED);
4426 urlaction(struct tab *t, struct karg *args)
4428 int rv = XT_CB_HANDLED;
4430 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4432 if (t == NULL)
4433 return (XT_CB_PASSTHROUGH);
4435 switch (args->i) {
4436 case XT_URL_SHOW:
4437 if (show_url == 0) {
4438 url_set(t, 1);
4439 url_set_visibility();
4441 break;
4442 case XT_URL_HIDE:
4443 if (show_url == 1) {
4444 url_set(t, 0);
4445 url_set_visibility();
4447 break;
4449 return (rv);
4453 tabaction(struct tab *t, struct karg *args)
4455 int rv = XT_CB_HANDLED;
4456 char *url = args->s;
4457 struct undo *u;
4458 struct tab *tt;
4460 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4462 if (t == NULL)
4463 return (XT_CB_PASSTHROUGH);
4465 switch (args->i) {
4466 case XT_TAB_NEW:
4467 if (strlen(url) > 0)
4468 create_new_tab(url, NULL, 1, args->precount);
4469 else
4470 create_new_tab(NULL, NULL, 1, args->precount);
4471 break;
4472 case XT_TAB_DELETE:
4473 if (args->precount < 0)
4474 delete_tab(t);
4475 else
4476 TAILQ_FOREACH(tt, &tabs, entry)
4477 if (tt->tab_id == args->precount - 1) {
4478 delete_tab(tt);
4479 break;
4481 break;
4482 case XT_TAB_DELQUIT:
4483 if (gtk_notebook_get_n_pages(notebook) > 1)
4484 delete_tab(t);
4485 else
4486 quit(t, args);
4487 break;
4488 case XT_TAB_OPEN:
4489 if (strlen(url) > 0)
4491 else {
4492 rv = XT_CB_PASSTHROUGH;
4493 goto done;
4495 load_uri(t, url);
4496 break;
4497 case XT_TAB_SHOW:
4498 if (show_tabs == 0) {
4499 show_tabs = 1;
4500 notebook_tab_set_visibility();
4502 break;
4503 case XT_TAB_HIDE:
4504 if (show_tabs == 1) {
4505 show_tabs = 0;
4506 notebook_tab_set_visibility();
4508 break;
4509 case XT_TAB_NEXTSTYLE:
4510 if (tab_style == XT_TABS_NORMAL) {
4511 tab_style = XT_TABS_COMPACT;
4512 recolor_compact_tabs();
4514 else
4515 tab_style = XT_TABS_NORMAL;
4516 notebook_tab_set_visibility();
4517 break;
4518 case XT_TAB_UNDO_CLOSE:
4519 if (undo_count == 0) {
4520 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4521 __func__);
4522 goto done;
4523 } else {
4524 undo_count--;
4525 u = TAILQ_FIRST(&undos);
4526 create_new_tab(u->uri, u, 1, -1);
4528 TAILQ_REMOVE(&undos, u, entry);
4529 g_free(u->uri);
4530 /* u->history is freed in create_new_tab() */
4531 g_free(u);
4533 break;
4534 default:
4535 rv = XT_CB_PASSTHROUGH;
4536 goto done;
4539 done:
4540 if (args->s) {
4541 g_free(args->s);
4542 args->s = NULL;
4545 return (rv);
4549 resizetab(struct tab *t, struct karg *args)
4551 if (t == NULL || args == NULL) {
4552 show_oops(NULL, "resizetab invalid parameters");
4553 return (XT_CB_PASSTHROUGH);
4556 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4557 t->tab_id, args->i);
4559 setzoom_webkit(t, args->i);
4561 return (XT_CB_HANDLED);
4565 movetab(struct tab *t, struct karg *args)
4567 int n, dest;
4569 if (t == NULL || args == NULL) {
4570 show_oops(NULL, "movetab invalid parameters");
4571 return (XT_CB_PASSTHROUGH);
4574 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4575 t->tab_id, args->i);
4577 if (args->i >= XT_TAB_INVALID)
4578 return (XT_CB_PASSTHROUGH);
4580 if (TAILQ_EMPTY(&tabs))
4581 return (XT_CB_PASSTHROUGH);
4583 n = gtk_notebook_get_n_pages(notebook);
4584 dest = gtk_notebook_get_current_page(notebook);
4586 switch (args->i) {
4587 case XT_TAB_NEXT:
4588 if (args->precount < 0)
4589 dest = dest == n - 1 ? 0 : dest + 1;
4590 else
4591 dest = args->precount - 1;
4593 break;
4594 case XT_TAB_PREV:
4595 if (args->precount < 0)
4596 dest -= 1;
4597 else
4598 dest -= args->precount % n;
4600 if (dest < 0)
4601 dest += n;
4603 break;
4604 case XT_TAB_FIRST:
4605 dest = 0;
4606 break;
4607 case XT_TAB_LAST:
4608 dest = n - 1;
4609 break;
4610 default:
4611 return (XT_CB_PASSTHROUGH);
4614 if (dest < 0 || dest >= n)
4615 return (XT_CB_PASSTHROUGH);
4616 if (t->tab_id == dest) {
4617 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4618 return (XT_CB_HANDLED);
4621 set_current_tab(dest);
4623 return (XT_CB_HANDLED);
4626 int cmd_prefix = 0;
4629 command(struct tab *t, struct karg *args)
4631 char *s = NULL, *ss = NULL;
4632 GdkColor color;
4633 const gchar *uri;
4635 if (t == NULL || args == NULL) {
4636 show_oops(NULL, "command invalid parameters");
4637 return (XT_CB_PASSTHROUGH);
4640 switch (args->i) {
4641 case '/':
4642 s = "/";
4643 break;
4644 case '?':
4645 s = "?";
4646 break;
4647 case ':':
4648 if (cmd_prefix == 0)
4649 s = ":";
4650 else {
4651 ss = g_strdup_printf(":%d", cmd_prefix);
4652 s = ss;
4653 cmd_prefix = 0;
4655 break;
4656 case XT_CMD_OPEN:
4657 s = ":open ";
4658 break;
4659 case XT_CMD_TABNEW:
4660 s = ":tabnew ";
4661 break;
4662 case XT_CMD_OPEN_CURRENT:
4663 s = ":open ";
4664 /* FALL THROUGH */
4665 case XT_CMD_TABNEW_CURRENT:
4666 if (!s) /* FALL THROUGH? */
4667 s = ":tabnew ";
4668 if ((uri = get_uri(t)) != NULL) {
4669 ss = g_strdup_printf("%s%s", s, uri);
4670 s = ss;
4672 break;
4673 default:
4674 show_oops(t, "command: invalid opcode %d", args->i);
4675 return (XT_CB_PASSTHROUGH);
4678 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4680 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4681 gdk_color_parse(XT_COLOR_WHITE, &color);
4682 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4683 show_cmd(t);
4684 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4685 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4687 if (ss)
4688 g_free(ss);
4690 return (XT_CB_HANDLED);
4694 * Return a new string with a download row (in html)
4695 * appended. Old string is freed.
4697 char *
4698 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4701 WebKitDownloadStatus stat;
4702 char *status_html = NULL, *cmd_html = NULL, *new_html;
4703 gdouble progress;
4704 char cur_sz[FMT_SCALED_STRSIZE];
4705 char tot_sz[FMT_SCALED_STRSIZE];
4706 char *xtp_prefix;
4708 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4710 /* All actions wil take this form:
4711 * xxxt://class/seskey
4713 xtp_prefix = g_strdup_printf("%s%d/%s/",
4714 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4716 stat = webkit_download_get_status(dl->download);
4718 switch (stat) {
4719 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4720 status_html = g_strdup_printf("Finished");
4721 cmd_html = g_strdup_printf(
4722 "<a href='%s%d/%d'>Remove</a>",
4723 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4724 break;
4725 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4726 /* gather size info */
4727 progress = 100 * webkit_download_get_progress(dl->download);
4729 fmt_scaled(
4730 webkit_download_get_current_size(dl->download), cur_sz);
4731 fmt_scaled(
4732 webkit_download_get_total_size(dl->download), tot_sz);
4734 status_html = g_strdup_printf(
4735 "<div style='width: 100%%' align='center'>"
4736 "<div class='progress-outer'>"
4737 "<div class='progress-inner' style='width: %.2f%%'>"
4738 "</div></div></div>"
4739 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4740 progress, cur_sz, tot_sz, progress);
4742 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4743 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4745 break;
4746 /* LLL */
4747 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4748 status_html = g_strdup_printf("Cancelled");
4749 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4750 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4751 break;
4752 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4753 status_html = g_strdup_printf("Error!");
4754 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4755 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4756 break;
4757 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4758 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4759 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4760 status_html = g_strdup_printf("Starting");
4761 break;
4762 default:
4763 show_oops(t, "%s: unknown download status", __func__);
4766 new_html = g_strdup_printf(
4767 "%s\n<tr><td>%s</td><td>%s</td>"
4768 "<td style='text-align:center'>%s</td></tr>\n",
4769 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4770 status_html, cmd_html);
4771 g_free(html);
4773 if (status_html)
4774 g_free(status_html);
4776 if (cmd_html)
4777 g_free(cmd_html);
4779 g_free(xtp_prefix);
4781 return new_html;
4785 * update all download tabs apart from one. Pass NULL if
4786 * you want to update all.
4788 void
4789 update_download_tabs(struct tab *apart_from)
4791 struct tab *t;
4792 if (!updating_dl_tabs) {
4793 updating_dl_tabs = 1; /* stop infinite recursion */
4794 TAILQ_FOREACH(t, &tabs, entry)
4795 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4796 && (t != apart_from))
4797 xtp_page_dl(t, NULL);
4798 updating_dl_tabs = 0;
4803 * update all cookie tabs apart from one. Pass NULL if
4804 * you want to update all.
4806 void
4807 update_cookie_tabs(struct tab *apart_from)
4809 struct tab *t;
4810 if (!updating_cl_tabs) {
4811 updating_cl_tabs = 1; /* stop infinite recursion */
4812 TAILQ_FOREACH(t, &tabs, entry)
4813 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4814 && (t != apart_from))
4815 xtp_page_cl(t, NULL);
4816 updating_cl_tabs = 0;
4821 * update all history tabs apart from one. Pass NULL if
4822 * you want to update all.
4824 void
4825 update_history_tabs(struct tab *apart_from)
4827 struct tab *t;
4829 if (!updating_hl_tabs) {
4830 updating_hl_tabs = 1; /* stop infinite recursion */
4831 TAILQ_FOREACH(t, &tabs, entry)
4832 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4833 && (t != apart_from))
4834 xtp_page_hl(t, NULL);
4835 updating_hl_tabs = 0;
4839 /* cookie management XTP page */
4841 xtp_page_cl(struct tab *t, struct karg *args)
4843 char *body, *page, *tmp;
4844 int i = 1; /* all ids start 1 */
4845 GSList *sc, *pc, *pc_start;
4846 SoupCookie *c;
4847 char *type, *table_headers, *last_domain;
4849 DNPRINTF(XT_D_CMD, "%s", __func__);
4851 if (t == NULL) {
4852 show_oops(NULL, "%s invalid parameters", __func__);
4853 return (1);
4856 /* Generate a new session key */
4857 if (!updating_cl_tabs)
4858 generate_xtp_session_key(&cl_session_key);
4860 /* table headers */
4861 table_headers = g_strdup_printf("<table><tr>"
4862 "<th>Type</th>"
4863 "<th>Name</th>"
4864 "<th style='width:200px'>Value</th>"
4865 "<th>Path</th>"
4866 "<th>Expires</th>"
4867 "<th>Secure</th>"
4868 "<th>HTTP<br />only</th>"
4869 "<th style='width:40px'>Rm</th></tr>\n");
4871 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4872 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4873 pc_start = pc;
4875 body = NULL;
4876 last_domain = strdup("");
4877 for (; sc; sc = sc->next) {
4878 c = sc->data;
4880 if (strcmp(last_domain, c->domain) != 0) {
4881 /* new domain */
4882 free(last_domain);
4883 last_domain = strdup(c->domain);
4885 if (body != NULL) {
4886 tmp = body;
4887 body = g_strdup_printf("%s</table>"
4888 "<h2>%s</h2>%s\n",
4889 body, c->domain, table_headers);
4890 g_free(tmp);
4891 } else {
4892 /* first domain */
4893 body = g_strdup_printf("<h2>%s</h2>%s\n",
4894 c->domain, table_headers);
4898 type = "Session";
4899 for (pc = pc_start; pc; pc = pc->next)
4900 if (soup_cookie_equal(pc->data, c)) {
4901 type = "Session + Persistent";
4902 break;
4905 tmp = body;
4906 body = g_strdup_printf(
4907 "%s\n<tr>"
4908 "<td>%s</td>"
4909 "<td style='word-wrap:normal'>%s</td>"
4910 "<td>"
4911 " <textarea rows='4'>%s</textarea>"
4912 "</td>"
4913 "<td>%s</td>"
4914 "<td>%s</td>"
4915 "<td>%d</td>"
4916 "<td>%d</td>"
4917 "<td style='text-align:center'>"
4918 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4919 body,
4920 type,
4921 c->name,
4922 c->value,
4923 c->path,
4924 c->expires ?
4925 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4926 c->secure,
4927 c->http_only,
4929 XT_XTP_STR,
4930 XT_XTP_CL,
4931 cl_session_key,
4932 XT_XTP_CL_REMOVE,
4936 g_free(tmp);
4937 i++;
4940 soup_cookies_free(sc);
4941 soup_cookies_free(pc);
4943 /* small message if there are none */
4944 if (i == 1) {
4945 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4946 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4948 tmp = body;
4949 body = g_strdup_printf("%s</table>", body);
4950 g_free(tmp);
4952 page = get_html_page("Cookie Jar", body, "", TRUE);
4953 g_free(body);
4954 g_free(table_headers);
4955 g_free(last_domain);
4957 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4958 update_cookie_tabs(t);
4960 g_free(page);
4962 return (0);
4966 xtp_page_hl(struct tab *t, struct karg *args)
4968 char *body, *page, *tmp;
4969 struct history *h;
4970 int i = 1; /* all ids start 1 */
4972 DNPRINTF(XT_D_CMD, "%s", __func__);
4974 if (t == NULL) {
4975 show_oops(NULL, "%s invalid parameters", __func__);
4976 return (1);
4979 /* Generate a new session key */
4980 if (!updating_hl_tabs)
4981 generate_xtp_session_key(&hl_session_key);
4983 /* body */
4984 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4985 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4987 RB_FOREACH_REVERSE(h, history_list, &hl) {
4988 tmp = body;
4989 body = g_strdup_printf(
4990 "%s\n<tr>"
4991 "<td><a href='%s'>%s</a></td>"
4992 "<td>%s</td>"
4993 "<td style='text-align: center'>"
4994 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4995 body, h->uri, h->uri, h->title,
4996 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4997 XT_XTP_HL_REMOVE, i);
4999 g_free(tmp);
5000 i++;
5003 /* small message if there are none */
5004 if (i == 1) {
5005 tmp = body;
5006 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5007 "colspan='3'>No History</td></tr>\n", body);
5008 g_free(tmp);
5011 tmp = body;
5012 body = g_strdup_printf("%s</table>", body);
5013 g_free(tmp);
5015 page = get_html_page("History", body, "", TRUE);
5016 g_free(body);
5019 * update all history manager tabs as the xtp session
5020 * key has now changed. No need to update the current tab.
5021 * Already did that above.
5023 update_history_tabs(t);
5025 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5026 g_free(page);
5028 return (0);
5032 * Generate a web page detailing the status of any downloads
5035 xtp_page_dl(struct tab *t, struct karg *args)
5037 struct download *dl;
5038 char *body, *page, *tmp;
5039 char *ref;
5040 int n_dl = 1;
5042 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5044 if (t == NULL) {
5045 show_oops(NULL, "%s invalid parameters", __func__);
5046 return (1);
5050 * Generate a new session key for next page instance.
5051 * This only happens for the top level call to xtp_page_dl()
5052 * in which case updating_dl_tabs is 0.
5054 if (!updating_dl_tabs)
5055 generate_xtp_session_key(&dl_session_key);
5057 /* header - with refresh so as to update */
5058 if (refresh_interval >= 1)
5059 ref = g_strdup_printf(
5060 "<meta http-equiv='refresh' content='%u"
5061 ";url=%s%d/%s/%d' />\n",
5062 refresh_interval,
5063 XT_XTP_STR,
5064 XT_XTP_DL,
5065 dl_session_key,
5066 XT_XTP_DL_LIST);
5067 else
5068 ref = g_strdup("");
5070 body = g_strdup_printf("<div align='center'>"
5071 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5072 "</p><table><tr><th style='width: 60%%'>"
5073 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5074 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5076 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5077 body = xtp_page_dl_row(t, body, dl);
5078 n_dl++;
5081 /* message if no downloads in list */
5082 if (n_dl == 1) {
5083 tmp = body;
5084 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5085 " style='text-align: center'>"
5086 "No downloads</td></tr>\n", body);
5087 g_free(tmp);
5090 tmp = body;
5091 body = g_strdup_printf("%s</table></div>", body);
5092 g_free(tmp);
5094 page = get_html_page("Downloads", body, ref, 1);
5095 g_free(ref);
5096 g_free(body);
5099 * update all download manager tabs as the xtp session
5100 * key has now changed. No need to update the current tab.
5101 * Already did that above.
5103 update_download_tabs(t);
5105 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5106 g_free(page);
5108 return (0);
5112 search(struct tab *t, struct karg *args)
5114 gboolean d;
5116 if (t == NULL || args == NULL) {
5117 show_oops(NULL, "search invalid parameters");
5118 return (1);
5121 switch (args->i) {
5122 case XT_SEARCH_NEXT:
5123 d = t->search_forward;
5124 break;
5125 case XT_SEARCH_PREV:
5126 d = !t->search_forward;
5127 break;
5128 default:
5129 return (XT_CB_PASSTHROUGH);
5132 if (t->search_text == NULL) {
5133 if (global_search == NULL)
5134 return (XT_CB_PASSTHROUGH);
5135 else {
5136 d = t->search_forward = TRUE;
5137 t->search_text = g_strdup(global_search);
5138 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5139 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5143 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5144 t->tab_id, args->i, t->search_forward, t->search_text);
5146 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5148 return (XT_CB_HANDLED);
5151 struct settings_args {
5152 char **body;
5153 int i;
5156 void
5157 print_setting(struct settings *s, char *val, void *cb_args)
5159 char *tmp, *color;
5160 struct settings_args *sa = cb_args;
5162 if (sa == NULL)
5163 return;
5165 if (s->flags & XT_SF_RUNTIME)
5166 color = "#22cc22";
5167 else
5168 color = "#cccccc";
5170 tmp = *sa->body;
5171 *sa->body = g_strdup_printf(
5172 "%s\n<tr>"
5173 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5174 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5175 *sa->body,
5176 color,
5177 s->name,
5178 color,
5181 g_free(tmp);
5182 sa->i++;
5186 set_show(struct tab *t, struct karg *args)
5188 char *body, *page, *tmp;
5189 int i = 1;
5190 struct settings_args sa;
5192 bzero(&sa, sizeof sa);
5193 sa.body = &body;
5195 /* body */
5196 body = g_strdup_printf("<div align='center'><table><tr>"
5197 "<th align='left'>Setting</th>"
5198 "<th align='left'>Value</th></tr>\n");
5200 settings_walk(print_setting, &sa);
5201 i = sa.i;
5203 /* small message if there are none */
5204 if (i == 1) {
5205 tmp = body;
5206 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5207 "colspan='2'>No settings</td></tr>\n", body);
5208 g_free(tmp);
5211 tmp = body;
5212 body = g_strdup_printf("%s</table></div>", body);
5213 g_free(tmp);
5215 page = get_html_page("Settings", body, "", 0);
5217 g_free(body);
5219 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5221 g_free(page);
5223 return (XT_CB_PASSTHROUGH);
5227 set(struct tab *t, struct karg *args)
5229 char *p, *val;
5230 int i;
5232 if (args == NULL || args->s == NULL)
5233 return (set_show(t, args));
5235 /* strip spaces */
5236 p = g_strstrip(args->s);
5238 if (strlen(p) == 0)
5239 return (set_show(t, args));
5241 /* we got some sort of string */
5242 val = g_strrstr(p, "=");
5243 if (val) {
5244 *val++ = '\0';
5245 val = g_strchomp(val);
5246 p = g_strchomp(p);
5248 for (i = 0; i < LENGTH(rs); i++) {
5249 if (strcmp(rs[i].name, p))
5250 continue;
5252 if (rs[i].activate) {
5253 if (rs[i].activate(val))
5254 show_oops(t, "%s invalid value %s",
5255 p, val);
5256 else
5257 show_oops(t, ":set %s = %s", p, val);
5258 goto done;
5259 } else {
5260 show_oops(t, "not a runtime option: %s", p);
5261 goto done;
5264 show_oops(t, "unknown option: %s", p);
5265 } else {
5266 p = g_strchomp(p);
5268 for (i = 0; i < LENGTH(rs); i++) {
5269 if (strcmp(rs[i].name, p))
5270 continue;
5272 /* XXX this could use some cleanup */
5273 switch (rs[i].type) {
5274 case XT_S_INT:
5275 if (rs[i].ival)
5276 show_oops(t, "%s = %d",
5277 rs[i].name, *rs[i].ival);
5278 else if (rs[i].s && rs[i].s->get)
5279 show_oops(t, "%s = %s",
5280 rs[i].name,
5281 rs[i].s->get(&rs[i]));
5282 else if (rs[i].s && rs[i].s->get == NULL)
5283 show_oops(t, "%s = ...", rs[i].name);
5284 else
5285 show_oops(t, "%s = ", rs[i].name);
5286 break;
5287 case XT_S_FLOAT:
5288 if (rs[i].fval)
5289 show_oops(t, "%s = %f",
5290 rs[i].name, *rs[i].fval);
5291 else if (rs[i].s && rs[i].s->get)
5292 show_oops(t, "%s = %s",
5293 rs[i].name,
5294 rs[i].s->get(&rs[i]));
5295 else if (rs[i].s && rs[i].s->get == NULL)
5296 show_oops(t, "%s = ...", rs[i].name);
5297 else
5298 show_oops(t, "%s = ", rs[i].name);
5299 break;
5300 case XT_S_STR:
5301 if (rs[i].sval && *rs[i].sval)
5302 show_oops(t, "%s = %s",
5303 rs[i].name, *rs[i].sval);
5304 else if (rs[i].s && rs[i].s->get)
5305 show_oops(t, "%s = %s",
5306 rs[i].name,
5307 rs[i].s->get(&rs[i]));
5308 else if (rs[i].s && rs[i].s->get == NULL)
5309 show_oops(t, "%s = ...", rs[i].name);
5310 else
5311 show_oops(t, "%s = ", rs[i].name);
5312 break;
5313 default:
5314 show_oops(t, "unknown type for %s", rs[i].name);
5315 goto done;
5318 goto done;
5320 show_oops(t, "unknown option: %s", p);
5322 done:
5323 return (XT_CB_PASSTHROUGH);
5327 session_save(struct tab *t, char *filename)
5329 struct karg a;
5330 int rv = 1;
5331 struct session *s;
5333 if (strlen(filename) == 0)
5334 goto done;
5336 if (filename[0] == '.' || filename[0] == '/')
5337 goto done;
5339 a.s = filename;
5340 if (save_tabs(t, &a))
5341 goto done;
5342 strlcpy(named_session, filename, sizeof named_session);
5344 /* add the new session to the list of sessions */
5345 s = g_malloc(sizeof(struct session));
5346 s->name = g_strdup(filename);
5347 TAILQ_INSERT_TAIL(&sessions, s, entry);
5349 rv = 0;
5350 done:
5351 return (rv);
5355 session_open(struct tab *t, char *filename)
5357 struct karg a;
5358 int rv = 1;
5360 if (strlen(filename) == 0)
5361 goto done;
5363 if (filename[0] == '.' || filename[0] == '/')
5364 goto done;
5366 a.s = filename;
5367 a.i = XT_SES_CLOSETABS;
5368 if (open_tabs(t, &a))
5369 goto done;
5371 strlcpy(named_session, filename, sizeof named_session);
5373 rv = 0;
5374 done:
5375 return (rv);
5379 session_delete(struct tab *t, char *filename)
5381 char file[PATH_MAX];
5382 int rv = 1;
5383 struct session *s;
5385 if (strlen(filename) == 0)
5386 goto done;
5388 if (filename[0] == '.' || filename[0] == '/')
5389 goto done;
5391 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5392 if (unlink(file))
5393 goto done;
5395 if (!strcmp(filename, named_session))
5396 strlcpy(named_session, XT_SAVED_TABS_FILE,
5397 sizeof named_session);
5399 /* remove session from sessions list */
5400 TAILQ_FOREACH(s, &sessions, entry) {
5401 if (!strcmp(s->name, filename))
5402 break;
5404 if (s == NULL)
5405 goto done;
5406 TAILQ_REMOVE(&sessions, s, entry);
5407 g_free((gpointer) s->name);
5408 g_free(s);
5410 rv = 0;
5411 done:
5412 return (rv);
5416 session_cmd(struct tab *t, struct karg *args)
5418 char *filename = args->s;
5420 if (t == NULL)
5421 return (1);
5423 if (args->i & XT_SHOW)
5424 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5425 XT_SAVED_TABS_FILE : named_session);
5426 else if (args->i & XT_SAVE) {
5427 if (session_save(t, filename)) {
5428 show_oops(t, "Can't save session: %s",
5429 filename ? filename : "INVALID");
5430 goto done;
5432 } else if (args->i & XT_OPEN) {
5433 if (session_open(t, filename)) {
5434 show_oops(t, "Can't open session: %s",
5435 filename ? filename : "INVALID");
5436 goto done;
5438 } else if (args->i & XT_DELETE) {
5439 if (session_delete(t, filename)) {
5440 show_oops(t, "Can't delete session: %s",
5441 filename ? filename : "INVALID");
5442 goto done;
5445 done:
5446 return (XT_CB_PASSTHROUGH);
5450 * Make a hardcopy of the page
5453 print_page(struct tab *t, struct karg *args)
5455 WebKitWebFrame *frame;
5456 GtkPageSetup *ps;
5457 GtkPrintOperation *op;
5458 GtkPrintOperationAction action;
5459 GtkPrintOperationResult print_res;
5460 GError *g_err = NULL;
5461 int marg_l, marg_r, marg_t, marg_b;
5463 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5465 ps = gtk_page_setup_new();
5466 op = gtk_print_operation_new();
5467 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5468 frame = webkit_web_view_get_main_frame(t->wv);
5470 /* the default margins are too small, so we will bump them */
5471 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5472 XT_PRINT_EXTRA_MARGIN;
5473 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5474 XT_PRINT_EXTRA_MARGIN;
5475 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5476 XT_PRINT_EXTRA_MARGIN;
5477 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5478 XT_PRINT_EXTRA_MARGIN;
5480 /* set margins */
5481 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5482 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5483 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5484 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5486 gtk_print_operation_set_default_page_setup(op, ps);
5488 /* this appears to free 'op' and 'ps' */
5489 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5491 /* check it worked */
5492 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5493 show_oops(NULL, "can't print: %s", g_err->message);
5494 g_error_free (g_err);
5495 return (1);
5498 return (0);
5502 go_home(struct tab *t, struct karg *args)
5504 load_uri(t, home);
5505 return (0);
5509 set_encoding(struct tab *t, struct karg *args)
5511 const gchar *e;
5513 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5514 e = webkit_web_view_get_custom_encoding(t->wv);
5515 if (e == NULL)
5516 e = webkit_web_view_get_encoding(t->wv);
5517 show_oops(t, "encoding: %s", e ? e : "N/A");
5518 } else
5519 webkit_web_view_set_custom_encoding(t->wv, args->s);
5521 return (0);
5525 restart(struct tab *t, struct karg *args)
5527 struct karg a;
5529 a.s = XT_RESTART_TABS_FILE;
5530 save_tabs(t, &a);
5531 execvp(start_argv[0], start_argv);
5532 /* NOTREACHED */
5534 return (0);
5537 #define CTRL GDK_CONTROL_MASK
5538 #define MOD1 GDK_MOD1_MASK
5539 #define SHFT GDK_SHIFT_MASK
5541 /* inherent to GTK not all keys will be caught at all times */
5542 /* XXX sort key bindings */
5543 struct key_binding {
5544 char *cmd;
5545 guint mask;
5546 guint use_in_entry;
5547 guint key;
5548 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5549 } keys[] = {
5550 { "cookiejar", MOD1, 0, GDK_j },
5551 { "downloadmgr", MOD1, 0, GDK_d },
5552 { "history", MOD1, 0, GDK_h },
5553 { "print", CTRL, 0, GDK_p },
5554 { "search", 0, 0, GDK_slash },
5555 { "searchb", 0, 0, GDK_question },
5556 { "statustoggle", CTRL, 0, GDK_n },
5557 { "command", 0, 0, GDK_colon },
5558 { "qa", CTRL, 0, GDK_q },
5559 { "restart", MOD1, 0, GDK_q },
5560 { "js toggle", CTRL, 0, GDK_j },
5561 { "cookie toggle", MOD1, 0, GDK_c },
5562 { "togglesrc", CTRL, 0, GDK_s },
5563 { "yankuri", 0, 0, GDK_y },
5564 { "pasteuricur", 0, 0, GDK_p },
5565 { "pasteurinew", 0, 0, GDK_P },
5566 { "toplevel toggle", 0, 0, GDK_F4 },
5567 { "help", 0, 0, GDK_F1 },
5568 { "run_script", MOD1, 0, GDK_r },
5570 /* search */
5571 { "searchnext", 0, 0, GDK_n },
5572 { "searchprevious", 0, 0, GDK_N },
5574 /* focus */
5575 { "focusaddress", 0, 0, GDK_F6 },
5576 { "focussearch", 0, 0, GDK_F7 },
5578 /* hinting */
5579 { "hinting", 0, 0, GDK_f },
5581 /* custom stylesheet */
5582 { "userstyle", 0, 0, GDK_i },
5584 /* navigation */
5585 { "goback", 0, 0, GDK_BackSpace },
5586 { "goback", MOD1, 0, GDK_Left },
5587 { "goforward", SHFT, 0, GDK_BackSpace },
5588 { "goforward", MOD1, 0, GDK_Right },
5589 { "reload", 0, 0, GDK_F5 },
5590 { "reload", CTRL, 0, GDK_r },
5591 { "reload", CTRL, 0, GDK_l },
5592 { "favorites", MOD1, 1, GDK_f },
5594 /* vertical movement */
5595 { "scrolldown", 0, 0, GDK_j },
5596 { "scrolldown", 0, 0, GDK_Down },
5597 { "scrollup", 0, 0, GDK_Up },
5598 { "scrollup", 0, 0, GDK_k },
5599 { "scrollbottom", 0, 0, GDK_G },
5600 { "scrollbottom", 0, 0, GDK_End },
5601 { "scrolltop", 0, 0, GDK_Home },
5602 { "scrollpagedown", 0, 0, GDK_space },
5603 { "scrollpagedown", CTRL, 0, GDK_f },
5604 { "scrollhalfdown", CTRL, 0, GDK_d },
5605 { "scrollpagedown", 0, 0, GDK_Page_Down },
5606 { "scrollpageup", 0, 0, GDK_Page_Up },
5607 { "scrollpageup", CTRL, 0, GDK_b },
5608 { "scrollhalfup", CTRL, 0, GDK_u },
5609 /* horizontal movement */
5610 { "scrollright", 0, 0, GDK_l },
5611 { "scrollright", 0, 0, GDK_Right },
5612 { "scrollleft", 0, 0, GDK_Left },
5613 { "scrollleft", 0, 0, GDK_h },
5614 { "scrollfarright", 0, 0, GDK_dollar },
5615 { "scrollfarleft", 0, 0, GDK_0 },
5617 /* tabs */
5618 { "tabnew", CTRL, 0, GDK_t },
5619 { "999tabnew", CTRL, 0, GDK_T },
5620 { "tabclose", CTRL, 1, GDK_w },
5621 { "tabundoclose", 0, 0, GDK_U },
5622 { "tabnext 1", CTRL, 0, GDK_1 },
5623 { "tabnext 2", CTRL, 0, GDK_2 },
5624 { "tabnext 3", CTRL, 0, GDK_3 },
5625 { "tabnext 4", CTRL, 0, GDK_4 },
5626 { "tabnext 5", CTRL, 0, GDK_5 },
5627 { "tabnext 6", CTRL, 0, GDK_6 },
5628 { "tabnext 7", CTRL, 0, GDK_7 },
5629 { "tabnext 8", CTRL, 0, GDK_8 },
5630 { "tabnext 9", CTRL, 0, GDK_9 },
5631 { "tabfirst", CTRL, 0, GDK_less },
5632 { "tablast", CTRL, 0, GDK_greater },
5633 { "tabprevious", CTRL, 0, GDK_Left },
5634 { "tabnext", CTRL, 0, GDK_Right },
5635 { "focusout", CTRL, 0, GDK_minus },
5636 { "focusin", CTRL, 0, GDK_plus },
5637 { "focusin", CTRL, 0, GDK_equal },
5638 { "focusreset", CTRL, 0, GDK_0 },
5640 /* command aliases (handy when -S flag is used) */
5641 { "promptopen", 0, 0, GDK_F9 },
5642 { "promptopencurrent", 0, 0, GDK_F10 },
5643 { "prompttabnew", 0, 0, GDK_F11 },
5644 { "prompttabnewcurrent",0, 0, GDK_F12 },
5646 TAILQ_HEAD(keybinding_list, key_binding);
5648 void
5649 walk_kb(struct settings *s,
5650 void (*cb)(struct settings *, char *, void *), void *cb_args)
5652 struct key_binding *k;
5653 char str[1024];
5655 if (s == NULL || cb == NULL) {
5656 show_oops(NULL, "walk_kb invalid parameters");
5657 return;
5660 TAILQ_FOREACH(k, &kbl, entry) {
5661 if (k->cmd == NULL)
5662 continue;
5663 str[0] = '\0';
5665 /* sanity */
5666 if (gdk_keyval_name(k->key) == NULL)
5667 continue;
5669 strlcat(str, k->cmd, sizeof str);
5670 strlcat(str, ",", sizeof str);
5672 if (k->mask & GDK_SHIFT_MASK)
5673 strlcat(str, "S-", sizeof str);
5674 if (k->mask & GDK_CONTROL_MASK)
5675 strlcat(str, "C-", sizeof str);
5676 if (k->mask & GDK_MOD1_MASK)
5677 strlcat(str, "M1-", sizeof str);
5678 if (k->mask & GDK_MOD2_MASK)
5679 strlcat(str, "M2-", sizeof str);
5680 if (k->mask & GDK_MOD3_MASK)
5681 strlcat(str, "M3-", sizeof str);
5682 if (k->mask & GDK_MOD4_MASK)
5683 strlcat(str, "M4-", sizeof str);
5684 if (k->mask & GDK_MOD5_MASK)
5685 strlcat(str, "M5-", sizeof str);
5687 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5688 cb(s, str, cb_args);
5692 void
5693 init_keybindings(void)
5695 int i;
5696 struct key_binding *k;
5698 for (i = 0; i < LENGTH(keys); i++) {
5699 k = g_malloc0(sizeof *k);
5700 k->cmd = keys[i].cmd;
5701 k->mask = keys[i].mask;
5702 k->use_in_entry = keys[i].use_in_entry;
5703 k->key = keys[i].key;
5704 TAILQ_INSERT_HEAD(&kbl, k, entry);
5706 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5707 k->cmd ? k->cmd : "unnamed key");
5711 void
5712 keybinding_clearall(void)
5714 struct key_binding *k, *next;
5716 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5717 next = TAILQ_NEXT(k, entry);
5718 if (k->cmd == NULL)
5719 continue;
5721 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5722 k->cmd ? k->cmd : "unnamed key");
5723 TAILQ_REMOVE(&kbl, k, entry);
5724 g_free(k);
5729 keybinding_add(char *cmd, char *key, int use_in_entry)
5731 struct key_binding *k;
5732 guint keyval, mask = 0;
5733 int i;
5735 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5737 /* Keys which are to be used in entry have been prefixed with an
5738 * exclamation mark. */
5739 if (use_in_entry)
5740 key++;
5742 /* find modifier keys */
5743 if (strstr(key, "S-"))
5744 mask |= GDK_SHIFT_MASK;
5745 if (strstr(key, "C-"))
5746 mask |= GDK_CONTROL_MASK;
5747 if (strstr(key, "M1-"))
5748 mask |= GDK_MOD1_MASK;
5749 if (strstr(key, "M2-"))
5750 mask |= GDK_MOD2_MASK;
5751 if (strstr(key, "M3-"))
5752 mask |= GDK_MOD3_MASK;
5753 if (strstr(key, "M4-"))
5754 mask |= GDK_MOD4_MASK;
5755 if (strstr(key, "M5-"))
5756 mask |= GDK_MOD5_MASK;
5758 /* find keyname */
5759 for (i = strlen(key) - 1; i > 0; i--)
5760 if (key[i] == '-')
5761 key = &key[i + 1];
5763 /* validate keyname */
5764 keyval = gdk_keyval_from_name(key);
5765 if (keyval == GDK_VoidSymbol) {
5766 warnx("invalid keybinding name %s", key);
5767 return (1);
5769 /* must run this test too, gtk+ doesn't handle 10 for example */
5770 if (gdk_keyval_name(keyval) == NULL) {
5771 warnx("invalid keybinding name %s", key);
5772 return (1);
5775 /* Remove eventual dupes. */
5776 TAILQ_FOREACH(k, &kbl, entry)
5777 if (k->key == keyval && k->mask == mask) {
5778 TAILQ_REMOVE(&kbl, k, entry);
5779 g_free(k);
5780 break;
5783 /* add keyname */
5784 k = g_malloc0(sizeof *k);
5785 k->cmd = g_strdup(cmd);
5786 k->mask = mask;
5787 k->use_in_entry = use_in_entry;
5788 k->key = keyval;
5790 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5791 k->cmd,
5792 k->mask,
5793 k->use_in_entry,
5794 k->key);
5795 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5796 k->cmd, gdk_keyval_name(keyval));
5798 TAILQ_INSERT_HEAD(&kbl, k, entry);
5800 return (0);
5804 add_kb(struct settings *s, char *entry)
5806 char *kb, *key;
5808 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5810 /* clearall is special */
5811 if (!strcmp(entry, "clearall")) {
5812 keybinding_clearall();
5813 return (0);
5816 kb = strstr(entry, ",");
5817 if (kb == NULL)
5818 return (1);
5819 *kb = '\0';
5820 key = kb + 1;
5822 return (keybinding_add(entry, key, key[0] == '!'));
5825 struct cmd {
5826 char *cmd;
5827 int level;
5828 int (*func)(struct tab *, struct karg *);
5829 int arg;
5830 int type;
5831 } cmds[] = {
5832 { "command", 0, command, ':', 0 },
5833 { "search", 0, command, '/', 0 },
5834 { "searchb", 0, command, '?', 0 },
5835 { "togglesrc", 0, toggle_src, 0, 0 },
5837 /* yanking and pasting */
5838 { "yankuri", 0, yank_uri, 0, 0 },
5839 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5840 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5841 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5843 /* search */
5844 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5845 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5847 /* focus */
5848 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5849 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5851 /* hinting */
5852 { "hinting", 0, hint, 0, 0 },
5854 /* custom stylesheet */
5855 { "userstyle", 0, userstyle, 0, 0 },
5857 /* navigation */
5858 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5859 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5860 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5862 /* vertical movement */
5863 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5864 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5865 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5866 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5867 { "1", 0, move, XT_MOVE_TOP, 0 },
5868 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5869 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5870 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5871 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5872 /* horizontal movement */
5873 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5874 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5875 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5876 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5878 { "favorites", 0, xtp_page_fl, 0, 0 },
5879 { "fav", 0, xtp_page_fl, 0, 0 },
5880 { "favadd", 0, add_favorite, 0, 0 },
5882 { "qall", 0, quit, 0, 0 },
5883 { "quitall", 0, quit, 0, 0 },
5884 { "w", 0, save_tabs, 0, 0 },
5885 { "wq", 0, save_tabs_and_quit, 0, 0 },
5886 { "help", 0, help, 0, 0 },
5887 { "about", 0, about, 0, 0 },
5888 { "stats", 0, stats, 0, 0 },
5889 { "version", 0, about, 0, 0 },
5891 /* js command */
5892 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5893 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5894 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5895 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5896 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5897 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5898 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5899 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5900 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5901 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5902 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5904 /* cookie command */
5905 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5906 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5907 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5908 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5909 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5910 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5911 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5912 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5913 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5914 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5915 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5917 /* toplevel (domain) command */
5918 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5919 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5921 /* cookie jar */
5922 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5924 /* cert command */
5925 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5926 { "save", 1, cert_cmd, XT_SAVE, 0 },
5927 { "show", 1, cert_cmd, XT_SHOW, 0 },
5929 { "ca", 0, ca_cmd, 0, 0 },
5930 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5931 { "dl", 0, xtp_page_dl, 0, 0 },
5932 { "h", 0, xtp_page_hl, 0, 0 },
5933 { "history", 0, xtp_page_hl, 0, 0 },
5934 { "home", 0, go_home, 0, 0 },
5935 { "restart", 0, restart, 0, 0 },
5936 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5937 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5938 { "statustoggle", 0, statustoggle, 0, 0 },
5939 { "run_script", 0, run_page_script, 0, XT_USERARG },
5941 { "print", 0, print_page, 0, 0 },
5943 /* tabs */
5944 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5945 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5946 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5947 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5948 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5949 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5950 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5951 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5952 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5953 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5954 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5955 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5956 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5957 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5958 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5959 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5960 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5961 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5962 { "buffers", 0, buffers, 0, 0 },
5963 { "ls", 0, buffers, 0, 0 },
5964 { "tabs", 0, buffers, 0, 0 },
5965 { "encoding", 0, set_encoding, 0, XT_USERARG },
5967 /* command aliases (handy when -S flag is used) */
5968 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5969 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5970 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5971 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5973 /* settings */
5974 { "set", 0, set, 0, XT_SETARG },
5976 { "fullscreen", 0, fullscreen, 0, 0 },
5977 { "f", 0, fullscreen, 0, 0 },
5979 /* sessions */
5980 { "session", 0, session_cmd, XT_SHOW, 0 },
5981 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5982 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5983 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5984 { "show", 1, session_cmd, XT_SHOW, 0 },
5987 struct {
5988 int index;
5989 int len;
5990 gchar *list[256];
5991 } cmd_status = {-1, 0};
5993 gboolean
5994 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5997 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5998 btn_down = 0;
6000 return (FALSE);
6003 gboolean
6004 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6006 struct karg a;
6008 hide_oops(t);
6009 hide_buffers(t);
6011 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6012 btn_down = 1;
6013 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
6014 /* go backward */
6015 a.i = XT_NAV_BACK;
6016 navaction(t, &a);
6018 return (TRUE);
6019 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
6020 /* go forward */
6021 a.i = XT_NAV_FORWARD;
6022 navaction(t, &a);
6024 return (TRUE);
6027 return (FALSE);
6030 gboolean
6031 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6033 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6035 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6036 delete_tab(t);
6038 return (FALSE);
6042 * cancel, remove, etc. downloads
6044 void
6045 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6047 struct download find, *d = NULL;
6049 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6051 /* some commands require a valid download id */
6052 if (cmd != XT_XTP_DL_LIST) {
6053 /* lookup download in question */
6054 find.id = id;
6055 d = RB_FIND(download_list, &downloads, &find);
6057 if (d == NULL) {
6058 show_oops(t, "%s: no such download", __func__);
6059 return;
6063 /* decide what to do */
6064 switch (cmd) {
6065 case XT_XTP_DL_CANCEL:
6066 webkit_download_cancel(d->download);
6067 break;
6068 case XT_XTP_DL_REMOVE:
6069 webkit_download_cancel(d->download); /* just incase */
6070 g_object_unref(d->download);
6071 RB_REMOVE(download_list, &downloads, d);
6072 break;
6073 case XT_XTP_DL_LIST:
6074 /* Nothing */
6075 break;
6076 default:
6077 show_oops(t, "%s: unknown command", __func__);
6078 break;
6080 xtp_page_dl(t, NULL);
6084 * Actions on history, only does one thing for now, but
6085 * we provide the function for future actions
6087 void
6088 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6090 struct history *h, *next;
6091 int i = 1;
6093 switch (cmd) {
6094 case XT_XTP_HL_REMOVE:
6095 /* walk backwards, as listed in reverse */
6096 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6097 next = RB_PREV(history_list, &hl, h);
6098 if (id == i) {
6099 RB_REMOVE(history_list, &hl, h);
6100 g_free((gpointer) h->title);
6101 g_free((gpointer) h->uri);
6102 g_free(h);
6103 break;
6105 i++;
6107 break;
6108 case XT_XTP_HL_LIST:
6109 /* Nothing - just xtp_page_hl() below */
6110 break;
6111 default:
6112 show_oops(t, "%s: unknown command", __func__);
6113 break;
6116 xtp_page_hl(t, NULL);
6119 /* remove a favorite */
6120 void
6121 remove_favorite(struct tab *t, int index)
6123 char file[PATH_MAX], *title, *uri = NULL;
6124 char *new_favs, *tmp;
6125 FILE *f;
6126 int i;
6127 size_t len, lineno;
6129 /* open favorites */
6130 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6132 if ((f = fopen(file, "r")) == NULL) {
6133 show_oops(t, "%s: can't open favorites: %s",
6134 __func__, strerror(errno));
6135 return;
6138 /* build a string which will become the new favroites file */
6139 new_favs = g_strdup("");
6141 for (i = 1;;) {
6142 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6143 if (feof(f) || ferror(f))
6144 break;
6145 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6146 if (len == 0) {
6147 free(title);
6148 title = NULL;
6149 continue;
6152 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6153 if (feof(f) || ferror(f)) {
6154 show_oops(t, "%s: can't parse favorites %s",
6155 __func__, strerror(errno));
6156 goto clean;
6160 /* as long as this isn't the one we are deleting add to file */
6161 if (i != index) {
6162 tmp = new_favs;
6163 new_favs = g_strdup_printf("%s%s\n%s\n",
6164 new_favs, title, uri);
6165 g_free(tmp);
6168 free(uri);
6169 uri = NULL;
6170 free(title);
6171 title = NULL;
6172 i++;
6174 fclose(f);
6176 /* write back new favorites file */
6177 if ((f = fopen(file, "w")) == NULL) {
6178 show_oops(t, "%s: can't open favorites: %s",
6179 __func__, strerror(errno));
6180 goto clean;
6183 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
6184 show_oops(t, "%s: can't fwrite"); /* shut gcc up */
6185 fclose(f);
6187 clean:
6188 if (uri)
6189 free(uri);
6190 if (title)
6191 free(title);
6193 g_free(new_favs);
6196 void
6197 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6199 switch (cmd) {
6200 case XT_XTP_FL_LIST:
6201 /* nothing, just the below call to xtp_page_fl() */
6202 break;
6203 case XT_XTP_FL_REMOVE:
6204 remove_favorite(t, arg);
6205 break;
6206 default:
6207 show_oops(t, "%s: invalid favorites command", __func__);
6208 break;
6211 xtp_page_fl(t, NULL);
6214 void
6215 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6217 switch (cmd) {
6218 case XT_XTP_CL_LIST:
6219 /* nothing, just xtp_page_cl() */
6220 break;
6221 case XT_XTP_CL_REMOVE:
6222 remove_cookie(arg);
6223 break;
6224 default:
6225 show_oops(t, "%s: unknown cookie xtp command", __func__);
6226 break;
6229 xtp_page_cl(t, NULL);
6232 /* link an XTP class to it's session key and handler function */
6233 struct xtp_despatch {
6234 uint8_t xtp_class;
6235 char **session_key;
6236 void (*handle_func)(struct tab *, uint8_t, int);
6239 struct xtp_despatch xtp_despatches[] = {
6240 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6241 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6242 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6243 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6244 { XT_XTP_INVALID, NULL, NULL }
6248 * is the url xtp protocol? (xxxt://)
6249 * if so, parse and despatch correct bahvior
6252 parse_xtp_url(struct tab *t, const char *url)
6254 char *dup = NULL, *p, *last = NULL;
6255 uint8_t n_tokens = 0;
6256 char *tokens[4] = {NULL, NULL, NULL, ""};
6257 struct xtp_despatch *dsp, *dsp_match = NULL;
6258 uint8_t req_class;
6259 int ret = FALSE;
6262 * tokens array meaning:
6263 * tokens[0] = class
6264 * tokens[1] = session key
6265 * tokens[2] = action
6266 * tokens[3] = optional argument
6269 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6271 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6272 goto clean;
6274 dup = g_strdup(url + strlen(XT_XTP_STR));
6276 /* split out the url */
6277 for ((p = strtok_r(dup, "/", &last)); p;
6278 (p = strtok_r(NULL, "/", &last))) {
6279 if (n_tokens < 4)
6280 tokens[n_tokens++] = p;
6283 /* should be atleast three fields 'class/seskey/command/arg' */
6284 if (n_tokens < 3)
6285 goto clean;
6287 dsp = xtp_despatches;
6288 req_class = atoi(tokens[0]);
6289 while (dsp->xtp_class) {
6290 if (dsp->xtp_class == req_class) {
6291 dsp_match = dsp;
6292 break;
6294 dsp++;
6297 /* did we find one atall? */
6298 if (dsp_match == NULL) {
6299 show_oops(t, "%s: no matching xtp despatch found", __func__);
6300 goto clean;
6303 /* check session key and call despatch function */
6304 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6305 ret = TRUE; /* all is well, this was a valid xtp request */
6306 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6309 clean:
6310 if (dup)
6311 g_free(dup);
6313 return (ret);
6318 void
6319 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6321 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6323 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6325 if (t == NULL) {
6326 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6327 return;
6330 if (uri == NULL) {
6331 show_oops(t, "activate_uri_entry_cb no uri");
6332 return;
6335 uri += strspn(uri, "\t ");
6337 /* if xxxt:// treat specially */
6338 if (parse_xtp_url(t, uri))
6339 return;
6341 /* otherwise continue to load page normally */
6342 load_uri(t, (gchar *)uri);
6343 focus_webview(t);
6346 void
6347 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6349 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6350 char *newuri = NULL;
6351 gchar *enc_search;
6353 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6355 if (t == NULL) {
6356 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6357 return;
6360 if (search_string == NULL) {
6361 show_oops(t, "no search_string");
6362 return;
6365 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6367 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6368 newuri = g_strdup_printf(search_string, enc_search);
6369 g_free(enc_search);
6371 marks_clear(t);
6372 webkit_web_view_load_uri(t->wv, newuri);
6373 focus_webview(t);
6375 if (newuri)
6376 g_free(newuri);
6379 void
6380 check_and_set_cookie(const gchar *uri, struct tab *t)
6382 struct domain *d = NULL;
6383 int es = 0;
6385 if (uri == NULL || t == NULL)
6386 return;
6388 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6389 es = 0;
6390 else
6391 es = 1;
6393 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6394 es ? "enable" : "disable", uri);
6396 g_object_set(G_OBJECT(t->settings),
6397 "enable-html5-local-storage", es, (char *)NULL);
6398 webkit_web_view_set_settings(t->wv, t->settings);
6401 void
6402 check_and_set_js(const gchar *uri, struct tab *t)
6404 struct domain *d = NULL;
6405 int es = 0;
6407 if (uri == NULL || t == NULL)
6408 return;
6410 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6411 es = 0;
6412 else
6413 es = 1;
6415 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6416 es ? "enable" : "disable", uri);
6418 g_object_set(G_OBJECT(t->settings),
6419 "enable-scripts", es, (char *)NULL);
6420 g_object_set(G_OBJECT(t->settings),
6421 "javascript-can-open-windows-automatically", es, (char *)NULL);
6422 webkit_web_view_set_settings(t->wv, t->settings);
6424 button_set_stockid(t->js_toggle,
6425 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6428 void
6429 color_address_bar(gpointer p)
6431 GdkColor color;
6432 struct tab *tt, *t = p;
6433 gchar *col_str = XT_COLOR_WHITE;
6434 const gchar *uri, *u = NULL, *error_str = NULL;
6436 #ifdef USE_THREADS
6437 gdk_threads_enter();
6438 #endif
6439 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6441 /* make sure t still exists */
6442 if (t == NULL)
6443 return;
6444 TAILQ_FOREACH(tt, &tabs, entry)
6445 if (t == tt)
6446 break;
6447 if (t != tt)
6448 goto done;
6450 if ((uri = get_uri(t)) == NULL)
6451 goto white;
6452 u = g_strdup(uri);
6454 #ifdef USE_THREADS
6455 gdk_threads_leave();
6456 #endif
6458 col_str = XT_COLOR_YELLOW;
6459 switch (load_compare_cert(u, &error_str)) {
6460 case CERT_LOCAL:
6461 col_str = XT_COLOR_BLUE;
6462 break;
6463 case CERT_TRUSTED:
6464 col_str = XT_COLOR_GREEN;
6465 break;
6466 case CERT_UNTRUSTED:
6467 col_str = XT_COLOR_YELLOW;
6468 break;
6469 case CERT_BAD:
6470 col_str = XT_COLOR_RED;
6471 break;
6474 #ifdef USE_THREADS
6475 gdk_threads_enter();
6476 #endif
6477 /* make sure t isn't deleted */
6478 TAILQ_FOREACH(tt, &tabs, entry)
6479 if (t == tt)
6480 break;
6481 if (t != tt)
6482 goto done;
6484 #ifdef USE_THREADS
6485 /* test to see if the user navigated away and canceled the thread */
6486 if (t->thread != g_thread_self())
6487 goto done;
6488 if ((uri = get_uri(t)) == NULL) {
6489 t->thread = NULL;
6490 goto done;
6492 if (strcmp(uri, u)) {
6493 /* make sure we are still the same url */
6494 t->thread = NULL;
6495 goto done;
6497 #endif
6498 white:
6499 gdk_color_parse(col_str, &color);
6500 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6502 if (!strcmp(col_str, XT_COLOR_WHITE))
6503 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6504 else
6505 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6507 if (error_str && error_str[0] != '\0')
6508 show_oops(t, "%s", error_str);
6509 #ifdef USE_THREADS
6510 t->thread = NULL;
6511 #endif
6512 done:
6513 /* t is invalid at this point */
6514 if (u)
6515 g_free((gpointer)u);
6516 #ifdef USE_THREADS
6517 gdk_threads_leave();
6518 #endif
6521 void
6522 show_ca_status(struct tab *t, const char *uri)
6524 GdkColor color;
6525 gchar *col_str = XT_COLOR_WHITE;
6527 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6528 ssl_strict_certs, ssl_ca_file, uri);
6530 if (t == NULL)
6531 return;
6533 if (uri == NULL)
6534 goto done;
6535 if (ssl_ca_file == NULL) {
6536 if (g_str_has_prefix(uri, "http://"))
6537 goto done;
6538 if (g_str_has_prefix(uri, "https://")) {
6539 col_str = XT_COLOR_RED;
6540 goto done;
6542 return;
6544 if (g_str_has_prefix(uri, "http://") ||
6545 !g_str_has_prefix(uri, "https://"))
6546 goto done;
6547 #ifdef USE_THREADS
6549 * It is not necessary to see if the thread is already running.
6550 * If the thread is in progress setting it to something else aborts it
6551 * on the way out.
6554 /* thread the coloring of the address bar */
6555 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
6556 #else
6557 color_address_bar(t);
6558 #endif
6559 return;
6561 done:
6562 if (col_str) {
6563 gdk_color_parse(col_str, &color);
6564 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6566 if (!strcmp(col_str, XT_COLOR_WHITE))
6567 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6568 else
6569 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6573 void
6574 free_favicon(struct tab *t)
6576 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6577 __func__, t->icon_download, t->icon_request);
6579 if (t->icon_request)
6580 g_object_unref(t->icon_request);
6581 if (t->icon_dest_uri)
6582 g_free(t->icon_dest_uri);
6584 t->icon_request = NULL;
6585 t->icon_dest_uri = NULL;
6588 void
6589 xt_icon_from_name(struct tab *t, gchar *name)
6591 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6592 GTK_ENTRY_ICON_PRIMARY, "text-html");
6593 if (show_url == 0)
6594 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6595 GTK_ENTRY_ICON_PRIMARY, "text-html");
6596 else
6597 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6598 GTK_ENTRY_ICON_PRIMARY, NULL);
6601 void
6602 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6604 GdkPixbuf *pb_scaled;
6606 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6607 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6608 GDK_INTERP_BILINEAR);
6609 else
6610 pb_scaled = pb;
6612 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6613 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6614 if (show_url == 0)
6615 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6616 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6617 else
6618 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6619 GTK_ENTRY_ICON_PRIMARY, NULL);
6621 if (pb_scaled != pb)
6622 g_object_unref(pb_scaled);
6625 void
6626 xt_icon_from_file(struct tab *t, char *file)
6628 GdkPixbuf *pb;
6630 if (g_str_has_prefix(file, "file://"))
6631 file += strlen("file://");
6633 pb = gdk_pixbuf_new_from_file(file, NULL);
6634 if (pb) {
6635 xt_icon_from_pixbuf(t, pb);
6636 g_object_unref(pb);
6637 } else
6638 xt_icon_from_name(t, "text-html");
6641 gboolean
6642 is_valid_icon(char *file)
6644 gboolean valid = 0;
6645 const char *mime_type;
6646 GFileInfo *fi;
6647 GFile *gf;
6649 gf = g_file_new_for_path(file);
6650 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6651 NULL, NULL);
6652 mime_type = g_file_info_get_content_type(fi);
6653 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6654 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6655 g_strcmp0(mime_type, "image/png") == 0 ||
6656 g_strcmp0(mime_type, "image/gif") == 0 ||
6657 g_strcmp0(mime_type, "application/octet-stream") == 0;
6658 g_object_unref(fi);
6659 g_object_unref(gf);
6661 return (valid);
6664 void
6665 set_favicon_from_file(struct tab *t, char *file)
6667 struct stat sb;
6669 if (t == NULL || file == NULL)
6670 return;
6672 if (g_str_has_prefix(file, "file://"))
6673 file += strlen("file://");
6674 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6676 if (!stat(file, &sb)) {
6677 if (sb.st_size == 0 || !is_valid_icon(file)) {
6678 /* corrupt icon so trash it */
6679 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6680 __func__, file);
6681 unlink(file);
6682 /* no need to set icon to default here */
6683 return;
6686 xt_icon_from_file(t, file);
6689 void
6690 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6691 WebKitWebView *wv)
6693 WebKitDownloadStatus status = webkit_download_get_status(download);
6694 struct tab *tt = NULL, *t = NULL;
6697 * find the webview instead of passing in the tab as it could have been
6698 * deleted from underneath us.
6700 TAILQ_FOREACH(tt, &tabs, entry) {
6701 if (tt->wv == wv) {
6702 t = tt;
6703 break;
6706 if (t == NULL)
6707 return;
6709 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6710 __func__, t->tab_id, status);
6712 switch (status) {
6713 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6714 /* -1 */
6715 t->icon_download = NULL;
6716 free_favicon(t);
6717 break;
6718 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6719 /* 0 */
6720 break;
6721 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6722 /* 1 */
6723 break;
6724 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6725 /* 2 */
6726 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6727 __func__, t->tab_id);
6728 t->icon_download = NULL;
6729 free_favicon(t);
6730 break;
6731 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6732 /* 3 */
6734 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6735 __func__, t->icon_dest_uri);
6736 set_favicon_from_file(t, t->icon_dest_uri);
6737 /* these will be freed post callback */
6738 t->icon_request = NULL;
6739 t->icon_download = NULL;
6740 break;
6741 default:
6742 break;
6746 void
6747 abort_favicon_download(struct tab *t)
6749 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6751 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6752 if (t->icon_download) {
6753 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6754 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6755 webkit_download_cancel(t->icon_download);
6756 t->icon_download = NULL;
6757 } else
6758 free_favicon(t);
6759 #endif
6761 xt_icon_from_name(t, "text-html");
6764 void
6765 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6767 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6769 if (uri == NULL || t == NULL)
6770 return;
6772 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6773 /* take icon from WebKitIconDatabase */
6774 GdkPixbuf *pb;
6776 pb = webkit_web_view_get_icon_pixbuf(wv);
6777 if (pb) {
6778 xt_icon_from_pixbuf(t, pb);
6779 g_object_unref(pb);
6780 } else
6781 xt_icon_from_name(t, "text-html");
6782 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6783 /* download icon to cache dir */
6784 gchar *name_hash, file[PATH_MAX];
6785 struct stat sb;
6787 if (t->icon_request) {
6788 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6789 return;
6792 /* check to see if we got the icon in cache */
6793 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6794 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6795 g_free(name_hash);
6797 if (!stat(file, &sb)) {
6798 if (sb.st_size > 0) {
6799 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6800 __func__, file);
6801 set_favicon_from_file(t, file);
6802 return;
6805 /* corrupt icon so trash it */
6806 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6807 __func__, file);
6808 unlink(file);
6811 /* create download for icon */
6812 t->icon_request = webkit_network_request_new(uri);
6813 if (t->icon_request == NULL) {
6814 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6815 __func__, uri);
6816 return;
6819 t->icon_download = webkit_download_new(t->icon_request);
6820 if (t->icon_download == NULL)
6821 return;
6823 /* we have to free icon_dest_uri later */
6824 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6825 webkit_download_set_destination_uri(t->icon_download,
6826 t->icon_dest_uri);
6828 if (webkit_download_get_status(t->icon_download) ==
6829 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6830 g_object_unref(t->icon_request);
6831 g_free(t->icon_dest_uri);
6832 t->icon_request = NULL;
6833 t->icon_dest_uri = NULL;
6834 return;
6837 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6838 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6840 webkit_download_start(t->icon_download);
6841 #endif
6844 void
6845 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6847 const gchar *uri = NULL, *title = NULL;
6848 struct history *h, find;
6849 struct karg a;
6850 GdkColor color;
6852 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6853 webkit_web_view_get_load_status(wview),
6854 get_uri(t) ? get_uri(t) : "NOTHING");
6856 if (t == NULL) {
6857 show_oops(NULL, "notify_load_status_cb invalid parameters");
6858 return;
6861 switch (webkit_web_view_get_load_status(wview)) {
6862 case WEBKIT_LOAD_PROVISIONAL:
6863 /* 0 */
6864 abort_favicon_download(t);
6865 #if GTK_CHECK_VERSION(2, 20, 0)
6866 gtk_widget_show(t->spinner);
6867 gtk_spinner_start(GTK_SPINNER(t->spinner));
6868 #endif
6869 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6871 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6873 /* assume we are a new address */
6874 gdk_color_parse("white", &color);
6875 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6876 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6878 /* take focus if we are visible */
6879 focus_webview(t);
6880 t->focus_wv = 1;
6881 #ifdef USE_THREAD
6882 /* kill color thread */
6883 t->thread = NULL;
6884 #endif
6885 break;
6887 case WEBKIT_LOAD_COMMITTED:
6888 /* 1 */
6889 uri = get_uri(t);
6890 if (uri == NULL)
6891 return;
6892 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6894 if (t->status) {
6895 g_free(t->status);
6896 t->status = NULL;
6898 set_status(t, (char *)uri, XT_STATUS_LOADING);
6900 /* check if js white listing is enabled */
6901 if (enable_cookie_whitelist)
6902 check_and_set_cookie(uri, t);
6903 if (enable_js_whitelist)
6904 check_and_set_js(uri, t);
6906 if (t->styled)
6907 apply_style(t);
6910 /* we know enough to autosave the session */
6911 if (session_autosave) {
6912 a.s = NULL;
6913 save_tabs(t, &a);
6916 show_ca_status(t, uri);
6917 break;
6919 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6920 /* 3 */
6921 break;
6923 case WEBKIT_LOAD_FINISHED:
6924 /* 2 */
6925 uri = get_uri(t);
6926 if (uri == NULL)
6927 return;
6929 if (!strncmp(uri, "http://", strlen("http://")) ||
6930 !strncmp(uri, "https://", strlen("https://")) ||
6931 !strncmp(uri, "file://", strlen("file://"))) {
6932 find.uri = uri;
6933 h = RB_FIND(history_list, &hl, &find);
6934 if (!h) {
6935 title = get_title(t, FALSE);
6936 h = g_malloc(sizeof *h);
6937 h->uri = g_strdup(uri);
6938 h->title = g_strdup(title);
6939 RB_INSERT(history_list, &hl, h);
6940 completion_add_uri(h->uri);
6941 update_history_tabs(NULL);
6945 set_status(t, (char *)uri, XT_STATUS_URI);
6946 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6947 case WEBKIT_LOAD_FAILED:
6948 /* 4 */
6949 #endif
6950 #if GTK_CHECK_VERSION(2, 20, 0)
6951 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6952 gtk_widget_hide(t->spinner);
6953 #endif
6954 default:
6955 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6956 break;
6959 if (t->item)
6960 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6961 else
6962 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6963 can_go_back_for_real(t));
6965 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6966 can_go_forward_for_real(t));
6969 void
6970 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6972 const gchar *title = NULL, *win_title = NULL;
6974 title = get_title(t, FALSE);
6975 win_title = get_title(t, TRUE);
6976 gtk_label_set_text(GTK_LABEL(t->label), title);
6977 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6978 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6979 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6982 void
6983 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6985 run_script(t, JS_HINTING);
6988 void
6989 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6991 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6992 progress == 100 ? 0 : (double)progress / 100);
6993 if (show_url == 0) {
6994 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6995 progress == 100 ? 0 : (double)progress / 100);
6998 update_statusbar_position(NULL, NULL);
7002 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
7003 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
7004 WebKitWebPolicyDecision *pd, struct tab *t)
7006 char *uri;
7007 WebKitWebNavigationReason reason;
7008 struct domain *d = NULL;
7010 if (t == NULL) {
7011 show_oops(NULL, "webview_npd_cb invalid parameters");
7012 return (FALSE);
7015 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
7016 t->ctrl_click,
7017 webkit_network_request_get_uri(request));
7019 uri = (char *)webkit_network_request_get_uri(request);
7021 /* if this is an xtp url, we don't load anything else */
7022 if (parse_xtp_url(t, uri))
7023 return (TRUE);
7025 if (t->ctrl_click) {
7026 t->ctrl_click = 0;
7027 create_new_tab(uri, NULL, ctrl_click_focus, -1);
7028 webkit_web_policy_decision_ignore(pd);
7029 return (TRUE); /* we made the decission */
7033 * This is a little hairy but it comes down to this:
7034 * when we run in whitelist mode we have to assist the browser in
7035 * opening the URL that it would have opened in a new tab.
7037 reason = webkit_web_navigation_action_get_reason(na);
7038 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
7039 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7040 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
7041 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7042 load_uri(t, uri);
7043 webkit_web_policy_decision_use(pd);
7044 return (TRUE); /* we made the decision */
7047 return (FALSE);
7050 WebKitWebView *
7051 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7053 struct tab *tt;
7054 struct domain *d = NULL;
7055 const gchar *uri;
7056 WebKitWebView *webview = NULL;
7058 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
7059 webkit_web_view_get_uri(wv));
7061 if (tabless) {
7062 /* open in current tab */
7063 webview = t->wv;
7064 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7065 uri = webkit_web_view_get_uri(wv);
7066 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7067 return (NULL);
7069 tt = create_new_tab(NULL, NULL, 1, -1);
7070 webview = tt->wv;
7071 } else if (enable_scripts == 1) {
7072 tt = create_new_tab(NULL, NULL, 1, -1);
7073 webview = tt->wv;
7076 return (webview);
7079 gboolean
7080 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
7082 const gchar *uri;
7083 struct domain *d = NULL;
7085 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7087 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7088 uri = webkit_web_view_get_uri(wv);
7089 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7090 return (FALSE);
7092 delete_tab(t);
7093 } else if (enable_scripts == 1)
7094 delete_tab(t);
7096 return (TRUE);
7100 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7102 /* we can not eat the event without throwing gtk off so defer it */
7104 /* catch middle click */
7105 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7106 t->ctrl_click = 1;
7107 goto done;
7110 /* catch ctrl click */
7111 if (e->type == GDK_BUTTON_RELEASE &&
7112 CLEAN(e->state) == GDK_CONTROL_MASK)
7113 t->ctrl_click = 1;
7114 else
7115 t->ctrl_click = 0;
7116 done:
7117 return (XT_CB_PASSTHROUGH);
7121 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7123 struct mime_type *m;
7125 m = find_mime_type(mime_type);
7126 if (m == NULL)
7127 return (1);
7128 if (m->mt_download)
7129 return (1);
7131 switch (fork()) {
7132 case -1:
7133 show_oops(t, "can't fork mime handler");
7134 return (1);
7135 /* NOTREACHED */
7136 case 0:
7137 break;
7138 default:
7139 return (0);
7142 /* child */
7143 execlp(m->mt_action, m->mt_action,
7144 webkit_network_request_get_uri(request), (void *)NULL);
7146 _exit(0);
7148 /* NOTREACHED */
7149 return (0);
7152 char *
7153 get_mime_type(const char *file)
7155 const gchar *m;
7156 char *mime_type = NULL;
7157 GFileInfo *fi;
7158 GFile *gf;
7160 if (g_str_has_prefix(file, "file://"))
7161 file += strlen("file://");
7163 gf = g_file_new_for_path(file);
7164 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7165 NULL, NULL);
7166 if ((m = g_file_info_get_content_type(fi)) != NULL)
7167 mime_type = g_strdup(m);
7168 g_object_unref(fi);
7169 g_object_unref(gf);
7171 return (mime_type);
7175 run_download_mimehandler(char *mime_type, char *file)
7177 struct mime_type *m;
7179 m = find_mime_type(mime_type);
7180 if (m == NULL)
7181 return (1);
7183 switch (fork()) {
7184 case -1:
7185 show_oops(NULL, "can't fork download mime handler");
7186 return (1);
7187 /* NOTREACHED */
7188 case 0:
7189 break;
7190 default:
7191 return (0);
7194 /* child */
7195 if (g_str_has_prefix(file, "file://"))
7196 file += strlen("file://");
7197 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7199 _exit(0);
7201 /* NOTREACHED */
7202 return (0);
7205 void
7206 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7207 WebKitWebView *wv)
7209 WebKitDownloadStatus status;
7210 const char *file = NULL;
7211 char *mime = NULL;
7213 if (download == NULL)
7214 return;
7215 status = webkit_download_get_status(download);
7216 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7217 return;
7219 file = webkit_download_get_destination_uri(download);
7220 if (file == NULL)
7221 return;
7222 mime = get_mime_type(file);
7223 if (mime == NULL)
7224 return;
7226 run_download_mimehandler((char *)mime, (char *)file);
7227 g_free(mime);
7231 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7232 WebKitNetworkRequest *request, char *mime_type,
7233 WebKitWebPolicyDecision *decision, struct tab *t)
7235 if (t == NULL) {
7236 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7237 return (FALSE);
7240 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7241 t->tab_id, mime_type);
7243 if (run_mimehandler(t, mime_type, request) == 0) {
7244 webkit_web_policy_decision_ignore(decision);
7245 focus_webview(t);
7246 return (TRUE);
7249 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7250 webkit_web_policy_decision_download(decision);
7251 return (TRUE);
7254 return (FALSE);
7258 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7259 struct tab *t)
7261 struct stat sb;
7262 const gchar *suggested_name;
7263 gchar *filename = NULL;
7264 char *uri = NULL;
7265 struct download *download_entry;
7266 int i, ret = TRUE;
7268 if (wk_download == NULL || t == NULL) {
7269 show_oops(NULL, "%s invalid parameters", __func__);
7270 return (FALSE);
7273 suggested_name = webkit_download_get_suggested_filename(wk_download);
7274 if (suggested_name == NULL)
7275 return (FALSE); /* abort download */
7277 i = 0;
7278 do {
7279 if (filename) {
7280 g_free(filename);
7281 filename = NULL;
7283 if (i) {
7284 g_free(uri);
7285 uri = NULL;
7286 filename = g_strdup_printf("%d%s", i, suggested_name);
7288 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7289 filename : suggested_name);
7290 i++;
7291 } while (!stat(uri + strlen("file://"), &sb));
7293 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7294 "local %s\n", __func__, t->tab_id, filename, uri);
7296 webkit_download_set_destination_uri(wk_download, uri);
7298 if (webkit_download_get_status(wk_download) ==
7299 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7300 show_oops(t, "%s: download failed to start", __func__);
7301 ret = FALSE;
7302 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7303 } else {
7304 /* connect "download first" mime handler */
7305 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7306 G_CALLBACK(download_status_changed_cb), NULL);
7308 download_entry = g_malloc(sizeof(struct download));
7309 download_entry->download = wk_download;
7310 download_entry->tab = t;
7311 download_entry->id = next_download_id++;
7312 RB_INSERT(download_list, &downloads, download_entry);
7313 /* get from history */
7314 g_object_ref(wk_download);
7315 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7316 show_oops(t, "Download of '%s' started...",
7317 basename((char *)webkit_download_get_destination_uri(wk_download)));
7320 if (uri)
7321 g_free(uri);
7323 if (filename)
7324 g_free(filename);
7326 /* sync other download manager tabs */
7327 update_download_tabs(NULL);
7330 * NOTE: never redirect/render the current tab before this
7331 * function returns. This will cause the download to never start.
7333 return (ret); /* start download */
7336 void
7337 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7339 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7341 if (t == NULL) {
7342 show_oops(NULL, "webview_hover_cb");
7343 return;
7346 if (uri)
7347 set_status(t, uri, XT_STATUS_LINK);
7348 else {
7349 if (t->status)
7350 set_status(t, t->status, XT_STATUS_NOTHING);
7355 mark(struct tab *t, struct karg *arg)
7357 char mark;
7358 int index;
7360 mark = arg->s[1];
7361 if ((index = marktoindex(mark)) == -1)
7362 return (-1);
7364 if (arg->i == XT_MARK_SET)
7365 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7366 else if (arg->i == XT_MARK_GOTO) {
7367 if (t->mark[index] == XT_INVALID_MARK) {
7368 show_oops(t, "mark '%c' does not exist", mark);
7369 return (-1);
7371 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7372 something changes the document size */
7373 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7376 return (0);
7379 void
7380 marks_clear(struct tab *t)
7382 int i;
7384 for (i = 0; i < LENGTH(t->mark); i++)
7385 t->mark[i] = XT_INVALID_MARK;
7389 qmarks_load(void)
7391 char file[PATH_MAX];
7392 char *line = NULL, *p;
7393 int index, i;
7394 FILE *f;
7395 size_t linelen;
7397 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7398 if ((f = fopen(file, "r+")) == NULL) {
7399 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7400 return (1);
7403 for (i = 1; ; i++) {
7404 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7405 break;
7406 if (strlen(line) == 0 || line[0] == '#') {
7407 free(line);
7408 line = NULL;
7409 continue;
7412 p = strtok(line, " \t");
7414 if (p == NULL || strlen(p) != 1 ||
7415 (index = marktoindex(*p)) == -1) {
7416 warnx("corrupt quickmarks file, line %d", i);
7417 break;
7420 p = strtok(NULL, " \t");
7421 if (qmarks[index] != NULL)
7422 g_free(qmarks[index]);
7423 qmarks[index] = g_strdup(p);
7426 fclose(f);
7428 return (0);
7432 qmarks_save(void)
7434 char file[PATH_MAX];
7435 int i;
7436 FILE *f;
7438 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7439 if ((f = fopen(file, "r+")) == NULL) {
7440 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7441 return (1);
7444 for (i = 0; i < XT_NOMARKS; i++)
7445 if (qmarks[i] != NULL)
7446 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7448 fclose(f);
7450 return (0);
7454 qmark(struct tab *t, struct karg *arg)
7456 char mark;
7457 int index;
7459 mark = arg->s[strlen(arg->s)-1];
7460 index = marktoindex(mark);
7461 if (index == -1)
7462 return (-1);
7464 switch (arg->i) {
7465 case XT_QMARK_SET:
7466 if (qmarks[index] != NULL)
7467 g_free(qmarks[index]);
7469 qmarks_load(); /* sync if multiple instances */
7470 qmarks[index] = g_strdup(get_uri(t));
7471 qmarks_save();
7472 break;
7473 case XT_QMARK_OPEN:
7474 if (qmarks[index] != NULL)
7475 load_uri(t, qmarks[index]);
7476 else {
7477 show_oops(t, "quickmark \"%c\" does not exist",
7478 mark);
7479 return (-1);
7481 break;
7482 case XT_QMARK_TAB:
7483 if (qmarks[index] != NULL)
7484 create_new_tab(qmarks[index], NULL, 1, -1);
7485 else {
7486 show_oops(t, "quickmark \"%c\" does not exist",
7487 mark);
7488 return (-1);
7490 break;
7493 return (0);
7497 go_up(struct tab *t, struct karg *args)
7499 int levels;
7500 char *uri;
7501 char *tmp;
7503 levels = atoi(args->s);
7504 if (levels == 0)
7505 levels = 1;
7507 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7508 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7509 return (1);
7510 tmp += strlen(XT_PROTO_DELIM);
7512 /* if an uri starts with a slash, leave it alone (for file:///) */
7513 if (tmp[0] == '/')
7514 tmp++;
7516 while (levels--) {
7517 char *p;
7519 p = strrchr(tmp, '/');
7520 if (p != NULL)
7521 *p = '\0';
7522 else
7523 break;
7526 load_uri(t, uri);
7527 g_free(uri);
7529 return (0);
7533 gototab(struct tab *t, struct karg *args)
7535 int tab;
7536 struct karg arg = {0, NULL, -1};
7538 tab = atoi(args->s);
7540 arg.i = XT_TAB_NEXT;
7541 arg.precount = tab;
7543 movetab(t, &arg);
7545 return (0);
7549 zoom_amount(struct tab *t, struct karg *arg)
7551 struct karg narg = {0, NULL, -1};
7553 narg.i = atoi(arg->s);
7554 resizetab(t, &narg);
7556 return (0);
7560 flip_colon(struct tab *t, struct karg *arg)
7562 struct karg narg = {0, NULL, -1};
7563 char *p;
7565 if (t == NULL || arg == NULL)
7566 return (1);
7568 p = strstr(arg->s, ":");
7569 if (p == NULL)
7570 return (1);
7571 *p = '\0';
7573 narg.i = ':';
7574 narg.s = arg->s;
7575 command(t, &narg);
7577 return (0);
7580 /* buffer commands receive the regex that triggered them in arg.s */
7581 char bcmd[XT_BUFCMD_SZ];
7582 struct buffercmd {
7583 char *regex;
7584 int precount;
7585 #define XT_PRE_NO (0)
7586 #define XT_PRE_YES (1)
7587 #define XT_PRE_MAYBE (2)
7588 char *cmd;
7589 int (*func)(struct tab *, struct karg *);
7590 int arg;
7591 regex_t cregex;
7592 } buffercmds[] = {
7593 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7594 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7595 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7596 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7597 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7598 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7599 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7600 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7601 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7602 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7603 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7604 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7605 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7606 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7607 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7608 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7609 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7610 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7613 void
7614 buffercmd_init(void)
7616 int i;
7618 for (i = 0; i < LENGTH(buffercmds); i++)
7619 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7620 REG_EXTENDED | REG_NOSUB))
7621 startpage_add("invalid buffercmd regex %s",
7622 buffercmds[i].regex);
7625 void
7626 buffercmd_abort(struct tab *t)
7628 int i;
7630 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7631 for (i = 0; i < LENGTH(bcmd); i++)
7632 bcmd[i] = '\0';
7634 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7635 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7638 void
7639 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7641 struct karg arg = {0, NULL, -1};
7643 arg.i = cmd->arg;
7644 arg.s = g_strdup(bcmd);
7646 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7647 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7648 cmd->func(t, &arg);
7650 if (arg.s)
7651 g_free(arg.s);
7653 buffercmd_abort(t);
7656 gboolean
7657 buffercmd_addkey(struct tab *t, guint keyval)
7659 int i, c, match ;
7660 char s[XT_BUFCMD_SZ];
7662 if (keyval == GDK_Escape) {
7663 buffercmd_abort(t);
7664 return (XT_CB_HANDLED);
7667 /* key with modifier or non-ascii character */
7668 if (!isascii(keyval))
7669 return (XT_CB_PASSTHROUGH);
7671 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7672 "to buffer \"%s\"\n", keyval, bcmd);
7674 for (i = 0; i < LENGTH(bcmd); i++)
7675 if (bcmd[i] == '\0') {
7676 bcmd[i] = keyval;
7677 break;
7680 /* buffer full, ignore input */
7681 if (i >= LENGTH(bcmd) -1) {
7682 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7683 buffercmd_abort(t);
7684 return (XT_CB_HANDLED);
7687 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7689 /* find exact match */
7690 for (i = 0; i < LENGTH(buffercmds); i++)
7691 if (regexec(&buffercmds[i].cregex, bcmd,
7692 (size_t) 0, NULL, 0) == 0) {
7693 buffercmd_execute(t, &buffercmds[i]);
7694 goto done;
7697 /* find non exact matches to see if we need to abort ot not */
7698 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7699 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7700 c = -1;
7701 s[0] = '\0';
7702 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7703 if (isdigit(bcmd[0])) {
7704 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7705 continue;
7706 } else {
7707 c = 0;
7708 if (sscanf(bcmd, "%s", s) == 0)
7709 continue;
7711 } else if (buffercmds[i].precount == XT_PRE_YES) {
7712 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7713 continue;
7714 } else {
7715 if (sscanf(bcmd, "%s", s) == 0)
7716 continue;
7718 if (c == -1 && buffercmds[i].precount)
7719 continue;
7720 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7721 match++;
7723 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7724 i, match, buffercmds[i].cmd, c, s);
7726 if (match == 0) {
7727 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7728 buffercmd_abort(t);
7731 done:
7732 return (XT_CB_HANDLED);
7735 gboolean
7736 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7738 struct key_binding *k;
7740 /* handle keybindings if buffercmd is empty.
7741 if not empty, allow commands like C-n */
7742 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7743 TAILQ_FOREACH(k, &kbl, entry)
7744 if (e->keyval == k->key
7745 && (entry ? k->use_in_entry : 1)) {
7746 if (k->mask == 0) {
7747 if ((e->state & (CTRL | MOD1)) == 0)
7748 return (cmd_execute(t, k->cmd));
7749 } else if ((e->state & k->mask) == k->mask) {
7750 return (cmd_execute(t, k->cmd));
7754 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7755 return buffercmd_addkey(t, e->keyval);
7757 return (XT_CB_PASSTHROUGH);
7761 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7763 char s[2], buf[128];
7764 const char *errstr = NULL;
7766 /* don't use w directly; use t->whatever instead */
7768 if (t == NULL) {
7769 show_oops(NULL, "wv_keypress_after_cb");
7770 return (XT_CB_PASSTHROUGH);
7773 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7774 e->keyval, e->state, t);
7776 if (t->hints_on) {
7777 /* ESC */
7778 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7779 disable_hints(t);
7780 return (XT_CB_HANDLED);
7783 /* RETURN */
7784 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7785 if (errstr) {
7786 /* we have a string */
7787 } else {
7788 /* we have a number */
7789 snprintf(buf, sizeof buf,
7790 "vimprobable_fire(%s)", t->hint_num);
7791 run_script(t, buf);
7793 disable_hints(t);
7796 /* BACKSPACE */
7797 /* XXX unfuck this */
7798 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7799 if (t->hint_mode == XT_HINT_NUMERICAL) {
7800 /* last input was numerical */
7801 int l;
7802 l = strlen(t->hint_num);
7803 if (l > 0) {
7804 l--;
7805 if (l == 0) {
7806 disable_hints(t);
7807 enable_hints(t);
7808 } else {
7809 t->hint_num[l] = '\0';
7810 goto num;
7813 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7814 /* last input was alphanumerical */
7815 int l;
7816 l = strlen(t->hint_buf);
7817 if (l > 0) {
7818 l--;
7819 if (l == 0) {
7820 disable_hints(t);
7821 enable_hints(t);
7822 } else {
7823 t->hint_buf[l] = '\0';
7824 goto anum;
7827 } else {
7828 /* bogus */
7829 disable_hints(t);
7833 /* numerical input */
7834 if (CLEAN(e->state) == 0 &&
7835 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7836 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7837 snprintf(s, sizeof s, "%c", e->keyval);
7838 strlcat(t->hint_num, s, sizeof t->hint_num);
7839 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7840 t->hint_num);
7841 num:
7842 if (errstr) {
7843 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7844 "invalid link number\n");
7845 disable_hints(t);
7846 } else {
7847 snprintf(buf, sizeof buf,
7848 "vimprobable_update_hints(%s)",
7849 t->hint_num);
7850 t->hint_mode = XT_HINT_NUMERICAL;
7851 run_script(t, buf);
7854 /* empty the counter buffer */
7855 bzero(t->hint_buf, sizeof t->hint_buf);
7856 return (XT_CB_HANDLED);
7859 /* alphanumerical input */
7860 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7861 e->keyval <= GDK_z) ||
7862 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7863 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7864 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7865 e->keyval <= GDK_9) ||
7866 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7867 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7868 snprintf(s, sizeof s, "%c", e->keyval);
7869 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7870 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7871 " %s\n", t->hint_buf);
7872 anum:
7873 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7874 run_script(t, buf);
7876 snprintf(buf, sizeof buf,
7877 "vimprobable_show_hints('%s')", t->hint_buf);
7878 t->hint_mode = XT_HINT_ALPHANUM;
7879 run_script(t, buf);
7881 /* empty the counter buffer */
7882 bzero(t->hint_num, sizeof t->hint_num);
7883 return (XT_CB_HANDLED);
7886 return (XT_CB_HANDLED);
7887 } else {
7888 /* prefix input*/
7889 snprintf(s, sizeof s, "%c", e->keyval);
7890 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7891 cmd_prefix = 10 * cmd_prefix + atoi(s);
7894 return (handle_keypress(t, e, 0));
7898 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7900 hide_oops(t);
7902 /* Hide buffers, if they are visible, with escape. */
7903 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7904 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7905 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7906 hide_buffers(t);
7907 return (XT_CB_HANDLED);
7910 return (XT_CB_PASSTHROUGH);
7913 gboolean
7914 search_continue(struct tab *t)
7916 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7917 gboolean rv = FALSE;
7919 if (c[0] == ':')
7920 goto done;
7921 if (strlen(c) == 1) {
7922 webkit_web_view_unmark_text_matches(t->wv);
7923 goto done;
7926 if (c[0] == '/')
7927 t->search_forward = TRUE;
7928 else if (c[0] == '?')
7929 t->search_forward = FALSE;
7930 else
7931 goto done;
7933 rv = TRUE;
7934 done:
7935 return (rv);
7938 gboolean
7939 search_cb(struct tab *t)
7941 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7942 GdkColor color;
7944 if (search_continue(t) == FALSE)
7945 goto done;
7947 /* search */
7948 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7949 TRUE) == FALSE) {
7950 /* not found, mark red */
7951 gdk_color_parse(XT_COLOR_RED, &color);
7952 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7953 /* unmark and remove selection */
7954 webkit_web_view_unmark_text_matches(t->wv);
7955 /* my kingdom for a way to unselect text in webview */
7956 } else {
7957 /* found, highlight all */
7958 webkit_web_view_unmark_text_matches(t->wv);
7959 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7960 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7961 gdk_color_parse(XT_COLOR_WHITE, &color);
7962 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7964 done:
7965 t->search_id = 0;
7966 return (FALSE);
7970 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7972 const gchar *c = gtk_entry_get_text(w);
7974 if (t == NULL) {
7975 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7976 return (XT_CB_PASSTHROUGH);
7979 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7980 e->keyval, e->state, t);
7982 if (search_continue(t) == FALSE)
7983 goto done;
7985 /* if search length is > 4 then no longer play timeout games */
7986 if (strlen(c) > 4) {
7987 if (t->search_id) {
7988 g_source_remove(t->search_id);
7989 t->search_id = 0;
7991 search_cb(t);
7992 goto done;
7995 /* reestablish a new timer if the user types fast */
7996 if (t->search_id)
7997 g_source_remove(t->search_id);
7998 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
8000 done:
8001 return (XT_CB_PASSTHROUGH);
8004 gboolean
8005 match_uri(const gchar *uri, const gchar *key) {
8006 gchar *voffset;
8007 size_t len;
8008 gboolean match = FALSE;
8010 len = strlen(key);
8012 if (!strncmp(key, uri, len))
8013 match = TRUE;
8014 else {
8015 voffset = strstr(uri, "/") + 2;
8016 if (!strncmp(key, voffset, len))
8017 match = TRUE;
8018 else if (g_str_has_prefix(voffset, "www.")) {
8019 voffset = voffset + strlen("www.");
8020 if (!strncmp(key, voffset, len))
8021 match = TRUE;
8025 return (match);
8028 gboolean
8029 match_session(const gchar *name, const gchar *key) {
8030 char *sub;
8032 sub = strcasestr(name, key);
8034 return sub == name;
8037 void
8038 cmd_getlist(int id, char *key)
8040 int i, dep, c = 0;
8041 struct history *h;
8042 struct session *s;
8044 if (id >= 0) {
8045 if (cmds[id].type & XT_URLARG) {
8046 RB_FOREACH_REVERSE(h, history_list, &hl)
8047 if (match_uri(h->uri, key)) {
8048 cmd_status.list[c] = (char *)h->uri;
8049 if (++c > 255)
8050 break;
8052 cmd_status.len = c;
8053 return;
8054 } else if (cmds[id].type & XT_SESSARG) {
8055 TAILQ_FOREACH(s, &sessions, entry)
8056 if (match_session(s->name, key)) {
8057 cmd_status.list[c] = (char *)s->name;
8058 if (++c > 255)
8059 break;
8061 cmd_status.len = c;
8062 return;
8063 } else if (cmds[id].type & XT_SETARG) {
8064 for (i = 0; i < LENGTH(rs); i++)
8065 if(!strncmp(key, rs[i].name, strlen(key)))
8066 cmd_status.list[c++] = rs[i].name;
8067 cmd_status.len = c;
8068 return;
8072 dep = (id == -1) ? 0 : cmds[id].level + 1;
8074 for (i = id + 1; i < LENGTH(cmds); i++) {
8075 if (cmds[i].level < dep)
8076 break;
8077 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
8078 strlen(key)) && !isdigit(cmds[i].cmd[0]))
8079 cmd_status.list[c++] = cmds[i].cmd;
8083 cmd_status.len = c;
8086 char *
8087 cmd_getnext(int dir)
8089 cmd_status.index += dir;
8091 if (cmd_status.index < 0)
8092 cmd_status.index = cmd_status.len - 1;
8093 else if (cmd_status.index >= cmd_status.len)
8094 cmd_status.index = 0;
8096 return cmd_status.list[cmd_status.index];
8100 cmd_tokenize(char *s, char *tokens[])
8102 int i = 0;
8103 char *tok, *last = NULL;
8104 size_t len = strlen(s);
8105 bool blank;
8107 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8108 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8109 tok = strtok_r(NULL, " ", &last), i++)
8110 tokens[i] = tok;
8112 if (blank && i < 3)
8113 tokens[i++] = "";
8115 return (i);
8118 void
8119 cmd_complete(struct tab *t, char *str, int dir)
8121 GtkEntry *w = GTK_ENTRY(t->cmd);
8122 int i, j, levels, c = 0, dep = 0, parent = -1;
8123 int matchcount = 0;
8124 char *tok, *match, *s = g_strdup(str);
8125 char *tokens[3];
8126 char res[XT_MAX_URL_LENGTH + 32] = ":";
8127 char *sc = s;
8129 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8131 /* copy prefix*/
8132 for (i = 0; isdigit(s[i]); i++)
8133 res[i + 1] = s[i];
8135 for (; isspace(s[i]); i++)
8136 res[i + 1] = s[i];
8138 s += i;
8140 levels = cmd_tokenize(s, tokens);
8142 for (i = 0; i < levels - 1; i++) {
8143 tok = tokens[i];
8144 matchcount = 0;
8145 for (j = c; j < LENGTH(cmds); j++) {
8146 if (cmds[j].level < dep)
8147 break;
8148 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8149 strlen(tok))) {
8150 matchcount++;
8151 c = j + 1;
8152 if (strlen(tok) == strlen(cmds[j].cmd)) {
8153 matchcount = 1;
8154 break;
8159 if (matchcount == 1) {
8160 strlcat(res, tok, sizeof res);
8161 strlcat(res, " ", sizeof res);
8162 dep++;
8163 } else {
8164 g_free(sc);
8165 return;
8168 parent = c - 1;
8171 if (cmd_status.index == -1)
8172 cmd_getlist(parent, tokens[i]);
8174 if (cmd_status.len > 0) {
8175 match = cmd_getnext(dir);
8176 strlcat(res, match, sizeof res);
8177 gtk_entry_set_text(w, res);
8178 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8181 g_free(sc);
8184 gboolean
8185 cmd_execute(struct tab *t, char *str)
8187 struct cmd *cmd = NULL;
8188 char *tok, *last = NULL, *s = g_strdup(str), *sc;
8189 char prefixstr[4];
8190 int j, len, c = 0, dep = 0, matchcount = 0;
8191 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8192 struct karg arg = {0, NULL, -1};
8194 sc = s;
8196 /* copy prefix*/
8197 for (j = 0; j<3 && isdigit(s[j]); j++)
8198 prefixstr[j]=s[j];
8200 prefixstr[j]='\0';
8202 s += j;
8203 while (isspace(s[0]))
8204 s++;
8206 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8207 prefix = atoi(prefixstr);
8208 else
8209 s = sc;
8211 for (tok = strtok_r(s, " ", &last); tok;
8212 tok = strtok_r(NULL, " ", &last)) {
8213 matchcount = 0;
8214 for (j = c; j < LENGTH(cmds); j++) {
8215 if (cmds[j].level < dep)
8216 break;
8217 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8218 strlen(tok);
8219 if (cmds[j].level == dep &&
8220 !strncmp(tok, cmds[j].cmd, len)) {
8221 matchcount++;
8222 c = j + 1;
8223 cmd = &cmds[j];
8224 if (len == strlen(cmds[j].cmd)) {
8225 matchcount = 1;
8226 break;
8230 if (matchcount == 1) {
8231 if (cmd->type > 0)
8232 goto execute_cmd;
8233 dep++;
8234 } else {
8235 show_oops(t, "Invalid command: %s", str);
8236 goto done;
8239 execute_cmd:
8240 if (cmd == NULL) {
8241 show_oops(t, "Empty command");
8242 goto done;
8244 arg.i = cmd->arg;
8246 if (prefix != -1)
8247 arg.precount = prefix;
8248 else if (cmd_prefix > 0)
8249 arg.precount = cmd_prefix;
8251 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8252 show_oops(t, "No prefix allowed: %s", str);
8253 goto done;
8255 if (cmd->type > 1)
8256 arg.s = last ? g_strdup(last) : g_strdup("");
8257 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8258 if (arg.s == NULL) {
8259 show_oops(t, "Invalid command");
8260 goto done;
8262 arg.precount = atoi(arg.s);
8263 if (arg.precount <= 0) {
8264 if (arg.s[0] == '0')
8265 show_oops(t, "Zero count");
8266 else
8267 show_oops(t, "Trailing characters");
8268 goto done;
8272 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8273 __func__, arg.precount, arg.s);
8275 cmd->func(t, &arg);
8277 rv = XT_CB_HANDLED;
8278 done:
8279 if (j > 0)
8280 cmd_prefix = 0;
8281 g_free(sc);
8282 if (arg.s)
8283 g_free(arg.s);
8285 return (rv);
8289 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8291 if (t == NULL) {
8292 show_oops(NULL, "entry_key_cb invalid parameters");
8293 return (XT_CB_PASSTHROUGH);
8296 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8297 e->keyval, e->state, t);
8299 hide_oops(t);
8301 if (e->keyval == GDK_Escape) {
8302 /* don't use focus_webview(t) because we want to type :cmds */
8303 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8306 return (handle_keypress(t, e, 1));
8309 struct command_entry *
8310 history_prev(struct command_list *l, struct command_entry *at)
8312 if (at == NULL)
8313 at = TAILQ_LAST(l, command_list);
8314 else {
8315 at = TAILQ_PREV(at, command_list, entry);
8316 if (at == NULL)
8317 at = TAILQ_LAST(l, command_list);
8320 return (at);
8323 struct command_entry *
8324 history_next(struct command_list *l, struct command_entry *at)
8326 if (at == NULL)
8327 at = TAILQ_FIRST(l);
8328 else {
8329 at = TAILQ_NEXT(at, entry);
8330 if (at == NULL)
8331 at = TAILQ_FIRST(l);
8334 return (at);
8338 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8340 int rv = XT_CB_HANDLED;
8341 const gchar *c = gtk_entry_get_text(w);
8343 if (t == NULL) {
8344 show_oops(NULL, "cmd_keypress_cb parameters");
8345 return (XT_CB_PASSTHROUGH);
8348 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8349 e->keyval, e->state, t);
8351 /* sanity */
8352 if (c == NULL)
8353 e->keyval = GDK_Escape;
8354 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8355 e->keyval = GDK_Escape;
8357 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8358 e->keyval != GDK_ISO_Left_Tab)
8359 cmd_status.index = -1;
8361 switch (e->keyval) {
8362 case GDK_Tab:
8363 if (c[0] == ':')
8364 cmd_complete(t, (char *)&c[1], 1);
8365 goto done;
8366 case GDK_ISO_Left_Tab:
8367 if (c[0] == ':')
8368 cmd_complete(t, (char *)&c[1], -1);
8370 goto done;
8371 case GDK_Down:
8372 if (c[0] != ':') {
8373 if ((search_at = history_next(&shl, search_at))) {
8374 search_at->line[0] = c[0];
8375 gtk_entry_set_text(w, search_at->line);
8376 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8378 } else {
8379 if ((history_at = history_prev(&chl, history_at))) {
8380 history_at->line[0] = c[0];
8381 gtk_entry_set_text(w, history_at->line);
8382 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8386 goto done;
8387 case GDK_Up:
8388 if (c[0] != ':') {
8389 if ((search_at = history_next(&shl, search_at))) {
8390 search_at->line[0] = c[0];
8391 gtk_entry_set_text(w, search_at->line);
8392 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8394 } else {
8395 if ((history_at = history_next(&chl, history_at))) {
8396 history_at->line[0] = c[0];
8397 gtk_entry_set_text(w, history_at->line);
8398 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8402 goto done;
8403 case GDK_BackSpace:
8404 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8405 break;
8406 /* FALLTHROUGH */
8407 case GDK_Escape:
8408 hide_cmd(t);
8409 focus_webview(t);
8411 /* cancel search */
8412 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8413 webkit_web_view_unmark_text_matches(t->wv);
8414 goto done;
8417 rv = XT_CB_PASSTHROUGH;
8418 done:
8419 return (rv);
8422 void
8423 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8425 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8428 void
8429 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8431 /* popup menu enabled */
8432 t->popup = 1;
8436 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8438 if (t == NULL) {
8439 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8440 return (XT_CB_PASSTHROUGH);
8443 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8444 t->tab_id, t->popup);
8446 /* if popup is enabled don't lose focus */
8447 if (t->popup) {
8448 t->popup = 0;
8449 return (XT_CB_PASSTHROUGH);
8452 hide_cmd(t);
8453 hide_oops(t);
8455 if (show_url == 0 || t->focus_wv)
8456 focus_webview(t);
8457 else
8458 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8460 return (XT_CB_PASSTHROUGH);
8463 void
8464 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8466 char *s;
8467 const gchar *c = gtk_entry_get_text(entry);
8469 if (t == NULL) {
8470 show_oops(NULL, "cmd_activate_cb invalid parameters");
8471 return;
8474 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8476 hide_cmd(t);
8478 /* sanity */
8479 if (c == NULL)
8480 goto done;
8481 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8482 goto done;
8483 if (strlen(c) < 2)
8484 goto done;
8485 s = (char *)&c[1];
8487 if (c[0] == '/' || c[0] == '?') {
8488 /* see if there is a timer pending */
8489 if (t->search_id) {
8490 g_source_remove(t->search_id);
8491 t->search_id = 0;
8492 search_cb(t);
8495 if (t->search_text) {
8496 g_free(t->search_text);
8497 t->search_text = NULL;
8500 t->search_text = g_strdup(s);
8501 if (global_search)
8502 g_free(global_search);
8503 global_search = g_strdup(s);
8504 t->search_forward = c[0] == '/';
8506 history_add(&shl, search_file, s, &search_history_count);
8507 goto done;
8510 history_add(&chl, command_file, s, &cmd_history_count);
8511 cmd_execute(t, s);
8512 done:
8513 return;
8516 void
8517 backward_cb(GtkWidget *w, struct tab *t)
8519 struct karg a;
8521 if (t == NULL) {
8522 show_oops(NULL, "backward_cb invalid parameters");
8523 return;
8526 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8528 a.i = XT_NAV_BACK;
8529 navaction(t, &a);
8532 void
8533 forward_cb(GtkWidget *w, struct tab *t)
8535 struct karg a;
8537 if (t == NULL) {
8538 show_oops(NULL, "forward_cb invalid parameters");
8539 return;
8542 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8544 a.i = XT_NAV_FORWARD;
8545 navaction(t, &a);
8548 void
8549 home_cb(GtkWidget *w, struct tab *t)
8551 if (t == NULL) {
8552 show_oops(NULL, "home_cb invalid parameters");
8553 return;
8556 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8558 load_uri(t, home);
8561 void
8562 stop_cb(GtkWidget *w, struct tab *t)
8564 WebKitWebFrame *frame;
8566 if (t == NULL) {
8567 show_oops(NULL, "stop_cb invalid parameters");
8568 return;
8571 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8573 frame = webkit_web_view_get_main_frame(t->wv);
8574 if (frame == NULL) {
8575 show_oops(t, "stop_cb: no frame");
8576 return;
8579 webkit_web_frame_stop_loading(frame);
8580 abort_favicon_download(t);
8583 void
8584 setup_webkit(struct tab *t)
8586 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8587 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8588 FALSE, (char *)NULL);
8589 else
8590 warnx("webkit does not have \"enable-dns-prefetching\" property");
8591 g_object_set(G_OBJECT(t->settings),
8592 "user-agent", t->user_agent, (char *)NULL);
8593 g_object_set(G_OBJECT(t->settings),
8594 "enable-scripts", enable_scripts, (char *)NULL);
8595 g_object_set(G_OBJECT(t->settings),
8596 "enable-plugins", enable_plugins, (char *)NULL);
8597 g_object_set(G_OBJECT(t->settings),
8598 "javascript-can-open-windows-automatically", enable_scripts,
8599 (char *)NULL);
8600 g_object_set(G_OBJECT(t->settings),
8601 "enable-html5-database", FALSE, (char *)NULL);
8602 g_object_set(G_OBJECT(t->settings),
8603 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8604 g_object_set(G_OBJECT(t->settings),
8605 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8606 g_object_set(G_OBJECT(t->settings),
8607 "spell_checking_languages", spell_check_languages, (char *)NULL);
8608 g_object_set(G_OBJECT(t->wv),
8609 "full-content-zoom", TRUE, (char *)NULL);
8611 webkit_web_view_set_settings(t->wv, t->settings);
8614 gboolean
8615 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8617 struct tab *ti, *t = NULL;
8618 gdouble view_size, value, max;
8619 gchar *position;
8621 TAILQ_FOREACH(ti, &tabs, entry)
8622 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8623 t = ti;
8624 break;
8627 if (t == NULL)
8628 return FALSE;
8630 if (adjustment == NULL)
8631 adjustment = gtk_scrolled_window_get_vadjustment(
8632 GTK_SCROLLED_WINDOW(t->browser_win));
8634 view_size = gtk_adjustment_get_page_size(adjustment);
8635 value = gtk_adjustment_get_value(adjustment);
8636 max = gtk_adjustment_get_upper(adjustment) - view_size;
8638 if (max == 0)
8639 position = g_strdup("All");
8640 else if (value == max)
8641 position = g_strdup("Bot");
8642 else if (value == 0)
8643 position = g_strdup("Top");
8644 else
8645 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8647 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8648 g_free(position);
8650 return (TRUE);
8653 GtkWidget *
8654 create_browser(struct tab *t)
8656 GtkWidget *w;
8657 gchar *strval;
8658 GtkAdjustment *adjustment;
8660 if (t == NULL) {
8661 show_oops(NULL, "create_browser invalid parameters");
8662 return (NULL);
8665 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8666 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8667 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8668 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8670 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8671 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8672 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8674 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8675 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8677 /* set defaults */
8678 t->settings = webkit_web_settings_new();
8680 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
8682 if (user_agent == NULL) {
8683 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8684 (char *)NULL);
8685 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8686 g_free(strval);
8687 } else
8688 t->user_agent = g_strdup(user_agent);
8690 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8692 adjustment =
8693 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8694 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8695 G_CALLBACK(update_statusbar_position), NULL);
8697 setup_webkit(t);
8699 return (w);
8702 GtkWidget *
8703 create_window(void)
8705 GtkWidget *w;
8707 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8708 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8709 gtk_widget_set_name(w, "xxxterm");
8710 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8711 g_signal_connect(G_OBJECT(w), "delete_event",
8712 G_CALLBACK (gtk_main_quit), NULL);
8714 return (w);
8717 GtkWidget *
8718 create_kiosk_toolbar(struct tab *t)
8720 GtkWidget *toolbar = NULL, *b;
8722 b = gtk_hbox_new(FALSE, 0);
8723 toolbar = b;
8724 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8726 /* backward button */
8727 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8728 gtk_widget_set_sensitive(t->backward, FALSE);
8729 g_signal_connect(G_OBJECT(t->backward), "clicked",
8730 G_CALLBACK(backward_cb), t);
8731 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8733 /* forward button */
8734 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8735 gtk_widget_set_sensitive(t->forward, FALSE);
8736 g_signal_connect(G_OBJECT(t->forward), "clicked",
8737 G_CALLBACK(forward_cb), t);
8738 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8740 /* home button */
8741 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8742 gtk_widget_set_sensitive(t->gohome, true);
8743 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8744 G_CALLBACK(home_cb), t);
8745 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8747 /* create widgets but don't use them */
8748 t->uri_entry = gtk_entry_new();
8749 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8750 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8751 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8753 return (toolbar);
8756 GtkWidget *
8757 create_toolbar(struct tab *t)
8759 GtkWidget *toolbar = NULL, *b, *eb1;
8761 b = gtk_hbox_new(FALSE, 0);
8762 toolbar = b;
8763 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8765 /* backward button */
8766 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8767 gtk_widget_set_sensitive(t->backward, FALSE);
8768 g_signal_connect(G_OBJECT(t->backward), "clicked",
8769 G_CALLBACK(backward_cb), t);
8770 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8772 /* forward button */
8773 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8774 gtk_widget_set_sensitive(t->forward, FALSE);
8775 g_signal_connect(G_OBJECT(t->forward), "clicked",
8776 G_CALLBACK(forward_cb), t);
8777 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8778 FALSE, 0);
8780 /* stop button */
8781 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8782 gtk_widget_set_sensitive(t->stop, FALSE);
8783 g_signal_connect(G_OBJECT(t->stop), "clicked",
8784 G_CALLBACK(stop_cb), t);
8785 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8786 FALSE, 0);
8788 /* JS button */
8789 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8790 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8791 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8792 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8793 G_CALLBACK(js_toggle_cb), t);
8794 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8796 t->uri_entry = gtk_entry_new();
8797 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8798 G_CALLBACK(activate_uri_entry_cb), t);
8799 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8800 G_CALLBACK(entry_key_cb), t);
8801 completion_add(t);
8802 eb1 = gtk_hbox_new(FALSE, 0);
8803 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8804 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8805 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8807 /* search entry */
8808 if (search_string) {
8809 GtkWidget *eb2;
8810 t->search_entry = gtk_entry_new();
8811 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8812 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8813 G_CALLBACK(activate_search_entry_cb), t);
8814 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8815 G_CALLBACK(entry_key_cb), t);
8816 gtk_widget_set_size_request(t->search_entry, -1, -1);
8817 eb2 = gtk_hbox_new(FALSE, 0);
8818 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8819 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8821 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8824 return (toolbar);
8827 GtkWidget *
8828 create_buffers(struct tab *t)
8830 GtkCellRenderer *renderer;
8831 GtkWidget *view;
8833 view = gtk_tree_view_new();
8835 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8837 renderer = gtk_cell_renderer_text_new();
8838 gtk_tree_view_insert_column_with_attributes
8839 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8841 renderer = gtk_cell_renderer_text_new();
8842 gtk_tree_view_insert_column_with_attributes
8843 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8844 (char *)NULL);
8846 gtk_tree_view_set_model
8847 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8849 return view;
8852 void
8853 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8854 GtkTreeViewColumn *col, struct tab *t)
8856 GtkTreeIter iter;
8857 guint id;
8859 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8861 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8862 path)) {
8863 gtk_tree_model_get
8864 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8865 set_current_tab(id - 1);
8868 hide_buffers(t);
8871 /* after tab reordering/creation/removal */
8872 void
8873 recalc_tabs(void)
8875 struct tab *t;
8876 int maxid = 0;
8878 TAILQ_FOREACH(t, &tabs, entry) {
8879 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8880 if (t->tab_id > maxid)
8881 maxid = t->tab_id;
8883 gtk_widget_show(t->tab_elems.sep);
8886 TAILQ_FOREACH(t, &tabs, entry) {
8887 if (t->tab_id == maxid) {
8888 gtk_widget_hide(t->tab_elems.sep);
8889 break;
8894 /* after active tab change */
8895 void
8896 recolor_compact_tabs(void)
8898 struct tab *t;
8899 int curid = 0;
8900 GdkColor color;
8902 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8903 TAILQ_FOREACH(t, &tabs, entry)
8904 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8905 &color);
8907 curid = gtk_notebook_get_current_page(notebook);
8908 TAILQ_FOREACH(t, &tabs, entry)
8909 if (t->tab_id == curid) {
8910 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8911 gtk_widget_modify_fg(t->tab_elems.label,
8912 GTK_STATE_NORMAL, &color);
8913 break;
8917 void
8918 set_current_tab(int page_num)
8920 buffercmd_abort(get_current_tab());
8921 gtk_notebook_set_current_page(notebook, page_num);
8922 recolor_compact_tabs();
8926 undo_close_tab_save(struct tab *t)
8928 int m, n;
8929 const gchar *uri;
8930 struct undo *u1, *u2;
8931 GList *items;
8932 WebKitWebHistoryItem *item;
8934 if ((uri = get_uri(t)) == NULL)
8935 return (1);
8937 u1 = g_malloc0(sizeof(struct undo));
8938 u1->uri = g_strdup(uri);
8940 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8942 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8943 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8944 u1->back = n;
8946 /* forward history */
8947 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8949 while (items) {
8950 item = items->data;
8951 u1->history = g_list_prepend(u1->history,
8952 webkit_web_history_item_copy(item));
8953 items = g_list_next(items);
8956 /* current item */
8957 if (m) {
8958 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8959 u1->history = g_list_prepend(u1->history,
8960 webkit_web_history_item_copy(item));
8963 /* back history */
8964 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8966 while (items) {
8967 item = items->data;
8968 u1->history = g_list_prepend(u1->history,
8969 webkit_web_history_item_copy(item));
8970 items = g_list_next(items);
8973 TAILQ_INSERT_HEAD(&undos, u1, entry);
8975 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8976 u2 = TAILQ_LAST(&undos, undo_tailq);
8977 TAILQ_REMOVE(&undos, u2, entry);
8978 g_free(u2->uri);
8979 g_list_free(u2->history);
8980 g_free(u2);
8981 } else
8982 undo_count++;
8984 return (0);
8987 void
8988 delete_tab(struct tab *t)
8990 struct karg a;
8992 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8994 if (t == NULL)
8995 return;
8998 * no need to join thread here because it won't access t on completion
9001 buffercmd_abort(t);
9002 TAILQ_REMOVE(&tabs, t, entry);
9004 /* Halt all webkit activity. */
9005 abort_favicon_download(t);
9006 webkit_web_view_stop_loading(t->wv);
9008 /* Save the tab, so we can undo the close. */
9009 undo_close_tab_save(t);
9011 if (browser_mode == XT_BM_KIOSK) {
9012 gtk_widget_destroy(t->uri_entry);
9013 gtk_widget_destroy(t->stop);
9014 gtk_widget_destroy(t->js_toggle);
9017 gtk_widget_destroy(t->tab_elems.eventbox);
9018 gtk_widget_destroy(t->vbox);
9020 /* just in case */
9021 if (t->search_id)
9022 g_source_remove(t->search_id);
9024 g_free(t->user_agent);
9025 g_free(t->stylesheet);
9026 g_free(t->tmp_uri);
9027 g_free(t);
9029 if (TAILQ_EMPTY(&tabs)) {
9030 if (browser_mode == XT_BM_KIOSK)
9031 create_new_tab(home, NULL, 1, -1);
9032 else
9033 create_new_tab(NULL, NULL, 1, -1);
9036 /* recreate session */
9037 if (session_autosave) {
9038 a.s = NULL;
9039 save_tabs(t, &a);
9042 recalc_tabs();
9043 recolor_compact_tabs();
9046 void
9047 update_statusbar_zoom(struct tab *t)
9049 gfloat zoom;
9050 char s[16] = { '\0' };
9052 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9053 if ((zoom <= 0.99 || zoom >= 1.01))
9054 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
9055 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
9058 void
9059 setzoom_webkit(struct tab *t, int adjust)
9061 #define XT_ZOOMPERCENT 0.04
9063 gfloat zoom;
9065 if (t == NULL) {
9066 show_oops(NULL, "setzoom_webkit invalid parameters");
9067 return;
9070 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9071 if (adjust == XT_ZOOM_IN)
9072 zoom += XT_ZOOMPERCENT;
9073 else if (adjust == XT_ZOOM_OUT)
9074 zoom -= XT_ZOOMPERCENT;
9075 else if (adjust > 0)
9076 zoom = default_zoom_level + adjust / 100.0 - 1.0;
9077 else {
9078 show_oops(t, "setzoom_webkit invalid zoom value");
9079 return;
9082 if (zoom < XT_ZOOMPERCENT)
9083 zoom = XT_ZOOMPERCENT;
9084 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
9085 update_statusbar_zoom(t);
9088 gboolean
9089 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
9091 struct tab *t = (struct tab *) data;
9093 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
9095 switch (event->button) {
9096 case 1:
9097 set_current_tab(t->tab_id);
9098 break;
9099 case 2:
9100 delete_tab(t);
9101 break;
9104 return TRUE;
9107 void
9108 append_tab(struct tab *t)
9110 if (t == NULL)
9111 return;
9113 TAILQ_INSERT_TAIL(&tabs, t, entry);
9114 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9117 GtkWidget *
9118 create_sbe(int width)
9120 GtkWidget *sbe;
9122 sbe = gtk_entry_new();
9123 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9124 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9125 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9126 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9127 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9128 gtk_widget_set_size_request(sbe, width, -1);
9130 return sbe;
9133 struct tab *
9134 create_new_tab(char *title, struct undo *u, int focus, int position)
9136 struct tab *t;
9137 int load = 1, id;
9138 GtkWidget *b, *bb;
9139 WebKitWebHistoryItem *item;
9140 GList *items;
9141 GdkColor color;
9142 char *p;
9143 int sbe_p = 0, sbe_b = 0,
9144 sbe_z = 0;
9146 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9148 if (tabless && !TAILQ_EMPTY(&tabs)) {
9149 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9150 return (NULL);
9153 t = g_malloc0(sizeof *t);
9155 if (title == NULL) {
9156 title = "(untitled)";
9157 load = 0;
9160 t->vbox = gtk_vbox_new(FALSE, 0);
9162 /* label + button for tab */
9163 b = gtk_hbox_new(FALSE, 0);
9164 t->tab_content = b;
9166 #if GTK_CHECK_VERSION(2, 20, 0)
9167 t->spinner = gtk_spinner_new();
9168 #endif
9169 t->label = gtk_label_new(title);
9170 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9171 gtk_widget_set_size_request(t->label, 100, 0);
9172 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9173 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9174 gtk_widget_set_size_request(b, 130, 0);
9176 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9177 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9178 #if GTK_CHECK_VERSION(2, 20, 0)
9179 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9180 #endif
9182 /* toolbar */
9183 if (browser_mode == XT_BM_KIOSK) {
9184 t->toolbar = create_kiosk_toolbar(t);
9185 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9187 } else {
9188 t->toolbar = create_toolbar(t);
9189 if (fancy_bar)
9190 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9191 FALSE, 0);
9194 /* marks */
9195 marks_clear(t);
9197 /* browser */
9198 t->browser_win = create_browser(t);
9199 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9201 /* oops message for user feedback */
9202 t->oops = gtk_entry_new();
9203 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9204 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9205 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9206 gdk_color_parse(XT_COLOR_RED, &color);
9207 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9208 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9209 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9211 /* command entry */
9212 t->cmd = gtk_entry_new();
9213 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9214 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9215 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9216 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9218 /* status bar */
9219 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9221 t->sbe.statusbar = gtk_entry_new();
9222 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9223 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9224 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9225 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9227 /* create these widgets only if specified in statusbar_elems */
9229 t->sbe.position = create_sbe(40);
9230 t->sbe.zoom = create_sbe(40);
9231 t->sbe.buffercmd = create_sbe(60);
9233 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9235 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9236 TRUE, FALSE);
9238 /* gtk widgets cannot be added to a box twice. sbe_* variables
9239 make sure of this */
9240 for (p = statusbar_elems; *p != '\0'; p++) {
9241 switch (*p) {
9242 case '|':
9244 GtkWidget *sep = gtk_vseparator_new();
9246 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9247 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9248 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9249 FALSE, FALSE, FALSE);
9250 break;
9252 case 'P':
9253 if (sbe_p) {
9254 warnx("flag \"%c\" specified more than "
9255 "once in statusbar_elems\n", *p);
9256 break;
9258 sbe_p = 1;
9259 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9260 t->sbe.position, FALSE, FALSE, FALSE);
9261 break;
9262 case 'B':
9263 if (sbe_b) {
9264 warnx("flag \"%c\" specified more than "
9265 "once in statusbar_elems\n", *p);
9266 break;
9268 sbe_b = 1;
9269 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9270 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9271 break;
9272 case 'Z':
9273 if (sbe_z) {
9274 warnx("flag \"%c\" specified more than "
9275 "once in statusbar_elems\n", *p);
9276 break;
9278 sbe_z = 1;
9279 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9280 t->sbe.zoom, FALSE, FALSE, FALSE);
9281 break;
9282 default:
9283 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9284 break;
9288 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9290 /* buffer list */
9291 t->buffers = create_buffers(t);
9292 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9294 /* xtp meaning is normal by default */
9295 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9297 /* set empty favicon */
9298 xt_icon_from_name(t, "text-html");
9300 /* and show it all */
9301 gtk_widget_show_all(b);
9302 gtk_widget_show_all(t->vbox);
9304 /* compact tab bar */
9305 t->tab_elems.label = gtk_label_new(title);
9306 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9307 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9308 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9309 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9311 t->tab_elems.eventbox = gtk_event_box_new();
9312 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9313 t->tab_elems.sep = gtk_vseparator_new();
9315 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9316 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9317 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9318 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9319 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9320 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9322 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9323 TRUE, 0);
9324 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9325 FALSE, 0);
9326 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9327 t->tab_elems.box);
9329 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9330 TRUE, 0);
9331 gtk_widget_show_all(t->tab_elems.eventbox);
9333 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9334 append_tab(t);
9335 else {
9336 id = position >= 0 ? position :
9337 gtk_notebook_get_current_page(notebook) + 1;
9338 if (id > gtk_notebook_get_n_pages(notebook))
9339 append_tab(t);
9340 else {
9341 TAILQ_INSERT_TAIL(&tabs, t, entry);
9342 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9343 gtk_box_reorder_child(GTK_BOX(tab_bar),
9344 t->tab_elems.eventbox, id);
9345 recalc_tabs();
9349 #if GTK_CHECK_VERSION(2, 20, 0)
9350 /* turn spinner off if we are a new tab without uri */
9351 if (!load) {
9352 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9353 gtk_widget_hide(t->spinner);
9355 #endif
9356 /* make notebook tabs reorderable */
9357 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9359 /* compact tabs clickable */
9360 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9361 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9363 g_object_connect(G_OBJECT(t->cmd),
9364 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9365 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9366 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9367 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9368 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9369 (char *)NULL);
9371 /* reuse wv_button_cb to hide oops */
9372 g_object_connect(G_OBJECT(t->oops),
9373 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9374 (char *)NULL);
9376 g_signal_connect(t->buffers,
9377 "row-activated", G_CALLBACK(row_activated_cb), t);
9378 g_object_connect(G_OBJECT(t->buffers),
9379 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9381 g_object_connect(G_OBJECT(t->wv),
9382 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9383 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9384 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9385 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9386 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9387 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9388 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9389 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9390 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9391 "signal::event", G_CALLBACK(webview_event_cb), t,
9392 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9393 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9394 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9395 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9396 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9397 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9398 (char *)NULL);
9399 g_signal_connect(t->wv,
9400 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9401 g_signal_connect(t->wv,
9402 "notify::title", G_CALLBACK(notify_title_cb), t);
9404 /* hijack the unused keys as if we were the browser */
9405 g_object_connect(G_OBJECT(t->toolbar),
9406 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9407 (char *)NULL);
9409 g_signal_connect(G_OBJECT(bb), "button_press_event",
9410 G_CALLBACK(tab_close_cb), t);
9412 /* setup history */
9413 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9414 /* restore the tab's history */
9415 if (u && u->history) {
9416 items = u->history;
9417 while (items) {
9418 item = items->data;
9419 webkit_web_back_forward_list_add_item(t->bfl, item);
9420 items = g_list_next(items);
9423 item = g_list_nth_data(u->history, u->back);
9424 if (item)
9425 webkit_web_view_go_to_back_forward_item(t->wv, item);
9427 g_list_free(items);
9428 g_list_free(u->history);
9429 } else
9430 webkit_web_back_forward_list_clear(t->bfl);
9432 /* hide stuff */
9433 hide_cmd(t);
9434 hide_oops(t);
9435 hide_buffers(t);
9436 url_set_visibility();
9437 statusbar_set_visibility();
9439 if (focus) {
9440 set_current_tab(t->tab_id);
9441 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9442 t->tab_id);
9444 if (load) {
9445 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9446 load_uri(t, title);
9447 } else {
9448 if (show_url == 1)
9449 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9450 else
9451 focus_webview(t);
9453 } else if (load)
9454 load_uri(t, title);
9456 recolor_compact_tabs();
9457 setzoom_webkit(t, XT_ZOOM_NORMAL);
9458 return (t);
9461 void
9462 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9463 gpointer *udata)
9465 struct tab *t;
9466 const gchar *uri;
9468 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9470 if (gtk_notebook_get_current_page(notebook) == -1)
9471 recalc_tabs();
9473 TAILQ_FOREACH(t, &tabs, entry) {
9474 if (t->tab_id == pn) {
9475 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9476 "%d\n", pn);
9478 uri = get_title(t, TRUE);
9479 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9481 hide_cmd(t);
9482 hide_oops(t);
9484 if (t->focus_wv) {
9485 /* can't use focus_webview here */
9486 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9492 void
9493 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9494 gpointer *udata)
9496 struct tab *t = NULL, *tt;
9498 recalc_tabs();
9500 TAILQ_FOREACH(tt, &tabs, entry)
9501 if (tt->tab_id == pn) {
9502 t = tt;
9503 break;
9505 if (t == NULL)
9506 return;
9507 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9509 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9510 t->tab_id);
9513 void
9514 menuitem_response(struct tab *t)
9516 gtk_notebook_set_current_page(notebook, t->tab_id);
9519 gboolean
9520 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9522 GtkWidget *menu, *menu_items;
9523 GdkEventButton *bevent;
9524 const gchar *uri;
9525 struct tab *ti;
9527 if (event->type == GDK_BUTTON_PRESS) {
9528 bevent = (GdkEventButton *) event;
9529 menu = gtk_menu_new();
9531 TAILQ_FOREACH(ti, &tabs, entry) {
9532 if ((uri = get_uri(ti)) == NULL)
9533 /* XXX make sure there is something to print */
9534 /* XXX add gui pages in here to look purdy */
9535 uri = "(untitled)";
9536 menu_items = gtk_menu_item_new_with_label(uri);
9537 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9538 gtk_widget_show(menu_items);
9540 g_signal_connect_swapped((menu_items),
9541 "activate", G_CALLBACK(menuitem_response),
9542 (gpointer)ti);
9545 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9546 bevent->button, bevent->time);
9548 /* unref object so it'll free itself when popped down */
9549 #if !GTK_CHECK_VERSION(3, 0, 0)
9550 /* XXX does not need unref with gtk+3? */
9551 g_object_ref_sink(menu);
9552 g_object_unref(menu);
9553 #endif
9555 return (TRUE /* eat event */);
9558 return (FALSE /* propagate */);
9562 icon_size_map(int icon_size)
9564 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9565 icon_size > GTK_ICON_SIZE_DIALOG)
9566 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9568 return (icon_size);
9571 GtkWidget *
9572 create_button(char *name, char *stockid, int size)
9574 GtkWidget *button, *image;
9575 gchar *rcstring;
9576 int gtk_icon_size;
9578 rcstring = g_strdup_printf(
9579 "style \"%s-style\"\n"
9580 "{\n"
9581 " GtkWidget::focus-padding = 0\n"
9582 " GtkWidget::focus-line-width = 0\n"
9583 " xthickness = 0\n"
9584 " ythickness = 0\n"
9585 "}\n"
9586 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9587 gtk_rc_parse_string(rcstring);
9588 g_free(rcstring);
9589 button = gtk_button_new();
9590 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9591 gtk_icon_size = icon_size_map(size ? size : icon_size);
9593 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9594 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9595 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9596 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9597 gtk_widget_set_name(button, name);
9598 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9600 return (button);
9603 void
9604 button_set_stockid(GtkWidget *button, char *stockid)
9606 GtkWidget *image;
9608 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9609 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9610 gtk_button_set_image(GTK_BUTTON(button), image);
9613 void
9614 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9616 gchar *p = NULL;
9617 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9618 gint len;
9620 if (xterm_workaround == 0)
9621 return;
9624 * xterm doesn't play nice with clipboards because it clears the
9625 * primary when clicked. We rely on primary being set to properly
9626 * handle middle mouse button clicks (paste). So when someone clears
9627 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9628 * other application behavior (as in DON'T clear primary).
9631 p = gtk_clipboard_wait_for_text(primary);
9632 if (p == NULL) {
9633 if (gdk_property_get(gdk_get_default_root_window(),
9634 atom,
9635 gdk_atom_intern("STRING", FALSE),
9637 1024 * 1024 /* picked out of my butt */,
9638 FALSE,
9639 NULL,
9640 NULL,
9641 &len,
9642 (guchar **)&p)) {
9643 /* yes sir, we need to NUL the string */
9644 p[len] = '\0';
9645 gtk_clipboard_set_text(primary, p, -1);
9649 if (p)
9650 g_free(p);
9653 void
9654 create_canvas(void)
9656 GtkWidget *vbox;
9657 GList *l = NULL;
9658 GdkPixbuf *pb;
9659 char file[PATH_MAX];
9660 int i;
9662 vbox = gtk_vbox_new(FALSE, 0);
9663 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9664 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9665 #if !GTK_CHECK_VERSION(3, 0, 0)
9666 /* XXX seems to be needed with gtk+2 */
9667 gtk_notebook_set_tab_hborder(notebook, 0);
9668 gtk_notebook_set_tab_vborder(notebook, 0);
9669 #endif
9670 gtk_notebook_set_scrollable(notebook, TRUE);
9671 gtk_notebook_set_show_border(notebook, FALSE);
9672 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9674 abtn = gtk_button_new();
9675 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9676 gtk_widget_set_size_request(arrow, -1, -1);
9677 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9678 gtk_widget_set_size_request(abtn, -1, 20);
9680 #if GTK_CHECK_VERSION(2, 20, 0)
9681 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9682 #endif
9683 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9685 /* compact tab bar */
9686 tab_bar = gtk_hbox_new(TRUE, 0);
9688 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9689 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9690 gtk_widget_set_size_request(vbox, -1, -1);
9692 g_object_connect(G_OBJECT(notebook),
9693 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9694 (char *)NULL);
9695 g_object_connect(G_OBJECT(notebook),
9696 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9697 NULL, (char *)NULL);
9698 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9699 G_CALLBACK(arrow_cb), NULL);
9701 main_window = create_window();
9702 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9704 /* icons */
9705 for (i = 0; i < LENGTH(icons); i++) {
9706 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9707 pb = gdk_pixbuf_new_from_file(file, NULL);
9708 l = g_list_append(l, pb);
9710 gtk_window_set_default_icon_list(l);
9712 /* clipboard work around */
9713 if (xterm_workaround)
9714 g_signal_connect(
9715 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9716 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9718 gtk_widget_show_all(abtn);
9719 gtk_widget_show_all(main_window);
9720 notebook_tab_set_visibility();
9723 void
9724 set_hook(void **hook, char *name)
9726 if (hook == NULL)
9727 errx(1, "set_hook");
9729 if (*hook == NULL) {
9730 *hook = dlsym(RTLD_NEXT, name);
9731 if (*hook == NULL)
9732 errx(1, "can't hook %s", name);
9736 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9737 gboolean
9738 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9740 g_return_val_if_fail(cookie1, FALSE);
9741 g_return_val_if_fail(cookie2, FALSE);
9743 return (!strcmp (cookie1->name, cookie2->name) &&
9744 !strcmp (cookie1->value, cookie2->value) &&
9745 !strcmp (cookie1->path, cookie2->path) &&
9746 !strcmp (cookie1->domain, cookie2->domain));
9749 void
9750 transfer_cookies(void)
9752 GSList *cf;
9753 SoupCookie *sc, *pc;
9755 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9757 for (;cf; cf = cf->next) {
9758 pc = cf->data;
9759 sc = soup_cookie_copy(pc);
9760 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9763 soup_cookies_free(cf);
9766 void
9767 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9769 GSList *cf;
9770 SoupCookie *ci;
9772 print_cookie("soup_cookie_jar_delete_cookie", c);
9774 if (cookies_enabled == 0)
9775 return;
9777 if (jar == NULL || c == NULL)
9778 return;
9780 /* find and remove from persistent jar */
9781 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9783 for (;cf; cf = cf->next) {
9784 ci = cf->data;
9785 if (soup_cookie_equal(ci, c)) {
9786 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9787 break;
9791 soup_cookies_free(cf);
9793 /* delete from session jar */
9794 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9797 void
9798 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9800 struct domain *d = NULL;
9801 SoupCookie *c;
9802 FILE *r_cookie_f;
9804 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9805 jar, p_cookiejar, s_cookiejar);
9807 if (cookies_enabled == 0)
9808 return;
9810 /* see if we are up and running */
9811 if (p_cookiejar == NULL) {
9812 _soup_cookie_jar_add_cookie(jar, cookie);
9813 return;
9815 /* disallow p_cookiejar adds, shouldn't happen */
9816 if (jar == p_cookiejar)
9817 return;
9819 /* sanity */
9820 if (jar == NULL || cookie == NULL)
9821 return;
9823 if (enable_cookie_whitelist &&
9824 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9825 blocked_cookies++;
9826 DNPRINTF(XT_D_COOKIE,
9827 "soup_cookie_jar_add_cookie: reject %s\n",
9828 cookie->domain);
9829 if (save_rejected_cookies) {
9830 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9831 show_oops(NULL, "can't open reject cookie file");
9832 return;
9834 fseek(r_cookie_f, 0, SEEK_END);
9835 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9836 cookie->http_only ? "#HttpOnly_" : "",
9837 cookie->domain,
9838 *cookie->domain == '.' ? "TRUE" : "FALSE",
9839 cookie->path,
9840 cookie->secure ? "TRUE" : "FALSE",
9841 cookie->expires ?
9842 (gulong)soup_date_to_time_t(cookie->expires) :
9844 cookie->name,
9845 cookie->value);
9846 fflush(r_cookie_f);
9847 fclose(r_cookie_f);
9849 if (!allow_volatile_cookies)
9850 return;
9853 if (cookie->expires == NULL && session_timeout) {
9854 soup_cookie_set_expires(cookie,
9855 soup_date_new_from_now(session_timeout));
9856 print_cookie("modified add cookie", cookie);
9859 /* see if we are white listed for persistence */
9860 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9861 /* add to persistent jar */
9862 c = soup_cookie_copy(cookie);
9863 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9864 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9867 /* add to session jar */
9868 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9869 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9872 void
9873 setup_cookies(void)
9875 char file[PATH_MAX];
9877 set_hook((void *)&_soup_cookie_jar_add_cookie,
9878 "soup_cookie_jar_add_cookie");
9879 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9880 "soup_cookie_jar_delete_cookie");
9882 if (cookies_enabled == 0)
9883 return;
9886 * the following code is intricate due to overriding several libsoup
9887 * functions.
9888 * do not alter order of these operations.
9891 /* rejected cookies */
9892 if (save_rejected_cookies)
9893 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9894 XT_REJECT_FILE);
9896 /* persistent cookies */
9897 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9898 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9900 /* session cookies */
9901 s_cookiejar = soup_cookie_jar_new();
9902 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9903 cookie_policy, (void *)NULL);
9904 transfer_cookies();
9906 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9909 void
9910 setup_proxy(char *uri)
9912 if (proxy_uri) {
9913 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9914 soup_uri_free(proxy_uri);
9915 proxy_uri = NULL;
9917 if (http_proxy) {
9918 if (http_proxy != uri) {
9919 g_free(http_proxy);
9920 http_proxy = NULL;
9924 if (uri) {
9925 http_proxy = g_strdup(uri);
9926 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9927 proxy_uri = soup_uri_new(http_proxy);
9928 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9929 g_object_set(session, "proxy-uri", proxy_uri,
9930 (char *)NULL);
9935 set_http_proxy(char *proxy)
9937 SoupURI *uri;
9939 if (proxy == NULL)
9940 return (1);
9942 /* see if we need to clear it instead */
9943 if (strlen(proxy) == 0) {
9944 setup_proxy(NULL);
9945 return (0);
9948 uri = soup_uri_new(proxy);
9949 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9950 return (1);
9952 setup_proxy(proxy);
9954 soup_uri_free(uri);
9956 return (0);
9960 send_cmd_to_socket(char *cmd)
9962 int s, len, rv = 1;
9963 struct sockaddr_un sa;
9965 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9966 warnx("%s: socket", __func__);
9967 return (rv);
9970 sa.sun_family = AF_UNIX;
9971 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9972 work_dir, XT_SOCKET_FILE);
9973 len = SUN_LEN(&sa);
9975 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9976 warnx("%s: connect", __func__);
9977 goto done;
9980 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9981 warnx("%s: send", __func__);
9982 goto done;
9985 rv = 0;
9986 done:
9987 close(s);
9988 return (rv);
9991 gboolean
9992 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9994 int s, n;
9995 char str[XT_MAX_URL_LENGTH];
9996 socklen_t t = sizeof(struct sockaddr_un);
9997 struct sockaddr_un sa;
9998 struct passwd *p;
9999 uid_t uid;
10000 gid_t gid;
10001 struct tab *tt;
10002 gint fd = g_io_channel_unix_get_fd(source);
10004 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
10005 warn("accept");
10006 return (FALSE);
10009 if (getpeereid(s, &uid, &gid) == -1) {
10010 warn("getpeereid");
10011 return (FALSE);
10013 if (uid != getuid() || gid != getgid()) {
10014 warnx("unauthorized user");
10015 return (FALSE);
10018 p = getpwuid(uid);
10019 if (p == NULL) {
10020 warnx("not a valid user");
10021 return (FALSE);
10024 n = recv(s, str, sizeof(str), 0);
10025 if (n <= 0)
10026 return (TRUE);
10028 tt = TAILQ_LAST(&tabs, tab_list);
10029 cmd_execute(tt, str);
10030 return (TRUE);
10034 is_running(void)
10036 int s, len, rv = 1;
10037 struct sockaddr_un sa;
10039 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10040 warn("is_running: socket");
10041 return (-1);
10044 sa.sun_family = AF_UNIX;
10045 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10046 work_dir, XT_SOCKET_FILE);
10047 len = SUN_LEN(&sa);
10049 /* connect to see if there is a listener */
10050 if (connect(s, (struct sockaddr *)&sa, len) == -1)
10051 rv = 0; /* not running */
10052 else
10053 rv = 1; /* already running */
10055 close(s);
10057 return (rv);
10061 build_socket(void)
10063 int s, len;
10064 struct sockaddr_un sa;
10066 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10067 warn("build_socket: socket");
10068 return (-1);
10071 sa.sun_family = AF_UNIX;
10072 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10073 work_dir, XT_SOCKET_FILE);
10074 len = SUN_LEN(&sa);
10076 /* connect to see if there is a listener */
10077 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10078 /* no listener so we will */
10079 unlink(sa.sun_path);
10081 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
10082 warn("build_socket: bind");
10083 goto done;
10086 if (listen(s, 1) == -1) {
10087 warn("build_socket: listen");
10088 goto done;
10091 return (s);
10094 done:
10095 close(s);
10096 return (-1);
10099 gboolean
10100 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10101 GtkTreeIter *iter, struct tab *t)
10103 gchar *value;
10105 gtk_tree_model_get(model, iter, 0, &value, -1);
10106 load_uri(t, value);
10107 g_free(value);
10109 return (FALSE);
10112 gboolean
10113 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10114 GtkTreeIter *iter, struct tab *t)
10116 gchar *value;
10118 gtk_tree_model_get(model, iter, 0, &value, -1);
10119 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10120 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10121 g_free(value);
10123 return (TRUE);
10126 void
10127 completion_add_uri(const gchar *uri)
10129 GtkTreeIter iter;
10131 /* add uri to list_store */
10132 gtk_list_store_append(completion_model, &iter);
10133 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10136 gboolean
10137 completion_match(GtkEntryCompletion *completion, const gchar *key,
10138 GtkTreeIter *iter, gpointer user_data)
10140 gchar *value;
10141 gboolean match = FALSE;
10143 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10144 -1);
10146 if (value == NULL)
10147 return FALSE;
10149 match = match_uri(value, key);
10151 g_free(value);
10152 return (match);
10155 void
10156 completion_add(struct tab *t)
10158 /* enable completion for tab */
10159 t->completion = gtk_entry_completion_new();
10160 gtk_entry_completion_set_text_column(t->completion, 0);
10161 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10162 gtk_entry_completion_set_model(t->completion,
10163 GTK_TREE_MODEL(completion_model));
10164 gtk_entry_completion_set_match_func(t->completion, completion_match,
10165 NULL, NULL);
10166 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10167 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10168 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10169 G_CALLBACK(completion_select_cb), t);
10170 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10171 G_CALLBACK(completion_hover_cb), t);
10174 void
10175 xxx_dir(char *dir)
10177 struct stat sb;
10179 if (stat(dir, &sb)) {
10180 if (mkdir(dir, S_IRWXU) == -1)
10181 err(1, "mkdir %s", dir);
10182 if (stat(dir, &sb))
10183 err(1, "stat %s", dir);
10185 if (S_ISDIR(sb.st_mode) == 0)
10186 errx(1, "%s not a dir", dir);
10187 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10188 warnx("fixing invalid permissions on %s", dir);
10189 if (chmod(dir, S_IRWXU) == -1)
10190 err(1, "chmod %s", dir);
10194 void
10195 usage(void)
10197 fprintf(stderr,
10198 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10199 exit(0);
10202 GStaticRecMutex my_gdk_mtx = G_STATIC_REC_MUTEX_INIT;
10203 volatile int mtx_depth;
10204 int mtx_complain;
10207 * The linux flash plugin violates the gdk locking mechanism.
10208 * Work around the issue by using a recursive mutex with some match applied
10209 * to see if we hit a buggy condition.
10211 * The following code is painful so just don't read it. It really doesn't
10212 * make much sense but seems to work.
10214 void
10215 mtx_lock(void)
10217 g_static_rec_mutex_lock(&my_gdk_mtx);
10218 mtx_depth++;
10220 if (mtx_depth <= 0) {
10221 /* should not happen */
10222 show_oops(NULL, "negative mutex locking bug, trying to "
10223 "correct");
10224 fprintf(stderr, "negative mutex locking bug, trying to "
10225 "correct\n");
10226 g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10227 g_static_rec_mutex_lock(&my_gdk_mtx);
10228 mtx_depth = 1;
10229 return;
10232 if (mtx_depth != 1) {
10233 /* decrease mutext depth to 1 */
10234 do {
10235 g_static_rec_mutex_unlock(&my_gdk_mtx);
10236 mtx_depth--;
10237 } while (mtx_depth > 1);
10241 void
10242 mtx_unlock(void)
10244 guint x;
10246 /* if mutex depth isn't 1 then something went bad */
10247 if (mtx_depth != 1) {
10248 x = g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10249 if (x != 1) {
10250 /* should not happen */
10251 show_oops(NULL, "mutex unlocking bug, trying to "
10252 "correct");
10253 fprintf(stderr, "mutex unlocking bug, trying to "
10254 "correct\n");
10256 mtx_depth = 0;
10257 if (mtx_complain == 0) {
10258 show_oops(NULL, "buggy mutex implementation detected, "
10259 "work around implemented");
10260 fprintf(stderr, "buggy mutex implementation detected, "
10261 "work around implemented");
10262 mtx_complain = 1;
10264 return;
10267 mtx_depth--;
10268 g_static_rec_mutex_unlock(&my_gdk_mtx);
10272 main(int argc, char *argv[])
10274 struct stat sb;
10275 int c, s, optn = 0, opte = 0, focus = 1;
10276 char conf[PATH_MAX] = { '\0' };
10277 char file[PATH_MAX];
10278 char *env_proxy = NULL;
10279 char *cmd = NULL;
10280 FILE *f = NULL;
10281 struct karg a;
10282 struct sigaction sact;
10283 GIOChannel *channel;
10284 struct rlimit rlp;
10286 start_argv = argv;
10288 /* prepare gtk */
10289 #ifdef USE_THREADS
10290 g_thread_init(NULL);
10291 gdk_threads_set_lock_functions(mtx_lock, mtx_unlock);
10292 gdk_threads_init();
10293 gdk_threads_enter();
10295 gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
10296 #endif
10297 gtk_init(&argc, &argv);
10299 gnutls_global_init();
10301 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10303 RB_INIT(&hl);
10304 RB_INIT(&js_wl);
10305 RB_INIT(&downloads);
10307 TAILQ_INIT(&sessions);
10308 TAILQ_INIT(&tabs);
10309 TAILQ_INIT(&mtl);
10310 TAILQ_INIT(&aliases);
10311 TAILQ_INIT(&undos);
10312 TAILQ_INIT(&kbl);
10313 TAILQ_INIT(&spl);
10314 TAILQ_INIT(&chl);
10315 TAILQ_INIT(&shl);
10317 /* fiddle with ulimits */
10318 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10319 warn("getrlimit");
10320 else {
10321 /* just use them all */
10322 rlp.rlim_cur = rlp.rlim_max;
10323 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10324 warn("setrlimit");
10325 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10326 warn("getrlimit");
10327 else if (rlp.rlim_cur <= 256)
10328 startpage_add("%s requires at least 256 file "
10329 "descriptors, currently it has up to %d available",
10330 __progname, rlp.rlim_cur);
10333 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10334 switch (c) {
10335 case 'S':
10336 show_url = 0;
10337 break;
10338 case 'T':
10339 show_tabs = 0;
10340 break;
10341 case 'V':
10342 errx(0 , "Version: %s", version);
10343 break;
10344 case 'f':
10345 strlcpy(conf, optarg, sizeof(conf));
10346 break;
10347 case 's':
10348 strlcpy(named_session, optarg, sizeof(named_session));
10349 break;
10350 case 't':
10351 tabless = 1;
10352 break;
10353 case 'n':
10354 optn = 1;
10355 break;
10356 case 'e':
10357 opte = 1;
10358 break;
10359 default:
10360 usage();
10361 /* NOTREACHED */
10364 argc -= optind;
10365 argv += optind;
10367 init_keybindings();
10369 /* generate session keys for xtp pages */
10370 generate_xtp_session_key(&dl_session_key);
10371 generate_xtp_session_key(&hl_session_key);
10372 generate_xtp_session_key(&cl_session_key);
10373 generate_xtp_session_key(&fl_session_key);
10375 /* signals */
10376 bzero(&sact, sizeof(sact));
10377 sigemptyset(&sact.sa_mask);
10378 sact.sa_handler = sigchild;
10379 sact.sa_flags = SA_NOCLDSTOP;
10380 sigaction(SIGCHLD, &sact, NULL);
10382 /* set download dir */
10383 pwd = getpwuid(getuid());
10384 if (pwd == NULL)
10385 errx(1, "invalid user %d", getuid());
10386 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10388 /* compile buffer command regexes */
10389 buffercmd_init();
10391 /* set default string settings */
10392 home = g_strdup("https://www.cyphertite.com");
10393 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10394 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10395 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10396 cmd_font_name = g_strdup("monospace normal 9");
10397 oops_font_name = g_strdup("monospace normal 9");
10398 statusbar_font_name = g_strdup("monospace normal 9");
10399 tabbar_font_name = g_strdup("monospace normal 9");
10400 statusbar_elems = g_strdup("BP");
10401 encoding = g_strdup("ISO-8859-1");
10403 /* read config file */
10404 if (strlen(conf) == 0)
10405 snprintf(conf, sizeof conf, "%s/.%s",
10406 pwd->pw_dir, XT_CONF_FILE);
10407 config_parse(conf, 0);
10409 /* init fonts */
10410 cmd_font = pango_font_description_from_string(cmd_font_name);
10411 oops_font = pango_font_description_from_string(oops_font_name);
10412 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10413 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10415 /* working directory */
10416 if (strlen(work_dir) == 0)
10417 snprintf(work_dir, sizeof work_dir, "%s/%s",
10418 pwd->pw_dir, XT_DIR);
10419 xxx_dir(work_dir);
10421 /* icon cache dir */
10422 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10423 xxx_dir(cache_dir);
10425 /* certs dir */
10426 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10427 xxx_dir(certs_dir);
10429 /* sessions dir */
10430 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10431 work_dir, XT_SESSIONS_DIR);
10432 xxx_dir(sessions_dir);
10434 /* runtime settings that can override config file */
10435 if (runtime_settings[0] != '\0')
10436 config_parse(runtime_settings, 1);
10438 /* download dir */
10439 if (!strcmp(download_dir, pwd->pw_dir))
10440 strlcat(download_dir, "/downloads", sizeof download_dir);
10441 xxx_dir(download_dir);
10443 /* favorites file */
10444 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10445 if (stat(file, &sb)) {
10446 warnx("favorites file doesn't exist, creating it");
10447 if ((f = fopen(file, "w")) == NULL)
10448 err(1, "favorites");
10449 fclose(f);
10452 /* quickmarks file */
10453 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10454 if (stat(file, &sb)) {
10455 warnx("quickmarks file doesn't exist, creating it");
10456 if ((f = fopen(file, "w")) == NULL)
10457 err(1, "quickmarks");
10458 fclose(f);
10461 /* search history */
10462 if (history_autosave) {
10463 snprintf(search_file, sizeof search_file, "%s/%s",
10464 work_dir, XT_SEARCH_FILE);
10465 if (stat(search_file, &sb)) {
10466 warnx("search history file doesn't exist, creating it");
10467 if ((f = fopen(search_file, "w")) == NULL)
10468 err(1, "search_history");
10469 fclose(f);
10471 history_read(&shl, search_file, &search_history_count);
10474 /* command history */
10475 if (history_autosave) {
10476 snprintf(command_file, sizeof command_file, "%s/%s",
10477 work_dir, XT_COMMAND_FILE);
10478 if (stat(command_file, &sb)) {
10479 warnx("command history file doesn't exist, creating it");
10480 if ((f = fopen(command_file, "w")) == NULL)
10481 err(1, "command_history");
10482 fclose(f);
10484 history_read(&chl, command_file, &cmd_history_count);
10487 /* cookies */
10488 session = webkit_get_default_session();
10489 setup_cookies();
10491 /* certs */
10492 if (ssl_ca_file) {
10493 if (stat(ssl_ca_file, &sb)) {
10494 warnx("no CA file: %s", ssl_ca_file);
10495 g_free(ssl_ca_file);
10496 ssl_ca_file = NULL;
10497 } else
10498 g_object_set(session,
10499 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10500 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10501 (void *)NULL);
10504 /* guess_search regex */
10505 if (url_regex == NULL)
10506 url_regex = g_strdup(XT_URL_REGEX);
10507 if (url_regex)
10508 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10509 startpage_add("invalid url regex %s", url_regex);
10511 /* proxy */
10512 env_proxy = getenv("http_proxy");
10513 if (env_proxy)
10514 setup_proxy(env_proxy);
10515 else
10516 setup_proxy(http_proxy);
10518 if (opte) {
10519 send_cmd_to_socket(argv[0]);
10520 exit(0);
10523 /* set some connection parameters */
10524 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10525 g_object_set(session, "max-conns-per-host", max_host_connections,
10526 (char *)NULL);
10528 /* see if there is already an xxxterm running */
10529 if (single_instance && is_running()) {
10530 optn = 1;
10531 warnx("already running");
10534 if (optn) {
10535 while (argc) {
10536 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10537 send_cmd_to_socket(cmd);
10538 if (cmd)
10539 g_free(cmd);
10541 argc--;
10542 argv++;
10544 exit(0);
10547 /* uri completion */
10548 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10550 /* buffers */
10551 buffers_store = gtk_list_store_new
10552 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10554 qmarks_load();
10556 /* go graphical */
10557 create_canvas();
10558 notebook_tab_set_visibility();
10560 if (save_global_history)
10561 restore_global_history();
10563 /* restore session list */
10564 restore_sessions_list();
10566 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10567 restore_saved_tabs();
10568 else {
10569 a.s = named_session;
10570 a.i = XT_SES_DONOTHING;
10571 open_tabs(NULL, &a);
10574 /* see if we have an exception */
10575 if (!TAILQ_EMPTY(&spl)) {
10576 create_new_tab("about:startpage", NULL, focus, -1);
10577 focus = 0;
10580 while (argc) {
10581 create_new_tab(argv[0], NULL, focus, -1);
10582 focus = 0;
10584 argc--;
10585 argv++;
10588 if (TAILQ_EMPTY(&tabs))
10589 create_new_tab(home, NULL, 1, -1);
10591 if (enable_socket)
10592 if ((s = build_socket()) != -1) {
10593 channel = g_io_channel_unix_new(s);
10594 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10597 gtk_main();
10599 #ifdef USE_THREADS
10600 gdk_threads_leave();
10601 g_static_rec_mutex_unlock_full(&my_gdk_mtx); /* just in case */
10602 #endif
10604 gnutls_global_deinit();
10606 if (url_regex)
10607 regfree(&url_re);
10609 return (0);