3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * multi letter commands
24 * pre and post counts for commands
25 * autocompletion on various inputs
26 * create privacy browsing
27 * - encrypted local data
43 #include <sys/types.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
50 #include "freebsd/util.h"
56 #include <sys/queue.h>
58 #include <sys/socket.h>
62 #include <gdk/gdkkeysyms.h>
63 #include <webkit/webkit.h>
64 #include <libsoup/soup.h>
65 #include <gnutls/gnutls.h>
66 #include <JavaScriptCore/JavaScript.h>
67 #include <gnutls/x509.h>
69 #include "javascript.h"
72 javascript.h borrowed from vimprobable2 under the following license:
74 Copyright (c) 2009 Leon Winter
75 Copyright (c) 2009 Hannes Schueller
76 Copyright (c) 2009 Matto Fransen
78 Permission is hereby granted, free of charge, to any person obtaining a copy
79 of this software and associated documentation files (the "Software"), to deal
80 in the Software without restriction, including without limitation the rights
81 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
82 copies of the Software, and to permit persons to whom the Software is
83 furnished to do so, subject to the following conditions:
85 The above copyright notice and this permission notice shall be included in
86 all copies or substantial portions of the Software.
88 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
89 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
90 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
91 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
92 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
93 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
97 static char *version
= "$xxxterm$";
99 /* hooked functions */
100 void (*_soup_cookie_jar_add_cookie
)(SoupCookieJar
*, SoupCookie
*);
101 void (*_soup_cookie_jar_delete_cookie
)(SoupCookieJar
*,
106 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
107 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
108 #define XT_D_MOVE 0x0001
109 #define XT_D_KEY 0x0002
110 #define XT_D_TAB 0x0004
111 #define XT_D_URL 0x0008
112 #define XT_D_CMD 0x0010
113 #define XT_D_NAV 0x0020
114 #define XT_D_DOWNLOAD 0x0040
115 #define XT_D_CONFIG 0x0080
116 #define XT_D_JS 0x0100
117 #define XT_D_FAVORITE 0x0200
118 #define XT_D_PRINTING 0x0400
119 #define XT_D_COOKIE 0x0800
120 #define XT_D_KEYBINDING 0x1000
121 u_int32_t swm_debug
= 0
137 #define DPRINTF(x...)
138 #define DNPRINTF(n,x...)
141 #define LENGTH(x) (sizeof x / sizeof x[0])
142 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
143 ~(GDK_BUTTON1_MASK) & \
144 ~(GDK_BUTTON2_MASK) & \
145 ~(GDK_BUTTON3_MASK) & \
146 ~(GDK_BUTTON4_MASK) & \
158 TAILQ_ENTRY(tab
) entry
;
160 GtkWidget
*tab_content
;
163 GtkWidget
*uri_entry
;
164 GtkWidget
*search_entry
;
166 GtkWidget
*browser_win
;
167 GtkWidget
*statusbar
;
174 GtkWidget
*js_toggle
;
175 GtkEntryCompletion
*completion
;
179 WebKitWebHistoryItem
*item
;
180 WebKitWebBackForwardList
*bfl
;
183 WebKitNetworkRequest
*icon_request
;
184 WebKitDownload
*icon_download
;
185 GdkPixbuf
*icon_pixbuf
;
186 gchar
*icon_dest_uri
;
188 /* adjustments for browser */
191 GtkAdjustment
*adjust_h
;
192 GtkAdjustment
*adjust_v
;
198 int xtp_meaning
; /* identifies dls/favorites */
203 #define XT_HINT_NONE (0)
204 #define XT_HINT_NUMERICAL (1)
205 #define XT_HINT_ALPHANUM (2)
209 /* custom stylesheet */
218 WebKitWebSettings
*settings
;
222 TAILQ_HEAD(tab_list
, tab
);
225 RB_ENTRY(history
) entry
;
229 RB_HEAD(history_list
, history
);
232 RB_ENTRY(download
) entry
;
234 WebKitDownload
*download
;
237 RB_HEAD(download_list
, download
);
240 RB_ENTRY(domain
) entry
;
242 int handy
; /* app use */
244 RB_HEAD(domain_list
, domain
);
247 TAILQ_ENTRY(undo
) entry
;
250 int back
; /* Keeps track of how many back
251 * history items there are. */
253 TAILQ_HEAD(undo_tailq
, undo
);
255 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
256 int next_download_id
= 1;
264 #define XT_NAME ("XXXTerm")
265 #define XT_DIR (".xxxterm")
266 #define XT_CACHE_DIR ("cache")
267 #define XT_CERT_DIR ("certs/")
268 #define XT_SESSIONS_DIR ("sessions/")
269 #define XT_CONF_FILE ("xxxterm.conf")
270 #define XT_FAVS_FILE ("favorites")
271 #define XT_SAVED_TABS_FILE ("main_session")
272 #define XT_RESTART_TABS_FILE ("restart_tabs")
273 #define XT_SOCKET_FILE ("socket")
274 #define XT_HISTORY_FILE ("history")
275 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
276 #define XT_CB_HANDLED (TRUE)
277 #define XT_CB_PASSTHROUGH (FALSE)
278 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
279 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
280 #define XT_DLMAN_REFRESH "10"
281 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
282 "td {overflow: hidden;" \
283 " padding: 2px 2px 2px 2px;" \
284 " border: 1px solid black}\n" \
285 "tr:hover {background: #ffff99 ;}\n" \
286 "th {background-color: #cccccc;" \
287 " border: 1px solid black}" \
288 "table {border-spacing: 0; " \
290 " border: 1px black solid;}\n" \
292 " border: 1px solid black;" \
298 " background: green;}" \
300 " font-size: small;" \
301 " text-align: center;}" \
303 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
304 #define XT_MAX_UNDO_CLOSE_TAB (32)
305 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
306 #define XT_PRINT_EXTRA_MARGIN 10
309 #define XT_COLOR_RED "#cc0000"
310 #define XT_COLOR_YELLOW "#ffff66"
311 #define XT_COLOR_BLUE "lightblue"
312 #define XT_COLOR_GREEN "#99ff66"
313 #define XT_COLOR_WHITE "white"
314 #define XT_COLOR_BLACK "black"
317 * xxxterm "protocol" (xtp)
318 * We use this for managing stuff like downloads and favorites. They
319 * make magical HTML pages in memory which have xxxt:// links in order
320 * to communicate with xxxterm's internals. These links take the format:
321 * xxxt://class/session_key/action/arg
323 * Don't begin xtp class/actions as 0. atoi returns that on error.
325 * Typically we have not put addition of items in this framework, as
326 * adding items is either done via an ex-command or via a keybinding instead.
329 #define XT_XTP_STR "xxxt://"
331 /* XTP classes (xxxt://<class>) */
332 #define XT_XTP_DL 1 /* downloads */
333 #define XT_XTP_HL 2 /* history */
334 #define XT_XTP_CL 3 /* cookies */
335 #define XT_XTP_FL 4 /* favorites */
337 /* XTP download actions */
338 #define XT_XTP_DL_LIST 1
339 #define XT_XTP_DL_CANCEL 2
340 #define XT_XTP_DL_REMOVE 3
342 /* XTP history actions */
343 #define XT_XTP_HL_LIST 1
344 #define XT_XTP_HL_REMOVE 2
346 /* XTP cookie actions */
347 #define XT_XTP_CL_LIST 1
348 #define XT_XTP_CL_REMOVE 2
350 /* XTP cookie actions */
351 #define XT_XTP_FL_LIST 1
352 #define XT_XTP_FL_REMOVE 2
354 /* xtp tab meanings - identifies which tabs have xtp pages in */
355 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
356 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
357 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
358 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
359 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
362 #define XT_MOVE_INVALID (0)
363 #define XT_MOVE_DOWN (1)
364 #define XT_MOVE_UP (2)
365 #define XT_MOVE_BOTTOM (3)
366 #define XT_MOVE_TOP (4)
367 #define XT_MOVE_PAGEDOWN (5)
368 #define XT_MOVE_PAGEUP (6)
369 #define XT_MOVE_HALFDOWN (7)
370 #define XT_MOVE_HALFUP (8)
371 #define XT_MOVE_LEFT (9)
372 #define XT_MOVE_FARLEFT (10)
373 #define XT_MOVE_RIGHT (11)
374 #define XT_MOVE_FARRIGHT (12)
376 #define XT_TAB_LAST (-4)
377 #define XT_TAB_FIRST (-3)
378 #define XT_TAB_PREV (-2)
379 #define XT_TAB_NEXT (-1)
380 #define XT_TAB_INVALID (0)
381 #define XT_TAB_NEW (1)
382 #define XT_TAB_DELETE (2)
383 #define XT_TAB_DELQUIT (3)
384 #define XT_TAB_OPEN (4)
385 #define XT_TAB_UNDO_CLOSE (5)
386 #define XT_TAB_SHOW (6)
387 #define XT_TAB_HIDE (7)
389 #define XT_NAV_INVALID (0)
390 #define XT_NAV_BACK (1)
391 #define XT_NAV_FORWARD (2)
392 #define XT_NAV_RELOAD (3)
393 #define XT_NAV_RELOAD_CACHE (4)
395 #define XT_FOCUS_INVALID (0)
396 #define XT_FOCUS_URI (1)
397 #define XT_FOCUS_SEARCH (2)
399 #define XT_SEARCH_INVALID (0)
400 #define XT_SEARCH_NEXT (1)
401 #define XT_SEARCH_PREV (2)
403 #define XT_PASTE_CURRENT_TAB (0)
404 #define XT_PASTE_NEW_TAB (1)
406 #define XT_FONT_SET (0)
408 #define XT_URL_SHOW (1)
409 #define XT_URL_HIDE (2)
411 #define XT_STATUSBAR_SHOW (1)
412 #define XT_STATUSBAR_HIDE (2)
414 #define XT_WL_TOGGLE (1<<0)
415 #define XT_WL_ENABLE (1<<1)
416 #define XT_WL_DISABLE (1<<2)
417 #define XT_WL_FQDN (1<<3) /* default */
418 #define XT_WL_TOPLEVEL (1<<4)
419 #define XT_WL_PERSISTENT (1<<5)
420 #define XT_WL_SESSION (1<<6)
422 #define XT_SHOW (1<<7)
423 #define XT_DELETE (1<<8)
424 #define XT_SAVE (1<<9)
425 #define XT_OPEN (1<<10)
427 #define XT_CMD_OPEN (0)
428 #define XT_CMD_OPEN_CURRENT (1)
429 #define XT_CMD_TABNEW (2)
430 #define XT_CMD_TABNEW_CURRENT (3)
432 #define XT_STATUS_NOTHING (0)
433 #define XT_STATUS_LINK (1)
434 #define XT_STATUS_URI (2)
435 #define XT_STATUS_LOADING (3)
437 #define XT_SES_DONOTHING (0)
438 #define XT_SES_CLOSETABS (1)
440 #define XT_BM_NORMAL (0)
441 #define XT_BM_WHITELIST (1)
442 #define XT_BM_KIOSK (2)
450 TAILQ_ENTRY(mime_type
) entry
;
452 TAILQ_HEAD(mime_type_list
, mime_type
);
458 TAILQ_ENTRY(alias
) entry
;
460 TAILQ_HEAD(alias_list
, alias
);
462 /* settings that require restart */
463 int tabless
= 0; /* allow only 1 tab */
464 int enable_socket
= 0;
465 int single_instance
= 0; /* only allow one xxxterm to run */
466 int fancy_bar
= 1; /* fancy toolbar */
467 int browser_mode
= XT_BM_NORMAL
;
469 /* runtime settings */
470 int show_tabs
= 1; /* show tabs on notebook */
471 int show_url
= 1; /* show url toolbar on notebook */
472 int show_statusbar
= 0; /* vimperator style status bar */
473 int ctrl_click_focus
= 0; /* ctrl click gets focus */
474 int cookies_enabled
= 1; /* enable cookies */
475 int read_only_cookies
= 0; /* enable to not write cookies */
476 int enable_scripts
= 1;
477 int enable_plugins
= 0;
478 int default_font_size
= 12;
479 gfloat default_zoom_level
= 1.0;
480 int window_height
= 768;
481 int window_width
= 1024;
482 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
483 unsigned refresh_interval
= 10; /* download refresh interval */
484 int enable_cookie_whitelist
= 0;
485 int enable_js_whitelist
= 0;
486 time_t session_timeout
= 3600; /* cookie session timeout */
487 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
488 char *ssl_ca_file
= NULL
;
489 char *resource_dir
= NULL
;
490 gboolean ssl_strict_certs
= FALSE
;
491 int append_next
= 1; /* append tab after current tab */
493 char *search_string
= NULL
;
494 char *http_proxy
= NULL
;
495 char download_dir
[PATH_MAX
];
496 char runtime_settings
[PATH_MAX
]; /* override of settings */
497 int allow_volatile_cookies
= 0;
498 int save_global_history
= 0; /* save global history to disk */
499 char *user_agent
= NULL
;
500 int save_rejected_cookies
= 0;
501 time_t session_autosave
= 0;
502 int guess_search
= 0;
503 int dns_prefetch
= FALSE
;
504 gint max_connections
= 25;
505 gint max_host_connections
= 5;
509 int set_download_dir(struct settings
*, char *);
510 int set_work_dir(struct settings
*, char *);
511 int set_runtime_dir(struct settings
*, char *);
512 int set_browser_mode(struct settings
*, char *);
513 int set_cookie_policy(struct settings
*, char *);
514 int add_alias(struct settings
*, char *);
515 int add_mime_type(struct settings
*, char *);
516 int add_cookie_wl(struct settings
*, char *);
517 int add_js_wl(struct settings
*, char *);
518 int add_kb(struct settings
*, char *);
519 void button_set_stockid(GtkWidget
*, char *);
520 GtkWidget
* create_button(char *, char *, int);
522 char *get_browser_mode(struct settings
*);
523 char *get_cookie_policy(struct settings
*);
525 char *get_download_dir(struct settings
*);
526 char *get_work_dir(struct settings
*);
527 char *get_runtime_dir(struct settings
*);
529 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
530 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
531 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
532 void walk_kb(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
533 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
536 int (*set
)(struct settings
*, char *);
537 char *(*get
)(struct settings
*);
538 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
541 struct special s_browser_mode
= {
547 struct special s_cookie
= {
553 struct special s_alias
= {
559 struct special s_mime
= {
565 struct special s_js
= {
571 struct special s_kb
= {
577 struct special s_cookie_wl
= {
583 struct special s_download_dir
= {
589 struct special s_work_dir
= {
598 #define XT_S_INVALID (0)
601 #define XT_S_FLOAT (3)
603 #define XT_SF_RESTART (1<<0)
604 #define XT_SF_RUNTIME (1<<1)
610 { "append_next", XT_S_INT
, 0, &append_next
, NULL
, NULL
},
611 { "allow_volatile_cookies", XT_S_INT
, 0, &allow_volatile_cookies
, NULL
, NULL
},
612 { "browser_mode", XT_S_INT
, 0, NULL
, NULL
,&s_browser_mode
},
613 { "cookie_policy", XT_S_INT
, 0, NULL
, NULL
,&s_cookie
},
614 { "cookies_enabled", XT_S_INT
, 0, &cookies_enabled
, NULL
, NULL
},
615 { "ctrl_click_focus", XT_S_INT
, 0, &ctrl_click_focus
, NULL
, NULL
},
616 { "default_font_size", XT_S_INT
, 0, &default_font_size
, NULL
, NULL
},
617 { "default_zoom_level", XT_S_FLOAT
, 0, NULL
, NULL
, NULL
, &default_zoom_level
},
618 { "download_dir", XT_S_STR
, 0, NULL
, NULL
,&s_download_dir
},
619 { "enable_cookie_whitelist", XT_S_INT
, 0, &enable_cookie_whitelist
, NULL
, NULL
},
620 { "enable_js_whitelist", XT_S_INT
, 0, &enable_js_whitelist
, NULL
, NULL
},
621 { "enable_plugins", XT_S_INT
, 0, &enable_plugins
, NULL
, NULL
},
622 { "enable_scripts", XT_S_INT
, 0, &enable_scripts
, NULL
, NULL
},
623 { "enable_socket", XT_S_INT
, XT_SF_RESTART
,&enable_socket
, NULL
, NULL
},
624 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
,&fancy_bar
, NULL
, NULL
},
625 { "home", XT_S_STR
, 0, NULL
, &home
, NULL
},
626 { "http_proxy", XT_S_STR
, 0, NULL
, &http_proxy
, NULL
},
627 { "icon_size", XT_S_INT
, 0, &icon_size
, NULL
, NULL
},
628 { "max_connections", XT_S_INT
, XT_SF_RESTART
,&max_connections
, NULL
, NULL
},
629 { "max_host_connections", XT_S_INT
, XT_SF_RESTART
,&max_host_connections
, NULL
, NULL
},
630 { "read_only_cookies", XT_S_INT
, 0, &read_only_cookies
, NULL
, NULL
},
631 { "refresh_interval", XT_S_INT
, 0, &refresh_interval
, NULL
, NULL
},
632 { "resource_dir", XT_S_STR
, 0, NULL
, &resource_dir
, NULL
},
633 { "search_string", XT_S_STR
, 0, NULL
, &search_string
, NULL
},
634 { "save_global_history", XT_S_INT
, XT_SF_RESTART
,&save_global_history
, NULL
, NULL
},
635 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
,&save_rejected_cookies
, NULL
, NULL
},
636 { "session_timeout", XT_S_INT
, 0, &session_timeout
, NULL
, NULL
},
637 { "session_autosave", XT_S_INT
, 0, &session_autosave
, NULL
, NULL
},
638 { "single_instance", XT_S_INT
, XT_SF_RESTART
,&single_instance
, NULL
, NULL
},
639 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
640 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
641 { "guess_search", XT_S_INT
, 0, &guess_search
, NULL
, NULL
},
642 { "show_statusbar", XT_S_INT
, 0, &show_statusbar
, NULL
, NULL
},
643 { "ssl_ca_file", XT_S_STR
, 0, NULL
, &ssl_ca_file
, NULL
},
644 { "ssl_strict_certs", XT_S_INT
, 0, &ssl_strict_certs
, NULL
, NULL
},
645 { "user_agent", XT_S_STR
, 0, NULL
, &user_agent
, NULL
},
646 { "window_height", XT_S_INT
, 0, &window_height
, NULL
, NULL
},
647 { "window_width", XT_S_INT
, 0, &window_width
, NULL
, NULL
},
648 { "work_dir", XT_S_STR
, 0, NULL
, NULL
,&s_work_dir
},
650 /* runtime settings */
651 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
652 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
653 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
654 { "keybinding", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_kb
},
655 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
658 int about(struct tab
*, struct karg
*);
659 int blank(struct tab
*, struct karg
*);
660 int cookie_show_wl(struct tab
*, struct karg
*);
661 int js_show_wl(struct tab
*, struct karg
*);
662 int help(struct tab
*, struct karg
*);
663 int set(struct tab
*, struct karg
*);
664 int stats(struct tab
*, struct karg
*);
665 int marco(struct tab
*, struct karg
*);
666 const char * marco_message(int *);
667 int xtp_page_cl(struct tab
*, struct karg
*);
668 int xtp_page_dl(struct tab
*, struct karg
*);
669 int xtp_page_fl(struct tab
*, struct karg
*);
670 int xtp_page_hl(struct tab
*, struct karg
*);
672 #define XT_URI_ABOUT ("about:")
673 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
674 #define XT_URI_ABOUT_ABOUT ("about")
675 #define XT_URI_ABOUT_BLANK ("blank")
676 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
677 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
678 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
679 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
680 #define XT_URI_ABOUT_FAVORITES ("favorites")
681 #define XT_URI_ABOUT_HELP ("help")
682 #define XT_URI_ABOUT_HISTORY ("history")
683 #define XT_URI_ABOUT_JSWL ("jswl")
684 #define XT_URI_ABOUT_SET ("set")
685 #define XT_URI_ABOUT_STATS ("stats")
686 #define XT_URI_ABOUT_MARCO ("marco")
690 int (*func
)(struct tab
*, struct karg
*);
692 { XT_URI_ABOUT_ABOUT
, about
},
693 { XT_URI_ABOUT_BLANK
, blank
},
694 { XT_URI_ABOUT_COOKIEWL
, cookie_show_wl
},
695 { XT_URI_ABOUT_COOKIEJAR
, xtp_page_cl
},
696 { XT_URI_ABOUT_DOWNLOADS
, xtp_page_dl
},
697 { XT_URI_ABOUT_FAVORITES
, xtp_page_fl
},
698 { XT_URI_ABOUT_HELP
, help
},
699 { XT_URI_ABOUT_HISTORY
, xtp_page_hl
},
700 { XT_URI_ABOUT_JSWL
, js_show_wl
},
701 { XT_URI_ABOUT_SET
, set
},
702 { XT_URI_ABOUT_STATS
, stats
},
703 { XT_URI_ABOUT_MARCO
, marco
},
707 extern char *__progname
;
710 GtkWidget
*main_window
;
711 GtkNotebook
*notebook
;
712 GtkWidget
*arrow
, *abtn
;
713 struct tab_list tabs
;
714 struct history_list hl
;
715 struct download_list downloads
;
716 struct domain_list c_wl
;
717 struct domain_list js_wl
;
718 struct undo_tailq undos
;
719 struct keybinding_list kbl
;
721 int updating_dl_tabs
= 0;
722 int updating_hl_tabs
= 0;
723 int updating_cl_tabs
= 0;
724 int updating_fl_tabs
= 0;
726 uint64_t blocked_cookies
= 0;
727 char named_session
[PATH_MAX
];
728 void update_favicon(struct tab
*);
729 int icon_size_map(int);
731 GtkListStore
*completion_model
;
732 void completion_add(struct tab
*);
733 void completion_add_uri(const gchar
*);
738 int saved_errno
, status
;
743 while ((pid
= waitpid(WAIT_ANY
, &status
, WNOHANG
)) != 0) {
747 if (errno
!= ECHILD
) {
749 clog_warn("sigchild: waitpid:");
755 if (WIFEXITED(status
)) {
756 if (WEXITSTATUS(status
) != 0) {
758 clog_warnx("sigchild: child exit status: %d",
759 WEXITSTATUS(status));
764 clog_warnx("sigchild: child is terminated abnormally");
773 is_g_object_setting(GObject
*o
, char *str
)
775 guint n_props
= 0, i
;
776 GParamSpec
**proplist
;
778 if (! G_IS_OBJECT(o
))
781 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
784 for (i
=0; i
< n_props
; i
++) {
785 if (! strcmp(proplist
[i
]->name
, str
))
792 load_webkit_string(struct tab
*t
, const char *str
, gchar
*title
)
798 /* we set this to indicate we want to manually do navaction */
800 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
801 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, NULL
);
804 uri
= g_strdup_printf("%s%s", XT_URI_ABOUT
, title
);
805 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
808 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[0]);
809 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
810 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
811 GTK_ENTRY_ICON_PRIMARY
, pb
);
812 gdk_pixbuf_unref(pb
);
817 set_status(struct tab
*t
, gchar
*s
, int status
)
825 case XT_STATUS_LOADING
:
826 type
= g_strdup_printf("Loading: %s", s
);
830 type
= g_strdup_printf("Link: %s", s
);
832 t
->status
= g_strdup(gtk_entry_get_text(GTK_ENTRY(t
->statusbar
)));
836 type
= g_strdup_printf("%s", s
);
838 t
->status
= g_strdup(type
);
842 t
->status
= g_strdup(s
);
844 case XT_STATUS_NOTHING
:
849 gtk_entry_set_text(GTK_ENTRY(t
->statusbar
), s
);
855 hide_oops(struct tab
*t
)
857 gtk_widget_hide(t
->oops
);
861 hide_cmd(struct tab
*t
)
863 gtk_widget_hide(t
->cmd
);
867 show_cmd(struct tab
*t
)
869 gtk_widget_hide(t
->oops
);
870 gtk_widget_show(t
->cmd
);
874 show_oops(struct tab
*t
, const char *fmt
, ...)
883 if (vasprintf(&msg
, fmt
, ap
) == -1)
884 errx(1, "show_oops failed");
887 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
888 gtk_widget_hide(t
->cmd
);
889 gtk_widget_show(t
->oops
);
892 /* XXX collapse with show_oops */
894 show_oops_s(const char *fmt
, ...)
898 struct tab
*ti
, *t
= NULL
;
903 TAILQ_FOREACH(ti
, &tabs
, entry
)
904 if (ti
->tab_id
== gtk_notebook_current_page(notebook
)) {
912 if (vasprintf(&msg
, fmt
, ap
) == -1)
913 errx(1, "show_oops_s failed");
916 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
917 gtk_widget_hide(t
->cmd
);
918 gtk_widget_show(t
->oops
);
922 get_as_string(struct settings
*s
)
933 warnx("get_as_string skip %s\n", s
->name
);
934 } else if (s
->type
== XT_S_INT
)
935 r
= g_strdup_printf("%d", *s
->ival
);
936 else if (s
->type
== XT_S_STR
)
937 r
= g_strdup(*s
->sval
);
938 else if (s
->type
== XT_S_FLOAT
)
939 r
= g_strdup_printf("%f", *s
->fval
);
941 r
= g_strdup_printf("INVALID TYPE");
947 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
952 for (i
= 0; i
< LENGTH(rs
); i
++) {
953 if (rs
[i
].s
&& rs
[i
].s
->walk
)
954 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
956 s
= get_as_string(&rs
[i
]);
957 cb(&rs
[i
], s
, cb_args
);
964 set_browser_mode(struct settings
*s
, char *val
)
966 if (!strcmp(val
, "whitelist")) {
967 browser_mode
= XT_BM_WHITELIST
;
968 allow_volatile_cookies
= 0;
969 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
971 enable_cookie_whitelist
= 1;
972 read_only_cookies
= 0;
973 save_rejected_cookies
= 0;
974 session_timeout
= 3600;
976 enable_js_whitelist
= 1;
977 } else if (!strcmp(val
, "normal")) {
978 browser_mode
= XT_BM_NORMAL
;
979 allow_volatile_cookies
= 0;
980 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
982 enable_cookie_whitelist
= 0;
983 read_only_cookies
= 0;
984 save_rejected_cookies
= 0;
985 session_timeout
= 3600;
987 enable_js_whitelist
= 0;
988 } else if (!strcmp(val
, "kiosk")) {
989 browser_mode
= XT_BM_KIOSK
;
990 allow_volatile_cookies
= 0;
991 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
993 enable_cookie_whitelist
= 0;
994 read_only_cookies
= 0;
995 save_rejected_cookies
= 0;
996 session_timeout
= 3600;
998 enable_js_whitelist
= 0;
1008 get_browser_mode(struct settings
*s
)
1012 if (browser_mode
== XT_BM_WHITELIST
)
1013 r
= g_strdup("whitelist");
1014 else if (browser_mode
== XT_BM_NORMAL
)
1015 r
= g_strdup("normal");
1016 else if (browser_mode
== XT_BM_KIOSK
)
1017 r
= g_strdup("kiosk");
1025 set_cookie_policy(struct settings
*s
, char *val
)
1027 if (!strcmp(val
, "no3rdparty"))
1028 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1029 else if (!strcmp(val
, "accept"))
1030 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1031 else if (!strcmp(val
, "reject"))
1032 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1040 get_cookie_policy(struct settings
*s
)
1044 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1045 r
= g_strdup("no3rdparty");
1046 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1047 r
= g_strdup("accept");
1048 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1049 r
= g_strdup("reject");
1057 get_download_dir(struct settings
*s
)
1059 if (download_dir
[0] == '\0')
1061 return (g_strdup(download_dir
));
1065 set_download_dir(struct settings
*s
, char *val
)
1068 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1069 pwd
->pw_dir
, &val
[1]);
1071 strlcpy(download_dir
, val
, sizeof download_dir
);
1078 * We use these to prevent people putting xxxt:// URLs on
1079 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1081 #define XT_XTP_SES_KEY_SZ 8
1082 #define XT_XTP_SES_KEY_HEX_FMT \
1083 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1084 char *dl_session_key
; /* downloads */
1085 char *hl_session_key
; /* history list */
1086 char *cl_session_key
; /* cookie list */
1087 char *fl_session_key
; /* favorites list */
1089 char work_dir
[PATH_MAX
];
1090 char certs_dir
[PATH_MAX
];
1091 char cache_dir
[PATH_MAX
];
1092 char sessions_dir
[PATH_MAX
];
1093 char cookie_file
[PATH_MAX
];
1094 SoupURI
*proxy_uri
= NULL
;
1095 SoupSession
*session
;
1096 SoupCookieJar
*s_cookiejar
;
1097 SoupCookieJar
*p_cookiejar
;
1098 char rc_fname
[PATH_MAX
];
1100 struct mime_type_list mtl
;
1101 struct alias_list aliases
;
1104 struct tab
*create_new_tab(char *, struct undo
*, int);
1105 void delete_tab(struct tab
*);
1106 void adjustfont_webkit(struct tab
*, int);
1107 int run_script(struct tab
*, char *);
1108 int download_rb_cmp(struct download
*, struct download
*);
1109 gboolean
cmd_execute(struct tab
*t
, char *str
);
1112 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1114 return (strcmp(h1
->uri
, h2
->uri
));
1116 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1119 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1121 return (strcmp(d1
->d
, d2
->d
));
1123 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1126 get_work_dir(struct settings
*s
)
1128 if (work_dir
[0] == '\0')
1130 return (g_strdup(work_dir
));
1134 set_work_dir(struct settings
*s
, char *val
)
1137 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1138 pwd
->pw_dir
, &val
[1]);
1140 strlcpy(work_dir
, val
, sizeof work_dir
);
1146 * generate a session key to secure xtp commands.
1147 * pass in a ptr to the key in question and it will
1148 * be modified in place.
1151 generate_xtp_session_key(char **key
)
1153 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1159 /* make a new one */
1160 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1161 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1162 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1163 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1165 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1169 * validate a xtp session key.
1173 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1175 if (strcmp(trusted
, untrusted
) != 0) {
1176 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1185 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1187 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1189 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1191 struct valid_url_types
{
1202 valid_url_type(char *url
)
1206 for (i
= 0; i
< LENGTH(vut
); i
++)
1207 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1214 print_cookie(char *msg
, SoupCookie
*c
)
1220 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1221 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1222 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1223 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1224 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1225 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1226 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1227 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1228 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1229 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1233 walk_alias(struct settings
*s
,
1234 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1239 if (s
== NULL
|| cb
== NULL
) {
1240 show_oops_s("walk_alias invalid parameters");
1244 TAILQ_FOREACH(a
, &aliases
, entry
) {
1245 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1246 cb(s
, str
, cb_args
);
1252 match_alias(char *url_in
)
1256 char *url_out
= NULL
, *search
, *enc_arg
;
1258 search
= g_strdup(url_in
);
1260 if (strsep(&arg
, " \t") == NULL
) {
1261 show_oops_s("match_alias: NULL URL");
1265 TAILQ_FOREACH(a
, &aliases
, entry
) {
1266 if (!strcmp(search
, a
->a_name
))
1271 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1274 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1275 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1278 url_out
= g_strdup(a
->a_uri
);
1286 guess_url_type(char *url_in
)
1289 char *url_out
= NULL
, *enc_search
= NULL
;
1291 url_out
= match_alias(url_in
);
1292 if (url_out
!= NULL
)
1297 * If there is no dot nor slash in the string and it isn't a
1298 * path to a local file and doesn't resolves to an IP, assume
1299 * that the user wants to search for the string.
1302 if (strchr(url_in
, '.') == NULL
&&
1303 strchr(url_in
, '/') == NULL
&&
1304 stat(url_in
, &sb
) != 0 &&
1305 gethostbyname(url_in
) == NULL
) {
1307 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1308 url_out
= g_strdup_printf(search_string
, enc_search
);
1314 /* XXX not sure about this heuristic */
1315 if (stat(url_in
, &sb
) == 0)
1316 url_out
= g_strdup_printf("file://%s", url_in
);
1318 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1320 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1326 load_uri(struct tab
*t
, gchar
*uri
)
1329 gchar
*newuri
= NULL
;
1335 /* Strip leading spaces. */
1336 while(*uri
&& isspace(*uri
))
1339 if (strlen(uri
) == 0) {
1344 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1345 for (i
= 0; i
< LENGTH(about_list
); i
++)
1346 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1347 bzero(&args
, sizeof args
);
1348 about_list
[i
].func(t
, &args
);
1351 show_oops(t
, "invalid about page");
1355 if (valid_url_type(uri
)) {
1356 newuri
= guess_url_type(uri
);
1360 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1361 webkit_web_view_load_uri(t
->wv
, uri
);
1368 get_uri(WebKitWebView
*wv
)
1370 WebKitWebFrame
*frame
;
1373 frame
= webkit_web_view_get_main_frame(wv
);
1374 uri
= webkit_web_frame_get_uri(frame
);
1376 if (uri
&& strlen(uri
) > 0)
1383 add_alias(struct settings
*s
, char *line
)
1386 struct alias
*a
= NULL
;
1388 if (s
== NULL
|| line
== NULL
) {
1389 show_oops_s("add_alias invalid parameters");
1394 a
= g_malloc(sizeof(*a
));
1396 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1397 show_oops_s("add_alias: incomplete alias definition");
1400 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1401 show_oops_s("add_alias: invalid alias definition");
1405 a
->a_name
= g_strdup(alias
);
1406 a
->a_uri
= g_strdup(l
);
1408 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1410 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1420 add_mime_type(struct settings
*s
, char *line
)
1424 struct mime_type
*m
= NULL
;
1425 int downloadfirst
= 0;
1427 /* XXX this could be smarter */
1429 if (line
== NULL
&& strlen(line
) == 0) {
1430 show_oops_s("add_mime_type invalid parameters");
1439 m
= g_malloc(sizeof(*m
));
1441 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1442 show_oops_s("add_mime_type: invalid mime_type");
1445 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1446 mime_type
[strlen(mime_type
) - 1] = '\0';
1451 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1452 show_oops_s("add_mime_type: invalid mime_type");
1456 m
->mt_type
= g_strdup(mime_type
);
1457 m
->mt_action
= g_strdup(l
);
1458 m
->mt_download
= downloadfirst
;
1460 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1461 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1463 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1473 find_mime_type(char *mime_type
)
1475 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1477 TAILQ_FOREACH(m
, &mtl
, entry
) {
1478 if (m
->mt_default
&&
1479 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1482 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1495 walk_mime_type(struct settings
*s
,
1496 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1498 struct mime_type
*m
;
1501 if (s
== NULL
|| cb
== NULL
)
1502 show_oops_s("walk_mime_type invalid parameters");
1504 TAILQ_FOREACH(m
, &mtl
, entry
) {
1505 str
= g_strdup_printf("%s%s --> %s",
1507 m
->mt_default
? "*" : "",
1509 cb(s
, str
, cb_args
);
1515 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1520 if (str
== NULL
|| wl
== NULL
)
1522 if (strlen(str
) < 2)
1525 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1527 /* treat *.moo.com the same as .moo.com */
1528 if (str
[0] == '*' && str
[1] == '.')
1530 else if (str
[0] == '.')
1535 d
= g_malloc(sizeof *d
);
1537 d
->d
= g_strdup_printf(".%s", str
);
1539 d
->d
= g_strdup(str
);
1542 if (RB_INSERT(domain_list
, wl
, d
))
1545 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1556 add_cookie_wl(struct settings
*s
, char *entry
)
1558 wl_add(entry
, &c_wl
, 1);
1563 walk_cookie_wl(struct settings
*s
,
1564 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1568 if (s
== NULL
|| cb
== NULL
) {
1569 show_oops_s("walk_cookie_wl invalid parameters");
1573 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1574 cb(s
, d
->d
, cb_args
);
1578 walk_js_wl(struct settings
*s
,
1579 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1583 if (s
== NULL
|| cb
== NULL
) {
1584 show_oops_s("walk_js_wl invalid parameters");
1588 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1589 cb(s
, d
->d
, cb_args
);
1593 add_js_wl(struct settings
*s
, char *entry
)
1595 wl_add(entry
, &js_wl
, 1 /* persistent */);
1600 wl_find(const gchar
*search
, struct domain_list
*wl
)
1603 struct domain
*d
= NULL
, dfind
;
1606 if (search
== NULL
|| wl
== NULL
)
1608 if (strlen(search
) < 2)
1611 if (search
[0] != '.')
1612 s
= g_strdup_printf(".%s", search
);
1614 s
= g_strdup(search
);
1616 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1619 d
= RB_FIND(domain_list
, wl
, &dfind
);
1633 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1639 if (s
== NULL
|| wl
== NULL
)
1642 if (!strncmp(s
, "http://", strlen("http://")))
1643 s
= &s
[strlen("http://")];
1644 else if (!strncmp(s
, "https://", strlen("https://")))
1645 s
= &s
[strlen("https://")];
1650 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1651 /* chop string at first slash */
1652 if (s
[i
] == '/' || s
[i
] == '\0') {
1655 r
= wl_find(ss
, wl
);
1664 get_toplevel_domain(char *domain
)
1671 if (strlen(domain
) < 2)
1674 s
= &domain
[strlen(domain
) - 1];
1675 while (s
!= domain
) {
1691 settings_add(char *var
, char *val
)
1698 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
1699 if (strcmp(var
, rs
[i
].name
))
1703 if (rs
[i
].s
->set(&rs
[i
], val
))
1704 errx(1, "invalid value for %s: %s", var
, val
);
1708 switch (rs
[i
].type
) {
1717 errx(1, "invalid sval for %s",
1731 errx(1, "invalid type for %s", var
);
1740 config_parse(char *filename
, int runtime
)
1743 char *line
, *cp
, *var
, *val
;
1744 size_t len
, lineno
= 0;
1746 char file
[PATH_MAX
];
1749 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
1751 if (filename
== NULL
)
1754 if (runtime
&& runtime_settings
[0] != '\0') {
1755 snprintf(file
, sizeof file
, "%s/%s",
1756 work_dir
, runtime_settings
);
1757 if (stat(file
, &sb
)) {
1758 warnx("runtime file doesn't exist, creating it");
1759 if ((f
= fopen(file
, "w")) == NULL
)
1761 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
1765 strlcpy(file
, filename
, sizeof file
);
1767 if ((config
= fopen(file
, "r")) == NULL
) {
1768 warn("config_parse: cannot open %s", filename
);
1773 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
1774 if (feof(config
) || ferror(config
))
1778 cp
+= (long)strspn(cp
, WS
);
1779 if (cp
[0] == '\0') {
1785 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
1786 errx(1, "invalid config file entry: %s", line
);
1788 cp
+= (long)strspn(cp
, WS
);
1790 if ((val
= strsep(&cp
, "\0")) == NULL
)
1793 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
1794 handled
= settings_add(var
, val
);
1796 errx(1, "invalid conf file entry: %s=%s", var
, val
);
1805 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1811 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1815 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1818 JSStringGetUTF8CString(jsref
, s
, l
);
1819 JSStringRelease(jsref
);
1825 disable_hints(struct tab
*t
)
1827 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1828 bzero(t
->hint_num
, sizeof t
->hint_num
);
1829 run_script(t
, "vimprobable_clear()");
1831 t
->hint_mode
= XT_HINT_NONE
;
1835 enable_hints(struct tab
*t
)
1837 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1838 run_script(t
, "vimprobable_show_hints()");
1840 t
->hint_mode
= XT_HINT_NONE
;
1843 #define XT_JS_OPEN ("open;")
1844 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1845 #define XT_JS_FIRE ("fire;")
1846 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1847 #define XT_JS_FOUND ("found;")
1848 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1851 run_script(struct tab
*t
, char *s
)
1853 JSGlobalContextRef ctx
;
1854 WebKitWebFrame
*frame
;
1856 JSValueRef val
, exception
;
1859 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
1860 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1862 frame
= webkit_web_view_get_main_frame(t
->wv
);
1863 ctx
= webkit_web_frame_get_global_context(frame
);
1865 str
= JSStringCreateWithUTF8CString(s
);
1866 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1867 NULL
, 0, &exception
);
1868 JSStringRelease(str
);
1870 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
1872 es
= js_ref_to_string(ctx
, exception
);
1873 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
1877 es
= js_ref_to_string(ctx
, val
);
1878 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
1880 /* handle return value right here */
1881 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
1883 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
1886 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
1887 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
1888 &es
[XT_JS_FIRE_LEN
]);
1893 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
1894 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
1905 hint(struct tab
*t
, struct karg
*args
)
1908 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
1910 if (t
->hints_on
== 0)
1919 apply_style(struct tab
*t
)
1921 g_object_set(G_OBJECT(t
->settings
),
1922 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
1926 userstyle(struct tab
*t
, struct karg
*args
)
1928 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
1932 g_object_set(G_OBJECT(t
->settings
),
1933 "user-stylesheet-uri", NULL
, (char *)NULL
);
1942 * Doesn't work fully, due to the following bug:
1943 * https://bugs.webkit.org/show_bug.cgi?id=51747
1946 restore_global_history(void)
1948 char file
[PATH_MAX
];
1954 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
1956 if ((f
= fopen(file
, "r")) == NULL
) {
1957 warnx("%s: fopen", __func__
);
1962 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1963 if (feof(f
) || ferror(f
))
1966 if ((title
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1967 if (feof(f
) || ferror(f
)) {
1969 warnx("%s: broken history file\n", __func__
);
1973 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
1974 webkit_web_history_item_new_with_data(uri
, title
);
1975 h
= g_malloc(sizeof(struct history
));
1976 h
->uri
= g_strdup(uri
);
1977 h
->title
= g_strdup(title
);
1978 RB_INSERT(history_list
, &hl
, h
);
1979 completion_add_uri(h
->uri
);
1981 warnx("%s: failed to restore history\n", __func__
);
1997 save_global_history_to_disk(struct tab
*t
)
1999 char file
[PATH_MAX
];
2003 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2005 if ((f
= fopen(file
, "w")) == NULL
) {
2006 show_oops(t
, "%s: global history file: %s",
2007 __func__
, strerror(errno
));
2011 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2012 if (h
->uri
&& h
->title
)
2013 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2022 quit(struct tab
*t
, struct karg
*args
)
2024 if (save_global_history
)
2025 save_global_history_to_disk(t
);
2033 open_tabs(struct tab
*t
, struct karg
*a
)
2035 char file
[PATH_MAX
];
2039 struct tab
*ti
, *tt
;
2044 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2045 if ((f
= fopen(file
, "r")) == NULL
)
2048 ti
= TAILQ_LAST(&tabs
, tab_list
);
2051 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2052 if (feof(f
) || ferror(f
))
2055 /* retrieve session name */
2056 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2057 strlcpy(named_session
,
2058 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2059 sizeof named_session
);
2063 if (uri
&& strlen(uri
))
2064 create_new_tab(uri
, NULL
, 1);
2070 /* close open tabs */
2071 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2073 tt
= TAILQ_FIRST(&tabs
);
2092 restore_saved_tabs(void)
2094 char file
[PATH_MAX
];
2095 int unlink_file
= 0;
2100 snprintf(file
, sizeof file
, "%s/%s",
2101 sessions_dir
, XT_RESTART_TABS_FILE
);
2102 if (stat(file
, &sb
) == -1)
2103 a
.s
= XT_SAVED_TABS_FILE
;
2106 a
.s
= XT_RESTART_TABS_FILE
;
2109 a
.i
= XT_SES_DONOTHING
;
2110 rv
= open_tabs(NULL
, &a
);
2119 save_tabs(struct tab
*t
, struct karg
*a
)
2121 char file
[PATH_MAX
];
2126 const gchar
**arr
= NULL
;
2131 snprintf(file
, sizeof file
, "%s/%s",
2132 sessions_dir
, named_session
);
2134 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2136 if ((f
= fopen(file
, "w")) == NULL
) {
2137 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2141 /* save session name */
2142 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2144 /* save tabs, in the order they are arranged in the notebook */
2145 TAILQ_FOREACH(ti
, &tabs
, entry
)
2148 arr
= g_malloc0(len
* sizeof(gchar
*));
2150 TAILQ_FOREACH(ti
, &tabs
, entry
) {
2151 if ((uri
= get_uri(ti
->wv
)) != NULL
)
2152 arr
[gtk_notebook_page_num(notebook
, ti
->vbox
)] = uri
;
2155 for (i
= 0; i
< len
; i
++)
2157 fprintf(f
, "%s\n", arr
[i
]);
2166 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2178 yank_uri(struct tab
*t
, struct karg
*args
)
2181 GtkClipboard
*clipboard
;
2183 if ((uri
= get_uri(t
->wv
)) == NULL
)
2186 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2187 gtk_clipboard_set_text(clipboard
, uri
, -1);
2198 paste_uri_cb(GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
2200 struct paste_args
*pap
;
2202 if (data
== NULL
|| text
== NULL
|| !strlen(text
))
2205 pap
= (struct paste_args
*)data
;
2208 case XT_PASTE_CURRENT_TAB
:
2209 load_uri(pap
->t
, (gchar
*)text
);
2211 case XT_PASTE_NEW_TAB
:
2212 create_new_tab((gchar
*)text
, NULL
, 1);
2220 paste_uri(struct tab
*t
, struct karg
*args
)
2222 GtkClipboard
*clipboard
;
2223 struct paste_args
*pap
;
2225 pap
= g_malloc(sizeof(struct paste_args
));
2230 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2231 gtk_clipboard_request_text(clipboard
, paste_uri_cb
, pap
);
2237 find_domain(const gchar
*s
, int add_dot
)
2240 char *r
= NULL
, *ss
= NULL
;
2245 if (!strncmp(s
, "http://", strlen("http://")))
2246 s
= &s
[strlen("http://")];
2247 else if (!strncmp(s
, "https://", strlen("https://")))
2248 s
= &s
[strlen("https://")];
2254 for (i
= 0; i
< strlen(ss
) + 1 /* yes er need this */; i
++)
2255 /* chop string at first slash */
2256 if (ss
[i
] == '/' || ss
[i
] == '\0') {
2259 r
= g_strdup_printf(".%s", ss
);
2270 toggle_cwl(struct tab
*t
, struct karg
*args
)
2274 char *dom
= NULL
, *dom_toggle
= NULL
;
2280 uri
= get_uri(t
->wv
);
2281 dom
= find_domain(uri
, 1);
2282 d
= wl_find(dom
, &c_wl
);
2289 if (args
->i
& XT_WL_TOGGLE
)
2291 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2293 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2296 if (args
->i
& XT_WL_TOPLEVEL
)
2297 dom_toggle
= get_toplevel_domain(dom
);
2302 /* enable cookies for domain */
2303 wl_add(dom_toggle
, &c_wl
, 0);
2305 /* disable cookies for domain */
2306 RB_REMOVE(domain_list
, &c_wl
, d
);
2308 webkit_web_view_reload(t
->wv
);
2315 toggle_js(struct tab
*t
, struct karg
*args
)
2320 char *dom
= NULL
, *dom_toggle
= NULL
;
2325 g_object_get(G_OBJECT(t
->settings
),
2326 "enable-scripts", &es
, (char *)NULL
);
2327 if (args
->i
& XT_WL_TOGGLE
)
2329 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2331 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2336 uri
= get_uri(t
->wv
);
2337 dom
= find_domain(uri
, 1);
2339 if (uri
== NULL
|| dom
== NULL
) {
2340 show_oops(t
, "Can't toggle domain in JavaScript white list");
2344 if (args
->i
& XT_WL_TOPLEVEL
)
2345 dom_toggle
= get_toplevel_domain(dom
);
2350 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2351 wl_add(dom_toggle
, &js_wl
, 0 /* session */);
2353 d
= wl_find(dom_toggle
, &js_wl
);
2355 RB_REMOVE(domain_list
, &js_wl
, d
);
2356 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2358 g_object_set(G_OBJECT(t
->settings
),
2359 "enable-scripts", es
, (char *)NULL
);
2360 g_object_set(G_OBJECT(t
->settings
),
2361 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2362 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2363 webkit_web_view_reload(t
->wv
);
2371 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2375 a
.i
= XT_WL_TOGGLE
| XT_WL_FQDN
;
2380 toggle_src(struct tab
*t
, struct karg
*args
)
2387 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2388 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2389 webkit_web_view_reload(t
->wv
);
2395 focus_webview(struct tab
*t
)
2400 /* only grab focus if we are visible */
2401 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2402 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2406 focus(struct tab
*t
, struct karg
*args
)
2408 if (t
== NULL
|| args
== NULL
)
2414 if (args
->i
== XT_FOCUS_URI
)
2415 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2416 else if (args
->i
== XT_FOCUS_SEARCH
)
2417 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2423 stats(struct tab
*t
, struct karg
*args
)
2425 char *stats
, *s
, line
[64 * 1024];
2426 uint64_t line_count
= 0;
2430 show_oops_s("stats invalid parameters");
2433 if (save_rejected_cookies
) {
2434 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2436 s
= fgets(line
, sizeof line
, r_cookie_f
);
2437 if (s
== NULL
|| feof(r_cookie_f
) ||
2443 snprintf(line
, sizeof line
,
2444 "<br>Cookies blocked(*) total: %llu", line_count
);
2446 show_oops(t
, "Can't open blocked cookies file: %s",
2450 stats
= g_strdup_printf(XT_DOCTYPE
2453 "<title>Statistics</title>"
2455 "<h1>Statistics</h1>"
2457 "Cookies blocked(*) this session: %llu"
2459 "<p><small><b>*</b> results vary based on settings"
2465 load_webkit_string(t
, stats
, XT_URI_ABOUT_STATS
);
2472 marco(struct tab
*t
, struct karg
*args
)
2474 char *message
, line
[64 * 1024];
2478 show_oops_s("marco invalid parameters");
2481 snprintf(line
, sizeof line
, "<br>%s", marco_message(&len
));
2483 message
= g_strdup_printf(XT_DOCTYPE
2486 "<title>Marco Sez...</title>"
2495 load_webkit_string(t
, message
, XT_URI_ABOUT_MARCO
);
2502 blank(struct tab
*t
, struct karg
*args
)
2505 show_oops_s("about invalid parameters");
2507 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2512 about(struct tab
*t
, struct karg
*args
)
2517 show_oops_s("about invalid parameters");
2519 about
= g_strdup_printf(XT_DOCTYPE
2522 "<title>About</title>"
2526 "<b>Version: %s</b><p>"
2529 "<li>Marco Peereboom <marco@peereboom.us></li>"
2530 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2531 "<li>Edd Barrett <vext01@gmail.com> </li>"
2532 "<li>Todd T. Fries <todd@fries.net> </li>"
2534 "Copyrights and licenses can be found on the XXXterm "
2535 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2541 load_webkit_string(t
, about
, XT_URI_ABOUT_ABOUT
);
2548 help(struct tab
*t
, struct karg
*args
)
2553 show_oops_s("help invalid parameters");
2558 "<title>XXXterm</title>"
2559 "<meta http-equiv=\"REFRESH\" content=\"0;"
2560 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2563 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2564 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2565 "cgi-bin/man-cgi?xxxterm</a>"
2570 load_webkit_string(t
, help
, XT_URI_ABOUT_HELP
);
2576 * update all favorite tabs apart from one. Pass NULL if
2577 * you want to update all.
2580 update_favorite_tabs(struct tab
*apart_from
)
2583 if (!updating_fl_tabs
) {
2584 updating_fl_tabs
= 1; /* stop infinite recursion */
2585 TAILQ_FOREACH(t
, &tabs
, entry
)
2586 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2587 && (t
!= apart_from
))
2588 xtp_page_fl(t
, NULL
);
2589 updating_fl_tabs
= 0;
2593 /* show a list of favorites (bookmarks) */
2595 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2597 char file
[PATH_MAX
];
2599 char *uri
= NULL
, *title
= NULL
;
2600 size_t len
, lineno
= 0;
2602 char *header
, *body
, *tmp
, *html
= NULL
;
2604 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2607 warn("%s: bad param", __func__
);
2609 /* mark tab as favorite list */
2610 t
->xtp_meaning
= XT_XTP_TAB_MEANING_FL
;
2612 /* new session key */
2613 if (!updating_fl_tabs
)
2614 generate_xtp_session_key(&fl_session_key
);
2616 /* open favorites */
2617 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
2618 if ((f
= fopen(file
, "r")) == NULL
) {
2619 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2624 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
2625 "<title>Favorites</title>\n"
2628 "<h1>Favorites</h1>\n",
2632 body
= g_strdup_printf("<div align='center'><table><tr>"
2633 "<th style='width: 4%%'>#</th><th>Link</th>"
2634 "<th style='width: 15%%'>Remove</th></tr>\n");
2637 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2638 if (feof(f
) || ferror(f
))
2646 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2647 if (feof(f
) || ferror(f
)) {
2648 show_oops(t
, "favorites file corrupt");
2654 body
= g_strdup_printf("%s<tr>"
2656 "<td><a href='%s'>%s</a></td>"
2657 "<td style='text-align: center'>"
2658 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2660 body
, i
, uri
, title
,
2661 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2673 /* if none, say so */
2676 body
= g_strdup_printf("%s<tr>"
2677 "<td colspan='3' style='text-align: center'>"
2678 "No favorites - To add one use the 'favadd' command."
2679 "</td></tr>", body
);
2690 html
= g_strdup_printf("%s%s</table></div></html>",
2692 load_webkit_string(t
, html
, XT_URI_ABOUT_FAVORITES
);
2695 update_favorite_tabs(t
);
2708 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2709 size_t cert_count
, char *title
)
2711 gnutls_datum_t cinfo
;
2712 char *tmp
, *header
, *body
, *footer
;
2715 header
= g_strdup_printf("<html><head><title>%s</title></head><body>", title
);
2716 footer
= g_strdup("</body></html>");
2717 body
= g_strdup("");
2719 for (i
= 0; i
< cert_count
; i
++) {
2720 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2725 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2726 body
, i
, cinfo
.data
);
2727 gnutls_free(cinfo
.data
);
2731 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
2735 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
2740 ca_cmd(struct tab
*t
, struct karg
*args
)
2743 int rv
= 1, certs
= 0, certs_read
;
2746 gnutls_x509_crt_t
*c
= NULL
;
2747 char *certs_buf
= NULL
, *s
;
2749 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
2750 show_oops(t
, "Can't open CA file: %s", strerror(errno
));
2754 if (fstat(fileno(f
), &sb
) == -1) {
2755 show_oops(t
, "Can't stat CA file: %s", strerror(errno
));
2759 certs_buf
= g_malloc(sb
.st_size
+ 1);
2760 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
2761 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
2764 certs_buf
[sb
.st_size
] = '\0';
2767 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
2769 s
+= strlen("BEGIN CERTIFICATE");
2772 bzero(&dt
, sizeof dt
);
2773 dt
.data
= certs_buf
;
2774 dt
.size
= sb
.st_size
;
2775 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
2776 certs_read
= gnutls_x509_crt_list_import(c
, &certs
, &dt
,
2777 GNUTLS_X509_FMT_PEM
, 0);
2778 if (certs_read
<= 0) {
2779 show_oops(t
, "No cert(s) available");
2782 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
2795 connect_socket_from_uri(const gchar
*uri
, char *domain
, size_t domain_sz
)
2798 struct addrinfo hints
, *res
= NULL
, *ai
;
2802 if (uri
&& !g_str_has_prefix(uri
, "https://"))
2805 su
= soup_uri_new(uri
);
2808 if (!SOUP_URI_VALID_FOR_HTTP(su
))
2811 snprintf(port
, sizeof port
, "%d", su
->port
);
2812 bzero(&hints
, sizeof(struct addrinfo
));
2813 hints
.ai_flags
= AI_CANONNAME
;
2814 hints
.ai_family
= AF_UNSPEC
;
2815 hints
.ai_socktype
= SOCK_STREAM
;
2817 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
2820 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
2821 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
2824 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
2827 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
2831 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
2836 strlcpy(domain
, su
->host
, domain_sz
);
2847 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
2850 gnutls_deinit(gsession
);
2852 gnutls_certificate_free_credentials(xcred
);
2858 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
2859 gnutls_certificate_credentials_t
*xc
)
2861 gnutls_certificate_credentials_t xcred
;
2862 gnutls_session_t gsession
;
2865 if (gs
== NULL
|| xc
== NULL
)
2871 gnutls_certificate_allocate_credentials(&xcred
);
2872 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
2873 GNUTLS_X509_FMT_PEM
);
2874 gnutls_init(&gsession
, GNUTLS_CLIENT
);
2875 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
2876 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
2877 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
2878 if ((rv
= gnutls_handshake(gsession
)) < 0) {
2879 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
2881 gnutls_error_is_fatal(rv
),
2882 gnutls_strerror_name(rv
));
2883 stop_tls(gsession
, xcred
);
2887 gnutls_credentials_type_t cred
;
2888 cred
= gnutls_auth_get_type(gsession
);
2889 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
2890 stop_tls(gsession
, xcred
);
2902 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
2906 const gnutls_datum_t
*cl
;
2907 gnutls_x509_crt_t
*all_certs
;
2910 if (certs
== NULL
|| cert_count
== NULL
)
2912 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
2914 cl
= gnutls_certificate_get_peers(gsession
, &len
);
2918 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
2919 for (i
= 0; i
< len
; i
++) {
2920 gnutls_x509_crt_init(&all_certs
[i
]);
2921 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
2922 GNUTLS_X509_FMT_PEM
< 0)) {
2936 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
2940 for (i
= 0; i
< cert_count
; i
++)
2941 gnutls_x509_crt_deinit(certs
[i
]);
2946 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2947 size_t cert_count
, char *domain
)
2950 char cert_buf
[64 * 1024], file
[PATH_MAX
];
2955 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
2958 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
2959 if ((f
= fopen(file
, "w")) == NULL
) {
2960 show_oops(t
, "Can't create cert file %s %s",
2961 file
, strerror(errno
));
2965 for (i
= 0; i
< cert_count
; i
++) {
2966 cert_buf_sz
= sizeof cert_buf
;
2967 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
2968 cert_buf
, &cert_buf_sz
)) {
2969 show_oops(t
, "gnutls_x509_crt_export failed");
2972 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
2973 show_oops(t
, "Can't write certs: %s", strerror(errno
));
2978 /* not the best spot but oh well */
2979 gdk_color_parse("lightblue", &color
);
2980 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
2981 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2982 gdk_color_parse(XT_COLOR_BLACK
, &color
);
2983 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2989 load_compare_cert(struct tab
*t
, struct karg
*args
)
2992 char domain
[8182], file
[PATH_MAX
];
2993 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
2994 int s
= -1, rv
= 1, i
;
2998 gnutls_session_t gsession
;
2999 gnutls_x509_crt_t
*certs
;
3000 gnutls_certificate_credentials_t xcred
;
3005 if ((uri
= get_uri(t
->wv
)) == NULL
)
3008 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
3012 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3013 show_oops(t
, "Start TLS failed");
3018 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3019 show_oops(t
, "Can't get connection certificates");
3023 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3024 if ((f
= fopen(file
, "r")) == NULL
)
3027 for (i
= 0; i
< cert_count
; i
++) {
3028 cert_buf_sz
= sizeof cert_buf
;
3029 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3030 cert_buf
, &cert_buf_sz
)) {
3033 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3034 rv
= -1; /* critical */
3037 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3038 rv
= -1; /* critical */
3047 free_connection_certs(certs
, cert_count
);
3049 /* we close the socket first for speed */
3052 stop_tls(gsession
, xcred
);
3058 cert_cmd(struct tab
*t
, struct karg
*args
)
3064 gnutls_session_t gsession
;
3065 gnutls_x509_crt_t
*certs
;
3066 gnutls_certificate_credentials_t xcred
;
3071 if ((uri
= get_uri(t
->wv
)) == NULL
) {
3072 show_oops(t
, "Invalid URI");
3076 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
3077 show_oops(t
, "Invalid certidicate URI: %s", uri
);
3082 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3083 show_oops(t
, "Start TLS failed");
3088 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3089 show_oops(t
, "get_connection_certs failed");
3093 if (args
->i
& XT_SHOW
)
3094 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3095 else if (args
->i
& XT_SAVE
)
3096 save_certs(t
, certs
, cert_count
, domain
);
3098 free_connection_certs(certs
, cert_count
);
3100 /* we close the socket first for speed */
3103 stop_tls(gsession
, xcred
);
3109 remove_cookie(int index
)
3115 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3117 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3119 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3123 print_cookie("remove cookie", c
);
3124 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3129 soup_cookies_free(cf
);
3135 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3138 char *tmp
, *header
, *body
, *footer
;
3140 /* we set this to indicate we want to manually do navaction */
3141 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3143 header
= g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3145 footer
= g_strdup("</body></html>");
3146 body
= g_strdup("");
3149 if (args
->i
& XT_WL_PERSISTENT
) {
3151 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3153 RB_FOREACH(d
, domain_list
, wl
) {
3157 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3163 if (args
->i
& XT_WL_SESSION
) {
3165 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3167 RB_FOREACH(d
, domain_list
, wl
) {
3171 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3176 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3181 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3183 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3189 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3191 char file
[PATH_MAX
];
3193 char *line
= NULL
, *lt
= NULL
;
3196 char *dom
= NULL
, *dom_save
= NULL
;
3202 if (t
== NULL
|| args
== NULL
)
3205 if (runtime_settings
[0] == '\0')
3208 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3209 if ((f
= fopen(file
, "r+")) == NULL
)
3212 uri
= get_uri(t
->wv
);
3213 dom
= find_domain(uri
, 1);
3214 if (uri
== NULL
|| dom
== NULL
) {
3215 show_oops(t
, "Can't add domain to %s white list",
3216 js
? "JavaScript" : "cookie");
3220 if (args
->i
& XT_WL_TOPLEVEL
) {
3222 if ((dom_save
= get_toplevel_domain(dom
)) == NULL
) {
3223 show_oops(t
, "invalid domain: %s", dom
);
3226 } else if (args
->i
& XT_WL_FQDN
) {
3232 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom_save
);
3235 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3238 if (!strcmp(line
, lt
))
3244 fprintf(f
, "%s\n", lt
);
3249 d
= wl_find(dom_save
, &js_wl
);
3251 settings_add("js_wl", dom_save
);
3252 d
= wl_find(dom_save
, &js_wl
);
3256 d
= wl_find(dom_save
, &c_wl
);
3258 settings_add("cookie_wl", dom_save
);
3259 d
= wl_find(dom_save
, &c_wl
);
3263 /* find and add to persistent jar */
3264 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3265 for (;cf
; cf
= cf
->next
) {
3267 if (!strcmp(dom_save
, ci
->domain
) ||
3268 !strcmp(&dom_save
[1], ci
->domain
)) /* deal with leading . */ {
3269 c
= soup_cookie_copy(ci
);
3270 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3273 soup_cookies_free(cf
);
3291 js_show_wl(struct tab
*t
, struct karg
*args
)
3293 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3294 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3300 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3302 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3303 wl_show(t
, args
, "Cookie White List", &c_wl
);
3309 cookie_cmd(struct tab
*t
, struct karg
*args
)
3311 if (args
->i
& XT_SHOW
)
3312 wl_show(t
, args
, "Cookie White List", &c_wl
);
3313 else if (args
->i
& XT_WL_TOGGLE
)
3314 toggle_cwl(t
, args
);
3315 else if (args
->i
& XT_SAVE
)
3316 wl_save(t
, args
, 0);
3317 else if (args
->i
& XT_DELETE
)
3318 show_oops(t
, "'cookie delete' currently unimplemented");
3324 js_cmd(struct tab
*t
, struct karg
*args
)
3326 if (args
->i
& XT_SHOW
)
3327 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3328 else if (args
->i
& XT_SAVE
)
3329 wl_save(t
, args
, 1);
3330 else if (args
->i
& XT_WL_TOGGLE
)
3332 else if (args
->i
& XT_DELETE
)
3333 show_oops(t
, "'js delete' currently unimplemented");
3339 add_favorite(struct tab
*t
, struct karg
*args
)
3341 char file
[PATH_MAX
];
3344 size_t urilen
, linelen
;
3345 const gchar
*uri
, *title
;
3350 /* don't allow adding of xtp pages to favorites */
3351 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3352 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3356 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3357 if ((f
= fopen(file
, "r+")) == NULL
) {
3358 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3362 title
= webkit_web_view_get_title(t
->wv
);
3363 uri
= get_uri(t
->wv
);
3368 if (title
== NULL
|| uri
== NULL
) {
3369 show_oops(t
, "can't add page to favorites");
3373 urilen
= strlen(uri
);
3376 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3377 if (feof(f
) || ferror(f
))
3380 if (linelen
== urilen
&& !strcmp(line
, uri
))
3387 fprintf(f
, "\n%s\n%s", title
, uri
);
3393 update_favorite_tabs(NULL
);
3399 navaction(struct tab
*t
, struct karg
*args
)
3401 WebKitWebHistoryItem
*item
;
3403 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3404 t
->tab_id
, args
->i
);
3407 if (args
->i
== XT_NAV_BACK
)
3408 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3410 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3412 return (XT_CB_PASSTHROUGH
);
3413 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
3415 return (XT_CB_PASSTHROUGH
);
3420 webkit_web_view_go_back(t
->wv
);
3422 case XT_NAV_FORWARD
:
3423 webkit_web_view_go_forward(t
->wv
);
3426 webkit_web_view_reload(t
->wv
);
3428 case XT_NAV_RELOAD_CACHE
:
3429 webkit_web_view_reload_bypass_cache(t
->wv
);
3432 return (XT_CB_PASSTHROUGH
);
3436 move(struct tab
*t
, struct karg
*args
)
3438 GtkAdjustment
*adjust
;
3439 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3444 case XT_MOVE_BOTTOM
:
3446 case XT_MOVE_PAGEDOWN
:
3447 case XT_MOVE_PAGEUP
:
3448 case XT_MOVE_HALFDOWN
:
3449 case XT_MOVE_HALFUP
:
3450 adjust
= t
->adjust_v
;
3453 adjust
= t
->adjust_h
;
3457 pos
= gtk_adjustment_get_value(adjust
);
3458 ps
= gtk_adjustment_get_page_size(adjust
);
3459 upper
= gtk_adjustment_get_upper(adjust
);
3460 lower
= gtk_adjustment_get_lower(adjust
);
3461 si
= gtk_adjustment_get_step_increment(adjust
);
3462 pi
= gtk_adjustment_get_page_increment(adjust
);
3465 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3466 "max %f si %f pi %f\n",
3467 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
3468 pos
, ps
, upper
, lower
, max
, si
, pi
);
3474 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3479 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3481 case XT_MOVE_BOTTOM
:
3482 case XT_MOVE_FARRIGHT
:
3483 gtk_adjustment_set_value(adjust
, max
);
3486 case XT_MOVE_FARLEFT
:
3487 gtk_adjustment_set_value(adjust
, lower
);
3489 case XT_MOVE_PAGEDOWN
:
3491 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3493 case XT_MOVE_PAGEUP
:
3495 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3497 case XT_MOVE_HALFDOWN
:
3499 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3501 case XT_MOVE_HALFUP
:
3503 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3506 return (XT_CB_PASSTHROUGH
);
3509 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3511 return (XT_CB_HANDLED
);
3515 url_set_visibility(void)
3519 TAILQ_FOREACH(t
, &tabs
, entry
) {
3520 if (show_url
== 0) {
3521 gtk_widget_hide(t
->toolbar
);
3524 gtk_widget_show(t
->toolbar
);
3529 notebook_tab_set_visibility(GtkNotebook
*notebook
)
3532 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3534 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3538 statusbar_set_visibility(void)
3542 TAILQ_FOREACH(t
, &tabs
, entry
) {
3543 if (show_statusbar
== 0) {
3544 gtk_widget_hide(t
->statusbar
);
3547 gtk_widget_show(t
->statusbar
);
3552 url_set(struct tab
*t
, int enable_url_entry
)
3557 show_url
= enable_url_entry
;
3559 if (enable_url_entry
) {
3560 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
3561 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3562 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
), 0);
3564 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
3565 GTK_ENTRY_ICON_PRIMARY
);
3567 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
3568 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
3569 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
3570 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
3576 fullscreen(struct tab
*t
, struct karg
*args
)
3578 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3581 return (XT_CB_PASSTHROUGH
);
3583 if (show_url
== 0) {
3591 url_set_visibility();
3592 notebook_tab_set_visibility(notebook
);
3594 return (XT_CB_HANDLED
);
3598 statusaction(struct tab
*t
, struct karg
*args
)
3600 int rv
= XT_CB_HANDLED
;
3602 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3605 return (XT_CB_PASSTHROUGH
);
3608 case XT_STATUSBAR_SHOW
:
3609 if (show_statusbar
== 0) {
3611 statusbar_set_visibility();
3614 case XT_STATUSBAR_HIDE
:
3615 if (show_statusbar
== 1) {
3617 statusbar_set_visibility();
3625 urlaction(struct tab
*t
, struct karg
*args
)
3627 int rv
= XT_CB_HANDLED
;
3629 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3632 return (XT_CB_PASSTHROUGH
);
3636 if (show_url
== 0) {
3638 url_set_visibility();
3642 if (show_url
== 1) {
3644 url_set_visibility();
3652 tabaction(struct tab
*t
, struct karg
*args
)
3654 int rv
= XT_CB_HANDLED
;
3655 char *url
= args
->s
;
3658 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
3661 return (XT_CB_PASSTHROUGH
);
3665 if (strlen(url
) > 0)
3666 create_new_tab(url
, NULL
, 1);
3668 create_new_tab(NULL
, NULL
, 1);
3673 case XT_TAB_DELQUIT
:
3674 if (gtk_notebook_get_n_pages(notebook
) > 1)
3680 if (strlen(url
) > 0)
3683 rv
= XT_CB_PASSTHROUGH
;
3689 if (show_tabs
== 0) {
3691 notebook_tab_set_visibility(notebook
);
3695 if (show_tabs
== 1) {
3697 notebook_tab_set_visibility(notebook
);
3700 case XT_TAB_UNDO_CLOSE
:
3701 if (undo_count
== 0) {
3702 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3706 u
= TAILQ_FIRST(&undos
);
3707 create_new_tab(u
->uri
, u
, 1);
3709 TAILQ_REMOVE(&undos
, u
, entry
);
3711 /* u->history is freed in create_new_tab() */
3716 rv
= XT_CB_PASSTHROUGH
;
3730 resizetab(struct tab
*t
, struct karg
*args
)
3732 if (t
== NULL
|| args
== NULL
) {
3733 show_oops_s("resizetab invalid parameters");
3734 return (XT_CB_PASSTHROUGH
);
3737 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
3738 t
->tab_id
, args
->i
);
3740 adjustfont_webkit(t
, args
->i
);
3742 return (XT_CB_HANDLED
);
3746 movetab(struct tab
*t
, struct karg
*args
)
3751 if (t
== NULL
|| args
== NULL
) {
3752 show_oops_s("movetab invalid parameters");
3753 return (XT_CB_PASSTHROUGH
);
3756 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
3757 t
->tab_id
, args
->i
);
3759 if (args
->i
== XT_TAB_INVALID
)
3760 return (XT_CB_PASSTHROUGH
);
3762 if (args
->i
< XT_TAB_INVALID
) {
3763 /* next or previous tab */
3764 if (TAILQ_EMPTY(&tabs
))
3765 return (XT_CB_PASSTHROUGH
);
3769 /* if at the last page, loop around to the first */
3770 if (gtk_notebook_get_current_page(notebook
) ==
3771 gtk_notebook_get_n_pages(notebook
) - 1)
3772 gtk_notebook_set_current_page(notebook
, 0);
3774 gtk_notebook_next_page(notebook
);
3777 /* if at the first page, loop around to the last */
3778 if (gtk_notebook_current_page(notebook
) == 0)
3779 gtk_notebook_set_current_page(notebook
,
3780 gtk_notebook_get_n_pages(notebook
) - 1);
3782 gtk_notebook_prev_page(notebook
);
3785 gtk_notebook_set_current_page(notebook
, 0);
3788 gtk_notebook_set_current_page(notebook
, -1);
3791 return (XT_CB_PASSTHROUGH
);
3794 return (XT_CB_HANDLED
);
3799 if (t
->tab_id
== x
) {
3800 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
3801 return (XT_CB_HANDLED
);
3804 TAILQ_FOREACH(tt
, &tabs
, entry
) {
3805 if (tt
->tab_id
== x
) {
3806 gtk_notebook_set_current_page(notebook
, x
);
3807 DNPRINTF(XT_D_TAB
, "movetab: going to %d\n", x
);
3813 return (XT_CB_HANDLED
);
3817 command(struct tab
*t
, struct karg
*args
)
3819 char *s
= NULL
, *ss
= NULL
;
3823 if (t
== NULL
|| args
== NULL
) {
3824 show_oops_s("command invalid parameters");
3825 return (XT_CB_PASSTHROUGH
);
3844 case XT_CMD_OPEN_CURRENT
:
3847 case XT_CMD_TABNEW_CURRENT
:
3848 if (!s
) /* FALL THROUGH? */
3850 if ((uri
= get_uri(t
->wv
)) != NULL
) {
3851 ss
= g_strdup_printf("%s%s", s
, uri
);
3856 show_oops(t
, "command: invalid opcode %d", args
->i
);
3857 return (XT_CB_PASSTHROUGH
);
3860 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
3862 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
3863 gdk_color_parse(XT_COLOR_WHITE
, &color
);
3864 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
3866 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
3867 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
3872 return (XT_CB_HANDLED
);
3876 * Return a new string with a download row (in html)
3877 * appended. Old string is freed.
3880 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
3883 WebKitDownloadStatus stat
;
3884 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
3886 char cur_sz
[FMT_SCALED_STRSIZE
];
3887 char tot_sz
[FMT_SCALED_STRSIZE
];
3890 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
3892 /* All actions wil take this form:
3893 * xxxt://class/seskey
3895 xtp_prefix
= g_strdup_printf("%s%d/%s/",
3896 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
3898 stat
= webkit_download_get_status(dl
->download
);
3901 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
3902 status_html
= g_strdup_printf("Finished");
3903 cmd_html
= g_strdup_printf(
3904 "<a href='%s%d/%d'>Remove</a>",
3905 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3907 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
3908 /* gather size info */
3909 progress
= 100 * webkit_download_get_progress(dl
->download
);
3912 webkit_download_get_current_size(dl
->download
), cur_sz
);
3914 webkit_download_get_total_size(dl
->download
), tot_sz
);
3916 status_html
= g_strdup_printf(
3917 "<div style='width: 100%%' align='center'>"
3918 "<div class='progress-outer'>"
3919 "<div class='progress-inner' style='width: %.2f%%'>"
3920 "</div></div></div>"
3921 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3922 progress
, cur_sz
, tot_sz
, progress
);
3924 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3925 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3929 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
3930 status_html
= g_strdup_printf("Cancelled");
3931 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3932 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3934 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
3935 status_html
= g_strdup_printf("Error!");
3936 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3937 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3939 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
3940 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3941 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3942 status_html
= g_strdup_printf("Starting");
3945 show_oops(t
, "%s: unknown download status", __func__
);
3948 new_html
= g_strdup_printf(
3949 "%s\n<tr><td>%s</td><td>%s</td>"
3950 "<td style='text-align:center'>%s</td></tr>\n",
3951 html
, basename(webkit_download_get_destination_uri(dl
->download
)),
3952 status_html
, cmd_html
);
3956 g_free(status_html
);
3967 * update all download tabs apart from one. Pass NULL if
3968 * you want to update all.
3971 update_download_tabs(struct tab
*apart_from
)
3974 if (!updating_dl_tabs
) {
3975 updating_dl_tabs
= 1; /* stop infinite recursion */
3976 TAILQ_FOREACH(t
, &tabs
, entry
)
3977 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
3978 && (t
!= apart_from
))
3979 xtp_page_dl(t
, NULL
);
3980 updating_dl_tabs
= 0;
3985 * update all cookie tabs apart from one. Pass NULL if
3986 * you want to update all.
3989 update_cookie_tabs(struct tab
*apart_from
)
3992 if (!updating_cl_tabs
) {
3993 updating_cl_tabs
= 1; /* stop infinite recursion */
3994 TAILQ_FOREACH(t
, &tabs
, entry
)
3995 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
3996 && (t
!= apart_from
))
3997 xtp_page_cl(t
, NULL
);
3998 updating_cl_tabs
= 0;
4003 * update all history tabs apart from one. Pass NULL if
4004 * you want to update all.
4007 update_history_tabs(struct tab
*apart_from
)
4011 if (!updating_hl_tabs
) {
4012 updating_hl_tabs
= 1; /* stop infinite recursion */
4013 TAILQ_FOREACH(t
, &tabs
, entry
)
4014 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4015 && (t
!= apart_from
))
4016 xtp_page_hl(t
, NULL
);
4017 updating_hl_tabs
= 0;
4021 /* cookie management XTP page */
4023 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4025 char *header
, *body
, *footer
, *page
, *tmp
;
4026 int i
= 1; /* all ids start 1 */
4027 GSList
*sc
, *pc
, *pc_start
;
4029 char *type
, *table_headers
;
4030 char *last_domain
= strdup("");
4032 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4035 show_oops_s("%s invalid parameters", __func__
);
4038 /* mark this tab as cookie jar */
4039 t
->xtp_meaning
= XT_XTP_TAB_MEANING_CL
;
4041 /* Generate a new session key */
4042 if (!updating_cl_tabs
)
4043 generate_xtp_session_key(&cl_session_key
);
4046 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4047 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4048 "</head><body><h1>Cookie Jar</h1>\n");
4051 table_headers
= g_strdup_printf("<div align='center'><table><tr>"
4058 "<th>HTTP<br />only</th>"
4059 "<th>Rm</th></tr>\n");
4061 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4062 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4066 for (; sc
; sc
= sc
->next
) {
4069 if (strcmp(last_domain
, c
->domain
) != 0) {
4072 last_domain
= strdup(c
->domain
);
4076 body
= g_strdup_printf("%s</table></div>"
4078 body
, c
->domain
, table_headers
);
4082 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4083 c
->domain
, table_headers
);
4088 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4089 if (soup_cookie_equal(pc
->data
, c
)) {
4090 type
= "Session + Persistent";
4095 body
= g_strdup_printf(
4097 "<td style='width: text-align: center'>%s</td>"
4098 "<td style='width: 1px'>%s</td>"
4099 "<td style='width=70%%;overflow: visible'>"
4100 " <textarea rows='4'>%s</textarea>"
4104 "<td style='width: 1px; text-align: center'>%d</td>"
4105 "<td style='width: 1px; text-align: center'>%d</td>"
4106 "<td style='width: 1px; text-align: center'>"
4107 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4114 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4129 soup_cookies_free(sc
);
4130 soup_cookies_free(pc
);
4132 /* small message if there are none */
4134 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4135 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4139 footer
= g_strdup_printf("</table></div></body></html>");
4141 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4146 g_free(table_headers
);
4147 g_free(last_domain
);
4149 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4150 update_cookie_tabs(t
);
4158 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4160 char *header
, *body
, *footer
, *page
, *tmp
;
4162 int i
= 1; /* all ids start 1 */
4164 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4167 show_oops_s("%s invalid parameters", __func__
);
4171 /* mark this tab as history manager */
4172 t
->xtp_meaning
= XT_XTP_TAB_MEANING_HL
;
4174 /* Generate a new session key */
4175 if (!updating_hl_tabs
)
4176 generate_xtp_session_key(&hl_session_key
);
4179 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
4180 "<title>History</title>\n"
4183 "<h1>History</h1>\n",
4187 body
= g_strdup_printf("<div align='center'><table><tr>"
4188 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4190 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4192 body
= g_strdup_printf(
4194 "<td><a href='%s'>%s</a></td>"
4196 "<td style='text-align: center'>"
4197 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4198 body
, h
->uri
, h
->uri
, h
->title
,
4199 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4200 XT_XTP_HL_REMOVE
, i
);
4206 /* small message if there are none */
4209 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4210 "colspan='3'>No History</td></tr>\n", body
);
4215 footer
= g_strdup_printf("</table></div></body></html>");
4217 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4220 * update all history manager tabs as the xtp session
4221 * key has now changed. No need to update the current tab.
4222 * Already did that above.
4224 update_history_tabs(t
);
4230 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4237 * Generate a web page detailing the status of any downloads
4240 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4242 struct download
*dl
;
4243 char *header
, *body
, *footer
, *page
, *tmp
;
4247 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4250 show_oops_s("%s invalid parameters", __func__
);
4253 /* mark as a download manager tab */
4254 t
->xtp_meaning
= XT_XTP_TAB_MEANING_DL
;
4257 * Generate a new session key for next page instance.
4258 * This only happens for the top level call to xtp_page_dl()
4259 * in which case updating_dl_tabs is 0.
4261 if (!updating_dl_tabs
)
4262 generate_xtp_session_key(&dl_session_key
);
4264 /* header - with refresh so as to update */
4265 if (refresh_interval
>= 1)
4266 ref
= g_strdup_printf(
4267 "<meta http-equiv='refresh' content='%u"
4268 ";url=%s%d/%s/%d' />\n",
4278 header
= g_strdup_printf(
4280 "<title>Downloads</title>\n%s%s</head>\n",
4281 XT_DOCTYPE XT_HTML_TAG
,
4285 body
= g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4286 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4287 "</p><table><tr><th style='width: 60%%'>"
4288 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4289 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4291 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4292 body
= xtp_page_dl_row(t
, body
, dl
);
4296 /* message if no downloads in list */
4299 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4300 " style='text-align: center'>"
4301 "No downloads</td></tr>\n", body
);
4306 footer
= g_strdup_printf("</table></div></body></html>");
4308 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4312 * update all download manager tabs as the xtp session
4313 * key has now changed. No need to update the current tab.
4314 * Already did that above.
4316 update_download_tabs(t
);
4323 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4330 search(struct tab
*t
, struct karg
*args
)
4334 if (t
== NULL
|| args
== NULL
) {
4335 show_oops_s("search invalid parameters");
4338 if (t
->search_text
== NULL
) {
4339 if (global_search
== NULL
)
4340 return (XT_CB_PASSTHROUGH
);
4342 t
->search_text
= g_strdup(global_search
);
4343 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4344 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4348 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4349 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4352 case XT_SEARCH_NEXT
:
4353 d
= t
->search_forward
;
4355 case XT_SEARCH_PREV
:
4356 d
= !t
->search_forward
;
4359 return (XT_CB_PASSTHROUGH
);
4362 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4364 return (XT_CB_HANDLED
);
4367 struct settings_args
{
4373 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4376 struct settings_args
*sa
= cb_args
;
4381 if (s
->flags
& XT_SF_RUNTIME
)
4387 *sa
->body
= g_strdup_printf(
4389 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4390 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4402 set(struct tab
*t
, struct karg
*args
)
4404 char *header
, *body
, *footer
, *page
, *tmp
;
4406 struct settings_args sa
;
4408 bzero(&sa
, sizeof sa
);
4412 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4413 "\n<head><title>Settings</title>\n"
4414 "</head><body><h1>Settings</h1>\n");
4417 body
= g_strdup_printf("<div align='center'><table><tr>"
4418 "<th align='left'>Setting</th>"
4419 "<th align='left'>Value</th></tr>\n");
4421 settings_walk(print_setting
, &sa
);
4424 /* small message if there are none */
4427 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4428 "colspan='2'>No settings</td></tr>\n", body
);
4433 footer
= g_strdup_printf("</table></div></body></html>");
4435 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4441 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4443 return (XT_CB_PASSTHROUGH
);
4447 session_save(struct tab
*t
, char *filename
)
4452 if (strlen(filename
) == 0)
4455 if (filename
[0] == '.' || filename
[0] == '/')
4459 if (save_tabs(t
, &a
))
4461 strlcpy(named_session
, filename
, sizeof named_session
);
4469 session_open(struct tab
*t
, char *filename
)
4474 if (strlen(filename
) == 0)
4477 if (filename
[0] == '.' || filename
[0] == '/')
4481 a
.i
= XT_SES_CLOSETABS
;
4482 if (open_tabs(t
, &a
))
4485 strlcpy(named_session
, filename
, sizeof named_session
);
4493 session_delete(struct tab
*t
, char *filename
)
4495 char file
[PATH_MAX
];
4498 if (strlen(filename
) == 0)
4501 if (filename
[0] == '.' || filename
[0] == '/')
4504 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
4508 if (!strcmp(filename
, named_session
))
4509 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
4510 sizeof named_session
);
4518 session_cmd(struct tab
*t
, struct karg
*args
)
4520 char *filename
= args
->s
;
4525 if (args
->i
& XT_SHOW
)
4526 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
4527 XT_SAVED_TABS_FILE
: named_session
);
4528 else if (args
->i
& XT_SAVE
) {
4529 if (session_save(t
, filename
)) {
4530 show_oops(t
, "Can't save session: %s",
4531 filename
? filename
: "INVALID");
4534 } else if (args
->i
& XT_OPEN
) {
4535 if (session_open(t
, filename
)) {
4536 show_oops(t
, "Can't open session: %s",
4537 filename
? filename
: "INVALID");
4540 } else if (args
->i
& XT_DELETE
) {
4541 if (session_delete(t
, filename
)) {
4542 show_oops(t
, "Can't delete session: %s",
4543 filename
? filename
: "INVALID");
4548 return (XT_CB_PASSTHROUGH
);
4552 * Make a hardcopy of the page
4555 print_page(struct tab
*t
, struct karg
*args
)
4557 WebKitWebFrame
*frame
;
4559 GtkPrintOperation
*op
;
4560 GtkPrintOperationAction action
;
4561 GtkPrintOperationResult print_res
;
4562 GError
*g_err
= NULL
;
4563 int marg_l
, marg_r
, marg_t
, marg_b
;
4565 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
4567 ps
= gtk_page_setup_new();
4568 op
= gtk_print_operation_new();
4569 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
4570 frame
= webkit_web_view_get_main_frame(t
->wv
);
4572 /* the default margins are too small, so we will bump them */
4573 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
4574 XT_PRINT_EXTRA_MARGIN
;
4575 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
4576 XT_PRINT_EXTRA_MARGIN
;
4577 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
4578 XT_PRINT_EXTRA_MARGIN
;
4579 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
4580 XT_PRINT_EXTRA_MARGIN
;
4583 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
4584 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
4585 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
4586 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
4588 gtk_print_operation_set_default_page_setup(op
, ps
);
4590 /* this appears to free 'op' and 'ps' */
4591 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
4593 /* check it worked */
4594 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
4595 show_oops_s("can't print: %s", g_err
->message
);
4596 g_error_free (g_err
);
4604 go_home(struct tab
*t
, struct karg
*args
)
4611 restart(struct tab
*t
, struct karg
*args
)
4615 a
.s
= XT_RESTART_TABS_FILE
;
4617 execvp(start_argv
[0], start_argv
);
4623 #define CTRL GDK_CONTROL_MASK
4624 #define MOD1 GDK_MOD1_MASK
4625 #define SHFT GDK_SHIFT_MASK
4627 /* inherent to GTK not all keys will be caught at all times */
4628 /* XXX sort key bindings */
4629 struct key_binding
{
4634 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
4636 { "cookiejar", MOD1
, 0, GDK_j
},
4637 { "downloadmgr", MOD1
, 0, GDK_d
},
4638 { "history", MOD1
, 0, GDK_h
},
4639 { "print", CTRL
, 0, GDK_p
},
4640 { "search", 0, 0, GDK_slash
},
4641 { "searchb", 0, 0, GDK_question
},
4642 { "command", 0, 0, GDK_colon
},
4643 { "quit", CTRL
, 0, GDK_q
},
4644 { "restart", MOD1
, 0, GDK_q
},
4645 { "js toggle", CTRL
, 0, GDK_j
},
4646 { "cookie toggle", MOD1
, 0, GDK_c
},
4647 { "togglesrc", CTRL
, 0, GDK_s
},
4648 { "yankuri", 0, 0, GDK_y
},
4649 { "pasteuricur", 0, 0, GDK_p
},
4650 { "pasteurinew", 0, 0, GDK_P
},
4653 { "searchnext", 0, 0, GDK_n
},
4654 { "searchprevious", 0, 0, GDK_N
},
4657 { "focusaddress", 0, 0, GDK_F6
},
4658 { "focussearch", 0, 0, GDK_F7
},
4661 { "hinting", 0, 0, GDK_f
},
4663 /* custom stylesheet */
4664 { "userstyle", 0, 0, GDK_i
},
4667 { "goback", 0, 0, GDK_BackSpace
},
4668 { "goback", MOD1
, 0, GDK_Left
},
4669 { "goforward", SHFT
, 0, GDK_BackSpace
},
4670 { "goforward", MOD1
, 0, GDK_Right
},
4671 { "reload", 0, 0, GDK_F5
},
4672 { "reload", CTRL
, 0, GDK_r
},
4673 { "reloadforce", CTRL
, 0, GDK_R
},
4674 { "reload", CTRL
, 0, GDK_l
},
4675 { "favorites", MOD1
, 1, GDK_f
},
4677 /* vertical movement */
4678 { "scrolldown", 0, 0, GDK_j
},
4679 { "scrolldown", 0, 0, GDK_Down
},
4680 { "scrollup", 0, 0, GDK_Up
},
4681 { "scrollup", 0, 0, GDK_k
},
4682 { "scrollbottom", 0, 0, GDK_G
},
4683 { "scrollbottom", 0, 0, GDK_End
},
4684 { "scrolltop", 0, 0, GDK_Home
},
4685 { "scrolltop", 0, 0, GDK_g
},
4686 { "scrollpagedown", 0, 0, GDK_space
},
4687 { "scrollpagedown", CTRL
, 0, GDK_f
},
4688 { "scrollhalfdown", CTRL
, 0, GDK_d
},
4689 { "scrollpagedown", 0, 0, GDK_Page_Down
},
4690 { "scrollpageup", 0, 0, GDK_Page_Up
},
4691 { "scrollpageup", CTRL
, 0, GDK_b
},
4692 { "scrollhalfup", CTRL
, 0, GDK_u
},
4693 /* horizontal movement */
4694 { "scrollright", 0, 0, GDK_l
},
4695 { "scrollright", 0, 0, GDK_Right
},
4696 { "scrollleft", 0, 0, GDK_Left
},
4697 { "scrollleft", 0, 0, GDK_h
},
4698 { "scrollfarright", 0, 0, GDK_dollar
},
4699 { "scrollfarleft", 0, 0, GDK_0
},
4702 { "tabnew", CTRL
, 0, GDK_t
},
4703 { "tabclose", CTRL
, 1, GDK_w
},
4704 { "tabundoclose", 0, 0, GDK_U
},
4705 { "tabgoto1", CTRL
, 0, GDK_1
},
4706 { "tabgoto2", CTRL
, 0, GDK_2
},
4707 { "tabgoto3", CTRL
, 0, GDK_3
},
4708 { "tabgoto4", CTRL
, 0, GDK_4
},
4709 { "tabgoto5", CTRL
, 0, GDK_5
},
4710 { "tabgoto6", CTRL
, 0, GDK_6
},
4711 { "tabgoto7", CTRL
, 0, GDK_7
},
4712 { "tabgoto8", CTRL
, 0, GDK_8
},
4713 { "tabgoto9", CTRL
, 0, GDK_9
},
4714 { "tabgoto10", CTRL
, 0, GDK_0
},
4715 { "tabfirst", CTRL
, 0, GDK_less
},
4716 { "tablast", CTRL
, 0, GDK_greater
},
4717 { "tabprevious", CTRL
, 0, GDK_Left
},
4718 { "tabnext", CTRL
, 0, GDK_Right
},
4719 { "focusout", CTRL
, 0, GDK_minus
},
4720 { "focusin", CTRL
, 0, GDK_plus
},
4721 { "focusin", CTRL
, 0, GDK_equal
},
4723 /* command aliases (handy when -S flag is used) */
4724 { "promptopen", 0, 0, GDK_F9
},
4725 { "promptopencurrent", 0, 0, GDK_F10
},
4726 { "prompttabnew", 0, 0, GDK_F11
},
4727 { "prompttabnewcurrent",0, 0, GDK_F12
},
4729 TAILQ_HEAD(keybinding_list
, key_binding
);
4732 walk_kb(struct settings
*s
,
4733 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
4735 struct key_binding
*k
;
4738 if (s
== NULL
|| cb
== NULL
) {
4739 show_oops_s("walk_kb invalid parameters");
4743 TAILQ_FOREACH(k
, &kbl
, entry
) {
4749 if (gdk_keyval_name(k
->key
) == NULL
)
4752 strlcat(str
, k
->cmd
, sizeof str
);
4753 strlcat(str
, ",", sizeof str
);
4755 if (k
->mask
& GDK_SHIFT_MASK
)
4756 strlcat(str
, "S-", sizeof str
);
4757 if (k
->mask
& GDK_CONTROL_MASK
)
4758 strlcat(str
, "C-", sizeof str
);
4759 if (k
->mask
& GDK_MOD1_MASK
)
4760 strlcat(str
, "M1-", sizeof str
);
4761 if (k
->mask
& GDK_MOD2_MASK
)
4762 strlcat(str
, "M2-", sizeof str
);
4763 if (k
->mask
& GDK_MOD3_MASK
)
4764 strlcat(str
, "M3-", sizeof str
);
4765 if (k
->mask
& GDK_MOD4_MASK
)
4766 strlcat(str
, "M4-", sizeof str
);
4767 if (k
->mask
& GDK_MOD5_MASK
)
4768 strlcat(str
, "M5-", sizeof str
);
4770 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
4771 cb(s
, str
, cb_args
);
4775 init_keybindings(void)
4778 struct key_binding
*k
;
4780 for (i
= 0; i
< LENGTH(keys
); i
++) {
4781 k
= g_malloc0(sizeof *k
);
4782 k
->cmd
= keys
[i
].cmd
;
4783 k
->mask
= keys
[i
].mask
;
4784 k
->use_in_entry
= keys
[i
].use_in_entry
;
4785 k
->key
= keys
[i
].key
;
4786 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4788 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
4789 k
->cmd
? k
->cmd
: "unnamed key");
4794 keybinding_clearall(void)
4796 struct key_binding
*k
, *next
;
4798 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
4799 next
= TAILQ_NEXT(k
, entry
);
4803 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
4804 k
->cmd
? k
->cmd
: "unnamed key");
4805 TAILQ_REMOVE(&kbl
, k
, entry
);
4811 keybinding_add(char *kb
, char *value
, struct key_binding
*orig
)
4813 struct key_binding
*k
;
4814 guint keyval
, mask
= 0;
4817 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s %s\n", kb
, value
, orig
->cmd
);
4821 if (strcmp(kb
, orig
->cmd
))
4824 /* find modifier keys */
4825 if (strstr(value
, "S-"))
4826 mask
|= GDK_SHIFT_MASK
;
4827 if (strstr(value
, "C-"))
4828 mask
|= GDK_CONTROL_MASK
;
4829 if (strstr(value
, "M1-"))
4830 mask
|= GDK_MOD1_MASK
;
4831 if (strstr(value
, "M2-"))
4832 mask
|= GDK_MOD2_MASK
;
4833 if (strstr(value
, "M3-"))
4834 mask
|= GDK_MOD3_MASK
;
4835 if (strstr(value
, "M4-"))
4836 mask
|= GDK_MOD4_MASK
;
4837 if (strstr(value
, "M5-"))
4838 mask
|= GDK_MOD5_MASK
;
4841 for (i
= strlen(value
) - 1; i
> 0; i
--)
4842 if (value
[i
] == '-')
4843 value
= &value
[i
+ 1];
4845 /* validate keyname */
4846 keyval
= gdk_keyval_from_name(value
);
4847 if (keyval
== GDK_VoidSymbol
) {
4848 warnx("invalid keybinding name %s", value
);
4851 /* must run this test too, gtk+ doesn't handle 10 for example */
4852 if (gdk_keyval_name(keyval
) == NULL
) {
4853 warnx("invalid keybinding name %s", value
);
4857 /* make sure it isn't a dupe */
4858 TAILQ_FOREACH(k
, &kbl
, entry
)
4859 if (k
->key
== keyval
&& k
->mask
== mask
) {
4860 warnx("duplicate keybinding for %s", value
);
4865 k
= g_malloc0(sizeof *k
);
4868 k
->use_in_entry
= orig
->use_in_entry
;
4871 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
4876 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
4877 k
->name
, gdk_keyval_name(keyval
));
4879 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4885 add_kb(struct settings
*s
, char *entry
)
4890 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
4892 /* clearall is special */
4893 if (!strcmp(entry
, "clearall")) {
4894 keybinding_clearall();
4898 kb
= strstr(entry
, ",");
4904 /* make sure it is a valid keybinding */
4905 for (i
= 0; i
< LENGTH(keys
); i
++)
4906 if (keys
[i
].cmd
&& !strcmp(entry
, keys
[i
].cmd
)) {
4907 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s 0x%x %d 0x%x\n",
4910 keys
[i
].use_in_entry
,
4913 return (keybinding_add(entry
, value
, &keys
[i
]));
4922 int (*func
)(struct tab
*, struct karg
*);
4924 bool userarg
; /* allow free text arg */
4926 { "command", 0, command
, {.i
= ':'}, FALSE
},
4927 { "search", 0, command
, {.i
= '/'}, FALSE
},
4928 { "searchb", 0, command
, {.i
= '?'}, FALSE
},
4929 { "togglesrc", 0, toggle_src
, {0}, FALSE
},
4931 /* yanking and pasting */
4932 { "yankuri", 0, yank_uri
, {0}, FALSE
},
4933 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4934 { "pasteuricur", 0, paste_uri
, {.i
= XT_PASTE_CURRENT_TAB
}, FALSE
},
4935 { "pasteurinew", 0, paste_uri
, {.i
= XT_PASTE_NEW_TAB
}, FALSE
},
4938 { "searchnext", 0, search
, {.i
= XT_SEARCH_NEXT
}, FALSE
},
4939 { "searchprevious", 0, search
, {.i
= XT_SEARCH_PREV
}, FALSE
},
4940 { "searchprev", 0, search
, {.i
= XT_SEARCH_PREV
}, FALSE
},
4943 { "focusaddress", 0, focus
, {.i
= XT_FOCUS_URI
}, FALSE
},
4944 { "focussearch", 0, focus
, {.i
= XT_FOCUS_SEARCH
}, FALSE
},
4947 { "hinting", 0, hint
, {.i
= 0}, FALSE
},
4949 /* custom stylesheet */
4950 { "userstyle", 0, userstyle
, {.i
= 0 }, FALSE
},
4953 { "goback", 0, navaction
, {.i
= XT_NAV_BACK
}, FALSE
},
4954 { "goforward", 0, navaction
, {.i
= XT_NAV_FORWARD
}, FALSE
},
4955 { "reload", 0, navaction
, {.i
= XT_NAV_RELOAD
}, FALSE
},
4956 { "reloadforce", 0, navaction
, {.i
= XT_NAV_RELOAD_CACHE
}, FALSE
},
4958 /* vertical movement */
4959 { "scrolldown", 0, move
, {.i
= XT_MOVE_DOWN
}, FALSE
},
4960 { "scrollup", 0, move
, {.i
= XT_MOVE_UP
}, FALSE
},
4961 { "scrollbottom", 0, move
, {.i
= XT_MOVE_BOTTOM
}, FALSE
},
4962 { "scrolltop", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4963 { "1", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4964 { "scrollhalfdown", 0, move
, {.i
= XT_MOVE_HALFDOWN
},FALSE
},
4965 { "scrollhalfup", 0, move
, {.i
= XT_MOVE_HALFUP
}, FALSE
},
4966 { "scrollpagedown", 0, move
, {.i
= XT_MOVE_PAGEDOWN
},FALSE
},
4967 { "scrollpageup", 0, move
, {.i
= XT_MOVE_PAGEUP
}, FALSE
},
4968 /* horizontal movement */
4969 { "scrollright", 0, move
, {.i
= XT_MOVE_RIGHT
}, FALSE
},
4970 { "scrollleft", 0, move
, {.i
= XT_MOVE_LEFT
}, FALSE
},
4971 { "scrollfarright", 0, move
, {.i
= XT_MOVE_FARRIGHT
},FALSE
},
4972 { "scrollfarleft", 0, move
, {.i
= XT_MOVE_FARLEFT
}, FALSE
},
4975 { "favorites", 0, xtp_page_fl
, {0}, FALSE
},
4976 { "fav", 0, xtp_page_fl
, {0}, FALSE
},
4977 { "favadd", 0, add_favorite
, {0}, FALSE
},
4979 { "quit", 0, quit
, {0}, FALSE
},
4980 { "q!", 0, quit
, {0}, FALSE
},
4981 { "qa", 0, quit
, {0}, FALSE
},
4982 { "qa!", 0, quit
, {0}, FALSE
},
4983 { "w", 0, save_tabs
, {0}, FALSE
},
4984 { "wq", 0, save_tabs_and_quit
, {0}, FALSE
},
4985 { "wq!", 0, save_tabs_and_quit
, {0}, FALSE
},
4986 { "help", 0, help
, {0}, FALSE
},
4987 { "about", 0, about
, {0}, FALSE
},
4988 { "stats", 0, stats
, {0}, FALSE
},
4989 { "version", 0, about
, {0}, FALSE
},
4990 { "cookiejar", 0, xtp_page_cl
, {0}, FALSE
},
4993 { "js", 0, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4994 { "save", 1, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4995 { "domain", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
4996 { "fqdn", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4997 { "show", 1, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4998 { "all", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4999 { "persistent", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
5000 { "session", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
5001 { "toggle", 1, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5002 { "domain", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5003 { "fqdn", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5005 /* cookie command */
5006 { "cookie", 0, cookie_cmd
, {0}, FALSE
},
5007 { "save", 1, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5008 { "domain", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
5009 { "fqdn", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5010 { "show", 1, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5011 { "all", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5012 { "persistent", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
5013 { "session", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
5014 { "toggle", 1, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5015 { "domain", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5016 { "fqdn", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5019 { "cert", 0, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5020 { "save", 1, cert_cmd
, {.i
= XT_SAVE
}, FALSE
},
5021 { "show", 1, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5023 { "ca", 0, ca_cmd
, {0}, FALSE
},
5024 { "downloadmgr", 0, xtp_page_dl
, {0}, FALSE
},
5025 { "dl", 0, xtp_page_dl
, {0}, FALSE
},
5026 { "h", 0, xtp_page_hl
, {0}, FALSE
},
5027 { "hist", 0, xtp_page_hl
, {0}, FALSE
},
5028 { "history", 0, xtp_page_hl
, {0}, FALSE
},
5029 { "home", 0, go_home
, {0}, FALSE
},
5030 { "restart", 0, restart
, {0}, FALSE
},
5031 { "urlhide", 0, urlaction
, {.i
= XT_URL_HIDE
}, FALSE
},
5032 { "urlh", 0, urlaction
, {.i
= XT_URL_HIDE
}, FALSE
},
5033 { "urlshow", 0, urlaction
, {.i
= XT_URL_SHOW
}, FALSE
},
5034 { "urls", 0, urlaction
, {.i
= XT_URL_SHOW
}, FALSE
},
5035 { "statushide", 0, statusaction
, {.i
= XT_STATUSBAR_HIDE
}, FALSE
},
5036 { "statush", 0, statusaction
, {.i
= XT_STATUSBAR_HIDE
}, FALSE
},
5037 { "statusshow", 0, statusaction
, {.i
= XT_STATUSBAR_SHOW
}, FALSE
},
5038 { "statuss", 0, statusaction
, {.i
= XT_STATUSBAR_SHOW
}, FALSE
},
5040 { "print", 0, print_page
, {0}, FALSE
},
5043 { "o", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5044 { "op", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5045 { "open", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5046 { "tabnew", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5047 { "tabedit", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5048 { "tabe", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5049 { "tabclose", 0, tabaction
, {.i
= XT_TAB_DELETE
}, FALSE
},
5050 { "tabundoclose", 0, tabaction
, {.i
= XT_TAB_UNDO_CLOSE
} },
5051 { "tabc", 0, tabaction
, {.i
= XT_TAB_DELETE
}, FALSE
},
5052 { "tabshow", 0, tabaction
, {.i
= XT_TAB_SHOW
}, FALSE
},
5053 { "tabs", 0, tabaction
, {.i
= XT_TAB_SHOW
}, FALSE
},
5054 { "tabhide", 0, tabaction
, {.i
= XT_TAB_HIDE
}, FALSE
},
5055 { "tabh", 0, tabaction
, {.i
= XT_TAB_HIDE
}, FALSE
},
5056 { "quit", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5057 { "q", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5058 /* XXX add count to these commands */
5059 { "tabfirst", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5060 { "tabfir", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5061 { "tabrewind", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5062 { "tabr", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5063 { "tablast", 0, movetab
, {.i
= XT_TAB_LAST
}, FALSE
},
5064 { "tabl", 0, movetab
, {.i
= XT_TAB_LAST
}, FALSE
},
5065 { "tabprevious", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5066 { "tabprev", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5067 { "tabp", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5068 { "tabnext", 0, movetab
, {.i
= XT_TAB_NEXT
}, FALSE
},
5069 { "tabn", 0, movetab
, {.i
= XT_TAB_NEXT
}, FALSE
},
5070 { "tabgoto1", 0, movetab
, {.i
= 1}, FALSE
},
5071 { "tabgoto2", 0, movetab
, {.i
= 2}, FALSE
},
5072 { "tabgoto3", 0, movetab
, {.i
= 3}, FALSE
},
5073 { "tabgoto4", 0, movetab
, {.i
= 4}, FALSE
},
5074 { "tabgoto5", 0, movetab
, {.i
= 5}, FALSE
},
5075 { "tabgoto6", 0, movetab
, {.i
= 6}, FALSE
},
5076 { "tabgoto7", 0, movetab
, {.i
= 7}, FALSE
},
5077 { "tabgoto8", 0, movetab
, {.i
= 8}, FALSE
},
5078 { "tabgoto9", 0, movetab
, {.i
= 9}, FALSE
},
5079 { "tabgoto10", 0, movetab
, {.i
= 10}, FALSE
},
5080 { "focusout", 0, resizetab
, {.i
= -1}, FALSE
},
5081 { "focusin", 0, resizetab
, {.i
= 1}, FALSE
},
5082 { "focusin", 0, resizetab
, {.i
= 1}, FALSE
},
5084 /* command aliases (handy when -S flag is used) */
5085 { "promptopen", 0, command
, {.i
= XT_CMD_OPEN
}, FALSE
},
5086 { "promptopencurrent", 0, command
, {.i
= XT_CMD_OPEN_CURRENT
}, FALSE
},
5087 { "prompttabnew", 0, command
, {.i
= XT_CMD_TABNEW
}, FALSE
},
5088 { "prompttabnewcurrent",0, command
, {.i
= XT_CMD_TABNEW_CURRENT
}, FALSE
},
5091 { "set", 0, set
, {0}, FALSE
},
5092 { "fullscreen", 0, fullscreen
, {0}, FALSE
},
5093 { "f", 0, fullscreen
, {0}, FALSE
},
5096 { "session", 0, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5097 { "delete", 1, session_cmd
, {.i
= XT_DELETE
}, TRUE
},
5098 { "open", 1, session_cmd
, {.i
= XT_OPEN
}, TRUE
},
5099 { "save", 1, session_cmd
, {.i
= XT_SAVE
}, TRUE
},
5100 { "show", 1, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5107 } cmd_status
= {-1, 0};
5110 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5116 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5122 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5124 a
.i
= XT_NAV_FORWARD
;
5134 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5136 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5138 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5145 * cancel, remove, etc. downloads
5148 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5150 struct download find
, *d
= NULL
;
5152 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5154 /* some commands require a valid download id */
5155 if (cmd
!= XT_XTP_DL_LIST
) {
5156 /* lookup download in question */
5158 d
= RB_FIND(download_list
, &downloads
, &find
);
5161 show_oops(t
, "%s: no such download", __func__
);
5166 /* decide what to do */
5168 case XT_XTP_DL_CANCEL
:
5169 webkit_download_cancel(d
->download
);
5171 case XT_XTP_DL_REMOVE
:
5172 webkit_download_cancel(d
->download
); /* just incase */
5173 g_object_unref(d
->download
);
5174 RB_REMOVE(download_list
, &downloads
, d
);
5176 case XT_XTP_DL_LIST
:
5180 show_oops(t
, "%s: unknown command", __func__
);
5183 xtp_page_dl(t
, NULL
);
5187 * Actions on history, only does one thing for now, but
5188 * we provide the function for future actions
5191 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5193 struct history
*h
, *next
;
5197 case XT_XTP_HL_REMOVE
:
5198 /* walk backwards, as listed in reverse */
5199 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5200 next
= RB_PREV(history_list
, &hl
, h
);
5202 RB_REMOVE(history_list
, &hl
, h
);
5203 g_free((gpointer
) h
->title
);
5204 g_free((gpointer
) h
->uri
);
5211 case XT_XTP_HL_LIST
:
5212 /* Nothing - just xtp_page_hl() below */
5215 show_oops(t
, "%s: unknown command", __func__
);
5219 xtp_page_hl(t
, NULL
);
5222 /* remove a favorite */
5224 remove_favorite(struct tab
*t
, int index
)
5226 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5227 char *new_favs
, *tmp
;
5232 /* open favorites */
5233 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5235 if ((f
= fopen(file
, "r")) == NULL
) {
5236 show_oops(t
, "%s: can't open favorites: %s",
5237 __func__
, strerror(errno
));
5241 /* build a string which will become the new favroites file */
5242 new_favs
= g_strdup_printf("%s", "");
5245 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5246 if (feof(f
) || ferror(f
))
5248 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5255 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5256 if (feof(f
) || ferror(f
)) {
5257 show_oops(t
, "%s: can't parse favorites %s",
5258 __func__
, strerror(errno
));
5263 /* as long as this isn't the one we are deleting add to file */
5266 new_favs
= g_strdup_printf("%s%s\n%s\n",
5267 new_favs
, title
, uri
);
5279 /* write back new favorites file */
5280 if ((f
= fopen(file
, "w")) == NULL
) {
5281 show_oops(t
, "%s: can't open favorites: %s",
5282 __func__
, strerror(errno
));
5286 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5299 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5302 case XT_XTP_FL_LIST
:
5303 /* nothing, just the below call to xtp_page_fl() */
5305 case XT_XTP_FL_REMOVE
:
5306 remove_favorite(t
, arg
);
5309 show_oops(t
, "%s: invalid favorites command", __func__
);
5313 xtp_page_fl(t
, NULL
);
5317 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5320 case XT_XTP_CL_LIST
:
5321 /* nothing, just xtp_page_cl() */
5323 case XT_XTP_CL_REMOVE
:
5327 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5331 xtp_page_cl(t
, NULL
);
5334 /* link an XTP class to it's session key and handler function */
5335 struct xtp_despatch
{
5338 void (*handle_func
)(struct tab
*, uint8_t, int);
5341 struct xtp_despatch xtp_despatches
[] = {
5342 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5343 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5344 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5345 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5346 { NULL
, NULL
, NULL
}
5350 * is the url xtp protocol? (xxxt://)
5351 * if so, parse and despatch correct bahvior
5354 parse_xtp_url(struct tab
*t
, const char *url
)
5356 char *dup
= NULL
, *p
, *last
;
5357 uint8_t n_tokens
= 0;
5358 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5359 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5363 * tokens array meaning:
5365 * tokens[1] = session key
5366 * tokens[2] = action
5367 * tokens[3] = optional argument
5370 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5372 /*xtp tab meaning is normal unless proven special */
5373 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5375 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5378 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5380 /* split out the url */
5381 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5382 (p
= strtok_r(NULL
, "/", &last
))) {
5384 tokens
[n_tokens
++] = p
;
5387 /* should be atleast three fields 'class/seskey/command/arg' */
5391 dsp
= xtp_despatches
;
5392 req_class
= atoi(tokens
[0]);
5393 while (dsp
->xtp_class
!= NULL
) {
5394 if (dsp
->xtp_class
== req_class
) {
5401 /* did we find one atall? */
5402 if (dsp_match
== NULL
) {
5403 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
5407 /* check session key and call despatch function */
5408 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
5409 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
5422 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5424 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
5426 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
5429 show_oops_s("activate_uri_entry_cb invalid parameters");
5434 show_oops(t
, "activate_uri_entry_cb no uri");
5438 uri
+= strspn(uri
, "\t ");
5440 /* if xxxt:// treat specially */
5441 if (!parse_xtp_url(t
, uri
)) {
5442 load_uri(t
, (gchar
*)uri
);
5448 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5450 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
5451 char *newuri
= NULL
;
5454 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
5457 show_oops_s("activate_search_entry_cb invalid parameters");
5461 if (search_string
== NULL
) {
5462 show_oops(t
, "no search_string");
5466 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
5467 newuri
= g_strdup_printf(search_string
, enc_search
);
5470 webkit_web_view_load_uri(t
->wv
, newuri
);
5478 check_and_set_js(const gchar
*uri
, struct tab
*t
)
5480 struct domain
*d
= NULL
;
5483 if (uri
== NULL
|| t
== NULL
)
5486 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5491 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
5492 es
? "enable" : "disable", uri
);
5494 g_object_set(G_OBJECT(t
->settings
),
5495 "enable-scripts", es
, (char *)NULL
);
5496 g_object_set(G_OBJECT(t
->settings
),
5497 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
5498 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5500 button_set_stockid(t
->js_toggle
,
5501 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
5505 show_ca_status(struct tab
*t
, const char *uri
)
5507 WebKitWebFrame
*frame
;
5508 WebKitWebDataSource
*source
;
5509 WebKitNetworkRequest
*request
;
5510 SoupMessage
*message
;
5512 gchar
*col_str
= XT_COLOR_WHITE
;
5515 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
5516 ssl_strict_certs
, ssl_ca_file
, uri
);
5520 if (ssl_ca_file
== NULL
) {
5521 if (g_str_has_prefix(uri
, "http://"))
5523 if (g_str_has_prefix(uri
, "https://")) {
5524 col_str
= XT_COLOR_RED
;
5529 if (g_str_has_prefix(uri
, "http://") ||
5530 !g_str_has_prefix(uri
, "https://"))
5533 frame
= webkit_web_view_get_main_frame(t
->wv
);
5534 source
= webkit_web_frame_get_data_source(frame
);
5535 request
= webkit_web_data_source_get_request(source
);
5536 message
= webkit_network_request_get_message(request
);
5538 if (message
&& (soup_message_get_flags(message
) &
5539 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
5540 col_str
= XT_COLOR_GREEN
;
5543 r
= load_compare_cert(t
, NULL
);
5545 col_str
= XT_COLOR_BLUE
;
5547 col_str
= XT_COLOR_YELLOW
;
5549 col_str
= XT_COLOR_RED
;
5554 gdk_color_parse(col_str
, &color
);
5555 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
5557 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
5558 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5560 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5561 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5564 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5566 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5567 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5574 free_favicon(struct tab
*t
)
5576 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p pix %p\n",
5577 __func__
, t
->icon_download
, t
->icon_request
, t
->icon_pixbuf
);
5579 if (t
->icon_request
)
5580 g_object_unref(t
->icon_request
);
5582 g_object_unref(t
->icon_pixbuf
);
5583 if (t
->icon_dest_uri
)
5584 g_free(t
->icon_dest_uri
);
5586 t
->icon_pixbuf
= NULL
;
5587 t
->icon_request
= NULL
;
5588 t
->icon_dest_uri
= NULL
;
5592 xt_icon_from_name(struct tab
*t
, gchar
*name
)
5594 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5595 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5597 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5598 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5600 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5601 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5605 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pixbuf
)
5607 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
5608 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5610 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
5611 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5613 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5614 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5618 is_valid_icon(char *file
)
5621 const char *mime_type
;
5625 gf
= g_file_new_for_path(file
);
5626 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5628 mime_type
= g_file_info_get_content_type(fi
);
5629 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
5630 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
5631 g_strcmp0(mime_type
, "image/png") == 0 ||
5632 g_strcmp0(mime_type
, "image/gif") == 0 ||
5633 g_strcmp0(mime_type
, "application/octet-stream") == 0;
5641 set_favicon_from_file(struct tab
*t
, char *file
)
5644 GdkPixbuf
*pixbuf
, *scaled
;
5647 if (t
== NULL
|| file
== NULL
)
5649 if (t
->icon_pixbuf
) {
5650 DNPRINTF(XT_D_DOWNLOAD
, "%s: icon already set\n", __func__
);
5654 if (g_str_has_prefix(file
, "file://"))
5655 file
+= strlen("file://");
5656 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
5658 if (!stat(file
, &sb
)) {
5659 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
5660 /* corrupt icon so trash it */
5661 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5664 /* no need to set icon to default here */
5669 pixbuf
= gdk_pixbuf_new_from_file(file
, NULL
);
5670 if (pixbuf
== NULL
) {
5671 xt_icon_from_name(t
, "text-html");
5675 g_object_get(pixbuf
, "width", &width
, "height", &height
,
5677 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d icon size %dx%d\n",
5678 __func__
, t
->tab_id
, width
, height
);
5680 if (width
> 16 || height
> 16) {
5681 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5682 GDK_INTERP_BILINEAR
);
5683 g_object_unref(pixbuf
);
5687 if (scaled
== NULL
) {
5688 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5689 GDK_INTERP_BILINEAR
);
5693 t
->icon_pixbuf
= scaled
;
5694 xt_icon_from_pixbuf(t
, t
->icon_pixbuf
);
5698 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5701 WebKitDownloadStatus status
= webkit_download_get_status(download
);
5702 struct tab
*tt
= NULL
, *t
= NULL
;
5705 * find the webview instead of passing in the tab as it could have been
5706 * deleted from underneath us.
5708 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5717 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
5718 __func__
, t
->tab_id
, status
);
5721 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
5723 t
->icon_download
= NULL
;
5726 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
5729 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
5732 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
5734 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
5735 __func__
, t
->tab_id
);
5736 t
->icon_download
= NULL
;
5739 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
5742 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
5743 __func__
, t
->icon_dest_uri
);
5744 set_favicon_from_file(t
, t
->icon_dest_uri
);
5745 /* these will be freed post callback */
5746 t
->icon_request
= NULL
;
5747 t
->icon_download
= NULL
;
5755 abort_favicon_download(struct tab
*t
)
5757 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
5759 if (t
->icon_download
) {
5760 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
5761 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5762 webkit_download_cancel(t
->icon_download
);
5763 t
->icon_download
= NULL
;
5767 xt_icon_from_name(t
, "text-html");
5771 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
5773 gchar
*name_hash
, file
[PATH_MAX
];
5776 DNPRINTF(XT_D_DOWNLOAD
, "notify_icon_loaded_cb %s\n", uri
);
5778 if (uri
== NULL
|| t
== NULL
)
5781 if (t
->icon_request
) {
5782 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
5786 /* check to see if we got the icon in cache */
5787 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
5788 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
5791 if (!stat(file
, &sb
)) {
5792 if (sb
.st_size
> 0) {
5793 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
5795 set_favicon_from_file(t
, file
);
5799 /* corrupt icon so trash it */
5800 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5805 /* create download for icon */
5806 t
->icon_request
= webkit_network_request_new(uri
);
5807 if (t
->icon_request
== NULL
) {
5808 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
5813 t
->icon_download
= webkit_download_new(t
->icon_request
);
5814 if (t
->icon_download
== NULL
) {
5815 fprintf(stderr
, "%s: icon_download", __func__
);
5819 /* we have to free icon_dest_uri later */
5820 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
5821 webkit_download_set_destination_uri(t
->icon_download
,
5824 if (webkit_download_get_status(t
->icon_download
) ==
5825 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5826 fprintf(stderr
, "%s: download failed to start", __func__
);
5827 g_object_unref(t
->icon_request
);
5828 g_free(t
->icon_dest_uri
);
5829 t
->icon_request
= NULL
;
5830 t
->icon_dest_uri
= NULL
;
5834 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
5835 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5837 webkit_download_start(t
->icon_download
);
5841 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5843 const gchar
*set
= NULL
, *uri
= NULL
, *title
= NULL
;
5844 struct history
*h
, find
;
5845 const gchar
*s_loading
;
5848 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
5849 webkit_web_view_get_load_status(wview
), get_uri(wview
) ? get_uri(wview
) : "NOTHING");
5852 show_oops_s("notify_load_status_cb invalid paramters");
5856 switch (webkit_web_view_get_load_status(wview
)) {
5857 case WEBKIT_LOAD_PROVISIONAL
:
5859 abort_favicon_download(t
);
5860 #if GTK_CHECK_VERSION(2, 20, 0)
5861 gtk_widget_show(t
->spinner
);
5862 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
5864 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
5866 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
5872 case WEBKIT_LOAD_COMMITTED
:
5874 if ((uri
= get_uri(wview
)) != NULL
) {
5875 if (strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
5876 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
5882 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
5885 /* check if js white listing is enabled */
5886 if (enable_js_whitelist
) {
5887 uri
= get_uri(wview
);
5888 check_and_set_js(uri
, t
);
5894 show_ca_status(t
, uri
);
5896 /* we know enough to autosave the session */
5897 if (session_autosave
) {
5903 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
5907 case WEBKIT_LOAD_FINISHED
:
5909 uri
= get_uri(wview
);
5913 if (!strncmp(uri
, "http://", strlen("http://")) ||
5914 !strncmp(uri
, "https://", strlen("https://")) ||
5915 !strncmp(uri
, "file://", strlen("file://"))) {
5917 h
= RB_FIND(history_list
, &hl
, &find
);
5919 title
= webkit_web_view_get_title(wview
);
5920 set
= title
? title
: uri
;
5921 h
= g_malloc(sizeof *h
);
5922 h
->uri
= g_strdup(uri
);
5923 h
->title
= g_strdup(set
);
5924 RB_INSERT(history_list
, &hl
, h
);
5925 completion_add_uri(h
->uri
);
5926 update_history_tabs(NULL
);
5930 set_status(t
, (char *)uri
, XT_STATUS_URI
);
5931 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5932 case WEBKIT_LOAD_FAILED
:
5935 #if GTK_CHECK_VERSION(2, 20, 0)
5936 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5937 gtk_widget_hide(t
->spinner
);
5939 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
5940 if (s_loading
&& !strcmp(s_loading
, "Loading"))
5941 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5943 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
5948 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
5950 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
5951 webkit_web_view_can_go_back(wview
));
5953 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
5954 webkit_web_view_can_go_forward(wview
));
5956 /* take focus if we are visible */
5961 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5963 const gchar
*set
= NULL
, *title
= NULL
;
5965 title
= webkit_web_view_get_title(wview
);
5966 set
= title
? title
: get_uri(wview
);
5967 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
5968 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
5972 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5974 run_script(t
, JS_HINTING
);
5978 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
5980 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
5981 progress
== 100 ? 0 : (double)progress
/ 100);
5982 if (show_url
== 0) {
5983 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
5984 progress
== 100 ? 0 : (double)progress
/ 100);
5989 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
5990 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
5991 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
5994 WebKitWebNavigationReason reason
;
5995 struct domain
*d
= NULL
;
5998 show_oops_s("webview_npd_cb invalid parameters");
6002 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
6004 webkit_network_request_get_uri(request
));
6006 uri
= (char *)webkit_network_request_get_uri(request
);
6008 if ((!parse_xtp_url(t
, uri
) && (t
->ctrl_click
))) {
6010 create_new_tab(uri
, NULL
, ctrl_click_focus
);
6011 webkit_web_policy_decision_ignore(pd
);
6012 return (TRUE
); /* we made the decission */
6016 * This is a little hairy but it comes down to this:
6017 * when we run in whitelist mode we have to assist the browser in
6018 * opening the URL that it would have opened in a new tab.
6020 reason
= webkit_web_navigation_action_get_reason(na
);
6021 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6022 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6023 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6026 webkit_web_policy_decision_use(pd
);
6027 return (TRUE
); /* we made the decission */
6034 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6037 struct domain
*d
= NULL
;
6039 WebKitWebView
*webview
= NULL
;
6041 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6042 webkit_web_view_get_uri(wv
));
6044 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6045 uri
= webkit_web_view_get_uri(wv
);
6046 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6049 tt
= create_new_tab(NULL
, NULL
, 1);
6051 } else if (enable_scripts
== 1) {
6052 tt
= create_new_tab(NULL
, NULL
, 1);
6060 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6063 struct domain
*d
= NULL
;
6065 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6067 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6068 uri
= webkit_web_view_get_uri(wv
);
6069 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6073 } else if (enable_scripts
== 1)
6080 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6082 /* we can not eat the event without throwing gtk off so defer it */
6084 /* catch middle click */
6085 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6090 /* catch ctrl click */
6091 if (e
->type
== GDK_BUTTON_RELEASE
&&
6092 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6097 return (XT_CB_PASSTHROUGH
);
6101 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6103 struct mime_type
*m
;
6105 m
= find_mime_type(mime_type
);
6113 show_oops(t
, "can't fork mime handler");
6122 execlp(m
->mt_action
, m
->mt_action
,
6123 webkit_network_request_get_uri(request
), (void *)NULL
);
6132 get_mime_type(char *file
)
6134 const char *mime_type
;
6138 if (g_str_has_prefix(file
, "file://"))
6139 file
+= strlen("file://");
6141 gf
= g_file_new_for_path(file
);
6142 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6144 mime_type
= g_file_info_get_content_type(fi
);
6152 run_download_mimehandler(char *mime_type
, char *file
)
6154 struct mime_type
*m
;
6156 m
= find_mime_type(mime_type
);
6162 show_oops_s("can't fork download mime handler");
6171 if (g_str_has_prefix(file
, "file://"))
6172 file
+= strlen("file://");
6173 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6182 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6185 WebKitDownloadStatus status
;
6186 const gchar
*file
= NULL
, *mime
= NULL
;
6188 if (download
== NULL
)
6190 status
= webkit_download_get_status(download
);
6191 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6194 file
= webkit_download_get_destination_uri(download
);
6197 mime
= get_mime_type((char *)file
);
6201 run_download_mimehandler((char *)mime
, (char *)file
);
6205 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6206 WebKitNetworkRequest
*request
, char *mime_type
,
6207 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6210 show_oops_s("webview_mimetype_cb invalid parameters");
6214 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6215 t
->tab_id
, mime_type
);
6217 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6218 webkit_web_policy_decision_ignore(decision
);
6223 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6224 webkit_web_policy_decision_download(decision
);
6232 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6235 const gchar
*filename
;
6237 struct download
*download_entry
;
6240 if (wk_download
== NULL
|| t
== NULL
) {
6241 show_oops_s("%s invalid parameters", __func__
);
6245 filename
= webkit_download_get_suggested_filename(wk_download
);
6246 if (filename
== NULL
)
6247 return (FALSE
); /* abort download */
6249 uri
= g_strdup_printf("file://%s/%s", download_dir
, filename
);
6251 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6252 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6254 webkit_download_set_destination_uri(wk_download
, uri
);
6256 if (webkit_download_get_status(wk_download
) ==
6257 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6258 show_oops(t
, "%s: download failed to start", __func__
);
6260 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6262 /* connect "download first" mime handler */
6263 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6264 G_CALLBACK(download_status_changed_cb
), NULL
);
6266 download_entry
= g_malloc(sizeof(struct download
));
6267 download_entry
->download
= wk_download
;
6268 download_entry
->tab
= t
;
6269 download_entry
->id
= next_download_id
++;
6270 RB_INSERT(download_list
, &downloads
, download_entry
);
6271 /* get from history */
6272 g_object_ref(wk_download
);
6273 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6274 show_oops(t
, "Download of '%s' started...",
6275 basename(webkit_download_get_destination_uri(wk_download
)));
6281 /* sync other download manager tabs */
6282 update_download_tabs(NULL
);
6285 * NOTE: never redirect/render the current tab before this
6286 * function returns. This will cause the download to never start.
6288 return (ret
); /* start download */
6292 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6294 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6297 show_oops_s("webview_hover_cb");
6302 set_status(t
, uri
, XT_STATUS_LINK
);
6305 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6310 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6312 struct key_binding
*k
;
6314 TAILQ_FOREACH(k
, &kbl
, entry
)
6315 if (e
->keyval
== k
->key
&& (entry
? k
->use_in_entry
: 1)) {
6317 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6318 return (cmd_execute(t
, k
->cmd
));
6319 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6320 return (cmd_execute(t
, k
->cmd
));
6324 return (XT_CB_PASSTHROUGH
);
6328 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6330 char s
[2], buf
[128];
6331 const char *errstr
= NULL
;
6334 /* don't use w directly; use t->whatever instead */
6337 show_oops_s("wv_keypress_after_cb");
6338 return (XT_CB_PASSTHROUGH
);
6341 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6342 e
->keyval
, e
->state
, t
);
6346 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6348 return (XT_CB_HANDLED
);
6352 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6353 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6355 /* we have a string */
6357 /* we have a number */
6358 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6366 /* XXX unfuck this */
6367 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6368 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6369 /* last input was numerical */
6371 l
= strlen(t
->hint_num
);
6378 t
->hint_num
[l
] = '\0';
6382 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6383 /* last input was alphanumerical */
6385 l
= strlen(t
->hint_buf
);
6392 t
->hint_buf
[l
] = '\0';
6402 /* numerical input */
6403 if (CLEAN(e
->state
) == 0 &&
6404 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6405 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6406 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6407 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6410 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6412 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6415 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6417 t
->hint_mode
= XT_HINT_NUMERICAL
;
6421 /* empty the counter buffer */
6422 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6423 return (XT_CB_HANDLED
);
6426 /* alphanumerical input */
6428 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6429 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6430 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6431 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6432 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6433 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6434 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6437 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6440 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6442 t
->hint_mode
= XT_HINT_ALPHANUM
;
6445 /* empty the counter buffer */
6446 bzero(t
->hint_num
, sizeof t
->hint_num
);
6447 return (XT_CB_HANDLED
);
6450 return (XT_CB_HANDLED
);
6453 return (handle_keypress(t
, e
, 0));
6457 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6461 return (XT_CB_PASSTHROUGH
);
6465 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6467 const gchar
*c
= gtk_entry_get_text(w
);
6471 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6472 e
->keyval
, e
->state
, t
);
6475 show_oops_s("cmd_keyrelease_cb invalid parameters");
6476 return (XT_CB_PASSTHROUGH
);
6479 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6480 e
->keyval
, e
->state
, t
);
6484 if (strlen(c
) == 1) {
6485 webkit_web_view_unmark_text_matches(t
->wv
);
6491 else if (c
[0] == '?')
6497 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6499 /* not found, mark red */
6500 gdk_color_parse(XT_COLOR_RED
, &color
);
6501 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6502 /* unmark and remove selection */
6503 webkit_web_view_unmark_text_matches(t
->wv
);
6504 /* my kingdom for a way to unselect text in webview */
6506 /* found, highlight all */
6507 webkit_web_view_unmark_text_matches(t
->wv
);
6508 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6509 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6510 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6511 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6514 return (XT_CB_PASSTHROUGH
);
6518 match_uri(const gchar
*uri
, const gchar
*key
) {
6521 gboolean match
= FALSE
;
6525 if (!strncmp(key
, uri
, len
))
6528 voffset
= strstr(uri
, "/") + 2;
6529 if (!strncmp(key
, voffset
, len
))
6531 else if (g_str_has_prefix(voffset
, "www.")) {
6532 voffset
= voffset
+ strlen("www.");
6533 if (!strncmp(key
, voffset
, len
))
6542 cmd_getlist(int id
, char *key
)
6547 if (id
>= 0 && (cmds
[id
].userarg
&& (cmds
[id
].arg
.i
== XT_TAB_OPEN
|| cmds
[id
].arg
.i
== XT_TAB_NEW
))) {
6548 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6549 if (match_uri(h
->uri
, key
)) {
6550 cmd_status
.list
[c
] = (char *)h
->uri
;
6559 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6561 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6562 if(cmds
[i
].level
< dep
)
6564 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6565 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6573 cmd_getnext(int dir
)
6575 cmd_status
.index
+= dir
;
6577 if (cmd_status
.index
<0)
6578 cmd_status
.index
= cmd_status
.len
-1;
6579 else if (cmd_status
.index
>= cmd_status
.len
)
6580 cmd_status
.index
= 0;
6582 return cmd_status
.list
[cmd_status
.index
];
6586 cmd_tokenize(char *s
, char *tokens
[])
6590 size_t len
= strlen(s
);
6591 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6593 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6603 cmd_complete(struct tab
*t
, char *str
, int dir
)
6605 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6606 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1;
6607 char *tok
, *match
, *s
= strdup(str
);
6609 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6611 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: complete %s\n", str
);
6613 levels
= cmd_tokenize(s
, tokens
);
6615 for (i
= 0; i
< levels
- 1; i
++) {
6617 for (j
= c
; j
< LENGTH(cmds
); j
++)
6618 if (cmds
[j
].level
== dep
&& !strcmp(tok
, cmds
[j
].cmd
)) {
6619 strlcat(res
, tok
, sizeof res
);
6620 strlcat(res
, " ", sizeof res
);
6629 if (cmd_status
.index
== -1)
6630 cmd_getlist(parent
, tokens
[i
]);
6632 if (cmd_status
.len
> 0) {
6633 match
= cmd_getnext(dir
);
6634 strlcat(res
, match
, sizeof res
);
6635 gtk_entry_set_text(w
, res
);
6636 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6641 cmd_valid(char *str
)
6643 char *tok
, *last
, *s
= g_strdup(str
);
6644 int i
, c
= 0, dep
= 0;
6646 for (tok
= strtok_r(s
, " ", &last
); tok
;
6647 tok
= strtok_r(NULL
, " ", &last
)) {
6648 for (i
= c
; i
< LENGTH(cmds
); i
++) {
6649 if (cmds
[i
].level
< dep
) {
6650 return (XT_CB_PASSTHROUGH
);
6652 if (cmds
[i
].level
== dep
&& !strcmp(tok
, cmds
[i
].cmd
)) {
6653 if (cmds
[i
].userarg
) {
6654 return (XT_CB_HANDLED
);
6661 if (i
== LENGTH(cmds
)) {
6662 return (XT_CB_PASSTHROUGH
);
6665 return (XT_CB_HANDLED
);
6669 cmd_execute(struct tab
*t
, char *str
)
6671 struct cmd
*cmd
= NULL
;
6672 char *tok
, *last
, *s
= g_strdup(str
);
6673 int j
, c
= 0, dep
= 0;
6675 for (tok
= strtok_r(s
, " ", &last
); tok
;
6676 tok
= strtok_r(NULL
, " ", &last
)) {
6677 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6678 if (cmds
[j
].level
< dep
) {
6679 show_oops(t
, "Invalid command: %s", str
);
6680 return (XT_CB_PASSTHROUGH
);
6682 if (cmds
[j
].level
== dep
&& !strcmp(tok
, cmds
[j
].cmd
)) {
6685 cmd
->arg
.s
= last
? g_strdup(last
) : g_strdup("");
6693 if (j
== LENGTH(cmds
)) {
6694 show_oops(t
, "Invalid command: %s", str
);
6695 return (XT_CB_PASSTHROUGH
);
6698 cmd
->arg
.s
= g_strdup(tok
);
6700 /* arg->s contains last token */
6701 cmd
->func(t
, &cmd
->arg
);
6705 return (XT_CB_HANDLED
);
6709 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6712 show_oops_s("entry_key_cb invalid parameters");
6713 return (XT_CB_PASSTHROUGH
);
6716 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6717 e
->keyval
, e
->state
, t
);
6721 if (e
->keyval
== GDK_Escape
) {
6722 /* don't use focus_webview(t) because we want to type :cmds */
6723 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6726 return (handle_keypress(t
, e
, 1));
6730 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6732 int rv
= XT_CB_HANDLED
;
6733 const gchar
*c
= gtk_entry_get_text(w
);
6736 show_oops_s("cmd_keypress_cb parameters");
6737 return (XT_CB_PASSTHROUGH
);
6740 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6741 e
->keyval
, e
->state
, t
);
6745 e
->keyval
= GDK_Escape
;
6746 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6747 e
->keyval
= GDK_Escape
;
6749 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
6750 cmd_status
.index
= -1;
6752 switch (e
->keyval
) {
6755 cmd_complete(t
, (char *)&c
[1], 1);
6757 case GDK_ISO_Left_Tab
:
6759 cmd_complete(t
, (char *)&c
[1], -1);
6763 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
6771 if (c
[0] == '/' || c
[0] == '?')
6772 webkit_web_view_unmark_text_matches(t
->wv
);
6776 rv
= XT_CB_PASSTHROUGH
;
6782 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6785 show_oops_s("cmd_focusout_cb invalid parameters");
6786 return (XT_CB_PASSTHROUGH
);
6788 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
6793 if (show_url
== 0 || t
->focus_wv
)
6796 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6798 return (XT_CB_PASSTHROUGH
);
6802 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6805 const gchar
*c
= gtk_entry_get_text(entry
);
6808 show_oops_s("cmd_activate_cb invalid parameters");
6812 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6817 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6823 if (c
[0] == '/' || c
[0] == '?') {
6824 if (t
->search_text
) {
6825 g_free(t
->search_text
);
6826 t
->search_text
= NULL
;
6829 t
->search_text
= g_strdup(s
);
6831 g_free(global_search
);
6832 global_search
= g_strdup(s
);
6833 t
->search_forward
= c
[0] == '/';
6845 backward_cb(GtkWidget
*w
, struct tab
*t
)
6850 show_oops_s("backward_cb invalid parameters");
6854 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6861 forward_cb(GtkWidget
*w
, struct tab
*t
)
6866 show_oops_s("forward_cb invalid parameters");
6870 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6872 a
.i
= XT_NAV_FORWARD
;
6877 home_cb(GtkWidget
*w
, struct tab
*t
)
6880 show_oops_s("home_cb invalid parameters");
6884 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6890 stop_cb(GtkWidget
*w
, struct tab
*t
)
6892 WebKitWebFrame
*frame
;
6895 show_oops_s("stop_cb invalid parameters");
6899 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
6901 frame
= webkit_web_view_get_main_frame(t
->wv
);
6902 if (frame
== NULL
) {
6903 show_oops(t
, "stop_cb: no frame");
6907 webkit_web_frame_stop_loading(frame
);
6908 abort_favicon_download(t
);
6912 setup_webkit(struct tab
*t
)
6914 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
6915 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
6916 FALSE
, (char *)NULL
);
6918 warnx("webkit does not have \"enable-dns-prefetching\" property");
6919 g_object_set(G_OBJECT(t
->settings
),
6920 "user-agent", t
->user_agent
, (char *)NULL
);
6921 g_object_set(G_OBJECT(t
->settings
),
6922 "enable-scripts", enable_scripts
, (char *)NULL
);
6923 g_object_set(G_OBJECT(t
->settings
),
6924 "enable-plugins", enable_plugins
, (char *)NULL
);
6925 g_object_set(G_OBJECT(t
->settings
),
6926 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
6927 g_object_set(G_OBJECT(t
->wv
),
6928 "full-content-zoom", TRUE
, (char *)NULL
);
6929 adjustfont_webkit(t
, XT_FONT_SET
);
6931 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6935 create_browser(struct tab
*t
)
6941 show_oops_s("create_browser invalid parameters");
6945 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
6946 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
6947 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
6948 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
6950 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
6951 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
6952 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
6954 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
6955 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
6958 t
->settings
= webkit_web_settings_new();
6960 if (user_agent
== NULL
) {
6961 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
6963 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
6966 t
->user_agent
= g_strdup(user_agent
);
6968 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
6980 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
6981 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
6982 gtk_widget_set_name(w
, "xxxterm");
6983 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
6984 g_signal_connect(G_OBJECT(w
), "delete_event",
6985 G_CALLBACK (gtk_main_quit
), NULL
);
6991 create_kiosk_toolbar(struct tab
*t
)
6993 GtkWidget
*toolbar
= NULL
, *b
;
6995 b
= gtk_hbox_new(FALSE
, 0);
6997 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
6999 /* backward button */
7000 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7001 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7002 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7003 G_CALLBACK(backward_cb
), t
);
7004 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7006 /* forward button */
7007 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7008 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7009 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7010 G_CALLBACK(forward_cb
), t
);
7011 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7014 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7015 gtk_widget_set_sensitive(t
->gohome
, true);
7016 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7017 G_CALLBACK(home_cb
), t
);
7018 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7020 /* create widgets but don't use them */
7021 t
->uri_entry
= gtk_entry_new();
7022 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7023 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7024 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7030 create_toolbar(struct tab
*t
)
7032 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7034 b
= gtk_hbox_new(FALSE
, 0);
7036 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7039 /* backward button */
7040 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7041 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7042 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7043 G_CALLBACK(backward_cb
), t
);
7044 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7046 /* forward button */
7047 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7048 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7049 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7050 G_CALLBACK(forward_cb
), t
);
7051 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7055 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7056 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7057 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7058 G_CALLBACK(stop_cb
), t
);
7059 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7063 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7064 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7065 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7066 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7067 G_CALLBACK(js_toggle_cb
), t
);
7068 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7071 t
->uri_entry
= gtk_entry_new();
7072 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7073 G_CALLBACK(activate_uri_entry_cb
), t
);
7074 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7075 G_CALLBACK(entry_key_cb
), t
);
7077 eb1
= gtk_hbox_new(FALSE
, 0);
7078 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7079 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7080 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7083 if (fancy_bar
&& search_string
) {
7085 t
->search_entry
= gtk_entry_new();
7086 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7087 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7088 G_CALLBACK(activate_search_entry_cb
), t
);
7089 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7090 G_CALLBACK(entry_key_cb
), t
);
7091 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7092 eb2
= gtk_hbox_new(FALSE
, 0);
7093 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7094 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7096 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7106 TAILQ_FOREACH(t
, &tabs
, entry
)
7107 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7111 undo_close_tab_save(struct tab
*t
)
7115 struct undo
*u1
, *u2
;
7117 WebKitWebHistoryItem
*item
;
7119 if ((uri
= get_uri(t
->wv
)) == NULL
)
7122 u1
= g_malloc0(sizeof(struct undo
));
7123 u1
->uri
= g_strdup(uri
);
7125 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7127 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7128 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7131 /* forward history */
7132 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7136 u1
->history
= g_list_prepend(u1
->history
,
7137 webkit_web_history_item_copy(item
));
7138 items
= g_list_next(items
);
7143 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7144 u1
->history
= g_list_prepend(u1
->history
,
7145 webkit_web_history_item_copy(item
));
7149 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7153 u1
->history
= g_list_prepend(u1
->history
,
7154 webkit_web_history_item_copy(item
));
7155 items
= g_list_next(items
);
7158 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7160 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7161 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7162 TAILQ_REMOVE(&undos
, u2
, entry
);
7164 g_list_free(u2
->history
);
7173 delete_tab(struct tab
*t
)
7177 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7182 TAILQ_REMOVE(&tabs
, t
, entry
);
7184 /* halt all webkit activity */
7185 abort_favicon_download(t
);
7186 webkit_web_view_stop_loading(t
->wv
);
7187 undo_close_tab_save(t
);
7189 if (browser_mode
== XT_BM_KIOSK
) {
7190 gtk_widget_destroy(t
->uri_entry
);
7191 gtk_widget_destroy(t
->stop
);
7192 gtk_widget_destroy(t
->js_toggle
);
7196 gtk_widget_destroy(t
->vbox
);
7197 g_free(t
->user_agent
);
7198 g_free(t
->stylesheet
);
7202 if (TAILQ_EMPTY(&tabs
)) {
7203 if (browser_mode
== XT_BM_KIOSK
)
7204 create_new_tab(home
, NULL
, 1);
7206 create_new_tab(NULL
, NULL
, 1);
7209 /* recreate session */
7210 if (session_autosave
) {
7217 adjustfont_webkit(struct tab
*t
, int adjust
)
7222 show_oops_s("adjustfont_webkit invalid parameters");
7226 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7227 if (adjust
== XT_FONT_SET
) {
7228 t
->font_size
= default_font_size
;
7229 zoom
= default_zoom_level
;
7230 t
->font_size
+= adjust
;
7231 g_object_set(G_OBJECT(t
->settings
), "default-font-size",
7232 t
->font_size
, (char *)NULL
);
7233 g_object_get(G_OBJECT(t
->settings
), "default-font-size",
7234 &t
->font_size
, (char *)NULL
);
7236 t
->font_size
+= adjust
;
7237 zoom
+= adjust
/25.0;
7242 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7243 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7247 append_tab(struct tab
*t
)
7252 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7253 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7257 create_new_tab(char *title
, struct undo
*u
, int focus
)
7260 int load
= 1, id
, notfound
;
7262 WebKitWebHistoryItem
*item
;
7266 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7268 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7269 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7273 t
= g_malloc0(sizeof *t
);
7275 if (title
== NULL
) {
7276 title
= "(untitled)";
7280 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7282 /* label + button for tab */
7283 b
= gtk_hbox_new(FALSE
, 0);
7286 #if GTK_CHECK_VERSION(2, 20, 0)
7287 t
->spinner
= gtk_spinner_new ();
7289 t
->label
= gtk_label_new(title
);
7290 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7291 gtk_widget_set_size_request(t
->label
, 100, 0);
7292 gtk_widget_set_size_request(b
, 130, 0);
7294 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7295 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7296 #if GTK_CHECK_VERSION(2, 20, 0)
7297 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7301 if (browser_mode
== XT_BM_KIOSK
)
7302 t
->toolbar
= create_kiosk_toolbar(t
);
7304 t
->toolbar
= create_toolbar(t
);
7306 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7309 t
->browser_win
= create_browser(t
);
7310 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7312 /* oops message for user feedback */
7313 t
->oops
= gtk_entry_new();
7314 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7315 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7316 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7317 gdk_color_parse(XT_COLOR_RED
, &color
);
7318 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7319 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7322 t
->cmd
= gtk_entry_new();
7323 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7324 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7325 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7328 t
->statusbar
= gtk_entry_new();
7329 gtk_entry_set_inner_border(GTK_ENTRY(t
->statusbar
), NULL
);
7330 gtk_entry_set_has_frame(GTK_ENTRY(t
->statusbar
), FALSE
);
7331 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7332 gdk_color_parse(XT_COLOR_BLACK
, &color
);
7333 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7334 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7335 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7336 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7338 /* xtp meaning is normal by default */
7339 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
7341 /* set empty favicon */
7342 xt_icon_from_name(t
, "text-html");
7344 /* and show it all */
7345 gtk_widget_show_all(b
);
7346 gtk_widget_show_all(t
->vbox
);
7348 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7352 id
= gtk_notebook_get_current_page(notebook
);
7353 TAILQ_FOREACH(tt
, &tabs
, entry
) {
7354 if (tt
->tab_id
== id
) {
7356 TAILQ_INSERT_AFTER(&tabs
, tt
, t
, entry
);
7357 gtk_notebook_insert_page(notebook
, t
->vbox
, b
,
7367 #if GTK_CHECK_VERSION(2, 20, 0)
7368 /* turn spinner off if we are a new tab without uri */
7370 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7371 gtk_widget_hide(t
->spinner
);
7374 /* make notebook tabs reorderable */
7375 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7377 g_object_connect(G_OBJECT(t
->cmd
),
7378 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7379 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7380 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7381 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7384 /* reuse wv_button_cb to hide oops */
7385 g_object_connect(G_OBJECT(t
->oops
),
7386 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7389 g_object_connect(G_OBJECT(t
->wv
),
7390 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7391 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7392 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7393 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7394 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7395 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7396 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7397 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7398 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7399 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7400 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
7401 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
7402 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7403 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7405 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7407 g_signal_connect(t
->wv
,
7408 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7409 g_signal_connect(t
->wv
,
7410 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7412 /* hijack the unused keys as if we were the browser */
7413 g_object_connect(G_OBJECT(t
->toolbar
),
7414 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7417 g_signal_connect(G_OBJECT(bb
), "button_press_event",
7418 G_CALLBACK(tab_close_cb
), t
);
7423 url_set_visibility();
7424 statusbar_set_visibility();
7427 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7428 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
7433 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
7437 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7442 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7443 /* restore the tab's history */
7444 if (u
&& u
->history
) {
7448 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
7449 items
= g_list_next(items
);
7452 item
= g_list_nth_data(u
->history
, u
->back
);
7454 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
7457 g_list_free(u
->history
);
7459 webkit_web_back_forward_list_clear(t
->bfl
);
7465 notebook_switchpage_cb(GtkNotebook
*nb
, GtkNotebookPage
*nbp
, guint pn
,
7471 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
7473 TAILQ_FOREACH(t
, &tabs
, entry
) {
7474 if (t
->tab_id
== pn
) {
7475 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
7478 uri
= webkit_web_view_get_title(t
->wv
);
7481 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
7493 menuitem_response(struct tab
*t
)
7495 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7499 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
7501 GtkWidget
*menu
, *menu_items
;
7502 GdkEventButton
*bevent
;
7506 if (event
->type
== GDK_BUTTON_PRESS
) {
7507 bevent
= (GdkEventButton
*) event
;
7508 menu
= gtk_menu_new();
7510 TAILQ_FOREACH(ti
, &tabs
, entry
) {
7511 if ((uri
= get_uri(ti
->wv
)) == NULL
)
7512 /* XXX make sure there is something to print */
7513 /* XXX add gui pages in here to look purdy */
7515 menu_items
= gtk_menu_item_new_with_label(uri
);
7516 gtk_menu_append(GTK_MENU (menu
), menu_items
);
7517 gtk_widget_show(menu_items
);
7519 gtk_signal_connect_object(GTK_OBJECT(menu_items
),
7520 "activate", GTK_SIGNAL_FUNC(menuitem_response
),
7524 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
7525 bevent
->button
, bevent
->time
);
7527 /* unref object so it'll free itself when popped down */
7528 g_object_ref_sink(menu
);
7529 g_object_unref(menu
);
7531 return (TRUE
/* eat event */);
7534 return (FALSE
/* propagate */);
7538 icon_size_map(int icon_size
)
7540 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
7541 icon_size
> GTK_ICON_SIZE_DIALOG
)
7542 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
7548 create_button(char *name
, char *stockid
, int size
)
7550 GtkWidget
*button
, *image
;
7554 rcstring
= g_strdup_printf(
7555 "style \"%s-style\"\n"
7557 " GtkWidget::focus-padding = 0\n"
7558 " GtkWidget::focus-line-width = 0\n"
7562 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
7563 gtk_rc_parse_string(rcstring
);
7565 button
= gtk_button_new();
7566 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
7567 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
7569 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
7570 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7571 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
7572 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
7573 gtk_widget_set_name(button
, name
);
7574 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
7580 button_set_stockid(GtkWidget
*button
, char *stockid
)
7584 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
7585 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7586 gtk_button_set_image(GTK_BUTTON(button
), image
);
7595 char file
[PATH_MAX
];
7598 vbox
= gtk_vbox_new(FALSE
, 0);
7599 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
7600 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
7601 gtk_notebook_set_tab_hborder(notebook
, 0);
7602 gtk_notebook_set_tab_vborder(notebook
, 0);
7603 gtk_notebook_set_scrollable(notebook
, TRUE
);
7604 notebook_tab_set_visibility(notebook
);
7605 gtk_notebook_set_show_border(notebook
, FALSE
);
7606 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
7608 abtn
= gtk_button_new();
7609 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
7610 gtk_widget_set_size_request(arrow
, -1, -1);
7611 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
7612 gtk_widget_set_size_request(abtn
, -1, 20);
7613 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
7615 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
7616 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
7617 gtk_widget_set_size_request(vbox
, -1, -1);
7619 g_object_connect(G_OBJECT(notebook
),
7620 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
7622 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
7623 G_CALLBACK(arrow_cb
), NULL
);
7625 main_window
= create_window();
7626 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
7627 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
7630 for (i
= 0; i
< LENGTH(icons
); i
++) {
7631 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
7632 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
7633 l
= g_list_append(l
, pb
);
7635 gtk_window_set_default_icon_list(l
);
7637 gtk_widget_show_all(abtn
);
7638 gtk_widget_show_all(main_window
);
7642 set_hook(void **hook
, char *name
)
7645 errx(1, "set_hook");
7647 if (*hook
== NULL
) {
7648 *hook
= dlsym(RTLD_NEXT
, name
);
7650 errx(1, "can't hook %s", name
);
7654 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7656 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
7658 g_return_val_if_fail(cookie1
, FALSE
);
7659 g_return_val_if_fail(cookie2
, FALSE
);
7661 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
7662 !strcmp (cookie1
->value
, cookie2
->value
) &&
7663 !strcmp (cookie1
->path
, cookie2
->path
) &&
7664 !strcmp (cookie1
->domain
, cookie2
->domain
));
7668 transfer_cookies(void)
7671 SoupCookie
*sc
, *pc
;
7673 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7675 for (;cf
; cf
= cf
->next
) {
7677 sc
= soup_cookie_copy(pc
);
7678 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
7681 soup_cookies_free(cf
);
7685 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
7690 print_cookie("soup_cookie_jar_delete_cookie", c
);
7692 if (cookies_enabled
== 0)
7695 if (jar
== NULL
|| c
== NULL
)
7698 /* find and remove from persistent jar */
7699 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7701 for (;cf
; cf
= cf
->next
) {
7703 if (soup_cookie_equal(ci
, c
)) {
7704 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
7709 soup_cookies_free(cf
);
7711 /* delete from session jar */
7712 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
7716 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
7718 struct domain
*d
= NULL
;
7722 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
7723 jar
, p_cookiejar
, s_cookiejar
);
7725 if (cookies_enabled
== 0)
7728 /* see if we are up and running */
7729 if (p_cookiejar
== NULL
) {
7730 _soup_cookie_jar_add_cookie(jar
, cookie
);
7733 /* disallow p_cookiejar adds, shouldn't happen */
7734 if (jar
== p_cookiejar
)
7738 if (jar
== NULL
|| cookie
== NULL
)
7741 if (enable_cookie_whitelist
&&
7742 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
7744 DNPRINTF(XT_D_COOKIE
,
7745 "soup_cookie_jar_add_cookie: reject %s\n",
7747 if (save_rejected_cookies
) {
7748 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
7749 show_oops_s("can't open reject cookie file");
7752 fseek(r_cookie_f
, 0, SEEK_END
);
7753 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7754 cookie
->http_only
? "#HttpOnly_" : "",
7756 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
7758 cookie
->secure
? "TRUE" : "FALSE",
7760 (gulong
)soup_date_to_time_t(cookie
->expires
) :
7767 if (!allow_volatile_cookies
)
7771 if (cookie
->expires
== NULL
&& session_timeout
) {
7772 soup_cookie_set_expires(cookie
,
7773 soup_date_new_from_now(session_timeout
));
7774 print_cookie("modified add cookie", cookie
);
7777 /* see if we are white listed for persistence */
7778 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
7779 /* add to persistent jar */
7780 c
= soup_cookie_copy(cookie
);
7781 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
7782 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
7785 /* add to session jar */
7786 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
7787 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
7793 char file
[PATH_MAX
];
7795 set_hook((void *)&_soup_cookie_jar_add_cookie
,
7796 "soup_cookie_jar_add_cookie");
7797 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
7798 "soup_cookie_jar_delete_cookie");
7800 if (cookies_enabled
== 0)
7804 * the following code is intricate due to overriding several libsoup
7806 * do not alter order of these operations.
7809 /* rejected cookies */
7810 if (save_rejected_cookies
)
7811 snprintf(rc_fname
, sizeof file
, "%s/rejected.txt", work_dir
);
7813 /* persistent cookies */
7814 snprintf(file
, sizeof file
, "%s/cookies.txt", work_dir
);
7815 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
7817 /* session cookies */
7818 s_cookiejar
= soup_cookie_jar_new();
7819 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
7820 cookie_policy
, (void *)NULL
);
7823 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
7827 setup_proxy(char *uri
)
7830 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
7831 soup_uri_free(proxy_uri
);
7835 if (http_proxy
!= uri
) {
7842 http_proxy
= g_strdup(uri
);
7843 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
7844 proxy_uri
= soup_uri_new(http_proxy
);
7845 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
7850 send_cmd_to_socket(char *cmd
)
7853 struct sockaddr_un sa
;
7855 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7856 warnx("%s: socket", __func__
);
7860 sa
.sun_family
= AF_UNIX
;
7861 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7862 work_dir
, XT_SOCKET_FILE
);
7865 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7866 warnx("%s: connect", __func__
);
7870 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
7871 warnx("%s: send", __func__
);
7882 socket_watcher(gpointer data
, gint fd
, GdkInputCondition cond
)
7885 char str
[XT_MAX_URL_LENGTH
];
7886 socklen_t t
= sizeof(struct sockaddr_un
);
7887 struct sockaddr_un sa
;
7893 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
7898 if (getpeereid(s
, &uid
, &gid
) == -1) {
7902 if (uid
!= getuid() || gid
!= getgid()) {
7903 warnx("unauthorized user");
7909 warnx("not a valid user");
7913 n
= recv(s
, str
, sizeof(str
), 0);
7917 tt
= TAILQ_LAST(&tabs
, tab_list
);
7918 cmd_execute(tt
, str
);
7925 struct sockaddr_un sa
;
7927 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7928 warn("is_running: socket");
7932 sa
.sun_family
= AF_UNIX
;
7933 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7934 work_dir
, XT_SOCKET_FILE
);
7937 /* connect to see if there is a listener */
7938 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
7939 rv
= 0; /* not running */
7941 rv
= 1; /* already running */
7952 struct sockaddr_un sa
;
7954 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7955 warn("build_socket: socket");
7959 sa
.sun_family
= AF_UNIX
;
7960 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7961 work_dir
, XT_SOCKET_FILE
);
7964 /* connect to see if there is a listener */
7965 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7966 /* no listener so we will */
7967 unlink(sa
.sun_path
);
7969 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7970 warn("build_socket: bind");
7974 if (listen(s
, 1) == -1) {
7975 warn("build_socket: listen");
7988 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
7989 GtkTreeIter
*iter
, struct tab
*t
)
7993 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8000 completion_add_uri(const gchar
*uri
)
8004 /* add uri to list_store */
8005 gtk_list_store_append(completion_model
, &iter
);
8006 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
8010 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
8011 GtkTreeIter
*iter
, gpointer user_data
)
8014 gboolean match
= FALSE
;
8016 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8022 match
= match_uri(value
, key
);
8029 completion_add(struct tab
*t
)
8031 /* enable completion for tab */
8032 t
->completion
= gtk_entry_completion_new();
8033 gtk_entry_completion_set_text_column(t
->completion
, 0);
8034 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8035 gtk_entry_completion_set_model(t
->completion
,
8036 GTK_TREE_MODEL(completion_model
));
8037 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8039 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8040 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8041 G_CALLBACK(completion_select_cb
), t
);
8048 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8053 main(int argc
, char *argv
[])
8056 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8057 char conf
[PATH_MAX
] = { '\0' };
8058 char file
[PATH_MAX
];
8059 char *env_proxy
= NULL
;
8062 struct sigaction sact
;
8063 gchar
*priority
= g_strdup("NORMAL");
8067 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8069 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8078 errx(0 , "Version: %s", version
);
8081 strlcpy(conf
, optarg
, sizeof(conf
));
8084 strlcpy(named_session
, optarg
, sizeof(named_session
));
8105 RB_INIT(&downloads
);
8109 TAILQ_INIT(&aliases
);
8115 gnutls_global_init();
8117 /* generate session keys for xtp pages */
8118 generate_xtp_session_key(&dl_session_key
);
8119 generate_xtp_session_key(&hl_session_key
);
8120 generate_xtp_session_key(&cl_session_key
);
8121 generate_xtp_session_key(&fl_session_key
);
8124 gtk_init(&argc
, &argv
);
8125 if (!g_thread_supported())
8126 g_thread_init(NULL
);
8129 bzero(&sact
, sizeof(sact
));
8130 sigemptyset(&sact
.sa_mask
);
8131 sact
.sa_handler
= sigchild
;
8132 sact
.sa_flags
= SA_NOCLDSTOP
;
8133 sigaction(SIGCHLD
, &sact
, NULL
);
8135 /* set download dir */
8136 pwd
= getpwuid(getuid());
8138 errx(1, "invalid user %d", getuid());
8139 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8141 /* set default string settings */
8142 home
= g_strdup("http://www.peereboom.us");
8143 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8144 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
8145 strlcpy(runtime_settings
,"runtime", sizeof runtime_settings
);
8147 /* read config file */
8148 if (strlen(conf
) == 0)
8149 snprintf(conf
, sizeof conf
, "%s/.%s",
8150 pwd
->pw_dir
, XT_CONF_FILE
);
8151 config_parse(conf
, 0);
8153 /* working directory */
8154 if (strlen(work_dir
) == 0)
8155 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
8156 pwd
->pw_dir
, XT_DIR
);
8157 if (stat(work_dir
, &sb
)) {
8158 if (mkdir(work_dir
, S_IRWXU
) == -1)
8159 err(1, "mkdir work_dir");
8160 if (stat(work_dir
, &sb
))
8161 err(1, "stat work_dir");
8163 if (S_ISDIR(sb
.st_mode
) == 0)
8164 errx(1, "%s not a dir", work_dir
);
8165 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8166 warnx("fixing invalid permissions on %s", work_dir
);
8167 if (chmod(work_dir
, S_IRWXU
) == -1)
8171 /* icon cache dir */
8172 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
8173 if (stat(cache_dir
, &sb
)) {
8174 if (mkdir(cache_dir
, S_IRWXU
) == -1)
8175 err(1, "mkdir cache_dir");
8176 if (stat(cache_dir
, &sb
))
8177 err(1, "stat cache_dir");
8179 if (S_ISDIR(sb
.st_mode
) == 0)
8180 errx(1, "%s not a dir", cache_dir
);
8181 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8182 warnx("fixing invalid permissions on %s", cache_dir
);
8183 if (chmod(cache_dir
, S_IRWXU
) == -1)
8188 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
8189 if (stat(certs_dir
, &sb
)) {
8190 if (mkdir(certs_dir
, S_IRWXU
) == -1)
8191 err(1, "mkdir certs_dir");
8192 if (stat(certs_dir
, &sb
))
8193 err(1, "stat certs_dir");
8195 if (S_ISDIR(sb
.st_mode
) == 0)
8196 errx(1, "%s not a dir", certs_dir
);
8197 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8198 warnx("fixing invalid permissions on %s", certs_dir
);
8199 if (chmod(certs_dir
, S_IRWXU
) == -1)
8204 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
8205 work_dir
, XT_SESSIONS_DIR
);
8206 if (stat(sessions_dir
, &sb
)) {
8207 if (mkdir(sessions_dir
, S_IRWXU
) == -1)
8208 err(1, "mkdir sessions_dir");
8209 if (stat(sessions_dir
, &sb
))
8210 err(1, "stat sessions_dir");
8212 if (S_ISDIR(sb
.st_mode
) == 0)
8213 errx(1, "%s not a dir", sessions_dir
);
8214 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8215 warnx("fixing invalid permissions on %s", sessions_dir
);
8216 if (chmod(sessions_dir
, S_IRWXU
) == -1)
8219 /* runtime settings that can override config file */
8220 if (runtime_settings
[0] != '\0')
8221 config_parse(runtime_settings
, 1);
8224 if (!strcmp(download_dir
, pwd
->pw_dir
))
8225 strlcat(download_dir
, "/downloads", sizeof download_dir
);
8226 if (stat(download_dir
, &sb
)) {
8227 if (mkdir(download_dir
, S_IRWXU
) == -1)
8228 err(1, "mkdir download_dir");
8229 if (stat(download_dir
, &sb
))
8230 err(1, "stat download_dir");
8232 if (S_ISDIR(sb
.st_mode
) == 0)
8233 errx(1, "%s not a dir", download_dir
);
8234 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8235 warnx("fixing invalid permissions on %s", download_dir
);
8236 if (chmod(download_dir
, S_IRWXU
) == -1)
8240 /* favorites file */
8241 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
8242 if (stat(file
, &sb
)) {
8243 warnx("favorites file doesn't exist, creating it");
8244 if ((f
= fopen(file
, "w")) == NULL
)
8245 err(1, "favorites");
8250 session
= webkit_get_default_session();
8251 /* XXX ssl-priority property not quite available yet */
8252 if (is_g_object_setting(G_OBJECT(session
), "ssl-priority"))
8253 g_object_set(G_OBJECT(session
), "ssl-priority", priority
,
8256 warnx("session does not have \"ssl-priority\" property");
8261 if (stat(ssl_ca_file
, &sb
)) {
8262 warn("no CA file: %s", ssl_ca_file
);
8263 g_free(ssl_ca_file
);
8266 g_object_set(session
,
8267 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
8268 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8273 env_proxy
= getenv("http_proxy");
8275 setup_proxy(env_proxy
);
8277 setup_proxy(http_proxy
);
8280 send_cmd_to_socket(argv
[0]);
8284 /* set some connection parameters */
8285 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8286 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8289 /* see if there is already an xxxterm running */
8290 if (single_instance
&& is_running()) {
8292 warnx("already running");
8298 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8299 send_cmd_to_socket(cmd
);
8309 /* uri completion */
8310 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8315 if (save_global_history
)
8316 restore_global_history();
8318 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
8319 restore_saved_tabs();
8321 a
.s
= named_session
;
8322 a
.i
= XT_SES_DONOTHING
;
8323 open_tabs(NULL
, &a
);
8327 create_new_tab(argv
[0], NULL
, focus
);
8334 if (TAILQ_EMPTY(&tabs
))
8335 create_new_tab(home
, NULL
, 1);
8338 if ((s
= build_socket()) != -1)
8339 gdk_input_add(s
, GDK_INPUT_READ
, socket_watcher
, NULL
);
8343 gnutls_global_deinit();