get rid of threads altogether
[xxxterm.git] / xxxterm.c
bloba435ed804dce33da32786494d5c7f1948c90bd8d
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 /* hints */
234 int hints_on;
235 int hint_mode;
236 #define XT_HINT_NONE (0)
237 #define XT_HINT_NUMERICAL (1)
238 #define XT_HINT_ALPHANUM (2)
239 char hint_buf[128];
240 char hint_num[128];
242 /* custom stylesheet */
243 int styled;
244 char *stylesheet;
246 /* search */
247 char *search_text;
248 int search_forward;
249 guint search_id;
251 /* settings */
252 WebKitWebSettings *settings;
253 gchar *user_agent;
255 /* marks */
256 double mark[XT_NOMARKS];
258 TAILQ_HEAD(tab_list, tab);
260 struct history {
261 RB_ENTRY(history) entry;
262 const gchar *uri;
263 const gchar *title;
265 RB_HEAD(history_list, history);
267 struct session {
268 TAILQ_ENTRY(session) entry;
269 const gchar *name;
271 TAILQ_HEAD(session_list, session);
273 struct download {
274 RB_ENTRY(download) entry;
275 int id;
276 WebKitDownload *download;
277 struct tab *tab;
279 RB_HEAD(download_list, download);
281 struct domain {
282 RB_ENTRY(domain) entry;
283 gchar *d;
284 int handy; /* app use */
286 RB_HEAD(domain_list, domain);
288 struct undo {
289 TAILQ_ENTRY(undo) entry;
290 gchar *uri;
291 GList *history;
292 int back; /* Keeps track of how many back
293 * history items there are. */
295 TAILQ_HEAD(undo_tailq, undo);
297 struct sp {
298 char *line;
299 TAILQ_ENTRY(sp) entry;
301 TAILQ_HEAD(sp_list, sp);
303 struct command_entry {
304 char *line;
305 TAILQ_ENTRY(command_entry) entry;
307 TAILQ_HEAD(command_list, command_entry);
309 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
310 int next_download_id = 1;
312 struct karg {
313 int i;
314 char *s;
315 int precount;
318 /* defines */
319 #define XT_NAME ("XXXTerm")
320 #define XT_DIR (".xxxterm")
321 #define XT_CACHE_DIR ("cache")
322 #define XT_CERT_DIR ("certs/")
323 #define XT_SESSIONS_DIR ("sessions/")
324 #define XT_CONF_FILE ("xxxterm.conf")
325 #define XT_FAVS_FILE ("favorites")
326 #define XT_QMARKS_FILE ("quickmarks")
327 #define XT_SAVED_TABS_FILE ("main_session")
328 #define XT_RESTART_TABS_FILE ("restart_tabs")
329 #define XT_SOCKET_FILE ("socket")
330 #define XT_HISTORY_FILE ("history")
331 #define XT_REJECT_FILE ("rejected.txt")
332 #define XT_COOKIE_FILE ("cookies.txt")
333 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
334 #define XT_SEARCH_FILE ("search_history")
335 #define XT_COMMAND_FILE ("command_history")
336 #define XT_CB_HANDLED (TRUE)
337 #define XT_CB_PASSTHROUGH (FALSE)
338 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
339 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
340 #define XT_DLMAN_REFRESH "10"
341 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
342 "td{overflow: hidden;" \
343 " padding: 2px 2px 2px 2px;" \
344 " border: 1px solid black;" \
345 " vertical-align:top;" \
346 " word-wrap: break-word}\n" \
347 "tr:hover{background: #ffff99}\n" \
348 "th{background-color: #cccccc;" \
349 " border: 1px solid black}\n" \
350 "table{width: 100%%;" \
351 " border: 1px black solid;" \
352 " border-collapse:collapse}\n" \
353 ".progress-outer{" \
354 "border: 1px solid black;" \
355 " height: 8px;" \
356 " width: 90%%}\n" \
357 ".progress-inner{float: left;" \
358 " height: 8px;" \
359 " background: green}\n" \
360 ".dlstatus{font-size: small;" \
361 " text-align: center}\n" \
362 "</style>\n"
363 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
364 #define XT_MAX_UNDO_CLOSE_TAB (32)
365 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
366 #define XT_PRINT_EXTRA_MARGIN 10
367 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
368 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
370 /* colors */
371 #define XT_COLOR_RED "#cc0000"
372 #define XT_COLOR_YELLOW "#ffff66"
373 #define XT_COLOR_BLUE "lightblue"
374 #define XT_COLOR_GREEN "#99ff66"
375 #define XT_COLOR_WHITE "white"
376 #define XT_COLOR_BLACK "black"
378 #define XT_COLOR_CT_BACKGROUND "#000000"
379 #define XT_COLOR_CT_INACTIVE "#dddddd"
380 #define XT_COLOR_CT_ACTIVE "#bbbb00"
381 #define XT_COLOR_CT_SEPARATOR "#555555"
383 #define XT_COLOR_SB_SEPARATOR "#555555"
385 #define XT_PROTO_DELIM "://"
388 * xxxterm "protocol" (xtp)
389 * We use this for managing stuff like downloads and favorites. They
390 * make magical HTML pages in memory which have xxxt:// links in order
391 * to communicate with xxxterm's internals. These links take the format:
392 * xxxt://class/session_key/action/arg
394 * Don't begin xtp class/actions as 0. atoi returns that on error.
396 * Typically we have not put addition of items in this framework, as
397 * adding items is either done via an ex-command or via a keybinding instead.
400 #define XT_XTP_STR "xxxt://"
402 /* XTP classes (xxxt://<class>) */
403 #define XT_XTP_INVALID 0 /* invalid */
404 #define XT_XTP_DL 1 /* downloads */
405 #define XT_XTP_HL 2 /* history */
406 #define XT_XTP_CL 3 /* cookies */
407 #define XT_XTP_FL 4 /* favorites */
409 /* XTP download actions */
410 #define XT_XTP_DL_LIST 1
411 #define XT_XTP_DL_CANCEL 2
412 #define XT_XTP_DL_REMOVE 3
414 /* XTP history actions */
415 #define XT_XTP_HL_LIST 1
416 #define XT_XTP_HL_REMOVE 2
418 /* XTP cookie actions */
419 #define XT_XTP_CL_LIST 1
420 #define XT_XTP_CL_REMOVE 2
422 /* XTP cookie actions */
423 #define XT_XTP_FL_LIST 1
424 #define XT_XTP_FL_REMOVE 2
426 /* actions */
427 #define XT_MOVE_INVALID (0)
428 #define XT_MOVE_DOWN (1)
429 #define XT_MOVE_UP (2)
430 #define XT_MOVE_BOTTOM (3)
431 #define XT_MOVE_TOP (4)
432 #define XT_MOVE_PAGEDOWN (5)
433 #define XT_MOVE_PAGEUP (6)
434 #define XT_MOVE_HALFDOWN (7)
435 #define XT_MOVE_HALFUP (8)
436 #define XT_MOVE_LEFT (9)
437 #define XT_MOVE_FARLEFT (10)
438 #define XT_MOVE_RIGHT (11)
439 #define XT_MOVE_FARRIGHT (12)
440 #define XT_MOVE_PERCENT (13)
442 #define XT_QMARK_SET (0)
443 #define XT_QMARK_OPEN (1)
444 #define XT_QMARK_TAB (2)
446 #define XT_MARK_SET (0)
447 #define XT_MARK_GOTO (1)
449 #define XT_TAB_LAST (-4)
450 #define XT_TAB_FIRST (-3)
451 #define XT_TAB_PREV (-2)
452 #define XT_TAB_NEXT (-1)
453 #define XT_TAB_INVALID (0)
454 #define XT_TAB_NEW (1)
455 #define XT_TAB_DELETE (2)
456 #define XT_TAB_DELQUIT (3)
457 #define XT_TAB_OPEN (4)
458 #define XT_TAB_UNDO_CLOSE (5)
459 #define XT_TAB_SHOW (6)
460 #define XT_TAB_HIDE (7)
461 #define XT_TAB_NEXTSTYLE (8)
463 #define XT_NAV_INVALID (0)
464 #define XT_NAV_BACK (1)
465 #define XT_NAV_FORWARD (2)
466 #define XT_NAV_RELOAD (3)
468 #define XT_FOCUS_INVALID (0)
469 #define XT_FOCUS_URI (1)
470 #define XT_FOCUS_SEARCH (2)
472 #define XT_SEARCH_INVALID (0)
473 #define XT_SEARCH_NEXT (1)
474 #define XT_SEARCH_PREV (2)
476 #define XT_PASTE_CURRENT_TAB (0)
477 #define XT_PASTE_NEW_TAB (1)
479 #define XT_ZOOM_IN (-1)
480 #define XT_ZOOM_OUT (-2)
481 #define XT_ZOOM_NORMAL (100)
483 #define XT_URL_SHOW (1)
484 #define XT_URL_HIDE (2)
486 #define XT_WL_TOGGLE (1<<0)
487 #define XT_WL_ENABLE (1<<1)
488 #define XT_WL_DISABLE (1<<2)
489 #define XT_WL_FQDN (1<<3) /* default */
490 #define XT_WL_TOPLEVEL (1<<4)
491 #define XT_WL_PERSISTENT (1<<5)
492 #define XT_WL_SESSION (1<<6)
493 #define XT_WL_RELOAD (1<<7)
495 #define XT_SHOW (1<<7)
496 #define XT_DELETE (1<<8)
497 #define XT_SAVE (1<<9)
498 #define XT_OPEN (1<<10)
500 #define XT_CMD_OPEN (0)
501 #define XT_CMD_OPEN_CURRENT (1)
502 #define XT_CMD_TABNEW (2)
503 #define XT_CMD_TABNEW_CURRENT (3)
505 #define XT_STATUS_NOTHING (0)
506 #define XT_STATUS_LINK (1)
507 #define XT_STATUS_URI (2)
508 #define XT_STATUS_LOADING (3)
510 #define XT_SES_DONOTHING (0)
511 #define XT_SES_CLOSETABS (1)
513 #define XT_BM_NORMAL (0)
514 #define XT_BM_WHITELIST (1)
515 #define XT_BM_KIOSK (2)
517 #define XT_PREFIX (1<<0)
518 #define XT_USERARG (1<<1)
519 #define XT_URLARG (1<<2)
520 #define XT_INTARG (1<<3)
521 #define XT_SESSARG (1<<4)
522 #define XT_SETARG (1<<5)
524 #define XT_TABS_NORMAL 0
525 #define XT_TABS_COMPACT 1
527 #define XT_BUFCMD_SZ (8)
529 /* mime types */
530 struct mime_type {
531 char *mt_type;
532 char *mt_action;
533 int mt_default;
534 int mt_download;
535 TAILQ_ENTRY(mime_type) entry;
537 TAILQ_HEAD(mime_type_list, mime_type);
539 /* uri aliases */
540 struct alias {
541 char *a_name;
542 char *a_uri;
543 TAILQ_ENTRY(alias) entry;
545 TAILQ_HEAD(alias_list, alias);
547 /* settings that require restart */
548 int tabless = 0; /* allow only 1 tab */
549 int enable_socket = 0;
550 int single_instance = 0; /* only allow one xxxterm to run */
551 int fancy_bar = 1; /* fancy toolbar */
552 int browser_mode = XT_BM_NORMAL;
553 int enable_localstorage = 1;
554 char *statusbar_elems = NULL;
556 /* runtime settings */
557 int show_tabs = 1; /* show tabs on notebook */
558 int tab_style = XT_TABS_NORMAL; /* tab bar style */
559 int show_url = 1; /* show url toolbar on notebook */
560 int show_statusbar = 0; /* vimperator style status bar */
561 int ctrl_click_focus = 0; /* ctrl click gets focus */
562 int cookies_enabled = 1; /* enable cookies */
563 int read_only_cookies = 0; /* enable to not write cookies */
564 int enable_scripts = 1;
565 int enable_plugins = 0;
566 gfloat default_zoom_level = 1.0;
567 char default_script[PATH_MAX];
568 int window_height = 768;
569 int window_width = 1024;
570 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
571 int refresh_interval = 10; /* download refresh interval */
572 int enable_cookie_whitelist = 0;
573 int enable_js_whitelist = 0;
574 int session_timeout = 3600; /* cookie session timeout */
575 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
576 char *ssl_ca_file = NULL;
577 char *resource_dir = NULL;
578 gboolean ssl_strict_certs = FALSE;
579 int append_next = 1; /* append tab after current tab */
580 char *home = NULL;
581 char *search_string = NULL;
582 char *http_proxy = NULL;
583 char download_dir[PATH_MAX];
584 char runtime_settings[PATH_MAX]; /* override of settings */
585 int allow_volatile_cookies = 0;
586 int save_global_history = 0; /* save global history to disk */
587 char *user_agent = NULL;
588 int save_rejected_cookies = 0;
589 int session_autosave = 0;
590 int guess_search = 0;
591 int dns_prefetch = FALSE;
592 gint max_connections = 25;
593 gint max_host_connections = 5;
594 gint enable_spell_checking = 0;
595 char *spell_check_languages = NULL;
596 int xterm_workaround = 0;
597 char *url_regex = NULL;
598 int history_autosave = 0;
599 char search_file[PATH_MAX];
600 char command_file[PATH_MAX];
602 char *cmd_font_name = NULL;
603 char *oops_font_name = NULL;
604 char *statusbar_font_name = NULL;
605 char *tabbar_font_name = NULL;
606 PangoFontDescription *cmd_font;
607 PangoFontDescription *oops_font;
608 PangoFontDescription *statusbar_font;
609 PangoFontDescription *tabbar_font;
610 char *qmarks[XT_NOMARKS];
612 int btn_down; /* M1 down in any wv */
613 regex_t url_re; /* guess_search regex */
615 struct settings;
616 struct key_binding;
617 int set_browser_mode(struct settings *, char *);
618 int set_cookie_policy(struct settings *, char *);
619 int set_download_dir(struct settings *, char *);
620 int set_default_script(struct settings *, char *);
621 int set_runtime_dir(struct settings *, char *);
622 int set_tab_style(struct settings *, char *);
623 int set_work_dir(struct settings *, char *);
624 int add_alias(struct settings *, char *);
625 int add_mime_type(struct settings *, char *);
626 int add_cookie_wl(struct settings *, char *);
627 int add_js_wl(struct settings *, char *);
628 int add_kb(struct settings *, char *);
629 void button_set_stockid(GtkWidget *, char *);
630 GtkWidget * create_button(char *, char *, int);
632 char *get_browser_mode(struct settings *);
633 char *get_cookie_policy(struct settings *);
634 char *get_download_dir(struct settings *);
635 char *get_default_script(struct settings *);
636 char *get_runtime_dir(struct settings *);
637 char *get_tab_style(struct settings *);
638 char *get_work_dir(struct settings *);
639 void startpage_add(const char *, ...);
641 void walk_alias(struct settings *, void (*)(struct settings *,
642 char *, void *), void *);
643 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
644 char *, void *), void *);
645 void walk_js_wl(struct settings *, void (*)(struct settings *,
646 char *, void *), void *);
647 void walk_kb(struct settings *, void (*)(struct settings *, char *,
648 void *), void *);
649 void walk_mime_type(struct settings *, void (*)(struct settings *,
650 char *, void *), void *);
652 void recalc_tabs(void);
653 void recolor_compact_tabs(void);
654 void set_current_tab(int page_num);
655 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
656 void marks_clear(struct tab *t);
658 int set_http_proxy(char *);
660 struct special {
661 int (*set)(struct settings *, char *);
662 char *(*get)(struct settings *);
663 void (*walk)(struct settings *,
664 void (*cb)(struct settings *, char *, void *),
665 void *);
668 struct special s_browser_mode = {
669 set_browser_mode,
670 get_browser_mode,
671 NULL
674 struct special s_cookie = {
675 set_cookie_policy,
676 get_cookie_policy,
677 NULL
680 struct special s_alias = {
681 add_alias,
682 NULL,
683 walk_alias
686 struct special s_mime = {
687 add_mime_type,
688 NULL,
689 walk_mime_type
692 struct special s_js = {
693 add_js_wl,
694 NULL,
695 walk_js_wl
698 struct special s_kb = {
699 add_kb,
700 NULL,
701 walk_kb
704 struct special s_cookie_wl = {
705 add_cookie_wl,
706 NULL,
707 walk_cookie_wl
710 struct special s_default_script = {
711 set_default_script,
712 get_default_script,
713 NULL
716 struct special s_download_dir = {
717 set_download_dir,
718 get_download_dir,
719 NULL
722 struct special s_work_dir = {
723 set_work_dir,
724 get_work_dir,
725 NULL
728 struct special s_tab_style = {
729 set_tab_style,
730 get_tab_style,
731 NULL
734 struct settings {
735 char *name;
736 int type;
737 #define XT_S_INVALID (0)
738 #define XT_S_INT (1)
739 #define XT_S_STR (2)
740 #define XT_S_FLOAT (3)
741 uint32_t flags;
742 #define XT_SF_RESTART (1<<0)
743 #define XT_SF_RUNTIME (1<<1)
744 int *ival;
745 char **sval;
746 struct special *s;
747 gfloat *fval;
748 int (*activate)(char *);
749 } rs[] = {
750 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
751 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
752 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
753 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
754 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
755 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
756 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
757 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
758 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
759 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
760 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
761 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
762 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
763 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
764 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
765 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
766 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
767 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
768 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
769 { "home", XT_S_STR, 0, NULL, &home, NULL },
770 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
771 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
772 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
773 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
774 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
775 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
776 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
777 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
778 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
779 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
780 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
781 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
782 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
783 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
784 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
785 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
786 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
787 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
788 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
789 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
790 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
791 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
792 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
793 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
794 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
795 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
796 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
798 /* font settings */
799 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
800 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
801 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
802 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
804 /* runtime settings */
805 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
806 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
807 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
808 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
809 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
812 int about(struct tab *, struct karg *);
813 int blank(struct tab *, struct karg *);
814 int ca_cmd(struct tab *, struct karg *);
815 int cookie_show_wl(struct tab *, struct karg *);
816 int js_show_wl(struct tab *, struct karg *);
817 int help(struct tab *, struct karg *);
818 int set(struct tab *, struct karg *);
819 int stats(struct tab *, struct karg *);
820 int marco(struct tab *, struct karg *);
821 int startpage(struct tab *, struct karg *);
822 const char * marco_message(int *);
823 int xtp_page_cl(struct tab *, struct karg *);
824 int xtp_page_dl(struct tab *, struct karg *);
825 int xtp_page_fl(struct tab *, struct karg *);
826 int xtp_page_hl(struct tab *, struct karg *);
827 void xt_icon_from_file(struct tab *, char *);
828 const gchar *get_uri(struct tab *);
829 const gchar *get_title(struct tab *, bool);
831 #define XT_URI_ABOUT ("about:")
832 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
833 #define XT_URI_ABOUT_ABOUT ("about")
834 #define XT_URI_ABOUT_BLANK ("blank")
835 #define XT_URI_ABOUT_CERTS ("certs")
836 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
837 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
838 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
839 #define XT_URI_ABOUT_FAVORITES ("favorites")
840 #define XT_URI_ABOUT_HELP ("help")
841 #define XT_URI_ABOUT_HISTORY ("history")
842 #define XT_URI_ABOUT_JSWL ("jswl")
843 #define XT_URI_ABOUT_SET ("set")
844 #define XT_URI_ABOUT_STATS ("stats")
845 #define XT_URI_ABOUT_MARCO ("marco")
846 #define XT_URI_ABOUT_STARTPAGE ("startpage")
848 struct about_type {
849 char *name;
850 int (*func)(struct tab *, struct karg *);
851 } about_list[] = {
852 { XT_URI_ABOUT_ABOUT, about },
853 { XT_URI_ABOUT_BLANK, blank },
854 { XT_URI_ABOUT_CERTS, ca_cmd },
855 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
856 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
857 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
858 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
859 { XT_URI_ABOUT_HELP, help },
860 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
861 { XT_URI_ABOUT_JSWL, js_show_wl },
862 { XT_URI_ABOUT_SET, set },
863 { XT_URI_ABOUT_STATS, stats },
864 { XT_URI_ABOUT_MARCO, marco },
865 { XT_URI_ABOUT_STARTPAGE, startpage },
868 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
869 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
870 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
871 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
872 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
873 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
874 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
876 /* globals */
877 extern char *__progname;
878 char **start_argv;
879 struct passwd *pwd;
880 GtkWidget *main_window;
881 GtkNotebook *notebook;
882 GtkWidget *tab_bar;
883 GtkWidget *arrow, *abtn;
884 struct tab_list tabs;
885 struct history_list hl;
886 struct session_list sessions;
887 struct download_list downloads;
888 struct domain_list c_wl;
889 struct domain_list js_wl;
890 struct undo_tailq undos;
891 struct keybinding_list kbl;
892 struct sp_list spl;
893 struct command_list chl;
894 struct command_list shl;
895 struct command_entry *history_at;
896 struct command_entry *search_at;
897 int undo_count;
898 int updating_dl_tabs = 0;
899 int updating_hl_tabs = 0;
900 int updating_cl_tabs = 0;
901 int updating_fl_tabs = 0;
902 int cmd_history_count = 0;
903 int search_history_count = 0;
904 char *global_search;
905 uint64_t blocked_cookies = 0;
906 char named_session[PATH_MAX];
907 GtkListStore *completion_model;
908 GtkListStore *buffers_store;
910 void xxx_dir(char *);
911 int icon_size_map(int);
912 void completion_add(struct tab *);
913 void completion_add_uri(const gchar *);
914 void show_oops(struct tab *, const char *, ...);
916 void
917 history_delete(struct command_list *l, int *counter)
919 struct command_entry *c;
921 if (l == NULL || counter == NULL)
922 return;
924 c = TAILQ_LAST(l, command_list);
925 if (c == NULL)
926 return;
928 TAILQ_REMOVE(l, c, entry);
929 *counter -= 1;
930 g_free(c->line);
931 g_free(c);
934 void
935 history_add(struct command_list *list, char *file, char *l, int *counter)
937 struct command_entry *c;
938 FILE *f;
940 if (list == NULL || l == NULL || counter == NULL)
941 return;
943 /* don't add the same line */
944 c = TAILQ_FIRST(list);
945 if (c)
946 if (!strcmp(c->line + 1 /* skip space */, l))
947 return;
949 c = g_malloc0(sizeof *c);
950 c->line = g_strdup_printf(" %s", l);
952 *counter += 1;
953 TAILQ_INSERT_HEAD(list, c, entry);
955 if (*counter > 1000)
956 history_delete(list, counter);
958 if (history_autosave && file) {
959 f = fopen(file, "w");
960 if (f == NULL) {
961 show_oops(NULL, "couldn't write history %s", file);
962 return;
965 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
966 c->line[0] = ' ';
967 fprintf(f, "%s\n", c->line);
970 fclose(f);
975 history_read(struct command_list *list, char *file, int *counter)
977 FILE *f;
978 char *s, line[65536];
980 if (list == NULL || file == NULL)
981 return (1);
983 f = fopen(file, "r");
984 if (f == NULL) {
985 startpage_add("couldn't open history file %s", file);
986 return (1);
989 for (;;) {
990 s = fgets(line, sizeof line, f);
991 if (s == NULL || feof(f) || ferror(f))
992 break;
993 if ((s = strchr(line, '\n')) == NULL) {
994 startpage_add("invalid history file %s", file);
995 fclose(f);
996 return (1);
998 *s = '\0';
1000 history_add(list, NULL, line + 1, counter);
1003 fclose(f);
1005 return (0);
1008 /* marks and quickmarks array storage.
1009 * first a-z, then A-Z, then 0-9 */
1010 char
1011 indextomark(int i)
1013 if (i < 0)
1014 return 0;
1016 if (i >= 0 && i <= 'z' - 'a')
1017 return 'a' + i;
1019 i -= 'z' - 'a' + 1;
1020 if (i >= 0 && i <= 'Z' - 'A')
1021 return 'A' + i;
1023 i -= 'Z' - 'A' + 1;
1024 if (i >= 10)
1025 return 0;
1027 return i + '0';
1031 marktoindex(char m)
1033 int ret = 0;
1035 if (m >= 'a' && m <= 'z')
1036 return ret + m - 'a';
1038 ret += 'z' - 'a' + 1;
1039 if (m >= 'A' && m <= 'Z')
1040 return ret + m - 'A';
1042 ret += 'Z' - 'A' + 1;
1043 if (m >= '0' && m <= '9')
1044 return ret + m - '0';
1046 return -1;
1050 void
1051 sigchild(int sig)
1053 int saved_errno, status;
1054 pid_t pid;
1056 saved_errno = errno;
1058 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1059 if (pid == -1) {
1060 if (errno == EINTR)
1061 continue;
1062 if (errno != ECHILD) {
1064 clog_warn("sigchild: waitpid:");
1067 break;
1070 if (WIFEXITED(status)) {
1071 if (WEXITSTATUS(status) != 0) {
1073 clog_warnx("sigchild: child exit status: %d",
1074 WEXITSTATUS(status));
1077 } else {
1079 clog_warnx("sigchild: child is terminated abnormally");
1084 errno = saved_errno;
1088 is_g_object_setting(GObject *o, char *str)
1090 guint n_props = 0, i;
1091 GParamSpec **proplist;
1093 if (! G_IS_OBJECT(o))
1094 return (0);
1096 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1097 &n_props);
1099 for (i=0; i < n_props; i++) {
1100 if (! strcmp(proplist[i]->name, str))
1101 return (1);
1103 return (0);
1106 gchar *
1107 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1109 gchar *r;
1111 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1112 "<head>\n"
1113 "<title>%s</title>\n"
1114 "%s"
1115 "%s"
1116 "</head>\n"
1117 "<body>\n"
1118 "<h1>%s</h1>\n"
1119 "%s\n</body>\n"
1120 "</html>",
1121 title,
1122 addstyles ? XT_PAGE_STYLE : "",
1123 head,
1124 title,
1125 body);
1127 return r;
1131 * Display a web page from a HTML string in memory, rather than from a URL
1133 void
1134 load_webkit_string(struct tab *t, const char *str, gchar *title)
1136 char file[PATH_MAX];
1137 int i;
1139 /* we set this to indicate we want to manually do navaction */
1140 if (t->bfl)
1141 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1143 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1144 if (title) {
1145 /* set t->xtp_meaning */
1146 for (i = 0; i < LENGTH(about_list); i++)
1147 if (!strcmp(title, about_list[i].name)) {
1148 t->xtp_meaning = i;
1149 break;
1152 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1153 #if GTK_CHECK_VERSION(2, 20, 0)
1154 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1155 gtk_widget_hide(t->spinner);
1156 #endif
1157 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1158 xt_icon_from_file(t, file);
1162 struct tab *
1163 get_current_tab(void)
1165 struct tab *t;
1167 TAILQ_FOREACH(t, &tabs, entry) {
1168 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1169 return (t);
1172 warnx("%s: no current tab", __func__);
1174 return (NULL);
1177 void
1178 set_status(struct tab *t, gchar *s, int status)
1180 gchar *type = NULL;
1182 if (s == NULL)
1183 return;
1185 switch (status) {
1186 case XT_STATUS_LOADING:
1187 type = g_strdup_printf("Loading: %s", s);
1188 s = type;
1189 break;
1190 case XT_STATUS_LINK:
1191 type = g_strdup_printf("Link: %s", s);
1192 if (!t->status)
1193 t->status = g_strdup(gtk_entry_get_text(
1194 GTK_ENTRY(t->sbe.statusbar)));
1195 s = type;
1196 break;
1197 case XT_STATUS_URI:
1198 type = g_strdup_printf("%s", s);
1199 if (!t->status) {
1200 t->status = g_strdup(type);
1202 s = type;
1203 if (!t->status)
1204 t->status = g_strdup(s);
1205 break;
1206 case XT_STATUS_NOTHING:
1207 /* FALL THROUGH */
1208 default:
1209 break;
1211 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1212 if (type)
1213 g_free(type);
1216 void
1217 hide_cmd(struct tab *t)
1219 history_at = NULL; /* just in case */
1220 search_at = NULL; /* just in case */
1221 gtk_widget_hide(t->cmd);
1224 void
1225 show_cmd(struct tab *t)
1227 history_at = NULL;
1228 search_at = NULL;
1229 gtk_widget_hide(t->oops);
1230 gtk_widget_show(t->cmd);
1233 void
1234 hide_buffers(struct tab *t)
1236 gtk_widget_hide(t->buffers);
1237 gtk_list_store_clear(buffers_store);
1240 enum {
1241 COL_ID = 0,
1242 COL_TITLE,
1243 NUM_COLS
1247 sort_tabs_by_page_num(struct tab ***stabs)
1249 int num_tabs = 0;
1250 struct tab *t;
1252 num_tabs = gtk_notebook_get_n_pages(notebook);
1254 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1256 TAILQ_FOREACH(t, &tabs, entry)
1257 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1259 return (num_tabs);
1262 void
1263 buffers_make_list(void)
1265 int i, num_tabs;
1266 const gchar *title = NULL;
1267 GtkTreeIter iter;
1268 struct tab **stabs = NULL;
1270 num_tabs = sort_tabs_by_page_num(&stabs);
1272 for (i = 0; i < num_tabs; i++)
1273 if (stabs[i]) {
1274 gtk_list_store_append(buffers_store, &iter);
1275 title = get_title(stabs[i], FALSE);
1276 gtk_list_store_set(buffers_store, &iter,
1277 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1278 * rather than 0. */
1279 COL_TITLE, title,
1280 -1);
1283 g_free(stabs);
1286 void
1287 show_buffers(struct tab *t)
1289 buffers_make_list();
1290 gtk_widget_show(t->buffers);
1291 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1294 void
1295 toggle_buffers(struct tab *t)
1297 if (gtk_widget_get_visible(t->buffers))
1298 hide_buffers(t);
1299 else
1300 show_buffers(t);
1304 buffers(struct tab *t, struct karg *args)
1306 show_buffers(t);
1308 return (0);
1311 void
1312 hide_oops(struct tab *t)
1314 gtk_widget_hide(t->oops);
1317 void
1318 show_oops(struct tab *at, const char *fmt, ...)
1320 va_list ap;
1321 char *msg = NULL;
1322 struct tab *t = NULL;
1324 if (fmt == NULL)
1325 return;
1327 if (at == NULL) {
1328 if ((t = get_current_tab()) == NULL)
1329 return;
1330 } else
1331 t = at;
1333 va_start(ap, fmt);
1334 if (vasprintf(&msg, fmt, ap) == -1)
1335 errx(1, "show_oops failed");
1336 va_end(ap);
1338 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1339 gtk_widget_hide(t->cmd);
1340 gtk_widget_show(t->oops);
1342 if (msg)
1343 free(msg);
1346 char *
1347 get_as_string(struct settings *s)
1349 char *r = NULL;
1351 if (s == NULL)
1352 return (NULL);
1354 if (s->s) {
1355 if (s->s->get)
1356 r = s->s->get(s);
1357 else
1358 warnx("get_as_string skip %s\n", s->name);
1359 } else if (s->type == XT_S_INT)
1360 r = g_strdup_printf("%d", *s->ival);
1361 else if (s->type == XT_S_STR)
1362 r = g_strdup(*s->sval);
1363 else if (s->type == XT_S_FLOAT)
1364 r = g_strdup_printf("%f", *s->fval);
1365 else
1366 r = g_strdup_printf("INVALID TYPE");
1368 return (r);
1371 void
1372 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1374 int i;
1375 char *s;
1377 for (i = 0; i < LENGTH(rs); i++) {
1378 if (rs[i].s && rs[i].s->walk)
1379 rs[i].s->walk(&rs[i], cb, cb_args);
1380 else {
1381 s = get_as_string(&rs[i]);
1382 cb(&rs[i], s, cb_args);
1383 g_free(s);
1389 set_browser_mode(struct settings *s, char *val)
1391 if (!strcmp(val, "whitelist")) {
1392 browser_mode = XT_BM_WHITELIST;
1393 allow_volatile_cookies = 0;
1394 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1395 cookies_enabled = 1;
1396 enable_cookie_whitelist = 1;
1397 read_only_cookies = 0;
1398 save_rejected_cookies = 0;
1399 session_timeout = 3600;
1400 enable_scripts = 0;
1401 enable_js_whitelist = 1;
1402 enable_localstorage = 0;
1403 } else if (!strcmp(val, "normal")) {
1404 browser_mode = XT_BM_NORMAL;
1405 allow_volatile_cookies = 0;
1406 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1407 cookies_enabled = 1;
1408 enable_cookie_whitelist = 0;
1409 read_only_cookies = 0;
1410 save_rejected_cookies = 0;
1411 session_timeout = 3600;
1412 enable_scripts = 1;
1413 enable_js_whitelist = 0;
1414 enable_localstorage = 1;
1415 } else if (!strcmp(val, "kiosk")) {
1416 browser_mode = XT_BM_KIOSK;
1417 allow_volatile_cookies = 0;
1418 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1419 cookies_enabled = 1;
1420 enable_cookie_whitelist = 0;
1421 read_only_cookies = 0;
1422 save_rejected_cookies = 0;
1423 session_timeout = 3600;
1424 enable_scripts = 1;
1425 enable_js_whitelist = 0;
1426 enable_localstorage = 1;
1427 show_tabs = 0;
1428 tabless = 1;
1429 } else
1430 return (1);
1432 return (0);
1435 char *
1436 get_browser_mode(struct settings *s)
1438 char *r = NULL;
1440 if (browser_mode == XT_BM_WHITELIST)
1441 r = g_strdup("whitelist");
1442 else if (browser_mode == XT_BM_NORMAL)
1443 r = g_strdup("normal");
1444 else if (browser_mode == XT_BM_KIOSK)
1445 r = g_strdup("kiosk");
1446 else
1447 return (NULL);
1449 return (r);
1453 set_cookie_policy(struct settings *s, char *val)
1455 if (!strcmp(val, "no3rdparty"))
1456 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1457 else if (!strcmp(val, "accept"))
1458 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1459 else if (!strcmp(val, "reject"))
1460 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1461 else
1462 return (1);
1464 return (0);
1467 char *
1468 get_cookie_policy(struct settings *s)
1470 char *r = NULL;
1472 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1473 r = g_strdup("no3rdparty");
1474 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1475 r = g_strdup("accept");
1476 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1477 r = g_strdup("reject");
1478 else
1479 return (NULL);
1481 return (r);
1484 char *
1485 get_default_script(struct settings *s)
1487 if (default_script[0] == '\0')
1488 return (0);
1489 return (g_strdup(default_script));
1493 set_default_script(struct settings *s, char *val)
1495 if (val[0] == '~')
1496 snprintf(default_script, sizeof default_script, "%s/%s",
1497 pwd->pw_dir, &val[1]);
1498 else
1499 strlcpy(default_script, val, sizeof default_script);
1501 return (0);
1504 char *
1505 get_download_dir(struct settings *s)
1507 if (download_dir[0] == '\0')
1508 return (0);
1509 return (g_strdup(download_dir));
1513 set_download_dir(struct settings *s, char *val)
1515 if (val[0] == '~')
1516 snprintf(download_dir, sizeof download_dir, "%s/%s",
1517 pwd->pw_dir, &val[1]);
1518 else
1519 strlcpy(download_dir, val, sizeof download_dir);
1521 return (0);
1525 * Session IDs.
1526 * We use these to prevent people putting xxxt:// URLs on
1527 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1529 #define XT_XTP_SES_KEY_SZ 8
1530 #define XT_XTP_SES_KEY_HEX_FMT \
1531 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1532 char *dl_session_key; /* downloads */
1533 char *hl_session_key; /* history list */
1534 char *cl_session_key; /* cookie list */
1535 char *fl_session_key; /* favorites list */
1537 char work_dir[PATH_MAX];
1538 char certs_dir[PATH_MAX];
1539 char cache_dir[PATH_MAX];
1540 char sessions_dir[PATH_MAX];
1541 char cookie_file[PATH_MAX];
1542 SoupURI *proxy_uri = NULL;
1543 SoupSession *session;
1544 SoupCookieJar *s_cookiejar;
1545 SoupCookieJar *p_cookiejar;
1546 char rc_fname[PATH_MAX];
1548 struct mime_type_list mtl;
1549 struct alias_list aliases;
1551 /* protos */
1552 struct tab *create_new_tab(char *, struct undo *, int, int);
1553 void delete_tab(struct tab *);
1554 void setzoom_webkit(struct tab *, int);
1555 int run_script(struct tab *, char *);
1556 int download_rb_cmp(struct download *, struct download *);
1557 gboolean cmd_execute(struct tab *t, char *str);
1560 history_rb_cmp(struct history *h1, struct history *h2)
1562 return (strcmp(h1->uri, h2->uri));
1564 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1567 domain_rb_cmp(struct domain *d1, struct domain *d2)
1569 return (strcmp(d1->d, d2->d));
1571 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1573 char *
1574 get_work_dir(struct settings *s)
1576 if (work_dir[0] == '\0')
1577 return (0);
1578 return (g_strdup(work_dir));
1582 set_work_dir(struct settings *s, char *val)
1584 if (val[0] == '~')
1585 snprintf(work_dir, sizeof work_dir, "%s/%s",
1586 pwd->pw_dir, &val[1]);
1587 else
1588 strlcpy(work_dir, val, sizeof work_dir);
1590 return (0);
1593 char *
1594 get_tab_style(struct settings *s)
1596 if (tab_style == XT_TABS_NORMAL)
1597 return (g_strdup("normal"));
1598 else
1599 return (g_strdup("compact"));
1603 set_tab_style(struct settings *s, char *val)
1605 if (!strcmp(val, "normal"))
1606 tab_style = XT_TABS_NORMAL;
1607 else if (!strcmp(val, "compact"))
1608 tab_style = XT_TABS_COMPACT;
1609 else
1610 return (1);
1612 return (0);
1616 * generate a session key to secure xtp commands.
1617 * pass in a ptr to the key in question and it will
1618 * be modified in place.
1620 void
1621 generate_xtp_session_key(char **key)
1623 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1625 /* free old key */
1626 if (*key)
1627 g_free(*key);
1629 /* make a new one */
1630 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1631 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1632 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1633 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1635 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1639 * validate a xtp session key.
1640 * return 1 if OK
1643 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1645 if (strcmp(trusted, untrusted) != 0) {
1646 show_oops(t, "%s: xtp session key mismatch possible spoof",
1647 __func__);
1648 return (0);
1651 return (1);
1655 download_rb_cmp(struct download *e1, struct download *e2)
1657 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1659 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1661 struct valid_url_types {
1662 char *type;
1663 } vut[] = {
1664 { "http://" },
1665 { "https://" },
1666 { "ftp://" },
1667 { "file://" },
1668 { XT_XTP_STR },
1672 valid_url_type(char *url)
1674 int i;
1676 for (i = 0; i < LENGTH(vut); i++)
1677 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1678 return (0);
1680 return (1);
1683 void
1684 print_cookie(char *msg, SoupCookie *c)
1686 if (c == NULL)
1687 return;
1689 if (msg)
1690 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1691 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1692 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1693 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1694 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1695 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1696 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1697 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1698 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1699 DNPRINTF(XT_D_COOKIE, "====================================\n");
1702 void
1703 walk_alias(struct settings *s,
1704 void (*cb)(struct settings *, char *, void *), void *cb_args)
1706 struct alias *a;
1707 char *str;
1709 if (s == NULL || cb == NULL) {
1710 show_oops(NULL, "walk_alias invalid parameters");
1711 return;
1714 TAILQ_FOREACH(a, &aliases, entry) {
1715 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1716 cb(s, str, cb_args);
1717 g_free(str);
1721 char *
1722 match_alias(char *url_in)
1724 struct alias *a;
1725 char *arg;
1726 char *url_out = NULL, *search, *enc_arg;
1728 search = g_strdup(url_in);
1729 arg = search;
1730 if (strsep(&arg, " \t") == NULL) {
1731 show_oops(NULL, "match_alias: NULL URL");
1732 goto done;
1735 TAILQ_FOREACH(a, &aliases, entry) {
1736 if (!strcmp(search, a->a_name))
1737 break;
1740 if (a != NULL) {
1741 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1742 a->a_name);
1743 if (arg != NULL) {
1744 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1745 url_out = g_strdup_printf(a->a_uri, enc_arg);
1746 g_free(enc_arg);
1747 } else
1748 url_out = g_strdup_printf(a->a_uri, "");
1750 done:
1751 g_free(search);
1752 return (url_out);
1755 char *
1756 guess_url_type(char *url_in)
1758 struct stat sb;
1759 char *url_out = NULL, *enc_search = NULL;
1760 int i;
1762 /* substitute aliases */
1763 url_out = match_alias(url_in);
1764 if (url_out != NULL)
1765 return (url_out);
1767 /* see if we are an about page */
1768 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1769 for (i = 0; i < LENGTH(about_list); i++)
1770 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1771 about_list[i].name)) {
1772 url_out = g_strdup(url_in);
1773 goto done;
1776 if (guess_search && url_regex &&
1777 !(g_str_has_prefix(url_in, "http://") ||
1778 g_str_has_prefix(url_in, "https://"))) {
1779 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1780 /* invalid URI so search instead */
1781 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1782 url_out = g_strdup_printf(search_string, enc_search);
1783 g_free(enc_search);
1784 goto done;
1788 /* XXX not sure about this heuristic */
1789 if (stat(url_in, &sb) == 0)
1790 url_out = g_strdup_printf("file://%s", url_in);
1791 else
1792 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1793 done:
1794 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1796 return (url_out);
1799 void
1800 load_uri(struct tab *t, gchar *uri)
1802 struct karg args;
1803 gchar *newuri = NULL;
1804 int i;
1806 if (uri == NULL)
1807 return;
1809 /* Strip leading spaces. */
1810 while (*uri && isspace(*uri))
1811 uri++;
1813 if (strlen(uri) == 0) {
1814 blank(t, NULL);
1815 return;
1818 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1820 if (valid_url_type(uri)) {
1821 newuri = guess_url_type(uri);
1822 uri = newuri;
1825 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1826 for (i = 0; i < LENGTH(about_list); i++)
1827 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1828 bzero(&args, sizeof args);
1829 about_list[i].func(t, &args);
1830 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1831 FALSE);
1832 goto done;
1834 show_oops(t, "invalid about page");
1835 goto done;
1838 set_status(t, (char *)uri, XT_STATUS_LOADING);
1839 marks_clear(t);
1840 webkit_web_view_load_uri(t->wv, uri);
1841 done:
1842 if (newuri)
1843 g_free(newuri);
1846 const gchar *
1847 get_uri(struct tab *t)
1849 const gchar *uri = NULL;
1851 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1852 return t->tmp_uri;
1853 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1854 uri = webkit_web_view_get_uri(t->wv);
1855 } else {
1856 /* use tmp_uri to make sure it is g_freed */
1857 if (t->tmp_uri)
1858 g_free(t->tmp_uri);
1859 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1860 about_list[t->xtp_meaning].name);
1861 uri = t->tmp_uri;
1863 return uri;
1866 const gchar *
1867 get_title(struct tab *t, bool window)
1869 const gchar *set = NULL, *title = NULL;
1870 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1872 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1873 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1874 goto notitle;
1876 title = webkit_web_view_get_title(t->wv);
1877 if ((set = title ? title : get_uri(t)))
1878 return set;
1880 notitle:
1881 set = window ? XT_NAME : "(untitled)";
1883 return set;
1887 add_alias(struct settings *s, char *line)
1889 char *l, *alias;
1890 struct alias *a = NULL;
1892 if (s == NULL || line == NULL) {
1893 show_oops(NULL, "add_alias invalid parameters");
1894 return (1);
1897 l = line;
1898 a = g_malloc(sizeof(*a));
1900 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1901 show_oops(NULL, "add_alias: incomplete alias definition");
1902 goto bad;
1904 if (strlen(alias) == 0 || strlen(l) == 0) {
1905 show_oops(NULL, "add_alias: invalid alias definition");
1906 goto bad;
1909 a->a_name = g_strdup(alias);
1910 a->a_uri = g_strdup(l);
1912 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1914 TAILQ_INSERT_TAIL(&aliases, a, entry);
1916 return (0);
1917 bad:
1918 if (a)
1919 g_free(a);
1920 return (1);
1924 add_mime_type(struct settings *s, char *line)
1926 char *mime_type;
1927 char *l;
1928 struct mime_type *m = NULL;
1929 int downloadfirst = 0;
1931 /* XXX this could be smarter */
1933 if (line == NULL || strlen(line) == 0) {
1934 show_oops(NULL, "add_mime_type invalid parameters");
1935 return (1);
1938 l = line;
1939 if (*l == '@') {
1940 downloadfirst = 1;
1941 l++;
1943 m = g_malloc(sizeof(*m));
1945 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1946 show_oops(NULL, "add_mime_type: invalid mime_type");
1947 goto bad;
1949 if (mime_type[strlen(mime_type) - 1] == '*') {
1950 mime_type[strlen(mime_type) - 1] = '\0';
1951 m->mt_default = 1;
1952 } else
1953 m->mt_default = 0;
1955 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1956 show_oops(NULL, "add_mime_type: invalid mime_type");
1957 goto bad;
1960 m->mt_type = g_strdup(mime_type);
1961 m->mt_action = g_strdup(l);
1962 m->mt_download = downloadfirst;
1964 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1965 m->mt_type, m->mt_action, m->mt_default);
1967 TAILQ_INSERT_TAIL(&mtl, m, entry);
1969 return (0);
1970 bad:
1971 if (m)
1972 g_free(m);
1973 return (1);
1976 struct mime_type *
1977 find_mime_type(char *mime_type)
1979 struct mime_type *m, *def = NULL, *rv = NULL;
1981 TAILQ_FOREACH(m, &mtl, entry) {
1982 if (m->mt_default &&
1983 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1984 def = m;
1986 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1987 rv = m;
1988 break;
1992 if (rv == NULL)
1993 rv = def;
1995 return (rv);
1998 void
1999 walk_mime_type(struct settings *s,
2000 void (*cb)(struct settings *, char *, void *), void *cb_args)
2002 struct mime_type *m;
2003 char *str;
2005 if (s == NULL || cb == NULL) {
2006 show_oops(NULL, "walk_mime_type invalid parameters");
2007 return;
2010 TAILQ_FOREACH(m, &mtl, entry) {
2011 str = g_strdup_printf("%s%s --> %s",
2012 m->mt_type,
2013 m->mt_default ? "*" : "",
2014 m->mt_action);
2015 cb(s, str, cb_args);
2016 g_free(str);
2020 void
2021 wl_add(char *str, struct domain_list *wl, int handy)
2023 struct domain *d;
2024 int add_dot = 0;
2025 char *p;
2027 if (str == NULL || wl == NULL || strlen(str) < 2)
2028 return;
2030 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2032 /* treat *.moo.com the same as .moo.com */
2033 if (str[0] == '*' && str[1] == '.')
2034 str = &str[1];
2035 else if (str[0] == '.')
2036 str = &str[0];
2037 else
2038 add_dot = 1;
2040 /* slice off port number */
2041 p = g_strrstr(str, ":");
2042 if (p)
2043 *p = '\0';
2045 d = g_malloc(sizeof *d);
2046 if (add_dot)
2047 d->d = g_strdup_printf(".%s", str);
2048 else
2049 d->d = g_strdup(str);
2050 d->handy = handy;
2052 if (RB_INSERT(domain_list, wl, d))
2053 goto unwind;
2055 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2056 return;
2057 unwind:
2058 if (d) {
2059 if (d->d)
2060 g_free(d->d);
2061 g_free(d);
2066 add_cookie_wl(struct settings *s, char *entry)
2068 wl_add(entry, &c_wl, 1);
2069 return (0);
2072 void
2073 walk_cookie_wl(struct settings *s,
2074 void (*cb)(struct settings *, char *, void *), void *cb_args)
2076 struct domain *d;
2078 if (s == NULL || cb == NULL) {
2079 show_oops(NULL, "walk_cookie_wl invalid parameters");
2080 return;
2083 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2084 cb(s, d->d, cb_args);
2087 void
2088 walk_js_wl(struct settings *s,
2089 void (*cb)(struct settings *, char *, void *), void *cb_args)
2091 struct domain *d;
2093 if (s == NULL || cb == NULL) {
2094 show_oops(NULL, "walk_js_wl invalid parameters");
2095 return;
2098 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2099 cb(s, d->d, cb_args);
2103 add_js_wl(struct settings *s, char *entry)
2105 wl_add(entry, &js_wl, 1 /* persistent */);
2106 return (0);
2109 struct domain *
2110 wl_find(const gchar *search, struct domain_list *wl)
2112 int i;
2113 struct domain *d = NULL, dfind;
2114 gchar *s = NULL;
2116 if (search == NULL || wl == NULL)
2117 return (NULL);
2118 if (strlen(search) < 2)
2119 return (NULL);
2121 if (search[0] != '.')
2122 s = g_strdup_printf(".%s", search);
2123 else
2124 s = g_strdup(search);
2126 for (i = strlen(s) - 1; i >= 0; i--) {
2127 if (s[i] == '.') {
2128 dfind.d = &s[i];
2129 d = RB_FIND(domain_list, wl, &dfind);
2130 if (d)
2131 goto done;
2135 done:
2136 if (s)
2137 g_free(s);
2139 return (d);
2142 struct domain *
2143 wl_find_uri(const gchar *s, struct domain_list *wl)
2145 int i;
2146 char *ss;
2147 struct domain *r;
2149 if (s == NULL || wl == NULL)
2150 return (NULL);
2152 if (!strncmp(s, "http://", strlen("http://")))
2153 s = &s[strlen("http://")];
2154 else if (!strncmp(s, "https://", strlen("https://")))
2155 s = &s[strlen("https://")];
2157 if (strlen(s) < 2)
2158 return (NULL);
2160 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2161 /* chop string at first slash */
2162 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2163 ss = g_strdup(s);
2164 ss[i] = '\0';
2165 r = wl_find(ss, wl);
2166 g_free(ss);
2167 return (r);
2170 return (NULL);
2174 settings_add(char *var, char *val)
2176 int i, rv, *p;
2177 gfloat *f;
2178 char **s;
2180 /* get settings */
2181 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2182 if (strcmp(var, rs[i].name))
2183 continue;
2185 if (rs[i].s) {
2186 if (rs[i].s->set(&rs[i], val))
2187 errx(1, "invalid value for %s: %s", var, val);
2188 rv = 1;
2189 break;
2190 } else
2191 switch (rs[i].type) {
2192 case XT_S_INT:
2193 p = rs[i].ival;
2194 *p = atoi(val);
2195 rv = 1;
2196 break;
2197 case XT_S_STR:
2198 s = rs[i].sval;
2199 if (s == NULL)
2200 errx(1, "invalid sval for %s",
2201 rs[i].name);
2202 if (*s)
2203 g_free(*s);
2204 *s = g_strdup(val);
2205 rv = 1;
2206 break;
2207 case XT_S_FLOAT:
2208 f = rs[i].fval;
2209 *f = atof(val);
2210 rv = 1;
2211 break;
2212 case XT_S_INVALID:
2213 default:
2214 errx(1, "invalid type for %s", var);
2216 break;
2218 return (rv);
2221 #define WS "\n= \t"
2222 void
2223 config_parse(char *filename, int runtime)
2225 FILE *config, *f;
2226 char *line, *cp, *var, *val;
2227 size_t len, lineno = 0;
2228 int handled;
2229 char file[PATH_MAX];
2230 struct stat sb;
2232 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2234 if (filename == NULL)
2235 return;
2237 if (runtime && runtime_settings[0] != '\0') {
2238 snprintf(file, sizeof file, "%s/%s",
2239 work_dir, runtime_settings);
2240 if (stat(file, &sb)) {
2241 warnx("runtime file doesn't exist, creating it");
2242 if ((f = fopen(file, "w")) == NULL)
2243 err(1, "runtime");
2244 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2245 fclose(f);
2247 } else
2248 strlcpy(file, filename, sizeof file);
2250 if ((config = fopen(file, "r")) == NULL) {
2251 warn("config_parse: cannot open %s", filename);
2252 return;
2255 for (;;) {
2256 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2257 if (feof(config) || ferror(config))
2258 break;
2260 cp = line;
2261 cp += (long)strspn(cp, WS);
2262 if (cp[0] == '\0') {
2263 /* empty line */
2264 free(line);
2265 continue;
2268 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2269 startpage_add("invalid configuration file entry: %s",
2270 line);
2272 cp += (long)strspn(cp, WS);
2274 if ((val = strsep(&cp, "\0")) == NULL)
2275 break;
2277 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2278 handled = settings_add(var, val);
2279 if (handled == 0)
2280 startpage_add("invalid configuration file entry: %s=%s",
2281 var, val);
2283 free(line);
2286 fclose(config);
2289 char *
2290 js_ref_to_string(JSContextRef context, JSValueRef ref)
2292 char *s = NULL;
2293 size_t l;
2294 JSStringRef jsref;
2296 jsref = JSValueToStringCopy(context, ref, NULL);
2297 if (jsref == NULL)
2298 return (NULL);
2300 l = JSStringGetMaximumUTF8CStringSize(jsref);
2301 s = g_malloc(l);
2302 if (s)
2303 JSStringGetUTF8CString(jsref, s, l);
2304 JSStringRelease(jsref);
2306 return (s);
2309 void
2310 disable_hints(struct tab *t)
2312 bzero(t->hint_buf, sizeof t->hint_buf);
2313 bzero(t->hint_num, sizeof t->hint_num);
2314 run_script(t, "vimprobable_clear()");
2315 t->hints_on = 0;
2316 t->hint_mode = XT_HINT_NONE;
2319 void
2320 enable_hints(struct tab *t)
2322 bzero(t->hint_buf, sizeof t->hint_buf);
2323 run_script(t, "vimprobable_show_hints()");
2324 t->hints_on = 1;
2325 t->hint_mode = XT_HINT_NONE;
2328 #define XT_JS_OPEN ("open;")
2329 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2330 #define XT_JS_FIRE ("fire;")
2331 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2332 #define XT_JS_FOUND ("found;")
2333 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2336 run_script(struct tab *t, char *s)
2338 JSGlobalContextRef ctx;
2339 WebKitWebFrame *frame;
2340 JSStringRef str;
2341 JSValueRef val, exception;
2342 char *es, buf[128];
2344 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2345 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2347 frame = webkit_web_view_get_main_frame(t->wv);
2348 ctx = webkit_web_frame_get_global_context(frame);
2350 str = JSStringCreateWithUTF8CString(s);
2351 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2352 NULL, 0, &exception);
2353 JSStringRelease(str);
2355 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2356 if (val == NULL) {
2357 es = js_ref_to_string(ctx, exception);
2358 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2359 g_free(es);
2360 return (1);
2361 } else {
2362 es = js_ref_to_string(ctx, val);
2363 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2365 /* handle return value right here */
2366 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2367 disable_hints(t);
2368 marks_clear(t);
2369 load_uri(t, &es[XT_JS_OPEN_LEN]);
2372 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2373 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2374 &es[XT_JS_FIRE_LEN]);
2375 run_script(t, buf);
2376 disable_hints(t);
2379 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2380 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2381 disable_hints(t);
2384 g_free(es);
2387 return (0);
2391 hint(struct tab *t, struct karg *args)
2394 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2396 if (t->hints_on == 0)
2397 enable_hints(t);
2398 else
2399 disable_hints(t);
2401 return (0);
2404 void
2405 apply_style(struct tab *t)
2407 g_object_set(G_OBJECT(t->settings),
2408 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2412 userstyle(struct tab *t, struct karg *args)
2414 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2416 if (t->styled) {
2417 t->styled = 0;
2418 g_object_set(G_OBJECT(t->settings),
2419 "user-stylesheet-uri", NULL, (char *)NULL);
2420 } else {
2421 t->styled = 1;
2422 apply_style(t);
2424 return (0);
2428 * Doesn't work fully, due to the following bug:
2429 * https://bugs.webkit.org/show_bug.cgi?id=51747
2432 restore_global_history(void)
2434 char file[PATH_MAX];
2435 FILE *f;
2436 struct history *h;
2437 gchar *uri;
2438 gchar *title;
2439 const char delim[3] = {'\\', '\\', '\0'};
2441 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2443 if ((f = fopen(file, "r")) == NULL) {
2444 warnx("%s: fopen", __func__);
2445 return (1);
2448 for (;;) {
2449 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2450 if (feof(f) || ferror(f))
2451 break;
2453 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2454 if (feof(f) || ferror(f)) {
2455 free(uri);
2456 warnx("%s: broken history file\n", __func__);
2457 return (1);
2460 if (uri && strlen(uri) && title && strlen(title)) {
2461 webkit_web_history_item_new_with_data(uri, title);
2462 h = g_malloc(sizeof(struct history));
2463 h->uri = g_strdup(uri);
2464 h->title = g_strdup(title);
2465 RB_INSERT(history_list, &hl, h);
2466 completion_add_uri(h->uri);
2467 } else {
2468 warnx("%s: failed to restore history\n", __func__);
2469 free(uri);
2470 free(title);
2471 return (1);
2474 free(uri);
2475 free(title);
2476 uri = NULL;
2477 title = NULL;
2480 return (0);
2484 save_global_history_to_disk(struct tab *t)
2486 char file[PATH_MAX];
2487 FILE *f;
2488 struct history *h;
2490 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2492 if ((f = fopen(file, "w")) == NULL) {
2493 show_oops(t, "%s: global history file: %s",
2494 __func__, strerror(errno));
2495 return (1);
2498 RB_FOREACH_REVERSE(h, history_list, &hl) {
2499 if (h->uri && h->title)
2500 fprintf(f, "%s\n%s\n", h->uri, h->title);
2503 fclose(f);
2505 return (0);
2509 quit(struct tab *t, struct karg *args)
2511 if (save_global_history)
2512 save_global_history_to_disk(t);
2514 gtk_main_quit();
2516 return (1);
2519 void
2520 restore_sessions_list(void)
2522 DIR *sdir = NULL;
2523 struct dirent *dp = NULL;
2524 struct session *s;
2526 sdir = opendir(sessions_dir);
2527 if (sdir) {
2528 while ((dp = readdir(sdir)) != NULL)
2529 if (dp->d_type == DT_REG) {
2530 s = g_malloc(sizeof(struct session));
2531 s->name = g_strdup(dp->d_name);
2532 TAILQ_INSERT_TAIL(&sessions, s, entry);
2534 closedir(sdir);
2539 open_tabs(struct tab *t, struct karg *a)
2541 char file[PATH_MAX];
2542 FILE *f = NULL;
2543 char *uri = NULL;
2544 int rv = 1;
2545 struct tab *ti, *tt;
2547 if (a == NULL)
2548 goto done;
2550 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2551 if ((f = fopen(file, "r")) == NULL)
2552 goto done;
2554 ti = TAILQ_LAST(&tabs, tab_list);
2556 for (;;) {
2557 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2558 if (feof(f) || ferror(f))
2559 break;
2561 /* retrieve session name */
2562 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2563 strlcpy(named_session,
2564 &uri[strlen(XT_SAVE_SESSION_ID)],
2565 sizeof named_session);
2566 continue;
2569 if (uri && strlen(uri))
2570 create_new_tab(uri, NULL, 1, -1);
2572 free(uri);
2573 uri = NULL;
2576 /* close open tabs */
2577 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2578 for (;;) {
2579 tt = TAILQ_FIRST(&tabs);
2580 if (tt != ti) {
2581 delete_tab(tt);
2582 continue;
2584 delete_tab(tt);
2585 break;
2587 recalc_tabs();
2590 rv = 0;
2591 done:
2592 if (f)
2593 fclose(f);
2595 return (rv);
2599 restore_saved_tabs(void)
2601 char file[PATH_MAX];
2602 int unlink_file = 0;
2603 struct stat sb;
2604 struct karg a;
2605 int rv = 0;
2607 snprintf(file, sizeof file, "%s/%s",
2608 sessions_dir, XT_RESTART_TABS_FILE);
2609 if (stat(file, &sb) == -1)
2610 a.s = XT_SAVED_TABS_FILE;
2611 else {
2612 unlink_file = 1;
2613 a.s = XT_RESTART_TABS_FILE;
2616 a.i = XT_SES_DONOTHING;
2617 rv = open_tabs(NULL, &a);
2619 if (unlink_file)
2620 unlink(file);
2622 return (rv);
2626 save_tabs(struct tab *t, struct karg *a)
2628 char file[PATH_MAX];
2629 FILE *f;
2630 int num_tabs = 0, i;
2631 struct tab **stabs = NULL;
2633 if (a == NULL)
2634 return (1);
2635 if (a->s == NULL)
2636 snprintf(file, sizeof file, "%s/%s",
2637 sessions_dir, named_session);
2638 else
2639 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2641 if ((f = fopen(file, "w")) == NULL) {
2642 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2643 return (1);
2646 /* save session name */
2647 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2649 /* Save tabs, in the order they are arranged in the notebook. */
2650 num_tabs = sort_tabs_by_page_num(&stabs);
2652 for (i = 0; i < num_tabs; i++)
2653 if (stabs[i]) {
2654 if (get_uri(stabs[i]) != NULL)
2655 fprintf(f, "%s\n", get_uri(stabs[i]));
2656 else if (gtk_entry_get_text(GTK_ENTRY(
2657 stabs[i]->uri_entry)))
2658 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2659 stabs[i]->uri_entry)));
2662 g_free(stabs);
2664 /* try and make sure this gets to disk NOW. XXX Backup first? */
2665 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2666 show_oops(t, "May not have managed to save session: %s",
2667 strerror(errno));
2670 fclose(f);
2672 return (0);
2676 save_tabs_and_quit(struct tab *t, struct karg *args)
2678 struct karg a;
2680 a.s = NULL;
2681 save_tabs(t, &a);
2682 quit(t, NULL);
2684 return (1);
2688 run_page_script(struct tab *t, struct karg *args)
2690 const gchar *uri;
2691 char *tmp, script[PATH_MAX];
2693 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2694 if (tmp[0] == '\0') {
2695 show_oops(t, "no script specified");
2696 return (1);
2699 if ((uri = get_uri(t)) == NULL) {
2700 show_oops(t, "tab is empty, not running script");
2701 return (1);
2704 if (tmp[0] == '~')
2705 snprintf(script, sizeof script, "%s/%s",
2706 pwd->pw_dir, &tmp[1]);
2707 else
2708 strlcpy(script, tmp, sizeof script);
2710 switch (fork()) {
2711 case -1:
2712 show_oops(t, "can't fork to run script");
2713 return (1);
2714 /* NOTREACHED */
2715 case 0:
2716 break;
2717 default:
2718 return (0);
2721 /* child */
2722 execlp(script, script, uri, (void *)NULL);
2724 _exit(0);
2726 /* NOTREACHED */
2728 return (0);
2732 yank_uri(struct tab *t, struct karg *args)
2734 const gchar *uri;
2735 GtkClipboard *clipboard;
2737 if ((uri = get_uri(t)) == NULL)
2738 return (1);
2740 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2741 gtk_clipboard_set_text(clipboard, uri, -1);
2743 return (0);
2747 paste_uri(struct tab *t, struct karg *args)
2749 GtkClipboard *clipboard;
2750 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2751 gint len;
2752 gchar *p = NULL, *uri;
2754 /* try primary clipboard first */
2755 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2756 p = gtk_clipboard_wait_for_text(clipboard);
2758 /* if it failed get whatever text is in cut_buffer0 */
2759 if (p == NULL && xterm_workaround)
2760 if (gdk_property_get(gdk_get_default_root_window(),
2761 atom,
2762 gdk_atom_intern("STRING", FALSE),
2764 1024 * 1024 /* picked out of my butt */,
2765 FALSE,
2766 NULL,
2767 NULL,
2768 &len,
2769 (guchar **)&p)) {
2770 /* yes sir, we need to NUL the string */
2771 p[len] = '\0';
2774 if (p) {
2775 uri = p;
2776 while (*uri && isspace(*uri))
2777 uri++;
2778 if (strlen(uri) == 0) {
2779 show_oops(t, "empty paste buffer");
2780 goto done;
2782 if (guess_search == 0 && valid_url_type(uri)) {
2783 /* we can be clever and paste this in search box */
2784 show_oops(t, "not a valid URL");
2785 goto done;
2788 if (args->i == XT_PASTE_CURRENT_TAB)
2789 load_uri(t, uri);
2790 else if (args->i == XT_PASTE_NEW_TAB)
2791 create_new_tab(uri, NULL, 1, -1);
2794 done:
2795 if (p)
2796 g_free(p);
2798 return (0);
2801 gchar *
2802 find_domain(const gchar *s, int toplevel)
2804 SoupURI *uri;
2805 gchar *ret, *p;
2807 if (s == NULL)
2808 return (NULL);
2810 uri = soup_uri_new(s);
2812 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2813 return (NULL);
2816 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2817 if ((p = strrchr(uri->host, '.')) != NULL) {
2818 while(--p >= uri->host && *p != '.');
2819 p++;
2820 } else
2821 p = uri->host;
2822 } else
2823 p = uri->host;
2825 ret = g_strdup_printf(".%s", p);
2827 soup_uri_free(uri);
2829 return ret;
2833 toggle_cwl(struct tab *t, struct karg *args)
2835 struct domain *d;
2836 const gchar *uri;
2837 char *dom = NULL;
2838 int es;
2840 if (args == NULL)
2841 return (1);
2843 uri = get_uri(t);
2844 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2846 if (uri == NULL || dom == NULL ||
2847 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2848 show_oops(t, "Can't toggle domain in cookie white list");
2849 goto done;
2851 d = wl_find(dom, &c_wl);
2853 if (d == NULL)
2854 es = 0;
2855 else
2856 es = 1;
2858 if (args->i & XT_WL_TOGGLE)
2859 es = !es;
2860 else if ((args->i & XT_WL_ENABLE) && es != 1)
2861 es = 1;
2862 else if ((args->i & XT_WL_DISABLE) && es != 0)
2863 es = 0;
2865 if (es)
2866 /* enable cookies for domain */
2867 wl_add(dom, &c_wl, 0);
2868 else
2869 /* disable cookies for domain */
2870 RB_REMOVE(domain_list, &c_wl, d);
2872 if (args->i & XT_WL_RELOAD)
2873 webkit_web_view_reload(t->wv);
2875 done:
2876 g_free(dom);
2877 return (0);
2881 toggle_js(struct tab *t, struct karg *args)
2883 int es;
2884 const gchar *uri;
2885 struct domain *d;
2886 char *dom = NULL;
2888 if (args == NULL)
2889 return (1);
2891 g_object_get(G_OBJECT(t->settings),
2892 "enable-scripts", &es, (char *)NULL);
2893 if (args->i & XT_WL_TOGGLE)
2894 es = !es;
2895 else if ((args->i & XT_WL_ENABLE) && es != 1)
2896 es = 1;
2897 else if ((args->i & XT_WL_DISABLE) && es != 0)
2898 es = 0;
2899 else
2900 return (1);
2902 uri = get_uri(t);
2903 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2905 if (uri == NULL || dom == NULL ||
2906 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2907 show_oops(t, "Can't toggle domain in JavaScript white list");
2908 goto done;
2911 if (es) {
2912 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2913 wl_add(dom, &js_wl, 0 /* session */);
2914 } else {
2915 d = wl_find(dom, &js_wl);
2916 if (d)
2917 RB_REMOVE(domain_list, &js_wl, d);
2918 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2920 g_object_set(G_OBJECT(t->settings),
2921 "enable-scripts", es, (char *)NULL);
2922 g_object_set(G_OBJECT(t->settings),
2923 "javascript-can-open-windows-automatically", es, (char *)NULL);
2924 webkit_web_view_set_settings(t->wv, t->settings);
2926 if (args->i & XT_WL_RELOAD)
2927 webkit_web_view_reload(t->wv);
2928 done:
2929 if (dom)
2930 g_free(dom);
2931 return (0);
2934 void
2935 js_toggle_cb(GtkWidget *w, struct tab *t)
2937 struct karg a;
2939 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2940 toggle_cwl(t, &a);
2942 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2943 toggle_js(t, &a);
2947 toggle_src(struct tab *t, struct karg *args)
2949 gboolean mode;
2951 if (t == NULL)
2952 return (0);
2954 mode = webkit_web_view_get_view_source_mode(t->wv);
2955 webkit_web_view_set_view_source_mode(t->wv, !mode);
2956 webkit_web_view_reload(t->wv);
2958 return (0);
2961 void
2962 focus_webview(struct tab *t)
2964 if (t == NULL)
2965 return;
2967 /* only grab focus if we are visible */
2968 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2969 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2973 focus(struct tab *t, struct karg *args)
2975 if (t == NULL || args == NULL)
2976 return (1);
2978 if (show_url == 0)
2979 return (0);
2981 if (args->i == XT_FOCUS_URI)
2982 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2983 else if (args->i == XT_FOCUS_SEARCH)
2984 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2986 return (0);
2990 stats(struct tab *t, struct karg *args)
2992 char *page, *body, *s, line[64 * 1024];
2993 uint64_t line_count = 0;
2994 FILE *r_cookie_f;
2996 if (t == NULL)
2997 show_oops(NULL, "stats invalid parameters");
2999 line[0] = '\0';
3000 if (save_rejected_cookies) {
3001 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3002 for (;;) {
3003 s = fgets(line, sizeof line, r_cookie_f);
3004 if (s == NULL || feof(r_cookie_f) ||
3005 ferror(r_cookie_f))
3006 break;
3007 line_count++;
3009 fclose(r_cookie_f);
3010 snprintf(line, sizeof line,
3011 "<br/>Cookies blocked(*) total: %llu", line_count);
3012 } else
3013 show_oops(t, "Can't open blocked cookies file: %s",
3014 strerror(errno));
3017 body = g_strdup_printf(
3018 "Cookies blocked(*) this session: %llu"
3019 "%s"
3020 "<p><small><b>*</b> results vary based on settings</small></p>",
3021 blocked_cookies,
3022 line);
3024 page = get_html_page("Statistics", body, "", 0);
3025 g_free(body);
3027 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3028 g_free(page);
3030 return (0);
3034 marco(struct tab *t, struct karg *args)
3036 char *page, line[64 * 1024];
3037 int len;
3039 if (t == NULL)
3040 show_oops(NULL, "marco invalid parameters");
3042 line[0] = '\0';
3043 snprintf(line, sizeof line, "%s", marco_message(&len));
3045 page = get_html_page("Marco Sez...", line, "", 0);
3047 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3048 g_free(page);
3050 return (0);
3054 blank(struct tab *t, struct karg *args)
3056 if (t == NULL)
3057 show_oops(NULL, "blank invalid parameters");
3059 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3061 return (0);
3065 about(struct tab *t, struct karg *args)
3067 char *page, *body;
3069 if (t == NULL)
3070 show_oops(NULL, "about invalid parameters");
3072 body = g_strdup_printf("<b>Version: %s</b>"
3073 #ifdef XXXTERM_BUILDSTR
3074 "<br><b>Build: %s</b>"
3075 #endif
3076 "<p>"
3077 "Authors:"
3078 "<ul>"
3079 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3080 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3081 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3082 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3083 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3084 "</ul>"
3085 "Copyrights and licenses can be found on the XXXTerm "
3086 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3087 "</p>",
3088 #ifdef XXXTERM_BUILDSTR
3089 version, XXXTERM_BUILDSTR
3090 #else
3091 version
3092 #endif
3095 page = get_html_page("About", body, "", 0);
3096 g_free(body);
3098 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3099 g_free(page);
3101 return (0);
3105 help(struct tab *t, struct karg *args)
3107 char *page, *head, *body;
3109 if (t == NULL)
3110 show_oops(NULL, "help invalid parameters");
3112 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3113 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3114 "</head>\n";
3115 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3116 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3117 "cgi-bin/man-cgi?xxxterm</a>";
3119 page = get_html_page(XT_NAME, body, head, FALSE);
3121 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3122 g_free(page);
3124 return (0);
3128 startpage(struct tab *t, struct karg *args)
3130 char *page, *body, *b;
3131 struct sp *s;
3133 if (t == NULL)
3134 show_oops(NULL, "startpage invalid parameters");
3136 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3138 TAILQ_FOREACH(s, &spl, entry) {
3139 b = body;
3140 body = g_strdup_printf("%s%s<br>", body, s->line);
3141 g_free(b);
3144 page = get_html_page("Startup Exception", body, "", 0);
3145 g_free(body);
3147 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3148 g_free(page);
3150 return (0);
3153 void
3154 startpage_add(const char *fmt, ...)
3156 va_list ap;
3157 char *msg;
3158 struct sp *s;
3160 if (fmt == NULL)
3161 return;
3163 va_start(ap, fmt);
3164 if (vasprintf(&msg, fmt, ap) == -1)
3165 errx(1, "startpage_add failed");
3166 va_end(ap);
3168 s = g_malloc0(sizeof *s);
3169 s->line = msg;
3171 TAILQ_INSERT_TAIL(&spl, s, entry);
3175 * update all favorite tabs apart from one. Pass NULL if
3176 * you want to update all.
3178 void
3179 update_favorite_tabs(struct tab *apart_from)
3181 struct tab *t;
3182 if (!updating_fl_tabs) {
3183 updating_fl_tabs = 1; /* stop infinite recursion */
3184 TAILQ_FOREACH(t, &tabs, entry)
3185 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3186 && (t != apart_from))
3187 xtp_page_fl(t, NULL);
3188 updating_fl_tabs = 0;
3192 /* show a list of favorites (bookmarks) */
3194 xtp_page_fl(struct tab *t, struct karg *args)
3196 char file[PATH_MAX];
3197 FILE *f;
3198 char *uri = NULL, *title = NULL;
3199 size_t len, lineno = 0;
3200 int i, failed = 0;
3201 char *body, *tmp, *page = NULL;
3202 const char delim[3] = {'\\', '\\', '\0'};
3204 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3206 if (t == NULL)
3207 warn("%s: bad param", __func__);
3209 /* new session key */
3210 if (!updating_fl_tabs)
3211 generate_xtp_session_key(&fl_session_key);
3213 /* open favorites */
3214 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3215 if ((f = fopen(file, "r")) == NULL) {
3216 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3217 return (1);
3220 /* body */
3221 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3222 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3223 "<th style='width: 40px'>Rm</th></tr>\n");
3225 for (i = 1;;) {
3226 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3227 if (feof(f) || ferror(f))
3228 break;
3229 if (strlen(title) == 0 || title[0] == '#') {
3230 free(title);
3231 title = NULL;
3232 continue;
3235 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3236 if (feof(f) || ferror(f)) {
3237 show_oops(t, "favorites file corrupt");
3238 failed = 1;
3239 break;
3242 tmp = body;
3243 body = g_strdup_printf("%s<tr>"
3244 "<td>%d</td>"
3245 "<td><a href='%s'>%s</a></td>"
3246 "<td style='text-align: center'>"
3247 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3248 "</tr>\n",
3249 body, i, uri, title,
3250 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3252 g_free(tmp);
3254 free(uri);
3255 uri = NULL;
3256 free(title);
3257 title = NULL;
3258 i++;
3260 fclose(f);
3262 /* if none, say so */
3263 if (i == 1) {
3264 tmp = body;
3265 body = g_strdup_printf("%s<tr>"
3266 "<td colspan='3' style='text-align: center'>"
3267 "No favorites - To add one use the 'favadd' command."
3268 "</td></tr>", body);
3269 g_free(tmp);
3272 tmp = body;
3273 body = g_strdup_printf("%s</table>", body);
3274 g_free(tmp);
3276 if (uri)
3277 free(uri);
3278 if (title)
3279 free(title);
3281 /* render */
3282 if (!failed) {
3283 page = get_html_page("Favorites", body, "", 1);
3284 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3285 g_free(page);
3288 update_favorite_tabs(t);
3290 if (body)
3291 g_free(body);
3293 return (failed);
3296 void
3297 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3298 size_t cert_count, char *title)
3300 gnutls_datum_t cinfo;
3301 char *tmp, *body;
3302 int i;
3304 body = g_strdup("");
3306 for (i = 0; i < cert_count; i++) {
3307 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3308 &cinfo))
3309 return;
3311 tmp = body;
3312 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3313 body, i, cinfo.data);
3314 gnutls_free(cinfo.data);
3315 g_free(tmp);
3318 tmp = get_html_page(title, body, "", 0);
3319 g_free(body);
3321 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3322 g_free(tmp);
3326 ca_cmd(struct tab *t, struct karg *args)
3328 FILE *f = NULL;
3329 int rv = 1, certs = 0, certs_read;
3330 struct stat sb;
3331 gnutls_datum_t dt;
3332 gnutls_x509_crt_t *c = NULL;
3333 char *certs_buf = NULL, *s;
3335 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3336 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3337 return (1);
3340 if (fstat(fileno(f), &sb) == -1) {
3341 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3342 goto done;
3345 certs_buf = g_malloc(sb.st_size + 1);
3346 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3347 show_oops(t, "Can't read CA file: %s", strerror(errno));
3348 goto done;
3350 certs_buf[sb.st_size] = '\0';
3352 s = certs_buf;
3353 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3354 certs++;
3355 s += strlen("BEGIN CERTIFICATE");
3358 bzero(&dt, sizeof dt);
3359 dt.data = (unsigned char *)certs_buf;
3360 dt.size = sb.st_size;
3361 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3362 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3363 GNUTLS_X509_FMT_PEM, 0);
3364 if (certs_read <= 0) {
3365 show_oops(t, "No cert(s) available");
3366 goto done;
3368 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3369 done:
3370 if (c)
3371 g_free(c);
3372 if (certs_buf)
3373 g_free(certs_buf);
3374 if (f)
3375 fclose(f);
3377 return (rv);
3381 connect_socket_from_uri(struct tab *t, const gchar *uri, char *domain,
3382 size_t domain_sz)
3384 SoupURI *su = NULL;
3385 struct addrinfo hints, *res = NULL, *ai;
3386 int rv = -1, s = -1, on, error;
3387 char port[8];
3389 if (uri && !g_str_has_prefix(uri, "https://")) {
3390 show_oops(t, "invalid URI");
3391 goto done;
3394 su = soup_uri_new(uri);
3395 if (su == NULL) {
3396 show_oops(t, "invalid soup URI");
3397 goto done;
3399 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3400 show_oops(t, "invalid HTTPS URI");
3401 goto done;
3404 snprintf(port, sizeof port, "%d", su->port);
3405 bzero(&hints, sizeof(struct addrinfo));
3406 hints.ai_flags = AI_CANONNAME;
3407 hints.ai_family = AF_UNSPEC;
3408 hints.ai_socktype = SOCK_STREAM;
3410 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3411 show_oops(t, "getaddrinfo failed: %s", gai_strerror(errno));
3412 goto done;
3415 for (ai = res; ai; ai = ai->ai_next) {
3416 if (s != -1) {
3417 close(s);
3418 s = -1;
3421 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3422 continue;
3423 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3424 if (s == -1)
3425 continue;
3426 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3427 sizeof(on)) == -1)
3428 continue;
3429 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3430 break;
3432 if (s == -1) {
3433 show_oops(t, "could not obtain certificates from: %s",
3434 su->host);
3435 goto done;
3438 if (domain)
3439 strlcpy(domain, su->host, domain_sz);
3440 rv = s;
3441 done:
3442 if (su)
3443 soup_uri_free(su);
3444 if (res)
3445 freeaddrinfo(res);
3446 if (rv == -1 && s != -1)
3447 close(s);
3449 return (rv);
3453 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3455 if (gsession)
3456 gnutls_deinit(gsession);
3457 if (xcred)
3458 gnutls_certificate_free_credentials(xcred);
3460 return (0);
3464 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3465 gnutls_certificate_credentials_t *xc)
3467 gnutls_certificate_credentials_t xcred;
3468 gnutls_session_t gsession;
3469 int rv = 1;
3471 if (gs == NULL || xc == NULL)
3472 goto done;
3474 *gs = NULL;
3475 *xc = NULL;
3477 gnutls_certificate_allocate_credentials(&xcred);
3478 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3479 GNUTLS_X509_FMT_PEM);
3481 gnutls_init(&gsession, GNUTLS_CLIENT);
3482 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3483 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3484 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3485 if ((rv = gnutls_handshake(gsession)) < 0) {
3486 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3488 gnutls_error_is_fatal(rv),
3489 gnutls_strerror_name(rv));
3490 stop_tls(gsession, xcred);
3491 goto done;
3494 gnutls_credentials_type_t cred;
3495 cred = gnutls_auth_get_type(gsession);
3496 if (cred != GNUTLS_CRD_CERTIFICATE) {
3497 show_oops(t, "gnutls_auth_get_type failed %d", (int)cred);
3498 stop_tls(gsession, xcred);
3499 goto done;
3502 *gs = gsession;
3503 *xc = xcred;
3504 rv = 0;
3505 done:
3506 return (rv);
3510 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3511 size_t *cert_count)
3513 unsigned int len;
3514 const gnutls_datum_t *cl;
3515 gnutls_x509_crt_t *all_certs;
3516 int i, rv = 1;
3518 if (certs == NULL || cert_count == NULL)
3519 goto done;
3520 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3521 goto done;
3522 cl = gnutls_certificate_get_peers(gsession, &len);
3523 if (len == 0)
3524 goto done;
3526 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3527 for (i = 0; i < len; i++) {
3528 gnutls_x509_crt_init(&all_certs[i]);
3529 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3530 GNUTLS_X509_FMT_PEM < 0)) {
3531 g_free(all_certs);
3532 goto done;
3536 *certs = all_certs;
3537 *cert_count = len;
3538 rv = 0;
3539 done:
3540 return (rv);
3543 void
3544 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3546 int i;
3548 for (i = 0; i < cert_count; i++)
3549 gnutls_x509_crt_deinit(certs[i]);
3550 g_free(certs);
3553 void
3554 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3556 GdkColor c_text, c_base;
3558 gdk_color_parse(text, &c_text);
3559 gdk_color_parse(base, &c_base);
3561 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3562 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3563 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3564 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3566 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3567 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3568 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3569 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3572 void
3573 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3574 size_t cert_count, char *domain)
3576 size_t cert_buf_sz;
3577 char cert_buf[64 * 1024], file[PATH_MAX];
3578 int i;
3579 FILE *f;
3580 GdkColor color;
3582 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3583 return;
3585 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3586 if ((f = fopen(file, "w")) == NULL) {
3587 show_oops(t, "Can't create cert file %s %s",
3588 file, strerror(errno));
3589 return;
3592 for (i = 0; i < cert_count; i++) {
3593 cert_buf_sz = sizeof cert_buf;
3594 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3595 cert_buf, &cert_buf_sz)) {
3596 show_oops(t, "gnutls_x509_crt_export failed");
3597 goto done;
3599 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3600 show_oops(t, "Can't write certs: %s", strerror(errno));
3601 goto done;
3605 /* not the best spot but oh well */
3606 gdk_color_parse("lightblue", &color);
3607 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3608 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3609 done:
3610 fclose(f);
3613 enum cert_trust {
3614 CERT_LOCAL,
3615 CERT_TRUSTED,
3616 CERT_UNTRUSTED,
3617 CERT_BAD
3620 enum cert_trust
3621 load_compare_cert(struct tab *t, struct karg *args)
3623 const gchar *uri;
3624 char domain[8182], file[PATH_MAX];
3625 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3626 int s = -1, i;
3627 unsigned int error;
3628 FILE *f = NULL;
3629 size_t cert_buf_sz, cert_count;
3630 enum cert_trust rv = CERT_UNTRUSTED;
3631 char serr[80];
3632 gnutls_session_t gsession;
3633 gnutls_x509_crt_t *certs;
3634 gnutls_certificate_credentials_t xcred;
3636 DNPRINTF(XT_D_URL, "%s: %p %p\n", __func__, t, args);
3638 if (t == NULL)
3639 return (rv);
3641 if ((uri = get_uri(t)) == NULL)
3642 return (rv);
3643 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3645 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1)
3646 return (rv);
3647 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3649 /* go ssl/tls */
3650 if (start_tls(t, s, &gsession, &xcred))
3651 goto done;
3652 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3654 /* verify certs in case cert file doesn't exist */
3655 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3656 GNUTLS_E_SUCCESS) {
3657 show_oops(t, "Invalid certificates");
3658 goto done;
3661 /* get certs */
3662 if (get_connection_certs(gsession, &certs, &cert_count)) {
3663 show_oops(t, "Can't get connection certificates");
3664 goto done;
3667 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3668 if ((f = fopen(file, "r")) == NULL) {
3669 if (!error)
3670 rv = CERT_TRUSTED;
3671 goto freeit;
3674 for (i = 0; i < cert_count; i++) {
3675 cert_buf_sz = sizeof cert_buf;
3676 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3677 cert_buf, &cert_buf_sz)) {
3678 goto freeit;
3680 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3681 rv = CERT_BAD; /* critical */
3682 goto freeit;
3684 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3685 rv = CERT_BAD; /* critical */
3686 goto freeit;
3688 rv = CERT_LOCAL;
3691 freeit:
3692 if (f)
3693 fclose(f);
3694 free_connection_certs(certs, cert_count);
3695 done:
3696 /* we close the socket first for speed */
3697 if (s != -1)
3698 close(s);
3700 /* only complain if we didn't save it locally */
3701 if (error && rv != CERT_LOCAL) {
3702 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3703 if (error & GNUTLS_CERT_INVALID)
3704 strlcat(serr, "invalid, ", sizeof serr);
3705 if (error & GNUTLS_CERT_REVOKED)
3706 strlcat(serr, "revoked, ", sizeof serr);
3707 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3708 strlcat(serr, "signer not found, ", sizeof serr);
3709 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3710 strlcat(serr, "not signed by CA, ", sizeof serr);
3711 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3712 strlcat(serr, "insecure algorithm, ", sizeof serr);
3713 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3714 strlcat(serr, "not activated, ", sizeof serr);
3715 if (error & GNUTLS_CERT_EXPIRED)
3716 strlcat(serr, "expired, ", sizeof serr);
3717 for (i = strlen(serr) - 1; i > 0; i--)
3718 if (serr[i] == ',') {
3719 serr[i] = '\0';
3720 break;
3722 show_oops(t, serr);
3725 stop_tls(gsession, xcred);
3727 return (rv);
3731 cert_cmd(struct tab *t, struct karg *args)
3733 const gchar *uri;
3734 char domain[8182];
3735 int s = -1;
3736 size_t cert_count;
3737 gnutls_session_t gsession;
3738 gnutls_x509_crt_t *certs;
3739 gnutls_certificate_credentials_t xcred;
3741 if (t == NULL)
3742 return (1);
3744 if (ssl_ca_file == NULL) {
3745 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3746 return (1);
3749 if ((uri = get_uri(t)) == NULL) {
3750 show_oops(t, "Invalid URI");
3751 return (1);
3754 if ((s = connect_socket_from_uri(t, uri, domain, sizeof domain)) == -1) {
3755 show_oops(t, "Invalid certificate URI: %s", uri);
3756 return (1);
3759 /* go ssl/tls */
3760 if (start_tls(t, s, &gsession, &xcred))
3761 goto done;
3763 /* get certs */
3764 if (get_connection_certs(gsession, &certs, &cert_count)) {
3765 show_oops(t, "get_connection_certs failed");
3766 goto done;
3769 if (args->i & XT_SHOW)
3770 show_certs(t, certs, cert_count, "Certificate Chain");
3771 else if (args->i & XT_SAVE)
3772 save_certs(t, certs, cert_count, domain);
3774 free_connection_certs(certs, cert_count);
3775 done:
3776 /* we close the socket first for speed */
3777 if (s != -1)
3778 close(s);
3779 stop_tls(gsession, xcred);
3781 return (0);
3785 remove_cookie(int index)
3787 int i, rv = 1;
3788 GSList *cf;
3789 SoupCookie *c;
3791 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3793 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3795 for (i = 1; cf; cf = cf->next, i++) {
3796 if (i != index)
3797 continue;
3798 c = cf->data;
3799 print_cookie("remove cookie", c);
3800 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3801 rv = 0;
3802 break;
3805 soup_cookies_free(cf);
3807 return (rv);
3811 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3813 struct domain *d;
3814 char *tmp, *body;
3816 body = g_strdup("");
3818 /* p list */
3819 if (args->i & XT_WL_PERSISTENT) {
3820 tmp = body;
3821 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3822 g_free(tmp);
3823 RB_FOREACH(d, domain_list, wl) {
3824 if (d->handy == 0)
3825 continue;
3826 tmp = body;
3827 body = g_strdup_printf("%s%s<br/>", body, d->d);
3828 g_free(tmp);
3832 /* s list */
3833 if (args->i & XT_WL_SESSION) {
3834 tmp = body;
3835 body = g_strdup_printf("%s<h2>Session</h2>", body);
3836 g_free(tmp);
3837 RB_FOREACH(d, domain_list, wl) {
3838 if (d->handy == 1)
3839 continue;
3840 tmp = body;
3841 body = g_strdup_printf("%s%s<br/>", body, d->d);
3842 g_free(tmp);
3846 tmp = get_html_page(title, body, "", 0);
3847 g_free(body);
3848 if (wl == &js_wl)
3849 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3850 else
3851 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3852 g_free(tmp);
3853 return (0);
3857 wl_save(struct tab *t, struct karg *args, int js)
3859 char file[PATH_MAX];
3860 FILE *f;
3861 char *line = NULL, *lt = NULL, *dom = NULL;
3862 size_t linelen;
3863 const gchar *uri;
3864 struct karg a;
3865 struct domain *d;
3866 GSList *cf;
3867 SoupCookie *ci, *c;
3869 if (t == NULL || args == NULL)
3870 return (1);
3872 if (runtime_settings[0] == '\0')
3873 return (1);
3875 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3876 if ((f = fopen(file, "r+")) == NULL)
3877 return (1);
3879 uri = get_uri(t);
3880 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3881 if (uri == NULL || dom == NULL ||
3882 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3883 show_oops(t, "Can't add domain to %s white list",
3884 js ? "JavaScript" : "cookie");
3885 goto done;
3888 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3890 while (!feof(f)) {
3891 line = fparseln(f, &linelen, NULL, NULL, 0);
3892 if (line == NULL)
3893 continue;
3894 if (!strcmp(line, lt))
3895 goto done;
3896 free(line);
3897 line = NULL;
3900 fprintf(f, "%s\n", lt);
3902 a.i = XT_WL_ENABLE;
3903 a.i |= args->i;
3904 if (js) {
3905 d = wl_find(dom, &js_wl);
3906 if (!d) {
3907 settings_add("js_wl", dom);
3908 d = wl_find(dom, &js_wl);
3910 toggle_js(t, &a);
3911 } else {
3912 d = wl_find(dom, &c_wl);
3913 if (!d) {
3914 settings_add("cookie_wl", dom);
3915 d = wl_find(dom, &c_wl);
3917 toggle_cwl(t, &a);
3919 /* find and add to persistent jar */
3920 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3921 for (;cf; cf = cf->next) {
3922 ci = cf->data;
3923 if (!strcmp(dom, ci->domain) ||
3924 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3925 c = soup_cookie_copy(ci);
3926 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3929 soup_cookies_free(cf);
3931 if (d)
3932 d->handy = 1;
3934 done:
3935 if (line)
3936 free(line);
3937 if (dom)
3938 g_free(dom);
3939 if (lt)
3940 g_free(lt);
3941 fclose(f);
3943 return (0);
3947 js_show_wl(struct tab *t, struct karg *args)
3949 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3950 wl_show(t, args, "JavaScript White List", &js_wl);
3952 return (0);
3956 cookie_show_wl(struct tab *t, struct karg *args)
3958 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3959 wl_show(t, args, "Cookie White List", &c_wl);
3961 return (0);
3965 cookie_cmd(struct tab *t, struct karg *args)
3967 if (args->i & XT_SHOW)
3968 wl_show(t, args, "Cookie White List", &c_wl);
3969 else if (args->i & XT_WL_TOGGLE) {
3970 args->i |= XT_WL_RELOAD;
3971 toggle_cwl(t, args);
3972 } else if (args->i & XT_SAVE) {
3973 args->i |= XT_WL_RELOAD;
3974 wl_save(t, args, 0);
3975 } else if (args->i & XT_DELETE)
3976 show_oops(t, "'cookie delete' currently unimplemented");
3978 return (0);
3982 js_cmd(struct tab *t, struct karg *args)
3984 if (args->i & XT_SHOW)
3985 wl_show(t, args, "JavaScript White List", &js_wl);
3986 else if (args->i & XT_SAVE) {
3987 args->i |= XT_WL_RELOAD;
3988 wl_save(t, args, 1);
3989 } else if (args->i & XT_WL_TOGGLE) {
3990 args->i |= XT_WL_RELOAD;
3991 toggle_js(t, args);
3992 } else if (args->i & XT_DELETE)
3993 show_oops(t, "'js delete' currently unimplemented");
3995 return (0);
3999 toplevel_cmd(struct tab *t, struct karg *args)
4001 js_toggle_cb(t->js_toggle, t);
4003 return (0);
4007 add_favorite(struct tab *t, struct karg *args)
4009 char file[PATH_MAX];
4010 FILE *f;
4011 char *line = NULL;
4012 size_t urilen, linelen;
4013 const gchar *uri, *title;
4015 if (t == NULL)
4016 return (1);
4018 /* don't allow adding of xtp pages to favorites */
4019 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4020 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4021 return (1);
4024 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4025 if ((f = fopen(file, "r+")) == NULL) {
4026 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4027 return (1);
4030 title = get_title(t, FALSE);
4031 uri = get_uri(t);
4033 if (title == NULL || uri == NULL) {
4034 show_oops(t, "can't add page to favorites");
4035 goto done;
4038 urilen = strlen(uri);
4040 for (;;) {
4041 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4042 if (feof(f) || ferror(f))
4043 break;
4045 if (linelen == urilen && !strcmp(line, uri))
4046 goto done;
4048 free(line);
4049 line = NULL;
4052 fprintf(f, "\n%s\n%s", title, uri);
4053 done:
4054 if (line)
4055 free(line);
4056 fclose(f);
4058 update_favorite_tabs(NULL);
4060 return (0);
4064 can_go_back_for_real(struct tab *t)
4066 int i;
4067 WebKitWebHistoryItem *item;
4069 /* rely on webkit to make sure we can go backward when on an about page */
4070 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4071 return (webkit_web_view_can_go_forward(t->wv));
4074 /* the back/forwars list is stupid so help determine if we can go back */
4075 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4076 item != NULL;
4077 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4078 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4079 return (TRUE);
4082 return (FALSE);
4086 can_go_forward_for_real(struct tab *t)
4088 int i;
4089 WebKitWebHistoryItem *item;
4091 /* rely on webkit to make sure we can go forward when on an about page */
4092 if (get_uri(t) == NULL || g_str_has_prefix(get_uri(t), "about:"))
4093 return (webkit_web_view_can_go_forward(t->wv));
4095 /* the back/forwars list is stupid so help selecting a different item */
4096 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4097 item != NULL;
4098 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4099 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t)))
4100 return (TRUE);
4103 return (FALSE);
4106 void
4107 go_back_for_real(struct tab *t)
4109 int i;
4110 WebKitWebHistoryItem *item;
4112 /* the back/forwars list is stupid so help selecting a different item */
4113 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4114 item != NULL;
4115 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4116 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t))) {
4117 webkit_web_view_go_to_back_forward_item(t->wv, item);
4118 break;
4123 void
4124 go_forward_for_real(struct tab *t)
4126 int i;
4127 WebKitWebHistoryItem *item;
4129 /* the back/forwars list is stupid so help selecting a different item */
4130 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4131 item != NULL;
4132 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4133 if (strcmp(webkit_web_history_item_get_uri(item), get_uri(t))) {
4134 webkit_web_view_go_to_back_forward_item(t->wv, item);
4135 break;
4141 navaction(struct tab *t, struct karg *args)
4143 WebKitWebHistoryItem *item;
4144 WebKitWebFrame *frame;
4146 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4147 t->tab_id, args->i);
4149 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4150 if (t->item) {
4151 if (args->i == XT_NAV_BACK)
4152 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4153 else
4154 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4155 if (item == NULL)
4156 return (XT_CB_PASSTHROUGH);
4157 webkit_web_view_go_to_back_forward_item(t->wv, item);
4158 t->item = NULL;
4159 return (XT_CB_PASSTHROUGH);
4162 switch (args->i) {
4163 case XT_NAV_BACK:
4164 marks_clear(t);
4165 go_back_for_real(t);
4166 break;
4167 case XT_NAV_FORWARD:
4168 marks_clear(t);
4169 go_forward_for_real(t);
4170 break;
4171 case XT_NAV_RELOAD:
4172 frame = webkit_web_view_get_main_frame(t->wv);
4173 webkit_web_frame_reload(frame);
4174 break;
4176 return (XT_CB_PASSTHROUGH);
4180 move(struct tab *t, struct karg *args)
4182 GtkAdjustment *adjust;
4183 double pi, si, pos, ps, upper, lower, max;
4184 double percent;
4186 switch (args->i) {
4187 case XT_MOVE_DOWN:
4188 case XT_MOVE_UP:
4189 case XT_MOVE_BOTTOM:
4190 case XT_MOVE_TOP:
4191 case XT_MOVE_PAGEDOWN:
4192 case XT_MOVE_PAGEUP:
4193 case XT_MOVE_HALFDOWN:
4194 case XT_MOVE_HALFUP:
4195 case XT_MOVE_PERCENT:
4196 adjust = t->adjust_v;
4197 break;
4198 default:
4199 adjust = t->adjust_h;
4200 break;
4203 pos = gtk_adjustment_get_value(adjust);
4204 ps = gtk_adjustment_get_page_size(adjust);
4205 upper = gtk_adjustment_get_upper(adjust);
4206 lower = gtk_adjustment_get_lower(adjust);
4207 si = gtk_adjustment_get_step_increment(adjust);
4208 pi = gtk_adjustment_get_page_increment(adjust);
4209 max = upper - ps;
4211 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4212 "max %f si %f pi %f\n",
4213 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4214 pos, ps, upper, lower, max, si, pi);
4216 switch (args->i) {
4217 case XT_MOVE_DOWN:
4218 case XT_MOVE_RIGHT:
4219 pos += si;
4220 gtk_adjustment_set_value(adjust, MIN(pos, max));
4221 break;
4222 case XT_MOVE_UP:
4223 case XT_MOVE_LEFT:
4224 pos -= si;
4225 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4226 break;
4227 case XT_MOVE_BOTTOM:
4228 case XT_MOVE_FARRIGHT:
4229 gtk_adjustment_set_value(adjust, max);
4230 break;
4231 case XT_MOVE_TOP:
4232 case XT_MOVE_FARLEFT:
4233 gtk_adjustment_set_value(adjust, lower);
4234 break;
4235 case XT_MOVE_PAGEDOWN:
4236 pos += pi;
4237 gtk_adjustment_set_value(adjust, MIN(pos, max));
4238 break;
4239 case XT_MOVE_PAGEUP:
4240 pos -= pi;
4241 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4242 break;
4243 case XT_MOVE_HALFDOWN:
4244 pos += pi / 2;
4245 gtk_adjustment_set_value(adjust, MIN(pos, max));
4246 break;
4247 case XT_MOVE_HALFUP:
4248 pos -= pi / 2;
4249 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4250 break;
4251 case XT_MOVE_PERCENT:
4252 percent = atoi(args->s) / 100.0;
4253 pos = max * percent;
4254 if (pos < 0.0 || pos > max)
4255 break;
4256 gtk_adjustment_set_value(adjust, pos);
4257 break;
4258 default:
4259 return (XT_CB_PASSTHROUGH);
4262 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4264 return (XT_CB_HANDLED);
4267 void
4268 url_set_visibility(void)
4270 struct tab *t;
4272 TAILQ_FOREACH(t, &tabs, entry)
4273 if (show_url == 0) {
4274 gtk_widget_hide(t->toolbar);
4275 focus_webview(t);
4276 } else
4277 gtk_widget_show(t->toolbar);
4280 void
4281 notebook_tab_set_visibility(void)
4283 if (show_tabs == 0) {
4284 gtk_widget_hide(tab_bar);
4285 gtk_notebook_set_show_tabs(notebook, FALSE);
4286 } else {
4287 if (tab_style == XT_TABS_NORMAL) {
4288 gtk_widget_hide(tab_bar);
4289 gtk_notebook_set_show_tabs(notebook, TRUE);
4290 } else if (tab_style == XT_TABS_COMPACT) {
4291 gtk_widget_show(tab_bar);
4292 gtk_notebook_set_show_tabs(notebook, FALSE);
4297 void
4298 statusbar_set_visibility(void)
4300 struct tab *t;
4302 TAILQ_FOREACH(t, &tabs, entry)
4303 if (show_statusbar == 0) {
4304 gtk_widget_hide(t->statusbar_box);
4305 focus_webview(t);
4306 } else
4307 gtk_widget_show(t->statusbar_box);
4310 void
4311 url_set(struct tab *t, int enable_url_entry)
4313 GdkPixbuf *pixbuf;
4314 int progress;
4316 show_url = enable_url_entry;
4318 if (enable_url_entry) {
4319 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4320 GTK_ENTRY_ICON_PRIMARY, NULL);
4321 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4322 } else {
4323 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4324 GTK_ENTRY_ICON_PRIMARY);
4325 progress =
4326 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4327 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4328 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4329 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4330 progress);
4335 fullscreen(struct tab *t, struct karg *args)
4337 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4339 if (t == NULL)
4340 return (XT_CB_PASSTHROUGH);
4342 if (show_url == 0) {
4343 url_set(t, 1);
4344 show_tabs = 1;
4345 } else {
4346 url_set(t, 0);
4347 show_tabs = 0;
4350 url_set_visibility();
4351 notebook_tab_set_visibility();
4353 return (XT_CB_HANDLED);
4357 statustoggle(struct tab *t, struct karg *args)
4359 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4361 if (show_statusbar == 1) {
4362 show_statusbar = 0;
4363 statusbar_set_visibility();
4364 } else if (show_statusbar == 0) {
4365 show_statusbar = 1;
4366 statusbar_set_visibility();
4368 return (XT_CB_HANDLED);
4372 urlaction(struct tab *t, struct karg *args)
4374 int rv = XT_CB_HANDLED;
4376 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4378 if (t == NULL)
4379 return (XT_CB_PASSTHROUGH);
4381 switch (args->i) {
4382 case XT_URL_SHOW:
4383 if (show_url == 0) {
4384 url_set(t, 1);
4385 url_set_visibility();
4387 break;
4388 case XT_URL_HIDE:
4389 if (show_url == 1) {
4390 url_set(t, 0);
4391 url_set_visibility();
4393 break;
4395 return (rv);
4399 tabaction(struct tab *t, struct karg *args)
4401 int rv = XT_CB_HANDLED;
4402 char *url = args->s;
4403 struct undo *u;
4404 struct tab *tt;
4406 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4408 if (t == NULL)
4409 return (XT_CB_PASSTHROUGH);
4411 switch (args->i) {
4412 case XT_TAB_NEW:
4413 if (strlen(url) > 0)
4414 create_new_tab(url, NULL, 1, args->precount);
4415 else
4416 create_new_tab(NULL, NULL, 1, args->precount);
4417 break;
4418 case XT_TAB_DELETE:
4419 if (args->precount < 0)
4420 delete_tab(t);
4421 else
4422 TAILQ_FOREACH(tt, &tabs, entry)
4423 if (tt->tab_id == args->precount - 1) {
4424 delete_tab(tt);
4425 break;
4427 break;
4428 case XT_TAB_DELQUIT:
4429 if (gtk_notebook_get_n_pages(notebook) > 1)
4430 delete_tab(t);
4431 else
4432 quit(t, args);
4433 break;
4434 case XT_TAB_OPEN:
4435 if (strlen(url) > 0)
4437 else {
4438 rv = XT_CB_PASSTHROUGH;
4439 goto done;
4441 load_uri(t, url);
4442 break;
4443 case XT_TAB_SHOW:
4444 if (show_tabs == 0) {
4445 show_tabs = 1;
4446 notebook_tab_set_visibility();
4448 break;
4449 case XT_TAB_HIDE:
4450 if (show_tabs == 1) {
4451 show_tabs = 0;
4452 notebook_tab_set_visibility();
4454 break;
4455 case XT_TAB_NEXTSTYLE:
4456 if (tab_style == XT_TABS_NORMAL) {
4457 tab_style = XT_TABS_COMPACT;
4458 recolor_compact_tabs();
4460 else
4461 tab_style = XT_TABS_NORMAL;
4462 notebook_tab_set_visibility();
4463 break;
4464 case XT_TAB_UNDO_CLOSE:
4465 if (undo_count == 0) {
4466 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4467 __func__);
4468 goto done;
4469 } else {
4470 undo_count--;
4471 u = TAILQ_FIRST(&undos);
4472 create_new_tab(u->uri, u, 1, -1);
4474 TAILQ_REMOVE(&undos, u, entry);
4475 g_free(u->uri);
4476 /* u->history is freed in create_new_tab() */
4477 g_free(u);
4479 break;
4480 default:
4481 rv = XT_CB_PASSTHROUGH;
4482 goto done;
4485 done:
4486 if (args->s) {
4487 g_free(args->s);
4488 args->s = NULL;
4491 return (rv);
4495 resizetab(struct tab *t, struct karg *args)
4497 if (t == NULL || args == NULL) {
4498 show_oops(NULL, "resizetab invalid parameters");
4499 return (XT_CB_PASSTHROUGH);
4502 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4503 t->tab_id, args->i);
4505 setzoom_webkit(t, args->i);
4507 return (XT_CB_HANDLED);
4511 movetab(struct tab *t, struct karg *args)
4513 int n, dest;
4515 if (t == NULL || args == NULL) {
4516 show_oops(NULL, "movetab invalid parameters");
4517 return (XT_CB_PASSTHROUGH);
4520 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4521 t->tab_id, args->i);
4523 if (args->i >= XT_TAB_INVALID)
4524 return (XT_CB_PASSTHROUGH);
4526 if (TAILQ_EMPTY(&tabs))
4527 return (XT_CB_PASSTHROUGH);
4529 n = gtk_notebook_get_n_pages(notebook);
4530 dest = gtk_notebook_get_current_page(notebook);
4532 switch (args->i) {
4533 case XT_TAB_NEXT:
4534 if (args->precount < 0)
4535 dest = dest == n - 1 ? 0 : dest + 1;
4536 else
4537 dest = args->precount - 1;
4539 break;
4540 case XT_TAB_PREV:
4541 if (args->precount < 0)
4542 dest -= 1;
4543 else
4544 dest -= args->precount % n;
4546 if (dest < 0)
4547 dest += n;
4549 break;
4550 case XT_TAB_FIRST:
4551 dest = 0;
4552 break;
4553 case XT_TAB_LAST:
4554 dest = n - 1;
4555 break;
4556 default:
4557 return (XT_CB_PASSTHROUGH);
4560 if (dest < 0 || dest >= n)
4561 return (XT_CB_PASSTHROUGH);
4562 if (t->tab_id == dest) {
4563 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4564 return (XT_CB_HANDLED);
4567 set_current_tab(dest);
4569 return (XT_CB_HANDLED);
4572 int cmd_prefix = 0;
4575 command(struct tab *t, struct karg *args)
4577 char *s = NULL, *ss = NULL;
4578 GdkColor color;
4579 const gchar *uri;
4581 if (t == NULL || args == NULL) {
4582 show_oops(NULL, "command invalid parameters");
4583 return (XT_CB_PASSTHROUGH);
4586 switch (args->i) {
4587 case '/':
4588 s = "/";
4589 break;
4590 case '?':
4591 s = "?";
4592 break;
4593 case ':':
4594 if (cmd_prefix == 0)
4595 s = ":";
4596 else {
4597 ss = g_strdup_printf(":%d", cmd_prefix);
4598 s = ss;
4599 cmd_prefix = 0;
4601 break;
4602 case XT_CMD_OPEN:
4603 s = ":open ";
4604 break;
4605 case XT_CMD_TABNEW:
4606 s = ":tabnew ";
4607 break;
4608 case XT_CMD_OPEN_CURRENT:
4609 s = ":open ";
4610 /* FALL THROUGH */
4611 case XT_CMD_TABNEW_CURRENT:
4612 if (!s) /* FALL THROUGH? */
4613 s = ":tabnew ";
4614 if ((uri = get_uri(t)) != NULL) {
4615 ss = g_strdup_printf("%s%s", s, uri);
4616 s = ss;
4618 break;
4619 default:
4620 show_oops(t, "command: invalid opcode %d", args->i);
4621 return (XT_CB_PASSTHROUGH);
4624 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4626 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4627 gdk_color_parse(XT_COLOR_WHITE, &color);
4628 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4629 show_cmd(t);
4630 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4631 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4633 if (ss)
4634 g_free(ss);
4636 return (XT_CB_HANDLED);
4640 * Return a new string with a download row (in html)
4641 * appended. Old string is freed.
4643 char *
4644 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4647 WebKitDownloadStatus stat;
4648 char *status_html = NULL, *cmd_html = NULL, *new_html;
4649 gdouble progress;
4650 char cur_sz[FMT_SCALED_STRSIZE];
4651 char tot_sz[FMT_SCALED_STRSIZE];
4652 char *xtp_prefix;
4654 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4656 /* All actions wil take this form:
4657 * xxxt://class/seskey
4659 xtp_prefix = g_strdup_printf("%s%d/%s/",
4660 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4662 stat = webkit_download_get_status(dl->download);
4664 switch (stat) {
4665 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4666 status_html = g_strdup_printf("Finished");
4667 cmd_html = g_strdup_printf(
4668 "<a href='%s%d/%d'>Remove</a>",
4669 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4670 break;
4671 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4672 /* gather size info */
4673 progress = 100 * webkit_download_get_progress(dl->download);
4675 fmt_scaled(
4676 webkit_download_get_current_size(dl->download), cur_sz);
4677 fmt_scaled(
4678 webkit_download_get_total_size(dl->download), tot_sz);
4680 status_html = g_strdup_printf(
4681 "<div style='width: 100%%' align='center'>"
4682 "<div class='progress-outer'>"
4683 "<div class='progress-inner' style='width: %.2f%%'>"
4684 "</div></div></div>"
4685 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4686 progress, cur_sz, tot_sz, progress);
4688 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4689 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4691 break;
4692 /* LLL */
4693 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4694 status_html = g_strdup_printf("Cancelled");
4695 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4696 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4697 break;
4698 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4699 status_html = g_strdup_printf("Error!");
4700 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4701 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4702 break;
4703 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4704 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4705 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4706 status_html = g_strdup_printf("Starting");
4707 break;
4708 default:
4709 show_oops(t, "%s: unknown download status", __func__);
4712 new_html = g_strdup_printf(
4713 "%s\n<tr><td>%s</td><td>%s</td>"
4714 "<td style='text-align:center'>%s</td></tr>\n",
4715 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4716 status_html, cmd_html);
4717 g_free(html);
4719 if (status_html)
4720 g_free(status_html);
4722 if (cmd_html)
4723 g_free(cmd_html);
4725 g_free(xtp_prefix);
4727 return new_html;
4731 * update all download tabs apart from one. Pass NULL if
4732 * you want to update all.
4734 void
4735 update_download_tabs(struct tab *apart_from)
4737 struct tab *t;
4738 if (!updating_dl_tabs) {
4739 updating_dl_tabs = 1; /* stop infinite recursion */
4740 TAILQ_FOREACH(t, &tabs, entry)
4741 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4742 && (t != apart_from))
4743 xtp_page_dl(t, NULL);
4744 updating_dl_tabs = 0;
4749 * update all cookie tabs apart from one. Pass NULL if
4750 * you want to update all.
4752 void
4753 update_cookie_tabs(struct tab *apart_from)
4755 struct tab *t;
4756 if (!updating_cl_tabs) {
4757 updating_cl_tabs = 1; /* stop infinite recursion */
4758 TAILQ_FOREACH(t, &tabs, entry)
4759 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4760 && (t != apart_from))
4761 xtp_page_cl(t, NULL);
4762 updating_cl_tabs = 0;
4767 * update all history tabs apart from one. Pass NULL if
4768 * you want to update all.
4770 void
4771 update_history_tabs(struct tab *apart_from)
4773 struct tab *t;
4775 if (!updating_hl_tabs) {
4776 updating_hl_tabs = 1; /* stop infinite recursion */
4777 TAILQ_FOREACH(t, &tabs, entry)
4778 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4779 && (t != apart_from))
4780 xtp_page_hl(t, NULL);
4781 updating_hl_tabs = 0;
4785 /* cookie management XTP page */
4787 xtp_page_cl(struct tab *t, struct karg *args)
4789 char *body, *page, *tmp;
4790 int i = 1; /* all ids start 1 */
4791 GSList *sc, *pc, *pc_start;
4792 SoupCookie *c;
4793 char *type, *table_headers, *last_domain;
4795 DNPRINTF(XT_D_CMD, "%s", __func__);
4797 if (t == NULL) {
4798 show_oops(NULL, "%s invalid parameters", __func__);
4799 return (1);
4802 /* Generate a new session key */
4803 if (!updating_cl_tabs)
4804 generate_xtp_session_key(&cl_session_key);
4806 /* table headers */
4807 table_headers = g_strdup_printf("<table><tr>"
4808 "<th>Type</th>"
4809 "<th>Name</th>"
4810 "<th style='width:200px'>Value</th>"
4811 "<th>Path</th>"
4812 "<th>Expires</th>"
4813 "<th>Secure</th>"
4814 "<th>HTTP<br />only</th>"
4815 "<th style='width:40px'>Rm</th></tr>\n");
4817 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4818 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4819 pc_start = pc;
4821 body = NULL;
4822 last_domain = strdup("");
4823 for (; sc; sc = sc->next) {
4824 c = sc->data;
4826 if (strcmp(last_domain, c->domain) != 0) {
4827 /* new domain */
4828 free(last_domain);
4829 last_domain = strdup(c->domain);
4831 if (body != NULL) {
4832 tmp = body;
4833 body = g_strdup_printf("%s</table>"
4834 "<h2>%s</h2>%s\n",
4835 body, c->domain, table_headers);
4836 g_free(tmp);
4837 } else {
4838 /* first domain */
4839 body = g_strdup_printf("<h2>%s</h2>%s\n",
4840 c->domain, table_headers);
4844 type = "Session";
4845 for (pc = pc_start; pc; pc = pc->next)
4846 if (soup_cookie_equal(pc->data, c)) {
4847 type = "Session + Persistent";
4848 break;
4851 tmp = body;
4852 body = g_strdup_printf(
4853 "%s\n<tr>"
4854 "<td>%s</td>"
4855 "<td style='word-wrap:normal'>%s</td>"
4856 "<td>"
4857 " <textarea rows='4'>%s</textarea>"
4858 "</td>"
4859 "<td>%s</td>"
4860 "<td>%s</td>"
4861 "<td>%d</td>"
4862 "<td>%d</td>"
4863 "<td style='text-align:center'>"
4864 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4865 body,
4866 type,
4867 c->name,
4868 c->value,
4869 c->path,
4870 c->expires ?
4871 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4872 c->secure,
4873 c->http_only,
4875 XT_XTP_STR,
4876 XT_XTP_CL,
4877 cl_session_key,
4878 XT_XTP_CL_REMOVE,
4882 g_free(tmp);
4883 i++;
4886 soup_cookies_free(sc);
4887 soup_cookies_free(pc);
4889 /* small message if there are none */
4890 if (i == 1) {
4891 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4892 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4894 tmp = body;
4895 body = g_strdup_printf("%s</table>", body);
4896 g_free(tmp);
4898 page = get_html_page("Cookie Jar", body, "", TRUE);
4899 g_free(body);
4900 g_free(table_headers);
4901 g_free(last_domain);
4903 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4904 update_cookie_tabs(t);
4906 g_free(page);
4908 return (0);
4912 xtp_page_hl(struct tab *t, struct karg *args)
4914 char *body, *page, *tmp;
4915 struct history *h;
4916 int i = 1; /* all ids start 1 */
4918 DNPRINTF(XT_D_CMD, "%s", __func__);
4920 if (t == NULL) {
4921 show_oops(NULL, "%s invalid parameters", __func__);
4922 return (1);
4925 /* Generate a new session key */
4926 if (!updating_hl_tabs)
4927 generate_xtp_session_key(&hl_session_key);
4929 /* body */
4930 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4931 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4933 RB_FOREACH_REVERSE(h, history_list, &hl) {
4934 tmp = body;
4935 body = g_strdup_printf(
4936 "%s\n<tr>"
4937 "<td><a href='%s'>%s</a></td>"
4938 "<td>%s</td>"
4939 "<td style='text-align: center'>"
4940 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4941 body, h->uri, h->uri, h->title,
4942 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4943 XT_XTP_HL_REMOVE, i);
4945 g_free(tmp);
4946 i++;
4949 /* small message if there are none */
4950 if (i == 1) {
4951 tmp = body;
4952 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4953 "colspan='3'>No History</td></tr>\n", body);
4954 g_free(tmp);
4957 tmp = body;
4958 body = g_strdup_printf("%s</table>", body);
4959 g_free(tmp);
4961 page = get_html_page("History", body, "", TRUE);
4962 g_free(body);
4965 * update all history manager tabs as the xtp session
4966 * key has now changed. No need to update the current tab.
4967 * Already did that above.
4969 update_history_tabs(t);
4971 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4972 g_free(page);
4974 return (0);
4978 * Generate a web page detailing the status of any downloads
4981 xtp_page_dl(struct tab *t, struct karg *args)
4983 struct download *dl;
4984 char *body, *page, *tmp;
4985 char *ref;
4986 int n_dl = 1;
4988 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4990 if (t == NULL) {
4991 show_oops(NULL, "%s invalid parameters", __func__);
4992 return (1);
4996 * Generate a new session key for next page instance.
4997 * This only happens for the top level call to xtp_page_dl()
4998 * in which case updating_dl_tabs is 0.
5000 if (!updating_dl_tabs)
5001 generate_xtp_session_key(&dl_session_key);
5003 /* header - with refresh so as to update */
5004 if (refresh_interval >= 1)
5005 ref = g_strdup_printf(
5006 "<meta http-equiv='refresh' content='%u"
5007 ";url=%s%d/%s/%d' />\n",
5008 refresh_interval,
5009 XT_XTP_STR,
5010 XT_XTP_DL,
5011 dl_session_key,
5012 XT_XTP_DL_LIST);
5013 else
5014 ref = g_strdup("");
5016 body = g_strdup_printf("<div align='center'>"
5017 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5018 "</p><table><tr><th style='width: 60%%'>"
5019 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5020 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5022 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5023 body = xtp_page_dl_row(t, body, dl);
5024 n_dl++;
5027 /* message if no downloads in list */
5028 if (n_dl == 1) {
5029 tmp = body;
5030 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5031 " style='text-align: center'>"
5032 "No downloads</td></tr>\n", body);
5033 g_free(tmp);
5036 tmp = body;
5037 body = g_strdup_printf("%s</table></div>", body);
5038 g_free(tmp);
5040 page = get_html_page("Downloads", body, ref, 1);
5041 g_free(ref);
5042 g_free(body);
5045 * update all download manager tabs as the xtp session
5046 * key has now changed. No need to update the current tab.
5047 * Already did that above.
5049 update_download_tabs(t);
5051 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5052 g_free(page);
5054 return (0);
5058 search(struct tab *t, struct karg *args)
5060 gboolean d;
5062 if (t == NULL || args == NULL) {
5063 show_oops(NULL, "search invalid parameters");
5064 return (1);
5066 if (t->search_text == NULL) {
5067 if (global_search == NULL)
5068 return (XT_CB_PASSTHROUGH);
5069 else {
5070 t->search_text = g_strdup(global_search);
5071 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5072 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5076 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5077 t->tab_id, args->i, t->search_forward, t->search_text);
5079 switch (args->i) {
5080 case XT_SEARCH_NEXT:
5081 d = t->search_forward;
5082 break;
5083 case XT_SEARCH_PREV:
5084 d = !t->search_forward;
5085 break;
5086 default:
5087 return (XT_CB_PASSTHROUGH);
5090 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5092 return (XT_CB_HANDLED);
5095 struct settings_args {
5096 char **body;
5097 int i;
5100 void
5101 print_setting(struct settings *s, char *val, void *cb_args)
5103 char *tmp, *color;
5104 struct settings_args *sa = cb_args;
5106 if (sa == NULL)
5107 return;
5109 if (s->flags & XT_SF_RUNTIME)
5110 color = "#22cc22";
5111 else
5112 color = "#cccccc";
5114 tmp = *sa->body;
5115 *sa->body = g_strdup_printf(
5116 "%s\n<tr>"
5117 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5118 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5119 *sa->body,
5120 color,
5121 s->name,
5122 color,
5125 g_free(tmp);
5126 sa->i++;
5130 set_show(struct tab *t, struct karg *args)
5132 char *body, *page, *tmp;
5133 int i = 1;
5134 struct settings_args sa;
5136 bzero(&sa, sizeof sa);
5137 sa.body = &body;
5139 /* body */
5140 body = g_strdup_printf("<div align='center'><table><tr>"
5141 "<th align='left'>Setting</th>"
5142 "<th align='left'>Value</th></tr>\n");
5144 settings_walk(print_setting, &sa);
5145 i = sa.i;
5147 /* small message if there are none */
5148 if (i == 1) {
5149 tmp = body;
5150 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5151 "colspan='2'>No settings</td></tr>\n", body);
5152 g_free(tmp);
5155 tmp = body;
5156 body = g_strdup_printf("%s</table></div>", body);
5157 g_free(tmp);
5159 page = get_html_page("Settings", body, "", 0);
5161 g_free(body);
5163 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5165 g_free(page);
5167 return (XT_CB_PASSTHROUGH);
5171 set(struct tab *t, struct karg *args)
5173 char *p, *val;
5174 int i;
5176 if (args == NULL || args->s == NULL)
5177 return (set_show(t, args));
5179 /* strip spaces */
5180 p = g_strstrip(args->s);
5182 if (strlen(p) == 0)
5183 return (set_show(t, args));
5185 /* we got some sort of string */
5186 val = g_strrstr(p, "=");
5187 if (val) {
5188 *val++ = '\0';
5189 val = g_strchomp(val);
5190 p = g_strchomp(p);
5192 for (i = 0; i < LENGTH(rs); i++) {
5193 if (strcmp(rs[i].name, p))
5194 continue;
5196 if (rs[i].activate) {
5197 if (rs[i].activate(val))
5198 show_oops(t, "%s invalid value %s",
5199 p, val);
5200 else
5201 show_oops(t, ":set %s = %s", p, val);
5202 goto done;
5203 } else {
5204 show_oops(t, "not a runtime option: %s", p);
5205 goto done;
5208 show_oops(t, "unknown option: %s", p);
5209 } else {
5210 p = g_strchomp(p);
5212 for (i = 0; i < LENGTH(rs); i++) {
5213 if (strcmp(rs[i].name, p))
5214 continue;
5216 /* XXX this could use some cleanup */
5217 switch (rs[i].type) {
5218 case XT_S_INT:
5219 if (rs[i].ival)
5220 show_oops(t, "%s = %d",
5221 rs[i].name, *rs[i].ival);
5222 else if (rs[i].s && rs[i].s->get)
5223 show_oops(t, "%s = %s",
5224 rs[i].name,
5225 rs[i].s->get(&rs[i]));
5226 else if (rs[i].s && rs[i].s->get == NULL)
5227 show_oops(t, "%s = ...", rs[i].name);
5228 else
5229 show_oops(t, "%s = ", rs[i].name);
5230 break;
5231 case XT_S_FLOAT:
5232 if (rs[i].fval)
5233 show_oops(t, "%s = %f",
5234 rs[i].name, *rs[i].fval);
5235 else if (rs[i].s && rs[i].s->get)
5236 show_oops(t, "%s = %s",
5237 rs[i].name,
5238 rs[i].s->get(&rs[i]));
5239 else if (rs[i].s && rs[i].s->get == NULL)
5240 show_oops(t, "%s = ...", rs[i].name);
5241 else
5242 show_oops(t, "%s = ", rs[i].name);
5243 break;
5244 case XT_S_STR:
5245 if (rs[i].sval && *rs[i].sval)
5246 show_oops(t, "%s = %s",
5247 rs[i].name, *rs[i].sval);
5248 else if (rs[i].s && rs[i].s->get)
5249 show_oops(t, "%s = %s",
5250 rs[i].name,
5251 rs[i].s->get(&rs[i]));
5252 else if (rs[i].s && rs[i].s->get == NULL)
5253 show_oops(t, "%s = ...", rs[i].name);
5254 else
5255 show_oops(t, "%s = ", rs[i].name);
5256 break;
5257 default:
5258 show_oops(t, "unknown type for %s", rs[i].name);
5259 goto done;
5262 goto done;
5264 show_oops(t, "unknown option: %s", p);
5266 done:
5267 return (XT_CB_PASSTHROUGH);
5271 session_save(struct tab *t, char *filename)
5273 struct karg a;
5274 int rv = 1;
5275 struct session *s;
5277 if (strlen(filename) == 0)
5278 goto done;
5280 if (filename[0] == '.' || filename[0] == '/')
5281 goto done;
5283 a.s = filename;
5284 if (save_tabs(t, &a))
5285 goto done;
5286 strlcpy(named_session, filename, sizeof named_session);
5288 /* add the new session to the list of sessions */
5289 s = g_malloc(sizeof(struct session));
5290 s->name = g_strdup(filename);
5291 TAILQ_INSERT_TAIL(&sessions, s, entry);
5293 rv = 0;
5294 done:
5295 return (rv);
5299 session_open(struct tab *t, char *filename)
5301 struct karg a;
5302 int rv = 1;
5304 if (strlen(filename) == 0)
5305 goto done;
5307 if (filename[0] == '.' || filename[0] == '/')
5308 goto done;
5310 a.s = filename;
5311 a.i = XT_SES_CLOSETABS;
5312 if (open_tabs(t, &a))
5313 goto done;
5315 strlcpy(named_session, filename, sizeof named_session);
5317 rv = 0;
5318 done:
5319 return (rv);
5323 session_delete(struct tab *t, char *filename)
5325 char file[PATH_MAX];
5326 int rv = 1;
5327 struct session *s;
5329 if (strlen(filename) == 0)
5330 goto done;
5332 if (filename[0] == '.' || filename[0] == '/')
5333 goto done;
5335 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5336 if (unlink(file))
5337 goto done;
5339 if (!strcmp(filename, named_session))
5340 strlcpy(named_session, XT_SAVED_TABS_FILE,
5341 sizeof named_session);
5343 /* remove session from sessions list */
5344 TAILQ_FOREACH(s, &sessions, entry) {
5345 if (!strcmp(s->name, filename))
5346 break;
5348 TAILQ_REMOVE(&sessions, s, entry);
5349 g_free((gpointer) s->name);
5350 g_free(s);
5352 rv = 0;
5353 done:
5354 return (rv);
5358 session_cmd(struct tab *t, struct karg *args)
5360 char *filename = args->s;
5362 if (t == NULL)
5363 return (1);
5365 if (args->i & XT_SHOW)
5366 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5367 XT_SAVED_TABS_FILE : named_session);
5368 else if (args->i & XT_SAVE) {
5369 if (session_save(t, filename)) {
5370 show_oops(t, "Can't save session: %s",
5371 filename ? filename : "INVALID");
5372 goto done;
5374 } else if (args->i & XT_OPEN) {
5375 if (session_open(t, filename)) {
5376 show_oops(t, "Can't open session: %s",
5377 filename ? filename : "INVALID");
5378 goto done;
5380 } else if (args->i & XT_DELETE) {
5381 if (session_delete(t, filename)) {
5382 show_oops(t, "Can't delete session: %s",
5383 filename ? filename : "INVALID");
5384 goto done;
5387 done:
5388 return (XT_CB_PASSTHROUGH);
5392 * Make a hardcopy of the page
5395 print_page(struct tab *t, struct karg *args)
5397 WebKitWebFrame *frame;
5398 GtkPageSetup *ps;
5399 GtkPrintOperation *op;
5400 GtkPrintOperationAction action;
5401 GtkPrintOperationResult print_res;
5402 GError *g_err = NULL;
5403 int marg_l, marg_r, marg_t, marg_b;
5405 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5407 ps = gtk_page_setup_new();
5408 op = gtk_print_operation_new();
5409 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5410 frame = webkit_web_view_get_main_frame(t->wv);
5412 /* the default margins are too small, so we will bump them */
5413 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5414 XT_PRINT_EXTRA_MARGIN;
5415 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5416 XT_PRINT_EXTRA_MARGIN;
5417 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5418 XT_PRINT_EXTRA_MARGIN;
5419 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5420 XT_PRINT_EXTRA_MARGIN;
5422 /* set margins */
5423 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5424 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5425 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5426 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5428 gtk_print_operation_set_default_page_setup(op, ps);
5430 /* this appears to free 'op' and 'ps' */
5431 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5433 /* check it worked */
5434 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5435 show_oops(NULL, "can't print: %s", g_err->message);
5436 g_error_free (g_err);
5437 return (1);
5440 return (0);
5444 go_home(struct tab *t, struct karg *args)
5446 load_uri(t, home);
5447 return (0);
5451 restart(struct tab *t, struct karg *args)
5453 struct karg a;
5455 a.s = XT_RESTART_TABS_FILE;
5456 save_tabs(t, &a);
5457 execvp(start_argv[0], start_argv);
5458 /* NOTREACHED */
5460 return (0);
5463 #define CTRL GDK_CONTROL_MASK
5464 #define MOD1 GDK_MOD1_MASK
5465 #define SHFT GDK_SHIFT_MASK
5467 /* inherent to GTK not all keys will be caught at all times */
5468 /* XXX sort key bindings */
5469 struct key_binding {
5470 char *cmd;
5471 guint mask;
5472 guint use_in_entry;
5473 guint key;
5474 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5475 } keys[] = {
5476 { "cookiejar", MOD1, 0, GDK_j },
5477 { "downloadmgr", MOD1, 0, GDK_d },
5478 { "history", MOD1, 0, GDK_h },
5479 { "print", CTRL, 0, GDK_p },
5480 { "search", 0, 0, GDK_slash },
5481 { "searchb", 0, 0, GDK_question },
5482 { "statustoggle", CTRL, 0, GDK_n },
5483 { "command", 0, 0, GDK_colon },
5484 { "qa", CTRL, 0, GDK_q },
5485 { "restart", MOD1, 0, GDK_q },
5486 { "js toggle", CTRL, 0, GDK_j },
5487 { "cookie toggle", MOD1, 0, GDK_c },
5488 { "togglesrc", CTRL, 0, GDK_s },
5489 { "yankuri", 0, 0, GDK_y },
5490 { "pasteuricur", 0, 0, GDK_p },
5491 { "pasteurinew", 0, 0, GDK_P },
5492 { "toplevel toggle", 0, 0, GDK_F4 },
5493 { "help", 0, 0, GDK_F1 },
5494 { "run_script", MOD1, 0, GDK_r },
5496 /* search */
5497 { "searchnext", 0, 0, GDK_n },
5498 { "searchprevious", 0, 0, GDK_N },
5500 /* focus */
5501 { "focusaddress", 0, 0, GDK_F6 },
5502 { "focussearch", 0, 0, GDK_F7 },
5504 /* hinting */
5505 { "hinting", 0, 0, GDK_f },
5507 /* custom stylesheet */
5508 { "userstyle", 0, 0, GDK_i },
5510 /* navigation */
5511 { "goback", 0, 0, GDK_BackSpace },
5512 { "goback", MOD1, 0, GDK_Left },
5513 { "goforward", SHFT, 0, GDK_BackSpace },
5514 { "goforward", MOD1, 0, GDK_Right },
5515 { "reload", 0, 0, GDK_F5 },
5516 { "reload", CTRL, 0, GDK_r },
5517 { "reload", CTRL, 0, GDK_l },
5518 { "favorites", MOD1, 1, GDK_f },
5520 /* vertical movement */
5521 { "scrolldown", 0, 0, GDK_j },
5522 { "scrolldown", 0, 0, GDK_Down },
5523 { "scrollup", 0, 0, GDK_Up },
5524 { "scrollup", 0, 0, GDK_k },
5525 { "scrollbottom", 0, 0, GDK_G },
5526 { "scrollbottom", 0, 0, GDK_End },
5527 { "scrolltop", 0, 0, GDK_Home },
5528 { "scrollpagedown", 0, 0, GDK_space },
5529 { "scrollpagedown", CTRL, 0, GDK_f },
5530 { "scrollhalfdown", CTRL, 0, GDK_d },
5531 { "scrollpagedown", 0, 0, GDK_Page_Down },
5532 { "scrollpageup", 0, 0, GDK_Page_Up },
5533 { "scrollpageup", CTRL, 0, GDK_b },
5534 { "scrollhalfup", CTRL, 0, GDK_u },
5535 /* horizontal movement */
5536 { "scrollright", 0, 0, GDK_l },
5537 { "scrollright", 0, 0, GDK_Right },
5538 { "scrollleft", 0, 0, GDK_Left },
5539 { "scrollleft", 0, 0, GDK_h },
5540 { "scrollfarright", 0, 0, GDK_dollar },
5541 { "scrollfarleft", 0, 0, GDK_0 },
5543 /* tabs */
5544 { "tabnew", CTRL, 0, GDK_t },
5545 { "999tabnew", CTRL, 0, GDK_T },
5546 { "tabclose", CTRL, 1, GDK_w },
5547 { "tabundoclose", 0, 0, GDK_U },
5548 { "tabnext 1", CTRL, 0, GDK_1 },
5549 { "tabnext 2", CTRL, 0, GDK_2 },
5550 { "tabnext 3", CTRL, 0, GDK_3 },
5551 { "tabnext 4", CTRL, 0, GDK_4 },
5552 { "tabnext 5", CTRL, 0, GDK_5 },
5553 { "tabnext 6", CTRL, 0, GDK_6 },
5554 { "tabnext 7", CTRL, 0, GDK_7 },
5555 { "tabnext 8", CTRL, 0, GDK_8 },
5556 { "tabnext 9", CTRL, 0, GDK_9 },
5557 { "tabfirst", CTRL, 0, GDK_less },
5558 { "tablast", CTRL, 0, GDK_greater },
5559 { "tabprevious", CTRL, 0, GDK_Left },
5560 { "tabnext", CTRL, 0, GDK_Right },
5561 { "focusout", CTRL, 0, GDK_minus },
5562 { "focusin", CTRL, 0, GDK_plus },
5563 { "focusin", CTRL, 0, GDK_equal },
5564 { "focusreset", CTRL, 0, GDK_0 },
5566 /* command aliases (handy when -S flag is used) */
5567 { "promptopen", 0, 0, GDK_F9 },
5568 { "promptopencurrent", 0, 0, GDK_F10 },
5569 { "prompttabnew", 0, 0, GDK_F11 },
5570 { "prompttabnewcurrent",0, 0, GDK_F12 },
5572 TAILQ_HEAD(keybinding_list, key_binding);
5574 void
5575 walk_kb(struct settings *s,
5576 void (*cb)(struct settings *, char *, void *), void *cb_args)
5578 struct key_binding *k;
5579 char str[1024];
5581 if (s == NULL || cb == NULL) {
5582 show_oops(NULL, "walk_kb invalid parameters");
5583 return;
5586 TAILQ_FOREACH(k, &kbl, entry) {
5587 if (k->cmd == NULL)
5588 continue;
5589 str[0] = '\0';
5591 /* sanity */
5592 if (gdk_keyval_name(k->key) == NULL)
5593 continue;
5595 strlcat(str, k->cmd, sizeof str);
5596 strlcat(str, ",", sizeof str);
5598 if (k->mask & GDK_SHIFT_MASK)
5599 strlcat(str, "S-", sizeof str);
5600 if (k->mask & GDK_CONTROL_MASK)
5601 strlcat(str, "C-", sizeof str);
5602 if (k->mask & GDK_MOD1_MASK)
5603 strlcat(str, "M1-", sizeof str);
5604 if (k->mask & GDK_MOD2_MASK)
5605 strlcat(str, "M2-", sizeof str);
5606 if (k->mask & GDK_MOD3_MASK)
5607 strlcat(str, "M3-", sizeof str);
5608 if (k->mask & GDK_MOD4_MASK)
5609 strlcat(str, "M4-", sizeof str);
5610 if (k->mask & GDK_MOD5_MASK)
5611 strlcat(str, "M5-", sizeof str);
5613 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5614 cb(s, str, cb_args);
5618 void
5619 init_keybindings(void)
5621 int i;
5622 struct key_binding *k;
5624 for (i = 0; i < LENGTH(keys); i++) {
5625 k = g_malloc0(sizeof *k);
5626 k->cmd = keys[i].cmd;
5627 k->mask = keys[i].mask;
5628 k->use_in_entry = keys[i].use_in_entry;
5629 k->key = keys[i].key;
5630 TAILQ_INSERT_HEAD(&kbl, k, entry);
5632 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5633 k->cmd ? k->cmd : "unnamed key");
5637 void
5638 keybinding_clearall(void)
5640 struct key_binding *k, *next;
5642 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5643 next = TAILQ_NEXT(k, entry);
5644 if (k->cmd == NULL)
5645 continue;
5647 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5648 k->cmd ? k->cmd : "unnamed key");
5649 TAILQ_REMOVE(&kbl, k, entry);
5650 g_free(k);
5655 keybinding_add(char *cmd, char *key, int use_in_entry)
5657 struct key_binding *k;
5658 guint keyval, mask = 0;
5659 int i;
5661 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5663 /* Keys which are to be used in entry have been prefixed with an
5664 * exclamation mark. */
5665 if (use_in_entry)
5666 key++;
5668 /* find modifier keys */
5669 if (strstr(key, "S-"))
5670 mask |= GDK_SHIFT_MASK;
5671 if (strstr(key, "C-"))
5672 mask |= GDK_CONTROL_MASK;
5673 if (strstr(key, "M1-"))
5674 mask |= GDK_MOD1_MASK;
5675 if (strstr(key, "M2-"))
5676 mask |= GDK_MOD2_MASK;
5677 if (strstr(key, "M3-"))
5678 mask |= GDK_MOD3_MASK;
5679 if (strstr(key, "M4-"))
5680 mask |= GDK_MOD4_MASK;
5681 if (strstr(key, "M5-"))
5682 mask |= GDK_MOD5_MASK;
5684 /* find keyname */
5685 for (i = strlen(key) - 1; i > 0; i--)
5686 if (key[i] == '-')
5687 key = &key[i + 1];
5689 /* validate keyname */
5690 keyval = gdk_keyval_from_name(key);
5691 if (keyval == GDK_VoidSymbol) {
5692 warnx("invalid keybinding name %s", key);
5693 return (1);
5695 /* must run this test too, gtk+ doesn't handle 10 for example */
5696 if (gdk_keyval_name(keyval) == NULL) {
5697 warnx("invalid keybinding name %s", key);
5698 return (1);
5701 /* Remove eventual dupes. */
5702 TAILQ_FOREACH(k, &kbl, entry)
5703 if (k->key == keyval && k->mask == mask) {
5704 TAILQ_REMOVE(&kbl, k, entry);
5705 g_free(k);
5706 break;
5709 /* add keyname */
5710 k = g_malloc0(sizeof *k);
5711 k->cmd = g_strdup(cmd);
5712 k->mask = mask;
5713 k->use_in_entry = use_in_entry;
5714 k->key = keyval;
5716 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5717 k->cmd,
5718 k->mask,
5719 k->use_in_entry,
5720 k->key);
5721 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5722 k->cmd, gdk_keyval_name(keyval));
5724 TAILQ_INSERT_HEAD(&kbl, k, entry);
5726 return (0);
5730 add_kb(struct settings *s, char *entry)
5732 char *kb, *key;
5734 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5736 /* clearall is special */
5737 if (!strcmp(entry, "clearall")) {
5738 keybinding_clearall();
5739 return (0);
5742 kb = strstr(entry, ",");
5743 if (kb == NULL)
5744 return (1);
5745 *kb = '\0';
5746 key = kb + 1;
5748 return (keybinding_add(entry, key, key[0] == '!'));
5751 struct cmd {
5752 char *cmd;
5753 int level;
5754 int (*func)(struct tab *, struct karg *);
5755 int arg;
5756 int type;
5757 } cmds[] = {
5758 { "command", 0, command, ':', 0 },
5759 { "search", 0, command, '/', 0 },
5760 { "searchb", 0, command, '?', 0 },
5761 { "togglesrc", 0, toggle_src, 0, 0 },
5763 /* yanking and pasting */
5764 { "yankuri", 0, yank_uri, 0, 0 },
5765 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5766 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5767 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5769 /* search */
5770 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5771 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5773 /* focus */
5774 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5775 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5777 /* hinting */
5778 { "hinting", 0, hint, 0, 0 },
5780 /* custom stylesheet */
5781 { "userstyle", 0, userstyle, 0, 0 },
5783 /* navigation */
5784 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5785 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5786 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5788 /* vertical movement */
5789 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5790 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5791 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5792 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5793 { "1", 0, move, XT_MOVE_TOP, 0 },
5794 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5795 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5796 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5797 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5798 /* horizontal movement */
5799 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5800 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5801 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5802 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5804 { "favorites", 0, xtp_page_fl, 0, 0 },
5805 { "fav", 0, xtp_page_fl, 0, 0 },
5806 { "favadd", 0, add_favorite, 0, 0 },
5808 { "qall", 0, quit, 0, 0 },
5809 { "quitall", 0, quit, 0, 0 },
5810 { "w", 0, save_tabs, 0, 0 },
5811 { "wq", 0, save_tabs_and_quit, 0, 0 },
5812 { "help", 0, help, 0, 0 },
5813 { "about", 0, about, 0, 0 },
5814 { "stats", 0, stats, 0, 0 },
5815 { "version", 0, about, 0, 0 },
5817 /* js command */
5818 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5819 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5820 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5821 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5822 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5823 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5824 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5825 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5826 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5827 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5828 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5830 /* cookie command */
5831 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5832 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5833 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5834 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5835 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5836 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5837 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5838 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5839 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5840 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5841 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5843 /* toplevel (domain) command */
5844 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5845 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5847 /* cookie jar */
5848 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5850 /* cert command */
5851 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5852 { "save", 1, cert_cmd, XT_SAVE, 0 },
5853 { "show", 1, cert_cmd, XT_SHOW, 0 },
5855 { "ca", 0, ca_cmd, 0, 0 },
5856 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5857 { "dl", 0, xtp_page_dl, 0, 0 },
5858 { "h", 0, xtp_page_hl, 0, 0 },
5859 { "history", 0, xtp_page_hl, 0, 0 },
5860 { "home", 0, go_home, 0, 0 },
5861 { "restart", 0, restart, 0, 0 },
5862 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5863 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5864 { "statustoggle", 0, statustoggle, 0, 0 },
5865 { "run_script", 0, run_page_script, 0, XT_USERARG },
5867 { "print", 0, print_page, 0, 0 },
5869 /* tabs */
5870 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5871 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5872 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5873 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5874 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5875 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5876 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5877 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5878 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5879 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5880 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5881 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5882 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5883 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5884 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5885 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5886 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5887 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5888 { "buffers", 0, buffers, 0, 0 },
5889 { "ls", 0, buffers, 0, 0 },
5890 { "tabs", 0, buffers, 0, 0 },
5892 /* command aliases (handy when -S flag is used) */
5893 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5894 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5895 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5896 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5898 /* settings */
5899 { "set", 0, set, 0, XT_SETARG },
5901 { "fullscreen", 0, fullscreen, 0, 0 },
5902 { "f", 0, fullscreen, 0, 0 },
5904 /* sessions */
5905 { "session", 0, session_cmd, XT_SHOW, 0 },
5906 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5907 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5908 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5909 { "show", 1, session_cmd, XT_SHOW, 0 },
5912 struct {
5913 int index;
5914 int len;
5915 gchar *list[256];
5916 } cmd_status = {-1, 0};
5918 gboolean
5919 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5922 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5923 btn_down = 0;
5925 return (FALSE);
5928 gboolean
5929 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5931 struct karg a;
5933 hide_oops(t);
5934 hide_buffers(t);
5936 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5937 btn_down = 1;
5938 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5939 /* go backward */
5940 a.i = XT_NAV_BACK;
5941 navaction(t, &a);
5943 return (TRUE);
5944 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5945 /* go forward */
5946 a.i = XT_NAV_FORWARD;
5947 navaction(t, &a);
5949 return (TRUE);
5952 return (FALSE);
5955 gboolean
5956 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5958 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5960 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5961 delete_tab(t);
5963 return (FALSE);
5967 * cancel, remove, etc. downloads
5969 void
5970 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5972 struct download find, *d = NULL;
5974 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5976 /* some commands require a valid download id */
5977 if (cmd != XT_XTP_DL_LIST) {
5978 /* lookup download in question */
5979 find.id = id;
5980 d = RB_FIND(download_list, &downloads, &find);
5982 if (d == NULL) {
5983 show_oops(t, "%s: no such download", __func__);
5984 return;
5988 /* decide what to do */
5989 switch (cmd) {
5990 case XT_XTP_DL_CANCEL:
5991 webkit_download_cancel(d->download);
5992 break;
5993 case XT_XTP_DL_REMOVE:
5994 webkit_download_cancel(d->download); /* just incase */
5995 g_object_unref(d->download);
5996 RB_REMOVE(download_list, &downloads, d);
5997 break;
5998 case XT_XTP_DL_LIST:
5999 /* Nothing */
6000 break;
6001 default:
6002 show_oops(t, "%s: unknown command", __func__);
6003 break;
6005 xtp_page_dl(t, NULL);
6009 * Actions on history, only does one thing for now, but
6010 * we provide the function for future actions
6012 void
6013 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6015 struct history *h, *next;
6016 int i = 1;
6018 switch (cmd) {
6019 case XT_XTP_HL_REMOVE:
6020 /* walk backwards, as listed in reverse */
6021 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6022 next = RB_PREV(history_list, &hl, h);
6023 if (id == i) {
6024 RB_REMOVE(history_list, &hl, h);
6025 g_free((gpointer) h->title);
6026 g_free((gpointer) h->uri);
6027 g_free(h);
6028 break;
6030 i++;
6032 break;
6033 case XT_XTP_HL_LIST:
6034 /* Nothing - just xtp_page_hl() below */
6035 break;
6036 default:
6037 show_oops(t, "%s: unknown command", __func__);
6038 break;
6041 xtp_page_hl(t, NULL);
6044 /* remove a favorite */
6045 void
6046 remove_favorite(struct tab *t, int index)
6048 char file[PATH_MAX], *title, *uri = NULL;
6049 char *new_favs, *tmp;
6050 FILE *f;
6051 int i;
6052 size_t len, lineno;
6054 /* open favorites */
6055 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6057 if ((f = fopen(file, "r")) == NULL) {
6058 show_oops(t, "%s: can't open favorites: %s",
6059 __func__, strerror(errno));
6060 return;
6063 /* build a string which will become the new favroites file */
6064 new_favs = g_strdup("");
6066 for (i = 1;;) {
6067 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6068 if (feof(f) || ferror(f))
6069 break;
6070 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6071 if (len == 0) {
6072 free(title);
6073 title = NULL;
6074 continue;
6077 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6078 if (feof(f) || ferror(f)) {
6079 show_oops(t, "%s: can't parse favorites %s",
6080 __func__, strerror(errno));
6081 goto clean;
6085 /* as long as this isn't the one we are deleting add to file */
6086 if (i != index) {
6087 tmp = new_favs;
6088 new_favs = g_strdup_printf("%s%s\n%s\n",
6089 new_favs, title, uri);
6090 g_free(tmp);
6093 free(uri);
6094 uri = NULL;
6095 free(title);
6096 title = NULL;
6097 i++;
6099 fclose(f);
6101 /* write back new favorites file */
6102 if ((f = fopen(file, "w")) == NULL) {
6103 show_oops(t, "%s: can't open favorites: %s",
6104 __func__, strerror(errno));
6105 goto clean;
6108 fwrite(new_favs, strlen(new_favs), 1, f);
6109 fclose(f);
6111 clean:
6112 if (uri)
6113 free(uri);
6114 if (title)
6115 free(title);
6117 g_free(new_favs);
6120 void
6121 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6123 switch (cmd) {
6124 case XT_XTP_FL_LIST:
6125 /* nothing, just the below call to xtp_page_fl() */
6126 break;
6127 case XT_XTP_FL_REMOVE:
6128 remove_favorite(t, arg);
6129 break;
6130 default:
6131 show_oops(t, "%s: invalid favorites command", __func__);
6132 break;
6135 xtp_page_fl(t, NULL);
6138 void
6139 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6141 switch (cmd) {
6142 case XT_XTP_CL_LIST:
6143 /* nothing, just xtp_page_cl() */
6144 break;
6145 case XT_XTP_CL_REMOVE:
6146 remove_cookie(arg);
6147 break;
6148 default:
6149 show_oops(t, "%s: unknown cookie xtp command", __func__);
6150 break;
6153 xtp_page_cl(t, NULL);
6156 /* link an XTP class to it's session key and handler function */
6157 struct xtp_despatch {
6158 uint8_t xtp_class;
6159 char **session_key;
6160 void (*handle_func)(struct tab *, uint8_t, int);
6163 struct xtp_despatch xtp_despatches[] = {
6164 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6165 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6166 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6167 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6168 { XT_XTP_INVALID, NULL, NULL }
6172 * is the url xtp protocol? (xxxt://)
6173 * if so, parse and despatch correct bahvior
6176 parse_xtp_url(struct tab *t, const char *url)
6178 char *dup = NULL, *p, *last;
6179 uint8_t n_tokens = 0;
6180 char *tokens[4] = {NULL, NULL, NULL, ""};
6181 struct xtp_despatch *dsp, *dsp_match = NULL;
6182 uint8_t req_class;
6183 int ret = FALSE;
6186 * tokens array meaning:
6187 * tokens[0] = class
6188 * tokens[1] = session key
6189 * tokens[2] = action
6190 * tokens[3] = optional argument
6193 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6195 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6196 goto clean;
6198 dup = g_strdup(url + strlen(XT_XTP_STR));
6200 /* split out the url */
6201 for ((p = strtok_r(dup, "/", &last)); p;
6202 (p = strtok_r(NULL, "/", &last))) {
6203 if (n_tokens < 4)
6204 tokens[n_tokens++] = p;
6207 /* should be atleast three fields 'class/seskey/command/arg' */
6208 if (n_tokens < 3)
6209 goto clean;
6211 dsp = xtp_despatches;
6212 req_class = atoi(tokens[0]);
6213 while (dsp->xtp_class) {
6214 if (dsp->xtp_class == req_class) {
6215 dsp_match = dsp;
6216 break;
6218 dsp++;
6221 /* did we find one atall? */
6222 if (dsp_match == NULL) {
6223 show_oops(t, "%s: no matching xtp despatch found", __func__);
6224 goto clean;
6227 /* check session key and call despatch function */
6228 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6229 ret = TRUE; /* all is well, this was a valid xtp request */
6230 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6233 clean:
6234 if (dup)
6235 g_free(dup);
6237 return (ret);
6242 void
6243 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6245 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6247 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6249 if (t == NULL) {
6250 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6251 return;
6254 if (uri == NULL) {
6255 show_oops(t, "activate_uri_entry_cb no uri");
6256 return;
6259 uri += strspn(uri, "\t ");
6261 /* if xxxt:// treat specially */
6262 if (parse_xtp_url(t, uri))
6263 return;
6265 /* otherwise continue to load page normally */
6266 load_uri(t, (gchar *)uri);
6267 focus_webview(t);
6270 void
6271 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6273 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6274 char *newuri = NULL;
6275 gchar *enc_search;
6277 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6279 if (t == NULL) {
6280 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6281 return;
6284 if (search_string == NULL) {
6285 show_oops(t, "no search_string");
6286 return;
6289 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6291 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6292 newuri = g_strdup_printf(search_string, enc_search);
6293 g_free(enc_search);
6295 marks_clear(t);
6296 webkit_web_view_load_uri(t->wv, newuri);
6297 focus_webview(t);
6299 if (newuri)
6300 g_free(newuri);
6303 void
6304 check_and_set_cookie(const gchar *uri, struct tab *t)
6306 struct domain *d = NULL;
6307 int es = 0;
6309 if (uri == NULL || t == NULL)
6310 return;
6312 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6313 es = 0;
6314 else
6315 es = 1;
6317 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6318 es ? "enable" : "disable", uri);
6320 g_object_set(G_OBJECT(t->settings),
6321 "enable-html5-local-storage", es, (char *)NULL);
6322 webkit_web_view_set_settings(t->wv, t->settings);
6325 void
6326 check_and_set_js(const gchar *uri, struct tab *t)
6328 struct domain *d = NULL;
6329 int es = 0;
6331 if (uri == NULL || t == NULL)
6332 return;
6334 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6335 es = 0;
6336 else
6337 es = 1;
6339 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6340 es ? "enable" : "disable", uri);
6342 g_object_set(G_OBJECT(t->settings),
6343 "enable-scripts", es, (char *)NULL);
6344 g_object_set(G_OBJECT(t->settings),
6345 "javascript-can-open-windows-automatically", es, (char *)NULL);
6346 webkit_web_view_set_settings(t->wv, t->settings);
6348 button_set_stockid(t->js_toggle,
6349 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6352 gboolean
6353 color_address_bar(gpointer p)
6355 GdkColor color;
6356 struct tab *tt, *t = p;
6357 gchar *col_str = XT_COLOR_YELLOW;
6359 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6361 /* make sure t still exists */
6362 if (t == NULL)
6363 goto done;
6364 TAILQ_FOREACH(tt, &tabs, entry)
6365 if (t == tt)
6366 break;
6367 if (t != tt)
6368 goto done;
6370 switch (load_compare_cert(t, NULL)) {
6371 case CERT_LOCAL:
6372 col_str = XT_COLOR_BLUE;
6373 break;
6374 case CERT_TRUSTED:
6375 col_str = XT_COLOR_GREEN;
6376 break;
6377 case CERT_UNTRUSTED:
6378 col_str = XT_COLOR_YELLOW;
6379 break;
6380 case CERT_BAD:
6381 col_str = XT_COLOR_RED;
6382 break;
6385 gdk_color_parse(col_str, &color);
6386 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6388 if (!strcmp(col_str, XT_COLOR_WHITE))
6389 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6390 else
6391 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6393 col_str = NULL;
6394 done:
6395 return (FALSE);
6398 void
6399 show_ca_status(struct tab *t, const char *uri)
6401 GdkColor color;
6402 gchar *col_str = XT_COLOR_WHITE;
6404 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6405 ssl_strict_certs, ssl_ca_file, uri);
6407 if (t == NULL)
6408 return;
6410 if (uri == NULL)
6411 goto done;
6412 if (ssl_ca_file == NULL) {
6413 if (g_str_has_prefix(uri, "http://"))
6414 goto done;
6415 if (g_str_has_prefix(uri, "https://")) {
6416 col_str = XT_COLOR_RED;
6417 goto done;
6419 return;
6421 if (g_str_has_prefix(uri, "http://") ||
6422 !g_str_has_prefix(uri, "https://"))
6423 goto done;
6425 color_address_bar(t);
6427 return;
6429 done:
6430 if (col_str) {
6431 gdk_color_parse(col_str, &color);
6432 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6434 if (!strcmp(col_str, XT_COLOR_WHITE))
6435 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6436 else
6437 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6441 void
6442 free_favicon(struct tab *t)
6444 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6445 __func__, t->icon_download, t->icon_request);
6447 if (t->icon_request)
6448 g_object_unref(t->icon_request);
6449 if (t->icon_dest_uri)
6450 g_free(t->icon_dest_uri);
6452 t->icon_request = NULL;
6453 t->icon_dest_uri = NULL;
6456 void
6457 xt_icon_from_name(struct tab *t, gchar *name)
6459 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6460 GTK_ENTRY_ICON_PRIMARY, "text-html");
6461 if (show_url == 0)
6462 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6463 GTK_ENTRY_ICON_PRIMARY, "text-html");
6464 else
6465 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6466 GTK_ENTRY_ICON_PRIMARY, NULL);
6469 void
6470 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6472 GdkPixbuf *pb_scaled;
6474 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6475 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6476 GDK_INTERP_BILINEAR);
6477 else
6478 pb_scaled = pb;
6480 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6481 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6482 if (show_url == 0)
6483 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6484 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6485 else
6486 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6487 GTK_ENTRY_ICON_PRIMARY, NULL);
6489 if (pb_scaled != pb)
6490 g_object_unref(pb_scaled);
6493 void
6494 xt_icon_from_file(struct tab *t, char *file)
6496 GdkPixbuf *pb;
6498 if (g_str_has_prefix(file, "file://"))
6499 file += strlen("file://");
6501 pb = gdk_pixbuf_new_from_file(file, NULL);
6502 if (pb) {
6503 xt_icon_from_pixbuf(t, pb);
6504 g_object_unref(pb);
6505 } else
6506 xt_icon_from_name(t, "text-html");
6509 gboolean
6510 is_valid_icon(char *file)
6512 gboolean valid = 0;
6513 const char *mime_type;
6514 GFileInfo *fi;
6515 GFile *gf;
6517 gf = g_file_new_for_path(file);
6518 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6519 NULL, NULL);
6520 mime_type = g_file_info_get_content_type(fi);
6521 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6522 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6523 g_strcmp0(mime_type, "image/png") == 0 ||
6524 g_strcmp0(mime_type, "image/gif") == 0 ||
6525 g_strcmp0(mime_type, "application/octet-stream") == 0;
6526 g_object_unref(fi);
6527 g_object_unref(gf);
6529 return (valid);
6532 void
6533 set_favicon_from_file(struct tab *t, char *file)
6535 struct stat sb;
6537 if (t == NULL || file == NULL)
6538 return;
6540 if (g_str_has_prefix(file, "file://"))
6541 file += strlen("file://");
6542 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6544 if (!stat(file, &sb)) {
6545 if (sb.st_size == 0 || !is_valid_icon(file)) {
6546 /* corrupt icon so trash it */
6547 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6548 __func__, file);
6549 unlink(file);
6550 /* no need to set icon to default here */
6551 return;
6554 xt_icon_from_file(t, file);
6557 void
6558 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6559 WebKitWebView *wv)
6561 WebKitDownloadStatus status = webkit_download_get_status(download);
6562 struct tab *tt = NULL, *t = NULL;
6565 * find the webview instead of passing in the tab as it could have been
6566 * deleted from underneath us.
6568 TAILQ_FOREACH(tt, &tabs, entry) {
6569 if (tt->wv == wv) {
6570 t = tt;
6571 break;
6574 if (t == NULL)
6575 return;
6577 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6578 __func__, t->tab_id, status);
6580 switch (status) {
6581 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6582 /* -1 */
6583 t->icon_download = NULL;
6584 free_favicon(t);
6585 break;
6586 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6587 /* 0 */
6588 break;
6589 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6590 /* 1 */
6591 break;
6592 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6593 /* 2 */
6594 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6595 __func__, t->tab_id);
6596 t->icon_download = NULL;
6597 free_favicon(t);
6598 break;
6599 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6600 /* 3 */
6602 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6603 __func__, t->icon_dest_uri);
6604 set_favicon_from_file(t, t->icon_dest_uri);
6605 /* these will be freed post callback */
6606 t->icon_request = NULL;
6607 t->icon_download = NULL;
6608 break;
6609 default:
6610 break;
6614 void
6615 abort_favicon_download(struct tab *t)
6617 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6619 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6620 if (t->icon_download) {
6621 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6622 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6623 webkit_download_cancel(t->icon_download);
6624 t->icon_download = NULL;
6625 } else
6626 free_favicon(t);
6627 #endif
6629 xt_icon_from_name(t, "text-html");
6632 void
6633 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6635 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6637 if (uri == NULL || t == NULL)
6638 return;
6640 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6641 /* take icon from WebKitIconDatabase */
6642 GdkPixbuf *pb;
6644 pb = webkit_web_view_get_icon_pixbuf(wv);
6645 if (pb) {
6646 xt_icon_from_pixbuf(t, pb);
6647 g_object_unref(pb);
6648 } else
6649 xt_icon_from_name(t, "text-html");
6650 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6651 /* download icon to cache dir */
6652 gchar *name_hash, file[PATH_MAX];
6653 struct stat sb;
6655 if (t->icon_request) {
6656 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6657 return;
6660 /* check to see if we got the icon in cache */
6661 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6662 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6663 g_free(name_hash);
6665 if (!stat(file, &sb)) {
6666 if (sb.st_size > 0) {
6667 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6668 __func__, file);
6669 set_favicon_from_file(t, file);
6670 return;
6673 /* corrupt icon so trash it */
6674 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6675 __func__, file);
6676 unlink(file);
6679 /* create download for icon */
6680 t->icon_request = webkit_network_request_new(uri);
6681 if (t->icon_request == NULL) {
6682 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6683 __func__, uri);
6684 return;
6687 t->icon_download = webkit_download_new(t->icon_request);
6688 if (t->icon_download == NULL)
6689 return;
6691 /* we have to free icon_dest_uri later */
6692 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6693 webkit_download_set_destination_uri(t->icon_download,
6694 t->icon_dest_uri);
6696 if (webkit_download_get_status(t->icon_download) ==
6697 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6698 g_object_unref(t->icon_request);
6699 g_free(t->icon_dest_uri);
6700 t->icon_request = NULL;
6701 t->icon_dest_uri = NULL;
6702 return;
6705 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6706 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6708 webkit_download_start(t->icon_download);
6709 #endif
6712 void
6713 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6715 const gchar *uri = NULL, *title = NULL;
6716 struct history *h, find;
6717 struct karg a;
6718 GdkColor color;
6720 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6721 webkit_web_view_get_load_status(wview),
6722 get_uri(t) ? get_uri(t) : "NOTHING");
6724 if (t == NULL) {
6725 show_oops(NULL, "notify_load_status_cb invalid parameters");
6726 return;
6729 switch (webkit_web_view_get_load_status(wview)) {
6730 case WEBKIT_LOAD_PROVISIONAL:
6731 /* 0 */
6732 abort_favicon_download(t);
6733 #if GTK_CHECK_VERSION(2, 20, 0)
6734 gtk_widget_show(t->spinner);
6735 gtk_spinner_start(GTK_SPINNER(t->spinner));
6736 #endif
6737 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6739 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6741 /* assume we are a new address */
6742 gdk_color_parse("white", &color);
6743 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6744 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6746 /* take focus if we are visible */
6747 focus_webview(t);
6748 t->focus_wv = 1;
6750 break;
6752 case WEBKIT_LOAD_COMMITTED:
6753 /* 1 */
6754 uri = get_uri(t);
6755 if (uri == NULL)
6756 return;
6757 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6759 if (t->status) {
6760 g_free(t->status);
6761 t->status = NULL;
6763 set_status(t, (char *)uri, XT_STATUS_LOADING);
6765 /* check if js white listing is enabled */
6766 if (enable_cookie_whitelist)
6767 check_and_set_cookie(uri, t);
6768 if (enable_js_whitelist)
6769 check_and_set_js(uri, t);
6771 if (t->styled)
6772 apply_style(t);
6775 /* we know enough to autosave the session */
6776 if (session_autosave) {
6777 a.s = NULL;
6778 save_tabs(t, &a);
6781 show_ca_status(t, uri);
6782 break;
6784 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6785 /* 3 */
6786 break;
6788 case WEBKIT_LOAD_FINISHED:
6789 /* 2 */
6790 uri = get_uri(t);
6791 if (uri == NULL)
6792 return;
6794 if (!strncmp(uri, "http://", strlen("http://")) ||
6795 !strncmp(uri, "https://", strlen("https://")) ||
6796 !strncmp(uri, "file://", strlen("file://"))) {
6797 find.uri = uri;
6798 h = RB_FIND(history_list, &hl, &find);
6799 if (!h) {
6800 title = get_title(t, FALSE);
6801 h = g_malloc(sizeof *h);
6802 h->uri = g_strdup(uri);
6803 h->title = g_strdup(title);
6804 RB_INSERT(history_list, &hl, h);
6805 completion_add_uri(h->uri);
6806 update_history_tabs(NULL);
6810 set_status(t, (char *)uri, XT_STATUS_URI);
6811 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6812 case WEBKIT_LOAD_FAILED:
6813 /* 4 */
6814 #endif
6815 #if GTK_CHECK_VERSION(2, 20, 0)
6816 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6817 gtk_widget_hide(t->spinner);
6818 #endif
6819 default:
6820 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6821 break;
6824 if (t->item)
6825 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6826 else
6827 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6828 can_go_back_for_real(t));
6830 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6831 can_go_forward_for_real(t));
6834 #if 0
6835 gboolean
6836 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6837 gchar *uri, gpointer web_error,struct tab *t)
6840 * XXX this function is wrong
6841 * it overwrites perfectly good urls with garbage on load errors
6842 * those happen often when popups fail to resolve dns
6844 if (t->tmp_uri)
6845 g_free(t->tmp_uri);
6846 t->tmp_uri = g_strdup(uri);
6847 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6848 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6849 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6850 set_status(t, uri, XT_STATUS_NOTHING);
6852 return (FALSE);
6854 #endif
6856 void
6857 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6859 const gchar *title = NULL, *win_title = NULL;
6861 title = get_title(t, FALSE);
6862 win_title = get_title(t, TRUE);
6863 gtk_label_set_text(GTK_LABEL(t->label), title);
6864 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6865 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6866 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6869 void
6870 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6872 run_script(t, JS_HINTING);
6875 void
6876 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6878 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6879 progress == 100 ? 0 : (double)progress / 100);
6880 if (show_url == 0) {
6881 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6882 progress == 100 ? 0 : (double)progress / 100);
6885 update_statusbar_position(NULL, NULL);
6889 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6890 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6891 WebKitWebPolicyDecision *pd, struct tab *t)
6893 char *uri;
6894 WebKitWebNavigationReason reason;
6895 struct domain *d = NULL;
6897 if (t == NULL) {
6898 show_oops(NULL, "webview_npd_cb invalid parameters");
6899 return (FALSE);
6902 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6903 t->ctrl_click,
6904 webkit_network_request_get_uri(request));
6906 uri = (char *)webkit_network_request_get_uri(request);
6908 /* if this is an xtp url, we don't load anything else */
6909 if (parse_xtp_url(t, uri))
6910 return (TRUE);
6912 if (t->ctrl_click) {
6913 t->ctrl_click = 0;
6914 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6915 webkit_web_policy_decision_ignore(pd);
6916 return (TRUE); /* we made the decission */
6920 * This is a little hairy but it comes down to this:
6921 * when we run in whitelist mode we have to assist the browser in
6922 * opening the URL that it would have opened in a new tab.
6924 reason = webkit_web_navigation_action_get_reason(na);
6925 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6926 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6927 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6928 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6929 load_uri(t, uri);
6930 webkit_web_policy_decision_use(pd);
6931 return (TRUE); /* we made the decision */
6934 return (FALSE);
6937 WebKitWebView *
6938 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6940 struct tab *tt;
6941 struct domain *d = NULL;
6942 const gchar *uri;
6943 WebKitWebView *webview = NULL;
6945 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6946 webkit_web_view_get_uri(wv));
6948 if (tabless) {
6949 /* open in current tab */
6950 webview = t->wv;
6951 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6952 uri = webkit_web_view_get_uri(wv);
6953 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6954 return (NULL);
6956 tt = create_new_tab(NULL, NULL, 1, -1);
6957 webview = tt->wv;
6958 } else if (enable_scripts == 1) {
6959 tt = create_new_tab(NULL, NULL, 1, -1);
6960 webview = tt->wv;
6963 return (webview);
6966 gboolean
6967 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6969 const gchar *uri;
6970 struct domain *d = NULL;
6972 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6974 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6975 uri = webkit_web_view_get_uri(wv);
6976 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6977 return (FALSE);
6979 delete_tab(t);
6980 } else if (enable_scripts == 1)
6981 delete_tab(t);
6983 return (TRUE);
6987 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6989 /* we can not eat the event without throwing gtk off so defer it */
6991 /* catch middle click */
6992 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6993 t->ctrl_click = 1;
6994 goto done;
6997 /* catch ctrl click */
6998 if (e->type == GDK_BUTTON_RELEASE &&
6999 CLEAN(e->state) == GDK_CONTROL_MASK)
7000 t->ctrl_click = 1;
7001 else
7002 t->ctrl_click = 0;
7003 done:
7004 return (XT_CB_PASSTHROUGH);
7008 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7010 struct mime_type *m;
7012 m = find_mime_type(mime_type);
7013 if (m == NULL)
7014 return (1);
7015 if (m->mt_download)
7016 return (1);
7018 switch (fork()) {
7019 case -1:
7020 show_oops(t, "can't fork mime handler");
7021 return (1);
7022 /* NOTREACHED */
7023 case 0:
7024 break;
7025 default:
7026 return (0);
7029 /* child */
7030 execlp(m->mt_action, m->mt_action,
7031 webkit_network_request_get_uri(request), (void *)NULL);
7033 _exit(0);
7035 /* NOTREACHED */
7036 return (0);
7039 const gchar *
7040 get_mime_type(char *file)
7042 const char *mime_type;
7043 GFileInfo *fi;
7044 GFile *gf;
7046 if (g_str_has_prefix(file, "file://"))
7047 file += strlen("file://");
7049 gf = g_file_new_for_path(file);
7050 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7051 NULL, NULL);
7052 mime_type = g_file_info_get_content_type(fi);
7053 g_object_unref(fi);
7054 g_object_unref(gf);
7056 return (mime_type);
7060 run_download_mimehandler(char *mime_type, char *file)
7062 struct mime_type *m;
7064 m = find_mime_type(mime_type);
7065 if (m == NULL)
7066 return (1);
7068 switch (fork()) {
7069 case -1:
7070 show_oops(NULL, "can't fork download mime handler");
7071 return (1);
7072 /* NOTREACHED */
7073 case 0:
7074 break;
7075 default:
7076 return (0);
7079 /* child */
7080 if (g_str_has_prefix(file, "file://"))
7081 file += strlen("file://");
7082 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7084 _exit(0);
7086 /* NOTREACHED */
7087 return (0);
7090 void
7091 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7092 WebKitWebView *wv)
7094 WebKitDownloadStatus status;
7095 const gchar *file = NULL, *mime = NULL;
7097 if (download == NULL)
7098 return;
7099 status = webkit_download_get_status(download);
7100 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7101 return;
7103 file = webkit_download_get_destination_uri(download);
7104 if (file == NULL)
7105 return;
7106 mime = get_mime_type((char *)file);
7107 if (mime == NULL)
7108 return;
7110 run_download_mimehandler((char *)mime, (char *)file);
7114 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7115 WebKitNetworkRequest *request, char *mime_type,
7116 WebKitWebPolicyDecision *decision, struct tab *t)
7118 if (t == NULL) {
7119 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7120 return (FALSE);
7123 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7124 t->tab_id, mime_type);
7126 if (run_mimehandler(t, mime_type, request) == 0) {
7127 webkit_web_policy_decision_ignore(decision);
7128 focus_webview(t);
7129 return (TRUE);
7132 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7133 webkit_web_policy_decision_download(decision);
7134 return (TRUE);
7137 return (FALSE);
7141 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7142 struct tab *t)
7144 struct stat sb;
7145 const gchar *suggested_name;
7146 gchar *filename = NULL;
7147 char *uri = NULL;
7148 struct download *download_entry;
7149 int i, ret = TRUE;
7151 if (wk_download == NULL || t == NULL) {
7152 show_oops(NULL, "%s invalid parameters", __func__);
7153 return (FALSE);
7156 suggested_name = webkit_download_get_suggested_filename(wk_download);
7157 if (suggested_name == NULL)
7158 return (FALSE); /* abort download */
7160 i = 0;
7161 do {
7162 if (filename) {
7163 g_free(filename);
7164 filename = NULL;
7166 if (i) {
7167 g_free(uri);
7168 uri = NULL;
7169 filename = g_strdup_printf("%d%s", i, suggested_name);
7171 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7172 filename : suggested_name);
7173 i++;
7174 } while (!stat(uri + strlen("file://"), &sb));
7176 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7177 "local %s\n", __func__, t->tab_id, filename, uri);
7179 webkit_download_set_destination_uri(wk_download, uri);
7181 if (webkit_download_get_status(wk_download) ==
7182 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7183 show_oops(t, "%s: download failed to start", __func__);
7184 ret = FALSE;
7185 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7186 } else {
7187 /* connect "download first" mime handler */
7188 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7189 G_CALLBACK(download_status_changed_cb), NULL);
7191 download_entry = g_malloc(sizeof(struct download));
7192 download_entry->download = wk_download;
7193 download_entry->tab = t;
7194 download_entry->id = next_download_id++;
7195 RB_INSERT(download_list, &downloads, download_entry);
7196 /* get from history */
7197 g_object_ref(wk_download);
7198 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7199 show_oops(t, "Download of '%s' started...",
7200 basename((char *)webkit_download_get_destination_uri(wk_download)));
7203 if (uri)
7204 g_free(uri);
7206 if (filename)
7207 g_free(filename);
7209 /* sync other download manager tabs */
7210 update_download_tabs(NULL);
7213 * NOTE: never redirect/render the current tab before this
7214 * function returns. This will cause the download to never start.
7216 return (ret); /* start download */
7219 void
7220 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7222 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7224 if (t == NULL) {
7225 show_oops(NULL, "webview_hover_cb");
7226 return;
7229 if (uri)
7230 set_status(t, uri, XT_STATUS_LINK);
7231 else {
7232 if (t->status)
7233 set_status(t, t->status, XT_STATUS_NOTHING);
7238 mark(struct tab *t, struct karg *arg)
7240 char mark;
7241 int index;
7243 mark = arg->s[1];
7244 if ((index = marktoindex(mark)) == -1)
7245 return -1;
7247 if (arg->i == XT_MARK_SET)
7248 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7249 else if (arg->i == XT_MARK_GOTO) {
7250 if (t->mark[index] == XT_INVALID_MARK) {
7251 show_oops(t, "mark '%c' does not exist", mark);
7252 return -1;
7254 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7255 something changes the document size */
7256 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7259 return 0;
7262 void
7263 marks_clear(struct tab *t)
7265 int i;
7267 for (i = 0; i < LENGTH(t->mark); i++)
7268 t->mark[i] = XT_INVALID_MARK;
7272 qmarks_load(void)
7274 char file[PATH_MAX];
7275 char *line = NULL, *p;
7276 int index, i;
7277 FILE *f;
7278 size_t linelen;
7280 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7281 if ((f = fopen(file, "r+")) == NULL) {
7282 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7283 return (1);
7286 for (i = 1; ; i++) {
7287 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7288 if (feof(f) || ferror(f))
7289 break;
7290 if (strlen(line) == 0 || line[0] == '#') {
7291 free(line);
7292 line = NULL;
7293 continue;
7296 p = strtok(line, " \t");
7298 if (p == NULL || strlen(p) != 1 ||
7299 (index = marktoindex(*p)) == -1) {
7300 warnx("corrupt quickmarks file, line %d", i);
7301 break;
7304 p = strtok(NULL, " \t");
7305 if (qmarks[index] != NULL)
7306 g_free(qmarks[index]);
7307 qmarks[index] = g_strdup(p);
7310 fclose(f);
7312 return (0);
7316 qmarks_save(void)
7318 char file[PATH_MAX];
7319 int i;
7320 FILE *f;
7322 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7323 if ((f = fopen(file, "r+")) == NULL) {
7324 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7325 return (1);
7328 for (i = 0; i < XT_NOMARKS; i++)
7329 if (qmarks[i] != NULL)
7330 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7332 fclose(f);
7334 return (0);
7338 qmark(struct tab *t, struct karg *arg)
7340 char mark;
7341 int index;
7343 mark = arg->s[strlen(arg->s)-1];
7344 index = marktoindex(mark);
7345 if (index == -1)
7346 return (-1);
7348 switch (arg->i) {
7349 case XT_QMARK_SET:
7350 if (qmarks[index] != NULL)
7351 g_free(qmarks[index]);
7353 qmarks_load(); /* sync if multiple instances */
7354 qmarks[index] = g_strdup(get_uri(t));
7355 qmarks_save();
7356 break;
7357 case XT_QMARK_OPEN:
7358 if (qmarks[index] != NULL)
7359 load_uri(t, qmarks[index]);
7360 else {
7361 show_oops(t, "quickmark \"%c\" does not exist",
7362 mark);
7363 return (-1);
7365 break;
7366 case XT_QMARK_TAB:
7367 if (qmarks[index] != NULL)
7368 create_new_tab(qmarks[index], NULL, 1, -1);
7369 else {
7370 show_oops(t, "quickmark \"%c\" does not exist",
7371 mark);
7372 return (-1);
7374 break;
7377 return (0);
7381 go_up(struct tab *t, struct karg *args)
7383 int levels;
7384 char *uri;
7385 char *tmp;
7387 levels = atoi(args->s);
7388 if (levels == 0)
7389 levels = 1;
7391 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7392 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7393 return 1;
7394 tmp += strlen(XT_PROTO_DELIM);
7396 /* if an uri starts with a slash, leave it alone (for file:///) */
7397 if (tmp[0] == '/')
7398 tmp++;
7400 while (levels--) {
7401 char *p;
7403 p = strrchr(tmp, '/');
7404 if (p != NULL)
7405 *p = '\0';
7406 else
7407 break;
7410 load_uri(t, uri);
7411 g_free(uri);
7413 return (0);
7417 gototab(struct tab *t, struct karg *args)
7419 int tab;
7420 struct karg arg = {0, NULL, -1};
7422 tab = atoi(args->s);
7424 arg.i = XT_TAB_NEXT;
7425 arg.precount = tab;
7427 movetab(t, &arg);
7429 return (0);
7433 zoom_amount(struct tab *t, struct karg *arg)
7435 struct karg narg = {0, NULL, -1};
7437 narg.i = atoi(arg->s);
7438 resizetab(t, &narg);
7440 return 0;
7444 flip_colon(struct tab *t, struct karg *arg)
7446 struct karg narg = {0, NULL, -1};
7447 char *p;
7449 if (t == NULL || arg == NULL)
7450 return (1);
7452 p = strstr(arg->s, ":");
7453 if (p == NULL)
7454 return (1);
7455 *p = '\0';
7457 narg.i = ':';
7458 narg.s = arg->s;
7459 command(t, &narg);
7461 return (0);
7464 /* buffer commands receive the regex that triggered them in arg.s */
7465 char bcmd[XT_BUFCMD_SZ];
7466 struct buffercmd {
7467 char *regex;
7468 int precount;
7469 #define XT_PRE_NO (0)
7470 #define XT_PRE_YES (1)
7471 #define XT_PRE_MAYBE (2)
7472 char *cmd;
7473 int (*func)(struct tab *, struct karg *);
7474 int arg;
7475 regex_t cregex;
7476 } buffercmds[] = {
7477 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7478 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7479 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7480 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7481 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7482 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7483 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7484 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7485 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7486 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7487 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7488 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7489 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7490 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7491 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7492 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7493 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7494 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7497 void
7498 buffercmd_init(void)
7500 int i;
7502 for (i = 0; i < LENGTH(buffercmds); i++)
7503 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7504 REG_EXTENDED | REG_NOSUB))
7505 startpage_add("invalid buffercmd regex %s",
7506 buffercmds[i].regex);
7509 void
7510 buffercmd_abort(struct tab *t)
7512 int i;
7514 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7515 for (i = 0; i < LENGTH(bcmd); i++)
7516 bcmd[i] = '\0';
7518 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7519 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7522 void
7523 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7525 struct karg arg = {0, NULL, -1};
7527 arg.i = cmd->arg;
7528 arg.s = g_strdup(bcmd);
7530 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7531 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7532 cmd->func(t, &arg);
7534 if (arg.s)
7535 g_free(arg.s);
7537 buffercmd_abort(t);
7540 gboolean
7541 buffercmd_addkey(struct tab *t, guint keyval)
7543 int i, c, match ;
7544 char s[XT_BUFCMD_SZ];
7546 if (keyval == GDK_Escape) {
7547 buffercmd_abort(t);
7548 return (XT_CB_HANDLED);
7551 /* key with modifier or non-ascii character */
7552 if (!isascii(keyval))
7553 return (XT_CB_PASSTHROUGH);
7555 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7556 "to buffer \"%s\"\n", keyval, bcmd);
7558 for (i = 0; i < LENGTH(bcmd); i++)
7559 if (bcmd[i] == '\0') {
7560 bcmd[i] = keyval;
7561 break;
7564 /* buffer full, ignore input */
7565 if (i >= LENGTH(bcmd) -1) {
7566 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7567 buffercmd_abort(t);
7568 return (XT_CB_HANDLED);
7571 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7573 /* find exact match */
7574 for (i = 0; i < LENGTH(buffercmds); i++)
7575 if (regexec(&buffercmds[i].cregex, bcmd,
7576 (size_t) 0, NULL, 0) == 0) {
7577 buffercmd_execute(t, &buffercmds[i]);
7578 goto done;
7581 /* find non exact matches to see if we need to abort ot not */
7582 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7583 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7584 c = -1;
7585 s[0] = '\0';
7586 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7587 if (isdigit(bcmd[0])) {
7588 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7589 continue;
7590 } else {
7591 c = 0;
7592 if (sscanf(bcmd, "%s", s) == 0)
7593 continue;
7595 } else if (buffercmds[i].precount == XT_PRE_YES) {
7596 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7597 continue;
7598 } else {
7599 if (sscanf(bcmd, "%s", s) == 0)
7600 continue;
7602 if (c == -1 && buffercmds[i].precount)
7603 continue;
7604 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7605 match++;
7607 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7608 i, match, buffercmds[i].cmd, c, s);
7610 if (match == 0) {
7611 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7612 buffercmd_abort(t);
7615 done:
7616 return (XT_CB_HANDLED);
7619 gboolean
7620 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7622 struct key_binding *k;
7624 /* handle keybindings if buffercmd is empty.
7625 if not empty, allow commands like C-n */
7626 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7627 TAILQ_FOREACH(k, &kbl, entry)
7628 if (e->keyval == k->key
7629 && (entry ? k->use_in_entry : 1)) {
7630 if (k->mask == 0) {
7631 if ((e->state & (CTRL | MOD1)) == 0)
7632 return (cmd_execute(t, k->cmd));
7633 } else if ((e->state & k->mask) == k->mask) {
7634 return (cmd_execute(t, k->cmd));
7638 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7639 return buffercmd_addkey(t, e->keyval);
7641 return (XT_CB_PASSTHROUGH);
7645 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7647 char s[2], buf[128];
7648 const char *errstr = NULL;
7650 /* don't use w directly; use t->whatever instead */
7652 if (t == NULL) {
7653 show_oops(NULL, "wv_keypress_after_cb");
7654 return (XT_CB_PASSTHROUGH);
7657 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7658 e->keyval, e->state, t);
7660 if (t->hints_on) {
7661 /* ESC */
7662 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7663 disable_hints(t);
7664 return (XT_CB_HANDLED);
7667 /* RETURN */
7668 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7669 if (errstr) {
7670 /* we have a string */
7671 } else {
7672 /* we have a number */
7673 snprintf(buf, sizeof buf,
7674 "vimprobable_fire(%s)", t->hint_num);
7675 run_script(t, buf);
7677 disable_hints(t);
7680 /* BACKSPACE */
7681 /* XXX unfuck this */
7682 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7683 if (t->hint_mode == XT_HINT_NUMERICAL) {
7684 /* last input was numerical */
7685 int l;
7686 l = strlen(t->hint_num);
7687 if (l > 0) {
7688 l--;
7689 if (l == 0) {
7690 disable_hints(t);
7691 enable_hints(t);
7692 } else {
7693 t->hint_num[l] = '\0';
7694 goto num;
7697 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7698 /* last input was alphanumerical */
7699 int l;
7700 l = strlen(t->hint_buf);
7701 if (l > 0) {
7702 l--;
7703 if (l == 0) {
7704 disable_hints(t);
7705 enable_hints(t);
7706 } else {
7707 t->hint_buf[l] = '\0';
7708 goto anum;
7711 } else {
7712 /* bogus */
7713 disable_hints(t);
7717 /* numerical input */
7718 if (CLEAN(e->state) == 0 &&
7719 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7720 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7721 snprintf(s, sizeof s, "%c", e->keyval);
7722 strlcat(t->hint_num, s, sizeof t->hint_num);
7723 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7724 t->hint_num);
7725 num:
7726 if (errstr) {
7727 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7728 "invalid link number\n");
7729 disable_hints(t);
7730 } else {
7731 snprintf(buf, sizeof buf,
7732 "vimprobable_update_hints(%s)",
7733 t->hint_num);
7734 t->hint_mode = XT_HINT_NUMERICAL;
7735 run_script(t, buf);
7738 /* empty the counter buffer */
7739 bzero(t->hint_buf, sizeof t->hint_buf);
7740 return (XT_CB_HANDLED);
7743 /* alphanumerical input */
7744 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7745 e->keyval <= GDK_z) ||
7746 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7747 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7748 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7749 e->keyval <= GDK_9) ||
7750 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7751 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7752 snprintf(s, sizeof s, "%c", e->keyval);
7753 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7754 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7755 " %s\n", t->hint_buf);
7756 anum:
7757 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7758 run_script(t, buf);
7760 snprintf(buf, sizeof buf,
7761 "vimprobable_show_hints('%s')", t->hint_buf);
7762 t->hint_mode = XT_HINT_ALPHANUM;
7763 run_script(t, buf);
7765 /* empty the counter buffer */
7766 bzero(t->hint_num, sizeof t->hint_num);
7767 return (XT_CB_HANDLED);
7770 return (XT_CB_HANDLED);
7771 } else {
7772 /* prefix input*/
7773 snprintf(s, sizeof s, "%c", e->keyval);
7774 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7775 cmd_prefix = 10 * cmd_prefix + atoi(s);
7778 return (handle_keypress(t, e, 0));
7782 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7784 hide_oops(t);
7786 /* Hide buffers, if they are visible, with escape. */
7787 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7788 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7789 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7790 hide_buffers(t);
7791 return (XT_CB_HANDLED);
7794 return (XT_CB_PASSTHROUGH);
7797 gboolean
7798 search_continue(struct tab *t)
7800 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7801 gboolean rv = FALSE;
7803 if (c[0] == ':')
7804 goto done;
7805 if (strlen(c) == 1) {
7806 webkit_web_view_unmark_text_matches(t->wv);
7807 goto done;
7810 if (c[0] == '/')
7811 t->search_forward = TRUE;
7812 else if (c[0] == '?')
7813 t->search_forward = FALSE;
7814 else
7815 goto done;
7817 rv = TRUE;
7818 done:
7819 return (rv);
7822 gboolean
7823 search_cb(struct tab *t)
7825 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7826 GdkColor color;
7828 if (search_continue(t) == FALSE)
7829 goto done;
7831 /* search */
7832 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7833 TRUE) == FALSE) {
7834 /* not found, mark red */
7835 gdk_color_parse(XT_COLOR_RED, &color);
7836 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7837 /* unmark and remove selection */
7838 webkit_web_view_unmark_text_matches(t->wv);
7839 /* my kingdom for a way to unselect text in webview */
7840 } else {
7841 /* found, highlight all */
7842 webkit_web_view_unmark_text_matches(t->wv);
7843 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7844 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7845 gdk_color_parse(XT_COLOR_WHITE, &color);
7846 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7848 done:
7849 t->search_id = 0;
7850 return (FALSE);
7854 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7856 const gchar *c = gtk_entry_get_text(w);
7858 if (t == NULL) {
7859 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7860 return (XT_CB_PASSTHROUGH);
7863 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7864 e->keyval, e->state, t);
7866 if (search_continue(t) == FALSE)
7867 goto done;
7869 /* if search length is > 4 then no longer play timeout games */
7870 if (strlen(c) > 4) {
7871 if (t->search_id) {
7872 g_source_remove(t->search_id);
7873 t->search_id = 0;
7875 search_cb(t);
7876 goto done;
7879 /* reestablish a new timer if the user types fast */
7880 if (t->search_id)
7881 g_source_remove(t->search_id);
7882 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7884 done:
7885 return (XT_CB_PASSTHROUGH);
7888 gboolean
7889 match_uri(const gchar *uri, const gchar *key) {
7890 gchar *voffset;
7891 size_t len;
7892 gboolean match = FALSE;
7894 len = strlen(key);
7896 if (!strncmp(key, uri, len))
7897 match = TRUE;
7898 else {
7899 voffset = strstr(uri, "/") + 2;
7900 if (!strncmp(key, voffset, len))
7901 match = TRUE;
7902 else if (g_str_has_prefix(voffset, "www.")) {
7903 voffset = voffset + strlen("www.");
7904 if (!strncmp(key, voffset, len))
7905 match = TRUE;
7909 return (match);
7912 gboolean
7913 match_session(const gchar *name, const gchar *key) {
7914 char *sub;
7916 sub = strcasestr(name, key);
7918 return sub == name;
7921 void
7922 cmd_getlist(int id, char *key)
7924 int i, dep, c = 0;
7925 struct history *h;
7926 struct session *s;
7928 if (id >= 0) {
7929 if (cmds[id].type & XT_URLARG) {
7930 RB_FOREACH_REVERSE(h, history_list, &hl)
7931 if (match_uri(h->uri, key)) {
7932 cmd_status.list[c] = (char *)h->uri;
7933 if (++c > 255)
7934 break;
7936 cmd_status.len = c;
7937 return;
7938 } else if (cmds[id].type & XT_SESSARG) {
7939 TAILQ_FOREACH(s, &sessions, entry)
7940 if (match_session(s->name, key)) {
7941 cmd_status.list[c] = (char *)s->name;
7942 if (++c > 255)
7943 break;
7945 cmd_status.len = c;
7946 return;
7947 } else if (cmds[id].type & XT_SETARG) {
7948 for (i = 0; i < LENGTH(rs); i++)
7949 if(!strncmp(key, rs[i].name, strlen(key)))
7950 cmd_status.list[c++] = rs[i].name;
7951 cmd_status.len = c;
7952 return;
7956 dep = (id == -1) ? 0 : cmds[id].level + 1;
7958 for (i = id + 1; i < LENGTH(cmds); i++) {
7959 if (cmds[i].level < dep)
7960 break;
7961 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7962 strlen(key)) && !isdigit(cmds[i].cmd[0]))
7963 cmd_status.list[c++] = cmds[i].cmd;
7967 cmd_status.len = c;
7970 char *
7971 cmd_getnext(int dir)
7973 cmd_status.index += dir;
7975 if (cmd_status.index < 0)
7976 cmd_status.index = cmd_status.len - 1;
7977 else if (cmd_status.index >= cmd_status.len)
7978 cmd_status.index = 0;
7980 return cmd_status.list[cmd_status.index];
7984 cmd_tokenize(char *s, char *tokens[])
7986 int i = 0;
7987 char *tok, *last;
7988 size_t len = strlen(s);
7989 bool blank;
7991 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7992 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7993 tok = strtok_r(NULL, " ", &last), i++)
7994 tokens[i] = tok;
7996 if (blank && i < 3)
7997 tokens[i++] = "";
7999 return (i);
8002 void
8003 cmd_complete(struct tab *t, char *str, int dir)
8005 GtkEntry *w = GTK_ENTRY(t->cmd);
8006 int i, j, levels, c = 0, dep = 0, parent = -1;
8007 int matchcount = 0;
8008 char *tok, *match, *s = g_strdup(str);
8009 char *tokens[3];
8010 char res[XT_MAX_URL_LENGTH + 32] = ":";
8011 char *sc = s;
8013 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8015 /* copy prefix*/
8016 for (i = 0; isdigit(s[i]); i++)
8017 res[i + 1] = s[i];
8019 for (; isspace(s[i]); i++)
8020 res[i + 1] = s[i];
8022 s += i;
8024 levels = cmd_tokenize(s, tokens);
8026 for (i = 0; i < levels - 1; i++) {
8027 tok = tokens[i];
8028 matchcount = 0;
8029 for (j = c; j < LENGTH(cmds); j++) {
8030 if (cmds[j].level < dep)
8031 break;
8032 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8033 strlen(tok))) {
8034 matchcount++;
8035 c = j + 1;
8036 if (strlen(tok) == strlen(cmds[j].cmd)) {
8037 matchcount = 1;
8038 break;
8043 if (matchcount == 1) {
8044 strlcat(res, tok, sizeof res);
8045 strlcat(res, " ", sizeof res);
8046 dep++;
8047 } else {
8048 g_free(sc);
8049 return;
8052 parent = c - 1;
8055 if (cmd_status.index == -1)
8056 cmd_getlist(parent, tokens[i]);
8058 if (cmd_status.len > 0) {
8059 match = cmd_getnext(dir);
8060 strlcat(res, match, sizeof res);
8061 gtk_entry_set_text(w, res);
8062 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8065 g_free(sc);
8068 gboolean
8069 cmd_execute(struct tab *t, char *str)
8071 struct cmd *cmd = NULL;
8072 char *tok, *last, *s = g_strdup(str), *sc;
8073 char prefixstr[4];
8074 int j, len, c = 0, dep = 0, matchcount = 0;
8075 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8076 struct karg arg = {0, NULL, -1};
8078 sc = s;
8080 /* copy prefix*/
8081 for (j = 0; j<3 && isdigit(s[j]); j++)
8082 prefixstr[j]=s[j];
8084 prefixstr[j]='\0';
8086 s += j;
8087 while (isspace(s[0]))
8088 s++;
8090 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8091 prefix = atoi(prefixstr);
8092 else
8093 s = sc;
8095 for (tok = strtok_r(s, " ", &last); tok;
8096 tok = strtok_r(NULL, " ", &last)) {
8097 matchcount = 0;
8098 for (j = c; j < LENGTH(cmds); j++) {
8099 if (cmds[j].level < dep)
8100 break;
8101 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8102 strlen(tok);
8103 if (cmds[j].level == dep &&
8104 !strncmp(tok, cmds[j].cmd, len)) {
8105 matchcount++;
8106 c = j + 1;
8107 cmd = &cmds[j];
8108 if (len == strlen(cmds[j].cmd)) {
8109 matchcount = 1;
8110 break;
8114 if (matchcount == 1) {
8115 if (cmd->type > 0)
8116 goto execute_cmd;
8117 dep++;
8118 } else {
8119 show_oops(t, "Invalid command: %s", str);
8120 goto done;
8123 execute_cmd:
8124 arg.i = cmd->arg;
8126 if (prefix != -1)
8127 arg.precount = prefix;
8128 else if (cmd_prefix > 0)
8129 arg.precount = cmd_prefix;
8131 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8132 show_oops(t, "No prefix allowed: %s", str);
8133 goto done;
8135 if (cmd->type > 1)
8136 arg.s = last ? g_strdup(last) : g_strdup("");
8137 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8138 arg.precount = atoi(arg.s);
8139 if (arg.precount <= 0) {
8140 if (arg.s[0] == '0')
8141 show_oops(t, "Zero count");
8142 else
8143 show_oops(t, "Trailing characters");
8144 goto done;
8148 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8149 __func__, arg.precount, arg.s);
8151 cmd->func(t, &arg);
8153 rv = XT_CB_HANDLED;
8154 done:
8155 if (j > 0)
8156 cmd_prefix = 0;
8157 g_free(sc);
8158 if (arg.s)
8159 g_free(arg.s);
8161 return (rv);
8165 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8167 if (t == NULL) {
8168 show_oops(NULL, "entry_key_cb invalid parameters");
8169 return (XT_CB_PASSTHROUGH);
8172 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8173 e->keyval, e->state, t);
8175 hide_oops(t);
8177 if (e->keyval == GDK_Escape) {
8178 /* don't use focus_webview(t) because we want to type :cmds */
8179 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8182 return (handle_keypress(t, e, 1));
8185 struct command_entry *
8186 history_prev(struct command_list *l, struct command_entry *at)
8188 if (at == NULL)
8189 at = TAILQ_LAST(l, command_list);
8190 else {
8191 at = TAILQ_PREV(at, command_list, entry);
8192 if (at == NULL)
8193 at = TAILQ_LAST(l, command_list);
8196 return (at);
8199 struct command_entry *
8200 history_next(struct command_list *l, struct command_entry *at)
8202 if (at == NULL)
8203 at = TAILQ_FIRST(l);
8204 else {
8205 at = TAILQ_NEXT(at, entry);
8206 if (at == NULL)
8207 at = TAILQ_FIRST(l);
8210 return (at);
8214 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8216 int rv = XT_CB_HANDLED;
8217 const gchar *c = gtk_entry_get_text(w);
8219 if (t == NULL) {
8220 show_oops(NULL, "cmd_keypress_cb parameters");
8221 return (XT_CB_PASSTHROUGH);
8224 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8225 e->keyval, e->state, t);
8227 /* sanity */
8228 if (c == NULL)
8229 e->keyval = GDK_Escape;
8230 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8231 e->keyval = GDK_Escape;
8233 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8234 e->keyval != GDK_ISO_Left_Tab)
8235 cmd_status.index = -1;
8237 switch (e->keyval) {
8238 case GDK_Tab:
8239 if (c[0] == ':')
8240 cmd_complete(t, (char *)&c[1], 1);
8241 goto done;
8242 case GDK_ISO_Left_Tab:
8243 if (c[0] == ':')
8244 cmd_complete(t, (char *)&c[1], -1);
8246 goto done;
8247 case GDK_Down:
8248 if (c[0] != ':') {
8249 if ((search_at = history_next(&shl, search_at))) {
8250 search_at->line[0] = c[0];
8251 gtk_entry_set_text(w, search_at->line);
8252 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8254 } else {
8255 if ((history_at = history_prev(&chl, history_at))) {
8256 history_at->line[0] = c[0];
8257 gtk_entry_set_text(w, history_at->line);
8258 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8262 goto done;
8263 case GDK_Up:
8264 if (c[0] != ':') {
8265 if ((search_at = history_next(&shl, search_at))) {
8266 search_at->line[0] = c[0];
8267 gtk_entry_set_text(w, search_at->line);
8268 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8270 } else {
8271 if ((history_at = history_next(&chl, history_at))) {
8272 history_at->line[0] = c[0];
8273 gtk_entry_set_text(w, history_at->line);
8274 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8278 goto done;
8279 case GDK_BackSpace:
8280 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8281 break;
8282 /* FALLTHROUGH */
8283 case GDK_Escape:
8284 hide_cmd(t);
8285 focus_webview(t);
8287 /* cancel search */
8288 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8289 webkit_web_view_unmark_text_matches(t->wv);
8290 goto done;
8293 rv = XT_CB_PASSTHROUGH;
8294 done:
8295 return (rv);
8298 void
8299 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8301 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8304 void
8305 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8307 /* popup menu enabled */
8308 t->popup = 1;
8312 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8314 if (t == NULL) {
8315 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8316 return (XT_CB_PASSTHROUGH);
8319 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8320 t->tab_id, t->popup);
8322 /* if popup is enabled don't lose focus */
8323 if (t->popup) {
8324 t->popup = 0;
8325 return (XT_CB_PASSTHROUGH);
8328 hide_cmd(t);
8329 hide_oops(t);
8331 if (show_url == 0 || t->focus_wv)
8332 focus_webview(t);
8333 else
8334 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8336 return (XT_CB_PASSTHROUGH);
8339 void
8340 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8342 char *s;
8343 const gchar *c = gtk_entry_get_text(entry);
8345 if (t == NULL) {
8346 show_oops(NULL, "cmd_activate_cb invalid parameters");
8347 return;
8350 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8352 hide_cmd(t);
8354 /* sanity */
8355 if (c == NULL)
8356 goto done;
8357 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8358 goto done;
8359 if (strlen(c) < 2)
8360 goto done;
8361 s = (char *)&c[1];
8363 if (c[0] == '/' || c[0] == '?') {
8364 /* see if there is a timer pending */
8365 if (t->search_id) {
8366 g_source_remove(t->search_id);
8367 t->search_id = 0;
8368 search_cb(t);
8371 if (t->search_text) {
8372 g_free(t->search_text);
8373 t->search_text = NULL;
8376 t->search_text = g_strdup(s);
8377 if (global_search)
8378 g_free(global_search);
8379 global_search = g_strdup(s);
8380 t->search_forward = c[0] == '/';
8382 history_add(&shl, search_file, s, &search_history_count);
8383 goto done;
8386 history_add(&chl, command_file, s, &cmd_history_count);
8387 cmd_execute(t, s);
8388 done:
8389 return;
8392 void
8393 backward_cb(GtkWidget *w, struct tab *t)
8395 struct karg a;
8397 if (t == NULL) {
8398 show_oops(NULL, "backward_cb invalid parameters");
8399 return;
8402 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8404 a.i = XT_NAV_BACK;
8405 navaction(t, &a);
8408 void
8409 forward_cb(GtkWidget *w, struct tab *t)
8411 struct karg a;
8413 if (t == NULL) {
8414 show_oops(NULL, "forward_cb invalid parameters");
8415 return;
8418 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8420 a.i = XT_NAV_FORWARD;
8421 navaction(t, &a);
8424 void
8425 home_cb(GtkWidget *w, struct tab *t)
8427 if (t == NULL) {
8428 show_oops(NULL, "home_cb invalid parameters");
8429 return;
8432 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8434 load_uri(t, home);
8437 void
8438 stop_cb(GtkWidget *w, struct tab *t)
8440 WebKitWebFrame *frame;
8442 if (t == NULL) {
8443 show_oops(NULL, "stop_cb invalid parameters");
8444 return;
8447 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8449 frame = webkit_web_view_get_main_frame(t->wv);
8450 if (frame == NULL) {
8451 show_oops(t, "stop_cb: no frame");
8452 return;
8455 webkit_web_frame_stop_loading(frame);
8456 abort_favicon_download(t);
8459 void
8460 setup_webkit(struct tab *t)
8462 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8463 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8464 FALSE, (char *)NULL);
8465 else
8466 warnx("webkit does not have \"enable-dns-prefetching\" property");
8467 g_object_set(G_OBJECT(t->settings),
8468 "user-agent", t->user_agent, (char *)NULL);
8469 g_object_set(G_OBJECT(t->settings),
8470 "enable-scripts", enable_scripts, (char *)NULL);
8471 g_object_set(G_OBJECT(t->settings),
8472 "enable-plugins", enable_plugins, (char *)NULL);
8473 g_object_set(G_OBJECT(t->settings),
8474 "javascript-can-open-windows-automatically", enable_scripts,
8475 (char *)NULL);
8476 g_object_set(G_OBJECT(t->settings),
8477 "enable-html5-database", FALSE, (char *)NULL);
8478 g_object_set(G_OBJECT(t->settings),
8479 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8480 g_object_set(G_OBJECT(t->settings),
8481 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8482 g_object_set(G_OBJECT(t->settings),
8483 "spell_checking_languages", spell_check_languages, (char *)NULL);
8484 g_object_set(G_OBJECT(t->wv),
8485 "full-content-zoom", TRUE, (char *)NULL);
8487 webkit_web_view_set_settings(t->wv, t->settings);
8490 gboolean
8491 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8493 struct tab *ti, *t = NULL;
8494 gdouble view_size, value, max;
8495 gchar *position;
8497 TAILQ_FOREACH(ti, &tabs, entry)
8498 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8499 t = ti;
8500 break;
8503 if (t == NULL)
8504 return FALSE;
8506 if (adjustment == NULL)
8507 adjustment = gtk_scrolled_window_get_vadjustment(
8508 GTK_SCROLLED_WINDOW(t->browser_win));
8510 view_size = gtk_adjustment_get_page_size(adjustment);
8511 value = gtk_adjustment_get_value(adjustment);
8512 max = gtk_adjustment_get_upper(adjustment) - view_size;
8514 if (max == 0)
8515 position = g_strdup("All");
8516 else if (value == max)
8517 position = g_strdup("Bot");
8518 else if (value == 0)
8519 position = g_strdup("Top");
8520 else
8521 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8523 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8524 g_free(position);
8526 return (TRUE);
8529 GtkWidget *
8530 create_browser(struct tab *t)
8532 GtkWidget *w;
8533 gchar *strval;
8534 GtkAdjustment *adjustment;
8536 if (t == NULL) {
8537 show_oops(NULL, "create_browser invalid parameters");
8538 return (NULL);
8541 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8542 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8543 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8544 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8546 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8547 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8548 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8550 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8551 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8553 /* set defaults */
8554 t->settings = webkit_web_settings_new();
8556 if (user_agent == NULL) {
8557 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8558 (char *)NULL);
8559 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8560 g_free(strval);
8561 } else
8562 t->user_agent = g_strdup(user_agent);
8564 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8566 adjustment =
8567 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8568 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8569 G_CALLBACK(update_statusbar_position), NULL);
8571 setup_webkit(t);
8573 return (w);
8576 GtkWidget *
8577 create_window(void)
8579 GtkWidget *w;
8581 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8582 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8583 gtk_widget_set_name(w, "xxxterm");
8584 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8585 g_signal_connect(G_OBJECT(w), "delete_event",
8586 G_CALLBACK (gtk_main_quit), NULL);
8588 return (w);
8591 GtkWidget *
8592 create_kiosk_toolbar(struct tab *t)
8594 GtkWidget *toolbar = NULL, *b;
8596 b = gtk_hbox_new(FALSE, 0);
8597 toolbar = b;
8598 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8600 /* backward button */
8601 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8602 gtk_widget_set_sensitive(t->backward, FALSE);
8603 g_signal_connect(G_OBJECT(t->backward), "clicked",
8604 G_CALLBACK(backward_cb), t);
8605 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8607 /* forward button */
8608 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8609 gtk_widget_set_sensitive(t->forward, FALSE);
8610 g_signal_connect(G_OBJECT(t->forward), "clicked",
8611 G_CALLBACK(forward_cb), t);
8612 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8614 /* home button */
8615 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8616 gtk_widget_set_sensitive(t->gohome, true);
8617 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8618 G_CALLBACK(home_cb), t);
8619 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8621 /* create widgets but don't use them */
8622 t->uri_entry = gtk_entry_new();
8623 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8624 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8625 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8627 return (toolbar);
8630 GtkWidget *
8631 create_toolbar(struct tab *t)
8633 GtkWidget *toolbar = NULL, *b, *eb1;
8635 b = gtk_hbox_new(FALSE, 0);
8636 toolbar = b;
8637 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8639 /* backward button */
8640 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8641 gtk_widget_set_sensitive(t->backward, FALSE);
8642 g_signal_connect(G_OBJECT(t->backward), "clicked",
8643 G_CALLBACK(backward_cb), t);
8644 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8646 /* forward button */
8647 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8648 gtk_widget_set_sensitive(t->forward, FALSE);
8649 g_signal_connect(G_OBJECT(t->forward), "clicked",
8650 G_CALLBACK(forward_cb), t);
8651 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8652 FALSE, 0);
8654 /* stop button */
8655 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8656 gtk_widget_set_sensitive(t->stop, FALSE);
8657 g_signal_connect(G_OBJECT(t->stop), "clicked",
8658 G_CALLBACK(stop_cb), t);
8659 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8660 FALSE, 0);
8662 /* JS button */
8663 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8664 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8665 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8666 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8667 G_CALLBACK(js_toggle_cb), t);
8668 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8670 t->uri_entry = gtk_entry_new();
8671 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8672 G_CALLBACK(activate_uri_entry_cb), t);
8673 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8674 G_CALLBACK(entry_key_cb), t);
8675 completion_add(t);
8676 eb1 = gtk_hbox_new(FALSE, 0);
8677 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8678 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8679 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8681 /* search entry */
8682 if (search_string) {
8683 GtkWidget *eb2;
8684 t->search_entry = gtk_entry_new();
8685 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8686 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8687 G_CALLBACK(activate_search_entry_cb), t);
8688 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8689 G_CALLBACK(entry_key_cb), t);
8690 gtk_widget_set_size_request(t->search_entry, -1, -1);
8691 eb2 = gtk_hbox_new(FALSE, 0);
8692 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8693 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8695 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8698 return (toolbar);
8701 GtkWidget *
8702 create_buffers(struct tab *t)
8704 GtkCellRenderer *renderer;
8705 GtkWidget *view;
8707 view = gtk_tree_view_new();
8709 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8711 renderer = gtk_cell_renderer_text_new();
8712 gtk_tree_view_insert_column_with_attributes
8713 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8715 renderer = gtk_cell_renderer_text_new();
8716 gtk_tree_view_insert_column_with_attributes
8717 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8718 (char *)NULL);
8720 gtk_tree_view_set_model
8721 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8723 return view;
8726 void
8727 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8728 GtkTreeViewColumn *col, struct tab *t)
8730 GtkTreeIter iter;
8731 guint id;
8733 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8735 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8736 path)) {
8737 gtk_tree_model_get
8738 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8739 set_current_tab(id - 1);
8742 hide_buffers(t);
8745 /* after tab reordering/creation/removal */
8746 void
8747 recalc_tabs(void)
8749 struct tab *t;
8750 int maxid = 0;
8752 TAILQ_FOREACH(t, &tabs, entry) {
8753 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8754 if (t->tab_id > maxid)
8755 maxid = t->tab_id;
8757 gtk_widget_show(t->tab_elems.sep);
8760 TAILQ_FOREACH(t, &tabs, entry) {
8761 if (t->tab_id == maxid) {
8762 gtk_widget_hide(t->tab_elems.sep);
8763 break;
8768 /* after active tab change */
8769 void
8770 recolor_compact_tabs(void)
8772 struct tab *t;
8773 int curid = 0;
8774 GdkColor color;
8776 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8777 TAILQ_FOREACH(t, &tabs, entry)
8778 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8779 &color);
8781 curid = gtk_notebook_get_current_page(notebook);
8782 TAILQ_FOREACH(t, &tabs, entry)
8783 if (t->tab_id == curid) {
8784 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8785 gtk_widget_modify_fg(t->tab_elems.label,
8786 GTK_STATE_NORMAL, &color);
8787 break;
8791 void
8792 set_current_tab(int page_num)
8794 buffercmd_abort(get_current_tab());
8795 gtk_notebook_set_current_page(notebook, page_num);
8796 recolor_compact_tabs();
8800 undo_close_tab_save(struct tab *t)
8802 int m, n;
8803 const gchar *uri;
8804 struct undo *u1, *u2;
8805 GList *items;
8806 WebKitWebHistoryItem *item;
8808 if ((uri = get_uri(t)) == NULL)
8809 return (1);
8811 u1 = g_malloc0(sizeof(struct undo));
8812 u1->uri = g_strdup(uri);
8814 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8816 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8817 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8818 u1->back = n;
8820 /* forward history */
8821 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8823 while (items) {
8824 item = items->data;
8825 u1->history = g_list_prepend(u1->history,
8826 webkit_web_history_item_copy(item));
8827 items = g_list_next(items);
8830 /* current item */
8831 if (m) {
8832 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8833 u1->history = g_list_prepend(u1->history,
8834 webkit_web_history_item_copy(item));
8837 /* back history */
8838 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8840 while (items) {
8841 item = items->data;
8842 u1->history = g_list_prepend(u1->history,
8843 webkit_web_history_item_copy(item));
8844 items = g_list_next(items);
8847 TAILQ_INSERT_HEAD(&undos, u1, entry);
8849 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8850 u2 = TAILQ_LAST(&undos, undo_tailq);
8851 TAILQ_REMOVE(&undos, u2, entry);
8852 g_free(u2->uri);
8853 g_list_free(u2->history);
8854 g_free(u2);
8855 } else
8856 undo_count++;
8858 return (0);
8861 void
8862 delete_tab(struct tab *t)
8864 struct karg a;
8866 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8868 if (t == NULL)
8869 return;
8871 buffercmd_abort(t);
8872 TAILQ_REMOVE(&tabs, t, entry);
8874 /* Halt all webkit activity. */
8875 abort_favicon_download(t);
8876 webkit_web_view_stop_loading(t->wv);
8878 /* Save the tab, so we can undo the close. */
8879 undo_close_tab_save(t);
8881 if (browser_mode == XT_BM_KIOSK) {
8882 gtk_widget_destroy(t->uri_entry);
8883 gtk_widget_destroy(t->stop);
8884 gtk_widget_destroy(t->js_toggle);
8887 gtk_widget_destroy(t->tab_elems.eventbox);
8888 gtk_widget_destroy(t->vbox);
8890 /* just in case */
8891 if (t->search_id)
8892 g_source_remove(t->search_id);
8894 g_free(t->user_agent);
8895 g_free(t->stylesheet);
8896 g_free(t->tmp_uri);
8897 g_free(t);
8899 if (TAILQ_EMPTY(&tabs)) {
8900 if (browser_mode == XT_BM_KIOSK)
8901 create_new_tab(home, NULL, 1, -1);
8902 else
8903 create_new_tab(NULL, NULL, 1, -1);
8906 /* recreate session */
8907 if (session_autosave) {
8908 a.s = NULL;
8909 save_tabs(t, &a);
8912 recalc_tabs();
8913 recolor_compact_tabs();
8916 void
8917 update_statusbar_zoom(struct tab *t)
8919 gfloat zoom;
8920 char s[16] = { '\0' };
8922 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8923 if ((zoom <= 0.99 || zoom >= 1.01))
8924 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8925 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8928 void
8929 setzoom_webkit(struct tab *t, int adjust)
8931 #define XT_ZOOMPERCENT 0.04
8933 gfloat zoom;
8935 if (t == NULL) {
8936 show_oops(NULL, "setzoom_webkit invalid parameters");
8937 return;
8940 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8941 if (adjust == XT_ZOOM_IN)
8942 zoom += XT_ZOOMPERCENT;
8943 else if (adjust == XT_ZOOM_OUT)
8944 zoom -= XT_ZOOMPERCENT;
8945 else if (adjust > 0)
8946 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8947 else {
8948 show_oops(t, "setzoom_webkit invalid zoom value");
8949 return;
8952 if (zoom < XT_ZOOMPERCENT)
8953 zoom = XT_ZOOMPERCENT;
8954 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8955 update_statusbar_zoom(t);
8958 gboolean
8959 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8961 struct tab *t = (struct tab *) data;
8963 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8965 switch (event->button) {
8966 case 1:
8967 set_current_tab(t->tab_id);
8968 break;
8969 case 2:
8970 delete_tab(t);
8971 break;
8974 return TRUE;
8977 void
8978 append_tab(struct tab *t)
8980 if (t == NULL)
8981 return;
8983 TAILQ_INSERT_TAIL(&tabs, t, entry);
8984 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8987 GtkWidget *
8988 create_sbe(int width)
8990 GtkWidget *sbe;
8992 sbe = gtk_entry_new();
8993 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8994 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8995 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8996 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8997 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8998 gtk_widget_set_size_request(sbe, width, -1);
9000 return sbe;
9003 struct tab *
9004 create_new_tab(char *title, struct undo *u, int focus, int position)
9006 struct tab *t;
9007 int load = 1, id;
9008 GtkWidget *b, *bb;
9009 WebKitWebHistoryItem *item;
9010 GList *items;
9011 GdkColor color;
9012 char *p;
9013 int sbe_p = 0, sbe_b = 0,
9014 sbe_z = 0;
9016 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9018 if (tabless && !TAILQ_EMPTY(&tabs)) {
9019 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9020 return (NULL);
9023 t = g_malloc0(sizeof *t);
9025 if (title == NULL) {
9026 title = "(untitled)";
9027 load = 0;
9030 t->vbox = gtk_vbox_new(FALSE, 0);
9032 /* label + button for tab */
9033 b = gtk_hbox_new(FALSE, 0);
9034 t->tab_content = b;
9036 #if GTK_CHECK_VERSION(2, 20, 0)
9037 t->spinner = gtk_spinner_new();
9038 #endif
9039 t->label = gtk_label_new(title);
9040 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9041 gtk_widget_set_size_request(t->label, 100, 0);
9042 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9043 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9044 gtk_widget_set_size_request(b, 130, 0);
9046 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9047 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9048 #if GTK_CHECK_VERSION(2, 20, 0)
9049 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9050 #endif
9052 /* toolbar */
9053 if (browser_mode == XT_BM_KIOSK) {
9054 t->toolbar = create_kiosk_toolbar(t);
9055 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9057 } else {
9058 t->toolbar = create_toolbar(t);
9059 if (fancy_bar)
9060 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9061 FALSE, 0);
9064 /* marks */
9065 marks_clear(t);
9067 /* browser */
9068 t->browser_win = create_browser(t);
9069 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9071 /* oops message for user feedback */
9072 t->oops = gtk_entry_new();
9073 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9074 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9075 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9076 gdk_color_parse(XT_COLOR_RED, &color);
9077 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9078 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9079 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9081 /* command entry */
9082 t->cmd = gtk_entry_new();
9083 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9084 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9085 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9086 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9088 /* status bar */
9089 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9091 t->sbe.statusbar = gtk_entry_new();
9092 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9093 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9094 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9095 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9097 /* create these widgets only if specified in statusbar_elems */
9099 t->sbe.position = create_sbe(40);
9100 t->sbe.zoom = create_sbe(40);
9101 t->sbe.buffercmd = create_sbe(60);
9103 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9105 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9106 TRUE, FALSE);
9108 /* gtk widgets cannot be added to a box twice. sbe_* variables
9109 make sure of this */
9110 for (p = statusbar_elems; *p != '\0'; p++) {
9111 switch (*p) {
9112 case '|':
9114 GtkWidget *sep = gtk_vseparator_new();
9116 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9117 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9118 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9119 FALSE, FALSE, FALSE);
9120 break;
9122 case 'P':
9123 if (sbe_p) {
9124 warnx("flag \"%c\" specified more than "
9125 "once in statusbar_elems\n", *p);
9126 break;
9128 sbe_p = 1;
9129 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9130 t->sbe.position, FALSE, FALSE, FALSE);
9131 break;
9132 case 'B':
9133 if (sbe_b) {
9134 warnx("flag \"%c\" specified more than "
9135 "once in statusbar_elems\n", *p);
9136 break;
9138 sbe_b = 1;
9139 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9140 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9141 break;
9142 case 'Z':
9143 if (sbe_z) {
9144 warnx("flag \"%c\" specified more than "
9145 "once in statusbar_elems\n", *p);
9146 break;
9148 sbe_z = 1;
9149 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9150 t->sbe.zoom, FALSE, FALSE, FALSE);
9151 break;
9152 default:
9153 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9154 break;
9158 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9160 /* buffer list */
9161 t->buffers = create_buffers(t);
9162 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9164 /* xtp meaning is normal by default */
9165 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9167 /* set empty favicon */
9168 xt_icon_from_name(t, "text-html");
9170 /* and show it all */
9171 gtk_widget_show_all(b);
9172 gtk_widget_show_all(t->vbox);
9174 /* compact tab bar */
9175 t->tab_elems.label = gtk_label_new(title);
9176 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9177 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9178 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9179 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9181 t->tab_elems.eventbox = gtk_event_box_new();
9182 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9183 t->tab_elems.sep = gtk_vseparator_new();
9185 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9186 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9187 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9188 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9189 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9190 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9192 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9193 TRUE, 0);
9194 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9195 FALSE, 0);
9196 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9197 t->tab_elems.box);
9199 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9200 TRUE, 0);
9201 gtk_widget_show_all(t->tab_elems.eventbox);
9203 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9204 append_tab(t);
9205 else {
9206 id = position >= 0 ? position :
9207 gtk_notebook_get_current_page(notebook) + 1;
9208 if (id > gtk_notebook_get_n_pages(notebook))
9209 append_tab(t);
9210 else {
9211 TAILQ_INSERT_TAIL(&tabs, t, entry);
9212 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9213 gtk_box_reorder_child(GTK_BOX(tab_bar),
9214 t->tab_elems.eventbox, id);
9215 recalc_tabs();
9219 #if GTK_CHECK_VERSION(2, 20, 0)
9220 /* turn spinner off if we are a new tab without uri */
9221 if (!load) {
9222 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9223 gtk_widget_hide(t->spinner);
9225 #endif
9226 /* make notebook tabs reorderable */
9227 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9229 /* compact tabs clickable */
9230 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9231 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9233 g_object_connect(G_OBJECT(t->cmd),
9234 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9235 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9236 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9237 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9238 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9239 (char *)NULL);
9241 /* reuse wv_button_cb to hide oops */
9242 g_object_connect(G_OBJECT(t->oops),
9243 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9244 (char *)NULL);
9246 g_signal_connect(t->buffers,
9247 "row-activated", G_CALLBACK(row_activated_cb), t);
9248 g_object_connect(G_OBJECT(t->buffers),
9249 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9251 g_object_connect(G_OBJECT(t->wv),
9252 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9253 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9254 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9255 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9256 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9257 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9258 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9259 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9260 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9261 "signal::event", G_CALLBACK(webview_event_cb), t,
9262 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9263 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9264 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9265 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9266 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9267 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9268 (char *)NULL);
9269 g_signal_connect(t->wv,
9270 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9272 * XXX this puts invalid url in uri_entry and that is undesirable
9274 #if 0
9275 g_signal_connect(t->wv,
9276 "load-error", G_CALLBACK(notify_load_error_cb), t);
9277 #endif
9278 g_signal_connect(t->wv,
9279 "notify::title", G_CALLBACK(notify_title_cb), t);
9281 /* hijack the unused keys as if we were the browser */
9282 g_object_connect(G_OBJECT(t->toolbar),
9283 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9284 (char *)NULL);
9286 g_signal_connect(G_OBJECT(bb), "button_press_event",
9287 G_CALLBACK(tab_close_cb), t);
9289 /* setup history */
9290 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9291 /* restore the tab's history */
9292 if (u && u->history) {
9293 items = u->history;
9294 while (items) {
9295 item = items->data;
9296 webkit_web_back_forward_list_add_item(t->bfl, item);
9297 items = g_list_next(items);
9300 item = g_list_nth_data(u->history, u->back);
9301 if (item)
9302 webkit_web_view_go_to_back_forward_item(t->wv, item);
9304 g_list_free(items);
9305 g_list_free(u->history);
9306 } else
9307 webkit_web_back_forward_list_clear(t->bfl);
9309 /* hide stuff */
9310 hide_cmd(t);
9311 hide_oops(t);
9312 hide_buffers(t);
9313 url_set_visibility();
9314 statusbar_set_visibility();
9316 if (focus) {
9317 set_current_tab(t->tab_id);
9318 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9319 t->tab_id);
9322 if (load) {
9323 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9324 load_uri(t, title);
9325 } else {
9326 if (show_url == 1)
9327 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9328 else
9329 focus_webview(t);
9332 recolor_compact_tabs();
9333 setzoom_webkit(t, XT_ZOOM_NORMAL);
9334 return (t);
9337 void
9338 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9339 gpointer *udata)
9341 struct tab *t;
9342 const gchar *uri;
9344 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9346 if (gtk_notebook_get_current_page(notebook) == -1)
9347 recalc_tabs();
9349 TAILQ_FOREACH(t, &tabs, entry) {
9350 if (t->tab_id == pn) {
9351 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9352 "%d\n", pn);
9354 uri = get_title(t, TRUE);
9355 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9357 hide_cmd(t);
9358 hide_oops(t);
9360 if (t->focus_wv) {
9361 /* can't use focus_webview here */
9362 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9368 void
9369 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9370 gpointer *udata)
9372 struct tab *t = NULL, *tt;
9374 recalc_tabs();
9376 TAILQ_FOREACH(tt, &tabs, entry)
9377 if (tt->tab_id == pn) {
9378 t = tt;
9379 break;
9382 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9384 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9385 t->tab_id);
9388 void
9389 menuitem_response(struct tab *t)
9391 gtk_notebook_set_current_page(notebook, t->tab_id);
9394 gboolean
9395 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9397 GtkWidget *menu, *menu_items;
9398 GdkEventButton *bevent;
9399 const gchar *uri;
9400 struct tab *ti;
9402 if (event->type == GDK_BUTTON_PRESS) {
9403 bevent = (GdkEventButton *) event;
9404 menu = gtk_menu_new();
9406 TAILQ_FOREACH(ti, &tabs, entry) {
9407 if ((uri = get_uri(ti)) == NULL)
9408 /* XXX make sure there is something to print */
9409 /* XXX add gui pages in here to look purdy */
9410 uri = "(untitled)";
9411 menu_items = gtk_menu_item_new_with_label(uri);
9412 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9413 gtk_widget_show(menu_items);
9415 g_signal_connect_swapped((menu_items),
9416 "activate", G_CALLBACK(menuitem_response),
9417 (gpointer)ti);
9420 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9421 bevent->button, bevent->time);
9423 /* unref object so it'll free itself when popped down */
9424 #if !GTK_CHECK_VERSION(3, 0, 0)
9425 /* XXX does not need unref with gtk+3? */
9426 g_object_ref_sink(menu);
9427 g_object_unref(menu);
9428 #endif
9430 return (TRUE /* eat event */);
9433 return (FALSE /* propagate */);
9437 icon_size_map(int icon_size)
9439 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9440 icon_size > GTK_ICON_SIZE_DIALOG)
9441 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9443 return (icon_size);
9446 GtkWidget *
9447 create_button(char *name, char *stockid, int size)
9449 GtkWidget *button, *image;
9450 gchar *rcstring;
9451 int gtk_icon_size;
9453 rcstring = g_strdup_printf(
9454 "style \"%s-style\"\n"
9455 "{\n"
9456 " GtkWidget::focus-padding = 0\n"
9457 " GtkWidget::focus-line-width = 0\n"
9458 " xthickness = 0\n"
9459 " ythickness = 0\n"
9460 "}\n"
9461 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9462 gtk_rc_parse_string(rcstring);
9463 g_free(rcstring);
9464 button = gtk_button_new();
9465 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9466 gtk_icon_size = icon_size_map(size ? size : icon_size);
9468 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9469 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9470 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9471 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9472 gtk_widget_set_name(button, name);
9473 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9475 return (button);
9478 void
9479 button_set_stockid(GtkWidget *button, char *stockid)
9481 GtkWidget *image;
9483 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9484 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9485 gtk_button_set_image(GTK_BUTTON(button), image);
9488 void
9489 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9491 gchar *p = NULL;
9492 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9493 gint len;
9495 if (xterm_workaround == 0)
9496 return;
9499 * xterm doesn't play nice with clipboards because it clears the
9500 * primary when clicked. We rely on primary being set to properly
9501 * handle middle mouse button clicks (paste). So when someone clears
9502 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9503 * other application behavior (as in DON'T clear primary).
9506 p = gtk_clipboard_wait_for_text(primary);
9507 if (p == NULL) {
9508 if (gdk_property_get(gdk_get_default_root_window(),
9509 atom,
9510 gdk_atom_intern("STRING", FALSE),
9512 1024 * 1024 /* picked out of my butt */,
9513 FALSE,
9514 NULL,
9515 NULL,
9516 &len,
9517 (guchar **)&p)) {
9518 /* yes sir, we need to NUL the string */
9519 p[len] = '\0';
9520 gtk_clipboard_set_text(primary, p, -1);
9524 if (p)
9525 g_free(p);
9528 void
9529 create_canvas(void)
9531 GtkWidget *vbox;
9532 GList *l = NULL;
9533 GdkPixbuf *pb;
9534 char file[PATH_MAX];
9535 int i;
9537 vbox = gtk_vbox_new(FALSE, 0);
9538 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9539 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9540 #if !GTK_CHECK_VERSION(3, 0, 0)
9541 /* XXX seems to be needed with gtk+2 */
9542 gtk_notebook_set_tab_hborder(notebook, 0);
9543 gtk_notebook_set_tab_vborder(notebook, 0);
9544 #endif
9545 gtk_notebook_set_scrollable(notebook, TRUE);
9546 gtk_notebook_set_show_border(notebook, FALSE);
9547 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9549 abtn = gtk_button_new();
9550 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9551 gtk_widget_set_size_request(arrow, -1, -1);
9552 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9553 gtk_widget_set_size_request(abtn, -1, 20);
9555 #if GTK_CHECK_VERSION(2, 20, 0)
9556 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9557 #endif
9558 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9560 /* compact tab bar */
9561 tab_bar = gtk_hbox_new(TRUE, 0);
9563 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9564 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9565 gtk_widget_set_size_request(vbox, -1, -1);
9567 g_object_connect(G_OBJECT(notebook),
9568 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9569 (char *)NULL);
9570 g_object_connect(G_OBJECT(notebook),
9571 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9572 NULL, (char *)NULL);
9573 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9574 G_CALLBACK(arrow_cb), NULL);
9576 main_window = create_window();
9577 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9579 /* icons */
9580 for (i = 0; i < LENGTH(icons); i++) {
9581 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9582 pb = gdk_pixbuf_new_from_file(file, NULL);
9583 l = g_list_append(l, pb);
9585 gtk_window_set_default_icon_list(l);
9587 /* clipboard work around */
9588 if (xterm_workaround)
9589 g_signal_connect(
9590 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9591 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9593 gtk_widget_show_all(abtn);
9594 gtk_widget_show_all(main_window);
9595 notebook_tab_set_visibility();
9598 void
9599 set_hook(void **hook, char *name)
9601 if (hook == NULL)
9602 errx(1, "set_hook");
9604 if (*hook == NULL) {
9605 *hook = dlsym(RTLD_NEXT, name);
9606 if (*hook == NULL)
9607 errx(1, "can't hook %s", name);
9611 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9612 gboolean
9613 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9615 g_return_val_if_fail(cookie1, FALSE);
9616 g_return_val_if_fail(cookie2, FALSE);
9618 return (!strcmp (cookie1->name, cookie2->name) &&
9619 !strcmp (cookie1->value, cookie2->value) &&
9620 !strcmp (cookie1->path, cookie2->path) &&
9621 !strcmp (cookie1->domain, cookie2->domain));
9624 void
9625 transfer_cookies(void)
9627 GSList *cf;
9628 SoupCookie *sc, *pc;
9630 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9632 for (;cf; cf = cf->next) {
9633 pc = cf->data;
9634 sc = soup_cookie_copy(pc);
9635 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9638 soup_cookies_free(cf);
9641 void
9642 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9644 GSList *cf;
9645 SoupCookie *ci;
9647 print_cookie("soup_cookie_jar_delete_cookie", c);
9649 if (cookies_enabled == 0)
9650 return;
9652 if (jar == NULL || c == NULL)
9653 return;
9655 /* find and remove from persistent jar */
9656 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9658 for (;cf; cf = cf->next) {
9659 ci = cf->data;
9660 if (soup_cookie_equal(ci, c)) {
9661 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9662 break;
9666 soup_cookies_free(cf);
9668 /* delete from session jar */
9669 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9672 void
9673 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9675 struct domain *d = NULL;
9676 SoupCookie *c;
9677 FILE *r_cookie_f;
9679 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9680 jar, p_cookiejar, s_cookiejar);
9682 if (cookies_enabled == 0)
9683 return;
9685 /* see if we are up and running */
9686 if (p_cookiejar == NULL) {
9687 _soup_cookie_jar_add_cookie(jar, cookie);
9688 return;
9690 /* disallow p_cookiejar adds, shouldn't happen */
9691 if (jar == p_cookiejar)
9692 return;
9694 /* sanity */
9695 if (jar == NULL || cookie == NULL)
9696 return;
9698 if (enable_cookie_whitelist &&
9699 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9700 blocked_cookies++;
9701 DNPRINTF(XT_D_COOKIE,
9702 "soup_cookie_jar_add_cookie: reject %s\n",
9703 cookie->domain);
9704 if (save_rejected_cookies) {
9705 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9706 show_oops(NULL, "can't open reject cookie file");
9707 return;
9709 fseek(r_cookie_f, 0, SEEK_END);
9710 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9711 cookie->http_only ? "#HttpOnly_" : "",
9712 cookie->domain,
9713 *cookie->domain == '.' ? "TRUE" : "FALSE",
9714 cookie->path,
9715 cookie->secure ? "TRUE" : "FALSE",
9716 cookie->expires ?
9717 (gulong)soup_date_to_time_t(cookie->expires) :
9719 cookie->name,
9720 cookie->value);
9721 fflush(r_cookie_f);
9722 fclose(r_cookie_f);
9724 if (!allow_volatile_cookies)
9725 return;
9728 if (cookie->expires == NULL && session_timeout) {
9729 soup_cookie_set_expires(cookie,
9730 soup_date_new_from_now(session_timeout));
9731 print_cookie("modified add cookie", cookie);
9734 /* see if we are white listed for persistence */
9735 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9736 /* add to persistent jar */
9737 c = soup_cookie_copy(cookie);
9738 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9739 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9742 /* add to session jar */
9743 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9744 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9747 void
9748 setup_cookies(void)
9750 char file[PATH_MAX];
9752 set_hook((void *)&_soup_cookie_jar_add_cookie,
9753 "soup_cookie_jar_add_cookie");
9754 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9755 "soup_cookie_jar_delete_cookie");
9757 if (cookies_enabled == 0)
9758 return;
9761 * the following code is intricate due to overriding several libsoup
9762 * functions.
9763 * do not alter order of these operations.
9766 /* rejected cookies */
9767 if (save_rejected_cookies)
9768 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9769 XT_REJECT_FILE);
9771 /* persistent cookies */
9772 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9773 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9775 /* session cookies */
9776 s_cookiejar = soup_cookie_jar_new();
9777 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9778 cookie_policy, (void *)NULL);
9779 transfer_cookies();
9781 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9784 void
9785 setup_proxy(char *uri)
9787 if (proxy_uri) {
9788 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9789 soup_uri_free(proxy_uri);
9790 proxy_uri = NULL;
9792 if (http_proxy) {
9793 if (http_proxy != uri) {
9794 g_free(http_proxy);
9795 http_proxy = NULL;
9799 if (uri) {
9800 http_proxy = g_strdup(uri);
9801 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9802 proxy_uri = soup_uri_new(http_proxy);
9803 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9804 g_object_set(session, "proxy-uri", proxy_uri,
9805 (char *)NULL);
9810 set_http_proxy(char *proxy)
9812 SoupURI *uri;
9814 if (proxy == NULL)
9815 return (1);
9817 /* see if we need to clear it instead */
9818 if (strlen(proxy) == 0) {
9819 setup_proxy(NULL);
9820 return (0);
9823 uri = soup_uri_new(proxy);
9824 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9825 return (1);
9827 setup_proxy(proxy);
9829 soup_uri_free(uri);
9831 return (0);
9835 send_cmd_to_socket(char *cmd)
9837 int s, len, rv = 1;
9838 struct sockaddr_un sa;
9840 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9841 warnx("%s: socket", __func__);
9842 return (rv);
9845 sa.sun_family = AF_UNIX;
9846 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9847 work_dir, XT_SOCKET_FILE);
9848 len = SUN_LEN(&sa);
9850 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9851 warnx("%s: connect", __func__);
9852 goto done;
9855 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9856 warnx("%s: send", __func__);
9857 goto done;
9860 rv = 0;
9861 done:
9862 close(s);
9863 return (rv);
9866 gboolean
9867 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9869 int s, n;
9870 char str[XT_MAX_URL_LENGTH];
9871 socklen_t t = sizeof(struct sockaddr_un);
9872 struct sockaddr_un sa;
9873 struct passwd *p;
9874 uid_t uid;
9875 gid_t gid;
9876 struct tab *tt;
9877 gint fd = g_io_channel_unix_get_fd(source);
9879 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9880 warn("accept");
9881 return (FALSE);
9884 if (getpeereid(s, &uid, &gid) == -1) {
9885 warn("getpeereid");
9886 return (FALSE);
9888 if (uid != getuid() || gid != getgid()) {
9889 warnx("unauthorized user");
9890 return (FALSE);
9893 p = getpwuid(uid);
9894 if (p == NULL) {
9895 warnx("not a valid user");
9896 return (FALSE);
9899 n = recv(s, str, sizeof(str), 0);
9900 if (n <= 0)
9901 return (TRUE);
9903 tt = TAILQ_LAST(&tabs, tab_list);
9904 cmd_execute(tt, str);
9905 return (TRUE);
9909 is_running(void)
9911 int s, len, rv = 1;
9912 struct sockaddr_un sa;
9914 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9915 warn("is_running: socket");
9916 return (-1);
9919 sa.sun_family = AF_UNIX;
9920 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9921 work_dir, XT_SOCKET_FILE);
9922 len = SUN_LEN(&sa);
9924 /* connect to see if there is a listener */
9925 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9926 rv = 0; /* not running */
9927 else
9928 rv = 1; /* already running */
9930 close(s);
9932 return (rv);
9936 build_socket(void)
9938 int s, len;
9939 struct sockaddr_un sa;
9941 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9942 warn("build_socket: socket");
9943 return (-1);
9946 sa.sun_family = AF_UNIX;
9947 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9948 work_dir, XT_SOCKET_FILE);
9949 len = SUN_LEN(&sa);
9951 /* connect to see if there is a listener */
9952 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9953 /* no listener so we will */
9954 unlink(sa.sun_path);
9956 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9957 warn("build_socket: bind");
9958 goto done;
9961 if (listen(s, 1) == -1) {
9962 warn("build_socket: listen");
9963 goto done;
9966 return (s);
9969 done:
9970 close(s);
9971 return (-1);
9974 gboolean
9975 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9976 GtkTreeIter *iter, struct tab *t)
9978 gchar *value;
9980 gtk_tree_model_get(model, iter, 0, &value, -1);
9981 load_uri(t, value);
9982 g_free(value);
9984 return (FALSE);
9987 gboolean
9988 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9989 GtkTreeIter *iter, struct tab *t)
9991 gchar *value;
9993 gtk_tree_model_get(model, iter, 0, &value, -1);
9994 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9995 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9996 g_free(value);
9998 return (TRUE);
10001 void
10002 completion_add_uri(const gchar *uri)
10004 GtkTreeIter iter;
10006 /* add uri to list_store */
10007 gtk_list_store_append(completion_model, &iter);
10008 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10011 gboolean
10012 completion_match(GtkEntryCompletion *completion, const gchar *key,
10013 GtkTreeIter *iter, gpointer user_data)
10015 gchar *value;
10016 gboolean match = FALSE;
10018 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10019 -1);
10021 if (value == NULL)
10022 return FALSE;
10024 match = match_uri(value, key);
10026 g_free(value);
10027 return (match);
10030 void
10031 completion_add(struct tab *t)
10033 /* enable completion for tab */
10034 t->completion = gtk_entry_completion_new();
10035 gtk_entry_completion_set_text_column(t->completion, 0);
10036 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10037 gtk_entry_completion_set_model(t->completion,
10038 GTK_TREE_MODEL(completion_model));
10039 gtk_entry_completion_set_match_func(t->completion, completion_match,
10040 NULL, NULL);
10041 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10042 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10043 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10044 G_CALLBACK(completion_select_cb), t);
10045 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10046 G_CALLBACK(completion_hover_cb), t);
10049 void
10050 xxx_dir(char *dir)
10052 struct stat sb;
10054 if (stat(dir, &sb)) {
10055 if (mkdir(dir, S_IRWXU) == -1)
10056 err(1, "mkdir %s", dir);
10057 if (stat(dir, &sb))
10058 err(1, "stat %s", dir);
10060 if (S_ISDIR(sb.st_mode) == 0)
10061 errx(1, "%s not a dir", dir);
10062 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10063 warnx("fixing invalid permissions on %s", dir);
10064 if (chmod(dir, S_IRWXU) == -1)
10065 err(1, "chmod %s", dir);
10069 void
10070 usage(void)
10072 fprintf(stderr,
10073 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10074 exit(0);
10079 main(int argc, char *argv[])
10081 struct stat sb;
10082 int c, s, optn = 0, opte = 0, focus = 1;
10083 char conf[PATH_MAX] = { '\0' };
10084 char file[PATH_MAX];
10085 char *env_proxy = NULL;
10086 char *cmd = NULL;
10087 FILE *f = NULL;
10088 struct karg a;
10089 struct sigaction sact;
10090 GIOChannel *channel;
10091 struct rlimit rlp;
10093 start_argv = argv;
10095 /* prepare gtk */
10096 gtk_init(&argc, &argv);
10098 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10100 RB_INIT(&hl);
10101 RB_INIT(&js_wl);
10102 RB_INIT(&downloads);
10104 TAILQ_INIT(&sessions);
10105 TAILQ_INIT(&tabs);
10106 TAILQ_INIT(&mtl);
10107 TAILQ_INIT(&aliases);
10108 TAILQ_INIT(&undos);
10109 TAILQ_INIT(&kbl);
10110 TAILQ_INIT(&spl);
10111 TAILQ_INIT(&chl);
10112 TAILQ_INIT(&shl);
10114 /* fiddle with ulimits */
10115 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10116 warn("getrlimit");
10117 else {
10118 /* just use them all */
10119 rlp.rlim_cur = rlp.rlim_max;
10120 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10121 warn("setrlimit");
10122 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10123 warn("getrlimit");
10124 else if (rlp.rlim_cur <= 256)
10125 startpage_add("%s requires at least 256 file "
10126 "descriptors, currently it has up to %d available",
10127 __progname, rlp.rlim_cur);
10130 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10131 switch (c) {
10132 case 'S':
10133 show_url = 0;
10134 break;
10135 case 'T':
10136 show_tabs = 0;
10137 break;
10138 case 'V':
10139 errx(0 , "Version: %s", version);
10140 break;
10141 case 'f':
10142 strlcpy(conf, optarg, sizeof(conf));
10143 break;
10144 case 's':
10145 strlcpy(named_session, optarg, sizeof(named_session));
10146 break;
10147 case 't':
10148 tabless = 1;
10149 break;
10150 case 'n':
10151 optn = 1;
10152 break;
10153 case 'e':
10154 opte = 1;
10155 break;
10156 default:
10157 usage();
10158 /* NOTREACHED */
10161 argc -= optind;
10162 argv += optind;
10164 init_keybindings();
10166 gnutls_global_init();
10168 /* generate session keys for xtp pages */
10169 generate_xtp_session_key(&dl_session_key);
10170 generate_xtp_session_key(&hl_session_key);
10171 generate_xtp_session_key(&cl_session_key);
10172 generate_xtp_session_key(&fl_session_key);
10174 /* signals */
10175 bzero(&sact, sizeof(sact));
10176 sigemptyset(&sact.sa_mask);
10177 sact.sa_handler = sigchild;
10178 sact.sa_flags = SA_NOCLDSTOP;
10179 sigaction(SIGCHLD, &sact, NULL);
10181 /* set download dir */
10182 pwd = getpwuid(getuid());
10183 if (pwd == NULL)
10184 errx(1, "invalid user %d", getuid());
10185 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10187 /* compile buffer command regexes */
10188 buffercmd_init();
10190 /* set default string settings */
10191 home = g_strdup("https://www.cyphertite.com");
10192 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10193 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10194 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10195 cmd_font_name = g_strdup("monospace normal 9");
10196 oops_font_name = g_strdup("monospace normal 9");
10197 statusbar_font_name = g_strdup("monospace normal 9");
10198 tabbar_font_name = g_strdup("monospace normal 9");
10199 statusbar_elems = g_strdup("BP");
10201 /* read config file */
10202 if (strlen(conf) == 0)
10203 snprintf(conf, sizeof conf, "%s/.%s",
10204 pwd->pw_dir, XT_CONF_FILE);
10205 config_parse(conf, 0);
10207 /* init fonts */
10208 cmd_font = pango_font_description_from_string(cmd_font_name);
10209 oops_font = pango_font_description_from_string(oops_font_name);
10210 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10211 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10213 /* working directory */
10214 if (strlen(work_dir) == 0)
10215 snprintf(work_dir, sizeof work_dir, "%s/%s",
10216 pwd->pw_dir, XT_DIR);
10217 xxx_dir(work_dir);
10219 /* icon cache dir */
10220 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10221 xxx_dir(cache_dir);
10223 /* certs dir */
10224 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10225 xxx_dir(certs_dir);
10227 /* sessions dir */
10228 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10229 work_dir, XT_SESSIONS_DIR);
10230 xxx_dir(sessions_dir);
10232 /* runtime settings that can override config file */
10233 if (runtime_settings[0] != '\0')
10234 config_parse(runtime_settings, 1);
10236 /* download dir */
10237 if (!strcmp(download_dir, pwd->pw_dir))
10238 strlcat(download_dir, "/downloads", sizeof download_dir);
10239 xxx_dir(download_dir);
10241 /* favorites file */
10242 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10243 if (stat(file, &sb)) {
10244 warnx("favorites file doesn't exist, creating it");
10245 if ((f = fopen(file, "w")) == NULL)
10246 err(1, "favorites");
10247 fclose(f);
10250 /* quickmarks file */
10251 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10252 if (stat(file, &sb)) {
10253 warnx("quickmarks file doesn't exist, creating it");
10254 if ((f = fopen(file, "w")) == NULL)
10255 err(1, "quickmarks");
10256 fclose(f);
10259 /* search history */
10260 if (history_autosave) {
10261 snprintf(search_file, sizeof search_file, "%s/%s",
10262 work_dir, XT_SEARCH_FILE);
10263 if (stat(search_file, &sb)) {
10264 warnx("search history file doesn't exist, creating it");
10265 if ((f = fopen(search_file, "w")) == NULL)
10266 err(1, "search_history");
10267 fclose(f);
10269 history_read(&shl, search_file, &search_history_count);
10272 /* command history */
10273 if (history_autosave) {
10274 snprintf(command_file, sizeof command_file, "%s/%s",
10275 work_dir, XT_COMMAND_FILE);
10276 if (stat(command_file, &sb)) {
10277 warnx("command history file doesn't exist, creating it");
10278 if ((f = fopen(command_file, "w")) == NULL)
10279 err(1, "command_history");
10280 fclose(f);
10282 history_read(&chl, command_file, &cmd_history_count);
10285 /* cookies */
10286 session = webkit_get_default_session();
10287 setup_cookies();
10289 /* certs */
10290 if (ssl_ca_file) {
10291 if (stat(ssl_ca_file, &sb)) {
10292 warnx("no CA file: %s", ssl_ca_file);
10293 g_free(ssl_ca_file);
10294 ssl_ca_file = NULL;
10295 } else
10296 g_object_set(session,
10297 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10298 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10299 (void *)NULL);
10302 /* guess_search regex */
10303 if (url_regex == NULL)
10304 url_regex = g_strdup(XT_URL_REGEX);
10305 if (url_regex)
10306 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10307 startpage_add("invalid url regex %s", url_regex);
10309 /* proxy */
10310 env_proxy = getenv("http_proxy");
10311 if (env_proxy)
10312 setup_proxy(env_proxy);
10313 else
10314 setup_proxy(http_proxy);
10316 if (opte) {
10317 send_cmd_to_socket(argv[0]);
10318 exit(0);
10321 /* set some connection parameters */
10322 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10323 g_object_set(session, "max-conns-per-host", max_host_connections,
10324 (char *)NULL);
10326 /* see if there is already an xxxterm running */
10327 if (single_instance && is_running()) {
10328 optn = 1;
10329 warnx("already running");
10332 if (optn) {
10333 while (argc) {
10334 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10335 send_cmd_to_socket(cmd);
10336 if (cmd)
10337 g_free(cmd);
10339 argc--;
10340 argv++;
10342 exit(0);
10345 /* uri completion */
10346 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10348 /* buffers */
10349 buffers_store = gtk_list_store_new
10350 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10352 qmarks_load();
10354 /* go graphical */
10355 create_canvas();
10356 notebook_tab_set_visibility();
10358 if (save_global_history)
10359 restore_global_history();
10361 /* restore session list */
10362 restore_sessions_list();
10364 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10365 restore_saved_tabs();
10366 else {
10367 a.s = named_session;
10368 a.i = XT_SES_DONOTHING;
10369 open_tabs(NULL, &a);
10372 /* see if we have an exception */
10373 if (!TAILQ_EMPTY(&spl)) {
10374 create_new_tab("about:startpage", NULL, focus, -1);
10375 focus = 0;
10378 while (argc) {
10379 create_new_tab(argv[0], NULL, focus, -1);
10380 focus = 0;
10382 argc--;
10383 argv++;
10386 if (TAILQ_EMPTY(&tabs))
10387 create_new_tab(home, NULL, 1, -1);
10389 if (enable_socket)
10390 if ((s = build_socket()) != -1) {
10391 channel = g_io_channel_unix_new(s);
10392 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10395 gtk_main();
10397 gnutls_global_deinit();
10399 if (url_regex)
10400 regfree(&url_re);
10402 return (0);