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>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
44 #include <sys/types.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
51 #include "freebsd/util.h"
57 #include <sys/queue.h>
59 #include <sys/socket.h>
63 #include <gdk/gdkkeysyms.h>
64 #include <webkit/webkit.h>
65 #include <libsoup/soup.h>
66 #include <gnutls/gnutls.h>
67 #include <JavaScriptCore/JavaScript.h>
68 #include <gnutls/x509.h>
70 #include "javascript.h"
73 javascript.h borrowed from vimprobable2 under the following license:
75 Copyright (c) 2009 Leon Winter
76 Copyright (c) 2009 Hannes Schueller
77 Copyright (c) 2009 Matto Fransen
79 Permission is hereby granted, free of charge, to any person obtaining a copy
80 of this software and associated documentation files (the "Software"), to deal
81 in the Software without restriction, including without limitation the rights
82 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83 copies of the Software, and to permit persons to whom the Software is
84 furnished to do so, subject to the following conditions:
86 The above copyright notice and this permission notice shall be included in
87 all copies or substantial portions of the Software.
89 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
98 static char *version
= "$xxxterm$";
100 /* hooked functions */
101 void (*_soup_cookie_jar_add_cookie
)(SoupCookieJar
*, SoupCookie
*);
102 void (*_soup_cookie_jar_delete_cookie
)(SoupCookieJar
*,
107 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
108 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
109 #define XT_D_MOVE 0x0001
110 #define XT_D_KEY 0x0002
111 #define XT_D_TAB 0x0004
112 #define XT_D_URL 0x0008
113 #define XT_D_CMD 0x0010
114 #define XT_D_NAV 0x0020
115 #define XT_D_DOWNLOAD 0x0040
116 #define XT_D_CONFIG 0x0080
117 #define XT_D_JS 0x0100
118 #define XT_D_FAVORITE 0x0200
119 #define XT_D_PRINTING 0x0400
120 #define XT_D_COOKIE 0x0800
121 #define XT_D_KEYBINDING 0x1000
122 u_int32_t swm_debug
= 0
138 #define DPRINTF(x...)
139 #define DNPRINTF(n,x...)
142 #define LENGTH(x) (sizeof x / sizeof x[0])
143 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
144 ~(GDK_BUTTON1_MASK) & \
145 ~(GDK_BUTTON2_MASK) & \
146 ~(GDK_BUTTON3_MASK) & \
147 ~(GDK_BUTTON4_MASK) & \
159 TAILQ_ENTRY(tab
) entry
;
161 GtkWidget
*tab_content
;
164 GtkWidget
*uri_entry
;
165 GtkWidget
*search_entry
;
167 GtkWidget
*browser_win
;
168 GtkWidget
*statusbar
;
175 GtkWidget
*js_toggle
;
176 GtkEntryCompletion
*completion
;
180 WebKitWebHistoryItem
*item
;
181 WebKitWebBackForwardList
*bfl
;
184 WebKitNetworkRequest
*icon_request
;
185 WebKitDownload
*icon_download
;
186 GdkPixbuf
*icon_pixbuf
;
187 gchar
*icon_dest_uri
;
189 /* adjustments for browser */
192 GtkAdjustment
*adjust_h
;
193 GtkAdjustment
*adjust_v
;
199 int xtp_meaning
; /* identifies dls/favorites */
204 #define XT_HINT_NONE (0)
205 #define XT_HINT_NUMERICAL (1)
206 #define XT_HINT_ALPHANUM (2)
210 /* custom stylesheet */
219 WebKitWebSettings
*settings
;
223 TAILQ_HEAD(tab_list
, tab
);
226 RB_ENTRY(history
) entry
;
230 RB_HEAD(history_list
, history
);
233 RB_ENTRY(download
) entry
;
235 WebKitDownload
*download
;
238 RB_HEAD(download_list
, download
);
241 RB_ENTRY(domain
) entry
;
243 int handy
; /* app use */
245 RB_HEAD(domain_list
, domain
);
248 TAILQ_ENTRY(undo
) entry
;
251 int back
; /* Keeps track of how many back
252 * history items there are. */
254 TAILQ_HEAD(undo_tailq
, undo
);
256 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
257 int next_download_id
= 1;
265 #define XT_NAME ("XXXTerm")
266 #define XT_DIR (".xxxterm")
267 #define XT_CACHE_DIR ("cache")
268 #define XT_CERT_DIR ("certs/")
269 #define XT_SESSIONS_DIR ("sessions/")
270 #define XT_CONF_FILE ("xxxterm.conf")
271 #define XT_FAVS_FILE ("favorites")
272 #define XT_SAVED_TABS_FILE ("main_session")
273 #define XT_RESTART_TABS_FILE ("restart_tabs")
274 #define XT_SOCKET_FILE ("socket")
275 #define XT_HISTORY_FILE ("history")
276 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
277 #define XT_CB_HANDLED (TRUE)
278 #define XT_CB_PASSTHROUGH (FALSE)
279 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
280 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
281 #define XT_DLMAN_REFRESH "10"
282 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
283 "td {overflow: hidden;" \
284 " padding: 2px 2px 2px 2px;" \
285 " border: 1px solid black}\n" \
286 "tr:hover {background: #ffff99 ;}\n" \
287 "th {background-color: #cccccc;" \
288 " border: 1px solid black}" \
289 "table {border-spacing: 0; " \
291 " border: 1px black solid;}\n" \
293 " border: 1px solid black;" \
299 " background: green;}" \
301 " font-size: small;" \
302 " text-align: center;}" \
304 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
305 #define XT_MAX_UNDO_CLOSE_TAB (32)
306 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
307 #define XT_PRINT_EXTRA_MARGIN 10
310 #define XT_COLOR_RED "#cc0000"
311 #define XT_COLOR_YELLOW "#ffff66"
312 #define XT_COLOR_BLUE "lightblue"
313 #define XT_COLOR_GREEN "#99ff66"
314 #define XT_COLOR_WHITE "white"
315 #define XT_COLOR_BLACK "black"
318 * xxxterm "protocol" (xtp)
319 * We use this for managing stuff like downloads and favorites. They
320 * make magical HTML pages in memory which have xxxt:// links in order
321 * to communicate with xxxterm's internals. These links take the format:
322 * xxxt://class/session_key/action/arg
324 * Don't begin xtp class/actions as 0. atoi returns that on error.
326 * Typically we have not put addition of items in this framework, as
327 * adding items is either done via an ex-command or via a keybinding instead.
330 #define XT_XTP_STR "xxxt://"
332 /* XTP classes (xxxt://<class>) */
333 #define XT_XTP_DL 1 /* downloads */
334 #define XT_XTP_HL 2 /* history */
335 #define XT_XTP_CL 3 /* cookies */
336 #define XT_XTP_FL 4 /* favorites */
338 /* XTP download actions */
339 #define XT_XTP_DL_LIST 1
340 #define XT_XTP_DL_CANCEL 2
341 #define XT_XTP_DL_REMOVE 3
343 /* XTP history actions */
344 #define XT_XTP_HL_LIST 1
345 #define XT_XTP_HL_REMOVE 2
347 /* XTP cookie actions */
348 #define XT_XTP_CL_LIST 1
349 #define XT_XTP_CL_REMOVE 2
351 /* XTP cookie actions */
352 #define XT_XTP_FL_LIST 1
353 #define XT_XTP_FL_REMOVE 2
355 /* xtp tab meanings - identifies which tabs have xtp pages in */
356 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
357 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
358 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
359 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
360 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
363 #define XT_MOVE_INVALID (0)
364 #define XT_MOVE_DOWN (1)
365 #define XT_MOVE_UP (2)
366 #define XT_MOVE_BOTTOM (3)
367 #define XT_MOVE_TOP (4)
368 #define XT_MOVE_PAGEDOWN (5)
369 #define XT_MOVE_PAGEUP (6)
370 #define XT_MOVE_HALFDOWN (7)
371 #define XT_MOVE_HALFUP (8)
372 #define XT_MOVE_LEFT (9)
373 #define XT_MOVE_FARLEFT (10)
374 #define XT_MOVE_RIGHT (11)
375 #define XT_MOVE_FARRIGHT (12)
377 #define XT_TAB_LAST (-4)
378 #define XT_TAB_FIRST (-3)
379 #define XT_TAB_PREV (-2)
380 #define XT_TAB_NEXT (-1)
381 #define XT_TAB_INVALID (0)
382 #define XT_TAB_NEW (1)
383 #define XT_TAB_DELETE (2)
384 #define XT_TAB_DELQUIT (3)
385 #define XT_TAB_OPEN (4)
386 #define XT_TAB_UNDO_CLOSE (5)
387 #define XT_TAB_SHOW (6)
388 #define XT_TAB_HIDE (7)
390 #define XT_NAV_INVALID (0)
391 #define XT_NAV_BACK (1)
392 #define XT_NAV_FORWARD (2)
393 #define XT_NAV_RELOAD (3)
394 #define XT_NAV_RELOAD_CACHE (4)
396 #define XT_FOCUS_INVALID (0)
397 #define XT_FOCUS_URI (1)
398 #define XT_FOCUS_SEARCH (2)
400 #define XT_SEARCH_INVALID (0)
401 #define XT_SEARCH_NEXT (1)
402 #define XT_SEARCH_PREV (2)
404 #define XT_PASTE_CURRENT_TAB (0)
405 #define XT_PASTE_NEW_TAB (1)
407 #define XT_FONT_SET (0)
409 #define XT_URL_SHOW (1)
410 #define XT_URL_HIDE (2)
412 #define XT_STATUSBAR_SHOW (1)
413 #define XT_STATUSBAR_HIDE (2)
415 #define XT_WL_TOGGLE (1<<0)
416 #define XT_WL_ENABLE (1<<1)
417 #define XT_WL_DISABLE (1<<2)
418 #define XT_WL_FQDN (1<<3) /* default */
419 #define XT_WL_TOPLEVEL (1<<4)
420 #define XT_WL_PERSISTENT (1<<5)
421 #define XT_WL_SESSION (1<<6)
423 #define XT_SHOW (1<<7)
424 #define XT_DELETE (1<<8)
425 #define XT_SAVE (1<<9)
426 #define XT_OPEN (1<<10)
428 #define XT_CMD_OPEN (0)
429 #define XT_CMD_OPEN_CURRENT (1)
430 #define XT_CMD_TABNEW (2)
431 #define XT_CMD_TABNEW_CURRENT (3)
433 #define XT_STATUS_NOTHING (0)
434 #define XT_STATUS_LINK (1)
435 #define XT_STATUS_URI (2)
436 #define XT_STATUS_LOADING (3)
438 #define XT_SES_DONOTHING (0)
439 #define XT_SES_CLOSETABS (1)
441 #define XT_BM_NORMAL (0)
442 #define XT_BM_WHITELIST (1)
443 #define XT_BM_KIOSK (2)
451 TAILQ_ENTRY(mime_type
) entry
;
453 TAILQ_HEAD(mime_type_list
, mime_type
);
459 TAILQ_ENTRY(alias
) entry
;
461 TAILQ_HEAD(alias_list
, alias
);
463 /* settings that require restart */
464 int tabless
= 0; /* allow only 1 tab */
465 int enable_socket
= 0;
466 int single_instance
= 0; /* only allow one xxxterm to run */
467 int fancy_bar
= 1; /* fancy toolbar */
468 int browser_mode
= XT_BM_NORMAL
;
470 /* runtime settings */
471 int show_tabs
= 1; /* show tabs on notebook */
472 int show_url
= 1; /* show url toolbar on notebook */
473 int show_statusbar
= 0; /* vimperator style status bar */
474 int ctrl_click_focus
= 0; /* ctrl click gets focus */
475 int cookies_enabled
= 1; /* enable cookies */
476 int read_only_cookies
= 0; /* enable to not write cookies */
477 int enable_scripts
= 1;
478 int enable_plugins
= 0;
479 int default_font_size
= 12;
480 gfloat default_zoom_level
= 1.0;
481 int window_height
= 768;
482 int window_width
= 1024;
483 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
484 unsigned refresh_interval
= 10; /* download refresh interval */
485 int enable_cookie_whitelist
= 0;
486 int enable_js_whitelist
= 0;
487 time_t session_timeout
= 3600; /* cookie session timeout */
488 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
489 char *ssl_ca_file
= NULL
;
490 char *resource_dir
= NULL
;
491 gboolean ssl_strict_certs
= FALSE
;
492 int append_next
= 1; /* append tab after current tab */
494 char *search_string
= NULL
;
495 char *http_proxy
= NULL
;
496 char download_dir
[PATH_MAX
];
497 char runtime_settings
[PATH_MAX
]; /* override of settings */
498 int allow_volatile_cookies
= 0;
499 int save_global_history
= 0; /* save global history to disk */
500 char *user_agent
= NULL
;
501 int save_rejected_cookies
= 0;
502 time_t session_autosave
= 0;
503 int guess_search
= 0;
504 int dns_prefetch
= FALSE
;
505 gint max_connections
= 25;
506 gint max_host_connections
= 5;
510 int set_download_dir(struct settings
*, char *);
511 int set_work_dir(struct settings
*, char *);
512 int set_runtime_dir(struct settings
*, char *);
513 int set_browser_mode(struct settings
*, char *);
514 int set_cookie_policy(struct settings
*, char *);
515 int add_alias(struct settings
*, char *);
516 int add_mime_type(struct settings
*, char *);
517 int add_cookie_wl(struct settings
*, char *);
518 int add_js_wl(struct settings
*, char *);
519 int add_kb(struct settings
*, char *);
520 void button_set_stockid(GtkWidget
*, char *);
521 GtkWidget
* create_button(char *, char *, int);
523 char *get_browser_mode(struct settings
*);
524 char *get_cookie_policy(struct settings
*);
526 char *get_download_dir(struct settings
*);
527 char *get_work_dir(struct settings
*);
528 char *get_runtime_dir(struct settings
*);
530 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
531 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
532 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
533 void walk_kb(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
534 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
537 int (*set
)(struct settings
*, char *);
538 char *(*get
)(struct settings
*);
539 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
542 struct special s_browser_mode
= {
548 struct special s_cookie
= {
554 struct special s_alias
= {
560 struct special s_mime
= {
566 struct special s_js
= {
572 struct special s_kb
= {
578 struct special s_cookie_wl
= {
584 struct special s_download_dir
= {
590 struct special s_work_dir
= {
599 #define XT_S_INVALID (0)
602 #define XT_S_FLOAT (3)
604 #define XT_SF_RESTART (1<<0)
605 #define XT_SF_RUNTIME (1<<1)
611 { "append_next", XT_S_INT
, 0, &append_next
, NULL
, NULL
},
612 { "allow_volatile_cookies", XT_S_INT
, 0, &allow_volatile_cookies
, NULL
, NULL
},
613 { "browser_mode", XT_S_INT
, 0, NULL
, NULL
,&s_browser_mode
},
614 { "cookie_policy", XT_S_INT
, 0, NULL
, NULL
,&s_cookie
},
615 { "cookies_enabled", XT_S_INT
, 0, &cookies_enabled
, NULL
, NULL
},
616 { "ctrl_click_focus", XT_S_INT
, 0, &ctrl_click_focus
, NULL
, NULL
},
617 { "default_font_size", XT_S_INT
, 0, &default_font_size
, NULL
, NULL
},
618 { "default_zoom_level", XT_S_FLOAT
, 0, NULL
, NULL
, NULL
, &default_zoom_level
},
619 { "download_dir", XT_S_STR
, 0, NULL
, NULL
,&s_download_dir
},
620 { "enable_cookie_whitelist", XT_S_INT
, 0, &enable_cookie_whitelist
, NULL
, NULL
},
621 { "enable_js_whitelist", XT_S_INT
, 0, &enable_js_whitelist
, NULL
, NULL
},
622 { "enable_plugins", XT_S_INT
, 0, &enable_plugins
, NULL
, NULL
},
623 { "enable_scripts", XT_S_INT
, 0, &enable_scripts
, NULL
, NULL
},
624 { "enable_socket", XT_S_INT
, XT_SF_RESTART
,&enable_socket
, NULL
, NULL
},
625 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
,&fancy_bar
, NULL
, NULL
},
626 { "home", XT_S_STR
, 0, NULL
, &home
, NULL
},
627 { "http_proxy", XT_S_STR
, 0, NULL
, &http_proxy
, NULL
},
628 { "icon_size", XT_S_INT
, 0, &icon_size
, NULL
, NULL
},
629 { "max_connections", XT_S_INT
, XT_SF_RESTART
,&max_connections
, NULL
, NULL
},
630 { "max_host_connections", XT_S_INT
, XT_SF_RESTART
,&max_host_connections
, NULL
, NULL
},
631 { "read_only_cookies", XT_S_INT
, 0, &read_only_cookies
, NULL
, NULL
},
632 { "refresh_interval", XT_S_INT
, 0, &refresh_interval
, NULL
, NULL
},
633 { "resource_dir", XT_S_STR
, 0, NULL
, &resource_dir
, NULL
},
634 { "search_string", XT_S_STR
, 0, NULL
, &search_string
, NULL
},
635 { "save_global_history", XT_S_INT
, XT_SF_RESTART
,&save_global_history
, NULL
, NULL
},
636 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
,&save_rejected_cookies
, NULL
, NULL
},
637 { "session_timeout", XT_S_INT
, 0, &session_timeout
, NULL
, NULL
},
638 { "session_autosave", XT_S_INT
, 0, &session_autosave
, NULL
, NULL
},
639 { "single_instance", XT_S_INT
, XT_SF_RESTART
,&single_instance
, NULL
, NULL
},
640 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
641 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
642 { "guess_search", XT_S_INT
, 0, &guess_search
, NULL
, NULL
},
643 { "show_statusbar", XT_S_INT
, 0, &show_statusbar
, NULL
, NULL
},
644 { "ssl_ca_file", XT_S_STR
, 0, NULL
, &ssl_ca_file
, NULL
},
645 { "ssl_strict_certs", XT_S_INT
, 0, &ssl_strict_certs
, NULL
, NULL
},
646 { "user_agent", XT_S_STR
, 0, NULL
, &user_agent
, NULL
},
647 { "window_height", XT_S_INT
, 0, &window_height
, NULL
, NULL
},
648 { "window_width", XT_S_INT
, 0, &window_width
, NULL
, NULL
},
649 { "work_dir", XT_S_STR
, 0, NULL
, NULL
,&s_work_dir
},
651 /* runtime settings */
652 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
653 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
654 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
655 { "keybinding", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_kb
},
656 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
659 int about(struct tab
*, struct karg
*);
660 int blank(struct tab
*, struct karg
*);
661 int cookie_show_wl(struct tab
*, struct karg
*);
662 int js_show_wl(struct tab
*, struct karg
*);
663 int help(struct tab
*, struct karg
*);
664 int set(struct tab
*, struct karg
*);
665 int stats(struct tab
*, struct karg
*);
666 int marco(struct tab
*, struct karg
*);
667 const char * marco_message(int *);
668 int xtp_page_cl(struct tab
*, struct karg
*);
669 int xtp_page_dl(struct tab
*, struct karg
*);
670 int xtp_page_fl(struct tab
*, struct karg
*);
671 int xtp_page_hl(struct tab
*, struct karg
*);
673 #define XT_URI_ABOUT ("about:")
674 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
675 #define XT_URI_ABOUT_ABOUT ("about")
676 #define XT_URI_ABOUT_BLANK ("blank")
677 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
678 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
679 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
680 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
681 #define XT_URI_ABOUT_FAVORITES ("favorites")
682 #define XT_URI_ABOUT_HELP ("help")
683 #define XT_URI_ABOUT_HISTORY ("history")
684 #define XT_URI_ABOUT_JSWL ("jswl")
685 #define XT_URI_ABOUT_SET ("set")
686 #define XT_URI_ABOUT_STATS ("stats")
687 #define XT_URI_ABOUT_MARCO ("marco")
691 int (*func
)(struct tab
*, struct karg
*);
693 { XT_URI_ABOUT_ABOUT
, about
},
694 { XT_URI_ABOUT_BLANK
, blank
},
695 { XT_URI_ABOUT_COOKIEWL
, cookie_show_wl
},
696 { XT_URI_ABOUT_COOKIEJAR
, xtp_page_cl
},
697 { XT_URI_ABOUT_DOWNLOADS
, xtp_page_dl
},
698 { XT_URI_ABOUT_FAVORITES
, xtp_page_fl
},
699 { XT_URI_ABOUT_HELP
, help
},
700 { XT_URI_ABOUT_HISTORY
, xtp_page_hl
},
701 { XT_URI_ABOUT_JSWL
, js_show_wl
},
702 { XT_URI_ABOUT_SET
, set
},
703 { XT_URI_ABOUT_STATS
, stats
},
704 { XT_URI_ABOUT_MARCO
, marco
},
708 extern char *__progname
;
711 GtkWidget
*main_window
;
712 GtkNotebook
*notebook
;
713 GtkWidget
*arrow
, *abtn
;
714 struct tab_list tabs
;
715 struct history_list hl
;
716 struct download_list downloads
;
717 struct domain_list c_wl
;
718 struct domain_list js_wl
;
719 struct undo_tailq undos
;
720 struct keybinding_list kbl
;
722 int updating_dl_tabs
= 0;
723 int updating_hl_tabs
= 0;
724 int updating_cl_tabs
= 0;
725 int updating_fl_tabs
= 0;
727 uint64_t blocked_cookies
= 0;
728 char named_session
[PATH_MAX
];
729 void update_favicon(struct tab
*);
730 int icon_size_map(int);
732 GtkListStore
*completion_model
;
733 void completion_add(struct tab
*);
734 void completion_add_uri(const gchar
*);
739 int saved_errno
, status
;
744 while ((pid
= waitpid(WAIT_ANY
, &status
, WNOHANG
)) != 0) {
748 if (errno
!= ECHILD
) {
750 clog_warn("sigchild: waitpid:");
756 if (WIFEXITED(status
)) {
757 if (WEXITSTATUS(status
) != 0) {
759 clog_warnx("sigchild: child exit status: %d",
760 WEXITSTATUS(status));
765 clog_warnx("sigchild: child is terminated abnormally");
774 is_g_object_setting(GObject
*o
, char *str
)
776 guint n_props
= 0, i
;
777 GParamSpec
**proplist
;
779 if (! G_IS_OBJECT(o
))
782 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
785 for (i
=0; i
< n_props
; i
++) {
786 if (! strcmp(proplist
[i
]->name
, str
))
793 * Display a web page from a HTML string in memory, rather than from a URL
796 load_webkit_string(struct tab
*t
, const char *str
, gchar
*title
)
802 /* we set this to indicate we want to manually do navaction */
804 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
806 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, "");
807 #if GTK_CHECK_VERSION(2, 20, 0)
808 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
809 gtk_widget_hide(t
->spinner
);
813 uri
= g_strdup_printf("%s%s", XT_URI_ABOUT
, title
);
814 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
817 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[0]);
818 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
819 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
820 GTK_ENTRY_ICON_PRIMARY
, pb
);
821 gdk_pixbuf_unref(pb
);
826 set_status(struct tab
*t
, gchar
*s
, int status
)
834 case XT_STATUS_LOADING
:
835 type
= g_strdup_printf("Loading: %s", s
);
839 type
= g_strdup_printf("Link: %s", s
);
841 t
->status
= g_strdup(gtk_entry_get_text(GTK_ENTRY(t
->statusbar
)));
845 type
= g_strdup_printf("%s", s
);
847 t
->status
= g_strdup(type
);
851 t
->status
= g_strdup(s
);
853 case XT_STATUS_NOTHING
:
858 gtk_entry_set_text(GTK_ENTRY(t
->statusbar
), s
);
864 hide_oops(struct tab
*t
)
866 gtk_widget_hide(t
->oops
);
870 hide_cmd(struct tab
*t
)
872 gtk_widget_hide(t
->cmd
);
876 show_cmd(struct tab
*t
)
878 gtk_widget_hide(t
->oops
);
879 gtk_widget_show(t
->cmd
);
883 show_oops(struct tab
*t
, const char *fmt
, ...)
892 if (vasprintf(&msg
, fmt
, ap
) == -1)
893 errx(1, "show_oops failed");
896 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
897 gtk_widget_hide(t
->cmd
);
898 gtk_widget_show(t
->oops
);
901 /* XXX collapse with show_oops */
903 show_oops_s(const char *fmt
, ...)
907 struct tab
*ti
, *t
= NULL
;
912 TAILQ_FOREACH(ti
, &tabs
, entry
)
913 if (ti
->tab_id
== gtk_notebook_current_page(notebook
)) {
921 if (vasprintf(&msg
, fmt
, ap
) == -1)
922 errx(1, "show_oops_s failed");
925 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
926 gtk_widget_hide(t
->cmd
);
927 gtk_widget_show(t
->oops
);
931 get_as_string(struct settings
*s
)
942 warnx("get_as_string skip %s\n", s
->name
);
943 } else if (s
->type
== XT_S_INT
)
944 r
= g_strdup_printf("%d", *s
->ival
);
945 else if (s
->type
== XT_S_STR
)
946 r
= g_strdup(*s
->sval
);
947 else if (s
->type
== XT_S_FLOAT
)
948 r
= g_strdup_printf("%f", *s
->fval
);
950 r
= g_strdup_printf("INVALID TYPE");
956 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
961 for (i
= 0; i
< LENGTH(rs
); i
++) {
962 if (rs
[i
].s
&& rs
[i
].s
->walk
)
963 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
965 s
= get_as_string(&rs
[i
]);
966 cb(&rs
[i
], s
, cb_args
);
973 set_browser_mode(struct settings
*s
, char *val
)
975 if (!strcmp(val
, "whitelist")) {
976 browser_mode
= XT_BM_WHITELIST
;
977 allow_volatile_cookies
= 0;
978 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
980 enable_cookie_whitelist
= 1;
981 read_only_cookies
= 0;
982 save_rejected_cookies
= 0;
983 session_timeout
= 3600;
985 enable_js_whitelist
= 1;
986 } else if (!strcmp(val
, "normal")) {
987 browser_mode
= XT_BM_NORMAL
;
988 allow_volatile_cookies
= 0;
989 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
991 enable_cookie_whitelist
= 0;
992 read_only_cookies
= 0;
993 save_rejected_cookies
= 0;
994 session_timeout
= 3600;
996 enable_js_whitelist
= 0;
997 } else if (!strcmp(val
, "kiosk")) {
998 browser_mode
= XT_BM_KIOSK
;
999 allow_volatile_cookies
= 0;
1000 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1001 cookies_enabled
= 1;
1002 enable_cookie_whitelist
= 0;
1003 read_only_cookies
= 0;
1004 save_rejected_cookies
= 0;
1005 session_timeout
= 3600;
1007 enable_js_whitelist
= 0;
1017 get_browser_mode(struct settings
*s
)
1021 if (browser_mode
== XT_BM_WHITELIST
)
1022 r
= g_strdup("whitelist");
1023 else if (browser_mode
== XT_BM_NORMAL
)
1024 r
= g_strdup("normal");
1025 else if (browser_mode
== XT_BM_KIOSK
)
1026 r
= g_strdup("kiosk");
1034 set_cookie_policy(struct settings
*s
, char *val
)
1036 if (!strcmp(val
, "no3rdparty"))
1037 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1038 else if (!strcmp(val
, "accept"))
1039 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1040 else if (!strcmp(val
, "reject"))
1041 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1049 get_cookie_policy(struct settings
*s
)
1053 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1054 r
= g_strdup("no3rdparty");
1055 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1056 r
= g_strdup("accept");
1057 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1058 r
= g_strdup("reject");
1066 get_download_dir(struct settings
*s
)
1068 if (download_dir
[0] == '\0')
1070 return (g_strdup(download_dir
));
1074 set_download_dir(struct settings
*s
, char *val
)
1077 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1078 pwd
->pw_dir
, &val
[1]);
1080 strlcpy(download_dir
, val
, sizeof download_dir
);
1087 * We use these to prevent people putting xxxt:// URLs on
1088 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1090 #define XT_XTP_SES_KEY_SZ 8
1091 #define XT_XTP_SES_KEY_HEX_FMT \
1092 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1093 char *dl_session_key
; /* downloads */
1094 char *hl_session_key
; /* history list */
1095 char *cl_session_key
; /* cookie list */
1096 char *fl_session_key
; /* favorites list */
1098 char work_dir
[PATH_MAX
];
1099 char certs_dir
[PATH_MAX
];
1100 char cache_dir
[PATH_MAX
];
1101 char sessions_dir
[PATH_MAX
];
1102 char cookie_file
[PATH_MAX
];
1103 SoupURI
*proxy_uri
= NULL
;
1104 SoupSession
*session
;
1105 SoupCookieJar
*s_cookiejar
;
1106 SoupCookieJar
*p_cookiejar
;
1107 char rc_fname
[PATH_MAX
];
1109 struct mime_type_list mtl
;
1110 struct alias_list aliases
;
1113 struct tab
*create_new_tab(char *, struct undo
*, int);
1114 void delete_tab(struct tab
*);
1115 void adjustfont_webkit(struct tab
*, int);
1116 int run_script(struct tab
*, char *);
1117 int download_rb_cmp(struct download
*, struct download
*);
1118 gboolean
cmd_execute(struct tab
*t
, char *str
);
1121 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1123 return (strcmp(h1
->uri
, h2
->uri
));
1125 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1128 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1130 return (strcmp(d1
->d
, d2
->d
));
1132 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1135 get_work_dir(struct settings
*s
)
1137 if (work_dir
[0] == '\0')
1139 return (g_strdup(work_dir
));
1143 set_work_dir(struct settings
*s
, char *val
)
1146 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1147 pwd
->pw_dir
, &val
[1]);
1149 strlcpy(work_dir
, val
, sizeof work_dir
);
1155 * generate a session key to secure xtp commands.
1156 * pass in a ptr to the key in question and it will
1157 * be modified in place.
1160 generate_xtp_session_key(char **key
)
1162 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1168 /* make a new one */
1169 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1170 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1171 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1172 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1174 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1178 * validate a xtp session key.
1182 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1184 if (strcmp(trusted
, untrusted
) != 0) {
1185 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1194 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1196 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1198 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1200 struct valid_url_types
{
1211 valid_url_type(char *url
)
1215 for (i
= 0; i
< LENGTH(vut
); i
++)
1216 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1223 print_cookie(char *msg
, SoupCookie
*c
)
1229 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1230 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1231 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1232 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1233 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1234 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1235 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1236 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1237 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1238 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1242 walk_alias(struct settings
*s
,
1243 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1248 if (s
== NULL
|| cb
== NULL
) {
1249 show_oops_s("walk_alias invalid parameters");
1253 TAILQ_FOREACH(a
, &aliases
, entry
) {
1254 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1255 cb(s
, str
, cb_args
);
1261 match_alias(char *url_in
)
1265 char *url_out
= NULL
, *search
, *enc_arg
;
1267 search
= g_strdup(url_in
);
1269 if (strsep(&arg
, " \t") == NULL
) {
1270 show_oops_s("match_alias: NULL URL");
1274 TAILQ_FOREACH(a
, &aliases
, entry
) {
1275 if (!strcmp(search
, a
->a_name
))
1280 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1283 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1284 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1287 url_out
= g_strdup(a
->a_uri
);
1295 guess_url_type(char *url_in
)
1298 char *url_out
= NULL
, *enc_search
= NULL
;
1300 url_out
= match_alias(url_in
);
1301 if (url_out
!= NULL
)
1306 * If there is no dot nor slash in the string and it isn't a
1307 * path to a local file and doesn't resolves to an IP, assume
1308 * that the user wants to search for the string.
1311 if (strchr(url_in
, '.') == NULL
&&
1312 strchr(url_in
, '/') == NULL
&&
1313 stat(url_in
, &sb
) != 0 &&
1314 gethostbyname(url_in
) == NULL
) {
1316 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1317 url_out
= g_strdup_printf(search_string
, enc_search
);
1323 /* XXX not sure about this heuristic */
1324 if (stat(url_in
, &sb
) == 0)
1325 url_out
= g_strdup_printf("file://%s", url_in
);
1327 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1329 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1335 load_uri(struct tab
*t
, gchar
*uri
)
1338 gchar
*newuri
= NULL
;
1344 /* Strip leading spaces. */
1345 while(*uri
&& isspace(*uri
))
1348 if (strlen(uri
) == 0) {
1353 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1354 for (i
= 0; i
< LENGTH(about_list
); i
++)
1355 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1356 bzero(&args
, sizeof args
);
1357 about_list
[i
].func(t
, &args
);
1360 show_oops(t
, "invalid about page");
1364 if (valid_url_type(uri
)) {
1365 newuri
= guess_url_type(uri
);
1369 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1370 webkit_web_view_load_uri(t
->wv
, uri
);
1377 get_uri(WebKitWebView
*wv
)
1379 WebKitWebFrame
*frame
;
1382 frame
= webkit_web_view_get_main_frame(wv
);
1383 uri
= webkit_web_frame_get_uri(frame
);
1385 if (uri
&& strlen(uri
) > 0)
1392 add_alias(struct settings
*s
, char *line
)
1395 struct alias
*a
= NULL
;
1397 if (s
== NULL
|| line
== NULL
) {
1398 show_oops_s("add_alias invalid parameters");
1403 a
= g_malloc(sizeof(*a
));
1405 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1406 show_oops_s("add_alias: incomplete alias definition");
1409 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1410 show_oops_s("add_alias: invalid alias definition");
1414 a
->a_name
= g_strdup(alias
);
1415 a
->a_uri
= g_strdup(l
);
1417 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1419 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1429 add_mime_type(struct settings
*s
, char *line
)
1433 struct mime_type
*m
= NULL
;
1434 int downloadfirst
= 0;
1436 /* XXX this could be smarter */
1438 if (line
== NULL
&& strlen(line
) == 0) {
1439 show_oops_s("add_mime_type invalid parameters");
1448 m
= g_malloc(sizeof(*m
));
1450 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1451 show_oops_s("add_mime_type: invalid mime_type");
1454 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1455 mime_type
[strlen(mime_type
) - 1] = '\0';
1460 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1461 show_oops_s("add_mime_type: invalid mime_type");
1465 m
->mt_type
= g_strdup(mime_type
);
1466 m
->mt_action
= g_strdup(l
);
1467 m
->mt_download
= downloadfirst
;
1469 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1470 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1472 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1482 find_mime_type(char *mime_type
)
1484 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1486 TAILQ_FOREACH(m
, &mtl
, entry
) {
1487 if (m
->mt_default
&&
1488 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1491 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1504 walk_mime_type(struct settings
*s
,
1505 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1507 struct mime_type
*m
;
1510 if (s
== NULL
|| cb
== NULL
)
1511 show_oops_s("walk_mime_type invalid parameters");
1513 TAILQ_FOREACH(m
, &mtl
, entry
) {
1514 str
= g_strdup_printf("%s%s --> %s",
1516 m
->mt_default
? "*" : "",
1518 cb(s
, str
, cb_args
);
1524 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1529 if (str
== NULL
|| wl
== NULL
)
1531 if (strlen(str
) < 2)
1534 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1536 /* treat *.moo.com the same as .moo.com */
1537 if (str
[0] == '*' && str
[1] == '.')
1539 else if (str
[0] == '.')
1544 d
= g_malloc(sizeof *d
);
1546 d
->d
= g_strdup_printf(".%s", str
);
1548 d
->d
= g_strdup(str
);
1551 if (RB_INSERT(domain_list
, wl
, d
))
1554 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1565 add_cookie_wl(struct settings
*s
, char *entry
)
1567 wl_add(entry
, &c_wl
, 1);
1572 walk_cookie_wl(struct settings
*s
,
1573 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1577 if (s
== NULL
|| cb
== NULL
) {
1578 show_oops_s("walk_cookie_wl invalid parameters");
1582 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1583 cb(s
, d
->d
, cb_args
);
1587 walk_js_wl(struct settings
*s
,
1588 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1592 if (s
== NULL
|| cb
== NULL
) {
1593 show_oops_s("walk_js_wl invalid parameters");
1597 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1598 cb(s
, d
->d
, cb_args
);
1602 add_js_wl(struct settings
*s
, char *entry
)
1604 wl_add(entry
, &js_wl
, 1 /* persistent */);
1609 wl_find(const gchar
*search
, struct domain_list
*wl
)
1612 struct domain
*d
= NULL
, dfind
;
1615 if (search
== NULL
|| wl
== NULL
)
1617 if (strlen(search
) < 2)
1620 if (search
[0] != '.')
1621 s
= g_strdup_printf(".%s", search
);
1623 s
= g_strdup(search
);
1625 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1628 d
= RB_FIND(domain_list
, wl
, &dfind
);
1642 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1648 if (s
== NULL
|| wl
== NULL
)
1651 if (!strncmp(s
, "http://", strlen("http://")))
1652 s
= &s
[strlen("http://")];
1653 else if (!strncmp(s
, "https://", strlen("https://")))
1654 s
= &s
[strlen("https://")];
1659 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1660 /* chop string at first slash */
1661 if (s
[i
] == '/' || s
[i
] == '\0') {
1664 r
= wl_find(ss
, wl
);
1673 get_toplevel_domain(char *domain
)
1680 if (strlen(domain
) < 2)
1683 s
= &domain
[strlen(domain
) - 1];
1684 while (s
!= domain
) {
1700 settings_add(char *var
, char *val
)
1707 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
1708 if (strcmp(var
, rs
[i
].name
))
1712 if (rs
[i
].s
->set(&rs
[i
], val
))
1713 errx(1, "invalid value for %s: %s", var
, val
);
1717 switch (rs
[i
].type
) {
1726 errx(1, "invalid sval for %s",
1740 errx(1, "invalid type for %s", var
);
1749 config_parse(char *filename
, int runtime
)
1752 char *line
, *cp
, *var
, *val
;
1753 size_t len
, lineno
= 0;
1755 char file
[PATH_MAX
];
1758 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
1760 if (filename
== NULL
)
1763 if (runtime
&& runtime_settings
[0] != '\0') {
1764 snprintf(file
, sizeof file
, "%s/%s",
1765 work_dir
, runtime_settings
);
1766 if (stat(file
, &sb
)) {
1767 warnx("runtime file doesn't exist, creating it");
1768 if ((f
= fopen(file
, "w")) == NULL
)
1770 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
1774 strlcpy(file
, filename
, sizeof file
);
1776 if ((config
= fopen(file
, "r")) == NULL
) {
1777 warn("config_parse: cannot open %s", filename
);
1782 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
1783 if (feof(config
) || ferror(config
))
1787 cp
+= (long)strspn(cp
, WS
);
1788 if (cp
[0] == '\0') {
1794 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
1795 errx(1, "invalid config file entry: %s", line
);
1797 cp
+= (long)strspn(cp
, WS
);
1799 if ((val
= strsep(&cp
, "\0")) == NULL
)
1802 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
1803 handled
= settings_add(var
, val
);
1805 errx(1, "invalid conf file entry: %s=%s", var
, val
);
1814 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1820 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1824 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1827 JSStringGetUTF8CString(jsref
, s
, l
);
1828 JSStringRelease(jsref
);
1834 disable_hints(struct tab
*t
)
1836 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1837 bzero(t
->hint_num
, sizeof t
->hint_num
);
1838 run_script(t
, "vimprobable_clear()");
1840 t
->hint_mode
= XT_HINT_NONE
;
1844 enable_hints(struct tab
*t
)
1846 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1847 run_script(t
, "vimprobable_show_hints()");
1849 t
->hint_mode
= XT_HINT_NONE
;
1852 #define XT_JS_OPEN ("open;")
1853 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1854 #define XT_JS_FIRE ("fire;")
1855 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1856 #define XT_JS_FOUND ("found;")
1857 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1860 run_script(struct tab
*t
, char *s
)
1862 JSGlobalContextRef ctx
;
1863 WebKitWebFrame
*frame
;
1865 JSValueRef val
, exception
;
1868 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
1869 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1871 frame
= webkit_web_view_get_main_frame(t
->wv
);
1872 ctx
= webkit_web_frame_get_global_context(frame
);
1874 str
= JSStringCreateWithUTF8CString(s
);
1875 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1876 NULL
, 0, &exception
);
1877 JSStringRelease(str
);
1879 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
1881 es
= js_ref_to_string(ctx
, exception
);
1882 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
1886 es
= js_ref_to_string(ctx
, val
);
1887 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
1889 /* handle return value right here */
1890 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
1892 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
1895 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
1896 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
1897 &es
[XT_JS_FIRE_LEN
]);
1902 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
1903 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
1914 hint(struct tab
*t
, struct karg
*args
)
1917 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
1919 if (t
->hints_on
== 0)
1928 apply_style(struct tab
*t
)
1930 g_object_set(G_OBJECT(t
->settings
),
1931 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
1935 userstyle(struct tab
*t
, struct karg
*args
)
1937 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
1941 g_object_set(G_OBJECT(t
->settings
),
1942 "user-stylesheet-uri", NULL
, (char *)NULL
);
1951 * Doesn't work fully, due to the following bug:
1952 * https://bugs.webkit.org/show_bug.cgi?id=51747
1955 restore_global_history(void)
1957 char file
[PATH_MAX
];
1963 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
1965 if ((f
= fopen(file
, "r")) == NULL
) {
1966 warnx("%s: fopen", __func__
);
1971 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1972 if (feof(f
) || ferror(f
))
1975 if ((title
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1976 if (feof(f
) || ferror(f
)) {
1978 warnx("%s: broken history file\n", __func__
);
1982 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
1983 webkit_web_history_item_new_with_data(uri
, title
);
1984 h
= g_malloc(sizeof(struct history
));
1985 h
->uri
= g_strdup(uri
);
1986 h
->title
= g_strdup(title
);
1987 RB_INSERT(history_list
, &hl
, h
);
1988 completion_add_uri(h
->uri
);
1990 warnx("%s: failed to restore history\n", __func__
);
2006 save_global_history_to_disk(struct tab
*t
)
2008 char file
[PATH_MAX
];
2012 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2014 if ((f
= fopen(file
, "w")) == NULL
) {
2015 show_oops(t
, "%s: global history file: %s",
2016 __func__
, strerror(errno
));
2020 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2021 if (h
->uri
&& h
->title
)
2022 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2031 quit(struct tab
*t
, struct karg
*args
)
2033 if (save_global_history
)
2034 save_global_history_to_disk(t
);
2042 open_tabs(struct tab
*t
, struct karg
*a
)
2044 char file
[PATH_MAX
];
2048 struct tab
*ti
, *tt
;
2053 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2054 if ((f
= fopen(file
, "r")) == NULL
)
2057 ti
= TAILQ_LAST(&tabs
, tab_list
);
2060 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2061 if (feof(f
) || ferror(f
))
2064 /* retrieve session name */
2065 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2066 strlcpy(named_session
,
2067 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2068 sizeof named_session
);
2072 if (uri
&& strlen(uri
))
2073 create_new_tab(uri
, NULL
, 1);
2079 /* close open tabs */
2080 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2082 tt
= TAILQ_FIRST(&tabs
);
2101 restore_saved_tabs(void)
2103 char file
[PATH_MAX
];
2104 int unlink_file
= 0;
2109 snprintf(file
, sizeof file
, "%s/%s",
2110 sessions_dir
, XT_RESTART_TABS_FILE
);
2111 if (stat(file
, &sb
) == -1)
2112 a
.s
= XT_SAVED_TABS_FILE
;
2115 a
.s
= XT_RESTART_TABS_FILE
;
2118 a
.i
= XT_SES_DONOTHING
;
2119 rv
= open_tabs(NULL
, &a
);
2128 save_tabs(struct tab
*t
, struct karg
*a
)
2130 char file
[PATH_MAX
];
2135 const gchar
**arr
= NULL
;
2140 snprintf(file
, sizeof file
, "%s/%s",
2141 sessions_dir
, named_session
);
2143 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2145 if ((f
= fopen(file
, "w")) == NULL
) {
2146 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2150 /* save session name */
2151 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2153 /* save tabs, in the order they are arranged in the notebook */
2154 TAILQ_FOREACH(ti
, &tabs
, entry
)
2157 arr
= g_malloc0(len
* sizeof(gchar
*));
2159 TAILQ_FOREACH(ti
, &tabs
, entry
) {
2160 if ((uri
= get_uri(ti
->wv
)) != NULL
)
2161 arr
[gtk_notebook_page_num(notebook
, ti
->vbox
)] = uri
;
2164 for (i
= 0; i
< len
; i
++)
2166 fprintf(f
, "%s\n", arr
[i
]);
2175 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2187 yank_uri(struct tab
*t
, struct karg
*args
)
2190 GtkClipboard
*clipboard
;
2192 if ((uri
= get_uri(t
->wv
)) == NULL
)
2195 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2196 gtk_clipboard_set_text(clipboard
, uri
, -1);
2207 paste_uri_cb(GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
2209 struct paste_args
*pap
;
2211 if (data
== NULL
|| text
== NULL
|| !strlen(text
))
2214 pap
= (struct paste_args
*)data
;
2217 case XT_PASTE_CURRENT_TAB
:
2218 load_uri(pap
->t
, (gchar
*)text
);
2220 case XT_PASTE_NEW_TAB
:
2221 create_new_tab((gchar
*)text
, NULL
, 1);
2229 paste_uri(struct tab
*t
, struct karg
*args
)
2231 GtkClipboard
*clipboard
;
2232 struct paste_args
*pap
;
2234 pap
= g_malloc(sizeof(struct paste_args
));
2239 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2240 gtk_clipboard_request_text(clipboard
, paste_uri_cb
, pap
);
2246 find_domain(const gchar
*s
, int add_dot
)
2249 char *r
= NULL
, *ss
= NULL
;
2254 if (!strncmp(s
, "http://", strlen("http://")))
2255 s
= &s
[strlen("http://")];
2256 else if (!strncmp(s
, "https://", strlen("https://")))
2257 s
= &s
[strlen("https://")];
2263 for (i
= 0; i
< strlen(ss
) + 1 /* yes er need this */; i
++)
2264 /* chop string at first slash */
2265 if (ss
[i
] == '/' || ss
[i
] == '\0') {
2268 r
= g_strdup_printf(".%s", ss
);
2279 toggle_cwl(struct tab
*t
, struct karg
*args
)
2283 char *dom
= NULL
, *dom_toggle
= NULL
;
2289 uri
= get_uri(t
->wv
);
2290 dom
= find_domain(uri
, 1);
2291 d
= wl_find(dom
, &c_wl
);
2298 if (args
->i
& XT_WL_TOGGLE
)
2300 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2302 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2305 if (args
->i
& XT_WL_TOPLEVEL
)
2306 dom_toggle
= get_toplevel_domain(dom
);
2311 /* enable cookies for domain */
2312 wl_add(dom_toggle
, &c_wl
, 0);
2314 /* disable cookies for domain */
2315 RB_REMOVE(domain_list
, &c_wl
, d
);
2317 webkit_web_view_reload(t
->wv
);
2324 toggle_js(struct tab
*t
, struct karg
*args
)
2329 char *dom
= NULL
, *dom_toggle
= NULL
;
2334 g_object_get(G_OBJECT(t
->settings
),
2335 "enable-scripts", &es
, (char *)NULL
);
2336 if (args
->i
& XT_WL_TOGGLE
)
2338 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2340 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2345 uri
= get_uri(t
->wv
);
2346 dom
= find_domain(uri
, 1);
2348 if (uri
== NULL
|| dom
== NULL
) {
2349 show_oops(t
, "Can't toggle domain in JavaScript white list");
2353 if (args
->i
& XT_WL_TOPLEVEL
)
2354 dom_toggle
= get_toplevel_domain(dom
);
2359 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2360 wl_add(dom_toggle
, &js_wl
, 0 /* session */);
2362 d
= wl_find(dom_toggle
, &js_wl
);
2364 RB_REMOVE(domain_list
, &js_wl
, d
);
2365 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2367 g_object_set(G_OBJECT(t
->settings
),
2368 "enable-scripts", es
, (char *)NULL
);
2369 g_object_set(G_OBJECT(t
->settings
),
2370 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2371 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2372 webkit_web_view_reload(t
->wv
);
2380 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2384 a
.i
= XT_WL_TOGGLE
| XT_WL_FQDN
;
2389 toggle_src(struct tab
*t
, struct karg
*args
)
2396 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2397 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2398 webkit_web_view_reload(t
->wv
);
2404 focus_webview(struct tab
*t
)
2409 /* only grab focus if we are visible */
2410 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2411 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2415 focus(struct tab
*t
, struct karg
*args
)
2417 if (t
== NULL
|| args
== NULL
)
2423 if (args
->i
== XT_FOCUS_URI
)
2424 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2425 else if (args
->i
== XT_FOCUS_SEARCH
)
2426 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2432 stats(struct tab
*t
, struct karg
*args
)
2434 char *stats
, *s
, line
[64 * 1024];
2435 uint64_t line_count
= 0;
2439 show_oops_s("stats invalid parameters");
2442 if (save_rejected_cookies
) {
2443 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2445 s
= fgets(line
, sizeof line
, r_cookie_f
);
2446 if (s
== NULL
|| feof(r_cookie_f
) ||
2452 snprintf(line
, sizeof line
,
2453 "<br>Cookies blocked(*) total: %llu", line_count
);
2455 show_oops(t
, "Can't open blocked cookies file: %s",
2459 stats
= g_strdup_printf(XT_DOCTYPE
2462 "<title>Statistics</title>"
2464 "<h1>Statistics</h1>"
2466 "Cookies blocked(*) this session: %llu"
2468 "<p><small><b>*</b> results vary based on settings"
2474 load_webkit_string(t
, stats
, XT_URI_ABOUT_STATS
);
2481 marco(struct tab
*t
, struct karg
*args
)
2483 char *message
, line
[64 * 1024];
2487 show_oops_s("marco invalid parameters");
2490 snprintf(line
, sizeof line
, "<br>%s", marco_message(&len
));
2492 message
= g_strdup_printf(XT_DOCTYPE
2495 "<title>Marco Sez...</title>"
2504 load_webkit_string(t
, message
, XT_URI_ABOUT_MARCO
);
2511 blank(struct tab
*t
, struct karg
*args
)
2514 show_oops_s("about invalid parameters");
2516 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2521 about(struct tab
*t
, struct karg
*args
)
2526 show_oops_s("about invalid parameters");
2528 about
= g_strdup_printf(XT_DOCTYPE
2531 "<title>About</title>"
2535 "<b>Version: %s</b><p>"
2538 "<li>Marco Peereboom <marco@peereboom.us></li>"
2539 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2540 "<li>Edd Barrett <vext01@gmail.com> </li>"
2541 "<li>Todd T. Fries <todd@fries.net> </li>"
2543 "Copyrights and licenses can be found on the XXXterm "
2544 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2550 load_webkit_string(t
, about
, XT_URI_ABOUT_ABOUT
);
2557 help(struct tab
*t
, struct karg
*args
)
2562 show_oops_s("help invalid parameters");
2567 "<title>XXXterm</title>"
2568 "<meta http-equiv=\"REFRESH\" content=\"0;"
2569 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2572 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2573 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2574 "cgi-bin/man-cgi?xxxterm</a>"
2579 load_webkit_string(t
, help
, XT_URI_ABOUT_HELP
);
2585 * update all favorite tabs apart from one. Pass NULL if
2586 * you want to update all.
2589 update_favorite_tabs(struct tab
*apart_from
)
2592 if (!updating_fl_tabs
) {
2593 updating_fl_tabs
= 1; /* stop infinite recursion */
2594 TAILQ_FOREACH(t
, &tabs
, entry
)
2595 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2596 && (t
!= apart_from
))
2597 xtp_page_fl(t
, NULL
);
2598 updating_fl_tabs
= 0;
2602 /* show a list of favorites (bookmarks) */
2604 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2606 char file
[PATH_MAX
];
2608 char *uri
= NULL
, *title
= NULL
;
2609 size_t len
, lineno
= 0;
2611 char *header
, *body
, *tmp
, *html
= NULL
;
2613 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2616 warn("%s: bad param", __func__
);
2618 /* mark tab as favorite list */
2619 t
->xtp_meaning
= XT_XTP_TAB_MEANING_FL
;
2621 /* new session key */
2622 if (!updating_fl_tabs
)
2623 generate_xtp_session_key(&fl_session_key
);
2625 /* open favorites */
2626 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
2627 if ((f
= fopen(file
, "r")) == NULL
) {
2628 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2633 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
2634 "<title>Favorites</title>\n"
2637 "<h1>Favorites</h1>\n",
2641 body
= g_strdup_printf("<div align='center'><table><tr>"
2642 "<th style='width: 4%%'>#</th><th>Link</th>"
2643 "<th style='width: 15%%'>Remove</th></tr>\n");
2646 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2647 if (feof(f
) || ferror(f
))
2655 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2656 if (feof(f
) || ferror(f
)) {
2657 show_oops(t
, "favorites file corrupt");
2663 body
= g_strdup_printf("%s<tr>"
2665 "<td><a href='%s'>%s</a></td>"
2666 "<td style='text-align: center'>"
2667 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2669 body
, i
, uri
, title
,
2670 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2682 /* if none, say so */
2685 body
= g_strdup_printf("%s<tr>"
2686 "<td colspan='3' style='text-align: center'>"
2687 "No favorites - To add one use the 'favadd' command."
2688 "</td></tr>", body
);
2699 html
= g_strdup_printf("%s%s</table></div></html>",
2701 load_webkit_string(t
, html
, XT_URI_ABOUT_FAVORITES
);
2704 update_favorite_tabs(t
);
2717 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2718 size_t cert_count
, char *title
)
2720 gnutls_datum_t cinfo
;
2721 char *tmp
, *header
, *body
, *footer
;
2724 header
= g_strdup_printf("<html><head><title>%s</title></head><body>", title
);
2725 footer
= g_strdup("</body></html>");
2726 body
= g_strdup("");
2728 for (i
= 0; i
< cert_count
; i
++) {
2729 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2734 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2735 body
, i
, cinfo
.data
);
2736 gnutls_free(cinfo
.data
);
2740 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
2744 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
2749 ca_cmd(struct tab
*t
, struct karg
*args
)
2752 int rv
= 1, certs
= 0, certs_read
;
2755 gnutls_x509_crt_t
*c
= NULL
;
2756 char *certs_buf
= NULL
, *s
;
2758 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
2759 show_oops(t
, "Can't open CA file: %s", strerror(errno
));
2763 if (fstat(fileno(f
), &sb
) == -1) {
2764 show_oops(t
, "Can't stat CA file: %s", strerror(errno
));
2768 certs_buf
= g_malloc(sb
.st_size
+ 1);
2769 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
2770 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
2773 certs_buf
[sb
.st_size
] = '\0';
2776 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
2778 s
+= strlen("BEGIN CERTIFICATE");
2781 bzero(&dt
, sizeof dt
);
2782 dt
.data
= certs_buf
;
2783 dt
.size
= sb
.st_size
;
2784 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
2785 certs_read
= gnutls_x509_crt_list_import(c
, &certs
, &dt
,
2786 GNUTLS_X509_FMT_PEM
, 0);
2787 if (certs_read
<= 0) {
2788 show_oops(t
, "No cert(s) available");
2791 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
2804 connect_socket_from_uri(const gchar
*uri
, char *domain
, size_t domain_sz
)
2807 struct addrinfo hints
, *res
= NULL
, *ai
;
2811 if (uri
&& !g_str_has_prefix(uri
, "https://"))
2814 su
= soup_uri_new(uri
);
2817 if (!SOUP_URI_VALID_FOR_HTTP(su
))
2820 snprintf(port
, sizeof port
, "%d", su
->port
);
2821 bzero(&hints
, sizeof(struct addrinfo
));
2822 hints
.ai_flags
= AI_CANONNAME
;
2823 hints
.ai_family
= AF_UNSPEC
;
2824 hints
.ai_socktype
= SOCK_STREAM
;
2826 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
2829 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
2830 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
2833 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
2836 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
2840 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
2845 strlcpy(domain
, su
->host
, domain_sz
);
2856 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
2859 gnutls_deinit(gsession
);
2861 gnutls_certificate_free_credentials(xcred
);
2867 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
2868 gnutls_certificate_credentials_t
*xc
)
2870 gnutls_certificate_credentials_t xcred
;
2871 gnutls_session_t gsession
;
2874 if (gs
== NULL
|| xc
== NULL
)
2880 gnutls_certificate_allocate_credentials(&xcred
);
2881 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
2882 GNUTLS_X509_FMT_PEM
);
2883 gnutls_init(&gsession
, GNUTLS_CLIENT
);
2884 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
2885 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
2886 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
2887 if ((rv
= gnutls_handshake(gsession
)) < 0) {
2888 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
2890 gnutls_error_is_fatal(rv
),
2891 gnutls_strerror_name(rv
));
2892 stop_tls(gsession
, xcred
);
2896 gnutls_credentials_type_t cred
;
2897 cred
= gnutls_auth_get_type(gsession
);
2898 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
2899 stop_tls(gsession
, xcred
);
2911 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
2915 const gnutls_datum_t
*cl
;
2916 gnutls_x509_crt_t
*all_certs
;
2919 if (certs
== NULL
|| cert_count
== NULL
)
2921 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
2923 cl
= gnutls_certificate_get_peers(gsession
, &len
);
2927 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
2928 for (i
= 0; i
< len
; i
++) {
2929 gnutls_x509_crt_init(&all_certs
[i
]);
2930 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
2931 GNUTLS_X509_FMT_PEM
< 0)) {
2945 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
2949 for (i
= 0; i
< cert_count
; i
++)
2950 gnutls_x509_crt_deinit(certs
[i
]);
2955 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2956 size_t cert_count
, char *domain
)
2959 char cert_buf
[64 * 1024], file
[PATH_MAX
];
2964 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
2967 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
2968 if ((f
= fopen(file
, "w")) == NULL
) {
2969 show_oops(t
, "Can't create cert file %s %s",
2970 file
, strerror(errno
));
2974 for (i
= 0; i
< cert_count
; i
++) {
2975 cert_buf_sz
= sizeof cert_buf
;
2976 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
2977 cert_buf
, &cert_buf_sz
)) {
2978 show_oops(t
, "gnutls_x509_crt_export failed");
2981 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
2982 show_oops(t
, "Can't write certs: %s", strerror(errno
));
2987 /* not the best spot but oh well */
2988 gdk_color_parse("lightblue", &color
);
2989 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
2990 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2991 gdk_color_parse(XT_COLOR_BLACK
, &color
);
2992 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2998 load_compare_cert(struct tab
*t
, struct karg
*args
)
3001 char domain
[8182], file
[PATH_MAX
];
3002 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
3003 int s
= -1, rv
= 1, i
;
3007 gnutls_session_t gsession
;
3008 gnutls_x509_crt_t
*certs
;
3009 gnutls_certificate_credentials_t xcred
;
3014 if ((uri
= get_uri(t
->wv
)) == NULL
)
3017 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
3021 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3022 show_oops(t
, "Start TLS failed");
3027 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3028 show_oops(t
, "Can't get connection certificates");
3032 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3033 if ((f
= fopen(file
, "r")) == NULL
)
3036 for (i
= 0; i
< cert_count
; i
++) {
3037 cert_buf_sz
= sizeof cert_buf
;
3038 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3039 cert_buf
, &cert_buf_sz
)) {
3042 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3043 rv
= -1; /* critical */
3046 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3047 rv
= -1; /* critical */
3056 free_connection_certs(certs
, cert_count
);
3058 /* we close the socket first for speed */
3061 stop_tls(gsession
, xcred
);
3067 cert_cmd(struct tab
*t
, struct karg
*args
)
3073 gnutls_session_t gsession
;
3074 gnutls_x509_crt_t
*certs
;
3075 gnutls_certificate_credentials_t xcred
;
3080 if ((uri
= get_uri(t
->wv
)) == NULL
) {
3081 show_oops(t
, "Invalid URI");
3085 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
3086 show_oops(t
, "Invalid certidicate URI: %s", uri
);
3091 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3092 show_oops(t
, "Start TLS failed");
3097 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3098 show_oops(t
, "get_connection_certs failed");
3102 if (args
->i
& XT_SHOW
)
3103 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3104 else if (args
->i
& XT_SAVE
)
3105 save_certs(t
, certs
, cert_count
, domain
);
3107 free_connection_certs(certs
, cert_count
);
3109 /* we close the socket first for speed */
3112 stop_tls(gsession
, xcred
);
3118 remove_cookie(int index
)
3124 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3126 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3128 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3132 print_cookie("remove cookie", c
);
3133 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3138 soup_cookies_free(cf
);
3144 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3147 char *tmp
, *header
, *body
, *footer
;
3149 /* we set this to indicate we want to manually do navaction */
3150 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3152 header
= g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3154 footer
= g_strdup("</body></html>");
3155 body
= g_strdup("");
3158 if (args
->i
& XT_WL_PERSISTENT
) {
3160 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3162 RB_FOREACH(d
, domain_list
, wl
) {
3166 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3172 if (args
->i
& XT_WL_SESSION
) {
3174 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3176 RB_FOREACH(d
, domain_list
, wl
) {
3180 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3185 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3190 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3192 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3198 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3200 char file
[PATH_MAX
];
3202 char *line
= NULL
, *lt
= NULL
;
3205 char *dom
= NULL
, *dom_save
= NULL
;
3211 if (t
== NULL
|| args
== NULL
)
3214 if (runtime_settings
[0] == '\0')
3217 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3218 if ((f
= fopen(file
, "r+")) == NULL
)
3221 uri
= get_uri(t
->wv
);
3222 dom
= find_domain(uri
, 1);
3223 if (uri
== NULL
|| dom
== NULL
) {
3224 show_oops(t
, "Can't add domain to %s white list",
3225 js
? "JavaScript" : "cookie");
3229 if (args
->i
& XT_WL_TOPLEVEL
) {
3231 if ((dom_save
= get_toplevel_domain(dom
)) == NULL
) {
3232 show_oops(t
, "invalid domain: %s", dom
);
3235 } else if (args
->i
& XT_WL_FQDN
) {
3241 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom_save
);
3244 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3247 if (!strcmp(line
, lt
))
3253 fprintf(f
, "%s\n", lt
);
3258 d
= wl_find(dom_save
, &js_wl
);
3260 settings_add("js_wl", dom_save
);
3261 d
= wl_find(dom_save
, &js_wl
);
3265 d
= wl_find(dom_save
, &c_wl
);
3267 settings_add("cookie_wl", dom_save
);
3268 d
= wl_find(dom_save
, &c_wl
);
3272 /* find and add to persistent jar */
3273 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3274 for (;cf
; cf
= cf
->next
) {
3276 if (!strcmp(dom_save
, ci
->domain
) ||
3277 !strcmp(&dom_save
[1], ci
->domain
)) /* deal with leading . */ {
3278 c
= soup_cookie_copy(ci
);
3279 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3282 soup_cookies_free(cf
);
3300 js_show_wl(struct tab
*t
, struct karg
*args
)
3302 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3303 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3309 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3311 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3312 wl_show(t
, args
, "Cookie White List", &c_wl
);
3318 cookie_cmd(struct tab
*t
, struct karg
*args
)
3320 if (args
->i
& XT_SHOW
)
3321 wl_show(t
, args
, "Cookie White List", &c_wl
);
3322 else if (args
->i
& XT_WL_TOGGLE
)
3323 toggle_cwl(t
, args
);
3324 else if (args
->i
& XT_SAVE
)
3325 wl_save(t
, args
, 0);
3326 else if (args
->i
& XT_DELETE
)
3327 show_oops(t
, "'cookie delete' currently unimplemented");
3333 js_cmd(struct tab
*t
, struct karg
*args
)
3335 if (args
->i
& XT_SHOW
)
3336 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3337 else if (args
->i
& XT_SAVE
)
3338 wl_save(t
, args
, 1);
3339 else if (args
->i
& XT_WL_TOGGLE
)
3341 else if (args
->i
& XT_DELETE
)
3342 show_oops(t
, "'js delete' currently unimplemented");
3348 add_favorite(struct tab
*t
, struct karg
*args
)
3350 char file
[PATH_MAX
];
3353 size_t urilen
, linelen
;
3354 const gchar
*uri
, *title
;
3359 /* don't allow adding of xtp pages to favorites */
3360 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3361 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3365 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3366 if ((f
= fopen(file
, "r+")) == NULL
) {
3367 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3371 title
= webkit_web_view_get_title(t
->wv
);
3372 uri
= get_uri(t
->wv
);
3377 if (title
== NULL
|| uri
== NULL
) {
3378 show_oops(t
, "can't add page to favorites");
3382 urilen
= strlen(uri
);
3385 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3386 if (feof(f
) || ferror(f
))
3389 if (linelen
== urilen
&& !strcmp(line
, uri
))
3396 fprintf(f
, "\n%s\n%s", title
, uri
);
3402 update_favorite_tabs(NULL
);
3408 navaction(struct tab
*t
, struct karg
*args
)
3410 WebKitWebHistoryItem
*item
;
3412 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3413 t
->tab_id
, args
->i
);
3416 if (args
->i
== XT_NAV_BACK
)
3417 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3419 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3421 return (XT_CB_PASSTHROUGH
);
3422 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
3424 return (XT_CB_PASSTHROUGH
);
3429 webkit_web_view_go_back(t
->wv
);
3431 case XT_NAV_FORWARD
:
3432 webkit_web_view_go_forward(t
->wv
);
3435 webkit_web_view_reload(t
->wv
);
3437 case XT_NAV_RELOAD_CACHE
:
3438 webkit_web_view_reload_bypass_cache(t
->wv
);
3441 return (XT_CB_PASSTHROUGH
);
3445 move(struct tab
*t
, struct karg
*args
)
3447 GtkAdjustment
*adjust
;
3448 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3453 case XT_MOVE_BOTTOM
:
3455 case XT_MOVE_PAGEDOWN
:
3456 case XT_MOVE_PAGEUP
:
3457 case XT_MOVE_HALFDOWN
:
3458 case XT_MOVE_HALFUP
:
3459 adjust
= t
->adjust_v
;
3462 adjust
= t
->adjust_h
;
3466 pos
= gtk_adjustment_get_value(adjust
);
3467 ps
= gtk_adjustment_get_page_size(adjust
);
3468 upper
= gtk_adjustment_get_upper(adjust
);
3469 lower
= gtk_adjustment_get_lower(adjust
);
3470 si
= gtk_adjustment_get_step_increment(adjust
);
3471 pi
= gtk_adjustment_get_page_increment(adjust
);
3474 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3475 "max %f si %f pi %f\n",
3476 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
3477 pos
, ps
, upper
, lower
, max
, si
, pi
);
3483 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3488 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3490 case XT_MOVE_BOTTOM
:
3491 case XT_MOVE_FARRIGHT
:
3492 gtk_adjustment_set_value(adjust
, max
);
3495 case XT_MOVE_FARLEFT
:
3496 gtk_adjustment_set_value(adjust
, lower
);
3498 case XT_MOVE_PAGEDOWN
:
3500 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3502 case XT_MOVE_PAGEUP
:
3504 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3506 case XT_MOVE_HALFDOWN
:
3508 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3510 case XT_MOVE_HALFUP
:
3512 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3515 return (XT_CB_PASSTHROUGH
);
3518 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3520 return (XT_CB_HANDLED
);
3524 url_set_visibility(void)
3528 TAILQ_FOREACH(t
, &tabs
, entry
) {
3529 if (show_url
== 0) {
3530 gtk_widget_hide(t
->toolbar
);
3533 gtk_widget_show(t
->toolbar
);
3538 notebook_tab_set_visibility(GtkNotebook
*notebook
)
3541 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3543 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3547 statusbar_set_visibility(void)
3551 TAILQ_FOREACH(t
, &tabs
, entry
) {
3552 if (show_statusbar
== 0) {
3553 gtk_widget_hide(t
->statusbar
);
3556 gtk_widget_show(t
->statusbar
);
3561 url_set(struct tab
*t
, int enable_url_entry
)
3566 show_url
= enable_url_entry
;
3568 if (enable_url_entry
) {
3569 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
3570 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3571 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
), 0);
3573 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
3574 GTK_ENTRY_ICON_PRIMARY
);
3576 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
3577 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
3578 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
3579 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
3585 fullscreen(struct tab
*t
, struct karg
*args
)
3587 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3590 return (XT_CB_PASSTHROUGH
);
3592 if (show_url
== 0) {
3600 url_set_visibility();
3601 notebook_tab_set_visibility(notebook
);
3603 return (XT_CB_HANDLED
);
3607 statusaction(struct tab
*t
, struct karg
*args
)
3609 int rv
= XT_CB_HANDLED
;
3611 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3614 return (XT_CB_PASSTHROUGH
);
3617 case XT_STATUSBAR_SHOW
:
3618 if (show_statusbar
== 0) {
3620 statusbar_set_visibility();
3623 case XT_STATUSBAR_HIDE
:
3624 if (show_statusbar
== 1) {
3626 statusbar_set_visibility();
3634 urlaction(struct tab
*t
, struct karg
*args
)
3636 int rv
= XT_CB_HANDLED
;
3638 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3641 return (XT_CB_PASSTHROUGH
);
3645 if (show_url
== 0) {
3647 url_set_visibility();
3651 if (show_url
== 1) {
3653 url_set_visibility();
3661 tabaction(struct tab
*t
, struct karg
*args
)
3663 int rv
= XT_CB_HANDLED
;
3664 char *url
= args
->s
;
3667 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
3670 return (XT_CB_PASSTHROUGH
);
3674 if (strlen(url
) > 0)
3675 create_new_tab(url
, NULL
, 1);
3677 create_new_tab(NULL
, NULL
, 1);
3682 case XT_TAB_DELQUIT
:
3683 if (gtk_notebook_get_n_pages(notebook
) > 1)
3689 if (strlen(url
) > 0)
3692 rv
= XT_CB_PASSTHROUGH
;
3698 if (show_tabs
== 0) {
3700 notebook_tab_set_visibility(notebook
);
3704 if (show_tabs
== 1) {
3706 notebook_tab_set_visibility(notebook
);
3709 case XT_TAB_UNDO_CLOSE
:
3710 if (undo_count
== 0) {
3711 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3715 u
= TAILQ_FIRST(&undos
);
3716 create_new_tab(u
->uri
, u
, 1);
3718 TAILQ_REMOVE(&undos
, u
, entry
);
3720 /* u->history is freed in create_new_tab() */
3725 rv
= XT_CB_PASSTHROUGH
;
3739 resizetab(struct tab
*t
, struct karg
*args
)
3741 if (t
== NULL
|| args
== NULL
) {
3742 show_oops_s("resizetab invalid parameters");
3743 return (XT_CB_PASSTHROUGH
);
3746 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
3747 t
->tab_id
, args
->i
);
3749 adjustfont_webkit(t
, args
->i
);
3751 return (XT_CB_HANDLED
);
3755 movetab(struct tab
*t
, struct karg
*args
)
3760 if (t
== NULL
|| args
== NULL
) {
3761 show_oops_s("movetab invalid parameters");
3762 return (XT_CB_PASSTHROUGH
);
3765 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
3766 t
->tab_id
, args
->i
);
3768 if (args
->i
== XT_TAB_INVALID
)
3769 return (XT_CB_PASSTHROUGH
);
3771 if (args
->i
< XT_TAB_INVALID
) {
3772 /* next or previous tab */
3773 if (TAILQ_EMPTY(&tabs
))
3774 return (XT_CB_PASSTHROUGH
);
3778 if (strlen(args
->s
) == 0) {
3779 /* if at the last page, loop around to the first */
3780 if (gtk_notebook_get_current_page(notebook
) ==
3781 gtk_notebook_get_n_pages(notebook
) - 1)
3782 gtk_notebook_set_current_page(notebook
, 0);
3784 gtk_notebook_next_page(notebook
);
3786 x
= atoi(args
->s
) - 1;
3788 return (XT_CB_PASSTHROUGH
);
3790 if (t
->tab_id
== x
) {
3791 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
3792 return (XT_CB_HANDLED
);
3794 TAILQ_FOREACH(tt
, &tabs
, entry
) {
3795 if (tt
->tab_id
== x
) {
3796 gtk_notebook_set_current_page(notebook
, x
);
3797 DNPRINTF(XT_D_TAB
, "movetab: going to %d\n", x
);
3805 /* if at the first page, loop around to the last */
3806 if (gtk_notebook_current_page(notebook
) == 0)
3807 gtk_notebook_set_current_page(notebook
,
3808 gtk_notebook_get_n_pages(notebook
) - 1);
3810 gtk_notebook_prev_page(notebook
);
3813 gtk_notebook_set_current_page(notebook
, 0);
3816 gtk_notebook_set_current_page(notebook
, -1);
3819 return (XT_CB_PASSTHROUGH
);
3822 return (XT_CB_HANDLED
);
3825 return (XT_CB_HANDLED
);
3829 command(struct tab
*t
, struct karg
*args
)
3831 char *s
= NULL
, *ss
= NULL
;
3835 if (t
== NULL
|| args
== NULL
) {
3836 show_oops_s("command invalid parameters");
3837 return (XT_CB_PASSTHROUGH
);
3856 case XT_CMD_OPEN_CURRENT
:
3859 case XT_CMD_TABNEW_CURRENT
:
3860 if (!s
) /* FALL THROUGH? */
3862 if ((uri
= get_uri(t
->wv
)) != NULL
) {
3863 ss
= g_strdup_printf("%s%s", s
, uri
);
3868 show_oops(t
, "command: invalid opcode %d", args
->i
);
3869 return (XT_CB_PASSTHROUGH
);
3872 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
3874 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
3875 gdk_color_parse(XT_COLOR_WHITE
, &color
);
3876 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
3878 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
3879 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
3884 return (XT_CB_HANDLED
);
3888 * Return a new string with a download row (in html)
3889 * appended. Old string is freed.
3892 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
3895 WebKitDownloadStatus stat
;
3896 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
3898 char cur_sz
[FMT_SCALED_STRSIZE
];
3899 char tot_sz
[FMT_SCALED_STRSIZE
];
3902 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
3904 /* All actions wil take this form:
3905 * xxxt://class/seskey
3907 xtp_prefix
= g_strdup_printf("%s%d/%s/",
3908 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
3910 stat
= webkit_download_get_status(dl
->download
);
3913 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
3914 status_html
= g_strdup_printf("Finished");
3915 cmd_html
= g_strdup_printf(
3916 "<a href='%s%d/%d'>Remove</a>",
3917 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3919 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
3920 /* gather size info */
3921 progress
= 100 * webkit_download_get_progress(dl
->download
);
3924 webkit_download_get_current_size(dl
->download
), cur_sz
);
3926 webkit_download_get_total_size(dl
->download
), tot_sz
);
3928 status_html
= g_strdup_printf(
3929 "<div style='width: 100%%' align='center'>"
3930 "<div class='progress-outer'>"
3931 "<div class='progress-inner' style='width: %.2f%%'>"
3932 "</div></div></div>"
3933 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3934 progress
, cur_sz
, tot_sz
, progress
);
3936 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3937 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3941 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
3942 status_html
= g_strdup_printf("Cancelled");
3943 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3944 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3946 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
3947 status_html
= g_strdup_printf("Error!");
3948 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3949 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3951 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
3952 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3953 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3954 status_html
= g_strdup_printf("Starting");
3957 show_oops(t
, "%s: unknown download status", __func__
);
3960 new_html
= g_strdup_printf(
3961 "%s\n<tr><td>%s</td><td>%s</td>"
3962 "<td style='text-align:center'>%s</td></tr>\n",
3963 html
, basename(webkit_download_get_destination_uri(dl
->download
)),
3964 status_html
, cmd_html
);
3968 g_free(status_html
);
3979 * update all download tabs apart from one. Pass NULL if
3980 * you want to update all.
3983 update_download_tabs(struct tab
*apart_from
)
3986 if (!updating_dl_tabs
) {
3987 updating_dl_tabs
= 1; /* stop infinite recursion */
3988 TAILQ_FOREACH(t
, &tabs
, entry
)
3989 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
3990 && (t
!= apart_from
))
3991 xtp_page_dl(t
, NULL
);
3992 updating_dl_tabs
= 0;
3997 * update all cookie tabs apart from one. Pass NULL if
3998 * you want to update all.
4001 update_cookie_tabs(struct tab
*apart_from
)
4004 if (!updating_cl_tabs
) {
4005 updating_cl_tabs
= 1; /* stop infinite recursion */
4006 TAILQ_FOREACH(t
, &tabs
, entry
)
4007 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
4008 && (t
!= apart_from
))
4009 xtp_page_cl(t
, NULL
);
4010 updating_cl_tabs
= 0;
4015 * update all history tabs apart from one. Pass NULL if
4016 * you want to update all.
4019 update_history_tabs(struct tab
*apart_from
)
4023 if (!updating_hl_tabs
) {
4024 updating_hl_tabs
= 1; /* stop infinite recursion */
4025 TAILQ_FOREACH(t
, &tabs
, entry
)
4026 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4027 && (t
!= apart_from
))
4028 xtp_page_hl(t
, NULL
);
4029 updating_hl_tabs
= 0;
4033 /* cookie management XTP page */
4035 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4037 char *header
, *body
, *footer
, *page
, *tmp
;
4038 int i
= 1; /* all ids start 1 */
4039 GSList
*sc
, *pc
, *pc_start
;
4041 char *type
, *table_headers
;
4042 char *last_domain
= strdup("");
4044 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4047 show_oops_s("%s invalid parameters", __func__
);
4050 /* mark this tab as cookie jar */
4051 t
->xtp_meaning
= XT_XTP_TAB_MEANING_CL
;
4053 /* Generate a new session key */
4054 if (!updating_cl_tabs
)
4055 generate_xtp_session_key(&cl_session_key
);
4058 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4059 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4060 "</head><body><h1>Cookie Jar</h1>\n");
4063 table_headers
= g_strdup_printf("<div align='center'><table><tr>"
4070 "<th>HTTP<br />only</th>"
4071 "<th>Rm</th></tr>\n");
4073 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4074 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4078 for (; sc
; sc
= sc
->next
) {
4081 if (strcmp(last_domain
, c
->domain
) != 0) {
4084 last_domain
= strdup(c
->domain
);
4088 body
= g_strdup_printf("%s</table></div>"
4090 body
, c
->domain
, table_headers
);
4094 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4095 c
->domain
, table_headers
);
4100 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4101 if (soup_cookie_equal(pc
->data
, c
)) {
4102 type
= "Session + Persistent";
4107 body
= g_strdup_printf(
4109 "<td style='width: text-align: center'>%s</td>"
4110 "<td style='width: 1px'>%s</td>"
4111 "<td style='width=70%%;overflow: visible'>"
4112 " <textarea rows='4'>%s</textarea>"
4116 "<td style='width: 1px; text-align: center'>%d</td>"
4117 "<td style='width: 1px; text-align: center'>%d</td>"
4118 "<td style='width: 1px; text-align: center'>"
4119 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4126 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4141 soup_cookies_free(sc
);
4142 soup_cookies_free(pc
);
4144 /* small message if there are none */
4146 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4147 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4151 footer
= g_strdup_printf("</table></div></body></html>");
4153 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4158 g_free(table_headers
);
4159 g_free(last_domain
);
4161 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4162 update_cookie_tabs(t
);
4170 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4172 char *header
, *body
, *footer
, *page
, *tmp
;
4174 int i
= 1; /* all ids start 1 */
4176 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4179 show_oops_s("%s invalid parameters", __func__
);
4183 /* mark this tab as history manager */
4184 t
->xtp_meaning
= XT_XTP_TAB_MEANING_HL
;
4186 /* Generate a new session key */
4187 if (!updating_hl_tabs
)
4188 generate_xtp_session_key(&hl_session_key
);
4191 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
4192 "<title>History</title>\n"
4195 "<h1>History</h1>\n",
4199 body
= g_strdup_printf("<div align='center'><table><tr>"
4200 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4202 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4204 body
= g_strdup_printf(
4206 "<td><a href='%s'>%s</a></td>"
4208 "<td style='text-align: center'>"
4209 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4210 body
, h
->uri
, h
->uri
, h
->title
,
4211 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4212 XT_XTP_HL_REMOVE
, i
);
4218 /* small message if there are none */
4221 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4222 "colspan='3'>No History</td></tr>\n", body
);
4227 footer
= g_strdup_printf("</table></div></body></html>");
4229 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4232 * update all history manager tabs as the xtp session
4233 * key has now changed. No need to update the current tab.
4234 * Already did that above.
4236 update_history_tabs(t
);
4242 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4249 * Generate a web page detailing the status of any downloads
4252 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4254 struct download
*dl
;
4255 char *header
, *body
, *footer
, *page
, *tmp
;
4259 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4262 show_oops_s("%s invalid parameters", __func__
);
4265 /* mark as a download manager tab */
4266 t
->xtp_meaning
= XT_XTP_TAB_MEANING_DL
;
4269 * Generate a new session key for next page instance.
4270 * This only happens for the top level call to xtp_page_dl()
4271 * in which case updating_dl_tabs is 0.
4273 if (!updating_dl_tabs
)
4274 generate_xtp_session_key(&dl_session_key
);
4276 /* header - with refresh so as to update */
4277 if (refresh_interval
>= 1)
4278 ref
= g_strdup_printf(
4279 "<meta http-equiv='refresh' content='%u"
4280 ";url=%s%d/%s/%d' />\n",
4290 header
= g_strdup_printf(
4292 "<title>Downloads</title>\n%s%s</head>\n",
4293 XT_DOCTYPE XT_HTML_TAG
,
4297 body
= g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4298 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4299 "</p><table><tr><th style='width: 60%%'>"
4300 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4301 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4303 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4304 body
= xtp_page_dl_row(t
, body
, dl
);
4308 /* message if no downloads in list */
4311 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4312 " style='text-align: center'>"
4313 "No downloads</td></tr>\n", body
);
4318 footer
= g_strdup_printf("</table></div></body></html>");
4320 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4324 * update all download manager tabs as the xtp session
4325 * key has now changed. No need to update the current tab.
4326 * Already did that above.
4328 update_download_tabs(t
);
4335 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4342 search(struct tab
*t
, struct karg
*args
)
4346 if (t
== NULL
|| args
== NULL
) {
4347 show_oops_s("search invalid parameters");
4350 if (t
->search_text
== NULL
) {
4351 if (global_search
== NULL
)
4352 return (XT_CB_PASSTHROUGH
);
4354 t
->search_text
= g_strdup(global_search
);
4355 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4356 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4360 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4361 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4364 case XT_SEARCH_NEXT
:
4365 d
= t
->search_forward
;
4367 case XT_SEARCH_PREV
:
4368 d
= !t
->search_forward
;
4371 return (XT_CB_PASSTHROUGH
);
4374 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4376 return (XT_CB_HANDLED
);
4379 struct settings_args
{
4385 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4388 struct settings_args
*sa
= cb_args
;
4393 if (s
->flags
& XT_SF_RUNTIME
)
4399 *sa
->body
= g_strdup_printf(
4401 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4402 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4414 set(struct tab
*t
, struct karg
*args
)
4416 char *header
, *body
, *footer
, *page
, *tmp
;
4418 struct settings_args sa
;
4420 bzero(&sa
, sizeof sa
);
4424 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4425 "\n<head><title>Settings</title>\n"
4426 "</head><body><h1>Settings</h1>\n");
4429 body
= g_strdup_printf("<div align='center'><table><tr>"
4430 "<th align='left'>Setting</th>"
4431 "<th align='left'>Value</th></tr>\n");
4433 settings_walk(print_setting
, &sa
);
4436 /* small message if there are none */
4439 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4440 "colspan='2'>No settings</td></tr>\n", body
);
4445 footer
= g_strdup_printf("</table></div></body></html>");
4447 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4453 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4455 return (XT_CB_PASSTHROUGH
);
4459 session_save(struct tab
*t
, char *filename
)
4464 if (strlen(filename
) == 0)
4467 if (filename
[0] == '.' || filename
[0] == '/')
4471 if (save_tabs(t
, &a
))
4473 strlcpy(named_session
, filename
, sizeof named_session
);
4481 session_open(struct tab
*t
, char *filename
)
4486 if (strlen(filename
) == 0)
4489 if (filename
[0] == '.' || filename
[0] == '/')
4493 a
.i
= XT_SES_CLOSETABS
;
4494 if (open_tabs(t
, &a
))
4497 strlcpy(named_session
, filename
, sizeof named_session
);
4505 session_delete(struct tab
*t
, char *filename
)
4507 char file
[PATH_MAX
];
4510 if (strlen(filename
) == 0)
4513 if (filename
[0] == '.' || filename
[0] == '/')
4516 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
4520 if (!strcmp(filename
, named_session
))
4521 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
4522 sizeof named_session
);
4530 session_cmd(struct tab
*t
, struct karg
*args
)
4532 char *filename
= args
->s
;
4537 if (args
->i
& XT_SHOW
)
4538 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
4539 XT_SAVED_TABS_FILE
: named_session
);
4540 else if (args
->i
& XT_SAVE
) {
4541 if (session_save(t
, filename
)) {
4542 show_oops(t
, "Can't save session: %s",
4543 filename
? filename
: "INVALID");
4546 } else if (args
->i
& XT_OPEN
) {
4547 if (session_open(t
, filename
)) {
4548 show_oops(t
, "Can't open session: %s",
4549 filename
? filename
: "INVALID");
4552 } else if (args
->i
& XT_DELETE
) {
4553 if (session_delete(t
, filename
)) {
4554 show_oops(t
, "Can't delete session: %s",
4555 filename
? filename
: "INVALID");
4560 return (XT_CB_PASSTHROUGH
);
4564 * Make a hardcopy of the page
4567 print_page(struct tab
*t
, struct karg
*args
)
4569 WebKitWebFrame
*frame
;
4571 GtkPrintOperation
*op
;
4572 GtkPrintOperationAction action
;
4573 GtkPrintOperationResult print_res
;
4574 GError
*g_err
= NULL
;
4575 int marg_l
, marg_r
, marg_t
, marg_b
;
4577 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
4579 ps
= gtk_page_setup_new();
4580 op
= gtk_print_operation_new();
4581 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
4582 frame
= webkit_web_view_get_main_frame(t
->wv
);
4584 /* the default margins are too small, so we will bump them */
4585 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
4586 XT_PRINT_EXTRA_MARGIN
;
4587 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
4588 XT_PRINT_EXTRA_MARGIN
;
4589 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
4590 XT_PRINT_EXTRA_MARGIN
;
4591 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
4592 XT_PRINT_EXTRA_MARGIN
;
4595 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
4596 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
4597 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
4598 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
4600 gtk_print_operation_set_default_page_setup(op
, ps
);
4602 /* this appears to free 'op' and 'ps' */
4603 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
4605 /* check it worked */
4606 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
4607 show_oops_s("can't print: %s", g_err
->message
);
4608 g_error_free (g_err
);
4616 go_home(struct tab
*t
, struct karg
*args
)
4623 restart(struct tab
*t
, struct karg
*args
)
4627 a
.s
= XT_RESTART_TABS_FILE
;
4629 execvp(start_argv
[0], start_argv
);
4635 #define CTRL GDK_CONTROL_MASK
4636 #define MOD1 GDK_MOD1_MASK
4637 #define SHFT GDK_SHIFT_MASK
4639 /* inherent to GTK not all keys will be caught at all times */
4640 /* XXX sort key bindings */
4641 struct key_binding
{
4646 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
4648 { "cookiejar", MOD1
, 0, GDK_j
},
4649 { "downloadmgr", MOD1
, 0, GDK_d
},
4650 { "history", MOD1
, 0, GDK_h
},
4651 { "print", CTRL
, 0, GDK_p
},
4652 { "search", 0, 0, GDK_slash
},
4653 { "searchb", 0, 0, GDK_question
},
4654 { "command", 0, 0, GDK_colon
},
4655 { "qa", CTRL
, 0, GDK_q
},
4656 { "restart", MOD1
, 0, GDK_q
},
4657 { "js toggle", CTRL
, 0, GDK_j
},
4658 { "cookie toggle", MOD1
, 0, GDK_c
},
4659 { "togglesrc", CTRL
, 0, GDK_s
},
4660 { "yankuri", 0, 0, GDK_y
},
4661 { "pasteuricur", 0, 0, GDK_p
},
4662 { "pasteurinew", 0, 0, GDK_P
},
4665 { "searchnext", 0, 0, GDK_n
},
4666 { "searchprevious", 0, 0, GDK_N
},
4669 { "focusaddress", 0, 0, GDK_F6
},
4670 { "focussearch", 0, 0, GDK_F7
},
4673 { "hinting", 0, 0, GDK_f
},
4675 /* custom stylesheet */
4676 { "userstyle", 0, 0, GDK_i
},
4679 { "goback", 0, 0, GDK_BackSpace
},
4680 { "goback", MOD1
, 0, GDK_Left
},
4681 { "goforward", SHFT
, 0, GDK_BackSpace
},
4682 { "goforward", MOD1
, 0, GDK_Right
},
4683 { "reload", 0, 0, GDK_F5
},
4684 { "reload", CTRL
, 0, GDK_r
},
4685 { "reloadforce", CTRL
, 0, GDK_R
},
4686 { "reload", CTRL
, 0, GDK_l
},
4687 { "favorites", MOD1
, 1, GDK_f
},
4689 /* vertical movement */
4690 { "scrolldown", 0, 0, GDK_j
},
4691 { "scrolldown", 0, 0, GDK_Down
},
4692 { "scrollup", 0, 0, GDK_Up
},
4693 { "scrollup", 0, 0, GDK_k
},
4694 { "scrollbottom", 0, 0, GDK_G
},
4695 { "scrollbottom", 0, 0, GDK_End
},
4696 { "scrolltop", 0, 0, GDK_Home
},
4697 { "scrolltop", 0, 0, GDK_g
},
4698 { "scrollpagedown", 0, 0, GDK_space
},
4699 { "scrollpagedown", CTRL
, 0, GDK_f
},
4700 { "scrollhalfdown", CTRL
, 0, GDK_d
},
4701 { "scrollpagedown", 0, 0, GDK_Page_Down
},
4702 { "scrollpageup", 0, 0, GDK_Page_Up
},
4703 { "scrollpageup", CTRL
, 0, GDK_b
},
4704 { "scrollhalfup", CTRL
, 0, GDK_u
},
4705 /* horizontal movement */
4706 { "scrollright", 0, 0, GDK_l
},
4707 { "scrollright", 0, 0, GDK_Right
},
4708 { "scrollleft", 0, 0, GDK_Left
},
4709 { "scrollleft", 0, 0, GDK_h
},
4710 { "scrollfarright", 0, 0, GDK_dollar
},
4711 { "scrollfarleft", 0, 0, GDK_0
},
4714 { "tabnew", CTRL
, 0, GDK_t
},
4715 { "tabclose", CTRL
, 1, GDK_w
},
4716 { "tabundoclose", 0, 0, GDK_U
},
4717 { "tabnext 1", CTRL
, 0, GDK_1
},
4718 { "tabnext 2", CTRL
, 0, GDK_2
},
4719 { "tabnext 3", CTRL
, 0, GDK_3
},
4720 { "tabnext 4", CTRL
, 0, GDK_4
},
4721 { "tabnext 5", CTRL
, 0, GDK_5
},
4722 { "tabnext 6", CTRL
, 0, GDK_6
},
4723 { "tabnext 7", CTRL
, 0, GDK_7
},
4724 { "tabnext 8", CTRL
, 0, GDK_8
},
4725 { "tabnext 9", CTRL
, 0, GDK_9
},
4726 { "tabnext 10", CTRL
, 0, GDK_0
},
4727 { "tabfirst", CTRL
, 0, GDK_less
},
4728 { "tablast", CTRL
, 0, GDK_greater
},
4729 { "tabprevious", CTRL
, 0, GDK_Left
},
4730 { "tabnext", CTRL
, 0, GDK_Right
},
4731 { "focusout", CTRL
, 0, GDK_minus
},
4732 { "focusin", CTRL
, 0, GDK_plus
},
4733 { "focusin", CTRL
, 0, GDK_equal
},
4735 /* command aliases (handy when -S flag is used) */
4736 { "promptopen", 0, 0, GDK_F9
},
4737 { "promptopencurrent", 0, 0, GDK_F10
},
4738 { "prompttabnew", 0, 0, GDK_F11
},
4739 { "prompttabnewcurrent",0, 0, GDK_F12
},
4741 TAILQ_HEAD(keybinding_list
, key_binding
);
4744 walk_kb(struct settings
*s
,
4745 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
4747 struct key_binding
*k
;
4750 if (s
== NULL
|| cb
== NULL
) {
4751 show_oops_s("walk_kb invalid parameters");
4755 TAILQ_FOREACH(k
, &kbl
, entry
) {
4761 if (gdk_keyval_name(k
->key
) == NULL
)
4764 strlcat(str
, k
->cmd
, sizeof str
);
4765 strlcat(str
, ",", sizeof str
);
4767 if (k
->mask
& GDK_SHIFT_MASK
)
4768 strlcat(str
, "S-", sizeof str
);
4769 if (k
->mask
& GDK_CONTROL_MASK
)
4770 strlcat(str
, "C-", sizeof str
);
4771 if (k
->mask
& GDK_MOD1_MASK
)
4772 strlcat(str
, "M1-", sizeof str
);
4773 if (k
->mask
& GDK_MOD2_MASK
)
4774 strlcat(str
, "M2-", sizeof str
);
4775 if (k
->mask
& GDK_MOD3_MASK
)
4776 strlcat(str
, "M3-", sizeof str
);
4777 if (k
->mask
& GDK_MOD4_MASK
)
4778 strlcat(str
, "M4-", sizeof str
);
4779 if (k
->mask
& GDK_MOD5_MASK
)
4780 strlcat(str
, "M5-", sizeof str
);
4782 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
4783 cb(s
, str
, cb_args
);
4788 init_keybindings(void)
4791 struct key_binding
*k
;
4793 for (i
= 0; i
< LENGTH(keys
); i
++) {
4794 k
= g_malloc0(sizeof *k
);
4795 k
->cmd
= keys
[i
].cmd
;
4796 k
->mask
= keys
[i
].mask
;
4797 k
->use_in_entry
= keys
[i
].use_in_entry
;
4798 k
->key
= keys
[i
].key
;
4799 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4801 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
4802 k
->cmd
? k
->cmd
: "unnamed key");
4807 keybinding_clearall(void)
4809 struct key_binding
*k
, *next
;
4811 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
4812 next
= TAILQ_NEXT(k
, entry
);
4816 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
4817 k
->cmd
? k
->cmd
: "unnamed key");
4818 TAILQ_REMOVE(&kbl
, k
, entry
);
4824 keybinding_add(char *cmd
, char *key
, int use_in_entry
)
4826 struct key_binding
*k
;
4827 guint keyval
, mask
= 0;
4830 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s\n", cmd
, key
);
4832 /* Keys which are to be used in entry have been prefixed with an
4833 * exclamation mark. */
4837 /* find modifier keys */
4838 if (strstr(key
, "S-"))
4839 mask
|= GDK_SHIFT_MASK
;
4840 if (strstr(key
, "C-"))
4841 mask
|= GDK_CONTROL_MASK
;
4842 if (strstr(key
, "M1-"))
4843 mask
|= GDK_MOD1_MASK
;
4844 if (strstr(key
, "M2-"))
4845 mask
|= GDK_MOD2_MASK
;
4846 if (strstr(key
, "M3-"))
4847 mask
|= GDK_MOD3_MASK
;
4848 if (strstr(key
, "M4-"))
4849 mask
|= GDK_MOD4_MASK
;
4850 if (strstr(key
, "M5-"))
4851 mask
|= GDK_MOD5_MASK
;
4854 for (i
= strlen(key
) - 1; i
> 0; i
--)
4858 /* validate keyname */
4859 keyval
= gdk_keyval_from_name(key
);
4860 if (keyval
== GDK_VoidSymbol
) {
4861 warnx("invalid keybinding name %s", key
);
4864 /* must run this test too, gtk+ doesn't handle 10 for example */
4865 if (gdk_keyval_name(keyval
) == NULL
) {
4866 warnx("invalid keybinding name %s", key
);
4870 /* Remove eventual dupes. */
4871 TAILQ_FOREACH(k
, &kbl
, entry
)
4872 if (k
->key
== keyval
&& k
->mask
== mask
) {
4873 TAILQ_REMOVE(&kbl
, k
, entry
);
4879 k
= g_malloc0(sizeof *k
);
4880 k
->cmd
= g_strdup(cmd
);
4882 k
->use_in_entry
= use_in_entry
;
4885 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
4890 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
4891 k
->cmd
, gdk_keyval_name(keyval
));
4893 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4899 add_kb(struct settings
*s
, char *entry
)
4903 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
4905 /* clearall is special */
4906 if (!strcmp(entry
, "clearall")) {
4907 keybinding_clearall();
4911 kb
= strstr(entry
, ",");
4917 return (keybinding_add(entry
, key
, key
[0] == '!'));
4923 int (*func
)(struct tab
*, struct karg
*);
4925 bool userarg
; /* allow free text arg */
4927 { "command", 0, command
, {.i
= ':'}, FALSE
},
4928 { "search", 0, command
, {.i
= '/'}, FALSE
},
4929 { "searchb", 0, command
, {.i
= '?'}, FALSE
},
4930 { "togglesrc", 0, toggle_src
, {0}, FALSE
},
4932 /* yanking and pasting */
4933 { "yankuri", 0, yank_uri
, {0}, FALSE
},
4934 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4935 { "pasteuricur", 0, paste_uri
, {.i
= XT_PASTE_CURRENT_TAB
}, FALSE
},
4936 { "pasteurinew", 0, paste_uri
, {.i
= XT_PASTE_NEW_TAB
}, FALSE
},
4939 { "searchnext", 0, search
, {.i
= XT_SEARCH_NEXT
}, FALSE
},
4940 { "searchprevious", 0, search
, {.i
= XT_SEARCH_PREV
}, FALSE
},
4943 { "focusaddress", 0, focus
, {.i
= XT_FOCUS_URI
}, FALSE
},
4944 { "focussearch", 0, focus
, {.i
= XT_FOCUS_SEARCH
}, FALSE
},
4947 { "hinting", 0, hint
, {.i
= 0}, FALSE
},
4949 /* custom stylesheet */
4950 { "userstyle", 0, userstyle
, {.i
= 0 }, FALSE
},
4953 { "goback", 0, navaction
, {.i
= XT_NAV_BACK
}, FALSE
},
4954 { "goforward", 0, navaction
, {.i
= XT_NAV_FORWARD
}, FALSE
},
4955 { "reload", 0, navaction
, {.i
= XT_NAV_RELOAD
}, FALSE
},
4956 { "reloadforce", 0, navaction
, {.i
= XT_NAV_RELOAD_CACHE
}, FALSE
},
4958 /* vertical movement */
4959 { "scrolldown", 0, move
, {.i
= XT_MOVE_DOWN
}, FALSE
},
4960 { "scrollup", 0, move
, {.i
= XT_MOVE_UP
}, FALSE
},
4961 { "scrollbottom", 0, move
, {.i
= XT_MOVE_BOTTOM
}, FALSE
},
4962 { "scrolltop", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4963 { "1", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4964 { "scrollhalfdown", 0, move
, {.i
= XT_MOVE_HALFDOWN
},FALSE
},
4965 { "scrollhalfup", 0, move
, {.i
= XT_MOVE_HALFUP
}, FALSE
},
4966 { "scrollpagedown", 0, move
, {.i
= XT_MOVE_PAGEDOWN
},FALSE
},
4967 { "scrollpageup", 0, move
, {.i
= XT_MOVE_PAGEUP
}, FALSE
},
4968 /* horizontal movement */
4969 { "scrollright", 0, move
, {.i
= XT_MOVE_RIGHT
}, FALSE
},
4970 { "scrollleft", 0, move
, {.i
= XT_MOVE_LEFT
}, FALSE
},
4971 { "scrollfarright", 0, move
, {.i
= XT_MOVE_FARRIGHT
},FALSE
},
4972 { "scrollfarleft", 0, move
, {.i
= XT_MOVE_FARLEFT
}, FALSE
},
4975 { "favorites", 0, xtp_page_fl
, {0}, FALSE
},
4976 { "fav", 0, xtp_page_fl
, {0}, FALSE
},
4977 { "favadd", 0, add_favorite
, {0}, FALSE
},
4979 { "qall", 0, quit
, {0}, FALSE
},
4980 { "quitall", 0, quit
, {0}, FALSE
},
4981 { "w", 0, save_tabs
, {0}, FALSE
},
4982 { "wq", 0, save_tabs_and_quit
, {0}, FALSE
},
4983 { "help", 0, help
, {0}, FALSE
},
4984 { "about", 0, about
, {0}, FALSE
},
4985 { "stats", 0, stats
, {0}, FALSE
},
4986 { "version", 0, about
, {0}, FALSE
},
4987 { "cookiejar", 0, xtp_page_cl
, {0}, FALSE
},
4990 { "js", 0, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4991 { "save", 1, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4992 { "domain", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
4993 { "fqdn", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4994 { "show", 1, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4995 { "all", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4996 { "persistent", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
4997 { "session", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
4998 { "toggle", 1, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
4999 { "domain", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5000 { "fqdn", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5002 /* cookie command */
5003 { "cookie", 0, cookie_cmd
, {0}, FALSE
},
5004 { "save", 1, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5005 { "domain", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
5006 { "fqdn", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5007 { "show", 1, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5008 { "all", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5009 { "persistent", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
5010 { "session", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
5011 { "toggle", 1, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5012 { "domain", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5013 { "fqdn", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5016 { "cert", 0, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5017 { "save", 1, cert_cmd
, {.i
= XT_SAVE
}, FALSE
},
5018 { "show", 1, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5020 { "ca", 0, ca_cmd
, {0}, FALSE
},
5021 { "downloadmgr", 0, xtp_page_dl
, {0}, FALSE
},
5022 { "dl", 0, xtp_page_dl
, {0}, FALSE
},
5023 { "h", 0, xtp_page_hl
, {0}, FALSE
},
5024 { "history", 0, xtp_page_hl
, {0}, FALSE
},
5025 { "home", 0, go_home
, {0}, FALSE
},
5026 { "restart", 0, restart
, {0}, FALSE
},
5027 { "urlhide", 0, urlaction
, {.i
= XT_URL_HIDE
}, FALSE
},
5028 { "urlshow", 0, urlaction
, {.i
= XT_URL_SHOW
}, FALSE
},
5029 { "statushide", 0, statusaction
, {.i
= XT_STATUSBAR_HIDE
}, FALSE
},
5030 { "statusshow", 0, statusaction
, {.i
= XT_STATUSBAR_SHOW
}, FALSE
},
5032 { "print", 0, print_page
, {0}, FALSE
},
5035 { "open", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5036 { "tabnew", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5037 { "tabedit", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5038 { "tabclose", 0, tabaction
, {.i
= XT_TAB_DELETE
}, FALSE
},
5039 { "tabundoclose", 0, tabaction
, {.i
= XT_TAB_UNDO_CLOSE
} },
5040 { "tabshow", 0, tabaction
, {.i
= XT_TAB_SHOW
}, FALSE
},
5041 { "tabhide", 0, tabaction
, {.i
= XT_TAB_HIDE
}, FALSE
},
5042 { "quit", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5043 { "q", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5045 /* XXX add count to these commands */
5046 { "tabfirst", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5047 { "tabrewind", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5048 { "tablast", 0, movetab
, {.i
= XT_TAB_LAST
}, FALSE
},
5049 { "tabprevious", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5050 { "tabnext", 0, movetab
, {.i
= XT_TAB_NEXT
}, TRUE
},
5051 { "focusout", 0, resizetab
, {.i
= -1}, FALSE
},
5052 { "focusin", 0, resizetab
, {.i
= 1}, FALSE
},
5054 /* command aliases (handy when -S flag is used) */
5055 { "promptopen", 0, command
, {.i
= XT_CMD_OPEN
}, FALSE
},
5056 { "promptopencurrent", 0, command
, {.i
= XT_CMD_OPEN_CURRENT
}, FALSE
},
5057 { "prompttabnew", 0, command
, {.i
= XT_CMD_TABNEW
}, FALSE
},
5058 { "prompttabnewcurrent",0, command
, {.i
= XT_CMD_TABNEW_CURRENT
}, FALSE
},
5061 { "set", 0, set
, {0}, FALSE
},
5062 { "fullscreen", 0, fullscreen
, {0}, FALSE
},
5063 { "f", 0, fullscreen
, {0}, FALSE
},
5066 { "session", 0, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5067 { "delete", 1, session_cmd
, {.i
= XT_DELETE
}, TRUE
},
5068 { "open", 1, session_cmd
, {.i
= XT_OPEN
}, TRUE
},
5069 { "save", 1, session_cmd
, {.i
= XT_SAVE
}, TRUE
},
5070 { "show", 1, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5077 } cmd_status
= {-1, 0};
5080 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5086 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5092 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5094 a
.i
= XT_NAV_FORWARD
;
5104 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5106 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5108 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5115 * cancel, remove, etc. downloads
5118 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5120 struct download find
, *d
= NULL
;
5122 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5124 /* some commands require a valid download id */
5125 if (cmd
!= XT_XTP_DL_LIST
) {
5126 /* lookup download in question */
5128 d
= RB_FIND(download_list
, &downloads
, &find
);
5131 show_oops(t
, "%s: no such download", __func__
);
5136 /* decide what to do */
5138 case XT_XTP_DL_CANCEL
:
5139 webkit_download_cancel(d
->download
);
5141 case XT_XTP_DL_REMOVE
:
5142 webkit_download_cancel(d
->download
); /* just incase */
5143 g_object_unref(d
->download
);
5144 RB_REMOVE(download_list
, &downloads
, d
);
5146 case XT_XTP_DL_LIST
:
5150 show_oops(t
, "%s: unknown command", __func__
);
5153 xtp_page_dl(t
, NULL
);
5157 * Actions on history, only does one thing for now, but
5158 * we provide the function for future actions
5161 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5163 struct history
*h
, *next
;
5167 case XT_XTP_HL_REMOVE
:
5168 /* walk backwards, as listed in reverse */
5169 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5170 next
= RB_PREV(history_list
, &hl
, h
);
5172 RB_REMOVE(history_list
, &hl
, h
);
5173 g_free((gpointer
) h
->title
);
5174 g_free((gpointer
) h
->uri
);
5181 case XT_XTP_HL_LIST
:
5182 /* Nothing - just xtp_page_hl() below */
5185 show_oops(t
, "%s: unknown command", __func__
);
5189 xtp_page_hl(t
, NULL
);
5192 /* remove a favorite */
5194 remove_favorite(struct tab
*t
, int index
)
5196 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5197 char *new_favs
, *tmp
;
5202 /* open favorites */
5203 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5205 if ((f
= fopen(file
, "r")) == NULL
) {
5206 show_oops(t
, "%s: can't open favorites: %s",
5207 __func__
, strerror(errno
));
5211 /* build a string which will become the new favroites file */
5212 new_favs
= g_strdup_printf("%s", "");
5215 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5216 if (feof(f
) || ferror(f
))
5218 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5225 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5226 if (feof(f
) || ferror(f
)) {
5227 show_oops(t
, "%s: can't parse favorites %s",
5228 __func__
, strerror(errno
));
5233 /* as long as this isn't the one we are deleting add to file */
5236 new_favs
= g_strdup_printf("%s%s\n%s\n",
5237 new_favs
, title
, uri
);
5249 /* write back new favorites file */
5250 if ((f
= fopen(file
, "w")) == NULL
) {
5251 show_oops(t
, "%s: can't open favorites: %s",
5252 __func__
, strerror(errno
));
5256 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5269 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5272 case XT_XTP_FL_LIST
:
5273 /* nothing, just the below call to xtp_page_fl() */
5275 case XT_XTP_FL_REMOVE
:
5276 remove_favorite(t
, arg
);
5279 show_oops(t
, "%s: invalid favorites command", __func__
);
5283 xtp_page_fl(t
, NULL
);
5287 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5290 case XT_XTP_CL_LIST
:
5291 /* nothing, just xtp_page_cl() */
5293 case XT_XTP_CL_REMOVE
:
5297 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5301 xtp_page_cl(t
, NULL
);
5304 /* link an XTP class to it's session key and handler function */
5305 struct xtp_despatch
{
5308 void (*handle_func
)(struct tab
*, uint8_t, int);
5311 struct xtp_despatch xtp_despatches
[] = {
5312 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5313 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5314 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5315 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5316 { NULL
, NULL
, NULL
}
5320 * is the url xtp protocol? (xxxt://)
5321 * if so, parse and despatch correct bahvior
5324 parse_xtp_url(struct tab
*t
, const char *url
)
5326 char *dup
= NULL
, *p
, *last
;
5327 uint8_t n_tokens
= 0;
5328 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5329 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5334 * tokens array meaning:
5336 * tokens[1] = session key
5337 * tokens[2] = action
5338 * tokens[3] = optional argument
5341 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5343 /*xtp tab meaning is normal unless proven special */
5344 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5346 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5349 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5351 /* split out the url */
5352 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5353 (p
= strtok_r(NULL
, "/", &last
))) {
5355 tokens
[n_tokens
++] = p
;
5358 /* should be atleast three fields 'class/seskey/command/arg' */
5362 dsp
= xtp_despatches
;
5363 req_class
= atoi(tokens
[0]);
5364 while (dsp
->xtp_class
!= NULL
) {
5365 if (dsp
->xtp_class
== req_class
) {
5372 /* did we find one atall? */
5373 if (dsp_match
== NULL
) {
5374 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
5378 /* check session key and call despatch function */
5379 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
5380 ret
= TRUE
; /* all is well, this was a valid xtp request */
5381 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
5394 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5396 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
5398 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
5401 show_oops_s("activate_uri_entry_cb invalid parameters");
5406 show_oops(t
, "activate_uri_entry_cb no uri");
5410 uri
+= strspn(uri
, "\t ");
5412 /* if xxxt:// treat specially */
5413 if (parse_xtp_url(t
, uri
))
5416 /* otherwise continue to load page normally */
5417 load_uri(t
, (gchar
*)uri
);
5422 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5424 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
5425 char *newuri
= NULL
;
5428 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
5431 show_oops_s("activate_search_entry_cb invalid parameters");
5435 if (search_string
== NULL
) {
5436 show_oops(t
, "no search_string");
5440 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
5441 newuri
= g_strdup_printf(search_string
, enc_search
);
5444 webkit_web_view_load_uri(t
->wv
, newuri
);
5452 check_and_set_js(const gchar
*uri
, struct tab
*t
)
5454 struct domain
*d
= NULL
;
5457 if (uri
== NULL
|| t
== NULL
)
5460 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5465 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
5466 es
? "enable" : "disable", uri
);
5468 g_object_set(G_OBJECT(t
->settings
),
5469 "enable-scripts", es
, (char *)NULL
);
5470 g_object_set(G_OBJECT(t
->settings
),
5471 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
5472 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5474 button_set_stockid(t
->js_toggle
,
5475 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
5479 show_ca_status(struct tab
*t
, const char *uri
)
5481 WebKitWebFrame
*frame
;
5482 WebKitWebDataSource
*source
;
5483 WebKitNetworkRequest
*request
;
5484 SoupMessage
*message
;
5486 gchar
*col_str
= XT_COLOR_WHITE
;
5489 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
5490 ssl_strict_certs
, ssl_ca_file
, uri
);
5494 if (ssl_ca_file
== NULL
) {
5495 if (g_str_has_prefix(uri
, "http://"))
5497 if (g_str_has_prefix(uri
, "https://")) {
5498 col_str
= XT_COLOR_RED
;
5503 if (g_str_has_prefix(uri
, "http://") ||
5504 !g_str_has_prefix(uri
, "https://"))
5507 frame
= webkit_web_view_get_main_frame(t
->wv
);
5508 source
= webkit_web_frame_get_data_source(frame
);
5509 request
= webkit_web_data_source_get_request(source
);
5510 message
= webkit_network_request_get_message(request
);
5512 if (message
&& (soup_message_get_flags(message
) &
5513 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
5514 col_str
= XT_COLOR_GREEN
;
5517 r
= load_compare_cert(t
, NULL
);
5519 col_str
= XT_COLOR_BLUE
;
5521 col_str
= XT_COLOR_YELLOW
;
5523 col_str
= XT_COLOR_RED
;
5528 gdk_color_parse(col_str
, &color
);
5529 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
5531 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
5532 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5534 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5535 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5538 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5540 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5541 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5548 free_favicon(struct tab
*t
)
5550 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p pix %p\n",
5551 __func__
, t
->icon_download
, t
->icon_request
, t
->icon_pixbuf
);
5553 if (t
->icon_request
)
5554 g_object_unref(t
->icon_request
);
5556 g_object_unref(t
->icon_pixbuf
);
5557 if (t
->icon_dest_uri
)
5558 g_free(t
->icon_dest_uri
);
5560 t
->icon_pixbuf
= NULL
;
5561 t
->icon_request
= NULL
;
5562 t
->icon_dest_uri
= NULL
;
5566 xt_icon_from_name(struct tab
*t
, gchar
*name
)
5568 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5569 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5571 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5572 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5574 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5575 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5579 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pixbuf
)
5581 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
5582 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5584 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
5585 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5587 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5588 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5592 is_valid_icon(char *file
)
5595 const char *mime_type
;
5599 gf
= g_file_new_for_path(file
);
5600 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5602 mime_type
= g_file_info_get_content_type(fi
);
5603 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
5604 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
5605 g_strcmp0(mime_type
, "image/png") == 0 ||
5606 g_strcmp0(mime_type
, "image/gif") == 0 ||
5607 g_strcmp0(mime_type
, "application/octet-stream") == 0;
5615 set_favicon_from_file(struct tab
*t
, char *file
)
5618 GdkPixbuf
*pixbuf
, *scaled
;
5621 if (t
== NULL
|| file
== NULL
)
5623 if (t
->icon_pixbuf
) {
5624 DNPRINTF(XT_D_DOWNLOAD
, "%s: icon already set\n", __func__
);
5628 if (g_str_has_prefix(file
, "file://"))
5629 file
+= strlen("file://");
5630 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
5632 if (!stat(file
, &sb
)) {
5633 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
5634 /* corrupt icon so trash it */
5635 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5638 /* no need to set icon to default here */
5643 pixbuf
= gdk_pixbuf_new_from_file(file
, NULL
);
5644 if (pixbuf
== NULL
) {
5645 xt_icon_from_name(t
, "text-html");
5649 g_object_get(pixbuf
, "width", &width
, "height", &height
,
5651 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d icon size %dx%d\n",
5652 __func__
, t
->tab_id
, width
, height
);
5654 if (width
> 16 || height
> 16) {
5655 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5656 GDK_INTERP_BILINEAR
);
5657 g_object_unref(pixbuf
);
5661 if (scaled
== NULL
) {
5662 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5663 GDK_INTERP_BILINEAR
);
5667 t
->icon_pixbuf
= scaled
;
5668 xt_icon_from_pixbuf(t
, t
->icon_pixbuf
);
5672 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5675 WebKitDownloadStatus status
= webkit_download_get_status(download
);
5676 struct tab
*tt
= NULL
, *t
= NULL
;
5679 * find the webview instead of passing in the tab as it could have been
5680 * deleted from underneath us.
5682 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5691 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
5692 __func__
, t
->tab_id
, status
);
5695 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
5697 t
->icon_download
= NULL
;
5700 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
5703 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
5706 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
5708 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
5709 __func__
, t
->tab_id
);
5710 t
->icon_download
= NULL
;
5713 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
5716 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
5717 __func__
, t
->icon_dest_uri
);
5718 set_favicon_from_file(t
, t
->icon_dest_uri
);
5719 /* these will be freed post callback */
5720 t
->icon_request
= NULL
;
5721 t
->icon_download
= NULL
;
5729 abort_favicon_download(struct tab
*t
)
5731 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
5733 if (t
->icon_download
) {
5734 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
5735 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5736 webkit_download_cancel(t
->icon_download
);
5737 t
->icon_download
= NULL
;
5741 xt_icon_from_name(t
, "text-html");
5745 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
5747 gchar
*name_hash
, file
[PATH_MAX
];
5750 DNPRINTF(XT_D_DOWNLOAD
, "notify_icon_loaded_cb %s\n", uri
);
5752 if (uri
== NULL
|| t
== NULL
)
5755 if (t
->icon_request
) {
5756 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
5760 /* check to see if we got the icon in cache */
5761 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
5762 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
5765 if (!stat(file
, &sb
)) {
5766 if (sb
.st_size
> 0) {
5767 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
5769 set_favicon_from_file(t
, file
);
5773 /* corrupt icon so trash it */
5774 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5779 /* create download for icon */
5780 t
->icon_request
= webkit_network_request_new(uri
);
5781 if (t
->icon_request
== NULL
) {
5782 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
5787 t
->icon_download
= webkit_download_new(t
->icon_request
);
5788 if (t
->icon_download
== NULL
) {
5789 fprintf(stderr
, "%s: icon_download", __func__
);
5793 /* we have to free icon_dest_uri later */
5794 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
5795 webkit_download_set_destination_uri(t
->icon_download
,
5798 if (webkit_download_get_status(t
->icon_download
) ==
5799 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5800 fprintf(stderr
, "%s: download failed to start", __func__
);
5801 g_object_unref(t
->icon_request
);
5802 g_free(t
->icon_dest_uri
);
5803 t
->icon_request
= NULL
;
5804 t
->icon_dest_uri
= NULL
;
5808 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
5809 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5811 webkit_download_start(t
->icon_download
);
5815 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5817 const gchar
*set
= NULL
, *uri
= NULL
, *title
= NULL
;
5818 struct history
*h
, find
;
5819 const gchar
*s_loading
;
5822 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
5823 webkit_web_view_get_load_status(wview
), get_uri(wview
) ? get_uri(wview
) : "NOTHING");
5826 show_oops_s("notify_load_status_cb invalid paramters");
5830 switch (webkit_web_view_get_load_status(wview
)) {
5831 case WEBKIT_LOAD_PROVISIONAL
:
5833 abort_favicon_download(t
);
5834 #if GTK_CHECK_VERSION(2, 20, 0)
5835 gtk_widget_show(t
->spinner
);
5836 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
5838 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
5840 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
5846 case WEBKIT_LOAD_COMMITTED
:
5848 if ((uri
= get_uri(wview
)) != NULL
) {
5849 if (strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
5850 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
5856 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
5859 /* check if js white listing is enabled */
5860 if (enable_js_whitelist
) {
5861 uri
= get_uri(wview
);
5862 check_and_set_js(uri
, t
);
5868 show_ca_status(t
, uri
);
5870 /* we know enough to autosave the session */
5871 if (session_autosave
) {
5877 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
5881 case WEBKIT_LOAD_FINISHED
:
5883 uri
= get_uri(wview
);
5887 if (!strncmp(uri
, "http://", strlen("http://")) ||
5888 !strncmp(uri
, "https://", strlen("https://")) ||
5889 !strncmp(uri
, "file://", strlen("file://"))) {
5891 h
= RB_FIND(history_list
, &hl
, &find
);
5893 title
= webkit_web_view_get_title(wview
);
5894 set
= title
? title
: uri
;
5895 h
= g_malloc(sizeof *h
);
5896 h
->uri
= g_strdup(uri
);
5897 h
->title
= g_strdup(set
);
5898 RB_INSERT(history_list
, &hl
, h
);
5899 completion_add_uri(h
->uri
);
5900 update_history_tabs(NULL
);
5904 set_status(t
, (char *)uri
, XT_STATUS_URI
);
5905 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5906 case WEBKIT_LOAD_FAILED
:
5909 #if GTK_CHECK_VERSION(2, 20, 0)
5910 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5911 gtk_widget_hide(t
->spinner
);
5913 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
5914 if (s_loading
&& !strcmp(s_loading
, "Loading"))
5915 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5917 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
5922 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
5924 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
5925 webkit_web_view_can_go_back(wview
));
5927 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
5928 webkit_web_view_can_go_forward(wview
));
5930 /* take focus if we are visible */
5935 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5937 const gchar
*set
= NULL
, *title
= NULL
;
5939 title
= webkit_web_view_get_title(wview
);
5940 set
= title
? title
: get_uri(wview
);
5941 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
5942 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
5946 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5948 run_script(t
, JS_HINTING
);
5952 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
5954 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
5955 progress
== 100 ? 0 : (double)progress
/ 100);
5956 if (show_url
== 0) {
5957 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
5958 progress
== 100 ? 0 : (double)progress
/ 100);
5963 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
5964 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
5965 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
5968 WebKitWebNavigationReason reason
;
5969 struct domain
*d
= NULL
;
5972 show_oops_s("webview_npd_cb invalid parameters");
5976 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
5978 webkit_network_request_get_uri(request
));
5980 uri
= (char *)webkit_network_request_get_uri(request
);
5982 /* if this is an xtp url, we don't load anything else */
5983 if (parse_xtp_url(t
, uri
))
5986 if (t
->ctrl_click
) {
5988 create_new_tab(uri
, NULL
, ctrl_click_focus
);
5989 webkit_web_policy_decision_ignore(pd
);
5990 return (TRUE
); /* we made the decission */
5994 * This is a little hairy but it comes down to this:
5995 * when we run in whitelist mode we have to assist the browser in
5996 * opening the URL that it would have opened in a new tab.
5998 reason
= webkit_web_navigation_action_get_reason(na
);
5999 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6000 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6001 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6004 webkit_web_policy_decision_use(pd
);
6005 return (TRUE
); /* we made the decission */
6012 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6015 struct domain
*d
= NULL
;
6017 WebKitWebView
*webview
= NULL
;
6019 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6020 webkit_web_view_get_uri(wv
));
6023 /* open in current tab */
6025 } else if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6026 uri
= webkit_web_view_get_uri(wv
);
6027 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6030 tt
= create_new_tab(NULL
, NULL
, 1);
6032 } else if (enable_scripts
== 1) {
6033 tt
= create_new_tab(NULL
, NULL
, 1);
6041 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6044 struct domain
*d
= NULL
;
6046 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6048 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6049 uri
= webkit_web_view_get_uri(wv
);
6050 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6054 } else if (enable_scripts
== 1)
6061 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6063 /* we can not eat the event without throwing gtk off so defer it */
6065 /* catch middle click */
6066 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6071 /* catch ctrl click */
6072 if (e
->type
== GDK_BUTTON_RELEASE
&&
6073 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6078 return (XT_CB_PASSTHROUGH
);
6082 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6084 struct mime_type
*m
;
6086 m
= find_mime_type(mime_type
);
6094 show_oops(t
, "can't fork mime handler");
6103 execlp(m
->mt_action
, m
->mt_action
,
6104 webkit_network_request_get_uri(request
), (void *)NULL
);
6113 get_mime_type(char *file
)
6115 const char *mime_type
;
6119 if (g_str_has_prefix(file
, "file://"))
6120 file
+= strlen("file://");
6122 gf
= g_file_new_for_path(file
);
6123 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6125 mime_type
= g_file_info_get_content_type(fi
);
6133 run_download_mimehandler(char *mime_type
, char *file
)
6135 struct mime_type
*m
;
6137 m
= find_mime_type(mime_type
);
6143 show_oops_s("can't fork download mime handler");
6152 if (g_str_has_prefix(file
, "file://"))
6153 file
+= strlen("file://");
6154 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6163 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6166 WebKitDownloadStatus status
;
6167 const gchar
*file
= NULL
, *mime
= NULL
;
6169 if (download
== NULL
)
6171 status
= webkit_download_get_status(download
);
6172 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6175 file
= webkit_download_get_destination_uri(download
);
6178 mime
= get_mime_type((char *)file
);
6182 run_download_mimehandler((char *)mime
, (char *)file
);
6186 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6187 WebKitNetworkRequest
*request
, char *mime_type
,
6188 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6191 show_oops_s("webview_mimetype_cb invalid parameters");
6195 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6196 t
->tab_id
, mime_type
);
6198 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6199 webkit_web_policy_decision_ignore(decision
);
6204 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6205 webkit_web_policy_decision_download(decision
);
6213 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6216 const gchar
*filename
;
6218 struct download
*download_entry
;
6221 if (wk_download
== NULL
|| t
== NULL
) {
6222 show_oops_s("%s invalid parameters", __func__
);
6226 filename
= webkit_download_get_suggested_filename(wk_download
);
6227 if (filename
== NULL
)
6228 return (FALSE
); /* abort download */
6230 uri
= g_strdup_printf("file://%s/%s", download_dir
, filename
);
6232 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6233 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6235 webkit_download_set_destination_uri(wk_download
, uri
);
6237 if (webkit_download_get_status(wk_download
) ==
6238 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6239 show_oops(t
, "%s: download failed to start", __func__
);
6241 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6243 /* connect "download first" mime handler */
6244 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6245 G_CALLBACK(download_status_changed_cb
), NULL
);
6247 download_entry
= g_malloc(sizeof(struct download
));
6248 download_entry
->download
= wk_download
;
6249 download_entry
->tab
= t
;
6250 download_entry
->id
= next_download_id
++;
6251 RB_INSERT(download_list
, &downloads
, download_entry
);
6252 /* get from history */
6253 g_object_ref(wk_download
);
6254 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6255 show_oops(t
, "Download of '%s' started...",
6256 basename(webkit_download_get_destination_uri(wk_download
)));
6262 /* sync other download manager tabs */
6263 update_download_tabs(NULL
);
6266 * NOTE: never redirect/render the current tab before this
6267 * function returns. This will cause the download to never start.
6269 return (ret
); /* start download */
6273 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6275 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6278 show_oops_s("webview_hover_cb");
6283 set_status(t
, uri
, XT_STATUS_LINK
);
6286 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6291 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6293 struct key_binding
*k
;
6295 TAILQ_FOREACH(k
, &kbl
, entry
)
6296 if (e
->keyval
== k
->key
&& (entry
? k
->use_in_entry
: 1)) {
6298 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6299 return (cmd_execute(t
, k
->cmd
));
6300 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6301 return (cmd_execute(t
, k
->cmd
));
6305 return (XT_CB_PASSTHROUGH
);
6309 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6311 char s
[2], buf
[128];
6312 const char *errstr
= NULL
;
6315 /* don't use w directly; use t->whatever instead */
6318 show_oops_s("wv_keypress_after_cb");
6319 return (XT_CB_PASSTHROUGH
);
6322 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6323 e
->keyval
, e
->state
, t
);
6327 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6329 return (XT_CB_HANDLED
);
6333 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6334 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6336 /* we have a string */
6338 /* we have a number */
6339 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6347 /* XXX unfuck this */
6348 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6349 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6350 /* last input was numerical */
6352 l
= strlen(t
->hint_num
);
6359 t
->hint_num
[l
] = '\0';
6363 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6364 /* last input was alphanumerical */
6366 l
= strlen(t
->hint_buf
);
6373 t
->hint_buf
[l
] = '\0';
6383 /* numerical input */
6384 if (CLEAN(e
->state
) == 0 &&
6385 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6386 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6387 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6388 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6391 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6393 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6396 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6398 t
->hint_mode
= XT_HINT_NUMERICAL
;
6402 /* empty the counter buffer */
6403 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6404 return (XT_CB_HANDLED
);
6407 /* alphanumerical input */
6409 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6410 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6411 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6412 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6413 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6414 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6415 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6418 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6421 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6423 t
->hint_mode
= XT_HINT_ALPHANUM
;
6426 /* empty the counter buffer */
6427 bzero(t
->hint_num
, sizeof t
->hint_num
);
6428 return (XT_CB_HANDLED
);
6431 return (XT_CB_HANDLED
);
6434 return (handle_keypress(t
, e
, 0));
6438 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6442 return (XT_CB_PASSTHROUGH
);
6446 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6448 const gchar
*c
= gtk_entry_get_text(w
);
6452 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6453 e
->keyval
, e
->state
, t
);
6456 show_oops_s("cmd_keyrelease_cb invalid parameters");
6457 return (XT_CB_PASSTHROUGH
);
6460 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6461 e
->keyval
, e
->state
, t
);
6465 if (strlen(c
) == 1) {
6466 webkit_web_view_unmark_text_matches(t
->wv
);
6472 else if (c
[0] == '?')
6478 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6480 /* not found, mark red */
6481 gdk_color_parse(XT_COLOR_RED
, &color
);
6482 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6483 /* unmark and remove selection */
6484 webkit_web_view_unmark_text_matches(t
->wv
);
6485 /* my kingdom for a way to unselect text in webview */
6487 /* found, highlight all */
6488 webkit_web_view_unmark_text_matches(t
->wv
);
6489 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6490 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6491 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6492 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6495 return (XT_CB_PASSTHROUGH
);
6499 match_uri(const gchar
*uri
, const gchar
*key
) {
6502 gboolean match
= FALSE
;
6506 if (!strncmp(key
, uri
, len
))
6509 voffset
= strstr(uri
, "/") + 2;
6510 if (!strncmp(key
, voffset
, len
))
6512 else if (g_str_has_prefix(voffset
, "www.")) {
6513 voffset
= voffset
+ strlen("www.");
6514 if (!strncmp(key
, voffset
, len
))
6523 cmd_getlist(int id
, char *key
)
6528 if (id
>= 0 && (cmds
[id
].userarg
&& (cmds
[id
].arg
.i
== XT_TAB_OPEN
|| cmds
[id
].arg
.i
== XT_TAB_NEW
))) {
6529 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6530 if (match_uri(h
->uri
, key
)) {
6531 cmd_status
.list
[c
] = (char *)h
->uri
;
6540 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6542 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6543 if(cmds
[i
].level
< dep
)
6545 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6546 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6554 cmd_getnext(int dir
)
6556 cmd_status
.index
+= dir
;
6558 if (cmd_status
.index
< 0)
6559 cmd_status
.index
= cmd_status
.len
- 1;
6560 else if (cmd_status
.index
>= cmd_status
.len
)
6561 cmd_status
.index
= 0;
6563 return cmd_status
.list
[cmd_status
.index
];
6567 cmd_tokenize(char *s
, char *tokens
[])
6571 size_t len
= strlen(s
);
6572 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6574 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6584 cmd_complete(struct tab
*t
, char *str
, int dir
)
6586 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6587 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1, matchcount
= 0;
6588 char *tok
, *match
, *s
= g_strdup(str
);
6590 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6592 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: complete %s\n", str
);
6594 levels
= cmd_tokenize(s
, tokens
);
6597 for (i
= 0; i
< levels
- 1; i
++) {
6600 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6601 if (cmds
[j
].level
< dep
)
6603 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, strlen(tok
))) {
6606 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
6613 if (matchcount
== 1) {
6614 strlcat(res
, tok
, sizeof res
);
6615 strlcat(res
, " ", sizeof res
);
6623 if (cmd_status
.index
== -1)
6624 cmd_getlist(parent
, tokens
[i
]);
6626 if (cmd_status
.len
> 0) {
6627 match
= cmd_getnext(dir
);
6628 strlcat(res
, match
, sizeof res
);
6629 gtk_entry_set_text(w
, res
);
6630 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6635 cmd_execute(struct tab
*t
, char *str
)
6637 struct cmd
*cmd
= NULL
;
6638 char *tok
, *last
, *s
= g_strdup(str
);
6639 int j
, len
, c
= 0, dep
= 0, matchcount
= 0;
6641 for (tok
= strtok_r(s
, " ", &last
); tok
;
6642 tok
= strtok_r(NULL
, " ", &last
)) {
6644 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6645 if (cmds
[j
].level
< dep
)
6647 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1: strlen(tok
);
6648 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, len
)) {
6652 if (len
== strlen(cmds
[j
].cmd
)) {
6658 if (matchcount
== 1) {
6663 show_oops(t
, "Invalid command: %s", str
);
6665 return (XT_CB_PASSTHROUGH
);
6670 cmd
->arg
.s
= last
? g_strdup(last
) : g_strdup("");
6672 cmd
->arg
.s
= g_strdup(tok
);
6674 /* arg->s contains last token */
6675 cmd
->func(t
, &cmd
->arg
);
6680 return (XT_CB_HANDLED
);
6684 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6687 show_oops_s("entry_key_cb invalid parameters");
6688 return (XT_CB_PASSTHROUGH
);
6691 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6692 e
->keyval
, e
->state
, t
);
6696 if (e
->keyval
== GDK_Escape
) {
6697 /* don't use focus_webview(t) because we want to type :cmds */
6698 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6701 return (handle_keypress(t
, e
, 1));
6705 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6707 int rv
= XT_CB_HANDLED
;
6708 const gchar
*c
= gtk_entry_get_text(w
);
6711 show_oops_s("cmd_keypress_cb parameters");
6712 return (XT_CB_PASSTHROUGH
);
6715 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6716 e
->keyval
, e
->state
, t
);
6720 e
->keyval
= GDK_Escape
;
6721 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6722 e
->keyval
= GDK_Escape
;
6724 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
6725 cmd_status
.index
= -1;
6727 switch (e
->keyval
) {
6730 cmd_complete(t
, (char *)&c
[1], 1);
6732 case GDK_ISO_Left_Tab
:
6734 cmd_complete(t
, (char *)&c
[1], -1);
6738 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
6746 if (c
[0] == '/' || c
[0] == '?')
6747 webkit_web_view_unmark_text_matches(t
->wv
);
6751 rv
= XT_CB_PASSTHROUGH
;
6757 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6760 show_oops_s("cmd_focusout_cb invalid parameters");
6761 return (XT_CB_PASSTHROUGH
);
6763 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
6768 if (show_url
== 0 || t
->focus_wv
)
6771 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6773 return (XT_CB_PASSTHROUGH
);
6777 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6780 const gchar
*c
= gtk_entry_get_text(entry
);
6783 show_oops_s("cmd_activate_cb invalid parameters");
6787 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6794 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6800 if (c
[0] == '/' || c
[0] == '?') {
6801 if (t
->search_text
) {
6802 g_free(t
->search_text
);
6803 t
->search_text
= NULL
;
6806 t
->search_text
= g_strdup(s
);
6808 g_free(global_search
);
6809 global_search
= g_strdup(s
);
6810 t
->search_forward
= c
[0] == '/';
6822 backward_cb(GtkWidget
*w
, struct tab
*t
)
6827 show_oops_s("backward_cb invalid parameters");
6831 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6838 forward_cb(GtkWidget
*w
, struct tab
*t
)
6843 show_oops_s("forward_cb invalid parameters");
6847 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6849 a
.i
= XT_NAV_FORWARD
;
6854 home_cb(GtkWidget
*w
, struct tab
*t
)
6857 show_oops_s("home_cb invalid parameters");
6861 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6867 stop_cb(GtkWidget
*w
, struct tab
*t
)
6869 WebKitWebFrame
*frame
;
6872 show_oops_s("stop_cb invalid parameters");
6876 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
6878 frame
= webkit_web_view_get_main_frame(t
->wv
);
6879 if (frame
== NULL
) {
6880 show_oops(t
, "stop_cb: no frame");
6884 webkit_web_frame_stop_loading(frame
);
6885 abort_favicon_download(t
);
6889 setup_webkit(struct tab
*t
)
6891 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
6892 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
6893 FALSE
, (char *)NULL
);
6895 warnx("webkit does not have \"enable-dns-prefetching\" property");
6896 g_object_set(G_OBJECT(t
->settings
),
6897 "user-agent", t
->user_agent
, (char *)NULL
);
6898 g_object_set(G_OBJECT(t
->settings
),
6899 "enable-scripts", enable_scripts
, (char *)NULL
);
6900 g_object_set(G_OBJECT(t
->settings
),
6901 "enable-plugins", enable_plugins
, (char *)NULL
);
6902 g_object_set(G_OBJECT(t
->settings
),
6903 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
6904 g_object_set(G_OBJECT(t
->wv
),
6905 "full-content-zoom", TRUE
, (char *)NULL
);
6906 adjustfont_webkit(t
, XT_FONT_SET
);
6908 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6912 create_browser(struct tab
*t
)
6918 show_oops_s("create_browser invalid parameters");
6922 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
6923 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
6924 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
6925 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
6927 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
6928 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
6929 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
6931 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
6932 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
6935 t
->settings
= webkit_web_settings_new();
6937 if (user_agent
== NULL
) {
6938 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
6940 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
6943 t
->user_agent
= g_strdup(user_agent
);
6945 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
6957 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
6958 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
6959 gtk_widget_set_name(w
, "xxxterm");
6960 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
6961 g_signal_connect(G_OBJECT(w
), "delete_event",
6962 G_CALLBACK (gtk_main_quit
), NULL
);
6968 create_kiosk_toolbar(struct tab
*t
)
6970 GtkWidget
*toolbar
= NULL
, *b
;
6972 b
= gtk_hbox_new(FALSE
, 0);
6974 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
6976 /* backward button */
6977 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
6978 gtk_widget_set_sensitive(t
->backward
, FALSE
);
6979 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
6980 G_CALLBACK(backward_cb
), t
);
6981 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
6983 /* forward button */
6984 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
6985 gtk_widget_set_sensitive(t
->forward
, FALSE
);
6986 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
6987 G_CALLBACK(forward_cb
), t
);
6988 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
6991 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
6992 gtk_widget_set_sensitive(t
->gohome
, true);
6993 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
6994 G_CALLBACK(home_cb
), t
);
6995 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
6997 /* create widgets but don't use them */
6998 t
->uri_entry
= gtk_entry_new();
6999 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7000 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7001 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7007 create_toolbar(struct tab
*t
)
7009 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7011 b
= gtk_hbox_new(FALSE
, 0);
7013 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7016 /* backward button */
7017 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7018 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7019 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7020 G_CALLBACK(backward_cb
), t
);
7021 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7023 /* forward button */
7024 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7025 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7026 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7027 G_CALLBACK(forward_cb
), t
);
7028 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7032 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7033 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7034 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7035 G_CALLBACK(stop_cb
), t
);
7036 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7040 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7041 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7042 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7043 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7044 G_CALLBACK(js_toggle_cb
), t
);
7045 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7048 t
->uri_entry
= gtk_entry_new();
7049 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7050 G_CALLBACK(activate_uri_entry_cb
), t
);
7051 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7052 G_CALLBACK(entry_key_cb
), t
);
7054 eb1
= gtk_hbox_new(FALSE
, 0);
7055 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7056 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7057 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7060 if (fancy_bar
&& search_string
) {
7062 t
->search_entry
= gtk_entry_new();
7063 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7064 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7065 G_CALLBACK(activate_search_entry_cb
), t
);
7066 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7067 G_CALLBACK(entry_key_cb
), t
);
7068 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7069 eb2
= gtk_hbox_new(FALSE
, 0);
7070 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7071 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7073 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7083 TAILQ_FOREACH(t
, &tabs
, entry
)
7084 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7088 undo_close_tab_save(struct tab
*t
)
7092 struct undo
*u1
, *u2
;
7094 WebKitWebHistoryItem
*item
;
7096 if ((uri
= get_uri(t
->wv
)) == NULL
)
7099 u1
= g_malloc0(sizeof(struct undo
));
7100 u1
->uri
= g_strdup(uri
);
7102 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7104 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7105 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7108 /* forward history */
7109 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7113 u1
->history
= g_list_prepend(u1
->history
,
7114 webkit_web_history_item_copy(item
));
7115 items
= g_list_next(items
);
7120 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7121 u1
->history
= g_list_prepend(u1
->history
,
7122 webkit_web_history_item_copy(item
));
7126 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7130 u1
->history
= g_list_prepend(u1
->history
,
7131 webkit_web_history_item_copy(item
));
7132 items
= g_list_next(items
);
7135 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7137 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7138 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7139 TAILQ_REMOVE(&undos
, u2
, entry
);
7141 g_list_free(u2
->history
);
7150 delete_tab(struct tab
*t
)
7154 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7159 TAILQ_REMOVE(&tabs
, t
, entry
);
7161 /* halt all webkit activity */
7162 abort_favicon_download(t
);
7163 webkit_web_view_stop_loading(t
->wv
);
7164 undo_close_tab_save(t
);
7166 if (browser_mode
== XT_BM_KIOSK
) {
7167 gtk_widget_destroy(t
->uri_entry
);
7168 gtk_widget_destroy(t
->stop
);
7169 gtk_widget_destroy(t
->js_toggle
);
7172 gtk_widget_destroy(t
->vbox
);
7173 g_free(t
->user_agent
);
7174 g_free(t
->stylesheet
);
7178 if (TAILQ_EMPTY(&tabs
)) {
7179 if (browser_mode
== XT_BM_KIOSK
)
7180 create_new_tab(home
, NULL
, 1);
7182 create_new_tab(NULL
, NULL
, 1);
7185 /* recreate session */
7186 if (session_autosave
) {
7193 adjustfont_webkit(struct tab
*t
, int adjust
)
7198 show_oops_s("adjustfont_webkit invalid parameters");
7202 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7203 if (adjust
== XT_FONT_SET
) {
7204 t
->font_size
= default_font_size
;
7205 zoom
= default_zoom_level
;
7206 t
->font_size
+= adjust
;
7207 g_object_set(G_OBJECT(t
->settings
), "default-font-size",
7208 t
->font_size
, (char *)NULL
);
7209 g_object_get(G_OBJECT(t
->settings
), "default-font-size",
7210 &t
->font_size
, (char *)NULL
);
7212 t
->font_size
+= adjust
;
7213 zoom
+= adjust
/25.0;
7218 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7219 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7223 append_tab(struct tab
*t
)
7228 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7229 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7233 create_new_tab(char *title
, struct undo
*u
, int focus
)
7236 int load
= 1, id
, notfound
;
7238 WebKitWebHistoryItem
*item
;
7242 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7244 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7245 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7249 t
= g_malloc0(sizeof *t
);
7251 if (title
== NULL
) {
7252 title
= "(untitled)";
7256 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7258 /* label + button for tab */
7259 b
= gtk_hbox_new(FALSE
, 0);
7262 #if GTK_CHECK_VERSION(2, 20, 0)
7263 t
->spinner
= gtk_spinner_new ();
7265 t
->label
= gtk_label_new(title
);
7266 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7267 gtk_widget_set_size_request(t
->label
, 100, 0);
7268 gtk_widget_set_size_request(b
, 130, 0);
7270 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7271 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7272 #if GTK_CHECK_VERSION(2, 20, 0)
7273 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7277 if (browser_mode
== XT_BM_KIOSK
)
7278 t
->toolbar
= create_kiosk_toolbar(t
);
7280 t
->toolbar
= create_toolbar(t
);
7282 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7285 t
->browser_win
= create_browser(t
);
7286 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7288 /* oops message for user feedback */
7289 t
->oops
= gtk_entry_new();
7290 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7291 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7292 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7293 gdk_color_parse(XT_COLOR_RED
, &color
);
7294 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7295 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7298 t
->cmd
= gtk_entry_new();
7299 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7300 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7301 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7304 t
->statusbar
= gtk_entry_new();
7305 gtk_entry_set_inner_border(GTK_ENTRY(t
->statusbar
), NULL
);
7306 gtk_entry_set_has_frame(GTK_ENTRY(t
->statusbar
), FALSE
);
7307 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7308 gdk_color_parse(XT_COLOR_BLACK
, &color
);
7309 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7310 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7311 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7312 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7314 /* xtp meaning is normal by default */
7315 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
7317 /* set empty favicon */
7318 xt_icon_from_name(t
, "text-html");
7320 /* and show it all */
7321 gtk_widget_show_all(b
);
7322 gtk_widget_show_all(t
->vbox
);
7324 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7328 id
= gtk_notebook_get_current_page(notebook
);
7329 TAILQ_FOREACH(tt
, &tabs
, entry
) {
7330 if (tt
->tab_id
== id
) {
7332 TAILQ_INSERT_AFTER(&tabs
, tt
, t
, entry
);
7333 gtk_notebook_insert_page(notebook
, t
->vbox
, b
,
7343 #if GTK_CHECK_VERSION(2, 20, 0)
7344 /* turn spinner off if we are a new tab without uri */
7346 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7347 gtk_widget_hide(t
->spinner
);
7350 /* make notebook tabs reorderable */
7351 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7353 g_object_connect(G_OBJECT(t
->cmd
),
7354 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7355 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7356 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7357 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7360 /* reuse wv_button_cb to hide oops */
7361 g_object_connect(G_OBJECT(t
->oops
),
7362 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7365 g_object_connect(G_OBJECT(t
->wv
),
7366 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7367 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7368 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7369 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7370 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7371 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7372 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7373 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7374 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7375 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7376 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
7377 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
7378 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7379 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7381 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7383 g_signal_connect(t
->wv
,
7384 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7385 g_signal_connect(t
->wv
,
7386 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7388 /* hijack the unused keys as if we were the browser */
7389 g_object_connect(G_OBJECT(t
->toolbar
),
7390 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7393 g_signal_connect(G_OBJECT(bb
), "button_press_event",
7394 G_CALLBACK(tab_close_cb
), t
);
7399 url_set_visibility();
7400 statusbar_set_visibility();
7403 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7404 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
7409 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
7413 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7418 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7419 /* restore the tab's history */
7420 if (u
&& u
->history
) {
7424 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
7425 items
= g_list_next(items
);
7428 item
= g_list_nth_data(u
->history
, u
->back
);
7430 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
7433 g_list_free(u
->history
);
7435 webkit_web_back_forward_list_clear(t
->bfl
);
7441 notebook_switchpage_cb(GtkNotebook
*nb
, GtkNotebookPage
*nbp
, guint pn
,
7447 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
7449 TAILQ_FOREACH(t
, &tabs
, entry
) {
7450 if (t
->tab_id
== pn
) {
7451 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
7454 uri
= webkit_web_view_get_title(t
->wv
);
7457 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
7469 menuitem_response(struct tab
*t
)
7471 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7475 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
7477 GtkWidget
*menu
, *menu_items
;
7478 GdkEventButton
*bevent
;
7482 if (event
->type
== GDK_BUTTON_PRESS
) {
7483 bevent
= (GdkEventButton
*) event
;
7484 menu
= gtk_menu_new();
7486 TAILQ_FOREACH(ti
, &tabs
, entry
) {
7487 if ((uri
= get_uri(ti
->wv
)) == NULL
)
7488 /* XXX make sure there is something to print */
7489 /* XXX add gui pages in here to look purdy */
7491 menu_items
= gtk_menu_item_new_with_label(uri
);
7492 gtk_menu_append(GTK_MENU (menu
), menu_items
);
7493 gtk_widget_show(menu_items
);
7495 gtk_signal_connect_object(GTK_OBJECT(menu_items
),
7496 "activate", GTK_SIGNAL_FUNC(menuitem_response
),
7500 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
7501 bevent
->button
, bevent
->time
);
7503 /* unref object so it'll free itself when popped down */
7504 g_object_ref_sink(menu
);
7505 g_object_unref(menu
);
7507 return (TRUE
/* eat event */);
7510 return (FALSE
/* propagate */);
7514 icon_size_map(int icon_size
)
7516 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
7517 icon_size
> GTK_ICON_SIZE_DIALOG
)
7518 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
7524 create_button(char *name
, char *stockid
, int size
)
7526 GtkWidget
*button
, *image
;
7530 rcstring
= g_strdup_printf(
7531 "style \"%s-style\"\n"
7533 " GtkWidget::focus-padding = 0\n"
7534 " GtkWidget::focus-line-width = 0\n"
7538 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
7539 gtk_rc_parse_string(rcstring
);
7541 button
= gtk_button_new();
7542 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
7543 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
7545 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
7546 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7547 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
7548 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
7549 gtk_widget_set_name(button
, name
);
7550 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
7556 button_set_stockid(GtkWidget
*button
, char *stockid
)
7560 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
7561 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7562 gtk_button_set_image(GTK_BUTTON(button
), image
);
7571 char file
[PATH_MAX
];
7574 vbox
= gtk_vbox_new(FALSE
, 0);
7575 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
7576 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
7577 gtk_notebook_set_tab_hborder(notebook
, 0);
7578 gtk_notebook_set_tab_vborder(notebook
, 0);
7579 gtk_notebook_set_scrollable(notebook
, TRUE
);
7580 notebook_tab_set_visibility(notebook
);
7581 gtk_notebook_set_show_border(notebook
, FALSE
);
7582 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
7584 abtn
= gtk_button_new();
7585 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
7586 gtk_widget_set_size_request(arrow
, -1, -1);
7587 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
7588 gtk_widget_set_size_request(abtn
, -1, 20);
7590 #if GTK_CHECK_VERSION(2, 20, 0)
7591 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
7593 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
7594 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
7595 gtk_widget_set_size_request(vbox
, -1, -1);
7597 g_object_connect(G_OBJECT(notebook
),
7598 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
7600 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
7601 G_CALLBACK(arrow_cb
), NULL
);
7603 main_window
= create_window();
7604 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
7605 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
7608 for (i
= 0; i
< LENGTH(icons
); i
++) {
7609 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
7610 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
7611 l
= g_list_append(l
, pb
);
7613 gtk_window_set_default_icon_list(l
);
7615 gtk_widget_show_all(abtn
);
7616 gtk_widget_show_all(main_window
);
7620 set_hook(void **hook
, char *name
)
7623 errx(1, "set_hook");
7625 if (*hook
== NULL
) {
7626 *hook
= dlsym(RTLD_NEXT
, name
);
7628 errx(1, "can't hook %s", name
);
7632 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7634 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
7636 g_return_val_if_fail(cookie1
, FALSE
);
7637 g_return_val_if_fail(cookie2
, FALSE
);
7639 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
7640 !strcmp (cookie1
->value
, cookie2
->value
) &&
7641 !strcmp (cookie1
->path
, cookie2
->path
) &&
7642 !strcmp (cookie1
->domain
, cookie2
->domain
));
7646 transfer_cookies(void)
7649 SoupCookie
*sc
, *pc
;
7651 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7653 for (;cf
; cf
= cf
->next
) {
7655 sc
= soup_cookie_copy(pc
);
7656 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
7659 soup_cookies_free(cf
);
7663 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
7668 print_cookie("soup_cookie_jar_delete_cookie", c
);
7670 if (cookies_enabled
== 0)
7673 if (jar
== NULL
|| c
== NULL
)
7676 /* find and remove from persistent jar */
7677 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7679 for (;cf
; cf
= cf
->next
) {
7681 if (soup_cookie_equal(ci
, c
)) {
7682 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
7687 soup_cookies_free(cf
);
7689 /* delete from session jar */
7690 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
7694 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
7696 struct domain
*d
= NULL
;
7700 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
7701 jar
, p_cookiejar
, s_cookiejar
);
7703 if (cookies_enabled
== 0)
7706 /* see if we are up and running */
7707 if (p_cookiejar
== NULL
) {
7708 _soup_cookie_jar_add_cookie(jar
, cookie
);
7711 /* disallow p_cookiejar adds, shouldn't happen */
7712 if (jar
== p_cookiejar
)
7716 if (jar
== NULL
|| cookie
== NULL
)
7719 if (enable_cookie_whitelist
&&
7720 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
7722 DNPRINTF(XT_D_COOKIE
,
7723 "soup_cookie_jar_add_cookie: reject %s\n",
7725 if (save_rejected_cookies
) {
7726 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
7727 show_oops_s("can't open reject cookie file");
7730 fseek(r_cookie_f
, 0, SEEK_END
);
7731 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7732 cookie
->http_only
? "#HttpOnly_" : "",
7734 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
7736 cookie
->secure
? "TRUE" : "FALSE",
7738 (gulong
)soup_date_to_time_t(cookie
->expires
) :
7745 if (!allow_volatile_cookies
)
7749 if (cookie
->expires
== NULL
&& session_timeout
) {
7750 soup_cookie_set_expires(cookie
,
7751 soup_date_new_from_now(session_timeout
));
7752 print_cookie("modified add cookie", cookie
);
7755 /* see if we are white listed for persistence */
7756 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
7757 /* add to persistent jar */
7758 c
= soup_cookie_copy(cookie
);
7759 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
7760 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
7763 /* add to session jar */
7764 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
7765 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
7771 char file
[PATH_MAX
];
7773 set_hook((void *)&_soup_cookie_jar_add_cookie
,
7774 "soup_cookie_jar_add_cookie");
7775 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
7776 "soup_cookie_jar_delete_cookie");
7778 if (cookies_enabled
== 0)
7782 * the following code is intricate due to overriding several libsoup
7784 * do not alter order of these operations.
7787 /* rejected cookies */
7788 if (save_rejected_cookies
)
7789 snprintf(rc_fname
, sizeof file
, "%s/rejected.txt", work_dir
);
7791 /* persistent cookies */
7792 snprintf(file
, sizeof file
, "%s/cookies.txt", work_dir
);
7793 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
7795 /* session cookies */
7796 s_cookiejar
= soup_cookie_jar_new();
7797 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
7798 cookie_policy
, (void *)NULL
);
7801 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
7805 setup_proxy(char *uri
)
7808 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
7809 soup_uri_free(proxy_uri
);
7813 if (http_proxy
!= uri
) {
7820 http_proxy
= g_strdup(uri
);
7821 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
7822 proxy_uri
= soup_uri_new(http_proxy
);
7823 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
7828 send_cmd_to_socket(char *cmd
)
7831 struct sockaddr_un sa
;
7833 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7834 warnx("%s: socket", __func__
);
7838 sa
.sun_family
= AF_UNIX
;
7839 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7840 work_dir
, XT_SOCKET_FILE
);
7843 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7844 warnx("%s: connect", __func__
);
7848 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
7849 warnx("%s: send", __func__
);
7860 socket_watcher(gpointer data
, gint fd
, GdkInputCondition cond
)
7863 char str
[XT_MAX_URL_LENGTH
];
7864 socklen_t t
= sizeof(struct sockaddr_un
);
7865 struct sockaddr_un sa
;
7871 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
7876 if (getpeereid(s
, &uid
, &gid
) == -1) {
7880 if (uid
!= getuid() || gid
!= getgid()) {
7881 warnx("unauthorized user");
7887 warnx("not a valid user");
7891 n
= recv(s
, str
, sizeof(str
), 0);
7895 tt
= TAILQ_LAST(&tabs
, tab_list
);
7896 cmd_execute(tt
, str
);
7903 struct sockaddr_un sa
;
7905 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7906 warn("is_running: socket");
7910 sa
.sun_family
= AF_UNIX
;
7911 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7912 work_dir
, XT_SOCKET_FILE
);
7915 /* connect to see if there is a listener */
7916 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
7917 rv
= 0; /* not running */
7919 rv
= 1; /* already running */
7930 struct sockaddr_un sa
;
7932 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7933 warn("build_socket: socket");
7937 sa
.sun_family
= AF_UNIX
;
7938 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7939 work_dir
, XT_SOCKET_FILE
);
7942 /* connect to see if there is a listener */
7943 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7944 /* no listener so we will */
7945 unlink(sa
.sun_path
);
7947 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7948 warn("build_socket: bind");
7952 if (listen(s
, 1) == -1) {
7953 warn("build_socket: listen");
7966 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
7967 GtkTreeIter
*iter
, struct tab
*t
)
7971 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
7978 completion_add_uri(const gchar
*uri
)
7982 /* add uri to list_store */
7983 gtk_list_store_append(completion_model
, &iter
);
7984 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
7988 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
7989 GtkTreeIter
*iter
, gpointer user_data
)
7992 gboolean match
= FALSE
;
7994 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8000 match
= match_uri(value
, key
);
8007 completion_add(struct tab
*t
)
8009 /* enable completion for tab */
8010 t
->completion
= gtk_entry_completion_new();
8011 gtk_entry_completion_set_text_column(t
->completion
, 0);
8012 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8013 gtk_entry_completion_set_model(t
->completion
,
8014 GTK_TREE_MODEL(completion_model
));
8015 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8017 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8018 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8019 G_CALLBACK(completion_select_cb
), t
);
8026 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8031 main(int argc
, char *argv
[])
8034 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8035 char conf
[PATH_MAX
] = { '\0' };
8036 char file
[PATH_MAX
];
8037 char *env_proxy
= NULL
;
8040 struct sigaction sact
;
8041 gchar
*priority
= g_strdup("NORMAL");
8045 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8047 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8056 errx(0 , "Version: %s", version
);
8059 strlcpy(conf
, optarg
, sizeof(conf
));
8062 strlcpy(named_session
, optarg
, sizeof(named_session
));
8083 RB_INIT(&downloads
);
8087 TAILQ_INIT(&aliases
);
8093 gnutls_global_init();
8095 /* generate session keys for xtp pages */
8096 generate_xtp_session_key(&dl_session_key
);
8097 generate_xtp_session_key(&hl_session_key
);
8098 generate_xtp_session_key(&cl_session_key
);
8099 generate_xtp_session_key(&fl_session_key
);
8102 gtk_init(&argc
, &argv
);
8103 if (!g_thread_supported())
8104 g_thread_init(NULL
);
8107 bzero(&sact
, sizeof(sact
));
8108 sigemptyset(&sact
.sa_mask
);
8109 sact
.sa_handler
= sigchild
;
8110 sact
.sa_flags
= SA_NOCLDSTOP
;
8111 sigaction(SIGCHLD
, &sact
, NULL
);
8113 /* set download dir */
8114 pwd
= getpwuid(getuid());
8116 errx(1, "invalid user %d", getuid());
8117 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8119 /* set default string settings */
8120 home
= g_strdup("http://www.peereboom.us");
8121 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8122 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
8123 strlcpy(runtime_settings
,"runtime", sizeof runtime_settings
);
8125 /* read config file */
8126 if (strlen(conf
) == 0)
8127 snprintf(conf
, sizeof conf
, "%s/.%s",
8128 pwd
->pw_dir
, XT_CONF_FILE
);
8129 config_parse(conf
, 0);
8131 /* working directory */
8132 if (strlen(work_dir
) == 0)
8133 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
8134 pwd
->pw_dir
, XT_DIR
);
8135 if (stat(work_dir
, &sb
)) {
8136 if (mkdir(work_dir
, S_IRWXU
) == -1)
8137 err(1, "mkdir work_dir");
8138 if (stat(work_dir
, &sb
))
8139 err(1, "stat work_dir");
8141 if (S_ISDIR(sb
.st_mode
) == 0)
8142 errx(1, "%s not a dir", work_dir
);
8143 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8144 warnx("fixing invalid permissions on %s", work_dir
);
8145 if (chmod(work_dir
, S_IRWXU
) == -1)
8149 /* icon cache dir */
8150 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
8151 if (stat(cache_dir
, &sb
)) {
8152 if (mkdir(cache_dir
, S_IRWXU
) == -1)
8153 err(1, "mkdir cache_dir");
8154 if (stat(cache_dir
, &sb
))
8155 err(1, "stat cache_dir");
8157 if (S_ISDIR(sb
.st_mode
) == 0)
8158 errx(1, "%s not a dir", cache_dir
);
8159 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8160 warnx("fixing invalid permissions on %s", cache_dir
);
8161 if (chmod(cache_dir
, S_IRWXU
) == -1)
8166 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
8167 if (stat(certs_dir
, &sb
)) {
8168 if (mkdir(certs_dir
, S_IRWXU
) == -1)
8169 err(1, "mkdir certs_dir");
8170 if (stat(certs_dir
, &sb
))
8171 err(1, "stat certs_dir");
8173 if (S_ISDIR(sb
.st_mode
) == 0)
8174 errx(1, "%s not a dir", certs_dir
);
8175 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8176 warnx("fixing invalid permissions on %s", certs_dir
);
8177 if (chmod(certs_dir
, S_IRWXU
) == -1)
8182 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
8183 work_dir
, XT_SESSIONS_DIR
);
8184 if (stat(sessions_dir
, &sb
)) {
8185 if (mkdir(sessions_dir
, S_IRWXU
) == -1)
8186 err(1, "mkdir sessions_dir");
8187 if (stat(sessions_dir
, &sb
))
8188 err(1, "stat sessions_dir");
8190 if (S_ISDIR(sb
.st_mode
) == 0)
8191 errx(1, "%s not a dir", sessions_dir
);
8192 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8193 warnx("fixing invalid permissions on %s", sessions_dir
);
8194 if (chmod(sessions_dir
, S_IRWXU
) == -1)
8197 /* runtime settings that can override config file */
8198 if (runtime_settings
[0] != '\0')
8199 config_parse(runtime_settings
, 1);
8202 if (!strcmp(download_dir
, pwd
->pw_dir
))
8203 strlcat(download_dir
, "/downloads", sizeof download_dir
);
8204 if (stat(download_dir
, &sb
)) {
8205 if (mkdir(download_dir
, S_IRWXU
) == -1)
8206 err(1, "mkdir download_dir");
8207 if (stat(download_dir
, &sb
))
8208 err(1, "stat download_dir");
8210 if (S_ISDIR(sb
.st_mode
) == 0)
8211 errx(1, "%s not a dir", download_dir
);
8212 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8213 warnx("fixing invalid permissions on %s", download_dir
);
8214 if (chmod(download_dir
, S_IRWXU
) == -1)
8218 /* favorites file */
8219 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
8220 if (stat(file
, &sb
)) {
8221 warnx("favorites file doesn't exist, creating it");
8222 if ((f
= fopen(file
, "w")) == NULL
)
8223 err(1, "favorites");
8228 session
= webkit_get_default_session();
8229 /* XXX ssl-priority property not quite available yet */
8230 if (is_g_object_setting(G_OBJECT(session
), "ssl-priority"))
8231 g_object_set(G_OBJECT(session
), "ssl-priority", priority
,
8234 warnx("session does not have \"ssl-priority\" property");
8239 if (stat(ssl_ca_file
, &sb
)) {
8240 warn("no CA file: %s", ssl_ca_file
);
8241 g_free(ssl_ca_file
);
8244 g_object_set(session
,
8245 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
8246 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8251 env_proxy
= getenv("http_proxy");
8253 setup_proxy(env_proxy
);
8255 setup_proxy(http_proxy
);
8258 send_cmd_to_socket(argv
[0]);
8262 /* set some connection parameters */
8263 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8264 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8267 /* see if there is already an xxxterm running */
8268 if (single_instance
&& is_running()) {
8270 warnx("already running");
8276 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8277 send_cmd_to_socket(cmd
);
8287 /* uri completion */
8288 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8293 if (save_global_history
)
8294 restore_global_history();
8296 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
8297 restore_saved_tabs();
8299 a
.s
= named_session
;
8300 a
.i
= XT_SES_DONOTHING
;
8301 open_tabs(NULL
, &a
);
8305 create_new_tab(argv
[0], NULL
, focus
);
8312 if (TAILQ_EMPTY(&tabs
))
8313 create_new_tab(home
, NULL
, 1);
8316 if ((s
= build_socket()) != -1)
8317 gdk_input_add(s
, GDK_INPUT_READ
, socket_watcher
, NULL
);
8321 gnutls_global_deinit();