remove XXX
[xxxterm.git] / xxxterm.c
blob7ef76411793eccd2419ed25abe958cf45dce090b
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 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
32 #include <ctype.h>
33 #include <dlfcn.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <libgen.h>
37 #include <pthread.h>
38 #include <pwd.h>
39 #include <regex.h>
40 #include <signal.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #if defined(__linux__)
49 #include "linux/util.h"
50 #include "linux/tree.h"
51 #elif defined(__FreeBSD__)
52 #include <libutil.h>
53 #include "freebsd/util.h"
54 #include <sys/tree.h>
55 #else /* OpenBSD */
56 #include <util.h>
57 #include <sys/tree.h>
58 #endif
59 #include <sys/queue.h>
60 #include <sys/resource.h>
61 #include <sys/socket.h>
62 #include <sys/stat.h>
63 #include <sys/time.h>
64 #include <sys/un.h>
66 #include <gtk/gtk.h>
67 #include <gdk/gdkkeysyms.h>
69 #if GTK_CHECK_VERSION(3,0,0)
70 /* we still use GDK_* instead of GDK_KEY_* */
71 #include <gdk/gdkkeysyms-compat.h>
72 #endif
74 #include <webkit/webkit.h>
75 #include <libsoup/soup.h>
76 #include <gnutls/gnutls.h>
77 #include <JavaScriptCore/JavaScript.h>
78 #include <gnutls/x509.h>
80 #include "javascript.h"
83 javascript.h borrowed from vimprobable2 under the following license:
85 Copyright (c) 2009 Leon Winter
86 Copyright (c) 2009 Hannes Schueller
87 Copyright (c) 2009 Matto Fransen
89 Permission is hereby granted, free of charge, to any person obtaining a copy
90 of this software and associated documentation files (the "Software"), to deal
91 in the Software without restriction, including without limitation the rights
92 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
93 copies of the Software, and to permit persons to whom the Software is
94 furnished to do so, subject to the following conditions:
96 The above copyright notice and this permission notice shall be included in
97 all copies or substantial portions of the Software.
99 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
100 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
101 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
102 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
103 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
104 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
105 THE SOFTWARE.
108 static char *version = "$xxxterm$";
110 /* hooked functions */
111 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
112 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
113 SoupCookie *);
115 /*#define XT_DEBUG*/
116 #ifdef XT_DEBUG
117 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
118 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
119 #define XT_D_MOVE 0x0001
120 #define XT_D_KEY 0x0002
121 #define XT_D_TAB 0x0004
122 #define XT_D_URL 0x0008
123 #define XT_D_CMD 0x0010
124 #define XT_D_NAV 0x0020
125 #define XT_D_DOWNLOAD 0x0040
126 #define XT_D_CONFIG 0x0080
127 #define XT_D_JS 0x0100
128 #define XT_D_FAVORITE 0x0200
129 #define XT_D_PRINTING 0x0400
130 #define XT_D_COOKIE 0x0800
131 #define XT_D_KEYBINDING 0x1000
132 #define XT_D_CLIP 0x2000
133 #define XT_D_BUFFERCMD 0x4000
134 u_int32_t swm_debug = 0
135 | XT_D_MOVE
136 | XT_D_KEY
137 | XT_D_TAB
138 | XT_D_URL
139 | XT_D_CMD
140 | XT_D_NAV
141 | XT_D_DOWNLOAD
142 | XT_D_CONFIG
143 | XT_D_JS
144 | XT_D_FAVORITE
145 | XT_D_PRINTING
146 | XT_D_COOKIE
147 | XT_D_KEYBINDING
148 | XT_D_CLIP
149 | XT_D_BUFFERCMD
151 #else
152 #define DPRINTF(x...)
153 #define DNPRINTF(n,x...)
154 #endif
156 #define LENGTH(x) (sizeof x / sizeof x[0])
157 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
158 ~(GDK_BUTTON1_MASK) & \
159 ~(GDK_BUTTON2_MASK) & \
160 ~(GDK_BUTTON3_MASK) & \
161 ~(GDK_BUTTON4_MASK) & \
162 ~(GDK_BUTTON5_MASK))
164 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
166 char *icons[] = {
167 "xxxtermicon16.png",
168 "xxxtermicon32.png",
169 "xxxtermicon48.png",
170 "xxxtermicon64.png",
171 "xxxtermicon128.png"
174 struct tab {
175 TAILQ_ENTRY(tab) entry;
176 GtkWidget *vbox;
177 GtkWidget *tab_content;
178 struct {
179 GtkWidget *label;
180 GtkWidget *eventbox;
181 GtkWidget *box;
182 GtkWidget *sep;
183 } tab_elems;
184 GtkWidget *label;
185 GtkWidget *spinner;
186 GtkWidget *uri_entry;
187 GtkWidget *search_entry;
188 GtkWidget *toolbar;
189 GtkWidget *browser_win;
190 GtkWidget *statusbar_box;
191 struct {
192 GtkWidget *statusbar;
193 GtkWidget *buffercmd;
194 GtkWidget *zoom;
195 GtkWidget *position;
196 } sbe;
197 GtkWidget *cmd;
198 GtkWidget *buffers;
199 GtkWidget *oops;
200 GtkWidget *backward;
201 GtkWidget *forward;
202 GtkWidget *stop;
203 GtkWidget *gohome;
204 GtkWidget *js_toggle;
205 GtkEntryCompletion *completion;
206 guint tab_id;
207 WebKitWebView *wv;
209 WebKitWebHistoryItem *item;
210 WebKitWebBackForwardList *bfl;
212 /* favicon */
213 WebKitNetworkRequest *icon_request;
214 WebKitDownload *icon_download;
215 gchar *icon_dest_uri;
217 /* adjustments for browser */
218 GtkScrollbar *sb_h;
219 GtkScrollbar *sb_v;
220 GtkAdjustment *adjust_h;
221 GtkAdjustment *adjust_v;
223 /* flags */
224 int focus_wv;
225 int ctrl_click;
226 gchar *status;
227 int xtp_meaning; /* identifies dls/favorites */
228 gchar *tmp_uri;
230 /* hints */
231 int hints_on;
232 int hint_mode;
233 #define XT_HINT_NONE (0)
234 #define XT_HINT_NUMERICAL (1)
235 #define XT_HINT_ALPHANUM (2)
236 char hint_buf[128];
237 char hint_num[128];
239 /* custom stylesheet */
240 int styled;
241 char *stylesheet;
243 /* search */
244 char *search_text;
245 int search_forward;
247 /* settings */
248 WebKitWebSettings *settings;
249 gchar *user_agent;
251 /* marks */
252 double mark[XT_NOMARKS];
254 TAILQ_HEAD(tab_list, tab);
256 struct history {
257 RB_ENTRY(history) entry;
258 const gchar *uri;
259 const gchar *title;
261 RB_HEAD(history_list, history);
263 struct download {
264 RB_ENTRY(download) entry;
265 int id;
266 WebKitDownload *download;
267 struct tab *tab;
269 RB_HEAD(download_list, download);
271 struct domain {
272 RB_ENTRY(domain) entry;
273 gchar *d;
274 int handy; /* app use */
276 RB_HEAD(domain_list, domain);
278 struct undo {
279 TAILQ_ENTRY(undo) entry;
280 gchar *uri;
281 GList *history;
282 int back; /* Keeps track of how many back
283 * history items there are. */
285 TAILQ_HEAD(undo_tailq, undo);
287 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
288 int next_download_id = 1;
290 struct karg {
291 int i;
292 char *s;
293 int p;
296 /* defines */
297 #define XT_NAME ("XXXTerm")
298 #define XT_DIR (".xxxterm")
299 #define XT_CACHE_DIR ("cache")
300 #define XT_CERT_DIR ("certs/")
301 #define XT_SESSIONS_DIR ("sessions/")
302 #define XT_CONF_FILE ("xxxterm.conf")
303 #define XT_FAVS_FILE ("favorites")
304 #define XT_QMARKS_FILE ("quickmarks")
305 #define XT_SAVED_TABS_FILE ("main_session")
306 #define XT_RESTART_TABS_FILE ("restart_tabs")
307 #define XT_SOCKET_FILE ("socket")
308 #define XT_HISTORY_FILE ("history")
309 #define XT_REJECT_FILE ("rejected.txt")
310 #define XT_COOKIE_FILE ("cookies.txt")
311 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
312 #define XT_CB_HANDLED (TRUE)
313 #define XT_CB_PASSTHROUGH (FALSE)
314 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
315 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
316 #define XT_DLMAN_REFRESH "10"
317 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
318 "td{overflow: hidden;" \
319 " padding: 2px 2px 2px 2px;" \
320 " border: 1px solid black;" \
321 " vertical-align:top;" \
322 " word-wrap: break-word}\n" \
323 "tr:hover{background: #ffff99}\n" \
324 "th{background-color: #cccccc;" \
325 " border: 1px solid black}\n" \
326 "table{width: 100%%;" \
327 " border: 1px black solid;" \
328 " border-collapse:collapse}\n" \
329 ".progress-outer{" \
330 "border: 1px solid black;" \
331 " height: 8px;" \
332 " width: 90%%}\n" \
333 ".progress-inner{float: left;" \
334 " height: 8px;" \
335 " background: green}\n" \
336 ".dlstatus{font-size: small;" \
337 " text-align: center}\n" \
338 "</style>\n"
339 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
340 #define XT_MAX_UNDO_CLOSE_TAB (32)
341 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
342 #define XT_PRINT_EXTRA_MARGIN 10
343 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
345 /* colors */
346 #define XT_COLOR_RED "#cc0000"
347 #define XT_COLOR_YELLOW "#ffff66"
348 #define XT_COLOR_BLUE "lightblue"
349 #define XT_COLOR_GREEN "#99ff66"
350 #define XT_COLOR_WHITE "white"
351 #define XT_COLOR_BLACK "black"
353 #define XT_COLOR_CT_BACKGROUND "#000000"
354 #define XT_COLOR_CT_INACTIVE "#dddddd"
355 #define XT_COLOR_CT_ACTIVE "#bbbb00"
356 #define XT_COLOR_CT_SEPARATOR "#555555"
358 #define XT_COLOR_SB_SEPARATOR "#555555"
360 #define XT_PROTO_DELIM "://"
363 * xxxterm "protocol" (xtp)
364 * We use this for managing stuff like downloads and favorites. They
365 * make magical HTML pages in memory which have xxxt:// links in order
366 * to communicate with xxxterm's internals. These links take the format:
367 * xxxt://class/session_key/action/arg
369 * Don't begin xtp class/actions as 0. atoi returns that on error.
371 * Typically we have not put addition of items in this framework, as
372 * adding items is either done via an ex-command or via a keybinding instead.
375 #define XT_XTP_STR "xxxt://"
377 /* XTP classes (xxxt://<class>) */
378 #define XT_XTP_INVALID 0 /* invalid */
379 #define XT_XTP_DL 1 /* downloads */
380 #define XT_XTP_HL 2 /* history */
381 #define XT_XTP_CL 3 /* cookies */
382 #define XT_XTP_FL 4 /* favorites */
384 /* XTP download actions */
385 #define XT_XTP_DL_LIST 1
386 #define XT_XTP_DL_CANCEL 2
387 #define XT_XTP_DL_REMOVE 3
389 /* XTP history actions */
390 #define XT_XTP_HL_LIST 1
391 #define XT_XTP_HL_REMOVE 2
393 /* XTP cookie actions */
394 #define XT_XTP_CL_LIST 1
395 #define XT_XTP_CL_REMOVE 2
397 /* XTP cookie actions */
398 #define XT_XTP_FL_LIST 1
399 #define XT_XTP_FL_REMOVE 2
401 /* actions */
402 #define XT_MOVE_INVALID (0)
403 #define XT_MOVE_DOWN (1)
404 #define XT_MOVE_UP (2)
405 #define XT_MOVE_BOTTOM (3)
406 #define XT_MOVE_TOP (4)
407 #define XT_MOVE_PAGEDOWN (5)
408 #define XT_MOVE_PAGEUP (6)
409 #define XT_MOVE_HALFDOWN (7)
410 #define XT_MOVE_HALFUP (8)
411 #define XT_MOVE_LEFT (9)
412 #define XT_MOVE_FARLEFT (10)
413 #define XT_MOVE_RIGHT (11)
414 #define XT_MOVE_FARRIGHT (12)
415 #define XT_MOVE_PERCENT (13)
417 #define XT_QMARK_SET (0)
418 #define XT_QMARK_OPEN (1)
419 #define XT_QMARK_TAB (2)
421 #define XT_MARK_SET (0)
422 #define XT_MARK_GOTO (1)
424 #define XT_TAB_LAST (-4)
425 #define XT_TAB_FIRST (-3)
426 #define XT_TAB_PREV (-2)
427 #define XT_TAB_NEXT (-1)
428 #define XT_TAB_INVALID (0)
429 #define XT_TAB_NEW (1)
430 #define XT_TAB_DELETE (2)
431 #define XT_TAB_DELQUIT (3)
432 #define XT_TAB_OPEN (4)
433 #define XT_TAB_UNDO_CLOSE (5)
434 #define XT_TAB_SHOW (6)
435 #define XT_TAB_HIDE (7)
436 #define XT_TAB_NEXTSTYLE (8)
438 #define XT_NAV_INVALID (0)
439 #define XT_NAV_BACK (1)
440 #define XT_NAV_FORWARD (2)
441 #define XT_NAV_RELOAD (3)
443 #define XT_FOCUS_INVALID (0)
444 #define XT_FOCUS_URI (1)
445 #define XT_FOCUS_SEARCH (2)
447 #define XT_SEARCH_INVALID (0)
448 #define XT_SEARCH_NEXT (1)
449 #define XT_SEARCH_PREV (2)
451 #define XT_PASTE_CURRENT_TAB (0)
452 #define XT_PASTE_NEW_TAB (1)
454 #define XT_ZOOM_IN (-1)
455 #define XT_ZOOM_OUT (-2)
456 #define XT_ZOOM_NORMAL (100)
458 #define XT_URL_SHOW (1)
459 #define XT_URL_HIDE (2)
461 #define XT_WL_TOGGLE (1<<0)
462 #define XT_WL_ENABLE (1<<1)
463 #define XT_WL_DISABLE (1<<2)
464 #define XT_WL_FQDN (1<<3) /* default */
465 #define XT_WL_TOPLEVEL (1<<4)
466 #define XT_WL_PERSISTENT (1<<5)
467 #define XT_WL_SESSION (1<<6)
468 #define XT_WL_RELOAD (1<<7)
470 #define XT_SHOW (1<<7)
471 #define XT_DELETE (1<<8)
472 #define XT_SAVE (1<<9)
473 #define XT_OPEN (1<<10)
475 #define XT_CMD_OPEN (0)
476 #define XT_CMD_OPEN_CURRENT (1)
477 #define XT_CMD_TABNEW (2)
478 #define XT_CMD_TABNEW_CURRENT (3)
480 #define XT_STATUS_NOTHING (0)
481 #define XT_STATUS_LINK (1)
482 #define XT_STATUS_URI (2)
483 #define XT_STATUS_LOADING (3)
485 #define XT_SES_DONOTHING (0)
486 #define XT_SES_CLOSETABS (1)
488 #define XT_BM_NORMAL (0)
489 #define XT_BM_WHITELIST (1)
490 #define XT_BM_KIOSK (2)
492 #define XT_PREFIX (1<<0)
493 #define XT_USERARG (1<<1)
494 #define XT_URLARG (1<<2)
495 #define XT_INTARG (1<<3)
497 #define XT_TABS_NORMAL 0
498 #define XT_TABS_COMPACT 1
500 /* mime types */
501 struct mime_type {
502 char *mt_type;
503 char *mt_action;
504 int mt_default;
505 int mt_download;
506 TAILQ_ENTRY(mime_type) entry;
508 TAILQ_HEAD(mime_type_list, mime_type);
510 /* uri aliases */
511 struct alias {
512 char *a_name;
513 char *a_uri;
514 TAILQ_ENTRY(alias) entry;
516 TAILQ_HEAD(alias_list, alias);
518 /* settings that require restart */
519 int tabless = 0; /* allow only 1 tab */
520 int enable_socket = 0;
521 int single_instance = 0; /* only allow one xxxterm to run */
522 int fancy_bar = 1; /* fancy toolbar */
523 int browser_mode = XT_BM_NORMAL;
524 int enable_localstorage = 0;
525 char *statusbar_elems = NULL;
527 /* runtime settings */
528 int show_tabs = 1; /* show tabs on notebook */
529 int tab_style = XT_TABS_NORMAL; /* tab bar style */
530 int show_url = 1; /* show url toolbar on notebook */
531 int show_statusbar = 0; /* vimperator style status bar */
532 int ctrl_click_focus = 0; /* ctrl click gets focus */
533 int cookies_enabled = 1; /* enable cookies */
534 int read_only_cookies = 0; /* enable to not write cookies */
535 int enable_scripts = 1;
536 int enable_plugins = 0;
537 gfloat default_zoom_level = 1.0;
538 char default_script[PATH_MAX];
539 int window_height = 768;
540 int window_width = 1024;
541 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
542 int refresh_interval = 10; /* download refresh interval */
543 int enable_cookie_whitelist = 0;
544 int enable_js_whitelist = 0;
545 int session_timeout = 3600; /* cookie session timeout */
546 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
547 char *ssl_ca_file = NULL;
548 char *resource_dir = NULL;
549 gboolean ssl_strict_certs = FALSE;
550 int append_next = 1; /* append tab after current tab */
551 char *home = NULL;
552 char *search_string = NULL;
553 char *http_proxy = NULL;
554 char download_dir[PATH_MAX];
555 char runtime_settings[PATH_MAX]; /* override of settings */
556 int allow_volatile_cookies = 0;
557 int save_global_history = 0; /* save global history to disk */
558 char *user_agent = NULL;
559 int save_rejected_cookies = 0;
560 int session_autosave = 0;
561 int guess_search = 0;
562 int dns_prefetch = FALSE;
563 gint max_connections = 25;
564 gint max_host_connections = 5;
565 gint enable_spell_checking = 0;
566 char *spell_check_languages = NULL;
568 char *cmd_font_name = NULL;
569 char *oops_font_name = NULL;
570 char *statusbar_font_name = NULL;
571 char *tabbar_font_name = NULL;
572 PangoFontDescription *cmd_font;
573 PangoFontDescription *oops_font;
574 PangoFontDescription *statusbar_font;
575 PangoFontDescription *tabbar_font;
576 char *qmarks[XT_NOMARKS];
578 int btn_down; /* M1 down in any wv */
580 struct settings;
581 struct key_binding;
582 int set_browser_mode(struct settings *, char *);
583 int set_cookie_policy(struct settings *, char *);
584 int set_download_dir(struct settings *, char *);
585 int set_default_script(struct settings *, char *);
586 int set_runtime_dir(struct settings *, char *);
587 int set_tab_style(struct settings *, char *);
588 int set_work_dir(struct settings *, char *);
589 int add_alias(struct settings *, char *);
590 int add_mime_type(struct settings *, char *);
591 int add_cookie_wl(struct settings *, char *);
592 int add_js_wl(struct settings *, char *);
593 int add_kb(struct settings *, char *);
594 void button_set_stockid(GtkWidget *, char *);
595 GtkWidget * create_button(char *, char *, int);
597 char *get_browser_mode(struct settings *);
598 char *get_cookie_policy(struct settings *);
599 char *get_download_dir(struct settings *);
600 char *get_default_script(struct settings *);
601 char *get_runtime_dir(struct settings *);
602 char *get_tab_style(struct settings *);
603 char *get_work_dir(struct settings *);
605 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
606 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
607 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
608 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
609 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
611 void recalc_tabs(void);
612 void recolor_compact_tabs(void);
613 void set_current_tab(int page_num);
614 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
615 void marks_clear(struct tab *t);
617 struct special {
618 int (*set)(struct settings *, char *);
619 char *(*get)(struct settings *);
620 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
623 struct special s_browser_mode = {
624 set_browser_mode,
625 get_browser_mode,
626 NULL
629 struct special s_cookie = {
630 set_cookie_policy,
631 get_cookie_policy,
632 NULL
635 struct special s_alias = {
636 add_alias,
637 NULL,
638 walk_alias
641 struct special s_mime = {
642 add_mime_type,
643 NULL,
644 walk_mime_type
647 struct special s_js = {
648 add_js_wl,
649 NULL,
650 walk_js_wl
653 struct special s_kb = {
654 add_kb,
655 NULL,
656 walk_kb
659 struct special s_cookie_wl = {
660 add_cookie_wl,
661 NULL,
662 walk_cookie_wl
665 struct special s_default_script = {
666 set_default_script,
667 get_default_script,
668 NULL
671 struct special s_download_dir = {
672 set_download_dir,
673 get_download_dir,
674 NULL
677 struct special s_work_dir = {
678 set_work_dir,
679 get_work_dir,
680 NULL
683 struct special s_tab_style = {
684 set_tab_style,
685 get_tab_style,
686 NULL
689 struct settings {
690 char *name;
691 int type;
692 #define XT_S_INVALID (0)
693 #define XT_S_INT (1)
694 #define XT_S_STR (2)
695 #define XT_S_FLOAT (3)
696 uint32_t flags;
697 #define XT_SF_RESTART (1<<0)
698 #define XT_SF_RUNTIME (1<<1)
699 int *ival;
700 char **sval;
701 struct special *s;
702 gfloat *fval;
703 } rs[] = {
704 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
705 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
706 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
707 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
708 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
709 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
710 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
711 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
712 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
713 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
714 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
715 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
716 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
717 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
718 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
719 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
720 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
721 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
722 { "home", XT_S_STR, 0, NULL, &home, NULL },
723 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
724 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
725 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
726 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
727 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
728 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
729 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
730 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
731 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
732 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
733 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
734 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
735 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
736 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
737 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
738 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
739 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
740 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
741 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
742 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
743 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
744 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
745 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
746 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
747 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
749 /* font settings */
750 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
751 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
752 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
753 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
755 /* runtime settings */
756 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
757 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
758 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
759 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
760 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
763 int about(struct tab *, struct karg *);
764 int blank(struct tab *, struct karg *);
765 int ca_cmd(struct tab *, struct karg *);
766 int cookie_show_wl(struct tab *, struct karg *);
767 int js_show_wl(struct tab *, struct karg *);
768 int help(struct tab *, struct karg *);
769 int set(struct tab *, struct karg *);
770 int stats(struct tab *, struct karg *);
771 int marco(struct tab *, struct karg *);
772 const char * marco_message(int *);
773 int xtp_page_cl(struct tab *, struct karg *);
774 int xtp_page_dl(struct tab *, struct karg *);
775 int xtp_page_fl(struct tab *, struct karg *);
776 int xtp_page_hl(struct tab *, struct karg *);
777 void xt_icon_from_file(struct tab *, char *);
778 const gchar *get_uri(struct tab *);
779 const gchar *get_title(struct tab *, bool);
781 #define XT_URI_ABOUT ("about:")
782 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
783 #define XT_URI_ABOUT_ABOUT ("about")
784 #define XT_URI_ABOUT_BLANK ("blank")
785 #define XT_URI_ABOUT_CERTS ("certs")
786 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
787 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
788 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
789 #define XT_URI_ABOUT_FAVORITES ("favorites")
790 #define XT_URI_ABOUT_HELP ("help")
791 #define XT_URI_ABOUT_HISTORY ("history")
792 #define XT_URI_ABOUT_JSWL ("jswl")
793 #define XT_URI_ABOUT_SET ("set")
794 #define XT_URI_ABOUT_STATS ("stats")
795 #define XT_URI_ABOUT_MARCO ("marco")
797 struct about_type {
798 char *name;
799 int (*func)(struct tab *, struct karg *);
800 } about_list[] = {
801 { XT_URI_ABOUT_ABOUT, about },
802 { XT_URI_ABOUT_BLANK, blank },
803 { XT_URI_ABOUT_CERTS, ca_cmd },
804 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
805 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
806 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
807 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
808 { XT_URI_ABOUT_HELP, help },
809 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
810 { XT_URI_ABOUT_JSWL, js_show_wl },
811 { XT_URI_ABOUT_SET, set },
812 { XT_URI_ABOUT_STATS, stats },
813 { XT_URI_ABOUT_MARCO, marco },
816 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
817 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
818 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
819 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
820 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
821 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
822 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
824 /* globals */
825 extern char *__progname;
826 char **start_argv;
827 struct passwd *pwd;
828 GtkWidget *main_window;
829 GtkNotebook *notebook;
830 GtkWidget *tab_bar;
831 GtkWidget *arrow, *abtn;
832 struct tab_list tabs;
833 struct history_list hl;
834 struct download_list downloads;
835 struct domain_list c_wl;
836 struct domain_list js_wl;
837 struct undo_tailq undos;
838 struct keybinding_list kbl;
839 int undo_count;
840 int updating_dl_tabs = 0;
841 int updating_hl_tabs = 0;
842 int updating_cl_tabs = 0;
843 int updating_fl_tabs = 0;
844 char *global_search;
845 uint64_t blocked_cookies = 0;
846 char named_session[PATH_MAX];
847 int icon_size_map(int);
849 GtkListStore *completion_model;
850 void completion_add(struct tab *);
851 void completion_add_uri(const gchar *);
852 GtkListStore *buffers_store;
853 void xxx_dir(char *);
855 /* marks and quickmarks array storage.
856 * first a-z, then A-Z, then 0-9 */
857 char
858 indextomark(int i)
860 if (i < 0)
861 return 0;
863 if (i >= 0 && i <= 'z' - 'a')
864 return 'a' + i;
866 i -= 'z' - 'a' + 1;
867 if (i >= 0 && i <= 'Z' - 'A')
868 return 'A' + i;
870 i -= 'Z' - 'A' + 1;
871 if (i >= 10)
872 return 0;
874 return i + '0';
878 marktoindex(char m)
880 int ret = 0;
882 if (m >= 'a' && m <= 'z')
883 return ret + m - 'a';
885 ret += 'z' - 'a' + 1;
886 if (m >= 'A' && m <= 'Z')
887 return ret + m - 'A';
889 ret += 'Z' - 'A' + 1;
890 if (m >= '0' && m <= '9')
891 return ret + m - '0';
893 return -1;
897 void
898 sigchild(int sig)
900 int saved_errno, status;
901 pid_t pid;
903 saved_errno = errno;
905 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
906 if (pid == -1) {
907 if (errno == EINTR)
908 continue;
909 if (errno != ECHILD) {
911 clog_warn("sigchild: waitpid:");
914 break;
917 if (WIFEXITED(status)) {
918 if (WEXITSTATUS(status) != 0) {
920 clog_warnx("sigchild: child exit status: %d",
921 WEXITSTATUS(status));
924 } else {
926 clog_warnx("sigchild: child is terminated abnormally");
931 errno = saved_errno;
935 is_g_object_setting(GObject *o, char *str)
937 guint n_props = 0, i;
938 GParamSpec **proplist;
940 if (! G_IS_OBJECT(o))
941 return (0);
943 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
944 &n_props);
946 for (i=0; i < n_props; i++) {
947 if (! strcmp(proplist[i]->name, str))
948 return (1);
950 return (0);
953 gchar *
954 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
956 gchar *r;
958 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
959 "<head>\n"
960 "<title>%s</title>\n"
961 "%s"
962 "%s"
963 "</head>\n"
964 "<body>\n"
965 "<h1>%s</h1>\n"
966 "%s\n</body>\n"
967 "</html>",
968 title,
969 addstyles ? XT_PAGE_STYLE : "",
970 head,
971 title,
972 body);
974 return r;
978 * Display a web page from a HTML string in memory, rather than from a URL
980 void
981 load_webkit_string(struct tab *t, const char *str, gchar *title)
983 char file[PATH_MAX];
984 int i;
986 /* we set this to indicate we want to manually do navaction */
987 if (t->bfl)
988 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
990 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
991 if (title) {
992 /* set t->xtp_meaning */
993 for (i = 0; i < LENGTH(about_list); i++)
994 if (!strcmp(title, about_list[i].name)) {
995 t->xtp_meaning = i;
996 break;
999 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
1000 #if GTK_CHECK_VERSION(2, 20, 0)
1001 gtk_spinner_stop(GTK_SPINNER(t->spinner));
1002 gtk_widget_hide(t->spinner);
1003 #endif
1004 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
1005 xt_icon_from_file(t, file);
1009 struct tab *
1010 get_current_tab(void)
1012 struct tab *t;
1014 TAILQ_FOREACH(t, &tabs, entry) {
1015 if (t->tab_id == gtk_notebook_get_current_page(notebook))
1016 return (t);
1019 warnx("%s: no current tab", __func__);
1021 return (NULL);
1024 void
1025 set_status(struct tab *t, gchar *s, int status)
1027 gchar *type = NULL;
1029 if (s == NULL)
1030 return;
1032 switch (status) {
1033 case XT_STATUS_LOADING:
1034 type = g_strdup_printf("Loading: %s", s);
1035 s = type;
1036 break;
1037 case XT_STATUS_LINK:
1038 type = g_strdup_printf("Link: %s", s);
1039 if (!t->status)
1040 t->status = g_strdup(gtk_entry_get_text(
1041 GTK_ENTRY(t->sbe.statusbar)));
1042 s = type;
1043 break;
1044 case XT_STATUS_URI:
1045 type = g_strdup_printf("%s", s);
1046 if (!t->status) {
1047 t->status = g_strdup(type);
1049 s = type;
1050 if (!t->status)
1051 t->status = g_strdup(s);
1052 break;
1053 case XT_STATUS_NOTHING:
1054 /* FALL THROUGH */
1055 default:
1056 break;
1058 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1059 if (type)
1060 g_free(type);
1063 void
1064 hide_cmd(struct tab *t)
1066 gtk_widget_hide(t->cmd);
1069 void
1070 show_cmd(struct tab *t)
1072 gtk_widget_hide(t->oops);
1073 gtk_widget_show(t->cmd);
1076 void
1077 hide_buffers(struct tab *t)
1079 gtk_widget_hide(t->buffers);
1080 gtk_list_store_clear(buffers_store);
1083 enum {
1084 COL_ID = 0,
1085 COL_TITLE,
1086 NUM_COLS
1090 sort_tabs_by_page_num(struct tab ***stabs)
1092 int num_tabs = 0;
1093 struct tab *t;
1095 num_tabs = gtk_notebook_get_n_pages(notebook);
1097 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1099 TAILQ_FOREACH(t, &tabs, entry)
1100 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1102 return (num_tabs);
1105 void
1106 buffers_make_list(void)
1108 int i, num_tabs;
1109 const gchar *title = NULL;
1110 GtkTreeIter iter;
1111 struct tab **stabs = NULL;
1113 num_tabs = sort_tabs_by_page_num(&stabs);
1115 for (i = 0; i < num_tabs; i++)
1116 if (stabs[i]) {
1117 gtk_list_store_append(buffers_store, &iter);
1118 title = get_title(stabs[i], FALSE);
1119 gtk_list_store_set(buffers_store, &iter,
1120 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1121 * rather than 0. */
1122 COL_TITLE, title,
1123 -1);
1126 g_free(stabs);
1129 void
1130 show_buffers(struct tab *t)
1132 buffers_make_list();
1133 gtk_widget_show(t->buffers);
1134 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1137 void
1138 toggle_buffers(struct tab *t)
1140 if (gtk_widget_get_visible(t->buffers))
1141 hide_buffers(t);
1142 else
1143 show_buffers(t);
1147 buffers(struct tab *t, struct karg *args)
1149 show_buffers(t);
1151 return (0);
1154 void
1155 hide_oops(struct tab *t)
1157 gtk_widget_hide(t->oops);
1160 void
1161 show_oops(struct tab *at, const char *fmt, ...)
1163 va_list ap;
1164 char *msg;
1165 struct tab *t = NULL;
1167 if (fmt == NULL)
1168 return;
1170 if (at == NULL) {
1171 if ((t = get_current_tab()) == NULL)
1172 return;
1173 } else
1174 t = at;
1176 va_start(ap, fmt);
1177 if (vasprintf(&msg, fmt, ap) == -1)
1178 errx(1, "show_oops failed");
1179 va_end(ap);
1181 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1182 gtk_widget_hide(t->cmd);
1183 gtk_widget_show(t->oops);
1186 char *
1187 get_as_string(struct settings *s)
1189 char *r = NULL;
1191 if (s == NULL)
1192 return (NULL);
1194 if (s->s) {
1195 if (s->s->get)
1196 r = s->s->get(s);
1197 else
1198 warnx("get_as_string skip %s\n", s->name);
1199 } else if (s->type == XT_S_INT)
1200 r = g_strdup_printf("%d", *s->ival);
1201 else if (s->type == XT_S_STR)
1202 r = g_strdup(*s->sval);
1203 else if (s->type == XT_S_FLOAT)
1204 r = g_strdup_printf("%f", *s->fval);
1205 else
1206 r = g_strdup_printf("INVALID TYPE");
1208 return (r);
1211 void
1212 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1214 int i;
1215 char *s;
1217 for (i = 0; i < LENGTH(rs); i++) {
1218 if (rs[i].s && rs[i].s->walk)
1219 rs[i].s->walk(&rs[i], cb, cb_args);
1220 else {
1221 s = get_as_string(&rs[i]);
1222 cb(&rs[i], s, cb_args);
1223 g_free(s);
1229 set_browser_mode(struct settings *s, char *val)
1231 if (!strcmp(val, "whitelist")) {
1232 browser_mode = XT_BM_WHITELIST;
1233 allow_volatile_cookies = 0;
1234 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1235 cookies_enabled = 1;
1236 enable_cookie_whitelist = 1;
1237 read_only_cookies = 0;
1238 save_rejected_cookies = 0;
1239 session_timeout = 3600;
1240 enable_scripts = 0;
1241 enable_js_whitelist = 1;
1242 enable_localstorage = 0;
1243 } else if (!strcmp(val, "normal")) {
1244 browser_mode = XT_BM_NORMAL;
1245 allow_volatile_cookies = 0;
1246 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1247 cookies_enabled = 1;
1248 enable_cookie_whitelist = 0;
1249 read_only_cookies = 0;
1250 save_rejected_cookies = 0;
1251 session_timeout = 3600;
1252 enable_scripts = 1;
1253 enable_js_whitelist = 0;
1254 enable_localstorage = 1;
1255 } else if (!strcmp(val, "kiosk")) {
1256 browser_mode = XT_BM_KIOSK;
1257 allow_volatile_cookies = 0;
1258 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1259 cookies_enabled = 1;
1260 enable_cookie_whitelist = 0;
1261 read_only_cookies = 0;
1262 save_rejected_cookies = 0;
1263 session_timeout = 3600;
1264 enable_scripts = 1;
1265 enable_js_whitelist = 0;
1266 enable_localstorage = 1;
1267 show_tabs = 0;
1268 tabless = 1;
1269 } else
1270 return (1);
1272 return (0);
1275 char *
1276 get_browser_mode(struct settings *s)
1278 char *r = NULL;
1280 if (browser_mode == XT_BM_WHITELIST)
1281 r = g_strdup("whitelist");
1282 else if (browser_mode == XT_BM_NORMAL)
1283 r = g_strdup("normal");
1284 else if (browser_mode == XT_BM_KIOSK)
1285 r = g_strdup("kiosk");
1286 else
1287 return (NULL);
1289 return (r);
1293 set_cookie_policy(struct settings *s, char *val)
1295 if (!strcmp(val, "no3rdparty"))
1296 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1297 else if (!strcmp(val, "accept"))
1298 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1299 else if (!strcmp(val, "reject"))
1300 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1301 else
1302 return (1);
1304 return (0);
1307 char *
1308 get_cookie_policy(struct settings *s)
1310 char *r = NULL;
1312 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1313 r = g_strdup("no3rdparty");
1314 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1315 r = g_strdup("accept");
1316 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1317 r = g_strdup("reject");
1318 else
1319 return (NULL);
1321 return (r);
1324 char *
1325 get_default_script(struct settings *s)
1327 if (default_script[0] == '\0')
1328 return (0);
1329 return (g_strdup(default_script));
1333 set_default_script(struct settings *s, char *val)
1335 if (val[0] == '~')
1336 snprintf(default_script, sizeof default_script, "%s/%s",
1337 pwd->pw_dir, &val[1]);
1338 else
1339 strlcpy(default_script, val, sizeof default_script);
1341 return (0);
1344 char *
1345 get_download_dir(struct settings *s)
1347 if (download_dir[0] == '\0')
1348 return (0);
1349 return (g_strdup(download_dir));
1353 set_download_dir(struct settings *s, char *val)
1355 if (val[0] == '~')
1356 snprintf(download_dir, sizeof download_dir, "%s/%s",
1357 pwd->pw_dir, &val[1]);
1358 else
1359 strlcpy(download_dir, val, sizeof download_dir);
1361 return (0);
1365 * Session IDs.
1366 * We use these to prevent people putting xxxt:// URLs on
1367 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1369 #define XT_XTP_SES_KEY_SZ 8
1370 #define XT_XTP_SES_KEY_HEX_FMT \
1371 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1372 char *dl_session_key; /* downloads */
1373 char *hl_session_key; /* history list */
1374 char *cl_session_key; /* cookie list */
1375 char *fl_session_key; /* favorites list */
1377 char work_dir[PATH_MAX];
1378 char certs_dir[PATH_MAX];
1379 char cache_dir[PATH_MAX];
1380 char sessions_dir[PATH_MAX];
1381 char cookie_file[PATH_MAX];
1382 SoupURI *proxy_uri = NULL;
1383 SoupSession *session;
1384 SoupCookieJar *s_cookiejar;
1385 SoupCookieJar *p_cookiejar;
1386 char rc_fname[PATH_MAX];
1388 struct mime_type_list mtl;
1389 struct alias_list aliases;
1391 /* protos */
1392 struct tab *create_new_tab(char *, struct undo *, int, int);
1393 void delete_tab(struct tab *);
1394 void setzoom_webkit(struct tab *, int);
1395 int run_script(struct tab *, char *);
1396 int download_rb_cmp(struct download *, struct download *);
1397 gboolean cmd_execute(struct tab *t, char *str);
1400 history_rb_cmp(struct history *h1, struct history *h2)
1402 return (strcmp(h1->uri, h2->uri));
1404 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1407 domain_rb_cmp(struct domain *d1, struct domain *d2)
1409 return (strcmp(d1->d, d2->d));
1411 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1413 char *
1414 get_work_dir(struct settings *s)
1416 if (work_dir[0] == '\0')
1417 return (0);
1418 return (g_strdup(work_dir));
1422 set_work_dir(struct settings *s, char *val)
1424 if (val[0] == '~')
1425 snprintf(work_dir, sizeof work_dir, "%s/%s",
1426 pwd->pw_dir, &val[1]);
1427 else
1428 strlcpy(work_dir, val, sizeof work_dir);
1430 return (0);
1433 char *
1434 get_tab_style(struct settings *s)
1436 if (tab_style == XT_TABS_NORMAL)
1437 return (g_strdup("normal"));
1438 else
1439 return (g_strdup("compact"));
1443 set_tab_style(struct settings *s, char *val)
1445 if (!strcmp(val, "normal"))
1446 tab_style = XT_TABS_NORMAL;
1447 else if (!strcmp(val, "compact"))
1448 tab_style = XT_TABS_COMPACT;
1449 else
1450 return (1);
1452 return (0);
1456 * generate a session key to secure xtp commands.
1457 * pass in a ptr to the key in question and it will
1458 * be modified in place.
1460 void
1461 generate_xtp_session_key(char **key)
1463 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1465 /* free old key */
1466 if (*key)
1467 g_free(*key);
1469 /* make a new one */
1470 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1471 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1472 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1473 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1475 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1479 * validate a xtp session key.
1480 * return 1 if OK
1483 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1485 if (strcmp(trusted, untrusted) != 0) {
1486 show_oops(t, "%s: xtp session key mismatch possible spoof",
1487 __func__);
1488 return (0);
1491 return (1);
1495 download_rb_cmp(struct download *e1, struct download *e2)
1497 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1499 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1501 struct valid_url_types {
1502 char *type;
1503 } vut[] = {
1504 { "http://" },
1505 { "https://" },
1506 { "ftp://" },
1507 { "file://" },
1508 { XT_XTP_STR },
1512 valid_url_type(char *url)
1514 int i;
1516 for (i = 0; i < LENGTH(vut); i++)
1517 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1518 return (0);
1520 return (1);
1523 void
1524 print_cookie(char *msg, SoupCookie *c)
1526 if (c == NULL)
1527 return;
1529 if (msg)
1530 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1531 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1532 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1533 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1534 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1535 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1536 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1537 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1538 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1539 DNPRINTF(XT_D_COOKIE, "====================================\n");
1542 void
1543 walk_alias(struct settings *s,
1544 void (*cb)(struct settings *, char *, void *), void *cb_args)
1546 struct alias *a;
1547 char *str;
1549 if (s == NULL || cb == NULL) {
1550 show_oops(NULL, "walk_alias invalid parameters");
1551 return;
1554 TAILQ_FOREACH(a, &aliases, entry) {
1555 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1556 cb(s, str, cb_args);
1557 g_free(str);
1561 char *
1562 match_alias(char *url_in)
1564 struct alias *a;
1565 char *arg;
1566 char *url_out = NULL, *search, *enc_arg;
1568 search = g_strdup(url_in);
1569 arg = search;
1570 if (strsep(&arg, " \t") == NULL) {
1571 show_oops(NULL, "match_alias: NULL URL");
1572 goto done;
1575 TAILQ_FOREACH(a, &aliases, entry) {
1576 if (!strcmp(search, a->a_name))
1577 break;
1580 if (a != NULL) {
1581 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1582 a->a_name);
1583 if (arg != NULL) {
1584 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1585 url_out = g_strdup_printf(a->a_uri, enc_arg);
1586 g_free(enc_arg);
1587 } else
1588 url_out = g_strdup_printf(a->a_uri, "");
1590 done:
1591 g_free(search);
1592 return (url_out);
1595 char *
1596 guess_url_type(char *url_in)
1598 struct stat sb;
1599 char *url_out = NULL, *enc_search = NULL;
1601 url_out = match_alias(url_in);
1602 if (url_out != NULL)
1603 return (url_out);
1605 if (guess_search) {
1607 * If there is no dot nor slash in the string and it isn't a
1608 * path to a local file and doesn't resolves to an IP, assume
1609 * that the user wants to search for the string.
1612 if (strchr(url_in, '.') == NULL &&
1613 strchr(url_in, '/') == NULL &&
1614 stat(url_in, &sb) != 0 &&
1615 gethostbyname(url_in) == NULL) {
1617 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1618 url_out = g_strdup_printf(search_string, enc_search);
1619 g_free(enc_search);
1620 return (url_out);
1624 /* XXX not sure about this heuristic */
1625 if (stat(url_in, &sb) == 0)
1626 url_out = g_strdup_printf("file://%s", url_in);
1627 else
1628 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1630 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1632 return (url_out);
1635 void
1636 load_uri(struct tab *t, gchar *uri)
1638 struct karg args;
1639 gchar *newuri = NULL;
1640 int i;
1642 if (uri == NULL)
1643 return;
1645 /* Strip leading spaces. */
1646 while (*uri && isspace(*uri))
1647 uri++;
1649 if (strlen(uri) == 0) {
1650 blank(t, NULL);
1651 return;
1654 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1656 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1657 for (i = 0; i < LENGTH(about_list); i++)
1658 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1659 bzero(&args, sizeof args);
1660 about_list[i].func(t, &args);
1661 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1662 FALSE);
1663 return;
1665 show_oops(t, "invalid about page");
1666 return;
1669 if (valid_url_type(uri)) {
1670 newuri = guess_url_type(uri);
1671 uri = newuri;
1674 set_status(t, (char *)uri, XT_STATUS_LOADING);
1675 marks_clear(t);
1676 webkit_web_view_load_uri(t->wv, uri);
1678 if (newuri)
1679 g_free(newuri);
1682 const gchar *
1683 get_uri(struct tab *t)
1685 const gchar *uri = NULL;
1687 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1688 return t->tmp_uri;
1689 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1690 uri = webkit_web_view_get_uri(t->wv);
1691 } else {
1692 /* use tmp_uri to make sure it is g_freed */
1693 if (t->tmp_uri)
1694 g_free(t->tmp_uri);
1695 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1696 about_list[t->xtp_meaning].name);
1697 uri = t->tmp_uri;
1699 return uri;
1702 const gchar *
1703 get_title(struct tab *t, bool window)
1705 const gchar *set = NULL, *title = NULL;
1706 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1708 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1709 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1710 goto notitle;
1712 title = webkit_web_view_get_title(t->wv);
1713 if ((set = title ? title : get_uri(t)))
1714 return set;
1716 notitle:
1717 set = window ? XT_NAME : "(untitled)";
1719 return set;
1723 add_alias(struct settings *s, char *line)
1725 char *l, *alias;
1726 struct alias *a = NULL;
1728 if (s == NULL || line == NULL) {
1729 show_oops(NULL, "add_alias invalid parameters");
1730 return (1);
1733 l = line;
1734 a = g_malloc(sizeof(*a));
1736 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1737 show_oops(NULL, "add_alias: incomplete alias definition");
1738 goto bad;
1740 if (strlen(alias) == 0 || strlen(l) == 0) {
1741 show_oops(NULL, "add_alias: invalid alias definition");
1742 goto bad;
1745 a->a_name = g_strdup(alias);
1746 a->a_uri = g_strdup(l);
1748 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1750 TAILQ_INSERT_TAIL(&aliases, a, entry);
1752 return (0);
1753 bad:
1754 if (a)
1755 g_free(a);
1756 return (1);
1760 add_mime_type(struct settings *s, char *line)
1762 char *mime_type;
1763 char *l;
1764 struct mime_type *m = NULL;
1765 int downloadfirst = 0;
1767 /* XXX this could be smarter */
1769 if (line == NULL || strlen(line) == 0) {
1770 show_oops(NULL, "add_mime_type invalid parameters");
1771 return (1);
1774 l = line;
1775 if (*l == '@') {
1776 downloadfirst = 1;
1777 l++;
1779 m = g_malloc(sizeof(*m));
1781 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1782 show_oops(NULL, "add_mime_type: invalid mime_type");
1783 goto bad;
1785 if (mime_type[strlen(mime_type) - 1] == '*') {
1786 mime_type[strlen(mime_type) - 1] = '\0';
1787 m->mt_default = 1;
1788 } else
1789 m->mt_default = 0;
1791 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1792 show_oops(NULL, "add_mime_type: invalid mime_type");
1793 goto bad;
1796 m->mt_type = g_strdup(mime_type);
1797 m->mt_action = g_strdup(l);
1798 m->mt_download = downloadfirst;
1800 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1801 m->mt_type, m->mt_action, m->mt_default);
1803 TAILQ_INSERT_TAIL(&mtl, m, entry);
1805 return (0);
1806 bad:
1807 if (m)
1808 g_free(m);
1809 return (1);
1812 struct mime_type *
1813 find_mime_type(char *mime_type)
1815 struct mime_type *m, *def = NULL, *rv = NULL;
1817 TAILQ_FOREACH(m, &mtl, entry) {
1818 if (m->mt_default &&
1819 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1820 def = m;
1822 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1823 rv = m;
1824 break;
1828 if (rv == NULL)
1829 rv = def;
1831 return (rv);
1834 void
1835 walk_mime_type(struct settings *s,
1836 void (*cb)(struct settings *, char *, void *), void *cb_args)
1838 struct mime_type *m;
1839 char *str;
1841 if (s == NULL || cb == NULL) {
1842 show_oops(NULL, "walk_mime_type invalid parameters");
1843 return;
1846 TAILQ_FOREACH(m, &mtl, entry) {
1847 str = g_strdup_printf("%s%s --> %s",
1848 m->mt_type,
1849 m->mt_default ? "*" : "",
1850 m->mt_action);
1851 cb(s, str, cb_args);
1852 g_free(str);
1856 void
1857 wl_add(char *str, struct domain_list *wl, int handy)
1859 struct domain *d;
1860 int add_dot = 0;
1862 if (str == NULL || wl == NULL || strlen(str) < 2)
1863 return;
1865 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1867 /* treat *.moo.com the same as .moo.com */
1868 if (str[0] == '*' && str[1] == '.')
1869 str = &str[1];
1870 else if (str[0] == '.')
1871 str = &str[0];
1872 else
1873 add_dot = 1;
1875 d = g_malloc(sizeof *d);
1876 if (add_dot)
1877 d->d = g_strdup_printf(".%s", str);
1878 else
1879 d->d = g_strdup(str);
1880 d->handy = handy;
1882 if (RB_INSERT(domain_list, wl, d))
1883 goto unwind;
1885 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1886 return;
1887 unwind:
1888 if (d) {
1889 if (d->d)
1890 g_free(d->d);
1891 g_free(d);
1896 add_cookie_wl(struct settings *s, char *entry)
1898 wl_add(entry, &c_wl, 1);
1899 return (0);
1902 void
1903 walk_cookie_wl(struct settings *s,
1904 void (*cb)(struct settings *, char *, void *), void *cb_args)
1906 struct domain *d;
1908 if (s == NULL || cb == NULL) {
1909 show_oops(NULL, "walk_cookie_wl invalid parameters");
1910 return;
1913 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1914 cb(s, d->d, cb_args);
1917 void
1918 walk_js_wl(struct settings *s,
1919 void (*cb)(struct settings *, char *, void *), void *cb_args)
1921 struct domain *d;
1923 if (s == NULL || cb == NULL) {
1924 show_oops(NULL, "walk_js_wl invalid parameters");
1925 return;
1928 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1929 cb(s, d->d, cb_args);
1933 add_js_wl(struct settings *s, char *entry)
1935 wl_add(entry, &js_wl, 1 /* persistent */);
1936 return (0);
1939 struct domain *
1940 wl_find(const gchar *search, struct domain_list *wl)
1942 int i;
1943 struct domain *d = NULL, dfind;
1944 gchar *s = NULL;
1946 if (search == NULL || wl == NULL)
1947 return (NULL);
1948 if (strlen(search) < 2)
1949 return (NULL);
1951 if (search[0] != '.')
1952 s = g_strdup_printf(".%s", search);
1953 else
1954 s = g_strdup(search);
1956 for (i = strlen(s) - 1; i >= 0; i--) {
1957 if (s[i] == '.') {
1958 dfind.d = &s[i];
1959 d = RB_FIND(domain_list, wl, &dfind);
1960 if (d)
1961 goto done;
1965 done:
1966 if (s)
1967 g_free(s);
1969 return (d);
1972 struct domain *
1973 wl_find_uri(const gchar *s, struct domain_list *wl)
1975 int i;
1976 char *ss;
1977 struct domain *r;
1979 if (s == NULL || wl == NULL)
1980 return (NULL);
1982 if (!strncmp(s, "http://", strlen("http://")))
1983 s = &s[strlen("http://")];
1984 else if (!strncmp(s, "https://", strlen("https://")))
1985 s = &s[strlen("https://")];
1987 if (strlen(s) < 2)
1988 return (NULL);
1990 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1991 /* chop string at first slash */
1992 if (s[i] == '/' || s[i] == '\0') {
1993 ss = g_strdup(s);
1994 ss[i] = '\0';
1995 r = wl_find(ss, wl);
1996 g_free(ss);
1997 return (r);
2000 return (NULL);
2004 settings_add(char *var, char *val)
2006 int i, rv, *p;
2007 gfloat *f;
2008 char **s;
2010 /* get settings */
2011 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2012 if (strcmp(var, rs[i].name))
2013 continue;
2015 if (rs[i].s) {
2016 if (rs[i].s->set(&rs[i], val))
2017 errx(1, "invalid value for %s: %s", var, val);
2018 rv = 1;
2019 break;
2020 } else
2021 switch (rs[i].type) {
2022 case XT_S_INT:
2023 p = rs[i].ival;
2024 *p = atoi(val);
2025 rv = 1;
2026 break;
2027 case XT_S_STR:
2028 s = rs[i].sval;
2029 if (s == NULL)
2030 errx(1, "invalid sval for %s",
2031 rs[i].name);
2032 if (*s)
2033 g_free(*s);
2034 *s = g_strdup(val);
2035 rv = 1;
2036 break;
2037 case XT_S_FLOAT:
2038 f = rs[i].fval;
2039 *f = atof(val);
2040 rv = 1;
2041 break;
2042 case XT_S_INVALID:
2043 default:
2044 errx(1, "invalid type for %s", var);
2046 break;
2048 return (rv);
2051 #define WS "\n= \t"
2052 void
2053 config_parse(char *filename, int runtime)
2055 FILE *config, *f;
2056 char *line, *cp, *var, *val;
2057 size_t len, lineno = 0;
2058 int handled;
2059 char file[PATH_MAX];
2060 struct stat sb;
2062 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2064 if (filename == NULL)
2065 return;
2067 if (runtime && runtime_settings[0] != '\0') {
2068 snprintf(file, sizeof file, "%s/%s",
2069 work_dir, runtime_settings);
2070 if (stat(file, &sb)) {
2071 warnx("runtime file doesn't exist, creating it");
2072 if ((f = fopen(file, "w")) == NULL)
2073 err(1, "runtime");
2074 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2075 fclose(f);
2077 } else
2078 strlcpy(file, filename, sizeof file);
2080 if ((config = fopen(file, "r")) == NULL) {
2081 warn("config_parse: cannot open %s", filename);
2082 return;
2085 for (;;) {
2086 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2087 if (feof(config) || ferror(config))
2088 break;
2090 cp = line;
2091 cp += (long)strspn(cp, WS);
2092 if (cp[0] == '\0') {
2093 /* empty line */
2094 free(line);
2095 continue;
2098 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2099 errx(1, "invalid config file entry: %s", line);
2101 cp += (long)strspn(cp, WS);
2103 if ((val = strsep(&cp, "\0")) == NULL)
2104 break;
2106 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2107 handled = settings_add(var, val);
2108 if (handled == 0)
2109 errx(1, "invalid conf file entry: %s=%s", var, val);
2111 free(line);
2114 fclose(config);
2117 char *
2118 js_ref_to_string(JSContextRef context, JSValueRef ref)
2120 char *s = NULL;
2121 size_t l;
2122 JSStringRef jsref;
2124 jsref = JSValueToStringCopy(context, ref, NULL);
2125 if (jsref == NULL)
2126 return (NULL);
2128 l = JSStringGetMaximumUTF8CStringSize(jsref);
2129 s = g_malloc(l);
2130 if (s)
2131 JSStringGetUTF8CString(jsref, s, l);
2132 JSStringRelease(jsref);
2134 return (s);
2137 void
2138 disable_hints(struct tab *t)
2140 bzero(t->hint_buf, sizeof t->hint_buf);
2141 bzero(t->hint_num, sizeof t->hint_num);
2142 run_script(t, "vimprobable_clear()");
2143 t->hints_on = 0;
2144 t->hint_mode = XT_HINT_NONE;
2147 void
2148 enable_hints(struct tab *t)
2150 bzero(t->hint_buf, sizeof t->hint_buf);
2151 run_script(t, "vimprobable_show_hints()");
2152 t->hints_on = 1;
2153 t->hint_mode = XT_HINT_NONE;
2156 #define XT_JS_OPEN ("open;")
2157 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2158 #define XT_JS_FIRE ("fire;")
2159 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2160 #define XT_JS_FOUND ("found;")
2161 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2164 run_script(struct tab *t, char *s)
2166 JSGlobalContextRef ctx;
2167 WebKitWebFrame *frame;
2168 JSStringRef str;
2169 JSValueRef val, exception;
2170 char *es, buf[128];
2172 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2173 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2175 frame = webkit_web_view_get_main_frame(t->wv);
2176 ctx = webkit_web_frame_get_global_context(frame);
2178 str = JSStringCreateWithUTF8CString(s);
2179 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2180 NULL, 0, &exception);
2181 JSStringRelease(str);
2183 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2184 if (val == NULL) {
2185 es = js_ref_to_string(ctx, exception);
2186 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2187 g_free(es);
2188 return (1);
2189 } else {
2190 es = js_ref_to_string(ctx, val);
2191 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2193 /* handle return value right here */
2194 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2195 disable_hints(t);
2196 marks_clear(t);
2197 load_uri(t, &es[XT_JS_OPEN_LEN]);
2200 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2201 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2202 &es[XT_JS_FIRE_LEN]);
2203 run_script(t, buf);
2204 disable_hints(t);
2207 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2208 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2209 disable_hints(t);
2212 g_free(es);
2215 return (0);
2219 hint(struct tab *t, struct karg *args)
2222 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2224 if (t->hints_on == 0)
2225 enable_hints(t);
2226 else
2227 disable_hints(t);
2229 return (0);
2232 void
2233 apply_style(struct tab *t)
2235 g_object_set(G_OBJECT(t->settings),
2236 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2240 userstyle(struct tab *t, struct karg *args)
2242 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2244 if (t->styled) {
2245 t->styled = 0;
2246 g_object_set(G_OBJECT(t->settings),
2247 "user-stylesheet-uri", NULL, (char *)NULL);
2248 } else {
2249 t->styled = 1;
2250 apply_style(t);
2252 return (0);
2256 * Doesn't work fully, due to the following bug:
2257 * https://bugs.webkit.org/show_bug.cgi?id=51747
2260 restore_global_history(void)
2262 char file[PATH_MAX];
2263 FILE *f;
2264 struct history *h;
2265 gchar *uri;
2266 gchar *title;
2267 const char delim[3] = {'\\', '\\', '\0'};
2269 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2271 if ((f = fopen(file, "r")) == NULL) {
2272 warnx("%s: fopen", __func__);
2273 return (1);
2276 for (;;) {
2277 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2278 if (feof(f) || ferror(f))
2279 break;
2281 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2282 if (feof(f) || ferror(f)) {
2283 free(uri);
2284 warnx("%s: broken history file\n", __func__);
2285 return (1);
2288 if (uri && strlen(uri) && title && strlen(title)) {
2289 webkit_web_history_item_new_with_data(uri, title);
2290 h = g_malloc(sizeof(struct history));
2291 h->uri = g_strdup(uri);
2292 h->title = g_strdup(title);
2293 RB_INSERT(history_list, &hl, h);
2294 completion_add_uri(h->uri);
2295 } else {
2296 warnx("%s: failed to restore history\n", __func__);
2297 free(uri);
2298 free(title);
2299 return (1);
2302 free(uri);
2303 free(title);
2304 uri = NULL;
2305 title = NULL;
2308 return (0);
2312 save_global_history_to_disk(struct tab *t)
2314 char file[PATH_MAX];
2315 FILE *f;
2316 struct history *h;
2318 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2320 if ((f = fopen(file, "w")) == NULL) {
2321 show_oops(t, "%s: global history file: %s",
2322 __func__, strerror(errno));
2323 return (1);
2326 RB_FOREACH_REVERSE(h, history_list, &hl) {
2327 if (h->uri && h->title)
2328 fprintf(f, "%s\n%s\n", h->uri, h->title);
2331 fclose(f);
2333 return (0);
2337 quit(struct tab *t, struct karg *args)
2339 if (save_global_history)
2340 save_global_history_to_disk(t);
2342 gtk_main_quit();
2344 return (1);
2348 open_tabs(struct tab *t, struct karg *a)
2350 char file[PATH_MAX];
2351 FILE *f = NULL;
2352 char *uri = NULL;
2353 int rv = 1;
2354 struct tab *ti, *tt;
2356 if (a == NULL)
2357 goto done;
2359 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2360 if ((f = fopen(file, "r")) == NULL)
2361 goto done;
2363 ti = TAILQ_LAST(&tabs, tab_list);
2365 for (;;) {
2366 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2367 if (feof(f) || ferror(f))
2368 break;
2370 /* retrieve session name */
2371 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2372 strlcpy(named_session,
2373 &uri[strlen(XT_SAVE_SESSION_ID)],
2374 sizeof named_session);
2375 continue;
2378 if (uri && strlen(uri))
2379 create_new_tab(uri, NULL, 1, -1);
2381 free(uri);
2382 uri = NULL;
2385 /* close open tabs */
2386 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2387 for (;;) {
2388 tt = TAILQ_FIRST(&tabs);
2389 if (tt != ti) {
2390 delete_tab(tt);
2391 continue;
2393 delete_tab(tt);
2394 break;
2396 recalc_tabs();
2399 rv = 0;
2400 done:
2401 if (f)
2402 fclose(f);
2404 return (rv);
2408 restore_saved_tabs(void)
2410 char file[PATH_MAX];
2411 int unlink_file = 0;
2412 struct stat sb;
2413 struct karg a;
2414 int rv = 0;
2416 snprintf(file, sizeof file, "%s/%s",
2417 sessions_dir, XT_RESTART_TABS_FILE);
2418 if (stat(file, &sb) == -1)
2419 a.s = XT_SAVED_TABS_FILE;
2420 else {
2421 unlink_file = 1;
2422 a.s = XT_RESTART_TABS_FILE;
2425 a.i = XT_SES_DONOTHING;
2426 rv = open_tabs(NULL, &a);
2428 if (unlink_file)
2429 unlink(file);
2431 return (rv);
2435 save_tabs(struct tab *t, struct karg *a)
2437 char file[PATH_MAX];
2438 FILE *f;
2439 int num_tabs = 0, i;
2440 struct tab **stabs = NULL;
2442 if (a == NULL)
2443 return (1);
2444 if (a->s == NULL)
2445 snprintf(file, sizeof file, "%s/%s",
2446 sessions_dir, named_session);
2447 else
2448 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2450 if ((f = fopen(file, "w")) == NULL) {
2451 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2452 return (1);
2455 /* save session name */
2456 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2458 /* Save tabs, in the order they are arranged in the notebook. */
2459 num_tabs = sort_tabs_by_page_num(&stabs);
2461 for (i = 0; i < num_tabs; i++)
2462 if (stabs[i] && get_uri(stabs[i]) != NULL)
2463 fprintf(f, "%s\n", get_uri(stabs[i]));
2465 g_free(stabs);
2467 /* try and make sure this gets to disk NOW. XXX Backup first? */
2468 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2469 show_oops(t, "May not have managed to save session: %s",
2470 strerror(errno));
2473 fclose(f);
2475 return (0);
2479 save_tabs_and_quit(struct tab *t, struct karg *args)
2481 struct karg a;
2483 a.s = NULL;
2484 save_tabs(t, &a);
2485 quit(t, NULL);
2487 return (1);
2491 run_page_script(struct tab *t, struct karg *args)
2493 const gchar *uri;
2494 char *tmp, script[PATH_MAX];
2496 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2497 if (tmp[0] == '\0') {
2498 show_oops(t, "no script specified");
2499 return (1);
2502 if ((uri = get_uri(t)) == NULL) {
2503 show_oops(t, "tab is empty, not running script");
2504 return (1);
2507 if (tmp[0] == '~')
2508 snprintf(script, sizeof script, "%s/%s",
2509 pwd->pw_dir, &tmp[1]);
2510 else
2511 strlcpy(script, tmp, sizeof script);
2513 switch (fork()) {
2514 case -1:
2515 show_oops(t, "can't fork to run script");
2516 return (1);
2517 /* NOTREACHED */
2518 case 0:
2519 break;
2520 default:
2521 return (0);
2524 /* child */
2525 execlp(script, script, uri, (void *)NULL);
2527 _exit(0);
2529 /* NOTREACHED */
2531 return (0);
2535 yank_uri(struct tab *t, struct karg *args)
2537 const gchar *uri;
2538 GtkClipboard *clipboard;
2540 if ((uri = get_uri(t)) == NULL)
2541 return (1);
2543 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2544 gtk_clipboard_set_text(clipboard, uri, -1);
2546 return (0);
2550 paste_uri(struct tab *t, struct karg *args)
2552 GtkClipboard *clipboard;
2553 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2554 gint len;
2555 gchar *p = NULL, *uri;
2557 /* try primary clipboard first */
2558 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2559 p = gtk_clipboard_wait_for_text(clipboard);
2561 /* if it failed get whatever text is in cut_buffer0 */
2562 if (p == NULL)
2563 if (gdk_property_get(gdk_get_default_root_window(),
2564 atom,
2565 gdk_atom_intern("STRING", FALSE),
2567 1024 * 1024 /* picked out of my butt */,
2568 FALSE,
2569 NULL,
2570 NULL,
2571 &len,
2572 (guchar **)&p)) {
2573 /* yes sir, we need to NUL the string */
2574 p[len] = '\0';
2577 if (p) {
2578 uri = p;
2579 while (*uri && isspace(*uri))
2580 uri++;
2581 if (strlen(uri) == 0) {
2582 show_oops(t, "empty paste buffer");
2583 goto done;
2585 if (guess_search == 0 && valid_url_type(uri)) {
2586 /* we can be clever and paste this in search box */
2587 show_oops(t, "not a valid URL");
2588 goto done;
2591 if (args->i == XT_PASTE_CURRENT_TAB)
2592 load_uri(t, uri);
2593 else if (args->i == XT_PASTE_NEW_TAB)
2594 create_new_tab(uri, NULL, 1, -1);
2597 done:
2598 if (p)
2599 g_free(p);
2601 return (0);
2604 gchar *
2605 find_domain(const gchar *s, int toplevel)
2607 SoupURI *uri;
2608 gchar *ret, *p;
2610 if (s == NULL)
2611 return (NULL);
2613 uri = soup_uri_new(s);
2615 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2616 return (NULL);
2619 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2620 if ((p = strrchr(uri->host, '.')) != NULL) {
2621 while(--p >= uri->host && *p != '.');
2622 p++;
2623 } else
2624 p = uri->host;
2625 } else
2626 p = uri->host;
2628 if (uri->port == 80)
2629 ret = g_strdup_printf(".%s", p);
2630 else
2631 ret = g_strdup_printf(".%s:%d", p, uri->port);
2633 soup_uri_free(uri);
2635 return ret;
2639 toggle_cwl(struct tab *t, struct karg *args)
2641 struct domain *d;
2642 const gchar *uri;
2643 char *dom = NULL;
2644 int es;
2646 if (args == NULL)
2647 return (1);
2649 uri = get_uri(t);
2650 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2652 if (uri == NULL || dom == NULL ||
2653 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2654 show_oops(t, "Can't toggle domain in cookie white list");
2655 goto done;
2657 d = wl_find(dom, &c_wl);
2659 if (d == NULL)
2660 es = 0;
2661 else
2662 es = 1;
2664 if (args->i & XT_WL_TOGGLE)
2665 es = !es;
2666 else if ((args->i & XT_WL_ENABLE) && es != 1)
2667 es = 1;
2668 else if ((args->i & XT_WL_DISABLE) && es != 0)
2669 es = 0;
2671 if (es)
2672 /* enable cookies for domain */
2673 wl_add(dom, &c_wl, 0);
2674 else
2675 /* disable cookies for domain */
2676 RB_REMOVE(domain_list, &c_wl, d);
2678 if (args->i & XT_WL_RELOAD)
2679 webkit_web_view_reload(t->wv);
2681 done:
2682 g_free(dom);
2683 return (0);
2687 toggle_js(struct tab *t, struct karg *args)
2689 int es;
2690 const gchar *uri;
2691 struct domain *d;
2692 char *dom = NULL;
2694 if (args == NULL)
2695 return (1);
2697 g_object_get(G_OBJECT(t->settings),
2698 "enable-scripts", &es, (char *)NULL);
2699 if (args->i & XT_WL_TOGGLE)
2700 es = !es;
2701 else if ((args->i & XT_WL_ENABLE) && es != 1)
2702 es = 1;
2703 else if ((args->i & XT_WL_DISABLE) && es != 0)
2704 es = 0;
2705 else
2706 return (1);
2708 uri = get_uri(t);
2709 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2711 if (uri == NULL || dom == NULL ||
2712 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2713 show_oops(t, "Can't toggle domain in JavaScript white list");
2714 goto done;
2717 if (es) {
2718 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2719 wl_add(dom, &js_wl, 0 /* session */);
2720 } else {
2721 d = wl_find(dom, &js_wl);
2722 if (d)
2723 RB_REMOVE(domain_list, &js_wl, d);
2724 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2726 g_object_set(G_OBJECT(t->settings),
2727 "enable-scripts", es, (char *)NULL);
2728 g_object_set(G_OBJECT(t->settings),
2729 "javascript-can-open-windows-automatically", es, (char *)NULL);
2730 webkit_web_view_set_settings(t->wv, t->settings);
2732 if (args->i & XT_WL_RELOAD)
2733 webkit_web_view_reload(t->wv);
2734 done:
2735 if (dom)
2736 g_free(dom);
2737 return (0);
2740 void
2741 js_toggle_cb(GtkWidget *w, struct tab *t)
2743 struct karg a;
2745 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2746 toggle_cwl(t, &a);
2748 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2749 toggle_js(t, &a);
2753 toggle_src(struct tab *t, struct karg *args)
2755 gboolean mode;
2757 if (t == NULL)
2758 return (0);
2760 mode = webkit_web_view_get_view_source_mode(t->wv);
2761 webkit_web_view_set_view_source_mode(t->wv, !mode);
2762 webkit_web_view_reload(t->wv);
2764 return (0);
2767 void
2768 focus_webview(struct tab *t)
2770 if (t == NULL)
2771 return;
2773 /* only grab focus if we are visible */
2774 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2775 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2779 focus(struct tab *t, struct karg *args)
2781 if (t == NULL || args == NULL)
2782 return (1);
2784 if (show_url == 0)
2785 return (0);
2787 if (args->i == XT_FOCUS_URI)
2788 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2789 else if (args->i == XT_FOCUS_SEARCH)
2790 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2792 return (0);
2796 stats(struct tab *t, struct karg *args)
2798 char *page, *body, *s, line[64 * 1024];
2799 uint64_t line_count = 0;
2800 FILE *r_cookie_f;
2802 if (t == NULL)
2803 show_oops(NULL, "stats invalid parameters");
2805 line[0] = '\0';
2806 if (save_rejected_cookies) {
2807 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2808 for (;;) {
2809 s = fgets(line, sizeof line, r_cookie_f);
2810 if (s == NULL || feof(r_cookie_f) ||
2811 ferror(r_cookie_f))
2812 break;
2813 line_count++;
2815 fclose(r_cookie_f);
2816 snprintf(line, sizeof line,
2817 "<br/>Cookies blocked(*) total: %llu", line_count);
2818 } else
2819 show_oops(t, "Can't open blocked cookies file: %s",
2820 strerror(errno));
2823 body = g_strdup_printf(
2824 "Cookies blocked(*) this session: %llu"
2825 "%s"
2826 "<p><small><b>*</b> results vary based on settings</small></p>",
2827 blocked_cookies,
2828 line);
2830 page = get_html_page("Statistics", body, "", 0);
2831 g_free(body);
2833 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2834 g_free(page);
2836 return (0);
2840 marco(struct tab *t, struct karg *args)
2842 char *page, line[64 * 1024];
2843 int len;
2845 if (t == NULL)
2846 show_oops(NULL, "marco invalid parameters");
2848 line[0] = '\0';
2849 snprintf(line, sizeof line, "%s", marco_message(&len));
2851 page = get_html_page("Marco Sez...", line, "", 0);
2853 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2854 g_free(page);
2856 return (0);
2860 blank(struct tab *t, struct karg *args)
2862 if (t == NULL)
2863 show_oops(NULL, "blank invalid parameters");
2865 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2867 return (0);
2870 about(struct tab *t, struct karg *args)
2872 char *page, *body;
2874 if (t == NULL)
2875 show_oops(NULL, "about invalid parameters");
2877 body = g_strdup_printf("<b>Version: %s</b><p>"
2878 "Authors:"
2879 "<ul>"
2880 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2881 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2882 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2883 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2884 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2885 "</ul>"
2886 "Copyrights and licenses can be found on the XXXTerm "
2887 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2888 version
2891 page = get_html_page("About", body, "", 0);
2892 g_free(body);
2894 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2895 g_free(page);
2897 return (0);
2901 help(struct tab *t, struct karg *args)
2903 char *page, *head, *body;
2905 if (t == NULL)
2906 show_oops(NULL, "help invalid parameters");
2908 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2909 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2910 "</head>\n";
2911 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2912 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2913 "cgi-bin/man-cgi?xxxterm</a>";
2915 page = get_html_page(XT_NAME, body, head, FALSE);
2917 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2918 g_free(page);
2920 return (0);
2924 * update all favorite tabs apart from one. Pass NULL if
2925 * you want to update all.
2927 void
2928 update_favorite_tabs(struct tab *apart_from)
2930 struct tab *t;
2931 if (!updating_fl_tabs) {
2932 updating_fl_tabs = 1; /* stop infinite recursion */
2933 TAILQ_FOREACH(t, &tabs, entry)
2934 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2935 && (t != apart_from))
2936 xtp_page_fl(t, NULL);
2937 updating_fl_tabs = 0;
2941 /* show a list of favorites (bookmarks) */
2943 xtp_page_fl(struct tab *t, struct karg *args)
2945 char file[PATH_MAX];
2946 FILE *f;
2947 char *uri = NULL, *title = NULL;
2948 size_t len, lineno = 0;
2949 int i, failed = 0;
2950 char *body, *tmp, *page = NULL;
2951 const char delim[3] = {'\\', '\\', '\0'};
2953 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2955 if (t == NULL)
2956 warn("%s: bad param", __func__);
2958 /* new session key */
2959 if (!updating_fl_tabs)
2960 generate_xtp_session_key(&fl_session_key);
2962 /* open favorites */
2963 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2964 if ((f = fopen(file, "r")) == NULL) {
2965 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2966 return (1);
2969 /* body */
2970 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2971 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2972 "<th style='width: 40px'>Rm</th></tr>\n");
2974 for (i = 1;;) {
2975 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2976 if (feof(f) || ferror(f))
2977 break;
2978 if (strlen(title) == 0 || title[0] == '#') {
2979 free(title);
2980 title = NULL;
2981 continue;
2984 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2985 if (feof(f) || ferror(f)) {
2986 show_oops(t, "favorites file corrupt");
2987 failed = 1;
2988 break;
2991 tmp = body;
2992 body = g_strdup_printf("%s<tr>"
2993 "<td>%d</td>"
2994 "<td><a href='%s'>%s</a></td>"
2995 "<td style='text-align: center'>"
2996 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2997 "</tr>\n",
2998 body, i, uri, title,
2999 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3001 g_free(tmp);
3003 free(uri);
3004 uri = NULL;
3005 free(title);
3006 title = NULL;
3007 i++;
3009 fclose(f);
3011 /* if none, say so */
3012 if (i == 1) {
3013 tmp = body;
3014 body = g_strdup_printf("%s<tr>"
3015 "<td colspan='3' style='text-align: center'>"
3016 "No favorites - To add one use the 'favadd' command."
3017 "</td></tr>", body);
3018 g_free(tmp);
3021 tmp = body;
3022 body = g_strdup_printf("%s</table>", body);
3023 g_free(tmp);
3025 if (uri)
3026 free(uri);
3027 if (title)
3028 free(title);
3030 /* render */
3031 if (!failed) {
3032 page = get_html_page("Favorites", body, "", 1);
3033 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3034 g_free(page);
3037 update_favorite_tabs(t);
3039 if (body)
3040 g_free(body);
3042 return (failed);
3045 void
3046 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3047 size_t cert_count, char *title)
3049 gnutls_datum_t cinfo;
3050 char *tmp, *body;
3051 int i;
3053 body = g_strdup("");
3055 for (i = 0; i < cert_count; i++) {
3056 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3057 &cinfo))
3058 return;
3060 tmp = body;
3061 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3062 body, i, cinfo.data);
3063 gnutls_free(cinfo.data);
3064 g_free(tmp);
3067 tmp = get_html_page(title, body, "", 0);
3068 g_free(body);
3070 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3071 g_free(tmp);
3075 ca_cmd(struct tab *t, struct karg *args)
3077 FILE *f = NULL;
3078 int rv = 1, certs = 0, certs_read;
3079 struct stat sb;
3080 gnutls_datum_t dt;
3081 gnutls_x509_crt_t *c = NULL;
3082 char *certs_buf = NULL, *s;
3084 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3085 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3086 return (1);
3089 if (fstat(fileno(f), &sb) == -1) {
3090 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3091 goto done;
3094 certs_buf = g_malloc(sb.st_size + 1);
3095 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3096 show_oops(t, "Can't read CA file: %s", strerror(errno));
3097 goto done;
3099 certs_buf[sb.st_size] = '\0';
3101 s = certs_buf;
3102 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3103 certs++;
3104 s += strlen("BEGIN CERTIFICATE");
3107 bzero(&dt, sizeof dt);
3108 dt.data = (unsigned char *)certs_buf;
3109 dt.size = sb.st_size;
3110 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3111 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3112 GNUTLS_X509_FMT_PEM, 0);
3113 if (certs_read <= 0) {
3114 show_oops(t, "No cert(s) available");
3115 goto done;
3117 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3118 done:
3119 if (c)
3120 g_free(c);
3121 if (certs_buf)
3122 g_free(certs_buf);
3123 if (f)
3124 fclose(f);
3126 return (rv);
3130 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
3132 SoupURI *su = NULL;
3133 struct addrinfo hints, *res = NULL, *ai;
3134 int s = -1, on;
3135 char port[8];
3137 if (uri && !g_str_has_prefix(uri, "https://"))
3138 goto done;
3140 su = soup_uri_new(uri);
3141 if (su == NULL)
3142 goto done;
3143 if (!SOUP_URI_VALID_FOR_HTTP(su))
3144 goto done;
3146 snprintf(port, sizeof port, "%d", su->port);
3147 bzero(&hints, sizeof(struct addrinfo));
3148 hints.ai_flags = AI_CANONNAME;
3149 hints.ai_family = AF_UNSPEC;
3150 hints.ai_socktype = SOCK_STREAM;
3152 if (getaddrinfo(su->host, port, &hints, &res))
3153 goto done;
3155 for (ai = res; ai; ai = ai->ai_next) {
3156 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3157 continue;
3159 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3160 if (s < 0)
3161 goto done;
3162 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3163 sizeof(on)) == -1)
3164 goto done;
3166 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
3167 goto done;
3170 if (domain)
3171 strlcpy(domain, su->host, domain_sz);
3172 done:
3173 if (su)
3174 soup_uri_free(su);
3175 if (res)
3176 freeaddrinfo(res);
3178 return (s);
3182 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3184 if (gsession)
3185 gnutls_deinit(gsession);
3186 if (xcred)
3187 gnutls_certificate_free_credentials(xcred);
3189 return (0);
3193 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3194 gnutls_certificate_credentials_t *xc)
3196 gnutls_certificate_credentials_t xcred;
3197 gnutls_session_t gsession;
3198 int rv = 1;
3200 if (gs == NULL || xc == NULL)
3201 goto done;
3203 *gs = NULL;
3204 *xc = NULL;
3206 gnutls_certificate_allocate_credentials(&xcred);
3207 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3208 GNUTLS_X509_FMT_PEM);
3209 gnutls_init(&gsession, GNUTLS_CLIENT);
3210 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3211 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3212 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3213 if ((rv = gnutls_handshake(gsession)) < 0) {
3214 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3216 gnutls_error_is_fatal(rv),
3217 gnutls_strerror_name(rv));
3218 stop_tls(gsession, xcred);
3219 goto done;
3222 gnutls_credentials_type_t cred;
3223 cred = gnutls_auth_get_type(gsession);
3224 if (cred != GNUTLS_CRD_CERTIFICATE) {
3225 stop_tls(gsession, xcred);
3226 goto done;
3229 *gs = gsession;
3230 *xc = xcred;
3231 rv = 0;
3232 done:
3233 return (rv);
3237 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3238 size_t *cert_count)
3240 unsigned int len;
3241 const gnutls_datum_t *cl;
3242 gnutls_x509_crt_t *all_certs;
3243 int i, rv = 1;
3245 if (certs == NULL || cert_count == NULL)
3246 goto done;
3247 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3248 goto done;
3249 cl = gnutls_certificate_get_peers(gsession, &len);
3250 if (len == 0)
3251 goto done;
3253 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3254 for (i = 0; i < len; i++) {
3255 gnutls_x509_crt_init(&all_certs[i]);
3256 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3257 GNUTLS_X509_FMT_PEM < 0)) {
3258 g_free(all_certs);
3259 goto done;
3263 *certs = all_certs;
3264 *cert_count = len;
3265 rv = 0;
3266 done:
3267 return (rv);
3270 void
3271 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3273 int i;
3275 for (i = 0; i < cert_count; i++)
3276 gnutls_x509_crt_deinit(certs[i]);
3277 g_free(certs);
3280 void
3281 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3283 GdkColor c_text, c_base;
3285 gdk_color_parse(text, &c_text);
3286 gdk_color_parse(base, &c_base);
3288 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3289 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3290 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3291 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3293 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3294 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3295 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3296 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3299 void
3300 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3301 size_t cert_count, char *domain)
3303 size_t cert_buf_sz;
3304 char cert_buf[64 * 1024], file[PATH_MAX];
3305 int i;
3306 FILE *f;
3307 GdkColor color;
3309 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3310 return;
3312 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3313 if ((f = fopen(file, "w")) == NULL) {
3314 show_oops(t, "Can't create cert file %s %s",
3315 file, strerror(errno));
3316 return;
3319 for (i = 0; i < cert_count; i++) {
3320 cert_buf_sz = sizeof cert_buf;
3321 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3322 cert_buf, &cert_buf_sz)) {
3323 show_oops(t, "gnutls_x509_crt_export failed");
3324 goto done;
3326 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3327 show_oops(t, "Can't write certs: %s", strerror(errno));
3328 goto done;
3332 /* not the best spot but oh well */
3333 gdk_color_parse("lightblue", &color);
3334 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3335 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3336 done:
3337 fclose(f);
3341 load_compare_cert(struct tab *t, struct karg *args)
3343 const gchar *uri;
3344 char domain[8182], file[PATH_MAX];
3345 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3346 int s = -1, rv = 1, i;
3347 size_t cert_count;
3348 FILE *f = NULL;
3349 size_t cert_buf_sz;
3350 gnutls_session_t gsession;
3351 gnutls_x509_crt_t *certs;
3352 gnutls_certificate_credentials_t xcred;
3354 if (t == NULL)
3355 return (1);
3357 if ((uri = get_uri(t)) == NULL)
3358 return (1);
3360 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3361 return (1);
3363 /* go ssl/tls */
3364 if (start_tls(t, s, &gsession, &xcred)) {
3365 show_oops(t, "Start TLS failed");
3366 goto done;
3369 /* get certs */
3370 if (get_connection_certs(gsession, &certs, &cert_count)) {
3371 show_oops(t, "Can't get connection certificates");
3372 goto done;
3375 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3376 if ((f = fopen(file, "r")) == NULL)
3377 goto freeit;
3379 for (i = 0; i < cert_count; i++) {
3380 cert_buf_sz = sizeof cert_buf;
3381 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3382 cert_buf, &cert_buf_sz)) {
3383 goto freeit;
3385 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3386 rv = -1; /* critical */
3387 goto freeit;
3389 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3390 rv = -1; /* critical */
3391 goto freeit;
3395 rv = 0;
3396 freeit:
3397 if (f)
3398 fclose(f);
3399 free_connection_certs(certs, cert_count);
3400 done:
3401 /* we close the socket first for speed */
3402 if (s != -1)
3403 close(s);
3404 stop_tls(gsession, xcred);
3406 return (rv);
3410 cert_cmd(struct tab *t, struct karg *args)
3412 const gchar *uri;
3413 char domain[8182];
3414 int s = -1;
3415 size_t cert_count;
3416 gnutls_session_t gsession;
3417 gnutls_x509_crt_t *certs;
3418 gnutls_certificate_credentials_t xcred;
3420 if (t == NULL)
3421 return (1);
3423 if (ssl_ca_file == NULL) {
3424 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3425 return (1);
3428 if ((uri = get_uri(t)) == NULL) {
3429 show_oops(t, "Invalid URI");
3430 return (1);
3433 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3434 show_oops(t, "Invalid certificate URI: %s", uri);
3435 return (1);
3438 /* go ssl/tls */
3439 if (start_tls(t, s, &gsession, &xcred)) {
3440 show_oops(t, "Start TLS failed");
3441 goto done;
3444 /* get certs */
3445 if (get_connection_certs(gsession, &certs, &cert_count)) {
3446 show_oops(t, "get_connection_certs failed");
3447 goto done;
3450 if (args->i & XT_SHOW)
3451 show_certs(t, certs, cert_count, "Certificate Chain");
3452 else if (args->i & XT_SAVE)
3453 save_certs(t, certs, cert_count, domain);
3455 free_connection_certs(certs, cert_count);
3456 done:
3457 /* we close the socket first for speed */
3458 if (s != -1)
3459 close(s);
3460 stop_tls(gsession, xcred);
3462 return (0);
3466 remove_cookie(int index)
3468 int i, rv = 1;
3469 GSList *cf;
3470 SoupCookie *c;
3472 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3474 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3476 for (i = 1; cf; cf = cf->next, i++) {
3477 if (i != index)
3478 continue;
3479 c = cf->data;
3480 print_cookie("remove cookie", c);
3481 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3482 rv = 0;
3483 break;
3486 soup_cookies_free(cf);
3488 return (rv);
3492 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3494 struct domain *d;
3495 char *tmp, *body;
3497 body = g_strdup("");
3499 /* p list */
3500 if (args->i & XT_WL_PERSISTENT) {
3501 tmp = body;
3502 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3503 g_free(tmp);
3504 RB_FOREACH(d, domain_list, wl) {
3505 if (d->handy == 0)
3506 continue;
3507 tmp = body;
3508 body = g_strdup_printf("%s%s<br/>", body, d->d);
3509 g_free(tmp);
3513 /* s list */
3514 if (args->i & XT_WL_SESSION) {
3515 tmp = body;
3516 body = g_strdup_printf("%s<h2>Session</h2>", body);
3517 g_free(tmp);
3518 RB_FOREACH(d, domain_list, wl) {
3519 if (d->handy == 1)
3520 continue;
3521 tmp = body;
3522 body = g_strdup_printf("%s%s<br/>", body, d->d);
3523 g_free(tmp);
3527 tmp = get_html_page(title, body, "", 0);
3528 g_free(body);
3529 if (wl == &js_wl)
3530 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3531 else
3532 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3533 g_free(tmp);
3534 return (0);
3538 wl_save(struct tab *t, struct karg *args, int js)
3540 char file[PATH_MAX];
3541 FILE *f;
3542 char *line = NULL, *lt = NULL, *dom = NULL;
3543 size_t linelen;
3544 const gchar *uri;
3545 struct karg a;
3546 struct domain *d;
3547 GSList *cf;
3548 SoupCookie *ci, *c;
3550 if (t == NULL || args == NULL)
3551 return (1);
3553 if (runtime_settings[0] == '\0')
3554 return (1);
3556 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3557 if ((f = fopen(file, "r+")) == NULL)
3558 return (1);
3560 uri = get_uri(t);
3561 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3562 if (uri == NULL || dom == NULL ||
3563 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3564 show_oops(t, "Can't add domain to %s white list",
3565 js ? "JavaScript" : "cookie");
3566 goto done;
3569 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3571 while (!feof(f)) {
3572 line = fparseln(f, &linelen, NULL, NULL, 0);
3573 if (line == NULL)
3574 continue;
3575 if (!strcmp(line, lt))
3576 goto done;
3577 free(line);
3578 line = NULL;
3581 fprintf(f, "%s\n", lt);
3583 a.i = XT_WL_ENABLE;
3584 a.i |= args->i;
3585 if (js) {
3586 d = wl_find(dom, &js_wl);
3587 if (!d) {
3588 settings_add("js_wl", dom);
3589 d = wl_find(dom, &js_wl);
3591 toggle_js(t, &a);
3592 } else {
3593 d = wl_find(dom, &c_wl);
3594 if (!d) {
3595 settings_add("cookie_wl", dom);
3596 d = wl_find(dom, &c_wl);
3598 toggle_cwl(t, &a);
3600 /* find and add to persistent jar */
3601 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3602 for (;cf; cf = cf->next) {
3603 ci = cf->data;
3604 if (!strcmp(dom, ci->domain) ||
3605 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3606 c = soup_cookie_copy(ci);
3607 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3610 soup_cookies_free(cf);
3612 if (d)
3613 d->handy = 1;
3615 done:
3616 if (line)
3617 free(line);
3618 if (dom)
3619 g_free(dom);
3620 if (lt)
3621 g_free(lt);
3622 fclose(f);
3624 return (0);
3628 js_show_wl(struct tab *t, struct karg *args)
3630 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3631 wl_show(t, args, "JavaScript White List", &js_wl);
3633 return (0);
3637 cookie_show_wl(struct tab *t, struct karg *args)
3639 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3640 wl_show(t, args, "Cookie White List", &c_wl);
3642 return (0);
3646 cookie_cmd(struct tab *t, struct karg *args)
3648 if (args->i & XT_SHOW)
3649 wl_show(t, args, "Cookie White List", &c_wl);
3650 else if (args->i & XT_WL_TOGGLE) {
3651 args->i |= XT_WL_RELOAD;
3652 toggle_cwl(t, args);
3653 } else if (args->i & XT_SAVE) {
3654 args->i |= XT_WL_RELOAD;
3655 wl_save(t, args, 0);
3656 } else if (args->i & XT_DELETE)
3657 show_oops(t, "'cookie delete' currently unimplemented");
3659 return (0);
3663 js_cmd(struct tab *t, struct karg *args)
3665 if (args->i & XT_SHOW)
3666 wl_show(t, args, "JavaScript White List", &js_wl);
3667 else if (args->i & XT_SAVE) {
3668 args->i |= XT_WL_RELOAD;
3669 wl_save(t, args, 1);
3670 } else if (args->i & XT_WL_TOGGLE) {
3671 args->i |= XT_WL_RELOAD;
3672 toggle_js(t, args);
3673 } else if (args->i & XT_DELETE)
3674 show_oops(t, "'js delete' currently unimplemented");
3676 return (0);
3680 toplevel_cmd(struct tab *t, struct karg *args)
3682 js_toggle_cb(t->js_toggle, t);
3684 return (0);
3688 add_favorite(struct tab *t, struct karg *args)
3690 char file[PATH_MAX];
3691 FILE *f;
3692 char *line = NULL;
3693 size_t urilen, linelen;
3694 const gchar *uri, *title;
3696 if (t == NULL)
3697 return (1);
3699 /* don't allow adding of xtp pages to favorites */
3700 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3701 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3702 return (1);
3705 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3706 if ((f = fopen(file, "r+")) == NULL) {
3707 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3708 return (1);
3711 title = get_title(t, FALSE);
3712 uri = get_uri(t);
3714 if (title == NULL || uri == NULL) {
3715 show_oops(t, "can't add page to favorites");
3716 goto done;
3719 urilen = strlen(uri);
3721 for (;;) {
3722 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3723 if (feof(f) || ferror(f))
3724 break;
3726 if (linelen == urilen && !strcmp(line, uri))
3727 goto done;
3729 free(line);
3730 line = NULL;
3733 fprintf(f, "\n%s\n%s", title, uri);
3734 done:
3735 if (line)
3736 free(line);
3737 fclose(f);
3739 update_favorite_tabs(NULL);
3741 return (0);
3745 navaction(struct tab *t, struct karg *args)
3747 WebKitWebHistoryItem *item;
3749 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3750 t->tab_id, args->i);
3752 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3754 if (t->item) {
3755 if (args->i == XT_NAV_BACK)
3756 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3757 else
3758 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3759 if (item == NULL)
3760 return (XT_CB_PASSTHROUGH);
3761 webkit_web_view_go_to_back_forward_item(t->wv, item);
3762 t->item = NULL;
3763 return (XT_CB_PASSTHROUGH);
3766 switch (args->i) {
3767 case XT_NAV_BACK:
3768 marks_clear(t);
3769 item = webkit_web_back_forward_list_get_back_item(t->bfl);
3770 if (item)
3771 webkit_web_view_go_to_back_forward_item(t->wv, item);
3772 break;
3773 case XT_NAV_FORWARD:
3774 marks_clear(t);
3775 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3776 if (item)
3777 webkit_web_view_go_to_back_forward_item(t->wv, item);
3778 break;
3779 case XT_NAV_RELOAD:
3780 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3781 if (item)
3782 webkit_web_view_go_to_back_forward_item(t->wv, item);
3783 break;
3785 return (XT_CB_PASSTHROUGH);
3789 move(struct tab *t, struct karg *args)
3791 GtkAdjustment *adjust;
3792 double pi, si, pos, ps, upper, lower, max;
3794 switch (args->i) {
3795 case XT_MOVE_DOWN:
3796 case XT_MOVE_UP:
3797 case XT_MOVE_BOTTOM:
3798 case XT_MOVE_TOP:
3799 case XT_MOVE_PAGEDOWN:
3800 case XT_MOVE_PAGEUP:
3801 case XT_MOVE_HALFDOWN:
3802 case XT_MOVE_HALFUP:
3803 case XT_MOVE_PERCENT:
3804 adjust = t->adjust_v;
3805 break;
3806 default:
3807 adjust = t->adjust_h;
3808 break;
3811 pos = gtk_adjustment_get_value(adjust);
3812 ps = gtk_adjustment_get_page_size(adjust);
3813 upper = gtk_adjustment_get_upper(adjust);
3814 lower = gtk_adjustment_get_lower(adjust);
3815 si = gtk_adjustment_get_step_increment(adjust);
3816 pi = gtk_adjustment_get_page_increment(adjust);
3817 max = upper - ps;
3819 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3820 "max %f si %f pi %f\n",
3821 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3822 pos, ps, upper, lower, max, si, pi);
3824 switch (args->i) {
3825 case XT_MOVE_DOWN:
3826 case XT_MOVE_RIGHT:
3827 pos += si;
3828 gtk_adjustment_set_value(adjust, MIN(pos, max));
3829 break;
3830 case XT_MOVE_UP:
3831 case XT_MOVE_LEFT:
3832 pos -= si;
3833 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3834 break;
3835 case XT_MOVE_BOTTOM:
3836 case XT_MOVE_FARRIGHT:
3837 gtk_adjustment_set_value(adjust, max);
3838 break;
3839 case XT_MOVE_TOP:
3840 case XT_MOVE_FARLEFT:
3841 gtk_adjustment_set_value(adjust, lower);
3842 break;
3843 case XT_MOVE_PAGEDOWN:
3844 pos += pi;
3845 gtk_adjustment_set_value(adjust, MIN(pos, max));
3846 break;
3847 case XT_MOVE_PAGEUP:
3848 pos -= pi;
3849 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3850 break;
3851 case XT_MOVE_HALFDOWN:
3852 pos += pi / 2;
3853 gtk_adjustment_set_value(adjust, MIN(pos, max));
3854 break;
3855 case XT_MOVE_HALFUP:
3856 pos -= pi / 2;
3857 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3858 break;
3859 case XT_MOVE_PERCENT:
3861 double percent;
3863 percent = atoi(args->s) / 100.0;
3864 pos = max * percent;
3865 if (pos < 0.0 || pos > max)
3866 break;
3868 gtk_adjustment_set_value(adjust, pos);
3869 break;
3871 default:
3872 return (XT_CB_PASSTHROUGH);
3875 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3877 return (XT_CB_HANDLED);
3880 void
3881 url_set_visibility(void)
3883 struct tab *t;
3885 TAILQ_FOREACH(t, &tabs, entry)
3886 if (show_url == 0) {
3887 gtk_widget_hide(t->toolbar);
3888 focus_webview(t);
3889 } else
3890 gtk_widget_show(t->toolbar);
3893 void
3894 notebook_tab_set_visibility(void)
3896 if (show_tabs == 0) {
3897 gtk_widget_hide(tab_bar);
3898 gtk_notebook_set_show_tabs(notebook, FALSE);
3899 } else {
3900 if (tab_style == XT_TABS_NORMAL) {
3901 gtk_widget_hide(tab_bar);
3902 gtk_notebook_set_show_tabs(notebook, TRUE);
3903 } else if (tab_style == XT_TABS_COMPACT) {
3904 gtk_widget_show(tab_bar);
3905 gtk_notebook_set_show_tabs(notebook, FALSE);
3910 void
3911 statusbar_set_visibility(void)
3913 struct tab *t;
3915 TAILQ_FOREACH(t, &tabs, entry)
3916 if (show_statusbar == 0) {
3917 gtk_widget_hide(t->statusbar_box);
3918 focus_webview(t);
3919 } else
3920 gtk_widget_show(t->statusbar_box);
3923 void
3924 url_set(struct tab *t, int enable_url_entry)
3926 GdkPixbuf *pixbuf;
3927 int progress;
3929 show_url = enable_url_entry;
3931 if (enable_url_entry) {
3932 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
3933 GTK_ENTRY_ICON_PRIMARY, NULL);
3934 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
3935 } else {
3936 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3937 GTK_ENTRY_ICON_PRIMARY);
3938 progress =
3939 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3940 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
3941 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3942 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
3943 progress);
3948 fullscreen(struct tab *t, struct karg *args)
3950 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3952 if (t == NULL)
3953 return (XT_CB_PASSTHROUGH);
3955 if (show_url == 0) {
3956 url_set(t, 1);
3957 show_tabs = 1;
3958 } else {
3959 url_set(t, 0);
3960 show_tabs = 0;
3963 url_set_visibility();
3964 notebook_tab_set_visibility();
3966 return (XT_CB_HANDLED);
3970 statustoggle(struct tab *t, struct karg *args)
3972 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3974 if (show_statusbar == 1) {
3975 show_statusbar = 0;
3976 statusbar_set_visibility();
3977 } else if (show_statusbar == 0) {
3978 show_statusbar = 1;
3979 statusbar_set_visibility();
3981 return (XT_CB_HANDLED);
3985 urlaction(struct tab *t, struct karg *args)
3987 int rv = XT_CB_HANDLED;
3989 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3991 if (t == NULL)
3992 return (XT_CB_PASSTHROUGH);
3994 switch (args->i) {
3995 case XT_URL_SHOW:
3996 if (show_url == 0) {
3997 url_set(t, 1);
3998 url_set_visibility();
4000 break;
4001 case XT_URL_HIDE:
4002 if (show_url == 1) {
4003 url_set(t, 0);
4004 url_set_visibility();
4006 break;
4008 return (rv);
4012 tabaction(struct tab *t, struct karg *args)
4014 int rv = XT_CB_HANDLED;
4015 char *url = args->s;
4016 struct undo *u;
4017 struct tab *tt;
4019 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4021 if (t == NULL)
4022 return (XT_CB_PASSTHROUGH);
4024 switch (args->i) {
4025 case XT_TAB_NEW:
4026 if (strlen(url) > 0)
4027 create_new_tab(url, NULL, 1, args->p);
4028 else
4029 create_new_tab(NULL, NULL, 1, args->p);
4030 break;
4031 case XT_TAB_DELETE:
4032 if (args->p < 0)
4033 delete_tab(t);
4034 else
4035 TAILQ_FOREACH(tt, &tabs, entry)
4036 if (tt->tab_id == args->p - 1) {
4037 delete_tab(tt);
4038 break;
4040 break;
4041 case XT_TAB_DELQUIT:
4042 if (gtk_notebook_get_n_pages(notebook) > 1)
4043 delete_tab(t);
4044 else
4045 quit(t, args);
4046 break;
4047 case XT_TAB_OPEN:
4048 if (strlen(url) > 0)
4050 else {
4051 rv = XT_CB_PASSTHROUGH;
4052 goto done;
4054 load_uri(t, url);
4055 break;
4056 case XT_TAB_SHOW:
4057 if (show_tabs == 0) {
4058 show_tabs = 1;
4059 notebook_tab_set_visibility();
4061 break;
4062 case XT_TAB_HIDE:
4063 if (show_tabs == 1) {
4064 show_tabs = 0;
4065 notebook_tab_set_visibility();
4067 break;
4068 case XT_TAB_NEXTSTYLE:
4069 if (tab_style == XT_TABS_NORMAL) {
4070 tab_style = XT_TABS_COMPACT;
4071 recolor_compact_tabs();
4073 else
4074 tab_style = XT_TABS_NORMAL;
4075 notebook_tab_set_visibility();
4076 break;
4077 case XT_TAB_UNDO_CLOSE:
4078 if (undo_count == 0) {
4079 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4080 __func__);
4081 goto done;
4082 } else {
4083 undo_count--;
4084 u = TAILQ_FIRST(&undos);
4085 create_new_tab(u->uri, u, 1, -1);
4087 TAILQ_REMOVE(&undos, u, entry);
4088 g_free(u->uri);
4089 /* u->history is freed in create_new_tab() */
4090 g_free(u);
4092 break;
4093 default:
4094 rv = XT_CB_PASSTHROUGH;
4095 goto done;
4098 done:
4099 if (args->s) {
4100 g_free(args->s);
4101 args->s = NULL;
4104 return (rv);
4108 resizetab(struct tab *t, struct karg *args)
4110 if (t == NULL || args == NULL) {
4111 show_oops(NULL, "resizetab invalid parameters");
4112 return (XT_CB_PASSTHROUGH);
4115 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4116 t->tab_id, args->i);
4118 setzoom_webkit(t, args->i);
4120 return (XT_CB_HANDLED);
4124 movetab(struct tab *t, struct karg *args)
4126 int n, dest;
4128 if (t == NULL || args == NULL) {
4129 show_oops(NULL, "movetab invalid parameters");
4130 return (XT_CB_PASSTHROUGH);
4133 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4134 t->tab_id, args->i);
4136 if (args->i >= XT_TAB_INVALID)
4137 return (XT_CB_PASSTHROUGH);
4139 if (TAILQ_EMPTY(&tabs))
4140 return (XT_CB_PASSTHROUGH);
4142 n = gtk_notebook_get_n_pages(notebook);
4143 dest = gtk_notebook_get_current_page(notebook);
4145 switch (args->i) {
4146 case XT_TAB_NEXT:
4147 if (args->p < 0)
4148 dest = dest == n - 1 ? 0 : dest + 1;
4149 else
4150 dest = args->p - 1;
4152 break;
4153 case XT_TAB_PREV:
4154 if (args->p < 0)
4155 dest -= 1;
4156 else
4157 dest -= args->p % n;
4159 if (dest < 0)
4160 dest += n;
4162 break;
4163 case XT_TAB_FIRST:
4164 dest = 0;
4165 break;
4166 case XT_TAB_LAST:
4167 dest = n - 1;
4168 break;
4169 default:
4170 return (XT_CB_PASSTHROUGH);
4173 if (dest < 0 || dest >= n)
4174 return (XT_CB_PASSTHROUGH);
4175 if (t->tab_id == dest) {
4176 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4177 return (XT_CB_HANDLED);
4180 set_current_tab(dest);
4182 return (XT_CB_HANDLED);
4185 int cmd_prefix = 0;
4188 command(struct tab *t, struct karg *args)
4190 char *s = NULL, *ss = NULL;
4191 GdkColor color;
4192 const gchar *uri;
4194 if (t == NULL || args == NULL) {
4195 show_oops(NULL, "command invalid parameters");
4196 return (XT_CB_PASSTHROUGH);
4199 switch (args->i) {
4200 case '/':
4201 s = "/";
4202 break;
4203 case '?':
4204 s = "?";
4205 break;
4206 case ':':
4207 if (cmd_prefix == 0)
4208 s = ":";
4209 else {
4210 ss = g_strdup_printf(":%d", cmd_prefix);
4211 s = ss;
4212 cmd_prefix = 0;
4214 break;
4215 case XT_CMD_OPEN:
4216 s = ":open ";
4217 break;
4218 case XT_CMD_TABNEW:
4219 s = ":tabnew ";
4220 break;
4221 case XT_CMD_OPEN_CURRENT:
4222 s = ":open ";
4223 /* FALL THROUGH */
4224 case XT_CMD_TABNEW_CURRENT:
4225 if (!s) /* FALL THROUGH? */
4226 s = ":tabnew ";
4227 if ((uri = get_uri(t)) != NULL) {
4228 ss = g_strdup_printf("%s%s", s, uri);
4229 s = ss;
4231 break;
4232 default:
4233 show_oops(t, "command: invalid opcode %d", args->i);
4234 return (XT_CB_PASSTHROUGH);
4237 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4239 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4240 gdk_color_parse(XT_COLOR_WHITE, &color);
4241 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4242 show_cmd(t);
4243 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4244 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4246 if (ss)
4247 g_free(ss);
4249 return (XT_CB_HANDLED);
4253 * Return a new string with a download row (in html)
4254 * appended. Old string is freed.
4256 char *
4257 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4260 WebKitDownloadStatus stat;
4261 char *status_html = NULL, *cmd_html = NULL, *new_html;
4262 gdouble progress;
4263 char cur_sz[FMT_SCALED_STRSIZE];
4264 char tot_sz[FMT_SCALED_STRSIZE];
4265 char *xtp_prefix;
4267 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4269 /* All actions wil take this form:
4270 * xxxt://class/seskey
4272 xtp_prefix = g_strdup_printf("%s%d/%s/",
4273 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4275 stat = webkit_download_get_status(dl->download);
4277 switch (stat) {
4278 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4279 status_html = g_strdup_printf("Finished");
4280 cmd_html = g_strdup_printf(
4281 "<a href='%s%d/%d'>Remove</a>",
4282 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4283 break;
4284 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4285 /* gather size info */
4286 progress = 100 * webkit_download_get_progress(dl->download);
4288 fmt_scaled(
4289 webkit_download_get_current_size(dl->download), cur_sz);
4290 fmt_scaled(
4291 webkit_download_get_total_size(dl->download), tot_sz);
4293 status_html = g_strdup_printf(
4294 "<div style='width: 100%%' align='center'>"
4295 "<div class='progress-outer'>"
4296 "<div class='progress-inner' style='width: %.2f%%'>"
4297 "</div></div></div>"
4298 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4299 progress, cur_sz, tot_sz, progress);
4301 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4302 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4304 break;
4305 /* LLL */
4306 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4307 status_html = g_strdup_printf("Cancelled");
4308 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4309 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4310 break;
4311 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4312 status_html = g_strdup_printf("Error!");
4313 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4314 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4315 break;
4316 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4317 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4318 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4319 status_html = g_strdup_printf("Starting");
4320 break;
4321 default:
4322 show_oops(t, "%s: unknown download status", __func__);
4325 new_html = g_strdup_printf(
4326 "%s\n<tr><td>%s</td><td>%s</td>"
4327 "<td style='text-align:center'>%s</td></tr>\n",
4328 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4329 status_html, cmd_html);
4330 g_free(html);
4332 if (status_html)
4333 g_free(status_html);
4335 if (cmd_html)
4336 g_free(cmd_html);
4338 g_free(xtp_prefix);
4340 return new_html;
4344 * update all download tabs apart from one. Pass NULL if
4345 * you want to update all.
4347 void
4348 update_download_tabs(struct tab *apart_from)
4350 struct tab *t;
4351 if (!updating_dl_tabs) {
4352 updating_dl_tabs = 1; /* stop infinite recursion */
4353 TAILQ_FOREACH(t, &tabs, entry)
4354 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4355 && (t != apart_from))
4356 xtp_page_dl(t, NULL);
4357 updating_dl_tabs = 0;
4362 * update all cookie tabs apart from one. Pass NULL if
4363 * you want to update all.
4365 void
4366 update_cookie_tabs(struct tab *apart_from)
4368 struct tab *t;
4369 if (!updating_cl_tabs) {
4370 updating_cl_tabs = 1; /* stop infinite recursion */
4371 TAILQ_FOREACH(t, &tabs, entry)
4372 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4373 && (t != apart_from))
4374 xtp_page_cl(t, NULL);
4375 updating_cl_tabs = 0;
4380 * update all history tabs apart from one. Pass NULL if
4381 * you want to update all.
4383 void
4384 update_history_tabs(struct tab *apart_from)
4386 struct tab *t;
4388 if (!updating_hl_tabs) {
4389 updating_hl_tabs = 1; /* stop infinite recursion */
4390 TAILQ_FOREACH(t, &tabs, entry)
4391 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4392 && (t != apart_from))
4393 xtp_page_hl(t, NULL);
4394 updating_hl_tabs = 0;
4398 /* cookie management XTP page */
4400 xtp_page_cl(struct tab *t, struct karg *args)
4402 char *body, *page, *tmp;
4403 int i = 1; /* all ids start 1 */
4404 GSList *sc, *pc, *pc_start;
4405 SoupCookie *c;
4406 char *type, *table_headers, *last_domain;
4408 DNPRINTF(XT_D_CMD, "%s", __func__);
4410 if (t == NULL) {
4411 show_oops(NULL, "%s invalid parameters", __func__);
4412 return (1);
4415 /* Generate a new session key */
4416 if (!updating_cl_tabs)
4417 generate_xtp_session_key(&cl_session_key);
4419 /* table headers */
4420 table_headers = g_strdup_printf("<table><tr>"
4421 "<th>Type</th>"
4422 "<th>Name</th>"
4423 "<th style='width:200px'>Value</th>"
4424 "<th>Path</th>"
4425 "<th>Expires</th>"
4426 "<th>Secure</th>"
4427 "<th>HTTP<br />only</th>"
4428 "<th style='width:40px'>Rm</th></tr>\n");
4430 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4431 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4432 pc_start = pc;
4434 body = NULL;
4435 last_domain = strdup("");
4436 for (; sc; sc = sc->next) {
4437 c = sc->data;
4439 if (strcmp(last_domain, c->domain) != 0) {
4440 /* new domain */
4441 free(last_domain);
4442 last_domain = strdup(c->domain);
4444 if (body != NULL) {
4445 tmp = body;
4446 body = g_strdup_printf("%s</table>"
4447 "<h2>%s</h2>%s\n",
4448 body, c->domain, table_headers);
4449 g_free(tmp);
4450 } else {
4451 /* first domain */
4452 body = g_strdup_printf("<h2>%s</h2>%s\n",
4453 c->domain, table_headers);
4457 type = "Session";
4458 for (pc = pc_start; pc; pc = pc->next)
4459 if (soup_cookie_equal(pc->data, c)) {
4460 type = "Session + Persistent";
4461 break;
4464 tmp = body;
4465 body = g_strdup_printf(
4466 "%s\n<tr>"
4467 "<td>%s</td>"
4468 "<td style='word-wrap:normal'>%s</td>"
4469 "<td>"
4470 " <textarea rows='4'>%s</textarea>"
4471 "</td>"
4472 "<td>%s</td>"
4473 "<td>%s</td>"
4474 "<td>%d</td>"
4475 "<td>%d</td>"
4476 "<td style='text-align:center'>"
4477 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4478 body,
4479 type,
4480 c->name,
4481 c->value,
4482 c->path,
4483 c->expires ?
4484 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4485 c->secure,
4486 c->http_only,
4488 XT_XTP_STR,
4489 XT_XTP_CL,
4490 cl_session_key,
4491 XT_XTP_CL_REMOVE,
4495 g_free(tmp);
4496 i++;
4499 soup_cookies_free(sc);
4500 soup_cookies_free(pc);
4502 /* small message if there are none */
4503 if (i == 1) {
4504 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4505 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4507 tmp = body;
4508 body = g_strdup_printf("%s</table>", body);
4509 g_free(tmp);
4511 page = get_html_page("Cookie Jar", body, "", TRUE);
4512 g_free(body);
4513 g_free(table_headers);
4514 g_free(last_domain);
4516 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4517 update_cookie_tabs(t);
4519 g_free(page);
4521 return (0);
4525 xtp_page_hl(struct tab *t, struct karg *args)
4527 char *body, *page, *tmp;
4528 struct history *h;
4529 int i = 1; /* all ids start 1 */
4531 DNPRINTF(XT_D_CMD, "%s", __func__);
4533 if (t == NULL) {
4534 show_oops(NULL, "%s invalid parameters", __func__);
4535 return (1);
4538 /* Generate a new session key */
4539 if (!updating_hl_tabs)
4540 generate_xtp_session_key(&hl_session_key);
4542 /* body */
4543 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4544 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4546 RB_FOREACH_REVERSE(h, history_list, &hl) {
4547 tmp = body;
4548 body = g_strdup_printf(
4549 "%s\n<tr>"
4550 "<td><a href='%s'>%s</a></td>"
4551 "<td>%s</td>"
4552 "<td style='text-align: center'>"
4553 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4554 body, h->uri, h->uri, h->title,
4555 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4556 XT_XTP_HL_REMOVE, i);
4558 g_free(tmp);
4559 i++;
4562 /* small message if there are none */
4563 if (i == 1) {
4564 tmp = body;
4565 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4566 "colspan='3'>No History</td></tr>\n", body);
4567 g_free(tmp);
4570 tmp = body;
4571 body = g_strdup_printf("%s</table>", body);
4572 g_free(tmp);
4574 page = get_html_page("History", body, "", TRUE);
4575 g_free(body);
4578 * update all history manager tabs as the xtp session
4579 * key has now changed. No need to update the current tab.
4580 * Already did that above.
4582 update_history_tabs(t);
4584 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4585 g_free(page);
4587 return (0);
4591 * Generate a web page detailing the status of any downloads
4594 xtp_page_dl(struct tab *t, struct karg *args)
4596 struct download *dl;
4597 char *body, *page, *tmp;
4598 char *ref;
4599 int n_dl = 1;
4601 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4603 if (t == NULL) {
4604 show_oops(NULL, "%s invalid parameters", __func__);
4605 return (1);
4609 * Generate a new session key for next page instance.
4610 * This only happens for the top level call to xtp_page_dl()
4611 * in which case updating_dl_tabs is 0.
4613 if (!updating_dl_tabs)
4614 generate_xtp_session_key(&dl_session_key);
4616 /* header - with refresh so as to update */
4617 if (refresh_interval >= 1)
4618 ref = g_strdup_printf(
4619 "<meta http-equiv='refresh' content='%u"
4620 ";url=%s%d/%s/%d' />\n",
4621 refresh_interval,
4622 XT_XTP_STR,
4623 XT_XTP_DL,
4624 dl_session_key,
4625 XT_XTP_DL_LIST);
4626 else
4627 ref = g_strdup("");
4629 body = g_strdup_printf("<div align='center'>"
4630 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4631 "</p><table><tr><th style='width: 60%%'>"
4632 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4633 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4635 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4636 body = xtp_page_dl_row(t, body, dl);
4637 n_dl++;
4640 /* message if no downloads in list */
4641 if (n_dl == 1) {
4642 tmp = body;
4643 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4644 " style='text-align: center'>"
4645 "No downloads</td></tr>\n", body);
4646 g_free(tmp);
4649 tmp = body;
4650 body = g_strdup_printf("%s</table></div>", body);
4651 g_free(tmp);
4653 page = get_html_page("Downloads", body, ref, 1);
4654 g_free(ref);
4655 g_free(body);
4658 * update all download manager tabs as the xtp session
4659 * key has now changed. No need to update the current tab.
4660 * Already did that above.
4662 update_download_tabs(t);
4664 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4665 g_free(page);
4667 return (0);
4671 search(struct tab *t, struct karg *args)
4673 gboolean d;
4675 if (t == NULL || args == NULL) {
4676 show_oops(NULL, "search invalid parameters");
4677 return (1);
4679 if (t->search_text == NULL) {
4680 if (global_search == NULL)
4681 return (XT_CB_PASSTHROUGH);
4682 else {
4683 t->search_text = g_strdup(global_search);
4684 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4685 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4689 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4690 t->tab_id, args->i, t->search_forward, t->search_text);
4692 switch (args->i) {
4693 case XT_SEARCH_NEXT:
4694 d = t->search_forward;
4695 break;
4696 case XT_SEARCH_PREV:
4697 d = !t->search_forward;
4698 break;
4699 default:
4700 return (XT_CB_PASSTHROUGH);
4703 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4705 return (XT_CB_HANDLED);
4708 struct settings_args {
4709 char **body;
4710 int i;
4713 void
4714 print_setting(struct settings *s, char *val, void *cb_args)
4716 char *tmp, *color;
4717 struct settings_args *sa = cb_args;
4719 if (sa == NULL)
4720 return;
4722 if (s->flags & XT_SF_RUNTIME)
4723 color = "#22cc22";
4724 else
4725 color = "#cccccc";
4727 tmp = *sa->body;
4728 *sa->body = g_strdup_printf(
4729 "%s\n<tr>"
4730 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4731 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4732 *sa->body,
4733 color,
4734 s->name,
4735 color,
4738 g_free(tmp);
4739 sa->i++;
4743 set(struct tab *t, struct karg *args)
4745 char *body, *page, *tmp;
4746 int i = 1;
4747 struct settings_args sa;
4749 bzero(&sa, sizeof sa);
4750 sa.body = &body;
4752 /* body */
4753 body = g_strdup_printf("<div align='center'><table><tr>"
4754 "<th align='left'>Setting</th>"
4755 "<th align='left'>Value</th></tr>\n");
4757 settings_walk(print_setting, &sa);
4758 i = sa.i;
4760 /* small message if there are none */
4761 if (i == 1) {
4762 tmp = body;
4763 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4764 "colspan='2'>No settings</td></tr>\n", body);
4765 g_free(tmp);
4768 tmp = body;
4769 body = g_strdup_printf("%s</table></div>", body);
4770 g_free(tmp);
4772 page = get_html_page("Settings", body, "", 0);
4774 g_free(body);
4776 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4778 g_free(page);
4780 return (XT_CB_PASSTHROUGH);
4784 session_save(struct tab *t, char *filename)
4786 struct karg a;
4787 int rv = 1;
4789 if (strlen(filename) == 0)
4790 goto done;
4792 if (filename[0] == '.' || filename[0] == '/')
4793 goto done;
4795 a.s = filename;
4796 if (save_tabs(t, &a))
4797 goto done;
4798 strlcpy(named_session, filename, sizeof named_session);
4800 rv = 0;
4801 done:
4802 return (rv);
4806 session_open(struct tab *t, char *filename)
4808 struct karg a;
4809 int rv = 1;
4811 if (strlen(filename) == 0)
4812 goto done;
4814 if (filename[0] == '.' || filename[0] == '/')
4815 goto done;
4817 a.s = filename;
4818 a.i = XT_SES_CLOSETABS;
4819 if (open_tabs(t, &a))
4820 goto done;
4822 strlcpy(named_session, filename, sizeof named_session);
4824 rv = 0;
4825 done:
4826 return (rv);
4830 session_delete(struct tab *t, char *filename)
4832 char file[PATH_MAX];
4833 int rv = 1;
4835 if (strlen(filename) == 0)
4836 goto done;
4838 if (filename[0] == '.' || filename[0] == '/')
4839 goto done;
4841 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4842 if (unlink(file))
4843 goto done;
4845 if (!strcmp(filename, named_session))
4846 strlcpy(named_session, XT_SAVED_TABS_FILE,
4847 sizeof named_session);
4849 rv = 0;
4850 done:
4851 return (rv);
4855 session_cmd(struct tab *t, struct karg *args)
4857 char *filename = args->s;
4859 if (t == NULL)
4860 return (1);
4862 if (args->i & XT_SHOW)
4863 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4864 XT_SAVED_TABS_FILE : named_session);
4865 else if (args->i & XT_SAVE) {
4866 if (session_save(t, filename)) {
4867 show_oops(t, "Can't save session: %s",
4868 filename ? filename : "INVALID");
4869 goto done;
4871 } else if (args->i & XT_OPEN) {
4872 if (session_open(t, filename)) {
4873 show_oops(t, "Can't open session: %s",
4874 filename ? filename : "INVALID");
4875 goto done;
4877 } else if (args->i & XT_DELETE) {
4878 if (session_delete(t, filename)) {
4879 show_oops(t, "Can't delete session: %s",
4880 filename ? filename : "INVALID");
4881 goto done;
4884 done:
4885 return (XT_CB_PASSTHROUGH);
4889 * Make a hardcopy of the page
4892 print_page(struct tab *t, struct karg *args)
4894 WebKitWebFrame *frame;
4895 GtkPageSetup *ps;
4896 GtkPrintOperation *op;
4897 GtkPrintOperationAction action;
4898 GtkPrintOperationResult print_res;
4899 GError *g_err = NULL;
4900 int marg_l, marg_r, marg_t, marg_b;
4902 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4904 ps = gtk_page_setup_new();
4905 op = gtk_print_operation_new();
4906 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4907 frame = webkit_web_view_get_main_frame(t->wv);
4909 /* the default margins are too small, so we will bump them */
4910 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4911 XT_PRINT_EXTRA_MARGIN;
4912 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4913 XT_PRINT_EXTRA_MARGIN;
4914 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4915 XT_PRINT_EXTRA_MARGIN;
4916 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4917 XT_PRINT_EXTRA_MARGIN;
4919 /* set margins */
4920 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4921 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4922 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4923 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4925 gtk_print_operation_set_default_page_setup(op, ps);
4927 /* this appears to free 'op' and 'ps' */
4928 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4930 /* check it worked */
4931 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4932 show_oops(NULL, "can't print: %s", g_err->message);
4933 g_error_free (g_err);
4934 return (1);
4937 return (0);
4941 go_home(struct tab *t, struct karg *args)
4943 load_uri(t, home);
4944 return (0);
4948 restart(struct tab *t, struct karg *args)
4950 struct karg a;
4952 a.s = XT_RESTART_TABS_FILE;
4953 save_tabs(t, &a);
4954 execvp(start_argv[0], start_argv);
4955 /* NOTREACHED */
4957 return (0);
4960 #define CTRL GDK_CONTROL_MASK
4961 #define MOD1 GDK_MOD1_MASK
4962 #define SHFT GDK_SHIFT_MASK
4964 /* inherent to GTK not all keys will be caught at all times */
4965 /* XXX sort key bindings */
4966 struct key_binding {
4967 char *cmd;
4968 guint mask;
4969 guint use_in_entry;
4970 guint key;
4971 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4972 } keys[] = {
4973 { "cookiejar", MOD1, 0, GDK_j },
4974 { "downloadmgr", MOD1, 0, GDK_d },
4975 { "history", MOD1, 0, GDK_h },
4976 { "print", CTRL, 0, GDK_p },
4977 { "search", 0, 0, GDK_slash },
4978 { "searchb", 0, 0, GDK_question },
4979 { "statustoggle", CTRL, 0, GDK_n },
4980 { "command", 0, 0, GDK_colon },
4981 { "qa", CTRL, 0, GDK_q },
4982 { "restart", MOD1, 0, GDK_q },
4983 { "js toggle", CTRL, 0, GDK_j },
4984 { "cookie toggle", MOD1, 0, GDK_c },
4985 { "togglesrc", CTRL, 0, GDK_s },
4986 { "yankuri", 0, 0, GDK_y },
4987 { "pasteuricur", 0, 0, GDK_p },
4988 { "pasteurinew", 0, 0, GDK_P },
4989 { "toplevel toggle", 0, 0, GDK_F4 },
4990 { "help", 0, 0, GDK_F1 },
4991 { "run_script", MOD1, 0, GDK_r },
4993 /* search */
4994 { "searchnext", 0, 0, GDK_n },
4995 { "searchprevious", 0, 0, GDK_N },
4997 /* focus */
4998 { "focusaddress", 0, 0, GDK_F6 },
4999 { "focussearch", 0, 0, GDK_F7 },
5001 /* hinting */
5002 { "hinting", 0, 0, GDK_f },
5004 /* custom stylesheet */
5005 { "userstyle", 0, 0, GDK_i },
5007 /* navigation */
5008 { "goback", 0, 0, GDK_BackSpace },
5009 { "goback", MOD1, 0, GDK_Left },
5010 { "goforward", SHFT, 0, GDK_BackSpace },
5011 { "goforward", MOD1, 0, GDK_Right },
5012 { "reload", 0, 0, GDK_F5 },
5013 { "reload", CTRL, 0, GDK_r },
5014 { "reload", CTRL, 0, GDK_l },
5015 { "favorites", MOD1, 1, GDK_f },
5017 /* vertical movement */
5018 { "scrolldown", 0, 0, GDK_j },
5019 { "scrolldown", 0, 0, GDK_Down },
5020 { "scrollup", 0, 0, GDK_Up },
5021 { "scrollup", 0, 0, GDK_k },
5022 { "scrollbottom", 0, 0, GDK_G },
5023 { "scrollbottom", 0, 0, GDK_End },
5024 { "scrolltop", 0, 0, GDK_Home },
5025 { "scrollpagedown", 0, 0, GDK_space },
5026 { "scrollpagedown", CTRL, 0, GDK_f },
5027 { "scrollhalfdown", CTRL, 0, GDK_d },
5028 { "scrollpagedown", 0, 0, GDK_Page_Down },
5029 { "scrollpageup", 0, 0, GDK_Page_Up },
5030 { "scrollpageup", CTRL, 0, GDK_b },
5031 { "scrollhalfup", CTRL, 0, GDK_u },
5032 /* horizontal movement */
5033 { "scrollright", 0, 0, GDK_l },
5034 { "scrollright", 0, 0, GDK_Right },
5035 { "scrollleft", 0, 0, GDK_Left },
5036 { "scrollleft", 0, 0, GDK_h },
5037 { "scrollfarright", 0, 0, GDK_dollar },
5038 { "scrollfarleft", 0, 0, GDK_0 },
5040 /* tabs */
5041 { "tabnew", CTRL, 0, GDK_t },
5042 { "999tabnew", CTRL, 0, GDK_T },
5043 { "tabclose", CTRL, 1, GDK_w },
5044 { "tabundoclose", 0, 0, GDK_U },
5045 { "tabnext 1", CTRL, 0, GDK_1 },
5046 { "tabnext 2", CTRL, 0, GDK_2 },
5047 { "tabnext 3", CTRL, 0, GDK_3 },
5048 { "tabnext 4", CTRL, 0, GDK_4 },
5049 { "tabnext 5", CTRL, 0, GDK_5 },
5050 { "tabnext 6", CTRL, 0, GDK_6 },
5051 { "tabnext 7", CTRL, 0, GDK_7 },
5052 { "tabnext 8", CTRL, 0, GDK_8 },
5053 { "tabnext 9", CTRL, 0, GDK_9 },
5054 { "tabfirst", CTRL, 0, GDK_less },
5055 { "tablast", CTRL, 0, GDK_greater },
5056 { "tabprevious", CTRL, 0, GDK_Left },
5057 { "tabnext", CTRL, 0, GDK_Right },
5058 { "focusout", CTRL, 0, GDK_minus },
5059 { "focusin", CTRL, 0, GDK_plus },
5060 { "focusin", CTRL, 0, GDK_equal },
5061 { "focusreset", CTRL, 0, GDK_0 },
5063 /* command aliases (handy when -S flag is used) */
5064 { "promptopen", 0, 0, GDK_F9 },
5065 { "promptopencurrent", 0, 0, GDK_F10 },
5066 { "prompttabnew", 0, 0, GDK_F11 },
5067 { "prompttabnewcurrent",0, 0, GDK_F12 },
5069 TAILQ_HEAD(keybinding_list, key_binding);
5071 void
5072 walk_kb(struct settings *s,
5073 void (*cb)(struct settings *, char *, void *), void *cb_args)
5075 struct key_binding *k;
5076 char str[1024];
5078 if (s == NULL || cb == NULL) {
5079 show_oops(NULL, "walk_kb invalid parameters");
5080 return;
5083 TAILQ_FOREACH(k, &kbl, entry) {
5084 if (k->cmd == NULL)
5085 continue;
5086 str[0] = '\0';
5088 /* sanity */
5089 if (gdk_keyval_name(k->key) == NULL)
5090 continue;
5092 strlcat(str, k->cmd, sizeof str);
5093 strlcat(str, ",", sizeof str);
5095 if (k->mask & GDK_SHIFT_MASK)
5096 strlcat(str, "S-", sizeof str);
5097 if (k->mask & GDK_CONTROL_MASK)
5098 strlcat(str, "C-", sizeof str);
5099 if (k->mask & GDK_MOD1_MASK)
5100 strlcat(str, "M1-", sizeof str);
5101 if (k->mask & GDK_MOD2_MASK)
5102 strlcat(str, "M2-", sizeof str);
5103 if (k->mask & GDK_MOD3_MASK)
5104 strlcat(str, "M3-", sizeof str);
5105 if (k->mask & GDK_MOD4_MASK)
5106 strlcat(str, "M4-", sizeof str);
5107 if (k->mask & GDK_MOD5_MASK)
5108 strlcat(str, "M5-", sizeof str);
5110 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5111 cb(s, str, cb_args);
5115 void
5116 init_keybindings(void)
5118 int i;
5119 struct key_binding *k;
5121 for (i = 0; i < LENGTH(keys); i++) {
5122 k = g_malloc0(sizeof *k);
5123 k->cmd = keys[i].cmd;
5124 k->mask = keys[i].mask;
5125 k->use_in_entry = keys[i].use_in_entry;
5126 k->key = keys[i].key;
5127 TAILQ_INSERT_HEAD(&kbl, k, entry);
5129 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5130 k->cmd ? k->cmd : "unnamed key");
5134 void
5135 keybinding_clearall(void)
5137 struct key_binding *k, *next;
5139 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5140 next = TAILQ_NEXT(k, entry);
5141 if (k->cmd == NULL)
5142 continue;
5144 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5145 k->cmd ? k->cmd : "unnamed key");
5146 TAILQ_REMOVE(&kbl, k, entry);
5147 g_free(k);
5152 keybinding_add(char *cmd, char *key, int use_in_entry)
5154 struct key_binding *k;
5155 guint keyval, mask = 0;
5156 int i;
5158 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5160 /* Keys which are to be used in entry have been prefixed with an
5161 * exclamation mark. */
5162 if (use_in_entry)
5163 key++;
5165 /* find modifier keys */
5166 if (strstr(key, "S-"))
5167 mask |= GDK_SHIFT_MASK;
5168 if (strstr(key, "C-"))
5169 mask |= GDK_CONTROL_MASK;
5170 if (strstr(key, "M1-"))
5171 mask |= GDK_MOD1_MASK;
5172 if (strstr(key, "M2-"))
5173 mask |= GDK_MOD2_MASK;
5174 if (strstr(key, "M3-"))
5175 mask |= GDK_MOD3_MASK;
5176 if (strstr(key, "M4-"))
5177 mask |= GDK_MOD4_MASK;
5178 if (strstr(key, "M5-"))
5179 mask |= GDK_MOD5_MASK;
5181 /* find keyname */
5182 for (i = strlen(key) - 1; i > 0; i--)
5183 if (key[i] == '-')
5184 key = &key[i + 1];
5186 /* validate keyname */
5187 keyval = gdk_keyval_from_name(key);
5188 if (keyval == GDK_VoidSymbol) {
5189 warnx("invalid keybinding name %s", key);
5190 return (1);
5192 /* must run this test too, gtk+ doesn't handle 10 for example */
5193 if (gdk_keyval_name(keyval) == NULL) {
5194 warnx("invalid keybinding name %s", key);
5195 return (1);
5198 /* Remove eventual dupes. */
5199 TAILQ_FOREACH(k, &kbl, entry)
5200 if (k->key == keyval && k->mask == mask) {
5201 TAILQ_REMOVE(&kbl, k, entry);
5202 g_free(k);
5203 break;
5206 /* add keyname */
5207 k = g_malloc0(sizeof *k);
5208 k->cmd = g_strdup(cmd);
5209 k->mask = mask;
5210 k->use_in_entry = use_in_entry;
5211 k->key = keyval;
5213 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5214 k->cmd,
5215 k->mask,
5216 k->use_in_entry,
5217 k->key);
5218 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5219 k->cmd, gdk_keyval_name(keyval));
5221 TAILQ_INSERT_HEAD(&kbl, k, entry);
5223 return (0);
5227 add_kb(struct settings *s, char *entry)
5229 char *kb, *key;
5231 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5233 /* clearall is special */
5234 if (!strcmp(entry, "clearall")) {
5235 keybinding_clearall();
5236 return (0);
5239 kb = strstr(entry, ",");
5240 if (kb == NULL)
5241 return (1);
5242 *kb = '\0';
5243 key = kb + 1;
5245 return (keybinding_add(entry, key, key[0] == '!'));
5248 struct cmd {
5249 char *cmd;
5250 int level;
5251 int (*func)(struct tab *, struct karg *);
5252 int arg;
5253 int type;
5254 } cmds[] = {
5255 { "command", 0, command, ':', 0 },
5256 { "search", 0, command, '/', 0 },
5257 { "searchb", 0, command, '?', 0 },
5258 { "togglesrc", 0, toggle_src, 0, 0 },
5260 /* yanking and pasting */
5261 { "yankuri", 0, yank_uri, 0, 0 },
5262 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5263 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5264 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5266 /* search */
5267 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5268 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5270 /* focus */
5271 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5272 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5274 /* hinting */
5275 { "hinting", 0, hint, 0, 0 },
5277 /* custom stylesheet */
5278 { "userstyle", 0, userstyle, 0, 0 },
5280 /* navigation */
5281 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5282 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5283 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5285 /* vertical movement */
5286 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5287 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5288 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5289 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5290 { "1", 0, move, XT_MOVE_TOP, 0 },
5291 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5292 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5293 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5294 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5295 /* horizontal movement */
5296 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5297 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5298 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5299 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5301 { "favorites", 0, xtp_page_fl, 0, 0 },
5302 { "fav", 0, xtp_page_fl, 0, 0 },
5303 { "favadd", 0, add_favorite, 0, 0 },
5305 { "qall", 0, quit, 0, 0 },
5306 { "quitall", 0, quit, 0, 0 },
5307 { "w", 0, save_tabs, 0, 0 },
5308 { "wq", 0, save_tabs_and_quit, 0, 0 },
5309 { "help", 0, help, 0, 0 },
5310 { "about", 0, about, 0, 0 },
5311 { "stats", 0, stats, 0, 0 },
5312 { "version", 0, about, 0, 0 },
5314 /* js command */
5315 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5316 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5317 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5318 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5319 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5320 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5321 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5322 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5323 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5324 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5325 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5327 /* cookie command */
5328 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5329 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5330 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5331 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5332 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5333 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5334 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5335 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5336 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5337 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5338 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5340 /* toplevel (domain) command */
5341 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5342 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5344 /* cookie jar */
5345 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5347 /* cert command */
5348 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5349 { "save", 1, cert_cmd, XT_SAVE, 0 },
5350 { "show", 1, cert_cmd, XT_SHOW, 0 },
5352 { "ca", 0, ca_cmd, 0, 0 },
5353 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5354 { "dl", 0, xtp_page_dl, 0, 0 },
5355 { "h", 0, xtp_page_hl, 0, 0 },
5356 { "history", 0, xtp_page_hl, 0, 0 },
5357 { "home", 0, go_home, 0, 0 },
5358 { "restart", 0, restart, 0, 0 },
5359 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5360 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5361 { "statustoggle", 0, statustoggle, 0, 0 },
5362 { "run_script", 0, run_page_script, 0, XT_USERARG },
5364 { "print", 0, print_page, 0, 0 },
5366 /* tabs */
5367 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5368 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5369 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5370 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5371 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5372 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5373 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5374 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5375 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5376 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5377 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5378 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5379 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5380 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5381 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5382 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5383 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5384 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5385 { "buffers", 0, buffers, 0, 0 },
5386 { "ls", 0, buffers, 0, 0 },
5387 { "tabs", 0, buffers, 0, 0 },
5389 /* command aliases (handy when -S flag is used) */
5390 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5391 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5392 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5393 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5395 /* settings */
5396 { "set", 0, set, 0, 0 },
5397 { "fullscreen", 0, fullscreen, 0, 0 },
5398 { "f", 0, fullscreen, 0, 0 },
5400 /* sessions */
5401 { "session", 0, session_cmd, XT_SHOW, 0 },
5402 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5403 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5404 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5405 { "show", 1, session_cmd, XT_SHOW, 0 },
5408 struct {
5409 int index;
5410 int len;
5411 gchar *list[256];
5412 } cmd_status = {-1, 0};
5414 gboolean
5415 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5418 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5419 btn_down = 0;
5421 return (FALSE);
5424 gboolean
5425 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5427 struct karg a;
5429 hide_oops(t);
5430 hide_buffers(t);
5432 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5433 btn_down = 1;
5434 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5435 /* go backward */
5436 a.i = XT_NAV_BACK;
5437 navaction(t, &a);
5439 return (TRUE);
5440 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5441 /* go forward */
5442 a.i = XT_NAV_FORWARD;
5443 navaction(t, &a);
5445 return (TRUE);
5448 return (FALSE);
5451 gboolean
5452 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5454 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5456 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5457 delete_tab(t);
5459 return (FALSE);
5463 * cancel, remove, etc. downloads
5465 void
5466 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5468 struct download find, *d = NULL;
5470 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5472 /* some commands require a valid download id */
5473 if (cmd != XT_XTP_DL_LIST) {
5474 /* lookup download in question */
5475 find.id = id;
5476 d = RB_FIND(download_list, &downloads, &find);
5478 if (d == NULL) {
5479 show_oops(t, "%s: no such download", __func__);
5480 return;
5484 /* decide what to do */
5485 switch (cmd) {
5486 case XT_XTP_DL_CANCEL:
5487 webkit_download_cancel(d->download);
5488 break;
5489 case XT_XTP_DL_REMOVE:
5490 webkit_download_cancel(d->download); /* just incase */
5491 g_object_unref(d->download);
5492 RB_REMOVE(download_list, &downloads, d);
5493 break;
5494 case XT_XTP_DL_LIST:
5495 /* Nothing */
5496 break;
5497 default:
5498 show_oops(t, "%s: unknown command", __func__);
5499 break;
5501 xtp_page_dl(t, NULL);
5505 * Actions on history, only does one thing for now, but
5506 * we provide the function for future actions
5508 void
5509 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5511 struct history *h, *next;
5512 int i = 1;
5514 switch (cmd) {
5515 case XT_XTP_HL_REMOVE:
5516 /* walk backwards, as listed in reverse */
5517 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5518 next = RB_PREV(history_list, &hl, h);
5519 if (id == i) {
5520 RB_REMOVE(history_list, &hl, h);
5521 g_free((gpointer) h->title);
5522 g_free((gpointer) h->uri);
5523 g_free(h);
5524 break;
5526 i++;
5528 break;
5529 case XT_XTP_HL_LIST:
5530 /* Nothing - just xtp_page_hl() below */
5531 break;
5532 default:
5533 show_oops(t, "%s: unknown command", __func__);
5534 break;
5537 xtp_page_hl(t, NULL);
5540 /* remove a favorite */
5541 void
5542 remove_favorite(struct tab *t, int index)
5544 char file[PATH_MAX], *title, *uri = NULL;
5545 char *new_favs, *tmp;
5546 FILE *f;
5547 int i;
5548 size_t len, lineno;
5550 /* open favorites */
5551 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5553 if ((f = fopen(file, "r")) == NULL) {
5554 show_oops(t, "%s: can't open favorites: %s",
5555 __func__, strerror(errno));
5556 return;
5559 /* build a string which will become the new favroites file */
5560 new_favs = g_strdup("");
5562 for (i = 1;;) {
5563 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5564 if (feof(f) || ferror(f))
5565 break;
5566 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5567 if (len == 0) {
5568 free(title);
5569 title = NULL;
5570 continue;
5573 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5574 if (feof(f) || ferror(f)) {
5575 show_oops(t, "%s: can't parse favorites %s",
5576 __func__, strerror(errno));
5577 goto clean;
5581 /* as long as this isn't the one we are deleting add to file */
5582 if (i != index) {
5583 tmp = new_favs;
5584 new_favs = g_strdup_printf("%s%s\n%s\n",
5585 new_favs, title, uri);
5586 g_free(tmp);
5589 free(uri);
5590 uri = NULL;
5591 free(title);
5592 title = NULL;
5593 i++;
5595 fclose(f);
5597 /* write back new favorites file */
5598 if ((f = fopen(file, "w")) == NULL) {
5599 show_oops(t, "%s: can't open favorites: %s",
5600 __func__, strerror(errno));
5601 goto clean;
5604 fwrite(new_favs, strlen(new_favs), 1, f);
5605 fclose(f);
5607 clean:
5608 if (uri)
5609 free(uri);
5610 if (title)
5611 free(title);
5613 g_free(new_favs);
5616 void
5617 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5619 switch (cmd) {
5620 case XT_XTP_FL_LIST:
5621 /* nothing, just the below call to xtp_page_fl() */
5622 break;
5623 case XT_XTP_FL_REMOVE:
5624 remove_favorite(t, arg);
5625 break;
5626 default:
5627 show_oops(t, "%s: invalid favorites command", __func__);
5628 break;
5631 xtp_page_fl(t, NULL);
5634 void
5635 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5637 switch (cmd) {
5638 case XT_XTP_CL_LIST:
5639 /* nothing, just xtp_page_cl() */
5640 break;
5641 case XT_XTP_CL_REMOVE:
5642 remove_cookie(arg);
5643 break;
5644 default:
5645 show_oops(t, "%s: unknown cookie xtp command", __func__);
5646 break;
5649 xtp_page_cl(t, NULL);
5652 /* link an XTP class to it's session key and handler function */
5653 struct xtp_despatch {
5654 uint8_t xtp_class;
5655 char **session_key;
5656 void (*handle_func)(struct tab *, uint8_t, int);
5659 struct xtp_despatch xtp_despatches[] = {
5660 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5661 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5662 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5663 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5664 { XT_XTP_INVALID, NULL, NULL }
5668 * is the url xtp protocol? (xxxt://)
5669 * if so, parse and despatch correct bahvior
5672 parse_xtp_url(struct tab *t, const char *url)
5674 char *dup = NULL, *p, *last;
5675 uint8_t n_tokens = 0;
5676 char *tokens[4] = {NULL, NULL, NULL, ""};
5677 struct xtp_despatch *dsp, *dsp_match = NULL;
5678 uint8_t req_class;
5679 int ret = FALSE;
5682 * tokens array meaning:
5683 * tokens[0] = class
5684 * tokens[1] = session key
5685 * tokens[2] = action
5686 * tokens[3] = optional argument
5689 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5691 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5692 goto clean;
5694 dup = g_strdup(url + strlen(XT_XTP_STR));
5696 /* split out the url */
5697 for ((p = strtok_r(dup, "/", &last)); p;
5698 (p = strtok_r(NULL, "/", &last))) {
5699 if (n_tokens < 4)
5700 tokens[n_tokens++] = p;
5703 /* should be atleast three fields 'class/seskey/command/arg' */
5704 if (n_tokens < 3)
5705 goto clean;
5707 dsp = xtp_despatches;
5708 req_class = atoi(tokens[0]);
5709 while (dsp->xtp_class) {
5710 if (dsp->xtp_class == req_class) {
5711 dsp_match = dsp;
5712 break;
5714 dsp++;
5717 /* did we find one atall? */
5718 if (dsp_match == NULL) {
5719 show_oops(t, "%s: no matching xtp despatch found", __func__);
5720 goto clean;
5723 /* check session key and call despatch function */
5724 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5725 ret = TRUE; /* all is well, this was a valid xtp request */
5726 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5729 clean:
5730 if (dup)
5731 g_free(dup);
5733 return (ret);
5738 void
5739 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5741 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5743 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5745 if (t == NULL) {
5746 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5747 return;
5750 if (uri == NULL) {
5751 show_oops(t, "activate_uri_entry_cb no uri");
5752 return;
5755 uri += strspn(uri, "\t ");
5757 /* if xxxt:// treat specially */
5758 if (parse_xtp_url(t, uri))
5759 return;
5761 /* otherwise continue to load page normally */
5762 load_uri(t, (gchar *)uri);
5763 focus_webview(t);
5766 void
5767 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5769 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5770 char *newuri = NULL;
5771 gchar *enc_search;
5773 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5775 if (t == NULL) {
5776 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5777 return;
5780 if (search_string == NULL) {
5781 show_oops(t, "no search_string");
5782 return;
5785 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5787 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5788 newuri = g_strdup_printf(search_string, enc_search);
5789 g_free(enc_search);
5791 marks_clear(t);
5792 webkit_web_view_load_uri(t->wv, newuri);
5793 focus_webview(t);
5795 if (newuri)
5796 g_free(newuri);
5799 void
5800 check_and_set_js(const gchar *uri, struct tab *t)
5802 struct domain *d = NULL;
5803 int es = 0;
5805 if (uri == NULL || t == NULL)
5806 return;
5808 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5809 es = 0;
5810 else
5811 es = 1;
5813 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5814 es ? "enable" : "disable", uri);
5816 g_object_set(G_OBJECT(t->settings),
5817 "enable-scripts", es, (char *)NULL);
5818 g_object_set(G_OBJECT(t->settings),
5819 "javascript-can-open-windows-automatically", es, (char *)NULL);
5820 webkit_web_view_set_settings(t->wv, t->settings);
5822 button_set_stockid(t->js_toggle,
5823 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5826 void
5827 show_ca_status(struct tab *t, const char *uri)
5829 WebKitWebFrame *frame;
5830 WebKitWebDataSource *source;
5831 WebKitNetworkRequest *request;
5832 SoupMessage *message;
5833 GdkColor color;
5834 gchar *col_str = XT_COLOR_WHITE;
5835 int r;
5837 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5838 ssl_strict_certs, ssl_ca_file, uri);
5840 if (uri == NULL)
5841 goto done;
5842 if (ssl_ca_file == NULL) {
5843 if (g_str_has_prefix(uri, "http://"))
5844 goto done;
5845 if (g_str_has_prefix(uri, "https://")) {
5846 col_str = XT_COLOR_RED;
5847 goto done;
5849 return;
5851 if (g_str_has_prefix(uri, "http://") ||
5852 !g_str_has_prefix(uri, "https://"))
5853 goto done;
5855 frame = webkit_web_view_get_main_frame(t->wv);
5856 source = webkit_web_frame_get_data_source(frame);
5857 request = webkit_web_data_source_get_request(source);
5858 message = webkit_network_request_get_message(request);
5860 if (message && (soup_message_get_flags(message) &
5861 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5862 col_str = XT_COLOR_GREEN;
5863 goto done;
5864 } else {
5865 r = load_compare_cert(t, NULL);
5866 if (r == 0)
5867 col_str = XT_COLOR_BLUE;
5868 else if (r == 1)
5869 col_str = XT_COLOR_YELLOW;
5870 else
5871 col_str = XT_COLOR_RED;
5872 goto done;
5874 done:
5875 if (col_str) {
5876 gdk_color_parse(col_str, &color);
5877 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5879 if (!strcmp(col_str, XT_COLOR_WHITE))
5880 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5881 else
5882 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5886 void
5887 free_favicon(struct tab *t)
5889 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5890 __func__, t->icon_download, t->icon_request);
5892 if (t->icon_request)
5893 g_object_unref(t->icon_request);
5894 if (t->icon_dest_uri)
5895 g_free(t->icon_dest_uri);
5897 t->icon_request = NULL;
5898 t->icon_dest_uri = NULL;
5901 void
5902 xt_icon_from_name(struct tab *t, gchar *name)
5904 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5905 GTK_ENTRY_ICON_PRIMARY, "text-html");
5906 if (show_url == 0)
5907 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5908 GTK_ENTRY_ICON_PRIMARY, "text-html");
5909 else
5910 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5911 GTK_ENTRY_ICON_PRIMARY, NULL);
5914 void
5915 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5917 GdkPixbuf *pb_scaled;
5919 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5920 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
5921 GDK_INTERP_BILINEAR);
5922 else
5923 pb_scaled = pb;
5925 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5926 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5927 if (show_url == 0)
5928 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
5929 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5930 else
5931 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5932 GTK_ENTRY_ICON_PRIMARY, NULL);
5934 if (pb_scaled != pb)
5935 g_object_unref(pb_scaled);
5938 void
5939 xt_icon_from_file(struct tab *t, char *file)
5941 GdkPixbuf *pb;
5943 if (g_str_has_prefix(file, "file://"))
5944 file += strlen("file://");
5946 pb = gdk_pixbuf_new_from_file(file, NULL);
5947 if (pb) {
5948 xt_icon_from_pixbuf(t, pb);
5949 g_object_unref(pb);
5950 } else
5951 xt_icon_from_name(t, "text-html");
5954 gboolean
5955 is_valid_icon(char *file)
5957 gboolean valid = 0;
5958 const char *mime_type;
5959 GFileInfo *fi;
5960 GFile *gf;
5962 gf = g_file_new_for_path(file);
5963 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5964 NULL, NULL);
5965 mime_type = g_file_info_get_content_type(fi);
5966 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5967 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5968 g_strcmp0(mime_type, "image/png") == 0 ||
5969 g_strcmp0(mime_type, "image/gif") == 0 ||
5970 g_strcmp0(mime_type, "application/octet-stream") == 0;
5971 g_object_unref(fi);
5972 g_object_unref(gf);
5974 return (valid);
5977 void
5978 set_favicon_from_file(struct tab *t, char *file)
5980 struct stat sb;
5982 if (t == NULL || file == NULL)
5983 return;
5985 if (g_str_has_prefix(file, "file://"))
5986 file += strlen("file://");
5987 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5989 if (!stat(file, &sb)) {
5990 if (sb.st_size == 0 || !is_valid_icon(file)) {
5991 /* corrupt icon so trash it */
5992 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5993 __func__, file);
5994 unlink(file);
5995 /* no need to set icon to default here */
5996 return;
5999 xt_icon_from_file(t, file);
6002 void
6003 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6004 WebKitWebView *wv)
6006 WebKitDownloadStatus status = webkit_download_get_status(download);
6007 struct tab *tt = NULL, *t = NULL;
6010 * find the webview instead of passing in the tab as it could have been
6011 * deleted from underneath us.
6013 TAILQ_FOREACH(tt, &tabs, entry) {
6014 if (tt->wv == wv) {
6015 t = tt;
6016 break;
6019 if (t == NULL)
6020 return;
6022 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6023 __func__, t->tab_id, status);
6025 switch (status) {
6026 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6027 /* -1 */
6028 t->icon_download = NULL;
6029 free_favicon(t);
6030 break;
6031 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6032 /* 0 */
6033 break;
6034 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6035 /* 1 */
6036 break;
6037 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6038 /* 2 */
6039 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6040 __func__, t->tab_id);
6041 t->icon_download = NULL;
6042 free_favicon(t);
6043 break;
6044 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6045 /* 3 */
6047 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6048 __func__, t->icon_dest_uri);
6049 set_favicon_from_file(t, t->icon_dest_uri);
6050 /* these will be freed post callback */
6051 t->icon_request = NULL;
6052 t->icon_download = NULL;
6053 break;
6054 default:
6055 break;
6059 void
6060 abort_favicon_download(struct tab *t)
6062 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6064 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6065 if (t->icon_download) {
6066 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6067 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6068 webkit_download_cancel(t->icon_download);
6069 t->icon_download = NULL;
6070 } else
6071 free_favicon(t);
6072 #endif
6074 xt_icon_from_name(t, "text-html");
6077 void
6078 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6080 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6082 if (uri == NULL || t == NULL)
6083 return;
6085 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6086 /* take icon from WebKitIconDatabase */
6087 GdkPixbuf *pb;
6089 pb = webkit_web_view_get_icon_pixbuf(wv);
6090 if (pb) {
6091 xt_icon_from_pixbuf(t, pb);
6092 g_object_unref(pb);
6093 } else
6094 xt_icon_from_name(t, "text-html");
6095 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6096 /* download icon to cache dir */
6097 gchar *name_hash, file[PATH_MAX];
6098 struct stat sb;
6100 if (t->icon_request) {
6101 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6102 return;
6105 /* check to see if we got the icon in cache */
6106 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6107 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6108 g_free(name_hash);
6110 if (!stat(file, &sb)) {
6111 if (sb.st_size > 0) {
6112 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6113 __func__, file);
6114 set_favicon_from_file(t, file);
6115 return;
6118 /* corrupt icon so trash it */
6119 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6120 __func__, file);
6121 unlink(file);
6124 /* create download for icon */
6125 t->icon_request = webkit_network_request_new(uri);
6126 if (t->icon_request == NULL) {
6127 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6128 __func__, uri);
6129 return;
6132 t->icon_download = webkit_download_new(t->icon_request);
6133 if (t->icon_download == NULL)
6134 return;
6136 /* we have to free icon_dest_uri later */
6137 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6138 webkit_download_set_destination_uri(t->icon_download,
6139 t->icon_dest_uri);
6141 if (webkit_download_get_status(t->icon_download) ==
6142 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6143 g_object_unref(t->icon_request);
6144 g_free(t->icon_dest_uri);
6145 t->icon_request = NULL;
6146 t->icon_dest_uri = NULL;
6147 return;
6150 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6151 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6153 webkit_download_start(t->icon_download);
6154 #endif
6157 void
6158 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6160 const gchar *uri = NULL, *title = NULL;
6161 struct history *h, find;
6162 struct karg a;
6164 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6165 webkit_web_view_get_load_status(wview),
6166 get_uri(t) ? get_uri(t) : "NOTHING");
6168 if (t == NULL) {
6169 show_oops(NULL, "notify_load_status_cb invalid parameters");
6170 return;
6173 switch (webkit_web_view_get_load_status(wview)) {
6174 case WEBKIT_LOAD_PROVISIONAL:
6175 /* 0 */
6176 abort_favicon_download(t);
6177 #if GTK_CHECK_VERSION(2, 20, 0)
6178 gtk_widget_show(t->spinner);
6179 gtk_spinner_start(GTK_SPINNER(t->spinner));
6180 #endif
6181 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6183 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6185 /* take focus if we are visible */
6186 focus_webview(t);
6187 t->focus_wv = 1;
6189 break;
6191 case WEBKIT_LOAD_COMMITTED:
6192 /* 1 */
6193 uri = get_uri(t);
6194 if (uri == NULL)
6195 return;
6196 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6198 if (t->status) {
6199 g_free(t->status);
6200 t->status = NULL;
6202 set_status(t, (char *)uri, XT_STATUS_LOADING);
6204 /* check if js white listing is enabled */
6205 if (enable_js_whitelist) {
6206 check_and_set_js(uri, t);
6209 if (t->styled)
6210 apply_style(t);
6212 show_ca_status(t, uri);
6214 /* we know enough to autosave the session */
6215 if (session_autosave) {
6216 a.s = NULL;
6217 save_tabs(t, &a);
6219 break;
6221 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6222 /* 3 */
6223 break;
6225 case WEBKIT_LOAD_FINISHED:
6226 /* 2 */
6227 uri = get_uri(t);
6228 if (uri == NULL)
6229 return;
6231 if (!strncmp(uri, "http://", strlen("http://")) ||
6232 !strncmp(uri, "https://", strlen("https://")) ||
6233 !strncmp(uri, "file://", strlen("file://"))) {
6234 find.uri = uri;
6235 h = RB_FIND(history_list, &hl, &find);
6236 if (!h) {
6237 title = get_title(t, FALSE);
6238 h = g_malloc(sizeof *h);
6239 h->uri = g_strdup(uri);
6240 h->title = g_strdup(title);
6241 RB_INSERT(history_list, &hl, h);
6242 completion_add_uri(h->uri);
6243 update_history_tabs(NULL);
6247 set_status(t, (char *)uri, XT_STATUS_URI);
6248 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6249 case WEBKIT_LOAD_FAILED:
6250 /* 4 */
6251 #endif
6252 #if GTK_CHECK_VERSION(2, 20, 0)
6253 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6254 gtk_widget_hide(t->spinner);
6255 #endif
6256 default:
6257 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6258 break;
6261 if (t->item)
6262 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6263 else
6264 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6265 webkit_web_view_can_go_back(wview));
6267 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6268 webkit_web_view_can_go_forward(wview));
6271 gboolean
6272 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6273 gchar *uri, gpointer web_error,struct tab *t)
6275 if (t->tmp_uri)
6276 g_free(t->tmp_uri);
6277 t->tmp_uri = g_strdup(uri);
6278 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6279 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6280 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6281 set_status(t, uri, XT_STATUS_NOTHING);
6283 return (FALSE);
6286 void
6287 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6289 const gchar *title = NULL, *win_title = NULL;
6291 title = get_title(t, FALSE);
6292 win_title = get_title(t, TRUE);
6293 gtk_label_set_text(GTK_LABEL(t->label), title);
6294 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6295 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6296 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6299 void
6300 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6302 run_script(t, JS_HINTING);
6305 void
6306 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6308 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6309 progress == 100 ? 0 : (double)progress / 100);
6310 if (show_url == 0) {
6311 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6312 progress == 100 ? 0 : (double)progress / 100);
6315 update_statusbar_position(NULL, NULL);
6319 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6320 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6321 WebKitWebPolicyDecision *pd, struct tab *t)
6323 char *uri;
6324 WebKitWebNavigationReason reason;
6325 struct domain *d = NULL;
6327 if (t == NULL) {
6328 show_oops(NULL, "webview_npd_cb invalid parameters");
6329 return (FALSE);
6332 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6333 t->ctrl_click,
6334 webkit_network_request_get_uri(request));
6336 uri = (char *)webkit_network_request_get_uri(request);
6338 /* if this is an xtp url, we don't load anything else */
6339 if (parse_xtp_url(t, uri))
6340 return (TRUE);
6342 if (t->ctrl_click) {
6343 t->ctrl_click = 0;
6344 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6345 webkit_web_policy_decision_ignore(pd);
6346 return (TRUE); /* we made the decission */
6350 * This is a little hairy but it comes down to this:
6351 * when we run in whitelist mode we have to assist the browser in
6352 * opening the URL that it would have opened in a new tab.
6354 reason = webkit_web_navigation_action_get_reason(na);
6355 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6356 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6357 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6358 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6359 load_uri(t, uri);
6360 webkit_web_policy_decision_use(pd);
6361 return (TRUE); /* we made the decision */
6364 return (FALSE);
6367 WebKitWebView *
6368 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6370 struct tab *tt;
6371 struct domain *d = NULL;
6372 const gchar *uri;
6373 WebKitWebView *webview = NULL;
6375 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6376 webkit_web_view_get_uri(wv));
6378 if (tabless) {
6379 /* open in current tab */
6380 webview = t->wv;
6381 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6382 uri = webkit_web_view_get_uri(wv);
6383 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6384 return (NULL);
6386 tt = create_new_tab(NULL, NULL, 1, -1);
6387 webview = tt->wv;
6388 } else if (enable_scripts == 1) {
6389 tt = create_new_tab(NULL, NULL, 1, -1);
6390 webview = tt->wv;
6393 return (webview);
6396 gboolean
6397 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6399 const gchar *uri;
6400 struct domain *d = NULL;
6402 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6404 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6405 uri = webkit_web_view_get_uri(wv);
6406 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6407 return (FALSE);
6409 delete_tab(t);
6410 } else if (enable_scripts == 1)
6411 delete_tab(t);
6413 return (TRUE);
6417 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6419 /* we can not eat the event without throwing gtk off so defer it */
6421 /* catch middle click */
6422 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6423 t->ctrl_click = 1;
6424 goto done;
6427 /* catch ctrl click */
6428 if (e->type == GDK_BUTTON_RELEASE &&
6429 CLEAN(e->state) == GDK_CONTROL_MASK)
6430 t->ctrl_click = 1;
6431 else
6432 t->ctrl_click = 0;
6433 done:
6434 return (XT_CB_PASSTHROUGH);
6438 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6440 struct mime_type *m;
6442 m = find_mime_type(mime_type);
6443 if (m == NULL)
6444 return (1);
6445 if (m->mt_download)
6446 return (1);
6448 switch (fork()) {
6449 case -1:
6450 show_oops(t, "can't fork mime handler");
6451 return (1);
6452 /* NOTREACHED */
6453 case 0:
6454 break;
6455 default:
6456 return (0);
6459 /* child */
6460 execlp(m->mt_action, m->mt_action,
6461 webkit_network_request_get_uri(request), (void *)NULL);
6463 _exit(0);
6465 /* NOTREACHED */
6466 return (0);
6469 const gchar *
6470 get_mime_type(char *file)
6472 const char *mime_type;
6473 GFileInfo *fi;
6474 GFile *gf;
6476 if (g_str_has_prefix(file, "file://"))
6477 file += strlen("file://");
6479 gf = g_file_new_for_path(file);
6480 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6481 NULL, NULL);
6482 mime_type = g_file_info_get_content_type(fi);
6483 g_object_unref(fi);
6484 g_object_unref(gf);
6486 return (mime_type);
6490 run_download_mimehandler(char *mime_type, char *file)
6492 struct mime_type *m;
6494 m = find_mime_type(mime_type);
6495 if (m == NULL)
6496 return (1);
6498 switch (fork()) {
6499 case -1:
6500 show_oops(NULL, "can't fork download mime handler");
6501 return (1);
6502 /* NOTREACHED */
6503 case 0:
6504 break;
6505 default:
6506 return (0);
6509 /* child */
6510 if (g_str_has_prefix(file, "file://"))
6511 file += strlen("file://");
6512 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6514 _exit(0);
6516 /* NOTREACHED */
6517 return (0);
6520 void
6521 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6522 WebKitWebView *wv)
6524 WebKitDownloadStatus status;
6525 const gchar *file = NULL, *mime = NULL;
6527 if (download == NULL)
6528 return;
6529 status = webkit_download_get_status(download);
6530 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6531 return;
6533 file = webkit_download_get_destination_uri(download);
6534 if (file == NULL)
6535 return;
6536 mime = get_mime_type((char *)file);
6537 if (mime == NULL)
6538 return;
6540 run_download_mimehandler((char *)mime, (char *)file);
6544 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6545 WebKitNetworkRequest *request, char *mime_type,
6546 WebKitWebPolicyDecision *decision, struct tab *t)
6548 if (t == NULL) {
6549 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6550 return (FALSE);
6553 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6554 t->tab_id, mime_type);
6556 if (run_mimehandler(t, mime_type, request) == 0) {
6557 webkit_web_policy_decision_ignore(decision);
6558 focus_webview(t);
6559 return (TRUE);
6562 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6563 webkit_web_policy_decision_download(decision);
6564 return (TRUE);
6567 return (FALSE);
6571 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6572 struct tab *t)
6574 struct stat sb;
6575 const gchar *suggested_name;
6576 gchar *filename = NULL;
6577 char *uri = NULL;
6578 struct download *download_entry;
6579 int i, ret = TRUE;
6581 if (wk_download == NULL || t == NULL) {
6582 show_oops(NULL, "%s invalid parameters", __func__);
6583 return (FALSE);
6586 suggested_name = webkit_download_get_suggested_filename(wk_download);
6587 if (suggested_name == NULL)
6588 return (FALSE); /* abort download */
6590 i = 0;
6591 do {
6592 if (filename) {
6593 g_free(filename);
6594 filename = NULL;
6596 if (i) {
6597 g_free(uri);
6598 uri = NULL;
6599 filename = g_strdup_printf("%d%s", i, suggested_name);
6601 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6602 filename : suggested_name);
6603 i++;
6604 } while (!stat(uri + strlen("file://"), &sb));
6606 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6607 "local %s\n", __func__, t->tab_id, filename, uri);
6609 webkit_download_set_destination_uri(wk_download, uri);
6611 if (webkit_download_get_status(wk_download) ==
6612 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6613 show_oops(t, "%s: download failed to start", __func__);
6614 ret = FALSE;
6615 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6616 } else {
6617 /* connect "download first" mime handler */
6618 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6619 G_CALLBACK(download_status_changed_cb), NULL);
6621 download_entry = g_malloc(sizeof(struct download));
6622 download_entry->download = wk_download;
6623 download_entry->tab = t;
6624 download_entry->id = next_download_id++;
6625 RB_INSERT(download_list, &downloads, download_entry);
6626 /* get from history */
6627 g_object_ref(wk_download);
6628 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6629 show_oops(t, "Download of '%s' started...",
6630 basename((char *)webkit_download_get_destination_uri(wk_download)));
6633 if (uri)
6634 g_free(uri);
6636 if (filename)
6637 g_free(filename);
6639 /* sync other download manager tabs */
6640 update_download_tabs(NULL);
6643 * NOTE: never redirect/render the current tab before this
6644 * function returns. This will cause the download to never start.
6646 return (ret); /* start download */
6649 void
6650 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6652 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6654 if (t == NULL) {
6655 show_oops(NULL, "webview_hover_cb");
6656 return;
6659 if (uri)
6660 set_status(t, uri, XT_STATUS_LINK);
6661 else {
6662 if (t->status)
6663 set_status(t, t->status, XT_STATUS_NOTHING);
6668 mark(struct tab *t, struct karg *arg)
6670 char mark;
6671 int index;
6673 mark = arg->s[1];
6674 if ((index = marktoindex(mark)) == -1)
6675 return -1;
6677 if (arg->i == XT_MARK_SET)
6678 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6679 else if (arg->i == XT_MARK_GOTO) {
6680 if (t->mark[index] == XT_INVALID_MARK) {
6681 show_oops(t, "mark '%c' does not exist", mark);
6682 return -1;
6684 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6685 something changes the document size */
6686 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6689 return 0;
6692 void
6693 marks_clear(struct tab *t)
6695 int i;
6697 for (i = 0; i < LENGTH(t->mark); i++)
6698 t->mark[i] = XT_INVALID_MARK;
6702 qmarks_load(void)
6704 char file[PATH_MAX];
6705 char *line = NULL, *p, mark;
6706 int index, i;
6707 FILE *f;
6708 size_t linelen;
6710 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6711 if ((f = fopen(file, "r+")) == NULL) {
6712 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6713 return (1);
6716 for (i = 1; ; i++) {
6717 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
6718 if (feof(f) || ferror(f))
6719 break;
6720 if (strlen(line) == 0 || line[0] == '#') {
6721 free(line);
6722 line = NULL;
6723 continue;
6726 p = strtok(line, " \t");
6728 if (p == NULL || strlen(p) != 1 ||
6729 (index = marktoindex(*p)) == -1) {
6730 warnx("corrupt quickmarks file, line %d", i);
6731 break;
6734 mark = *p;
6735 p = strtok(NULL, " \t");
6736 if (qmarks[index] != NULL)
6737 g_free(qmarks[index]);
6738 qmarks[index] = g_strdup(p);
6741 fclose(f);
6743 return (0);
6747 qmarks_save(void)
6749 char file[PATH_MAX];
6750 int i;
6751 FILE *f;
6753 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6754 if ((f = fopen(file, "r+")) == NULL) {
6755 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6756 return (1);
6759 for (i = 0; i < XT_NOMARKS; i++)
6760 if (qmarks[i] != NULL)
6761 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
6763 fclose(f);
6765 return (0);
6769 qmark(struct tab *t, struct karg *arg)
6771 char mark;
6772 int index;
6774 mark = arg->s[strlen(arg->s)-1];
6775 index = marktoindex(mark);
6776 if (index == -1)
6777 return (-1);
6779 switch (arg->i) {
6780 case XT_QMARK_SET:
6781 if (qmarks[index] != NULL)
6782 g_free(qmarks[index]);
6784 qmarks_load(); /* sync if multiple instances */
6785 qmarks[index] = g_strdup(get_uri(t));
6786 qmarks_save();
6787 break;
6788 case XT_QMARK_OPEN:
6789 if (qmarks[index] != NULL)
6790 load_uri(t, qmarks[index]);
6791 else {
6792 show_oops(t, "quickmark \"%c\" does not exist",
6793 mark);
6794 return (-1);
6796 break;
6797 case XT_QMARK_TAB:
6798 if (qmarks[index] != NULL)
6799 create_new_tab(qmarks[index], NULL, 1, -1);
6800 else {
6801 show_oops(t, "quickmark \"%c\" does not exist",
6802 mark);
6803 return (-1);
6805 break;
6808 return (0);
6812 go_up(struct tab *t, struct karg *args)
6814 int levels;
6815 char *uri;
6816 char *tmp;
6818 levels = atoi(args->s);
6819 if (levels == 0)
6820 levels = 1;
6822 uri = g_strdup(webkit_web_view_get_uri(t->wv));
6823 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
6824 return 1;
6825 tmp += strlen(XT_PROTO_DELIM);
6827 /* if an uri starts with a slash, leave it alone (for file:///) */
6828 if (tmp[0] == '/')
6829 tmp++;
6831 while (levels--) {
6832 char *p;
6834 p = strrchr(tmp, '/');
6835 if (p != NULL)
6836 *p = '\0';
6837 else
6838 break;
6841 load_uri(t, uri);
6842 g_free(uri);
6844 return 0;
6848 gototab(struct tab *t, struct karg *args)
6850 int tab;
6851 struct karg arg = {0, NULL, -1};
6853 tab = atoi(args->s);
6855 arg.i = XT_TAB_NEXT;
6856 arg.p = tab;
6858 movetab(t, &arg);
6860 return 0;
6864 zoom_amount(struct tab *t, struct karg *arg)
6866 struct karg narg = {0, NULL, -1};
6868 narg.i = atoi(arg->s);
6869 resizetab(t, &narg);
6871 return 0;
6874 /* buffer commands receive the regex that triggered them in arg.s */
6875 char bcmd[8];
6876 struct buffercmd {
6877 char *regex;
6878 int (*func)(struct tab *, struct karg *);
6879 int arg;
6880 regex_t cregex;
6881 } buffercmds[] = {
6882 { "^[0-9]*gu$", go_up, 0 },
6883 { "^gg$", move, XT_MOVE_TOP },
6884 { "^gG$", move, XT_MOVE_BOTTOM },
6885 { "^[0-9]+%$", move, XT_MOVE_PERCENT },
6886 { "^gh$", go_home, 0 },
6887 { "^m[a-zA-Z0-9]$", mark, XT_MARK_SET },
6888 { "^[`'][a-zA-Z0-9]$", mark, XT_MARK_GOTO },
6889 { "^[0-9]+t$", gototab, 0 },
6890 { "^M[a-zA-Z0-9]$", qmark, XT_QMARK_SET },
6891 { "^go[a-zA-Z0-9]$", qmark, XT_QMARK_OPEN },
6892 { "^gn[a-zA-Z0-9]$", qmark, XT_QMARK_TAB },
6893 { "^ZR$", restart, 0 },
6894 { "^ZZ$", quit, 0 },
6895 { "^zi$", resizetab, XT_ZOOM_IN },
6896 { "^zo$", resizetab, XT_ZOOM_OUT },
6897 { "^z0$", resizetab, XT_ZOOM_NORMAL },
6898 { "^[0-9]+Z$", zoom_amount, 0 },
6901 void
6902 buffercmd_init(void)
6904 int i;
6906 for (i = 0; i < LENGTH(buffercmds); i++)
6907 regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
6908 REG_EXTENDED);
6911 void
6912 buffercmd_abort(struct tab *t)
6914 int i;
6916 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
6917 for (i = 0; i < LENGTH(bcmd); i++)
6918 bcmd[i] = '\0';
6920 cmd_prefix = 0; /* clear prefix for non-buffer commands */
6921 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6924 void
6925 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
6927 struct karg arg = {0, NULL, -1};
6929 arg.i = cmd->arg;
6930 arg.s = g_strdup(bcmd);
6932 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
6933 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
6934 cmd->func(t, &arg);
6936 if (arg.s)
6937 g_free(arg.s);
6939 buffercmd_abort(t);
6942 gboolean
6943 buffercmd_addkey(struct tab *t, guint keyval)
6945 int i;
6947 if (keyval == GDK_Escape) {
6948 buffercmd_abort(t);
6949 return (XT_CB_HANDLED);
6952 /* key with modifier or non-ascii character */
6953 if (!isascii(keyval))
6954 return (XT_CB_PASSTHROUGH);
6956 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
6957 "to buffer \"%s\"\n", keyval, bcmd);
6959 for (i = 0; i < LENGTH(bcmd); i++)
6960 if (bcmd[i] == '\0') {
6961 bcmd[i] = keyval;
6962 break;
6965 /* buffer full, ignore input */
6966 if (i == LENGTH(bcmd)) {
6967 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
6968 return (XT_CB_HANDLED);
6971 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6973 for (i = 0; i < LENGTH(buffercmds); i++)
6974 if (regexec(&buffercmds[i].cregex, bcmd,
6975 (size_t) 0, NULL, 0) == 0) {
6976 buffercmd_execute(t, &buffercmds[i]);
6977 break;
6980 return (XT_CB_HANDLED);
6983 gboolean
6984 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6986 struct key_binding *k;
6988 /* handle keybindings if buffercmd is empty.
6989 if not empty, allow commands like C-n */
6990 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
6991 TAILQ_FOREACH(k, &kbl, entry)
6992 if (e->keyval == k->key
6993 && (entry ? k->use_in_entry : 1)) {
6994 if (k->mask == 0) {
6995 if ((e->state & (CTRL | MOD1)) == 0)
6996 return (cmd_execute(t, k->cmd));
6997 } else if ((e->state & k->mask) == k->mask) {
6998 return (cmd_execute(t, k->cmd));
7002 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7003 return buffercmd_addkey(t, e->keyval);
7005 return (XT_CB_PASSTHROUGH);
7009 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7011 char s[2], buf[128];
7012 const char *errstr = NULL;
7014 /* don't use w directly; use t->whatever instead */
7016 if (t == NULL) {
7017 show_oops(NULL, "wv_keypress_after_cb");
7018 return (XT_CB_PASSTHROUGH);
7021 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7022 e->keyval, e->state, t);
7024 if (t->hints_on) {
7025 /* ESC */
7026 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7027 disable_hints(t);
7028 return (XT_CB_HANDLED);
7031 /* RETURN */
7032 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7033 if (errstr) {
7034 /* we have a string */
7035 } else {
7036 /* we have a number */
7037 snprintf(buf, sizeof buf,
7038 "vimprobable_fire(%s)", t->hint_num);
7039 run_script(t, buf);
7041 disable_hints(t);
7044 /* BACKSPACE */
7045 /* XXX unfuck this */
7046 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7047 if (t->hint_mode == XT_HINT_NUMERICAL) {
7048 /* last input was numerical */
7049 int l;
7050 l = strlen(t->hint_num);
7051 if (l > 0) {
7052 l--;
7053 if (l == 0) {
7054 disable_hints(t);
7055 enable_hints(t);
7056 } else {
7057 t->hint_num[l] = '\0';
7058 goto num;
7061 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7062 /* last input was alphanumerical */
7063 int l;
7064 l = strlen(t->hint_buf);
7065 if (l > 0) {
7066 l--;
7067 if (l == 0) {
7068 disable_hints(t);
7069 enable_hints(t);
7070 } else {
7071 t->hint_buf[l] = '\0';
7072 goto anum;
7075 } else {
7076 /* bogus */
7077 disable_hints(t);
7081 /* numerical input */
7082 if (CLEAN(e->state) == 0 &&
7083 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7084 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7085 snprintf(s, sizeof s, "%c", e->keyval);
7086 strlcat(t->hint_num, s, sizeof t->hint_num);
7087 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7088 t->hint_num);
7089 num:
7090 if (errstr) {
7091 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7092 "invalid link number\n");
7093 disable_hints(t);
7094 } else {
7095 snprintf(buf, sizeof buf,
7096 "vimprobable_update_hints(%s)",
7097 t->hint_num);
7098 t->hint_mode = XT_HINT_NUMERICAL;
7099 run_script(t, buf);
7102 /* empty the counter buffer */
7103 bzero(t->hint_buf, sizeof t->hint_buf);
7104 return (XT_CB_HANDLED);
7107 /* alphanumerical input */
7108 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7109 e->keyval <= GDK_z) ||
7110 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7111 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7112 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7113 e->keyval <= GDK_9) ||
7114 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7115 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7116 snprintf(s, sizeof s, "%c", e->keyval);
7117 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7118 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7119 " %s\n", t->hint_buf);
7120 anum:
7121 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7122 run_script(t, buf);
7124 snprintf(buf, sizeof buf,
7125 "vimprobable_show_hints('%s')", t->hint_buf);
7126 t->hint_mode = XT_HINT_ALPHANUM;
7127 run_script(t, buf);
7129 /* empty the counter buffer */
7130 bzero(t->hint_num, sizeof t->hint_num);
7131 return (XT_CB_HANDLED);
7134 return (XT_CB_HANDLED);
7135 } else {
7136 /* prefix input*/
7137 snprintf(s, sizeof s, "%c", e->keyval);
7138 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7139 cmd_prefix = 10 * cmd_prefix + atoi(s);
7142 return (handle_keypress(t, e, 0));
7146 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7148 hide_oops(t);
7150 /* Hide buffers, if they are visible, with escape. */
7151 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7152 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7153 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7154 hide_buffers(t);
7155 return (XT_CB_HANDLED);
7158 return (XT_CB_PASSTHROUGH);
7162 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7164 const gchar *c = gtk_entry_get_text(w);
7165 GdkColor color;
7166 int forward = TRUE;
7168 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7169 e->keyval, e->state, t);
7171 if (t == NULL) {
7172 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7173 return (XT_CB_PASSTHROUGH);
7176 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7177 e->keyval, e->state, t);
7179 if (c[0] == ':')
7180 goto done;
7181 if (strlen(c) == 1) {
7182 webkit_web_view_unmark_text_matches(t->wv);
7183 goto done;
7186 if (c[0] == '/')
7187 forward = TRUE;
7188 else if (c[0] == '?')
7189 forward = FALSE;
7190 else
7191 goto done;
7193 /* search */
7194 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
7195 FALSE) {
7196 /* not found, mark red */
7197 gdk_color_parse(XT_COLOR_RED, &color);
7198 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7199 /* unmark and remove selection */
7200 webkit_web_view_unmark_text_matches(t->wv);
7201 /* my kingdom for a way to unselect text in webview */
7202 } else {
7203 /* found, highlight all */
7204 webkit_web_view_unmark_text_matches(t->wv);
7205 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7206 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7207 gdk_color_parse(XT_COLOR_WHITE, &color);
7208 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7210 done:
7211 return (XT_CB_PASSTHROUGH);
7214 gboolean
7215 match_uri(const gchar *uri, const gchar *key) {
7216 gchar *voffset;
7217 size_t len;
7218 gboolean match = FALSE;
7220 len = strlen(key);
7222 if (!strncmp(key, uri, len))
7223 match = TRUE;
7224 else {
7225 voffset = strstr(uri, "/") + 2;
7226 if (!strncmp(key, voffset, len))
7227 match = TRUE;
7228 else if (g_str_has_prefix(voffset, "www.")) {
7229 voffset = voffset + strlen("www.");
7230 if (!strncmp(key, voffset, len))
7231 match = TRUE;
7235 return (match);
7238 void
7239 cmd_getlist(int id, char *key)
7241 int i, dep, c = 0;
7242 struct history *h;
7244 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7245 RB_FOREACH_REVERSE(h, history_list, &hl)
7246 if (match_uri(h->uri, key)) {
7247 cmd_status.list[c] = (char *)h->uri;
7248 if (++c > 255)
7249 break;
7252 cmd_status.len = c;
7253 return;
7256 dep = (id == -1) ? 0 : cmds[id].level + 1;
7258 for (i = id + 1; i < LENGTH(cmds); i++) {
7259 if (cmds[i].level < dep)
7260 break;
7261 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7262 strlen(key)))
7263 cmd_status.list[c++] = cmds[i].cmd;
7267 cmd_status.len = c;
7270 char *
7271 cmd_getnext(int dir)
7273 cmd_status.index += dir;
7275 if (cmd_status.index < 0)
7276 cmd_status.index = cmd_status.len - 1;
7277 else if (cmd_status.index >= cmd_status.len)
7278 cmd_status.index = 0;
7280 return cmd_status.list[cmd_status.index];
7284 cmd_tokenize(char *s, char *tokens[])
7286 int i = 0;
7287 char *tok, *last;
7288 size_t len = strlen(s);
7289 bool blank;
7291 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7292 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7293 tok = strtok_r(NULL, " ", &last), i++)
7294 tokens[i] = tok;
7296 if (blank && i < 3)
7297 tokens[i++] = "";
7299 return (i);
7302 void
7303 cmd_complete(struct tab *t, char *str, int dir)
7305 GtkEntry *w = GTK_ENTRY(t->cmd);
7306 int i, j, levels, c = 0, dep = 0, parent = -1;
7307 int matchcount = 0;
7308 char *tok, *match, *s = g_strdup(str);
7309 char *tokens[3];
7310 char res[XT_MAX_URL_LENGTH + 32] = ":";
7311 char *sc = s;
7313 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7315 /* copy prefix*/
7316 for (i = 0; isdigit(s[i]); i++)
7317 res[i + 1] = s[i];
7319 for (; isspace(s[i]); i++)
7320 res[i + 1] = s[i];
7322 s += i;
7324 levels = cmd_tokenize(s, tokens);
7326 for (i = 0; i < levels - 1; i++) {
7327 tok = tokens[i];
7328 matchcount = 0;
7329 for (j = c; j < LENGTH(cmds); j++) {
7330 if (cmds[j].level < dep)
7331 break;
7332 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7333 strlen(tok))) {
7334 matchcount++;
7335 c = j + 1;
7336 if (strlen(tok) == strlen(cmds[j].cmd)) {
7337 matchcount = 1;
7338 break;
7343 if (matchcount == 1) {
7344 strlcat(res, tok, sizeof res);
7345 strlcat(res, " ", sizeof res);
7346 dep++;
7347 } else {
7348 g_free(sc);
7349 return;
7352 parent = c - 1;
7355 if (cmd_status.index == -1)
7356 cmd_getlist(parent, tokens[i]);
7358 if (cmd_status.len > 0) {
7359 match = cmd_getnext(dir);
7360 strlcat(res, match, sizeof res);
7361 gtk_entry_set_text(w, res);
7362 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7365 g_free(sc);
7368 gboolean
7369 cmd_execute(struct tab *t, char *str)
7371 struct cmd *cmd = NULL;
7372 char *tok, *last, *s = g_strdup(str), *sc;
7373 char prefixstr[4];
7374 int j, len, c = 0, dep = 0, matchcount = 0;
7375 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7376 struct karg arg = {0, NULL, -1};
7378 sc = s;
7380 /* copy prefix*/
7381 for (j = 0; j<3 && isdigit(s[j]); j++)
7382 prefixstr[j]=s[j];
7384 prefixstr[j]='\0';
7386 s += j;
7387 while (isspace(s[0]))
7388 s++;
7390 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7391 prefix = atoi(prefixstr);
7392 else
7393 s = sc;
7395 for (tok = strtok_r(s, " ", &last); tok;
7396 tok = strtok_r(NULL, " ", &last)) {
7397 matchcount = 0;
7398 for (j = c; j < LENGTH(cmds); j++) {
7399 if (cmds[j].level < dep)
7400 break;
7401 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7402 strlen(tok);
7403 if (cmds[j].level == dep &&
7404 !strncmp(tok, cmds[j].cmd, len)) {
7405 matchcount++;
7406 c = j + 1;
7407 cmd = &cmds[j];
7408 if (len == strlen(cmds[j].cmd)) {
7409 matchcount = 1;
7410 break;
7414 if (matchcount == 1) {
7415 if (cmd->type > 0)
7416 goto execute_cmd;
7417 dep++;
7418 } else {
7419 show_oops(t, "Invalid command: %s", str);
7420 goto done;
7423 execute_cmd:
7424 arg.i = cmd->arg;
7426 if (prefix != -1)
7427 arg.p = prefix;
7428 else if (cmd_prefix > 0)
7429 arg.p = cmd_prefix;
7431 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
7432 show_oops(t, "No prefix allowed: %s", str);
7433 goto done;
7435 if (cmd->type > 1)
7436 arg.s = last ? g_strdup(last) : g_strdup("");
7437 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7438 arg.p = atoi(arg.s);
7439 if (arg.p <= 0) {
7440 if (arg.s[0]=='0')
7441 show_oops(t, "Zero count");
7442 else
7443 show_oops(t, "Trailing characters");
7444 goto done;
7448 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
7450 cmd->func(t, &arg);
7452 rv = XT_CB_HANDLED;
7453 done:
7454 if (j > 0)
7455 cmd_prefix = 0;
7456 g_free(sc);
7457 if (arg.s)
7458 g_free(arg.s);
7460 return (rv);
7464 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7466 if (t == NULL) {
7467 show_oops(NULL, "entry_key_cb invalid parameters");
7468 return (XT_CB_PASSTHROUGH);
7471 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7472 e->keyval, e->state, t);
7474 hide_oops(t);
7476 if (e->keyval == GDK_Escape) {
7477 /* don't use focus_webview(t) because we want to type :cmds */
7478 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7481 return (handle_keypress(t, e, 1));
7485 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7487 int rv = XT_CB_HANDLED;
7488 const gchar *c = gtk_entry_get_text(w);
7490 if (t == NULL) {
7491 show_oops(NULL, "cmd_keypress_cb parameters");
7492 return (XT_CB_PASSTHROUGH);
7495 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7496 e->keyval, e->state, t);
7498 /* sanity */
7499 if (c == NULL)
7500 e->keyval = GDK_Escape;
7501 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7502 e->keyval = GDK_Escape;
7504 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7505 e->keyval != GDK_ISO_Left_Tab)
7506 cmd_status.index = -1;
7508 switch (e->keyval) {
7509 case GDK_Tab:
7510 if (c[0] == ':')
7511 cmd_complete(t, (char *)&c[1], 1);
7512 goto done;
7513 case GDK_ISO_Left_Tab:
7514 if (c[0] == ':')
7515 cmd_complete(t, (char *)&c[1], -1);
7517 goto done;
7518 case GDK_BackSpace:
7519 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7520 break;
7521 /* FALLTHROUGH */
7522 case GDK_Escape:
7523 hide_cmd(t);
7524 focus_webview(t);
7526 /* cancel search */
7527 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7528 webkit_web_view_unmark_text_matches(t->wv);
7529 goto done;
7532 rv = XT_CB_PASSTHROUGH;
7533 done:
7534 return (rv);
7538 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7540 if (t == NULL) {
7541 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7542 return (XT_CB_PASSTHROUGH);
7544 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7546 hide_cmd(t);
7547 hide_oops(t);
7549 if (show_url == 0 || t->focus_wv)
7550 focus_webview(t);
7551 else
7552 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7554 return (XT_CB_PASSTHROUGH);
7557 void
7558 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7560 char *s;
7561 const gchar *c = gtk_entry_get_text(entry);
7563 if (t == NULL) {
7564 show_oops(NULL, "cmd_activate_cb invalid parameters");
7565 return;
7568 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7570 hide_cmd(t);
7572 /* sanity */
7573 if (c == NULL)
7574 goto done;
7575 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7576 goto done;
7577 if (strlen(c) < 2)
7578 goto done;
7579 s = (char *)&c[1];
7581 if (c[0] == '/' || c[0] == '?') {
7582 if (t->search_text) {
7583 g_free(t->search_text);
7584 t->search_text = NULL;
7587 t->search_text = g_strdup(s);
7588 if (global_search)
7589 g_free(global_search);
7590 global_search = g_strdup(s);
7591 t->search_forward = c[0] == '/';
7593 goto done;
7596 cmd_execute(t, s);
7598 done:
7599 return;
7602 void
7603 backward_cb(GtkWidget *w, struct tab *t)
7605 struct karg a;
7607 if (t == NULL) {
7608 show_oops(NULL, "backward_cb invalid parameters");
7609 return;
7612 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7614 a.i = XT_NAV_BACK;
7615 navaction(t, &a);
7618 void
7619 forward_cb(GtkWidget *w, struct tab *t)
7621 struct karg a;
7623 if (t == NULL) {
7624 show_oops(NULL, "forward_cb invalid parameters");
7625 return;
7628 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7630 a.i = XT_NAV_FORWARD;
7631 navaction(t, &a);
7634 void
7635 home_cb(GtkWidget *w, struct tab *t)
7637 if (t == NULL) {
7638 show_oops(NULL, "home_cb invalid parameters");
7639 return;
7642 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7644 load_uri(t, home);
7647 void
7648 stop_cb(GtkWidget *w, struct tab *t)
7650 WebKitWebFrame *frame;
7652 if (t == NULL) {
7653 show_oops(NULL, "stop_cb invalid parameters");
7654 return;
7657 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7659 frame = webkit_web_view_get_main_frame(t->wv);
7660 if (frame == NULL) {
7661 show_oops(t, "stop_cb: no frame");
7662 return;
7665 webkit_web_frame_stop_loading(frame);
7666 abort_favicon_download(t);
7669 void
7670 setup_webkit(struct tab *t)
7672 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7673 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7674 FALSE, (char *)NULL);
7675 else
7676 warnx("webkit does not have \"enable-dns-prefetching\" property");
7677 g_object_set(G_OBJECT(t->settings),
7678 "user-agent", t->user_agent, (char *)NULL);
7679 g_object_set(G_OBJECT(t->settings),
7680 "enable-scripts", enable_scripts, (char *)NULL);
7681 g_object_set(G_OBJECT(t->settings),
7682 "enable-plugins", enable_plugins, (char *)NULL);
7683 g_object_set(G_OBJECT(t->settings),
7684 "javascript-can-open-windows-automatically", enable_scripts,
7685 (char *)NULL);
7686 g_object_set(G_OBJECT(t->settings),
7687 "enable-html5-database", FALSE, (char *)NULL);
7688 g_object_set(G_OBJECT(t->settings),
7689 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7690 g_object_set(G_OBJECT(t->settings),
7691 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7692 g_object_set(G_OBJECT(t->settings),
7693 "spell_checking_languages", spell_check_languages, (char *)NULL);
7694 g_object_set(G_OBJECT(t->wv),
7695 "full-content-zoom", TRUE, (char *)NULL);
7697 webkit_web_view_set_settings(t->wv, t->settings);
7700 gboolean
7701 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7703 struct tab *ti, *t = NULL;
7704 gdouble view_size, value, max;
7705 gchar *position;
7707 TAILQ_FOREACH(ti, &tabs, entry)
7708 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7709 t = ti;
7710 break;
7713 if (t == NULL)
7714 return FALSE;
7716 if (adjustment == NULL)
7717 adjustment = gtk_scrolled_window_get_vadjustment(
7718 GTK_SCROLLED_WINDOW(t->browser_win));
7720 view_size = gtk_adjustment_get_page_size(adjustment);
7721 value = gtk_adjustment_get_value(adjustment);
7722 max = gtk_adjustment_get_upper(adjustment) - view_size;
7724 if (max == 0)
7725 position = g_strdup("All");
7726 else if (value == max)
7727 position = g_strdup("Bot");
7728 else if (value == 0)
7729 position = g_strdup("Top");
7730 else
7731 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7733 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7734 g_free(position);
7736 return (TRUE);
7739 GtkWidget *
7740 create_browser(struct tab *t)
7742 GtkWidget *w;
7743 gchar *strval;
7744 GtkAdjustment *adjustment;
7746 if (t == NULL) {
7747 show_oops(NULL, "create_browser invalid parameters");
7748 return (NULL);
7751 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7752 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7753 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7754 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7756 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7757 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7758 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7760 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7761 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7763 /* set defaults */
7764 t->settings = webkit_web_settings_new();
7766 if (user_agent == NULL) {
7767 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7768 (char *)NULL);
7769 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7770 g_free(strval);
7771 } else
7772 t->user_agent = g_strdup(user_agent);
7774 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7776 adjustment =
7777 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7778 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7779 G_CALLBACK(update_statusbar_position), NULL);
7781 setup_webkit(t);
7783 return (w);
7786 GtkWidget *
7787 create_window(void)
7789 GtkWidget *w;
7791 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7792 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7793 gtk_widget_set_name(w, "xxxterm");
7794 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7795 g_signal_connect(G_OBJECT(w), "delete_event",
7796 G_CALLBACK (gtk_main_quit), NULL);
7798 return (w);
7801 GtkWidget *
7802 create_kiosk_toolbar(struct tab *t)
7804 GtkWidget *toolbar = NULL, *b;
7806 b = gtk_hbox_new(FALSE, 0);
7807 toolbar = b;
7808 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7810 /* backward button */
7811 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7812 gtk_widget_set_sensitive(t->backward, FALSE);
7813 g_signal_connect(G_OBJECT(t->backward), "clicked",
7814 G_CALLBACK(backward_cb), t);
7815 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7817 /* forward button */
7818 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7819 gtk_widget_set_sensitive(t->forward, FALSE);
7820 g_signal_connect(G_OBJECT(t->forward), "clicked",
7821 G_CALLBACK(forward_cb), t);
7822 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7824 /* home button */
7825 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7826 gtk_widget_set_sensitive(t->gohome, true);
7827 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7828 G_CALLBACK(home_cb), t);
7829 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7831 /* create widgets but don't use them */
7832 t->uri_entry = gtk_entry_new();
7833 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7834 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7835 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7837 return (toolbar);
7840 GtkWidget *
7841 create_toolbar(struct tab *t)
7843 GtkWidget *toolbar = NULL, *b, *eb1;
7845 b = gtk_hbox_new(FALSE, 0);
7846 toolbar = b;
7847 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7849 /* backward button */
7850 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7851 gtk_widget_set_sensitive(t->backward, FALSE);
7852 g_signal_connect(G_OBJECT(t->backward), "clicked",
7853 G_CALLBACK(backward_cb), t);
7854 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7856 /* forward button */
7857 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7858 gtk_widget_set_sensitive(t->forward, FALSE);
7859 g_signal_connect(G_OBJECT(t->forward), "clicked",
7860 G_CALLBACK(forward_cb), t);
7861 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7862 FALSE, 0);
7864 /* stop button */
7865 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7866 gtk_widget_set_sensitive(t->stop, FALSE);
7867 g_signal_connect(G_OBJECT(t->stop), "clicked",
7868 G_CALLBACK(stop_cb), t);
7869 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7870 FALSE, 0);
7872 /* JS button */
7873 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7874 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7875 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7876 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7877 G_CALLBACK(js_toggle_cb), t);
7878 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7880 t->uri_entry = gtk_entry_new();
7881 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7882 G_CALLBACK(activate_uri_entry_cb), t);
7883 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7884 G_CALLBACK(entry_key_cb), t);
7885 completion_add(t);
7886 eb1 = gtk_hbox_new(FALSE, 0);
7887 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7888 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7889 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7891 /* search entry */
7892 if (search_string) {
7893 GtkWidget *eb2;
7894 t->search_entry = gtk_entry_new();
7895 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7896 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7897 G_CALLBACK(activate_search_entry_cb), t);
7898 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7899 G_CALLBACK(entry_key_cb), t);
7900 gtk_widget_set_size_request(t->search_entry, -1, -1);
7901 eb2 = gtk_hbox_new(FALSE, 0);
7902 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7903 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7905 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7908 return (toolbar);
7911 GtkWidget *
7912 create_buffers(struct tab *t)
7914 GtkCellRenderer *renderer;
7915 GtkWidget *view;
7917 view = gtk_tree_view_new();
7919 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7921 renderer = gtk_cell_renderer_text_new();
7922 gtk_tree_view_insert_column_with_attributes
7923 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7925 renderer = gtk_cell_renderer_text_new();
7926 gtk_tree_view_insert_column_with_attributes
7927 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
7928 NULL);
7930 gtk_tree_view_set_model
7931 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7933 return view;
7936 void
7937 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7938 GtkTreeViewColumn *col, struct tab *t)
7940 GtkTreeIter iter;
7941 guint id;
7943 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7945 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
7946 path)) {
7947 gtk_tree_model_get
7948 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7949 set_current_tab(id - 1);
7952 hide_buffers(t);
7955 /* after tab reordering/creation/removal */
7956 void
7957 recalc_tabs(void)
7959 struct tab *t;
7960 int maxid = 0;
7962 TAILQ_FOREACH(t, &tabs, entry) {
7963 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7964 if (t->tab_id > maxid)
7965 maxid = t->tab_id;
7967 gtk_widget_show(t->tab_elems.sep);
7970 TAILQ_FOREACH(t, &tabs, entry) {
7971 if (t->tab_id == maxid) {
7972 gtk_widget_hide(t->tab_elems.sep);
7973 break;
7978 /* after active tab change */
7979 void
7980 recolor_compact_tabs(void)
7982 struct tab *t;
7983 int curid = 0;
7984 GdkColor color;
7986 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7987 TAILQ_FOREACH(t, &tabs, entry)
7988 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
7989 &color);
7991 curid = gtk_notebook_get_current_page(notebook);
7992 TAILQ_FOREACH(t, &tabs, entry)
7993 if (t->tab_id == curid) {
7994 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7995 gtk_widget_modify_fg(t->tab_elems.label,
7996 GTK_STATE_NORMAL, &color);
7997 break;
8001 void
8002 set_current_tab(int page_num)
8004 buffercmd_abort(get_current_tab());
8005 gtk_notebook_set_current_page(notebook, page_num);
8006 recolor_compact_tabs();
8010 undo_close_tab_save(struct tab *t)
8012 int m, n;
8013 const gchar *uri;
8014 struct undo *u1, *u2;
8015 GList *items;
8016 WebKitWebHistoryItem *item;
8018 if ((uri = get_uri(t)) == NULL)
8019 return (1);
8021 u1 = g_malloc0(sizeof(struct undo));
8022 u1->uri = g_strdup(uri);
8024 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8026 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8027 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8028 u1->back = n;
8030 /* forward history */
8031 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8033 while (items) {
8034 item = items->data;
8035 u1->history = g_list_prepend(u1->history,
8036 webkit_web_history_item_copy(item));
8037 items = g_list_next(items);
8040 /* current item */
8041 if (m) {
8042 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8043 u1->history = g_list_prepend(u1->history,
8044 webkit_web_history_item_copy(item));
8047 /* back history */
8048 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8050 while (items) {
8051 item = items->data;
8052 u1->history = g_list_prepend(u1->history,
8053 webkit_web_history_item_copy(item));
8054 items = g_list_next(items);
8057 TAILQ_INSERT_HEAD(&undos, u1, entry);
8059 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8060 u2 = TAILQ_LAST(&undos, undo_tailq);
8061 TAILQ_REMOVE(&undos, u2, entry);
8062 g_free(u2->uri);
8063 g_list_free(u2->history);
8064 g_free(u2);
8065 } else
8066 undo_count++;
8068 return (0);
8071 void
8072 delete_tab(struct tab *t)
8074 struct karg a;
8076 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8078 if (t == NULL)
8079 return;
8081 TAILQ_REMOVE(&tabs, t, entry);
8083 /* Halt all webkit activity. */
8084 abort_favicon_download(t);
8085 webkit_web_view_stop_loading(t->wv);
8087 /* Save the tab, so we can undo the close. */
8088 undo_close_tab_save(t);
8090 if (browser_mode == XT_BM_KIOSK) {
8091 gtk_widget_destroy(t->uri_entry);
8092 gtk_widget_destroy(t->stop);
8093 gtk_widget_destroy(t->js_toggle);
8096 gtk_widget_destroy(t->tab_elems.eventbox);
8097 gtk_widget_destroy(t->vbox);
8099 g_free(t->user_agent);
8100 g_free(t->stylesheet);
8101 g_free(t->tmp_uri);
8102 g_free(t);
8104 if (TAILQ_EMPTY(&tabs)) {
8105 if (browser_mode == XT_BM_KIOSK)
8106 create_new_tab(home, NULL, 1, -1);
8107 else
8108 create_new_tab(NULL, NULL, 1, -1);
8111 /* recreate session */
8112 if (session_autosave) {
8113 a.s = NULL;
8114 save_tabs(t, &a);
8117 recalc_tabs();
8118 recolor_compact_tabs();
8121 void
8122 update_statusbar_zoom(struct tab *t)
8124 gfloat zoom;
8125 char s[16] = { '\0' };
8127 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8128 if ((zoom <= 0.99 || zoom >= 1.01))
8129 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8130 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8133 void
8134 setzoom_webkit(struct tab *t, int adjust)
8136 #define XT_ZOOMPERCENT 0.04
8138 gfloat zoom;
8140 if (t == NULL) {
8141 show_oops(NULL, "setzoom_webkit invalid parameters");
8142 return;
8145 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8146 if (adjust == XT_ZOOM_IN)
8147 zoom += XT_ZOOMPERCENT;
8148 else if (adjust == XT_ZOOM_OUT)
8149 zoom -= XT_ZOOMPERCENT;
8150 else if (adjust > 0)
8151 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8152 else {
8153 show_oops(t, "setzoom_webkit invalid zoom value");
8154 return;
8157 if (zoom < XT_ZOOMPERCENT)
8158 zoom = XT_ZOOMPERCENT;
8159 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8160 update_statusbar_zoom(t);
8163 gboolean
8164 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8166 struct tab *t = (struct tab *) data;
8168 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8170 switch (event->button) {
8171 case 1:
8172 set_current_tab(t->tab_id);
8173 break;
8174 case 2:
8175 delete_tab(t);
8176 break;
8179 return TRUE;
8182 void
8183 append_tab(struct tab *t)
8185 if (t == NULL)
8186 return;
8188 TAILQ_INSERT_TAIL(&tabs, t, entry);
8189 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8192 struct tab *
8193 create_new_tab(char *title, struct undo *u, int focus, int position)
8195 struct tab *t;
8196 int load = 1, id;
8197 GtkWidget *b, *bb;
8198 WebKitWebHistoryItem *item;
8199 GList *items;
8200 GdkColor color;
8201 char *p;
8202 int sbe_p = 0, sbe_b = 0,
8203 sbe_z = 0;
8205 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8207 if (tabless && !TAILQ_EMPTY(&tabs)) {
8208 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8209 return (NULL);
8212 t = g_malloc0(sizeof *t);
8214 if (title == NULL) {
8215 title = "(untitled)";
8216 load = 0;
8219 t->vbox = gtk_vbox_new(FALSE, 0);
8221 /* label + button for tab */
8222 b = gtk_hbox_new(FALSE, 0);
8223 t->tab_content = b;
8225 #if GTK_CHECK_VERSION(2, 20, 0)
8226 t->spinner = gtk_spinner_new();
8227 #endif
8228 t->label = gtk_label_new(title);
8229 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8230 gtk_widget_set_size_request(t->label, 100, 0);
8231 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8232 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8233 gtk_widget_set_size_request(b, 130, 0);
8235 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8236 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8237 #if GTK_CHECK_VERSION(2, 20, 0)
8238 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8239 #endif
8241 /* toolbar */
8242 if (browser_mode == XT_BM_KIOSK) {
8243 t->toolbar = create_kiosk_toolbar(t);
8244 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8246 } else {
8247 t->toolbar = create_toolbar(t);
8248 if (fancy_bar)
8249 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8250 FALSE, 0);
8253 /* marks */
8254 marks_clear(t);
8256 /* browser */
8257 t->browser_win = create_browser(t);
8258 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8260 /* oops message for user feedback */
8261 t->oops = gtk_entry_new();
8262 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8263 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8264 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8265 gdk_color_parse(XT_COLOR_RED, &color);
8266 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8267 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8268 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8270 /* command entry */
8271 t->cmd = gtk_entry_new();
8272 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8273 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8274 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8275 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8277 /* status bar */
8278 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8280 t->sbe.statusbar = gtk_entry_new();
8281 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8282 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8283 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8284 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8286 /* create these widgets only if specified in statusbar_elems */
8287 t->sbe.position = gtk_entry_new();
8288 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.position), NULL);
8289 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.position), FALSE);
8290 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.position), FALSE);
8291 gtk_widget_modify_font(GTK_WIDGET(t->sbe.position), statusbar_font);
8292 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.position), 1.0);
8293 gtk_widget_set_size_request(t->sbe.position, 40, -1);
8295 t->sbe.zoom = gtk_entry_new();
8296 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.zoom), NULL);
8297 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.zoom), FALSE);
8298 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.zoom), FALSE);
8299 gtk_widget_modify_font(GTK_WIDGET(t->sbe.zoom), statusbar_font);
8300 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.zoom), 1.0);
8301 gtk_widget_set_size_request(t->sbe.zoom, 40, -1);
8303 t->sbe.buffercmd = gtk_entry_new();
8304 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.buffercmd), NULL);
8305 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.buffercmd), FALSE);
8306 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.buffercmd), FALSE);
8307 gtk_widget_modify_font(GTK_WIDGET(t->sbe.buffercmd), statusbar_font);
8308 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.buffercmd), 1.0);
8309 gtk_widget_set_size_request(t->sbe.buffercmd, 60, -1);
8311 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8313 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8314 TRUE, FALSE);
8316 /* gtk widgets cannot be added to a box twice. sbe_* variables
8317 make sure of this */
8318 for (p = statusbar_elems; *p != '\0'; p++) {
8319 switch (*p) {
8320 case '|':
8322 GtkWidget *sep = gtk_vseparator_new();
8324 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8325 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8326 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8327 FALSE, FALSE, FALSE);
8328 break;
8330 case 'P':
8331 if (sbe_p) {
8332 warnx("flag \"%c\" specified more than "
8333 "once in statusbar_elems\n", *p);
8334 break;
8336 sbe_p = 1;
8337 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8338 t->sbe.position, FALSE, FALSE, FALSE);
8339 break;
8340 case 'B':
8341 if (sbe_b) {
8342 warnx("flag \"%c\" specified more than "
8343 "once in statusbar_elems\n", *p);
8344 break;
8346 sbe_b = 1;
8347 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8348 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8349 break;
8350 case 'Z':
8351 if (sbe_z) {
8352 warnx("flag \"%c\" specified more than "
8353 "once in statusbar_elems\n", *p);
8354 break;
8356 sbe_z = 1;
8357 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8358 t->sbe.zoom, FALSE, FALSE, FALSE);
8359 break;
8360 default:
8361 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8362 break;
8366 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8368 /* buffer list */
8369 t->buffers = create_buffers(t);
8370 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8372 /* xtp meaning is normal by default */
8373 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8375 /* set empty favicon */
8376 xt_icon_from_name(t, "text-html");
8378 /* and show it all */
8379 gtk_widget_show_all(b);
8380 gtk_widget_show_all(t->vbox);
8382 /* compact tab bar */
8383 t->tab_elems.label = gtk_label_new(title);
8384 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8385 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8386 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8387 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8389 t->tab_elems.eventbox = gtk_event_box_new();
8390 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8391 t->tab_elems.sep = gtk_vseparator_new();
8393 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8394 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8395 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8396 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8397 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8398 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8400 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8401 TRUE, 0);
8402 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8403 FALSE, 0);
8404 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8405 t->tab_elems.box);
8407 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8408 TRUE, 0);
8409 gtk_widget_show_all(t->tab_elems.eventbox);
8411 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8412 append_tab(t);
8413 else {
8414 id = position >= 0 ? position :
8415 gtk_notebook_get_current_page(notebook) + 1;
8416 if (id > gtk_notebook_get_n_pages(notebook))
8417 append_tab(t);
8418 else {
8419 TAILQ_INSERT_TAIL(&tabs, t, entry);
8420 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8421 gtk_box_reorder_child(GTK_BOX(tab_bar),
8422 t->tab_elems.eventbox, id);
8423 recalc_tabs();
8427 #if GTK_CHECK_VERSION(2, 20, 0)
8428 /* turn spinner off if we are a new tab without uri */
8429 if (!load) {
8430 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8431 gtk_widget_hide(t->spinner);
8433 #endif
8434 /* make notebook tabs reorderable */
8435 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8437 /* compact tabs clickable */
8438 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8439 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8441 g_object_connect(G_OBJECT(t->cmd),
8442 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8443 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8444 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8445 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8446 (char *)NULL);
8448 /* reuse wv_button_cb to hide oops */
8449 g_object_connect(G_OBJECT(t->oops),
8450 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8451 (char *)NULL);
8453 g_signal_connect(t->buffers,
8454 "row-activated", G_CALLBACK(row_activated_cb), t);
8455 g_object_connect(G_OBJECT(t->buffers),
8456 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8458 g_object_connect(G_OBJECT(t->wv),
8459 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8460 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8461 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8462 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8463 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8464 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8465 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8466 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8467 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8468 "signal::event", G_CALLBACK(webview_event_cb), t,
8469 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8470 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8471 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8472 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8473 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8474 (char *)NULL);
8475 g_signal_connect(t->wv,
8476 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8477 g_signal_connect(t->wv,
8478 "load-error", G_CALLBACK(notify_load_error_cb), t);
8479 g_signal_connect(t->wv,
8480 "notify::title", G_CALLBACK(notify_title_cb), t);
8482 /* hijack the unused keys as if we were the browser */
8483 g_object_connect(G_OBJECT(t->toolbar),
8484 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8485 (char *)NULL);
8487 g_signal_connect(G_OBJECT(bb), "button_press_event",
8488 G_CALLBACK(tab_close_cb), t);
8490 /* hide stuff */
8491 hide_cmd(t);
8492 hide_oops(t);
8493 hide_buffers(t);
8494 url_set_visibility();
8495 statusbar_set_visibility();
8497 if (focus) {
8498 set_current_tab(t->tab_id);
8499 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8500 t->tab_id);
8503 if (load) {
8504 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8505 load_uri(t, title);
8506 } else {
8507 if (show_url == 1)
8508 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8509 else
8510 focus_webview(t);
8513 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8514 /* restore the tab's history */
8515 if (u && u->history) {
8516 items = u->history;
8517 while (items) {
8518 item = items->data;
8519 webkit_web_back_forward_list_add_item(t->bfl, item);
8520 items = g_list_next(items);
8523 item = g_list_nth_data(u->history, u->back);
8524 if (item)
8525 webkit_web_view_go_to_back_forward_item(t->wv, item);
8527 g_list_free(items);
8528 g_list_free(u->history);
8529 } else
8530 webkit_web_back_forward_list_clear(t->bfl);
8532 recolor_compact_tabs();
8533 return (t);
8536 void
8537 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8538 gpointer *udata)
8540 struct tab *t;
8541 const gchar *uri;
8543 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8545 if (gtk_notebook_get_current_page(notebook) == -1)
8546 recalc_tabs();
8548 TAILQ_FOREACH(t, &tabs, entry) {
8549 if (t->tab_id == pn) {
8550 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8551 "%d\n", pn);
8553 uri = get_title(t, TRUE);
8554 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8556 hide_cmd(t);
8557 hide_oops(t);
8559 if (t->focus_wv) {
8560 /* can't use focus_webview here */
8561 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8567 void
8568 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8569 gpointer *udata)
8571 struct tab *t = NULL, *tt;
8573 recalc_tabs();
8575 TAILQ_FOREACH(tt, &tabs, entry)
8576 if (tt->tab_id == pn) {
8577 t = tt;
8578 break;
8581 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8583 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8584 t->tab_id);
8587 void
8588 menuitem_response(struct tab *t)
8590 gtk_notebook_set_current_page(notebook, t->tab_id);
8593 gboolean
8594 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8596 GtkWidget *menu, *menu_items;
8597 GdkEventButton *bevent;
8598 const gchar *uri;
8599 struct tab *ti;
8601 if (event->type == GDK_BUTTON_PRESS) {
8602 bevent = (GdkEventButton *) event;
8603 menu = gtk_menu_new();
8605 TAILQ_FOREACH(ti, &tabs, entry) {
8606 if ((uri = get_uri(ti)) == NULL)
8607 /* XXX make sure there is something to print */
8608 /* XXX add gui pages in here to look purdy */
8609 uri = "(untitled)";
8610 menu_items = gtk_menu_item_new_with_label(uri);
8611 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8612 gtk_widget_show(menu_items);
8614 g_signal_connect_swapped((menu_items),
8615 "activate", G_CALLBACK(menuitem_response),
8616 (gpointer)ti);
8619 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8620 bevent->button, bevent->time);
8622 /* unref object so it'll free itself when popped down */
8623 #if !GTK_CHECK_VERSION(3, 0, 0)
8624 /* XXX does not need unref with gtk+3? */
8625 g_object_ref_sink(menu);
8626 g_object_unref(menu);
8627 #endif
8629 return (TRUE /* eat event */);
8632 return (FALSE /* propagate */);
8636 icon_size_map(int icon_size)
8638 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8639 icon_size > GTK_ICON_SIZE_DIALOG)
8640 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8642 return (icon_size);
8645 GtkWidget *
8646 create_button(char *name, char *stockid, int size)
8648 GtkWidget *button, *image;
8649 gchar *rcstring;
8650 int gtk_icon_size;
8652 rcstring = g_strdup_printf(
8653 "style \"%s-style\"\n"
8654 "{\n"
8655 " GtkWidget::focus-padding = 0\n"
8656 " GtkWidget::focus-line-width = 0\n"
8657 " xthickness = 0\n"
8658 " ythickness = 0\n"
8659 "}\n"
8660 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8661 gtk_rc_parse_string(rcstring);
8662 g_free(rcstring);
8663 button = gtk_button_new();
8664 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8665 gtk_icon_size = icon_size_map(size ? size : icon_size);
8667 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8668 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8669 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8670 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8671 gtk_widget_set_name(button, name);
8672 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8674 return (button);
8677 void
8678 button_set_stockid(GtkWidget *button, char *stockid)
8680 GtkWidget *image;
8682 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8683 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8684 gtk_button_set_image(GTK_BUTTON(button), image);
8687 void
8688 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8690 gchar *p = NULL;
8691 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
8692 gint len;
8695 * xterm doesn't play nice with clipboards because it clears the
8696 * primary when clicked. We rely on primary being set to properly
8697 * handle middle mouse button clicks (paste). So when someone clears
8698 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
8699 * other application behavior (as in DON'T clear primary).
8702 p = gtk_clipboard_wait_for_text(primary);
8703 if (p == NULL) {
8704 if (gdk_property_get(gdk_get_default_root_window(),
8705 atom,
8706 gdk_atom_intern("STRING", FALSE),
8708 1024 * 1024 /* picked out of my butt */,
8709 FALSE,
8710 NULL,
8711 NULL,
8712 &len,
8713 (guchar **)&p)) {
8714 /* yes sir, we need to NUL the string */
8715 p[len] = '\0';
8716 gtk_clipboard_set_text(primary, p, -1);
8720 if (p)
8721 g_free(p);
8724 void
8725 create_canvas(void)
8727 GtkWidget *vbox;
8728 GList *l = NULL;
8729 GdkPixbuf *pb;
8730 char file[PATH_MAX];
8731 int i;
8733 vbox = gtk_vbox_new(FALSE, 0);
8734 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8735 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8736 #if !GTK_CHECK_VERSION(3, 0, 0)
8737 /* XXX seems to be needed with gtk+2 */
8738 gtk_notebook_set_tab_hborder(notebook, 0);
8739 gtk_notebook_set_tab_vborder(notebook, 0);
8740 #endif
8741 gtk_notebook_set_scrollable(notebook, TRUE);
8742 gtk_notebook_set_show_border(notebook, FALSE);
8743 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8745 abtn = gtk_button_new();
8746 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8747 gtk_widget_set_size_request(arrow, -1, -1);
8748 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8749 gtk_widget_set_size_request(abtn, -1, 20);
8751 #if GTK_CHECK_VERSION(2, 20, 0)
8752 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8753 #endif
8754 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8756 /* compact tab bar */
8757 tab_bar = gtk_hbox_new(TRUE, 0);
8759 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8760 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8761 gtk_widget_set_size_request(vbox, -1, -1);
8763 g_object_connect(G_OBJECT(notebook),
8764 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8765 (char *)NULL);
8766 g_object_connect(G_OBJECT(notebook),
8767 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
8768 NULL, (char *)NULL);
8769 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8770 G_CALLBACK(arrow_cb), NULL);
8772 main_window = create_window();
8773 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8775 /* icons */
8776 for (i = 0; i < LENGTH(icons); i++) {
8777 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8778 pb = gdk_pixbuf_new_from_file(file, NULL);
8779 l = g_list_append(l, pb);
8781 gtk_window_set_default_icon_list(l);
8783 /* clipboard */
8784 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8785 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8787 gtk_widget_show_all(abtn);
8788 gtk_widget_show_all(main_window);
8789 notebook_tab_set_visibility();
8792 void
8793 set_hook(void **hook, char *name)
8795 if (hook == NULL)
8796 errx(1, "set_hook");
8798 if (*hook == NULL) {
8799 *hook = dlsym(RTLD_NEXT, name);
8800 if (*hook == NULL)
8801 errx(1, "can't hook %s", name);
8805 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8806 gboolean
8807 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8809 g_return_val_if_fail(cookie1, FALSE);
8810 g_return_val_if_fail(cookie2, FALSE);
8812 return (!strcmp (cookie1->name, cookie2->name) &&
8813 !strcmp (cookie1->value, cookie2->value) &&
8814 !strcmp (cookie1->path, cookie2->path) &&
8815 !strcmp (cookie1->domain, cookie2->domain));
8818 void
8819 transfer_cookies(void)
8821 GSList *cf;
8822 SoupCookie *sc, *pc;
8824 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8826 for (;cf; cf = cf->next) {
8827 pc = cf->data;
8828 sc = soup_cookie_copy(pc);
8829 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8832 soup_cookies_free(cf);
8835 void
8836 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8838 GSList *cf;
8839 SoupCookie *ci;
8841 print_cookie("soup_cookie_jar_delete_cookie", c);
8843 if (cookies_enabled == 0)
8844 return;
8846 if (jar == NULL || c == NULL)
8847 return;
8849 /* find and remove from persistent jar */
8850 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8852 for (;cf; cf = cf->next) {
8853 ci = cf->data;
8854 if (soup_cookie_equal(ci, c)) {
8855 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8856 break;
8860 soup_cookies_free(cf);
8862 /* delete from session jar */
8863 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8866 void
8867 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8869 struct domain *d = NULL;
8870 SoupCookie *c;
8871 FILE *r_cookie_f;
8873 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8874 jar, p_cookiejar, s_cookiejar);
8876 if (cookies_enabled == 0)
8877 return;
8879 /* see if we are up and running */
8880 if (p_cookiejar == NULL) {
8881 _soup_cookie_jar_add_cookie(jar, cookie);
8882 return;
8884 /* disallow p_cookiejar adds, shouldn't happen */
8885 if (jar == p_cookiejar)
8886 return;
8888 /* sanity */
8889 if (jar == NULL || cookie == NULL)
8890 return;
8892 if (enable_cookie_whitelist &&
8893 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8894 blocked_cookies++;
8895 DNPRINTF(XT_D_COOKIE,
8896 "soup_cookie_jar_add_cookie: reject %s\n",
8897 cookie->domain);
8898 if (save_rejected_cookies) {
8899 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8900 show_oops(NULL, "can't open reject cookie file");
8901 return;
8903 fseek(r_cookie_f, 0, SEEK_END);
8904 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8905 cookie->http_only ? "#HttpOnly_" : "",
8906 cookie->domain,
8907 *cookie->domain == '.' ? "TRUE" : "FALSE",
8908 cookie->path,
8909 cookie->secure ? "TRUE" : "FALSE",
8910 cookie->expires ?
8911 (gulong)soup_date_to_time_t(cookie->expires) :
8913 cookie->name,
8914 cookie->value);
8915 fflush(r_cookie_f);
8916 fclose(r_cookie_f);
8918 if (!allow_volatile_cookies)
8919 return;
8922 if (cookie->expires == NULL && session_timeout) {
8923 soup_cookie_set_expires(cookie,
8924 soup_date_new_from_now(session_timeout));
8925 print_cookie("modified add cookie", cookie);
8928 /* see if we are white listed for persistence */
8929 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8930 /* add to persistent jar */
8931 c = soup_cookie_copy(cookie);
8932 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8933 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8936 /* add to session jar */
8937 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8938 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8941 void
8942 setup_cookies(void)
8944 char file[PATH_MAX];
8946 set_hook((void *)&_soup_cookie_jar_add_cookie,
8947 "soup_cookie_jar_add_cookie");
8948 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8949 "soup_cookie_jar_delete_cookie");
8951 if (cookies_enabled == 0)
8952 return;
8955 * the following code is intricate due to overriding several libsoup
8956 * functions.
8957 * do not alter order of these operations.
8960 /* rejected cookies */
8961 if (save_rejected_cookies)
8962 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
8963 XT_REJECT_FILE);
8965 /* persistent cookies */
8966 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8967 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8969 /* session cookies */
8970 s_cookiejar = soup_cookie_jar_new();
8971 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8972 cookie_policy, (void *)NULL);
8973 transfer_cookies();
8975 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8978 void
8979 setup_proxy(char *uri)
8981 if (proxy_uri) {
8982 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8983 soup_uri_free(proxy_uri);
8984 proxy_uri = NULL;
8986 if (http_proxy) {
8987 if (http_proxy != uri) {
8988 g_free(http_proxy);
8989 http_proxy = NULL;
8993 if (uri) {
8994 http_proxy = g_strdup(uri);
8995 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8996 proxy_uri = soup_uri_new(http_proxy);
8997 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
9002 send_cmd_to_socket(char *cmd)
9004 int s, len, rv = 1;
9005 struct sockaddr_un sa;
9007 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9008 warnx("%s: socket", __func__);
9009 return (rv);
9012 sa.sun_family = AF_UNIX;
9013 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9014 work_dir, XT_SOCKET_FILE);
9015 len = SUN_LEN(&sa);
9017 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9018 warnx("%s: connect", __func__);
9019 goto done;
9022 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9023 warnx("%s: send", __func__);
9024 goto done;
9027 rv = 0;
9028 done:
9029 close(s);
9030 return (rv);
9033 gboolean
9034 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9036 int s, n;
9037 char str[XT_MAX_URL_LENGTH];
9038 socklen_t t = sizeof(struct sockaddr_un);
9039 struct sockaddr_un sa;
9040 struct passwd *p;
9041 uid_t uid;
9042 gid_t gid;
9043 struct tab *tt;
9044 gint fd = g_io_channel_unix_get_fd(source);
9046 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9047 warn("accept");
9048 return (FALSE);
9051 if (getpeereid(s, &uid, &gid) == -1) {
9052 warn("getpeereid");
9053 return (FALSE);
9055 if (uid != getuid() || gid != getgid()) {
9056 warnx("unauthorized user");
9057 return (FALSE);
9060 p = getpwuid(uid);
9061 if (p == NULL) {
9062 warnx("not a valid user");
9063 return (FALSE);
9066 n = recv(s, str, sizeof(str), 0);
9067 if (n <= 0)
9068 return (TRUE);
9070 tt = TAILQ_LAST(&tabs, tab_list);
9071 cmd_execute(tt, str);
9072 return (TRUE);
9076 is_running(void)
9078 int s, len, rv = 1;
9079 struct sockaddr_un sa;
9081 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9082 warn("is_running: socket");
9083 return (-1);
9086 sa.sun_family = AF_UNIX;
9087 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9088 work_dir, XT_SOCKET_FILE);
9089 len = SUN_LEN(&sa);
9091 /* connect to see if there is a listener */
9092 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9093 rv = 0; /* not running */
9094 else
9095 rv = 1; /* already running */
9097 close(s);
9099 return (rv);
9103 build_socket(void)
9105 int s, len;
9106 struct sockaddr_un sa;
9108 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9109 warn("build_socket: socket");
9110 return (-1);
9113 sa.sun_family = AF_UNIX;
9114 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9115 work_dir, XT_SOCKET_FILE);
9116 len = SUN_LEN(&sa);
9118 /* connect to see if there is a listener */
9119 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9120 /* no listener so we will */
9121 unlink(sa.sun_path);
9123 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9124 warn("build_socket: bind");
9125 goto done;
9128 if (listen(s, 1) == -1) {
9129 warn("build_socket: listen");
9130 goto done;
9133 return (s);
9136 done:
9137 close(s);
9138 return (-1);
9141 gboolean
9142 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9143 GtkTreeIter *iter, struct tab *t)
9145 gchar *value;
9147 gtk_tree_model_get(model, iter, 0, &value, -1);
9148 load_uri(t, value);
9149 g_free(value);
9151 return (FALSE);
9154 gboolean
9155 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9156 GtkTreeIter *iter, struct tab *t)
9158 gchar *value;
9160 gtk_tree_model_get(model, iter, 0, &value, -1);
9161 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9162 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9163 g_free(value);
9165 return (TRUE);
9168 void
9169 completion_add_uri(const gchar *uri)
9171 GtkTreeIter iter;
9173 /* add uri to list_store */
9174 gtk_list_store_append(completion_model, &iter);
9175 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9178 gboolean
9179 completion_match(GtkEntryCompletion *completion, const gchar *key,
9180 GtkTreeIter *iter, gpointer user_data)
9182 gchar *value;
9183 gboolean match = FALSE;
9185 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9186 -1);
9188 if (value == NULL)
9189 return FALSE;
9191 match = match_uri(value, key);
9193 g_free(value);
9194 return (match);
9197 void
9198 completion_add(struct tab *t)
9200 /* enable completion for tab */
9201 t->completion = gtk_entry_completion_new();
9202 gtk_entry_completion_set_text_column(t->completion, 0);
9203 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9204 gtk_entry_completion_set_model(t->completion,
9205 GTK_TREE_MODEL(completion_model));
9206 gtk_entry_completion_set_match_func(t->completion, completion_match,
9207 NULL, NULL);
9208 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9209 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9210 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9211 G_CALLBACK(completion_select_cb), t);
9212 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9213 G_CALLBACK(completion_hover_cb), t);
9216 void
9217 xxx_dir(char *dir)
9219 struct stat sb;
9221 if (stat(dir, &sb)) {
9222 if (mkdir(dir, S_IRWXU) == -1)
9223 err(1, "mkdir %s", dir);
9224 if (stat(dir, &sb))
9225 err(1, "stat %s", dir);
9227 if (S_ISDIR(sb.st_mode) == 0)
9228 errx(1, "%s not a dir", dir);
9229 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9230 warnx("fixing invalid permissions on %s", dir);
9231 if (chmod(dir, S_IRWXU) == -1)
9232 err(1, "chmod %s", dir);
9236 void
9237 usage(void)
9239 fprintf(stderr,
9240 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9241 exit(0);
9246 main(int argc, char *argv[])
9248 struct stat sb;
9249 int c, s, optn = 0, opte = 0, focus = 1;
9250 char conf[PATH_MAX] = { '\0' };
9251 char file[PATH_MAX];
9252 char *env_proxy = NULL;
9253 char *cmd = NULL;
9254 FILE *f = NULL;
9255 struct karg a;
9256 struct sigaction sact;
9257 GIOChannel *channel;
9258 struct rlimit rlp;
9260 start_argv = argv;
9262 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9264 /* fiddle with ulimits */
9265 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9266 warn("getrlimit");
9267 else {
9268 /* just use them all */
9269 rlp.rlim_cur = rlp.rlim_max;
9270 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9271 warn("setrlimit");
9272 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9273 warn("getrlimit");
9274 else if (rlp.rlim_cur <= 256)
9275 warnx("%s requires at least 256 file descriptors",
9276 __progname);
9279 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9280 switch (c) {
9281 case 'S':
9282 show_url = 0;
9283 break;
9284 case 'T':
9285 show_tabs = 0;
9286 break;
9287 case 'V':
9288 errx(0 , "Version: %s", version);
9289 break;
9290 case 'f':
9291 strlcpy(conf, optarg, sizeof(conf));
9292 break;
9293 case 's':
9294 strlcpy(named_session, optarg, sizeof(named_session));
9295 break;
9296 case 't':
9297 tabless = 1;
9298 break;
9299 case 'n':
9300 optn = 1;
9301 break;
9302 case 'e':
9303 opte = 1;
9304 break;
9305 default:
9306 usage();
9307 /* NOTREACHED */
9310 argc -= optind;
9311 argv += optind;
9313 RB_INIT(&hl);
9314 RB_INIT(&js_wl);
9315 RB_INIT(&downloads);
9317 TAILQ_INIT(&tabs);
9318 TAILQ_INIT(&mtl);
9319 TAILQ_INIT(&aliases);
9320 TAILQ_INIT(&undos);
9321 TAILQ_INIT(&kbl);
9323 init_keybindings();
9325 gnutls_global_init();
9327 /* generate session keys for xtp pages */
9328 generate_xtp_session_key(&dl_session_key);
9329 generate_xtp_session_key(&hl_session_key);
9330 generate_xtp_session_key(&cl_session_key);
9331 generate_xtp_session_key(&fl_session_key);
9333 /* prepare gtk */
9334 gtk_init(&argc, &argv);
9335 if (!g_thread_supported())
9336 g_thread_init(NULL);
9338 /* signals */
9339 bzero(&sact, sizeof(sact));
9340 sigemptyset(&sact.sa_mask);
9341 sact.sa_handler = sigchild;
9342 sact.sa_flags = SA_NOCLDSTOP;
9343 sigaction(SIGCHLD, &sact, NULL);
9345 /* set download dir */
9346 pwd = getpwuid(getuid());
9347 if (pwd == NULL)
9348 errx(1, "invalid user %d", getuid());
9349 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9351 /* compile buffer command regexes */
9352 buffercmd_init();
9354 /* set default string settings */
9355 home = g_strdup("https://www.cyphertite.com");
9356 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9357 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9358 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9359 cmd_font_name = g_strdup("monospace normal 9");
9360 oops_font_name = g_strdup("monospace normal 9");
9361 statusbar_font_name = g_strdup("monospace normal 9");
9362 tabbar_font_name = g_strdup("monospace normal 9");
9363 statusbar_elems = g_strdup("BP");
9365 /* read config file */
9366 if (strlen(conf) == 0)
9367 snprintf(conf, sizeof conf, "%s/.%s",
9368 pwd->pw_dir, XT_CONF_FILE);
9369 config_parse(conf, 0);
9371 /* init fonts */
9372 cmd_font = pango_font_description_from_string(cmd_font_name);
9373 oops_font = pango_font_description_from_string(oops_font_name);
9374 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9375 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9377 /* working directory */
9378 if (strlen(work_dir) == 0)
9379 snprintf(work_dir, sizeof work_dir, "%s/%s",
9380 pwd->pw_dir, XT_DIR);
9381 xxx_dir(work_dir);
9383 /* icon cache dir */
9384 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9385 xxx_dir(cache_dir);
9387 /* certs dir */
9388 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9389 xxx_dir(certs_dir);
9391 /* sessions dir */
9392 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9393 work_dir, XT_SESSIONS_DIR);
9394 xxx_dir(sessions_dir);
9396 /* runtime settings that can override config file */
9397 if (runtime_settings[0] != '\0')
9398 config_parse(runtime_settings, 1);
9400 /* download dir */
9401 if (!strcmp(download_dir, pwd->pw_dir))
9402 strlcat(download_dir, "/downloads", sizeof download_dir);
9403 xxx_dir(download_dir);
9405 /* favorites file */
9406 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9407 if (stat(file, &sb)) {
9408 warnx("favorites file doesn't exist, creating it");
9409 if ((f = fopen(file, "w")) == NULL)
9410 err(1, "favorites");
9411 fclose(f);
9414 /* quickmarks file */
9415 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9416 if (stat(file, &sb)) {
9417 warnx("quickmarks file doesn't exist, creating it");
9418 if ((f = fopen(file, "w")) == NULL)
9419 err(1, "quickmarks");
9420 fclose(f);
9423 /* cookies */
9424 session = webkit_get_default_session();
9425 setup_cookies();
9427 /* certs */
9428 if (ssl_ca_file) {
9429 if (stat(ssl_ca_file, &sb)) {
9430 warnx("no CA file: %s", ssl_ca_file);
9431 g_free(ssl_ca_file);
9432 ssl_ca_file = NULL;
9433 } else
9434 g_object_set(session,
9435 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9436 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9437 (void *)NULL);
9440 /* proxy */
9441 env_proxy = getenv("http_proxy");
9442 if (env_proxy)
9443 setup_proxy(env_proxy);
9444 else
9445 setup_proxy(http_proxy);
9447 if (opte) {
9448 send_cmd_to_socket(argv[0]);
9449 exit(0);
9452 /* set some connection parameters */
9453 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9454 g_object_set(session, "max-conns-per-host", max_host_connections,
9455 (char *)NULL);
9457 /* see if there is already an xxxterm running */
9458 if (single_instance && is_running()) {
9459 optn = 1;
9460 warnx("already running");
9463 if (optn) {
9464 while (argc) {
9465 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9466 send_cmd_to_socket(cmd);
9467 if (cmd)
9468 g_free(cmd);
9470 argc--;
9471 argv++;
9473 exit(0);
9476 /* uri completion */
9477 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9479 /* buffers */
9480 buffers_store = gtk_list_store_new
9481 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9483 qmarks_load();
9485 /* go graphical */
9486 create_canvas();
9487 notebook_tab_set_visibility();
9489 if (save_global_history)
9490 restore_global_history();
9492 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9493 restore_saved_tabs();
9494 else {
9495 a.s = named_session;
9496 a.i = XT_SES_DONOTHING;
9497 open_tabs(NULL, &a);
9500 while (argc) {
9501 create_new_tab(argv[0], NULL, focus, -1);
9502 focus = 0;
9504 argc--;
9505 argv++;
9508 if (TAILQ_EMPTY(&tabs))
9509 create_new_tab(home, NULL, 1, -1);
9511 if (enable_socket)
9512 if ((s = build_socket()) != -1) {
9513 channel = g_io_channel_unix_new(s);
9514 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9517 gtk_main();
9519 gnutls_global_deinit();
9521 return (0);