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;
1007 get_browser_mode(struct settings
*s
)
1011 if (browser_mode
== XT_BM_WHITELIST
)
1012 r
= g_strdup("whitelist");
1013 else if (browser_mode
== XT_BM_NORMAL
)
1014 r
= g_strdup("normal");
1015 else if (browser_mode
== XT_BM_KIOSK
)
1016 r
= g_strdup("kiosk");
1024 set_cookie_policy(struct settings
*s
, char *val
)
1026 if (!strcmp(val
, "no3rdparty"))
1027 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1028 else if (!strcmp(val
, "accept"))
1029 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1030 else if (!strcmp(val
, "reject"))
1031 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1039 get_cookie_policy(struct settings
*s
)
1043 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1044 r
= g_strdup("no3rdparty");
1045 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1046 r
= g_strdup("accept");
1047 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1048 r
= g_strdup("reject");
1056 get_download_dir(struct settings
*s
)
1058 if (download_dir
[0] == '\0')
1060 return (g_strdup(download_dir
));
1064 set_download_dir(struct settings
*s
, char *val
)
1067 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1068 pwd
->pw_dir
, &val
[1]);
1070 strlcpy(download_dir
, val
, sizeof download_dir
);
1077 * We use these to prevent people putting xxxt:// URLs on
1078 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1080 #define XT_XTP_SES_KEY_SZ 8
1081 #define XT_XTP_SES_KEY_HEX_FMT \
1082 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1083 char *dl_session_key
; /* downloads */
1084 char *hl_session_key
; /* history list */
1085 char *cl_session_key
; /* cookie list */
1086 char *fl_session_key
; /* favorites list */
1088 char work_dir
[PATH_MAX
];
1089 char certs_dir
[PATH_MAX
];
1090 char cache_dir
[PATH_MAX
];
1091 char sessions_dir
[PATH_MAX
];
1092 char cookie_file
[PATH_MAX
];
1093 SoupURI
*proxy_uri
= NULL
;
1094 SoupSession
*session
;
1095 SoupCookieJar
*s_cookiejar
;
1096 SoupCookieJar
*p_cookiejar
;
1097 char rc_fname
[PATH_MAX
];
1099 struct mime_type_list mtl
;
1100 struct alias_list aliases
;
1103 struct tab
*create_new_tab(char *, struct undo
*, int);
1104 void delete_tab(struct tab
*);
1105 void adjustfont_webkit(struct tab
*, int);
1106 int run_script(struct tab
*, char *);
1107 int download_rb_cmp(struct download
*, struct download
*);
1108 gboolean
cmd_execute(struct tab
*t
, char *str
);
1111 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1113 return (strcmp(h1
->uri
, h2
->uri
));
1115 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1118 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1120 return (strcmp(d1
->d
, d2
->d
));
1122 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1125 get_work_dir(struct settings
*s
)
1127 if (work_dir
[0] == '\0')
1129 return (g_strdup(work_dir
));
1133 set_work_dir(struct settings
*s
, char *val
)
1136 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1137 pwd
->pw_dir
, &val
[1]);
1139 strlcpy(work_dir
, val
, sizeof work_dir
);
1145 * generate a session key to secure xtp commands.
1146 * pass in a ptr to the key in question and it will
1147 * be modified in place.
1150 generate_xtp_session_key(char **key
)
1152 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1158 /* make a new one */
1159 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1160 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1161 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1162 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1164 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1168 * validate a xtp session key.
1172 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1174 if (strcmp(trusted
, untrusted
) != 0) {
1175 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1184 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1186 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1188 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1190 struct valid_url_types
{
1201 valid_url_type(char *url
)
1205 for (i
= 0; i
< LENGTH(vut
); i
++)
1206 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1213 print_cookie(char *msg
, SoupCookie
*c
)
1219 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1220 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1221 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1222 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1223 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1224 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1225 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1226 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1227 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1228 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1232 walk_alias(struct settings
*s
,
1233 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1238 if (s
== NULL
|| cb
== NULL
) {
1239 show_oops_s("walk_alias invalid parameters");
1243 TAILQ_FOREACH(a
, &aliases
, entry
) {
1244 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1245 cb(s
, str
, cb_args
);
1251 match_alias(char *url_in
)
1255 char *url_out
= NULL
, *search
, *enc_arg
;
1257 search
= g_strdup(url_in
);
1259 if (strsep(&arg
, " \t") == NULL
) {
1260 show_oops_s("match_alias: NULL URL");
1264 TAILQ_FOREACH(a
, &aliases
, entry
) {
1265 if (!strcmp(search
, a
->a_name
))
1270 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1273 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1274 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1277 url_out
= g_strdup(a
->a_uri
);
1285 guess_url_type(char *url_in
)
1288 char *url_out
= NULL
, *enc_search
= NULL
;
1290 url_out
= match_alias(url_in
);
1291 if (url_out
!= NULL
)
1296 * If there is no dot nor slash in the string and it isn't a
1297 * path to a local file and doesn't resolves to an IP, assume
1298 * that the user wants to search for the string.
1301 if (strchr(url_in
, '.') == NULL
&&
1302 strchr(url_in
, '/') == NULL
&&
1303 stat(url_in
, &sb
) != 0 &&
1304 gethostbyname(url_in
) == NULL
) {
1306 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1307 url_out
= g_strdup_printf(search_string
, enc_search
);
1313 /* XXX not sure about this heuristic */
1314 if (stat(url_in
, &sb
) == 0)
1315 url_out
= g_strdup_printf("file://%s", url_in
);
1317 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1319 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1325 load_uri(struct tab
*t
, gchar
*uri
)
1328 gchar
*newuri
= NULL
;
1334 /* Strip leading spaces. */
1335 while(*uri
&& isspace(*uri
))
1338 if (strlen(uri
) == 0) {
1343 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1344 for (i
= 0; i
< LENGTH(about_list
); i
++)
1345 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1346 bzero(&args
, sizeof args
);
1347 about_list
[i
].func(t
, &args
);
1350 show_oops(t
, "invalid about page");
1354 if (valid_url_type(uri
)) {
1355 newuri
= guess_url_type(uri
);
1359 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1360 webkit_web_view_load_uri(t
->wv
, uri
);
1367 get_uri(WebKitWebView
*wv
)
1369 WebKitWebFrame
*frame
;
1372 frame
= webkit_web_view_get_main_frame(wv
);
1373 uri
= webkit_web_frame_get_uri(frame
);
1375 if (uri
&& strlen(uri
) > 0)
1382 add_alias(struct settings
*s
, char *line
)
1385 struct alias
*a
= NULL
;
1387 if (s
== NULL
|| line
== NULL
) {
1388 show_oops_s("add_alias invalid parameters");
1393 a
= g_malloc(sizeof(*a
));
1395 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1396 show_oops_s("add_alias: incomplete alias definition");
1399 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1400 show_oops_s("add_alias: invalid alias definition");
1404 a
->a_name
= g_strdup(alias
);
1405 a
->a_uri
= g_strdup(l
);
1407 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1409 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1419 add_mime_type(struct settings
*s
, char *line
)
1423 struct mime_type
*m
= NULL
;
1424 int downloadfirst
= 0;
1426 /* XXX this could be smarter */
1428 if (line
== NULL
&& strlen(line
) == 0) {
1429 show_oops_s("add_mime_type invalid parameters");
1438 m
= g_malloc(sizeof(*m
));
1440 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1441 show_oops_s("add_mime_type: invalid mime_type");
1444 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1445 mime_type
[strlen(mime_type
) - 1] = '\0';
1450 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1451 show_oops_s("add_mime_type: invalid mime_type");
1455 m
->mt_type
= g_strdup(mime_type
);
1456 m
->mt_action
= g_strdup(l
);
1457 m
->mt_download
= downloadfirst
;
1459 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1460 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1462 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1472 find_mime_type(char *mime_type
)
1474 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1476 TAILQ_FOREACH(m
, &mtl
, entry
) {
1477 if (m
->mt_default
&&
1478 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1481 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1494 walk_mime_type(struct settings
*s
,
1495 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1497 struct mime_type
*m
;
1500 if (s
== NULL
|| cb
== NULL
)
1501 show_oops_s("walk_mime_type invalid parameters");
1503 TAILQ_FOREACH(m
, &mtl
, entry
) {
1504 str
= g_strdup_printf("%s%s --> %s",
1506 m
->mt_default
? "*" : "",
1508 cb(s
, str
, cb_args
);
1514 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1519 if (str
== NULL
|| wl
== NULL
)
1521 if (strlen(str
) < 2)
1524 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1526 /* treat *.moo.com the same as .moo.com */
1527 if (str
[0] == '*' && str
[1] == '.')
1529 else if (str
[0] == '.')
1534 d
= g_malloc(sizeof *d
);
1536 d
->d
= g_strdup_printf(".%s", str
);
1538 d
->d
= g_strdup(str
);
1541 if (RB_INSERT(domain_list
, wl
, d
))
1544 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1555 add_cookie_wl(struct settings
*s
, char *entry
)
1557 wl_add(entry
, &c_wl
, 1);
1562 walk_cookie_wl(struct settings
*s
,
1563 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1567 if (s
== NULL
|| cb
== NULL
) {
1568 show_oops_s("walk_cookie_wl invalid parameters");
1572 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1573 cb(s
, d
->d
, cb_args
);
1577 walk_js_wl(struct settings
*s
,
1578 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1582 if (s
== NULL
|| cb
== NULL
) {
1583 show_oops_s("walk_js_wl invalid parameters");
1587 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1588 cb(s
, d
->d
, cb_args
);
1592 add_js_wl(struct settings
*s
, char *entry
)
1594 wl_add(entry
, &js_wl
, 1 /* persistent */);
1599 wl_find(const gchar
*search
, struct domain_list
*wl
)
1602 struct domain
*d
= NULL
, dfind
;
1605 if (search
== NULL
|| wl
== NULL
)
1607 if (strlen(search
) < 2)
1610 if (search
[0] != '.')
1611 s
= g_strdup_printf(".%s", search
);
1613 s
= g_strdup(search
);
1615 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1618 d
= RB_FIND(domain_list
, wl
, &dfind
);
1632 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1638 if (s
== NULL
|| wl
== NULL
)
1641 if (!strncmp(s
, "http://", strlen("http://")))
1642 s
= &s
[strlen("http://")];
1643 else if (!strncmp(s
, "https://", strlen("https://")))
1644 s
= &s
[strlen("https://")];
1649 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1650 /* chop string at first slash */
1651 if (s
[i
] == '/' || s
[i
] == '\0') {
1654 r
= wl_find(ss
, wl
);
1663 get_toplevel_domain(char *domain
)
1670 if (strlen(domain
) < 2)
1673 s
= &domain
[strlen(domain
) - 1];
1674 while (s
!= domain
) {
1690 settings_add(char *var
, char *val
)
1697 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
1698 if (strcmp(var
, rs
[i
].name
))
1702 if (rs
[i
].s
->set(&rs
[i
], val
))
1703 errx(1, "invalid value for %s: %s", var
, val
);
1707 switch (rs
[i
].type
) {
1716 errx(1, "invalid sval for %s",
1730 errx(1, "invalid type for %s", var
);
1739 config_parse(char *filename
, int runtime
)
1742 char *line
, *cp
, *var
, *val
;
1743 size_t len
, lineno
= 0;
1745 char file
[PATH_MAX
];
1748 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
1750 if (filename
== NULL
)
1753 if (runtime
&& runtime_settings
[0] != '\0') {
1754 snprintf(file
, sizeof file
, "%s/%s",
1755 work_dir
, runtime_settings
);
1756 if (stat(file
, &sb
)) {
1757 warnx("runtime file doesn't exist, creating it");
1758 if ((f
= fopen(file
, "w")) == NULL
)
1760 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
1764 strlcpy(file
, filename
, sizeof file
);
1766 if ((config
= fopen(file
, "r")) == NULL
) {
1767 warn("config_parse: cannot open %s", filename
);
1772 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
1773 if (feof(config
) || ferror(config
))
1777 cp
+= (long)strspn(cp
, WS
);
1778 if (cp
[0] == '\0') {
1784 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
1785 errx(1, "invalid config file entry: %s", line
);
1787 cp
+= (long)strspn(cp
, WS
);
1789 if ((val
= strsep(&cp
, "\0")) == NULL
)
1792 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
1793 handled
= settings_add(var
, val
);
1795 errx(1, "invalid conf file entry: %s=%s", var
, val
);
1804 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1810 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1814 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1817 JSStringGetUTF8CString(jsref
, s
, l
);
1818 JSStringRelease(jsref
);
1824 disable_hints(struct tab
*t
)
1826 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1827 bzero(t
->hint_num
, sizeof t
->hint_num
);
1828 run_script(t
, "vimprobable_clear()");
1830 t
->hint_mode
= XT_HINT_NONE
;
1834 enable_hints(struct tab
*t
)
1836 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1837 run_script(t
, "vimprobable_show_hints()");
1839 t
->hint_mode
= XT_HINT_NONE
;
1842 #define XT_JS_OPEN ("open;")
1843 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1844 #define XT_JS_FIRE ("fire;")
1845 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1846 #define XT_JS_FOUND ("found;")
1847 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1850 run_script(struct tab
*t
, char *s
)
1852 JSGlobalContextRef ctx
;
1853 WebKitWebFrame
*frame
;
1855 JSValueRef val
, exception
;
1858 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
1859 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1861 frame
= webkit_web_view_get_main_frame(t
->wv
);
1862 ctx
= webkit_web_frame_get_global_context(frame
);
1864 str
= JSStringCreateWithUTF8CString(s
);
1865 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1866 NULL
, 0, &exception
);
1867 JSStringRelease(str
);
1869 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
1871 es
= js_ref_to_string(ctx
, exception
);
1872 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
1876 es
= js_ref_to_string(ctx
, val
);
1877 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
1879 /* handle return value right here */
1880 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
1882 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
1885 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
1886 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
1887 &es
[XT_JS_FIRE_LEN
]);
1892 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
1893 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
1904 hint(struct tab
*t
, struct karg
*args
)
1907 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
1909 if (t
->hints_on
== 0)
1918 apply_style(struct tab
*t
)
1920 g_object_set(G_OBJECT(t
->settings
),
1921 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
1925 userstyle(struct tab
*t
, struct karg
*args
)
1927 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
1931 g_object_set(G_OBJECT(t
->settings
),
1932 "user-stylesheet-uri", NULL
, (char *)NULL
);
1941 * Doesn't work fully, due to the following bug:
1942 * https://bugs.webkit.org/show_bug.cgi?id=51747
1945 restore_global_history(void)
1947 char file
[PATH_MAX
];
1953 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
1955 if ((f
= fopen(file
, "r")) == NULL
) {
1956 warnx("%s: fopen", __func__
);
1961 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1962 if (feof(f
) || ferror(f
))
1965 if ((title
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1966 if (feof(f
) || ferror(f
)) {
1968 warnx("%s: broken history file\n", __func__
);
1972 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
1973 webkit_web_history_item_new_with_data(uri
, title
);
1974 h
= g_malloc(sizeof(struct history
));
1975 h
->uri
= g_strdup(uri
);
1976 h
->title
= g_strdup(title
);
1977 RB_INSERT(history_list
, &hl
, h
);
1978 completion_add_uri(h
->uri
);
1980 warnx("%s: failed to restore history\n", __func__
);
1996 save_global_history_to_disk(struct tab
*t
)
1998 char file
[PATH_MAX
];
2002 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2004 if ((f
= fopen(file
, "w")) == NULL
) {
2005 show_oops(t
, "%s: global history file: %s",
2006 __func__
, strerror(errno
));
2010 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2011 if (h
->uri
&& h
->title
)
2012 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2021 quit(struct tab
*t
, struct karg
*args
)
2023 if (save_global_history
)
2024 save_global_history_to_disk(t
);
2032 open_tabs(struct tab
*t
, struct karg
*a
)
2034 char file
[PATH_MAX
];
2038 struct tab
*ti
, *tt
;
2043 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2044 if ((f
= fopen(file
, "r")) == NULL
)
2047 ti
= TAILQ_LAST(&tabs
, tab_list
);
2050 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2051 if (feof(f
) || ferror(f
))
2054 /* retrieve session name */
2055 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2056 strlcpy(named_session
,
2057 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2058 sizeof named_session
);
2062 if (uri
&& strlen(uri
))
2063 create_new_tab(uri
, NULL
, 1);
2069 /* close open tabs */
2070 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2072 tt
= TAILQ_FIRST(&tabs
);
2091 restore_saved_tabs(void)
2093 char file
[PATH_MAX
];
2094 int unlink_file
= 0;
2099 snprintf(file
, sizeof file
, "%s/%s",
2100 sessions_dir
, XT_RESTART_TABS_FILE
);
2101 if (stat(file
, &sb
) == -1)
2102 a
.s
= XT_SAVED_TABS_FILE
;
2105 a
.s
= XT_RESTART_TABS_FILE
;
2108 a
.i
= XT_SES_DONOTHING
;
2109 rv
= open_tabs(NULL
, &a
);
2118 save_tabs(struct tab
*t
, struct karg
*a
)
2120 char file
[PATH_MAX
];
2125 const gchar
**arr
= NULL
;
2130 snprintf(file
, sizeof file
, "%s/%s",
2131 sessions_dir
, named_session
);
2133 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2135 if ((f
= fopen(file
, "w")) == NULL
) {
2136 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2140 /* save session name */
2141 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2143 /* save tabs, in the order they are arranged in the notebook */
2144 TAILQ_FOREACH(ti
, &tabs
, entry
)
2147 arr
= g_malloc0(len
* sizeof(gchar
*));
2149 TAILQ_FOREACH(ti
, &tabs
, entry
) {
2150 if ((uri
= get_uri(ti
->wv
)) != NULL
)
2151 arr
[gtk_notebook_page_num(notebook
, ti
->vbox
)] = uri
;
2154 for (i
= 0; i
< len
; i
++)
2156 fprintf(f
, "%s\n", arr
[i
]);
2165 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2177 yank_uri(struct tab
*t
, struct karg
*args
)
2180 GtkClipboard
*clipboard
;
2182 if ((uri
= get_uri(t
->wv
)) == NULL
)
2185 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2186 gtk_clipboard_set_text(clipboard
, uri
, -1);
2197 paste_uri_cb(GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
2199 struct paste_args
*pap
;
2201 if (data
== NULL
|| text
== NULL
|| !strlen(text
))
2204 pap
= (struct paste_args
*)data
;
2207 case XT_PASTE_CURRENT_TAB
:
2208 load_uri(pap
->t
, (gchar
*)text
);
2210 case XT_PASTE_NEW_TAB
:
2211 create_new_tab((gchar
*)text
, NULL
, 1);
2219 paste_uri(struct tab
*t
, struct karg
*args
)
2221 GtkClipboard
*clipboard
;
2222 struct paste_args
*pap
;
2224 pap
= g_malloc(sizeof(struct paste_args
));
2229 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2230 gtk_clipboard_request_text(clipboard
, paste_uri_cb
, pap
);
2236 find_domain(const gchar
*s
, int add_dot
)
2239 char *r
= NULL
, *ss
= NULL
;
2244 if (!strncmp(s
, "http://", strlen("http://")))
2245 s
= &s
[strlen("http://")];
2246 else if (!strncmp(s
, "https://", strlen("https://")))
2247 s
= &s
[strlen("https://")];
2253 for (i
= 0; i
< strlen(ss
) + 1 /* yes er need this */; i
++)
2254 /* chop string at first slash */
2255 if (ss
[i
] == '/' || ss
[i
] == '\0') {
2258 r
= g_strdup_printf(".%s", ss
);
2269 toggle_cwl(struct tab
*t
, struct karg
*args
)
2273 char *dom
= NULL
, *dom_toggle
= NULL
;
2279 uri
= get_uri(t
->wv
);
2280 dom
= find_domain(uri
, 1);
2281 d
= wl_find(dom
, &c_wl
);
2288 if (args
->i
& XT_WL_TOGGLE
)
2290 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2292 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2295 if (args
->i
& XT_WL_TOPLEVEL
)
2296 dom_toggle
= get_toplevel_domain(dom
);
2301 /* enable cookies for domain */
2302 wl_add(dom_toggle
, &c_wl
, 0);
2304 /* disable cookies for domain */
2305 RB_REMOVE(domain_list
, &c_wl
, d
);
2307 webkit_web_view_reload(t
->wv
);
2314 toggle_js(struct tab
*t
, struct karg
*args
)
2319 char *dom
= NULL
, *dom_toggle
= NULL
;
2324 g_object_get(G_OBJECT(t
->settings
),
2325 "enable-scripts", &es
, (char *)NULL
);
2326 if (args
->i
& XT_WL_TOGGLE
)
2328 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2330 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2335 uri
= get_uri(t
->wv
);
2336 dom
= find_domain(uri
, 1);
2338 if (uri
== NULL
|| dom
== NULL
) {
2339 show_oops(t
, "Can't toggle domain in JavaScript white list");
2343 if (args
->i
& XT_WL_TOPLEVEL
)
2344 dom_toggle
= get_toplevel_domain(dom
);
2349 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2350 wl_add(dom_toggle
, &js_wl
, 0 /* session */);
2352 d
= wl_find(dom_toggle
, &js_wl
);
2354 RB_REMOVE(domain_list
, &js_wl
, d
);
2355 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2357 g_object_set(G_OBJECT(t
->settings
),
2358 "enable-scripts", es
, (char *)NULL
);
2359 g_object_set(G_OBJECT(t
->settings
),
2360 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2361 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2362 webkit_web_view_reload(t
->wv
);
2370 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2374 a
.i
= XT_WL_TOGGLE
| XT_WL_FQDN
;
2379 toggle_src(struct tab
*t
, struct karg
*args
)
2386 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2387 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2388 webkit_web_view_reload(t
->wv
);
2394 focus_webview(struct tab
*t
)
2399 /* only grab focus if we are visible */
2400 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2401 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2405 focus(struct tab
*t
, struct karg
*args
)
2407 if (t
== NULL
|| args
== NULL
)
2413 if (args
->i
== XT_FOCUS_URI
)
2414 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2415 else if (args
->i
== XT_FOCUS_SEARCH
)
2416 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2422 stats(struct tab
*t
, struct karg
*args
)
2424 char *stats
, *s
, line
[64 * 1024];
2425 uint64_t line_count
= 0;
2429 show_oops_s("stats invalid parameters");
2432 if (save_rejected_cookies
) {
2433 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2435 s
= fgets(line
, sizeof line
, r_cookie_f
);
2436 if (s
== NULL
|| feof(r_cookie_f
) ||
2442 snprintf(line
, sizeof line
,
2443 "<br>Cookies blocked(*) total: %llu", line_count
);
2445 show_oops(t
, "Can't open blocked cookies file: %s",
2449 stats
= g_strdup_printf(XT_DOCTYPE
2452 "<title>Statistics</title>"
2454 "<h1>Statistics</h1>"
2456 "Cookies blocked(*) this session: %llu"
2458 "<p><small><b>*</b> results vary based on settings"
2464 load_webkit_string(t
, stats
, XT_URI_ABOUT_STATS
);
2471 marco(struct tab
*t
, struct karg
*args
)
2473 char *message
, line
[64 * 1024];
2477 show_oops_s("marco invalid parameters");
2480 snprintf(line
, sizeof line
, "<br>%s", marco_message(&len
));
2482 message
= g_strdup_printf(XT_DOCTYPE
2485 "<title>Marco Sez...</title>"
2494 load_webkit_string(t
, message
, XT_URI_ABOUT_MARCO
);
2501 blank(struct tab
*t
, struct karg
*args
)
2504 show_oops_s("about invalid parameters");
2506 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2511 about(struct tab
*t
, struct karg
*args
)
2516 show_oops_s("about invalid parameters");
2518 about
= g_strdup_printf(XT_DOCTYPE
2521 "<title>About</title>"
2525 "<b>Version: %s</b><p>"
2528 "<li>Marco Peereboom <marco@peereboom.us></li>"
2529 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2530 "<li>Edd Barrett <vext01@gmail.com> </li>"
2531 "<li>Todd T. Fries <todd@fries.net> </li>"
2533 "Copyrights and licenses can be found on the XXXterm "
2534 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2540 load_webkit_string(t
, about
, XT_URI_ABOUT_ABOUT
);
2547 help(struct tab
*t
, struct karg
*args
)
2552 show_oops_s("help invalid parameters");
2557 "<title>XXXterm</title>"
2558 "<meta http-equiv=\"REFRESH\" content=\"0;"
2559 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2562 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2563 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2564 "cgi-bin/man-cgi?xxxterm</a>"
2569 load_webkit_string(t
, help
, XT_URI_ABOUT_HELP
);
2575 * update all favorite tabs apart from one. Pass NULL if
2576 * you want to update all.
2579 update_favorite_tabs(struct tab
*apart_from
)
2582 if (!updating_fl_tabs
) {
2583 updating_fl_tabs
= 1; /* stop infinite recursion */
2584 TAILQ_FOREACH(t
, &tabs
, entry
)
2585 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2586 && (t
!= apart_from
))
2587 xtp_page_fl(t
, NULL
);
2588 updating_fl_tabs
= 0;
2592 /* show a list of favorites (bookmarks) */
2594 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2596 char file
[PATH_MAX
];
2598 char *uri
= NULL
, *title
= NULL
;
2599 size_t len
, lineno
= 0;
2601 char *header
, *body
, *tmp
, *html
= NULL
;
2603 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2606 warn("%s: bad param", __func__
);
2608 /* mark tab as favorite list */
2609 t
->xtp_meaning
= XT_XTP_TAB_MEANING_FL
;
2611 /* new session key */
2612 if (!updating_fl_tabs
)
2613 generate_xtp_session_key(&fl_session_key
);
2615 /* open favorites */
2616 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
2617 if ((f
= fopen(file
, "r")) == NULL
) {
2618 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2623 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
2624 "<title>Favorites</title>\n"
2627 "<h1>Favorites</h1>\n",
2631 body
= g_strdup_printf("<div align='center'><table><tr>"
2632 "<th style='width: 4%%'>#</th><th>Link</th>"
2633 "<th style='width: 15%%'>Remove</th></tr>\n");
2636 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2637 if (feof(f
) || ferror(f
))
2645 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2646 if (feof(f
) || ferror(f
)) {
2647 show_oops(t
, "favorites file corrupt");
2653 body
= g_strdup_printf("%s<tr>"
2655 "<td><a href='%s'>%s</a></td>"
2656 "<td style='text-align: center'>"
2657 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2659 body
, i
, uri
, title
,
2660 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2672 /* if none, say so */
2675 body
= g_strdup_printf("%s<tr>"
2676 "<td colspan='3' style='text-align: center'>"
2677 "No favorites - To add one use the 'favadd' command."
2678 "</td></tr>", body
);
2689 html
= g_strdup_printf("%s%s</table></div></html>",
2691 load_webkit_string(t
, html
, XT_URI_ABOUT_FAVORITES
);
2694 update_favorite_tabs(t
);
2707 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2708 size_t cert_count
, char *title
)
2710 gnutls_datum_t cinfo
;
2711 char *tmp
, *header
, *body
, *footer
;
2714 header
= g_strdup_printf("<html><head><title>%s</title></head><body>", title
);
2715 footer
= g_strdup("</body></html>");
2716 body
= g_strdup("");
2718 for (i
= 0; i
< cert_count
; i
++) {
2719 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2724 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2725 body
, i
, cinfo
.data
);
2726 gnutls_free(cinfo
.data
);
2730 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
2734 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
2739 ca_cmd(struct tab
*t
, struct karg
*args
)
2742 int rv
= 1, certs
= 0, certs_read
;
2745 gnutls_x509_crt_t
*c
= NULL
;
2746 char *certs_buf
= NULL
, *s
;
2748 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
2749 show_oops(t
, "Can't open CA file: %s", strerror(errno
));
2753 if (fstat(fileno(f
), &sb
) == -1) {
2754 show_oops(t
, "Can't stat CA file: %s", strerror(errno
));
2758 certs_buf
= g_malloc(sb
.st_size
+ 1);
2759 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
2760 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
2763 certs_buf
[sb
.st_size
] = '\0';
2766 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
2768 s
+= strlen("BEGIN CERTIFICATE");
2771 bzero(&dt
, sizeof dt
);
2772 dt
.data
= certs_buf
;
2773 dt
.size
= sb
.st_size
;
2774 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
2775 certs_read
= gnutls_x509_crt_list_import(c
, &certs
, &dt
,
2776 GNUTLS_X509_FMT_PEM
, 0);
2777 if (certs_read
<= 0) {
2778 show_oops(t
, "No cert(s) available");
2781 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
2794 connect_socket_from_uri(const gchar
*uri
, char *domain
, size_t domain_sz
)
2797 struct addrinfo hints
, *res
= NULL
, *ai
;
2801 if (uri
&& !g_str_has_prefix(uri
, "https://"))
2804 su
= soup_uri_new(uri
);
2807 if (!SOUP_URI_VALID_FOR_HTTP(su
))
2810 snprintf(port
, sizeof port
, "%d", su
->port
);
2811 bzero(&hints
, sizeof(struct addrinfo
));
2812 hints
.ai_flags
= AI_CANONNAME
;
2813 hints
.ai_family
= AF_UNSPEC
;
2814 hints
.ai_socktype
= SOCK_STREAM
;
2816 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
2819 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
2820 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
2823 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
2826 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
2830 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
2835 strlcpy(domain
, su
->host
, domain_sz
);
2846 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
2849 gnutls_deinit(gsession
);
2851 gnutls_certificate_free_credentials(xcred
);
2857 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
2858 gnutls_certificate_credentials_t
*xc
)
2860 gnutls_certificate_credentials_t xcred
;
2861 gnutls_session_t gsession
;
2864 if (gs
== NULL
|| xc
== NULL
)
2870 gnutls_certificate_allocate_credentials(&xcred
);
2871 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
2872 GNUTLS_X509_FMT_PEM
);
2873 gnutls_init(&gsession
, GNUTLS_CLIENT
);
2874 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
2875 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
2876 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
2877 if ((rv
= gnutls_handshake(gsession
)) < 0) {
2878 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
2880 gnutls_error_is_fatal(rv
),
2881 gnutls_strerror_name(rv
));
2882 stop_tls(gsession
, xcred
);
2886 gnutls_credentials_type_t cred
;
2887 cred
= gnutls_auth_get_type(gsession
);
2888 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
2889 stop_tls(gsession
, xcred
);
2901 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
2905 const gnutls_datum_t
*cl
;
2906 gnutls_x509_crt_t
*all_certs
;
2909 if (certs
== NULL
|| cert_count
== NULL
)
2911 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
2913 cl
= gnutls_certificate_get_peers(gsession
, &len
);
2917 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
2918 for (i
= 0; i
< len
; i
++) {
2919 gnutls_x509_crt_init(&all_certs
[i
]);
2920 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
2921 GNUTLS_X509_FMT_PEM
< 0)) {
2935 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
2939 for (i
= 0; i
< cert_count
; i
++)
2940 gnutls_x509_crt_deinit(certs
[i
]);
2945 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2946 size_t cert_count
, char *domain
)
2949 char cert_buf
[64 * 1024], file
[PATH_MAX
];
2954 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
2957 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
2958 if ((f
= fopen(file
, "w")) == NULL
) {
2959 show_oops(t
, "Can't create cert file %s %s",
2960 file
, strerror(errno
));
2964 for (i
= 0; i
< cert_count
; i
++) {
2965 cert_buf_sz
= sizeof cert_buf
;
2966 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
2967 cert_buf
, &cert_buf_sz
)) {
2968 show_oops(t
, "gnutls_x509_crt_export failed");
2971 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
2972 show_oops(t
, "Can't write certs: %s", strerror(errno
));
2977 /* not the best spot but oh well */
2978 gdk_color_parse("lightblue", &color
);
2979 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
2980 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2981 gdk_color_parse(XT_COLOR_BLACK
, &color
);
2982 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2988 load_compare_cert(struct tab
*t
, struct karg
*args
)
2991 char domain
[8182], file
[PATH_MAX
];
2992 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
2993 int s
= -1, rv
= 1, i
;
2997 gnutls_session_t gsession
;
2998 gnutls_x509_crt_t
*certs
;
2999 gnutls_certificate_credentials_t xcred
;
3004 if ((uri
= get_uri(t
->wv
)) == NULL
)
3007 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
3011 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3012 show_oops(t
, "Start TLS failed");
3017 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3018 show_oops(t
, "Can't get connection certificates");
3022 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3023 if ((f
= fopen(file
, "r")) == NULL
)
3026 for (i
= 0; i
< cert_count
; i
++) {
3027 cert_buf_sz
= sizeof cert_buf
;
3028 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3029 cert_buf
, &cert_buf_sz
)) {
3032 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3033 rv
= -1; /* critical */
3036 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3037 rv
= -1; /* critical */
3046 free_connection_certs(certs
, cert_count
);
3048 /* we close the socket first for speed */
3051 stop_tls(gsession
, xcred
);
3057 cert_cmd(struct tab
*t
, struct karg
*args
)
3063 gnutls_session_t gsession
;
3064 gnutls_x509_crt_t
*certs
;
3065 gnutls_certificate_credentials_t xcred
;
3070 if ((uri
= get_uri(t
->wv
)) == NULL
) {
3071 show_oops(t
, "Invalid URI");
3075 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
3076 show_oops(t
, "Invalid certidicate URI: %s", uri
);
3081 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3082 show_oops(t
, "Start TLS failed");
3087 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3088 show_oops(t
, "get_connection_certs failed");
3092 if (args
->i
& XT_SHOW
)
3093 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3094 else if (args
->i
& XT_SAVE
)
3095 save_certs(t
, certs
, cert_count
, domain
);
3097 free_connection_certs(certs
, cert_count
);
3099 /* we close the socket first for speed */
3102 stop_tls(gsession
, xcred
);
3108 remove_cookie(int index
)
3114 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3116 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3118 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3122 print_cookie("remove cookie", c
);
3123 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3128 soup_cookies_free(cf
);
3134 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3137 char *tmp
, *header
, *body
, *footer
;
3139 /* we set this to indicate we want to manually do navaction */
3140 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3142 header
= g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3144 footer
= g_strdup("</body></html>");
3145 body
= g_strdup("");
3148 if (args
->i
& XT_WL_PERSISTENT
) {
3150 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3152 RB_FOREACH(d
, domain_list
, wl
) {
3156 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3162 if (args
->i
& XT_WL_SESSION
) {
3164 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3166 RB_FOREACH(d
, domain_list
, wl
) {
3170 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3175 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3180 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3182 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3188 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3190 char file
[PATH_MAX
];
3192 char *line
= NULL
, *lt
= NULL
;
3195 char *dom
= NULL
, *dom_save
= NULL
;
3201 if (t
== NULL
|| args
== NULL
)
3204 if (runtime_settings
[0] == '\0')
3207 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3208 if ((f
= fopen(file
, "r+")) == NULL
)
3211 uri
= get_uri(t
->wv
);
3212 dom
= find_domain(uri
, 1);
3213 if (uri
== NULL
|| dom
== NULL
) {
3214 show_oops(t
, "Can't add domain to %s white list",
3215 js
? "JavaScript" : "cookie");
3219 if (args
->i
& XT_WL_TOPLEVEL
) {
3221 if ((dom_save
= get_toplevel_domain(dom
)) == NULL
) {
3222 show_oops(t
, "invalid domain: %s", dom
);
3225 } else if (args
->i
& XT_WL_FQDN
) {
3231 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom_save
);
3234 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3237 if (!strcmp(line
, lt
))
3243 fprintf(f
, "%s\n", lt
);
3248 d
= wl_find(dom_save
, &js_wl
);
3250 settings_add("js_wl", dom_save
);
3251 d
= wl_find(dom_save
, &js_wl
);
3255 d
= wl_find(dom_save
, &c_wl
);
3257 settings_add("cookie_wl", dom_save
);
3258 d
= wl_find(dom_save
, &c_wl
);
3262 /* find and add to persistent jar */
3263 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3264 for (;cf
; cf
= cf
->next
) {
3266 if (!strcmp(dom_save
, ci
->domain
) ||
3267 !strcmp(&dom_save
[1], ci
->domain
)) /* deal with leading . */ {
3268 c
= soup_cookie_copy(ci
);
3269 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3272 soup_cookies_free(cf
);
3290 js_show_wl(struct tab
*t
, struct karg
*args
)
3292 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3293 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3299 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3301 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3302 wl_show(t
, args
, "Cookie White List", &c_wl
);
3308 cookie_cmd(struct tab
*t
, struct karg
*args
)
3310 if (args
->i
& XT_SHOW
)
3311 wl_show(t
, args
, "Cookie White List", &c_wl
);
3312 else if (args
->i
& XT_WL_TOGGLE
)
3313 toggle_cwl(t
, args
);
3314 else if (args
->i
& XT_SAVE
)
3315 wl_save(t
, args
, 0);
3316 else if (args
->i
& XT_DELETE
)
3317 show_oops(t
, "'cookie delete' currently unimplemented");
3323 js_cmd(struct tab
*t
, struct karg
*args
)
3325 if (args
->i
& XT_SHOW
)
3326 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3327 else if (args
->i
& XT_SAVE
)
3328 wl_save(t
, args
, 1);
3329 else if (args
->i
& XT_WL_TOGGLE
)
3331 else if (args
->i
& XT_DELETE
)
3332 show_oops(t
, "'js delete' currently unimplemented");
3338 add_favorite(struct tab
*t
, struct karg
*args
)
3340 char file
[PATH_MAX
];
3343 size_t urilen
, linelen
;
3344 const gchar
*uri
, *title
;
3349 /* don't allow adding of xtp pages to favorites */
3350 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3351 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3355 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3356 if ((f
= fopen(file
, "r+")) == NULL
) {
3357 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3361 title
= webkit_web_view_get_title(t
->wv
);
3362 uri
= get_uri(t
->wv
);
3367 if (title
== NULL
|| uri
== NULL
) {
3368 show_oops(t
, "can't add page to favorites");
3372 urilen
= strlen(uri
);
3375 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3376 if (feof(f
) || ferror(f
))
3379 if (linelen
== urilen
&& !strcmp(line
, uri
))
3386 fprintf(f
, "\n%s\n%s", title
, uri
);
3392 update_favorite_tabs(NULL
);
3398 navaction(struct tab
*t
, struct karg
*args
)
3400 WebKitWebHistoryItem
*item
;
3402 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3403 t
->tab_id
, args
->i
);
3406 if (args
->i
== XT_NAV_BACK
)
3407 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3409 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3411 return (XT_CB_PASSTHROUGH
);
3412 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
3414 return (XT_CB_PASSTHROUGH
);
3419 webkit_web_view_go_back(t
->wv
);
3421 case XT_NAV_FORWARD
:
3422 webkit_web_view_go_forward(t
->wv
);
3425 webkit_web_view_reload(t
->wv
);
3427 case XT_NAV_RELOAD_CACHE
:
3428 webkit_web_view_reload_bypass_cache(t
->wv
);
3431 return (XT_CB_PASSTHROUGH
);
3435 move(struct tab
*t
, struct karg
*args
)
3437 GtkAdjustment
*adjust
;
3438 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3443 case XT_MOVE_BOTTOM
:
3445 case XT_MOVE_PAGEDOWN
:
3446 case XT_MOVE_PAGEUP
:
3447 case XT_MOVE_HALFDOWN
:
3448 case XT_MOVE_HALFUP
:
3449 adjust
= t
->adjust_v
;
3452 adjust
= t
->adjust_h
;
3456 pos
= gtk_adjustment_get_value(adjust
);
3457 ps
= gtk_adjustment_get_page_size(adjust
);
3458 upper
= gtk_adjustment_get_upper(adjust
);
3459 lower
= gtk_adjustment_get_lower(adjust
);
3460 si
= gtk_adjustment_get_step_increment(adjust
);
3461 pi
= gtk_adjustment_get_page_increment(adjust
);
3464 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3465 "max %f si %f pi %f\n",
3466 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
3467 pos
, ps
, upper
, lower
, max
, si
, pi
);
3473 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3478 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3480 case XT_MOVE_BOTTOM
:
3481 case XT_MOVE_FARRIGHT
:
3482 gtk_adjustment_set_value(adjust
, max
);
3485 case XT_MOVE_FARLEFT
:
3486 gtk_adjustment_set_value(adjust
, lower
);
3488 case XT_MOVE_PAGEDOWN
:
3490 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3492 case XT_MOVE_PAGEUP
:
3494 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3496 case XT_MOVE_HALFDOWN
:
3498 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3500 case XT_MOVE_HALFUP
:
3502 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3505 return (XT_CB_PASSTHROUGH
);
3508 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3510 return (XT_CB_HANDLED
);
3514 url_set_visibility(void)
3518 TAILQ_FOREACH(t
, &tabs
, entry
) {
3519 if (show_url
== 0) {
3520 gtk_widget_hide(t
->toolbar
);
3523 gtk_widget_show(t
->toolbar
);
3528 notebook_tab_set_visibility(GtkNotebook
*notebook
)
3531 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3533 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3537 statusbar_set_visibility(void)
3541 TAILQ_FOREACH(t
, &tabs
, entry
) {
3542 if (show_statusbar
== 0) {
3543 gtk_widget_hide(t
->statusbar
);
3546 gtk_widget_show(t
->statusbar
);
3551 url_set(struct tab
*t
, int enable_url_entry
)
3556 show_url
= enable_url_entry
;
3558 if (enable_url_entry
) {
3559 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
3560 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3561 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
), 0);
3563 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
3564 GTK_ENTRY_ICON_PRIMARY
);
3566 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
3567 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
3568 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
3569 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
3575 fullscreen(struct tab
*t
, struct karg
*args
)
3577 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3580 return (XT_CB_PASSTHROUGH
);
3582 if (show_url
== 0) {
3590 url_set_visibility();
3591 notebook_tab_set_visibility(notebook
);
3593 return (XT_CB_HANDLED
);
3597 statusaction(struct tab
*t
, struct karg
*args
)
3599 int rv
= XT_CB_HANDLED
;
3601 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3604 return (XT_CB_PASSTHROUGH
);
3607 case XT_STATUSBAR_SHOW
:
3608 if (show_statusbar
== 0) {
3610 statusbar_set_visibility();
3613 case XT_STATUSBAR_HIDE
:
3614 if (show_statusbar
== 1) {
3616 statusbar_set_visibility();
3624 urlaction(struct tab
*t
, struct karg
*args
)
3626 int rv
= XT_CB_HANDLED
;
3628 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3631 return (XT_CB_PASSTHROUGH
);
3635 if (show_url
== 0) {
3637 url_set_visibility();
3641 if (show_url
== 1) {
3643 url_set_visibility();
3651 tabaction(struct tab
*t
, struct karg
*args
)
3653 int rv
= XT_CB_HANDLED
;
3654 char *url
= args
->s
;
3657 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
3660 return (XT_CB_PASSTHROUGH
);
3664 if (strlen(url
) > 0)
3665 create_new_tab(url
, NULL
, 1);
3667 create_new_tab(NULL
, NULL
, 1);
3672 case XT_TAB_DELQUIT
:
3673 if (gtk_notebook_get_n_pages(notebook
) > 1)
3679 if (strlen(url
) > 0)
3682 rv
= XT_CB_PASSTHROUGH
;
3688 if (show_tabs
== 0) {
3690 notebook_tab_set_visibility(notebook
);
3694 if (show_tabs
== 1) {
3696 notebook_tab_set_visibility(notebook
);
3699 case XT_TAB_UNDO_CLOSE
:
3700 if (undo_count
== 0) {
3701 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3705 u
= TAILQ_FIRST(&undos
);
3706 create_new_tab(u
->uri
, u
, 1);
3708 TAILQ_REMOVE(&undos
, u
, entry
);
3710 /* u->history is freed in create_new_tab() */
3715 rv
= XT_CB_PASSTHROUGH
;
3729 resizetab(struct tab
*t
, struct karg
*args
)
3731 if (t
== NULL
|| args
== NULL
) {
3732 show_oops_s("resizetab invalid parameters");
3733 return (XT_CB_PASSTHROUGH
);
3736 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
3737 t
->tab_id
, args
->i
);
3739 adjustfont_webkit(t
, args
->i
);
3741 return (XT_CB_HANDLED
);
3745 movetab(struct tab
*t
, struct karg
*args
)
3750 if (t
== NULL
|| args
== NULL
) {
3751 show_oops_s("movetab invalid parameters");
3752 return (XT_CB_PASSTHROUGH
);
3755 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
3756 t
->tab_id
, args
->i
);
3758 if (args
->i
== XT_TAB_INVALID
)
3759 return (XT_CB_PASSTHROUGH
);
3761 if (args
->i
< XT_TAB_INVALID
) {
3762 /* next or previous tab */
3763 if (TAILQ_EMPTY(&tabs
))
3764 return (XT_CB_PASSTHROUGH
);
3768 /* if at the last page, loop around to the first */
3769 if (gtk_notebook_get_current_page(notebook
) ==
3770 gtk_notebook_get_n_pages(notebook
) - 1)
3771 gtk_notebook_set_current_page(notebook
, 0);
3773 gtk_notebook_next_page(notebook
);
3776 /* if at the first page, loop around to the last */
3777 if (gtk_notebook_current_page(notebook
) == 0)
3778 gtk_notebook_set_current_page(notebook
,
3779 gtk_notebook_get_n_pages(notebook
) - 1);
3781 gtk_notebook_prev_page(notebook
);
3784 gtk_notebook_set_current_page(notebook
, 0);
3787 gtk_notebook_set_current_page(notebook
, -1);
3790 return (XT_CB_PASSTHROUGH
);
3793 return (XT_CB_HANDLED
);
3798 if (t
->tab_id
== x
) {
3799 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
3800 return (XT_CB_HANDLED
);
3803 TAILQ_FOREACH(tt
, &tabs
, entry
) {
3804 if (tt
->tab_id
== x
) {
3805 gtk_notebook_set_current_page(notebook
, x
);
3806 DNPRINTF(XT_D_TAB
, "movetab: going to %d\n", x
);
3812 return (XT_CB_HANDLED
);
3816 command(struct tab
*t
, struct karg
*args
)
3818 char *s
= NULL
, *ss
= NULL
;
3822 if (t
== NULL
|| args
== NULL
) {
3823 show_oops_s("command invalid parameters");
3824 return (XT_CB_PASSTHROUGH
);
3843 case XT_CMD_OPEN_CURRENT
:
3846 case XT_CMD_TABNEW_CURRENT
:
3847 if (!s
) /* FALL THROUGH? */
3849 if ((uri
= get_uri(t
->wv
)) != NULL
) {
3850 ss
= g_strdup_printf("%s%s", s
, uri
);
3855 show_oops(t
, "command: invalid opcode %d", args
->i
);
3856 return (XT_CB_PASSTHROUGH
);
3859 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
3861 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
3862 gdk_color_parse(XT_COLOR_WHITE
, &color
);
3863 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
3865 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
3866 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
3871 return (XT_CB_HANDLED
);
3875 * Return a new string with a download row (in html)
3876 * appended. Old string is freed.
3879 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
3882 WebKitDownloadStatus stat
;
3883 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
3885 char cur_sz
[FMT_SCALED_STRSIZE
];
3886 char tot_sz
[FMT_SCALED_STRSIZE
];
3889 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
3891 /* All actions wil take this form:
3892 * xxxt://class/seskey
3894 xtp_prefix
= g_strdup_printf("%s%d/%s/",
3895 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
3897 stat
= webkit_download_get_status(dl
->download
);
3900 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
3901 status_html
= g_strdup_printf("Finished");
3902 cmd_html
= g_strdup_printf(
3903 "<a href='%s%d/%d'>Remove</a>",
3904 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3906 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
3907 /* gather size info */
3908 progress
= 100 * webkit_download_get_progress(dl
->download
);
3911 webkit_download_get_current_size(dl
->download
), cur_sz
);
3913 webkit_download_get_total_size(dl
->download
), tot_sz
);
3915 status_html
= g_strdup_printf(
3916 "<div style='width: 100%%' align='center'>"
3917 "<div class='progress-outer'>"
3918 "<div class='progress-inner' style='width: %.2f%%'>"
3919 "</div></div></div>"
3920 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3921 progress
, cur_sz
, tot_sz
, progress
);
3923 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3924 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3928 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
3929 status_html
= g_strdup_printf("Cancelled");
3930 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3931 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3933 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
3934 status_html
= g_strdup_printf("Error!");
3935 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3936 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3938 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
3939 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3940 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3941 status_html
= g_strdup_printf("Starting");
3944 show_oops(t
, "%s: unknown download status", __func__
);
3947 new_html
= g_strdup_printf(
3948 "%s\n<tr><td>%s</td><td>%s</td>"
3949 "<td style='text-align:center'>%s</td></tr>\n",
3950 html
, basename(webkit_download_get_destination_uri(dl
->download
)),
3951 status_html
, cmd_html
);
3955 g_free(status_html
);
3966 * update all download tabs apart from one. Pass NULL if
3967 * you want to update all.
3970 update_download_tabs(struct tab
*apart_from
)
3973 if (!updating_dl_tabs
) {
3974 updating_dl_tabs
= 1; /* stop infinite recursion */
3975 TAILQ_FOREACH(t
, &tabs
, entry
)
3976 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
3977 && (t
!= apart_from
))
3978 xtp_page_dl(t
, NULL
);
3979 updating_dl_tabs
= 0;
3984 * update all cookie tabs apart from one. Pass NULL if
3985 * you want to update all.
3988 update_cookie_tabs(struct tab
*apart_from
)
3991 if (!updating_cl_tabs
) {
3992 updating_cl_tabs
= 1; /* stop infinite recursion */
3993 TAILQ_FOREACH(t
, &tabs
, entry
)
3994 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
3995 && (t
!= apart_from
))
3996 xtp_page_cl(t
, NULL
);
3997 updating_cl_tabs
= 0;
4002 * update all history tabs apart from one. Pass NULL if
4003 * you want to update all.
4006 update_history_tabs(struct tab
*apart_from
)
4010 if (!updating_hl_tabs
) {
4011 updating_hl_tabs
= 1; /* stop infinite recursion */
4012 TAILQ_FOREACH(t
, &tabs
, entry
)
4013 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4014 && (t
!= apart_from
))
4015 xtp_page_hl(t
, NULL
);
4016 updating_hl_tabs
= 0;
4020 /* cookie management XTP page */
4022 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4024 char *header
, *body
, *footer
, *page
, *tmp
;
4025 int i
= 1; /* all ids start 1 */
4026 GSList
*sc
, *pc
, *pc_start
;
4028 char *type
, *table_headers
;
4029 char *last_domain
= strdup("");
4031 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4034 show_oops_s("%s invalid parameters", __func__
);
4037 /* mark this tab as cookie jar */
4038 t
->xtp_meaning
= XT_XTP_TAB_MEANING_CL
;
4040 /* Generate a new session key */
4041 if (!updating_cl_tabs
)
4042 generate_xtp_session_key(&cl_session_key
);
4045 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4046 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4047 "</head><body><h1>Cookie Jar</h1>\n");
4050 table_headers
= g_strdup_printf("<div align='center'><table><tr>"
4057 "<th>HTTP<br />only</th>"
4058 "<th>Rm</th></tr>\n");
4060 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4061 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4065 for (; sc
; sc
= sc
->next
) {
4068 if (strcmp(last_domain
, c
->domain
) != 0) {
4071 last_domain
= strdup(c
->domain
);
4075 body
= g_strdup_printf("%s</table></div>"
4077 body
, c
->domain
, table_headers
);
4081 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4082 c
->domain
, table_headers
);
4087 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4088 if (soup_cookie_equal(pc
->data
, c
)) {
4089 type
= "Session + Persistent";
4094 body
= g_strdup_printf(
4096 "<td style='width: text-align: center'>%s</td>"
4097 "<td style='width: 1px'>%s</td>"
4098 "<td style='width=70%%;overflow: visible'>"
4099 " <textarea rows='4'>%s</textarea>"
4103 "<td style='width: 1px; text-align: center'>%d</td>"
4104 "<td style='width: 1px; text-align: center'>%d</td>"
4105 "<td style='width: 1px; text-align: center'>"
4106 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4113 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4128 soup_cookies_free(sc
);
4129 soup_cookies_free(pc
);
4131 /* small message if there are none */
4133 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4134 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4138 footer
= g_strdup_printf("</table></div></body></html>");
4140 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4145 g_free(table_headers
);
4146 g_free(last_domain
);
4148 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4149 update_cookie_tabs(t
);
4157 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4159 char *header
, *body
, *footer
, *page
, *tmp
;
4161 int i
= 1; /* all ids start 1 */
4163 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4166 show_oops_s("%s invalid parameters", __func__
);
4170 /* mark this tab as history manager */
4171 t
->xtp_meaning
= XT_XTP_TAB_MEANING_HL
;
4173 /* Generate a new session key */
4174 if (!updating_hl_tabs
)
4175 generate_xtp_session_key(&hl_session_key
);
4178 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
4179 "<title>History</title>\n"
4182 "<h1>History</h1>\n",
4186 body
= g_strdup_printf("<div align='center'><table><tr>"
4187 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4189 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4191 body
= g_strdup_printf(
4193 "<td><a href='%s'>%s</a></td>"
4195 "<td style='text-align: center'>"
4196 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4197 body
, h
->uri
, h
->uri
, h
->title
,
4198 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4199 XT_XTP_HL_REMOVE
, i
);
4205 /* small message if there are none */
4208 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4209 "colspan='3'>No History</td></tr>\n", body
);
4214 footer
= g_strdup_printf("</table></div></body></html>");
4216 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4219 * update all history manager tabs as the xtp session
4220 * key has now changed. No need to update the current tab.
4221 * Already did that above.
4223 update_history_tabs(t
);
4229 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4236 * Generate a web page detailing the status of any downloads
4239 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4241 struct download
*dl
;
4242 char *header
, *body
, *footer
, *page
, *tmp
;
4246 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4249 show_oops_s("%s invalid parameters", __func__
);
4252 /* mark as a download manager tab */
4253 t
->xtp_meaning
= XT_XTP_TAB_MEANING_DL
;
4256 * Generate a new session key for next page instance.
4257 * This only happens for the top level call to xtp_page_dl()
4258 * in which case updating_dl_tabs is 0.
4260 if (!updating_dl_tabs
)
4261 generate_xtp_session_key(&dl_session_key
);
4263 /* header - with refresh so as to update */
4264 if (refresh_interval
>= 1)
4265 ref
= g_strdup_printf(
4266 "<meta http-equiv='refresh' content='%u"
4267 ";url=%s%d/%s/%d' />\n",
4277 header
= g_strdup_printf(
4279 "<title>Downloads</title>\n%s%s</head>\n",
4280 XT_DOCTYPE XT_HTML_TAG
,
4284 body
= g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4285 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4286 "</p><table><tr><th style='width: 60%%'>"
4287 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4288 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4290 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4291 body
= xtp_page_dl_row(t
, body
, dl
);
4295 /* message if no downloads in list */
4298 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4299 " style='text-align: center'>"
4300 "No downloads</td></tr>\n", body
);
4305 footer
= g_strdup_printf("</table></div></body></html>");
4307 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4311 * update all download manager tabs as the xtp session
4312 * key has now changed. No need to update the current tab.
4313 * Already did that above.
4315 update_download_tabs(t
);
4322 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4329 search(struct tab
*t
, struct karg
*args
)
4333 if (t
== NULL
|| args
== NULL
) {
4334 show_oops_s("search invalid parameters");
4337 if (t
->search_text
== NULL
) {
4338 if (global_search
== NULL
)
4339 return (XT_CB_PASSTHROUGH
);
4341 t
->search_text
= g_strdup(global_search
);
4342 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4343 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4347 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4348 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4351 case XT_SEARCH_NEXT
:
4352 d
= t
->search_forward
;
4354 case XT_SEARCH_PREV
:
4355 d
= !t
->search_forward
;
4358 return (XT_CB_PASSTHROUGH
);
4361 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4363 return (XT_CB_HANDLED
);
4366 struct settings_args
{
4372 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4375 struct settings_args
*sa
= cb_args
;
4380 if (s
->flags
& XT_SF_RUNTIME
)
4386 *sa
->body
= g_strdup_printf(
4388 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4389 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4401 set(struct tab
*t
, struct karg
*args
)
4403 char *header
, *body
, *footer
, *page
, *tmp
;
4405 struct settings_args sa
;
4407 bzero(&sa
, sizeof sa
);
4411 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4412 "\n<head><title>Settings</title>\n"
4413 "</head><body><h1>Settings</h1>\n");
4416 body
= g_strdup_printf("<div align='center'><table><tr>"
4417 "<th align='left'>Setting</th>"
4418 "<th align='left'>Value</th></tr>\n");
4420 settings_walk(print_setting
, &sa
);
4423 /* small message if there are none */
4426 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4427 "colspan='2'>No settings</td></tr>\n", body
);
4432 footer
= g_strdup_printf("</table></div></body></html>");
4434 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4440 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4442 return (XT_CB_PASSTHROUGH
);
4446 session_save(struct tab
*t
, char *filename
)
4451 if (strlen(filename
) == 0)
4454 if (filename
[0] == '.' || filename
[0] == '/')
4458 if (save_tabs(t
, &a
))
4460 strlcpy(named_session
, filename
, sizeof named_session
);
4468 session_open(struct tab
*t
, char *filename
)
4473 if (strlen(filename
) == 0)
4476 if (filename
[0] == '.' || filename
[0] == '/')
4480 a
.i
= XT_SES_CLOSETABS
;
4481 if (open_tabs(t
, &a
))
4484 strlcpy(named_session
, filename
, sizeof named_session
);
4492 session_delete(struct tab
*t
, char *filename
)
4494 char file
[PATH_MAX
];
4497 if (strlen(filename
) == 0)
4500 if (filename
[0] == '.' || filename
[0] == '/')
4503 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
4507 if (!strcmp(filename
, named_session
))
4508 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
4509 sizeof named_session
);
4517 session_cmd(struct tab
*t
, struct karg
*args
)
4519 char *filename
= args
->s
;
4524 if (args
->i
& XT_SHOW
)
4525 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
4526 XT_SAVED_TABS_FILE
: named_session
);
4527 else if (args
->i
& XT_SAVE
) {
4528 if (session_save(t
, filename
)) {
4529 show_oops(t
, "Can't save session: %s",
4530 filename
? filename
: "INVALID");
4533 } else if (args
->i
& XT_OPEN
) {
4534 if (session_open(t
, filename
)) {
4535 show_oops(t
, "Can't open session: %s",
4536 filename
? filename
: "INVALID");
4539 } else if (args
->i
& XT_DELETE
) {
4540 if (session_delete(t
, filename
)) {
4541 show_oops(t
, "Can't delete session: %s",
4542 filename
? filename
: "INVALID");
4547 return (XT_CB_PASSTHROUGH
);
4551 * Make a hardcopy of the page
4554 print_page(struct tab
*t
, struct karg
*args
)
4556 WebKitWebFrame
*frame
;
4558 GtkPrintOperation
*op
;
4559 GtkPrintOperationAction action
;
4560 GtkPrintOperationResult print_res
;
4561 GError
*g_err
= NULL
;
4562 int marg_l
, marg_r
, marg_t
, marg_b
;
4564 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
4566 ps
= gtk_page_setup_new();
4567 op
= gtk_print_operation_new();
4568 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
4569 frame
= webkit_web_view_get_main_frame(t
->wv
);
4571 /* the default margins are too small, so we will bump them */
4572 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
4573 XT_PRINT_EXTRA_MARGIN
;
4574 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
4575 XT_PRINT_EXTRA_MARGIN
;
4576 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
4577 XT_PRINT_EXTRA_MARGIN
;
4578 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
4579 XT_PRINT_EXTRA_MARGIN
;
4582 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
4583 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
4584 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
4585 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
4587 gtk_print_operation_set_default_page_setup(op
, ps
);
4589 /* this appears to free 'op' and 'ps' */
4590 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
4592 /* check it worked */
4593 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
4594 show_oops_s("can't print: %s", g_err
->message
);
4595 g_error_free (g_err
);
4603 go_home(struct tab
*t
, struct karg
*args
)
4610 restart(struct tab
*t
, struct karg
*args
)
4614 a
.s
= XT_RESTART_TABS_FILE
;
4616 execvp(start_argv
[0], start_argv
);
4622 #define CTRL GDK_CONTROL_MASK
4623 #define MOD1 GDK_MOD1_MASK
4624 #define SHFT GDK_SHIFT_MASK
4626 /* inherent to GTK not all keys will be caught at all times */
4627 /* XXX sort key bindings */
4628 struct key_binding
{
4633 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
4635 { "cookiejar", MOD1
, 0, GDK_j
},
4636 { "downloadmgr", MOD1
, 0, GDK_d
},
4637 { "history", MOD1
, 0, GDK_h
},
4638 { "print", CTRL
, 0, GDK_p
},
4639 { "search", 0, 0, GDK_slash
},
4640 { "searchb", 0, 0, GDK_question
},
4641 { "command", 0, 0, GDK_colon
},
4642 { "quit", CTRL
, 0, GDK_q
},
4643 { "restart", MOD1
, 0, GDK_q
},
4644 { "js toggle", CTRL
, 0, GDK_j
},
4645 { "cookie toggle", MOD1
, 0, GDK_c
},
4646 { "togglesrc", CTRL
, 0, GDK_s
},
4647 { "yankuri", 0, 0, GDK_y
},
4648 { "pasteuricur", 0, 0, GDK_p
},
4649 { "pasteurinew", 0, 0, GDK_P
},
4652 { "searchnext", 0, 0, GDK_n
},
4653 { "searchprevious", 0, 0, GDK_N
},
4656 { "focusaddress", 0, 0, GDK_F6
},
4657 { "focussearch", 0, 0, GDK_F7
},
4660 { "hinting", 0, 0, GDK_f
},
4662 /* custom stylesheet */
4663 { "userstyle", 0, 0, GDK_i
},
4666 { "goback", 0, 0, GDK_BackSpace
},
4667 { "goback", MOD1
, 0, GDK_Left
},
4668 { "goforward", SHFT
, 0, GDK_BackSpace
},
4669 { "goforward", MOD1
, 0, GDK_Right
},
4670 { "reload", 0, 0, GDK_F5
},
4671 { "reload", CTRL
, 0, GDK_r
},
4672 { "reloadforce", CTRL
, 0, GDK_R
},
4673 { "reload", CTRL
, 0, GDK_l
},
4674 { "favorites", MOD1
, 1, GDK_f
},
4676 /* vertical movement */
4677 { "scrolldown", 0, 0, GDK_j
},
4678 { "scrolldown", 0, 0, GDK_Down
},
4679 { "scrollup", 0, 0, GDK_Up
},
4680 { "scrollup", 0, 0, GDK_k
},
4681 { "scrollbottom", 0, 0, GDK_G
},
4682 { "scrollbottom", 0, 0, GDK_End
},
4683 { "scrolltop", 0, 0, GDK_Home
},
4684 { "scrolltop", 0, 0, GDK_g
},
4685 { "scrollpagedown", 0, 0, GDK_space
},
4686 { "scrollpagedown", CTRL
, 0, GDK_f
},
4687 { "scrollhalfdown", CTRL
, 0, GDK_d
},
4688 { "scrollpagedown", 0, 0, GDK_Page_Down
},
4689 { "scrollpageup", 0, 0, GDK_Page_Up
},
4690 { "scrollpageup", CTRL
, 0, GDK_b
},
4691 { "scrollhalfup", CTRL
, 0, GDK_u
},
4692 /* horizontal movement */
4693 { "scrollright", 0, 0, GDK_l
},
4694 { "scrollright", 0, 0, GDK_Right
},
4695 { "scrollleft", 0, 0, GDK_Left
},
4696 { "scrollleft", 0, 0, GDK_h
},
4697 { "scrollfarright", 0, 0, GDK_dollar
},
4698 { "scrollfarleft", 0, 0, GDK_0
},
4701 { "tabnew", CTRL
, 0, GDK_t
},
4702 { "tabclose", CTRL
, 1, GDK_w
},
4703 { "tabundoclose", 0, 0, GDK_U
},
4704 { "tabgoto1", CTRL
, 0, GDK_1
},
4705 { "tabgoto2", CTRL
, 0, GDK_2
},
4706 { "tabgoto3", CTRL
, 0, GDK_3
},
4707 { "tabgoto4", CTRL
, 0, GDK_4
},
4708 { "tabgoto5", CTRL
, 0, GDK_5
},
4709 { "tabgoto6", CTRL
, 0, GDK_6
},
4710 { "tabgoto7", CTRL
, 0, GDK_7
},
4711 { "tabgoto8", CTRL
, 0, GDK_8
},
4712 { "tabgoto9", CTRL
, 0, GDK_9
},
4713 { "tabgoto10", CTRL
, 0, GDK_0
},
4714 { "tabfirst", CTRL
, 0, GDK_less
},
4715 { "tablast", CTRL
, 0, GDK_greater
},
4716 { "tabprevious", CTRL
, 0, GDK_Left
},
4717 { "tabnext", CTRL
, 0, GDK_Right
},
4718 { "focusout", CTRL
, 0, GDK_minus
},
4719 { "focusin", CTRL
, 0, GDK_plus
},
4720 { "focusin", CTRL
, 0, GDK_equal
},
4722 /* command aliases (handy when -S flag is used) */
4723 { "promptopen", 0, 0, GDK_F9
},
4724 { "promptopencurrent", 0, 0, GDK_F10
},
4725 { "prompttabnew", 0, 0, GDK_F11
},
4726 { "prompttabnewcurrent",0, 0, GDK_F12
},
4728 TAILQ_HEAD(keybinding_list
, key_binding
);
4731 walk_kb(struct settings
*s
,
4732 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
4734 struct key_binding
*k
;
4737 if (s
== NULL
|| cb
== NULL
) {
4738 show_oops_s("walk_kb invalid parameters");
4742 TAILQ_FOREACH(k
, &kbl
, entry
) {
4748 if (gdk_keyval_name(k
->key
) == NULL
)
4751 strlcat(str
, k
->cmd
, sizeof str
);
4752 strlcat(str
, ",", sizeof str
);
4754 if (k
->mask
& GDK_SHIFT_MASK
)
4755 strlcat(str
, "S-", sizeof str
);
4756 if (k
->mask
& GDK_CONTROL_MASK
)
4757 strlcat(str
, "C-", sizeof str
);
4758 if (k
->mask
& GDK_MOD1_MASK
)
4759 strlcat(str
, "M1-", sizeof str
);
4760 if (k
->mask
& GDK_MOD2_MASK
)
4761 strlcat(str
, "M2-", sizeof str
);
4762 if (k
->mask
& GDK_MOD3_MASK
)
4763 strlcat(str
, "M3-", sizeof str
);
4764 if (k
->mask
& GDK_MOD4_MASK
)
4765 strlcat(str
, "M4-", sizeof str
);
4766 if (k
->mask
& GDK_MOD5_MASK
)
4767 strlcat(str
, "M5-", sizeof str
);
4769 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
4770 cb(s
, str
, cb_args
);
4774 init_keybindings(void)
4777 struct key_binding
*k
;
4779 for (i
= 0; i
< LENGTH(keys
); i
++) {
4780 k
= g_malloc0(sizeof *k
);
4781 k
->cmd
= keys
[i
].cmd
;
4782 k
->mask
= keys
[i
].mask
;
4783 k
->use_in_entry
= keys
[i
].use_in_entry
;
4784 k
->key
= keys
[i
].key
;
4785 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4787 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
4788 k
->cmd
? k
->cmd
: "unnamed key");
4793 keybinding_clearall(void)
4795 struct key_binding
*k
, *next
;
4797 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
4798 next
= TAILQ_NEXT(k
, entry
);
4802 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
4803 k
->cmd
? k
->cmd
: "unnamed key");
4804 TAILQ_REMOVE(&kbl
, k
, entry
);
4810 keybinding_add(char *kb
, char *value
, struct key_binding
*orig
)
4812 struct key_binding
*k
;
4813 guint keyval
, mask
= 0;
4816 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s %s\n", kb
, value
, orig
->cmd
);
4820 if (strcmp(kb
, orig
->cmd
))
4823 /* find modifier keys */
4824 if (strstr(value
, "S-"))
4825 mask
|= GDK_SHIFT_MASK
;
4826 if (strstr(value
, "C-"))
4827 mask
|= GDK_CONTROL_MASK
;
4828 if (strstr(value
, "M1-"))
4829 mask
|= GDK_MOD1_MASK
;
4830 if (strstr(value
, "M2-"))
4831 mask
|= GDK_MOD2_MASK
;
4832 if (strstr(value
, "M3-"))
4833 mask
|= GDK_MOD3_MASK
;
4834 if (strstr(value
, "M4-"))
4835 mask
|= GDK_MOD4_MASK
;
4836 if (strstr(value
, "M5-"))
4837 mask
|= GDK_MOD5_MASK
;
4840 for (i
= strlen(value
) - 1; i
> 0; i
--)
4841 if (value
[i
] == '-')
4842 value
= &value
[i
+ 1];
4844 /* validate keyname */
4845 keyval
= gdk_keyval_from_name(value
);
4846 if (keyval
== GDK_VoidSymbol
) {
4847 warnx("invalid keybinding name %s", value
);
4850 /* must run this test too, gtk+ doesn't handle 10 for example */
4851 if (gdk_keyval_name(keyval
) == NULL
) {
4852 warnx("invalid keybinding name %s", value
);
4856 /* make sure it isn't a dupe */
4857 TAILQ_FOREACH(k
, &kbl
, entry
)
4858 if (k
->key
== keyval
&& k
->mask
== mask
) {
4859 warnx("duplicate keybinding for %s", value
);
4864 k
= g_malloc0(sizeof *k
);
4867 k
->use_in_entry
= orig
->use_in_entry
;
4870 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
4875 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
4876 k
->name
, gdk_keyval_name(keyval
));
4878 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4884 add_kb(struct settings
*s
, char *entry
)
4889 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
4891 /* clearall is special */
4892 if (!strcmp(entry
, "clearall")) {
4893 keybinding_clearall();
4897 kb
= strstr(entry
, ",");
4903 /* make sure it is a valid keybinding */
4904 for (i
= 0; i
< LENGTH(keys
); i
++)
4905 if (keys
[i
].cmd
&& !strcmp(entry
, keys
[i
].cmd
)) {
4906 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s 0x%x %d 0x%x\n",
4909 keys
[i
].use_in_entry
,
4912 return (keybinding_add(entry
, value
, &keys
[i
]));
4921 int (*func
)(struct tab
*, struct karg
*);
4923 bool userarg
; /* allow free text arg */
4925 { "command", 0, command
, {.i
= ':'}, FALSE
},
4926 { "search", 0, command
, {.i
= '/'}, FALSE
},
4927 { "searchb", 0, command
, {.i
= '?'}, FALSE
},
4928 { "togglesrc", 0, toggle_src
, {0}, FALSE
},
4930 /* yanking and pasting */
4931 { "yankuri", 0, yank_uri
, {0}, FALSE
},
4932 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4933 { "pasteuricur", 0, paste_uri
, {.i
= XT_PASTE_CURRENT_TAB
}, FALSE
},
4934 { "pasteurinew", 0, paste_uri
, {.i
= XT_PASTE_NEW_TAB
}, FALSE
},
4937 { "searchnext", 0, search
, {.i
= XT_SEARCH_NEXT
}, FALSE
},
4938 { "searchprevious", 0, search
, {.i
= XT_SEARCH_PREV
}, FALSE
},
4939 { "searchprev", 0, search
, {.i
= XT_SEARCH_PREV
}, FALSE
},
4942 { "focusaddress", 0, focus
, {.i
= XT_FOCUS_URI
}, FALSE
},
4943 { "focussearch", 0, focus
, {.i
= XT_FOCUS_SEARCH
}, FALSE
},
4946 { "hinting", 0, hint
, {.i
= 0}, FALSE
},
4948 /* custom stylesheet */
4949 { "userstyle", 0, userstyle
, {.i
= 0 }, FALSE
},
4952 { "goback", 0, navaction
, {.i
= XT_NAV_BACK
}, FALSE
},
4953 { "goforward", 0, navaction
, {.i
= XT_NAV_FORWARD
}, FALSE
},
4954 { "reload", 0, navaction
, {.i
= XT_NAV_RELOAD
}, FALSE
},
4955 { "reloadforce", 0, navaction
, {.i
= XT_NAV_RELOAD_CACHE
}, FALSE
},
4957 /* vertical movement */
4958 { "scrolldown", 0, move
, {.i
= XT_MOVE_DOWN
}, FALSE
},
4959 { "scrollup", 0, move
, {.i
= XT_MOVE_UP
}, FALSE
},
4960 { "scrollbottom", 0, move
, {.i
= XT_MOVE_BOTTOM
}, FALSE
},
4961 { "scrolltop", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4962 { "1", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4963 { "scrollhalfdown", 0, move
, {.i
= XT_MOVE_HALFDOWN
},FALSE
},
4964 { "scrollhalfup", 0, move
, {.i
= XT_MOVE_HALFUP
}, FALSE
},
4965 { "scrollpagedown", 0, move
, {.i
= XT_MOVE_PAGEDOWN
},FALSE
},
4966 { "scrollpageup", 0, move
, {.i
= XT_MOVE_PAGEUP
}, FALSE
},
4967 /* horizontal movement */
4968 { "scrollright", 0, move
, {.i
= XT_MOVE_RIGHT
}, FALSE
},
4969 { "scrollleft", 0, move
, {.i
= XT_MOVE_LEFT
}, FALSE
},
4970 { "scrollfarright", 0, move
, {.i
= XT_MOVE_FARRIGHT
},FALSE
},
4971 { "scrollfarleft", 0, move
, {.i
= XT_MOVE_FARLEFT
}, FALSE
},
4974 { "favorites", 0, xtp_page_fl
, {0}, FALSE
},
4975 { "fav", 0, xtp_page_fl
, {0}, FALSE
},
4976 { "favadd", 0, add_favorite
, {0}, FALSE
},
4978 { "quit", 0, quit
, {0}, FALSE
},
4979 { "q!", 0, quit
, {0}, FALSE
},
4980 { "qa", 0, quit
, {0}, FALSE
},
4981 { "qa!", 0, quit
, {0}, FALSE
},
4982 { "w", 0, save_tabs
, {0}, FALSE
},
4983 { "wq", 0, save_tabs_and_quit
, {0}, FALSE
},
4984 { "wq!", 0, save_tabs_and_quit
, {0}, FALSE
},
4985 { "help", 0, help
, {0}, FALSE
},
4986 { "about", 0, about
, {0}, FALSE
},
4987 { "stats", 0, stats
, {0}, FALSE
},
4988 { "version", 0, about
, {0}, FALSE
},
4989 { "cookiejar", 0, xtp_page_cl
, {0}, FALSE
},
4992 { "js", 0, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4993 { "save", 1, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4994 { "domain", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
4995 { "fqdn", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4996 { "show", 1, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4997 { "all", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4998 { "persistent", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
4999 { "session", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
5000 { "toggle", 1, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5001 { "domain", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5002 { "fqdn", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5004 /* cookie command */
5005 { "cookie", 0, cookie_cmd
, {0}, FALSE
},
5006 { "save", 1, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5007 { "domain", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
5008 { "fqdn", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5009 { "show", 1, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5010 { "all", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5011 { "persistent", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
5012 { "session", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
5013 { "toggle", 1, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5014 { "domain", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5015 { "fqdn", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5018 { "cert", 0, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5019 { "save", 1, cert_cmd
, {.i
= XT_SAVE
}, FALSE
},
5020 { "show", 1, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5022 { "ca", 0, ca_cmd
, {0}, FALSE
},
5023 { "downloadmgr", 0, xtp_page_dl
, {0}, FALSE
},
5024 { "dl", 0, xtp_page_dl
, {0}, FALSE
},
5025 { "h", 0, xtp_page_hl
, {0}, FALSE
},
5026 { "hist", 0, xtp_page_hl
, {0}, FALSE
},
5027 { "history", 0, xtp_page_hl
, {0}, FALSE
},
5028 { "home", 0, go_home
, {0}, FALSE
},
5029 { "restart", 0, restart
, {0}, FALSE
},
5030 { "urlhide", 0, urlaction
, {.i
= XT_URL_HIDE
}, FALSE
},
5031 { "urlh", 0, urlaction
, {.i
= XT_URL_HIDE
}, FALSE
},
5032 { "urlshow", 0, urlaction
, {.i
= XT_URL_SHOW
}, FALSE
},
5033 { "urls", 0, urlaction
, {.i
= XT_URL_SHOW
}, FALSE
},
5034 { "statushide", 0, statusaction
, {.i
= XT_STATUSBAR_HIDE
}, FALSE
},
5035 { "statush", 0, statusaction
, {.i
= XT_STATUSBAR_HIDE
}, FALSE
},
5036 { "statusshow", 0, statusaction
, {.i
= XT_STATUSBAR_SHOW
}, FALSE
},
5037 { "statuss", 0, statusaction
, {.i
= XT_STATUSBAR_SHOW
}, FALSE
},
5039 { "print", 0, print_page
, {0}, FALSE
},
5042 { "o", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5043 { "op", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5044 { "open", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5045 { "tabnew", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5046 { "tabedit", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5047 { "tabe", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5048 { "tabclose", 0, tabaction
, {.i
= XT_TAB_DELETE
}, FALSE
},
5049 { "tabundoclose", 0, tabaction
, {.i
= XT_TAB_UNDO_CLOSE
} },
5050 { "tabc", 0, tabaction
, {.i
= XT_TAB_DELETE
}, FALSE
},
5051 { "tabshow", 0, tabaction
, {.i
= XT_TAB_SHOW
}, FALSE
},
5052 { "tabs", 0, tabaction
, {.i
= XT_TAB_SHOW
}, FALSE
},
5053 { "tabhide", 0, tabaction
, {.i
= XT_TAB_HIDE
}, FALSE
},
5054 { "tabh", 0, tabaction
, {.i
= XT_TAB_HIDE
}, FALSE
},
5055 { "quit", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5056 { "q", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5057 /* XXX add count to these commands */
5058 { "tabfirst", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5059 { "tabfir", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5060 { "tabrewind", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5061 { "tabr", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5062 { "tablast", 0, movetab
, {.i
= XT_TAB_LAST
}, FALSE
},
5063 { "tabl", 0, movetab
, {.i
= XT_TAB_LAST
}, FALSE
},
5064 { "tabprevious", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5065 { "tabprev", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5066 { "tabp", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5067 { "tabnext", 0, movetab
, {.i
= XT_TAB_NEXT
}, FALSE
},
5068 { "tabn", 0, movetab
, {.i
= XT_TAB_NEXT
}, FALSE
},
5069 { "tabgoto1", 0, movetab
, {.i
= 1}, FALSE
},
5070 { "tabgoto2", 0, movetab
, {.i
= 2}, FALSE
},
5071 { "tabgoto3", 0, movetab
, {.i
= 3}, FALSE
},
5072 { "tabgoto4", 0, movetab
, {.i
= 4}, FALSE
},
5073 { "tabgoto5", 0, movetab
, {.i
= 5}, FALSE
},
5074 { "tabgoto6", 0, movetab
, {.i
= 6}, FALSE
},
5075 { "tabgoto7", 0, movetab
, {.i
= 7}, FALSE
},
5076 { "tabgoto8", 0, movetab
, {.i
= 8}, FALSE
},
5077 { "tabgoto9", 0, movetab
, {.i
= 9}, FALSE
},
5078 { "tabgoto10", 0, movetab
, {.i
= 10}, FALSE
},
5079 { "focusout", 0, resizetab
, {.i
= -1}, FALSE
},
5080 { "focusin", 0, resizetab
, {.i
= 1}, FALSE
},
5081 { "focusin", 0, resizetab
, {.i
= 1}, FALSE
},
5083 /* command aliases (handy when -S flag is used) */
5084 { "promptopen", 0, command
, {.i
= XT_CMD_OPEN
}, FALSE
},
5085 { "promptopencurrent", 0, command
, {.i
= XT_CMD_OPEN_CURRENT
}, FALSE
},
5086 { "prompttabnew", 0, command
, {.i
= XT_CMD_TABNEW
}, FALSE
},
5087 { "prompttabnewcurrent",0, command
, {.i
= XT_CMD_TABNEW_CURRENT
}, FALSE
},
5090 { "set", 0, set
, {0}, FALSE
},
5091 { "fullscreen", 0, fullscreen
, {0}, FALSE
},
5092 { "f", 0, fullscreen
, {0}, FALSE
},
5095 { "session", 0, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5096 { "delete", 1, session_cmd
, {.i
= XT_DELETE
}, TRUE
},
5097 { "open", 1, session_cmd
, {.i
= XT_OPEN
}, TRUE
},
5098 { "save", 1, session_cmd
, {.i
= XT_SAVE
}, TRUE
},
5099 { "show", 1, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5106 } cmd_status
= {-1, 0};
5109 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5115 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5121 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5123 a
.i
= XT_NAV_FORWARD
;
5133 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5135 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5137 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5144 * cancel, remove, etc. downloads
5147 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5149 struct download find
, *d
= NULL
;
5151 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5153 /* some commands require a valid download id */
5154 if (cmd
!= XT_XTP_DL_LIST
) {
5155 /* lookup download in question */
5157 d
= RB_FIND(download_list
, &downloads
, &find
);
5160 show_oops(t
, "%s: no such download", __func__
);
5165 /* decide what to do */
5167 case XT_XTP_DL_CANCEL
:
5168 webkit_download_cancel(d
->download
);
5170 case XT_XTP_DL_REMOVE
:
5171 webkit_download_cancel(d
->download
); /* just incase */
5172 g_object_unref(d
->download
);
5173 RB_REMOVE(download_list
, &downloads
, d
);
5175 case XT_XTP_DL_LIST
:
5179 show_oops(t
, "%s: unknown command", __func__
);
5182 xtp_page_dl(t
, NULL
);
5186 * Actions on history, only does one thing for now, but
5187 * we provide the function for future actions
5190 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5192 struct history
*h
, *next
;
5196 case XT_XTP_HL_REMOVE
:
5197 /* walk backwards, as listed in reverse */
5198 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5199 next
= RB_PREV(history_list
, &hl
, h
);
5201 RB_REMOVE(history_list
, &hl
, h
);
5202 g_free((gpointer
) h
->title
);
5203 g_free((gpointer
) h
->uri
);
5210 case XT_XTP_HL_LIST
:
5211 /* Nothing - just xtp_page_hl() below */
5214 show_oops(t
, "%s: unknown command", __func__
);
5218 xtp_page_hl(t
, NULL
);
5221 /* remove a favorite */
5223 remove_favorite(struct tab
*t
, int index
)
5225 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5226 char *new_favs
, *tmp
;
5231 /* open favorites */
5232 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5234 if ((f
= fopen(file
, "r")) == NULL
) {
5235 show_oops(t
, "%s: can't open favorites: %s",
5236 __func__
, strerror(errno
));
5240 /* build a string which will become the new favroites file */
5241 new_favs
= g_strdup_printf("%s", "");
5244 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5245 if (feof(f
) || ferror(f
))
5247 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5254 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5255 if (feof(f
) || ferror(f
)) {
5256 show_oops(t
, "%s: can't parse favorites %s",
5257 __func__
, strerror(errno
));
5262 /* as long as this isn't the one we are deleting add to file */
5265 new_favs
= g_strdup_printf("%s%s\n%s\n",
5266 new_favs
, title
, uri
);
5278 /* write back new favorites file */
5279 if ((f
= fopen(file
, "w")) == NULL
) {
5280 show_oops(t
, "%s: can't open favorites: %s",
5281 __func__
, strerror(errno
));
5285 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5298 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5301 case XT_XTP_FL_LIST
:
5302 /* nothing, just the below call to xtp_page_fl() */
5304 case XT_XTP_FL_REMOVE
:
5305 remove_favorite(t
, arg
);
5308 show_oops(t
, "%s: invalid favorites command", __func__
);
5312 xtp_page_fl(t
, NULL
);
5316 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5319 case XT_XTP_CL_LIST
:
5320 /* nothing, just xtp_page_cl() */
5322 case XT_XTP_CL_REMOVE
:
5326 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5330 xtp_page_cl(t
, NULL
);
5333 /* link an XTP class to it's session key and handler function */
5334 struct xtp_despatch
{
5337 void (*handle_func
)(struct tab
*, uint8_t, int);
5340 struct xtp_despatch xtp_despatches
[] = {
5341 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5342 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5343 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5344 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5345 { NULL
, NULL
, NULL
}
5349 * is the url xtp protocol? (xxxt://)
5350 * if so, parse and despatch correct bahvior
5353 parse_xtp_url(struct tab
*t
, const char *url
)
5355 char *dup
= NULL
, *p
, *last
;
5356 uint8_t n_tokens
= 0;
5357 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5358 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5362 * tokens array meaning:
5364 * tokens[1] = session key
5365 * tokens[2] = action
5366 * tokens[3] = optional argument
5369 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5371 /*xtp tab meaning is normal unless proven special */
5372 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5374 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5377 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5379 /* split out the url */
5380 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5381 (p
= strtok_r(NULL
, "/", &last
))) {
5383 tokens
[n_tokens
++] = p
;
5386 /* should be atleast three fields 'class/seskey/command/arg' */
5390 dsp
= xtp_despatches
;
5391 req_class
= atoi(tokens
[0]);
5392 while (dsp
->xtp_class
!= NULL
) {
5393 if (dsp
->xtp_class
== req_class
) {
5400 /* did we find one atall? */
5401 if (dsp_match
== NULL
) {
5402 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
5406 /* check session key and call despatch function */
5407 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
5408 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
5421 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5423 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
5425 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
5428 show_oops_s("activate_uri_entry_cb invalid parameters");
5433 show_oops(t
, "activate_uri_entry_cb no uri");
5437 uri
+= strspn(uri
, "\t ");
5439 /* if xxxt:// treat specially */
5440 if (!parse_xtp_url(t
, uri
)) {
5441 load_uri(t
, (gchar
*)uri
);
5447 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5449 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
5450 char *newuri
= NULL
;
5453 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
5456 show_oops_s("activate_search_entry_cb invalid parameters");
5460 if (search_string
== NULL
) {
5461 show_oops(t
, "no search_string");
5465 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
5466 newuri
= g_strdup_printf(search_string
, enc_search
);
5469 webkit_web_view_load_uri(t
->wv
, newuri
);
5477 check_and_set_js(const gchar
*uri
, struct tab
*t
)
5479 struct domain
*d
= NULL
;
5482 if (uri
== NULL
|| t
== NULL
)
5485 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5490 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
5491 es
? "enable" : "disable", uri
);
5493 g_object_set(G_OBJECT(t
->settings
),
5494 "enable-scripts", es
, (char *)NULL
);
5495 g_object_set(G_OBJECT(t
->settings
),
5496 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
5497 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5499 button_set_stockid(t
->js_toggle
,
5500 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
5504 show_ca_status(struct tab
*t
, const char *uri
)
5506 WebKitWebFrame
*frame
;
5507 WebKitWebDataSource
*source
;
5508 WebKitNetworkRequest
*request
;
5509 SoupMessage
*message
;
5511 gchar
*col_str
= XT_COLOR_WHITE
;
5514 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
5515 ssl_strict_certs
, ssl_ca_file
, uri
);
5519 if (ssl_ca_file
== NULL
) {
5520 if (g_str_has_prefix(uri
, "http://"))
5522 if (g_str_has_prefix(uri
, "https://")) {
5523 col_str
= XT_COLOR_RED
;
5528 if (g_str_has_prefix(uri
, "http://") ||
5529 !g_str_has_prefix(uri
, "https://"))
5532 frame
= webkit_web_view_get_main_frame(t
->wv
);
5533 source
= webkit_web_frame_get_data_source(frame
);
5534 request
= webkit_web_data_source_get_request(source
);
5535 message
= webkit_network_request_get_message(request
);
5537 if (message
&& (soup_message_get_flags(message
) &
5538 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
5539 col_str
= XT_COLOR_GREEN
;
5542 r
= load_compare_cert(t
, NULL
);
5544 col_str
= XT_COLOR_BLUE
;
5546 col_str
= XT_COLOR_YELLOW
;
5548 col_str
= XT_COLOR_RED
;
5553 gdk_color_parse(col_str
, &color
);
5554 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
5556 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
5557 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5559 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5560 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5563 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5565 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5566 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5573 free_favicon(struct tab
*t
)
5575 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p pix %p\n",
5576 __func__
, t
->icon_download
, t
->icon_request
, t
->icon_pixbuf
);
5578 if (t
->icon_request
)
5579 g_object_unref(t
->icon_request
);
5581 g_object_unref(t
->icon_pixbuf
);
5582 if (t
->icon_dest_uri
)
5583 g_free(t
->icon_dest_uri
);
5585 t
->icon_pixbuf
= NULL
;
5586 t
->icon_request
= NULL
;
5587 t
->icon_dest_uri
= NULL
;
5591 xt_icon_from_name(struct tab
*t
, gchar
*name
)
5593 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5594 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5596 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5597 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5599 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5600 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5604 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pixbuf
)
5606 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
5607 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5609 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
5610 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5612 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5613 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5617 is_valid_icon(char *file
)
5620 const char *mime_type
;
5624 gf
= g_file_new_for_path(file
);
5625 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5627 mime_type
= g_file_info_get_content_type(fi
);
5628 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
5629 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
5630 g_strcmp0(mime_type
, "image/png") == 0 ||
5631 g_strcmp0(mime_type
, "image/gif") == 0 ||
5632 g_strcmp0(mime_type
, "application/octet-stream") == 0;
5640 set_favicon_from_file(struct tab
*t
, char *file
)
5643 GdkPixbuf
*pixbuf
, *scaled
;
5646 if (t
== NULL
|| file
== NULL
)
5648 if (t
->icon_pixbuf
) {
5649 DNPRINTF(XT_D_DOWNLOAD
, "%s: icon already set\n", __func__
);
5653 if (g_str_has_prefix(file
, "file://"))
5654 file
+= strlen("file://");
5655 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
5657 if (!stat(file
, &sb
)) {
5658 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
5659 /* corrupt icon so trash it */
5660 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5663 /* no need to set icon to default here */
5668 pixbuf
= gdk_pixbuf_new_from_file(file
, NULL
);
5669 if (pixbuf
== NULL
) {
5670 xt_icon_from_name(t
, "text-html");
5674 g_object_get(pixbuf
, "width", &width
, "height", &height
,
5676 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d icon size %dx%d\n",
5677 __func__
, t
->tab_id
, width
, height
);
5679 if (width
> 16 || height
> 16) {
5680 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5681 GDK_INTERP_BILINEAR
);
5682 g_object_unref(pixbuf
);
5686 if (scaled
== NULL
) {
5687 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5688 GDK_INTERP_BILINEAR
);
5692 t
->icon_pixbuf
= scaled
;
5693 xt_icon_from_pixbuf(t
, t
->icon_pixbuf
);
5697 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5700 WebKitDownloadStatus status
= webkit_download_get_status(download
);
5701 struct tab
*tt
= NULL
, *t
= NULL
;
5704 * find the webview instead of passing in the tab as it could have been
5705 * deleted from underneath us.
5707 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5716 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
5717 __func__
, t
->tab_id
, status
);
5720 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
5722 t
->icon_download
= NULL
;
5725 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
5728 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
5731 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
5733 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
5734 __func__
, t
->tab_id
);
5735 t
->icon_download
= NULL
;
5738 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
5741 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
5742 __func__
, t
->icon_dest_uri
);
5743 set_favicon_from_file(t
, t
->icon_dest_uri
);
5744 /* these will be freed post callback */
5745 t
->icon_request
= NULL
;
5746 t
->icon_download
= NULL
;
5754 abort_favicon_download(struct tab
*t
)
5756 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
5758 if (t
->icon_download
) {
5759 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
5760 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5761 webkit_download_cancel(t
->icon_download
);
5762 t
->icon_download
= NULL
;
5766 xt_icon_from_name(t
, "text-html");
5770 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
5772 gchar
*name_hash
, file
[PATH_MAX
];
5775 DNPRINTF(XT_D_DOWNLOAD
, "notify_icon_loaded_cb %s\n", uri
);
5777 if (uri
== NULL
|| t
== NULL
)
5780 if (t
->icon_request
) {
5781 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
5785 /* check to see if we got the icon in cache */
5786 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
5787 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
5790 if (!stat(file
, &sb
)) {
5791 if (sb
.st_size
> 0) {
5792 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
5794 set_favicon_from_file(t
, file
);
5798 /* corrupt icon so trash it */
5799 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5804 /* create download for icon */
5805 t
->icon_request
= webkit_network_request_new(uri
);
5806 if (t
->icon_request
== NULL
) {
5807 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
5812 t
->icon_download
= webkit_download_new(t
->icon_request
);
5813 if (t
->icon_download
== NULL
) {
5814 fprintf(stderr
, "%s: icon_download", __func__
);
5818 /* we have to free icon_dest_uri later */
5819 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
5820 webkit_download_set_destination_uri(t
->icon_download
,
5823 if (webkit_download_get_status(t
->icon_download
) ==
5824 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5825 fprintf(stderr
, "%s: download failed to start", __func__
);
5826 g_object_unref(t
->icon_request
);
5827 g_free(t
->icon_dest_uri
);
5828 t
->icon_request
= NULL
;
5829 t
->icon_dest_uri
= NULL
;
5833 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
5834 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5836 webkit_download_start(t
->icon_download
);
5840 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5842 const gchar
*set
= NULL
, *uri
= NULL
, *title
= NULL
;
5843 struct history
*h
, find
;
5844 const gchar
*s_loading
;
5847 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
5848 webkit_web_view_get_load_status(wview
), get_uri(wview
) ? get_uri(wview
) : "NOTHING");
5851 show_oops_s("notify_load_status_cb invalid paramters");
5855 switch (webkit_web_view_get_load_status(wview
)) {
5856 case WEBKIT_LOAD_PROVISIONAL
:
5858 abort_favicon_download(t
);
5859 #if GTK_CHECK_VERSION(2, 20, 0)
5860 gtk_widget_show(t
->spinner
);
5861 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
5863 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
5865 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
5871 case WEBKIT_LOAD_COMMITTED
:
5873 if ((uri
= get_uri(wview
)) != NULL
) {
5874 if (strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
5875 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
5881 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
5884 /* check if js white listing is enabled */
5885 if (enable_js_whitelist
) {
5886 uri
= get_uri(wview
);
5887 check_and_set_js(uri
, t
);
5893 show_ca_status(t
, uri
);
5895 /* we know enough to autosave the session */
5896 if (session_autosave
) {
5902 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
5906 case WEBKIT_LOAD_FINISHED
:
5908 uri
= get_uri(wview
);
5912 if (!strncmp(uri
, "http://", strlen("http://")) ||
5913 !strncmp(uri
, "https://", strlen("https://")) ||
5914 !strncmp(uri
, "file://", strlen("file://"))) {
5916 h
= RB_FIND(history_list
, &hl
, &find
);
5918 title
= webkit_web_view_get_title(wview
);
5919 set
= title
? title
: uri
;
5920 h
= g_malloc(sizeof *h
);
5921 h
->uri
= g_strdup(uri
);
5922 h
->title
= g_strdup(set
);
5923 RB_INSERT(history_list
, &hl
, h
);
5924 completion_add_uri(h
->uri
);
5925 update_history_tabs(NULL
);
5929 set_status(t
, (char *)uri
, XT_STATUS_URI
);
5930 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5931 case WEBKIT_LOAD_FAILED
:
5934 #if GTK_CHECK_VERSION(2, 20, 0)
5935 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5936 gtk_widget_hide(t
->spinner
);
5938 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
5939 if (s_loading
&& !strcmp(s_loading
, "Loading"))
5940 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5942 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
5947 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
5949 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
5950 webkit_web_view_can_go_back(wview
));
5952 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
5953 webkit_web_view_can_go_forward(wview
));
5955 /* take focus if we are visible */
5960 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5962 const gchar
*set
= NULL
, *title
= NULL
;
5964 title
= webkit_web_view_get_title(wview
);
5965 set
= title
? title
: get_uri(wview
);
5966 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
5967 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
5971 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5973 run_script(t
, JS_HINTING
);
5977 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
5979 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
5980 progress
== 100 ? 0 : (double)progress
/ 100);
5981 if (show_url
== 0) {
5982 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
5983 progress
== 100 ? 0 : (double)progress
/ 100);
5988 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
5989 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
5990 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
5993 WebKitWebNavigationReason reason
;
5994 struct domain
*d
= NULL
;
5997 show_oops_s("webview_npd_cb invalid parameters");
6001 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
6003 webkit_network_request_get_uri(request
));
6005 uri
= (char *)webkit_network_request_get_uri(request
);
6007 if ((!parse_xtp_url(t
, uri
) && (t
->ctrl_click
))) {
6009 create_new_tab(uri
, NULL
, ctrl_click_focus
);
6010 webkit_web_policy_decision_ignore(pd
);
6011 return (TRUE
); /* we made the decission */
6015 * This is a little hairy but it comes down to this:
6016 * when we run in whitelist mode we have to assist the browser in
6017 * opening the URL that it would have opened in a new tab.
6019 reason
= webkit_web_navigation_action_get_reason(na
);
6020 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6021 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6022 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6025 webkit_web_policy_decision_use(pd
);
6026 return (TRUE
); /* we made the decission */
6033 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6036 struct domain
*d
= NULL
;
6038 WebKitWebView
*webview
= NULL
;
6040 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6041 webkit_web_view_get_uri(wv
));
6043 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6044 uri
= webkit_web_view_get_uri(wv
);
6045 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6048 tt
= create_new_tab(NULL
, NULL
, 1);
6050 } else if (enable_scripts
== 1) {
6051 tt
= create_new_tab(NULL
, NULL
, 1);
6059 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6062 struct domain
*d
= NULL
;
6064 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6066 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6067 uri
= webkit_web_view_get_uri(wv
);
6068 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6072 } else if (enable_scripts
== 1)
6079 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6081 /* we can not eat the event without throwing gtk off so defer it */
6083 /* catch middle click */
6084 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6089 /* catch ctrl click */
6090 if (e
->type
== GDK_BUTTON_RELEASE
&&
6091 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6096 return (XT_CB_PASSTHROUGH
);
6100 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6102 struct mime_type
*m
;
6104 m
= find_mime_type(mime_type
);
6112 show_oops(t
, "can't fork mime handler");
6121 execlp(m
->mt_action
, m
->mt_action
,
6122 webkit_network_request_get_uri(request
), (void *)NULL
);
6131 get_mime_type(char *file
)
6133 const char *mime_type
;
6137 if (g_str_has_prefix(file
, "file://"))
6138 file
+= strlen("file://");
6140 gf
= g_file_new_for_path(file
);
6141 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6143 mime_type
= g_file_info_get_content_type(fi
);
6151 run_download_mimehandler(char *mime_type
, char *file
)
6153 struct mime_type
*m
;
6155 m
= find_mime_type(mime_type
);
6161 show_oops_s("can't fork download mime handler");
6170 if (g_str_has_prefix(file
, "file://"))
6171 file
+= strlen("file://");
6172 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6181 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6184 WebKitDownloadStatus status
;
6185 const gchar
*file
= NULL
, *mime
= NULL
;
6187 if (download
== NULL
)
6189 status
= webkit_download_get_status(download
);
6190 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6193 file
= webkit_download_get_destination_uri(download
);
6196 mime
= get_mime_type((char *)file
);
6200 run_download_mimehandler((char *)mime
, (char *)file
);
6204 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6205 WebKitNetworkRequest
*request
, char *mime_type
,
6206 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6209 show_oops_s("webview_mimetype_cb invalid parameters");
6213 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6214 t
->tab_id
, mime_type
);
6216 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6217 webkit_web_policy_decision_ignore(decision
);
6222 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6223 webkit_web_policy_decision_download(decision
);
6231 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6234 const gchar
*filename
;
6236 struct download
*download_entry
;
6239 if (wk_download
== NULL
|| t
== NULL
) {
6240 show_oops_s("%s invalid parameters", __func__
);
6244 filename
= webkit_download_get_suggested_filename(wk_download
);
6245 if (filename
== NULL
)
6246 return (FALSE
); /* abort download */
6248 uri
= g_strdup_printf("file://%s/%s", download_dir
, filename
);
6250 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6251 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6253 webkit_download_set_destination_uri(wk_download
, uri
);
6255 if (webkit_download_get_status(wk_download
) ==
6256 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6257 show_oops(t
, "%s: download failed to start", __func__
);
6259 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6261 /* connect "download first" mime handler */
6262 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6263 G_CALLBACK(download_status_changed_cb
), NULL
);
6265 download_entry
= g_malloc(sizeof(struct download
));
6266 download_entry
->download
= wk_download
;
6267 download_entry
->tab
= t
;
6268 download_entry
->id
= next_download_id
++;
6269 RB_INSERT(download_list
, &downloads
, download_entry
);
6270 /* get from history */
6271 g_object_ref(wk_download
);
6272 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6273 show_oops(t
, "Download of '%s' started...",
6274 basename(webkit_download_get_destination_uri(wk_download
)));
6280 /* sync other download manager tabs */
6281 update_download_tabs(NULL
);
6284 * NOTE: never redirect/render the current tab before this
6285 * function returns. This will cause the download to never start.
6287 return (ret
); /* start download */
6291 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6293 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6296 show_oops_s("webview_hover_cb");
6301 set_status(t
, uri
, XT_STATUS_LINK
);
6304 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6309 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6311 struct key_binding
*k
;
6313 TAILQ_FOREACH(k
, &kbl
, entry
)
6314 if (e
->keyval
== k
->key
&& (entry
? k
->use_in_entry
: 1)) {
6316 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6317 return (cmd_execute(t
, k
->cmd
));
6318 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6319 return (cmd_execute(t
, k
->cmd
));
6323 return (XT_CB_PASSTHROUGH
);
6327 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6329 char s
[2], buf
[128];
6330 const char *errstr
= NULL
;
6333 /* don't use w directly; use t->whatever instead */
6336 show_oops_s("wv_keypress_after_cb");
6337 return (XT_CB_PASSTHROUGH
);
6340 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6341 e
->keyval
, e
->state
, t
);
6345 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6347 return (XT_CB_HANDLED
);
6351 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6352 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6354 /* we have a string */
6356 /* we have a number */
6357 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6365 /* XXX unfuck this */
6366 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6367 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6368 /* last input was numerical */
6370 l
= strlen(t
->hint_num
);
6377 t
->hint_num
[l
] = '\0';
6381 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6382 /* last input was alphanumerical */
6384 l
= strlen(t
->hint_buf
);
6391 t
->hint_buf
[l
] = '\0';
6401 /* numerical input */
6402 if (CLEAN(e
->state
) == 0 &&
6403 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6404 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6405 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6406 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6409 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6411 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6414 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6416 t
->hint_mode
= XT_HINT_NUMERICAL
;
6420 /* empty the counter buffer */
6421 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6422 return (XT_CB_HANDLED
);
6425 /* alphanumerical input */
6427 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6428 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6429 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6430 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6431 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6432 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6433 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6436 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6439 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6441 t
->hint_mode
= XT_HINT_ALPHANUM
;
6444 /* empty the counter buffer */
6445 bzero(t
->hint_num
, sizeof t
->hint_num
);
6446 return (XT_CB_HANDLED
);
6449 return (XT_CB_HANDLED
);
6452 return (handle_keypress(t
, e
, 0));
6456 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6460 return (XT_CB_PASSTHROUGH
);
6464 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6466 const gchar
*c
= gtk_entry_get_text(w
);
6470 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6471 e
->keyval
, e
->state
, t
);
6474 show_oops_s("cmd_keyrelease_cb invalid parameters");
6475 return (XT_CB_PASSTHROUGH
);
6478 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6479 e
->keyval
, e
->state
, t
);
6483 if (strlen(c
) == 1) {
6484 webkit_web_view_unmark_text_matches(t
->wv
);
6490 else if (c
[0] == '?')
6496 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6498 /* not found, mark red */
6499 gdk_color_parse(XT_COLOR_RED
, &color
);
6500 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6501 /* unmark and remove selection */
6502 webkit_web_view_unmark_text_matches(t
->wv
);
6503 /* my kingdom for a way to unselect text in webview */
6505 /* found, highlight all */
6506 webkit_web_view_unmark_text_matches(t
->wv
);
6507 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6508 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6509 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6510 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6513 return (XT_CB_PASSTHROUGH
);
6517 match_uri(const gchar
*uri
, const gchar
*key
) {
6520 gboolean match
= FALSE
;
6524 if (!strncmp(key
, uri
, len
))
6527 voffset
= strstr(uri
, "/") + 2;
6528 if (!strncmp(key
, voffset
, len
))
6530 else if (g_str_has_prefix(voffset
, "www.")) {
6531 voffset
= voffset
+ strlen("www.");
6532 if (!strncmp(key
, voffset
, len
))
6541 cmd_getlist(int id
, char *key
)
6546 if (id
>= 0 && (cmds
[id
].userarg
&& (cmds
[id
].arg
.i
== XT_TAB_OPEN
|| cmds
[id
].arg
.i
== XT_TAB_NEW
))) {
6547 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6548 if (match_uri(h
->uri
, key
)) {
6549 cmd_status
.list
[c
] = (char *)h
->uri
;
6558 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6560 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6561 if(cmds
[i
].level
< dep
)
6563 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6564 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6572 cmd_getnext(int dir
)
6574 cmd_status
.index
+= dir
;
6576 if (cmd_status
.index
<0)
6577 cmd_status
.index
= cmd_status
.len
-1;
6578 else if (cmd_status
.index
>= cmd_status
.len
)
6579 cmd_status
.index
= 0;
6581 return cmd_status
.list
[cmd_status
.index
];
6585 cmd_tokenize(char *s
, char *tokens
[])
6589 size_t len
= strlen(s
);
6590 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6592 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6602 cmd_complete(struct tab
*t
, char *str
, int dir
)
6604 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6605 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1;
6606 char *tok
, *match
, *s
= strdup(str
);
6608 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6610 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: complete %s\n", str
);
6612 levels
= cmd_tokenize(s
, tokens
);
6614 for (i
= 0; i
< levels
- 1; i
++) {
6616 for (j
= c
; j
< LENGTH(cmds
); j
++)
6617 if (cmds
[j
].level
== dep
&& !strcmp(tok
, cmds
[j
].cmd
)) {
6618 strlcat(res
, tok
, sizeof res
);
6619 strlcat(res
, " ", sizeof res
);
6628 if (cmd_status
.index
== -1)
6629 cmd_getlist(parent
, tokens
[i
]);
6631 if (cmd_status
.len
> 0) {
6632 match
= cmd_getnext(dir
);
6633 strlcat(res
, match
, sizeof res
);
6634 gtk_entry_set_text(w
, res
);
6635 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6640 cmd_valid(char *str
)
6642 char *tok
, *last
, *s
= g_strdup(str
);
6643 int i
, c
= 0, dep
= 0;
6645 for (tok
= strtok_r(s
, " ", &last
); tok
;
6646 tok
= strtok_r(NULL
, " ", &last
)) {
6647 for (i
= c
; i
< LENGTH(cmds
); i
++) {
6648 if (cmds
[i
].level
< dep
) {
6649 return (XT_CB_PASSTHROUGH
);
6651 if (cmds
[i
].level
== dep
&& !strcmp(tok
, cmds
[i
].cmd
)) {
6652 if (cmds
[i
].userarg
) {
6653 return (XT_CB_HANDLED
);
6660 if (i
== LENGTH(cmds
)) {
6661 return (XT_CB_PASSTHROUGH
);
6664 return (XT_CB_HANDLED
);
6668 cmd_execute(struct tab
*t
, char *str
)
6670 struct cmd
*cmd
= NULL
;
6671 char *tok
, *last
, *s
= g_strdup(str
);
6672 int j
, c
= 0, dep
= 0;
6674 for (tok
= strtok_r(s
, " ", &last
); tok
;
6675 tok
= strtok_r(NULL
, " ", &last
)) {
6676 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6677 if (cmds
[j
].level
< dep
) {
6678 show_oops(t
, "Invalid command: %s", str
);
6679 return (XT_CB_PASSTHROUGH
);
6681 if (cmds
[j
].level
== dep
&& !strcmp(tok
, cmds
[j
].cmd
)) {
6684 cmd
->arg
.s
= last
? g_strdup(last
) : g_strdup("");
6692 if (j
== LENGTH(cmds
)) {
6693 show_oops(t
, "Invalid command: %s", str
);
6694 return (XT_CB_PASSTHROUGH
);
6697 cmd
->arg
.s
= g_strdup(tok
);
6699 /* arg->s contains last token */
6700 cmd
->func(t
, &cmd
->arg
);
6704 return (XT_CB_HANDLED
);
6708 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6711 show_oops_s("entry_key_cb invalid parameters");
6712 return (XT_CB_PASSTHROUGH
);
6715 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6716 e
->keyval
, e
->state
, t
);
6720 if (e
->keyval
== GDK_Escape
) {
6721 /* don't use focus_webview(t) because we want to type :cmds */
6722 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6725 return (handle_keypress(t
, e
, 1));
6729 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6731 int rv
= XT_CB_HANDLED
;
6732 const gchar
*c
= gtk_entry_get_text(w
);
6735 show_oops_s("cmd_keypress_cb parameters");
6736 return (XT_CB_PASSTHROUGH
);
6739 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6740 e
->keyval
, e
->state
, t
);
6744 e
->keyval
= GDK_Escape
;
6745 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6746 e
->keyval
= GDK_Escape
;
6748 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
6749 cmd_status
.index
= -1;
6751 switch (e
->keyval
) {
6754 cmd_complete(t
, (char *)&c
[1], 1);
6756 case GDK_ISO_Left_Tab
:
6758 cmd_complete(t
, (char *)&c
[1], -1);
6762 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
6770 if (c
[0] == '/' || c
[0] == '?')
6771 webkit_web_view_unmark_text_matches(t
->wv
);
6775 rv
= XT_CB_PASSTHROUGH
;
6781 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6784 show_oops_s("cmd_focusout_cb invalid parameters");
6785 return (XT_CB_PASSTHROUGH
);
6787 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
6792 if (show_url
== 0 || t
->focus_wv
)
6795 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6797 return (XT_CB_PASSTHROUGH
);
6801 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6804 const gchar
*c
= gtk_entry_get_text(entry
);
6807 show_oops_s("cmd_activate_cb invalid parameters");
6811 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6816 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6822 if (c
[0] == '/' || c
[0] == '?') {
6823 if (t
->search_text
) {
6824 g_free(t
->search_text
);
6825 t
->search_text
= NULL
;
6828 t
->search_text
= g_strdup(s
);
6830 g_free(global_search
);
6831 global_search
= g_strdup(s
);
6832 t
->search_forward
= c
[0] == '/';
6844 backward_cb(GtkWidget
*w
, struct tab
*t
)
6849 show_oops_s("backward_cb invalid parameters");
6853 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6860 forward_cb(GtkWidget
*w
, struct tab
*t
)
6865 show_oops_s("forward_cb invalid parameters");
6869 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6871 a
.i
= XT_NAV_FORWARD
;
6876 home_cb(GtkWidget
*w
, struct tab
*t
)
6879 show_oops_s("home_cb invalid parameters");
6883 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6889 stop_cb(GtkWidget
*w
, struct tab
*t
)
6891 WebKitWebFrame
*frame
;
6894 show_oops_s("stop_cb invalid parameters");
6898 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
6900 frame
= webkit_web_view_get_main_frame(t
->wv
);
6901 if (frame
== NULL
) {
6902 show_oops(t
, "stop_cb: no frame");
6906 webkit_web_frame_stop_loading(frame
);
6907 abort_favicon_download(t
);
6911 setup_webkit(struct tab
*t
)
6913 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
6914 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
6915 FALSE
, (char *)NULL
);
6917 warnx("webkit does not have \"enable-dns-prefetching\" property");
6918 g_object_set(G_OBJECT(t
->settings
),
6919 "user-agent", t
->user_agent
, (char *)NULL
);
6920 g_object_set(G_OBJECT(t
->settings
),
6921 "enable-scripts", enable_scripts
, (char *)NULL
);
6922 g_object_set(G_OBJECT(t
->settings
),
6923 "enable-plugins", enable_plugins
, (char *)NULL
);
6924 g_object_set(G_OBJECT(t
->settings
),
6925 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
6926 g_object_set(G_OBJECT(t
->wv
),
6927 "full-content-zoom", TRUE
, (char *)NULL
);
6928 adjustfont_webkit(t
, XT_FONT_SET
);
6930 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6934 create_browser(struct tab
*t
)
6940 show_oops_s("create_browser invalid parameters");
6944 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
6945 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
6946 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
6947 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
6949 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
6950 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
6951 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
6953 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
6954 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
6957 t
->settings
= webkit_web_settings_new();
6959 if (user_agent
== NULL
) {
6960 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
6962 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
6965 t
->user_agent
= g_strdup(user_agent
);
6967 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
6979 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
6980 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
6981 gtk_widget_set_name(w
, "xxxterm");
6982 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
6983 g_signal_connect(G_OBJECT(w
), "delete_event",
6984 G_CALLBACK (gtk_main_quit
), NULL
);
6990 create_kiosk_toolbar(struct tab
*t
)
6992 GtkWidget
*toolbar
= NULL
, *b
;
6994 b
= gtk_hbox_new(FALSE
, 0);
6996 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
6998 /* backward button */
6999 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7000 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7001 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7002 G_CALLBACK(backward_cb
), t
);
7003 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7005 /* forward button */
7006 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7007 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7008 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7009 G_CALLBACK(forward_cb
), t
);
7010 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7013 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7014 gtk_widget_set_sensitive(t
->gohome
, true);
7015 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7016 G_CALLBACK(home_cb
), t
);
7017 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7019 /* create widgets but don't use them */
7020 t
->uri_entry
= gtk_entry_new();
7021 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7022 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7023 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7029 create_toolbar(struct tab
*t
)
7031 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7033 b
= gtk_hbox_new(FALSE
, 0);
7035 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7038 /* backward button */
7039 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7040 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7041 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7042 G_CALLBACK(backward_cb
), t
);
7043 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7045 /* forward button */
7046 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7047 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7048 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7049 G_CALLBACK(forward_cb
), t
);
7050 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7054 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7055 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7056 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7057 G_CALLBACK(stop_cb
), t
);
7058 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7062 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7063 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7064 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7065 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7066 G_CALLBACK(js_toggle_cb
), t
);
7067 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7070 t
->uri_entry
= gtk_entry_new();
7071 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7072 G_CALLBACK(activate_uri_entry_cb
), t
);
7073 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7074 G_CALLBACK(entry_key_cb
), t
);
7076 eb1
= gtk_hbox_new(FALSE
, 0);
7077 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7078 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7079 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7082 if (fancy_bar
&& search_string
) {
7084 t
->search_entry
= gtk_entry_new();
7085 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7086 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7087 G_CALLBACK(activate_search_entry_cb
), t
);
7088 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7089 G_CALLBACK(entry_key_cb
), t
);
7090 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7091 eb2
= gtk_hbox_new(FALSE
, 0);
7092 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7093 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7095 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7105 TAILQ_FOREACH(t
, &tabs
, entry
)
7106 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7110 undo_close_tab_save(struct tab
*t
)
7114 struct undo
*u1
, *u2
;
7116 WebKitWebHistoryItem
*item
;
7118 if ((uri
= get_uri(t
->wv
)) == NULL
)
7121 u1
= g_malloc0(sizeof(struct undo
));
7122 u1
->uri
= g_strdup(uri
);
7124 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7126 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7127 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7130 /* forward history */
7131 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7135 u1
->history
= g_list_prepend(u1
->history
,
7136 webkit_web_history_item_copy(item
));
7137 items
= g_list_next(items
);
7142 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7143 u1
->history
= g_list_prepend(u1
->history
,
7144 webkit_web_history_item_copy(item
));
7148 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7152 u1
->history
= g_list_prepend(u1
->history
,
7153 webkit_web_history_item_copy(item
));
7154 items
= g_list_next(items
);
7157 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7159 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7160 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7161 TAILQ_REMOVE(&undos
, u2
, entry
);
7163 g_list_free(u2
->history
);
7172 delete_tab(struct tab
*t
)
7176 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7181 TAILQ_REMOVE(&tabs
, t
, entry
);
7183 /* halt all webkit activity */
7184 abort_favicon_download(t
);
7185 webkit_web_view_stop_loading(t
->wv
);
7186 undo_close_tab_save(t
);
7188 if (browser_mode
== XT_BM_KIOSK
) {
7189 gtk_widget_destroy(t
->uri_entry
);
7190 gtk_widget_destroy(t
->stop
);
7191 gtk_widget_destroy(t
->js_toggle
);
7195 gtk_widget_destroy(t
->vbox
);
7196 g_free(t
->user_agent
);
7197 g_free(t
->stylesheet
);
7201 if (TAILQ_EMPTY(&tabs
)) {
7202 if (browser_mode
== XT_BM_KIOSK
)
7203 create_new_tab(home
, NULL
, 1);
7205 create_new_tab(NULL
, NULL
, 1);
7208 /* recreate session */
7209 if (session_autosave
) {
7216 adjustfont_webkit(struct tab
*t
, int adjust
)
7221 show_oops_s("adjustfont_webkit invalid parameters");
7225 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7226 if (adjust
== XT_FONT_SET
) {
7227 t
->font_size
= default_font_size
;
7228 zoom
= default_zoom_level
;
7229 t
->font_size
+= adjust
;
7230 g_object_set(G_OBJECT(t
->settings
), "default-font-size",
7231 t
->font_size
, (char *)NULL
);
7232 g_object_get(G_OBJECT(t
->settings
), "default-font-size",
7233 &t
->font_size
, (char *)NULL
);
7235 t
->font_size
+= adjust
;
7236 zoom
+= adjust
/25.0;
7241 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7242 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7246 append_tab(struct tab
*t
)
7251 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7252 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7256 create_new_tab(char *title
, struct undo
*u
, int focus
)
7259 int load
= 1, id
, notfound
;
7261 WebKitWebHistoryItem
*item
;
7265 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7267 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7268 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7272 t
= g_malloc0(sizeof *t
);
7274 if (title
== NULL
) {
7275 title
= "(untitled)";
7279 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7281 /* label + button for tab */
7282 b
= gtk_hbox_new(FALSE
, 0);
7285 #if GTK_CHECK_VERSION(2, 20, 0)
7286 t
->spinner
= gtk_spinner_new ();
7288 t
->label
= gtk_label_new(title
);
7289 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7290 gtk_widget_set_size_request(t
->label
, 100, 0);
7291 gtk_widget_set_size_request(b
, 130, 0);
7293 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7294 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7295 #if GTK_CHECK_VERSION(2, 20, 0)
7296 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7300 if (browser_mode
== XT_BM_KIOSK
)
7301 t
->toolbar
= create_kiosk_toolbar(t
);
7303 t
->toolbar
= create_toolbar(t
);
7305 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7308 t
->browser_win
= create_browser(t
);
7309 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7311 /* oops message for user feedback */
7312 t
->oops
= gtk_entry_new();
7313 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7314 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7315 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7316 gdk_color_parse(XT_COLOR_RED
, &color
);
7317 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7318 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7321 t
->cmd
= gtk_entry_new();
7322 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7323 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7324 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7327 t
->statusbar
= gtk_entry_new();
7328 gtk_entry_set_inner_border(GTK_ENTRY(t
->statusbar
), NULL
);
7329 gtk_entry_set_has_frame(GTK_ENTRY(t
->statusbar
), FALSE
);
7330 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7331 gdk_color_parse(XT_COLOR_BLACK
, &color
);
7332 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7333 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7334 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7335 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7337 /* xtp meaning is normal by default */
7338 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
7340 /* set empty favicon */
7341 xt_icon_from_name(t
, "text-html");
7343 /* and show it all */
7344 gtk_widget_show_all(b
);
7345 gtk_widget_show_all(t
->vbox
);
7347 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7351 id
= gtk_notebook_get_current_page(notebook
);
7352 TAILQ_FOREACH(tt
, &tabs
, entry
) {
7353 if (tt
->tab_id
== id
) {
7355 TAILQ_INSERT_AFTER(&tabs
, tt
, t
, entry
);
7356 gtk_notebook_insert_page(notebook
, t
->vbox
, b
,
7366 #if GTK_CHECK_VERSION(2, 20, 0)
7367 /* turn spinner off if we are a new tab without uri */
7369 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7370 gtk_widget_hide(t
->spinner
);
7373 /* make notebook tabs reorderable */
7374 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7376 g_object_connect(G_OBJECT(t
->cmd
),
7377 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7378 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7379 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7380 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7383 /* reuse wv_button_cb to hide oops */
7384 g_object_connect(G_OBJECT(t
->oops
),
7385 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7388 g_object_connect(G_OBJECT(t
->wv
),
7389 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7390 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7391 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7392 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7393 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7394 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7395 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7396 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7397 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7398 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7399 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
7400 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
7401 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7402 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7404 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7406 g_signal_connect(t
->wv
,
7407 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7408 g_signal_connect(t
->wv
,
7409 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7411 /* hijack the unused keys as if we were the browser */
7412 g_object_connect(G_OBJECT(t
->toolbar
),
7413 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7416 g_signal_connect(G_OBJECT(bb
), "button_press_event",
7417 G_CALLBACK(tab_close_cb
), t
);
7422 url_set_visibility();
7423 statusbar_set_visibility();
7426 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7427 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
7432 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
7436 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7441 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7442 /* restore the tab's history */
7443 if (u
&& u
->history
) {
7447 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
7448 items
= g_list_next(items
);
7451 item
= g_list_nth_data(u
->history
, u
->back
);
7453 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
7456 g_list_free(u
->history
);
7458 webkit_web_back_forward_list_clear(t
->bfl
);
7464 notebook_switchpage_cb(GtkNotebook
*nb
, GtkNotebookPage
*nbp
, guint pn
,
7470 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
7472 TAILQ_FOREACH(t
, &tabs
, entry
) {
7473 if (t
->tab_id
== pn
) {
7474 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
7477 uri
= webkit_web_view_get_title(t
->wv
);
7480 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
7492 menuitem_response(struct tab
*t
)
7494 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7498 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
7500 GtkWidget
*menu
, *menu_items
;
7501 GdkEventButton
*bevent
;
7505 if (event
->type
== GDK_BUTTON_PRESS
) {
7506 bevent
= (GdkEventButton
*) event
;
7507 menu
= gtk_menu_new();
7509 TAILQ_FOREACH(ti
, &tabs
, entry
) {
7510 if ((uri
= get_uri(ti
->wv
)) == NULL
)
7511 /* XXX make sure there is something to print */
7512 /* XXX add gui pages in here to look purdy */
7514 menu_items
= gtk_menu_item_new_with_label(uri
);
7515 gtk_menu_append(GTK_MENU (menu
), menu_items
);
7516 gtk_widget_show(menu_items
);
7518 gtk_signal_connect_object(GTK_OBJECT(menu_items
),
7519 "activate", GTK_SIGNAL_FUNC(menuitem_response
),
7523 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
7524 bevent
->button
, bevent
->time
);
7526 /* unref object so it'll free itself when popped down */
7527 g_object_ref_sink(menu
);
7528 g_object_unref(menu
);
7530 return (TRUE
/* eat event */);
7533 return (FALSE
/* propagate */);
7537 icon_size_map(int icon_size
)
7539 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
7540 icon_size
> GTK_ICON_SIZE_DIALOG
)
7541 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
7547 create_button(char *name
, char *stockid
, int size
)
7549 GtkWidget
*button
, *image
;
7552 rcstring
= g_strdup_printf(
7553 "style \"%s-style\"\n"
7555 " GtkWidget::focus-padding = 0\n"
7556 " GtkWidget::focus-line-width = 0\n"
7560 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
7561 gtk_rc_parse_string(rcstring
);
7563 button
= gtk_button_new();
7564 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
7565 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
7567 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
7568 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7569 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
7570 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
7571 gtk_widget_set_name(button
, name
);
7572 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
7573 gtk_widget_set_tooltip_text(button
, name
);
7579 button_set_stockid(GtkWidget
*button
, char *stockid
)
7583 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
7584 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7585 gtk_button_set_image(GTK_BUTTON(button
), image
);
7594 char file
[PATH_MAX
];
7597 vbox
= gtk_vbox_new(FALSE
, 0);
7598 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
7599 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
7600 gtk_notebook_set_tab_hborder(notebook
, 0);
7601 gtk_notebook_set_tab_vborder(notebook
, 0);
7602 gtk_notebook_set_scrollable(notebook
, TRUE
);
7603 notebook_tab_set_visibility(notebook
);
7604 gtk_notebook_set_show_border(notebook
, FALSE
);
7605 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
7607 abtn
= gtk_button_new();
7608 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
7609 gtk_widget_set_size_request(arrow
, -1, -1);
7610 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
7611 gtk_widget_set_size_request(abtn
, -1, 20);
7612 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
7614 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
7615 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
7616 gtk_widget_set_size_request(vbox
, -1, -1);
7618 g_object_connect(G_OBJECT(notebook
),
7619 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
7621 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
7622 G_CALLBACK(arrow_cb
), NULL
);
7624 main_window
= create_window();
7625 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
7626 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
7629 for (i
= 0; i
< LENGTH(icons
); i
++) {
7630 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
7631 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
7632 l
= g_list_append(l
, pb
);
7634 gtk_window_set_default_icon_list(l
);
7636 gtk_widget_show_all(abtn
);
7637 gtk_widget_show_all(main_window
);
7641 set_hook(void **hook
, char *name
)
7644 errx(1, "set_hook");
7646 if (*hook
== NULL
) {
7647 *hook
= dlsym(RTLD_NEXT
, name
);
7649 errx(1, "can't hook %s", name
);
7653 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7655 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
7657 g_return_val_if_fail(cookie1
, FALSE
);
7658 g_return_val_if_fail(cookie2
, FALSE
);
7660 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
7661 !strcmp (cookie1
->value
, cookie2
->value
) &&
7662 !strcmp (cookie1
->path
, cookie2
->path
) &&
7663 !strcmp (cookie1
->domain
, cookie2
->domain
));
7667 transfer_cookies(void)
7670 SoupCookie
*sc
, *pc
;
7672 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7674 for (;cf
; cf
= cf
->next
) {
7676 sc
= soup_cookie_copy(pc
);
7677 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
7680 soup_cookies_free(cf
);
7684 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
7689 print_cookie("soup_cookie_jar_delete_cookie", c
);
7691 if (cookies_enabled
== 0)
7694 if (jar
== NULL
|| c
== NULL
)
7697 /* find and remove from persistent jar */
7698 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7700 for (;cf
; cf
= cf
->next
) {
7702 if (soup_cookie_equal(ci
, c
)) {
7703 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
7708 soup_cookies_free(cf
);
7710 /* delete from session jar */
7711 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
7715 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
7717 struct domain
*d
= NULL
;
7721 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
7722 jar
, p_cookiejar
, s_cookiejar
);
7724 if (cookies_enabled
== 0)
7727 /* see if we are up and running */
7728 if (p_cookiejar
== NULL
) {
7729 _soup_cookie_jar_add_cookie(jar
, cookie
);
7732 /* disallow p_cookiejar adds, shouldn't happen */
7733 if (jar
== p_cookiejar
)
7737 if (jar
== NULL
|| cookie
== NULL
)
7740 if (enable_cookie_whitelist
&&
7741 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
7743 DNPRINTF(XT_D_COOKIE
,
7744 "soup_cookie_jar_add_cookie: reject %s\n",
7746 if (save_rejected_cookies
) {
7747 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
7748 show_oops_s("can't open reject cookie file");
7751 fseek(r_cookie_f
, 0, SEEK_END
);
7752 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7753 cookie
->http_only
? "#HttpOnly_" : "",
7755 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
7757 cookie
->secure
? "TRUE" : "FALSE",
7759 (gulong
)soup_date_to_time_t(cookie
->expires
) :
7766 if (!allow_volatile_cookies
)
7770 if (cookie
->expires
== NULL
&& session_timeout
) {
7771 soup_cookie_set_expires(cookie
,
7772 soup_date_new_from_now(session_timeout
));
7773 print_cookie("modified add cookie", cookie
);
7776 /* see if we are white listed for persistence */
7777 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
7778 /* add to persistent jar */
7779 c
= soup_cookie_copy(cookie
);
7780 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
7781 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
7784 /* add to session jar */
7785 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
7786 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
7792 char file
[PATH_MAX
];
7794 set_hook((void *)&_soup_cookie_jar_add_cookie
,
7795 "soup_cookie_jar_add_cookie");
7796 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
7797 "soup_cookie_jar_delete_cookie");
7799 if (cookies_enabled
== 0)
7803 * the following code is intricate due to overriding several libsoup
7805 * do not alter order of these operations.
7808 /* rejected cookies */
7809 if (save_rejected_cookies
)
7810 snprintf(rc_fname
, sizeof file
, "%s/rejected.txt", work_dir
);
7812 /* persistent cookies */
7813 snprintf(file
, sizeof file
, "%s/cookies.txt", work_dir
);
7814 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
7816 /* session cookies */
7817 s_cookiejar
= soup_cookie_jar_new();
7818 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
7819 cookie_policy
, (void *)NULL
);
7822 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
7826 setup_proxy(char *uri
)
7829 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
7830 soup_uri_free(proxy_uri
);
7834 if (http_proxy
!= uri
) {
7841 http_proxy
= g_strdup(uri
);
7842 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
7843 proxy_uri
= soup_uri_new(http_proxy
);
7844 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
7849 send_cmd_to_socket(char *cmd
)
7852 struct sockaddr_un sa
;
7854 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7855 warnx("%s: socket", __func__
);
7859 sa
.sun_family
= AF_UNIX
;
7860 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7861 work_dir
, XT_SOCKET_FILE
);
7864 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7865 warnx("%s: connect", __func__
);
7869 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
7870 warnx("%s: send", __func__
);
7881 socket_watcher(gpointer data
, gint fd
, GdkInputCondition cond
)
7884 char str
[XT_MAX_URL_LENGTH
];
7885 socklen_t t
= sizeof(struct sockaddr_un
);
7886 struct sockaddr_un sa
;
7892 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
7897 if (getpeereid(s
, &uid
, &gid
) == -1) {
7901 if (uid
!= getuid() || gid
!= getgid()) {
7902 warnx("unauthorized user");
7908 warnx("not a valid user");
7912 n
= recv(s
, str
, sizeof(str
), 0);
7916 tt
= TAILQ_LAST(&tabs
, tab_list
);
7917 cmd_execute(tt
, str
);
7924 struct sockaddr_un sa
;
7926 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7927 warn("is_running: socket");
7931 sa
.sun_family
= AF_UNIX
;
7932 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7933 work_dir
, XT_SOCKET_FILE
);
7936 /* connect to see if there is a listener */
7937 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
7938 rv
= 0; /* not running */
7940 rv
= 1; /* already running */
7951 struct sockaddr_un sa
;
7953 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7954 warn("build_socket: socket");
7958 sa
.sun_family
= AF_UNIX
;
7959 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7960 work_dir
, XT_SOCKET_FILE
);
7963 /* connect to see if there is a listener */
7964 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7965 /* no listener so we will */
7966 unlink(sa
.sun_path
);
7968 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7969 warn("build_socket: bind");
7973 if (listen(s
, 1) == -1) {
7974 warn("build_socket: listen");
7987 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
7988 GtkTreeIter
*iter
, struct tab
*t
)
7992 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
7999 completion_add_uri(const gchar
*uri
)
8003 /* add uri to list_store */
8004 gtk_list_store_append(completion_model
, &iter
);
8005 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
8009 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
8010 GtkTreeIter
*iter
, gpointer user_data
)
8013 gboolean match
= FALSE
;
8015 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8021 match
= match_uri(value
, key
);
8028 completion_add(struct tab
*t
)
8030 /* enable completion for tab */
8031 t
->completion
= gtk_entry_completion_new();
8032 gtk_entry_completion_set_text_column(t
->completion
, 0);
8033 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8034 gtk_entry_completion_set_model(t
->completion
,
8035 GTK_TREE_MODEL(completion_model
));
8036 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8038 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8039 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8040 G_CALLBACK(completion_select_cb
), t
);
8047 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8052 main(int argc
, char *argv
[])
8055 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8056 char conf
[PATH_MAX
] = { '\0' };
8057 char file
[PATH_MAX
];
8058 char *env_proxy
= NULL
;
8061 struct sigaction sact
;
8062 gchar
*priority
= g_strdup("NORMAL");
8066 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8068 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8077 errx(0 , "Version: %s", version
);
8080 strlcpy(conf
, optarg
, sizeof(conf
));
8083 strlcpy(named_session
, optarg
, sizeof(named_session
));
8104 RB_INIT(&downloads
);
8108 TAILQ_INIT(&aliases
);
8114 gnutls_global_init();
8116 /* generate session keys for xtp pages */
8117 generate_xtp_session_key(&dl_session_key
);
8118 generate_xtp_session_key(&hl_session_key
);
8119 generate_xtp_session_key(&cl_session_key
);
8120 generate_xtp_session_key(&fl_session_key
);
8123 gtk_init(&argc
, &argv
);
8124 if (!g_thread_supported())
8125 g_thread_init(NULL
);
8128 bzero(&sact
, sizeof(sact
));
8129 sigemptyset(&sact
.sa_mask
);
8130 sact
.sa_handler
= sigchild
;
8131 sact
.sa_flags
= SA_NOCLDSTOP
;
8132 sigaction(SIGCHLD
, &sact
, NULL
);
8134 /* set download dir */
8135 pwd
= getpwuid(getuid());
8137 errx(1, "invalid user %d", getuid());
8138 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8140 /* set default string settings */
8141 home
= g_strdup("http://www.peereboom.us");
8142 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8143 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
8144 strlcpy(runtime_settings
,"runtime", sizeof runtime_settings
);
8146 /* read config file */
8147 if (strlen(conf
) == 0)
8148 snprintf(conf
, sizeof conf
, "%s/.%s",
8149 pwd
->pw_dir
, XT_CONF_FILE
);
8150 config_parse(conf
, 0);
8152 /* working directory */
8153 if (strlen(work_dir
) == 0)
8154 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
8155 pwd
->pw_dir
, XT_DIR
);
8156 if (stat(work_dir
, &sb
)) {
8157 if (mkdir(work_dir
, S_IRWXU
) == -1)
8158 err(1, "mkdir work_dir");
8159 if (stat(work_dir
, &sb
))
8160 err(1, "stat work_dir");
8162 if (S_ISDIR(sb
.st_mode
) == 0)
8163 errx(1, "%s not a dir", work_dir
);
8164 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8165 warnx("fixing invalid permissions on %s", work_dir
);
8166 if (chmod(work_dir
, S_IRWXU
) == -1)
8170 /* icon cache dir */
8171 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
8172 if (stat(cache_dir
, &sb
)) {
8173 if (mkdir(cache_dir
, S_IRWXU
) == -1)
8174 err(1, "mkdir cache_dir");
8175 if (stat(cache_dir
, &sb
))
8176 err(1, "stat cache_dir");
8178 if (S_ISDIR(sb
.st_mode
) == 0)
8179 errx(1, "%s not a dir", cache_dir
);
8180 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8181 warnx("fixing invalid permissions on %s", cache_dir
);
8182 if (chmod(cache_dir
, S_IRWXU
) == -1)
8187 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
8188 if (stat(certs_dir
, &sb
)) {
8189 if (mkdir(certs_dir
, S_IRWXU
) == -1)
8190 err(1, "mkdir certs_dir");
8191 if (stat(certs_dir
, &sb
))
8192 err(1, "stat certs_dir");
8194 if (S_ISDIR(sb
.st_mode
) == 0)
8195 errx(1, "%s not a dir", certs_dir
);
8196 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8197 warnx("fixing invalid permissions on %s", certs_dir
);
8198 if (chmod(certs_dir
, S_IRWXU
) == -1)
8203 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
8204 work_dir
, XT_SESSIONS_DIR
);
8205 if (stat(sessions_dir
, &sb
)) {
8206 if (mkdir(sessions_dir
, S_IRWXU
) == -1)
8207 err(1, "mkdir sessions_dir");
8208 if (stat(sessions_dir
, &sb
))
8209 err(1, "stat sessions_dir");
8211 if (S_ISDIR(sb
.st_mode
) == 0)
8212 errx(1, "%s not a dir", sessions_dir
);
8213 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8214 warnx("fixing invalid permissions on %s", sessions_dir
);
8215 if (chmod(sessions_dir
, S_IRWXU
) == -1)
8218 /* runtime settings that can override config file */
8219 if (runtime_settings
[0] != '\0')
8220 config_parse(runtime_settings
, 1);
8223 if (!strcmp(download_dir
, pwd
->pw_dir
))
8224 strlcat(download_dir
, "/downloads", sizeof download_dir
);
8225 if (stat(download_dir
, &sb
)) {
8226 if (mkdir(download_dir
, S_IRWXU
) == -1)
8227 err(1, "mkdir download_dir");
8228 if (stat(download_dir
, &sb
))
8229 err(1, "stat download_dir");
8231 if (S_ISDIR(sb
.st_mode
) == 0)
8232 errx(1, "%s not a dir", download_dir
);
8233 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8234 warnx("fixing invalid permissions on %s", download_dir
);
8235 if (chmod(download_dir
, S_IRWXU
) == -1)
8239 /* favorites file */
8240 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
8241 if (stat(file
, &sb
)) {
8242 warnx("favorites file doesn't exist, creating it");
8243 if ((f
= fopen(file
, "w")) == NULL
)
8244 err(1, "favorites");
8249 session
= webkit_get_default_session();
8250 /* XXX ssl-priority property not quite available yet */
8251 if (is_g_object_setting(G_OBJECT(session
), "ssl-priority"))
8252 g_object_set(G_OBJECT(session
), "ssl-priority", priority
,
8255 warnx("session does not have \"ssl-priority\" property");
8260 if (stat(ssl_ca_file
, &sb
)) {
8261 warn("no CA file: %s", ssl_ca_file
);
8262 g_free(ssl_ca_file
);
8265 g_object_set(session
,
8266 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
8267 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8272 env_proxy
= getenv("http_proxy");
8274 setup_proxy(env_proxy
);
8276 setup_proxy(http_proxy
);
8279 send_cmd_to_socket(argv
[0]);
8283 /* set some connection parameters */
8284 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8285 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8288 /* see if there is already an xxxterm running */
8289 if (single_instance
&& is_running()) {
8291 warnx("already running");
8297 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8298 send_cmd_to_socket(cmd
);
8308 /* uri completion */
8309 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8314 if (save_global_history
)
8315 restore_global_history();
8317 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
8318 restore_saved_tabs();
8320 a
.s
= named_session
;
8321 a
.i
= XT_SES_DONOTHING
;
8322 open_tabs(NULL
, &a
);
8326 create_new_tab(argv
[0], NULL
, focus
);
8333 if (TAILQ_EMPTY(&tabs
))
8334 create_new_tab(home
, NULL
, 1);
8337 if ((s
= build_socket()) != -1)
8338 gdk_input_add(s
, GDK_INPUT_READ
, socket_watcher
, NULL
);
8342 gnutls_global_deinit();