when a cert is saved always blue the address bar.
[xxxterm.git] / xxxterm.c
blob38f37e466afb2fc814ef5d0361a48eb4dad8c388
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;
5864 /* see if we need to override green */
5865 r = load_compare_cert(t, NULL);
5866 if (r == 0)
5867 col_str = XT_COLOR_BLUE;
5868 goto done;
5869 } else {
5870 r = load_compare_cert(t, NULL);
5871 if (r == 0)
5872 col_str = XT_COLOR_BLUE;
5873 else if (r == 1)
5874 col_str = XT_COLOR_YELLOW;
5875 else
5876 col_str = XT_COLOR_RED;
5877 goto done;
5879 done:
5880 if (col_str) {
5881 gdk_color_parse(col_str, &color);
5882 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5884 if (!strcmp(col_str, XT_COLOR_WHITE))
5885 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5886 else
5887 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5891 void
5892 free_favicon(struct tab *t)
5894 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5895 __func__, t->icon_download, t->icon_request);
5897 if (t->icon_request)
5898 g_object_unref(t->icon_request);
5899 if (t->icon_dest_uri)
5900 g_free(t->icon_dest_uri);
5902 t->icon_request = NULL;
5903 t->icon_dest_uri = NULL;
5906 void
5907 xt_icon_from_name(struct tab *t, gchar *name)
5909 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5910 GTK_ENTRY_ICON_PRIMARY, "text-html");
5911 if (show_url == 0)
5912 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5913 GTK_ENTRY_ICON_PRIMARY, "text-html");
5914 else
5915 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5916 GTK_ENTRY_ICON_PRIMARY, NULL);
5919 void
5920 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5922 GdkPixbuf *pb_scaled;
5924 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5925 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
5926 GDK_INTERP_BILINEAR);
5927 else
5928 pb_scaled = pb;
5930 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5931 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5932 if (show_url == 0)
5933 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
5934 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5935 else
5936 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5937 GTK_ENTRY_ICON_PRIMARY, NULL);
5939 if (pb_scaled != pb)
5940 g_object_unref(pb_scaled);
5943 void
5944 xt_icon_from_file(struct tab *t, char *file)
5946 GdkPixbuf *pb;
5948 if (g_str_has_prefix(file, "file://"))
5949 file += strlen("file://");
5951 pb = gdk_pixbuf_new_from_file(file, NULL);
5952 if (pb) {
5953 xt_icon_from_pixbuf(t, pb);
5954 g_object_unref(pb);
5955 } else
5956 xt_icon_from_name(t, "text-html");
5959 gboolean
5960 is_valid_icon(char *file)
5962 gboolean valid = 0;
5963 const char *mime_type;
5964 GFileInfo *fi;
5965 GFile *gf;
5967 gf = g_file_new_for_path(file);
5968 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5969 NULL, NULL);
5970 mime_type = g_file_info_get_content_type(fi);
5971 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5972 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5973 g_strcmp0(mime_type, "image/png") == 0 ||
5974 g_strcmp0(mime_type, "image/gif") == 0 ||
5975 g_strcmp0(mime_type, "application/octet-stream") == 0;
5976 g_object_unref(fi);
5977 g_object_unref(gf);
5979 return (valid);
5982 void
5983 set_favicon_from_file(struct tab *t, char *file)
5985 struct stat sb;
5987 if (t == NULL || file == NULL)
5988 return;
5990 if (g_str_has_prefix(file, "file://"))
5991 file += strlen("file://");
5992 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5994 if (!stat(file, &sb)) {
5995 if (sb.st_size == 0 || !is_valid_icon(file)) {
5996 /* corrupt icon so trash it */
5997 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5998 __func__, file);
5999 unlink(file);
6000 /* no need to set icon to default here */
6001 return;
6004 xt_icon_from_file(t, file);
6007 void
6008 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6009 WebKitWebView *wv)
6011 WebKitDownloadStatus status = webkit_download_get_status(download);
6012 struct tab *tt = NULL, *t = NULL;
6015 * find the webview instead of passing in the tab as it could have been
6016 * deleted from underneath us.
6018 TAILQ_FOREACH(tt, &tabs, entry) {
6019 if (tt->wv == wv) {
6020 t = tt;
6021 break;
6024 if (t == NULL)
6025 return;
6027 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6028 __func__, t->tab_id, status);
6030 switch (status) {
6031 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6032 /* -1 */
6033 t->icon_download = NULL;
6034 free_favicon(t);
6035 break;
6036 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6037 /* 0 */
6038 break;
6039 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6040 /* 1 */
6041 break;
6042 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6043 /* 2 */
6044 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6045 __func__, t->tab_id);
6046 t->icon_download = NULL;
6047 free_favicon(t);
6048 break;
6049 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6050 /* 3 */
6052 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6053 __func__, t->icon_dest_uri);
6054 set_favicon_from_file(t, t->icon_dest_uri);
6055 /* these will be freed post callback */
6056 t->icon_request = NULL;
6057 t->icon_download = NULL;
6058 break;
6059 default:
6060 break;
6064 void
6065 abort_favicon_download(struct tab *t)
6067 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6069 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6070 if (t->icon_download) {
6071 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6072 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6073 webkit_download_cancel(t->icon_download);
6074 t->icon_download = NULL;
6075 } else
6076 free_favicon(t);
6077 #endif
6079 xt_icon_from_name(t, "text-html");
6082 void
6083 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6085 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6087 if (uri == NULL || t == NULL)
6088 return;
6090 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6091 /* take icon from WebKitIconDatabase */
6092 GdkPixbuf *pb;
6094 pb = webkit_web_view_get_icon_pixbuf(wv);
6095 if (pb) {
6096 xt_icon_from_pixbuf(t, pb);
6097 g_object_unref(pb);
6098 } else
6099 xt_icon_from_name(t, "text-html");
6100 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6101 /* download icon to cache dir */
6102 gchar *name_hash, file[PATH_MAX];
6103 struct stat sb;
6105 if (t->icon_request) {
6106 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6107 return;
6110 /* check to see if we got the icon in cache */
6111 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6112 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6113 g_free(name_hash);
6115 if (!stat(file, &sb)) {
6116 if (sb.st_size > 0) {
6117 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6118 __func__, file);
6119 set_favicon_from_file(t, file);
6120 return;
6123 /* corrupt icon so trash it */
6124 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6125 __func__, file);
6126 unlink(file);
6129 /* create download for icon */
6130 t->icon_request = webkit_network_request_new(uri);
6131 if (t->icon_request == NULL) {
6132 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6133 __func__, uri);
6134 return;
6137 t->icon_download = webkit_download_new(t->icon_request);
6138 if (t->icon_download == NULL)
6139 return;
6141 /* we have to free icon_dest_uri later */
6142 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6143 webkit_download_set_destination_uri(t->icon_download,
6144 t->icon_dest_uri);
6146 if (webkit_download_get_status(t->icon_download) ==
6147 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6148 g_object_unref(t->icon_request);
6149 g_free(t->icon_dest_uri);
6150 t->icon_request = NULL;
6151 t->icon_dest_uri = NULL;
6152 return;
6155 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6156 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6158 webkit_download_start(t->icon_download);
6159 #endif
6162 void
6163 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6165 const gchar *uri = NULL, *title = NULL;
6166 struct history *h, find;
6167 struct karg a;
6169 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6170 webkit_web_view_get_load_status(wview),
6171 get_uri(t) ? get_uri(t) : "NOTHING");
6173 if (t == NULL) {
6174 show_oops(NULL, "notify_load_status_cb invalid parameters");
6175 return;
6178 switch (webkit_web_view_get_load_status(wview)) {
6179 case WEBKIT_LOAD_PROVISIONAL:
6180 /* 0 */
6181 abort_favicon_download(t);
6182 #if GTK_CHECK_VERSION(2, 20, 0)
6183 gtk_widget_show(t->spinner);
6184 gtk_spinner_start(GTK_SPINNER(t->spinner));
6185 #endif
6186 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6188 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6190 /* take focus if we are visible */
6191 focus_webview(t);
6192 t->focus_wv = 1;
6194 break;
6196 case WEBKIT_LOAD_COMMITTED:
6197 /* 1 */
6198 uri = get_uri(t);
6199 if (uri == NULL)
6200 return;
6201 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6203 if (t->status) {
6204 g_free(t->status);
6205 t->status = NULL;
6207 set_status(t, (char *)uri, XT_STATUS_LOADING);
6209 /* check if js white listing is enabled */
6210 if (enable_js_whitelist) {
6211 check_and_set_js(uri, t);
6214 if (t->styled)
6215 apply_style(t);
6218 /* we know enough to autosave the session */
6219 if (session_autosave) {
6220 a.s = NULL;
6221 save_tabs(t, &a);
6224 show_ca_status(t, uri);
6225 break;
6227 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6228 /* 3 */
6229 break;
6231 case WEBKIT_LOAD_FINISHED:
6232 /* 2 */
6233 uri = get_uri(t);
6234 if (uri == NULL)
6235 return;
6237 if (!strncmp(uri, "http://", strlen("http://")) ||
6238 !strncmp(uri, "https://", strlen("https://")) ||
6239 !strncmp(uri, "file://", strlen("file://"))) {
6240 find.uri = uri;
6241 h = RB_FIND(history_list, &hl, &find);
6242 if (!h) {
6243 title = get_title(t, FALSE);
6244 h = g_malloc(sizeof *h);
6245 h->uri = g_strdup(uri);
6246 h->title = g_strdup(title);
6247 RB_INSERT(history_list, &hl, h);
6248 completion_add_uri(h->uri);
6249 update_history_tabs(NULL);
6253 set_status(t, (char *)uri, XT_STATUS_URI);
6254 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6255 case WEBKIT_LOAD_FAILED:
6256 /* 4 */
6257 #endif
6258 #if GTK_CHECK_VERSION(2, 20, 0)
6259 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6260 gtk_widget_hide(t->spinner);
6261 #endif
6262 default:
6263 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6264 break;
6267 if (t->item)
6268 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6269 else
6270 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6271 webkit_web_view_can_go_back(wview));
6273 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6274 webkit_web_view_can_go_forward(wview));
6277 gboolean
6278 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame,
6279 gchar *uri, gpointer web_error,struct tab *t)
6281 if (t->tmp_uri)
6282 g_free(t->tmp_uri);
6283 t->tmp_uri = g_strdup(uri);
6284 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6285 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6286 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6287 set_status(t, uri, XT_STATUS_NOTHING);
6289 return (FALSE);
6292 void
6293 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6295 const gchar *title = NULL, *win_title = NULL;
6297 title = get_title(t, FALSE);
6298 win_title = get_title(t, TRUE);
6299 gtk_label_set_text(GTK_LABEL(t->label), title);
6300 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6301 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6302 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6305 void
6306 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6308 run_script(t, JS_HINTING);
6311 void
6312 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6314 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6315 progress == 100 ? 0 : (double)progress / 100);
6316 if (show_url == 0) {
6317 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6318 progress == 100 ? 0 : (double)progress / 100);
6321 update_statusbar_position(NULL, NULL);
6325 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6326 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6327 WebKitWebPolicyDecision *pd, struct tab *t)
6329 char *uri;
6330 WebKitWebNavigationReason reason;
6331 struct domain *d = NULL;
6333 if (t == NULL) {
6334 show_oops(NULL, "webview_npd_cb invalid parameters");
6335 return (FALSE);
6338 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6339 t->ctrl_click,
6340 webkit_network_request_get_uri(request));
6342 uri = (char *)webkit_network_request_get_uri(request);
6344 /* if this is an xtp url, we don't load anything else */
6345 if (parse_xtp_url(t, uri))
6346 return (TRUE);
6348 if (t->ctrl_click) {
6349 t->ctrl_click = 0;
6350 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6351 webkit_web_policy_decision_ignore(pd);
6352 return (TRUE); /* we made the decission */
6356 * This is a little hairy but it comes down to this:
6357 * when we run in whitelist mode we have to assist the browser in
6358 * opening the URL that it would have opened in a new tab.
6360 reason = webkit_web_navigation_action_get_reason(na);
6361 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6362 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6363 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6364 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6365 load_uri(t, uri);
6366 webkit_web_policy_decision_use(pd);
6367 return (TRUE); /* we made the decision */
6370 return (FALSE);
6373 WebKitWebView *
6374 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6376 struct tab *tt;
6377 struct domain *d = NULL;
6378 const gchar *uri;
6379 WebKitWebView *webview = NULL;
6381 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6382 webkit_web_view_get_uri(wv));
6384 if (tabless) {
6385 /* open in current tab */
6386 webview = t->wv;
6387 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6388 uri = webkit_web_view_get_uri(wv);
6389 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6390 return (NULL);
6392 tt = create_new_tab(NULL, NULL, 1, -1);
6393 webview = tt->wv;
6394 } else if (enable_scripts == 1) {
6395 tt = create_new_tab(NULL, NULL, 1, -1);
6396 webview = tt->wv;
6399 return (webview);
6402 gboolean
6403 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6405 const gchar *uri;
6406 struct domain *d = NULL;
6408 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6410 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6411 uri = webkit_web_view_get_uri(wv);
6412 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6413 return (FALSE);
6415 delete_tab(t);
6416 } else if (enable_scripts == 1)
6417 delete_tab(t);
6419 return (TRUE);
6423 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6425 /* we can not eat the event without throwing gtk off so defer it */
6427 /* catch middle click */
6428 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6429 t->ctrl_click = 1;
6430 goto done;
6433 /* catch ctrl click */
6434 if (e->type == GDK_BUTTON_RELEASE &&
6435 CLEAN(e->state) == GDK_CONTROL_MASK)
6436 t->ctrl_click = 1;
6437 else
6438 t->ctrl_click = 0;
6439 done:
6440 return (XT_CB_PASSTHROUGH);
6444 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6446 struct mime_type *m;
6448 m = find_mime_type(mime_type);
6449 if (m == NULL)
6450 return (1);
6451 if (m->mt_download)
6452 return (1);
6454 switch (fork()) {
6455 case -1:
6456 show_oops(t, "can't fork mime handler");
6457 return (1);
6458 /* NOTREACHED */
6459 case 0:
6460 break;
6461 default:
6462 return (0);
6465 /* child */
6466 execlp(m->mt_action, m->mt_action,
6467 webkit_network_request_get_uri(request), (void *)NULL);
6469 _exit(0);
6471 /* NOTREACHED */
6472 return (0);
6475 const gchar *
6476 get_mime_type(char *file)
6478 const char *mime_type;
6479 GFileInfo *fi;
6480 GFile *gf;
6482 if (g_str_has_prefix(file, "file://"))
6483 file += strlen("file://");
6485 gf = g_file_new_for_path(file);
6486 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6487 NULL, NULL);
6488 mime_type = g_file_info_get_content_type(fi);
6489 g_object_unref(fi);
6490 g_object_unref(gf);
6492 return (mime_type);
6496 run_download_mimehandler(char *mime_type, char *file)
6498 struct mime_type *m;
6500 m = find_mime_type(mime_type);
6501 if (m == NULL)
6502 return (1);
6504 switch (fork()) {
6505 case -1:
6506 show_oops(NULL, "can't fork download mime handler");
6507 return (1);
6508 /* NOTREACHED */
6509 case 0:
6510 break;
6511 default:
6512 return (0);
6515 /* child */
6516 if (g_str_has_prefix(file, "file://"))
6517 file += strlen("file://");
6518 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6520 _exit(0);
6522 /* NOTREACHED */
6523 return (0);
6526 void
6527 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6528 WebKitWebView *wv)
6530 WebKitDownloadStatus status;
6531 const gchar *file = NULL, *mime = NULL;
6533 if (download == NULL)
6534 return;
6535 status = webkit_download_get_status(download);
6536 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6537 return;
6539 file = webkit_download_get_destination_uri(download);
6540 if (file == NULL)
6541 return;
6542 mime = get_mime_type((char *)file);
6543 if (mime == NULL)
6544 return;
6546 run_download_mimehandler((char *)mime, (char *)file);
6550 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6551 WebKitNetworkRequest *request, char *mime_type,
6552 WebKitWebPolicyDecision *decision, struct tab *t)
6554 if (t == NULL) {
6555 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6556 return (FALSE);
6559 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6560 t->tab_id, mime_type);
6562 if (run_mimehandler(t, mime_type, request) == 0) {
6563 webkit_web_policy_decision_ignore(decision);
6564 focus_webview(t);
6565 return (TRUE);
6568 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6569 webkit_web_policy_decision_download(decision);
6570 return (TRUE);
6573 return (FALSE);
6577 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6578 struct tab *t)
6580 struct stat sb;
6581 const gchar *suggested_name;
6582 gchar *filename = NULL;
6583 char *uri = NULL;
6584 struct download *download_entry;
6585 int i, ret = TRUE;
6587 if (wk_download == NULL || t == NULL) {
6588 show_oops(NULL, "%s invalid parameters", __func__);
6589 return (FALSE);
6592 suggested_name = webkit_download_get_suggested_filename(wk_download);
6593 if (suggested_name == NULL)
6594 return (FALSE); /* abort download */
6596 i = 0;
6597 do {
6598 if (filename) {
6599 g_free(filename);
6600 filename = NULL;
6602 if (i) {
6603 g_free(uri);
6604 uri = NULL;
6605 filename = g_strdup_printf("%d%s", i, suggested_name);
6607 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6608 filename : suggested_name);
6609 i++;
6610 } while (!stat(uri + strlen("file://"), &sb));
6612 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6613 "local %s\n", __func__, t->tab_id, filename, uri);
6615 webkit_download_set_destination_uri(wk_download, uri);
6617 if (webkit_download_get_status(wk_download) ==
6618 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6619 show_oops(t, "%s: download failed to start", __func__);
6620 ret = FALSE;
6621 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6622 } else {
6623 /* connect "download first" mime handler */
6624 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6625 G_CALLBACK(download_status_changed_cb), NULL);
6627 download_entry = g_malloc(sizeof(struct download));
6628 download_entry->download = wk_download;
6629 download_entry->tab = t;
6630 download_entry->id = next_download_id++;
6631 RB_INSERT(download_list, &downloads, download_entry);
6632 /* get from history */
6633 g_object_ref(wk_download);
6634 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6635 show_oops(t, "Download of '%s' started...",
6636 basename((char *)webkit_download_get_destination_uri(wk_download)));
6639 if (uri)
6640 g_free(uri);
6642 if (filename)
6643 g_free(filename);
6645 /* sync other download manager tabs */
6646 update_download_tabs(NULL);
6649 * NOTE: never redirect/render the current tab before this
6650 * function returns. This will cause the download to never start.
6652 return (ret); /* start download */
6655 void
6656 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6658 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6660 if (t == NULL) {
6661 show_oops(NULL, "webview_hover_cb");
6662 return;
6665 if (uri)
6666 set_status(t, uri, XT_STATUS_LINK);
6667 else {
6668 if (t->status)
6669 set_status(t, t->status, XT_STATUS_NOTHING);
6674 mark(struct tab *t, struct karg *arg)
6676 char mark;
6677 int index;
6679 mark = arg->s[1];
6680 if ((index = marktoindex(mark)) == -1)
6681 return -1;
6683 if (arg->i == XT_MARK_SET)
6684 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6685 else if (arg->i == XT_MARK_GOTO) {
6686 if (t->mark[index] == XT_INVALID_MARK) {
6687 show_oops(t, "mark '%c' does not exist", mark);
6688 return -1;
6690 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6691 something changes the document size */
6692 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6695 return 0;
6698 void
6699 marks_clear(struct tab *t)
6701 int i;
6703 for (i = 0; i < LENGTH(t->mark); i++)
6704 t->mark[i] = XT_INVALID_MARK;
6708 qmarks_load(void)
6710 char file[PATH_MAX];
6711 char *line = NULL, *p, mark;
6712 int index, i;
6713 FILE *f;
6714 size_t linelen;
6716 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6717 if ((f = fopen(file, "r+")) == NULL) {
6718 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6719 return (1);
6722 for (i = 1; ; i++) {
6723 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
6724 if (feof(f) || ferror(f))
6725 break;
6726 if (strlen(line) == 0 || line[0] == '#') {
6727 free(line);
6728 line = NULL;
6729 continue;
6732 p = strtok(line, " \t");
6734 if (p == NULL || strlen(p) != 1 ||
6735 (index = marktoindex(*p)) == -1) {
6736 warnx("corrupt quickmarks file, line %d", i);
6737 break;
6740 mark = *p;
6741 p = strtok(NULL, " \t");
6742 if (qmarks[index] != NULL)
6743 g_free(qmarks[index]);
6744 qmarks[index] = g_strdup(p);
6747 fclose(f);
6749 return (0);
6753 qmarks_save(void)
6755 char file[PATH_MAX];
6756 int i;
6757 FILE *f;
6759 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6760 if ((f = fopen(file, "r+")) == NULL) {
6761 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6762 return (1);
6765 for (i = 0; i < XT_NOMARKS; i++)
6766 if (qmarks[i] != NULL)
6767 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
6769 fclose(f);
6771 return (0);
6775 qmark(struct tab *t, struct karg *arg)
6777 char mark;
6778 int index;
6780 mark = arg->s[strlen(arg->s)-1];
6781 index = marktoindex(mark);
6782 if (index == -1)
6783 return (-1);
6785 switch (arg->i) {
6786 case XT_QMARK_SET:
6787 if (qmarks[index] != NULL)
6788 g_free(qmarks[index]);
6790 qmarks_load(); /* sync if multiple instances */
6791 qmarks[index] = g_strdup(get_uri(t));
6792 qmarks_save();
6793 break;
6794 case XT_QMARK_OPEN:
6795 if (qmarks[index] != NULL)
6796 load_uri(t, qmarks[index]);
6797 else {
6798 show_oops(t, "quickmark \"%c\" does not exist",
6799 mark);
6800 return (-1);
6802 break;
6803 case XT_QMARK_TAB:
6804 if (qmarks[index] != NULL)
6805 create_new_tab(qmarks[index], NULL, 1, -1);
6806 else {
6807 show_oops(t, "quickmark \"%c\" does not exist",
6808 mark);
6809 return (-1);
6811 break;
6814 return (0);
6818 go_up(struct tab *t, struct karg *args)
6820 int levels;
6821 char *uri;
6822 char *tmp;
6824 levels = atoi(args->s);
6825 if (levels == 0)
6826 levels = 1;
6828 uri = g_strdup(webkit_web_view_get_uri(t->wv));
6829 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
6830 return 1;
6831 tmp += strlen(XT_PROTO_DELIM);
6833 /* if an uri starts with a slash, leave it alone (for file:///) */
6834 if (tmp[0] == '/')
6835 tmp++;
6837 while (levels--) {
6838 char *p;
6840 p = strrchr(tmp, '/');
6841 if (p != NULL)
6842 *p = '\0';
6843 else
6844 break;
6847 load_uri(t, uri);
6848 g_free(uri);
6850 return 0;
6854 gototab(struct tab *t, struct karg *args)
6856 int tab;
6857 struct karg arg = {0, NULL, -1};
6859 tab = atoi(args->s);
6861 arg.i = XT_TAB_NEXT;
6862 arg.p = tab;
6864 movetab(t, &arg);
6866 return 0;
6870 zoom_amount(struct tab *t, struct karg *arg)
6872 struct karg narg = {0, NULL, -1};
6874 narg.i = atoi(arg->s);
6875 resizetab(t, &narg);
6877 return 0;
6880 /* buffer commands receive the regex that triggered them in arg.s */
6881 char bcmd[8];
6882 struct buffercmd {
6883 char *regex;
6884 int (*func)(struct tab *, struct karg *);
6885 int arg;
6886 regex_t cregex;
6887 } buffercmds[] = {
6888 { "^[0-9]*gu$", go_up, 0 },
6889 { "^gg$", move, XT_MOVE_TOP },
6890 { "^gG$", move, XT_MOVE_BOTTOM },
6891 { "^[0-9]+%$", move, XT_MOVE_PERCENT },
6892 { "^gh$", go_home, 0 },
6893 { "^m[a-zA-Z0-9]$", mark, XT_MARK_SET },
6894 { "^[`'][a-zA-Z0-9]$", mark, XT_MARK_GOTO },
6895 { "^[0-9]+t$", gototab, 0 },
6896 { "^M[a-zA-Z0-9]$", qmark, XT_QMARK_SET },
6897 { "^go[a-zA-Z0-9]$", qmark, XT_QMARK_OPEN },
6898 { "^gn[a-zA-Z0-9]$", qmark, XT_QMARK_TAB },
6899 { "^ZR$", restart, 0 },
6900 { "^ZZ$", quit, 0 },
6901 { "^zi$", resizetab, XT_ZOOM_IN },
6902 { "^zo$", resizetab, XT_ZOOM_OUT },
6903 { "^z0$", resizetab, XT_ZOOM_NORMAL },
6904 { "^[0-9]+Z$", zoom_amount, 0 },
6907 void
6908 buffercmd_init(void)
6910 int i;
6912 for (i = 0; i < LENGTH(buffercmds); i++)
6913 regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
6914 REG_EXTENDED);
6917 void
6918 buffercmd_abort(struct tab *t)
6920 int i;
6922 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
6923 for (i = 0; i < LENGTH(bcmd); i++)
6924 bcmd[i] = '\0';
6926 cmd_prefix = 0; /* clear prefix for non-buffer commands */
6927 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6930 void
6931 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
6933 struct karg arg = {0, NULL, -1};
6935 arg.i = cmd->arg;
6936 arg.s = g_strdup(bcmd);
6938 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
6939 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
6940 cmd->func(t, &arg);
6942 if (arg.s)
6943 g_free(arg.s);
6945 buffercmd_abort(t);
6948 gboolean
6949 buffercmd_addkey(struct tab *t, guint keyval)
6951 int i;
6953 if (keyval == GDK_Escape) {
6954 buffercmd_abort(t);
6955 return (XT_CB_HANDLED);
6958 /* key with modifier or non-ascii character */
6959 if (!isascii(keyval))
6960 return (XT_CB_PASSTHROUGH);
6962 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
6963 "to buffer \"%s\"\n", keyval, bcmd);
6965 for (i = 0; i < LENGTH(bcmd); i++)
6966 if (bcmd[i] == '\0') {
6967 bcmd[i] = keyval;
6968 break;
6971 /* buffer full, ignore input */
6972 if (i == LENGTH(bcmd)) {
6973 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
6974 return (XT_CB_HANDLED);
6977 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6979 for (i = 0; i < LENGTH(buffercmds); i++)
6980 if (regexec(&buffercmds[i].cregex, bcmd,
6981 (size_t) 0, NULL, 0) == 0) {
6982 buffercmd_execute(t, &buffercmds[i]);
6983 break;
6986 return (XT_CB_HANDLED);
6989 gboolean
6990 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6992 struct key_binding *k;
6994 /* handle keybindings if buffercmd is empty.
6995 if not empty, allow commands like C-n */
6996 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
6997 TAILQ_FOREACH(k, &kbl, entry)
6998 if (e->keyval == k->key
6999 && (entry ? k->use_in_entry : 1)) {
7000 if (k->mask == 0) {
7001 if ((e->state & (CTRL | MOD1)) == 0)
7002 return (cmd_execute(t, k->cmd));
7003 } else if ((e->state & k->mask) == k->mask) {
7004 return (cmd_execute(t, k->cmd));
7008 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7009 return buffercmd_addkey(t, e->keyval);
7011 return (XT_CB_PASSTHROUGH);
7015 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7017 char s[2], buf[128];
7018 const char *errstr = NULL;
7020 /* don't use w directly; use t->whatever instead */
7022 if (t == NULL) {
7023 show_oops(NULL, "wv_keypress_after_cb");
7024 return (XT_CB_PASSTHROUGH);
7027 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7028 e->keyval, e->state, t);
7030 if (t->hints_on) {
7031 /* ESC */
7032 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7033 disable_hints(t);
7034 return (XT_CB_HANDLED);
7037 /* RETURN */
7038 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
7039 if (errstr) {
7040 /* we have a string */
7041 } else {
7042 /* we have a number */
7043 snprintf(buf, sizeof buf,
7044 "vimprobable_fire(%s)", t->hint_num);
7045 run_script(t, buf);
7047 disable_hints(t);
7050 /* BACKSPACE */
7051 /* XXX unfuck this */
7052 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
7053 if (t->hint_mode == XT_HINT_NUMERICAL) {
7054 /* last input was numerical */
7055 int l;
7056 l = strlen(t->hint_num);
7057 if (l > 0) {
7058 l--;
7059 if (l == 0) {
7060 disable_hints(t);
7061 enable_hints(t);
7062 } else {
7063 t->hint_num[l] = '\0';
7064 goto num;
7067 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
7068 /* last input was alphanumerical */
7069 int l;
7070 l = strlen(t->hint_buf);
7071 if (l > 0) {
7072 l--;
7073 if (l == 0) {
7074 disable_hints(t);
7075 enable_hints(t);
7076 } else {
7077 t->hint_buf[l] = '\0';
7078 goto anum;
7081 } else {
7082 /* bogus */
7083 disable_hints(t);
7087 /* numerical input */
7088 if (CLEAN(e->state) == 0 &&
7089 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
7090 (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
7091 snprintf(s, sizeof s, "%c", e->keyval);
7092 strlcat(t->hint_num, s, sizeof t->hint_num);
7093 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
7094 t->hint_num);
7095 num:
7096 if (errstr) {
7097 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
7098 "invalid link number\n");
7099 disable_hints(t);
7100 } else {
7101 snprintf(buf, sizeof buf,
7102 "vimprobable_update_hints(%s)",
7103 t->hint_num);
7104 t->hint_mode = XT_HINT_NUMERICAL;
7105 run_script(t, buf);
7108 /* empty the counter buffer */
7109 bzero(t->hint_buf, sizeof t->hint_buf);
7110 return (XT_CB_HANDLED);
7113 /* alphanumerical input */
7114 if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
7115 e->keyval <= GDK_z) ||
7116 (CLEAN(e->state) == GDK_SHIFT_MASK &&
7117 e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
7118 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
7119 e->keyval <= GDK_9) ||
7120 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
7121 (t->hint_mode != XT_HINT_NUMERICAL))))) {
7122 snprintf(s, sizeof s, "%c", e->keyval);
7123 strlcat(t->hint_buf, s, sizeof t->hint_buf);
7124 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
7125 " %s\n", t->hint_buf);
7126 anum:
7127 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
7128 run_script(t, buf);
7130 snprintf(buf, sizeof buf,
7131 "vimprobable_show_hints('%s')", t->hint_buf);
7132 t->hint_mode = XT_HINT_ALPHANUM;
7133 run_script(t, buf);
7135 /* empty the counter buffer */
7136 bzero(t->hint_num, sizeof t->hint_num);
7137 return (XT_CB_HANDLED);
7140 return (XT_CB_HANDLED);
7141 } else {
7142 /* prefix input*/
7143 snprintf(s, sizeof s, "%c", e->keyval);
7144 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7145 cmd_prefix = 10 * cmd_prefix + atoi(s);
7148 return (handle_keypress(t, e, 0));
7152 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7154 hide_oops(t);
7156 /* Hide buffers, if they are visible, with escape. */
7157 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7158 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7159 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7160 hide_buffers(t);
7161 return (XT_CB_HANDLED);
7164 return (XT_CB_PASSTHROUGH);
7168 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7170 const gchar *c = gtk_entry_get_text(w);
7171 GdkColor color;
7172 int forward = TRUE;
7174 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7175 e->keyval, e->state, t);
7177 if (t == NULL) {
7178 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
7179 return (XT_CB_PASSTHROUGH);
7182 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7183 e->keyval, e->state, t);
7185 if (c[0] == ':')
7186 goto done;
7187 if (strlen(c) == 1) {
7188 webkit_web_view_unmark_text_matches(t->wv);
7189 goto done;
7192 if (c[0] == '/')
7193 forward = TRUE;
7194 else if (c[0] == '?')
7195 forward = FALSE;
7196 else
7197 goto done;
7199 /* search */
7200 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
7201 FALSE) {
7202 /* not found, mark red */
7203 gdk_color_parse(XT_COLOR_RED, &color);
7204 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7205 /* unmark and remove selection */
7206 webkit_web_view_unmark_text_matches(t->wv);
7207 /* my kingdom for a way to unselect text in webview */
7208 } else {
7209 /* found, highlight all */
7210 webkit_web_view_unmark_text_matches(t->wv);
7211 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
7212 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
7213 gdk_color_parse(XT_COLOR_WHITE, &color);
7214 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
7216 done:
7217 return (XT_CB_PASSTHROUGH);
7220 gboolean
7221 match_uri(const gchar *uri, const gchar *key) {
7222 gchar *voffset;
7223 size_t len;
7224 gboolean match = FALSE;
7226 len = strlen(key);
7228 if (!strncmp(key, uri, len))
7229 match = TRUE;
7230 else {
7231 voffset = strstr(uri, "/") + 2;
7232 if (!strncmp(key, voffset, len))
7233 match = TRUE;
7234 else if (g_str_has_prefix(voffset, "www.")) {
7235 voffset = voffset + strlen("www.");
7236 if (!strncmp(key, voffset, len))
7237 match = TRUE;
7241 return (match);
7244 void
7245 cmd_getlist(int id, char *key)
7247 int i, dep, c = 0;
7248 struct history *h;
7250 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
7251 RB_FOREACH_REVERSE(h, history_list, &hl)
7252 if (match_uri(h->uri, key)) {
7253 cmd_status.list[c] = (char *)h->uri;
7254 if (++c > 255)
7255 break;
7258 cmd_status.len = c;
7259 return;
7262 dep = (id == -1) ? 0 : cmds[id].level + 1;
7264 for (i = id + 1; i < LENGTH(cmds); i++) {
7265 if (cmds[i].level < dep)
7266 break;
7267 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
7268 strlen(key)))
7269 cmd_status.list[c++] = cmds[i].cmd;
7273 cmd_status.len = c;
7276 char *
7277 cmd_getnext(int dir)
7279 cmd_status.index += dir;
7281 if (cmd_status.index < 0)
7282 cmd_status.index = cmd_status.len - 1;
7283 else if (cmd_status.index >= cmd_status.len)
7284 cmd_status.index = 0;
7286 return cmd_status.list[cmd_status.index];
7290 cmd_tokenize(char *s, char *tokens[])
7292 int i = 0;
7293 char *tok, *last;
7294 size_t len = strlen(s);
7295 bool blank;
7297 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
7298 for (tok = strtok_r(s, " ", &last); tok && i < 3;
7299 tok = strtok_r(NULL, " ", &last), i++)
7300 tokens[i] = tok;
7302 if (blank && i < 3)
7303 tokens[i++] = "";
7305 return (i);
7308 void
7309 cmd_complete(struct tab *t, char *str, int dir)
7311 GtkEntry *w = GTK_ENTRY(t->cmd);
7312 int i, j, levels, c = 0, dep = 0, parent = -1;
7313 int matchcount = 0;
7314 char *tok, *match, *s = g_strdup(str);
7315 char *tokens[3];
7316 char res[XT_MAX_URL_LENGTH + 32] = ":";
7317 char *sc = s;
7319 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
7321 /* copy prefix*/
7322 for (i = 0; isdigit(s[i]); i++)
7323 res[i + 1] = s[i];
7325 for (; isspace(s[i]); i++)
7326 res[i + 1] = s[i];
7328 s += i;
7330 levels = cmd_tokenize(s, tokens);
7332 for (i = 0; i < levels - 1; i++) {
7333 tok = tokens[i];
7334 matchcount = 0;
7335 for (j = c; j < LENGTH(cmds); j++) {
7336 if (cmds[j].level < dep)
7337 break;
7338 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7339 strlen(tok))) {
7340 matchcount++;
7341 c = j + 1;
7342 if (strlen(tok) == strlen(cmds[j].cmd)) {
7343 matchcount = 1;
7344 break;
7349 if (matchcount == 1) {
7350 strlcat(res, tok, sizeof res);
7351 strlcat(res, " ", sizeof res);
7352 dep++;
7353 } else {
7354 g_free(sc);
7355 return;
7358 parent = c - 1;
7361 if (cmd_status.index == -1)
7362 cmd_getlist(parent, tokens[i]);
7364 if (cmd_status.len > 0) {
7365 match = cmd_getnext(dir);
7366 strlcat(res, match, sizeof res);
7367 gtk_entry_set_text(w, res);
7368 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7371 g_free(sc);
7374 gboolean
7375 cmd_execute(struct tab *t, char *str)
7377 struct cmd *cmd = NULL;
7378 char *tok, *last, *s = g_strdup(str), *sc;
7379 char prefixstr[4];
7380 int j, len, c = 0, dep = 0, matchcount = 0;
7381 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7382 struct karg arg = {0, NULL, -1};
7384 sc = s;
7386 /* copy prefix*/
7387 for (j = 0; j<3 && isdigit(s[j]); j++)
7388 prefixstr[j]=s[j];
7390 prefixstr[j]='\0';
7392 s += j;
7393 while (isspace(s[0]))
7394 s++;
7396 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7397 prefix = atoi(prefixstr);
7398 else
7399 s = sc;
7401 for (tok = strtok_r(s, " ", &last); tok;
7402 tok = strtok_r(NULL, " ", &last)) {
7403 matchcount = 0;
7404 for (j = c; j < LENGTH(cmds); j++) {
7405 if (cmds[j].level < dep)
7406 break;
7407 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7408 strlen(tok);
7409 if (cmds[j].level == dep &&
7410 !strncmp(tok, cmds[j].cmd, len)) {
7411 matchcount++;
7412 c = j + 1;
7413 cmd = &cmds[j];
7414 if (len == strlen(cmds[j].cmd)) {
7415 matchcount = 1;
7416 break;
7420 if (matchcount == 1) {
7421 if (cmd->type > 0)
7422 goto execute_cmd;
7423 dep++;
7424 } else {
7425 show_oops(t, "Invalid command: %s", str);
7426 goto done;
7429 execute_cmd:
7430 arg.i = cmd->arg;
7432 if (prefix != -1)
7433 arg.p = prefix;
7434 else if (cmd_prefix > 0)
7435 arg.p = cmd_prefix;
7437 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
7438 show_oops(t, "No prefix allowed: %s", str);
7439 goto done;
7441 if (cmd->type > 1)
7442 arg.s = last ? g_strdup(last) : g_strdup("");
7443 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7444 arg.p = atoi(arg.s);
7445 if (arg.p <= 0) {
7446 if (arg.s[0]=='0')
7447 show_oops(t, "Zero count");
7448 else
7449 show_oops(t, "Trailing characters");
7450 goto done;
7454 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
7456 cmd->func(t, &arg);
7458 rv = XT_CB_HANDLED;
7459 done:
7460 if (j > 0)
7461 cmd_prefix = 0;
7462 g_free(sc);
7463 if (arg.s)
7464 g_free(arg.s);
7466 return (rv);
7470 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7472 if (t == NULL) {
7473 show_oops(NULL, "entry_key_cb invalid parameters");
7474 return (XT_CB_PASSTHROUGH);
7477 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7478 e->keyval, e->state, t);
7480 hide_oops(t);
7482 if (e->keyval == GDK_Escape) {
7483 /* don't use focus_webview(t) because we want to type :cmds */
7484 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7487 return (handle_keypress(t, e, 1));
7491 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7493 int rv = XT_CB_HANDLED;
7494 const gchar *c = gtk_entry_get_text(w);
7496 if (t == NULL) {
7497 show_oops(NULL, "cmd_keypress_cb parameters");
7498 return (XT_CB_PASSTHROUGH);
7501 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7502 e->keyval, e->state, t);
7504 /* sanity */
7505 if (c == NULL)
7506 e->keyval = GDK_Escape;
7507 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7508 e->keyval = GDK_Escape;
7510 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7511 e->keyval != GDK_ISO_Left_Tab)
7512 cmd_status.index = -1;
7514 switch (e->keyval) {
7515 case GDK_Tab:
7516 if (c[0] == ':')
7517 cmd_complete(t, (char *)&c[1], 1);
7518 goto done;
7519 case GDK_ISO_Left_Tab:
7520 if (c[0] == ':')
7521 cmd_complete(t, (char *)&c[1], -1);
7523 goto done;
7524 case GDK_BackSpace:
7525 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7526 break;
7527 /* FALLTHROUGH */
7528 case GDK_Escape:
7529 hide_cmd(t);
7530 focus_webview(t);
7532 /* cancel search */
7533 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7534 webkit_web_view_unmark_text_matches(t->wv);
7535 goto done;
7538 rv = XT_CB_PASSTHROUGH;
7539 done:
7540 return (rv);
7544 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7546 if (t == NULL) {
7547 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7548 return (XT_CB_PASSTHROUGH);
7550 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7552 hide_cmd(t);
7553 hide_oops(t);
7555 if (show_url == 0 || t->focus_wv)
7556 focus_webview(t);
7557 else
7558 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7560 return (XT_CB_PASSTHROUGH);
7563 void
7564 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7566 char *s;
7567 const gchar *c = gtk_entry_get_text(entry);
7569 if (t == NULL) {
7570 show_oops(NULL, "cmd_activate_cb invalid parameters");
7571 return;
7574 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7576 hide_cmd(t);
7578 /* sanity */
7579 if (c == NULL)
7580 goto done;
7581 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7582 goto done;
7583 if (strlen(c) < 2)
7584 goto done;
7585 s = (char *)&c[1];
7587 if (c[0] == '/' || c[0] == '?') {
7588 if (t->search_text) {
7589 g_free(t->search_text);
7590 t->search_text = NULL;
7593 t->search_text = g_strdup(s);
7594 if (global_search)
7595 g_free(global_search);
7596 global_search = g_strdup(s);
7597 t->search_forward = c[0] == '/';
7599 goto done;
7602 cmd_execute(t, s);
7604 done:
7605 return;
7608 void
7609 backward_cb(GtkWidget *w, struct tab *t)
7611 struct karg a;
7613 if (t == NULL) {
7614 show_oops(NULL, "backward_cb invalid parameters");
7615 return;
7618 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7620 a.i = XT_NAV_BACK;
7621 navaction(t, &a);
7624 void
7625 forward_cb(GtkWidget *w, struct tab *t)
7627 struct karg a;
7629 if (t == NULL) {
7630 show_oops(NULL, "forward_cb invalid parameters");
7631 return;
7634 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7636 a.i = XT_NAV_FORWARD;
7637 navaction(t, &a);
7640 void
7641 home_cb(GtkWidget *w, struct tab *t)
7643 if (t == NULL) {
7644 show_oops(NULL, "home_cb invalid parameters");
7645 return;
7648 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7650 load_uri(t, home);
7653 void
7654 stop_cb(GtkWidget *w, struct tab *t)
7656 WebKitWebFrame *frame;
7658 if (t == NULL) {
7659 show_oops(NULL, "stop_cb invalid parameters");
7660 return;
7663 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7665 frame = webkit_web_view_get_main_frame(t->wv);
7666 if (frame == NULL) {
7667 show_oops(t, "stop_cb: no frame");
7668 return;
7671 webkit_web_frame_stop_loading(frame);
7672 abort_favicon_download(t);
7675 void
7676 setup_webkit(struct tab *t)
7678 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7679 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7680 FALSE, (char *)NULL);
7681 else
7682 warnx("webkit does not have \"enable-dns-prefetching\" property");
7683 g_object_set(G_OBJECT(t->settings),
7684 "user-agent", t->user_agent, (char *)NULL);
7685 g_object_set(G_OBJECT(t->settings),
7686 "enable-scripts", enable_scripts, (char *)NULL);
7687 g_object_set(G_OBJECT(t->settings),
7688 "enable-plugins", enable_plugins, (char *)NULL);
7689 g_object_set(G_OBJECT(t->settings),
7690 "javascript-can-open-windows-automatically", enable_scripts,
7691 (char *)NULL);
7692 g_object_set(G_OBJECT(t->settings),
7693 "enable-html5-database", FALSE, (char *)NULL);
7694 g_object_set(G_OBJECT(t->settings),
7695 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7696 g_object_set(G_OBJECT(t->settings),
7697 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7698 g_object_set(G_OBJECT(t->settings),
7699 "spell_checking_languages", spell_check_languages, (char *)NULL);
7700 g_object_set(G_OBJECT(t->wv),
7701 "full-content-zoom", TRUE, (char *)NULL);
7703 webkit_web_view_set_settings(t->wv, t->settings);
7706 gboolean
7707 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7709 struct tab *ti, *t = NULL;
7710 gdouble view_size, value, max;
7711 gchar *position;
7713 TAILQ_FOREACH(ti, &tabs, entry)
7714 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7715 t = ti;
7716 break;
7719 if (t == NULL)
7720 return FALSE;
7722 if (adjustment == NULL)
7723 adjustment = gtk_scrolled_window_get_vadjustment(
7724 GTK_SCROLLED_WINDOW(t->browser_win));
7726 view_size = gtk_adjustment_get_page_size(adjustment);
7727 value = gtk_adjustment_get_value(adjustment);
7728 max = gtk_adjustment_get_upper(adjustment) - view_size;
7730 if (max == 0)
7731 position = g_strdup("All");
7732 else if (value == max)
7733 position = g_strdup("Bot");
7734 else if (value == 0)
7735 position = g_strdup("Top");
7736 else
7737 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7739 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7740 g_free(position);
7742 return (TRUE);
7745 GtkWidget *
7746 create_browser(struct tab *t)
7748 GtkWidget *w;
7749 gchar *strval;
7750 GtkAdjustment *adjustment;
7752 if (t == NULL) {
7753 show_oops(NULL, "create_browser invalid parameters");
7754 return (NULL);
7757 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7758 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7759 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7760 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7762 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7763 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7764 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7766 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7767 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7769 /* set defaults */
7770 t->settings = webkit_web_settings_new();
7772 if (user_agent == NULL) {
7773 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7774 (char *)NULL);
7775 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7776 g_free(strval);
7777 } else
7778 t->user_agent = g_strdup(user_agent);
7780 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7782 adjustment =
7783 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7784 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7785 G_CALLBACK(update_statusbar_position), NULL);
7787 setup_webkit(t);
7789 return (w);
7792 GtkWidget *
7793 create_window(void)
7795 GtkWidget *w;
7797 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7798 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7799 gtk_widget_set_name(w, "xxxterm");
7800 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7801 g_signal_connect(G_OBJECT(w), "delete_event",
7802 G_CALLBACK (gtk_main_quit), NULL);
7804 return (w);
7807 GtkWidget *
7808 create_kiosk_toolbar(struct tab *t)
7810 GtkWidget *toolbar = NULL, *b;
7812 b = gtk_hbox_new(FALSE, 0);
7813 toolbar = b;
7814 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7816 /* backward button */
7817 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7818 gtk_widget_set_sensitive(t->backward, FALSE);
7819 g_signal_connect(G_OBJECT(t->backward), "clicked",
7820 G_CALLBACK(backward_cb), t);
7821 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7823 /* forward button */
7824 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7825 gtk_widget_set_sensitive(t->forward, FALSE);
7826 g_signal_connect(G_OBJECT(t->forward), "clicked",
7827 G_CALLBACK(forward_cb), t);
7828 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7830 /* home button */
7831 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7832 gtk_widget_set_sensitive(t->gohome, true);
7833 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7834 G_CALLBACK(home_cb), t);
7835 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7837 /* create widgets but don't use them */
7838 t->uri_entry = gtk_entry_new();
7839 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7840 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7841 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7843 return (toolbar);
7846 GtkWidget *
7847 create_toolbar(struct tab *t)
7849 GtkWidget *toolbar = NULL, *b, *eb1;
7851 b = gtk_hbox_new(FALSE, 0);
7852 toolbar = b;
7853 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7855 /* backward button */
7856 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7857 gtk_widget_set_sensitive(t->backward, FALSE);
7858 g_signal_connect(G_OBJECT(t->backward), "clicked",
7859 G_CALLBACK(backward_cb), t);
7860 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7862 /* forward button */
7863 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7864 gtk_widget_set_sensitive(t->forward, FALSE);
7865 g_signal_connect(G_OBJECT(t->forward), "clicked",
7866 G_CALLBACK(forward_cb), t);
7867 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7868 FALSE, 0);
7870 /* stop button */
7871 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7872 gtk_widget_set_sensitive(t->stop, FALSE);
7873 g_signal_connect(G_OBJECT(t->stop), "clicked",
7874 G_CALLBACK(stop_cb), t);
7875 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7876 FALSE, 0);
7878 /* JS button */
7879 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7880 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7881 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7882 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7883 G_CALLBACK(js_toggle_cb), t);
7884 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7886 t->uri_entry = gtk_entry_new();
7887 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7888 G_CALLBACK(activate_uri_entry_cb), t);
7889 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7890 G_CALLBACK(entry_key_cb), t);
7891 completion_add(t);
7892 eb1 = gtk_hbox_new(FALSE, 0);
7893 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7894 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7895 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7897 /* search entry */
7898 if (search_string) {
7899 GtkWidget *eb2;
7900 t->search_entry = gtk_entry_new();
7901 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7902 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7903 G_CALLBACK(activate_search_entry_cb), t);
7904 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7905 G_CALLBACK(entry_key_cb), t);
7906 gtk_widget_set_size_request(t->search_entry, -1, -1);
7907 eb2 = gtk_hbox_new(FALSE, 0);
7908 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7909 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7911 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7914 return (toolbar);
7917 GtkWidget *
7918 create_buffers(struct tab *t)
7920 GtkCellRenderer *renderer;
7921 GtkWidget *view;
7923 view = gtk_tree_view_new();
7925 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7927 renderer = gtk_cell_renderer_text_new();
7928 gtk_tree_view_insert_column_with_attributes
7929 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7931 renderer = gtk_cell_renderer_text_new();
7932 gtk_tree_view_insert_column_with_attributes
7933 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
7934 NULL);
7936 gtk_tree_view_set_model
7937 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7939 return view;
7942 void
7943 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7944 GtkTreeViewColumn *col, struct tab *t)
7946 GtkTreeIter iter;
7947 guint id;
7949 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7951 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
7952 path)) {
7953 gtk_tree_model_get
7954 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7955 set_current_tab(id - 1);
7958 hide_buffers(t);
7961 /* after tab reordering/creation/removal */
7962 void
7963 recalc_tabs(void)
7965 struct tab *t;
7966 int maxid = 0;
7968 TAILQ_FOREACH(t, &tabs, entry) {
7969 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7970 if (t->tab_id > maxid)
7971 maxid = t->tab_id;
7973 gtk_widget_show(t->tab_elems.sep);
7976 TAILQ_FOREACH(t, &tabs, entry) {
7977 if (t->tab_id == maxid) {
7978 gtk_widget_hide(t->tab_elems.sep);
7979 break;
7984 /* after active tab change */
7985 void
7986 recolor_compact_tabs(void)
7988 struct tab *t;
7989 int curid = 0;
7990 GdkColor color;
7992 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7993 TAILQ_FOREACH(t, &tabs, entry)
7994 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
7995 &color);
7997 curid = gtk_notebook_get_current_page(notebook);
7998 TAILQ_FOREACH(t, &tabs, entry)
7999 if (t->tab_id == curid) {
8000 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
8001 gtk_widget_modify_fg(t->tab_elems.label,
8002 GTK_STATE_NORMAL, &color);
8003 break;
8007 void
8008 set_current_tab(int page_num)
8010 buffercmd_abort(get_current_tab());
8011 gtk_notebook_set_current_page(notebook, page_num);
8012 recolor_compact_tabs();
8016 undo_close_tab_save(struct tab *t)
8018 int m, n;
8019 const gchar *uri;
8020 struct undo *u1, *u2;
8021 GList *items;
8022 WebKitWebHistoryItem *item;
8024 if ((uri = get_uri(t)) == NULL)
8025 return (1);
8027 u1 = g_malloc0(sizeof(struct undo));
8028 u1->uri = g_strdup(uri);
8030 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8032 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
8033 n = webkit_web_back_forward_list_get_back_length(t->bfl);
8034 u1->back = n;
8036 /* forward history */
8037 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
8039 while (items) {
8040 item = items->data;
8041 u1->history = g_list_prepend(u1->history,
8042 webkit_web_history_item_copy(item));
8043 items = g_list_next(items);
8046 /* current item */
8047 if (m) {
8048 item = webkit_web_back_forward_list_get_current_item(t->bfl);
8049 u1->history = g_list_prepend(u1->history,
8050 webkit_web_history_item_copy(item));
8053 /* back history */
8054 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
8056 while (items) {
8057 item = items->data;
8058 u1->history = g_list_prepend(u1->history,
8059 webkit_web_history_item_copy(item));
8060 items = g_list_next(items);
8063 TAILQ_INSERT_HEAD(&undos, u1, entry);
8065 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
8066 u2 = TAILQ_LAST(&undos, undo_tailq);
8067 TAILQ_REMOVE(&undos, u2, entry);
8068 g_free(u2->uri);
8069 g_list_free(u2->history);
8070 g_free(u2);
8071 } else
8072 undo_count++;
8074 return (0);
8077 void
8078 delete_tab(struct tab *t)
8080 struct karg a;
8082 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
8084 if (t == NULL)
8085 return;
8087 TAILQ_REMOVE(&tabs, t, entry);
8089 /* Halt all webkit activity. */
8090 abort_favicon_download(t);
8091 webkit_web_view_stop_loading(t->wv);
8093 /* Save the tab, so we can undo the close. */
8094 undo_close_tab_save(t);
8096 if (browser_mode == XT_BM_KIOSK) {
8097 gtk_widget_destroy(t->uri_entry);
8098 gtk_widget_destroy(t->stop);
8099 gtk_widget_destroy(t->js_toggle);
8102 gtk_widget_destroy(t->tab_elems.eventbox);
8103 gtk_widget_destroy(t->vbox);
8105 g_free(t->user_agent);
8106 g_free(t->stylesheet);
8107 g_free(t->tmp_uri);
8108 g_free(t);
8110 if (TAILQ_EMPTY(&tabs)) {
8111 if (browser_mode == XT_BM_KIOSK)
8112 create_new_tab(home, NULL, 1, -1);
8113 else
8114 create_new_tab(NULL, NULL, 1, -1);
8117 /* recreate session */
8118 if (session_autosave) {
8119 a.s = NULL;
8120 save_tabs(t, &a);
8123 recalc_tabs();
8124 recolor_compact_tabs();
8127 void
8128 update_statusbar_zoom(struct tab *t)
8130 gfloat zoom;
8131 char s[16] = { '\0' };
8133 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8134 if ((zoom <= 0.99 || zoom >= 1.01))
8135 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
8136 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
8139 void
8140 setzoom_webkit(struct tab *t, int adjust)
8142 #define XT_ZOOMPERCENT 0.04
8144 gfloat zoom;
8146 if (t == NULL) {
8147 show_oops(NULL, "setzoom_webkit invalid parameters");
8148 return;
8151 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
8152 if (adjust == XT_ZOOM_IN)
8153 zoom += XT_ZOOMPERCENT;
8154 else if (adjust == XT_ZOOM_OUT)
8155 zoom -= XT_ZOOMPERCENT;
8156 else if (adjust > 0)
8157 zoom = default_zoom_level + adjust / 100.0 - 1.0;
8158 else {
8159 show_oops(t, "setzoom_webkit invalid zoom value");
8160 return;
8163 if (zoom < XT_ZOOMPERCENT)
8164 zoom = XT_ZOOMPERCENT;
8165 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
8166 update_statusbar_zoom(t);
8169 gboolean
8170 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
8172 struct tab *t = (struct tab *) data;
8174 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
8176 switch (event->button) {
8177 case 1:
8178 set_current_tab(t->tab_id);
8179 break;
8180 case 2:
8181 delete_tab(t);
8182 break;
8185 return TRUE;
8188 void
8189 append_tab(struct tab *t)
8191 if (t == NULL)
8192 return;
8194 TAILQ_INSERT_TAIL(&tabs, t, entry);
8195 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8198 struct tab *
8199 create_new_tab(char *title, struct undo *u, int focus, int position)
8201 struct tab *t;
8202 int load = 1, id;
8203 GtkWidget *b, *bb;
8204 WebKitWebHistoryItem *item;
8205 GList *items;
8206 GdkColor color;
8207 char *p;
8208 int sbe_p = 0, sbe_b = 0,
8209 sbe_z = 0;
8211 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8213 if (tabless && !TAILQ_EMPTY(&tabs)) {
8214 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8215 return (NULL);
8218 t = g_malloc0(sizeof *t);
8220 if (title == NULL) {
8221 title = "(untitled)";
8222 load = 0;
8225 t->vbox = gtk_vbox_new(FALSE, 0);
8227 /* label + button for tab */
8228 b = gtk_hbox_new(FALSE, 0);
8229 t->tab_content = b;
8231 #if GTK_CHECK_VERSION(2, 20, 0)
8232 t->spinner = gtk_spinner_new();
8233 #endif
8234 t->label = gtk_label_new(title);
8235 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8236 gtk_widget_set_size_request(t->label, 100, 0);
8237 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8238 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8239 gtk_widget_set_size_request(b, 130, 0);
8241 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8242 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8243 #if GTK_CHECK_VERSION(2, 20, 0)
8244 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8245 #endif
8247 /* toolbar */
8248 if (browser_mode == XT_BM_KIOSK) {
8249 t->toolbar = create_kiosk_toolbar(t);
8250 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8252 } else {
8253 t->toolbar = create_toolbar(t);
8254 if (fancy_bar)
8255 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8256 FALSE, 0);
8259 /* marks */
8260 marks_clear(t);
8262 /* browser */
8263 t->browser_win = create_browser(t);
8264 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8266 /* oops message for user feedback */
8267 t->oops = gtk_entry_new();
8268 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8269 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8270 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8271 gdk_color_parse(XT_COLOR_RED, &color);
8272 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8273 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8274 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8276 /* command entry */
8277 t->cmd = gtk_entry_new();
8278 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8279 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8280 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8281 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8283 /* status bar */
8284 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8286 t->sbe.statusbar = gtk_entry_new();
8287 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8288 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8289 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8290 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8292 /* create these widgets only if specified in statusbar_elems */
8293 t->sbe.position = gtk_entry_new();
8294 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.position), NULL);
8295 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.position), FALSE);
8296 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.position), FALSE);
8297 gtk_widget_modify_font(GTK_WIDGET(t->sbe.position), statusbar_font);
8298 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.position), 1.0);
8299 gtk_widget_set_size_request(t->sbe.position, 40, -1);
8301 t->sbe.zoom = gtk_entry_new();
8302 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.zoom), NULL);
8303 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.zoom), FALSE);
8304 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.zoom), FALSE);
8305 gtk_widget_modify_font(GTK_WIDGET(t->sbe.zoom), statusbar_font);
8306 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.zoom), 1.0);
8307 gtk_widget_set_size_request(t->sbe.zoom, 40, -1);
8309 t->sbe.buffercmd = gtk_entry_new();
8310 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.buffercmd), NULL);
8311 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.buffercmd), FALSE);
8312 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.buffercmd), FALSE);
8313 gtk_widget_modify_font(GTK_WIDGET(t->sbe.buffercmd), statusbar_font);
8314 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.buffercmd), 1.0);
8315 gtk_widget_set_size_request(t->sbe.buffercmd, 60, -1);
8317 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8319 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8320 TRUE, FALSE);
8322 /* gtk widgets cannot be added to a box twice. sbe_* variables
8323 make sure of this */
8324 for (p = statusbar_elems; *p != '\0'; p++) {
8325 switch (*p) {
8326 case '|':
8328 GtkWidget *sep = gtk_vseparator_new();
8330 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8331 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8332 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8333 FALSE, FALSE, FALSE);
8334 break;
8336 case 'P':
8337 if (sbe_p) {
8338 warnx("flag \"%c\" specified more than "
8339 "once in statusbar_elems\n", *p);
8340 break;
8342 sbe_p = 1;
8343 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8344 t->sbe.position, FALSE, FALSE, FALSE);
8345 break;
8346 case 'B':
8347 if (sbe_b) {
8348 warnx("flag \"%c\" specified more than "
8349 "once in statusbar_elems\n", *p);
8350 break;
8352 sbe_b = 1;
8353 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8354 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8355 break;
8356 case 'Z':
8357 if (sbe_z) {
8358 warnx("flag \"%c\" specified more than "
8359 "once in statusbar_elems\n", *p);
8360 break;
8362 sbe_z = 1;
8363 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8364 t->sbe.zoom, FALSE, FALSE, FALSE);
8365 break;
8366 default:
8367 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8368 break;
8372 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8374 /* buffer list */
8375 t->buffers = create_buffers(t);
8376 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8378 /* xtp meaning is normal by default */
8379 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8381 /* set empty favicon */
8382 xt_icon_from_name(t, "text-html");
8384 /* and show it all */
8385 gtk_widget_show_all(b);
8386 gtk_widget_show_all(t->vbox);
8388 /* compact tab bar */
8389 t->tab_elems.label = gtk_label_new(title);
8390 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8391 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8392 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8393 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8395 t->tab_elems.eventbox = gtk_event_box_new();
8396 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8397 t->tab_elems.sep = gtk_vseparator_new();
8399 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8400 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8401 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8402 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8403 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8404 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8406 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8407 TRUE, 0);
8408 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8409 FALSE, 0);
8410 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8411 t->tab_elems.box);
8413 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8414 TRUE, 0);
8415 gtk_widget_show_all(t->tab_elems.eventbox);
8417 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8418 append_tab(t);
8419 else {
8420 id = position >= 0 ? position :
8421 gtk_notebook_get_current_page(notebook) + 1;
8422 if (id > gtk_notebook_get_n_pages(notebook))
8423 append_tab(t);
8424 else {
8425 TAILQ_INSERT_TAIL(&tabs, t, entry);
8426 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8427 gtk_box_reorder_child(GTK_BOX(tab_bar),
8428 t->tab_elems.eventbox, id);
8429 recalc_tabs();
8433 #if GTK_CHECK_VERSION(2, 20, 0)
8434 /* turn spinner off if we are a new tab without uri */
8435 if (!load) {
8436 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8437 gtk_widget_hide(t->spinner);
8439 #endif
8440 /* make notebook tabs reorderable */
8441 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8443 /* compact tabs clickable */
8444 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8445 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8447 g_object_connect(G_OBJECT(t->cmd),
8448 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8449 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8450 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8451 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8452 (char *)NULL);
8454 /* reuse wv_button_cb to hide oops */
8455 g_object_connect(G_OBJECT(t->oops),
8456 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8457 (char *)NULL);
8459 g_signal_connect(t->buffers,
8460 "row-activated", G_CALLBACK(row_activated_cb), t);
8461 g_object_connect(G_OBJECT(t->buffers),
8462 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8464 g_object_connect(G_OBJECT(t->wv),
8465 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8466 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8467 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8468 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8469 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8470 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8471 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8472 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8473 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8474 "signal::event", G_CALLBACK(webview_event_cb), t,
8475 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8476 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8477 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8478 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8479 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8480 (char *)NULL);
8481 g_signal_connect(t->wv,
8482 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8483 g_signal_connect(t->wv,
8484 "load-error", G_CALLBACK(notify_load_error_cb), t);
8485 g_signal_connect(t->wv,
8486 "notify::title", G_CALLBACK(notify_title_cb), t);
8488 /* hijack the unused keys as if we were the browser */
8489 g_object_connect(G_OBJECT(t->toolbar),
8490 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8491 (char *)NULL);
8493 g_signal_connect(G_OBJECT(bb), "button_press_event",
8494 G_CALLBACK(tab_close_cb), t);
8496 /* hide stuff */
8497 hide_cmd(t);
8498 hide_oops(t);
8499 hide_buffers(t);
8500 url_set_visibility();
8501 statusbar_set_visibility();
8503 if (focus) {
8504 set_current_tab(t->tab_id);
8505 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8506 t->tab_id);
8509 if (load) {
8510 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8511 load_uri(t, title);
8512 } else {
8513 if (show_url == 1)
8514 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8515 else
8516 focus_webview(t);
8519 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8520 /* restore the tab's history */
8521 if (u && u->history) {
8522 items = u->history;
8523 while (items) {
8524 item = items->data;
8525 webkit_web_back_forward_list_add_item(t->bfl, item);
8526 items = g_list_next(items);
8529 item = g_list_nth_data(u->history, u->back);
8530 if (item)
8531 webkit_web_view_go_to_back_forward_item(t->wv, item);
8533 g_list_free(items);
8534 g_list_free(u->history);
8535 } else
8536 webkit_web_back_forward_list_clear(t->bfl);
8538 recolor_compact_tabs();
8539 return (t);
8542 void
8543 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8544 gpointer *udata)
8546 struct tab *t;
8547 const gchar *uri;
8549 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8551 if (gtk_notebook_get_current_page(notebook) == -1)
8552 recalc_tabs();
8554 TAILQ_FOREACH(t, &tabs, entry) {
8555 if (t->tab_id == pn) {
8556 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8557 "%d\n", pn);
8559 uri = get_title(t, TRUE);
8560 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8562 hide_cmd(t);
8563 hide_oops(t);
8565 if (t->focus_wv) {
8566 /* can't use focus_webview here */
8567 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8573 void
8574 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8575 gpointer *udata)
8577 struct tab *t = NULL, *tt;
8579 recalc_tabs();
8581 TAILQ_FOREACH(tt, &tabs, entry)
8582 if (tt->tab_id == pn) {
8583 t = tt;
8584 break;
8587 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8589 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8590 t->tab_id);
8593 void
8594 menuitem_response(struct tab *t)
8596 gtk_notebook_set_current_page(notebook, t->tab_id);
8599 gboolean
8600 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8602 GtkWidget *menu, *menu_items;
8603 GdkEventButton *bevent;
8604 const gchar *uri;
8605 struct tab *ti;
8607 if (event->type == GDK_BUTTON_PRESS) {
8608 bevent = (GdkEventButton *) event;
8609 menu = gtk_menu_new();
8611 TAILQ_FOREACH(ti, &tabs, entry) {
8612 if ((uri = get_uri(ti)) == NULL)
8613 /* XXX make sure there is something to print */
8614 /* XXX add gui pages in here to look purdy */
8615 uri = "(untitled)";
8616 menu_items = gtk_menu_item_new_with_label(uri);
8617 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8618 gtk_widget_show(menu_items);
8620 g_signal_connect_swapped((menu_items),
8621 "activate", G_CALLBACK(menuitem_response),
8622 (gpointer)ti);
8625 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8626 bevent->button, bevent->time);
8628 /* unref object so it'll free itself when popped down */
8629 #if !GTK_CHECK_VERSION(3, 0, 0)
8630 /* XXX does not need unref with gtk+3? */
8631 g_object_ref_sink(menu);
8632 g_object_unref(menu);
8633 #endif
8635 return (TRUE /* eat event */);
8638 return (FALSE /* propagate */);
8642 icon_size_map(int icon_size)
8644 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8645 icon_size > GTK_ICON_SIZE_DIALOG)
8646 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8648 return (icon_size);
8651 GtkWidget *
8652 create_button(char *name, char *stockid, int size)
8654 GtkWidget *button, *image;
8655 gchar *rcstring;
8656 int gtk_icon_size;
8658 rcstring = g_strdup_printf(
8659 "style \"%s-style\"\n"
8660 "{\n"
8661 " GtkWidget::focus-padding = 0\n"
8662 " GtkWidget::focus-line-width = 0\n"
8663 " xthickness = 0\n"
8664 " ythickness = 0\n"
8665 "}\n"
8666 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8667 gtk_rc_parse_string(rcstring);
8668 g_free(rcstring);
8669 button = gtk_button_new();
8670 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8671 gtk_icon_size = icon_size_map(size ? size : icon_size);
8673 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8674 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8675 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8676 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8677 gtk_widget_set_name(button, name);
8678 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8680 return (button);
8683 void
8684 button_set_stockid(GtkWidget *button, char *stockid)
8686 GtkWidget *image;
8688 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8689 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8690 gtk_button_set_image(GTK_BUTTON(button), image);
8693 void
8694 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8696 gchar *p = NULL;
8697 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
8698 gint len;
8701 * xterm doesn't play nice with clipboards because it clears the
8702 * primary when clicked. We rely on primary being set to properly
8703 * handle middle mouse button clicks (paste). So when someone clears
8704 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
8705 * other application behavior (as in DON'T clear primary).
8708 p = gtk_clipboard_wait_for_text(primary);
8709 if (p == NULL) {
8710 if (gdk_property_get(gdk_get_default_root_window(),
8711 atom,
8712 gdk_atom_intern("STRING", FALSE),
8714 1024 * 1024 /* picked out of my butt */,
8715 FALSE,
8716 NULL,
8717 NULL,
8718 &len,
8719 (guchar **)&p)) {
8720 /* yes sir, we need to NUL the string */
8721 p[len] = '\0';
8722 gtk_clipboard_set_text(primary, p, -1);
8726 if (p)
8727 g_free(p);
8730 void
8731 create_canvas(void)
8733 GtkWidget *vbox;
8734 GList *l = NULL;
8735 GdkPixbuf *pb;
8736 char file[PATH_MAX];
8737 int i;
8739 vbox = gtk_vbox_new(FALSE, 0);
8740 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8741 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8742 #if !GTK_CHECK_VERSION(3, 0, 0)
8743 /* XXX seems to be needed with gtk+2 */
8744 gtk_notebook_set_tab_hborder(notebook, 0);
8745 gtk_notebook_set_tab_vborder(notebook, 0);
8746 #endif
8747 gtk_notebook_set_scrollable(notebook, TRUE);
8748 gtk_notebook_set_show_border(notebook, FALSE);
8749 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8751 abtn = gtk_button_new();
8752 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8753 gtk_widget_set_size_request(arrow, -1, -1);
8754 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8755 gtk_widget_set_size_request(abtn, -1, 20);
8757 #if GTK_CHECK_VERSION(2, 20, 0)
8758 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8759 #endif
8760 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8762 /* compact tab bar */
8763 tab_bar = gtk_hbox_new(TRUE, 0);
8765 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8766 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8767 gtk_widget_set_size_request(vbox, -1, -1);
8769 g_object_connect(G_OBJECT(notebook),
8770 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8771 (char *)NULL);
8772 g_object_connect(G_OBJECT(notebook),
8773 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
8774 NULL, (char *)NULL);
8775 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8776 G_CALLBACK(arrow_cb), NULL);
8778 main_window = create_window();
8779 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8781 /* icons */
8782 for (i = 0; i < LENGTH(icons); i++) {
8783 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8784 pb = gdk_pixbuf_new_from_file(file, NULL);
8785 l = g_list_append(l, pb);
8787 gtk_window_set_default_icon_list(l);
8789 /* clipboard */
8790 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8791 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8793 gtk_widget_show_all(abtn);
8794 gtk_widget_show_all(main_window);
8795 notebook_tab_set_visibility();
8798 void
8799 set_hook(void **hook, char *name)
8801 if (hook == NULL)
8802 errx(1, "set_hook");
8804 if (*hook == NULL) {
8805 *hook = dlsym(RTLD_NEXT, name);
8806 if (*hook == NULL)
8807 errx(1, "can't hook %s", name);
8811 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8812 gboolean
8813 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8815 g_return_val_if_fail(cookie1, FALSE);
8816 g_return_val_if_fail(cookie2, FALSE);
8818 return (!strcmp (cookie1->name, cookie2->name) &&
8819 !strcmp (cookie1->value, cookie2->value) &&
8820 !strcmp (cookie1->path, cookie2->path) &&
8821 !strcmp (cookie1->domain, cookie2->domain));
8824 void
8825 transfer_cookies(void)
8827 GSList *cf;
8828 SoupCookie *sc, *pc;
8830 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8832 for (;cf; cf = cf->next) {
8833 pc = cf->data;
8834 sc = soup_cookie_copy(pc);
8835 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8838 soup_cookies_free(cf);
8841 void
8842 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8844 GSList *cf;
8845 SoupCookie *ci;
8847 print_cookie("soup_cookie_jar_delete_cookie", c);
8849 if (cookies_enabled == 0)
8850 return;
8852 if (jar == NULL || c == NULL)
8853 return;
8855 /* find and remove from persistent jar */
8856 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8858 for (;cf; cf = cf->next) {
8859 ci = cf->data;
8860 if (soup_cookie_equal(ci, c)) {
8861 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8862 break;
8866 soup_cookies_free(cf);
8868 /* delete from session jar */
8869 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8872 void
8873 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8875 struct domain *d = NULL;
8876 SoupCookie *c;
8877 FILE *r_cookie_f;
8879 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8880 jar, p_cookiejar, s_cookiejar);
8882 if (cookies_enabled == 0)
8883 return;
8885 /* see if we are up and running */
8886 if (p_cookiejar == NULL) {
8887 _soup_cookie_jar_add_cookie(jar, cookie);
8888 return;
8890 /* disallow p_cookiejar adds, shouldn't happen */
8891 if (jar == p_cookiejar)
8892 return;
8894 /* sanity */
8895 if (jar == NULL || cookie == NULL)
8896 return;
8898 if (enable_cookie_whitelist &&
8899 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8900 blocked_cookies++;
8901 DNPRINTF(XT_D_COOKIE,
8902 "soup_cookie_jar_add_cookie: reject %s\n",
8903 cookie->domain);
8904 if (save_rejected_cookies) {
8905 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8906 show_oops(NULL, "can't open reject cookie file");
8907 return;
8909 fseek(r_cookie_f, 0, SEEK_END);
8910 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8911 cookie->http_only ? "#HttpOnly_" : "",
8912 cookie->domain,
8913 *cookie->domain == '.' ? "TRUE" : "FALSE",
8914 cookie->path,
8915 cookie->secure ? "TRUE" : "FALSE",
8916 cookie->expires ?
8917 (gulong)soup_date_to_time_t(cookie->expires) :
8919 cookie->name,
8920 cookie->value);
8921 fflush(r_cookie_f);
8922 fclose(r_cookie_f);
8924 if (!allow_volatile_cookies)
8925 return;
8928 if (cookie->expires == NULL && session_timeout) {
8929 soup_cookie_set_expires(cookie,
8930 soup_date_new_from_now(session_timeout));
8931 print_cookie("modified add cookie", cookie);
8934 /* see if we are white listed for persistence */
8935 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8936 /* add to persistent jar */
8937 c = soup_cookie_copy(cookie);
8938 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8939 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8942 /* add to session jar */
8943 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8944 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8947 void
8948 setup_cookies(void)
8950 char file[PATH_MAX];
8952 set_hook((void *)&_soup_cookie_jar_add_cookie,
8953 "soup_cookie_jar_add_cookie");
8954 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8955 "soup_cookie_jar_delete_cookie");
8957 if (cookies_enabled == 0)
8958 return;
8961 * the following code is intricate due to overriding several libsoup
8962 * functions.
8963 * do not alter order of these operations.
8966 /* rejected cookies */
8967 if (save_rejected_cookies)
8968 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
8969 XT_REJECT_FILE);
8971 /* persistent cookies */
8972 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8973 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8975 /* session cookies */
8976 s_cookiejar = soup_cookie_jar_new();
8977 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8978 cookie_policy, (void *)NULL);
8979 transfer_cookies();
8981 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8984 void
8985 setup_proxy(char *uri)
8987 if (proxy_uri) {
8988 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8989 soup_uri_free(proxy_uri);
8990 proxy_uri = NULL;
8992 if (http_proxy) {
8993 if (http_proxy != uri) {
8994 g_free(http_proxy);
8995 http_proxy = NULL;
8999 if (uri) {
9000 http_proxy = g_strdup(uri);
9001 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
9002 proxy_uri = soup_uri_new(http_proxy);
9003 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
9008 send_cmd_to_socket(char *cmd)
9010 int s, len, rv = 1;
9011 struct sockaddr_un sa;
9013 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9014 warnx("%s: socket", __func__);
9015 return (rv);
9018 sa.sun_family = AF_UNIX;
9019 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9020 work_dir, XT_SOCKET_FILE);
9021 len = SUN_LEN(&sa);
9023 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9024 warnx("%s: connect", __func__);
9025 goto done;
9028 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
9029 warnx("%s: send", __func__);
9030 goto done;
9033 rv = 0;
9034 done:
9035 close(s);
9036 return (rv);
9039 gboolean
9040 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
9042 int s, n;
9043 char str[XT_MAX_URL_LENGTH];
9044 socklen_t t = sizeof(struct sockaddr_un);
9045 struct sockaddr_un sa;
9046 struct passwd *p;
9047 uid_t uid;
9048 gid_t gid;
9049 struct tab *tt;
9050 gint fd = g_io_channel_unix_get_fd(source);
9052 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
9053 warn("accept");
9054 return (FALSE);
9057 if (getpeereid(s, &uid, &gid) == -1) {
9058 warn("getpeereid");
9059 return (FALSE);
9061 if (uid != getuid() || gid != getgid()) {
9062 warnx("unauthorized user");
9063 return (FALSE);
9066 p = getpwuid(uid);
9067 if (p == NULL) {
9068 warnx("not a valid user");
9069 return (FALSE);
9072 n = recv(s, str, sizeof(str), 0);
9073 if (n <= 0)
9074 return (TRUE);
9076 tt = TAILQ_LAST(&tabs, tab_list);
9077 cmd_execute(tt, str);
9078 return (TRUE);
9082 is_running(void)
9084 int s, len, rv = 1;
9085 struct sockaddr_un sa;
9087 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9088 warn("is_running: socket");
9089 return (-1);
9092 sa.sun_family = AF_UNIX;
9093 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9094 work_dir, XT_SOCKET_FILE);
9095 len = SUN_LEN(&sa);
9097 /* connect to see if there is a listener */
9098 if (connect(s, (struct sockaddr *)&sa, len) == -1)
9099 rv = 0; /* not running */
9100 else
9101 rv = 1; /* already running */
9103 close(s);
9105 return (rv);
9109 build_socket(void)
9111 int s, len;
9112 struct sockaddr_un sa;
9114 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
9115 warn("build_socket: socket");
9116 return (-1);
9119 sa.sun_family = AF_UNIX;
9120 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
9121 work_dir, XT_SOCKET_FILE);
9122 len = SUN_LEN(&sa);
9124 /* connect to see if there is a listener */
9125 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
9126 /* no listener so we will */
9127 unlink(sa.sun_path);
9129 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
9130 warn("build_socket: bind");
9131 goto done;
9134 if (listen(s, 1) == -1) {
9135 warn("build_socket: listen");
9136 goto done;
9139 return (s);
9142 done:
9143 close(s);
9144 return (-1);
9147 gboolean
9148 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9149 GtkTreeIter *iter, struct tab *t)
9151 gchar *value;
9153 gtk_tree_model_get(model, iter, 0, &value, -1);
9154 load_uri(t, value);
9155 g_free(value);
9157 return (FALSE);
9160 gboolean
9161 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9162 GtkTreeIter *iter, struct tab *t)
9164 gchar *value;
9166 gtk_tree_model_get(model, iter, 0, &value, -1);
9167 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9168 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9169 g_free(value);
9171 return (TRUE);
9174 void
9175 completion_add_uri(const gchar *uri)
9177 GtkTreeIter iter;
9179 /* add uri to list_store */
9180 gtk_list_store_append(completion_model, &iter);
9181 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9184 gboolean
9185 completion_match(GtkEntryCompletion *completion, const gchar *key,
9186 GtkTreeIter *iter, gpointer user_data)
9188 gchar *value;
9189 gboolean match = FALSE;
9191 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9192 -1);
9194 if (value == NULL)
9195 return FALSE;
9197 match = match_uri(value, key);
9199 g_free(value);
9200 return (match);
9203 void
9204 completion_add(struct tab *t)
9206 /* enable completion for tab */
9207 t->completion = gtk_entry_completion_new();
9208 gtk_entry_completion_set_text_column(t->completion, 0);
9209 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9210 gtk_entry_completion_set_model(t->completion,
9211 GTK_TREE_MODEL(completion_model));
9212 gtk_entry_completion_set_match_func(t->completion, completion_match,
9213 NULL, NULL);
9214 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9215 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9216 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9217 G_CALLBACK(completion_select_cb), t);
9218 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9219 G_CALLBACK(completion_hover_cb), t);
9222 void
9223 xxx_dir(char *dir)
9225 struct stat sb;
9227 if (stat(dir, &sb)) {
9228 if (mkdir(dir, S_IRWXU) == -1)
9229 err(1, "mkdir %s", dir);
9230 if (stat(dir, &sb))
9231 err(1, "stat %s", dir);
9233 if (S_ISDIR(sb.st_mode) == 0)
9234 errx(1, "%s not a dir", dir);
9235 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9236 warnx("fixing invalid permissions on %s", dir);
9237 if (chmod(dir, S_IRWXU) == -1)
9238 err(1, "chmod %s", dir);
9242 void
9243 usage(void)
9245 fprintf(stderr,
9246 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9247 exit(0);
9252 main(int argc, char *argv[])
9254 struct stat sb;
9255 int c, s, optn = 0, opte = 0, focus = 1;
9256 char conf[PATH_MAX] = { '\0' };
9257 char file[PATH_MAX];
9258 char *env_proxy = NULL;
9259 char *cmd = NULL;
9260 FILE *f = NULL;
9261 struct karg a;
9262 struct sigaction sact;
9263 GIOChannel *channel;
9264 struct rlimit rlp;
9266 start_argv = argv;
9268 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9270 /* fiddle with ulimits */
9271 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9272 warn("getrlimit");
9273 else {
9274 /* just use them all */
9275 rlp.rlim_cur = rlp.rlim_max;
9276 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9277 warn("setrlimit");
9278 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9279 warn("getrlimit");
9280 else if (rlp.rlim_cur <= 256)
9281 warnx("%s requires at least 256 file descriptors",
9282 __progname);
9285 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9286 switch (c) {
9287 case 'S':
9288 show_url = 0;
9289 break;
9290 case 'T':
9291 show_tabs = 0;
9292 break;
9293 case 'V':
9294 errx(0 , "Version: %s", version);
9295 break;
9296 case 'f':
9297 strlcpy(conf, optarg, sizeof(conf));
9298 break;
9299 case 's':
9300 strlcpy(named_session, optarg, sizeof(named_session));
9301 break;
9302 case 't':
9303 tabless = 1;
9304 break;
9305 case 'n':
9306 optn = 1;
9307 break;
9308 case 'e':
9309 opte = 1;
9310 break;
9311 default:
9312 usage();
9313 /* NOTREACHED */
9316 argc -= optind;
9317 argv += optind;
9319 RB_INIT(&hl);
9320 RB_INIT(&js_wl);
9321 RB_INIT(&downloads);
9323 TAILQ_INIT(&tabs);
9324 TAILQ_INIT(&mtl);
9325 TAILQ_INIT(&aliases);
9326 TAILQ_INIT(&undos);
9327 TAILQ_INIT(&kbl);
9329 init_keybindings();
9331 gnutls_global_init();
9333 /* generate session keys for xtp pages */
9334 generate_xtp_session_key(&dl_session_key);
9335 generate_xtp_session_key(&hl_session_key);
9336 generate_xtp_session_key(&cl_session_key);
9337 generate_xtp_session_key(&fl_session_key);
9339 /* prepare gtk */
9340 gtk_init(&argc, &argv);
9341 if (!g_thread_supported())
9342 g_thread_init(NULL);
9344 /* signals */
9345 bzero(&sact, sizeof(sact));
9346 sigemptyset(&sact.sa_mask);
9347 sact.sa_handler = sigchild;
9348 sact.sa_flags = SA_NOCLDSTOP;
9349 sigaction(SIGCHLD, &sact, NULL);
9351 /* set download dir */
9352 pwd = getpwuid(getuid());
9353 if (pwd == NULL)
9354 errx(1, "invalid user %d", getuid());
9355 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9357 /* compile buffer command regexes */
9358 buffercmd_init();
9360 /* set default string settings */
9361 home = g_strdup("https://www.cyphertite.com");
9362 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9363 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9364 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9365 cmd_font_name = g_strdup("monospace normal 9");
9366 oops_font_name = g_strdup("monospace normal 9");
9367 statusbar_font_name = g_strdup("monospace normal 9");
9368 tabbar_font_name = g_strdup("monospace normal 9");
9369 statusbar_elems = g_strdup("BP");
9371 /* read config file */
9372 if (strlen(conf) == 0)
9373 snprintf(conf, sizeof conf, "%s/.%s",
9374 pwd->pw_dir, XT_CONF_FILE);
9375 config_parse(conf, 0);
9377 /* init fonts */
9378 cmd_font = pango_font_description_from_string(cmd_font_name);
9379 oops_font = pango_font_description_from_string(oops_font_name);
9380 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9381 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9383 /* working directory */
9384 if (strlen(work_dir) == 0)
9385 snprintf(work_dir, sizeof work_dir, "%s/%s",
9386 pwd->pw_dir, XT_DIR);
9387 xxx_dir(work_dir);
9389 /* icon cache dir */
9390 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9391 xxx_dir(cache_dir);
9393 /* certs dir */
9394 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9395 xxx_dir(certs_dir);
9397 /* sessions dir */
9398 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9399 work_dir, XT_SESSIONS_DIR);
9400 xxx_dir(sessions_dir);
9402 /* runtime settings that can override config file */
9403 if (runtime_settings[0] != '\0')
9404 config_parse(runtime_settings, 1);
9406 /* download dir */
9407 if (!strcmp(download_dir, pwd->pw_dir))
9408 strlcat(download_dir, "/downloads", sizeof download_dir);
9409 xxx_dir(download_dir);
9411 /* favorites file */
9412 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9413 if (stat(file, &sb)) {
9414 warnx("favorites file doesn't exist, creating it");
9415 if ((f = fopen(file, "w")) == NULL)
9416 err(1, "favorites");
9417 fclose(f);
9420 /* quickmarks file */
9421 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9422 if (stat(file, &sb)) {
9423 warnx("quickmarks file doesn't exist, creating it");
9424 if ((f = fopen(file, "w")) == NULL)
9425 err(1, "quickmarks");
9426 fclose(f);
9429 /* cookies */
9430 session = webkit_get_default_session();
9431 setup_cookies();
9433 /* certs */
9434 if (ssl_ca_file) {
9435 if (stat(ssl_ca_file, &sb)) {
9436 warnx("no CA file: %s", ssl_ca_file);
9437 g_free(ssl_ca_file);
9438 ssl_ca_file = NULL;
9439 } else
9440 g_object_set(session,
9441 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9442 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9443 (void *)NULL);
9446 /* proxy */
9447 env_proxy = getenv("http_proxy");
9448 if (env_proxy)
9449 setup_proxy(env_proxy);
9450 else
9451 setup_proxy(http_proxy);
9453 if (opte) {
9454 send_cmd_to_socket(argv[0]);
9455 exit(0);
9458 /* set some connection parameters */
9459 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9460 g_object_set(session, "max-conns-per-host", max_host_connections,
9461 (char *)NULL);
9463 /* see if there is already an xxxterm running */
9464 if (single_instance && is_running()) {
9465 optn = 1;
9466 warnx("already running");
9469 if (optn) {
9470 while (argc) {
9471 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9472 send_cmd_to_socket(cmd);
9473 if (cmd)
9474 g_free(cmd);
9476 argc--;
9477 argv++;
9479 exit(0);
9482 /* uri completion */
9483 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9485 /* buffers */
9486 buffers_store = gtk_list_store_new
9487 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9489 qmarks_load();
9491 /* go graphical */
9492 create_canvas();
9493 notebook_tab_set_visibility();
9495 if (save_global_history)
9496 restore_global_history();
9498 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9499 restore_saved_tabs();
9500 else {
9501 a.s = named_session;
9502 a.i = XT_SES_DONOTHING;
9503 open_tabs(NULL, &a);
9506 while (argc) {
9507 create_new_tab(argv[0], NULL, focus, -1);
9508 focus = 0;
9510 argc--;
9511 argv++;
9514 if (TAILQ_EMPTY(&tabs))
9515 create_new_tab(home, NULL, 1, -1);
9517 if (enable_socket)
9518 if ((s = build_socket()) != -1) {
9519 channel = g_io_channel_unix_new(s);
9520 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9523 gtk_main();
9525 gnutls_global_deinit();
9527 return (0);