fix mime_type on linux that got clobbered
[xxxterm.git] / xxxterm.c
blob88b0a14e9a25e13e6889513ea0b5bfe69b435c87
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * create privacy browsing
26 * - encrypted local data
29 #include <ctype.h>
30 #include <dlfcn.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <libgen.h>
34 #include <pwd.h>
35 #include <regex.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <dirent.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #include <bsd/stdlib.h>
49 # if !defined(sane_libbsd_headers)
50 void arc4random_buf(void *, size_t);
51 # endif
52 #elif defined(__FreeBSD__)
53 #include <libutil.h>
54 #include "freebsd/util.h"
55 #include <sys/tree.h>
56 #else /* OpenBSD */
57 #include <util.h>
58 #include <sys/tree.h>
59 #endif
60 #include <sys/queue.h>
61 #include <sys/resource.h>
62 #include <sys/socket.h>
63 #include <sys/stat.h>
64 #include <sys/time.h>
65 #include <sys/un.h>
67 #include <gtk/gtk.h>
68 #include <gdk/gdkkeysyms.h>
70 #if GTK_CHECK_VERSION(3,0,0)
71 /* we still use GDK_* instead of GDK_KEY_* */
72 #include <gdk/gdkkeysyms-compat.h>
73 #endif
75 #include <webkit/webkit.h>
76 #include <libsoup/soup.h>
77 #include <gnutls/gnutls.h>
78 #include <JavaScriptCore/JavaScript.h>
79 #include <gnutls/x509.h>
81 #include "version.h"
82 #include "javascript.h"
85 javascript.h borrowed from vimprobable2 under the following license:
87 Copyright (c) 2009 Leon Winter
88 Copyright (c) 2009 Hannes Schueller
89 Copyright (c) 2009 Matto Fransen
91 Permission is hereby granted, free of charge, to any person obtaining a copy
92 of this software and associated documentation files (the "Software"), to deal
93 in the Software without restriction, including without limitation the rights
94 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
95 copies of the Software, and to permit persons to whom the Software is
96 furnished to do so, subject to the following conditions:
98 The above copyright notice and this permission notice shall be included in
99 all copies or substantial portions of the Software.
101 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
102 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
103 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
104 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
105 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
106 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
107 THE SOFTWARE.
110 static char *version = XXXTERM_VERSION;
112 /* hooked functions */
113 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
114 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
115 SoupCookie *);
117 /*#define XT_DEBUG*/
118 #ifdef XT_DEBUG
119 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
120 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
121 #define XT_D_MOVE 0x0001
122 #define XT_D_KEY 0x0002
123 #define XT_D_TAB 0x0004
124 #define XT_D_URL 0x0008
125 #define XT_D_CMD 0x0010
126 #define XT_D_NAV 0x0020
127 #define XT_D_DOWNLOAD 0x0040
128 #define XT_D_CONFIG 0x0080
129 #define XT_D_JS 0x0100
130 #define XT_D_FAVORITE 0x0200
131 #define XT_D_PRINTING 0x0400
132 #define XT_D_COOKIE 0x0800
133 #define XT_D_KEYBINDING 0x1000
134 #define XT_D_CLIP 0x2000
135 #define XT_D_BUFFERCMD 0x4000
136 u_int32_t swm_debug = 0
137 | XT_D_MOVE
138 | XT_D_KEY
139 | XT_D_TAB
140 | XT_D_URL
141 | XT_D_CMD
142 | XT_D_NAV
143 | XT_D_DOWNLOAD
144 | XT_D_CONFIG
145 | XT_D_JS
146 | XT_D_FAVORITE
147 | XT_D_PRINTING
148 | XT_D_COOKIE
149 | XT_D_KEYBINDING
150 | XT_D_CLIP
151 | XT_D_BUFFERCMD
153 #else
154 #define DPRINTF(x...)
155 #define DNPRINTF(n,x...)
156 #endif
158 #define LENGTH(x) (sizeof x / sizeof x[0])
159 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
160 ~(GDK_BUTTON1_MASK) & \
161 ~(GDK_BUTTON2_MASK) & \
162 ~(GDK_BUTTON3_MASK) & \
163 ~(GDK_BUTTON4_MASK) & \
164 ~(GDK_BUTTON5_MASK))
166 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
168 char *icons[] = {
169 "xxxtermicon16.png",
170 "xxxtermicon32.png",
171 "xxxtermicon48.png",
172 "xxxtermicon64.png",
173 "xxxtermicon128.png"
176 struct tab {
177 TAILQ_ENTRY(tab) entry;
178 GtkWidget *vbox;
179 GtkWidget *tab_content;
180 struct {
181 GtkWidget *label;
182 GtkWidget *eventbox;
183 GtkWidget *box;
184 GtkWidget *sep;
185 } tab_elems;
186 GtkWidget *label;
187 GtkWidget *spinner;
188 GtkWidget *uri_entry;
189 GtkWidget *search_entry;
190 GtkWidget *toolbar;
191 GtkWidget *browser_win;
192 GtkWidget *statusbar_box;
193 struct {
194 GtkWidget *statusbar;
195 GtkWidget *buffercmd;
196 GtkWidget *zoom;
197 GtkWidget *position;
198 } sbe;
199 GtkWidget *cmd;
200 GtkWidget *buffers;
201 GtkWidget *oops;
202 GtkWidget *backward;
203 GtkWidget *forward;
204 GtkWidget *stop;
205 GtkWidget *gohome;
206 GtkWidget *js_toggle;
207 GtkEntryCompletion *completion;
208 guint tab_id;
209 WebKitWebView *wv;
211 WebKitWebHistoryItem *item;
212 WebKitWebBackForwardList *bfl;
214 /* favicon */
215 WebKitNetworkRequest *icon_request;
216 WebKitDownload *icon_download;
217 gchar *icon_dest_uri;
219 /* adjustments for browser */
220 GtkScrollbar *sb_h;
221 GtkScrollbar *sb_v;
222 GtkAdjustment *adjust_h;
223 GtkAdjustment *adjust_v;
225 /* flags */
226 int focus_wv;
227 int ctrl_click;
228 gchar *status;
229 int xtp_meaning; /* identifies dls/favorites */
230 gchar *tmp_uri;
231 int popup; /* 1 if cmd_entry has popup visible */
233 /* https thread stuff */
234 GThread *thread;
236 /* hints */
237 int hints_on;
238 int hint_mode;
239 #define XT_HINT_NONE (0)
240 #define XT_HINT_NUMERICAL (1)
241 #define XT_HINT_ALPHANUM (2)
242 char hint_buf[128];
243 char hint_num[128];
245 /* custom stylesheet */
246 int styled;
247 char *stylesheet;
249 /* search */
250 char *search_text;
251 int search_forward;
252 guint search_id;
254 /* settings */
255 WebKitWebSettings *settings;
256 gchar *user_agent;
258 /* marks */
259 double mark[XT_NOMARKS];
261 TAILQ_HEAD(tab_list, tab);
263 struct history {
264 RB_ENTRY(history) entry;
265 const gchar *uri;
266 const gchar *title;
268 RB_HEAD(history_list, history);
270 struct session {
271 TAILQ_ENTRY(session) entry;
272 const gchar *name;
274 TAILQ_HEAD(session_list, session);
276 struct download {
277 RB_ENTRY(download) entry;
278 int id;
279 WebKitDownload *download;
280 struct tab *tab;
282 RB_HEAD(download_list, download);
284 struct domain {
285 RB_ENTRY(domain) entry;
286 gchar *d;
287 int handy; /* app use */
289 RB_HEAD(domain_list, domain);
291 struct undo {
292 TAILQ_ENTRY(undo) entry;
293 gchar *uri;
294 GList *history;
295 int back; /* Keeps track of how many back
296 * history items there are. */
298 TAILQ_HEAD(undo_tailq, undo);
300 struct sp {
301 char *line;
302 TAILQ_ENTRY(sp) entry;
304 TAILQ_HEAD(sp_list, sp);
306 struct command_entry {
307 char *line;
308 TAILQ_ENTRY(command_entry) entry;
310 TAILQ_HEAD(command_list, command_entry);
312 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
313 int next_download_id = 1;
315 struct karg {
316 int i;
317 char *s;
318 int precount;
321 /* defines */
322 #define XT_NAME ("XXXTerm")
323 #define XT_DIR (".xxxterm")
324 #define XT_CACHE_DIR ("cache")
325 #define XT_CERT_DIR ("certs/")
326 #define XT_SESSIONS_DIR ("sessions/")
327 #define XT_CONF_FILE ("xxxterm.conf")
328 #define XT_FAVS_FILE ("favorites")
329 #define XT_QMARKS_FILE ("quickmarks")
330 #define XT_SAVED_TABS_FILE ("main_session")
331 #define XT_RESTART_TABS_FILE ("restart_tabs")
332 #define XT_SOCKET_FILE ("socket")
333 #define XT_HISTORY_FILE ("history")
334 #define XT_REJECT_FILE ("rejected.txt")
335 #define XT_COOKIE_FILE ("cookies.txt")
336 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
337 #define XT_SEARCH_FILE ("search_history")
338 #define XT_COMMAND_FILE ("command_history")
339 #define XT_CB_HANDLED (TRUE)
340 #define XT_CB_PASSTHROUGH (FALSE)
341 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
342 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
343 #define XT_DLMAN_REFRESH "10"
344 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
345 "td{overflow: hidden;" \
346 " padding: 2px 2px 2px 2px;" \
347 " border: 1px solid black;" \
348 " vertical-align:top;" \
349 " word-wrap: break-word}\n" \
350 "tr:hover{background: #ffff99}\n" \
351 "th{background-color: #cccccc;" \
352 " border: 1px solid black}\n" \
353 "table{width: 100%%;" \
354 " border: 1px black solid;" \
355 " border-collapse:collapse}\n" \
356 ".progress-outer{" \
357 "border: 1px solid black;" \
358 " height: 8px;" \
359 " width: 90%%}\n" \
360 ".progress-inner{float: left;" \
361 " height: 8px;" \
362 " background: green}\n" \
363 ".dlstatus{font-size: small;" \
364 " text-align: center}\n" \
365 "</style>\n"
366 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
367 #define XT_MAX_UNDO_CLOSE_TAB (32)
368 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
369 #define XT_PRINT_EXTRA_MARGIN 10
370 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
371 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
373 /* colors */
374 #define XT_COLOR_RED "#cc0000"
375 #define XT_COLOR_YELLOW "#ffff66"
376 #define XT_COLOR_BLUE "lightblue"
377 #define XT_COLOR_GREEN "#99ff66"
378 #define XT_COLOR_WHITE "white"
379 #define XT_COLOR_BLACK "black"
381 #define XT_COLOR_CT_BACKGROUND "#000000"
382 #define XT_COLOR_CT_INACTIVE "#dddddd"
383 #define XT_COLOR_CT_ACTIVE "#bbbb00"
384 #define XT_COLOR_CT_SEPARATOR "#555555"
386 #define XT_COLOR_SB_SEPARATOR "#555555"
388 #define XT_PROTO_DELIM "://"
391 * xxxterm "protocol" (xtp)
392 * We use this for managing stuff like downloads and favorites. They
393 * make magical HTML pages in memory which have xxxt:// links in order
394 * to communicate with xxxterm's internals. These links take the format:
395 * xxxt://class/session_key/action/arg
397 * Don't begin xtp class/actions as 0. atoi returns that on error.
399 * Typically we have not put addition of items in this framework, as
400 * adding items is either done via an ex-command or via a keybinding instead.
403 #define XT_XTP_STR "xxxt://"
405 /* XTP classes (xxxt://<class>) */
406 #define XT_XTP_INVALID 0 /* invalid */
407 #define XT_XTP_DL 1 /* downloads */
408 #define XT_XTP_HL 2 /* history */
409 #define XT_XTP_CL 3 /* cookies */
410 #define XT_XTP_FL 4 /* favorites */
412 /* XTP download actions */
413 #define XT_XTP_DL_LIST 1
414 #define XT_XTP_DL_CANCEL 2
415 #define XT_XTP_DL_REMOVE 3
417 /* XTP history actions */
418 #define XT_XTP_HL_LIST 1
419 #define XT_XTP_HL_REMOVE 2
421 /* XTP cookie actions */
422 #define XT_XTP_CL_LIST 1
423 #define XT_XTP_CL_REMOVE 2
425 /* XTP cookie actions */
426 #define XT_XTP_FL_LIST 1
427 #define XT_XTP_FL_REMOVE 2
429 /* actions */
430 #define XT_MOVE_INVALID (0)
431 #define XT_MOVE_DOWN (1)
432 #define XT_MOVE_UP (2)
433 #define XT_MOVE_BOTTOM (3)
434 #define XT_MOVE_TOP (4)
435 #define XT_MOVE_PAGEDOWN (5)
436 #define XT_MOVE_PAGEUP (6)
437 #define XT_MOVE_HALFDOWN (7)
438 #define XT_MOVE_HALFUP (8)
439 #define XT_MOVE_LEFT (9)
440 #define XT_MOVE_FARLEFT (10)
441 #define XT_MOVE_RIGHT (11)
442 #define XT_MOVE_FARRIGHT (12)
443 #define XT_MOVE_PERCENT (13)
445 #define XT_QMARK_SET (0)
446 #define XT_QMARK_OPEN (1)
447 #define XT_QMARK_TAB (2)
449 #define XT_MARK_SET (0)
450 #define XT_MARK_GOTO (1)
452 #define XT_TAB_LAST (-4)
453 #define XT_TAB_FIRST (-3)
454 #define XT_TAB_PREV (-2)
455 #define XT_TAB_NEXT (-1)
456 #define XT_TAB_INVALID (0)
457 #define XT_TAB_NEW (1)
458 #define XT_TAB_DELETE (2)
459 #define XT_TAB_DELQUIT (3)
460 #define XT_TAB_OPEN (4)
461 #define XT_TAB_UNDO_CLOSE (5)
462 #define XT_TAB_SHOW (6)
463 #define XT_TAB_HIDE (7)
464 #define XT_TAB_NEXTSTYLE (8)
466 #define XT_NAV_INVALID (0)
467 #define XT_NAV_BACK (1)
468 #define XT_NAV_FORWARD (2)
469 #define XT_NAV_RELOAD (3)
471 #define XT_FOCUS_INVALID (0)
472 #define XT_FOCUS_URI (1)
473 #define XT_FOCUS_SEARCH (2)
475 #define XT_SEARCH_INVALID (0)
476 #define XT_SEARCH_NEXT (1)
477 #define XT_SEARCH_PREV (2)
479 #define XT_PASTE_CURRENT_TAB (0)
480 #define XT_PASTE_NEW_TAB (1)
482 #define XT_ZOOM_IN (-1)
483 #define XT_ZOOM_OUT (-2)
484 #define XT_ZOOM_NORMAL (100)
486 #define XT_URL_SHOW (1)
487 #define XT_URL_HIDE (2)
489 #define XT_WL_TOGGLE (1<<0)
490 #define XT_WL_ENABLE (1<<1)
491 #define XT_WL_DISABLE (1<<2)
492 #define XT_WL_FQDN (1<<3) /* default */
493 #define XT_WL_TOPLEVEL (1<<4)
494 #define XT_WL_PERSISTENT (1<<5)
495 #define XT_WL_SESSION (1<<6)
496 #define XT_WL_RELOAD (1<<7)
498 #define XT_SHOW (1<<7)
499 #define XT_DELETE (1<<8)
500 #define XT_SAVE (1<<9)
501 #define XT_OPEN (1<<10)
503 #define XT_CMD_OPEN (0)
504 #define XT_CMD_OPEN_CURRENT (1)
505 #define XT_CMD_TABNEW (2)
506 #define XT_CMD_TABNEW_CURRENT (3)
508 #define XT_STATUS_NOTHING (0)
509 #define XT_STATUS_LINK (1)
510 #define XT_STATUS_URI (2)
511 #define XT_STATUS_LOADING (3)
513 #define XT_SES_DONOTHING (0)
514 #define XT_SES_CLOSETABS (1)
516 #define XT_BM_NORMAL (0)
517 #define XT_BM_WHITELIST (1)
518 #define XT_BM_KIOSK (2)
520 #define XT_PREFIX (1<<0)
521 #define XT_USERARG (1<<1)
522 #define XT_URLARG (1<<2)
523 #define XT_INTARG (1<<3)
524 #define XT_SESSARG (1<<4)
525 #define XT_SETARG (1<<5)
527 #define XT_TABS_NORMAL 0
528 #define XT_TABS_COMPACT 1
530 #define XT_BUFCMD_SZ (8)
532 /* mime types */
533 struct mime_type {
534 char *mt_type;
535 char *mt_action;
536 int mt_default;
537 int mt_download;
538 TAILQ_ENTRY(mime_type) entry;
540 TAILQ_HEAD(mime_type_list, mime_type);
542 /* uri aliases */
543 struct alias {
544 char *a_name;
545 char *a_uri;
546 TAILQ_ENTRY(alias) entry;
548 TAILQ_HEAD(alias_list, alias);
550 /* settings that require restart */
551 int tabless = 0; /* allow only 1 tab */
552 int enable_socket = 0;
553 int single_instance = 0; /* only allow one xxxterm to run */
554 int fancy_bar = 1; /* fancy toolbar */
555 int browser_mode = XT_BM_NORMAL;
556 int enable_localstorage = 1;
557 char *statusbar_elems = NULL;
559 /* runtime settings */
560 int show_tabs = 1; /* show tabs on notebook */
561 int tab_style = XT_TABS_NORMAL; /* tab bar style */
562 int show_url = 1; /* show url toolbar on notebook */
563 int show_statusbar = 0; /* vimperator style status bar */
564 int ctrl_click_focus = 0; /* ctrl click gets focus */
565 int cookies_enabled = 1; /* enable cookies */
566 int read_only_cookies = 0; /* enable to not write cookies */
567 int enable_scripts = 1;
568 int enable_plugins = 0;
569 gfloat default_zoom_level = 1.0;
570 char default_script[PATH_MAX];
571 int window_height = 768;
572 int window_width = 1024;
573 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
574 int refresh_interval = 10; /* download refresh interval */
575 int enable_cookie_whitelist = 0;
576 int enable_js_whitelist = 0;
577 int session_timeout = 3600; /* cookie session timeout */
578 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
579 char *ssl_ca_file = NULL;
580 char *resource_dir = NULL;
581 gboolean ssl_strict_certs = FALSE;
582 int append_next = 1; /* append tab after current tab */
583 char *home = NULL;
584 char *search_string = NULL;
585 char *http_proxy = NULL;
586 char download_dir[PATH_MAX];
587 char runtime_settings[PATH_MAX]; /* override of settings */
588 int allow_volatile_cookies = 0;
589 int save_global_history = 0; /* save global history to disk */
590 char *user_agent = NULL;
591 int save_rejected_cookies = 0;
592 int session_autosave = 0;
593 int guess_search = 0;
594 int dns_prefetch = FALSE;
595 gint max_connections = 25;
596 gint max_host_connections = 5;
597 gint enable_spell_checking = 0;
598 char *spell_check_languages = NULL;
599 int xterm_workaround = 0;
600 char *url_regex = NULL;
601 int history_autosave = 0;
602 char search_file[PATH_MAX];
603 char command_file[PATH_MAX];
604 char *encoding = NULL;
606 char *cmd_font_name = NULL;
607 char *oops_font_name = NULL;
608 char *statusbar_font_name = NULL;
609 char *tabbar_font_name = NULL;
610 PangoFontDescription *cmd_font;
611 PangoFontDescription *oops_font;
612 PangoFontDescription *statusbar_font;
613 PangoFontDescription *tabbar_font;
614 char *qmarks[XT_NOMARKS];
616 int btn_down; /* M1 down in any wv */
617 regex_t url_re; /* guess_search regex */
619 struct settings;
620 struct key_binding;
621 int set_browser_mode(struct settings *, char *);
622 int set_cookie_policy(struct settings *, char *);
623 int set_download_dir(struct settings *, char *);
624 int set_default_script(struct settings *, char *);
625 int set_runtime_dir(struct settings *, char *);
626 int set_tab_style(struct settings *, char *);
627 int set_work_dir(struct settings *, char *);
628 int add_alias(struct settings *, char *);
629 int add_mime_type(struct settings *, char *);
630 int add_cookie_wl(struct settings *, char *);
631 int add_js_wl(struct settings *, char *);
632 int add_kb(struct settings *, char *);
633 void button_set_stockid(GtkWidget *, char *);
634 GtkWidget * create_button(char *, char *, int);
636 char *get_browser_mode(struct settings *);
637 char *get_cookie_policy(struct settings *);
638 char *get_download_dir(struct settings *);
639 char *get_default_script(struct settings *);
640 char *get_runtime_dir(struct settings *);
641 char *get_tab_style(struct settings *);
642 char *get_work_dir(struct settings *);
643 void startpage_add(const char *, ...);
645 void walk_alias(struct settings *, void (*)(struct settings *,
646 char *, void *), void *);
647 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
648 char *, void *), void *);
649 void walk_js_wl(struct settings *, void (*)(struct settings *,
650 char *, void *), void *);
651 void walk_kb(struct settings *, void (*)(struct settings *, char *,
652 void *), void *);
653 void walk_mime_type(struct settings *, void (*)(struct settings *,
654 char *, void *), void *);
656 void recalc_tabs(void);
657 void recolor_compact_tabs(void);
658 void set_current_tab(int page_num);
659 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
660 void marks_clear(struct tab *t);
662 int set_http_proxy(char *);
664 struct special {
665 int (*set)(struct settings *, char *);
666 char *(*get)(struct settings *);
667 void (*walk)(struct settings *,
668 void (*cb)(struct settings *, char *, void *),
669 void *);
672 struct special s_browser_mode = {
673 set_browser_mode,
674 get_browser_mode,
675 NULL
678 struct special s_cookie = {
679 set_cookie_policy,
680 get_cookie_policy,
681 NULL
684 struct special s_alias = {
685 add_alias,
686 NULL,
687 walk_alias
690 struct special s_mime = {
691 add_mime_type,
692 NULL,
693 walk_mime_type
696 struct special s_js = {
697 add_js_wl,
698 NULL,
699 walk_js_wl
702 struct special s_kb = {
703 add_kb,
704 NULL,
705 walk_kb
708 struct special s_cookie_wl = {
709 add_cookie_wl,
710 NULL,
711 walk_cookie_wl
714 struct special s_default_script = {
715 set_default_script,
716 get_default_script,
717 NULL
720 struct special s_download_dir = {
721 set_download_dir,
722 get_download_dir,
723 NULL
726 struct special s_work_dir = {
727 set_work_dir,
728 get_work_dir,
729 NULL
732 struct special s_tab_style = {
733 set_tab_style,
734 get_tab_style,
735 NULL
738 struct settings {
739 char *name;
740 int type;
741 #define XT_S_INVALID (0)
742 #define XT_S_INT (1)
743 #define XT_S_STR (2)
744 #define XT_S_FLOAT (3)
745 uint32_t flags;
746 #define XT_SF_RESTART (1<<0)
747 #define XT_SF_RUNTIME (1<<1)
748 int *ival;
749 char **sval;
750 struct special *s;
751 gfloat *fval;
752 int (*activate)(char *);
753 } rs[] = {
754 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
755 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
756 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
757 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
758 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
759 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
760 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
761 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
762 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
763 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
764 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
765 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
766 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
767 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
768 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
769 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
770 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
771 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
772 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
773 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
774 { "home", XT_S_STR, 0, NULL, &home, NULL },
775 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
776 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
777 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
778 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
779 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
780 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
781 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
782 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
783 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
784 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
785 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
786 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
787 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
788 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
789 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
790 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
791 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
792 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
793 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
794 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
795 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
796 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
797 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
798 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
799 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
800 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
801 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
803 /* font settings */
804 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
805 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
806 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
807 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
809 /* runtime settings */
810 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
811 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
812 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
813 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
814 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
817 int about(struct tab *, struct karg *);
818 int blank(struct tab *, struct karg *);
819 int ca_cmd(struct tab *, struct karg *);
820 int cookie_show_wl(struct tab *, struct karg *);
821 int js_show_wl(struct tab *, struct karg *);
822 int help(struct tab *, struct karg *);
823 int set(struct tab *, struct karg *);
824 int stats(struct tab *, struct karg *);
825 int marco(struct tab *, struct karg *);
826 int startpage(struct tab *, struct karg *);
827 const char * marco_message(int *);
828 int xtp_page_cl(struct tab *, struct karg *);
829 int xtp_page_dl(struct tab *, struct karg *);
830 int xtp_page_fl(struct tab *, struct karg *);
831 int xtp_page_hl(struct tab *, struct karg *);
832 void xt_icon_from_file(struct tab *, char *);
833 const gchar *get_uri(struct tab *);
834 const gchar *get_title(struct tab *, bool);
836 #define XT_URI_ABOUT ("about:")
837 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
838 #define XT_URI_ABOUT_ABOUT ("about")
839 #define XT_URI_ABOUT_BLANK ("blank")
840 #define XT_URI_ABOUT_CERTS ("certs")
841 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
842 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
843 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
844 #define XT_URI_ABOUT_FAVORITES ("favorites")
845 #define XT_URI_ABOUT_HELP ("help")
846 #define XT_URI_ABOUT_HISTORY ("history")
847 #define XT_URI_ABOUT_JSWL ("jswl")
848 #define XT_URI_ABOUT_SET ("set")
849 #define XT_URI_ABOUT_STATS ("stats")
850 #define XT_URI_ABOUT_MARCO ("marco")
851 #define XT_URI_ABOUT_STARTPAGE ("startpage")
853 struct about_type {
854 char *name;
855 int (*func)(struct tab *, struct karg *);
856 } about_list[] = {
857 { XT_URI_ABOUT_ABOUT, about },
858 { XT_URI_ABOUT_BLANK, blank },
859 { XT_URI_ABOUT_CERTS, ca_cmd },
860 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
861 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
862 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
863 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
864 { XT_URI_ABOUT_HELP, help },
865 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
866 { XT_URI_ABOUT_JSWL, js_show_wl },
867 { XT_URI_ABOUT_SET, set },
868 { XT_URI_ABOUT_STATS, stats },
869 { XT_URI_ABOUT_MARCO, marco },
870 { XT_URI_ABOUT_STARTPAGE, startpage },
873 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
874 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
875 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
876 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
877 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
878 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
879 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
881 /* globals */
882 extern char *__progname;
883 char **start_argv;
884 struct passwd *pwd;
885 GtkWidget *main_window;
886 GtkNotebook *notebook;
887 GtkWidget *tab_bar;
888 GtkWidget *arrow, *abtn;
889 struct tab_list tabs;
890 struct history_list hl;
891 struct session_list sessions;
892 struct download_list downloads;
893 struct domain_list c_wl;
894 struct domain_list js_wl;
895 struct undo_tailq undos;
896 struct keybinding_list kbl;
897 struct sp_list spl;
898 struct command_list chl;
899 struct command_list shl;
900 struct command_entry *history_at;
901 struct command_entry *search_at;
902 int undo_count;
903 int updating_dl_tabs = 0;
904 int updating_hl_tabs = 0;
905 int updating_cl_tabs = 0;
906 int updating_fl_tabs = 0;
907 int cmd_history_count = 0;
908 int search_history_count = 0;
909 char *global_search;
910 uint64_t blocked_cookies = 0;
911 char named_session[PATH_MAX];
912 GtkListStore *completion_model;
913 GtkListStore *buffers_store;
915 void xxx_dir(char *);
916 int icon_size_map(int);
917 void completion_add(struct tab *);
918 void completion_add_uri(const gchar *);
919 void show_oops(struct tab *, const char *, ...);
921 void
922 history_delete(struct command_list *l, int *counter)
924 struct command_entry *c;
926 if (l == NULL || counter == NULL)
927 return;
929 c = TAILQ_LAST(l, command_list);
930 if (c == NULL)
931 return;
933 TAILQ_REMOVE(l, c, entry);
934 *counter -= 1;
935 g_free(c->line);
936 g_free(c);
939 void
940 history_add(struct command_list *list, char *file, char *l, int *counter)
942 struct command_entry *c;
943 FILE *f;
945 if (list == NULL || l == NULL || counter == NULL)
946 return;
948 /* don't add the same line */
949 c = TAILQ_FIRST(list);
950 if (c)
951 if (!strcmp(c->line + 1 /* skip space */, l))
952 return;
954 c = g_malloc0(sizeof *c);
955 c->line = g_strdup_printf(" %s", l);
957 *counter += 1;
958 TAILQ_INSERT_HEAD(list, c, entry);
960 if (*counter > 1000)
961 history_delete(list, counter);
963 if (history_autosave && file) {
964 f = fopen(file, "w");
965 if (f == NULL) {
966 show_oops(NULL, "couldn't write history %s", file);
967 return;
970 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
971 c->line[0] = ' ';
972 fprintf(f, "%s\n", c->line);
975 fclose(f);
980 history_read(struct command_list *list, char *file, int *counter)
982 FILE *f;
983 char *s, line[65536];
985 if (list == NULL || file == NULL)
986 return (1);
988 f = fopen(file, "r");
989 if (f == NULL) {
990 startpage_add("couldn't open history file %s", file);
991 return (1);
994 for (;;) {
995 s = fgets(line, sizeof line, f);
996 if (s == NULL || feof(f) || ferror(f))
997 break;
998 if ((s = strchr(line, '\n')) == NULL) {
999 startpage_add("invalid history file %s", file);
1000 fclose(f);
1001 return (1);
1003 *s = '\0';
1005 history_add(list, NULL, line + 1, counter);
1008 fclose(f);
1010 return (0);
1013 /* marks and quickmarks array storage.
1014 * first a-z, then A-Z, then 0-9 */
1015 char
1016 indextomark(int i)
1018 if (i < 0)
1019 return 0;
1021 if (i >= 0 && i <= 'z' - 'a')
1022 return 'a' + i;
1024 i -= 'z' - 'a' + 1;
1025 if (i >= 0 && i <= 'Z' - 'A')
1026 return 'A' + i;
1028 i -= 'Z' - 'A' + 1;
1029 if (i >= 10)
1030 return 0;
1032 return i + '0';
1036 marktoindex(char m)
1038 int ret = 0;
1040 if (m >= 'a' && m <= 'z')
1041 return ret + m - 'a';
1043 ret += 'z' - 'a' + 1;
1044 if (m >= 'A' && m <= 'Z')
1045 return ret + m - 'A';
1047 ret += 'Z' - 'A' + 1;
1048 if (m >= '0' && m <= '9')
1049 return ret + m - '0';
1051 return -1;
1055 void
1056 sigchild(int sig)
1058 int saved_errno, status;
1059 pid_t pid;
1061 saved_errno = errno;
1063 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
1064 if (pid == -1) {
1065 if (errno == EINTR)
1066 continue;
1067 if (errno != ECHILD) {
1069 clog_warn("sigchild: waitpid:");
1072 break;
1075 if (WIFEXITED(status)) {
1076 if (WEXITSTATUS(status) != 0) {
1078 clog_warnx("sigchild: child exit status: %d",
1079 WEXITSTATUS(status));
1082 } else {
1084 clog_warnx("sigchild: child is terminated abnormally");
1089 errno = saved_errno;
1093 is_g_object_setting(GObject *o, char *str)
1095 guint n_props = 0, i;
1096 GParamSpec **proplist;
1098 if (! G_IS_OBJECT(o))
1099 return (0);
1101 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
1102 &n_props);
1104 for (i=0; i < n_props; i++) {
1105 if (! strcmp(proplist[i]->name, str))
1106 return (1);
1108 return (0);
1111 gchar *
1112 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
1114 gchar *r;
1116 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1117 "<head>\n"
1118 "<title>%s</title>\n"
1119 "%s"
1120 "%s"
1121 "</head>\n"
1122 "<body>\n"
1123 "<h1>%s</h1>\n"
1124 "%s\n</body>\n"
1125 "</html>",
1126 title,
1127 addstyles ? XT_PAGE_STYLE : "",
1128 head,
1129 title,
1130 body);
1132 return r;
1136 * Display a web page from a HTML string in memory, rather than from a URL
1138 void
1139 load_webkit_string(struct tab *t, const char *str, gchar *title)
1141 char file[PATH_MAX];
1142 int i;
1144 /* we set this to indicate we want to manually do navaction */
1145 if (t->bfl)
1146 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
1148 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1149 if (title) {
1150 /* set t->xtp_meaning */
1151 for (i = 0; i < LENGTH(about_list); i++)
1152 if (!strcmp(title, about_list[i].name)) {
1153 t->xtp_meaning = i;
1154 break;
1157 webkit_web_view_load_string(t->wv, str, NULL, encoding,
1158 "file://");
1159 #if GTK_CHECK_VERSION(2, 20, 0)
1160 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1161 gtk_widget_hide(t->spinner);
1162 #endif
1163 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1164 xt_icon_from_file(t, file);
1168 struct tab *
1169 get_current_tab(void)
1171 struct tab *t;
1173 TAILQ_FOREACH(t, &tabs, entry) {
1174 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1175 return (t);
1178 warnx("%s: no current tab", __func__);
1180 return (NULL);
1183 void
1184 set_status(struct tab *t, gchar *s, int status)
1186 gchar *type = NULL;
1188 if (s == NULL)
1189 return;
1191 switch (status) {
1192 case XT_STATUS_LOADING:
1193 type = g_strdup_printf("Loading: %s", s);
1194 s = type;
1195 break;
1196 case XT_STATUS_LINK:
1197 type = g_strdup_printf("Link: %s", s);
1198 if (!t->status)
1199 t->status = g_strdup(gtk_entry_get_text(
1200 GTK_ENTRY(t->sbe.statusbar)));
1201 s = type;
1202 break;
1203 case XT_STATUS_URI:
1204 type = g_strdup_printf("%s", s);
1205 if (!t->status) {
1206 t->status = g_strdup(type);
1208 s = type;
1209 if (!t->status)
1210 t->status = g_strdup(s);
1211 break;
1212 case XT_STATUS_NOTHING:
1213 /* FALL THROUGH */
1214 default:
1215 break;
1217 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1218 if (type)
1219 g_free(type);
1222 void
1223 hide_cmd(struct tab *t)
1225 history_at = NULL; /* just in case */
1226 search_at = NULL; /* just in case */
1227 gtk_widget_hide(t->cmd);
1230 void
1231 show_cmd(struct tab *t)
1233 history_at = NULL;
1234 search_at = NULL;
1235 gtk_widget_hide(t->oops);
1236 gtk_widget_show(t->cmd);
1239 void
1240 hide_buffers(struct tab *t)
1242 gtk_widget_hide(t->buffers);
1243 gtk_list_store_clear(buffers_store);
1246 enum {
1247 COL_ID = 0,
1248 COL_TITLE,
1249 NUM_COLS
1253 sort_tabs_by_page_num(struct tab ***stabs)
1255 int num_tabs = 0;
1256 struct tab *t;
1258 num_tabs = gtk_notebook_get_n_pages(notebook);
1260 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1262 TAILQ_FOREACH(t, &tabs, entry)
1263 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1265 return (num_tabs);
1268 void
1269 buffers_make_list(void)
1271 int i, num_tabs;
1272 const gchar *title = NULL;
1273 GtkTreeIter iter;
1274 struct tab **stabs = NULL;
1276 num_tabs = sort_tabs_by_page_num(&stabs);
1278 for (i = 0; i < num_tabs; i++)
1279 if (stabs[i]) {
1280 gtk_list_store_append(buffers_store, &iter);
1281 title = get_title(stabs[i], FALSE);
1282 gtk_list_store_set(buffers_store, &iter,
1283 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1284 * rather than 0. */
1285 COL_TITLE, title,
1286 -1);
1289 g_free(stabs);
1292 void
1293 show_buffers(struct tab *t)
1295 buffers_make_list();
1296 gtk_widget_show(t->buffers);
1297 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1300 void
1301 toggle_buffers(struct tab *t)
1303 if (gtk_widget_get_visible(t->buffers))
1304 hide_buffers(t);
1305 else
1306 show_buffers(t);
1310 buffers(struct tab *t, struct karg *args)
1312 show_buffers(t);
1314 return (0);
1317 void
1318 hide_oops(struct tab *t)
1320 gtk_widget_hide(t->oops);
1323 void
1324 show_oops(struct tab *at, const char *fmt, ...)
1326 va_list ap;
1327 char *msg = NULL;
1328 struct tab *t = NULL;
1330 if (fmt == NULL)
1331 return;
1333 if (at == NULL) {
1334 if ((t = get_current_tab()) == NULL)
1335 return;
1336 } else
1337 t = at;
1339 va_start(ap, fmt);
1340 if (vasprintf(&msg, fmt, ap) == -1)
1341 errx(1, "show_oops failed");
1342 va_end(ap);
1344 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1345 gtk_widget_hide(t->cmd);
1346 gtk_widget_show(t->oops);
1348 if (msg)
1349 free(msg);
1352 char *
1353 get_as_string(struct settings *s)
1355 char *r = NULL;
1357 if (s == NULL)
1358 return (NULL);
1360 if (s->s) {
1361 if (s->s->get)
1362 r = s->s->get(s);
1363 else
1364 warnx("get_as_string skip %s\n", s->name);
1365 } else if (s->type == XT_S_INT)
1366 r = g_strdup_printf("%d", *s->ival);
1367 else if (s->type == XT_S_STR)
1368 r = g_strdup(*s->sval);
1369 else if (s->type == XT_S_FLOAT)
1370 r = g_strdup_printf("%f", *s->fval);
1371 else
1372 r = g_strdup_printf("INVALID TYPE");
1374 return (r);
1377 void
1378 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1380 int i;
1381 char *s;
1383 for (i = 0; i < LENGTH(rs); i++) {
1384 if (rs[i].s && rs[i].s->walk)
1385 rs[i].s->walk(&rs[i], cb, cb_args);
1386 else {
1387 s = get_as_string(&rs[i]);
1388 cb(&rs[i], s, cb_args);
1389 g_free(s);
1395 set_browser_mode(struct settings *s, char *val)
1397 if (!strcmp(val, "whitelist")) {
1398 browser_mode = XT_BM_WHITELIST;
1399 allow_volatile_cookies = 0;
1400 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1401 cookies_enabled = 1;
1402 enable_cookie_whitelist = 1;
1403 read_only_cookies = 0;
1404 save_rejected_cookies = 0;
1405 session_timeout = 3600;
1406 enable_scripts = 0;
1407 enable_js_whitelist = 1;
1408 enable_localstorage = 0;
1409 } else if (!strcmp(val, "normal")) {
1410 browser_mode = XT_BM_NORMAL;
1411 allow_volatile_cookies = 0;
1412 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1413 cookies_enabled = 1;
1414 enable_cookie_whitelist = 0;
1415 read_only_cookies = 0;
1416 save_rejected_cookies = 0;
1417 session_timeout = 3600;
1418 enable_scripts = 1;
1419 enable_js_whitelist = 0;
1420 enable_localstorage = 1;
1421 } else if (!strcmp(val, "kiosk")) {
1422 browser_mode = XT_BM_KIOSK;
1423 allow_volatile_cookies = 0;
1424 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1425 cookies_enabled = 1;
1426 enable_cookie_whitelist = 0;
1427 read_only_cookies = 0;
1428 save_rejected_cookies = 0;
1429 session_timeout = 3600;
1430 enable_scripts = 1;
1431 enable_js_whitelist = 0;
1432 enable_localstorage = 1;
1433 show_tabs = 0;
1434 tabless = 1;
1435 } else
1436 return (1);
1438 return (0);
1441 char *
1442 get_browser_mode(struct settings *s)
1444 char *r = NULL;
1446 if (browser_mode == XT_BM_WHITELIST)
1447 r = g_strdup("whitelist");
1448 else if (browser_mode == XT_BM_NORMAL)
1449 r = g_strdup("normal");
1450 else if (browser_mode == XT_BM_KIOSK)
1451 r = g_strdup("kiosk");
1452 else
1453 return (NULL);
1455 return (r);
1459 set_cookie_policy(struct settings *s, char *val)
1461 if (!strcmp(val, "no3rdparty"))
1462 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1463 else if (!strcmp(val, "accept"))
1464 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1465 else if (!strcmp(val, "reject"))
1466 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1467 else
1468 return (1);
1470 return (0);
1473 char *
1474 get_cookie_policy(struct settings *s)
1476 char *r = NULL;
1478 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1479 r = g_strdup("no3rdparty");
1480 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1481 r = g_strdup("accept");
1482 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1483 r = g_strdup("reject");
1484 else
1485 return (NULL);
1487 return (r);
1490 char *
1491 get_default_script(struct settings *s)
1493 if (default_script[0] == '\0')
1494 return (0);
1495 return (g_strdup(default_script));
1499 set_default_script(struct settings *s, char *val)
1501 if (val[0] == '~')
1502 snprintf(default_script, sizeof default_script, "%s/%s",
1503 pwd->pw_dir, &val[1]);
1504 else
1505 strlcpy(default_script, val, sizeof default_script);
1507 return (0);
1510 char *
1511 get_download_dir(struct settings *s)
1513 if (download_dir[0] == '\0')
1514 return (0);
1515 return (g_strdup(download_dir));
1519 set_download_dir(struct settings *s, char *val)
1521 if (val[0] == '~')
1522 snprintf(download_dir, sizeof download_dir, "%s/%s",
1523 pwd->pw_dir, &val[1]);
1524 else
1525 strlcpy(download_dir, val, sizeof download_dir);
1527 return (0);
1531 * Session IDs.
1532 * We use these to prevent people putting xxxt:// URLs on
1533 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1535 #define XT_XTP_SES_KEY_SZ 8
1536 #define XT_XTP_SES_KEY_HEX_FMT \
1537 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1538 char *dl_session_key; /* downloads */
1539 char *hl_session_key; /* history list */
1540 char *cl_session_key; /* cookie list */
1541 char *fl_session_key; /* favorites list */
1543 char work_dir[PATH_MAX];
1544 char certs_dir[PATH_MAX];
1545 char cache_dir[PATH_MAX];
1546 char sessions_dir[PATH_MAX];
1547 char cookie_file[PATH_MAX];
1548 SoupURI *proxy_uri = NULL;
1549 SoupSession *session;
1550 SoupCookieJar *s_cookiejar;
1551 SoupCookieJar *p_cookiejar;
1552 char rc_fname[PATH_MAX];
1554 struct mime_type_list mtl;
1555 struct alias_list aliases;
1557 /* protos */
1558 struct tab *create_new_tab(char *, struct undo *, int, int);
1559 void delete_tab(struct tab *);
1560 void setzoom_webkit(struct tab *, int);
1561 int run_script(struct tab *, char *);
1562 int download_rb_cmp(struct download *, struct download *);
1563 gboolean cmd_execute(struct tab *t, char *str);
1566 history_rb_cmp(struct history *h1, struct history *h2)
1568 return (strcmp(h1->uri, h2->uri));
1570 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1573 domain_rb_cmp(struct domain *d1, struct domain *d2)
1575 return (strcmp(d1->d, d2->d));
1577 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1579 char *
1580 get_work_dir(struct settings *s)
1582 if (work_dir[0] == '\0')
1583 return (0);
1584 return (g_strdup(work_dir));
1588 set_work_dir(struct settings *s, char *val)
1590 if (val[0] == '~')
1591 snprintf(work_dir, sizeof work_dir, "%s/%s",
1592 pwd->pw_dir, &val[1]);
1593 else
1594 strlcpy(work_dir, val, sizeof work_dir);
1596 return (0);
1599 char *
1600 get_tab_style(struct settings *s)
1602 if (tab_style == XT_TABS_NORMAL)
1603 return (g_strdup("normal"));
1604 else
1605 return (g_strdup("compact"));
1609 set_tab_style(struct settings *s, char *val)
1611 if (!strcmp(val, "normal"))
1612 tab_style = XT_TABS_NORMAL;
1613 else if (!strcmp(val, "compact"))
1614 tab_style = XT_TABS_COMPACT;
1615 else
1616 return (1);
1618 return (0);
1622 * generate a session key to secure xtp commands.
1623 * pass in a ptr to the key in question and it will
1624 * be modified in place.
1626 void
1627 generate_xtp_session_key(char **key)
1629 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1631 /* free old key */
1632 if (*key)
1633 g_free(*key);
1635 /* make a new one */
1636 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1637 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1638 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1639 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1641 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1645 * validate a xtp session key.
1646 * return 1 if OK
1649 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1651 if (strcmp(trusted, untrusted) != 0) {
1652 show_oops(t, "%s: xtp session key mismatch possible spoof",
1653 __func__);
1654 return (0);
1657 return (1);
1661 download_rb_cmp(struct download *e1, struct download *e2)
1663 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1665 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1667 struct valid_url_types {
1668 char *type;
1669 } vut[] = {
1670 { "http://" },
1671 { "https://" },
1672 { "ftp://" },
1673 { "file://" },
1674 { XT_XTP_STR },
1678 valid_url_type(char *url)
1680 int i;
1682 for (i = 0; i < LENGTH(vut); i++)
1683 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1684 return (0);
1686 return (1);
1689 void
1690 print_cookie(char *msg, SoupCookie *c)
1692 if (c == NULL)
1693 return;
1695 if (msg)
1696 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1697 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1698 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1699 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1700 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1701 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1702 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1703 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1704 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1705 DNPRINTF(XT_D_COOKIE, "====================================\n");
1708 void
1709 walk_alias(struct settings *s,
1710 void (*cb)(struct settings *, char *, void *), void *cb_args)
1712 struct alias *a;
1713 char *str;
1715 if (s == NULL || cb == NULL) {
1716 show_oops(NULL, "walk_alias invalid parameters");
1717 return;
1720 TAILQ_FOREACH(a, &aliases, entry) {
1721 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1722 cb(s, str, cb_args);
1723 g_free(str);
1727 char *
1728 match_alias(char *url_in)
1730 struct alias *a;
1731 char *arg;
1732 char *url_out = NULL, *search, *enc_arg;
1734 search = g_strdup(url_in);
1735 arg = search;
1736 if (strsep(&arg, " \t") == NULL) {
1737 show_oops(NULL, "match_alias: NULL URL");
1738 goto done;
1741 TAILQ_FOREACH(a, &aliases, entry) {
1742 if (!strcmp(search, a->a_name))
1743 break;
1746 if (a != NULL) {
1747 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1748 a->a_name);
1749 if (arg != NULL) {
1750 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1751 url_out = g_strdup_printf(a->a_uri, enc_arg);
1752 g_free(enc_arg);
1753 } else
1754 url_out = g_strdup_printf(a->a_uri, "");
1756 done:
1757 g_free(search);
1758 return (url_out);
1761 char *
1762 guess_url_type(char *url_in)
1764 struct stat sb;
1765 char *url_out = NULL, *enc_search = NULL;
1766 int i;
1768 /* substitute aliases */
1769 url_out = match_alias(url_in);
1770 if (url_out != NULL)
1771 return (url_out);
1773 /* see if we are an about page */
1774 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1775 for (i = 0; i < LENGTH(about_list); i++)
1776 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1777 about_list[i].name)) {
1778 url_out = g_strdup(url_in);
1779 goto done;
1782 if (guess_search && url_regex &&
1783 !(g_str_has_prefix(url_in, "http://") ||
1784 g_str_has_prefix(url_in, "https://"))) {
1785 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1786 /* invalid URI so search instead */
1787 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1788 url_out = g_strdup_printf(search_string, enc_search);
1789 g_free(enc_search);
1790 goto done;
1794 /* XXX not sure about this heuristic */
1795 if (stat(url_in, &sb) == 0)
1796 url_out = g_strdup_printf("file://%s", url_in);
1797 else
1798 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1799 done:
1800 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1802 return (url_out);
1805 void
1806 load_uri(struct tab *t, gchar *uri)
1808 struct karg args;
1809 gchar *newuri = NULL;
1810 int i;
1812 if (uri == NULL)
1813 return;
1815 /* Strip leading spaces. */
1816 while (*uri && isspace(*uri))
1817 uri++;
1819 if (strlen(uri) == 0) {
1820 blank(t, NULL);
1821 return;
1824 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1826 if (valid_url_type(uri)) {
1827 newuri = guess_url_type(uri);
1828 uri = newuri;
1831 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1832 for (i = 0; i < LENGTH(about_list); i++)
1833 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1834 bzero(&args, sizeof args);
1835 about_list[i].func(t, &args);
1836 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1837 FALSE);
1838 goto done;
1840 show_oops(t, "invalid about page");
1841 goto done;
1844 set_status(t, (char *)uri, XT_STATUS_LOADING);
1845 marks_clear(t);
1846 webkit_web_view_load_uri(t->wv, uri);
1847 done:
1848 if (newuri)
1849 g_free(newuri);
1852 const gchar *
1853 get_uri(struct tab *t)
1855 const gchar *uri = NULL;
1857 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1858 return NULL;
1859 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1860 uri = webkit_web_view_get_uri(t->wv);
1861 } else {
1862 /* use tmp_uri to make sure it is g_freed */
1863 if (t->tmp_uri)
1864 g_free(t->tmp_uri);
1865 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1866 about_list[t->xtp_meaning].name);
1867 uri = t->tmp_uri;
1869 return uri;
1872 const gchar *
1873 get_title(struct tab *t, bool window)
1875 const gchar *set = NULL, *title = NULL;
1876 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1878 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1879 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1880 goto notitle;
1882 title = webkit_web_view_get_title(t->wv);
1883 if ((set = title ? title : get_uri(t)))
1884 return set;
1886 notitle:
1887 set = window ? XT_NAME : "(untitled)";
1889 return set;
1893 add_alias(struct settings *s, char *line)
1895 char *l, *alias;
1896 struct alias *a = NULL;
1898 if (s == NULL || line == NULL) {
1899 show_oops(NULL, "add_alias invalid parameters");
1900 return (1);
1903 l = line;
1904 a = g_malloc(sizeof(*a));
1906 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1907 show_oops(NULL, "add_alias: incomplete alias definition");
1908 goto bad;
1910 if (strlen(alias) == 0 || strlen(l) == 0) {
1911 show_oops(NULL, "add_alias: invalid alias definition");
1912 goto bad;
1915 a->a_name = g_strdup(alias);
1916 a->a_uri = g_strdup(l);
1918 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1920 TAILQ_INSERT_TAIL(&aliases, a, entry);
1922 return (0);
1923 bad:
1924 if (a)
1925 g_free(a);
1926 return (1);
1930 add_mime_type(struct settings *s, char *line)
1932 char *mime_type;
1933 char *l;
1934 struct mime_type *m = NULL;
1935 int downloadfirst = 0;
1937 /* XXX this could be smarter */
1939 if (line == NULL || strlen(line) == 0) {
1940 show_oops(NULL, "add_mime_type invalid parameters");
1941 return (1);
1944 l = line;
1945 if (*l == '@') {
1946 downloadfirst = 1;
1947 l++;
1949 m = g_malloc(sizeof(*m));
1951 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1952 show_oops(NULL, "add_mime_type: invalid mime_type");
1953 goto bad;
1955 if (mime_type[strlen(mime_type) - 1] == '*') {
1956 mime_type[strlen(mime_type) - 1] = '\0';
1957 m->mt_default = 1;
1958 } else
1959 m->mt_default = 0;
1961 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1962 show_oops(NULL, "add_mime_type: invalid mime_type");
1963 goto bad;
1966 m->mt_type = g_strdup(mime_type);
1967 m->mt_action = g_strdup(l);
1968 m->mt_download = downloadfirst;
1970 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1971 m->mt_type, m->mt_action, m->mt_default);
1973 TAILQ_INSERT_TAIL(&mtl, m, entry);
1975 return (0);
1976 bad:
1977 if (m)
1978 g_free(m);
1979 return (1);
1982 struct mime_type *
1983 find_mime_type(char *mime_type)
1985 struct mime_type *m, *def = NULL, *rv = NULL;
1987 TAILQ_FOREACH(m, &mtl, entry) {
1988 if (m->mt_default &&
1989 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1990 def = m;
1992 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1993 rv = m;
1994 break;
1998 if (rv == NULL)
1999 rv = def;
2001 return (rv);
2004 void
2005 walk_mime_type(struct settings *s,
2006 void (*cb)(struct settings *, char *, void *), void *cb_args)
2008 struct mime_type *m;
2009 char *str;
2011 if (s == NULL || cb == NULL) {
2012 show_oops(NULL, "walk_mime_type invalid parameters");
2013 return;
2016 TAILQ_FOREACH(m, &mtl, entry) {
2017 str = g_strdup_printf("%s%s --> %s",
2018 m->mt_type,
2019 m->mt_default ? "*" : "",
2020 m->mt_action);
2021 cb(s, str, cb_args);
2022 g_free(str);
2026 void
2027 wl_add(char *str, struct domain_list *wl, int handy)
2029 struct domain *d;
2030 int add_dot = 0;
2031 char *p;
2033 if (str == NULL || wl == NULL || strlen(str) < 2)
2034 return;
2036 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
2038 /* treat *.moo.com the same as .moo.com */
2039 if (str[0] == '*' && str[1] == '.')
2040 str = &str[1];
2041 else if (str[0] == '.')
2042 str = &str[0];
2043 else
2044 add_dot = 1;
2046 /* slice off port number */
2047 p = g_strrstr(str, ":");
2048 if (p)
2049 *p = '\0';
2051 d = g_malloc(sizeof *d);
2052 if (add_dot)
2053 d->d = g_strdup_printf(".%s", str);
2054 else
2055 d->d = g_strdup(str);
2056 d->handy = handy;
2058 if (RB_INSERT(domain_list, wl, d))
2059 goto unwind;
2061 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
2062 return;
2063 unwind:
2064 if (d) {
2065 if (d->d)
2066 g_free(d->d);
2067 g_free(d);
2072 add_cookie_wl(struct settings *s, char *entry)
2074 wl_add(entry, &c_wl, 1);
2075 return (0);
2078 void
2079 walk_cookie_wl(struct settings *s,
2080 void (*cb)(struct settings *, char *, void *), void *cb_args)
2082 struct domain *d;
2084 if (s == NULL || cb == NULL) {
2085 show_oops(NULL, "walk_cookie_wl invalid parameters");
2086 return;
2089 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
2090 cb(s, d->d, cb_args);
2093 void
2094 walk_js_wl(struct settings *s,
2095 void (*cb)(struct settings *, char *, void *), void *cb_args)
2097 struct domain *d;
2099 if (s == NULL || cb == NULL) {
2100 show_oops(NULL, "walk_js_wl invalid parameters");
2101 return;
2104 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
2105 cb(s, d->d, cb_args);
2109 add_js_wl(struct settings *s, char *entry)
2111 wl_add(entry, &js_wl, 1 /* persistent */);
2112 return (0);
2115 struct domain *
2116 wl_find(const gchar *search, struct domain_list *wl)
2118 int i;
2119 struct domain *d = NULL, dfind;
2120 gchar *s = NULL;
2122 if (search == NULL || wl == NULL)
2123 return (NULL);
2124 if (strlen(search) < 2)
2125 return (NULL);
2127 if (search[0] != '.')
2128 s = g_strdup_printf(".%s", search);
2129 else
2130 s = g_strdup(search);
2132 for (i = strlen(s) - 1; i >= 0; i--) {
2133 if (s[i] == '.') {
2134 dfind.d = &s[i];
2135 d = RB_FIND(domain_list, wl, &dfind);
2136 if (d)
2137 goto done;
2141 done:
2142 if (s)
2143 g_free(s);
2145 return (d);
2148 struct domain *
2149 wl_find_uri(const gchar *s, struct domain_list *wl)
2151 int i;
2152 char *ss;
2153 struct domain *r;
2155 if (s == NULL || wl == NULL)
2156 return (NULL);
2158 if (!strncmp(s, "http://", strlen("http://")))
2159 s = &s[strlen("http://")];
2160 else if (!strncmp(s, "https://", strlen("https://")))
2161 s = &s[strlen("https://")];
2163 if (strlen(s) < 2)
2164 return (NULL);
2166 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2167 /* chop string at first slash */
2168 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2169 ss = g_strdup(s);
2170 ss[i] = '\0';
2171 r = wl_find(ss, wl);
2172 g_free(ss);
2173 return (r);
2176 return (NULL);
2180 settings_add(char *var, char *val)
2182 int i, rv, *p;
2183 gfloat *f;
2184 char **s;
2186 /* get settings */
2187 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2188 if (strcmp(var, rs[i].name))
2189 continue;
2191 if (rs[i].s) {
2192 if (rs[i].s->set(&rs[i], val))
2193 errx(1, "invalid value for %s: %s", var, val);
2194 rv = 1;
2195 break;
2196 } else
2197 switch (rs[i].type) {
2198 case XT_S_INT:
2199 p = rs[i].ival;
2200 *p = atoi(val);
2201 rv = 1;
2202 break;
2203 case XT_S_STR:
2204 s = rs[i].sval;
2205 if (s == NULL)
2206 errx(1, "invalid sval for %s",
2207 rs[i].name);
2208 if (*s)
2209 g_free(*s);
2210 *s = g_strdup(val);
2211 rv = 1;
2212 break;
2213 case XT_S_FLOAT:
2214 f = rs[i].fval;
2215 *f = atof(val);
2216 rv = 1;
2217 break;
2218 case XT_S_INVALID:
2219 default:
2220 errx(1, "invalid type for %s", var);
2222 break;
2224 return (rv);
2227 #define WS "\n= \t"
2228 void
2229 config_parse(char *filename, int runtime)
2231 FILE *config, *f;
2232 char *line, *cp, *var, *val;
2233 size_t len, lineno = 0;
2234 int handled;
2235 char file[PATH_MAX];
2236 struct stat sb;
2238 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2240 if (filename == NULL)
2241 return;
2243 if (runtime && runtime_settings[0] != '\0') {
2244 snprintf(file, sizeof file, "%s/%s",
2245 work_dir, runtime_settings);
2246 if (stat(file, &sb)) {
2247 warnx("runtime file doesn't exist, creating it");
2248 if ((f = fopen(file, "w")) == NULL)
2249 err(1, "runtime");
2250 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2251 fclose(f);
2253 } else
2254 strlcpy(file, filename, sizeof file);
2256 if ((config = fopen(file, "r")) == NULL) {
2257 warn("config_parse: cannot open %s", filename);
2258 return;
2261 for (;;) {
2262 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2263 if (feof(config) || ferror(config))
2264 break;
2266 cp = line;
2267 cp += (long)strspn(cp, WS);
2268 if (cp[0] == '\0') {
2269 /* empty line */
2270 free(line);
2271 continue;
2274 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2275 startpage_add("invalid configuration file entry: %s",
2276 line);
2278 cp += (long)strspn(cp, WS);
2280 if ((val = strsep(&cp, "\0")) == NULL)
2281 break;
2283 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2284 handled = settings_add(var, val);
2285 if (handled == 0)
2286 startpage_add("invalid configuration file entry: %s=%s",
2287 var, val);
2289 free(line);
2292 fclose(config);
2295 char *
2296 js_ref_to_string(JSContextRef context, JSValueRef ref)
2298 char *s = NULL;
2299 size_t l;
2300 JSStringRef jsref;
2302 jsref = JSValueToStringCopy(context, ref, NULL);
2303 if (jsref == NULL)
2304 return (NULL);
2306 l = JSStringGetMaximumUTF8CStringSize(jsref);
2307 s = g_malloc(l);
2308 if (s)
2309 JSStringGetUTF8CString(jsref, s, l);
2310 JSStringRelease(jsref);
2312 return (s);
2315 void
2316 disable_hints(struct tab *t)
2318 bzero(t->hint_buf, sizeof t->hint_buf);
2319 bzero(t->hint_num, sizeof t->hint_num);
2320 run_script(t, "vimprobable_clear()");
2321 t->hints_on = 0;
2322 t->hint_mode = XT_HINT_NONE;
2325 void
2326 enable_hints(struct tab *t)
2328 bzero(t->hint_buf, sizeof t->hint_buf);
2329 run_script(t, "vimprobable_show_hints()");
2330 t->hints_on = 1;
2331 t->hint_mode = XT_HINT_NONE;
2334 #define XT_JS_OPEN ("open;")
2335 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2336 #define XT_JS_FIRE ("fire;")
2337 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2338 #define XT_JS_FOUND ("found;")
2339 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2342 run_script(struct tab *t, char *s)
2344 JSGlobalContextRef ctx;
2345 WebKitWebFrame *frame;
2346 JSStringRef str;
2347 JSValueRef val, exception;
2348 char *es, buf[128];
2350 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2351 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2353 frame = webkit_web_view_get_main_frame(t->wv);
2354 ctx = webkit_web_frame_get_global_context(frame);
2356 str = JSStringCreateWithUTF8CString(s);
2357 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2358 NULL, 0, &exception);
2359 JSStringRelease(str);
2361 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2362 if (val == NULL) {
2363 es = js_ref_to_string(ctx, exception);
2364 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2365 g_free(es);
2366 return (1);
2367 } else {
2368 es = js_ref_to_string(ctx, val);
2369 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2371 /* handle return value right here */
2372 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2373 disable_hints(t);
2374 marks_clear(t);
2375 load_uri(t, &es[XT_JS_OPEN_LEN]);
2378 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2379 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2380 &es[XT_JS_FIRE_LEN]);
2381 run_script(t, buf);
2382 disable_hints(t);
2385 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2386 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2387 disable_hints(t);
2390 g_free(es);
2393 return (0);
2397 hint(struct tab *t, struct karg *args)
2400 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2402 if (t->hints_on == 0)
2403 enable_hints(t);
2404 else
2405 disable_hints(t);
2407 return (0);
2410 void
2411 apply_style(struct tab *t)
2413 g_object_set(G_OBJECT(t->settings),
2414 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2418 userstyle(struct tab *t, struct karg *args)
2420 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2422 if (t->styled) {
2423 t->styled = 0;
2424 g_object_set(G_OBJECT(t->settings),
2425 "user-stylesheet-uri", NULL, (char *)NULL);
2426 } else {
2427 t->styled = 1;
2428 apply_style(t);
2430 return (0);
2434 * Doesn't work fully, due to the following bug:
2435 * https://bugs.webkit.org/show_bug.cgi?id=51747
2438 restore_global_history(void)
2440 char file[PATH_MAX];
2441 FILE *f;
2442 struct history *h;
2443 gchar *uri;
2444 gchar *title;
2445 const char delim[3] = {'\\', '\\', '\0'};
2447 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2449 if ((f = fopen(file, "r")) == NULL) {
2450 warnx("%s: fopen", __func__);
2451 return (1);
2454 for (;;) {
2455 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2456 if (feof(f) || ferror(f))
2457 break;
2459 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2460 if (feof(f) || ferror(f)) {
2461 free(uri);
2462 warnx("%s: broken history file\n", __func__);
2463 return (1);
2466 if (uri && strlen(uri) && title && strlen(title)) {
2467 webkit_web_history_item_new_with_data(uri, title);
2468 h = g_malloc(sizeof(struct history));
2469 h->uri = g_strdup(uri);
2470 h->title = g_strdup(title);
2471 RB_INSERT(history_list, &hl, h);
2472 completion_add_uri(h->uri);
2473 } else {
2474 warnx("%s: failed to restore history\n", __func__);
2475 free(uri);
2476 free(title);
2477 return (1);
2480 free(uri);
2481 free(title);
2482 uri = NULL;
2483 title = NULL;
2486 return (0);
2490 save_global_history_to_disk(struct tab *t)
2492 char file[PATH_MAX];
2493 FILE *f;
2494 struct history *h;
2496 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2498 if ((f = fopen(file, "w")) == NULL) {
2499 show_oops(t, "%s: global history file: %s",
2500 __func__, strerror(errno));
2501 return (1);
2504 RB_FOREACH_REVERSE(h, history_list, &hl) {
2505 if (h->uri && h->title)
2506 fprintf(f, "%s\n%s\n", h->uri, h->title);
2509 fclose(f);
2511 return (0);
2515 quit(struct tab *t, struct karg *args)
2517 if (save_global_history)
2518 save_global_history_to_disk(t);
2520 gtk_main_quit();
2522 return (1);
2525 void
2526 restore_sessions_list(void)
2528 DIR *sdir = NULL;
2529 struct dirent *dp = NULL;
2530 struct session *s;
2532 sdir = opendir(sessions_dir);
2533 if (sdir) {
2534 while ((dp = readdir(sdir)) != NULL)
2535 if (dp->d_type == DT_REG) {
2536 s = g_malloc(sizeof(struct session));
2537 s->name = g_strdup(dp->d_name);
2538 TAILQ_INSERT_TAIL(&sessions, s, entry);
2540 closedir(sdir);
2545 open_tabs(struct tab *t, struct karg *a)
2547 char file[PATH_MAX];
2548 FILE *f = NULL;
2549 char *uri = NULL;
2550 int rv = 1;
2551 struct tab *ti, *tt;
2553 if (a == NULL)
2554 goto done;
2556 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2557 if ((f = fopen(file, "r")) == NULL)
2558 goto done;
2560 ti = TAILQ_LAST(&tabs, tab_list);
2562 for (;;) {
2563 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2564 if (feof(f) || ferror(f))
2565 break;
2567 /* retrieve session name */
2568 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2569 strlcpy(named_session,
2570 &uri[strlen(XT_SAVE_SESSION_ID)],
2571 sizeof named_session);
2572 continue;
2575 if (uri && strlen(uri))
2576 create_new_tab(uri, NULL, 1, -1);
2578 free(uri);
2579 uri = NULL;
2582 /* close open tabs */
2583 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2584 for (;;) {
2585 tt = TAILQ_FIRST(&tabs);
2586 if (tt != ti) {
2587 delete_tab(tt);
2588 continue;
2590 delete_tab(tt);
2591 break;
2593 recalc_tabs();
2596 rv = 0;
2597 done:
2598 if (f)
2599 fclose(f);
2601 return (rv);
2605 restore_saved_tabs(void)
2607 char file[PATH_MAX];
2608 int unlink_file = 0;
2609 struct stat sb;
2610 struct karg a;
2611 int rv = 0;
2613 snprintf(file, sizeof file, "%s/%s",
2614 sessions_dir, XT_RESTART_TABS_FILE);
2615 if (stat(file, &sb) == -1)
2616 a.s = XT_SAVED_TABS_FILE;
2617 else {
2618 unlink_file = 1;
2619 a.s = XT_RESTART_TABS_FILE;
2622 a.i = XT_SES_DONOTHING;
2623 rv = open_tabs(NULL, &a);
2625 if (unlink_file)
2626 unlink(file);
2628 return (rv);
2632 save_tabs(struct tab *t, struct karg *a)
2634 char file[PATH_MAX];
2635 FILE *f;
2636 int num_tabs = 0, i;
2637 struct tab **stabs = NULL;
2639 if (a == NULL)
2640 return (1);
2641 if (a->s == NULL)
2642 snprintf(file, sizeof file, "%s/%s",
2643 sessions_dir, named_session);
2644 else
2645 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2647 if ((f = fopen(file, "w")) == NULL) {
2648 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2649 return (1);
2652 /* save session name */
2653 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2655 /* Save tabs, in the order they are arranged in the notebook. */
2656 num_tabs = sort_tabs_by_page_num(&stabs);
2658 for (i = 0; i < num_tabs; i++)
2659 if (stabs[i]) {
2660 if (get_uri(stabs[i]) != NULL)
2661 fprintf(f, "%s\n", get_uri(stabs[i]));
2662 else if (gtk_entry_get_text(GTK_ENTRY(
2663 stabs[i]->uri_entry)))
2664 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2665 stabs[i]->uri_entry)));
2668 g_free(stabs);
2670 /* try and make sure this gets to disk NOW. XXX Backup first? */
2671 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2672 show_oops(t, "May not have managed to save session: %s",
2673 strerror(errno));
2676 fclose(f);
2678 return (0);
2682 save_tabs_and_quit(struct tab *t, struct karg *args)
2684 struct karg a;
2686 a.s = NULL;
2687 save_tabs(t, &a);
2688 quit(t, NULL);
2690 return (1);
2694 run_page_script(struct tab *t, struct karg *args)
2696 const gchar *uri;
2697 char *tmp, script[PATH_MAX];
2699 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2700 if (tmp[0] == '\0') {
2701 show_oops(t, "no script specified");
2702 return (1);
2705 if ((uri = get_uri(t)) == NULL) {
2706 show_oops(t, "tab is empty, not running script");
2707 return (1);
2710 if (tmp[0] == '~')
2711 snprintf(script, sizeof script, "%s/%s",
2712 pwd->pw_dir, &tmp[1]);
2713 else
2714 strlcpy(script, tmp, sizeof script);
2716 switch (fork()) {
2717 case -1:
2718 show_oops(t, "can't fork to run script");
2719 return (1);
2720 /* NOTREACHED */
2721 case 0:
2722 break;
2723 default:
2724 return (0);
2727 /* child */
2728 execlp(script, script, uri, (void *)NULL);
2730 _exit(0);
2732 /* NOTREACHED */
2734 return (0);
2738 yank_uri(struct tab *t, struct karg *args)
2740 const gchar *uri;
2741 GtkClipboard *clipboard;
2743 if ((uri = get_uri(t)) == NULL)
2744 return (1);
2746 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2747 gtk_clipboard_set_text(clipboard, uri, -1);
2749 return (0);
2753 paste_uri(struct tab *t, struct karg *args)
2755 GtkClipboard *clipboard;
2756 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2757 gint len;
2758 gchar *p = NULL, *uri;
2760 /* try primary clipboard first */
2761 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2762 p = gtk_clipboard_wait_for_text(clipboard);
2764 /* if it failed get whatever text is in cut_buffer0 */
2765 if (p == NULL && xterm_workaround)
2766 if (gdk_property_get(gdk_get_default_root_window(),
2767 atom,
2768 gdk_atom_intern("STRING", FALSE),
2770 1024 * 1024 /* picked out of my butt */,
2771 FALSE,
2772 NULL,
2773 NULL,
2774 &len,
2775 (guchar **)&p)) {
2776 /* yes sir, we need to NUL the string */
2777 p[len] = '\0';
2780 if (p) {
2781 uri = p;
2782 while (*uri && isspace(*uri))
2783 uri++;
2784 if (strlen(uri) == 0) {
2785 show_oops(t, "empty paste buffer");
2786 goto done;
2788 if (guess_search == 0 && valid_url_type(uri)) {
2789 /* we can be clever and paste this in search box */
2790 show_oops(t, "not a valid URL");
2791 goto done;
2794 if (args->i == XT_PASTE_CURRENT_TAB)
2795 load_uri(t, uri);
2796 else if (args->i == XT_PASTE_NEW_TAB)
2797 create_new_tab(uri, NULL, 1, -1);
2800 done:
2801 if (p)
2802 g_free(p);
2804 return (0);
2807 gchar *
2808 find_domain(const gchar *s, int toplevel)
2810 SoupURI *uri;
2811 gchar *ret, *p;
2813 if (s == NULL)
2814 return (NULL);
2816 uri = soup_uri_new(s);
2818 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2819 return (NULL);
2822 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2823 if ((p = strrchr(uri->host, '.')) != NULL) {
2824 while(--p >= uri->host && *p != '.');
2825 p++;
2826 } else
2827 p = uri->host;
2828 } else
2829 p = uri->host;
2831 ret = g_strdup_printf(".%s", p);
2833 soup_uri_free(uri);
2835 return ret;
2839 toggle_cwl(struct tab *t, struct karg *args)
2841 struct domain *d;
2842 const gchar *uri;
2843 char *dom = NULL;
2844 int es;
2846 if (args == NULL)
2847 return (1);
2849 uri = get_uri(t);
2850 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2852 if (uri == NULL || dom == NULL ||
2853 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2854 show_oops(t, "Can't toggle domain in cookie white list");
2855 goto done;
2857 d = wl_find(dom, &c_wl);
2859 if (d == NULL)
2860 es = 0;
2861 else
2862 es = 1;
2864 if (args->i & XT_WL_TOGGLE)
2865 es = !es;
2866 else if ((args->i & XT_WL_ENABLE) && es != 1)
2867 es = 1;
2868 else if ((args->i & XT_WL_DISABLE) && es != 0)
2869 es = 0;
2871 if (es)
2872 /* enable cookies for domain */
2873 wl_add(dom, &c_wl, 0);
2874 else
2875 /* disable cookies for domain */
2876 RB_REMOVE(domain_list, &c_wl, d);
2878 if (args->i & XT_WL_RELOAD)
2879 webkit_web_view_reload(t->wv);
2881 done:
2882 g_free(dom);
2883 return (0);
2887 toggle_js(struct tab *t, struct karg *args)
2889 int es;
2890 const gchar *uri;
2891 struct domain *d;
2892 char *dom = NULL;
2894 if (args == NULL)
2895 return (1);
2897 g_object_get(G_OBJECT(t->settings),
2898 "enable-scripts", &es, (char *)NULL);
2899 if (args->i & XT_WL_TOGGLE)
2900 es = !es;
2901 else if ((args->i & XT_WL_ENABLE) && es != 1)
2902 es = 1;
2903 else if ((args->i & XT_WL_DISABLE) && es != 0)
2904 es = 0;
2905 else
2906 return (1);
2908 uri = get_uri(t);
2909 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2911 if (uri == NULL || dom == NULL ||
2912 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2913 show_oops(t, "Can't toggle domain in JavaScript white list");
2914 goto done;
2917 if (es) {
2918 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2919 wl_add(dom, &js_wl, 0 /* session */);
2920 } else {
2921 d = wl_find(dom, &js_wl);
2922 if (d)
2923 RB_REMOVE(domain_list, &js_wl, d);
2924 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2926 g_object_set(G_OBJECT(t->settings),
2927 "enable-scripts", es, (char *)NULL);
2928 g_object_set(G_OBJECT(t->settings),
2929 "javascript-can-open-windows-automatically", es, (char *)NULL);
2930 webkit_web_view_set_settings(t->wv, t->settings);
2932 if (args->i & XT_WL_RELOAD)
2933 webkit_web_view_reload(t->wv);
2934 done:
2935 if (dom)
2936 g_free(dom);
2937 return (0);
2940 void
2941 js_toggle_cb(GtkWidget *w, struct tab *t)
2943 struct karg a;
2945 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2946 toggle_cwl(t, &a);
2948 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2949 toggle_js(t, &a);
2953 toggle_src(struct tab *t, struct karg *args)
2955 gboolean mode;
2957 if (t == NULL)
2958 return (0);
2960 mode = webkit_web_view_get_view_source_mode(t->wv);
2961 webkit_web_view_set_view_source_mode(t->wv, !mode);
2962 webkit_web_view_reload(t->wv);
2964 return (0);
2967 void
2968 focus_webview(struct tab *t)
2970 if (t == NULL)
2971 return;
2973 /* only grab focus if we are visible */
2974 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2975 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2979 focus(struct tab *t, struct karg *args)
2981 if (t == NULL || args == NULL)
2982 return (1);
2984 if (show_url == 0)
2985 return (0);
2987 if (args->i == XT_FOCUS_URI)
2988 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2989 else if (args->i == XT_FOCUS_SEARCH)
2990 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2992 return (0);
2996 stats(struct tab *t, struct karg *args)
2998 char *page, *body, *s, line[64 * 1024];
2999 uint64_t line_count = 0;
3000 FILE *r_cookie_f;
3002 if (t == NULL)
3003 show_oops(NULL, "stats invalid parameters");
3005 line[0] = '\0';
3006 if (save_rejected_cookies) {
3007 if ((r_cookie_f = fopen(rc_fname, "r"))) {
3008 for (;;) {
3009 s = fgets(line, sizeof line, r_cookie_f);
3010 if (s == NULL || feof(r_cookie_f) ||
3011 ferror(r_cookie_f))
3012 break;
3013 line_count++;
3015 fclose(r_cookie_f);
3016 snprintf(line, sizeof line,
3017 "<br/>Cookies blocked(*) total: %llu", line_count);
3018 } else
3019 show_oops(t, "Can't open blocked cookies file: %s",
3020 strerror(errno));
3023 body = g_strdup_printf(
3024 "Cookies blocked(*) this session: %llu"
3025 "%s"
3026 "<p><small><b>*</b> results vary based on settings</small></p>",
3027 blocked_cookies,
3028 line);
3030 page = get_html_page("Statistics", body, "", 0);
3031 g_free(body);
3033 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
3034 g_free(page);
3036 return (0);
3040 marco(struct tab *t, struct karg *args)
3042 char *page, line[64 * 1024];
3043 int len;
3045 if (t == NULL)
3046 show_oops(NULL, "marco invalid parameters");
3048 line[0] = '\0';
3049 snprintf(line, sizeof line, "%s", marco_message(&len));
3051 page = get_html_page("Marco Sez...", line, "", 0);
3053 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
3054 g_free(page);
3056 return (0);
3060 blank(struct tab *t, struct karg *args)
3062 if (t == NULL)
3063 show_oops(NULL, "blank invalid parameters");
3065 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
3067 return (0);
3071 about(struct tab *t, struct karg *args)
3073 char *page, *body;
3075 if (t == NULL)
3076 show_oops(NULL, "about invalid parameters");
3078 body = g_strdup_printf("<b>Version: %s</b>"
3079 #ifdef XXXTERM_BUILDSTR
3080 "<br><b>Build: %s</b>"
3081 #endif
3082 "<p>"
3083 "Authors:"
3084 "<ul>"
3085 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3086 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3087 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3088 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3089 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3090 "</ul>"
3091 "Copyrights and licenses can be found on the XXXTerm "
3092 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3093 "</p>",
3094 #ifdef XXXTERM_BUILDSTR
3095 version, XXXTERM_BUILDSTR
3096 #else
3097 version
3098 #endif
3101 page = get_html_page("About", body, "", 0);
3102 g_free(body);
3104 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3105 g_free(page);
3107 return (0);
3111 help(struct tab *t, struct karg *args)
3113 char *page, *head, *body;
3115 if (t == NULL)
3116 show_oops(NULL, "help invalid parameters");
3118 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3119 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3120 "</head>\n";
3121 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3122 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3123 "cgi-bin/man-cgi?xxxterm</a>";
3125 page = get_html_page(XT_NAME, body, head, FALSE);
3127 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3128 g_free(page);
3130 return (0);
3134 startpage(struct tab *t, struct karg *args)
3136 char *page, *body, *b;
3137 struct sp *s;
3139 if (t == NULL)
3140 show_oops(NULL, "startpage invalid parameters");
3142 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3144 TAILQ_FOREACH(s, &spl, entry) {
3145 b = body;
3146 body = g_strdup_printf("%s%s<br>", body, s->line);
3147 g_free(b);
3150 page = get_html_page("Startup Exception", body, "", 0);
3151 g_free(body);
3153 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3154 g_free(page);
3156 return (0);
3159 void
3160 startpage_add(const char *fmt, ...)
3162 va_list ap;
3163 char *msg;
3164 struct sp *s;
3166 if (fmt == NULL)
3167 return;
3169 va_start(ap, fmt);
3170 if (vasprintf(&msg, fmt, ap) == -1)
3171 errx(1, "startpage_add failed");
3172 va_end(ap);
3174 s = g_malloc0(sizeof *s);
3175 s->line = msg;
3177 TAILQ_INSERT_TAIL(&spl, s, entry);
3181 * update all favorite tabs apart from one. Pass NULL if
3182 * you want to update all.
3184 void
3185 update_favorite_tabs(struct tab *apart_from)
3187 struct tab *t;
3188 if (!updating_fl_tabs) {
3189 updating_fl_tabs = 1; /* stop infinite recursion */
3190 TAILQ_FOREACH(t, &tabs, entry)
3191 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3192 && (t != apart_from))
3193 xtp_page_fl(t, NULL);
3194 updating_fl_tabs = 0;
3198 /* show a list of favorites (bookmarks) */
3200 xtp_page_fl(struct tab *t, struct karg *args)
3202 char file[PATH_MAX];
3203 FILE *f;
3204 char *uri = NULL, *title = NULL;
3205 size_t len, lineno = 0;
3206 int i, failed = 0;
3207 char *body, *tmp, *page = NULL;
3208 const char delim[3] = {'\\', '\\', '\0'};
3210 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3212 if (t == NULL)
3213 warn("%s: bad param", __func__);
3215 /* new session key */
3216 if (!updating_fl_tabs)
3217 generate_xtp_session_key(&fl_session_key);
3219 /* open favorites */
3220 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3221 if ((f = fopen(file, "r")) == NULL) {
3222 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3223 return (1);
3226 /* body */
3227 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3228 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3229 "<th style='width: 40px'>Rm</th></tr>\n");
3231 for (i = 1;;) {
3232 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3233 if (feof(f) || ferror(f))
3234 break;
3235 if (strlen(title) == 0 || title[0] == '#') {
3236 free(title);
3237 title = NULL;
3238 continue;
3241 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3242 if (feof(f) || ferror(f)) {
3243 show_oops(t, "favorites file corrupt");
3244 failed = 1;
3245 break;
3248 tmp = body;
3249 body = g_strdup_printf("%s<tr>"
3250 "<td>%d</td>"
3251 "<td><a href='%s'>%s</a></td>"
3252 "<td style='text-align: center'>"
3253 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3254 "</tr>\n",
3255 body, i, uri, title,
3256 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3258 g_free(tmp);
3260 free(uri);
3261 uri = NULL;
3262 free(title);
3263 title = NULL;
3264 i++;
3266 fclose(f);
3268 /* if none, say so */
3269 if (i == 1) {
3270 tmp = body;
3271 body = g_strdup_printf("%s<tr>"
3272 "<td colspan='3' style='text-align: center'>"
3273 "No favorites - To add one use the 'favadd' command."
3274 "</td></tr>", body);
3275 g_free(tmp);
3278 tmp = body;
3279 body = g_strdup_printf("%s</table>", body);
3280 g_free(tmp);
3282 if (uri)
3283 free(uri);
3284 if (title)
3285 free(title);
3287 /* render */
3288 if (!failed) {
3289 page = get_html_page("Favorites", body, "", 1);
3290 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3291 g_free(page);
3294 update_favorite_tabs(t);
3296 if (body)
3297 g_free(body);
3299 return (failed);
3302 void
3303 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3304 size_t cert_count, char *title)
3306 gnutls_datum_t cinfo;
3307 char *tmp, *body;
3308 int i;
3310 body = g_strdup("");
3312 for (i = 0; i < cert_count; i++) {
3313 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3314 &cinfo))
3315 return;
3317 tmp = body;
3318 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3319 body, i, cinfo.data);
3320 gnutls_free(cinfo.data);
3321 g_free(tmp);
3324 tmp = get_html_page(title, body, "", 0);
3325 g_free(body);
3327 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3328 g_free(tmp);
3332 ca_cmd(struct tab *t, struct karg *args)
3334 FILE *f = NULL;
3335 int rv = 1, certs = 0, certs_read;
3336 struct stat sb;
3337 gnutls_datum_t dt;
3338 gnutls_x509_crt_t *c = NULL;
3339 char *certs_buf = NULL, *s;
3341 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3342 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3343 return (1);
3346 if (fstat(fileno(f), &sb) == -1) {
3347 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3348 goto done;
3351 certs_buf = g_malloc(sb.st_size + 1);
3352 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3353 show_oops(t, "Can't read CA file: %s", strerror(errno));
3354 goto done;
3356 certs_buf[sb.st_size] = '\0';
3358 s = certs_buf;
3359 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3360 certs++;
3361 s += strlen("BEGIN CERTIFICATE");
3364 bzero(&dt, sizeof dt);
3365 dt.data = (unsigned char *)certs_buf;
3366 dt.size = sb.st_size;
3367 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3368 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3369 GNUTLS_X509_FMT_PEM, 0);
3370 if (certs_read <= 0) {
3371 show_oops(t, "No cert(s) available");
3372 goto done;
3374 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3375 done:
3376 if (c)
3377 g_free(c);
3378 if (certs_buf)
3379 g_free(certs_buf);
3380 if (f)
3381 fclose(f);
3383 return (rv);
3387 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
3388 size_t domain_sz)
3390 SoupURI *su = NULL;
3391 struct addrinfo hints, *res = NULL, *ai;
3392 int rv = -1, s = -1, on, error;
3393 char port[8];
3394 static gchar myerror[256];
3396 myerror[0] = '\0';
3397 *error_str = myerror;
3398 if (uri && !g_str_has_prefix(uri, "https://")) {
3399 *error_str = "invalid URI";
3400 goto done;
3403 su = soup_uri_new(uri);
3404 if (su == NULL) {
3405 *error_str = "invalid soup URI";
3406 goto done;
3408 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3409 *error_str = "invalid HTTPS URI";
3410 goto done;
3413 snprintf(port, sizeof port, "%d", su->port);
3414 bzero(&hints, sizeof(struct addrinfo));
3415 hints.ai_flags = AI_CANONNAME;
3416 hints.ai_family = AF_UNSPEC;
3417 hints.ai_socktype = SOCK_STREAM;
3419 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3420 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
3421 gai_strerror(errno));
3422 goto done;
3425 for (ai = res; ai; ai = ai->ai_next) {
3426 if (s != -1) {
3427 close(s);
3428 s = -1;
3431 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3432 continue;
3433 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3434 if (s == -1)
3435 continue;
3436 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3437 sizeof(on)) == -1)
3438 continue;
3439 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3440 break;
3442 if (s == -1) {
3443 snprintf(myerror, sizeof myerror,
3444 "could not obtain certificates from: %s",
3445 su->host);
3446 goto done;
3449 if (domain)
3450 strlcpy(domain, su->host, domain_sz);
3451 rv = s;
3452 done:
3453 if (su)
3454 soup_uri_free(su);
3455 if (res)
3456 freeaddrinfo(res);
3457 if (rv == -1 && s != -1)
3458 close(s);
3460 return (rv);
3464 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3466 if (gsession)
3467 gnutls_deinit(gsession);
3468 if (xcred)
3469 gnutls_certificate_free_credentials(xcred);
3471 return (0);
3475 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
3476 gnutls_certificate_credentials_t *xc)
3478 gnutls_certificate_credentials_t xcred;
3479 gnutls_session_t gsession;
3480 int rv = 1;
3481 static gchar myerror[1024];
3483 if (gs == NULL || xc == NULL)
3484 goto done;
3486 myerror[0] = '\0';
3487 *gs = NULL;
3488 *xc = NULL;
3490 gnutls_certificate_allocate_credentials(&xcred);
3491 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3492 GNUTLS_X509_FMT_PEM);
3494 gnutls_init(&gsession, GNUTLS_CLIENT);
3495 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3496 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3497 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3498 if ((rv = gnutls_handshake(gsession)) < 0) {
3499 snprintf(myerror, sizeof myerror,
3500 "gnutls_handshake failed %d fatal %d %s",
3502 gnutls_error_is_fatal(rv),
3503 gnutls_strerror_name(rv));
3504 stop_tls(gsession, xcred);
3505 goto done;
3508 gnutls_credentials_type_t cred;
3509 cred = gnutls_auth_get_type(gsession);
3510 if (cred != GNUTLS_CRD_CERTIFICATE) {
3511 snprintf(myerror, sizeof myerror,
3512 "gnutls_auth_get_type failed %d",
3513 (int)cred);
3514 stop_tls(gsession, xcred);
3515 goto done;
3518 *gs = gsession;
3519 *xc = xcred;
3520 rv = 0;
3521 done:
3522 *error_str = myerror;
3523 return (rv);
3527 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3528 size_t *cert_count)
3530 unsigned int len;
3531 const gnutls_datum_t *cl;
3532 gnutls_x509_crt_t *all_certs;
3533 int i, rv = 1;
3535 if (certs == NULL || cert_count == NULL)
3536 goto done;
3537 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3538 goto done;
3539 cl = gnutls_certificate_get_peers(gsession, &len);
3540 if (len == 0)
3541 goto done;
3543 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3544 for (i = 0; i < len; i++) {
3545 gnutls_x509_crt_init(&all_certs[i]);
3546 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3547 GNUTLS_X509_FMT_PEM < 0)) {
3548 g_free(all_certs);
3549 goto done;
3553 *certs = all_certs;
3554 *cert_count = len;
3555 rv = 0;
3556 done:
3557 return (rv);
3560 void
3561 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3563 int i;
3565 for (i = 0; i < cert_count; i++)
3566 gnutls_x509_crt_deinit(certs[i]);
3567 g_free(certs);
3570 void
3571 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3573 GdkColor c_text, c_base;
3575 gdk_color_parse(text, &c_text);
3576 gdk_color_parse(base, &c_base);
3578 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3579 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3580 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3581 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3583 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3584 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3585 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3586 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3589 void
3590 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3591 size_t cert_count, char *domain)
3593 size_t cert_buf_sz;
3594 char cert_buf[64 * 1024], file[PATH_MAX];
3595 int i;
3596 FILE *f;
3597 GdkColor color;
3599 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3600 return;
3602 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3603 if ((f = fopen(file, "w")) == NULL) {
3604 show_oops(t, "Can't create cert file %s %s",
3605 file, strerror(errno));
3606 return;
3609 for (i = 0; i < cert_count; i++) {
3610 cert_buf_sz = sizeof cert_buf;
3611 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3612 cert_buf, &cert_buf_sz)) {
3613 show_oops(t, "gnutls_x509_crt_export failed");
3614 goto done;
3616 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3617 show_oops(t, "Can't write certs: %s", strerror(errno));
3618 goto done;
3622 /* not the best spot but oh well */
3623 gdk_color_parse("lightblue", &color);
3624 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3625 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3626 done:
3627 fclose(f);
3630 enum cert_trust {
3631 CERT_LOCAL,
3632 CERT_TRUSTED,
3633 CERT_UNTRUSTED,
3634 CERT_BAD
3637 enum cert_trust
3638 load_compare_cert(const gchar *uri, const gchar **error_str)
3640 char domain[8182], file[PATH_MAX];
3641 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3642 int s = -1, i;
3643 unsigned int error;
3644 FILE *f = NULL;
3645 size_t cert_buf_sz, cert_count;
3646 enum cert_trust rv = CERT_UNTRUSTED;
3647 static gchar serr[80];
3648 gnutls_session_t gsession;
3649 gnutls_x509_crt_t *certs;
3650 gnutls_certificate_credentials_t xcred;
3652 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3654 serr[0] = '\0';
3655 *error_str = serr;
3656 if ((s = connect_socket_from_uri(uri, error_str, domain,
3657 sizeof domain)) == -1)
3658 return (rv);
3660 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3662 /* go ssl/tls */
3663 if (start_tls(error_str, s, &gsession, &xcred))
3664 goto done;
3665 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3667 /* verify certs in case cert file doesn't exist */
3668 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3669 GNUTLS_E_SUCCESS) {
3670 *error_str = "Invalid certificates";
3671 goto done;
3674 /* get certs */
3675 if (get_connection_certs(gsession, &certs, &cert_count)) {
3676 *error_str = "Can't get connection certificates";
3677 goto done;
3680 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3681 if ((f = fopen(file, "r")) == NULL) {
3682 if (!error)
3683 rv = CERT_TRUSTED;
3684 goto freeit;
3687 for (i = 0; i < cert_count; i++) {
3688 cert_buf_sz = sizeof cert_buf;
3689 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3690 cert_buf, &cert_buf_sz)) {
3691 goto freeit;
3693 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3694 rv = CERT_BAD; /* critical */
3695 goto freeit;
3697 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3698 rv = CERT_BAD; /* critical */
3699 goto freeit;
3701 rv = CERT_LOCAL;
3704 freeit:
3705 if (f)
3706 fclose(f);
3707 free_connection_certs(certs, cert_count);
3708 done:
3709 /* we close the socket first for speed */
3710 if (s != -1)
3711 close(s);
3713 /* only complain if we didn't save it locally */
3714 if (error && rv != CERT_LOCAL) {
3715 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3716 if (error & GNUTLS_CERT_INVALID)
3717 strlcat(serr, "invalid, ", sizeof serr);
3718 if (error & GNUTLS_CERT_REVOKED)
3719 strlcat(serr, "revoked, ", sizeof serr);
3720 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3721 strlcat(serr, "signer not found, ", sizeof serr);
3722 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3723 strlcat(serr, "not signed by CA, ", sizeof serr);
3724 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3725 strlcat(serr, "insecure algorithm, ", sizeof serr);
3726 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3727 strlcat(serr, "not activated, ", sizeof serr);
3728 if (error & GNUTLS_CERT_EXPIRED)
3729 strlcat(serr, "expired, ", sizeof serr);
3730 for (i = strlen(serr) - 1; i > 0; i--)
3731 if (serr[i] == ',') {
3732 serr[i] = '\0';
3733 break;
3735 *error_str = serr;
3738 stop_tls(gsession, xcred);
3740 return (rv);
3744 cert_cmd(struct tab *t, struct karg *args)
3746 const gchar *uri, *error_str = NULL;
3747 char domain[8182];
3748 int s = -1;
3749 size_t cert_count;
3750 gnutls_session_t gsession;
3751 gnutls_x509_crt_t *certs;
3752 gnutls_certificate_credentials_t xcred;
3754 if (t == NULL)
3755 return (1);
3757 if (ssl_ca_file == NULL) {
3758 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3759 return (1);
3762 if ((uri = get_uri(t)) == NULL) {
3763 show_oops(t, "Invalid URI");
3764 return (1);
3767 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3768 sizeof domain)) == -1) {
3769 show_oops(t, "%s", error_str);
3770 return (1);
3773 /* go ssl/tls */
3774 if (start_tls(&error_str, s, &gsession, &xcred))
3775 goto done;
3777 /* get certs */
3778 if (get_connection_certs(gsession, &certs, &cert_count)) {
3779 show_oops(t, "get_connection_certs failed");
3780 goto done;
3783 if (args->i & XT_SHOW)
3784 show_certs(t, certs, cert_count, "Certificate Chain");
3785 else if (args->i & XT_SAVE)
3786 save_certs(t, certs, cert_count, domain);
3788 free_connection_certs(certs, cert_count);
3789 done:
3790 /* we close the socket first for speed */
3791 if (s != -1)
3792 close(s);
3793 stop_tls(gsession, xcred);
3794 if (error_str)
3795 show_oops(t, "%s", error_str);
3796 return (0);
3800 remove_cookie(int index)
3802 int i, rv = 1;
3803 GSList *cf;
3804 SoupCookie *c;
3806 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3808 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3810 for (i = 1; cf; cf = cf->next, i++) {
3811 if (i != index)
3812 continue;
3813 c = cf->data;
3814 print_cookie("remove cookie", c);
3815 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3816 rv = 0;
3817 break;
3820 soup_cookies_free(cf);
3822 return (rv);
3826 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3828 struct domain *d;
3829 char *tmp, *body;
3831 body = g_strdup("");
3833 /* p list */
3834 if (args->i & XT_WL_PERSISTENT) {
3835 tmp = body;
3836 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3837 g_free(tmp);
3838 RB_FOREACH(d, domain_list, wl) {
3839 if (d->handy == 0)
3840 continue;
3841 tmp = body;
3842 body = g_strdup_printf("%s%s<br/>", body, d->d);
3843 g_free(tmp);
3847 /* s list */
3848 if (args->i & XT_WL_SESSION) {
3849 tmp = body;
3850 body = g_strdup_printf("%s<h2>Session</h2>", body);
3851 g_free(tmp);
3852 RB_FOREACH(d, domain_list, wl) {
3853 if (d->handy == 1)
3854 continue;
3855 tmp = body;
3856 body = g_strdup_printf("%s%s<br/>", body, d->d);
3857 g_free(tmp);
3861 tmp = get_html_page(title, body, "", 0);
3862 g_free(body);
3863 if (wl == &js_wl)
3864 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3865 else
3866 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3867 g_free(tmp);
3868 return (0);
3872 wl_save(struct tab *t, struct karg *args, int js)
3874 char file[PATH_MAX];
3875 FILE *f;
3876 char *line = NULL, *lt = NULL, *dom = NULL;
3877 size_t linelen;
3878 const gchar *uri;
3879 struct karg a;
3880 struct domain *d;
3881 GSList *cf;
3882 SoupCookie *ci, *c;
3884 if (t == NULL || args == NULL)
3885 return (1);
3887 if (runtime_settings[0] == '\0')
3888 return (1);
3890 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3891 if ((f = fopen(file, "r+")) == NULL)
3892 return (1);
3894 uri = get_uri(t);
3895 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3896 if (uri == NULL || dom == NULL ||
3897 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3898 show_oops(t, "Can't add domain to %s white list",
3899 js ? "JavaScript" : "cookie");
3900 goto done;
3903 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3905 while (!feof(f)) {
3906 line = fparseln(f, &linelen, NULL, NULL, 0);
3907 if (line == NULL)
3908 continue;
3909 if (!strcmp(line, lt))
3910 goto done;
3911 free(line);
3912 line = NULL;
3915 fprintf(f, "%s\n", lt);
3917 a.i = XT_WL_ENABLE;
3918 a.i |= args->i;
3919 if (js) {
3920 d = wl_find(dom, &js_wl);
3921 if (!d) {
3922 settings_add("js_wl", dom);
3923 d = wl_find(dom, &js_wl);
3925 toggle_js(t, &a);
3926 } else {
3927 d = wl_find(dom, &c_wl);
3928 if (!d) {
3929 settings_add("cookie_wl", dom);
3930 d = wl_find(dom, &c_wl);
3932 toggle_cwl(t, &a);
3934 /* find and add to persistent jar */
3935 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3936 for (;cf; cf = cf->next) {
3937 ci = cf->data;
3938 if (!strcmp(dom, ci->domain) ||
3939 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3940 c = soup_cookie_copy(ci);
3941 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3944 soup_cookies_free(cf);
3946 if (d)
3947 d->handy = 1;
3949 done:
3950 if (line)
3951 free(line);
3952 if (dom)
3953 g_free(dom);
3954 if (lt)
3955 g_free(lt);
3956 fclose(f);
3958 return (0);
3962 js_show_wl(struct tab *t, struct karg *args)
3964 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3965 wl_show(t, args, "JavaScript White List", &js_wl);
3967 return (0);
3971 cookie_show_wl(struct tab *t, struct karg *args)
3973 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3974 wl_show(t, args, "Cookie White List", &c_wl);
3976 return (0);
3980 cookie_cmd(struct tab *t, struct karg *args)
3982 if (args->i & XT_SHOW)
3983 wl_show(t, args, "Cookie White List", &c_wl);
3984 else if (args->i & XT_WL_TOGGLE) {
3985 args->i |= XT_WL_RELOAD;
3986 toggle_cwl(t, args);
3987 } else if (args->i & XT_SAVE) {
3988 args->i |= XT_WL_RELOAD;
3989 wl_save(t, args, 0);
3990 } else if (args->i & XT_DELETE)
3991 show_oops(t, "'cookie delete' currently unimplemented");
3993 return (0);
3997 js_cmd(struct tab *t, struct karg *args)
3999 if (args->i & XT_SHOW)
4000 wl_show(t, args, "JavaScript White List", &js_wl);
4001 else if (args->i & XT_SAVE) {
4002 args->i |= XT_WL_RELOAD;
4003 wl_save(t, args, 1);
4004 } else if (args->i & XT_WL_TOGGLE) {
4005 args->i |= XT_WL_RELOAD;
4006 toggle_js(t, args);
4007 } else if (args->i & XT_DELETE)
4008 show_oops(t, "'js delete' currently unimplemented");
4010 return (0);
4014 toplevel_cmd(struct tab *t, struct karg *args)
4016 js_toggle_cb(t->js_toggle, t);
4018 return (0);
4022 add_favorite(struct tab *t, struct karg *args)
4024 char file[PATH_MAX];
4025 FILE *f;
4026 char *line = NULL;
4027 size_t urilen, linelen;
4028 const gchar *uri, *title;
4030 if (t == NULL)
4031 return (1);
4033 /* don't allow adding of xtp pages to favorites */
4034 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4035 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4036 return (1);
4039 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4040 if ((f = fopen(file, "r+")) == NULL) {
4041 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4042 return (1);
4045 title = get_title(t, FALSE);
4046 uri = get_uri(t);
4048 if (title == NULL || uri == NULL) {
4049 show_oops(t, "can't add page to favorites");
4050 goto done;
4053 urilen = strlen(uri);
4055 for (;;) {
4056 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4057 if (feof(f) || ferror(f))
4058 break;
4060 if (linelen == urilen && !strcmp(line, uri))
4061 goto done;
4063 free(line);
4064 line = NULL;
4067 fprintf(f, "\n%s\n%s", title, uri);
4068 done:
4069 if (line)
4070 free(line);
4071 fclose(f);
4073 update_favorite_tabs(NULL);
4075 return (0);
4079 can_go_back_for_real(struct tab *t)
4081 int i;
4082 WebKitWebHistoryItem *item;
4083 const gchar *uri;
4085 if (t == NULL)
4086 return (FALSE);
4088 /* rely on webkit to make sure we can go backward when on an about page */
4089 uri = get_uri(t);
4090 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4091 return (webkit_web_view_can_go_back(t->wv));
4093 /* the back/forwars list is stupid so help determine if we can go back */
4094 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4095 item != NULL;
4096 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4097 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4098 return (TRUE);
4101 return (FALSE);
4105 can_go_forward_for_real(struct tab *t)
4107 int i;
4108 WebKitWebHistoryItem *item;
4109 const gchar *uri;
4111 if (t == NULL)
4112 return (FALSE);
4114 /* rely on webkit to make sure we can go forward when on an about page */
4115 uri = get_uri(t);
4116 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4117 return (webkit_web_view_can_go_forward(t->wv));
4119 /* the back/forwars list is stupid so help selecting a different item */
4120 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4121 item != NULL;
4122 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4123 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4124 return (TRUE);
4127 return (FALSE);
4130 void
4131 go_back_for_real(struct tab *t)
4133 int i;
4134 WebKitWebHistoryItem *item;
4135 const gchar *uri;
4137 if (t == NULL)
4138 return;
4140 uri = get_uri(t);
4141 if (uri == NULL) {
4142 webkit_web_view_go_back(t->wv);
4143 return;
4145 /* the back/forwars list is stupid so help selecting a different item */
4146 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4147 item != NULL;
4148 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4149 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4150 webkit_web_view_go_to_back_forward_item(t->wv, item);
4151 break;
4156 void
4157 go_forward_for_real(struct tab *t)
4159 int i;
4160 WebKitWebHistoryItem *item;
4161 const gchar *uri;
4163 if (t == NULL)
4164 return;
4166 uri = get_uri(t);
4167 if (uri == NULL) {
4168 webkit_web_view_go_forward(t->wv);
4169 return;
4171 /* the back/forwars list is stupid so help selecting a different item */
4172 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4173 item != NULL;
4174 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4175 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4176 webkit_web_view_go_to_back_forward_item(t->wv, item);
4177 break;
4183 navaction(struct tab *t, struct karg *args)
4185 WebKitWebHistoryItem *item;
4186 WebKitWebFrame *frame;
4188 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4189 t->tab_id, args->i);
4191 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4192 if (t->item) {
4193 if (args->i == XT_NAV_BACK)
4194 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4195 else
4196 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4197 if (item == NULL)
4198 return (XT_CB_PASSTHROUGH);
4199 webkit_web_view_go_to_back_forward_item(t->wv, item);
4200 t->item = NULL;
4201 return (XT_CB_PASSTHROUGH);
4204 switch (args->i) {
4205 case XT_NAV_BACK:
4206 marks_clear(t);
4207 go_back_for_real(t);
4208 break;
4209 case XT_NAV_FORWARD:
4210 marks_clear(t);
4211 go_forward_for_real(t);
4212 break;
4213 case XT_NAV_RELOAD:
4214 frame = webkit_web_view_get_main_frame(t->wv);
4215 webkit_web_frame_reload(frame);
4216 break;
4218 return (XT_CB_PASSTHROUGH);
4222 move(struct tab *t, struct karg *args)
4224 GtkAdjustment *adjust;
4225 double pi, si, pos, ps, upper, lower, max;
4226 double percent;
4228 switch (args->i) {
4229 case XT_MOVE_DOWN:
4230 case XT_MOVE_UP:
4231 case XT_MOVE_BOTTOM:
4232 case XT_MOVE_TOP:
4233 case XT_MOVE_PAGEDOWN:
4234 case XT_MOVE_PAGEUP:
4235 case XT_MOVE_HALFDOWN:
4236 case XT_MOVE_HALFUP:
4237 case XT_MOVE_PERCENT:
4238 adjust = t->adjust_v;
4239 break;
4240 default:
4241 adjust = t->adjust_h;
4242 break;
4245 pos = gtk_adjustment_get_value(adjust);
4246 ps = gtk_adjustment_get_page_size(adjust);
4247 upper = gtk_adjustment_get_upper(adjust);
4248 lower = gtk_adjustment_get_lower(adjust);
4249 si = gtk_adjustment_get_step_increment(adjust);
4250 pi = gtk_adjustment_get_page_increment(adjust);
4251 max = upper - ps;
4253 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4254 "max %f si %f pi %f\n",
4255 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4256 pos, ps, upper, lower, max, si, pi);
4258 switch (args->i) {
4259 case XT_MOVE_DOWN:
4260 case XT_MOVE_RIGHT:
4261 pos += si;
4262 gtk_adjustment_set_value(adjust, MIN(pos, max));
4263 break;
4264 case XT_MOVE_UP:
4265 case XT_MOVE_LEFT:
4266 pos -= si;
4267 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4268 break;
4269 case XT_MOVE_BOTTOM:
4270 case XT_MOVE_FARRIGHT:
4271 gtk_adjustment_set_value(adjust, max);
4272 break;
4273 case XT_MOVE_TOP:
4274 case XT_MOVE_FARLEFT:
4275 gtk_adjustment_set_value(adjust, lower);
4276 break;
4277 case XT_MOVE_PAGEDOWN:
4278 pos += pi;
4279 gtk_adjustment_set_value(adjust, MIN(pos, max));
4280 break;
4281 case XT_MOVE_PAGEUP:
4282 pos -= pi;
4283 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4284 break;
4285 case XT_MOVE_HALFDOWN:
4286 pos += pi / 2;
4287 gtk_adjustment_set_value(adjust, MIN(pos, max));
4288 break;
4289 case XT_MOVE_HALFUP:
4290 pos -= pi / 2;
4291 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4292 break;
4293 case XT_MOVE_PERCENT:
4294 percent = atoi(args->s) / 100.0;
4295 pos = max * percent;
4296 if (pos < 0.0 || pos > max)
4297 break;
4298 gtk_adjustment_set_value(adjust, pos);
4299 break;
4300 default:
4301 return (XT_CB_PASSTHROUGH);
4304 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4306 return (XT_CB_HANDLED);
4309 void
4310 url_set_visibility(void)
4312 struct tab *t;
4314 TAILQ_FOREACH(t, &tabs, entry)
4315 if (show_url == 0) {
4316 gtk_widget_hide(t->toolbar);
4317 focus_webview(t);
4318 } else
4319 gtk_widget_show(t->toolbar);
4322 void
4323 notebook_tab_set_visibility(void)
4325 if (show_tabs == 0) {
4326 gtk_widget_hide(tab_bar);
4327 gtk_notebook_set_show_tabs(notebook, FALSE);
4328 } else {
4329 if (tab_style == XT_TABS_NORMAL) {
4330 gtk_widget_hide(tab_bar);
4331 gtk_notebook_set_show_tabs(notebook, TRUE);
4332 } else if (tab_style == XT_TABS_COMPACT) {
4333 gtk_widget_show(tab_bar);
4334 gtk_notebook_set_show_tabs(notebook, FALSE);
4339 void
4340 statusbar_set_visibility(void)
4342 struct tab *t;
4344 TAILQ_FOREACH(t, &tabs, entry)
4345 if (show_statusbar == 0) {
4346 gtk_widget_hide(t->statusbar_box);
4347 focus_webview(t);
4348 } else
4349 gtk_widget_show(t->statusbar_box);
4352 void
4353 url_set(struct tab *t, int enable_url_entry)
4355 GdkPixbuf *pixbuf;
4356 int progress;
4358 show_url = enable_url_entry;
4360 if (enable_url_entry) {
4361 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4362 GTK_ENTRY_ICON_PRIMARY, NULL);
4363 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4364 } else {
4365 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4366 GTK_ENTRY_ICON_PRIMARY);
4367 progress =
4368 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4369 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4370 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4371 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4372 progress);
4377 fullscreen(struct tab *t, struct karg *args)
4379 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4381 if (t == NULL)
4382 return (XT_CB_PASSTHROUGH);
4384 if (show_url == 0) {
4385 url_set(t, 1);
4386 show_tabs = 1;
4387 } else {
4388 url_set(t, 0);
4389 show_tabs = 0;
4392 url_set_visibility();
4393 notebook_tab_set_visibility();
4395 return (XT_CB_HANDLED);
4399 statustoggle(struct tab *t, struct karg *args)
4401 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4403 if (show_statusbar == 1) {
4404 show_statusbar = 0;
4405 statusbar_set_visibility();
4406 } else if (show_statusbar == 0) {
4407 show_statusbar = 1;
4408 statusbar_set_visibility();
4410 return (XT_CB_HANDLED);
4414 urlaction(struct tab *t, struct karg *args)
4416 int rv = XT_CB_HANDLED;
4418 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4420 if (t == NULL)
4421 return (XT_CB_PASSTHROUGH);
4423 switch (args->i) {
4424 case XT_URL_SHOW:
4425 if (show_url == 0) {
4426 url_set(t, 1);
4427 url_set_visibility();
4429 break;
4430 case XT_URL_HIDE:
4431 if (show_url == 1) {
4432 url_set(t, 0);
4433 url_set_visibility();
4435 break;
4437 return (rv);
4441 tabaction(struct tab *t, struct karg *args)
4443 int rv = XT_CB_HANDLED;
4444 char *url = args->s;
4445 struct undo *u;
4446 struct tab *tt;
4448 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4450 if (t == NULL)
4451 return (XT_CB_PASSTHROUGH);
4453 switch (args->i) {
4454 case XT_TAB_NEW:
4455 if (strlen(url) > 0)
4456 create_new_tab(url, NULL, 1, args->precount);
4457 else
4458 create_new_tab(NULL, NULL, 1, args->precount);
4459 break;
4460 case XT_TAB_DELETE:
4461 if (args->precount < 0)
4462 delete_tab(t);
4463 else
4464 TAILQ_FOREACH(tt, &tabs, entry)
4465 if (tt->tab_id == args->precount - 1) {
4466 delete_tab(tt);
4467 break;
4469 break;
4470 case XT_TAB_DELQUIT:
4471 if (gtk_notebook_get_n_pages(notebook) > 1)
4472 delete_tab(t);
4473 else
4474 quit(t, args);
4475 break;
4476 case XT_TAB_OPEN:
4477 if (strlen(url) > 0)
4479 else {
4480 rv = XT_CB_PASSTHROUGH;
4481 goto done;
4483 load_uri(t, url);
4484 break;
4485 case XT_TAB_SHOW:
4486 if (show_tabs == 0) {
4487 show_tabs = 1;
4488 notebook_tab_set_visibility();
4490 break;
4491 case XT_TAB_HIDE:
4492 if (show_tabs == 1) {
4493 show_tabs = 0;
4494 notebook_tab_set_visibility();
4496 break;
4497 case XT_TAB_NEXTSTYLE:
4498 if (tab_style == XT_TABS_NORMAL) {
4499 tab_style = XT_TABS_COMPACT;
4500 recolor_compact_tabs();
4502 else
4503 tab_style = XT_TABS_NORMAL;
4504 notebook_tab_set_visibility();
4505 break;
4506 case XT_TAB_UNDO_CLOSE:
4507 if (undo_count == 0) {
4508 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4509 __func__);
4510 goto done;
4511 } else {
4512 undo_count--;
4513 u = TAILQ_FIRST(&undos);
4514 create_new_tab(u->uri, u, 1, -1);
4516 TAILQ_REMOVE(&undos, u, entry);
4517 g_free(u->uri);
4518 /* u->history is freed in create_new_tab() */
4519 g_free(u);
4521 break;
4522 default:
4523 rv = XT_CB_PASSTHROUGH;
4524 goto done;
4527 done:
4528 if (args->s) {
4529 g_free(args->s);
4530 args->s = NULL;
4533 return (rv);
4537 resizetab(struct tab *t, struct karg *args)
4539 if (t == NULL || args == NULL) {
4540 show_oops(NULL, "resizetab invalid parameters");
4541 return (XT_CB_PASSTHROUGH);
4544 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4545 t->tab_id, args->i);
4547 setzoom_webkit(t, args->i);
4549 return (XT_CB_HANDLED);
4553 movetab(struct tab *t, struct karg *args)
4555 int n, dest;
4557 if (t == NULL || args == NULL) {
4558 show_oops(NULL, "movetab invalid parameters");
4559 return (XT_CB_PASSTHROUGH);
4562 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4563 t->tab_id, args->i);
4565 if (args->i >= XT_TAB_INVALID)
4566 return (XT_CB_PASSTHROUGH);
4568 if (TAILQ_EMPTY(&tabs))
4569 return (XT_CB_PASSTHROUGH);
4571 n = gtk_notebook_get_n_pages(notebook);
4572 dest = gtk_notebook_get_current_page(notebook);
4574 switch (args->i) {
4575 case XT_TAB_NEXT:
4576 if (args->precount < 0)
4577 dest = dest == n - 1 ? 0 : dest + 1;
4578 else
4579 dest = args->precount - 1;
4581 break;
4582 case XT_TAB_PREV:
4583 if (args->precount < 0)
4584 dest -= 1;
4585 else
4586 dest -= args->precount % n;
4588 if (dest < 0)
4589 dest += n;
4591 break;
4592 case XT_TAB_FIRST:
4593 dest = 0;
4594 break;
4595 case XT_TAB_LAST:
4596 dest = n - 1;
4597 break;
4598 default:
4599 return (XT_CB_PASSTHROUGH);
4602 if (dest < 0 || dest >= n)
4603 return (XT_CB_PASSTHROUGH);
4604 if (t->tab_id == dest) {
4605 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4606 return (XT_CB_HANDLED);
4609 set_current_tab(dest);
4611 return (XT_CB_HANDLED);
4614 int cmd_prefix = 0;
4617 command(struct tab *t, struct karg *args)
4619 char *s = NULL, *ss = NULL;
4620 GdkColor color;
4621 const gchar *uri;
4623 if (t == NULL || args == NULL) {
4624 show_oops(NULL, "command invalid parameters");
4625 return (XT_CB_PASSTHROUGH);
4628 switch (args->i) {
4629 case '/':
4630 s = "/";
4631 break;
4632 case '?':
4633 s = "?";
4634 break;
4635 case ':':
4636 if (cmd_prefix == 0)
4637 s = ":";
4638 else {
4639 ss = g_strdup_printf(":%d", cmd_prefix);
4640 s = ss;
4641 cmd_prefix = 0;
4643 break;
4644 case XT_CMD_OPEN:
4645 s = ":open ";
4646 break;
4647 case XT_CMD_TABNEW:
4648 s = ":tabnew ";
4649 break;
4650 case XT_CMD_OPEN_CURRENT:
4651 s = ":open ";
4652 /* FALL THROUGH */
4653 case XT_CMD_TABNEW_CURRENT:
4654 if (!s) /* FALL THROUGH? */
4655 s = ":tabnew ";
4656 if ((uri = get_uri(t)) != NULL) {
4657 ss = g_strdup_printf("%s%s", s, uri);
4658 s = ss;
4660 break;
4661 default:
4662 show_oops(t, "command: invalid opcode %d", args->i);
4663 return (XT_CB_PASSTHROUGH);
4666 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4668 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4669 gdk_color_parse(XT_COLOR_WHITE, &color);
4670 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4671 show_cmd(t);
4672 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4673 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4675 if (ss)
4676 g_free(ss);
4678 return (XT_CB_HANDLED);
4682 * Return a new string with a download row (in html)
4683 * appended. Old string is freed.
4685 char *
4686 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4689 WebKitDownloadStatus stat;
4690 char *status_html = NULL, *cmd_html = NULL, *new_html;
4691 gdouble progress;
4692 char cur_sz[FMT_SCALED_STRSIZE];
4693 char tot_sz[FMT_SCALED_STRSIZE];
4694 char *xtp_prefix;
4696 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4698 /* All actions wil take this form:
4699 * xxxt://class/seskey
4701 xtp_prefix = g_strdup_printf("%s%d/%s/",
4702 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4704 stat = webkit_download_get_status(dl->download);
4706 switch (stat) {
4707 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4708 status_html = g_strdup_printf("Finished");
4709 cmd_html = g_strdup_printf(
4710 "<a href='%s%d/%d'>Remove</a>",
4711 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4712 break;
4713 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4714 /* gather size info */
4715 progress = 100 * webkit_download_get_progress(dl->download);
4717 fmt_scaled(
4718 webkit_download_get_current_size(dl->download), cur_sz);
4719 fmt_scaled(
4720 webkit_download_get_total_size(dl->download), tot_sz);
4722 status_html = g_strdup_printf(
4723 "<div style='width: 100%%' align='center'>"
4724 "<div class='progress-outer'>"
4725 "<div class='progress-inner' style='width: %.2f%%'>"
4726 "</div></div></div>"
4727 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4728 progress, cur_sz, tot_sz, progress);
4730 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4731 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4733 break;
4734 /* LLL */
4735 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4736 status_html = g_strdup_printf("Cancelled");
4737 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4738 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4739 break;
4740 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4741 status_html = g_strdup_printf("Error!");
4742 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4743 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4744 break;
4745 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4746 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4747 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4748 status_html = g_strdup_printf("Starting");
4749 break;
4750 default:
4751 show_oops(t, "%s: unknown download status", __func__);
4754 new_html = g_strdup_printf(
4755 "%s\n<tr><td>%s</td><td>%s</td>"
4756 "<td style='text-align:center'>%s</td></tr>\n",
4757 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4758 status_html, cmd_html);
4759 g_free(html);
4761 if (status_html)
4762 g_free(status_html);
4764 if (cmd_html)
4765 g_free(cmd_html);
4767 g_free(xtp_prefix);
4769 return new_html;
4773 * update all download tabs apart from one. Pass NULL if
4774 * you want to update all.
4776 void
4777 update_download_tabs(struct tab *apart_from)
4779 struct tab *t;
4780 if (!updating_dl_tabs) {
4781 updating_dl_tabs = 1; /* stop infinite recursion */
4782 TAILQ_FOREACH(t, &tabs, entry)
4783 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4784 && (t != apart_from))
4785 xtp_page_dl(t, NULL);
4786 updating_dl_tabs = 0;
4791 * update all cookie tabs apart from one. Pass NULL if
4792 * you want to update all.
4794 void
4795 update_cookie_tabs(struct tab *apart_from)
4797 struct tab *t;
4798 if (!updating_cl_tabs) {
4799 updating_cl_tabs = 1; /* stop infinite recursion */
4800 TAILQ_FOREACH(t, &tabs, entry)
4801 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4802 && (t != apart_from))
4803 xtp_page_cl(t, NULL);
4804 updating_cl_tabs = 0;
4809 * update all history tabs apart from one. Pass NULL if
4810 * you want to update all.
4812 void
4813 update_history_tabs(struct tab *apart_from)
4815 struct tab *t;
4817 if (!updating_hl_tabs) {
4818 updating_hl_tabs = 1; /* stop infinite recursion */
4819 TAILQ_FOREACH(t, &tabs, entry)
4820 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4821 && (t != apart_from))
4822 xtp_page_hl(t, NULL);
4823 updating_hl_tabs = 0;
4827 /* cookie management XTP page */
4829 xtp_page_cl(struct tab *t, struct karg *args)
4831 char *body, *page, *tmp;
4832 int i = 1; /* all ids start 1 */
4833 GSList *sc, *pc, *pc_start;
4834 SoupCookie *c;
4835 char *type, *table_headers, *last_domain;
4837 DNPRINTF(XT_D_CMD, "%s", __func__);
4839 if (t == NULL) {
4840 show_oops(NULL, "%s invalid parameters", __func__);
4841 return (1);
4844 /* Generate a new session key */
4845 if (!updating_cl_tabs)
4846 generate_xtp_session_key(&cl_session_key);
4848 /* table headers */
4849 table_headers = g_strdup_printf("<table><tr>"
4850 "<th>Type</th>"
4851 "<th>Name</th>"
4852 "<th style='width:200px'>Value</th>"
4853 "<th>Path</th>"
4854 "<th>Expires</th>"
4855 "<th>Secure</th>"
4856 "<th>HTTP<br />only</th>"
4857 "<th style='width:40px'>Rm</th></tr>\n");
4859 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4860 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4861 pc_start = pc;
4863 body = NULL;
4864 last_domain = strdup("");
4865 for (; sc; sc = sc->next) {
4866 c = sc->data;
4868 if (strcmp(last_domain, c->domain) != 0) {
4869 /* new domain */
4870 free(last_domain);
4871 last_domain = strdup(c->domain);
4873 if (body != NULL) {
4874 tmp = body;
4875 body = g_strdup_printf("%s</table>"
4876 "<h2>%s</h2>%s\n",
4877 body, c->domain, table_headers);
4878 g_free(tmp);
4879 } else {
4880 /* first domain */
4881 body = g_strdup_printf("<h2>%s</h2>%s\n",
4882 c->domain, table_headers);
4886 type = "Session";
4887 for (pc = pc_start; pc; pc = pc->next)
4888 if (soup_cookie_equal(pc->data, c)) {
4889 type = "Session + Persistent";
4890 break;
4893 tmp = body;
4894 body = g_strdup_printf(
4895 "%s\n<tr>"
4896 "<td>%s</td>"
4897 "<td style='word-wrap:normal'>%s</td>"
4898 "<td>"
4899 " <textarea rows='4'>%s</textarea>"
4900 "</td>"
4901 "<td>%s</td>"
4902 "<td>%s</td>"
4903 "<td>%d</td>"
4904 "<td>%d</td>"
4905 "<td style='text-align:center'>"
4906 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4907 body,
4908 type,
4909 c->name,
4910 c->value,
4911 c->path,
4912 c->expires ?
4913 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4914 c->secure,
4915 c->http_only,
4917 XT_XTP_STR,
4918 XT_XTP_CL,
4919 cl_session_key,
4920 XT_XTP_CL_REMOVE,
4924 g_free(tmp);
4925 i++;
4928 soup_cookies_free(sc);
4929 soup_cookies_free(pc);
4931 /* small message if there are none */
4932 if (i == 1) {
4933 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4934 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4936 tmp = body;
4937 body = g_strdup_printf("%s</table>", body);
4938 g_free(tmp);
4940 page = get_html_page("Cookie Jar", body, "", TRUE);
4941 g_free(body);
4942 g_free(table_headers);
4943 g_free(last_domain);
4945 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4946 update_cookie_tabs(t);
4948 g_free(page);
4950 return (0);
4954 xtp_page_hl(struct tab *t, struct karg *args)
4956 char *body, *page, *tmp;
4957 struct history *h;
4958 int i = 1; /* all ids start 1 */
4960 DNPRINTF(XT_D_CMD, "%s", __func__);
4962 if (t == NULL) {
4963 show_oops(NULL, "%s invalid parameters", __func__);
4964 return (1);
4967 /* Generate a new session key */
4968 if (!updating_hl_tabs)
4969 generate_xtp_session_key(&hl_session_key);
4971 /* body */
4972 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4973 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4975 RB_FOREACH_REVERSE(h, history_list, &hl) {
4976 tmp = body;
4977 body = g_strdup_printf(
4978 "%s\n<tr>"
4979 "<td><a href='%s'>%s</a></td>"
4980 "<td>%s</td>"
4981 "<td style='text-align: center'>"
4982 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4983 body, h->uri, h->uri, h->title,
4984 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4985 XT_XTP_HL_REMOVE, i);
4987 g_free(tmp);
4988 i++;
4991 /* small message if there are none */
4992 if (i == 1) {
4993 tmp = body;
4994 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4995 "colspan='3'>No History</td></tr>\n", body);
4996 g_free(tmp);
4999 tmp = body;
5000 body = g_strdup_printf("%s</table>", body);
5001 g_free(tmp);
5003 page = get_html_page("History", body, "", TRUE);
5004 g_free(body);
5007 * update all history manager tabs as the xtp session
5008 * key has now changed. No need to update the current tab.
5009 * Already did that above.
5011 update_history_tabs(t);
5013 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5014 g_free(page);
5016 return (0);
5020 * Generate a web page detailing the status of any downloads
5023 xtp_page_dl(struct tab *t, struct karg *args)
5025 struct download *dl;
5026 char *body, *page, *tmp;
5027 char *ref;
5028 int n_dl = 1;
5030 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5032 if (t == NULL) {
5033 show_oops(NULL, "%s invalid parameters", __func__);
5034 return (1);
5038 * Generate a new session key for next page instance.
5039 * This only happens for the top level call to xtp_page_dl()
5040 * in which case updating_dl_tabs is 0.
5042 if (!updating_dl_tabs)
5043 generate_xtp_session_key(&dl_session_key);
5045 /* header - with refresh so as to update */
5046 if (refresh_interval >= 1)
5047 ref = g_strdup_printf(
5048 "<meta http-equiv='refresh' content='%u"
5049 ";url=%s%d/%s/%d' />\n",
5050 refresh_interval,
5051 XT_XTP_STR,
5052 XT_XTP_DL,
5053 dl_session_key,
5054 XT_XTP_DL_LIST);
5055 else
5056 ref = g_strdup("");
5058 body = g_strdup_printf("<div align='center'>"
5059 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5060 "</p><table><tr><th style='width: 60%%'>"
5061 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5062 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5064 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5065 body = xtp_page_dl_row(t, body, dl);
5066 n_dl++;
5069 /* message if no downloads in list */
5070 if (n_dl == 1) {
5071 tmp = body;
5072 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5073 " style='text-align: center'>"
5074 "No downloads</td></tr>\n", body);
5075 g_free(tmp);
5078 tmp = body;
5079 body = g_strdup_printf("%s</table></div>", body);
5080 g_free(tmp);
5082 page = get_html_page("Downloads", body, ref, 1);
5083 g_free(ref);
5084 g_free(body);
5087 * update all download manager tabs as the xtp session
5088 * key has now changed. No need to update the current tab.
5089 * Already did that above.
5091 update_download_tabs(t);
5093 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5094 g_free(page);
5096 return (0);
5100 search(struct tab *t, struct karg *args)
5102 gboolean d;
5104 if (t == NULL || args == NULL) {
5105 show_oops(NULL, "search invalid parameters");
5106 return (1);
5109 switch (args->i) {
5110 case XT_SEARCH_NEXT:
5111 d = t->search_forward;
5112 break;
5113 case XT_SEARCH_PREV:
5114 d = !t->search_forward;
5115 break;
5116 default:
5117 return (XT_CB_PASSTHROUGH);
5120 if (t->search_text == NULL) {
5121 if (global_search == NULL)
5122 return (XT_CB_PASSTHROUGH);
5123 else {
5124 d = t->search_forward = TRUE;
5125 t->search_text = g_strdup(global_search);
5126 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5127 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5131 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5132 t->tab_id, args->i, t->search_forward, t->search_text);
5134 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5136 return (XT_CB_HANDLED);
5139 struct settings_args {
5140 char **body;
5141 int i;
5144 void
5145 print_setting(struct settings *s, char *val, void *cb_args)
5147 char *tmp, *color;
5148 struct settings_args *sa = cb_args;
5150 if (sa == NULL)
5151 return;
5153 if (s->flags & XT_SF_RUNTIME)
5154 color = "#22cc22";
5155 else
5156 color = "#cccccc";
5158 tmp = *sa->body;
5159 *sa->body = g_strdup_printf(
5160 "%s\n<tr>"
5161 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5162 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5163 *sa->body,
5164 color,
5165 s->name,
5166 color,
5169 g_free(tmp);
5170 sa->i++;
5174 set_show(struct tab *t, struct karg *args)
5176 char *body, *page, *tmp;
5177 int i = 1;
5178 struct settings_args sa;
5180 bzero(&sa, sizeof sa);
5181 sa.body = &body;
5183 /* body */
5184 body = g_strdup_printf("<div align='center'><table><tr>"
5185 "<th align='left'>Setting</th>"
5186 "<th align='left'>Value</th></tr>\n");
5188 settings_walk(print_setting, &sa);
5189 i = sa.i;
5191 /* small message if there are none */
5192 if (i == 1) {
5193 tmp = body;
5194 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5195 "colspan='2'>No settings</td></tr>\n", body);
5196 g_free(tmp);
5199 tmp = body;
5200 body = g_strdup_printf("%s</table></div>", body);
5201 g_free(tmp);
5203 page = get_html_page("Settings", body, "", 0);
5205 g_free(body);
5207 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5209 g_free(page);
5211 return (XT_CB_PASSTHROUGH);
5215 set(struct tab *t, struct karg *args)
5217 char *p, *val;
5218 int i;
5220 if (args == NULL || args->s == NULL)
5221 return (set_show(t, args));
5223 /* strip spaces */
5224 p = g_strstrip(args->s);
5226 if (strlen(p) == 0)
5227 return (set_show(t, args));
5229 /* we got some sort of string */
5230 val = g_strrstr(p, "=");
5231 if (val) {
5232 *val++ = '\0';
5233 val = g_strchomp(val);
5234 p = g_strchomp(p);
5236 for (i = 0; i < LENGTH(rs); i++) {
5237 if (strcmp(rs[i].name, p))
5238 continue;
5240 if (rs[i].activate) {
5241 if (rs[i].activate(val))
5242 show_oops(t, "%s invalid value %s",
5243 p, val);
5244 else
5245 show_oops(t, ":set %s = %s", p, val);
5246 goto done;
5247 } else {
5248 show_oops(t, "not a runtime option: %s", p);
5249 goto done;
5252 show_oops(t, "unknown option: %s", p);
5253 } else {
5254 p = g_strchomp(p);
5256 for (i = 0; i < LENGTH(rs); i++) {
5257 if (strcmp(rs[i].name, p))
5258 continue;
5260 /* XXX this could use some cleanup */
5261 switch (rs[i].type) {
5262 case XT_S_INT:
5263 if (rs[i].ival)
5264 show_oops(t, "%s = %d",
5265 rs[i].name, *rs[i].ival);
5266 else if (rs[i].s && rs[i].s->get)
5267 show_oops(t, "%s = %s",
5268 rs[i].name,
5269 rs[i].s->get(&rs[i]));
5270 else if (rs[i].s && rs[i].s->get == NULL)
5271 show_oops(t, "%s = ...", rs[i].name);
5272 else
5273 show_oops(t, "%s = ", rs[i].name);
5274 break;
5275 case XT_S_FLOAT:
5276 if (rs[i].fval)
5277 show_oops(t, "%s = %f",
5278 rs[i].name, *rs[i].fval);
5279 else if (rs[i].s && rs[i].s->get)
5280 show_oops(t, "%s = %s",
5281 rs[i].name,
5282 rs[i].s->get(&rs[i]));
5283 else if (rs[i].s && rs[i].s->get == NULL)
5284 show_oops(t, "%s = ...", rs[i].name);
5285 else
5286 show_oops(t, "%s = ", rs[i].name);
5287 break;
5288 case XT_S_STR:
5289 if (rs[i].sval && *rs[i].sval)
5290 show_oops(t, "%s = %s",
5291 rs[i].name, *rs[i].sval);
5292 else if (rs[i].s && rs[i].s->get)
5293 show_oops(t, "%s = %s",
5294 rs[i].name,
5295 rs[i].s->get(&rs[i]));
5296 else if (rs[i].s && rs[i].s->get == NULL)
5297 show_oops(t, "%s = ...", rs[i].name);
5298 else
5299 show_oops(t, "%s = ", rs[i].name);
5300 break;
5301 default:
5302 show_oops(t, "unknown type for %s", rs[i].name);
5303 goto done;
5306 goto done;
5308 show_oops(t, "unknown option: %s", p);
5310 done:
5311 return (XT_CB_PASSTHROUGH);
5315 session_save(struct tab *t, char *filename)
5317 struct karg a;
5318 int rv = 1;
5319 struct session *s;
5321 if (strlen(filename) == 0)
5322 goto done;
5324 if (filename[0] == '.' || filename[0] == '/')
5325 goto done;
5327 a.s = filename;
5328 if (save_tabs(t, &a))
5329 goto done;
5330 strlcpy(named_session, filename, sizeof named_session);
5332 /* add the new session to the list of sessions */
5333 s = g_malloc(sizeof(struct session));
5334 s->name = g_strdup(filename);
5335 TAILQ_INSERT_TAIL(&sessions, s, entry);
5337 rv = 0;
5338 done:
5339 return (rv);
5343 session_open(struct tab *t, char *filename)
5345 struct karg a;
5346 int rv = 1;
5348 if (strlen(filename) == 0)
5349 goto done;
5351 if (filename[0] == '.' || filename[0] == '/')
5352 goto done;
5354 a.s = filename;
5355 a.i = XT_SES_CLOSETABS;
5356 if (open_tabs(t, &a))
5357 goto done;
5359 strlcpy(named_session, filename, sizeof named_session);
5361 rv = 0;
5362 done:
5363 return (rv);
5367 session_delete(struct tab *t, char *filename)
5369 char file[PATH_MAX];
5370 int rv = 1;
5371 struct session *s;
5373 if (strlen(filename) == 0)
5374 goto done;
5376 if (filename[0] == '.' || filename[0] == '/')
5377 goto done;
5379 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5380 if (unlink(file))
5381 goto done;
5383 if (!strcmp(filename, named_session))
5384 strlcpy(named_session, XT_SAVED_TABS_FILE,
5385 sizeof named_session);
5387 /* remove session from sessions list */
5388 TAILQ_FOREACH(s, &sessions, entry) {
5389 if (!strcmp(s->name, filename))
5390 break;
5392 TAILQ_REMOVE(&sessions, s, entry);
5393 g_free((gpointer) s->name);
5394 g_free(s);
5396 rv = 0;
5397 done:
5398 return (rv);
5402 session_cmd(struct tab *t, struct karg *args)
5404 char *filename = args->s;
5406 if (t == NULL)
5407 return (1);
5409 if (args->i & XT_SHOW)
5410 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5411 XT_SAVED_TABS_FILE : named_session);
5412 else if (args->i & XT_SAVE) {
5413 if (session_save(t, filename)) {
5414 show_oops(t, "Can't save session: %s",
5415 filename ? filename : "INVALID");
5416 goto done;
5418 } else if (args->i & XT_OPEN) {
5419 if (session_open(t, filename)) {
5420 show_oops(t, "Can't open session: %s",
5421 filename ? filename : "INVALID");
5422 goto done;
5424 } else if (args->i & XT_DELETE) {
5425 if (session_delete(t, filename)) {
5426 show_oops(t, "Can't delete session: %s",
5427 filename ? filename : "INVALID");
5428 goto done;
5431 done:
5432 return (XT_CB_PASSTHROUGH);
5436 * Make a hardcopy of the page
5439 print_page(struct tab *t, struct karg *args)
5441 WebKitWebFrame *frame;
5442 GtkPageSetup *ps;
5443 GtkPrintOperation *op;
5444 GtkPrintOperationAction action;
5445 GtkPrintOperationResult print_res;
5446 GError *g_err = NULL;
5447 int marg_l, marg_r, marg_t, marg_b;
5449 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5451 ps = gtk_page_setup_new();
5452 op = gtk_print_operation_new();
5453 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5454 frame = webkit_web_view_get_main_frame(t->wv);
5456 /* the default margins are too small, so we will bump them */
5457 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5458 XT_PRINT_EXTRA_MARGIN;
5459 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5460 XT_PRINT_EXTRA_MARGIN;
5461 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5462 XT_PRINT_EXTRA_MARGIN;
5463 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5464 XT_PRINT_EXTRA_MARGIN;
5466 /* set margins */
5467 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5468 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5469 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5470 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5472 gtk_print_operation_set_default_page_setup(op, ps);
5474 /* this appears to free 'op' and 'ps' */
5475 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5477 /* check it worked */
5478 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5479 show_oops(NULL, "can't print: %s", g_err->message);
5480 g_error_free (g_err);
5481 return (1);
5484 return (0);
5488 go_home(struct tab *t, struct karg *args)
5490 load_uri(t, home);
5491 return (0);
5495 set_encoding(struct tab *t, struct karg *args)
5497 const gchar *e;
5499 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5500 e = webkit_web_view_get_custom_encoding(t->wv);
5501 if (e == NULL)
5502 e = webkit_web_view_get_encoding(t->wv);
5503 show_oops(t, "encoding: %s", e ? e : "N/A");
5504 } else
5505 webkit_web_view_set_custom_encoding(t->wv, args->s);
5507 return (0);
5511 restart(struct tab *t, struct karg *args)
5513 struct karg a;
5515 a.s = XT_RESTART_TABS_FILE;
5516 save_tabs(t, &a);
5517 execvp(start_argv[0], start_argv);
5518 /* NOTREACHED */
5520 return (0);
5523 #define CTRL GDK_CONTROL_MASK
5524 #define MOD1 GDK_MOD1_MASK
5525 #define SHFT GDK_SHIFT_MASK
5527 /* inherent to GTK not all keys will be caught at all times */
5528 /* XXX sort key bindings */
5529 struct key_binding {
5530 char *cmd;
5531 guint mask;
5532 guint use_in_entry;
5533 guint key;
5534 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5535 } keys[] = {
5536 { "cookiejar", MOD1, 0, GDK_j },
5537 { "downloadmgr", MOD1, 0, GDK_d },
5538 { "history", MOD1, 0, GDK_h },
5539 { "print", CTRL, 0, GDK_p },
5540 { "search", 0, 0, GDK_slash },
5541 { "searchb", 0, 0, GDK_question },
5542 { "statustoggle", CTRL, 0, GDK_n },
5543 { "command", 0, 0, GDK_colon },
5544 { "qa", CTRL, 0, GDK_q },
5545 { "restart", MOD1, 0, GDK_q },
5546 { "js toggle", CTRL, 0, GDK_j },
5547 { "cookie toggle", MOD1, 0, GDK_c },
5548 { "togglesrc", CTRL, 0, GDK_s },
5549 { "yankuri", 0, 0, GDK_y },
5550 { "pasteuricur", 0, 0, GDK_p },
5551 { "pasteurinew", 0, 0, GDK_P },
5552 { "toplevel toggle", 0, 0, GDK_F4 },
5553 { "help", 0, 0, GDK_F1 },
5554 { "run_script", MOD1, 0, GDK_r },
5556 /* search */
5557 { "searchnext", 0, 0, GDK_n },
5558 { "searchprevious", 0, 0, GDK_N },
5560 /* focus */
5561 { "focusaddress", 0, 0, GDK_F6 },
5562 { "focussearch", 0, 0, GDK_F7 },
5564 /* hinting */
5565 { "hinting", 0, 0, GDK_f },
5567 /* custom stylesheet */
5568 { "userstyle", 0, 0, GDK_i },
5570 /* navigation */
5571 { "goback", 0, 0, GDK_BackSpace },
5572 { "goback", MOD1, 0, GDK_Left },
5573 { "goforward", SHFT, 0, GDK_BackSpace },
5574 { "goforward", MOD1, 0, GDK_Right },
5575 { "reload", 0, 0, GDK_F5 },
5576 { "reload", CTRL, 0, GDK_r },
5577 { "reload", CTRL, 0, GDK_l },
5578 { "favorites", MOD1, 1, GDK_f },
5580 /* vertical movement */
5581 { "scrolldown", 0, 0, GDK_j },
5582 { "scrolldown", 0, 0, GDK_Down },
5583 { "scrollup", 0, 0, GDK_Up },
5584 { "scrollup", 0, 0, GDK_k },
5585 { "scrollbottom", 0, 0, GDK_G },
5586 { "scrollbottom", 0, 0, GDK_End },
5587 { "scrolltop", 0, 0, GDK_Home },
5588 { "scrollpagedown", 0, 0, GDK_space },
5589 { "scrollpagedown", CTRL, 0, GDK_f },
5590 { "scrollhalfdown", CTRL, 0, GDK_d },
5591 { "scrollpagedown", 0, 0, GDK_Page_Down },
5592 { "scrollpageup", 0, 0, GDK_Page_Up },
5593 { "scrollpageup", CTRL, 0, GDK_b },
5594 { "scrollhalfup", CTRL, 0, GDK_u },
5595 /* horizontal movement */
5596 { "scrollright", 0, 0, GDK_l },
5597 { "scrollright", 0, 0, GDK_Right },
5598 { "scrollleft", 0, 0, GDK_Left },
5599 { "scrollleft", 0, 0, GDK_h },
5600 { "scrollfarright", 0, 0, GDK_dollar },
5601 { "scrollfarleft", 0, 0, GDK_0 },
5603 /* tabs */
5604 { "tabnew", CTRL, 0, GDK_t },
5605 { "999tabnew", CTRL, 0, GDK_T },
5606 { "tabclose", CTRL, 1, GDK_w },
5607 { "tabundoclose", 0, 0, GDK_U },
5608 { "tabnext 1", CTRL, 0, GDK_1 },
5609 { "tabnext 2", CTRL, 0, GDK_2 },
5610 { "tabnext 3", CTRL, 0, GDK_3 },
5611 { "tabnext 4", CTRL, 0, GDK_4 },
5612 { "tabnext 5", CTRL, 0, GDK_5 },
5613 { "tabnext 6", CTRL, 0, GDK_6 },
5614 { "tabnext 7", CTRL, 0, GDK_7 },
5615 { "tabnext 8", CTRL, 0, GDK_8 },
5616 { "tabnext 9", CTRL, 0, GDK_9 },
5617 { "tabfirst", CTRL, 0, GDK_less },
5618 { "tablast", CTRL, 0, GDK_greater },
5619 { "tabprevious", CTRL, 0, GDK_Left },
5620 { "tabnext", CTRL, 0, GDK_Right },
5621 { "focusout", CTRL, 0, GDK_minus },
5622 { "focusin", CTRL, 0, GDK_plus },
5623 { "focusin", CTRL, 0, GDK_equal },
5624 { "focusreset", CTRL, 0, GDK_0 },
5626 /* command aliases (handy when -S flag is used) */
5627 { "promptopen", 0, 0, GDK_F9 },
5628 { "promptopencurrent", 0, 0, GDK_F10 },
5629 { "prompttabnew", 0, 0, GDK_F11 },
5630 { "prompttabnewcurrent",0, 0, GDK_F12 },
5632 TAILQ_HEAD(keybinding_list, key_binding);
5634 void
5635 walk_kb(struct settings *s,
5636 void (*cb)(struct settings *, char *, void *), void *cb_args)
5638 struct key_binding *k;
5639 char str[1024];
5641 if (s == NULL || cb == NULL) {
5642 show_oops(NULL, "walk_kb invalid parameters");
5643 return;
5646 TAILQ_FOREACH(k, &kbl, entry) {
5647 if (k->cmd == NULL)
5648 continue;
5649 str[0] = '\0';
5651 /* sanity */
5652 if (gdk_keyval_name(k->key) == NULL)
5653 continue;
5655 strlcat(str, k->cmd, sizeof str);
5656 strlcat(str, ",", sizeof str);
5658 if (k->mask & GDK_SHIFT_MASK)
5659 strlcat(str, "S-", sizeof str);
5660 if (k->mask & GDK_CONTROL_MASK)
5661 strlcat(str, "C-", sizeof str);
5662 if (k->mask & GDK_MOD1_MASK)
5663 strlcat(str, "M1-", sizeof str);
5664 if (k->mask & GDK_MOD2_MASK)
5665 strlcat(str, "M2-", sizeof str);
5666 if (k->mask & GDK_MOD3_MASK)
5667 strlcat(str, "M3-", sizeof str);
5668 if (k->mask & GDK_MOD4_MASK)
5669 strlcat(str, "M4-", sizeof str);
5670 if (k->mask & GDK_MOD5_MASK)
5671 strlcat(str, "M5-", sizeof str);
5673 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5674 cb(s, str, cb_args);
5678 void
5679 init_keybindings(void)
5681 int i;
5682 struct key_binding *k;
5684 for (i = 0; i < LENGTH(keys); i++) {
5685 k = g_malloc0(sizeof *k);
5686 k->cmd = keys[i].cmd;
5687 k->mask = keys[i].mask;
5688 k->use_in_entry = keys[i].use_in_entry;
5689 k->key = keys[i].key;
5690 TAILQ_INSERT_HEAD(&kbl, k, entry);
5692 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5693 k->cmd ? k->cmd : "unnamed key");
5697 void
5698 keybinding_clearall(void)
5700 struct key_binding *k, *next;
5702 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5703 next = TAILQ_NEXT(k, entry);
5704 if (k->cmd == NULL)
5705 continue;
5707 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5708 k->cmd ? k->cmd : "unnamed key");
5709 TAILQ_REMOVE(&kbl, k, entry);
5710 g_free(k);
5715 keybinding_add(char *cmd, char *key, int use_in_entry)
5717 struct key_binding *k;
5718 guint keyval, mask = 0;
5719 int i;
5721 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5723 /* Keys which are to be used in entry have been prefixed with an
5724 * exclamation mark. */
5725 if (use_in_entry)
5726 key++;
5728 /* find modifier keys */
5729 if (strstr(key, "S-"))
5730 mask |= GDK_SHIFT_MASK;
5731 if (strstr(key, "C-"))
5732 mask |= GDK_CONTROL_MASK;
5733 if (strstr(key, "M1-"))
5734 mask |= GDK_MOD1_MASK;
5735 if (strstr(key, "M2-"))
5736 mask |= GDK_MOD2_MASK;
5737 if (strstr(key, "M3-"))
5738 mask |= GDK_MOD3_MASK;
5739 if (strstr(key, "M4-"))
5740 mask |= GDK_MOD4_MASK;
5741 if (strstr(key, "M5-"))
5742 mask |= GDK_MOD5_MASK;
5744 /* find keyname */
5745 for (i = strlen(key) - 1; i > 0; i--)
5746 if (key[i] == '-')
5747 key = &key[i + 1];
5749 /* validate keyname */
5750 keyval = gdk_keyval_from_name(key);
5751 if (keyval == GDK_VoidSymbol) {
5752 warnx("invalid keybinding name %s", key);
5753 return (1);
5755 /* must run this test too, gtk+ doesn't handle 10 for example */
5756 if (gdk_keyval_name(keyval) == NULL) {
5757 warnx("invalid keybinding name %s", key);
5758 return (1);
5761 /* Remove eventual dupes. */
5762 TAILQ_FOREACH(k, &kbl, entry)
5763 if (k->key == keyval && k->mask == mask) {
5764 TAILQ_REMOVE(&kbl, k, entry);
5765 g_free(k);
5766 break;
5769 /* add keyname */
5770 k = g_malloc0(sizeof *k);
5771 k->cmd = g_strdup(cmd);
5772 k->mask = mask;
5773 k->use_in_entry = use_in_entry;
5774 k->key = keyval;
5776 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5777 k->cmd,
5778 k->mask,
5779 k->use_in_entry,
5780 k->key);
5781 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5782 k->cmd, gdk_keyval_name(keyval));
5784 TAILQ_INSERT_HEAD(&kbl, k, entry);
5786 return (0);
5790 add_kb(struct settings *s, char *entry)
5792 char *kb, *key;
5794 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5796 /* clearall is special */
5797 if (!strcmp(entry, "clearall")) {
5798 keybinding_clearall();
5799 return (0);
5802 kb = strstr(entry, ",");
5803 if (kb == NULL)
5804 return (1);
5805 *kb = '\0';
5806 key = kb + 1;
5808 return (keybinding_add(entry, key, key[0] == '!'));
5811 struct cmd {
5812 char *cmd;
5813 int level;
5814 int (*func)(struct tab *, struct karg *);
5815 int arg;
5816 int type;
5817 } cmds[] = {
5818 { "command", 0, command, ':', 0 },
5819 { "search", 0, command, '/', 0 },
5820 { "searchb", 0, command, '?', 0 },
5821 { "togglesrc", 0, toggle_src, 0, 0 },
5823 /* yanking and pasting */
5824 { "yankuri", 0, yank_uri, 0, 0 },
5825 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5826 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5827 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5829 /* search */
5830 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5831 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5833 /* focus */
5834 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5835 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5837 /* hinting */
5838 { "hinting", 0, hint, 0, 0 },
5840 /* custom stylesheet */
5841 { "userstyle", 0, userstyle, 0, 0 },
5843 /* navigation */
5844 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5845 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5846 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5848 /* vertical movement */
5849 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5850 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5851 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5852 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5853 { "1", 0, move, XT_MOVE_TOP, 0 },
5854 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5855 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5856 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5857 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5858 /* horizontal movement */
5859 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5860 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5861 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5862 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5864 { "favorites", 0, xtp_page_fl, 0, 0 },
5865 { "fav", 0, xtp_page_fl, 0, 0 },
5866 { "favadd", 0, add_favorite, 0, 0 },
5868 { "qall", 0, quit, 0, 0 },
5869 { "quitall", 0, quit, 0, 0 },
5870 { "w", 0, save_tabs, 0, 0 },
5871 { "wq", 0, save_tabs_and_quit, 0, 0 },
5872 { "help", 0, help, 0, 0 },
5873 { "about", 0, about, 0, 0 },
5874 { "stats", 0, stats, 0, 0 },
5875 { "version", 0, about, 0, 0 },
5877 /* js command */
5878 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5879 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5880 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5881 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5882 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5883 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5884 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5885 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5886 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5887 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5888 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5890 /* cookie command */
5891 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5892 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5893 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5894 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5895 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5896 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5897 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5898 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5899 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5900 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5901 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5903 /* toplevel (domain) command */
5904 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5905 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5907 /* cookie jar */
5908 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5910 /* cert command */
5911 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5912 { "save", 1, cert_cmd, XT_SAVE, 0 },
5913 { "show", 1, cert_cmd, XT_SHOW, 0 },
5915 { "ca", 0, ca_cmd, 0, 0 },
5916 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5917 { "dl", 0, xtp_page_dl, 0, 0 },
5918 { "h", 0, xtp_page_hl, 0, 0 },
5919 { "history", 0, xtp_page_hl, 0, 0 },
5920 { "home", 0, go_home, 0, 0 },
5921 { "restart", 0, restart, 0, 0 },
5922 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5923 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5924 { "statustoggle", 0, statustoggle, 0, 0 },
5925 { "run_script", 0, run_page_script, 0, XT_USERARG },
5927 { "print", 0, print_page, 0, 0 },
5929 /* tabs */
5930 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5931 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5932 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5933 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5934 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5935 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5936 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5937 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5938 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5939 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5940 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5941 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5942 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5943 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5944 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5945 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5946 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5947 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5948 { "buffers", 0, buffers, 0, 0 },
5949 { "ls", 0, buffers, 0, 0 },
5950 { "tabs", 0, buffers, 0, 0 },
5951 { "encoding", 0, set_encoding, 0, XT_USERARG },
5953 /* command aliases (handy when -S flag is used) */
5954 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5955 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5956 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5957 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5959 /* settings */
5960 { "set", 0, set, 0, XT_SETARG },
5962 { "fullscreen", 0, fullscreen, 0, 0 },
5963 { "f", 0, fullscreen, 0, 0 },
5965 /* sessions */
5966 { "session", 0, session_cmd, XT_SHOW, 0 },
5967 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5968 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5969 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5970 { "show", 1, session_cmd, XT_SHOW, 0 },
5973 struct {
5974 int index;
5975 int len;
5976 gchar *list[256];
5977 } cmd_status = {-1, 0};
5979 gboolean
5980 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5983 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5984 btn_down = 0;
5986 return (FALSE);
5989 gboolean
5990 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5992 struct karg a;
5994 hide_oops(t);
5995 hide_buffers(t);
5997 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5998 btn_down = 1;
5999 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
6000 /* go backward */
6001 a.i = XT_NAV_BACK;
6002 navaction(t, &a);
6004 return (TRUE);
6005 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
6006 /* go forward */
6007 a.i = XT_NAV_FORWARD;
6008 navaction(t, &a);
6010 return (TRUE);
6013 return (FALSE);
6016 gboolean
6017 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6019 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6021 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6022 delete_tab(t);
6024 return (FALSE);
6028 * cancel, remove, etc. downloads
6030 void
6031 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6033 struct download find, *d = NULL;
6035 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6037 /* some commands require a valid download id */
6038 if (cmd != XT_XTP_DL_LIST) {
6039 /* lookup download in question */
6040 find.id = id;
6041 d = RB_FIND(download_list, &downloads, &find);
6043 if (d == NULL) {
6044 show_oops(t, "%s: no such download", __func__);
6045 return;
6049 /* decide what to do */
6050 switch (cmd) {
6051 case XT_XTP_DL_CANCEL:
6052 webkit_download_cancel(d->download);
6053 break;
6054 case XT_XTP_DL_REMOVE:
6055 webkit_download_cancel(d->download); /* just incase */
6056 g_object_unref(d->download);
6057 RB_REMOVE(download_list, &downloads, d);
6058 break;
6059 case XT_XTP_DL_LIST:
6060 /* Nothing */
6061 break;
6062 default:
6063 show_oops(t, "%s: unknown command", __func__);
6064 break;
6066 xtp_page_dl(t, NULL);
6070 * Actions on history, only does one thing for now, but
6071 * we provide the function for future actions
6073 void
6074 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6076 struct history *h, *next;
6077 int i = 1;
6079 switch (cmd) {
6080 case XT_XTP_HL_REMOVE:
6081 /* walk backwards, as listed in reverse */
6082 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6083 next = RB_PREV(history_list, &hl, h);
6084 if (id == i) {
6085 RB_REMOVE(history_list, &hl, h);
6086 g_free((gpointer) h->title);
6087 g_free((gpointer) h->uri);
6088 g_free(h);
6089 break;
6091 i++;
6093 break;
6094 case XT_XTP_HL_LIST:
6095 /* Nothing - just xtp_page_hl() below */
6096 break;
6097 default:
6098 show_oops(t, "%s: unknown command", __func__);
6099 break;
6102 xtp_page_hl(t, NULL);
6105 /* remove a favorite */
6106 void
6107 remove_favorite(struct tab *t, int index)
6109 char file[PATH_MAX], *title, *uri = NULL;
6110 char *new_favs, *tmp;
6111 FILE *f;
6112 int i;
6113 size_t len, lineno;
6115 /* open favorites */
6116 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6118 if ((f = fopen(file, "r")) == NULL) {
6119 show_oops(t, "%s: can't open favorites: %s",
6120 __func__, strerror(errno));
6121 return;
6124 /* build a string which will become the new favroites file */
6125 new_favs = g_strdup("");
6127 for (i = 1;;) {
6128 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6129 if (feof(f) || ferror(f))
6130 break;
6131 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6132 if (len == 0) {
6133 free(title);
6134 title = NULL;
6135 continue;
6138 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6139 if (feof(f) || ferror(f)) {
6140 show_oops(t, "%s: can't parse favorites %s",
6141 __func__, strerror(errno));
6142 goto clean;
6146 /* as long as this isn't the one we are deleting add to file */
6147 if (i != index) {
6148 tmp = new_favs;
6149 new_favs = g_strdup_printf("%s%s\n%s\n",
6150 new_favs, title, uri);
6151 g_free(tmp);
6154 free(uri);
6155 uri = NULL;
6156 free(title);
6157 title = NULL;
6158 i++;
6160 fclose(f);
6162 /* write back new favorites file */
6163 if ((f = fopen(file, "w")) == NULL) {
6164 show_oops(t, "%s: can't open favorites: %s",
6165 __func__, strerror(errno));
6166 goto clean;
6169 fwrite(new_favs, strlen(new_favs), 1, f);
6170 fclose(f);
6172 clean:
6173 if (uri)
6174 free(uri);
6175 if (title)
6176 free(title);
6178 g_free(new_favs);
6181 void
6182 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6184 switch (cmd) {
6185 case XT_XTP_FL_LIST:
6186 /* nothing, just the below call to xtp_page_fl() */
6187 break;
6188 case XT_XTP_FL_REMOVE:
6189 remove_favorite(t, arg);
6190 break;
6191 default:
6192 show_oops(t, "%s: invalid favorites command", __func__);
6193 break;
6196 xtp_page_fl(t, NULL);
6199 void
6200 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6202 switch (cmd) {
6203 case XT_XTP_CL_LIST:
6204 /* nothing, just xtp_page_cl() */
6205 break;
6206 case XT_XTP_CL_REMOVE:
6207 remove_cookie(arg);
6208 break;
6209 default:
6210 show_oops(t, "%s: unknown cookie xtp command", __func__);
6211 break;
6214 xtp_page_cl(t, NULL);
6217 /* link an XTP class to it's session key and handler function */
6218 struct xtp_despatch {
6219 uint8_t xtp_class;
6220 char **session_key;
6221 void (*handle_func)(struct tab *, uint8_t, int);
6224 struct xtp_despatch xtp_despatches[] = {
6225 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6226 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6227 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6228 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6229 { XT_XTP_INVALID, NULL, NULL }
6233 * is the url xtp protocol? (xxxt://)
6234 * if so, parse and despatch correct bahvior
6237 parse_xtp_url(struct tab *t, const char *url)
6239 char *dup = NULL, *p, *last;
6240 uint8_t n_tokens = 0;
6241 char *tokens[4] = {NULL, NULL, NULL, ""};
6242 struct xtp_despatch *dsp, *dsp_match = NULL;
6243 uint8_t req_class;
6244 int ret = FALSE;
6247 * tokens array meaning:
6248 * tokens[0] = class
6249 * tokens[1] = session key
6250 * tokens[2] = action
6251 * tokens[3] = optional argument
6254 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6256 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6257 goto clean;
6259 dup = g_strdup(url + strlen(XT_XTP_STR));
6261 /* split out the url */
6262 for ((p = strtok_r(dup, "/", &last)); p;
6263 (p = strtok_r(NULL, "/", &last))) {
6264 if (n_tokens < 4)
6265 tokens[n_tokens++] = p;
6268 /* should be atleast three fields 'class/seskey/command/arg' */
6269 if (n_tokens < 3)
6270 goto clean;
6272 dsp = xtp_despatches;
6273 req_class = atoi(tokens[0]);
6274 while (dsp->xtp_class) {
6275 if (dsp->xtp_class == req_class) {
6276 dsp_match = dsp;
6277 break;
6279 dsp++;
6282 /* did we find one atall? */
6283 if (dsp_match == NULL) {
6284 show_oops(t, "%s: no matching xtp despatch found", __func__);
6285 goto clean;
6288 /* check session key and call despatch function */
6289 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6290 ret = TRUE; /* all is well, this was a valid xtp request */
6291 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6294 clean:
6295 if (dup)
6296 g_free(dup);
6298 return (ret);
6303 void
6304 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6306 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6308 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6310 if (t == NULL) {
6311 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6312 return;
6315 if (uri == NULL) {
6316 show_oops(t, "activate_uri_entry_cb no uri");
6317 return;
6320 uri += strspn(uri, "\t ");
6322 /* if xxxt:// treat specially */
6323 if (parse_xtp_url(t, uri))
6324 return;
6326 /* otherwise continue to load page normally */
6327 load_uri(t, (gchar *)uri);
6328 focus_webview(t);
6331 void
6332 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6334 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6335 char *newuri = NULL;
6336 gchar *enc_search;
6338 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6340 if (t == NULL) {
6341 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6342 return;
6345 if (search_string == NULL) {
6346 show_oops(t, "no search_string");
6347 return;
6350 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6352 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6353 newuri = g_strdup_printf(search_string, enc_search);
6354 g_free(enc_search);
6356 marks_clear(t);
6357 webkit_web_view_load_uri(t->wv, newuri);
6358 focus_webview(t);
6360 if (newuri)
6361 g_free(newuri);
6364 void
6365 check_and_set_cookie(const gchar *uri, struct tab *t)
6367 struct domain *d = NULL;
6368 int es = 0;
6370 if (uri == NULL || t == NULL)
6371 return;
6373 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6374 es = 0;
6375 else
6376 es = 1;
6378 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6379 es ? "enable" : "disable", uri);
6381 g_object_set(G_OBJECT(t->settings),
6382 "enable-html5-local-storage", es, (char *)NULL);
6383 webkit_web_view_set_settings(t->wv, t->settings);
6386 void
6387 check_and_set_js(const gchar *uri, struct tab *t)
6389 struct domain *d = NULL;
6390 int es = 0;
6392 if (uri == NULL || t == NULL)
6393 return;
6395 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6396 es = 0;
6397 else
6398 es = 1;
6400 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6401 es ? "enable" : "disable", uri);
6403 g_object_set(G_OBJECT(t->settings),
6404 "enable-scripts", es, (char *)NULL);
6405 g_object_set(G_OBJECT(t->settings),
6406 "javascript-can-open-windows-automatically", es, (char *)NULL);
6407 webkit_web_view_set_settings(t->wv, t->settings);
6409 button_set_stockid(t->js_toggle,
6410 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6413 void
6414 color_address_bar(gpointer p)
6416 GdkColor color;
6417 struct tab *tt, *t = p;
6418 gchar *col_str = XT_COLOR_WHITE;
6419 const gchar *uri, *u = NULL, *error_str = NULL;
6421 gdk_threads_enter();
6422 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6424 /* make sure t still exists */
6425 if (t == NULL)
6426 return;
6427 TAILQ_FOREACH(tt, &tabs, entry)
6428 if (t == tt)
6429 break;
6430 if (t != tt)
6431 goto done;
6433 if ((uri = get_uri(t)) == NULL)
6434 goto white;
6435 u = g_strdup(uri);
6437 gdk_threads_leave();
6439 col_str = XT_COLOR_YELLOW;
6440 switch (load_compare_cert(u, &error_str)) {
6441 case CERT_LOCAL:
6442 col_str = XT_COLOR_BLUE;
6443 break;
6444 case CERT_TRUSTED:
6445 col_str = XT_COLOR_GREEN;
6446 break;
6447 case CERT_UNTRUSTED:
6448 col_str = XT_COLOR_YELLOW;
6449 break;
6450 case CERT_BAD:
6451 col_str = XT_COLOR_RED;
6452 break;
6455 gdk_threads_enter();
6457 /* make sure t isn't deleted */
6458 TAILQ_FOREACH(tt, &tabs, entry)
6459 if (t == tt)
6460 break;
6461 if (t != tt)
6462 goto done;
6464 /* test to see if the user navigated away and canceled the thread */
6465 if (t->thread != g_thread_self())
6466 goto done;
6467 white:
6468 gdk_color_parse(col_str, &color);
6469 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6471 if (!strcmp(col_str, XT_COLOR_WHITE))
6472 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6473 else
6474 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6476 if (error_str && error_str[0] != '\0')
6477 show_oops(t, "%s", error_str);
6479 t->thread = NULL;
6480 done:
6481 /* t is invalid at this point */
6482 if (u)
6483 g_free((gpointer)u);
6484 gdk_threads_leave();
6487 void
6488 show_ca_status(struct tab *t, const char *uri)
6490 GdkColor color;
6491 gchar *col_str = XT_COLOR_WHITE;
6493 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6494 ssl_strict_certs, ssl_ca_file, uri);
6496 if (t == NULL)
6497 return;
6499 if (uri == NULL)
6500 goto done;
6501 if (ssl_ca_file == NULL) {
6502 if (g_str_has_prefix(uri, "http://"))
6503 goto done;
6504 if (g_str_has_prefix(uri, "https://")) {
6505 col_str = XT_COLOR_RED;
6506 goto done;
6508 return;
6510 if (g_str_has_prefix(uri, "http://") ||
6511 !g_str_has_prefix(uri, "https://"))
6512 goto done;
6515 * It is not necessary to see if the thread is already running.
6516 * If the thread is in progress setting it to something else aborts it
6517 * on the way out.
6520 /* thread the coloring of the address bar */
6521 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
6523 return;
6525 done:
6526 if (col_str) {
6527 gdk_color_parse(col_str, &color);
6528 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6530 if (!strcmp(col_str, XT_COLOR_WHITE))
6531 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6532 else
6533 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6537 void
6538 free_favicon(struct tab *t)
6540 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6541 __func__, t->icon_download, t->icon_request);
6543 if (t->icon_request)
6544 g_object_unref(t->icon_request);
6545 if (t->icon_dest_uri)
6546 g_free(t->icon_dest_uri);
6548 t->icon_request = NULL;
6549 t->icon_dest_uri = NULL;
6552 void
6553 xt_icon_from_name(struct tab *t, gchar *name)
6555 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6556 GTK_ENTRY_ICON_PRIMARY, "text-html");
6557 if (show_url == 0)
6558 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6559 GTK_ENTRY_ICON_PRIMARY, "text-html");
6560 else
6561 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6562 GTK_ENTRY_ICON_PRIMARY, NULL);
6565 void
6566 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6568 GdkPixbuf *pb_scaled;
6570 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6571 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6572 GDK_INTERP_BILINEAR);
6573 else
6574 pb_scaled = pb;
6576 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6577 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6578 if (show_url == 0)
6579 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6580 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6581 else
6582 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6583 GTK_ENTRY_ICON_PRIMARY, NULL);
6585 if (pb_scaled != pb)
6586 g_object_unref(pb_scaled);
6589 void
6590 xt_icon_from_file(struct tab *t, char *file)
6592 GdkPixbuf *pb;
6594 if (g_str_has_prefix(file, "file://"))
6595 file += strlen("file://");
6597 pb = gdk_pixbuf_new_from_file(file, NULL);
6598 if (pb) {
6599 xt_icon_from_pixbuf(t, pb);
6600 g_object_unref(pb);
6601 } else
6602 xt_icon_from_name(t, "text-html");
6605 gboolean
6606 is_valid_icon(char *file)
6608 gboolean valid = 0;
6609 const char *mime_type;
6610 GFileInfo *fi;
6611 GFile *gf;
6613 gf = g_file_new_for_path(file);
6614 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6615 NULL, NULL);
6616 mime_type = g_file_info_get_content_type(fi);
6617 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6618 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6619 g_strcmp0(mime_type, "image/png") == 0 ||
6620 g_strcmp0(mime_type, "image/gif") == 0 ||
6621 g_strcmp0(mime_type, "application/octet-stream") == 0;
6622 g_object_unref(fi);
6623 g_object_unref(gf);
6625 return (valid);
6628 void
6629 set_favicon_from_file(struct tab *t, char *file)
6631 struct stat sb;
6633 if (t == NULL || file == NULL)
6634 return;
6636 if (g_str_has_prefix(file, "file://"))
6637 file += strlen("file://");
6638 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6640 if (!stat(file, &sb)) {
6641 if (sb.st_size == 0 || !is_valid_icon(file)) {
6642 /* corrupt icon so trash it */
6643 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6644 __func__, file);
6645 unlink(file);
6646 /* no need to set icon to default here */
6647 return;
6650 xt_icon_from_file(t, file);
6653 void
6654 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6655 WebKitWebView *wv)
6657 WebKitDownloadStatus status = webkit_download_get_status(download);
6658 struct tab *tt = NULL, *t = NULL;
6661 * find the webview instead of passing in the tab as it could have been
6662 * deleted from underneath us.
6664 TAILQ_FOREACH(tt, &tabs, entry) {
6665 if (tt->wv == wv) {
6666 t = tt;
6667 break;
6670 if (t == NULL)
6671 return;
6673 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6674 __func__, t->tab_id, status);
6676 switch (status) {
6677 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6678 /* -1 */
6679 t->icon_download = NULL;
6680 free_favicon(t);
6681 break;
6682 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6683 /* 0 */
6684 break;
6685 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6686 /* 1 */
6687 break;
6688 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6689 /* 2 */
6690 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6691 __func__, t->tab_id);
6692 t->icon_download = NULL;
6693 free_favicon(t);
6694 break;
6695 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6696 /* 3 */
6698 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6699 __func__, t->icon_dest_uri);
6700 set_favicon_from_file(t, t->icon_dest_uri);
6701 /* these will be freed post callback */
6702 t->icon_request = NULL;
6703 t->icon_download = NULL;
6704 break;
6705 default:
6706 break;
6710 void
6711 abort_favicon_download(struct tab *t)
6713 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6715 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6716 if (t->icon_download) {
6717 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6718 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6719 webkit_download_cancel(t->icon_download);
6720 t->icon_download = NULL;
6721 } else
6722 free_favicon(t);
6723 #endif
6725 xt_icon_from_name(t, "text-html");
6728 void
6729 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6731 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6733 if (uri == NULL || t == NULL)
6734 return;
6736 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6737 /* take icon from WebKitIconDatabase */
6738 GdkPixbuf *pb;
6740 pb = webkit_web_view_get_icon_pixbuf(wv);
6741 if (pb) {
6742 xt_icon_from_pixbuf(t, pb);
6743 g_object_unref(pb);
6744 } else
6745 xt_icon_from_name(t, "text-html");
6746 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6747 /* download icon to cache dir */
6748 gchar *name_hash, file[PATH_MAX];
6749 struct stat sb;
6751 if (t->icon_request) {
6752 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6753 return;
6756 /* check to see if we got the icon in cache */
6757 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6758 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6759 g_free(name_hash);
6761 if (!stat(file, &sb)) {
6762 if (sb.st_size > 0) {
6763 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6764 __func__, file);
6765 set_favicon_from_file(t, file);
6766 return;
6769 /* corrupt icon so trash it */
6770 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6771 __func__, file);
6772 unlink(file);
6775 /* create download for icon */
6776 t->icon_request = webkit_network_request_new(uri);
6777 if (t->icon_request == NULL) {
6778 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6779 __func__, uri);
6780 return;
6783 t->icon_download = webkit_download_new(t->icon_request);
6784 if (t->icon_download == NULL)
6785 return;
6787 /* we have to free icon_dest_uri later */
6788 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6789 webkit_download_set_destination_uri(t->icon_download,
6790 t->icon_dest_uri);
6792 if (webkit_download_get_status(t->icon_download) ==
6793 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6794 g_object_unref(t->icon_request);
6795 g_free(t->icon_dest_uri);
6796 t->icon_request = NULL;
6797 t->icon_dest_uri = NULL;
6798 return;
6801 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6802 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6804 webkit_download_start(t->icon_download);
6805 #endif
6808 void
6809 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6811 const gchar *uri = NULL, *title = NULL;
6812 struct history *h, find;
6813 struct karg a;
6814 GdkColor color;
6816 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6817 webkit_web_view_get_load_status(wview),
6818 get_uri(t) ? get_uri(t) : "NOTHING");
6820 if (t == NULL) {
6821 show_oops(NULL, "notify_load_status_cb invalid parameters");
6822 return;
6825 switch (webkit_web_view_get_load_status(wview)) {
6826 case WEBKIT_LOAD_PROVISIONAL:
6827 /* 0 */
6828 abort_favicon_download(t);
6829 #if GTK_CHECK_VERSION(2, 20, 0)
6830 gtk_widget_show(t->spinner);
6831 gtk_spinner_start(GTK_SPINNER(t->spinner));
6832 #endif
6833 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6835 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6837 /* assume we are a new address */
6838 gdk_color_parse("white", &color);
6839 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6840 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6842 /* take focus if we are visible */
6843 focus_webview(t);
6844 t->focus_wv = 1;
6846 /* kill color thread */
6847 t->thread = NULL;
6849 break;
6851 case WEBKIT_LOAD_COMMITTED:
6852 /* 1 */
6853 uri = get_uri(t);
6854 if (uri == NULL)
6855 return;
6856 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6858 if (t->status) {
6859 g_free(t->status);
6860 t->status = NULL;
6862 set_status(t, (char *)uri, XT_STATUS_LOADING);
6864 /* check if js white listing is enabled */
6865 if (enable_cookie_whitelist)
6866 check_and_set_cookie(uri, t);
6867 if (enable_js_whitelist)
6868 check_and_set_js(uri, t);
6870 if (t->styled)
6871 apply_style(t);
6874 /* we know enough to autosave the session */
6875 if (session_autosave) {
6876 a.s = NULL;
6877 save_tabs(t, &a);
6880 show_ca_status(t, uri);
6881 break;
6883 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6884 /* 3 */
6885 break;
6887 case WEBKIT_LOAD_FINISHED:
6888 /* 2 */
6889 uri = get_uri(t);
6890 if (uri == NULL)
6891 return;
6893 if (!strncmp(uri, "http://", strlen("http://")) ||
6894 !strncmp(uri, "https://", strlen("https://")) ||
6895 !strncmp(uri, "file://", strlen("file://"))) {
6896 find.uri = uri;
6897 h = RB_FIND(history_list, &hl, &find);
6898 if (!h) {
6899 title = get_title(t, FALSE);
6900 h = g_malloc(sizeof *h);
6901 h->uri = g_strdup(uri);
6902 h->title = g_strdup(title);
6903 RB_INSERT(history_list, &hl, h);
6904 completion_add_uri(h->uri);
6905 update_history_tabs(NULL);
6909 set_status(t, (char *)uri, XT_STATUS_URI);
6910 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6911 case WEBKIT_LOAD_FAILED:
6912 /* 4 */
6913 #endif
6914 #if GTK_CHECK_VERSION(2, 20, 0)
6915 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6916 gtk_widget_hide(t->spinner);
6917 #endif
6918 default:
6919 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6920 break;
6923 if (t->item)
6924 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6925 else
6926 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6927 can_go_back_for_real(t));
6929 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6930 can_go_forward_for_real(t));
6933 void
6934 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6936 const gchar *title = NULL, *win_title = NULL;
6938 title = get_title(t, FALSE);
6939 win_title = get_title(t, TRUE);
6940 gtk_label_set_text(GTK_LABEL(t->label), title);
6941 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6942 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6943 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6946 void
6947 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6949 run_script(t, JS_HINTING);
6952 void
6953 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6955 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6956 progress == 100 ? 0 : (double)progress / 100);
6957 if (show_url == 0) {
6958 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6959 progress == 100 ? 0 : (double)progress / 100);
6962 update_statusbar_position(NULL, NULL);
6966 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6967 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6968 WebKitWebPolicyDecision *pd, struct tab *t)
6970 char *uri;
6971 WebKitWebNavigationReason reason;
6972 struct domain *d = NULL;
6974 if (t == NULL) {
6975 show_oops(NULL, "webview_npd_cb invalid parameters");
6976 return (FALSE);
6979 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6980 t->ctrl_click,
6981 webkit_network_request_get_uri(request));
6983 uri = (char *)webkit_network_request_get_uri(request);
6985 /* if this is an xtp url, we don't load anything else */
6986 if (parse_xtp_url(t, uri))
6987 return (TRUE);
6989 if (t->ctrl_click) {
6990 t->ctrl_click = 0;
6991 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6992 webkit_web_policy_decision_ignore(pd);
6993 return (TRUE); /* we made the decission */
6997 * This is a little hairy but it comes down to this:
6998 * when we run in whitelist mode we have to assist the browser in
6999 * opening the URL that it would have opened in a new tab.
7001 reason = webkit_web_navigation_action_get_reason(na);
7002 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
7003 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7004 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
7005 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7006 load_uri(t, uri);
7007 webkit_web_policy_decision_use(pd);
7008 return (TRUE); /* we made the decision */
7011 return (FALSE);
7014 WebKitWebView *
7015 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7017 struct tab *tt;
7018 struct domain *d = NULL;
7019 const gchar *uri;
7020 WebKitWebView *webview = NULL;
7022 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
7023 webkit_web_view_get_uri(wv));
7025 if (tabless) {
7026 /* open in current tab */
7027 webview = t->wv;
7028 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7029 uri = webkit_web_view_get_uri(wv);
7030 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7031 return (NULL);
7033 tt = create_new_tab(NULL, NULL, 1, -1);
7034 webview = tt->wv;
7035 } else if (enable_scripts == 1) {
7036 tt = create_new_tab(NULL, NULL, 1, -1);
7037 webview = tt->wv;
7040 return (webview);
7043 gboolean
7044 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
7046 const gchar *uri;
7047 struct domain *d = NULL;
7049 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7051 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7052 uri = webkit_web_view_get_uri(wv);
7053 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7054 return (FALSE);
7056 delete_tab(t);
7057 } else if (enable_scripts == 1)
7058 delete_tab(t);
7060 return (TRUE);
7064 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7066 /* we can not eat the event without throwing gtk off so defer it */
7068 /* catch middle click */
7069 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7070 t->ctrl_click = 1;
7071 goto done;
7074 /* catch ctrl click */
7075 if (e->type == GDK_BUTTON_RELEASE &&
7076 CLEAN(e->state) == GDK_CONTROL_MASK)
7077 t->ctrl_click = 1;
7078 else
7079 t->ctrl_click = 0;
7080 done:
7081 return (XT_CB_PASSTHROUGH);
7085 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7087 struct mime_type *m;
7089 m = find_mime_type(mime_type);
7090 if (m == NULL)
7091 return (1);
7092 if (m->mt_download)
7093 return (1);
7095 switch (fork()) {
7096 case -1:
7097 show_oops(t, "can't fork mime handler");
7098 return (1);
7099 /* NOTREACHED */
7100 case 0:
7101 break;
7102 default:
7103 return (0);
7106 /* child */
7107 execlp(m->mt_action, m->mt_action,
7108 webkit_network_request_get_uri(request), (void *)NULL);
7110 _exit(0);
7112 /* NOTREACHED */
7113 return (0);
7116 char *
7117 get_mime_type(const char *file)
7119 const gchar *m;
7120 char *mime_type = NULL;
7121 GFileInfo *fi;
7122 GFile *gf;
7124 if (g_str_has_prefix(file, "file://"))
7125 file += strlen("file://");
7127 gf = g_file_new_for_path(file);
7128 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7129 NULL, NULL);
7130 if ((m = g_file_info_get_content_type(fi)) != NULL)
7131 mime_type = g_strdup(m);
7132 g_object_unref(fi);
7133 g_object_unref(gf);
7135 return (mime_type);
7139 run_download_mimehandler(char *mime_type, char *file)
7141 struct mime_type *m;
7143 m = find_mime_type(mime_type);
7144 if (m == NULL)
7145 return (1);
7147 switch (fork()) {
7148 case -1:
7149 show_oops(NULL, "can't fork download mime handler");
7150 return (1);
7151 /* NOTREACHED */
7152 case 0:
7153 break;
7154 default:
7155 return (0);
7158 /* child */
7159 if (g_str_has_prefix(file, "file://"))
7160 file += strlen("file://");
7161 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7163 _exit(0);
7165 /* NOTREACHED */
7166 return (0);
7169 void
7170 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7171 WebKitWebView *wv)
7173 WebKitDownloadStatus status;
7174 const char *file = NULL;
7175 char *mime = NULL;
7177 if (download == NULL)
7178 return;
7179 status = webkit_download_get_status(download);
7180 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7181 return;
7183 file = webkit_download_get_destination_uri(download);
7184 if (file == NULL)
7185 return;
7186 mime = get_mime_type(file);
7187 if (mime == NULL)
7188 return;
7190 run_download_mimehandler((char *)mime, (char *)file);
7191 g_free(mime);
7195 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7196 WebKitNetworkRequest *request, char *mime_type,
7197 WebKitWebPolicyDecision *decision, struct tab *t)
7199 if (t == NULL) {
7200 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7201 return (FALSE);
7204 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7205 t->tab_id, mime_type);
7207 if (run_mimehandler(t, mime_type, request) == 0) {
7208 webkit_web_policy_decision_ignore(decision);
7209 focus_webview(t);
7210 return (TRUE);
7213 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7214 webkit_web_policy_decision_download(decision);
7215 return (TRUE);
7218 return (FALSE);
7222 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7223 struct tab *t)
7225 struct stat sb;
7226 const gchar *suggested_name;
7227 gchar *filename = NULL;
7228 char *uri = NULL;
7229 struct download *download_entry;
7230 int i, ret = TRUE;
7232 if (wk_download == NULL || t == NULL) {
7233 show_oops(NULL, "%s invalid parameters", __func__);
7234 return (FALSE);
7237 suggested_name = webkit_download_get_suggested_filename(wk_download);
7238 if (suggested_name == NULL)
7239 return (FALSE); /* abort download */
7241 i = 0;
7242 do {
7243 if (filename) {
7244 g_free(filename);
7245 filename = NULL;
7247 if (i) {
7248 g_free(uri);
7249 uri = NULL;
7250 filename = g_strdup_printf("%d%s", i, suggested_name);
7252 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7253 filename : suggested_name);
7254 i++;
7255 } while (!stat(uri + strlen("file://"), &sb));
7257 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7258 "local %s\n", __func__, t->tab_id, filename, uri);
7260 webkit_download_set_destination_uri(wk_download, uri);
7262 if (webkit_download_get_status(wk_download) ==
7263 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7264 show_oops(t, "%s: download failed to start", __func__);
7265 ret = FALSE;
7266 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7267 } else {
7268 /* connect "download first" mime handler */
7269 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7270 G_CALLBACK(download_status_changed_cb), NULL);
7272 download_entry = g_malloc(sizeof(struct download));
7273 download_entry->download = wk_download;
7274 download_entry->tab = t;
7275 download_entry->id = next_download_id++;
7276 RB_INSERT(download_list, &downloads, download_entry);
7277 /* get from history */
7278 g_object_ref(wk_download);
7279 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7280 show_oops(t, "Download of '%s' started...",
7281 basename((char *)webkit_download_get_destination_uri(wk_download)));
7284 if (uri)
7285 g_free(uri);
7287 if (filename)
7288 g_free(filename);
7290 /* sync other download manager tabs */
7291 update_download_tabs(NULL);
7294 * NOTE: never redirect/render the current tab before this
7295 * function returns. This will cause the download to never start.
7297 return (ret); /* start download */
7300 void
7301 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7303 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7305 if (t == NULL) {
7306 show_oops(NULL, "webview_hover_cb");
7307 return;
7310 if (uri)
7311 set_status(t, uri, XT_STATUS_LINK);
7312 else {
7313 if (t->status)
7314 set_status(t, t->status, XT_STATUS_NOTHING);
7319 mark(struct tab *t, struct karg *arg)
7321 char mark;
7322 int index;
7324 mark = arg->s[1];
7325 if ((index = marktoindex(mark)) == -1)
7326 return -1;
7328 if (arg->i == XT_MARK_SET)
7329 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7330 else if (arg->i == XT_MARK_GOTO) {
7331 if (t->mark[index] == XT_INVALID_MARK) {
7332 show_oops(t, "mark '%c' does not exist", mark);
7333 return -1;
7335 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7336 something changes the document size */
7337 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7340 return 0;
7343 void
7344 marks_clear(struct tab *t)
7346 int i;
7348 for (i = 0; i < LENGTH(t->mark); i++)
7349 t->mark[i] = XT_INVALID_MARK;
7353 qmarks_load(void)
7355 char file[PATH_MAX];
7356 char *line = NULL, *p;
7357 int index, i;
7358 FILE *f;
7359 size_t linelen;
7361 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7362 if ((f = fopen(file, "r+")) == NULL) {
7363 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7364 return (1);
7367 for (i = 1; ; i++) {
7368 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7369 if (feof(f) || ferror(f))
7370 break;
7371 if (strlen(line) == 0 || line[0] == '#') {
7372 free(line);
7373 line = NULL;
7374 continue;
7377 p = strtok(line, " \t");
7379 if (p == NULL || strlen(p) != 1 ||
7380 (index = marktoindex(*p)) == -1) {
7381 warnx("corrupt quickmarks file, line %d", i);
7382 break;
7385 p = strtok(NULL, " \t");
7386 if (qmarks[index] != NULL)
7387 g_free(qmarks[index]);
7388 qmarks[index] = g_strdup(p);
7391 fclose(f);
7393 return (0);
7397 qmarks_save(void)
7399 char file[PATH_MAX];
7400 int i;
7401 FILE *f;
7403 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7404 if ((f = fopen(file, "r+")) == NULL) {
7405 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7406 return (1);
7409 for (i = 0; i < XT_NOMARKS; i++)
7410 if (qmarks[i] != NULL)
7411 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7413 fclose(f);
7415 return (0);
7419 qmark(struct tab *t, struct karg *arg)
7421 char mark;
7422 int index;
7424 mark = arg->s[strlen(arg->s)-1];
7425 index = marktoindex(mark);
7426 if (index == -1)
7427 return (-1);
7429 switch (arg->i) {
7430 case XT_QMARK_SET:
7431 if (qmarks[index] != NULL)
7432 g_free(qmarks[index]);
7434 qmarks_load(); /* sync if multiple instances */
7435 qmarks[index] = g_strdup(get_uri(t));
7436 qmarks_save();
7437 break;
7438 case XT_QMARK_OPEN:
7439 if (qmarks[index] != NULL)
7440 load_uri(t, qmarks[index]);
7441 else {
7442 show_oops(t, "quickmark \"%c\" does not exist",
7443 mark);
7444 return (-1);
7446 break;
7447 case XT_QMARK_TAB:
7448 if (qmarks[index] != NULL)
7449 create_new_tab(qmarks[index], NULL, 1, -1);
7450 else {
7451 show_oops(t, "quickmark \"%c\" does not exist",
7452 mark);
7453 return (-1);
7455 break;
7458 return (0);
7462 go_up(struct tab *t, struct karg *args)
7464 int levels;
7465 char *uri;
7466 char *tmp;
7468 levels = atoi(args->s);
7469 if (levels == 0)
7470 levels = 1;
7472 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7473 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7474 return 1;
7475 tmp += strlen(XT_PROTO_DELIM);
7477 /* if an uri starts with a slash, leave it alone (for file:///) */
7478 if (tmp[0] == '/')
7479 tmp++;
7481 while (levels--) {
7482 char *p;
7484 p = strrchr(tmp, '/');
7485 if (p != NULL)
7486 *p = '\0';
7487 else
7488 break;
7491 load_uri(t, uri);
7492 g_free(uri);
7494 return (0);
7498 gototab(struct tab *t, struct karg *args)
7500 int tab;
7501 struct karg arg = {0, NULL, -1};
7503 tab = atoi(args->s);
7505 arg.i = XT_TAB_NEXT;
7506 arg.precount = tab;
7508 movetab(t, &arg);
7510 return (0);
7514 zoom_amount(struct tab *t, struct karg *arg)
7516 struct karg narg = {0, NULL, -1};
7518 narg.i = atoi(arg->s);
7519 resizetab(t, &narg);
7521 return 0;
7525 flip_colon(struct tab *t, struct karg *arg)
7527 struct karg narg = {0, NULL, -1};
7528 char *p;
7530 if (t == NULL || arg == NULL)
7531 return (1);
7533 p = strstr(arg->s, ":");
7534 if (p == NULL)
7535 return (1);
7536 *p = '\0';
7538 narg.i = ':';
7539 narg.s = arg->s;
7540 command(t, &narg);
7542 return (0);
7545 /* buffer commands receive the regex that triggered them in arg.s */
7546 char bcmd[XT_BUFCMD_SZ];
7547 struct buffercmd {
7548 char *regex;
7549 int precount;
7550 #define XT_PRE_NO (0)
7551 #define XT_PRE_YES (1)
7552 #define XT_PRE_MAYBE (2)
7553 char *cmd;
7554 int (*func)(struct tab *, struct karg *);
7555 int arg;
7556 regex_t cregex;
7557 } buffercmds[] = {
7558 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7559 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7560 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7561 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7562 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7563 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7564 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7565 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7566 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7567 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7568 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7569 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7570 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7571 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7572 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7573 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7574 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7575 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7578 void
7579 buffercmd_init(void)
7581 int i;
7583 for (i = 0; i < LENGTH(buffercmds); i++)
7584 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7585 REG_EXTENDED | REG_NOSUB))
7586 startpage_add("invalid buffercmd regex %s",
7587 buffercmds[i].regex);
7590 void
7591 buffercmd_abort(struct tab *t)
7593 int i;
7595 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7596 for (i = 0; i < LENGTH(bcmd); i++)
7597 bcmd[i] = '\0';
7599 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7600 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7603 void
7604 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7606 struct karg arg = {0, NULL, -1};
7608 arg.i = cmd->arg;
7609 arg.s = g_strdup(bcmd);
7611 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7612 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7613 cmd->func(t, &arg);
7615 if (arg.s)
7616 g_free(arg.s);
7618 buffercmd_abort(t);
7621 gboolean
7622 buffercmd_addkey(struct tab *t, guint keyval)
7624 int i, c, match ;
7625 char s[XT_BUFCMD_SZ];
7627 if (keyval == GDK_Escape) {
7628 buffercmd_abort(t);
7629 return (XT_CB_HANDLED);
7632 /* key with modifier or non-ascii character */
7633 if (!isascii(keyval))
7634 return (XT_CB_PASSTHROUGH);
7636 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7637 "to buffer \"%s\"\n", keyval, bcmd);
7639 for (i = 0; i < LENGTH(bcmd); i++)
7640 if (bcmd[i] == '\0') {
7641 bcmd[i] = keyval;
7642 break;
7645 /* buffer full, ignore input */
7646 if (i >= LENGTH(bcmd) -1) {
7647 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7648 buffercmd_abort(t);
7649 return (XT_CB_HANDLED);
7652 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7654 /* find exact match */
7655 for (i = 0; i < LENGTH(buffercmds); i++)
7656 if (regexec(&buffercmds[i].cregex, bcmd,
7657 (size_t) 0, NULL, 0) == 0) {
7658 buffercmd_execute(t, &buffercmds[i]);
7659 goto done;
7662 /* find non exact matches to see if we need to abort ot not */
7663 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7664 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7665 c = -1;
7666 s[0] = '\0';
7667 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7668 if (isdigit(bcmd[0])) {
7669 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7670 continue;
7671 } else {
7672 c = 0;
7673 if (sscanf(bcmd, "%s", s) == 0)
7674 continue;
7676 } else if (buffercmds[i].precount == XT_PRE_YES) {
7677 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7678 continue;
7679 } else {
7680 if (sscanf(bcmd, "%s", s) == 0)
7681 continue;
7683 if (c == -1 && buffercmds[i].precount)
7684 continue;
7685 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7686 match++;
7688 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7689 i, match, buffercmds[i].cmd, c, s);
7691 if (match == 0) {
7692 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7693 buffercmd_abort(t);
7696 done:
7697 return (XT_CB_HANDLED);
7700 gboolean
7701 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7703 struct key_binding *k;
7705 /* handle keybindings if buffercmd is empty.
7706 if not empty, allow commands like C-n */
7707 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7708 TAILQ_FOREACH(k, &kbl, entry)
7709 if (e->keyval == k->key
7710 && (entry ? k->use_in_entry : 1)) {
7711 if (k->mask == 0) {
7712 if ((e->state & (CTRL | MOD1)) == 0)
7713 return (cmd_execute(t, k->cmd));
7714 } else if ((e->state & k->mask) == k->mask) {
7715 return (cmd_execute(t, k->cmd));
7719 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7720 return buffercmd_addkey(t, e->keyval);
7722 return (XT_CB_PASSTHROUGH);
7726 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7728 char s[2], buf[128];
7729 const char *errstr = NULL;
7731 /* don't use w directly; use t->whatever instead */
7733 if (t == NULL) {
7734 show_oops(NULL, "wv_keypress_after_cb");
7735 return (XT_CB_PASSTHROUGH);
7738 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7739 e->keyval, e->state, t);
7741 if (t->hints_on) {
7742 /* ESC */
7743 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7744 disable_hints(t);
7745 return (XT_CB_HANDLED);
7748 /* RETURN */
7749 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7750 if (errstr) {
7751 /* we have a string */
7752 } else {
7753 /* we have a number */
7754 snprintf(buf, sizeof buf,
7755 "vimprobable_fire(%s)", t->hint_num);
7756 run_script(t, buf);
7758 disable_hints(t);
7761 /* BACKSPACE */
7762 /* XXX unfuck this */
7763 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7764 if (t->hint_mode == XT_HINT_NUMERICAL) {
7765 /* last input was numerical */
7766 int l;
7767 l = strlen(t->hint_num);
7768 if (l > 0) {
7769 l--;
7770 if (l == 0) {
7771 disable_hints(t);
7772 enable_hints(t);
7773 } else {
7774 t->hint_num[l] = '\0';
7775 goto num;
7778 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7779 /* last input was alphanumerical */
7780 int l;
7781 l = strlen(t->hint_buf);
7782 if (l > 0) {
7783 l--;
7784 if (l == 0) {
7785 disable_hints(t);
7786 enable_hints(t);
7787 } else {
7788 t->hint_buf[l] = '\0';
7789 goto anum;
7792 } else {
7793 /* bogus */
7794 disable_hints(t);
7798 /* numerical input */
7799 if (CLEAN(e->state) == 0 &&
7800 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7801 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7802 snprintf(s, sizeof s, "%c", e->keyval);
7803 strlcat(t->hint_num, s, sizeof t->hint_num);
7804 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7805 t->hint_num);
7806 num:
7807 if (errstr) {
7808 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7809 "invalid link number\n");
7810 disable_hints(t);
7811 } else {
7812 snprintf(buf, sizeof buf,
7813 "vimprobable_update_hints(%s)",
7814 t->hint_num);
7815 t->hint_mode = XT_HINT_NUMERICAL;
7816 run_script(t, buf);
7819 /* empty the counter buffer */
7820 bzero(t->hint_buf, sizeof t->hint_buf);
7821 return (XT_CB_HANDLED);
7824 /* alphanumerical input */
7825 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7826 e->keyval <= GDK_z) ||
7827 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7828 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7829 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7830 e->keyval <= GDK_9) ||
7831 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7832 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7833 snprintf(s, sizeof s, "%c", e->keyval);
7834 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7835 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7836 " %s\n", t->hint_buf);
7837 anum:
7838 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7839 run_script(t, buf);
7841 snprintf(buf, sizeof buf,
7842 "vimprobable_show_hints('%s')", t->hint_buf);
7843 t->hint_mode = XT_HINT_ALPHANUM;
7844 run_script(t, buf);
7846 /* empty the counter buffer */
7847 bzero(t->hint_num, sizeof t->hint_num);
7848 return (XT_CB_HANDLED);
7851 return (XT_CB_HANDLED);
7852 } else {
7853 /* prefix input*/
7854 snprintf(s, sizeof s, "%c", e->keyval);
7855 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7856 cmd_prefix = 10 * cmd_prefix + atoi(s);
7859 return (handle_keypress(t, e, 0));
7863 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7865 hide_oops(t);
7867 /* Hide buffers, if they are visible, with escape. */
7868 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7869 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7870 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7871 hide_buffers(t);
7872 return (XT_CB_HANDLED);
7875 return (XT_CB_PASSTHROUGH);
7878 gboolean
7879 search_continue(struct tab *t)
7881 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7882 gboolean rv = FALSE;
7884 if (c[0] == ':')
7885 goto done;
7886 if (strlen(c) == 1) {
7887 webkit_web_view_unmark_text_matches(t->wv);
7888 goto done;
7891 if (c[0] == '/')
7892 t->search_forward = TRUE;
7893 else if (c[0] == '?')
7894 t->search_forward = FALSE;
7895 else
7896 goto done;
7898 rv = TRUE;
7899 done:
7900 return (rv);
7903 gboolean
7904 search_cb(struct tab *t)
7906 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7907 GdkColor color;
7909 if (search_continue(t) == FALSE)
7910 goto done;
7912 /* search */
7913 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
7914 TRUE) == FALSE) {
7915 /* not found, mark red */
7916 gdk_color_parse(XT_COLOR_RED, &color);
7917 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7918 /* unmark and remove selection */
7919 webkit_web_view_unmark_text_matches(t->wv);
7920 /* my kingdom for a way to unselect text in webview */
7921 } else {
7922 /* found, highlight all */
7923 webkit_web_view_unmark_text_matches(t->wv);
7924 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7925 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7926 gdk_color_parse(XT_COLOR_WHITE, &color);
7927 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7929 done:
7930 t->search_id = 0;
7931 return (FALSE);
7935 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7937 const gchar *c = gtk_entry_get_text(w);
7939 if (t == NULL) {
7940 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7941 return (XT_CB_PASSTHROUGH);
7944 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7945 e->keyval, e->state, t);
7947 if (search_continue(t) == FALSE)
7948 goto done;
7950 /* if search length is > 4 then no longer play timeout games */
7951 if (strlen(c) > 4) {
7952 if (t->search_id) {
7953 g_source_remove(t->search_id);
7954 t->search_id = 0;
7956 search_cb(t);
7957 goto done;
7960 /* reestablish a new timer if the user types fast */
7961 if (t->search_id)
7962 g_source_remove(t->search_id);
7963 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
7965 done:
7966 return (XT_CB_PASSTHROUGH);
7969 gboolean
7970 match_uri(const gchar *uri, const gchar *key) {
7971 gchar *voffset;
7972 size_t len;
7973 gboolean match = FALSE;
7975 len = strlen(key);
7977 if (!strncmp(key, uri, len))
7978 match = TRUE;
7979 else {
7980 voffset = strstr(uri, "/") + 2;
7981 if (!strncmp(key, voffset, len))
7982 match = TRUE;
7983 else if (g_str_has_prefix(voffset, "www.")) {
7984 voffset = voffset + strlen("www.");
7985 if (!strncmp(key, voffset, len))
7986 match = TRUE;
7990 return (match);
7993 gboolean
7994 match_session(const gchar *name, const gchar *key) {
7995 char *sub;
7997 sub = strcasestr(name, key);
7999 return sub == name;
8002 void
8003 cmd_getlist(int id, char *key)
8005 int i, dep, c = 0;
8006 struct history *h;
8007 struct session *s;
8009 if (id >= 0) {
8010 if (cmds[id].type & XT_URLARG) {
8011 RB_FOREACH_REVERSE(h, history_list, &hl)
8012 if (match_uri(h->uri, key)) {
8013 cmd_status.list[c] = (char *)h->uri;
8014 if (++c > 255)
8015 break;
8017 cmd_status.len = c;
8018 return;
8019 } else if (cmds[id].type & XT_SESSARG) {
8020 TAILQ_FOREACH(s, &sessions, entry)
8021 if (match_session(s->name, key)) {
8022 cmd_status.list[c] = (char *)s->name;
8023 if (++c > 255)
8024 break;
8026 cmd_status.len = c;
8027 return;
8028 } else if (cmds[id].type & XT_SETARG) {
8029 for (i = 0; i < LENGTH(rs); i++)
8030 if(!strncmp(key, rs[i].name, strlen(key)))
8031 cmd_status.list[c++] = rs[i].name;
8032 cmd_status.len = c;
8033 return;
8037 dep = (id == -1) ? 0 : cmds[id].level + 1;
8039 for (i = id + 1; i < LENGTH(cmds); i++) {
8040 if (cmds[i].level < dep)
8041 break;
8042 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
8043 strlen(key)) && !isdigit(cmds[i].cmd[0]))
8044 cmd_status.list[c++] = cmds[i].cmd;
8048 cmd_status.len = c;
8051 char *
8052 cmd_getnext(int dir)
8054 cmd_status.index += dir;
8056 if (cmd_status.index < 0)
8057 cmd_status.index = cmd_status.len - 1;
8058 else if (cmd_status.index >= cmd_status.len)
8059 cmd_status.index = 0;
8061 return cmd_status.list[cmd_status.index];
8065 cmd_tokenize(char *s, char *tokens[])
8067 int i = 0;
8068 char *tok, *last;
8069 size_t len = strlen(s);
8070 bool blank;
8072 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8073 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8074 tok = strtok_r(NULL, " ", &last), i++)
8075 tokens[i] = tok;
8077 if (blank && i < 3)
8078 tokens[i++] = "";
8080 return (i);
8083 void
8084 cmd_complete(struct tab *t, char *str, int dir)
8086 GtkEntry *w = GTK_ENTRY(t->cmd);
8087 int i, j, levels, c = 0, dep = 0, parent = -1;
8088 int matchcount = 0;
8089 char *tok, *match, *s = g_strdup(str);
8090 char *tokens[3];
8091 char res[XT_MAX_URL_LENGTH + 32] = ":";
8092 char *sc = s;
8094 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8096 /* copy prefix*/
8097 for (i = 0; isdigit(s[i]); i++)
8098 res[i + 1] = s[i];
8100 for (; isspace(s[i]); i++)
8101 res[i + 1] = s[i];
8103 s += i;
8105 levels = cmd_tokenize(s, tokens);
8107 for (i = 0; i < levels - 1; i++) {
8108 tok = tokens[i];
8109 matchcount = 0;
8110 for (j = c; j < LENGTH(cmds); j++) {
8111 if (cmds[j].level < dep)
8112 break;
8113 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8114 strlen(tok))) {
8115 matchcount++;
8116 c = j + 1;
8117 if (strlen(tok) == strlen(cmds[j].cmd)) {
8118 matchcount = 1;
8119 break;
8124 if (matchcount == 1) {
8125 strlcat(res, tok, sizeof res);
8126 strlcat(res, " ", sizeof res);
8127 dep++;
8128 } else {
8129 g_free(sc);
8130 return;
8133 parent = c - 1;
8136 if (cmd_status.index == -1)
8137 cmd_getlist(parent, tokens[i]);
8139 if (cmd_status.len > 0) {
8140 match = cmd_getnext(dir);
8141 strlcat(res, match, sizeof res);
8142 gtk_entry_set_text(w, res);
8143 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8146 g_free(sc);
8149 gboolean
8150 cmd_execute(struct tab *t, char *str)
8152 struct cmd *cmd = NULL;
8153 char *tok, *last, *s = g_strdup(str), *sc;
8154 char prefixstr[4];
8155 int j, len, c = 0, dep = 0, matchcount = 0;
8156 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8157 struct karg arg = {0, NULL, -1};
8159 sc = s;
8161 /* copy prefix*/
8162 for (j = 0; j<3 && isdigit(s[j]); j++)
8163 prefixstr[j]=s[j];
8165 prefixstr[j]='\0';
8167 s += j;
8168 while (isspace(s[0]))
8169 s++;
8171 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8172 prefix = atoi(prefixstr);
8173 else
8174 s = sc;
8176 for (tok = strtok_r(s, " ", &last); tok;
8177 tok = strtok_r(NULL, " ", &last)) {
8178 matchcount = 0;
8179 for (j = c; j < LENGTH(cmds); j++) {
8180 if (cmds[j].level < dep)
8181 break;
8182 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8183 strlen(tok);
8184 if (cmds[j].level == dep &&
8185 !strncmp(tok, cmds[j].cmd, len)) {
8186 matchcount++;
8187 c = j + 1;
8188 cmd = &cmds[j];
8189 if (len == strlen(cmds[j].cmd)) {
8190 matchcount = 1;
8191 break;
8195 if (matchcount == 1) {
8196 if (cmd->type > 0)
8197 goto execute_cmd;
8198 dep++;
8199 } else {
8200 show_oops(t, "Invalid command: %s", str);
8201 goto done;
8204 execute_cmd:
8205 arg.i = cmd->arg;
8207 if (prefix != -1)
8208 arg.precount = prefix;
8209 else if (cmd_prefix > 0)
8210 arg.precount = cmd_prefix;
8212 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8213 show_oops(t, "No prefix allowed: %s", str);
8214 goto done;
8216 if (cmd->type > 1)
8217 arg.s = last ? g_strdup(last) : g_strdup("");
8218 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8219 arg.precount = atoi(arg.s);
8220 if (arg.precount <= 0) {
8221 if (arg.s[0] == '0')
8222 show_oops(t, "Zero count");
8223 else
8224 show_oops(t, "Trailing characters");
8225 goto done;
8229 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8230 __func__, arg.precount, arg.s);
8232 cmd->func(t, &arg);
8234 rv = XT_CB_HANDLED;
8235 done:
8236 if (j > 0)
8237 cmd_prefix = 0;
8238 g_free(sc);
8239 if (arg.s)
8240 g_free(arg.s);
8242 return (rv);
8246 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8248 if (t == NULL) {
8249 show_oops(NULL, "entry_key_cb invalid parameters");
8250 return (XT_CB_PASSTHROUGH);
8253 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8254 e->keyval, e->state, t);
8256 hide_oops(t);
8258 if (e->keyval == GDK_Escape) {
8259 /* don't use focus_webview(t) because we want to type :cmds */
8260 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8263 return (handle_keypress(t, e, 1));
8266 struct command_entry *
8267 history_prev(struct command_list *l, struct command_entry *at)
8269 if (at == NULL)
8270 at = TAILQ_LAST(l, command_list);
8271 else {
8272 at = TAILQ_PREV(at, command_list, entry);
8273 if (at == NULL)
8274 at = TAILQ_LAST(l, command_list);
8277 return (at);
8280 struct command_entry *
8281 history_next(struct command_list *l, struct command_entry *at)
8283 if (at == NULL)
8284 at = TAILQ_FIRST(l);
8285 else {
8286 at = TAILQ_NEXT(at, entry);
8287 if (at == NULL)
8288 at = TAILQ_FIRST(l);
8291 return (at);
8295 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8297 int rv = XT_CB_HANDLED;
8298 const gchar *c = gtk_entry_get_text(w);
8300 if (t == NULL) {
8301 show_oops(NULL, "cmd_keypress_cb parameters");
8302 return (XT_CB_PASSTHROUGH);
8305 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8306 e->keyval, e->state, t);
8308 /* sanity */
8309 if (c == NULL)
8310 e->keyval = GDK_Escape;
8311 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8312 e->keyval = GDK_Escape;
8314 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8315 e->keyval != GDK_ISO_Left_Tab)
8316 cmd_status.index = -1;
8318 switch (e->keyval) {
8319 case GDK_Tab:
8320 if (c[0] == ':')
8321 cmd_complete(t, (char *)&c[1], 1);
8322 goto done;
8323 case GDK_ISO_Left_Tab:
8324 if (c[0] == ':')
8325 cmd_complete(t, (char *)&c[1], -1);
8327 goto done;
8328 case GDK_Down:
8329 if (c[0] != ':') {
8330 if ((search_at = history_next(&shl, search_at))) {
8331 search_at->line[0] = c[0];
8332 gtk_entry_set_text(w, search_at->line);
8333 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8335 } else {
8336 if ((history_at = history_prev(&chl, history_at))) {
8337 history_at->line[0] = c[0];
8338 gtk_entry_set_text(w, history_at->line);
8339 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8343 goto done;
8344 case GDK_Up:
8345 if (c[0] != ':') {
8346 if ((search_at = history_next(&shl, search_at))) {
8347 search_at->line[0] = c[0];
8348 gtk_entry_set_text(w, search_at->line);
8349 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8351 } else {
8352 if ((history_at = history_next(&chl, history_at))) {
8353 history_at->line[0] = c[0];
8354 gtk_entry_set_text(w, history_at->line);
8355 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8359 goto done;
8360 case GDK_BackSpace:
8361 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
8362 break;
8363 /* FALLTHROUGH */
8364 case GDK_Escape:
8365 hide_cmd(t);
8366 focus_webview(t);
8368 /* cancel search */
8369 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8370 webkit_web_view_unmark_text_matches(t->wv);
8371 goto done;
8374 rv = XT_CB_PASSTHROUGH;
8375 done:
8376 return (rv);
8379 void
8380 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8382 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8385 void
8386 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8388 /* popup menu enabled */
8389 t->popup = 1;
8393 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8395 if (t == NULL) {
8396 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8397 return (XT_CB_PASSTHROUGH);
8400 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8401 t->tab_id, t->popup);
8403 /* if popup is enabled don't lose focus */
8404 if (t->popup) {
8405 t->popup = 0;
8406 return (XT_CB_PASSTHROUGH);
8409 hide_cmd(t);
8410 hide_oops(t);
8412 if (show_url == 0 || t->focus_wv)
8413 focus_webview(t);
8414 else
8415 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8417 return (XT_CB_PASSTHROUGH);
8420 void
8421 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8423 char *s;
8424 const gchar *c = gtk_entry_get_text(entry);
8426 if (t == NULL) {
8427 show_oops(NULL, "cmd_activate_cb invalid parameters");
8428 return;
8431 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8433 hide_cmd(t);
8435 /* sanity */
8436 if (c == NULL)
8437 goto done;
8438 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
8439 goto done;
8440 if (strlen(c) < 2)
8441 goto done;
8442 s = (char *)&c[1];
8444 if (c[0] == '/' || c[0] == '?') {
8445 /* see if there is a timer pending */
8446 if (t->search_id) {
8447 g_source_remove(t->search_id);
8448 t->search_id = 0;
8449 search_cb(t);
8452 if (t->search_text) {
8453 g_free(t->search_text);
8454 t->search_text = NULL;
8457 t->search_text = g_strdup(s);
8458 if (global_search)
8459 g_free(global_search);
8460 global_search = g_strdup(s);
8461 t->search_forward = c[0] == '/';
8463 history_add(&shl, search_file, s, &search_history_count);
8464 goto done;
8467 history_add(&chl, command_file, s, &cmd_history_count);
8468 cmd_execute(t, s);
8469 done:
8470 return;
8473 void
8474 backward_cb(GtkWidget *w, struct tab *t)
8476 struct karg a;
8478 if (t == NULL) {
8479 show_oops(NULL, "backward_cb invalid parameters");
8480 return;
8483 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8485 a.i = XT_NAV_BACK;
8486 navaction(t, &a);
8489 void
8490 forward_cb(GtkWidget *w, struct tab *t)
8492 struct karg a;
8494 if (t == NULL) {
8495 show_oops(NULL, "forward_cb invalid parameters");
8496 return;
8499 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8501 a.i = XT_NAV_FORWARD;
8502 navaction(t, &a);
8505 void
8506 home_cb(GtkWidget *w, struct tab *t)
8508 if (t == NULL) {
8509 show_oops(NULL, "home_cb invalid parameters");
8510 return;
8513 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8515 load_uri(t, home);
8518 void
8519 stop_cb(GtkWidget *w, struct tab *t)
8521 WebKitWebFrame *frame;
8523 if (t == NULL) {
8524 show_oops(NULL, "stop_cb invalid parameters");
8525 return;
8528 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8530 frame = webkit_web_view_get_main_frame(t->wv);
8531 if (frame == NULL) {
8532 show_oops(t, "stop_cb: no frame");
8533 return;
8536 webkit_web_frame_stop_loading(frame);
8537 abort_favicon_download(t);
8540 void
8541 setup_webkit(struct tab *t)
8543 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8544 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8545 FALSE, (char *)NULL);
8546 else
8547 warnx("webkit does not have \"enable-dns-prefetching\" property");
8548 g_object_set(G_OBJECT(t->settings),
8549 "user-agent", t->user_agent, (char *)NULL);
8550 g_object_set(G_OBJECT(t->settings),
8551 "enable-scripts", enable_scripts, (char *)NULL);
8552 g_object_set(G_OBJECT(t->settings),
8553 "enable-plugins", enable_plugins, (char *)NULL);
8554 g_object_set(G_OBJECT(t->settings),
8555 "javascript-can-open-windows-automatically", enable_scripts,
8556 (char *)NULL);
8557 g_object_set(G_OBJECT(t->settings),
8558 "enable-html5-database", FALSE, (char *)NULL);
8559 g_object_set(G_OBJECT(t->settings),
8560 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8561 g_object_set(G_OBJECT(t->settings),
8562 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8563 g_object_set(G_OBJECT(t->settings),
8564 "spell_checking_languages", spell_check_languages, (char *)NULL);
8565 g_object_set(G_OBJECT(t->wv),
8566 "full-content-zoom", TRUE, (char *)NULL);
8568 webkit_web_view_set_settings(t->wv, t->settings);
8571 gboolean
8572 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8574 struct tab *ti, *t = NULL;
8575 gdouble view_size, value, max;
8576 gchar *position;
8578 TAILQ_FOREACH(ti, &tabs, entry)
8579 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8580 t = ti;
8581 break;
8584 if (t == NULL)
8585 return FALSE;
8587 if (adjustment == NULL)
8588 adjustment = gtk_scrolled_window_get_vadjustment(
8589 GTK_SCROLLED_WINDOW(t->browser_win));
8591 view_size = gtk_adjustment_get_page_size(adjustment);
8592 value = gtk_adjustment_get_value(adjustment);
8593 max = gtk_adjustment_get_upper(adjustment) - view_size;
8595 if (max == 0)
8596 position = g_strdup("All");
8597 else if (value == max)
8598 position = g_strdup("Bot");
8599 else if (value == 0)
8600 position = g_strdup("Top");
8601 else
8602 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8604 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8605 g_free(position);
8607 return (TRUE);
8610 GtkWidget *
8611 create_browser(struct tab *t)
8613 GtkWidget *w;
8614 gchar *strval;
8615 GtkAdjustment *adjustment;
8617 if (t == NULL) {
8618 show_oops(NULL, "create_browser invalid parameters");
8619 return (NULL);
8622 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8623 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8624 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8625 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8627 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8628 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8629 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8631 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8632 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8634 /* set defaults */
8635 t->settings = webkit_web_settings_new();
8637 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
8639 if (user_agent == NULL) {
8640 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8641 (char *)NULL);
8642 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8643 g_free(strval);
8644 } else
8645 t->user_agent = g_strdup(user_agent);
8647 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8649 adjustment =
8650 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8651 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8652 G_CALLBACK(update_statusbar_position), NULL);
8654 setup_webkit(t);
8656 return (w);
8659 GtkWidget *
8660 create_window(void)
8662 GtkWidget *w;
8664 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8665 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8666 gtk_widget_set_name(w, "xxxterm");
8667 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
8668 g_signal_connect(G_OBJECT(w), "delete_event",
8669 G_CALLBACK (gtk_main_quit), NULL);
8671 return (w);
8674 GtkWidget *
8675 create_kiosk_toolbar(struct tab *t)
8677 GtkWidget *toolbar = NULL, *b;
8679 b = gtk_hbox_new(FALSE, 0);
8680 toolbar = b;
8681 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8683 /* backward button */
8684 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8685 gtk_widget_set_sensitive(t->backward, FALSE);
8686 g_signal_connect(G_OBJECT(t->backward), "clicked",
8687 G_CALLBACK(backward_cb), t);
8688 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8690 /* forward button */
8691 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8692 gtk_widget_set_sensitive(t->forward, FALSE);
8693 g_signal_connect(G_OBJECT(t->forward), "clicked",
8694 G_CALLBACK(forward_cb), t);
8695 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8697 /* home button */
8698 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8699 gtk_widget_set_sensitive(t->gohome, true);
8700 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8701 G_CALLBACK(home_cb), t);
8702 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8704 /* create widgets but don't use them */
8705 t->uri_entry = gtk_entry_new();
8706 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8707 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8708 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8710 return (toolbar);
8713 GtkWidget *
8714 create_toolbar(struct tab *t)
8716 GtkWidget *toolbar = NULL, *b, *eb1;
8718 b = gtk_hbox_new(FALSE, 0);
8719 toolbar = b;
8720 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8722 /* backward button */
8723 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8724 gtk_widget_set_sensitive(t->backward, FALSE);
8725 g_signal_connect(G_OBJECT(t->backward), "clicked",
8726 G_CALLBACK(backward_cb), t);
8727 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8729 /* forward button */
8730 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8731 gtk_widget_set_sensitive(t->forward, FALSE);
8732 g_signal_connect(G_OBJECT(t->forward), "clicked",
8733 G_CALLBACK(forward_cb), t);
8734 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8735 FALSE, 0);
8737 /* stop button */
8738 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8739 gtk_widget_set_sensitive(t->stop, FALSE);
8740 g_signal_connect(G_OBJECT(t->stop), "clicked",
8741 G_CALLBACK(stop_cb), t);
8742 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8743 FALSE, 0);
8745 /* JS button */
8746 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8747 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8748 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8749 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8750 G_CALLBACK(js_toggle_cb), t);
8751 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8753 t->uri_entry = gtk_entry_new();
8754 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8755 G_CALLBACK(activate_uri_entry_cb), t);
8756 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8757 G_CALLBACK(entry_key_cb), t);
8758 completion_add(t);
8759 eb1 = gtk_hbox_new(FALSE, 0);
8760 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8761 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8762 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8764 /* search entry */
8765 if (search_string) {
8766 GtkWidget *eb2;
8767 t->search_entry = gtk_entry_new();
8768 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8769 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8770 G_CALLBACK(activate_search_entry_cb), t);
8771 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8772 G_CALLBACK(entry_key_cb), t);
8773 gtk_widget_set_size_request(t->search_entry, -1, -1);
8774 eb2 = gtk_hbox_new(FALSE, 0);
8775 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8776 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8778 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8781 return (toolbar);
8784 GtkWidget *
8785 create_buffers(struct tab *t)
8787 GtkCellRenderer *renderer;
8788 GtkWidget *view;
8790 view = gtk_tree_view_new();
8792 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8794 renderer = gtk_cell_renderer_text_new();
8795 gtk_tree_view_insert_column_with_attributes
8796 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8798 renderer = gtk_cell_renderer_text_new();
8799 gtk_tree_view_insert_column_with_attributes
8800 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8801 (char *)NULL);
8803 gtk_tree_view_set_model
8804 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8806 return view;
8809 void
8810 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8811 GtkTreeViewColumn *col, struct tab *t)
8813 GtkTreeIter iter;
8814 guint id;
8816 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8818 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8819 path)) {
8820 gtk_tree_model_get
8821 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8822 set_current_tab(id - 1);
8825 hide_buffers(t);
8828 /* after tab reordering/creation/removal */
8829 void
8830 recalc_tabs(void)
8832 struct tab *t;
8833 int maxid = 0;
8835 TAILQ_FOREACH(t, &tabs, entry) {
8836 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8837 if (t->tab_id > maxid)
8838 maxid = t->tab_id;
8840 gtk_widget_show(t->tab_elems.sep);
8843 TAILQ_FOREACH(t, &tabs, entry) {
8844 if (t->tab_id == maxid) {
8845 gtk_widget_hide(t->tab_elems.sep);
8846 break;
8851 /* after active tab change */
8852 void
8853 recolor_compact_tabs(void)
8855 struct tab *t;
8856 int curid = 0;
8857 GdkColor color;
8859 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8860 TAILQ_FOREACH(t, &tabs, entry)
8861 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
8862 &color);
8864 curid = gtk_notebook_get_current_page(notebook);
8865 TAILQ_FOREACH(t, &tabs, entry)
8866 if (t->tab_id == curid) {
8867 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8868 gtk_widget_modify_fg(t->tab_elems.label,
8869 GTK_STATE_NORMAL, &color);
8870 break;
8874 void
8875 set_current_tab(int page_num)
8877 buffercmd_abort(get_current_tab());
8878 gtk_notebook_set_current_page(notebook, page_num);
8879 recolor_compact_tabs();
8883 undo_close_tab_save(struct tab *t)
8885 int m, n;
8886 const gchar *uri;
8887 struct undo *u1, *u2;
8888 GList *items;
8889 WebKitWebHistoryItem *item;
8891 if ((uri = get_uri(t)) == NULL)
8892 return (1);
8894 u1 = g_malloc0(sizeof(struct undo));
8895 u1->uri = g_strdup(uri);
8897 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8899 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8900 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8901 u1->back = n;
8903 /* forward history */
8904 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8906 while (items) {
8907 item = items->data;
8908 u1->history = g_list_prepend(u1->history,
8909 webkit_web_history_item_copy(item));
8910 items = g_list_next(items);
8913 /* current item */
8914 if (m) {
8915 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8916 u1->history = g_list_prepend(u1->history,
8917 webkit_web_history_item_copy(item));
8920 /* back history */
8921 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8923 while (items) {
8924 item = items->data;
8925 u1->history = g_list_prepend(u1->history,
8926 webkit_web_history_item_copy(item));
8927 items = g_list_next(items);
8930 TAILQ_INSERT_HEAD(&undos, u1, entry);
8932 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8933 u2 = TAILQ_LAST(&undos, undo_tailq);
8934 TAILQ_REMOVE(&undos, u2, entry);
8935 g_free(u2->uri);
8936 g_list_free(u2->history);
8937 g_free(u2);
8938 } else
8939 undo_count++;
8941 return (0);
8944 void
8945 delete_tab(struct tab *t)
8947 struct karg a;
8949 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8951 if (t == NULL)
8952 return;
8955 * no need to join thread here because it won't access t on completion
8958 buffercmd_abort(t);
8959 TAILQ_REMOVE(&tabs, t, entry);
8961 /* Halt all webkit activity. */
8962 abort_favicon_download(t);
8963 webkit_web_view_stop_loading(t->wv);
8965 /* Save the tab, so we can undo the close. */
8966 undo_close_tab_save(t);
8968 if (browser_mode == XT_BM_KIOSK) {
8969 gtk_widget_destroy(t->uri_entry);
8970 gtk_widget_destroy(t->stop);
8971 gtk_widget_destroy(t->js_toggle);
8974 gtk_widget_destroy(t->tab_elems.eventbox);
8975 gtk_widget_destroy(t->vbox);
8977 /* just in case */
8978 if (t->search_id)
8979 g_source_remove(t->search_id);
8981 g_free(t->user_agent);
8982 g_free(t->stylesheet);
8983 g_free(t->tmp_uri);
8984 g_free(t);
8986 if (TAILQ_EMPTY(&tabs)) {
8987 if (browser_mode == XT_BM_KIOSK)
8988 create_new_tab(home, NULL, 1, -1);
8989 else
8990 create_new_tab(NULL, NULL, 1, -1);
8993 /* recreate session */
8994 if (session_autosave) {
8995 a.s = NULL;
8996 save_tabs(t, &a);
8999 recalc_tabs();
9000 recolor_compact_tabs();
9003 void
9004 update_statusbar_zoom(struct tab *t)
9006 gfloat zoom;
9007 char s[16] = { '\0' };
9009 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9010 if ((zoom <= 0.99 || zoom >= 1.01))
9011 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
9012 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
9015 void
9016 setzoom_webkit(struct tab *t, int adjust)
9018 #define XT_ZOOMPERCENT 0.04
9020 gfloat zoom;
9022 if (t == NULL) {
9023 show_oops(NULL, "setzoom_webkit invalid parameters");
9024 return;
9027 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9028 if (adjust == XT_ZOOM_IN)
9029 zoom += XT_ZOOMPERCENT;
9030 else if (adjust == XT_ZOOM_OUT)
9031 zoom -= XT_ZOOMPERCENT;
9032 else if (adjust > 0)
9033 zoom = default_zoom_level + adjust / 100.0 - 1.0;
9034 else {
9035 show_oops(t, "setzoom_webkit invalid zoom value");
9036 return;
9039 if (zoom < XT_ZOOMPERCENT)
9040 zoom = XT_ZOOMPERCENT;
9041 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
9042 update_statusbar_zoom(t);
9045 gboolean
9046 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
9048 struct tab *t = (struct tab *) data;
9050 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
9052 switch (event->button) {
9053 case 1:
9054 set_current_tab(t->tab_id);
9055 break;
9056 case 2:
9057 delete_tab(t);
9058 break;
9061 return TRUE;
9064 void
9065 append_tab(struct tab *t)
9067 if (t == NULL)
9068 return;
9070 TAILQ_INSERT_TAIL(&tabs, t, entry);
9071 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9074 GtkWidget *
9075 create_sbe(int width)
9077 GtkWidget *sbe;
9079 sbe = gtk_entry_new();
9080 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9081 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9082 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9083 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9084 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9085 gtk_widget_set_size_request(sbe, width, -1);
9087 return sbe;
9090 struct tab *
9091 create_new_tab(char *title, struct undo *u, int focus, int position)
9093 struct tab *t;
9094 int load = 1, id;
9095 GtkWidget *b, *bb;
9096 WebKitWebHistoryItem *item;
9097 GList *items;
9098 GdkColor color;
9099 char *p;
9100 int sbe_p = 0, sbe_b = 0,
9101 sbe_z = 0;
9103 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9105 if (tabless && !TAILQ_EMPTY(&tabs)) {
9106 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9107 return (NULL);
9110 t = g_malloc0(sizeof *t);
9112 if (title == NULL) {
9113 title = "(untitled)";
9114 load = 0;
9117 t->vbox = gtk_vbox_new(FALSE, 0);
9119 /* label + button for tab */
9120 b = gtk_hbox_new(FALSE, 0);
9121 t->tab_content = b;
9123 #if GTK_CHECK_VERSION(2, 20, 0)
9124 t->spinner = gtk_spinner_new();
9125 #endif
9126 t->label = gtk_label_new(title);
9127 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9128 gtk_widget_set_size_request(t->label, 100, 0);
9129 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9130 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9131 gtk_widget_set_size_request(b, 130, 0);
9133 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9134 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9135 #if GTK_CHECK_VERSION(2, 20, 0)
9136 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9137 #endif
9139 /* toolbar */
9140 if (browser_mode == XT_BM_KIOSK) {
9141 t->toolbar = create_kiosk_toolbar(t);
9142 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9144 } else {
9145 t->toolbar = create_toolbar(t);
9146 if (fancy_bar)
9147 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9148 FALSE, 0);
9151 /* marks */
9152 marks_clear(t);
9154 /* browser */
9155 t->browser_win = create_browser(t);
9156 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9158 /* oops message for user feedback */
9159 t->oops = gtk_entry_new();
9160 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9161 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9162 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9163 gdk_color_parse(XT_COLOR_RED, &color);
9164 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9165 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9166 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9168 /* command entry */
9169 t->cmd = gtk_entry_new();
9170 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9171 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9172 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9173 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9175 /* status bar */
9176 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9178 t->sbe.statusbar = gtk_entry_new();
9179 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9180 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9181 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9182 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9184 /* create these widgets only if specified in statusbar_elems */
9186 t->sbe.position = create_sbe(40);
9187 t->sbe.zoom = create_sbe(40);
9188 t->sbe.buffercmd = create_sbe(60);
9190 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9192 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9193 TRUE, FALSE);
9195 /* gtk widgets cannot be added to a box twice. sbe_* variables
9196 make sure of this */
9197 for (p = statusbar_elems; *p != '\0'; p++) {
9198 switch (*p) {
9199 case '|':
9201 GtkWidget *sep = gtk_vseparator_new();
9203 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9204 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9205 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9206 FALSE, FALSE, FALSE);
9207 break;
9209 case 'P':
9210 if (sbe_p) {
9211 warnx("flag \"%c\" specified more than "
9212 "once in statusbar_elems\n", *p);
9213 break;
9215 sbe_p = 1;
9216 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9217 t->sbe.position, FALSE, FALSE, FALSE);
9218 break;
9219 case 'B':
9220 if (sbe_b) {
9221 warnx("flag \"%c\" specified more than "
9222 "once in statusbar_elems\n", *p);
9223 break;
9225 sbe_b = 1;
9226 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9227 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9228 break;
9229 case 'Z':
9230 if (sbe_z) {
9231 warnx("flag \"%c\" specified more than "
9232 "once in statusbar_elems\n", *p);
9233 break;
9235 sbe_z = 1;
9236 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9237 t->sbe.zoom, FALSE, FALSE, FALSE);
9238 break;
9239 default:
9240 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9241 break;
9245 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9247 /* buffer list */
9248 t->buffers = create_buffers(t);
9249 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9251 /* xtp meaning is normal by default */
9252 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9254 /* set empty favicon */
9255 xt_icon_from_name(t, "text-html");
9257 /* and show it all */
9258 gtk_widget_show_all(b);
9259 gtk_widget_show_all(t->vbox);
9261 /* compact tab bar */
9262 t->tab_elems.label = gtk_label_new(title);
9263 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9264 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9265 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9266 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9268 t->tab_elems.eventbox = gtk_event_box_new();
9269 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9270 t->tab_elems.sep = gtk_vseparator_new();
9272 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9273 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9274 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9275 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9276 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9277 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9279 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9280 TRUE, 0);
9281 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9282 FALSE, 0);
9283 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9284 t->tab_elems.box);
9286 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9287 TRUE, 0);
9288 gtk_widget_show_all(t->tab_elems.eventbox);
9290 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9291 append_tab(t);
9292 else {
9293 id = position >= 0 ? position :
9294 gtk_notebook_get_current_page(notebook) + 1;
9295 if (id > gtk_notebook_get_n_pages(notebook))
9296 append_tab(t);
9297 else {
9298 TAILQ_INSERT_TAIL(&tabs, t, entry);
9299 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9300 gtk_box_reorder_child(GTK_BOX(tab_bar),
9301 t->tab_elems.eventbox, id);
9302 recalc_tabs();
9306 #if GTK_CHECK_VERSION(2, 20, 0)
9307 /* turn spinner off if we are a new tab without uri */
9308 if (!load) {
9309 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9310 gtk_widget_hide(t->spinner);
9312 #endif
9313 /* make notebook tabs reorderable */
9314 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9316 /* compact tabs clickable */
9317 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9318 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9320 g_object_connect(G_OBJECT(t->cmd),
9321 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9322 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9323 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9324 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9325 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9326 (char *)NULL);
9328 /* reuse wv_button_cb to hide oops */
9329 g_object_connect(G_OBJECT(t->oops),
9330 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9331 (char *)NULL);
9333 g_signal_connect(t->buffers,
9334 "row-activated", G_CALLBACK(row_activated_cb), t);
9335 g_object_connect(G_OBJECT(t->buffers),
9336 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9338 g_object_connect(G_OBJECT(t->wv),
9339 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9340 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9341 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9342 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9343 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9344 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9345 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9346 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9347 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9348 "signal::event", G_CALLBACK(webview_event_cb), t,
9349 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9350 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9351 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9352 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9353 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9354 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9355 (char *)NULL);
9356 g_signal_connect(t->wv,
9357 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9358 g_signal_connect(t->wv,
9359 "notify::title", G_CALLBACK(notify_title_cb), t);
9361 /* hijack the unused keys as if we were the browser */
9362 g_object_connect(G_OBJECT(t->toolbar),
9363 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9364 (char *)NULL);
9366 g_signal_connect(G_OBJECT(bb), "button_press_event",
9367 G_CALLBACK(tab_close_cb), t);
9369 /* setup history */
9370 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9371 /* restore the tab's history */
9372 if (u && u->history) {
9373 items = u->history;
9374 while (items) {
9375 item = items->data;
9376 webkit_web_back_forward_list_add_item(t->bfl, item);
9377 items = g_list_next(items);
9380 item = g_list_nth_data(u->history, u->back);
9381 if (item)
9382 webkit_web_view_go_to_back_forward_item(t->wv, item);
9384 g_list_free(items);
9385 g_list_free(u->history);
9386 } else
9387 webkit_web_back_forward_list_clear(t->bfl);
9389 /* hide stuff */
9390 hide_cmd(t);
9391 hide_oops(t);
9392 hide_buffers(t);
9393 url_set_visibility();
9394 statusbar_set_visibility();
9396 if (focus) {
9397 set_current_tab(t->tab_id);
9398 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9399 t->tab_id);
9401 if (load) {
9402 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9403 load_uri(t, title);
9404 } else {
9405 if (show_url == 1)
9406 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9407 else
9408 focus_webview(t);
9410 } else if (load)
9411 load_uri(t, title);
9413 recolor_compact_tabs();
9414 setzoom_webkit(t, XT_ZOOM_NORMAL);
9415 return (t);
9418 void
9419 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9420 gpointer *udata)
9422 struct tab *t;
9423 const gchar *uri;
9425 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9427 if (gtk_notebook_get_current_page(notebook) == -1)
9428 recalc_tabs();
9430 TAILQ_FOREACH(t, &tabs, entry) {
9431 if (t->tab_id == pn) {
9432 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9433 "%d\n", pn);
9435 uri = get_title(t, TRUE);
9436 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9438 hide_cmd(t);
9439 hide_oops(t);
9441 if (t->focus_wv) {
9442 /* can't use focus_webview here */
9443 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9449 void
9450 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9451 gpointer *udata)
9453 struct tab *t = NULL, *tt;
9455 recalc_tabs();
9457 TAILQ_FOREACH(tt, &tabs, entry)
9458 if (tt->tab_id == pn) {
9459 t = tt;
9460 break;
9463 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9465 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9466 t->tab_id);
9469 void
9470 menuitem_response(struct tab *t)
9472 gtk_notebook_set_current_page(notebook, t->tab_id);
9475 gboolean
9476 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9478 GtkWidget *menu, *menu_items;
9479 GdkEventButton *bevent;
9480 const gchar *uri;
9481 struct tab *ti;
9483 if (event->type == GDK_BUTTON_PRESS) {
9484 bevent = (GdkEventButton *) event;
9485 menu = gtk_menu_new();
9487 TAILQ_FOREACH(ti, &tabs, entry) {
9488 if ((uri = get_uri(ti)) == NULL)
9489 /* XXX make sure there is something to print */
9490 /* XXX add gui pages in here to look purdy */
9491 uri = "(untitled)";
9492 menu_items = gtk_menu_item_new_with_label(uri);
9493 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9494 gtk_widget_show(menu_items);
9496 g_signal_connect_swapped((menu_items),
9497 "activate", G_CALLBACK(menuitem_response),
9498 (gpointer)ti);
9501 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9502 bevent->button, bevent->time);
9504 /* unref object so it'll free itself when popped down */
9505 #if !GTK_CHECK_VERSION(3, 0, 0)
9506 /* XXX does not need unref with gtk+3? */
9507 g_object_ref_sink(menu);
9508 g_object_unref(menu);
9509 #endif
9511 return (TRUE /* eat event */);
9514 return (FALSE /* propagate */);
9518 icon_size_map(int icon_size)
9520 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9521 icon_size > GTK_ICON_SIZE_DIALOG)
9522 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9524 return (icon_size);
9527 GtkWidget *
9528 create_button(char *name, char *stockid, int size)
9530 GtkWidget *button, *image;
9531 gchar *rcstring;
9532 int gtk_icon_size;
9534 rcstring = g_strdup_printf(
9535 "style \"%s-style\"\n"
9536 "{\n"
9537 " GtkWidget::focus-padding = 0\n"
9538 " GtkWidget::focus-line-width = 0\n"
9539 " xthickness = 0\n"
9540 " ythickness = 0\n"
9541 "}\n"
9542 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9543 gtk_rc_parse_string(rcstring);
9544 g_free(rcstring);
9545 button = gtk_button_new();
9546 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9547 gtk_icon_size = icon_size_map(size ? size : icon_size);
9549 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9550 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9551 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9552 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9553 gtk_widget_set_name(button, name);
9554 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9556 return (button);
9559 void
9560 button_set_stockid(GtkWidget *button, char *stockid)
9562 GtkWidget *image;
9564 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9565 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9566 gtk_button_set_image(GTK_BUTTON(button), image);
9569 void
9570 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9572 gchar *p = NULL;
9573 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9574 gint len;
9576 if (xterm_workaround == 0)
9577 return;
9580 * xterm doesn't play nice with clipboards because it clears the
9581 * primary when clicked. We rely on primary being set to properly
9582 * handle middle mouse button clicks (paste). So when someone clears
9583 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9584 * other application behavior (as in DON'T clear primary).
9587 p = gtk_clipboard_wait_for_text(primary);
9588 if (p == NULL) {
9589 if (gdk_property_get(gdk_get_default_root_window(),
9590 atom,
9591 gdk_atom_intern("STRING", FALSE),
9593 1024 * 1024 /* picked out of my butt */,
9594 FALSE,
9595 NULL,
9596 NULL,
9597 &len,
9598 (guchar **)&p)) {
9599 /* yes sir, we need to NUL the string */
9600 p[len] = '\0';
9601 gtk_clipboard_set_text(primary, p, -1);
9605 if (p)
9606 g_free(p);
9609 void
9610 create_canvas(void)
9612 GtkWidget *vbox;
9613 GList *l = NULL;
9614 GdkPixbuf *pb;
9615 char file[PATH_MAX];
9616 int i;
9618 vbox = gtk_vbox_new(FALSE, 0);
9619 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9620 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9621 #if !GTK_CHECK_VERSION(3, 0, 0)
9622 /* XXX seems to be needed with gtk+2 */
9623 gtk_notebook_set_tab_hborder(notebook, 0);
9624 gtk_notebook_set_tab_vborder(notebook, 0);
9625 #endif
9626 gtk_notebook_set_scrollable(notebook, TRUE);
9627 gtk_notebook_set_show_border(notebook, FALSE);
9628 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9630 abtn = gtk_button_new();
9631 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9632 gtk_widget_set_size_request(arrow, -1, -1);
9633 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9634 gtk_widget_set_size_request(abtn, -1, 20);
9636 #if GTK_CHECK_VERSION(2, 20, 0)
9637 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9638 #endif
9639 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9641 /* compact tab bar */
9642 tab_bar = gtk_hbox_new(TRUE, 0);
9644 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9645 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9646 gtk_widget_set_size_request(vbox, -1, -1);
9648 g_object_connect(G_OBJECT(notebook),
9649 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9650 (char *)NULL);
9651 g_object_connect(G_OBJECT(notebook),
9652 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9653 NULL, (char *)NULL);
9654 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9655 G_CALLBACK(arrow_cb), NULL);
9657 main_window = create_window();
9658 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9660 /* icons */
9661 for (i = 0; i < LENGTH(icons); i++) {
9662 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9663 pb = gdk_pixbuf_new_from_file(file, NULL);
9664 l = g_list_append(l, pb);
9666 gtk_window_set_default_icon_list(l);
9668 /* clipboard work around */
9669 if (xterm_workaround)
9670 g_signal_connect(
9671 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9672 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9674 gtk_widget_show_all(abtn);
9675 gtk_widget_show_all(main_window);
9676 notebook_tab_set_visibility();
9679 void
9680 set_hook(void **hook, char *name)
9682 if (hook == NULL)
9683 errx(1, "set_hook");
9685 if (*hook == NULL) {
9686 *hook = dlsym(RTLD_NEXT, name);
9687 if (*hook == NULL)
9688 errx(1, "can't hook %s", name);
9692 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9693 gboolean
9694 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9696 g_return_val_if_fail(cookie1, FALSE);
9697 g_return_val_if_fail(cookie2, FALSE);
9699 return (!strcmp (cookie1->name, cookie2->name) &&
9700 !strcmp (cookie1->value, cookie2->value) &&
9701 !strcmp (cookie1->path, cookie2->path) &&
9702 !strcmp (cookie1->domain, cookie2->domain));
9705 void
9706 transfer_cookies(void)
9708 GSList *cf;
9709 SoupCookie *sc, *pc;
9711 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9713 for (;cf; cf = cf->next) {
9714 pc = cf->data;
9715 sc = soup_cookie_copy(pc);
9716 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9719 soup_cookies_free(cf);
9722 void
9723 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9725 GSList *cf;
9726 SoupCookie *ci;
9728 print_cookie("soup_cookie_jar_delete_cookie", c);
9730 if (cookies_enabled == 0)
9731 return;
9733 if (jar == NULL || c == NULL)
9734 return;
9736 /* find and remove from persistent jar */
9737 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9739 for (;cf; cf = cf->next) {
9740 ci = cf->data;
9741 if (soup_cookie_equal(ci, c)) {
9742 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9743 break;
9747 soup_cookies_free(cf);
9749 /* delete from session jar */
9750 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9753 void
9754 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9756 struct domain *d = NULL;
9757 SoupCookie *c;
9758 FILE *r_cookie_f;
9760 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9761 jar, p_cookiejar, s_cookiejar);
9763 if (cookies_enabled == 0)
9764 return;
9766 /* see if we are up and running */
9767 if (p_cookiejar == NULL) {
9768 _soup_cookie_jar_add_cookie(jar, cookie);
9769 return;
9771 /* disallow p_cookiejar adds, shouldn't happen */
9772 if (jar == p_cookiejar)
9773 return;
9775 /* sanity */
9776 if (jar == NULL || cookie == NULL)
9777 return;
9779 if (enable_cookie_whitelist &&
9780 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9781 blocked_cookies++;
9782 DNPRINTF(XT_D_COOKIE,
9783 "soup_cookie_jar_add_cookie: reject %s\n",
9784 cookie->domain);
9785 if (save_rejected_cookies) {
9786 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9787 show_oops(NULL, "can't open reject cookie file");
9788 return;
9790 fseek(r_cookie_f, 0, SEEK_END);
9791 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9792 cookie->http_only ? "#HttpOnly_" : "",
9793 cookie->domain,
9794 *cookie->domain == '.' ? "TRUE" : "FALSE",
9795 cookie->path,
9796 cookie->secure ? "TRUE" : "FALSE",
9797 cookie->expires ?
9798 (gulong)soup_date_to_time_t(cookie->expires) :
9800 cookie->name,
9801 cookie->value);
9802 fflush(r_cookie_f);
9803 fclose(r_cookie_f);
9805 if (!allow_volatile_cookies)
9806 return;
9809 if (cookie->expires == NULL && session_timeout) {
9810 soup_cookie_set_expires(cookie,
9811 soup_date_new_from_now(session_timeout));
9812 print_cookie("modified add cookie", cookie);
9815 /* see if we are white listed for persistence */
9816 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9817 /* add to persistent jar */
9818 c = soup_cookie_copy(cookie);
9819 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9820 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9823 /* add to session jar */
9824 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9825 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9828 void
9829 setup_cookies(void)
9831 char file[PATH_MAX];
9833 set_hook((void *)&_soup_cookie_jar_add_cookie,
9834 "soup_cookie_jar_add_cookie");
9835 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9836 "soup_cookie_jar_delete_cookie");
9838 if (cookies_enabled == 0)
9839 return;
9842 * the following code is intricate due to overriding several libsoup
9843 * functions.
9844 * do not alter order of these operations.
9847 /* rejected cookies */
9848 if (save_rejected_cookies)
9849 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
9850 XT_REJECT_FILE);
9852 /* persistent cookies */
9853 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
9854 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
9856 /* session cookies */
9857 s_cookiejar = soup_cookie_jar_new();
9858 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
9859 cookie_policy, (void *)NULL);
9860 transfer_cookies();
9862 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
9865 void
9866 setup_proxy(char *uri)
9868 if (proxy_uri) {
9869 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
9870 soup_uri_free(proxy_uri);
9871 proxy_uri = NULL;
9873 if (http_proxy) {
9874 if (http_proxy != uri) {
9875 g_free(http_proxy);
9876 http_proxy = NULL;
9880 if (uri) {
9881 http_proxy = g_strdup(uri);
9882 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9883 proxy_uri = soup_uri_new(http_proxy);
9884 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
9885 g_object_set(session, "proxy-uri", proxy_uri,
9886 (char *)NULL);
9891 set_http_proxy(char *proxy)
9893 SoupURI *uri;
9895 if (proxy == NULL)
9896 return (1);
9898 /* see if we need to clear it instead */
9899 if (strlen(proxy) == 0) {
9900 setup_proxy(NULL);
9901 return (0);
9904 uri = soup_uri_new(proxy);
9905 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
9906 return (1);
9908 setup_proxy(proxy);
9910 soup_uri_free(uri);
9912 return (0);
9916 send_cmd_to_socket(char *cmd)
9918 int s, len, rv = 1;
9919 struct sockaddr_un sa;
9921 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9922 warnx("%s: socket", __func__);
9923 return (rv);
9926 sa.sun_family = AF_UNIX;
9927 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9928 work_dir, XT_SOCKET_FILE);
9929 len = SUN_LEN(&sa);
9931 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9932 warnx("%s: connect", __func__);
9933 goto done;
9936 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9937 warnx("%s: send", __func__);
9938 goto done;
9941 rv = 0;
9942 done:
9943 close(s);
9944 return (rv);
9947 gboolean
9948 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9950 int s, n;
9951 char str[XT_MAX_URL_LENGTH];
9952 socklen_t t = sizeof(struct sockaddr_un);
9953 struct sockaddr_un sa;
9954 struct passwd *p;
9955 uid_t uid;
9956 gid_t gid;
9957 struct tab *tt;
9958 gint fd = g_io_channel_unix_get_fd(source);
9960 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9961 warn("accept");
9962 return (FALSE);
9965 if (getpeereid(s, &uid, &gid) == -1) {
9966 warn("getpeereid");
9967 return (FALSE);
9969 if (uid != getuid() || gid != getgid()) {
9970 warnx("unauthorized user");
9971 return (FALSE);
9974 p = getpwuid(uid);
9975 if (p == NULL) {
9976 warnx("not a valid user");
9977 return (FALSE);
9980 n = recv(s, str, sizeof(str), 0);
9981 if (n <= 0)
9982 return (TRUE);
9984 tt = TAILQ_LAST(&tabs, tab_list);
9985 cmd_execute(tt, str);
9986 return (TRUE);
9990 is_running(void)
9992 int s, len, rv = 1;
9993 struct sockaddr_un sa;
9995 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9996 warn("is_running: socket");
9997 return (-1);
10000 sa.sun_family = AF_UNIX;
10001 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10002 work_dir, XT_SOCKET_FILE);
10003 len = SUN_LEN(&sa);
10005 /* connect to see if there is a listener */
10006 if (connect(s, (struct sockaddr *)&sa, len) == -1)
10007 rv = 0; /* not running */
10008 else
10009 rv = 1; /* already running */
10011 close(s);
10013 return (rv);
10017 build_socket(void)
10019 int s, len;
10020 struct sockaddr_un sa;
10022 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10023 warn("build_socket: socket");
10024 return (-1);
10027 sa.sun_family = AF_UNIX;
10028 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10029 work_dir, XT_SOCKET_FILE);
10030 len = SUN_LEN(&sa);
10032 /* connect to see if there is a listener */
10033 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10034 /* no listener so we will */
10035 unlink(sa.sun_path);
10037 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
10038 warn("build_socket: bind");
10039 goto done;
10042 if (listen(s, 1) == -1) {
10043 warn("build_socket: listen");
10044 goto done;
10047 return (s);
10050 done:
10051 close(s);
10052 return (-1);
10055 gboolean
10056 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10057 GtkTreeIter *iter, struct tab *t)
10059 gchar *value;
10061 gtk_tree_model_get(model, iter, 0, &value, -1);
10062 load_uri(t, value);
10063 g_free(value);
10065 return (FALSE);
10068 gboolean
10069 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10070 GtkTreeIter *iter, struct tab *t)
10072 gchar *value;
10074 gtk_tree_model_get(model, iter, 0, &value, -1);
10075 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10076 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10077 g_free(value);
10079 return (TRUE);
10082 void
10083 completion_add_uri(const gchar *uri)
10085 GtkTreeIter iter;
10087 /* add uri to list_store */
10088 gtk_list_store_append(completion_model, &iter);
10089 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10092 gboolean
10093 completion_match(GtkEntryCompletion *completion, const gchar *key,
10094 GtkTreeIter *iter, gpointer user_data)
10096 gchar *value;
10097 gboolean match = FALSE;
10099 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10100 -1);
10102 if (value == NULL)
10103 return FALSE;
10105 match = match_uri(value, key);
10107 g_free(value);
10108 return (match);
10111 void
10112 completion_add(struct tab *t)
10114 /* enable completion for tab */
10115 t->completion = gtk_entry_completion_new();
10116 gtk_entry_completion_set_text_column(t->completion, 0);
10117 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10118 gtk_entry_completion_set_model(t->completion,
10119 GTK_TREE_MODEL(completion_model));
10120 gtk_entry_completion_set_match_func(t->completion, completion_match,
10121 NULL, NULL);
10122 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10123 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10124 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10125 G_CALLBACK(completion_select_cb), t);
10126 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10127 G_CALLBACK(completion_hover_cb), t);
10130 void
10131 xxx_dir(char *dir)
10133 struct stat sb;
10135 if (stat(dir, &sb)) {
10136 if (mkdir(dir, S_IRWXU) == -1)
10137 err(1, "mkdir %s", dir);
10138 if (stat(dir, &sb))
10139 err(1, "stat %s", dir);
10141 if (S_ISDIR(sb.st_mode) == 0)
10142 errx(1, "%s not a dir", dir);
10143 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10144 warnx("fixing invalid permissions on %s", dir);
10145 if (chmod(dir, S_IRWXU) == -1)
10146 err(1, "chmod %s", dir);
10150 void
10151 usage(void)
10153 fprintf(stderr,
10154 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10155 exit(0);
10160 main(int argc, char *argv[])
10162 struct stat sb;
10163 int c, s, optn = 0, opte = 0, focus = 1;
10164 char conf[PATH_MAX] = { '\0' };
10165 char file[PATH_MAX];
10166 char *env_proxy = NULL;
10167 char *cmd = NULL;
10168 FILE *f = NULL;
10169 struct karg a;
10170 struct sigaction sact;
10171 GIOChannel *channel;
10172 struct rlimit rlp;
10174 start_argv = argv;
10176 /* prepare gtk */
10177 g_thread_init(NULL);
10178 gdk_threads_init();
10179 gdk_threads_enter();
10180 gtk_init(&argc, &argv);
10182 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10184 RB_INIT(&hl);
10185 RB_INIT(&js_wl);
10186 RB_INIT(&downloads);
10188 TAILQ_INIT(&sessions);
10189 TAILQ_INIT(&tabs);
10190 TAILQ_INIT(&mtl);
10191 TAILQ_INIT(&aliases);
10192 TAILQ_INIT(&undos);
10193 TAILQ_INIT(&kbl);
10194 TAILQ_INIT(&spl);
10195 TAILQ_INIT(&chl);
10196 TAILQ_INIT(&shl);
10198 /* fiddle with ulimits */
10199 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10200 warn("getrlimit");
10201 else {
10202 /* just use them all */
10203 rlp.rlim_cur = rlp.rlim_max;
10204 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10205 warn("setrlimit");
10206 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10207 warn("getrlimit");
10208 else if (rlp.rlim_cur <= 256)
10209 startpage_add("%s requires at least 256 file "
10210 "descriptors, currently it has up to %d available",
10211 __progname, rlp.rlim_cur);
10214 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10215 switch (c) {
10216 case 'S':
10217 show_url = 0;
10218 break;
10219 case 'T':
10220 show_tabs = 0;
10221 break;
10222 case 'V':
10223 errx(0 , "Version: %s", version);
10224 break;
10225 case 'f':
10226 strlcpy(conf, optarg, sizeof(conf));
10227 break;
10228 case 's':
10229 strlcpy(named_session, optarg, sizeof(named_session));
10230 break;
10231 case 't':
10232 tabless = 1;
10233 break;
10234 case 'n':
10235 optn = 1;
10236 break;
10237 case 'e':
10238 opte = 1;
10239 break;
10240 default:
10241 usage();
10242 /* NOTREACHED */
10245 argc -= optind;
10246 argv += optind;
10248 init_keybindings();
10250 gnutls_global_init();
10252 /* generate session keys for xtp pages */
10253 generate_xtp_session_key(&dl_session_key);
10254 generate_xtp_session_key(&hl_session_key);
10255 generate_xtp_session_key(&cl_session_key);
10256 generate_xtp_session_key(&fl_session_key);
10258 /* signals */
10259 bzero(&sact, sizeof(sact));
10260 sigemptyset(&sact.sa_mask);
10261 sact.sa_handler = sigchild;
10262 sact.sa_flags = SA_NOCLDSTOP;
10263 sigaction(SIGCHLD, &sact, NULL);
10265 /* set download dir */
10266 pwd = getpwuid(getuid());
10267 if (pwd == NULL)
10268 errx(1, "invalid user %d", getuid());
10269 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10271 /* compile buffer command regexes */
10272 buffercmd_init();
10274 /* set default string settings */
10275 home = g_strdup("https://www.cyphertite.com");
10276 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10277 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10278 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10279 cmd_font_name = g_strdup("monospace normal 9");
10280 oops_font_name = g_strdup("monospace normal 9");
10281 statusbar_font_name = g_strdup("monospace normal 9");
10282 tabbar_font_name = g_strdup("monospace normal 9");
10283 statusbar_elems = g_strdup("BP");
10284 encoding = g_strdup("ISO-8859-1");
10286 /* read config file */
10287 if (strlen(conf) == 0)
10288 snprintf(conf, sizeof conf, "%s/.%s",
10289 pwd->pw_dir, XT_CONF_FILE);
10290 config_parse(conf, 0);
10292 /* init fonts */
10293 cmd_font = pango_font_description_from_string(cmd_font_name);
10294 oops_font = pango_font_description_from_string(oops_font_name);
10295 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10296 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10298 /* working directory */
10299 if (strlen(work_dir) == 0)
10300 snprintf(work_dir, sizeof work_dir, "%s/%s",
10301 pwd->pw_dir, XT_DIR);
10302 xxx_dir(work_dir);
10304 /* icon cache dir */
10305 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10306 xxx_dir(cache_dir);
10308 /* certs dir */
10309 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10310 xxx_dir(certs_dir);
10312 /* sessions dir */
10313 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10314 work_dir, XT_SESSIONS_DIR);
10315 xxx_dir(sessions_dir);
10317 /* runtime settings that can override config file */
10318 if (runtime_settings[0] != '\0')
10319 config_parse(runtime_settings, 1);
10321 /* download dir */
10322 if (!strcmp(download_dir, pwd->pw_dir))
10323 strlcat(download_dir, "/downloads", sizeof download_dir);
10324 xxx_dir(download_dir);
10326 /* favorites file */
10327 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10328 if (stat(file, &sb)) {
10329 warnx("favorites file doesn't exist, creating it");
10330 if ((f = fopen(file, "w")) == NULL)
10331 err(1, "favorites");
10332 fclose(f);
10335 /* quickmarks file */
10336 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10337 if (stat(file, &sb)) {
10338 warnx("quickmarks file doesn't exist, creating it");
10339 if ((f = fopen(file, "w")) == NULL)
10340 err(1, "quickmarks");
10341 fclose(f);
10344 /* search history */
10345 if (history_autosave) {
10346 snprintf(search_file, sizeof search_file, "%s/%s",
10347 work_dir, XT_SEARCH_FILE);
10348 if (stat(search_file, &sb)) {
10349 warnx("search history file doesn't exist, creating it");
10350 if ((f = fopen(search_file, "w")) == NULL)
10351 err(1, "search_history");
10352 fclose(f);
10354 history_read(&shl, search_file, &search_history_count);
10357 /* command history */
10358 if (history_autosave) {
10359 snprintf(command_file, sizeof command_file, "%s/%s",
10360 work_dir, XT_COMMAND_FILE);
10361 if (stat(command_file, &sb)) {
10362 warnx("command history file doesn't exist, creating it");
10363 if ((f = fopen(command_file, "w")) == NULL)
10364 err(1, "command_history");
10365 fclose(f);
10367 history_read(&chl, command_file, &cmd_history_count);
10370 /* cookies */
10371 session = webkit_get_default_session();
10372 setup_cookies();
10374 /* certs */
10375 if (ssl_ca_file) {
10376 if (stat(ssl_ca_file, &sb)) {
10377 warnx("no CA file: %s", ssl_ca_file);
10378 g_free(ssl_ca_file);
10379 ssl_ca_file = NULL;
10380 } else
10381 g_object_set(session,
10382 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10383 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10384 (void *)NULL);
10387 /* guess_search regex */
10388 if (url_regex == NULL)
10389 url_regex = g_strdup(XT_URL_REGEX);
10390 if (url_regex)
10391 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10392 startpage_add("invalid url regex %s", url_regex);
10394 /* proxy */
10395 env_proxy = getenv("http_proxy");
10396 if (env_proxy)
10397 setup_proxy(env_proxy);
10398 else
10399 setup_proxy(http_proxy);
10401 if (opte) {
10402 send_cmd_to_socket(argv[0]);
10403 exit(0);
10406 /* set some connection parameters */
10407 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10408 g_object_set(session, "max-conns-per-host", max_host_connections,
10409 (char *)NULL);
10411 /* see if there is already an xxxterm running */
10412 if (single_instance && is_running()) {
10413 optn = 1;
10414 warnx("already running");
10417 if (optn) {
10418 while (argc) {
10419 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10420 send_cmd_to_socket(cmd);
10421 if (cmd)
10422 g_free(cmd);
10424 argc--;
10425 argv++;
10427 exit(0);
10430 /* uri completion */
10431 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10433 /* buffers */
10434 buffers_store = gtk_list_store_new
10435 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10437 qmarks_load();
10439 /* go graphical */
10440 create_canvas();
10441 notebook_tab_set_visibility();
10443 if (save_global_history)
10444 restore_global_history();
10446 /* restore session list */
10447 restore_sessions_list();
10449 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10450 restore_saved_tabs();
10451 else {
10452 a.s = named_session;
10453 a.i = XT_SES_DONOTHING;
10454 open_tabs(NULL, &a);
10457 /* see if we have an exception */
10458 if (!TAILQ_EMPTY(&spl)) {
10459 create_new_tab("about:startpage", NULL, focus, -1);
10460 focus = 0;
10463 while (argc) {
10464 create_new_tab(argv[0], NULL, focus, -1);
10465 focus = 0;
10467 argc--;
10468 argv++;
10471 if (TAILQ_EMPTY(&tabs))
10472 create_new_tab(home, NULL, 1, -1);
10474 if (enable_socket)
10475 if ((s = build_socket()) != -1) {
10476 channel = g_io_channel_unix_new(s);
10477 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10480 gtk_main();
10482 gnutls_global_deinit();
10484 if (url_regex)
10485 regfree(&url_re);
10487 gdk_threads_leave();
10489 return (0);