Switch to gtk3 by default
[xxxterm.git] / xxxterm.c
blobe9955fb721ccdf5a91732ec30f30acfa4707940b
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 <gnutls/gnutls.h>
78 #include <JavaScriptCore/JavaScript.h>
79 #include <gnutls/x509.h>
81 #include "version.h"
82 #include "javascript.h"
85 javascript.h borrowed from vimprobable2 under the following license:
87 Copyright (c) 2009 Leon Winter
88 Copyright (c) 2009 Hannes Schueller
89 Copyright (c) 2009 Matto Fransen
91 Permission is hereby granted, free of charge, to any person obtaining a copy
92 of this software and associated documentation files (the "Software"), to deal
93 in the Software without restriction, including without limitation the rights
94 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
95 copies of the Software, and to permit persons to whom the Software is
96 furnished to do so, subject to the following conditions:
98 The above copyright notice and this permission notice shall be included in
99 all copies or substantial portions of the Software.
101 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
102 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
103 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
104 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
105 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
106 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
107 THE SOFTWARE.
110 static char *version = XXXTERM_VERSION;
112 /* hooked functions */
113 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
114 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
115 SoupCookie *);
117 /*#define XT_DEBUG*/
118 #ifdef XT_DEBUG
119 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
120 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
121 #define XT_D_MOVE 0x0001
122 #define XT_D_KEY 0x0002
123 #define XT_D_TAB 0x0004
124 #define XT_D_URL 0x0008
125 #define XT_D_CMD 0x0010
126 #define XT_D_NAV 0x0020
127 #define XT_D_DOWNLOAD 0x0040
128 #define XT_D_CONFIG 0x0080
129 #define XT_D_JS 0x0100
130 #define XT_D_FAVORITE 0x0200
131 #define XT_D_PRINTING 0x0400
132 #define XT_D_COOKIE 0x0800
133 #define XT_D_KEYBINDING 0x1000
134 #define XT_D_CLIP 0x2000
135 #define XT_D_BUFFERCMD 0x4000
136 u_int32_t swm_debug = 0
137 | XT_D_MOVE
138 | XT_D_KEY
139 | XT_D_TAB
140 | XT_D_URL
141 | XT_D_CMD
142 | XT_D_NAV
143 | XT_D_DOWNLOAD
144 | XT_D_CONFIG
145 | XT_D_JS
146 | XT_D_FAVORITE
147 | XT_D_PRINTING
148 | XT_D_COOKIE
149 | XT_D_KEYBINDING
150 | XT_D_CLIP
151 | XT_D_BUFFERCMD
153 #else
154 #define DPRINTF(x...)
155 #define DNPRINTF(n,x...)
156 #endif
158 #define LENGTH(x) (sizeof x / sizeof x[0])
159 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
160 ~(GDK_BUTTON1_MASK) & \
161 ~(GDK_BUTTON2_MASK) & \
162 ~(GDK_BUTTON3_MASK) & \
163 ~(GDK_BUTTON4_MASK) & \
164 ~(GDK_BUTTON5_MASK))
166 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
168 char *icons[] = {
169 "xxxtermicon16.png",
170 "xxxtermicon32.png",
171 "xxxtermicon48.png",
172 "xxxtermicon64.png",
173 "xxxtermicon128.png"
176 struct tab {
177 TAILQ_ENTRY(tab) entry;
178 GtkWidget *vbox;
179 GtkWidget *tab_content;
180 struct {
181 GtkWidget *label;
182 GtkWidget *eventbox;
183 GtkWidget *box;
184 GtkWidget *sep;
185 } tab_elems;
186 GtkWidget *label;
187 GtkWidget *spinner;
188 GtkWidget *uri_entry;
189 GtkWidget *search_entry;
190 GtkWidget *toolbar;
191 GtkWidget *browser_win;
192 GtkWidget *statusbar_box;
193 struct {
194 GtkWidget *statusbar;
195 GtkWidget *buffercmd;
196 GtkWidget *zoom;
197 GtkWidget *position;
198 } sbe;
199 GtkWidget *cmd;
200 GtkWidget *buffers;
201 GtkWidget *oops;
202 GtkWidget *backward;
203 GtkWidget *forward;
204 GtkWidget *stop;
205 GtkWidget *gohome;
206 GtkWidget *js_toggle;
207 GtkEntryCompletion *completion;
208 guint tab_id;
209 WebKitWebView *wv;
211 WebKitWebHistoryItem *item;
212 WebKitWebBackForwardList *bfl;
214 /* favicon */
215 WebKitNetworkRequest *icon_request;
216 WebKitDownload *icon_download;
217 gchar *icon_dest_uri;
219 /* adjustments for browser */
220 GtkScrollbar *sb_h;
221 GtkScrollbar *sb_v;
222 GtkAdjustment *adjust_h;
223 GtkAdjustment *adjust_v;
225 /* flags */
226 int focus_wv;
227 int ctrl_click;
228 gchar *status;
229 int xtp_meaning; /* identifies dls/favorites */
230 gchar *tmp_uri;
231 int popup; /* 1 if cmd_entry has popup visible */
233 /* https thread stuff */
234 GThread *thread;
236 /* hints */
237 int hints_on;
238 int hint_mode;
239 #define XT_HINT_NONE (0)
240 #define XT_HINT_NUMERICAL (1)
241 #define XT_HINT_ALPHANUM (2)
242 char hint_buf[128];
243 char hint_num[128];
245 /* custom stylesheet */
246 int styled;
247 char *stylesheet;
249 /* search */
250 char *search_text;
251 int search_forward;
252 guint search_id;
254 /* settings */
255 WebKitWebSettings *settings;
256 gchar *user_agent;
258 /* marks */
259 double mark[XT_NOMARKS];
261 TAILQ_HEAD(tab_list, tab);
263 struct history {
264 RB_ENTRY(history) entry;
265 const gchar *uri;
266 const gchar *title;
268 RB_HEAD(history_list, history);
270 struct session {
271 TAILQ_ENTRY(session) entry;
272 const gchar *name;
274 TAILQ_HEAD(session_list, session);
276 struct download {
277 RB_ENTRY(download) entry;
278 int id;
279 WebKitDownload *download;
280 struct tab *tab;
282 RB_HEAD(download_list, download);
284 struct domain {
285 RB_ENTRY(domain) entry;
286 gchar *d;
287 int handy; /* app use */
289 RB_HEAD(domain_list, domain);
291 struct undo {
292 TAILQ_ENTRY(undo) entry;
293 gchar *uri;
294 GList *history;
295 int back; /* Keeps track of how many back
296 * history items there are. */
298 TAILQ_HEAD(undo_tailq, undo);
300 struct sp {
301 char *line;
302 TAILQ_ENTRY(sp) entry;
304 TAILQ_HEAD(sp_list, sp);
306 struct command_entry {
307 char *line;
308 TAILQ_ENTRY(command_entry) entry;
310 TAILQ_HEAD(command_list, command_entry);
312 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
313 int next_download_id = 1;
315 struct karg {
316 int i;
317 char *s;
318 int precount;
321 /* defines */
322 #define XT_NAME ("XXXTerm")
323 #define XT_DIR (".xxxterm")
324 #define XT_CACHE_DIR ("cache")
325 #define XT_CERT_DIR ("certs/")
326 #define XT_SESSIONS_DIR ("sessions/")
327 #define XT_CONF_FILE ("xxxterm.conf")
328 #define XT_FAVS_FILE ("favorites")
329 #define XT_QMARKS_FILE ("quickmarks")
330 #define XT_SAVED_TABS_FILE ("main_session")
331 #define XT_RESTART_TABS_FILE ("restart_tabs")
332 #define XT_SOCKET_FILE ("socket")
333 #define XT_HISTORY_FILE ("history")
334 #define XT_REJECT_FILE ("rejected.txt")
335 #define XT_COOKIE_FILE ("cookies.txt")
336 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
337 #define XT_SEARCH_FILE ("search_history")
338 #define XT_COMMAND_FILE ("command_history")
339 #define XT_CB_HANDLED (TRUE)
340 #define XT_CB_PASSTHROUGH (FALSE)
341 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
342 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
343 #define XT_DLMAN_REFRESH "10"
344 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
345 "td{overflow: hidden;" \
346 " padding: 2px 2px 2px 2px;" \
347 " border: 1px solid black;" \
348 " vertical-align:top;" \
349 " word-wrap: break-word}\n" \
350 "tr:hover{background: #ffff99}\n" \
351 "th{background-color: #cccccc;" \
352 " border: 1px solid black}\n" \
353 "table{width: 100%%;" \
354 " border: 1px black solid;" \
355 " border-collapse:collapse}\n" \
356 ".progress-outer{" \
357 "border: 1px solid black;" \
358 " height: 8px;" \
359 " width: 90%%}\n" \
360 ".progress-inner{float: left;" \
361 " height: 8px;" \
362 " background: green}\n" \
363 ".dlstatus{font-size: small;" \
364 " text-align: center}\n" \
365 "</style>\n"
366 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
367 #define XT_MAX_UNDO_CLOSE_TAB (32)
368 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
369 #define XT_PRINT_EXTRA_MARGIN 10
370 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
371 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
373 /* colors */
374 #define XT_COLOR_RED "#cc0000"
375 #define XT_COLOR_YELLOW "#ffff66"
376 #define XT_COLOR_BLUE "lightblue"
377 #define XT_COLOR_GREEN "#99ff66"
378 #define XT_COLOR_WHITE "white"
379 #define XT_COLOR_BLACK "black"
381 #define XT_COLOR_CT_BACKGROUND "#000000"
382 #define XT_COLOR_CT_INACTIVE "#dddddd"
383 #define XT_COLOR_CT_ACTIVE "#bbbb00"
384 #define XT_COLOR_CT_SEPARATOR "#555555"
386 #define XT_COLOR_SB_SEPARATOR "#555555"
388 #define XT_PROTO_DELIM "://"
391 * xxxterm "protocol" (xtp)
392 * We use this for managing stuff like downloads and favorites. They
393 * make magical HTML pages in memory which have xxxt:// links in order
394 * to communicate with xxxterm's internals. These links take the format:
395 * xxxt://class/session_key/action/arg
397 * Don't begin xtp class/actions as 0. atoi returns that on error.
399 * Typically we have not put addition of items in this framework, as
400 * adding items is either done via an ex-command or via a keybinding instead.
403 #define XT_XTP_STR "xxxt://"
405 /* XTP classes (xxxt://<class>) */
406 #define XT_XTP_INVALID 0 /* invalid */
407 #define XT_XTP_DL 1 /* downloads */
408 #define XT_XTP_HL 2 /* history */
409 #define XT_XTP_CL 3 /* cookies */
410 #define XT_XTP_FL 4 /* favorites */
412 /* XTP download actions */
413 #define XT_XTP_DL_LIST 1
414 #define XT_XTP_DL_CANCEL 2
415 #define XT_XTP_DL_REMOVE 3
417 /* XTP history actions */
418 #define XT_XTP_HL_LIST 1
419 #define XT_XTP_HL_REMOVE 2
421 /* XTP cookie actions */
422 #define XT_XTP_CL_LIST 1
423 #define XT_XTP_CL_REMOVE 2
425 /* XTP cookie actions */
426 #define XT_XTP_FL_LIST 1
427 #define XT_XTP_FL_REMOVE 2
429 /* actions */
430 #define XT_MOVE_INVALID (0)
431 #define XT_MOVE_DOWN (1)
432 #define XT_MOVE_UP (2)
433 #define XT_MOVE_BOTTOM (3)
434 #define XT_MOVE_TOP (4)
435 #define XT_MOVE_PAGEDOWN (5)
436 #define XT_MOVE_PAGEUP (6)
437 #define XT_MOVE_HALFDOWN (7)
438 #define XT_MOVE_HALFUP (8)
439 #define XT_MOVE_LEFT (9)
440 #define XT_MOVE_FARLEFT (10)
441 #define XT_MOVE_RIGHT (11)
442 #define XT_MOVE_FARRIGHT (12)
443 #define XT_MOVE_PERCENT (13)
445 #define XT_QMARK_SET (0)
446 #define XT_QMARK_OPEN (1)
447 #define XT_QMARK_TAB (2)
449 #define XT_MARK_SET (0)
450 #define XT_MARK_GOTO (1)
452 #define XT_TAB_LAST (-4)
453 #define XT_TAB_FIRST (-3)
454 #define XT_TAB_PREV (-2)
455 #define XT_TAB_NEXT (-1)
456 #define XT_TAB_INVALID (0)
457 #define XT_TAB_NEW (1)
458 #define XT_TAB_DELETE (2)
459 #define XT_TAB_DELQUIT (3)
460 #define XT_TAB_OPEN (4)
461 #define XT_TAB_UNDO_CLOSE (5)
462 #define XT_TAB_SHOW (6)
463 #define XT_TAB_HIDE (7)
464 #define XT_TAB_NEXTSTYLE (8)
466 #define XT_NAV_INVALID (0)
467 #define XT_NAV_BACK (1)
468 #define XT_NAV_FORWARD (2)
469 #define XT_NAV_RELOAD (3)
471 #define XT_FOCUS_INVALID (0)
472 #define XT_FOCUS_URI (1)
473 #define XT_FOCUS_SEARCH (2)
475 #define XT_SEARCH_INVALID (0)
476 #define XT_SEARCH_NEXT (1)
477 #define XT_SEARCH_PREV (2)
479 #define XT_PASTE_CURRENT_TAB (0)
480 #define XT_PASTE_NEW_TAB (1)
482 #define XT_ZOOM_IN (-1)
483 #define XT_ZOOM_OUT (-2)
484 #define XT_ZOOM_NORMAL (100)
486 #define XT_URL_SHOW (1)
487 #define XT_URL_HIDE (2)
489 #define XT_WL_TOGGLE (1<<0)
490 #define XT_WL_ENABLE (1<<1)
491 #define XT_WL_DISABLE (1<<2)
492 #define XT_WL_FQDN (1<<3) /* default */
493 #define XT_WL_TOPLEVEL (1<<4)
494 #define XT_WL_PERSISTENT (1<<5)
495 #define XT_WL_SESSION (1<<6)
496 #define XT_WL_RELOAD (1<<7)
498 #define XT_SHOW (1<<7)
499 #define XT_DELETE (1<<8)
500 #define XT_SAVE (1<<9)
501 #define XT_OPEN (1<<10)
503 #define XT_CMD_OPEN (0)
504 #define XT_CMD_OPEN_CURRENT (1)
505 #define XT_CMD_TABNEW (2)
506 #define XT_CMD_TABNEW_CURRENT (3)
508 #define XT_STATUS_NOTHING (0)
509 #define XT_STATUS_LINK (1)
510 #define XT_STATUS_URI (2)
511 #define XT_STATUS_LOADING (3)
513 #define XT_SES_DONOTHING (0)
514 #define XT_SES_CLOSETABS (1)
516 #define XT_BM_NORMAL (0)
517 #define XT_BM_WHITELIST (1)
518 #define XT_BM_KIOSK (2)
520 #define XT_PREFIX (1<<0)
521 #define XT_USERARG (1<<1)
522 #define XT_URLARG (1<<2)
523 #define XT_INTARG (1<<3)
524 #define XT_SESSARG (1<<4)
525 #define XT_SETARG (1<<5)
527 #define XT_TABS_NORMAL 0
528 #define XT_TABS_COMPACT 1
530 #define XT_BUFCMD_SZ (8)
532 /* mime types */
533 struct mime_type {
534 char *mt_type;
535 char *mt_action;
536 int mt_default;
537 int mt_download;
538 TAILQ_ENTRY(mime_type) entry;
540 TAILQ_HEAD(mime_type_list, mime_type);
542 /* uri aliases */
543 struct alias {
544 char *a_name;
545 char *a_uri;
546 TAILQ_ENTRY(alias) entry;
548 TAILQ_HEAD(alias_list, alias);
550 /* settings that require restart */
551 int tabless = 0; /* allow only 1 tab */
552 int enable_socket = 0;
553 int single_instance = 0; /* only allow one xxxterm to run */
554 int fancy_bar = 1; /* fancy toolbar */
555 int browser_mode = XT_BM_NORMAL;
556 int enable_localstorage = 1;
557 char *statusbar_elems = NULL;
559 /* runtime settings */
560 int show_tabs = 1; /* show tabs on notebook */
561 int tab_style = XT_TABS_NORMAL; /* tab bar style */
562 int show_url = 1; /* show url toolbar on notebook */
563 int show_statusbar = 0; /* vimperator style status bar */
564 int ctrl_click_focus = 0; /* ctrl click gets focus */
565 int cookies_enabled = 1; /* enable cookies */
566 int read_only_cookies = 0; /* enable to not write cookies */
567 int enable_scripts = 1;
568 int enable_plugins = 0;
569 gfloat default_zoom_level = 1.0;
570 char default_script[PATH_MAX];
571 int window_height = 768;
572 int window_width = 1024;
573 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
574 int refresh_interval = 10; /* download refresh interval */
575 int enable_cookie_whitelist = 0;
576 int enable_js_whitelist = 0;
577 int session_timeout = 3600; /* cookie session timeout */
578 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
579 char *ssl_ca_file = NULL;
580 char *resource_dir = NULL;
581 gboolean ssl_strict_certs = FALSE;
582 int append_next = 1; /* append tab after current tab */
583 char *home = NULL;
584 char *search_string = NULL;
585 char *http_proxy = NULL;
586 char download_dir[PATH_MAX];
587 char runtime_settings[PATH_MAX]; /* override of settings */
588 int allow_volatile_cookies = 0;
589 int save_global_history = 0; /* save global history to disk */
590 char *user_agent = NULL;
591 int save_rejected_cookies = 0;
592 int session_autosave = 0;
593 int guess_search = 0;
594 int dns_prefetch = FALSE;
595 gint max_connections = 25;
596 gint max_host_connections = 5;
597 gint enable_spell_checking = 0;
598 char *spell_check_languages = NULL;
599 int xterm_workaround = 0;
600 char *url_regex = NULL;
601 int history_autosave = 0;
602 char search_file[PATH_MAX];
603 char command_file[PATH_MAX];
604 char *encoding = NULL;
606 char *cmd_font_name = NULL;
607 char *oops_font_name = NULL;
608 char *statusbar_font_name = NULL;
609 char *tabbar_font_name = NULL;
610 PangoFontDescription *cmd_font;
611 PangoFontDescription *oops_font;
612 PangoFontDescription *statusbar_font;
613 PangoFontDescription *tabbar_font;
614 char *qmarks[XT_NOMARKS];
616 int btn_down; /* M1 down in any wv */
617 regex_t url_re; /* guess_search regex */
619 struct settings;
620 struct key_binding;
621 int set_browser_mode(struct settings *, char *);
622 int set_cookie_policy(struct settings *, char *);
623 int set_download_dir(struct settings *, char *);
624 int set_default_script(struct settings *, char *);
625 int set_runtime_dir(struct settings *, char *);
626 int set_tab_style(struct settings *, char *);
627 int set_work_dir(struct settings *, char *);
628 int add_alias(struct settings *, char *);
629 int add_mime_type(struct settings *, char *);
630 int add_cookie_wl(struct settings *, char *);
631 int add_js_wl(struct settings *, char *);
632 int add_kb(struct settings *, char *);
633 void button_set_stockid(GtkWidget *, char *);
634 GtkWidget * create_button(char *, char *, int);
636 char *get_browser_mode(struct settings *);
637 char *get_cookie_policy(struct settings *);
638 char *get_download_dir(struct settings *);
639 char *get_default_script(struct settings *);
640 char *get_runtime_dir(struct settings *);
641 char *get_tab_style(struct settings *);
642 char *get_work_dir(struct settings *);
643 void startpage_add(const char *, ...);
645 void walk_alias(struct settings *, void (*)(struct settings *,
646 char *, void *), void *);
647 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
648 char *, void *), void *);
649 void walk_js_wl(struct settings *, void (*)(struct settings *,
650 char *, void *), void *);
651 void walk_kb(struct settings *, void (*)(struct settings *, char *,
652 void *), void *);
653 void walk_mime_type(struct settings *, void (*)(struct settings *,
654 char *, void *), void *);
656 void recalc_tabs(void);
657 void recolor_compact_tabs(void);
658 void set_current_tab(int page_num);
659 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
660 void marks_clear(struct tab *t);
662 int set_http_proxy(char *);
664 struct special {
665 int (*set)(struct settings *, char *);
666 char *(*get)(struct settings *);
667 void (*walk)(struct settings *,
668 void (*cb)(struct settings *, char *, void *),
669 void *);
672 struct special s_browser_mode = {
673 set_browser_mode,
674 get_browser_mode,
675 NULL
678 struct special s_cookie = {
679 set_cookie_policy,
680 get_cookie_policy,
681 NULL
684 struct special s_alias = {
685 add_alias,
686 NULL,
687 walk_alias
690 struct special s_mime = {
691 add_mime_type,
692 NULL,
693 walk_mime_type
696 struct special s_js = {
697 add_js_wl,
698 NULL,
699 walk_js_wl
702 struct special s_kb = {
703 add_kb,
704 NULL,
705 walk_kb
708 struct special s_cookie_wl = {
709 add_cookie_wl,
710 NULL,
711 walk_cookie_wl
714 struct special s_default_script = {
715 set_default_script,
716 get_default_script,
717 NULL
720 struct special s_download_dir = {
721 set_download_dir,
722 get_download_dir,
723 NULL
726 struct special s_work_dir = {
727 set_work_dir,
728 get_work_dir,
729 NULL
732 struct special s_tab_style = {
733 set_tab_style,
734 get_tab_style,
735 NULL
738 struct settings {
739 char *name;
740 int type;
741 #define XT_S_INVALID (0)
742 #define XT_S_INT (1)
743 #define XT_S_STR (2)
744 #define XT_S_FLOAT (3)
745 uint32_t flags;
746 #define XT_SF_RESTART (1<<0)
747 #define XT_SF_RUNTIME (1<<1)
748 int *ival;
749 char **sval;
750 struct special *s;
751 gfloat *fval;
752 int (*activate)(char *);
753 } rs[] = {
754 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
755 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
756 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
757 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
758 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
759 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
760 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
761 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
762 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
763 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
764 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
765 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
766 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
767 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
768 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
769 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
770 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
771 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
772 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
773 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
774 { "home", XT_S_STR, 0, NULL, &home, NULL },
775 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
776 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
777 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
778 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
779 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
780 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
781 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
782 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
783 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
784 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
785 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
786 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
787 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
788 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
789 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
790 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
791 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
792 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
793 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
794 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
795 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
796 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
797 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
798 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
799 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
800 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
801 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
803 /* font settings */
804 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
805 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
806 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
807 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
809 /* runtime settings */
810 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
811 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
812 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
813 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
814 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
817 int about(struct tab *, struct karg *);
818 int blank(struct tab *, struct karg *);
819 int ca_cmd(struct tab *, struct karg *);
820 int cookie_show_wl(struct tab *, struct karg *);
821 int js_show_wl(struct tab *, struct karg *);
822 int help(struct tab *, struct karg *);
823 int set(struct tab *, struct karg *);
824 int stats(struct tab *, struct karg *);
825 int marco(struct tab *, struct karg *);
826 int startpage(struct tab *, struct karg *);
827 const char * marco_message(int *);
828 int xtp_page_cl(struct tab *, struct karg *);
829 int xtp_page_dl(struct tab *, struct karg *);
830 int xtp_page_fl(struct tab *, struct karg *);
831 int xtp_page_hl(struct tab *, struct karg *);
832 void xt_icon_from_file(struct tab *, char *);
833 const gchar *get_uri(struct tab *);
834 const gchar *get_title(struct tab *, bool);
836 #define XT_URI_ABOUT ("about:")
837 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
838 #define XT_URI_ABOUT_ABOUT ("about")
839 #define XT_URI_ABOUT_BLANK ("blank")
840 #define XT_URI_ABOUT_CERTS ("certs")
841 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
842 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
843 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
844 #define XT_URI_ABOUT_FAVORITES ("favorites")
845 #define XT_URI_ABOUT_HELP ("help")
846 #define XT_URI_ABOUT_HISTORY ("history")
847 #define XT_URI_ABOUT_JSWL ("jswl")
848 #define XT_URI_ABOUT_SET ("set")
849 #define XT_URI_ABOUT_STATS ("stats")
850 #define XT_URI_ABOUT_MARCO ("marco")
851 #define XT_URI_ABOUT_STARTPAGE ("startpage")
853 struct about_type {
854 char *name;
855 int (*func)(struct tab *, struct karg *);
856 } about_list[] = {
857 { XT_URI_ABOUT_ABOUT, about },
858 { XT_URI_ABOUT_BLANK, blank },
859 { XT_URI_ABOUT_CERTS, ca_cmd },
860 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
861 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
862 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
863 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
864 { XT_URI_ABOUT_HELP, help },
865 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
866 { XT_URI_ABOUT_JSWL, js_show_wl },
867 { XT_URI_ABOUT_SET, set },
868 { XT_URI_ABOUT_STATS, stats },
869 { XT_URI_ABOUT_MARCO, marco },
870 { XT_URI_ABOUT_STARTPAGE, startpage },
873 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
874 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
875 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
876 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
877 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
878 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
879 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
881 /* globals */
882 extern char *__progname;
883 char **start_argv;
884 struct passwd *pwd;
885 GtkWidget *main_window;
886 GtkNotebook *notebook;
887 GtkWidget *tab_bar;
888 GtkWidget *arrow, *abtn;
889 struct tab_list tabs;
890 struct history_list hl;
891 struct session_list sessions;
892 struct download_list downloads;
893 struct domain_list c_wl;
894 struct domain_list js_wl;
895 struct undo_tailq undos;
896 struct keybinding_list kbl;
897 struct sp_list spl;
898 struct command_list chl;
899 struct command_list shl;
900 struct command_entry *history_at;
901 struct command_entry *search_at;
902 int undo_count;
903 int updating_dl_tabs = 0;
904 int updating_hl_tabs = 0;
905 int updating_cl_tabs = 0;
906 int updating_fl_tabs = 0;
907 int cmd_history_count = 0;
908 int search_history_count = 0;
909 char *global_search;
910 uint64_t blocked_cookies = 0;
911 char named_session[PATH_MAX];
912 GtkListStore *completion_model;
913 GtkListStore *buffers_store;
915 void xxx_dir(char *);
916 int icon_size_map(int);
917 void completion_add(struct tab *);
918 void completion_add_uri(const gchar *);
919 void show_oops(struct tab *, const char *, ...);
921 void
922 history_delete(struct command_list *l, int *counter)
924 struct command_entry *c;
926 if (l == NULL || counter == NULL)
927 return;
929 c = TAILQ_LAST(l, command_list);
930 if (c == NULL)
931 return;
933 TAILQ_REMOVE(l, c, entry);
934 *counter -= 1;
935 g_free(c->line);
936 g_free(c);
939 void
940 history_add(struct command_list *list, char *file, char *l, int *counter)
942 struct command_entry *c;
943 FILE *f;
945 if (list == NULL || l == NULL || counter == NULL)
946 return;
948 /* don't add the same line */
949 c = TAILQ_FIRST(list);
950 if (c)
951 if (!strcmp(c->line + 1 /* skip space */, l))
952 return;
954 c = g_malloc0(sizeof *c);
955 c->line = g_strdup_printf(" %s", l);
957 *counter += 1;
958 TAILQ_INSERT_HEAD(list, c, entry);
960 if (*counter > 1000)
961 history_delete(list, counter);
963 if (history_autosave && file) {
964 f = fopen(file, "w");
965 if (f == NULL) {
966 show_oops(NULL, "couldn't write history %s", file);
967 return;
970 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
971 c->line[0] = ' ';
972 fprintf(f, "%s\n", c->line);
975 fclose(f);
980 history_read(struct command_list *list, char *file, int *counter)
982 FILE *f;
983 char *s, line[65536];
985 if (list == NULL || file == NULL)
986 return (1);
988 f = fopen(file, "r");
989 if (f == NULL) {
990 startpage_add("couldn't open history file %s", file);
991 return (1);
994 for (;;) {
995 s = fgets(line, sizeof line, f);
996 if (s == NULL || feof(f) || ferror(f))
997 break;
998 if ((s = strchr(line, '\n')) == NULL) {
999 startpage_add("invalid history file %s", file);
1000 fclose(f);
1001 return (1);
1003 *s = '\0';
1005 history_add(list, NULL, line + 1, counter);
1008 fclose(f);
1010 return (0);
1013 /* marks and quickmarks array storage.
1014 * first a-z, then A-Z, then 0-9 */
1015 char
1016 indextomark(int i)
1018 if (i < 0)
1019 return 0;
1021 if (i >= 0 && i <= 'z' - 'a')
1022 return 'a' + i;
1024 i -= 'z' - 'a' + 1;
1025 if (i >= 0 && i <= 'Z' - 'A')
1026 return 'A' + i;
1028 i -= 'Z' - 'A' + 1;
1029 if (i >= 10)
1030 return 0;
1032 return i + '0';
1036 marktoindex(char m)
1038 int ret = 0;
1040 if (m >= 'a' && m <= 'z')
1041 return ret + m - 'a';
1043 ret += 'z' - 'a' + 1;
1044 if (m >= 'A' && m <= 'Z')
1045 return ret + m - 'A';
1047 ret += 'Z' - 'A' + 1;
1048 if (m >= '0' && m <= '9')
1049 return ret + m - '0';
1051 return -1;
1055 void
1056 sigchild(int sig)
1058 int saved_errno, status;
1059 pid_t pid;
1061 saved_errno = errno;
1063 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1064 if (pid == -1) {
1065 if (errno == EINTR)
1066 continue;
1067 if (errno != ECHILD) {
1069 clog_warn("sigchild: waitpid:");
1072 break;
1075 if (WIFEXITED(status)) {
1076 if (WEXITSTATUS(status) != 0) {
1078 clog_warnx("sigchild: child exit status: %d",
1079 WEXITSTATUS(status));
1082 } else {
1084 clog_warnx("sigchild: child is terminated abnormally");
1089 errno = saved_errno;
1093 is_g_object_setting(GObject *o, char *str)
1095 guint n_props = 0, i;
1096 GParamSpec **proplist;
1098 if (! G_IS_OBJECT(o))
1099 return (0);
1101 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1102 &n_props);
1104 for (i=0; i < n_props; i++) {
1105 if (! strcmp(proplist[i]->name, str))
1106 return (1);
1108 return (0);
1111 gchar *
1112 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1114 gchar *r;
1116 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1117 "<head>\n"
1118 "<title>%s</title>\n"
1119 "%s"
1120 "%s"
1121 "</head>\n"
1122 "<body>\n"
1123 "<h1>%s</h1>\n"
1124 "%s\n</body>\n"
1125 "</html>",
1126 title,
1127 addstyles ? XT_PAGE_STYLE : "",
1128 head,
1129 title,
1130 body);
1132 return r;
1136 * Display a web page from a HTML string in memory, rather than from a URL
1138 void
1139 load_webkit_string(struct tab *t, const char *str, gchar *title)
1141 char file[PATH_MAX];
1142 int i;
1144 /* we set this to indicate we want to manually do navaction */
1145 if (t->bfl)
1146 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1148 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1149 if (title) {
1150 /* set t->xtp_meaning */
1151 for (i = 0; i < LENGTH(about_list); i++)
1152 if (!strcmp(title, about_list[i].name)) {
1153 t->xtp_meaning = i;
1154 break;
1157 webkit_web_view_load_string(t->wv, str, NULL, encoding,
1158 "file://");
1159 #if GTK_CHECK_VERSION(2, 20, 0)
1160 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1161 gtk_widget_hide(t->spinner);
1162 #endif
1163 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1164 xt_icon_from_file(t, file);
1168 struct tab *
1169 get_current_tab(void)
1171 struct tab *t;
1173 TAILQ_FOREACH(t, &tabs, entry) {
1174 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1175 return (t);
1178 warnx("%s: no current tab", __func__);
1180 return (NULL);
1183 void
1184 set_status(struct tab *t, gchar *s, int status)
1186 gchar *type = NULL;
1188 if (s == NULL)
1189 return;
1191 switch (status) {
1192 case XT_STATUS_LOADING:
1193 type = g_strdup_printf("Loading: %s", s);
1194 s = type;
1195 break;
1196 case XT_STATUS_LINK:
1197 type = g_strdup_printf("Link: %s", s);
1198 if (!t->status)
1199 t->status = g_strdup(gtk_entry_get_text(
1200 GTK_ENTRY(t->sbe.statusbar)));
1201 s = type;
1202 break;
1203 case XT_STATUS_URI:
1204 type = g_strdup_printf("%s", s);
1205 if (!t->status) {
1206 t->status = g_strdup(type);
1208 s = type;
1209 if (!t->status)
1210 t->status = g_strdup(s);
1211 break;
1212 case XT_STATUS_NOTHING:
1213 /* FALL THROUGH */
1214 default:
1215 break;
1217 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1218 if (type)
1219 g_free(type);
1222 void
1223 hide_cmd(struct tab *t)
1225 history_at = NULL; /* just in case */
1226 search_at = NULL; /* just in case */
1227 gtk_widget_hide(t->cmd);
1230 void
1231 show_cmd(struct tab *t)
1233 history_at = NULL;
1234 search_at = NULL;
1235 gtk_widget_hide(t->oops);
1236 gtk_widget_show(t->cmd);
1239 void
1240 hide_buffers(struct tab *t)
1242 gtk_widget_hide(t->buffers);
1243 gtk_list_store_clear(buffers_store);
1246 enum {
1247 COL_ID = 0,
1248 COL_TITLE,
1249 NUM_COLS
1253 sort_tabs_by_page_num(struct tab ***stabs)
1255 int num_tabs = 0;
1256 struct tab *t;
1258 num_tabs = gtk_notebook_get_n_pages(notebook);
1260 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1262 TAILQ_FOREACH(t, &tabs, entry)
1263 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1265 return (num_tabs);
1268 void
1269 buffers_make_list(void)
1271 int i, num_tabs;
1272 const gchar *title = NULL;
1273 GtkTreeIter iter;
1274 struct tab **stabs = NULL;
1276 num_tabs = sort_tabs_by_page_num(&stabs);
1278 for (i = 0; i < num_tabs; i++)
1279 if (stabs[i]) {
1280 gtk_list_store_append(buffers_store, &iter);
1281 title = get_title(stabs[i], FALSE);
1282 gtk_list_store_set(buffers_store, &iter,
1283 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1284 * rather than 0. */
1285 COL_TITLE, title,
1286 -1);
1289 g_free(stabs);
1292 void
1293 show_buffers(struct tab *t)
1295 buffers_make_list();
1296 gtk_widget_show(t->buffers);
1297 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1300 void
1301 toggle_buffers(struct tab *t)
1303 if (gtk_widget_get_visible(t->buffers))
1304 hide_buffers(t);
1305 else
1306 show_buffers(t);
1310 buffers(struct tab *t, struct karg *args)
1312 show_buffers(t);
1314 return (0);
1317 void
1318 hide_oops(struct tab *t)
1320 gtk_widget_hide(t->oops);
1323 void
1324 show_oops(struct tab *at, const char *fmt, ...)
1326 va_list ap;
1327 char *msg = NULL;
1328 struct tab *t = NULL;
1330 if (fmt == NULL)
1331 return;
1333 if (at == NULL) {
1334 if ((t = get_current_tab()) == NULL)
1335 return;
1336 } else
1337 t = at;
1339 va_start(ap, fmt);
1340 if (vasprintf(&msg, fmt, ap) == -1)
1341 errx(1, "show_oops failed");
1342 va_end(ap);
1344 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1345 gtk_widget_hide(t->cmd);
1346 gtk_widget_show(t->oops);
1348 if (msg)
1349 free(msg);
1352 char *
1353 get_as_string(struct settings *s)
1355 char *r = NULL;
1357 if (s == NULL)
1358 return (NULL);
1360 if (s->s) {
1361 if (s->s->get)
1362 r = s->s->get(s);
1363 else
1364 warnx("get_as_string skip %s\n", s->name);
1365 } else if (s->type == XT_S_INT)
1366 r = g_strdup_printf("%d", *s->ival);
1367 else if (s->type == XT_S_STR)
1368 r = g_strdup(*s->sval);
1369 else if (s->type == XT_S_FLOAT)
1370 r = g_strdup_printf("%f", *s->fval);
1371 else
1372 r = g_strdup_printf("INVALID TYPE");
1374 return (r);
1377 void
1378 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1380 int i;
1381 char *s;
1383 for (i = 0; i < LENGTH(rs); i++) {
1384 if (rs[i].s && rs[i].s->walk)
1385 rs[i].s->walk(&rs[i], cb, cb_args);
1386 else {
1387 s = get_as_string(&rs[i]);
1388 cb(&rs[i], s, cb_args);
1389 g_free(s);
1395 set_browser_mode(struct settings *s, char *val)
1397 if (!strcmp(val, "whitelist")) {
1398 browser_mode = XT_BM_WHITELIST;
1399 allow_volatile_cookies = 0;
1400 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1401 cookies_enabled = 1;
1402 enable_cookie_whitelist = 1;
1403 read_only_cookies = 0;
1404 save_rejected_cookies = 0;
1405 session_timeout = 3600;
1406 enable_scripts = 0;
1407 enable_js_whitelist = 1;
1408 enable_localstorage = 0;
1409 } else if (!strcmp(val, "normal")) {
1410 browser_mode = XT_BM_NORMAL;
1411 allow_volatile_cookies = 0;
1412 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1413 cookies_enabled = 1;
1414 enable_cookie_whitelist = 0;
1415 read_only_cookies = 0;
1416 save_rejected_cookies = 0;
1417 session_timeout = 3600;
1418 enable_scripts = 1;
1419 enable_js_whitelist = 0;
1420 enable_localstorage = 1;
1421 } else if (!strcmp(val, "kiosk")) {
1422 browser_mode = XT_BM_KIOSK;
1423 allow_volatile_cookies = 0;
1424 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1425 cookies_enabled = 1;
1426 enable_cookie_whitelist = 0;
1427 read_only_cookies = 0;
1428 save_rejected_cookies = 0;
1429 session_timeout = 3600;
1430 enable_scripts = 1;
1431 enable_js_whitelist = 0;
1432 enable_localstorage = 1;
1433 show_tabs = 0;
1434 tabless = 1;
1435 } else
1436 return (1);
1438 return (0);
1441 char *
1442 get_browser_mode(struct settings *s)
1444 char *r = NULL;
1446 if (browser_mode == XT_BM_WHITELIST)
1447 r = g_strdup("whitelist");
1448 else if (browser_mode == XT_BM_NORMAL)
1449 r = g_strdup("normal");
1450 else if (browser_mode == XT_BM_KIOSK)
1451 r = g_strdup("kiosk");
1452 else
1453 return (NULL);
1455 return (r);
1459 set_cookie_policy(struct settings *s, char *val)
1461 if (!strcmp(val, "no3rdparty"))
1462 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1463 else if (!strcmp(val, "accept"))
1464 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1465 else if (!strcmp(val, "reject"))
1466 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1467 else
1468 return (1);
1470 return (0);
1473 char *
1474 get_cookie_policy(struct settings *s)
1476 char *r = NULL;
1478 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1479 r = g_strdup("no3rdparty");
1480 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1481 r = g_strdup("accept");
1482 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1483 r = g_strdup("reject");
1484 else
1485 return (NULL);
1487 return (r);
1490 char *
1491 get_default_script(struct settings *s)
1493 if (default_script[0] == '\0')
1494 return (0);
1495 return (g_strdup(default_script));
1499 set_default_script(struct settings *s, char *val)
1501 if (val[0] == '~')
1502 snprintf(default_script, sizeof default_script, "%s/%s",
1503 pwd->pw_dir, &val[1]);
1504 else
1505 strlcpy(default_script, val, sizeof default_script);
1507 return (0);
1510 char *
1511 get_download_dir(struct settings *s)
1513 if (download_dir[0] == '\0')
1514 return (0);
1515 return (g_strdup(download_dir));
1519 set_download_dir(struct settings *s, char *val)
1521 if (val[0] == '~')
1522 snprintf(download_dir, sizeof download_dir, "%s/%s",
1523 pwd->pw_dir, &val[1]);
1524 else
1525 strlcpy(download_dir, val, sizeof download_dir);
1527 return (0);
1531 * Session IDs.
1532 * We use these to prevent people putting xxxt:// URLs on
1533 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1535 #define XT_XTP_SES_KEY_SZ 8
1536 #define XT_XTP_SES_KEY_HEX_FMT \
1537 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1538 char *dl_session_key; /* downloads */
1539 char *hl_session_key; /* history list */
1540 char *cl_session_key; /* cookie list */
1541 char *fl_session_key; /* favorites list */
1543 char work_dir[PATH_MAX];
1544 char certs_dir[PATH_MAX];
1545 char cache_dir[PATH_MAX];
1546 char sessions_dir[PATH_MAX];
1547 char cookie_file[PATH_MAX];
1548 SoupURI *proxy_uri = NULL;
1549 SoupSession *session;
1550 SoupCookieJar *s_cookiejar;
1551 SoupCookieJar *p_cookiejar;
1552 char rc_fname[PATH_MAX];
1554 struct mime_type_list mtl;
1555 struct alias_list aliases;
1557 /* protos */
1558 struct tab *create_new_tab(char *, struct undo *, int, int);
1559 void delete_tab(struct tab *);
1560 void setzoom_webkit(struct tab *, int);
1561 int run_script(struct tab *, char *);
1562 int download_rb_cmp(struct download *, struct download *);
1563 gboolean cmd_execute(struct tab *t, char *str);
1566 history_rb_cmp(struct history *h1, struct history *h2)
1568 return (strcmp(h1->uri, h2->uri));
1570 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1573 domain_rb_cmp(struct domain *d1, struct domain *d2)
1575 return (strcmp(d1->d, d2->d));
1577 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1579 char *
1580 get_work_dir(struct settings *s)
1582 if (work_dir[0] == '\0')
1583 return (0);
1584 return (g_strdup(work_dir));
1588 set_work_dir(struct settings *s, char *val)
1590 if (val[0] == '~')
1591 snprintf(work_dir, sizeof work_dir, "%s/%s",
1592 pwd->pw_dir, &val[1]);
1593 else
1594 strlcpy(work_dir, val, sizeof work_dir);
1596 return (0);
1599 char *
1600 get_tab_style(struct settings *s)
1602 if (tab_style == XT_TABS_NORMAL)
1603 return (g_strdup("normal"));
1604 else
1605 return (g_strdup("compact"));
1609 set_tab_style(struct settings *s, char *val)
1611 if (!strcmp(val, "normal"))
1612 tab_style = XT_TABS_NORMAL;
1613 else if (!strcmp(val, "compact"))
1614 tab_style = XT_TABS_COMPACT;
1615 else
1616 return (1);
1618 return (0);
1622 * generate a session key to secure xtp commands.
1623 * pass in a ptr to the key in question and it will
1624 * be modified in place.
1626 void
1627 generate_xtp_session_key(char **key)
1629 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1631 /* free old key */
1632 if (*key)
1633 g_free(*key);
1635 /* make a new one */
1636 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1637 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1638 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1639 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1641 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1645 * validate a xtp session key.
1646 * return 1 if OK
1649 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1651 if (strcmp(trusted, untrusted) != 0) {
1652 show_oops(t, "%s: xtp session key mismatch possible spoof",
1653 __func__);
1654 return (0);
1657 return (1);
1661 download_rb_cmp(struct download *e1, struct download *e2)
1663 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1665 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1667 struct valid_url_types {
1668 char *type;
1669 } vut[] = {
1670 { "http://" },
1671 { "https://" },
1672 { "ftp://" },
1673 { "file://" },
1674 { XT_XTP_STR },
1678 valid_url_type(char *url)
1680 int i;
1682 for (i = 0; i < LENGTH(vut); i++)
1683 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1684 return (0);
1686 return (1);
1689 void
1690 print_cookie(char *msg, SoupCookie *c)
1692 if (c == NULL)
1693 return;
1695 if (msg)
1696 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1697 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1698 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1699 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1700 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1701 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1702 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1703 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1704 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1705 DNPRINTF(XT_D_COOKIE, "====================================\n");
1708 void
1709 walk_alias(struct settings *s,
1710 void (*cb)(struct settings *, char *, void *), void *cb_args)
1712 struct alias *a;
1713 char *str;
1715 if (s == NULL || cb == NULL) {
1716 show_oops(NULL, "walk_alias invalid parameters");
1717 return;
1720 TAILQ_FOREACH(a, &aliases, entry) {
1721 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1722 cb(s, str, cb_args);
1723 g_free(str);
1727 char *
1728 match_alias(char *url_in)
1730 struct alias *a;
1731 char *arg;
1732 char *url_out = NULL, *search, *enc_arg;
1734 search = g_strdup(url_in);
1735 arg = search;
1736 if (strsep(&arg, " \t") == NULL) {
1737 show_oops(NULL, "match_alias: NULL URL");
1738 goto done;
1741 TAILQ_FOREACH(a, &aliases, entry) {
1742 if (!strcmp(search, a->a_name))
1743 break;
1746 if (a != NULL) {
1747 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1748 a->a_name);
1749 if (arg != NULL) {
1750 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1751 url_out = g_strdup_printf(a->a_uri, enc_arg);
1752 g_free(enc_arg);
1753 } else
1754 url_out = g_strdup_printf(a->a_uri, "");
1756 done:
1757 g_free(search);
1758 return (url_out);
1761 char *
1762 guess_url_type(char *url_in)
1764 struct stat sb;
1765 char *url_out = NULL, *enc_search = NULL;
1766 int i;
1768 /* substitute aliases */
1769 url_out = match_alias(url_in);
1770 if (url_out != NULL)
1771 return (url_out);
1773 /* see if we are an about page */
1774 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1775 for (i = 0; i < LENGTH(about_list); i++)
1776 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1777 about_list[i].name)) {
1778 url_out = g_strdup(url_in);
1779 goto done;
1782 if (guess_search && url_regex &&
1783 !(g_str_has_prefix(url_in, "http://") ||
1784 g_str_has_prefix(url_in, "https://"))) {
1785 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1786 /* invalid URI so search instead */
1787 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1788 url_out = g_strdup_printf(search_string, enc_search);
1789 g_free(enc_search);
1790 goto done;
1794 /* XXX not sure about this heuristic */
1795 if (stat(url_in, &sb) == 0)
1796 url_out = g_strdup_printf("file://%s", url_in);
1797 else
1798 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1799 done:
1800 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1802 return (url_out);
1805 void
1806 load_uri(struct tab *t, gchar *uri)
1808 struct karg args;
1809 gchar *newuri = NULL;
1810 int i;
1812 if (uri == NULL)
1813 return;
1815 /* Strip leading spaces. */
1816 while (*uri && isspace(*uri))
1817 uri++;
1819 if (strlen(uri) == 0) {
1820 blank(t, NULL);
1821 return;
1824 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1826 if (valid_url_type(uri)) {
1827 newuri = guess_url_type(uri);
1828 uri = newuri;
1831 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1832 for (i = 0; i < LENGTH(about_list); i++)
1833 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1834 bzero(&args, sizeof args);
1835 about_list[i].func(t, &args);
1836 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1837 FALSE);
1838 goto done;
1840 show_oops(t, "invalid about page");
1841 goto done;
1844 set_status(t, (char *)uri, XT_STATUS_LOADING);
1845 marks_clear(t);
1846 webkit_web_view_load_uri(t->wv, uri);
1847 done:
1848 if (newuri)
1849 g_free(newuri);
1852 const gchar *
1853 get_uri(struct tab *t)
1855 const gchar *uri = NULL;
1857 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1858 return NULL;
1859 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1860 uri = webkit_web_view_get_uri(t->wv);
1861 } else {
1862 /* use tmp_uri to make sure it is g_freed */
1863 if (t->tmp_uri)
1864 g_free(t->tmp_uri);
1865 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1866 about_list[t->xtp_meaning].name);
1867 uri = t->tmp_uri;
1869 return uri;
1872 const gchar *
1873 get_title(struct tab *t, bool window)
1875 const gchar *set = NULL, *title = NULL;
1876 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1878 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1879 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1880 goto notitle;
1882 title = webkit_web_view_get_title(t->wv);
1883 if ((set = title ? title : get_uri(t)))
1884 return set;
1886 notitle:
1887 set = window ? XT_NAME : "(untitled)";
1889 return set;
1893 add_alias(struct settings *s, char *line)
1895 char *l, *alias;
1896 struct alias *a = NULL;
1898 if (s == NULL || line == NULL) {
1899 show_oops(NULL, "add_alias invalid parameters");
1900 return (1);
1903 l = line;
1904 a = g_malloc(sizeof(*a));
1906 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1907 show_oops(NULL, "add_alias: incomplete alias definition");
1908 goto bad;
1910 if (strlen(alias) == 0 || strlen(l) == 0) {
1911 show_oops(NULL, "add_alias: invalid alias definition");
1912 goto bad;
1915 a->a_name = g_strdup(alias);
1916 a->a_uri = g_strdup(l);
1918 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1920 TAILQ_INSERT_TAIL(&aliases, a, entry);
1922 return (0);
1923 bad:
1924 if (a)
1925 g_free(a);
1926 return (1);
1930 add_mime_type(struct settings *s, char *line)
1932 char *mime_type;
1933 char *l;
1934 struct mime_type *m = NULL;
1935 int downloadfirst = 0;
1937 /* XXX this could be smarter */
1939 if (line == NULL || strlen(line) == 0) {
1940 show_oops(NULL, "add_mime_type invalid parameters");
1941 return (1);
1944 l = line;
1945 if (*l == '@') {
1946 downloadfirst = 1;
1947 l++;
1949 m = g_malloc(sizeof(*m));
1951 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1952 show_oops(NULL, "add_mime_type: invalid mime_type");
1953 goto bad;
1955 if (mime_type[strlen(mime_type) - 1] == '*') {
1956 mime_type[strlen(mime_type) - 1] = '\0';
1957 m->mt_default = 1;
1958 } else
1959 m->mt_default = 0;
1961 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1962 show_oops(NULL, "add_mime_type: invalid mime_type");
1963 goto bad;
1966 m->mt_type = g_strdup(mime_type);
1967 m->mt_action = g_strdup(l);
1968 m->mt_download = downloadfirst;
1970 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1971 m->mt_type, m->mt_action, m->mt_default);
1973 TAILQ_INSERT_TAIL(&mtl, m, entry);
1975 return (0);
1976 bad:
1977 if (m)
1978 g_free(m);
1979 return (1);
1982 struct mime_type *
1983 find_mime_type(char *mime_type)
1985 struct mime_type *m, *def = NULL, *rv = NULL;
1987 TAILQ_FOREACH(m, &mtl, entry) {
1988 if (m->mt_default &&
1989 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1990 def = m;
1992 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1993 rv = m;
1994 break;
1998 if (rv == NULL)
1999 rv = def;
2001 return (rv);
2004 void
2005 walk_mime_type(struct settings *s,
2006 void (*cb)(struct settings *, char *, void *), void *cb_args)
2008 struct mime_type *m;
2009 char *str;
2011 if (s == NULL || cb == NULL) {
2012 show_oops(NULL, "walk_mime_type invalid parameters");
2013 return;
2016 TAILQ_FOREACH(m, &mtl, entry) {
2017 str = g_strdup_printf("%s%s --> %s",
2018 m->mt_type,
2019 m->mt_default ? "*" : "",
2020 m->mt_action);
2021 cb(s, str, cb_args);
2022 g_free(str);
2026 void
2027 wl_add(char *str, struct domain_list *wl, int handy)
2029 struct domain *d;
2030 int add_dot = 0;
2031 char *p;
2033 if (str == NULL || wl == NULL || strlen(str) < 2)
2034 return;
2036 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2038 /* treat *.moo.com the same as .moo.com */
2039 if (str[0] == '*' && str[1] == '.')
2040 str = &str[1];
2041 else if (str[0] == '.')
2042 str = &str[0];
2043 else
2044 add_dot = 1;
2046 /* slice off port number */
2047 p = g_strrstr(str, ":");
2048 if (p)
2049 *p = '\0';
2051 d = g_malloc(sizeof *d);
2052 if (add_dot)
2053 d->d = g_strdup_printf(".%s", str);
2054 else
2055 d->d = g_strdup(str);
2056 d->handy = handy;
2058 if (RB_INSERT(domain_list, wl, d))
2059 goto unwind;
2061 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2062 return;
2063 unwind:
2064 if (d) {
2065 if (d->d)
2066 g_free(d->d);
2067 g_free(d);
2072 add_cookie_wl(struct settings *s, char *entry)
2074 wl_add(entry, &c_wl, 1);
2075 return (0);
2078 void
2079 walk_cookie_wl(struct settings *s,
2080 void (*cb)(struct settings *, char *, void *), void *cb_args)
2082 struct domain *d;
2084 if (s == NULL || cb == NULL) {
2085 show_oops(NULL, "walk_cookie_wl invalid parameters");
2086 return;
2089 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2090 cb(s, d->d, cb_args);
2093 void
2094 walk_js_wl(struct settings *s,
2095 void (*cb)(struct settings *, char *, void *), void *cb_args)
2097 struct domain *d;
2099 if (s == NULL || cb == NULL) {
2100 show_oops(NULL, "walk_js_wl invalid parameters");
2101 return;
2104 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2105 cb(s, d->d, cb_args);
2109 add_js_wl(struct settings *s, char *entry)
2111 wl_add(entry, &js_wl, 1 /* persistent */);
2112 return (0);
2115 struct domain *
2116 wl_find(const gchar *search, struct domain_list *wl)
2118 int i;
2119 struct domain *d = NULL, dfind;
2120 gchar *s = NULL;
2122 if (search == NULL || wl == NULL)
2123 return (NULL);
2124 if (strlen(search) < 2)
2125 return (NULL);
2127 if (search[0] != '.')
2128 s = g_strdup_printf(".%s", search);
2129 else
2130 s = g_strdup(search);
2132 for (i = strlen(s) - 1; i >= 0; i--) {
2133 if (s[i] == '.') {
2134 dfind.d = &s[i];
2135 d = RB_FIND(domain_list, wl, &dfind);
2136 if (d)
2137 goto done;
2141 done:
2142 if (s)
2143 g_free(s);
2145 return (d);
2148 struct domain *
2149 wl_find_uri(const gchar *s, struct domain_list *wl)
2151 int i;
2152 char *ss;
2153 struct domain *r;
2155 if (s == NULL || wl == NULL)
2156 return (NULL);
2158 if (!strncmp(s, "http://", strlen("http://")))
2159 s = &s[strlen("http://")];
2160 else if (!strncmp(s, "https://", strlen("https://")))
2161 s = &s[strlen("https://")];
2163 if (strlen(s) < 2)
2164 return (NULL);
2166 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2167 /* chop string at first slash */
2168 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2169 ss = g_strdup(s);
2170 ss[i] = '\0';
2171 r = wl_find(ss, wl);
2172 g_free(ss);
2173 return (r);
2176 return (NULL);
2180 settings_add(char *var, char *val)
2182 int i, rv, *p;
2183 gfloat *f;
2184 char **s;
2186 /* get settings */
2187 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2188 if (strcmp(var, rs[i].name))
2189 continue;
2191 if (rs[i].s) {
2192 if (rs[i].s->set(&rs[i], val))
2193 errx(1, "invalid value for %s: %s", var, val);
2194 rv = 1;
2195 break;
2196 } else
2197 switch (rs[i].type) {
2198 case XT_S_INT:
2199 p = rs[i].ival;
2200 *p = atoi(val);
2201 rv = 1;
2202 break;
2203 case XT_S_STR:
2204 s = rs[i].sval;
2205 if (s == NULL)
2206 errx(1, "invalid sval for %s",
2207 rs[i].name);
2208 if (*s)
2209 g_free(*s);
2210 *s = g_strdup(val);
2211 rv = 1;
2212 break;
2213 case XT_S_FLOAT:
2214 f = rs[i].fval;
2215 *f = atof(val);
2216 rv = 1;
2217 break;
2218 case XT_S_INVALID:
2219 default:
2220 errx(1, "invalid type for %s", var);
2222 break;
2224 return (rv);
2227 #define WS "\n= \t"
2228 void
2229 config_parse(char *filename, int runtime)
2231 FILE *config, *f;
2232 char *line, *cp, *var, *val;
2233 size_t len, lineno = 0;
2234 int handled;
2235 char file[PATH_MAX];
2236 struct stat sb;
2238 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2240 if (filename == NULL)
2241 return;
2243 if (runtime && runtime_settings[0] != '\0') {
2244 snprintf(file, sizeof file, "%s/%s",
2245 work_dir, runtime_settings);
2246 if (stat(file, &sb)) {
2247 warnx("runtime file doesn't exist, creating it");
2248 if ((f = fopen(file, "w")) == NULL)
2249 err(1, "runtime");
2250 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2251 fclose(f);
2253 } else
2254 strlcpy(file, filename, sizeof file);
2256 if ((config = fopen(file, "r")) == NULL) {
2257 warn("config_parse: cannot open %s", filename);
2258 return;
2261 for (;;) {
2262 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2263 if (feof(config) || ferror(config))
2264 break;
2266 cp = line;
2267 cp += (long)strspn(cp, WS);
2268 if (cp[0] == '\0') {
2269 /* empty line */
2270 free(line);
2271 continue;
2274 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2275 startpage_add("invalid configuration file entry: %s",
2276 line);
2278 cp += (long)strspn(cp, WS);
2280 if ((val = strsep(&cp, "\0")) == NULL)
2281 break;
2283 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2284 handled = settings_add(var, val);
2285 if (handled == 0)
2286 startpage_add("invalid configuration file entry: %s=%s",
2287 var, val);
2289 free(line);
2292 fclose(config);
2295 char *
2296 js_ref_to_string(JSContextRef context, JSValueRef ref)
2298 char *s = NULL;
2299 size_t l;
2300 JSStringRef jsref;
2302 jsref = JSValueToStringCopy(context, ref, NULL);
2303 if (jsref == NULL)
2304 return (NULL);
2306 l = JSStringGetMaximumUTF8CStringSize(jsref);
2307 s = g_malloc(l);
2308 if (s)
2309 JSStringGetUTF8CString(jsref, s, l);
2310 JSStringRelease(jsref);
2312 return (s);
2315 void
2316 disable_hints(struct tab *t)
2318 bzero(t->hint_buf, sizeof t->hint_buf);
2319 bzero(t->hint_num, sizeof t->hint_num);
2320 run_script(t, "vimprobable_clear()");
2321 t->hints_on = 0;
2322 t->hint_mode = XT_HINT_NONE;
2325 void
2326 enable_hints(struct tab *t)
2328 bzero(t->hint_buf, sizeof t->hint_buf);
2329 run_script(t, "vimprobable_show_hints()");
2330 t->hints_on = 1;
2331 t->hint_mode = XT_HINT_NONE;
2334 #define XT_JS_OPEN ("open;")
2335 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2336 #define XT_JS_FIRE ("fire;")
2337 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2338 #define XT_JS_FOUND ("found;")
2339 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2342 run_script(struct tab *t, char *s)
2344 JSGlobalContextRef ctx;
2345 WebKitWebFrame *frame;
2346 JSStringRef str;
2347 JSValueRef val, exception;
2348 char *es, buf[128];
2350 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2351 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2353 frame = webkit_web_view_get_main_frame(t->wv);
2354 ctx = webkit_web_frame_get_global_context(frame);
2356 str = JSStringCreateWithUTF8CString(s);
2357 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2358 NULL, 0, &exception);
2359 JSStringRelease(str);
2361 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2362 if (val == NULL) {
2363 es = js_ref_to_string(ctx, exception);
2364 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2365 g_free(es);
2366 return (1);
2367 } else {
2368 es = js_ref_to_string(ctx, val);
2369 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2371 /* handle return value right here */
2372 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2373 disable_hints(t);
2374 marks_clear(t);
2375 load_uri(t, &es[XT_JS_OPEN_LEN]);
2378 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2379 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2380 &es[XT_JS_FIRE_LEN]);
2381 run_script(t, buf);
2382 disable_hints(t);
2385 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2386 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2387 disable_hints(t);
2390 g_free(es);
2393 return (0);
2397 hint(struct tab *t, struct karg *args)
2400 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2402 if (t->hints_on == 0)
2403 enable_hints(t);
2404 else
2405 disable_hints(t);
2407 return (0);
2410 void
2411 apply_style(struct tab *t)
2413 g_object_set(G_OBJECT(t->settings),
2414 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2418 userstyle(struct tab *t, struct karg *args)
2420 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2422 if (t->styled) {
2423 t->styled = 0;
2424 g_object_set(G_OBJECT(t->settings),
2425 "user-stylesheet-uri", NULL, (char *)NULL);
2426 } else {
2427 t->styled = 1;
2428 apply_style(t);
2430 return (0);
2434 * Doesn't work fully, due to the following bug:
2435 * https://bugs.webkit.org/show_bug.cgi?id=51747
2438 restore_global_history(void)
2440 char file[PATH_MAX];
2441 FILE *f;
2442 struct history *h;
2443 gchar *uri;
2444 gchar *title;
2445 const char delim[3] = {'\\', '\\', '\0'};
2447 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2449 if ((f = fopen(file, "r")) == NULL) {
2450 warnx("%s: fopen", __func__);
2451 return (1);
2454 for (;;) {
2455 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2456 if (feof(f) || ferror(f))
2457 break;
2459 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2460 if (feof(f) || ferror(f)) {
2461 free(uri);
2462 warnx("%s: broken history file\n", __func__);
2463 return (1);
2466 if (uri && strlen(uri) && title && strlen(title)) {
2467 webkit_web_history_item_new_with_data(uri, title);
2468 h = g_malloc(sizeof(struct history));
2469 h->uri = g_strdup(uri);
2470 h->title = g_strdup(title);
2471 RB_INSERT(history_list, &hl, h);
2472 completion_add_uri(h->uri);
2473 } else {
2474 warnx("%s: failed to restore history\n", __func__);
2475 free(uri);
2476 free(title);
2477 return (1);
2480 free(uri);
2481 free(title);
2482 uri = NULL;
2483 title = NULL;
2486 return (0);
2490 save_global_history_to_disk(struct tab *t)
2492 char file[PATH_MAX];
2493 FILE *f;
2494 struct history *h;
2496 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2498 if ((f = fopen(file, "w")) == NULL) {
2499 show_oops(t, "%s: global history file: %s",
2500 __func__, strerror(errno));
2501 return (1);
2504 RB_FOREACH_REVERSE(h, history_list, &hl) {
2505 if (h->uri && h->title)
2506 fprintf(f, "%s\n%s\n", h->uri, h->title);
2509 fclose(f);
2511 return (0);
2515 quit(struct tab *t, struct karg *args)
2517 if (save_global_history)
2518 save_global_history_to_disk(t);
2520 gtk_main_quit();
2522 return (1);
2525 void
2526 restore_sessions_list(void)
2528 DIR *sdir = NULL;
2529 struct dirent *dp = NULL;
2530 struct session *s;
2532 sdir = opendir(sessions_dir);
2533 if (sdir) {
2534 while ((dp = readdir(sdir)) != NULL)
2535 if (dp->d_type == DT_REG) {
2536 s = g_malloc(sizeof(struct session));
2537 s->name = g_strdup(dp->d_name);
2538 TAILQ_INSERT_TAIL(&sessions, s, entry);
2540 closedir(sdir);
2545 open_tabs(struct tab *t, struct karg *a)
2547 char file[PATH_MAX];
2548 FILE *f = NULL;
2549 char *uri = NULL;
2550 int rv = 1;
2551 struct tab *ti, *tt;
2553 if (a == NULL)
2554 goto done;
2556 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2557 if ((f = fopen(file, "r")) == NULL)
2558 goto done;
2560 ti = TAILQ_LAST(&tabs, tab_list);
2562 for (;;) {
2563 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2564 if (feof(f) || ferror(f))
2565 break;
2567 /* retrieve session name */
2568 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2569 strlcpy(named_session,
2570 &uri[strlen(XT_SAVE_SESSION_ID)],
2571 sizeof named_session);
2572 continue;
2575 if (uri && strlen(uri))
2576 create_new_tab(uri, NULL, 1, -1);
2578 free(uri);
2579 uri = NULL;
2582 /* close open tabs */
2583 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2584 for (;;) {
2585 tt = TAILQ_FIRST(&tabs);
2586 if (tt != ti) {
2587 delete_tab(tt);
2588 continue;
2590 delete_tab(tt);
2591 break;
2593 recalc_tabs();
2596 rv = 0;
2597 done:
2598 if (f)
2599 fclose(f);
2601 return (rv);
2605 restore_saved_tabs(void)
2607 char file[PATH_MAX];
2608 int unlink_file = 0;
2609 struct stat sb;
2610 struct karg a;
2611 int rv = 0;
2613 snprintf(file, sizeof file, "%s/%s",
2614 sessions_dir, XT_RESTART_TABS_FILE);
2615 if (stat(file, &sb) == -1)
2616 a.s = XT_SAVED_TABS_FILE;
2617 else {
2618 unlink_file = 1;
2619 a.s = XT_RESTART_TABS_FILE;
2622 a.i = XT_SES_DONOTHING;
2623 rv = open_tabs(NULL, &a);
2625 if (unlink_file)
2626 unlink(file);
2628 return (rv);
2632 save_tabs(struct tab *t, struct karg *a)
2634 char file[PATH_MAX];
2635 FILE *f;
2636 int num_tabs = 0, i;
2637 struct tab **stabs = NULL;
2639 if (a == NULL)
2640 return (1);
2641 if (a->s == NULL)
2642 snprintf(file, sizeof file, "%s/%s",
2643 sessions_dir, named_session);
2644 else
2645 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2647 if ((f = fopen(file, "w")) == NULL) {
2648 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2649 return (1);
2652 /* save session name */
2653 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2655 /* Save tabs, in the order they are arranged in the notebook. */
2656 num_tabs = sort_tabs_by_page_num(&stabs);
2658 for (i = 0; i < num_tabs; i++)
2659 if (stabs[i]) {
2660 if (get_uri(stabs[i]) != NULL)
2661 fprintf(f, "%s\n", get_uri(stabs[i]));
2662 else if (gtk_entry_get_text(GTK_ENTRY(
2663 stabs[i]->uri_entry)))
2664 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2665 stabs[i]->uri_entry)));
2668 g_free(stabs);
2670 /* try and make sure this gets to disk NOW. XXX Backup first? */
2671 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2672 show_oops(t, "May not have managed to save session: %s",
2673 strerror(errno));
2676 fclose(f);
2678 return (0);
2682 save_tabs_and_quit(struct tab *t, struct karg *args)
2684 struct karg a;
2686 a.s = NULL;
2687 save_tabs(t, &a);
2688 quit(t, NULL);
2690 return (1);
2694 run_page_script(struct tab *t, struct karg *args)
2696 const gchar *uri;
2697 char *tmp, script[PATH_MAX];
2699 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2700 if (tmp[0] == '\0') {
2701 show_oops(t, "no script specified");
2702 return (1);
2705 if ((uri = get_uri(t)) == NULL) {
2706 show_oops(t, "tab is empty, not running script");
2707 return (1);
2710 if (tmp[0] == '~')
2711 snprintf(script, sizeof script, "%s/%s",
2712 pwd->pw_dir, &tmp[1]);
2713 else
2714 strlcpy(script, tmp, sizeof script);
2716 switch (fork()) {
2717 case -1:
2718 show_oops(t, "can't fork to run script");
2719 return (1);
2720 /* NOTREACHED */
2721 case 0:
2722 break;
2723 default:
2724 return (0);
2727 /* child */
2728 execlp(script, script, uri, (void *)NULL);
2730 _exit(0);
2732 /* NOTREACHED */
2734 return (0);
2738 yank_uri(struct tab *t, struct karg *args)
2740 const gchar *uri;
2741 GtkClipboard *clipboard;
2743 if ((uri = get_uri(t)) == NULL)
2744 return (1);
2746 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2747 gtk_clipboard_set_text(clipboard, uri, -1);
2749 return (0);
2753 paste_uri(struct tab *t, struct karg *args)
2755 GtkClipboard *clipboard;
2756 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2757 gint len;
2758 gchar *p = NULL, *uri;
2760 /* try primary clipboard first */
2761 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2762 p = gtk_clipboard_wait_for_text(clipboard);
2764 /* if it failed get whatever text is in cut_buffer0 */
2765 if (p == NULL && xterm_workaround)
2766 if (gdk_property_get(gdk_get_default_root_window(),
2767 atom,
2768 gdk_atom_intern("STRING", FALSE),
2770 1024 * 1024 /* picked out of my butt */,
2771 FALSE,
2772 NULL,
2773 NULL,
2774 &len,
2775 (guchar **)&p)) {
2776 /* yes sir, we need to NUL the string */
2777 p[len] = '\0';
2780 if (p) {
2781 uri = p;
2782 while (*uri && isspace(*uri))
2783 uri++;
2784 if (strlen(uri) == 0) {
2785 show_oops(t, "empty paste buffer");
2786 goto done;
2788 if (guess_search == 0 && valid_url_type(uri)) {
2789 /* we can be clever and paste this in search box */
2790 show_oops(t, "not a valid URL");
2791 goto done;
2794 if (args->i == XT_PASTE_CURRENT_TAB)
2795 load_uri(t, uri);
2796 else if (args->i == XT_PASTE_NEW_TAB)
2797 create_new_tab(uri, NULL, 1, -1);
2800 done:
2801 if (p)
2802 g_free(p);
2804 return (0);
2807 gchar *
2808 find_domain(const gchar *s, int toplevel)
2810 SoupURI *uri;
2811 gchar *ret, *p;
2813 if (s == NULL)
2814 return (NULL);
2816 uri = soup_uri_new(s);
2818 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2819 return (NULL);
2822 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2823 if ((p = strrchr(uri->host, '.')) != NULL) {
2824 while(--p >= uri->host && *p != '.');
2825 p++;
2826 } else
2827 p = uri->host;
2828 } else
2829 p = uri->host;
2831 ret = g_strdup_printf(".%s", p);
2833 soup_uri_free(uri);
2835 return ret;
2839 toggle_cwl(struct tab *t, struct karg *args)
2841 struct domain *d;
2842 const gchar *uri;
2843 char *dom = NULL;
2844 int es;
2846 if (args == NULL)
2847 return (1);
2849 uri = get_uri(t);
2850 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2852 if (uri == NULL || dom == NULL ||
2853 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2854 show_oops(t, "Can't toggle domain in cookie white list");
2855 goto done;
2857 d = wl_find(dom, &c_wl);
2859 if (d == NULL)
2860 es = 0;
2861 else
2862 es = 1;
2864 if (args->i & XT_WL_TOGGLE)
2865 es = !es;
2866 else if ((args->i & XT_WL_ENABLE) && es != 1)
2867 es = 1;
2868 else if ((args->i & XT_WL_DISABLE) && es != 0)
2869 es = 0;
2871 if (es)
2872 /* enable cookies for domain */
2873 wl_add(dom, &c_wl, 0);
2874 else
2875 /* disable cookies for domain */
2876 RB_REMOVE(domain_list, &c_wl, d);
2878 if (args->i & XT_WL_RELOAD)
2879 webkit_web_view_reload(t->wv);
2881 done:
2882 g_free(dom);
2883 return (0);
2887 toggle_js(struct tab *t, struct karg *args)
2889 int es;
2890 const gchar *uri;
2891 struct domain *d;
2892 char *dom = NULL;
2894 if (args == NULL)
2895 return (1);
2897 g_object_get(G_OBJECT(t->settings),
2898 "enable-scripts", &es, (char *)NULL);
2899 if (args->i & XT_WL_TOGGLE)
2900 es = !es;
2901 else if ((args->i & XT_WL_ENABLE) && es != 1)
2902 es = 1;
2903 else if ((args->i & XT_WL_DISABLE) && es != 0)
2904 es = 0;
2905 else
2906 return (1);
2908 uri = get_uri(t);
2909 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2911 if (uri == NULL || dom == NULL ||
2912 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2913 show_oops(t, "Can't toggle domain in JavaScript white list");
2914 goto done;
2917 if (es) {
2918 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2919 wl_add(dom, &js_wl, 0 /* session */);
2920 } else {
2921 d = wl_find(dom, &js_wl);
2922 if (d)
2923 RB_REMOVE(domain_list, &js_wl, d);
2924 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2926 g_object_set(G_OBJECT(t->settings),
2927 "enable-scripts", es, (char *)NULL);
2928 g_object_set(G_OBJECT(t->settings),
2929 "javascript-can-open-windows-automatically", es, (char *)NULL);
2930 webkit_web_view_set_settings(t->wv, t->settings);
2932 if (args->i & XT_WL_RELOAD)
2933 webkit_web_view_reload(t->wv);
2934 done:
2935 if (dom)
2936 g_free(dom);
2937 return (0);
2940 void
2941 js_toggle_cb(GtkWidget *w, struct tab *t)
2943 struct karg a;
2945 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2946 toggle_cwl(t, &a);
2948 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2949 toggle_js(t, &a);
2953 toggle_src(struct tab *t, struct karg *args)
2955 gboolean mode;
2957 if (t == NULL)
2958 return (0);
2960 mode = webkit_web_view_get_view_source_mode(t->wv);
2961 webkit_web_view_set_view_source_mode(t->wv, !mode);
2962 webkit_web_view_reload(t->wv);
2964 return (0);
2967 void
2968 focus_webview(struct tab *t)
2970 if (t == NULL)
2971 return;
2973 /* only grab focus if we are visible */
2974 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2975 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2979 focus(struct tab *t, struct karg *args)
2981 if (t == NULL || args == NULL)
2982 return (1);
2984 if (show_url == 0)
2985 return (0);
2987 if (args->i == XT_FOCUS_URI)
2988 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2989 else if (args->i == XT_FOCUS_SEARCH)
2990 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2992 return (0);
2996 stats(struct tab *t, struct karg *args)
2998 char *page, *body, *s, line[64 * 1024];
2999 uint64_t line_count = 0;
3000 FILE *r_cookie_f;
3002 if (t == NULL)
3003 show_oops(NULL, "stats invalid parameters");
3005 line[0] = '\0';
3006 if (save_rejected_cookies) {
3007 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3008 for (;;) {
3009 s = fgets(line, sizeof line, r_cookie_f);
3010 if (s == NULL || feof(r_cookie_f) ||
3011 ferror(r_cookie_f))
3012 break;
3013 line_count++;
3015 fclose(r_cookie_f);
3016 snprintf(line, sizeof line,
3017 "<br/>Cookies blocked(*) total: %llu", line_count);
3018 } else
3019 show_oops(t, "Can't open blocked cookies file: %s",
3020 strerror(errno));
3023 body = g_strdup_printf(
3024 "Cookies blocked(*) this session: %llu"
3025 "%s"
3026 "<p><small><b>*</b> results vary based on settings</small></p>",
3027 blocked_cookies,
3028 line);
3030 page = get_html_page("Statistics", body, "", 0);
3031 g_free(body);
3033 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3034 g_free(page);
3036 return (0);
3040 marco(struct tab *t, struct karg *args)
3042 char *page, line[64 * 1024];
3043 int len;
3045 if (t == NULL)
3046 show_oops(NULL, "marco invalid parameters");
3048 line[0] = '\0';
3049 snprintf(line, sizeof line, "%s", marco_message(&len));
3051 page = get_html_page("Marco Sez...", line, "", 0);
3053 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3054 g_free(page);
3056 return (0);
3060 blank(struct tab *t, struct karg *args)
3062 if (t == NULL)
3063 show_oops(NULL, "blank invalid parameters");
3065 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3067 return (0);
3071 about(struct tab *t, struct karg *args)
3073 char *page, *body;
3075 if (t == NULL)
3076 show_oops(NULL, "about invalid parameters");
3078 body = g_strdup_printf("<b>Version: %s</b>"
3079 #ifdef XXXTERM_BUILDSTR
3080 "<br><b>Build: %s</b>"
3081 #endif
3082 "<p>"
3083 "Authors:"
3084 "<ul>"
3085 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3086 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3087 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3088 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3089 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3090 "</ul>"
3091 "Copyrights and licenses can be found on the XXXTerm "
3092 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3093 "</p>",
3094 #ifdef XXXTERM_BUILDSTR
3095 version, XXXTERM_BUILDSTR
3096 #else
3097 version
3098 #endif
3101 page = get_html_page("About", body, "", 0);
3102 g_free(body);
3104 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3105 g_free(page);
3107 return (0);
3111 help(struct tab *t, struct karg *args)
3113 char *page, *head, *body;
3115 if (t == NULL)
3116 show_oops(NULL, "help invalid parameters");
3118 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3119 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3120 "</head>\n";
3121 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3122 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3123 "cgi-bin/man-cgi?xxxterm</a>";
3125 page = get_html_page(XT_NAME, body, head, FALSE);
3127 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3128 g_free(page);
3130 return (0);
3134 startpage(struct tab *t, struct karg *args)
3136 char *page, *body, *b;
3137 struct sp *s;
3139 if (t == NULL)
3140 show_oops(NULL, "startpage invalid parameters");
3142 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3144 TAILQ_FOREACH(s, &spl, entry) {
3145 b = body;
3146 body = g_strdup_printf("%s%s<br>", body, s->line);
3147 g_free(b);
3150 page = get_html_page("Startup Exception", body, "", 0);
3151 g_free(body);
3153 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3154 g_free(page);
3156 return (0);
3159 void
3160 startpage_add(const char *fmt, ...)
3162 va_list ap;
3163 char *msg;
3164 struct sp *s;
3166 if (fmt == NULL)
3167 return;
3169 va_start(ap, fmt);
3170 if (vasprintf(&msg, fmt, ap) == -1)
3171 errx(1, "startpage_add failed");
3172 va_end(ap);
3174 s = g_malloc0(sizeof *s);
3175 s->line = msg;
3177 TAILQ_INSERT_TAIL(&spl, s, entry);
3181 * update all favorite tabs apart from one. Pass NULL if
3182 * you want to update all.
3184 void
3185 update_favorite_tabs(struct tab *apart_from)
3187 struct tab *t;
3188 if (!updating_fl_tabs) {
3189 updating_fl_tabs = 1; /* stop infinite recursion */
3190 TAILQ_FOREACH(t, &tabs, entry)
3191 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3192 && (t != apart_from))
3193 xtp_page_fl(t, NULL);
3194 updating_fl_tabs = 0;
3198 /* show a list of favorites (bookmarks) */
3200 xtp_page_fl(struct tab *t, struct karg *args)
3202 char file[PATH_MAX];
3203 FILE *f;
3204 char *uri = NULL, *title = NULL;
3205 size_t len, lineno = 0;
3206 int i, failed = 0;
3207 char *body, *tmp, *page = NULL;
3208 const char delim[3] = {'\\', '\\', '\0'};
3210 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3212 if (t == NULL)
3213 warn("%s: bad param", __func__);
3215 /* new session key */
3216 if (!updating_fl_tabs)
3217 generate_xtp_session_key(&fl_session_key);
3219 /* open favorites */
3220 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3221 if ((f = fopen(file, "r")) == NULL) {
3222 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3223 return (1);
3226 /* body */
3227 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3228 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3229 "<th style='width: 40px'>Rm</th></tr>\n");
3231 for (i = 1;;) {
3232 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3233 break;
3234 if (strlen(title) == 0 || title[0] == '#') {
3235 free(title);
3236 title = NULL;
3237 continue;
3240 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3241 if (feof(f) || ferror(f)) {
3242 show_oops(t, "favorites file corrupt");
3243 failed = 1;
3244 break;
3247 tmp = body;
3248 body = g_strdup_printf("%s<tr>"
3249 "<td>%d</td>"
3250 "<td><a href='%s'>%s</a></td>"
3251 "<td style='text-align: center'>"
3252 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3253 "</tr>\n",
3254 body, i, uri, title,
3255 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3257 g_free(tmp);
3259 free(uri);
3260 uri = NULL;
3261 free(title);
3262 title = NULL;
3263 i++;
3265 fclose(f);
3267 /* if none, say so */
3268 if (i == 1) {
3269 tmp = body;
3270 body = g_strdup_printf("%s<tr>"
3271 "<td colspan='3' style='text-align: center'>"
3272 "No favorites - To add one use the 'favadd' command."
3273 "</td></tr>", body);
3274 g_free(tmp);
3277 tmp = body;
3278 body = g_strdup_printf("%s</table>", body);
3279 g_free(tmp);
3281 if (uri)
3282 free(uri);
3283 if (title)
3284 free(title);
3286 /* render */
3287 if (!failed) {
3288 page = get_html_page("Favorites", body, "", 1);
3289 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3290 g_free(page);
3293 update_favorite_tabs(t);
3295 if (body)
3296 g_free(body);
3298 return (failed);
3301 void
3302 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3303 size_t cert_count, char *title)
3305 gnutls_datum_t cinfo;
3306 char *tmp, *body;
3307 int i;
3309 body = g_strdup("");
3311 for (i = 0; i < cert_count; i++) {
3312 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3313 &cinfo))
3314 return;
3316 tmp = body;
3317 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3318 body, i, cinfo.data);
3319 gnutls_free(cinfo.data);
3320 g_free(tmp);
3323 tmp = get_html_page(title, body, "", 0);
3324 g_free(body);
3326 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3327 g_free(tmp);
3331 ca_cmd(struct tab *t, struct karg *args)
3333 FILE *f = NULL;
3334 int rv = 1, certs = 0, certs_read;
3335 struct stat sb;
3336 gnutls_datum_t dt;
3337 gnutls_x509_crt_t *c = NULL;
3338 char *certs_buf = NULL, *s;
3340 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3341 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3342 return (1);
3345 if (fstat(fileno(f), &sb) == -1) {
3346 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3347 goto done;
3350 certs_buf = g_malloc(sb.st_size + 1);
3351 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3352 show_oops(t, "Can't read CA file: %s", strerror(errno));
3353 goto done;
3355 certs_buf[sb.st_size] = '\0';
3357 s = certs_buf;
3358 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3359 certs++;
3360 s += strlen("BEGIN CERTIFICATE");
3363 bzero(&dt, sizeof dt);
3364 dt.data = (unsigned char *)certs_buf;
3365 dt.size = sb.st_size;
3366 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3367 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3368 GNUTLS_X509_FMT_PEM, 0);
3369 if (certs_read <= 0) {
3370 show_oops(t, "No cert(s) available");
3371 goto done;
3373 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3374 done:
3375 if (c)
3376 g_free(c);
3377 if (certs_buf)
3378 g_free(certs_buf);
3379 if (f)
3380 fclose(f);
3382 return (rv);
3386 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
3387 size_t domain_sz)
3389 SoupURI *su = NULL;
3390 struct addrinfo hints, *res = NULL, *ai;
3391 int rv = -1, s = -1, on, error;
3392 char port[8];
3393 static gchar myerror[256]; /* this is not thread safe */
3395 myerror[0] = '\0';
3396 *error_str = myerror;
3397 if (uri && !g_str_has_prefix(uri, "https://")) {
3398 *error_str = "invalid URI";
3399 goto done;
3402 su = soup_uri_new(uri);
3403 if (su == NULL) {
3404 *error_str = "invalid soup URI";
3405 goto done;
3407 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3408 *error_str = "invalid HTTPS URI";
3409 goto done;
3412 snprintf(port, sizeof port, "%d", su->port);
3413 bzero(&hints, sizeof(struct addrinfo));
3414 hints.ai_flags = AI_CANONNAME;
3415 hints.ai_family = AF_UNSPEC;
3416 hints.ai_socktype = SOCK_STREAM;
3418 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3419 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
3420 gai_strerror(errno));
3421 goto done;
3424 for (ai = res; ai; ai = ai->ai_next) {
3425 if (s != -1) {
3426 close(s);
3427 s = -1;
3430 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3431 continue;
3432 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3433 if (s == -1)
3434 continue;
3435 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3436 sizeof(on)) == -1)
3437 continue;
3438 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3439 break;
3441 if (s == -1) {
3442 snprintf(myerror, sizeof myerror,
3443 "could not obtain certificates from: %s",
3444 su->host);
3445 goto done;
3448 if (domain)
3449 strlcpy(domain, su->host, domain_sz);
3450 rv = s;
3451 done:
3452 if (su)
3453 soup_uri_free(su);
3454 if (res)
3455 freeaddrinfo(res);
3456 if (rv == -1 && s != -1)
3457 close(s);
3459 return (rv);
3463 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3465 if (gsession)
3466 gnutls_deinit(gsession);
3467 if (xcred)
3468 gnutls_certificate_free_credentials(xcred);
3470 return (0);
3474 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
3475 gnutls_certificate_credentials_t *xc)
3477 gnutls_certificate_credentials_t xcred;
3478 gnutls_session_t gsession;
3479 int rv = 1;
3480 static gchar myerror[1024]; /* this is not thread safe */
3482 if (gs == NULL || xc == NULL)
3483 goto done;
3485 myerror[0] = '\0';
3486 *gs = NULL;
3487 *xc = NULL;
3489 gnutls_certificate_allocate_credentials(&xcred);
3490 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3491 GNUTLS_X509_FMT_PEM);
3493 gnutls_init(&gsession, GNUTLS_CLIENT);
3494 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3495 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3496 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3497 if ((rv = gnutls_handshake(gsession)) < 0) {
3498 snprintf(myerror, sizeof myerror,
3499 "gnutls_handshake failed %d fatal %d %s",
3501 gnutls_error_is_fatal(rv),
3502 gnutls_strerror_name(rv));
3503 stop_tls(gsession, xcred);
3504 goto done;
3507 gnutls_credentials_type_t cred;
3508 cred = gnutls_auth_get_type(gsession);
3509 if (cred != GNUTLS_CRD_CERTIFICATE) {
3510 snprintf(myerror, sizeof myerror,
3511 "gnutls_auth_get_type failed %d",
3512 (int)cred);
3513 stop_tls(gsession, xcred);
3514 goto done;
3517 *gs = gsession;
3518 *xc = xcred;
3519 rv = 0;
3520 done:
3521 *error_str = myerror;
3522 return (rv);
3526 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3527 size_t *cert_count)
3529 unsigned int len;
3530 const gnutls_datum_t *cl;
3531 gnutls_x509_crt_t *all_certs;
3532 int i, rv = 1;
3534 if (certs == NULL || cert_count == NULL)
3535 goto done;
3536 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3537 goto done;
3538 cl = gnutls_certificate_get_peers(gsession, &len);
3539 if (len == 0)
3540 goto done;
3542 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3543 for (i = 0; i < len; i++) {
3544 gnutls_x509_crt_init(&all_certs[i]);
3545 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3546 GNUTLS_X509_FMT_PEM < 0)) {
3547 g_free(all_certs);
3548 goto done;
3552 *certs = all_certs;
3553 *cert_count = len;
3554 rv = 0;
3555 done:
3556 return (rv);
3559 void
3560 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3562 int i;
3564 for (i = 0; i < cert_count; i++)
3565 gnutls_x509_crt_deinit(certs[i]);
3566 g_free(certs);
3569 void
3570 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3572 GdkColor c_text, c_base;
3574 gdk_color_parse(text, &c_text);
3575 gdk_color_parse(base, &c_base);
3577 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3578 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3579 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3580 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3582 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3583 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3584 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3585 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3588 void
3589 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3590 size_t cert_count, char *domain)
3592 size_t cert_buf_sz;
3593 char cert_buf[64 * 1024], file[PATH_MAX];
3594 int i;
3595 FILE *f;
3596 GdkColor color;
3598 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3599 return;
3601 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3602 if ((f = fopen(file, "w")) == NULL) {
3603 show_oops(t, "Can't create cert file %s %s",
3604 file, strerror(errno));
3605 return;
3608 for (i = 0; i < cert_count; i++) {
3609 cert_buf_sz = sizeof cert_buf;
3610 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3611 cert_buf, &cert_buf_sz)) {
3612 show_oops(t, "gnutls_x509_crt_export failed");
3613 goto done;
3615 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3616 show_oops(t, "Can't write certs: %s", strerror(errno));
3617 goto done;
3621 /* not the best spot but oh well */
3622 gdk_color_parse("lightblue", &color);
3623 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3624 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3625 done:
3626 fclose(f);
3629 enum cert_trust {
3630 CERT_LOCAL,
3631 CERT_TRUSTED,
3632 CERT_UNTRUSTED,
3633 CERT_BAD
3636 enum cert_trust
3637 load_compare_cert(const gchar *uri, const gchar **error_str)
3639 char domain[8182], file[PATH_MAX];
3640 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3641 int s = -1, i;
3642 unsigned int error = 0;
3643 FILE *f = NULL;
3644 size_t cert_buf_sz, cert_count;
3645 enum cert_trust rv = CERT_UNTRUSTED;
3646 static gchar serr[80]; /* this isn't thread safe */
3647 gnutls_session_t gsession;
3648 gnutls_x509_crt_t *certs;
3649 gnutls_certificate_credentials_t xcred;
3651 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3653 serr[0] = '\0';
3654 *error_str = serr;
3655 if ((s = connect_socket_from_uri(uri, error_str, domain,
3656 sizeof domain)) == -1)
3657 return (rv);
3659 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3661 /* go ssl/tls */
3662 if (start_tls(error_str, s, &gsession, &xcred))
3663 goto done;
3664 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3666 /* verify certs in case cert file doesn't exist */
3667 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3668 GNUTLS_E_SUCCESS) {
3669 *error_str = "Invalid certificates";
3670 goto done;
3673 /* get certs */
3674 if (get_connection_certs(gsession, &certs, &cert_count)) {
3675 *error_str = "Can't get connection certificates";
3676 goto done;
3679 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3680 if ((f = fopen(file, "r")) == NULL) {
3681 if (!error)
3682 rv = CERT_TRUSTED;
3683 goto freeit;
3686 for (i = 0; i < cert_count; i++) {
3687 cert_buf_sz = sizeof cert_buf;
3688 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3689 cert_buf, &cert_buf_sz)) {
3690 goto freeit;
3692 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3693 rv = CERT_BAD; /* critical */
3694 goto freeit;
3696 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3697 rv = CERT_BAD; /* critical */
3698 goto freeit;
3700 rv = CERT_LOCAL;
3703 freeit:
3704 if (f)
3705 fclose(f);
3706 free_connection_certs(certs, cert_count);
3707 done:
3708 /* we close the socket first for speed */
3709 if (s != -1)
3710 close(s);
3712 /* only complain if we didn't save it locally */
3713 if (error && rv != CERT_LOCAL) {
3714 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3715 if (error & GNUTLS_CERT_INVALID)
3716 strlcat(serr, "invalid, ", sizeof serr);
3717 if (error & GNUTLS_CERT_REVOKED)
3718 strlcat(serr, "revoked, ", sizeof serr);
3719 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3720 strlcat(serr, "signer not found, ", sizeof serr);
3721 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3722 strlcat(serr, "not signed by CA, ", sizeof serr);
3723 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3724 strlcat(serr, "insecure algorithm, ", sizeof serr);
3725 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3726 strlcat(serr, "not activated, ", sizeof serr);
3727 if (error & GNUTLS_CERT_EXPIRED)
3728 strlcat(serr, "expired, ", sizeof serr);
3729 for (i = strlen(serr) - 1; i > 0; i--)
3730 if (serr[i] == ',') {
3731 serr[i] = '\0';
3732 break;
3734 *error_str = serr;
3737 stop_tls(gsession, xcred);
3739 return (rv);
3743 cert_cmd(struct tab *t, struct karg *args)
3745 const gchar *uri, *error_str = NULL;
3746 char domain[8182];
3747 int s = -1;
3748 size_t cert_count;
3749 gnutls_session_t gsession;
3750 gnutls_x509_crt_t *certs;
3751 gnutls_certificate_credentials_t xcred;
3753 if (t == NULL)
3754 return (1);
3756 if (ssl_ca_file == NULL) {
3757 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3758 return (1);
3761 if ((uri = get_uri(t)) == NULL) {
3762 show_oops(t, "Invalid URI");
3763 return (1);
3766 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3767 sizeof domain)) == -1) {
3768 show_oops(t, "%s", error_str);
3769 return (1);
3772 /* go ssl/tls */
3773 if (start_tls(&error_str, s, &gsession, &xcred))
3774 goto done;
3776 /* get certs */
3777 if (get_connection_certs(gsession, &certs, &cert_count)) {
3778 show_oops(t, "get_connection_certs failed");
3779 goto done;
3782 if (args->i & XT_SHOW)
3783 show_certs(t, certs, cert_count, "Certificate Chain");
3784 else if (args->i & XT_SAVE)
3785 save_certs(t, certs, cert_count, domain);
3787 free_connection_certs(certs, cert_count);
3788 done:
3789 /* we close the socket first for speed */
3790 if (s != -1)
3791 close(s);
3792 stop_tls(gsession, xcred);
3793 if (error_str)
3794 show_oops(t, "%s", error_str);
3795 return (0);
3799 remove_cookie(int index)
3801 int i, rv = 1;
3802 GSList *cf;
3803 SoupCookie *c;
3805 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3807 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3809 for (i = 1; cf; cf = cf->next, i++) {
3810 if (i != index)
3811 continue;
3812 c = cf->data;
3813 print_cookie("remove cookie", c);
3814 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3815 rv = 0;
3816 break;
3819 soup_cookies_free(cf);
3821 return (rv);
3825 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3827 struct domain *d;
3828 char *tmp, *body;
3830 body = g_strdup("");
3832 /* p list */
3833 if (args->i & XT_WL_PERSISTENT) {
3834 tmp = body;
3835 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3836 g_free(tmp);
3837 RB_FOREACH(d, domain_list, wl) {
3838 if (d->handy == 0)
3839 continue;
3840 tmp = body;
3841 body = g_strdup_printf("%s%s<br/>", body, d->d);
3842 g_free(tmp);
3846 /* s list */
3847 if (args->i & XT_WL_SESSION) {
3848 tmp = body;
3849 body = g_strdup_printf("%s<h2>Session</h2>", body);
3850 g_free(tmp);
3851 RB_FOREACH(d, domain_list, wl) {
3852 if (d->handy == 1)
3853 continue;
3854 tmp = body;
3855 body = g_strdup_printf("%s%s<br/>", body, d->d);
3856 g_free(tmp);
3860 tmp = get_html_page(title, body, "", 0);
3861 g_free(body);
3862 if (wl == &js_wl)
3863 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3864 else
3865 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3866 g_free(tmp);
3867 return (0);
3871 wl_save(struct tab *t, struct karg *args, int js)
3873 char file[PATH_MAX];
3874 FILE *f;
3875 char *line = NULL, *lt = NULL, *dom = NULL;
3876 size_t linelen;
3877 const gchar *uri;
3878 struct karg a;
3879 struct domain *d;
3880 GSList *cf;
3881 SoupCookie *ci, *c;
3883 if (t == NULL || args == NULL)
3884 return (1);
3886 if (runtime_settings[0] == '\0')
3887 return (1);
3889 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3890 if ((f = fopen(file, "r+")) == NULL)
3891 return (1);
3893 uri = get_uri(t);
3894 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3895 if (uri == NULL || dom == NULL ||
3896 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3897 show_oops(t, "Can't add domain to %s white list",
3898 js ? "JavaScript" : "cookie");
3899 goto done;
3902 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3904 while (!feof(f)) {
3905 line = fparseln(f, &linelen, NULL, NULL, 0);
3906 if (line == NULL)
3907 continue;
3908 if (!strcmp(line, lt))
3909 goto done;
3910 free(line);
3911 line = NULL;
3914 fprintf(f, "%s\n", lt);
3916 a.i = XT_WL_ENABLE;
3917 a.i |= args->i;
3918 if (js) {
3919 d = wl_find(dom, &js_wl);
3920 if (!d) {
3921 settings_add("js_wl", dom);
3922 d = wl_find(dom, &js_wl);
3924 toggle_js(t, &a);
3925 } else {
3926 d = wl_find(dom, &c_wl);
3927 if (!d) {
3928 settings_add("cookie_wl", dom);
3929 d = wl_find(dom, &c_wl);
3931 toggle_cwl(t, &a);
3933 /* find and add to persistent jar */
3934 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3935 for (;cf; cf = cf->next) {
3936 ci = cf->data;
3937 if (!strcmp(dom, ci->domain) ||
3938 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3939 c = soup_cookie_copy(ci);
3940 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3943 soup_cookies_free(cf);
3945 if (d)
3946 d->handy = 1;
3948 done:
3949 if (line)
3950 free(line);
3951 if (dom)
3952 g_free(dom);
3953 if (lt)
3954 g_free(lt);
3955 fclose(f);
3957 return (0);
3961 js_show_wl(struct tab *t, struct karg *args)
3963 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3964 wl_show(t, args, "JavaScript White List", &js_wl);
3966 return (0);
3970 cookie_show_wl(struct tab *t, struct karg *args)
3972 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3973 wl_show(t, args, "Cookie White List", &c_wl);
3975 return (0);
3979 cookie_cmd(struct tab *t, struct karg *args)
3981 if (args->i & XT_SHOW)
3982 wl_show(t, args, "Cookie White List", &c_wl);
3983 else if (args->i & XT_WL_TOGGLE) {
3984 args->i |= XT_WL_RELOAD;
3985 toggle_cwl(t, args);
3986 } else if (args->i & XT_SAVE) {
3987 args->i |= XT_WL_RELOAD;
3988 wl_save(t, args, 0);
3989 } else if (args->i & XT_DELETE)
3990 show_oops(t, "'cookie delete' currently unimplemented");
3992 return (0);
3996 js_cmd(struct tab *t, struct karg *args)
3998 if (args->i & XT_SHOW)
3999 wl_show(t, args, "JavaScript White List", &js_wl);
4000 else if (args->i & XT_SAVE) {
4001 args->i |= XT_WL_RELOAD;
4002 wl_save(t, args, 1);
4003 } else if (args->i & XT_WL_TOGGLE) {
4004 args->i |= XT_WL_RELOAD;
4005 toggle_js(t, args);
4006 } else if (args->i & XT_DELETE)
4007 show_oops(t, "'js delete' currently unimplemented");
4009 return (0);
4013 toplevel_cmd(struct tab *t, struct karg *args)
4015 js_toggle_cb(t->js_toggle, t);
4017 return (0);
4021 add_favorite(struct tab *t, struct karg *args)
4023 char file[PATH_MAX];
4024 FILE *f;
4025 char *line = NULL;
4026 size_t urilen, linelen;
4027 const gchar *uri, *title;
4029 if (t == NULL)
4030 return (1);
4032 /* don't allow adding of xtp pages to favorites */
4033 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4034 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4035 return (1);
4038 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4039 if ((f = fopen(file, "r+")) == NULL) {
4040 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4041 return (1);
4044 title = get_title(t, FALSE);
4045 uri = get_uri(t);
4047 if (title == NULL || uri == NULL) {
4048 show_oops(t, "can't add page to favorites");
4049 goto done;
4052 urilen = strlen(uri);
4054 for (;;) {
4055 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4056 if (feof(f) || ferror(f))
4057 break;
4059 if (linelen == urilen && !strcmp(line, uri))
4060 goto done;
4062 free(line);
4063 line = NULL;
4066 fprintf(f, "\n%s\n%s", title, uri);
4067 done:
4068 if (line)
4069 free(line);
4070 fclose(f);
4072 update_favorite_tabs(NULL);
4074 return (0);
4078 can_go_back_for_real(struct tab *t)
4080 int i;
4081 WebKitWebHistoryItem *item;
4082 const gchar *uri;
4084 if (t == NULL)
4085 return (FALSE);
4087 /* rely on webkit to make sure we can go backward when on an about page */
4088 uri = get_uri(t);
4089 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4090 return (webkit_web_view_can_go_back(t->wv));
4092 /* the back/forwars list is stupid so help determine if we can go back */
4093 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4094 item != NULL;
4095 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4096 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4097 return (TRUE);
4100 return (FALSE);
4104 can_go_forward_for_real(struct tab *t)
4106 int i;
4107 WebKitWebHistoryItem *item;
4108 const gchar *uri;
4110 if (t == NULL)
4111 return (FALSE);
4113 /* rely on webkit to make sure we can go forward when on an about page */
4114 uri = get_uri(t);
4115 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4116 return (webkit_web_view_can_go_forward(t->wv));
4118 /* the back/forwars list is stupid so help selecting a different item */
4119 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4120 item != NULL;
4121 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4122 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4123 return (TRUE);
4126 return (FALSE);
4129 void
4130 go_back_for_real(struct tab *t)
4132 int i;
4133 WebKitWebHistoryItem *item;
4134 const gchar *uri;
4136 if (t == NULL)
4137 return;
4139 uri = get_uri(t);
4140 if (uri == NULL) {
4141 webkit_web_view_go_back(t->wv);
4142 return;
4144 /* the back/forwars list is stupid so help selecting a different item */
4145 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4146 item != NULL;
4147 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4148 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4149 webkit_web_view_go_to_back_forward_item(t->wv, item);
4150 break;
4155 void
4156 go_forward_for_real(struct tab *t)
4158 int i;
4159 WebKitWebHistoryItem *item;
4160 const gchar *uri;
4162 if (t == NULL)
4163 return;
4165 uri = get_uri(t);
4166 if (uri == NULL) {
4167 webkit_web_view_go_forward(t->wv);
4168 return;
4170 /* the back/forwars list is stupid so help selecting a different item */
4171 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4172 item != NULL;
4173 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4174 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4175 webkit_web_view_go_to_back_forward_item(t->wv, item);
4176 break;
4182 navaction(struct tab *t, struct karg *args)
4184 WebKitWebHistoryItem *item;
4185 WebKitWebFrame *frame;
4187 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4188 t->tab_id, args->i);
4190 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4191 if (t->item) {
4192 if (args->i == XT_NAV_BACK)
4193 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4194 else
4195 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4196 if (item == NULL)
4197 return (XT_CB_PASSTHROUGH);
4198 webkit_web_view_go_to_back_forward_item(t->wv, item);
4199 t->item = NULL;
4200 return (XT_CB_PASSTHROUGH);
4203 switch (args->i) {
4204 case XT_NAV_BACK:
4205 marks_clear(t);
4206 go_back_for_real(t);
4207 break;
4208 case XT_NAV_FORWARD:
4209 marks_clear(t);
4210 go_forward_for_real(t);
4211 break;
4212 case XT_NAV_RELOAD:
4213 frame = webkit_web_view_get_main_frame(t->wv);
4214 webkit_web_frame_reload(frame);
4215 break;
4217 return (XT_CB_PASSTHROUGH);
4221 move(struct tab *t, struct karg *args)
4223 GtkAdjustment *adjust;
4224 double pi, si, pos, ps, upper, lower, max;
4225 double percent;
4227 switch (args->i) {
4228 case XT_MOVE_DOWN:
4229 case XT_MOVE_UP:
4230 case XT_MOVE_BOTTOM:
4231 case XT_MOVE_TOP:
4232 case XT_MOVE_PAGEDOWN:
4233 case XT_MOVE_PAGEUP:
4234 case XT_MOVE_HALFDOWN:
4235 case XT_MOVE_HALFUP:
4236 case XT_MOVE_PERCENT:
4237 adjust = t->adjust_v;
4238 break;
4239 default:
4240 adjust = t->adjust_h;
4241 break;
4244 pos = gtk_adjustment_get_value(adjust);
4245 ps = gtk_adjustment_get_page_size(adjust);
4246 upper = gtk_adjustment_get_upper(adjust);
4247 lower = gtk_adjustment_get_lower(adjust);
4248 si = gtk_adjustment_get_step_increment(adjust);
4249 pi = gtk_adjustment_get_page_increment(adjust);
4250 max = upper - ps;
4252 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4253 "max %f si %f pi %f\n",
4254 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4255 pos, ps, upper, lower, max, si, pi);
4257 switch (args->i) {
4258 case XT_MOVE_DOWN:
4259 case XT_MOVE_RIGHT:
4260 pos += si;
4261 gtk_adjustment_set_value(adjust, MIN(pos, max));
4262 break;
4263 case XT_MOVE_UP:
4264 case XT_MOVE_LEFT:
4265 pos -= si;
4266 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4267 break;
4268 case XT_MOVE_BOTTOM:
4269 case XT_MOVE_FARRIGHT:
4270 gtk_adjustment_set_value(adjust, max);
4271 break;
4272 case XT_MOVE_TOP:
4273 case XT_MOVE_FARLEFT:
4274 gtk_adjustment_set_value(adjust, lower);
4275 break;
4276 case XT_MOVE_PAGEDOWN:
4277 pos += pi;
4278 gtk_adjustment_set_value(adjust, MIN(pos, max));
4279 break;
4280 case XT_MOVE_PAGEUP:
4281 pos -= pi;
4282 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4283 break;
4284 case XT_MOVE_HALFDOWN:
4285 pos += pi / 2;
4286 gtk_adjustment_set_value(adjust, MIN(pos, max));
4287 break;
4288 case XT_MOVE_HALFUP:
4289 pos -= pi / 2;
4290 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4291 break;
4292 case XT_MOVE_PERCENT:
4293 percent = atoi(args->s) / 100.0;
4294 pos = max * percent;
4295 if (pos < 0.0 || pos > max)
4296 break;
4297 gtk_adjustment_set_value(adjust, pos);
4298 break;
4299 default:
4300 return (XT_CB_PASSTHROUGH);
4303 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4305 return (XT_CB_HANDLED);
4308 void
4309 url_set_visibility(void)
4311 struct tab *t;
4313 TAILQ_FOREACH(t, &tabs, entry)
4314 if (show_url == 0) {
4315 gtk_widget_hide(t->toolbar);
4316 focus_webview(t);
4317 } else
4318 gtk_widget_show(t->toolbar);
4321 void
4322 notebook_tab_set_visibility(void)
4324 if (show_tabs == 0) {
4325 gtk_widget_hide(tab_bar);
4326 gtk_notebook_set_show_tabs(notebook, FALSE);
4327 } else {
4328 if (tab_style == XT_TABS_NORMAL) {
4329 gtk_widget_hide(tab_bar);
4330 gtk_notebook_set_show_tabs(notebook, TRUE);
4331 } else if (tab_style == XT_TABS_COMPACT) {
4332 gtk_widget_show(tab_bar);
4333 gtk_notebook_set_show_tabs(notebook, FALSE);
4338 void
4339 statusbar_set_visibility(void)
4341 struct tab *t;
4343 TAILQ_FOREACH(t, &tabs, entry)
4344 if (show_statusbar == 0) {
4345 gtk_widget_hide(t->statusbar_box);
4346 focus_webview(t);
4347 } else
4348 gtk_widget_show(t->statusbar_box);
4351 void
4352 url_set(struct tab *t, int enable_url_entry)
4354 GdkPixbuf *pixbuf;
4355 int progress;
4357 show_url = enable_url_entry;
4359 if (enable_url_entry) {
4360 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4361 GTK_ENTRY_ICON_PRIMARY, NULL);
4362 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4363 } else {
4364 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4365 GTK_ENTRY_ICON_PRIMARY);
4366 progress =
4367 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4368 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4369 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4370 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4371 progress);
4376 fullscreen(struct tab *t, struct karg *args)
4378 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4380 if (t == NULL)
4381 return (XT_CB_PASSTHROUGH);
4383 if (show_url == 0) {
4384 url_set(t, 1);
4385 show_tabs = 1;
4386 } else {
4387 url_set(t, 0);
4388 show_tabs = 0;
4391 url_set_visibility();
4392 notebook_tab_set_visibility();
4394 return (XT_CB_HANDLED);
4398 statustoggle(struct tab *t, struct karg *args)
4400 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4402 if (show_statusbar == 1) {
4403 show_statusbar = 0;
4404 statusbar_set_visibility();
4405 } else if (show_statusbar == 0) {
4406 show_statusbar = 1;
4407 statusbar_set_visibility();
4409 return (XT_CB_HANDLED);
4413 urlaction(struct tab *t, struct karg *args)
4415 int rv = XT_CB_HANDLED;
4417 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4419 if (t == NULL)
4420 return (XT_CB_PASSTHROUGH);
4422 switch (args->i) {
4423 case XT_URL_SHOW:
4424 if (show_url == 0) {
4425 url_set(t, 1);
4426 url_set_visibility();
4428 break;
4429 case XT_URL_HIDE:
4430 if (show_url == 1) {
4431 url_set(t, 0);
4432 url_set_visibility();
4434 break;
4436 return (rv);
4440 tabaction(struct tab *t, struct karg *args)
4442 int rv = XT_CB_HANDLED;
4443 char *url = args->s;
4444 struct undo *u;
4445 struct tab *tt;
4447 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4449 if (t == NULL)
4450 return (XT_CB_PASSTHROUGH);
4452 switch (args->i) {
4453 case XT_TAB_NEW:
4454 if (strlen(url) > 0)
4455 create_new_tab(url, NULL, 1, args->precount);
4456 else
4457 create_new_tab(NULL, NULL, 1, args->precount);
4458 break;
4459 case XT_TAB_DELETE:
4460 if (args->precount < 0)
4461 delete_tab(t);
4462 else
4463 TAILQ_FOREACH(tt, &tabs, entry)
4464 if (tt->tab_id == args->precount - 1) {
4465 delete_tab(tt);
4466 break;
4468 break;
4469 case XT_TAB_DELQUIT:
4470 if (gtk_notebook_get_n_pages(notebook) > 1)
4471 delete_tab(t);
4472 else
4473 quit(t, args);
4474 break;
4475 case XT_TAB_OPEN:
4476 if (strlen(url) > 0)
4478 else {
4479 rv = XT_CB_PASSTHROUGH;
4480 goto done;
4482 load_uri(t, url);
4483 break;
4484 case XT_TAB_SHOW:
4485 if (show_tabs == 0) {
4486 show_tabs = 1;
4487 notebook_tab_set_visibility();
4489 break;
4490 case XT_TAB_HIDE:
4491 if (show_tabs == 1) {
4492 show_tabs = 0;
4493 notebook_tab_set_visibility();
4495 break;
4496 case XT_TAB_NEXTSTYLE:
4497 if (tab_style == XT_TABS_NORMAL) {
4498 tab_style = XT_TABS_COMPACT;
4499 recolor_compact_tabs();
4501 else
4502 tab_style = XT_TABS_NORMAL;
4503 notebook_tab_set_visibility();
4504 break;
4505 case XT_TAB_UNDO_CLOSE:
4506 if (undo_count == 0) {
4507 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4508 __func__);
4509 goto done;
4510 } else {
4511 undo_count--;
4512 u = TAILQ_FIRST(&undos);
4513 create_new_tab(u->uri, u, 1, -1);
4515 TAILQ_REMOVE(&undos, u, entry);
4516 g_free(u->uri);
4517 /* u->history is freed in create_new_tab() */
4518 g_free(u);
4520 break;
4521 default:
4522 rv = XT_CB_PASSTHROUGH;
4523 goto done;
4526 done:
4527 if (args->s) {
4528 g_free(args->s);
4529 args->s = NULL;
4532 return (rv);
4536 resizetab(struct tab *t, struct karg *args)
4538 if (t == NULL || args == NULL) {
4539 show_oops(NULL, "resizetab invalid parameters");
4540 return (XT_CB_PASSTHROUGH);
4543 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4544 t->tab_id, args->i);
4546 setzoom_webkit(t, args->i);
4548 return (XT_CB_HANDLED);
4552 movetab(struct tab *t, struct karg *args)
4554 int n, dest;
4556 if (t == NULL || args == NULL) {
4557 show_oops(NULL, "movetab invalid parameters");
4558 return (XT_CB_PASSTHROUGH);
4561 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4562 t->tab_id, args->i);
4564 if (args->i >= XT_TAB_INVALID)
4565 return (XT_CB_PASSTHROUGH);
4567 if (TAILQ_EMPTY(&tabs))
4568 return (XT_CB_PASSTHROUGH);
4570 n = gtk_notebook_get_n_pages(notebook);
4571 dest = gtk_notebook_get_current_page(notebook);
4573 switch (args->i) {
4574 case XT_TAB_NEXT:
4575 if (args->precount < 0)
4576 dest = dest == n - 1 ? 0 : dest + 1;
4577 else
4578 dest = args->precount - 1;
4580 break;
4581 case XT_TAB_PREV:
4582 if (args->precount < 0)
4583 dest -= 1;
4584 else
4585 dest -= args->precount % n;
4587 if (dest < 0)
4588 dest += n;
4590 break;
4591 case XT_TAB_FIRST:
4592 dest = 0;
4593 break;
4594 case XT_TAB_LAST:
4595 dest = n - 1;
4596 break;
4597 default:
4598 return (XT_CB_PASSTHROUGH);
4601 if (dest < 0 || dest >= n)
4602 return (XT_CB_PASSTHROUGH);
4603 if (t->tab_id == dest) {
4604 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4605 return (XT_CB_HANDLED);
4608 set_current_tab(dest);
4610 return (XT_CB_HANDLED);
4613 int cmd_prefix = 0;
4616 command(struct tab *t, struct karg *args)
4618 char *s = NULL, *ss = NULL;
4619 GdkColor color;
4620 const gchar *uri;
4622 if (t == NULL || args == NULL) {
4623 show_oops(NULL, "command invalid parameters");
4624 return (XT_CB_PASSTHROUGH);
4627 switch (args->i) {
4628 case '/':
4629 s = "/";
4630 break;
4631 case '?':
4632 s = "?";
4633 break;
4634 case ':':
4635 if (cmd_prefix == 0)
4636 s = ":";
4637 else {
4638 ss = g_strdup_printf(":%d", cmd_prefix);
4639 s = ss;
4640 cmd_prefix = 0;
4642 break;
4643 case XT_CMD_OPEN:
4644 s = ":open ";
4645 break;
4646 case XT_CMD_TABNEW:
4647 s = ":tabnew ";
4648 break;
4649 case XT_CMD_OPEN_CURRENT:
4650 s = ":open ";
4651 /* FALL THROUGH */
4652 case XT_CMD_TABNEW_CURRENT:
4653 if (!s) /* FALL THROUGH? */
4654 s = ":tabnew ";
4655 if ((uri = get_uri(t)) != NULL) {
4656 ss = g_strdup_printf("%s%s", s, uri);
4657 s = ss;
4659 break;
4660 default:
4661 show_oops(t, "command: invalid opcode %d", args->i);
4662 return (XT_CB_PASSTHROUGH);
4665 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4667 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4668 gdk_color_parse(XT_COLOR_WHITE, &color);
4669 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4670 show_cmd(t);
4671 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4672 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4674 if (ss)
4675 g_free(ss);
4677 return (XT_CB_HANDLED);
4681 * Return a new string with a download row (in html)
4682 * appended. Old string is freed.
4684 char *
4685 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4688 WebKitDownloadStatus stat;
4689 char *status_html = NULL, *cmd_html = NULL, *new_html;
4690 gdouble progress;
4691 char cur_sz[FMT_SCALED_STRSIZE];
4692 char tot_sz[FMT_SCALED_STRSIZE];
4693 char *xtp_prefix;
4695 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4697 /* All actions wil take this form:
4698 * xxxt://class/seskey
4700 xtp_prefix = g_strdup_printf("%s%d/%s/",
4701 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4703 stat = webkit_download_get_status(dl->download);
4705 switch (stat) {
4706 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4707 status_html = g_strdup_printf("Finished");
4708 cmd_html = g_strdup_printf(
4709 "<a href='%s%d/%d'>Remove</a>",
4710 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4711 break;
4712 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4713 /* gather size info */
4714 progress = 100 * webkit_download_get_progress(dl->download);
4716 fmt_scaled(
4717 webkit_download_get_current_size(dl->download), cur_sz);
4718 fmt_scaled(
4719 webkit_download_get_total_size(dl->download), tot_sz);
4721 status_html = g_strdup_printf(
4722 "<div style='width: 100%%' align='center'>"
4723 "<div class='progress-outer'>"
4724 "<div class='progress-inner' style='width: %.2f%%'>"
4725 "</div></div></div>"
4726 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4727 progress, cur_sz, tot_sz, progress);
4729 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4730 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4732 break;
4733 /* LLL */
4734 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4735 status_html = g_strdup_printf("Cancelled");
4736 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4737 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4738 break;
4739 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4740 status_html = g_strdup_printf("Error!");
4741 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4742 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4743 break;
4744 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4745 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4746 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4747 status_html = g_strdup_printf("Starting");
4748 break;
4749 default:
4750 show_oops(t, "%s: unknown download status", __func__);
4753 new_html = g_strdup_printf(
4754 "%s\n<tr><td>%s</td><td>%s</td>"
4755 "<td style='text-align:center'>%s</td></tr>\n",
4756 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4757 status_html, cmd_html);
4758 g_free(html);
4760 if (status_html)
4761 g_free(status_html);
4763 if (cmd_html)
4764 g_free(cmd_html);
4766 g_free(xtp_prefix);
4768 return new_html;
4772 * update all download tabs apart from one. Pass NULL if
4773 * you want to update all.
4775 void
4776 update_download_tabs(struct tab *apart_from)
4778 struct tab *t;
4779 if (!updating_dl_tabs) {
4780 updating_dl_tabs = 1; /* stop infinite recursion */
4781 TAILQ_FOREACH(t, &tabs, entry)
4782 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4783 && (t != apart_from))
4784 xtp_page_dl(t, NULL);
4785 updating_dl_tabs = 0;
4790 * update all cookie tabs apart from one. Pass NULL if
4791 * you want to update all.
4793 void
4794 update_cookie_tabs(struct tab *apart_from)
4796 struct tab *t;
4797 if (!updating_cl_tabs) {
4798 updating_cl_tabs = 1; /* stop infinite recursion */
4799 TAILQ_FOREACH(t, &tabs, entry)
4800 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4801 && (t != apart_from))
4802 xtp_page_cl(t, NULL);
4803 updating_cl_tabs = 0;
4808 * update all history tabs apart from one. Pass NULL if
4809 * you want to update all.
4811 void
4812 update_history_tabs(struct tab *apart_from)
4814 struct tab *t;
4816 if (!updating_hl_tabs) {
4817 updating_hl_tabs = 1; /* stop infinite recursion */
4818 TAILQ_FOREACH(t, &tabs, entry)
4819 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4820 && (t != apart_from))
4821 xtp_page_hl(t, NULL);
4822 updating_hl_tabs = 0;
4826 /* cookie management XTP page */
4828 xtp_page_cl(struct tab *t, struct karg *args)
4830 char *body, *page, *tmp;
4831 int i = 1; /* all ids start 1 */
4832 GSList *sc, *pc, *pc_start;
4833 SoupCookie *c;
4834 char *type, *table_headers, *last_domain;
4836 DNPRINTF(XT_D_CMD, "%s", __func__);
4838 if (t == NULL) {
4839 show_oops(NULL, "%s invalid parameters", __func__);
4840 return (1);
4843 /* Generate a new session key */
4844 if (!updating_cl_tabs)
4845 generate_xtp_session_key(&cl_session_key);
4847 /* table headers */
4848 table_headers = g_strdup_printf("<table><tr>"
4849 "<th>Type</th>"
4850 "<th>Name</th>"
4851 "<th style='width:200px'>Value</th>"
4852 "<th>Path</th>"
4853 "<th>Expires</th>"
4854 "<th>Secure</th>"
4855 "<th>HTTP<br />only</th>"
4856 "<th style='width:40px'>Rm</th></tr>\n");
4858 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4859 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4860 pc_start = pc;
4862 body = NULL;
4863 last_domain = strdup("");
4864 for (; sc; sc = sc->next) {
4865 c = sc->data;
4867 if (strcmp(last_domain, c->domain) != 0) {
4868 /* new domain */
4869 free(last_domain);
4870 last_domain = strdup(c->domain);
4872 if (body != NULL) {
4873 tmp = body;
4874 body = g_strdup_printf("%s</table>"
4875 "<h2>%s</h2>%s\n",
4876 body, c->domain, table_headers);
4877 g_free(tmp);
4878 } else {
4879 /* first domain */
4880 body = g_strdup_printf("<h2>%s</h2>%s\n",
4881 c->domain, table_headers);
4885 type = "Session";
4886 for (pc = pc_start; pc; pc = pc->next)
4887 if (soup_cookie_equal(pc->data, c)) {
4888 type = "Session + Persistent";
4889 break;
4892 tmp = body;
4893 body = g_strdup_printf(
4894 "%s\n<tr>"
4895 "<td>%s</td>"
4896 "<td style='word-wrap:normal'>%s</td>"
4897 "<td>"
4898 " <textarea rows='4'>%s</textarea>"
4899 "</td>"
4900 "<td>%s</td>"
4901 "<td>%s</td>"
4902 "<td>%d</td>"
4903 "<td>%d</td>"
4904 "<td style='text-align:center'>"
4905 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4906 body,
4907 type,
4908 c->name,
4909 c->value,
4910 c->path,
4911 c->expires ?
4912 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4913 c->secure,
4914 c->http_only,
4916 XT_XTP_STR,
4917 XT_XTP_CL,
4918 cl_session_key,
4919 XT_XTP_CL_REMOVE,
4923 g_free(tmp);
4924 i++;
4927 soup_cookies_free(sc);
4928 soup_cookies_free(pc);
4930 /* small message if there are none */
4931 if (i == 1) {
4932 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4933 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4935 tmp = body;
4936 body = g_strdup_printf("%s</table>", body);
4937 g_free(tmp);
4939 page = get_html_page("Cookie Jar", body, "", TRUE);
4940 g_free(body);
4941 g_free(table_headers);
4942 g_free(last_domain);
4944 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4945 update_cookie_tabs(t);
4947 g_free(page);
4949 return (0);
4953 xtp_page_hl(struct tab *t, struct karg *args)
4955 char *body, *page, *tmp;
4956 struct history *h;
4957 int i = 1; /* all ids start 1 */
4959 DNPRINTF(XT_D_CMD, "%s", __func__);
4961 if (t == NULL) {
4962 show_oops(NULL, "%s invalid parameters", __func__);
4963 return (1);
4966 /* Generate a new session key */
4967 if (!updating_hl_tabs)
4968 generate_xtp_session_key(&hl_session_key);
4970 /* body */
4971 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4972 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4974 RB_FOREACH_REVERSE(h, history_list, &hl) {
4975 tmp = body;
4976 body = g_strdup_printf(
4977 "%s\n<tr>"
4978 "<td><a href='%s'>%s</a></td>"
4979 "<td>%s</td>"
4980 "<td style='text-align: center'>"
4981 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4982 body, h->uri, h->uri, h->title,
4983 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4984 XT_XTP_HL_REMOVE, i);
4986 g_free(tmp);
4987 i++;
4990 /* small message if there are none */
4991 if (i == 1) {
4992 tmp = body;
4993 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4994 "colspan='3'>No History</td></tr>\n", body);
4995 g_free(tmp);
4998 tmp = body;
4999 body = g_strdup_printf("%s</table>", body);
5000 g_free(tmp);
5002 page = get_html_page("History", body, "", TRUE);
5003 g_free(body);
5006 * update all history manager tabs as the xtp session
5007 * key has now changed. No need to update the current tab.
5008 * Already did that above.
5010 update_history_tabs(t);
5012 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5013 g_free(page);
5015 return (0);
5019 * Generate a web page detailing the status of any downloads
5022 xtp_page_dl(struct tab *t, struct karg *args)
5024 struct download *dl;
5025 char *body, *page, *tmp;
5026 char *ref;
5027 int n_dl = 1;
5029 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5031 if (t == NULL) {
5032 show_oops(NULL, "%s invalid parameters", __func__);
5033 return (1);
5037 * Generate a new session key for next page instance.
5038 * This only happens for the top level call to xtp_page_dl()
5039 * in which case updating_dl_tabs is 0.
5041 if (!updating_dl_tabs)
5042 generate_xtp_session_key(&dl_session_key);
5044 /* header - with refresh so as to update */
5045 if (refresh_interval >= 1)
5046 ref = g_strdup_printf(
5047 "<meta http-equiv='refresh' content='%u"
5048 ";url=%s%d/%s/%d' />\n",
5049 refresh_interval,
5050 XT_XTP_STR,
5051 XT_XTP_DL,
5052 dl_session_key,
5053 XT_XTP_DL_LIST);
5054 else
5055 ref = g_strdup("");
5057 body = g_strdup_printf("<div align='center'>"
5058 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5059 "</p><table><tr><th style='width: 60%%'>"
5060 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5061 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5063 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5064 body = xtp_page_dl_row(t, body, dl);
5065 n_dl++;
5068 /* message if no downloads in list */
5069 if (n_dl == 1) {
5070 tmp = body;
5071 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5072 " style='text-align: center'>"
5073 "No downloads</td></tr>\n", body);
5074 g_free(tmp);
5077 tmp = body;
5078 body = g_strdup_printf("%s</table></div>", body);
5079 g_free(tmp);
5081 page = get_html_page("Downloads", body, ref, 1);
5082 g_free(ref);
5083 g_free(body);
5086 * update all download manager tabs as the xtp session
5087 * key has now changed. No need to update the current tab.
5088 * Already did that above.
5090 update_download_tabs(t);
5092 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5093 g_free(page);
5095 return (0);
5099 search(struct tab *t, struct karg *args)
5101 gboolean d;
5103 if (t == NULL || args == NULL) {
5104 show_oops(NULL, "search invalid parameters");
5105 return (1);
5108 switch (args->i) {
5109 case XT_SEARCH_NEXT:
5110 d = t->search_forward;
5111 break;
5112 case XT_SEARCH_PREV:
5113 d = !t->search_forward;
5114 break;
5115 default:
5116 return (XT_CB_PASSTHROUGH);
5119 if (t->search_text == NULL) {
5120 if (global_search == NULL)
5121 return (XT_CB_PASSTHROUGH);
5122 else {
5123 d = t->search_forward = TRUE;
5124 t->search_text = g_strdup(global_search);
5125 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5126 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5130 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5131 t->tab_id, args->i, t->search_forward, t->search_text);
5133 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5135 return (XT_CB_HANDLED);
5138 struct settings_args {
5139 char **body;
5140 int i;
5143 void
5144 print_setting(struct settings *s, char *val, void *cb_args)
5146 char *tmp, *color;
5147 struct settings_args *sa = cb_args;
5149 if (sa == NULL)
5150 return;
5152 if (s->flags & XT_SF_RUNTIME)
5153 color = "#22cc22";
5154 else
5155 color = "#cccccc";
5157 tmp = *sa->body;
5158 *sa->body = g_strdup_printf(
5159 "%s\n<tr>"
5160 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5161 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5162 *sa->body,
5163 color,
5164 s->name,
5165 color,
5168 g_free(tmp);
5169 sa->i++;
5173 set_show(struct tab *t, struct karg *args)
5175 char *body, *page, *tmp;
5176 int i = 1;
5177 struct settings_args sa;
5179 bzero(&sa, sizeof sa);
5180 sa.body = &body;
5182 /* body */
5183 body = g_strdup_printf("<div align='center'><table><tr>"
5184 "<th align='left'>Setting</th>"
5185 "<th align='left'>Value</th></tr>\n");
5187 settings_walk(print_setting, &sa);
5188 i = sa.i;
5190 /* small message if there are none */
5191 if (i == 1) {
5192 tmp = body;
5193 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5194 "colspan='2'>No settings</td></tr>\n", body);
5195 g_free(tmp);
5198 tmp = body;
5199 body = g_strdup_printf("%s</table></div>", body);
5200 g_free(tmp);
5202 page = get_html_page("Settings", body, "", 0);
5204 g_free(body);
5206 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5208 g_free(page);
5210 return (XT_CB_PASSTHROUGH);
5214 set(struct tab *t, struct karg *args)
5216 char *p, *val;
5217 int i;
5219 if (args == NULL || args->s == NULL)
5220 return (set_show(t, args));
5222 /* strip spaces */
5223 p = g_strstrip(args->s);
5225 if (strlen(p) == 0)
5226 return (set_show(t, args));
5228 /* we got some sort of string */
5229 val = g_strrstr(p, "=");
5230 if (val) {
5231 *val++ = '\0';
5232 val = g_strchomp(val);
5233 p = g_strchomp(p);
5235 for (i = 0; i < LENGTH(rs); i++) {
5236 if (strcmp(rs[i].name, p))
5237 continue;
5239 if (rs[i].activate) {
5240 if (rs[i].activate(val))
5241 show_oops(t, "%s invalid value %s",
5242 p, val);
5243 else
5244 show_oops(t, ":set %s = %s", p, val);
5245 goto done;
5246 } else {
5247 show_oops(t, "not a runtime option: %s", p);
5248 goto done;
5251 show_oops(t, "unknown option: %s", p);
5252 } else {
5253 p = g_strchomp(p);
5255 for (i = 0; i < LENGTH(rs); i++) {
5256 if (strcmp(rs[i].name, p))
5257 continue;
5259 /* XXX this could use some cleanup */
5260 switch (rs[i].type) {
5261 case XT_S_INT:
5262 if (rs[i].ival)
5263 show_oops(t, "%s = %d",
5264 rs[i].name, *rs[i].ival);
5265 else if (rs[i].s && rs[i].s->get)
5266 show_oops(t, "%s = %s",
5267 rs[i].name,
5268 rs[i].s->get(&rs[i]));
5269 else if (rs[i].s && rs[i].s->get == NULL)
5270 show_oops(t, "%s = ...", rs[i].name);
5271 else
5272 show_oops(t, "%s = ", rs[i].name);
5273 break;
5274 case XT_S_FLOAT:
5275 if (rs[i].fval)
5276 show_oops(t, "%s = %f",
5277 rs[i].name, *rs[i].fval);
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_STR:
5288 if (rs[i].sval && *rs[i].sval)
5289 show_oops(t, "%s = %s",
5290 rs[i].name, *rs[i].sval);
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 default:
5301 show_oops(t, "unknown type for %s", rs[i].name);
5302 goto done;
5305 goto done;
5307 show_oops(t, "unknown option: %s", p);
5309 done:
5310 return (XT_CB_PASSTHROUGH);
5314 session_save(struct tab *t, char *filename)
5316 struct karg a;
5317 int rv = 1;
5318 struct session *s;
5320 if (strlen(filename) == 0)
5321 goto done;
5323 if (filename[0] == '.' || filename[0] == '/')
5324 goto done;
5326 a.s = filename;
5327 if (save_tabs(t, &a))
5328 goto done;
5329 strlcpy(named_session, filename, sizeof named_session);
5331 /* add the new session to the list of sessions */
5332 s = g_malloc(sizeof(struct session));
5333 s->name = g_strdup(filename);
5334 TAILQ_INSERT_TAIL(&sessions, s, entry);
5336 rv = 0;
5337 done:
5338 return (rv);
5342 session_open(struct tab *t, char *filename)
5344 struct karg a;
5345 int rv = 1;
5347 if (strlen(filename) == 0)
5348 goto done;
5350 if (filename[0] == '.' || filename[0] == '/')
5351 goto done;
5353 a.s = filename;
5354 a.i = XT_SES_CLOSETABS;
5355 if (open_tabs(t, &a))
5356 goto done;
5358 strlcpy(named_session, filename, sizeof named_session);
5360 rv = 0;
5361 done:
5362 return (rv);
5366 session_delete(struct tab *t, char *filename)
5368 char file[PATH_MAX];
5369 int rv = 1;
5370 struct session *s;
5372 if (strlen(filename) == 0)
5373 goto done;
5375 if (filename[0] == '.' || filename[0] == '/')
5376 goto done;
5378 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5379 if (unlink(file))
5380 goto done;
5382 if (!strcmp(filename, named_session))
5383 strlcpy(named_session, XT_SAVED_TABS_FILE,
5384 sizeof named_session);
5386 /* remove session from sessions list */
5387 TAILQ_FOREACH(s, &sessions, entry) {
5388 if (!strcmp(s->name, filename))
5389 break;
5391 if (s == NULL)
5392 goto done;
5393 TAILQ_REMOVE(&sessions, s, entry);
5394 g_free((gpointer) s->name);
5395 g_free(s);
5397 rv = 0;
5398 done:
5399 return (rv);
5403 session_cmd(struct tab *t, struct karg *args)
5405 char *filename = args->s;
5407 if (t == NULL)
5408 return (1);
5410 if (args->i & XT_SHOW)
5411 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5412 XT_SAVED_TABS_FILE : named_session);
5413 else if (args->i & XT_SAVE) {
5414 if (session_save(t, filename)) {
5415 show_oops(t, "Can't save session: %s",
5416 filename ? filename : "INVALID");
5417 goto done;
5419 } else if (args->i & XT_OPEN) {
5420 if (session_open(t, filename)) {
5421 show_oops(t, "Can't open session: %s",
5422 filename ? filename : "INVALID");
5423 goto done;
5425 } else if (args->i & XT_DELETE) {
5426 if (session_delete(t, filename)) {
5427 show_oops(t, "Can't delete session: %s",
5428 filename ? filename : "INVALID");
5429 goto done;
5432 done:
5433 return (XT_CB_PASSTHROUGH);
5437 * Make a hardcopy of the page
5440 print_page(struct tab *t, struct karg *args)
5442 WebKitWebFrame *frame;
5443 GtkPageSetup *ps;
5444 GtkPrintOperation *op;
5445 GtkPrintOperationAction action;
5446 GtkPrintOperationResult print_res;
5447 GError *g_err = NULL;
5448 int marg_l, marg_r, marg_t, marg_b;
5450 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5452 ps = gtk_page_setup_new();
5453 op = gtk_print_operation_new();
5454 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5455 frame = webkit_web_view_get_main_frame(t->wv);
5457 /* the default margins are too small, so we will bump them */
5458 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5459 XT_PRINT_EXTRA_MARGIN;
5460 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5461 XT_PRINT_EXTRA_MARGIN;
5462 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5463 XT_PRINT_EXTRA_MARGIN;
5464 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5465 XT_PRINT_EXTRA_MARGIN;
5467 /* set margins */
5468 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5469 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5470 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5471 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5473 gtk_print_operation_set_default_page_setup(op, ps);
5475 /* this appears to free 'op' and 'ps' */
5476 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5478 /* check it worked */
5479 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5480 show_oops(NULL, "can't print: %s", g_err->message);
5481 g_error_free (g_err);
5482 return (1);
5485 return (0);
5489 go_home(struct tab *t, struct karg *args)
5491 load_uri(t, home);
5492 return (0);
5496 set_encoding(struct tab *t, struct karg *args)
5498 const gchar *e;
5500 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5501 e = webkit_web_view_get_custom_encoding(t->wv);
5502 if (e == NULL)
5503 e = webkit_web_view_get_encoding(t->wv);
5504 show_oops(t, "encoding: %s", e ? e : "N/A");
5505 } else
5506 webkit_web_view_set_custom_encoding(t->wv, args->s);
5508 return (0);
5512 restart(struct tab *t, struct karg *args)
5514 struct karg a;
5516 a.s = XT_RESTART_TABS_FILE;
5517 save_tabs(t, &a);
5518 execvp(start_argv[0], start_argv);
5519 /* NOTREACHED */
5521 return (0);
5524 #define CTRL GDK_CONTROL_MASK
5525 #define MOD1 GDK_MOD1_MASK
5526 #define SHFT GDK_SHIFT_MASK
5528 /* inherent to GTK not all keys will be caught at all times */
5529 /* XXX sort key bindings */
5530 struct key_binding {
5531 char *cmd;
5532 guint mask;
5533 guint use_in_entry;
5534 guint key;
5535 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5536 } keys[] = {
5537 { "cookiejar", MOD1, 0, GDK_j },
5538 { "downloadmgr", MOD1, 0, GDK_d },
5539 { "history", MOD1, 0, GDK_h },
5540 { "print", CTRL, 0, GDK_p },
5541 { "search", 0, 0, GDK_slash },
5542 { "searchb", 0, 0, GDK_question },
5543 { "statustoggle", CTRL, 0, GDK_n },
5544 { "command", 0, 0, GDK_colon },
5545 { "qa", CTRL, 0, GDK_q },
5546 { "restart", MOD1, 0, GDK_q },
5547 { "js toggle", CTRL, 0, GDK_j },
5548 { "cookie toggle", MOD1, 0, GDK_c },
5549 { "togglesrc", CTRL, 0, GDK_s },
5550 { "yankuri", 0, 0, GDK_y },
5551 { "pasteuricur", 0, 0, GDK_p },
5552 { "pasteurinew", 0, 0, GDK_P },
5553 { "toplevel toggle", 0, 0, GDK_F4 },
5554 { "help", 0, 0, GDK_F1 },
5555 { "run_script", MOD1, 0, GDK_r },
5557 /* search */
5558 { "searchnext", 0, 0, GDK_n },
5559 { "searchprevious", 0, 0, GDK_N },
5561 /* focus */
5562 { "focusaddress", 0, 0, GDK_F6 },
5563 { "focussearch", 0, 0, GDK_F7 },
5565 /* hinting */
5566 { "hinting", 0, 0, GDK_f },
5568 /* custom stylesheet */
5569 { "userstyle", 0, 0, GDK_i },
5571 /* navigation */
5572 { "goback", 0, 0, GDK_BackSpace },
5573 { "goback", MOD1, 0, GDK_Left },
5574 { "goforward", SHFT, 0, GDK_BackSpace },
5575 { "goforward", MOD1, 0, GDK_Right },
5576 { "reload", 0, 0, GDK_F5 },
5577 { "reload", CTRL, 0, GDK_r },
5578 { "reload", CTRL, 0, GDK_l },
5579 { "favorites", MOD1, 1, GDK_f },
5581 /* vertical movement */
5582 { "scrolldown", 0, 0, GDK_j },
5583 { "scrolldown", 0, 0, GDK_Down },
5584 { "scrollup", 0, 0, GDK_Up },
5585 { "scrollup", 0, 0, GDK_k },
5586 { "scrollbottom", 0, 0, GDK_G },
5587 { "scrollbottom", 0, 0, GDK_End },
5588 { "scrolltop", 0, 0, GDK_Home },
5589 { "scrollpagedown", 0, 0, GDK_space },
5590 { "scrollpagedown", CTRL, 0, GDK_f },
5591 { "scrollhalfdown", CTRL, 0, GDK_d },
5592 { "scrollpagedown", 0, 0, GDK_Page_Down },
5593 { "scrollpageup", 0, 0, GDK_Page_Up },
5594 { "scrollpageup", CTRL, 0, GDK_b },
5595 { "scrollhalfup", CTRL, 0, GDK_u },
5596 /* horizontal movement */
5597 { "scrollright", 0, 0, GDK_l },
5598 { "scrollright", 0, 0, GDK_Right },
5599 { "scrollleft", 0, 0, GDK_Left },
5600 { "scrollleft", 0, 0, GDK_h },
5601 { "scrollfarright", 0, 0, GDK_dollar },
5602 { "scrollfarleft", 0, 0, GDK_0 },
5604 /* tabs */
5605 { "tabnew", CTRL, 0, GDK_t },
5606 { "999tabnew", CTRL, 0, GDK_T },
5607 { "tabclose", CTRL, 1, GDK_w },
5608 { "tabundoclose", 0, 0, GDK_U },
5609 { "tabnext 1", CTRL, 0, GDK_1 },
5610 { "tabnext 2", CTRL, 0, GDK_2 },
5611 { "tabnext 3", CTRL, 0, GDK_3 },
5612 { "tabnext 4", CTRL, 0, GDK_4 },
5613 { "tabnext 5", CTRL, 0, GDK_5 },
5614 { "tabnext 6", CTRL, 0, GDK_6 },
5615 { "tabnext 7", CTRL, 0, GDK_7 },
5616 { "tabnext 8", CTRL, 0, GDK_8 },
5617 { "tabnext 9", CTRL, 0, GDK_9 },
5618 { "tabfirst", CTRL, 0, GDK_less },
5619 { "tablast", CTRL, 0, GDK_greater },
5620 { "tabprevious", CTRL, 0, GDK_Left },
5621 { "tabnext", CTRL, 0, GDK_Right },
5622 { "focusout", CTRL, 0, GDK_minus },
5623 { "focusin", CTRL, 0, GDK_plus },
5624 { "focusin", CTRL, 0, GDK_equal },
5625 { "focusreset", CTRL, 0, GDK_0 },
5627 /* command aliases (handy when -S flag is used) */
5628 { "promptopen", 0, 0, GDK_F9 },
5629 { "promptopencurrent", 0, 0, GDK_F10 },
5630 { "prompttabnew", 0, 0, GDK_F11 },
5631 { "prompttabnewcurrent",0, 0, GDK_F12 },
5633 TAILQ_HEAD(keybinding_list, key_binding);
5635 void
5636 walk_kb(struct settings *s,
5637 void (*cb)(struct settings *, char *, void *), void *cb_args)
5639 struct key_binding *k;
5640 char str[1024];
5642 if (s == NULL || cb == NULL) {
5643 show_oops(NULL, "walk_kb invalid parameters");
5644 return;
5647 TAILQ_FOREACH(k, &kbl, entry) {
5648 if (k->cmd == NULL)
5649 continue;
5650 str[0] = '\0';
5652 /* sanity */
5653 if (gdk_keyval_name(k->key) == NULL)
5654 continue;
5656 strlcat(str, k->cmd, sizeof str);
5657 strlcat(str, ",", sizeof str);
5659 if (k->mask & GDK_SHIFT_MASK)
5660 strlcat(str, "S-", sizeof str);
5661 if (k->mask & GDK_CONTROL_MASK)
5662 strlcat(str, "C-", sizeof str);
5663 if (k->mask & GDK_MOD1_MASK)
5664 strlcat(str, "M1-", sizeof str);
5665 if (k->mask & GDK_MOD2_MASK)
5666 strlcat(str, "M2-", sizeof str);
5667 if (k->mask & GDK_MOD3_MASK)
5668 strlcat(str, "M3-", sizeof str);
5669 if (k->mask & GDK_MOD4_MASK)
5670 strlcat(str, "M4-", sizeof str);
5671 if (k->mask & GDK_MOD5_MASK)
5672 strlcat(str, "M5-", sizeof str);
5674 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5675 cb(s, str, cb_args);
5679 void
5680 init_keybindings(void)
5682 int i;
5683 struct key_binding *k;
5685 for (i = 0; i < LENGTH(keys); i++) {
5686 k = g_malloc0(sizeof *k);
5687 k->cmd = keys[i].cmd;
5688 k->mask = keys[i].mask;
5689 k->use_in_entry = keys[i].use_in_entry;
5690 k->key = keys[i].key;
5691 TAILQ_INSERT_HEAD(&kbl, k, entry);
5693 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5694 k->cmd ? k->cmd : "unnamed key");
5698 void
5699 keybinding_clearall(void)
5701 struct key_binding *k, *next;
5703 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5704 next = TAILQ_NEXT(k, entry);
5705 if (k->cmd == NULL)
5706 continue;
5708 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5709 k->cmd ? k->cmd : "unnamed key");
5710 TAILQ_REMOVE(&kbl, k, entry);
5711 g_free(k);
5716 keybinding_add(char *cmd, char *key, int use_in_entry)
5718 struct key_binding *k;
5719 guint keyval, mask = 0;
5720 int i;
5722 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5724 /* Keys which are to be used in entry have been prefixed with an
5725 * exclamation mark. */
5726 if (use_in_entry)
5727 key++;
5729 /* find modifier keys */
5730 if (strstr(key, "S-"))
5731 mask |= GDK_SHIFT_MASK;
5732 if (strstr(key, "C-"))
5733 mask |= GDK_CONTROL_MASK;
5734 if (strstr(key, "M1-"))
5735 mask |= GDK_MOD1_MASK;
5736 if (strstr(key, "M2-"))
5737 mask |= GDK_MOD2_MASK;
5738 if (strstr(key, "M3-"))
5739 mask |= GDK_MOD3_MASK;
5740 if (strstr(key, "M4-"))
5741 mask |= GDK_MOD4_MASK;
5742 if (strstr(key, "M5-"))
5743 mask |= GDK_MOD5_MASK;
5745 /* find keyname */
5746 for (i = strlen(key) - 1; i > 0; i--)
5747 if (key[i] == '-')
5748 key = &key[i + 1];
5750 /* validate keyname */
5751 keyval = gdk_keyval_from_name(key);
5752 if (keyval == GDK_VoidSymbol) {
5753 warnx("invalid keybinding name %s", key);
5754 return (1);
5756 /* must run this test too, gtk+ doesn't handle 10 for example */
5757 if (gdk_keyval_name(keyval) == NULL) {
5758 warnx("invalid keybinding name %s", key);
5759 return (1);
5762 /* Remove eventual dupes. */
5763 TAILQ_FOREACH(k, &kbl, entry)
5764 if (k->key == keyval && k->mask == mask) {
5765 TAILQ_REMOVE(&kbl, k, entry);
5766 g_free(k);
5767 break;
5770 /* add keyname */
5771 k = g_malloc0(sizeof *k);
5772 k->cmd = g_strdup(cmd);
5773 k->mask = mask;
5774 k->use_in_entry = use_in_entry;
5775 k->key = keyval;
5777 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5778 k->cmd,
5779 k->mask,
5780 k->use_in_entry,
5781 k->key);
5782 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5783 k->cmd, gdk_keyval_name(keyval));
5785 TAILQ_INSERT_HEAD(&kbl, k, entry);
5787 return (0);
5791 add_kb(struct settings *s, char *entry)
5793 char *kb, *key;
5795 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5797 /* clearall is special */
5798 if (!strcmp(entry, "clearall")) {
5799 keybinding_clearall();
5800 return (0);
5803 kb = strstr(entry, ",");
5804 if (kb == NULL)
5805 return (1);
5806 *kb = '\0';
5807 key = kb + 1;
5809 return (keybinding_add(entry, key, key[0] == '!'));
5812 struct cmd {
5813 char *cmd;
5814 int level;
5815 int (*func)(struct tab *, struct karg *);
5816 int arg;
5817 int type;
5818 } cmds[] = {
5819 { "command", 0, command, ':', 0 },
5820 { "search", 0, command, '/', 0 },
5821 { "searchb", 0, command, '?', 0 },
5822 { "togglesrc", 0, toggle_src, 0, 0 },
5824 /* yanking and pasting */
5825 { "yankuri", 0, yank_uri, 0, 0 },
5826 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5827 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5828 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5830 /* search */
5831 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5832 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5834 /* focus */
5835 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5836 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5838 /* hinting */
5839 { "hinting", 0, hint, 0, 0 },
5841 /* custom stylesheet */
5842 { "userstyle", 0, userstyle, 0, 0 },
5844 /* navigation */
5845 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5846 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5847 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5849 /* vertical movement */
5850 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5851 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5852 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5853 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5854 { "1", 0, move, XT_MOVE_TOP, 0 },
5855 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5856 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5857 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5858 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5859 /* horizontal movement */
5860 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5861 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5862 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5863 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5865 { "favorites", 0, xtp_page_fl, 0, 0 },
5866 { "fav", 0, xtp_page_fl, 0, 0 },
5867 { "favadd", 0, add_favorite, 0, 0 },
5869 { "qall", 0, quit, 0, 0 },
5870 { "quitall", 0, quit, 0, 0 },
5871 { "w", 0, save_tabs, 0, 0 },
5872 { "wq", 0, save_tabs_and_quit, 0, 0 },
5873 { "help", 0, help, 0, 0 },
5874 { "about", 0, about, 0, 0 },
5875 { "stats", 0, stats, 0, 0 },
5876 { "version", 0, about, 0, 0 },
5878 /* js command */
5879 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5880 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5881 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5882 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5883 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5884 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5885 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5886 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5887 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5888 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5889 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5891 /* cookie command */
5892 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5893 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5894 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5895 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5896 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5897 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5898 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5899 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5900 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5901 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5902 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5904 /* toplevel (domain) command */
5905 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5906 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5908 /* cookie jar */
5909 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5911 /* cert command */
5912 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5913 { "save", 1, cert_cmd, XT_SAVE, 0 },
5914 { "show", 1, cert_cmd, XT_SHOW, 0 },
5916 { "ca", 0, ca_cmd, 0, 0 },
5917 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5918 { "dl", 0, xtp_page_dl, 0, 0 },
5919 { "h", 0, xtp_page_hl, 0, 0 },
5920 { "history", 0, xtp_page_hl, 0, 0 },
5921 { "home", 0, go_home, 0, 0 },
5922 { "restart", 0, restart, 0, 0 },
5923 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5924 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5925 { "statustoggle", 0, statustoggle, 0, 0 },
5926 { "run_script", 0, run_page_script, 0, XT_USERARG },
5928 { "print", 0, print_page, 0, 0 },
5930 /* tabs */
5931 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5932 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5933 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5934 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5935 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5936 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5937 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5938 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5939 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5940 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5941 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5942 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5943 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5944 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5945 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5946 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5947 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5948 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5949 { "buffers", 0, buffers, 0, 0 },
5950 { "ls", 0, buffers, 0, 0 },
5951 { "tabs", 0, buffers, 0, 0 },
5952 { "encoding", 0, set_encoding, 0, XT_USERARG },
5954 /* command aliases (handy when -S flag is used) */
5955 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5956 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5957 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5958 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5960 /* settings */
5961 { "set", 0, set, 0, XT_SETARG },
5963 { "fullscreen", 0, fullscreen, 0, 0 },
5964 { "f", 0, fullscreen, 0, 0 },
5966 /* sessions */
5967 { "session", 0, session_cmd, XT_SHOW, 0 },
5968 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5969 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5970 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5971 { "show", 1, session_cmd, XT_SHOW, 0 },
5974 struct {
5975 int index;
5976 int len;
5977 gchar *list[256];
5978 } cmd_status = {-1, 0};
5980 gboolean
5981 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5984 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5985 btn_down = 0;
5987 return (FALSE);
5990 gboolean
5991 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5993 struct karg a;
5995 hide_oops(t);
5996 hide_buffers(t);
5998 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5999 btn_down = 1;
6000 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
6001 /* go backward */
6002 a.i = XT_NAV_BACK;
6003 navaction(t, &a);
6005 return (TRUE);
6006 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
6007 /* go forward */
6008 a.i = XT_NAV_FORWARD;
6009 navaction(t, &a);
6011 return (TRUE);
6014 return (FALSE);
6017 gboolean
6018 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6020 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6022 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6023 delete_tab(t);
6025 return (FALSE);
6029 * cancel, remove, etc. downloads
6031 void
6032 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6034 struct download find, *d = NULL;
6036 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6038 /* some commands require a valid download id */
6039 if (cmd != XT_XTP_DL_LIST) {
6040 /* lookup download in question */
6041 find.id = id;
6042 d = RB_FIND(download_list, &downloads, &find);
6044 if (d == NULL) {
6045 show_oops(t, "%s: no such download", __func__);
6046 return;
6050 /* decide what to do */
6051 switch (cmd) {
6052 case XT_XTP_DL_CANCEL:
6053 webkit_download_cancel(d->download);
6054 break;
6055 case XT_XTP_DL_REMOVE:
6056 webkit_download_cancel(d->download); /* just incase */
6057 g_object_unref(d->download);
6058 RB_REMOVE(download_list, &downloads, d);
6059 break;
6060 case XT_XTP_DL_LIST:
6061 /* Nothing */
6062 break;
6063 default:
6064 show_oops(t, "%s: unknown command", __func__);
6065 break;
6067 xtp_page_dl(t, NULL);
6071 * Actions on history, only does one thing for now, but
6072 * we provide the function for future actions
6074 void
6075 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6077 struct history *h, *next;
6078 int i = 1;
6080 switch (cmd) {
6081 case XT_XTP_HL_REMOVE:
6082 /* walk backwards, as listed in reverse */
6083 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6084 next = RB_PREV(history_list, &hl, h);
6085 if (id == i) {
6086 RB_REMOVE(history_list, &hl, h);
6087 g_free((gpointer) h->title);
6088 g_free((gpointer) h->uri);
6089 g_free(h);
6090 break;
6092 i++;
6094 break;
6095 case XT_XTP_HL_LIST:
6096 /* Nothing - just xtp_page_hl() below */
6097 break;
6098 default:
6099 show_oops(t, "%s: unknown command", __func__);
6100 break;
6103 xtp_page_hl(t, NULL);
6106 /* remove a favorite */
6107 void
6108 remove_favorite(struct tab *t, int index)
6110 char file[PATH_MAX], *title, *uri = NULL;
6111 char *new_favs, *tmp;
6112 FILE *f;
6113 int i;
6114 size_t len, lineno;
6116 /* open favorites */
6117 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6119 if ((f = fopen(file, "r")) == NULL) {
6120 show_oops(t, "%s: can't open favorites: %s",
6121 __func__, strerror(errno));
6122 return;
6125 /* build a string which will become the new favroites file */
6126 new_favs = g_strdup("");
6128 for (i = 1;;) {
6129 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6130 if (feof(f) || ferror(f))
6131 break;
6132 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6133 if (len == 0) {
6134 free(title);
6135 title = NULL;
6136 continue;
6139 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6140 if (feof(f) || ferror(f)) {
6141 show_oops(t, "%s: can't parse favorites %s",
6142 __func__, strerror(errno));
6143 goto clean;
6147 /* as long as this isn't the one we are deleting add to file */
6148 if (i != index) {
6149 tmp = new_favs;
6150 new_favs = g_strdup_printf("%s%s\n%s\n",
6151 new_favs, title, uri);
6152 g_free(tmp);
6155 free(uri);
6156 uri = NULL;
6157 free(title);
6158 title = NULL;
6159 i++;
6161 fclose(f);
6163 /* write back new favorites file */
6164 if ((f = fopen(file, "w")) == NULL) {
6165 show_oops(t, "%s: can't open favorites: %s",
6166 __func__, strerror(errno));
6167 goto clean;
6170 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
6171 show_oops(t, "%s: can't fwrite"); /* shut gcc up */
6172 fclose(f);
6174 clean:
6175 if (uri)
6176 free(uri);
6177 if (title)
6178 free(title);
6180 g_free(new_favs);
6183 void
6184 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6186 switch (cmd) {
6187 case XT_XTP_FL_LIST:
6188 /* nothing, just the below call to xtp_page_fl() */
6189 break;
6190 case XT_XTP_FL_REMOVE:
6191 remove_favorite(t, arg);
6192 break;
6193 default:
6194 show_oops(t, "%s: invalid favorites command", __func__);
6195 break;
6198 xtp_page_fl(t, NULL);
6201 void
6202 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6204 switch (cmd) {
6205 case XT_XTP_CL_LIST:
6206 /* nothing, just xtp_page_cl() */
6207 break;
6208 case XT_XTP_CL_REMOVE:
6209 remove_cookie(arg);
6210 break;
6211 default:
6212 show_oops(t, "%s: unknown cookie xtp command", __func__);
6213 break;
6216 xtp_page_cl(t, NULL);
6219 /* link an XTP class to it's session key and handler function */
6220 struct xtp_despatch {
6221 uint8_t xtp_class;
6222 char **session_key;
6223 void (*handle_func)(struct tab *, uint8_t, int);
6226 struct xtp_despatch xtp_despatches[] = {
6227 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6228 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6229 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6230 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6231 { XT_XTP_INVALID, NULL, NULL }
6235 * is the url xtp protocol? (xxxt://)
6236 * if so, parse and despatch correct bahvior
6239 parse_xtp_url(struct tab *t, const char *url)
6241 char *dup = NULL, *p, *last = NULL;
6242 uint8_t n_tokens = 0;
6243 char *tokens[4] = {NULL, NULL, NULL, ""};
6244 struct xtp_despatch *dsp, *dsp_match = NULL;
6245 uint8_t req_class;
6246 int ret = FALSE;
6249 * tokens array meaning:
6250 * tokens[0] = class
6251 * tokens[1] = session key
6252 * tokens[2] = action
6253 * tokens[3] = optional argument
6256 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6258 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6259 goto clean;
6261 dup = g_strdup(url + strlen(XT_XTP_STR));
6263 /* split out the url */
6264 for ((p = strtok_r(dup, "/", &last)); p;
6265 (p = strtok_r(NULL, "/", &last))) {
6266 if (n_tokens < 4)
6267 tokens[n_tokens++] = p;
6270 /* should be atleast three fields 'class/seskey/command/arg' */
6271 if (n_tokens < 3)
6272 goto clean;
6274 dsp = xtp_despatches;
6275 req_class = atoi(tokens[0]);
6276 while (dsp->xtp_class) {
6277 if (dsp->xtp_class == req_class) {
6278 dsp_match = dsp;
6279 break;
6281 dsp++;
6284 /* did we find one atall? */
6285 if (dsp_match == NULL) {
6286 show_oops(t, "%s: no matching xtp despatch found", __func__);
6287 goto clean;
6290 /* check session key and call despatch function */
6291 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6292 ret = TRUE; /* all is well, this was a valid xtp request */
6293 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6296 clean:
6297 if (dup)
6298 g_free(dup);
6300 return (ret);
6305 void
6306 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6308 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6310 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6312 if (t == NULL) {
6313 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6314 return;
6317 if (uri == NULL) {
6318 show_oops(t, "activate_uri_entry_cb no uri");
6319 return;
6322 uri += strspn(uri, "\t ");
6324 /* if xxxt:// treat specially */
6325 if (parse_xtp_url(t, uri))
6326 return;
6328 /* otherwise continue to load page normally */
6329 load_uri(t, (gchar *)uri);
6330 focus_webview(t);
6333 void
6334 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6336 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6337 char *newuri = NULL;
6338 gchar *enc_search;
6340 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6342 if (t == NULL) {
6343 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6344 return;
6347 if (search_string == NULL) {
6348 show_oops(t, "no search_string");
6349 return;
6352 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6354 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6355 newuri = g_strdup_printf(search_string, enc_search);
6356 g_free(enc_search);
6358 marks_clear(t);
6359 webkit_web_view_load_uri(t->wv, newuri);
6360 focus_webview(t);
6362 if (newuri)
6363 g_free(newuri);
6366 void
6367 check_and_set_cookie(const gchar *uri, struct tab *t)
6369 struct domain *d = NULL;
6370 int es = 0;
6372 if (uri == NULL || t == NULL)
6373 return;
6375 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6376 es = 0;
6377 else
6378 es = 1;
6380 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6381 es ? "enable" : "disable", uri);
6383 g_object_set(G_OBJECT(t->settings),
6384 "enable-html5-local-storage", es, (char *)NULL);
6385 webkit_web_view_set_settings(t->wv, t->settings);
6388 void
6389 check_and_set_js(const gchar *uri, struct tab *t)
6391 struct domain *d = NULL;
6392 int es = 0;
6394 if (uri == NULL || t == NULL)
6395 return;
6397 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6398 es = 0;
6399 else
6400 es = 1;
6402 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6403 es ? "enable" : "disable", uri);
6405 g_object_set(G_OBJECT(t->settings),
6406 "enable-scripts", es, (char *)NULL);
6407 g_object_set(G_OBJECT(t->settings),
6408 "javascript-can-open-windows-automatically", es, (char *)NULL);
6409 webkit_web_view_set_settings(t->wv, t->settings);
6411 button_set_stockid(t->js_toggle,
6412 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6415 void
6416 color_address_bar(gpointer p)
6418 GdkColor color;
6419 struct tab *tt, *t = p;
6420 gchar *col_str = XT_COLOR_WHITE;
6421 const gchar *uri, *u = NULL, *error_str = NULL;
6423 gdk_threads_enter();
6424 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6426 /* make sure t still exists */
6427 if (t == NULL)
6428 return;
6429 TAILQ_FOREACH(tt, &tabs, entry)
6430 if (t == tt)
6431 break;
6432 if (t != tt)
6433 goto done;
6435 if ((uri = get_uri(t)) == NULL)
6436 goto white;
6437 u = g_strdup(uri);
6439 gdk_threads_leave();
6441 col_str = XT_COLOR_YELLOW;
6442 switch (load_compare_cert(u, &error_str)) {
6443 case CERT_LOCAL:
6444 col_str = XT_COLOR_BLUE;
6445 break;
6446 case CERT_TRUSTED:
6447 col_str = XT_COLOR_GREEN;
6448 break;
6449 case CERT_UNTRUSTED:
6450 col_str = XT_COLOR_YELLOW;
6451 break;
6452 case CERT_BAD:
6453 col_str = XT_COLOR_RED;
6454 break;
6457 gdk_threads_enter();
6459 /* make sure t isn't deleted */
6460 TAILQ_FOREACH(tt, &tabs, entry)
6461 if (t == tt)
6462 break;
6463 if (t != tt)
6464 goto done;
6466 /* test to see if the user navigated away and canceled the thread */
6467 if (t->thread != g_thread_self())
6468 goto done;
6469 white:
6470 gdk_color_parse(col_str, &color);
6471 #if GTK_CHECK_VERSION(3, 0, 0)
6472 gtk_widget_modify_bg(t->uri_entry, GTK_STATE_NORMAL, &color);
6473 #else
6474 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6475 #endif
6477 if (!strcmp(col_str, XT_COLOR_WHITE))
6478 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6479 else
6480 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6482 if (error_str && error_str[0] != '\0')
6483 show_oops(t, "%s", error_str);
6485 t->thread = NULL;
6486 done:
6487 /* t is invalid at this point */
6488 if (u)
6489 g_free((gpointer)u);
6490 gdk_threads_leave();
6493 void
6494 show_ca_status(struct tab *t, const char *uri)
6496 GdkColor color;
6497 gchar *col_str = XT_COLOR_WHITE;
6499 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6500 ssl_strict_certs, ssl_ca_file, uri);
6502 if (t == NULL)
6503 return;
6505 if (uri == NULL)
6506 goto done;
6507 if (ssl_ca_file == NULL) {
6508 if (g_str_has_prefix(uri, "http://"))
6509 goto done;
6510 if (g_str_has_prefix(uri, "https://")) {
6511 col_str = XT_COLOR_RED;
6512 goto done;
6514 return;
6516 if (g_str_has_prefix(uri, "http://") ||
6517 !g_str_has_prefix(uri, "https://"))
6518 goto done;
6521 * It is not necessary to see if the thread is already running.
6522 * If the thread is in progress setting it to something else aborts it
6523 * on the way out.
6526 /* thread the coloring of the address bar */
6527 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
6529 return;
6531 done:
6532 if (col_str) {
6533 gdk_color_parse(col_str, &color);
6534 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6536 if (!strcmp(col_str, XT_COLOR_WHITE))
6537 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6538 else
6539 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6543 void
6544 free_favicon(struct tab *t)
6546 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6547 __func__, t->icon_download, t->icon_request);
6549 if (t->icon_request)
6550 g_object_unref(t->icon_request);
6551 if (t->icon_dest_uri)
6552 g_free(t->icon_dest_uri);
6554 t->icon_request = NULL;
6555 t->icon_dest_uri = NULL;
6558 void
6559 xt_icon_from_name(struct tab *t, gchar *name)
6561 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6562 GTK_ENTRY_ICON_PRIMARY, "text-html");
6563 if (show_url == 0)
6564 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6565 GTK_ENTRY_ICON_PRIMARY, "text-html");
6566 else
6567 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6568 GTK_ENTRY_ICON_PRIMARY, NULL);
6571 void
6572 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6574 GdkPixbuf *pb_scaled;
6576 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6577 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6578 GDK_INTERP_BILINEAR);
6579 else
6580 pb_scaled = pb;
6582 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6583 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6584 if (show_url == 0)
6585 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6586 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6587 else
6588 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6589 GTK_ENTRY_ICON_PRIMARY, NULL);
6591 if (pb_scaled != pb)
6592 g_object_unref(pb_scaled);
6595 void
6596 xt_icon_from_file(struct tab *t, char *file)
6598 GdkPixbuf *pb;
6600 if (g_str_has_prefix(file, "file://"))
6601 file += strlen("file://");
6603 pb = gdk_pixbuf_new_from_file(file, NULL);
6604 if (pb) {
6605 xt_icon_from_pixbuf(t, pb);
6606 g_object_unref(pb);
6607 } else
6608 xt_icon_from_name(t, "text-html");
6611 gboolean
6612 is_valid_icon(char *file)
6614 gboolean valid = 0;
6615 const char *mime_type;
6616 GFileInfo *fi;
6617 GFile *gf;
6619 gf = g_file_new_for_path(file);
6620 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6621 NULL, NULL);
6622 mime_type = g_file_info_get_content_type(fi);
6623 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6624 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6625 g_strcmp0(mime_type, "image/png") == 0 ||
6626 g_strcmp0(mime_type, "image/gif") == 0 ||
6627 g_strcmp0(mime_type, "application/octet-stream") == 0;
6628 g_object_unref(fi);
6629 g_object_unref(gf);
6631 return (valid);
6634 void
6635 set_favicon_from_file(struct tab *t, char *file)
6637 struct stat sb;
6639 if (t == NULL || file == NULL)
6640 return;
6642 if (g_str_has_prefix(file, "file://"))
6643 file += strlen("file://");
6644 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6646 if (!stat(file, &sb)) {
6647 if (sb.st_size == 0 || !is_valid_icon(file)) {
6648 /* corrupt icon so trash it */
6649 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6650 __func__, file);
6651 unlink(file);
6652 /* no need to set icon to default here */
6653 return;
6656 xt_icon_from_file(t, file);
6659 void
6660 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6661 WebKitWebView *wv)
6663 WebKitDownloadStatus status = webkit_download_get_status(download);
6664 struct tab *tt = NULL, *t = NULL;
6667 * find the webview instead of passing in the tab as it could have been
6668 * deleted from underneath us.
6670 TAILQ_FOREACH(tt, &tabs, entry) {
6671 if (tt->wv == wv) {
6672 t = tt;
6673 break;
6676 if (t == NULL)
6677 return;
6679 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6680 __func__, t->tab_id, status);
6682 switch (status) {
6683 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6684 /* -1 */
6685 t->icon_download = NULL;
6686 free_favicon(t);
6687 break;
6688 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6689 /* 0 */
6690 break;
6691 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6692 /* 1 */
6693 break;
6694 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6695 /* 2 */
6696 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6697 __func__, t->tab_id);
6698 t->icon_download = NULL;
6699 free_favicon(t);
6700 break;
6701 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6702 /* 3 */
6704 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6705 __func__, t->icon_dest_uri);
6706 set_favicon_from_file(t, t->icon_dest_uri);
6707 /* these will be freed post callback */
6708 t->icon_request = NULL;
6709 t->icon_download = NULL;
6710 break;
6711 default:
6712 break;
6716 void
6717 abort_favicon_download(struct tab *t)
6719 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6721 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6722 if (t->icon_download) {
6723 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6724 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6725 webkit_download_cancel(t->icon_download);
6726 t->icon_download = NULL;
6727 } else
6728 free_favicon(t);
6729 #endif
6731 xt_icon_from_name(t, "text-html");
6734 void
6735 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6737 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6739 if (uri == NULL || t == NULL)
6740 return;
6742 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6743 /* take icon from WebKitIconDatabase */
6744 GdkPixbuf *pb;
6746 pb = webkit_web_view_get_icon_pixbuf(wv);
6747 if (pb) {
6748 xt_icon_from_pixbuf(t, pb);
6749 g_object_unref(pb);
6750 } else
6751 xt_icon_from_name(t, "text-html");
6752 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6753 /* download icon to cache dir */
6754 gchar *name_hash, file[PATH_MAX];
6755 struct stat sb;
6757 if (t->icon_request) {
6758 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6759 return;
6762 /* check to see if we got the icon in cache */
6763 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6764 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6765 g_free(name_hash);
6767 if (!stat(file, &sb)) {
6768 if (sb.st_size > 0) {
6769 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6770 __func__, file);
6771 set_favicon_from_file(t, file);
6772 return;
6775 /* corrupt icon so trash it */
6776 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6777 __func__, file);
6778 unlink(file);
6781 /* create download for icon */
6782 t->icon_request = webkit_network_request_new(uri);
6783 if (t->icon_request == NULL) {
6784 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6785 __func__, uri);
6786 return;
6789 t->icon_download = webkit_download_new(t->icon_request);
6790 if (t->icon_download == NULL)
6791 return;
6793 /* we have to free icon_dest_uri later */
6794 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6795 webkit_download_set_destination_uri(t->icon_download,
6796 t->icon_dest_uri);
6798 if (webkit_download_get_status(t->icon_download) ==
6799 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6800 g_object_unref(t->icon_request);
6801 g_free(t->icon_dest_uri);
6802 t->icon_request = NULL;
6803 t->icon_dest_uri = NULL;
6804 return;
6807 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6808 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6810 webkit_download_start(t->icon_download);
6811 #endif
6814 void
6815 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6817 const gchar *uri = NULL, *title = NULL;
6818 struct history *h, find;
6819 struct karg a;
6820 GdkColor color;
6822 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6823 webkit_web_view_get_load_status(wview),
6824 get_uri(t) ? get_uri(t) : "NOTHING");
6826 if (t == NULL) {
6827 show_oops(NULL, "notify_load_status_cb invalid parameters");
6828 return;
6831 switch (webkit_web_view_get_load_status(wview)) {
6832 case WEBKIT_LOAD_PROVISIONAL:
6833 /* 0 */
6834 abort_favicon_download(t);
6835 #if GTK_CHECK_VERSION(2, 20, 0)
6836 gtk_widget_show(t->spinner);
6837 gtk_spinner_start(GTK_SPINNER(t->spinner));
6838 #endif
6839 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6841 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6843 /* assume we are a new address */
6844 gdk_color_parse("white", &color);
6845 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6846 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6848 /* take focus if we are visible */
6849 focus_webview(t);
6850 t->focus_wv = 1;
6852 /* kill color thread */
6853 t->thread = NULL;
6855 break;
6857 case WEBKIT_LOAD_COMMITTED:
6858 /* 1 */
6859 uri = get_uri(t);
6860 if (uri == NULL)
6861 return;
6862 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6864 if (t->status) {
6865 g_free(t->status);
6866 t->status = NULL;
6868 set_status(t, (char *)uri, XT_STATUS_LOADING);
6870 /* check if js white listing is enabled */
6871 if (enable_cookie_whitelist)
6872 check_and_set_cookie(uri, t);
6873 if (enable_js_whitelist)
6874 check_and_set_js(uri, t);
6876 if (t->styled)
6877 apply_style(t);
6880 /* we know enough to autosave the session */
6881 if (session_autosave) {
6882 a.s = NULL;
6883 save_tabs(t, &a);
6886 show_ca_status(t, uri);
6887 break;
6889 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6890 /* 3 */
6891 break;
6893 case WEBKIT_LOAD_FINISHED:
6894 /* 2 */
6895 uri = get_uri(t);
6896 if (uri == NULL)
6897 return;
6899 if (!strncmp(uri, "http://", strlen("http://")) ||
6900 !strncmp(uri, "https://", strlen("https://")) ||
6901 !strncmp(uri, "file://", strlen("file://"))) {
6902 find.uri = uri;
6903 h = RB_FIND(history_list, &hl, &find);
6904 if (!h) {
6905 title = get_title(t, FALSE);
6906 h = g_malloc(sizeof *h);
6907 h->uri = g_strdup(uri);
6908 h->title = g_strdup(title);
6909 RB_INSERT(history_list, &hl, h);
6910 completion_add_uri(h->uri);
6911 update_history_tabs(NULL);
6915 set_status(t, (char *)uri, XT_STATUS_URI);
6916 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6917 case WEBKIT_LOAD_FAILED:
6918 /* 4 */
6919 #endif
6920 #if GTK_CHECK_VERSION(2, 20, 0)
6921 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6922 gtk_widget_hide(t->spinner);
6923 #endif
6924 default:
6925 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6926 break;
6929 if (t->item)
6930 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6931 else
6932 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6933 can_go_back_for_real(t));
6935 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6936 can_go_forward_for_real(t));
6939 void
6940 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6942 const gchar *title = NULL, *win_title = NULL;
6944 title = get_title(t, FALSE);
6945 win_title = get_title(t, TRUE);
6946 gtk_label_set_text(GTK_LABEL(t->label), title);
6947 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6948 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6949 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6952 void
6953 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6955 run_script(t, JS_HINTING);
6958 void
6959 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6961 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6962 progress == 100 ? 0 : (double)progress / 100);
6963 if (show_url == 0) {
6964 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6965 progress == 100 ? 0 : (double)progress / 100);
6968 update_statusbar_position(NULL, NULL);
6972 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6973 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6974 WebKitWebPolicyDecision *pd, struct tab *t)
6976 char *uri;
6977 WebKitWebNavigationReason reason;
6978 struct domain *d = NULL;
6980 if (t == NULL) {
6981 show_oops(NULL, "webview_npd_cb invalid parameters");
6982 return (FALSE);
6985 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6986 t->ctrl_click,
6987 webkit_network_request_get_uri(request));
6989 uri = (char *)webkit_network_request_get_uri(request);
6991 /* if this is an xtp url, we don't load anything else */
6992 if (parse_xtp_url(t, uri))
6993 return (TRUE);
6995 if (t->ctrl_click) {
6996 t->ctrl_click = 0;
6997 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6998 webkit_web_policy_decision_ignore(pd);
6999 return (TRUE); /* we made the decission */
7003 * This is a little hairy but it comes down to this:
7004 * when we run in whitelist mode we have to assist the browser in
7005 * opening the URL that it would have opened in a new tab.
7007 reason = webkit_web_navigation_action_get_reason(na);
7008 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
7009 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7010 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
7011 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7012 load_uri(t, uri);
7013 webkit_web_policy_decision_use(pd);
7014 return (TRUE); /* we made the decision */
7017 return (FALSE);
7020 WebKitWebView *
7021 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7023 struct tab *tt;
7024 struct domain *d = NULL;
7025 const gchar *uri;
7026 WebKitWebView *webview = NULL;
7028 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
7029 webkit_web_view_get_uri(wv));
7031 if (tabless) {
7032 /* open in current tab */
7033 webview = t->wv;
7034 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7035 uri = webkit_web_view_get_uri(wv);
7036 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7037 return (NULL);
7039 tt = create_new_tab(NULL, NULL, 1, -1);
7040 webview = tt->wv;
7041 } else if (enable_scripts == 1) {
7042 tt = create_new_tab(NULL, NULL, 1, -1);
7043 webview = tt->wv;
7046 return (webview);
7049 gboolean
7050 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
7052 const gchar *uri;
7053 struct domain *d = NULL;
7055 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7057 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7058 uri = webkit_web_view_get_uri(wv);
7059 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7060 return (FALSE);
7062 delete_tab(t);
7063 } else if (enable_scripts == 1)
7064 delete_tab(t);
7066 return (TRUE);
7070 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7072 /* we can not eat the event without throwing gtk off so defer it */
7074 /* catch middle click */
7075 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7076 t->ctrl_click = 1;
7077 goto done;
7080 /* catch ctrl click */
7081 if (e->type == GDK_BUTTON_RELEASE &&
7082 CLEAN(e->state) == GDK_CONTROL_MASK)
7083 t->ctrl_click = 1;
7084 else
7085 t->ctrl_click = 0;
7086 done:
7087 return (XT_CB_PASSTHROUGH);
7091 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7093 struct mime_type *m;
7095 m = find_mime_type(mime_type);
7096 if (m == NULL)
7097 return (1);
7098 if (m->mt_download)
7099 return (1);
7101 switch (fork()) {
7102 case -1:
7103 show_oops(t, "can't fork mime handler");
7104 return (1);
7105 /* NOTREACHED */
7106 case 0:
7107 break;
7108 default:
7109 return (0);
7112 /* child */
7113 execlp(m->mt_action, m->mt_action,
7114 webkit_network_request_get_uri(request), (void *)NULL);
7116 _exit(0);
7118 /* NOTREACHED */
7119 return (0);
7122 char *
7123 get_mime_type(const char *file)
7125 const gchar *m;
7126 char *mime_type = NULL;
7127 GFileInfo *fi;
7128 GFile *gf;
7130 if (g_str_has_prefix(file, "file://"))
7131 file += strlen("file://");
7133 gf = g_file_new_for_path(file);
7134 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7135 NULL, NULL);
7136 if ((m = g_file_info_get_content_type(fi)) != NULL)
7137 mime_type = g_strdup(m);
7138 g_object_unref(fi);
7139 g_object_unref(gf);
7141 return (mime_type);
7145 run_download_mimehandler(char *mime_type, char *file)
7147 struct mime_type *m;
7149 m = find_mime_type(mime_type);
7150 if (m == NULL)
7151 return (1);
7153 switch (fork()) {
7154 case -1:
7155 show_oops(NULL, "can't fork download mime handler");
7156 return (1);
7157 /* NOTREACHED */
7158 case 0:
7159 break;
7160 default:
7161 return (0);
7164 /* child */
7165 if (g_str_has_prefix(file, "file://"))
7166 file += strlen("file://");
7167 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7169 _exit(0);
7171 /* NOTREACHED */
7172 return (0);
7175 void
7176 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7177 WebKitWebView *wv)
7179 WebKitDownloadStatus status;
7180 const char *file = NULL;
7181 char *mime = NULL;
7183 if (download == NULL)
7184 return;
7185 status = webkit_download_get_status(download);
7186 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7187 return;
7189 file = webkit_download_get_destination_uri(download);
7190 if (file == NULL)
7191 return;
7192 mime = get_mime_type(file);
7193 if (mime == NULL)
7194 return;
7196 run_download_mimehandler((char *)mime, (char *)file);
7197 g_free(mime);
7201 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7202 WebKitNetworkRequest *request, char *mime_type,
7203 WebKitWebPolicyDecision *decision, struct tab *t)
7205 if (t == NULL) {
7206 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7207 return (FALSE);
7210 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7211 t->tab_id, mime_type);
7213 if (run_mimehandler(t, mime_type, request) == 0) {
7214 webkit_web_policy_decision_ignore(decision);
7215 focus_webview(t);
7216 return (TRUE);
7219 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7220 webkit_web_policy_decision_download(decision);
7221 return (TRUE);
7224 return (FALSE);
7228 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7229 struct tab *t)
7231 struct stat sb;
7232 const gchar *suggested_name;
7233 gchar *filename = NULL;
7234 char *uri = NULL;
7235 struct download *download_entry;
7236 int i, ret = TRUE;
7238 if (wk_download == NULL || t == NULL) {
7239 show_oops(NULL, "%s invalid parameters", __func__);
7240 return (FALSE);
7243 suggested_name = webkit_download_get_suggested_filename(wk_download);
7244 if (suggested_name == NULL)
7245 return (FALSE); /* abort download */
7247 i = 0;
7248 do {
7249 if (filename) {
7250 g_free(filename);
7251 filename = NULL;
7253 if (i) {
7254 g_free(uri);
7255 uri = NULL;
7256 filename = g_strdup_printf("%d%s", i, suggested_name);
7258 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7259 filename : suggested_name);
7260 i++;
7261 } while (!stat(uri + strlen("file://"), &sb));
7263 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7264 "local %s\n", __func__, t->tab_id, filename, uri);
7266 webkit_download_set_destination_uri(wk_download, uri);
7268 if (webkit_download_get_status(wk_download) ==
7269 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7270 show_oops(t, "%s: download failed to start", __func__);
7271 ret = FALSE;
7272 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7273 } else {
7274 /* connect "download first" mime handler */
7275 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7276 G_CALLBACK(download_status_changed_cb), NULL);
7278 download_entry = g_malloc(sizeof(struct download));
7279 download_entry->download = wk_download;
7280 download_entry->tab = t;
7281 download_entry->id = next_download_id++;
7282 RB_INSERT(download_list, &downloads, download_entry);
7283 /* get from history */
7284 g_object_ref(wk_download);
7285 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7286 show_oops(t, "Download of '%s' started...",
7287 basename((char *)webkit_download_get_destination_uri(wk_download)));
7290 if (uri)
7291 g_free(uri);
7293 if (filename)
7294 g_free(filename);
7296 /* sync other download manager tabs */
7297 update_download_tabs(NULL);
7300 * NOTE: never redirect/render the current tab before this
7301 * function returns. This will cause the download to never start.
7303 return (ret); /* start download */
7306 void
7307 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7309 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7311 if (t == NULL) {
7312 show_oops(NULL, "webview_hover_cb");
7313 return;
7316 if (uri)
7317 set_status(t, uri, XT_STATUS_LINK);
7318 else {
7319 if (t->status)
7320 set_status(t, t->status, XT_STATUS_NOTHING);
7325 mark(struct tab *t, struct karg *arg)
7327 char mark;
7328 int index;
7330 mark = arg->s[1];
7331 if ((index = marktoindex(mark)) == -1)
7332 return -1;
7334 if (arg->i == XT_MARK_SET)
7335 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7336 else if (arg->i == XT_MARK_GOTO) {
7337 if (t->mark[index] == XT_INVALID_MARK) {
7338 show_oops(t, "mark '%c' does not exist", mark);
7339 return -1;
7341 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7342 something changes the document size */
7343 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7346 return 0;
7349 void
7350 marks_clear(struct tab *t)
7352 int i;
7354 for (i = 0; i < LENGTH(t->mark); i++)
7355 t->mark[i] = XT_INVALID_MARK;
7359 qmarks_load(void)
7361 char file[PATH_MAX];
7362 char *line = NULL, *p;
7363 int index, i;
7364 FILE *f;
7365 size_t linelen;
7367 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7368 if ((f = fopen(file, "r+")) == NULL) {
7369 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7370 return (1);
7373 for (i = 1; ; i++) {
7374 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7375 break;
7376 if (strlen(line) == 0 || line[0] == '#') {
7377 free(line);
7378 line = NULL;
7379 continue;
7382 p = strtok(line, " \t");
7384 if (p == NULL || strlen(p) != 1 ||
7385 (index = marktoindex(*p)) == -1) {
7386 warnx("corrupt quickmarks file, line %d", i);
7387 break;
7390 p = strtok(NULL, " \t");
7391 if (qmarks[index] != NULL)
7392 g_free(qmarks[index]);
7393 qmarks[index] = g_strdup(p);
7396 fclose(f);
7398 return (0);
7402 qmarks_save(void)
7404 char file[PATH_MAX];
7405 int i;
7406 FILE *f;
7408 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7409 if ((f = fopen(file, "r+")) == NULL) {
7410 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7411 return (1);
7414 for (i = 0; i < XT_NOMARKS; i++)
7415 if (qmarks[i] != NULL)
7416 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7418 fclose(f);
7420 return (0);
7424 qmark(struct tab *t, struct karg *arg)
7426 char mark;
7427 int index;
7429 mark = arg->s[strlen(arg->s)-1];
7430 index = marktoindex(mark);
7431 if (index == -1)
7432 return (-1);
7434 switch (arg->i) {
7435 case XT_QMARK_SET:
7436 if (qmarks[index] != NULL)
7437 g_free(qmarks[index]);
7439 qmarks_load(); /* sync if multiple instances */
7440 qmarks[index] = g_strdup(get_uri(t));
7441 qmarks_save();
7442 break;
7443 case XT_QMARK_OPEN:
7444 if (qmarks[index] != NULL)
7445 load_uri(t, qmarks[index]);
7446 else {
7447 show_oops(t, "quickmark \"%c\" does not exist",
7448 mark);
7449 return (-1);
7451 break;
7452 case XT_QMARK_TAB:
7453 if (qmarks[index] != NULL)
7454 create_new_tab(qmarks[index], NULL, 1, -1);
7455 else {
7456 show_oops(t, "quickmark \"%c\" does not exist",
7457 mark);
7458 return (-1);
7460 break;
7463 return (0);
7467 go_up(struct tab *t, struct karg *args)
7469 int levels;
7470 char *uri;
7471 char *tmp;
7473 levels = atoi(args->s);
7474 if (levels == 0)
7475 levels = 1;
7477 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7478 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7479 return 1;
7480 tmp += strlen(XT_PROTO_DELIM);
7482 /* if an uri starts with a slash, leave it alone (for file:///) */
7483 if (tmp[0] == '/')
7484 tmp++;
7486 while (levels--) {
7487 char *p;
7489 p = strrchr(tmp, '/');
7490 if (p != NULL)
7491 *p = '\0';
7492 else
7493 break;
7496 load_uri(t, uri);
7497 g_free(uri);
7499 return (0);
7503 gototab(struct tab *t, struct karg *args)
7505 int tab;
7506 struct karg arg = {0, NULL, -1};
7508 tab = atoi(args->s);
7510 arg.i = XT_TAB_NEXT;
7511 arg.precount = tab;
7513 movetab(t, &arg);
7515 return (0);
7519 zoom_amount(struct tab *t, struct karg *arg)
7521 struct karg narg = {0, NULL, -1};
7523 narg.i = atoi(arg->s);
7524 resizetab(t, &narg);
7526 return 0;
7530 flip_colon(struct tab *t, struct karg *arg)
7532 struct karg narg = {0, NULL, -1};
7533 char *p;
7535 if (t == NULL || arg == NULL)
7536 return (1);
7538 p = strstr(arg->s, ":");
7539 if (p == NULL)
7540 return (1);
7541 *p = '\0';
7543 narg.i = ':';
7544 narg.s = arg->s;
7545 command(t, &narg);
7547 return (0);
7550 /* buffer commands receive the regex that triggered them in arg.s */
7551 char bcmd[XT_BUFCMD_SZ];
7552 struct buffercmd {
7553 char *regex;
7554 int precount;
7555 #define XT_PRE_NO (0)
7556 #define XT_PRE_YES (1)
7557 #define XT_PRE_MAYBE (2)
7558 char *cmd;
7559 int (*func)(struct tab *, struct karg *);
7560 int arg;
7561 regex_t cregex;
7562 } buffercmds[] = {
7563 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7564 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7565 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7566 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7567 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7568 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7569 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7570 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7571 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7572 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7573 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7574 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7575 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7576 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7577 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7578 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7579 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7580 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7583 void
7584 buffercmd_init(void)
7586 int i;
7588 for (i = 0; i < LENGTH(buffercmds); i++)
7589 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7590 REG_EXTENDED | REG_NOSUB))
7591 startpage_add("invalid buffercmd regex %s",
7592 buffercmds[i].regex);
7595 void
7596 buffercmd_abort(struct tab *t)
7598 int i;
7600 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7601 for (i = 0; i < LENGTH(bcmd); i++)
7602 bcmd[i] = '\0';
7604 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7605 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7608 void
7609 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7611 struct karg arg = {0, NULL, -1};
7613 arg.i = cmd->arg;
7614 arg.s = g_strdup(bcmd);
7616 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7617 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7618 cmd->func(t, &arg);
7620 if (arg.s)
7621 g_free(arg.s);
7623 buffercmd_abort(t);
7626 gboolean
7627 buffercmd_addkey(struct tab *t, guint keyval)
7629 int i, c, match ;
7630 char s[XT_BUFCMD_SZ];
7632 if (keyval == GDK_Escape) {
7633 buffercmd_abort(t);
7634 return (XT_CB_HANDLED);
7637 /* key with modifier or non-ascii character */
7638 if (!isascii(keyval))
7639 return (XT_CB_PASSTHROUGH);
7641 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7642 "to buffer \"%s\"\n", keyval, bcmd);
7644 for (i = 0; i < LENGTH(bcmd); i++)
7645 if (bcmd[i] == '\0') {
7646 bcmd[i] = keyval;
7647 break;
7650 /* buffer full, ignore input */
7651 if (i >= LENGTH(bcmd) -1) {
7652 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7653 buffercmd_abort(t);
7654 return (XT_CB_HANDLED);
7657 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7659 /* find exact match */
7660 for (i = 0; i < LENGTH(buffercmds); i++)
7661 if (regexec(&buffercmds[i].cregex, bcmd,
7662 (size_t) 0, NULL, 0) == 0) {
7663 buffercmd_execute(t, &buffercmds[i]);
7664 goto done;
7667 /* find non exact matches to see if we need to abort ot not */
7668 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7669 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7670 c = -1;
7671 s[0] = '\0';
7672 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7673 if (isdigit(bcmd[0])) {
7674 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7675 continue;
7676 } else {
7677 c = 0;
7678 if (sscanf(bcmd, "%s", s) == 0)
7679 continue;
7681 } else if (buffercmds[i].precount == XT_PRE_YES) {
7682 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7683 continue;
7684 } else {
7685 if (sscanf(bcmd, "%s", s) == 0)
7686 continue;
7688 if (c == -1 && buffercmds[i].precount)
7689 continue;
7690 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7691 match++;
7693 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7694 i, match, buffercmds[i].cmd, c, s);
7696 if (match == 0) {
7697 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7698 buffercmd_abort(t);
7701 done:
7702 return (XT_CB_HANDLED);
7705 gboolean
7706 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7708 struct key_binding *k;
7710 /* handle keybindings if buffercmd is empty.
7711 if not empty, allow commands like C-n */
7712 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7713 TAILQ_FOREACH(k, &kbl, entry)
7714 if (e->keyval == k->key
7715 && (entry ? k->use_in_entry : 1)) {
7716 if (k->mask == 0) {
7717 if ((e->state & (CTRL | MOD1)) == 0)
7718 return (cmd_execute(t, k->cmd));
7719 } else if ((e->state & k->mask) == k->mask) {
7720 return (cmd_execute(t, k->cmd));
7724 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7725 return buffercmd_addkey(t, e->keyval);
7727 return (XT_CB_PASSTHROUGH);
7731 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7733 char s[2], buf[128];
7734 const char *errstr = NULL;
7736 /* don't use w directly; use t->whatever instead */
7738 if (t == NULL) {
7739 show_oops(NULL, "wv_keypress_after_cb");
7740 return (XT_CB_PASSTHROUGH);
7743 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7744 e->keyval, e->state, t);
7746 if (t->hints_on) {
7747 /* ESC */
7748 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7749 disable_hints(t);
7750 return (XT_CB_HANDLED);
7753 /* RETURN */
7754 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7755 if (errstr) {
7756 /* we have a string */
7757 } else {
7758 /* we have a number */
7759 snprintf(buf, sizeof buf,
7760 "vimprobable_fire(%s)", t->hint_num);
7761 run_script(t, buf);
7763 disable_hints(t);
7766 /* BACKSPACE */
7767 /* XXX unfuck this */
7768 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7769 if (t->hint_mode == XT_HINT_NUMERICAL) {
7770 /* last input was numerical */
7771 int l;
7772 l = strlen(t->hint_num);
7773 if (l > 0) {
7774 l--;
7775 if (l == 0) {
7776 disable_hints(t);
7777 enable_hints(t);
7778 } else {
7779 t->hint_num[l] = '\0';
7780 goto num;
7783 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7784 /* last input was alphanumerical */
7785 int l;
7786 l = strlen(t->hint_buf);
7787 if (l > 0) {
7788 l--;
7789 if (l == 0) {
7790 disable_hints(t);
7791 enable_hints(t);
7792 } else {
7793 t->hint_buf[l] = '\0';
7794 goto anum;
7797 } else {
7798 /* bogus */
7799 disable_hints(t);
7803 /* numerical input */
7804 if (CLEAN(e->state) == 0 &&
7805 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7806 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7807 snprintf(s, sizeof s, "%c", e->keyval);
7808 strlcat(t->hint_num, s, sizeof t->hint_num);
7809 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7810 t->hint_num);
7811 num:
7812 if (errstr) {
7813 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7814 "invalid link number\n");
7815 disable_hints(t);
7816 } else {
7817 snprintf(buf, sizeof buf,
7818 "vimprobable_update_hints(%s)",
7819 t->hint_num);
7820 t->hint_mode = XT_HINT_NUMERICAL;
7821 run_script(t, buf);
7824 /* empty the counter buffer */
7825 bzero(t->hint_buf, sizeof t->hint_buf);
7826 return (XT_CB_HANDLED);
7829 /* alphanumerical input */
7830 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7831 e->keyval <= GDK_z) ||
7832 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7833 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7834 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7835 e->keyval <= GDK_9) ||
7836 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7837 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7838 snprintf(s, sizeof s, "%c", e->keyval);
7839 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7840 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7841 " %s\n", t->hint_buf);
7842 anum:
7843 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7844 run_script(t, buf);
7846 snprintf(buf, sizeof buf,
7847 "vimprobable_show_hints('%s')", t->hint_buf);
7848 t->hint_mode = XT_HINT_ALPHANUM;
7849 run_script(t, buf);
7851 /* empty the counter buffer */
7852 bzero(t->hint_num, sizeof t->hint_num);
7853 return (XT_CB_HANDLED);
7856 return (XT_CB_HANDLED);
7857 } else {
7858 /* prefix input*/
7859 snprintf(s, sizeof s, "%c", e->keyval);
7860 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7861 cmd_prefix = 10 * cmd_prefix + atoi(s);
7864 return (handle_keypress(t, e, 0));
7868 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7870 hide_oops(t);
7872 /* Hide buffers, if they are visible, with escape. */
7873 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7874 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7875 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7876 hide_buffers(t);
7877 return (XT_CB_HANDLED);
7880 return (XT_CB_PASSTHROUGH);
7883 gboolean
7884 search_continue(struct tab *t)
7886 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7887 gboolean rv = FALSE;
7889 if (c[0] == ':')
7890 goto done;
7891 if (strlen(c) == 1) {
7892 webkit_web_view_unmark_text_matches(t->wv);
7893 goto done;
7896 if (c[0] == '/')
7897 t->search_forward = TRUE;
7898 else if (c[0] == '?')
7899 t->search_forward = FALSE;
7900 else
7901 goto done;
7903 rv = TRUE;
7904 done:
7905 return (rv);
7908 gboolean
7909 search_cb(struct tab *t)
7911 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7912 GdkColor color;
7914 if (search_continue(t) == FALSE)
7915 goto done;
7917 /* search */
7918 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7919 TRUE) == FALSE) {
7920 /* not found, mark red */
7921 gdk_color_parse(XT_COLOR_RED, &color);
7922 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7923 /* unmark and remove selection */
7924 webkit_web_view_unmark_text_matches(t->wv);
7925 /* my kingdom for a way to unselect text in webview */
7926 } else {
7927 /* found, highlight all */
7928 webkit_web_view_unmark_text_matches(t->wv);
7929 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7930 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7931 gdk_color_parse(XT_COLOR_WHITE, &color);
7932 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7934 done:
7935 t->search_id = 0;
7936 return (FALSE);
7940 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7942 const gchar *c = gtk_entry_get_text(w);
7944 if (t == NULL) {
7945 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7946 return (XT_CB_PASSTHROUGH);
7949 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7950 e->keyval, e->state, t);
7952 if (search_continue(t) == FALSE)
7953 goto done;
7955 /* if search length is > 4 then no longer play timeout games */
7956 if (strlen(c) > 4) {
7957 if (t->search_id) {
7958 g_source_remove(t->search_id);
7959 t->search_id = 0;
7961 search_cb(t);
7962 goto done;
7965 /* reestablish a new timer if the user types fast */
7966 if (t->search_id)
7967 g_source_remove(t->search_id);
7968 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7970 done:
7971 return (XT_CB_PASSTHROUGH);
7974 gboolean
7975 match_uri(const gchar *uri, const gchar *key) {
7976 gchar *voffset;
7977 size_t len;
7978 gboolean match = FALSE;
7980 len = strlen(key);
7982 if (!strncmp(key, uri, len))
7983 match = TRUE;
7984 else {
7985 voffset = strstr(uri, "/") + 2;
7986 if (!strncmp(key, voffset, len))
7987 match = TRUE;
7988 else if (g_str_has_prefix(voffset, "www.")) {
7989 voffset = voffset + strlen("www.");
7990 if (!strncmp(key, voffset, len))
7991 match = TRUE;
7995 return (match);
7998 gboolean
7999 match_session(const gchar *name, const gchar *key) {
8000 char *sub;
8002 sub = strcasestr(name, key);
8004 return sub == name;
8007 void
8008 cmd_getlist(int id, char *key)
8010 int i, dep, c = 0;
8011 struct history *h;
8012 struct session *s;
8014 if (id >= 0) {
8015 if (cmds[id].type & XT_URLARG) {
8016 RB_FOREACH_REVERSE(h, history_list, &hl)
8017 if (match_uri(h->uri, key)) {
8018 cmd_status.list[c] = (char *)h->uri;
8019 if (++c > 255)
8020 break;
8022 cmd_status.len = c;
8023 return;
8024 } else if (cmds[id].type & XT_SESSARG) {
8025 TAILQ_FOREACH(s, &sessions, entry)
8026 if (match_session(s->name, key)) {
8027 cmd_status.list[c] = (char *)s->name;
8028 if (++c > 255)
8029 break;
8031 cmd_status.len = c;
8032 return;
8033 } else if (cmds[id].type & XT_SETARG) {
8034 for (i = 0; i < LENGTH(rs); i++)
8035 if(!strncmp(key, rs[i].name, strlen(key)))
8036 cmd_status.list[c++] = rs[i].name;
8037 cmd_status.len = c;
8038 return;
8042 dep = (id == -1) ? 0 : cmds[id].level + 1;
8044 for (i = id + 1; i < LENGTH(cmds); i++) {
8045 if (cmds[i].level < dep)
8046 break;
8047 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
8048 strlen(key)) && !isdigit(cmds[i].cmd[0]))
8049 cmd_status.list[c++] = cmds[i].cmd;
8053 cmd_status.len = c;
8056 char *
8057 cmd_getnext(int dir)
8059 cmd_status.index += dir;
8061 if (cmd_status.index < 0)
8062 cmd_status.index = cmd_status.len - 1;
8063 else if (cmd_status.index >= cmd_status.len)
8064 cmd_status.index = 0;
8066 return cmd_status.list[cmd_status.index];
8070 cmd_tokenize(char *s, char *tokens[])
8072 int i = 0;
8073 char *tok, *last = NULL;
8074 size_t len = strlen(s);
8075 bool blank;
8077 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8078 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8079 tok = strtok_r(NULL, " ", &last), i++)
8080 tokens[i] = tok;
8082 if (blank && i < 3)
8083 tokens[i++] = "";
8085 return (i);
8088 void
8089 cmd_complete(struct tab *t, char *str, int dir)
8091 GtkEntry *w = GTK_ENTRY(t->cmd);
8092 int i, j, levels, c = 0, dep = 0, parent = -1;
8093 int matchcount = 0;
8094 char *tok, *match, *s = g_strdup(str);
8095 char *tokens[3];
8096 char res[XT_MAX_URL_LENGTH + 32] = ":";
8097 char *sc = s;
8099 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8101 /* copy prefix*/
8102 for (i = 0; isdigit(s[i]); i++)
8103 res[i + 1] = s[i];
8105 for (; isspace(s[i]); i++)
8106 res[i + 1] = s[i];
8108 s += i;
8110 levels = cmd_tokenize(s, tokens);
8112 for (i = 0; i < levels - 1; i++) {
8113 tok = tokens[i];
8114 matchcount = 0;
8115 for (j = c; j < LENGTH(cmds); j++) {
8116 if (cmds[j].level < dep)
8117 break;
8118 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8119 strlen(tok))) {
8120 matchcount++;
8121 c = j + 1;
8122 if (strlen(tok) == strlen(cmds[j].cmd)) {
8123 matchcount = 1;
8124 break;
8129 if (matchcount == 1) {
8130 strlcat(res, tok, sizeof res);
8131 strlcat(res, " ", sizeof res);
8132 dep++;
8133 } else {
8134 g_free(sc);
8135 return;
8138 parent = c - 1;
8141 if (cmd_status.index == -1)
8142 cmd_getlist(parent, tokens[i]);
8144 if (cmd_status.len > 0) {
8145 match = cmd_getnext(dir);
8146 strlcat(res, match, sizeof res);
8147 gtk_entry_set_text(w, res);
8148 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8151 g_free(sc);
8154 gboolean
8155 cmd_execute(struct tab *t, char *str)
8157 struct cmd *cmd = NULL;
8158 char *tok, *last = NULL, *s = g_strdup(str), *sc;
8159 char prefixstr[4];
8160 int j, len, c = 0, dep = 0, matchcount = 0;
8161 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8162 struct karg arg = {0, NULL, -1};
8164 sc = s;
8166 /* copy prefix*/
8167 for (j = 0; j<3 && isdigit(s[j]); j++)
8168 prefixstr[j]=s[j];
8170 prefixstr[j]='\0';
8172 s += j;
8173 while (isspace(s[0]))
8174 s++;
8176 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8177 prefix = atoi(prefixstr);
8178 else
8179 s = sc;
8181 for (tok = strtok_r(s, " ", &last); tok;
8182 tok = strtok_r(NULL, " ", &last)) {
8183 matchcount = 0;
8184 for (j = c; j < LENGTH(cmds); j++) {
8185 if (cmds[j].level < dep)
8186 break;
8187 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8188 strlen(tok);
8189 if (cmds[j].level == dep &&
8190 !strncmp(tok, cmds[j].cmd, len)) {
8191 matchcount++;
8192 c = j + 1;
8193 cmd = &cmds[j];
8194 if (len == strlen(cmds[j].cmd)) {
8195 matchcount = 1;
8196 break;
8200 if (matchcount == 1) {
8201 if (cmd->type > 0)
8202 goto execute_cmd;
8203 dep++;
8204 } else {
8205 show_oops(t, "Invalid command: %s", str);
8206 goto done;
8209 execute_cmd:
8210 if (cmd == NULL) {
8211 show_oops(t, "Empty command");
8212 goto done;
8214 arg.i = cmd->arg;
8216 if (prefix != -1)
8217 arg.precount = prefix;
8218 else if (cmd_prefix > 0)
8219 arg.precount = cmd_prefix;
8221 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8222 show_oops(t, "No prefix allowed: %s", str);
8223 goto done;
8225 if (cmd->type > 1)
8226 arg.s = last ? g_strdup(last) : g_strdup("");
8227 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8228 if (arg.s == NULL) {
8229 show_oops(t, "Invalid command");
8230 goto done;
8232 arg.precount = atoi(arg.s);
8233 if (arg.precount <= 0) {
8234 if (arg.s[0] == '0')
8235 show_oops(t, "Zero count");
8236 else
8237 show_oops(t, "Trailing characters");
8238 goto done;
8242 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8243 __func__, arg.precount, arg.s);
8245 cmd->func(t, &arg);
8247 rv = XT_CB_HANDLED;
8248 done:
8249 if (j > 0)
8250 cmd_prefix = 0;
8251 g_free(sc);
8252 if (arg.s)
8253 g_free(arg.s);
8255 return (rv);
8259 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8261 if (t == NULL) {
8262 show_oops(NULL, "entry_key_cb invalid parameters");
8263 return (XT_CB_PASSTHROUGH);
8266 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8267 e->keyval, e->state, t);
8269 hide_oops(t);
8271 if (e->keyval == GDK_Escape) {
8272 /* don't use focus_webview(t) because we want to type :cmds */
8273 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8276 return (handle_keypress(t, e, 1));
8279 struct command_entry *
8280 history_prev(struct command_list *l, struct command_entry *at)
8282 if (at == NULL)
8283 at = TAILQ_LAST(l, command_list);
8284 else {
8285 at = TAILQ_PREV(at, command_list, entry);
8286 if (at == NULL)
8287 at = TAILQ_LAST(l, command_list);
8290 return (at);
8293 struct command_entry *
8294 history_next(struct command_list *l, struct command_entry *at)
8296 if (at == NULL)
8297 at = TAILQ_FIRST(l);
8298 else {
8299 at = TAILQ_NEXT(at, entry);
8300 if (at == NULL)
8301 at = TAILQ_FIRST(l);
8304 return (at);
8308 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8310 int rv = XT_CB_HANDLED;
8311 const gchar *c = gtk_entry_get_text(w);
8313 if (t == NULL) {
8314 show_oops(NULL, "cmd_keypress_cb parameters");
8315 return (XT_CB_PASSTHROUGH);
8318 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8319 e->keyval, e->state, t);
8321 /* sanity */
8322 if (c == NULL)
8323 e->keyval = GDK_Escape;
8324 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8325 e->keyval = GDK_Escape;
8327 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8328 e->keyval != GDK_ISO_Left_Tab)
8329 cmd_status.index = -1;
8331 switch (e->keyval) {
8332 case GDK_Tab:
8333 if (c[0] == ':')
8334 cmd_complete(t, (char *)&c[1], 1);
8335 goto done;
8336 case GDK_ISO_Left_Tab:
8337 if (c[0] == ':')
8338 cmd_complete(t, (char *)&c[1], -1);
8340 goto done;
8341 case GDK_Down:
8342 if (c[0] != ':') {
8343 if ((search_at = history_next(&shl, search_at))) {
8344 search_at->line[0] = c[0];
8345 gtk_entry_set_text(w, search_at->line);
8346 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8348 } else {
8349 if ((history_at = history_prev(&chl, history_at))) {
8350 history_at->line[0] = c[0];
8351 gtk_entry_set_text(w, history_at->line);
8352 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8356 goto done;
8357 case GDK_Up:
8358 if (c[0] != ':') {
8359 if ((search_at = history_next(&shl, search_at))) {
8360 search_at->line[0] = c[0];
8361 gtk_entry_set_text(w, search_at->line);
8362 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8364 } else {
8365 if ((history_at = history_next(&chl, history_at))) {
8366 history_at->line[0] = c[0];
8367 gtk_entry_set_text(w, history_at->line);
8368 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8372 goto done;
8373 case GDK_BackSpace:
8374 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8375 break;
8376 /* FALLTHROUGH */
8377 case GDK_Escape:
8378 hide_cmd(t);
8379 focus_webview(t);
8381 /* cancel search */
8382 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8383 webkit_web_view_unmark_text_matches(t->wv);
8384 goto done;
8387 rv = XT_CB_PASSTHROUGH;
8388 done:
8389 return (rv);
8392 void
8393 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8395 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8398 void
8399 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8401 /* popup menu enabled */
8402 t->popup = 1;
8406 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8408 if (t == NULL) {
8409 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8410 return (XT_CB_PASSTHROUGH);
8413 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8414 t->tab_id, t->popup);
8416 /* if popup is enabled don't lose focus */
8417 if (t->popup) {
8418 t->popup = 0;
8419 return (XT_CB_PASSTHROUGH);
8422 hide_cmd(t);
8423 hide_oops(t);
8425 if (show_url == 0 || t->focus_wv)
8426 focus_webview(t);
8427 else
8428 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8430 return (XT_CB_PASSTHROUGH);
8433 void
8434 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8436 char *s;
8437 const gchar *c = gtk_entry_get_text(entry);
8439 if (t == NULL) {
8440 show_oops(NULL, "cmd_activate_cb invalid parameters");
8441 return;
8444 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8446 hide_cmd(t);
8448 /* sanity */
8449 if (c == NULL)
8450 goto done;
8451 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8452 goto done;
8453 if (strlen(c) < 2)
8454 goto done;
8455 s = (char *)&c[1];
8457 if (c[0] == '/' || c[0] == '?') {
8458 /* see if there is a timer pending */
8459 if (t->search_id) {
8460 g_source_remove(t->search_id);
8461 t->search_id = 0;
8462 search_cb(t);
8465 if (t->search_text) {
8466 g_free(t->search_text);
8467 t->search_text = NULL;
8470 t->search_text = g_strdup(s);
8471 if (global_search)
8472 g_free(global_search);
8473 global_search = g_strdup(s);
8474 t->search_forward = c[0] == '/';
8476 history_add(&shl, search_file, s, &search_history_count);
8477 goto done;
8480 history_add(&chl, command_file, s, &cmd_history_count);
8481 cmd_execute(t, s);
8482 done:
8483 return;
8486 void
8487 backward_cb(GtkWidget *w, struct tab *t)
8489 struct karg a;
8491 if (t == NULL) {
8492 show_oops(NULL, "backward_cb invalid parameters");
8493 return;
8496 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8498 a.i = XT_NAV_BACK;
8499 navaction(t, &a);
8502 void
8503 forward_cb(GtkWidget *w, struct tab *t)
8505 struct karg a;
8507 if (t == NULL) {
8508 show_oops(NULL, "forward_cb invalid parameters");
8509 return;
8512 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8514 a.i = XT_NAV_FORWARD;
8515 navaction(t, &a);
8518 void
8519 home_cb(GtkWidget *w, struct tab *t)
8521 if (t == NULL) {
8522 show_oops(NULL, "home_cb invalid parameters");
8523 return;
8526 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8528 load_uri(t, home);
8531 void
8532 stop_cb(GtkWidget *w, struct tab *t)
8534 WebKitWebFrame *frame;
8536 if (t == NULL) {
8537 show_oops(NULL, "stop_cb invalid parameters");
8538 return;
8541 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8543 frame = webkit_web_view_get_main_frame(t->wv);
8544 if (frame == NULL) {
8545 show_oops(t, "stop_cb: no frame");
8546 return;
8549 webkit_web_frame_stop_loading(frame);
8550 abort_favicon_download(t);
8553 void
8554 setup_webkit(struct tab *t)
8556 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8557 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8558 FALSE, (char *)NULL);
8559 else
8560 warnx("webkit does not have \"enable-dns-prefetching\" property");
8561 g_object_set(G_OBJECT(t->settings),
8562 "user-agent", t->user_agent, (char *)NULL);
8563 g_object_set(G_OBJECT(t->settings),
8564 "enable-scripts", enable_scripts, (char *)NULL);
8565 g_object_set(G_OBJECT(t->settings),
8566 "enable-plugins", enable_plugins, (char *)NULL);
8567 g_object_set(G_OBJECT(t->settings),
8568 "javascript-can-open-windows-automatically", enable_scripts,
8569 (char *)NULL);
8570 g_object_set(G_OBJECT(t->settings),
8571 "enable-html5-database", FALSE, (char *)NULL);
8572 g_object_set(G_OBJECT(t->settings),
8573 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8574 g_object_set(G_OBJECT(t->settings),
8575 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8576 g_object_set(G_OBJECT(t->settings),
8577 "spell_checking_languages", spell_check_languages, (char *)NULL);
8578 g_object_set(G_OBJECT(t->wv),
8579 "full-content-zoom", TRUE, (char *)NULL);
8581 webkit_web_view_set_settings(t->wv, t->settings);
8584 gboolean
8585 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8587 struct tab *ti, *t = NULL;
8588 gdouble view_size, value, max;
8589 gchar *position;
8591 TAILQ_FOREACH(ti, &tabs, entry)
8592 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8593 t = ti;
8594 break;
8597 if (t == NULL)
8598 return FALSE;
8600 if (adjustment == NULL)
8601 adjustment = gtk_scrolled_window_get_vadjustment(
8602 GTK_SCROLLED_WINDOW(t->browser_win));
8604 view_size = gtk_adjustment_get_page_size(adjustment);
8605 value = gtk_adjustment_get_value(adjustment);
8606 max = gtk_adjustment_get_upper(adjustment) - view_size;
8608 if (max == 0)
8609 position = g_strdup("All");
8610 else if (value == max)
8611 position = g_strdup("Bot");
8612 else if (value == 0)
8613 position = g_strdup("Top");
8614 else
8615 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8617 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8618 g_free(position);
8620 return (TRUE);
8623 GtkWidget *
8624 create_browser(struct tab *t)
8626 GtkWidget *w;
8627 gchar *strval;
8628 GtkAdjustment *adjustment;
8630 if (t == NULL) {
8631 show_oops(NULL, "create_browser invalid parameters");
8632 return (NULL);
8635 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8636 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8637 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8638 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8640 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8641 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8642 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8644 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8645 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8647 /* set defaults */
8648 t->settings = webkit_web_settings_new();
8650 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
8652 if (user_agent == NULL) {
8653 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8654 (char *)NULL);
8655 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8656 g_free(strval);
8657 } else
8658 t->user_agent = g_strdup(user_agent);
8660 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8662 adjustment =
8663 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8664 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8665 G_CALLBACK(update_statusbar_position), NULL);
8667 setup_webkit(t);
8669 return (w);
8672 GtkWidget *
8673 create_window(void)
8675 GtkWidget *w;
8677 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8678 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8679 gtk_widget_set_name(w, "xxxterm");
8680 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8681 g_signal_connect(G_OBJECT(w), "delete_event",
8682 G_CALLBACK (gtk_main_quit), NULL);
8684 return (w);
8687 GtkWidget *
8688 create_kiosk_toolbar(struct tab *t)
8690 GtkWidget *toolbar = NULL, *b;
8692 b = gtk_hbox_new(FALSE, 0);
8693 toolbar = b;
8694 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8696 /* backward button */
8697 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8698 gtk_widget_set_sensitive(t->backward, FALSE);
8699 g_signal_connect(G_OBJECT(t->backward), "clicked",
8700 G_CALLBACK(backward_cb), t);
8701 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8703 /* forward button */
8704 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8705 gtk_widget_set_sensitive(t->forward, FALSE);
8706 g_signal_connect(G_OBJECT(t->forward), "clicked",
8707 G_CALLBACK(forward_cb), t);
8708 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8710 /* home button */
8711 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8712 gtk_widget_set_sensitive(t->gohome, true);
8713 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8714 G_CALLBACK(home_cb), t);
8715 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8717 /* create widgets but don't use them */
8718 t->uri_entry = gtk_entry_new();
8719 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8720 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8721 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8723 return (toolbar);
8726 GtkWidget *
8727 create_toolbar(struct tab *t)
8729 GtkWidget *toolbar = NULL, *b, *eb1;
8731 b = gtk_hbox_new(FALSE, 0);
8732 toolbar = b;
8733 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8735 /* backward button */
8736 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8737 gtk_widget_set_sensitive(t->backward, FALSE);
8738 g_signal_connect(G_OBJECT(t->backward), "clicked",
8739 G_CALLBACK(backward_cb), t);
8740 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8742 /* forward button */
8743 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8744 gtk_widget_set_sensitive(t->forward, FALSE);
8745 g_signal_connect(G_OBJECT(t->forward), "clicked",
8746 G_CALLBACK(forward_cb), t);
8747 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8748 FALSE, 0);
8750 /* stop button */
8751 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8752 gtk_widget_set_sensitive(t->stop, FALSE);
8753 g_signal_connect(G_OBJECT(t->stop), "clicked",
8754 G_CALLBACK(stop_cb), t);
8755 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8756 FALSE, 0);
8758 /* JS button */
8759 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8760 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8761 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8762 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8763 G_CALLBACK(js_toggle_cb), t);
8764 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8766 t->uri_entry = gtk_entry_new();
8767 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8768 G_CALLBACK(activate_uri_entry_cb), t);
8769 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8770 G_CALLBACK(entry_key_cb), t);
8771 completion_add(t);
8772 eb1 = gtk_hbox_new(FALSE, 0);
8773 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8774 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8775 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8777 /* search entry */
8778 if (search_string) {
8779 GtkWidget *eb2;
8780 t->search_entry = gtk_entry_new();
8781 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8782 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8783 G_CALLBACK(activate_search_entry_cb), t);
8784 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8785 G_CALLBACK(entry_key_cb), t);
8786 gtk_widget_set_size_request(t->search_entry, -1, -1);
8787 eb2 = gtk_hbox_new(FALSE, 0);
8788 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8789 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8791 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8794 return (toolbar);
8797 GtkWidget *
8798 create_buffers(struct tab *t)
8800 GtkCellRenderer *renderer;
8801 GtkWidget *view;
8803 view = gtk_tree_view_new();
8805 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8807 renderer = gtk_cell_renderer_text_new();
8808 gtk_tree_view_insert_column_with_attributes
8809 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8811 renderer = gtk_cell_renderer_text_new();
8812 gtk_tree_view_insert_column_with_attributes
8813 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8814 (char *)NULL);
8816 gtk_tree_view_set_model
8817 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8819 return view;
8822 void
8823 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8824 GtkTreeViewColumn *col, struct tab *t)
8826 GtkTreeIter iter;
8827 guint id;
8829 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8831 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8832 path)) {
8833 gtk_tree_model_get
8834 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8835 set_current_tab(id - 1);
8838 hide_buffers(t);
8841 /* after tab reordering/creation/removal */
8842 void
8843 recalc_tabs(void)
8845 struct tab *t;
8846 int maxid = 0;
8848 TAILQ_FOREACH(t, &tabs, entry) {
8849 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8850 if (t->tab_id > maxid)
8851 maxid = t->tab_id;
8853 gtk_widget_show(t->tab_elems.sep);
8856 TAILQ_FOREACH(t, &tabs, entry) {
8857 if (t->tab_id == maxid) {
8858 gtk_widget_hide(t->tab_elems.sep);
8859 break;
8864 /* after active tab change */
8865 void
8866 recolor_compact_tabs(void)
8868 struct tab *t;
8869 int curid = 0;
8870 GdkColor color;
8872 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8873 TAILQ_FOREACH(t, &tabs, entry)
8874 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8875 &color);
8877 curid = gtk_notebook_get_current_page(notebook);
8878 TAILQ_FOREACH(t, &tabs, entry)
8879 if (t->tab_id == curid) {
8880 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8881 gtk_widget_modify_fg(t->tab_elems.label,
8882 GTK_STATE_NORMAL, &color);
8883 break;
8887 void
8888 set_current_tab(int page_num)
8890 buffercmd_abort(get_current_tab());
8891 gtk_notebook_set_current_page(notebook, page_num);
8892 recolor_compact_tabs();
8896 undo_close_tab_save(struct tab *t)
8898 int m, n;
8899 const gchar *uri;
8900 struct undo *u1, *u2;
8901 GList *items;
8902 WebKitWebHistoryItem *item;
8904 if ((uri = get_uri(t)) == NULL)
8905 return (1);
8907 u1 = g_malloc0(sizeof(struct undo));
8908 u1->uri = g_strdup(uri);
8910 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8912 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8913 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8914 u1->back = n;
8916 /* forward history */
8917 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8919 while (items) {
8920 item = items->data;
8921 u1->history = g_list_prepend(u1->history,
8922 webkit_web_history_item_copy(item));
8923 items = g_list_next(items);
8926 /* current item */
8927 if (m) {
8928 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8929 u1->history = g_list_prepend(u1->history,
8930 webkit_web_history_item_copy(item));
8933 /* back history */
8934 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8936 while (items) {
8937 item = items->data;
8938 u1->history = g_list_prepend(u1->history,
8939 webkit_web_history_item_copy(item));
8940 items = g_list_next(items);
8943 TAILQ_INSERT_HEAD(&undos, u1, entry);
8945 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8946 u2 = TAILQ_LAST(&undos, undo_tailq);
8947 TAILQ_REMOVE(&undos, u2, entry);
8948 g_free(u2->uri);
8949 g_list_free(u2->history);
8950 g_free(u2);
8951 } else
8952 undo_count++;
8954 return (0);
8957 void
8958 delete_tab(struct tab *t)
8960 struct karg a;
8962 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8964 if (t == NULL)
8965 return;
8968 * no need to join thread here because it won't access t on completion
8971 buffercmd_abort(t);
8972 TAILQ_REMOVE(&tabs, t, entry);
8974 /* Halt all webkit activity. */
8975 abort_favicon_download(t);
8976 webkit_web_view_stop_loading(t->wv);
8978 /* Save the tab, so we can undo the close. */
8979 undo_close_tab_save(t);
8981 if (browser_mode == XT_BM_KIOSK) {
8982 gtk_widget_destroy(t->uri_entry);
8983 gtk_widget_destroy(t->stop);
8984 gtk_widget_destroy(t->js_toggle);
8987 gtk_widget_destroy(t->tab_elems.eventbox);
8988 gtk_widget_destroy(t->vbox);
8990 /* just in case */
8991 if (t->search_id)
8992 g_source_remove(t->search_id);
8994 g_free(t->user_agent);
8995 g_free(t->stylesheet);
8996 g_free(t->tmp_uri);
8997 g_free(t);
8999 if (TAILQ_EMPTY(&tabs)) {
9000 if (browser_mode == XT_BM_KIOSK)
9001 create_new_tab(home, NULL, 1, -1);
9002 else
9003 create_new_tab(NULL, NULL, 1, -1);
9006 /* recreate session */
9007 if (session_autosave) {
9008 a.s = NULL;
9009 save_tabs(t, &a);
9012 recalc_tabs();
9013 recolor_compact_tabs();
9016 void
9017 update_statusbar_zoom(struct tab *t)
9019 gfloat zoom;
9020 char s[16] = { '\0' };
9022 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9023 if ((zoom <= 0.99 || zoom >= 1.01))
9024 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
9025 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
9028 void
9029 setzoom_webkit(struct tab *t, int adjust)
9031 #define XT_ZOOMPERCENT 0.04
9033 gfloat zoom;
9035 if (t == NULL) {
9036 show_oops(NULL, "setzoom_webkit invalid parameters");
9037 return;
9040 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9041 if (adjust == XT_ZOOM_IN)
9042 zoom += XT_ZOOMPERCENT;
9043 else if (adjust == XT_ZOOM_OUT)
9044 zoom -= XT_ZOOMPERCENT;
9045 else if (adjust > 0)
9046 zoom = default_zoom_level + adjust / 100.0 - 1.0;
9047 else {
9048 show_oops(t, "setzoom_webkit invalid zoom value");
9049 return;
9052 if (zoom < XT_ZOOMPERCENT)
9053 zoom = XT_ZOOMPERCENT;
9054 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
9055 update_statusbar_zoom(t);
9058 gboolean
9059 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
9061 struct tab *t = (struct tab *) data;
9063 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
9065 switch (event->button) {
9066 case 1:
9067 set_current_tab(t->tab_id);
9068 break;
9069 case 2:
9070 delete_tab(t);
9071 break;
9074 return TRUE;
9077 void
9078 append_tab(struct tab *t)
9080 if (t == NULL)
9081 return;
9083 TAILQ_INSERT_TAIL(&tabs, t, entry);
9084 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9087 GtkWidget *
9088 create_sbe(int width)
9090 GtkWidget *sbe;
9092 sbe = gtk_entry_new();
9093 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9094 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9095 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9096 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9097 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9098 gtk_widget_set_size_request(sbe, width, -1);
9100 return sbe;
9103 struct tab *
9104 create_new_tab(char *title, struct undo *u, int focus, int position)
9106 struct tab *t;
9107 int load = 1, id;
9108 GtkWidget *b, *bb;
9109 WebKitWebHistoryItem *item;
9110 GList *items;
9111 GdkColor color;
9112 char *p;
9113 int sbe_p = 0, sbe_b = 0,
9114 sbe_z = 0;
9116 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9118 if (tabless && !TAILQ_EMPTY(&tabs)) {
9119 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9120 return (NULL);
9123 t = g_malloc0(sizeof *t);
9125 if (title == NULL) {
9126 title = "(untitled)";
9127 load = 0;
9130 t->vbox = gtk_vbox_new(FALSE, 0);
9132 /* label + button for tab */
9133 b = gtk_hbox_new(FALSE, 0);
9134 t->tab_content = b;
9136 #if GTK_CHECK_VERSION(2, 20, 0)
9137 t->spinner = gtk_spinner_new();
9138 #endif
9139 t->label = gtk_label_new(title);
9140 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9141 gtk_widget_set_size_request(t->label, 100, 0);
9142 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9143 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9144 gtk_widget_set_size_request(b, 130, 0);
9146 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9147 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9148 #if GTK_CHECK_VERSION(2, 20, 0)
9149 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9150 #endif
9152 /* toolbar */
9153 if (browser_mode == XT_BM_KIOSK) {
9154 t->toolbar = create_kiosk_toolbar(t);
9155 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9157 } else {
9158 t->toolbar = create_toolbar(t);
9159 if (fancy_bar)
9160 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9161 FALSE, 0);
9164 /* marks */
9165 marks_clear(t);
9167 /* browser */
9168 t->browser_win = create_browser(t);
9169 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9171 /* oops message for user feedback */
9172 t->oops = gtk_entry_new();
9173 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9174 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9175 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9176 gdk_color_parse(XT_COLOR_RED, &color);
9177 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9178 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9179 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9181 /* command entry */
9182 t->cmd = gtk_entry_new();
9183 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9184 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9185 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9186 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9188 /* status bar */
9189 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9191 t->sbe.statusbar = gtk_entry_new();
9192 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9193 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9194 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9195 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9197 /* create these widgets only if specified in statusbar_elems */
9199 t->sbe.position = create_sbe(40);
9200 t->sbe.zoom = create_sbe(40);
9201 t->sbe.buffercmd = create_sbe(60);
9203 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9205 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9206 TRUE, FALSE);
9208 /* gtk widgets cannot be added to a box twice. sbe_* variables
9209 make sure of this */
9210 for (p = statusbar_elems; *p != '\0'; p++) {
9211 switch (*p) {
9212 case '|':
9214 GtkWidget *sep = gtk_vseparator_new();
9216 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9217 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9218 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9219 FALSE, FALSE, FALSE);
9220 break;
9222 case 'P':
9223 if (sbe_p) {
9224 warnx("flag \"%c\" specified more than "
9225 "once in statusbar_elems\n", *p);
9226 break;
9228 sbe_p = 1;
9229 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9230 t->sbe.position, FALSE, FALSE, FALSE);
9231 break;
9232 case 'B':
9233 if (sbe_b) {
9234 warnx("flag \"%c\" specified more than "
9235 "once in statusbar_elems\n", *p);
9236 break;
9238 sbe_b = 1;
9239 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9240 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9241 break;
9242 case 'Z':
9243 if (sbe_z) {
9244 warnx("flag \"%c\" specified more than "
9245 "once in statusbar_elems\n", *p);
9246 break;
9248 sbe_z = 1;
9249 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9250 t->sbe.zoom, FALSE, FALSE, FALSE);
9251 break;
9252 default:
9253 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9254 break;
9258 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9260 /* buffer list */
9261 t->buffers = create_buffers(t);
9262 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9264 /* xtp meaning is normal by default */
9265 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9267 /* set empty favicon */
9268 xt_icon_from_name(t, "text-html");
9270 /* and show it all */
9271 gtk_widget_show_all(b);
9272 gtk_widget_show_all(t->vbox);
9274 /* compact tab bar */
9275 t->tab_elems.label = gtk_label_new(title);
9276 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9277 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9278 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9279 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9281 t->tab_elems.eventbox = gtk_event_box_new();
9282 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9283 t->tab_elems.sep = gtk_vseparator_new();
9285 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9286 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9287 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9288 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9289 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9290 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9292 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9293 TRUE, 0);
9294 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9295 FALSE, 0);
9296 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9297 t->tab_elems.box);
9299 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9300 TRUE, 0);
9301 gtk_widget_show_all(t->tab_elems.eventbox);
9303 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9304 append_tab(t);
9305 else {
9306 id = position >= 0 ? position :
9307 gtk_notebook_get_current_page(notebook) + 1;
9308 if (id > gtk_notebook_get_n_pages(notebook))
9309 append_tab(t);
9310 else {
9311 TAILQ_INSERT_TAIL(&tabs, t, entry);
9312 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9313 gtk_box_reorder_child(GTK_BOX(tab_bar),
9314 t->tab_elems.eventbox, id);
9315 recalc_tabs();
9319 #if GTK_CHECK_VERSION(2, 20, 0)
9320 /* turn spinner off if we are a new tab without uri */
9321 if (!load) {
9322 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9323 gtk_widget_hide(t->spinner);
9325 #endif
9326 /* make notebook tabs reorderable */
9327 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9329 /* compact tabs clickable */
9330 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9331 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9333 g_object_connect(G_OBJECT(t->cmd),
9334 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9335 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9336 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9337 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9338 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9339 (char *)NULL);
9341 /* reuse wv_button_cb to hide oops */
9342 g_object_connect(G_OBJECT(t->oops),
9343 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9344 (char *)NULL);
9346 g_signal_connect(t->buffers,
9347 "row-activated", G_CALLBACK(row_activated_cb), t);
9348 g_object_connect(G_OBJECT(t->buffers),
9349 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9351 g_object_connect(G_OBJECT(t->wv),
9352 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9353 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9354 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9355 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9356 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9357 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9358 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9359 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9360 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9361 "signal::event", G_CALLBACK(webview_event_cb), t,
9362 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9363 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9364 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9365 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9366 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9367 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9368 (char *)NULL);
9369 g_signal_connect(t->wv,
9370 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9371 g_signal_connect(t->wv,
9372 "notify::title", G_CALLBACK(notify_title_cb), t);
9374 /* hijack the unused keys as if we were the browser */
9375 g_object_connect(G_OBJECT(t->toolbar),
9376 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9377 (char *)NULL);
9379 g_signal_connect(G_OBJECT(bb), "button_press_event",
9380 G_CALLBACK(tab_close_cb), t);
9382 /* setup history */
9383 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9384 /* restore the tab's history */
9385 if (u && u->history) {
9386 items = u->history;
9387 while (items) {
9388 item = items->data;
9389 webkit_web_back_forward_list_add_item(t->bfl, item);
9390 items = g_list_next(items);
9393 item = g_list_nth_data(u->history, u->back);
9394 if (item)
9395 webkit_web_view_go_to_back_forward_item(t->wv, item);
9397 g_list_free(items);
9398 g_list_free(u->history);
9399 } else
9400 webkit_web_back_forward_list_clear(t->bfl);
9402 /* hide stuff */
9403 hide_cmd(t);
9404 hide_oops(t);
9405 hide_buffers(t);
9406 url_set_visibility();
9407 statusbar_set_visibility();
9409 if (focus) {
9410 set_current_tab(t->tab_id);
9411 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9412 t->tab_id);
9414 if (load) {
9415 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9416 load_uri(t, title);
9417 } else {
9418 if (show_url == 1)
9419 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9420 else
9421 focus_webview(t);
9423 } else if (load)
9424 load_uri(t, title);
9426 recolor_compact_tabs();
9427 setzoom_webkit(t, XT_ZOOM_NORMAL);
9428 return (t);
9431 void
9432 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9433 gpointer *udata)
9435 struct tab *t;
9436 const gchar *uri;
9438 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9440 if (gtk_notebook_get_current_page(notebook) == -1)
9441 recalc_tabs();
9443 TAILQ_FOREACH(t, &tabs, entry) {
9444 if (t->tab_id == pn) {
9445 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9446 "%d\n", pn);
9448 uri = get_title(t, TRUE);
9449 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9451 hide_cmd(t);
9452 hide_oops(t);
9454 if (t->focus_wv) {
9455 /* can't use focus_webview here */
9456 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9462 void
9463 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9464 gpointer *udata)
9466 struct tab *t = NULL, *tt;
9468 recalc_tabs();
9470 TAILQ_FOREACH(tt, &tabs, entry)
9471 if (tt->tab_id == pn) {
9472 t = tt;
9473 break;
9475 if (t == NULL)
9476 return;
9477 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9479 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9480 t->tab_id);
9483 void
9484 menuitem_response(struct tab *t)
9486 gtk_notebook_set_current_page(notebook, t->tab_id);
9489 gboolean
9490 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9492 GtkWidget *menu, *menu_items;
9493 GdkEventButton *bevent;
9494 const gchar *uri;
9495 struct tab *ti;
9497 if (event->type == GDK_BUTTON_PRESS) {
9498 bevent = (GdkEventButton *) event;
9499 menu = gtk_menu_new();
9501 TAILQ_FOREACH(ti, &tabs, entry) {
9502 if ((uri = get_uri(ti)) == NULL)
9503 /* XXX make sure there is something to print */
9504 /* XXX add gui pages in here to look purdy */
9505 uri = "(untitled)";
9506 menu_items = gtk_menu_item_new_with_label(uri);
9507 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9508 gtk_widget_show(menu_items);
9510 g_signal_connect_swapped((menu_items),
9511 "activate", G_CALLBACK(menuitem_response),
9512 (gpointer)ti);
9515 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9516 bevent->button, bevent->time);
9518 /* unref object so it'll free itself when popped down */
9519 #if !GTK_CHECK_VERSION(3, 0, 0)
9520 /* XXX does not need unref with gtk+3? */
9521 g_object_ref_sink(menu);
9522 g_object_unref(menu);
9523 #endif
9525 return (TRUE /* eat event */);
9528 return (FALSE /* propagate */);
9532 icon_size_map(int icon_size)
9534 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9535 icon_size > GTK_ICON_SIZE_DIALOG)
9536 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9538 return (icon_size);
9541 GtkWidget *
9542 create_button(char *name, char *stockid, int size)
9544 GtkWidget *button, *image;
9545 gchar *rcstring;
9546 int gtk_icon_size;
9548 rcstring = g_strdup_printf(
9549 "style \"%s-style\"\n"
9550 "{\n"
9551 " GtkWidget::focus-padding = 0\n"
9552 " GtkWidget::focus-line-width = 0\n"
9553 " xthickness = 0\n"
9554 " ythickness = 0\n"
9555 "}\n"
9556 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9557 gtk_rc_parse_string(rcstring);
9558 g_free(rcstring);
9559 button = gtk_button_new();
9560 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9561 gtk_icon_size = icon_size_map(size ? size : icon_size);
9563 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9564 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9565 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9566 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9567 gtk_widget_set_name(button, name);
9568 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9570 return (button);
9573 void
9574 button_set_stockid(GtkWidget *button, char *stockid)
9576 GtkWidget *image;
9578 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9579 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9580 gtk_button_set_image(GTK_BUTTON(button), image);
9583 void
9584 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9586 gchar *p = NULL;
9587 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9588 gint len;
9590 if (xterm_workaround == 0)
9591 return;
9594 * xterm doesn't play nice with clipboards because it clears the
9595 * primary when clicked. We rely on primary being set to properly
9596 * handle middle mouse button clicks (paste). So when someone clears
9597 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9598 * other application behavior (as in DON'T clear primary).
9601 p = gtk_clipboard_wait_for_text(primary);
9602 if (p == NULL) {
9603 if (gdk_property_get(gdk_get_default_root_window(),
9604 atom,
9605 gdk_atom_intern("STRING", FALSE),
9607 1024 * 1024 /* picked out of my butt */,
9608 FALSE,
9609 NULL,
9610 NULL,
9611 &len,
9612 (guchar **)&p)) {
9613 /* yes sir, we need to NUL the string */
9614 p[len] = '\0';
9615 gtk_clipboard_set_text(primary, p, -1);
9619 if (p)
9620 g_free(p);
9623 void
9624 create_canvas(void)
9626 GtkWidget *vbox;
9627 GList *l = NULL;
9628 GdkPixbuf *pb;
9629 char file[PATH_MAX];
9630 int i;
9632 vbox = gtk_vbox_new(FALSE, 0);
9633 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9634 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9635 #if !GTK_CHECK_VERSION(3, 0, 0)
9636 /* XXX seems to be needed with gtk+2 */
9637 gtk_notebook_set_tab_hborder(notebook, 0);
9638 gtk_notebook_set_tab_vborder(notebook, 0);
9639 #endif
9640 gtk_notebook_set_scrollable(notebook, TRUE);
9641 gtk_notebook_set_show_border(notebook, FALSE);
9642 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9644 abtn = gtk_button_new();
9645 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9646 gtk_widget_set_size_request(arrow, -1, -1);
9647 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9648 gtk_widget_set_size_request(abtn, -1, 20);
9650 #if GTK_CHECK_VERSION(2, 20, 0)
9651 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9652 #endif
9653 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9655 /* compact tab bar */
9656 tab_bar = gtk_hbox_new(TRUE, 0);
9658 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9659 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9660 gtk_widget_set_size_request(vbox, -1, -1);
9662 g_object_connect(G_OBJECT(notebook),
9663 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9664 (char *)NULL);
9665 g_object_connect(G_OBJECT(notebook),
9666 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9667 NULL, (char *)NULL);
9668 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9669 G_CALLBACK(arrow_cb), NULL);
9671 main_window = create_window();
9672 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9674 /* icons */
9675 for (i = 0; i < LENGTH(icons); i++) {
9676 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9677 pb = gdk_pixbuf_new_from_file(file, NULL);
9678 l = g_list_append(l, pb);
9680 gtk_window_set_default_icon_list(l);
9682 /* clipboard work around */
9683 if (xterm_workaround)
9684 g_signal_connect(
9685 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9686 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9688 gtk_widget_show_all(abtn);
9689 gtk_widget_show_all(main_window);
9690 notebook_tab_set_visibility();
9693 void
9694 set_hook(void **hook, char *name)
9696 if (hook == NULL)
9697 errx(1, "set_hook");
9699 if (*hook == NULL) {
9700 *hook = dlsym(RTLD_NEXT, name);
9701 if (*hook == NULL)
9702 errx(1, "can't hook %s", name);
9706 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9707 gboolean
9708 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9710 g_return_val_if_fail(cookie1, FALSE);
9711 g_return_val_if_fail(cookie2, FALSE);
9713 return (!strcmp (cookie1->name, cookie2->name) &&
9714 !strcmp (cookie1->value, cookie2->value) &&
9715 !strcmp (cookie1->path, cookie2->path) &&
9716 !strcmp (cookie1->domain, cookie2->domain));
9719 void
9720 transfer_cookies(void)
9722 GSList *cf;
9723 SoupCookie *sc, *pc;
9725 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9727 for (;cf; cf = cf->next) {
9728 pc = cf->data;
9729 sc = soup_cookie_copy(pc);
9730 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9733 soup_cookies_free(cf);
9736 void
9737 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9739 GSList *cf;
9740 SoupCookie *ci;
9742 print_cookie("soup_cookie_jar_delete_cookie", c);
9744 if (cookies_enabled == 0)
9745 return;
9747 if (jar == NULL || c == NULL)
9748 return;
9750 /* find and remove from persistent jar */
9751 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9753 for (;cf; cf = cf->next) {
9754 ci = cf->data;
9755 if (soup_cookie_equal(ci, c)) {
9756 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9757 break;
9761 soup_cookies_free(cf);
9763 /* delete from session jar */
9764 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9767 void
9768 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9770 struct domain *d = NULL;
9771 SoupCookie *c;
9772 FILE *r_cookie_f;
9774 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9775 jar, p_cookiejar, s_cookiejar);
9777 if (cookies_enabled == 0)
9778 return;
9780 /* see if we are up and running */
9781 if (p_cookiejar == NULL) {
9782 _soup_cookie_jar_add_cookie(jar, cookie);
9783 return;
9785 /* disallow p_cookiejar adds, shouldn't happen */
9786 if (jar == p_cookiejar)
9787 return;
9789 /* sanity */
9790 if (jar == NULL || cookie == NULL)
9791 return;
9793 if (enable_cookie_whitelist &&
9794 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9795 blocked_cookies++;
9796 DNPRINTF(XT_D_COOKIE,
9797 "soup_cookie_jar_add_cookie: reject %s\n",
9798 cookie->domain);
9799 if (save_rejected_cookies) {
9800 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9801 show_oops(NULL, "can't open reject cookie file");
9802 return;
9804 fseek(r_cookie_f, 0, SEEK_END);
9805 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9806 cookie->http_only ? "#HttpOnly_" : "",
9807 cookie->domain,
9808 *cookie->domain == '.' ? "TRUE" : "FALSE",
9809 cookie->path,
9810 cookie->secure ? "TRUE" : "FALSE",
9811 cookie->expires ?
9812 (gulong)soup_date_to_time_t(cookie->expires) :
9814 cookie->name,
9815 cookie->value);
9816 fflush(r_cookie_f);
9817 fclose(r_cookie_f);
9819 if (!allow_volatile_cookies)
9820 return;
9823 if (cookie->expires == NULL && session_timeout) {
9824 soup_cookie_set_expires(cookie,
9825 soup_date_new_from_now(session_timeout));
9826 print_cookie("modified add cookie", cookie);
9829 /* see if we are white listed for persistence */
9830 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9831 /* add to persistent jar */
9832 c = soup_cookie_copy(cookie);
9833 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9834 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9837 /* add to session jar */
9838 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9839 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9842 void
9843 setup_cookies(void)
9845 char file[PATH_MAX];
9847 set_hook((void *)&_soup_cookie_jar_add_cookie,
9848 "soup_cookie_jar_add_cookie");
9849 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9850 "soup_cookie_jar_delete_cookie");
9852 if (cookies_enabled == 0)
9853 return;
9856 * the following code is intricate due to overriding several libsoup
9857 * functions.
9858 * do not alter order of these operations.
9861 /* rejected cookies */
9862 if (save_rejected_cookies)
9863 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9864 XT_REJECT_FILE);
9866 /* persistent cookies */
9867 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9868 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9870 /* session cookies */
9871 s_cookiejar = soup_cookie_jar_new();
9872 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9873 cookie_policy, (void *)NULL);
9874 transfer_cookies();
9876 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9879 void
9880 setup_proxy(char *uri)
9882 if (proxy_uri) {
9883 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9884 soup_uri_free(proxy_uri);
9885 proxy_uri = NULL;
9887 if (http_proxy) {
9888 if (http_proxy != uri) {
9889 g_free(http_proxy);
9890 http_proxy = NULL;
9894 if (uri) {
9895 http_proxy = g_strdup(uri);
9896 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9897 proxy_uri = soup_uri_new(http_proxy);
9898 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9899 g_object_set(session, "proxy-uri", proxy_uri,
9900 (char *)NULL);
9905 set_http_proxy(char *proxy)
9907 SoupURI *uri;
9909 if (proxy == NULL)
9910 return (1);
9912 /* see if we need to clear it instead */
9913 if (strlen(proxy) == 0) {
9914 setup_proxy(NULL);
9915 return (0);
9918 uri = soup_uri_new(proxy);
9919 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9920 return (1);
9922 setup_proxy(proxy);
9924 soup_uri_free(uri);
9926 return (0);
9930 send_cmd_to_socket(char *cmd)
9932 int s, len, rv = 1;
9933 struct sockaddr_un sa;
9935 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9936 warnx("%s: socket", __func__);
9937 return (rv);
9940 sa.sun_family = AF_UNIX;
9941 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9942 work_dir, XT_SOCKET_FILE);
9943 len = SUN_LEN(&sa);
9945 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9946 warnx("%s: connect", __func__);
9947 goto done;
9950 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9951 warnx("%s: send", __func__);
9952 goto done;
9955 rv = 0;
9956 done:
9957 close(s);
9958 return (rv);
9961 gboolean
9962 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9964 int s, n;
9965 char str[XT_MAX_URL_LENGTH];
9966 socklen_t t = sizeof(struct sockaddr_un);
9967 struct sockaddr_un sa;
9968 struct passwd *p;
9969 uid_t uid;
9970 gid_t gid;
9971 struct tab *tt;
9972 gint fd = g_io_channel_unix_get_fd(source);
9974 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9975 warn("accept");
9976 return (FALSE);
9979 if (getpeereid(s, &uid, &gid) == -1) {
9980 warn("getpeereid");
9981 return (FALSE);
9983 if (uid != getuid() || gid != getgid()) {
9984 warnx("unauthorized user");
9985 return (FALSE);
9988 p = getpwuid(uid);
9989 if (p == NULL) {
9990 warnx("not a valid user");
9991 return (FALSE);
9994 n = recv(s, str, sizeof(str), 0);
9995 if (n <= 0)
9996 return (TRUE);
9998 tt = TAILQ_LAST(&tabs, tab_list);
9999 cmd_execute(tt, str);
10000 return (TRUE);
10004 is_running(void)
10006 int s, len, rv = 1;
10007 struct sockaddr_un sa;
10009 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10010 warn("is_running: socket");
10011 return (-1);
10014 sa.sun_family = AF_UNIX;
10015 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10016 work_dir, XT_SOCKET_FILE);
10017 len = SUN_LEN(&sa);
10019 /* connect to see if there is a listener */
10020 if (connect(s, (struct sockaddr *)&sa, len) == -1)
10021 rv = 0; /* not running */
10022 else
10023 rv = 1; /* already running */
10025 close(s);
10027 return (rv);
10031 build_socket(void)
10033 int s, len;
10034 struct sockaddr_un sa;
10036 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10037 warn("build_socket: socket");
10038 return (-1);
10041 sa.sun_family = AF_UNIX;
10042 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10043 work_dir, XT_SOCKET_FILE);
10044 len = SUN_LEN(&sa);
10046 /* connect to see if there is a listener */
10047 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10048 /* no listener so we will */
10049 unlink(sa.sun_path);
10051 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
10052 warn("build_socket: bind");
10053 goto done;
10056 if (listen(s, 1) == -1) {
10057 warn("build_socket: listen");
10058 goto done;
10061 return (s);
10064 done:
10065 close(s);
10066 return (-1);
10069 gboolean
10070 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10071 GtkTreeIter *iter, struct tab *t)
10073 gchar *value;
10075 gtk_tree_model_get(model, iter, 0, &value, -1);
10076 load_uri(t, value);
10077 g_free(value);
10079 return (FALSE);
10082 gboolean
10083 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10084 GtkTreeIter *iter, struct tab *t)
10086 gchar *value;
10088 gtk_tree_model_get(model, iter, 0, &value, -1);
10089 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10090 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10091 g_free(value);
10093 return (TRUE);
10096 void
10097 completion_add_uri(const gchar *uri)
10099 GtkTreeIter iter;
10101 /* add uri to list_store */
10102 gtk_list_store_append(completion_model, &iter);
10103 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10106 gboolean
10107 completion_match(GtkEntryCompletion *completion, const gchar *key,
10108 GtkTreeIter *iter, gpointer user_data)
10110 gchar *value;
10111 gboolean match = FALSE;
10113 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10114 -1);
10116 if (value == NULL)
10117 return FALSE;
10119 match = match_uri(value, key);
10121 g_free(value);
10122 return (match);
10125 void
10126 completion_add(struct tab *t)
10128 /* enable completion for tab */
10129 t->completion = gtk_entry_completion_new();
10130 gtk_entry_completion_set_text_column(t->completion, 0);
10131 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10132 gtk_entry_completion_set_model(t->completion,
10133 GTK_TREE_MODEL(completion_model));
10134 gtk_entry_completion_set_match_func(t->completion, completion_match,
10135 NULL, NULL);
10136 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10137 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10138 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10139 G_CALLBACK(completion_select_cb), t);
10140 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10141 G_CALLBACK(completion_hover_cb), t);
10144 void
10145 xxx_dir(char *dir)
10147 struct stat sb;
10149 if (stat(dir, &sb)) {
10150 if (mkdir(dir, S_IRWXU) == -1)
10151 err(1, "mkdir %s", dir);
10152 if (stat(dir, &sb))
10153 err(1, "stat %s", dir);
10155 if (S_ISDIR(sb.st_mode) == 0)
10156 errx(1, "%s not a dir", dir);
10157 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10158 warnx("fixing invalid permissions on %s", dir);
10159 if (chmod(dir, S_IRWXU) == -1)
10160 err(1, "chmod %s", dir);
10164 void
10165 usage(void)
10167 fprintf(stderr,
10168 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10169 exit(0);
10174 main(int argc, char *argv[])
10176 struct stat sb;
10177 int c, s, optn = 0, opte = 0, focus = 1;
10178 char conf[PATH_MAX] = { '\0' };
10179 char file[PATH_MAX];
10180 char *env_proxy = NULL;
10181 char *cmd = NULL;
10182 FILE *f = NULL;
10183 struct karg a;
10184 struct sigaction sact;
10185 GIOChannel *channel;
10186 struct rlimit rlp;
10188 start_argv = argv;
10190 /* prepare gtk */
10191 g_thread_init(NULL);
10192 #if !defined(__linux__)
10193 gdk_threads_init();
10194 #endif
10195 gdk_threads_enter();
10196 gtk_init(&argc, &argv);
10198 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10200 RB_INIT(&hl);
10201 RB_INIT(&js_wl);
10202 RB_INIT(&downloads);
10204 TAILQ_INIT(&sessions);
10205 TAILQ_INIT(&tabs);
10206 TAILQ_INIT(&mtl);
10207 TAILQ_INIT(&aliases);
10208 TAILQ_INIT(&undos);
10209 TAILQ_INIT(&kbl);
10210 TAILQ_INIT(&spl);
10211 TAILQ_INIT(&chl);
10212 TAILQ_INIT(&shl);
10214 /* fiddle with ulimits */
10215 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10216 warn("getrlimit");
10217 else {
10218 /* just use them all */
10219 rlp.rlim_cur = rlp.rlim_max;
10220 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10221 warn("setrlimit");
10222 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10223 warn("getrlimit");
10224 else if (rlp.rlim_cur <= 256)
10225 startpage_add("%s requires at least 256 file "
10226 "descriptors, currently it has up to %d available",
10227 __progname, rlp.rlim_cur);
10230 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10231 switch (c) {
10232 case 'S':
10233 show_url = 0;
10234 break;
10235 case 'T':
10236 show_tabs = 0;
10237 break;
10238 case 'V':
10239 errx(0 , "Version: %s", version);
10240 break;
10241 case 'f':
10242 strlcpy(conf, optarg, sizeof(conf));
10243 break;
10244 case 's':
10245 strlcpy(named_session, optarg, sizeof(named_session));
10246 break;
10247 case 't':
10248 tabless = 1;
10249 break;
10250 case 'n':
10251 optn = 1;
10252 break;
10253 case 'e':
10254 opte = 1;
10255 break;
10256 default:
10257 usage();
10258 /* NOTREACHED */
10261 argc -= optind;
10262 argv += optind;
10264 init_keybindings();
10266 gnutls_global_init();
10268 /* generate session keys for xtp pages */
10269 generate_xtp_session_key(&dl_session_key);
10270 generate_xtp_session_key(&hl_session_key);
10271 generate_xtp_session_key(&cl_session_key);
10272 generate_xtp_session_key(&fl_session_key);
10274 /* signals */
10275 bzero(&sact, sizeof(sact));
10276 sigemptyset(&sact.sa_mask);
10277 sact.sa_handler = sigchild;
10278 sact.sa_flags = SA_NOCLDSTOP;
10279 sigaction(SIGCHLD, &sact, NULL);
10281 /* set download dir */
10282 pwd = getpwuid(getuid());
10283 if (pwd == NULL)
10284 errx(1, "invalid user %d", getuid());
10285 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10287 /* compile buffer command regexes */
10288 buffercmd_init();
10290 /* set default string settings */
10291 home = g_strdup("https://www.cyphertite.com");
10292 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10293 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10294 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10295 cmd_font_name = g_strdup("monospace normal 9");
10296 oops_font_name = g_strdup("monospace normal 9");
10297 statusbar_font_name = g_strdup("monospace normal 9");
10298 tabbar_font_name = g_strdup("monospace normal 9");
10299 statusbar_elems = g_strdup("BP");
10300 encoding = g_strdup("ISO-8859-1");
10302 /* read config file */
10303 if (strlen(conf) == 0)
10304 snprintf(conf, sizeof conf, "%s/.%s",
10305 pwd->pw_dir, XT_CONF_FILE);
10306 config_parse(conf, 0);
10308 /* init fonts */
10309 cmd_font = pango_font_description_from_string(cmd_font_name);
10310 oops_font = pango_font_description_from_string(oops_font_name);
10311 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10312 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10314 /* working directory */
10315 if (strlen(work_dir) == 0)
10316 snprintf(work_dir, sizeof work_dir, "%s/%s",
10317 pwd->pw_dir, XT_DIR);
10318 xxx_dir(work_dir);
10320 /* icon cache dir */
10321 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10322 xxx_dir(cache_dir);
10324 /* certs dir */
10325 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10326 xxx_dir(certs_dir);
10328 /* sessions dir */
10329 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10330 work_dir, XT_SESSIONS_DIR);
10331 xxx_dir(sessions_dir);
10333 /* runtime settings that can override config file */
10334 if (runtime_settings[0] != '\0')
10335 config_parse(runtime_settings, 1);
10337 /* download dir */
10338 if (!strcmp(download_dir, pwd->pw_dir))
10339 strlcat(download_dir, "/downloads", sizeof download_dir);
10340 xxx_dir(download_dir);
10342 /* favorites file */
10343 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10344 if (stat(file, &sb)) {
10345 warnx("favorites file doesn't exist, creating it");
10346 if ((f = fopen(file, "w")) == NULL)
10347 err(1, "favorites");
10348 fclose(f);
10351 /* quickmarks file */
10352 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10353 if (stat(file, &sb)) {
10354 warnx("quickmarks file doesn't exist, creating it");
10355 if ((f = fopen(file, "w")) == NULL)
10356 err(1, "quickmarks");
10357 fclose(f);
10360 /* search history */
10361 if (history_autosave) {
10362 snprintf(search_file, sizeof search_file, "%s/%s",
10363 work_dir, XT_SEARCH_FILE);
10364 if (stat(search_file, &sb)) {
10365 warnx("search history file doesn't exist, creating it");
10366 if ((f = fopen(search_file, "w")) == NULL)
10367 err(1, "search_history");
10368 fclose(f);
10370 history_read(&shl, search_file, &search_history_count);
10373 /* command history */
10374 if (history_autosave) {
10375 snprintf(command_file, sizeof command_file, "%s/%s",
10376 work_dir, XT_COMMAND_FILE);
10377 if (stat(command_file, &sb)) {
10378 warnx("command history file doesn't exist, creating it");
10379 if ((f = fopen(command_file, "w")) == NULL)
10380 err(1, "command_history");
10381 fclose(f);
10383 history_read(&chl, command_file, &cmd_history_count);
10386 /* cookies */
10387 session = webkit_get_default_session();
10388 setup_cookies();
10390 /* certs */
10391 if (ssl_ca_file) {
10392 if (stat(ssl_ca_file, &sb)) {
10393 warnx("no CA file: %s", ssl_ca_file);
10394 g_free(ssl_ca_file);
10395 ssl_ca_file = NULL;
10396 } else
10397 g_object_set(session,
10398 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10399 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10400 (void *)NULL);
10403 /* guess_search regex */
10404 if (url_regex == NULL)
10405 url_regex = g_strdup(XT_URL_REGEX);
10406 if (url_regex)
10407 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10408 startpage_add("invalid url regex %s", url_regex);
10410 /* proxy */
10411 env_proxy = getenv("http_proxy");
10412 if (env_proxy)
10413 setup_proxy(env_proxy);
10414 else
10415 setup_proxy(http_proxy);
10417 if (opte) {
10418 send_cmd_to_socket(argv[0]);
10419 exit(0);
10422 /* set some connection parameters */
10423 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10424 g_object_set(session, "max-conns-per-host", max_host_connections,
10425 (char *)NULL);
10427 /* see if there is already an xxxterm running */
10428 if (single_instance && is_running()) {
10429 optn = 1;
10430 warnx("already running");
10433 if (optn) {
10434 while (argc) {
10435 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10436 send_cmd_to_socket(cmd);
10437 if (cmd)
10438 g_free(cmd);
10440 argc--;
10441 argv++;
10443 exit(0);
10446 /* uri completion */
10447 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10449 /* buffers */
10450 buffers_store = gtk_list_store_new
10451 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10453 qmarks_load();
10455 /* go graphical */
10456 create_canvas();
10457 notebook_tab_set_visibility();
10459 if (save_global_history)
10460 restore_global_history();
10462 /* restore session list */
10463 restore_sessions_list();
10465 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10466 restore_saved_tabs();
10467 else {
10468 a.s = named_session;
10469 a.i = XT_SES_DONOTHING;
10470 open_tabs(NULL, &a);
10473 /* see if we have an exception */
10474 if (!TAILQ_EMPTY(&spl)) {
10475 create_new_tab("about:startpage", NULL, focus, -1);
10476 focus = 0;
10479 while (argc) {
10480 create_new_tab(argv[0], NULL, focus, -1);
10481 focus = 0;
10483 argc--;
10484 argv++;
10487 if (TAILQ_EMPTY(&tabs))
10488 create_new_tab(home, NULL, 1, -1);
10490 if (enable_socket)
10491 if ((s = build_socket()) != -1) {
10492 channel = g_io_channel_unix_new(s);
10493 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10496 gtk_main();
10498 gnutls_global_deinit();
10500 if (url_regex)
10501 regfree(&url_re);
10503 gdk_threads_leave();
10505 return (0);