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_INVALID 0 /* invalid */
334 #define XT_XTP_DL 1 /* downloads */
335 #define XT_XTP_HL 2 /* history */
336 #define XT_XTP_CL 3 /* cookies */
337 #define XT_XTP_FL 4 /* favorites */
339 /* XTP download actions */
340 #define XT_XTP_DL_LIST 1
341 #define XT_XTP_DL_CANCEL 2
342 #define XT_XTP_DL_REMOVE 3
344 /* XTP history actions */
345 #define XT_XTP_HL_LIST 1
346 #define XT_XTP_HL_REMOVE 2
348 /* XTP cookie actions */
349 #define XT_XTP_CL_LIST 1
350 #define XT_XTP_CL_REMOVE 2
352 /* XTP cookie actions */
353 #define XT_XTP_FL_LIST 1
354 #define XT_XTP_FL_REMOVE 2
356 /* xtp tab meanings - identifies which tabs have xtp pages in */
357 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
358 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
359 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
360 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
361 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
364 #define XT_MOVE_INVALID (0)
365 #define XT_MOVE_DOWN (1)
366 #define XT_MOVE_UP (2)
367 #define XT_MOVE_BOTTOM (3)
368 #define XT_MOVE_TOP (4)
369 #define XT_MOVE_PAGEDOWN (5)
370 #define XT_MOVE_PAGEUP (6)
371 #define XT_MOVE_HALFDOWN (7)
372 #define XT_MOVE_HALFUP (8)
373 #define XT_MOVE_LEFT (9)
374 #define XT_MOVE_FARLEFT (10)
375 #define XT_MOVE_RIGHT (11)
376 #define XT_MOVE_FARRIGHT (12)
378 #define XT_TAB_LAST (-4)
379 #define XT_TAB_FIRST (-3)
380 #define XT_TAB_PREV (-2)
381 #define XT_TAB_NEXT (-1)
382 #define XT_TAB_INVALID (0)
383 #define XT_TAB_NEW (1)
384 #define XT_TAB_DELETE (2)
385 #define XT_TAB_DELQUIT (3)
386 #define XT_TAB_OPEN (4)
387 #define XT_TAB_UNDO_CLOSE (5)
388 #define XT_TAB_SHOW (6)
389 #define XT_TAB_HIDE (7)
391 #define XT_NAV_INVALID (0)
392 #define XT_NAV_BACK (1)
393 #define XT_NAV_FORWARD (2)
394 #define XT_NAV_RELOAD (3)
395 #define XT_NAV_RELOAD_CACHE (4)
397 #define XT_FOCUS_INVALID (0)
398 #define XT_FOCUS_URI (1)
399 #define XT_FOCUS_SEARCH (2)
401 #define XT_SEARCH_INVALID (0)
402 #define XT_SEARCH_NEXT (1)
403 #define XT_SEARCH_PREV (2)
405 #define XT_PASTE_CURRENT_TAB (0)
406 #define XT_PASTE_NEW_TAB (1)
408 #define XT_FONT_SET (0)
410 #define XT_URL_SHOW (1)
411 #define XT_URL_HIDE (2)
413 #define XT_STATUSBAR_SHOW (1)
414 #define XT_STATUSBAR_HIDE (2)
416 #define XT_WL_TOGGLE (1<<0)
417 #define XT_WL_ENABLE (1<<1)
418 #define XT_WL_DISABLE (1<<2)
419 #define XT_WL_FQDN (1<<3) /* default */
420 #define XT_WL_TOPLEVEL (1<<4)
421 #define XT_WL_PERSISTENT (1<<5)
422 #define XT_WL_SESSION (1<<6)
424 #define XT_SHOW (1<<7)
425 #define XT_DELETE (1<<8)
426 #define XT_SAVE (1<<9)
427 #define XT_OPEN (1<<10)
429 #define XT_CMD_OPEN (0)
430 #define XT_CMD_OPEN_CURRENT (1)
431 #define XT_CMD_TABNEW (2)
432 #define XT_CMD_TABNEW_CURRENT (3)
434 #define XT_STATUS_NOTHING (0)
435 #define XT_STATUS_LINK (1)
436 #define XT_STATUS_URI (2)
437 #define XT_STATUS_LOADING (3)
439 #define XT_SES_DONOTHING (0)
440 #define XT_SES_CLOSETABS (1)
442 #define XT_BM_NORMAL (0)
443 #define XT_BM_WHITELIST (1)
444 #define XT_BM_KIOSK (2)
452 TAILQ_ENTRY(mime_type
) entry
;
454 TAILQ_HEAD(mime_type_list
, mime_type
);
460 TAILQ_ENTRY(alias
) entry
;
462 TAILQ_HEAD(alias_list
, alias
);
464 /* settings that require restart */
465 int tabless
= 0; /* allow only 1 tab */
466 int enable_socket
= 0;
467 int single_instance
= 0; /* only allow one xxxterm to run */
468 int fancy_bar
= 1; /* fancy toolbar */
469 int browser_mode
= XT_BM_NORMAL
;
471 /* runtime settings */
472 int show_tabs
= 1; /* show tabs on notebook */
473 int show_url
= 1; /* show url toolbar on notebook */
474 int show_statusbar
= 0; /* vimperator style status bar */
475 int ctrl_click_focus
= 0; /* ctrl click gets focus */
476 int cookies_enabled
= 1; /* enable cookies */
477 int read_only_cookies
= 0; /* enable to not write cookies */
478 int enable_scripts
= 1;
479 int enable_plugins
= 0;
480 int default_font_size
= 12;
481 gfloat default_zoom_level
= 1.0;
482 int window_height
= 768;
483 int window_width
= 1024;
484 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
485 unsigned refresh_interval
= 10; /* download refresh interval */
486 int enable_cookie_whitelist
= 0;
487 int enable_js_whitelist
= 0;
488 time_t session_timeout
= 3600; /* cookie session timeout */
489 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
490 char *ssl_ca_file
= NULL
;
491 char *resource_dir
= NULL
;
492 gboolean ssl_strict_certs
= FALSE
;
493 int append_next
= 1; /* append tab after current tab */
495 char *search_string
= NULL
;
496 char *http_proxy
= NULL
;
497 char download_dir
[PATH_MAX
];
498 char runtime_settings
[PATH_MAX
]; /* override of settings */
499 int allow_volatile_cookies
= 0;
500 int save_global_history
= 0; /* save global history to disk */
501 char *user_agent
= NULL
;
502 int save_rejected_cookies
= 0;
503 time_t session_autosave
= 0;
504 int guess_search
= 0;
505 int dns_prefetch
= FALSE
;
506 gint max_connections
= 25;
507 gint max_host_connections
= 5;
511 int set_download_dir(struct settings
*, char *);
512 int set_work_dir(struct settings
*, char *);
513 int set_runtime_dir(struct settings
*, char *);
514 int set_browser_mode(struct settings
*, char *);
515 int set_cookie_policy(struct settings
*, char *);
516 int add_alias(struct settings
*, char *);
517 int add_mime_type(struct settings
*, char *);
518 int add_cookie_wl(struct settings
*, char *);
519 int add_js_wl(struct settings
*, char *);
520 int add_kb(struct settings
*, char *);
521 void button_set_stockid(GtkWidget
*, char *);
522 GtkWidget
* create_button(char *, char *, int);
524 char *get_browser_mode(struct settings
*);
525 char *get_cookie_policy(struct settings
*);
527 char *get_download_dir(struct settings
*);
528 char *get_work_dir(struct settings
*);
529 char *get_runtime_dir(struct settings
*);
531 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
532 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
533 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
534 void walk_kb(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
535 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
538 int (*set
)(struct settings
*, char *);
539 char *(*get
)(struct settings
*);
540 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
543 struct special s_browser_mode
= {
549 struct special s_cookie
= {
555 struct special s_alias
= {
561 struct special s_mime
= {
567 struct special s_js
= {
573 struct special s_kb
= {
579 struct special s_cookie_wl
= {
585 struct special s_download_dir
= {
591 struct special s_work_dir
= {
600 #define XT_S_INVALID (0)
603 #define XT_S_FLOAT (3)
605 #define XT_SF_RESTART (1<<0)
606 #define XT_SF_RUNTIME (1<<1)
612 { "append_next", XT_S_INT
, 0, &append_next
, NULL
, NULL
},
613 { "allow_volatile_cookies", XT_S_INT
, 0, &allow_volatile_cookies
, NULL
, NULL
},
614 { "browser_mode", XT_S_INT
, 0, NULL
, NULL
,&s_browser_mode
},
615 { "cookie_policy", XT_S_INT
, 0, NULL
, NULL
,&s_cookie
},
616 { "cookies_enabled", XT_S_INT
, 0, &cookies_enabled
, NULL
, NULL
},
617 { "ctrl_click_focus", XT_S_INT
, 0, &ctrl_click_focus
, NULL
, NULL
},
618 { "default_font_size", XT_S_INT
, 0, &default_font_size
, NULL
, NULL
},
619 { "default_zoom_level", XT_S_FLOAT
, 0, NULL
, NULL
, NULL
, &default_zoom_level
},
620 { "download_dir", XT_S_STR
, 0, NULL
, NULL
,&s_download_dir
},
621 { "enable_cookie_whitelist", XT_S_INT
, 0, &enable_cookie_whitelist
, NULL
, NULL
},
622 { "enable_js_whitelist", XT_S_INT
, 0, &enable_js_whitelist
, NULL
, NULL
},
623 { "enable_plugins", XT_S_INT
, 0, &enable_plugins
, NULL
, NULL
},
624 { "enable_scripts", XT_S_INT
, 0, &enable_scripts
, NULL
, NULL
},
625 { "enable_socket", XT_S_INT
, XT_SF_RESTART
,&enable_socket
, NULL
, NULL
},
626 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
,&fancy_bar
, NULL
, NULL
},
627 { "home", XT_S_STR
, 0, NULL
, &home
, NULL
},
628 { "http_proxy", XT_S_STR
, 0, NULL
, &http_proxy
, NULL
},
629 { "icon_size", XT_S_INT
, 0, &icon_size
, NULL
, NULL
},
630 { "max_connections", XT_S_INT
, XT_SF_RESTART
,&max_connections
, NULL
, NULL
},
631 { "max_host_connections", XT_S_INT
, XT_SF_RESTART
,&max_host_connections
, NULL
, NULL
},
632 { "read_only_cookies", XT_S_INT
, 0, &read_only_cookies
, NULL
, NULL
},
633 { "refresh_interval", XT_S_INT
, 0, &refresh_interval
, NULL
, NULL
},
634 { "resource_dir", XT_S_STR
, 0, NULL
, &resource_dir
, NULL
},
635 { "search_string", XT_S_STR
, 0, NULL
, &search_string
, NULL
},
636 { "save_global_history", XT_S_INT
, XT_SF_RESTART
,&save_global_history
, NULL
, NULL
},
637 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
,&save_rejected_cookies
, NULL
, NULL
},
638 { "session_timeout", XT_S_INT
, 0, &session_timeout
, NULL
, NULL
},
639 { "session_autosave", XT_S_INT
, 0, &session_autosave
, NULL
, NULL
},
640 { "single_instance", XT_S_INT
, XT_SF_RESTART
,&single_instance
, NULL
, NULL
},
641 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
642 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
643 { "guess_search", XT_S_INT
, 0, &guess_search
, NULL
, NULL
},
644 { "show_statusbar", XT_S_INT
, 0, &show_statusbar
, NULL
, NULL
},
645 { "ssl_ca_file", XT_S_STR
, 0, NULL
, &ssl_ca_file
, NULL
},
646 { "ssl_strict_certs", XT_S_INT
, 0, &ssl_strict_certs
, NULL
, NULL
},
647 { "user_agent", XT_S_STR
, 0, NULL
, &user_agent
, NULL
},
648 { "window_height", XT_S_INT
, 0, &window_height
, NULL
, NULL
},
649 { "window_width", XT_S_INT
, 0, &window_width
, NULL
, NULL
},
650 { "work_dir", XT_S_STR
, 0, NULL
, NULL
,&s_work_dir
},
652 /* runtime settings */
653 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
654 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
655 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
656 { "keybinding", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_kb
},
657 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
660 int about(struct tab
*, struct karg
*);
661 int blank(struct tab
*, struct karg
*);
662 int cookie_show_wl(struct tab
*, struct karg
*);
663 int js_show_wl(struct tab
*, struct karg
*);
664 int help(struct tab
*, struct karg
*);
665 int set(struct tab
*, struct karg
*);
666 int stats(struct tab
*, struct karg
*);
667 int marco(struct tab
*, struct karg
*);
668 const char * marco_message(int *);
669 int xtp_page_cl(struct tab
*, struct karg
*);
670 int xtp_page_dl(struct tab
*, struct karg
*);
671 int xtp_page_fl(struct tab
*, struct karg
*);
672 int xtp_page_hl(struct tab
*, struct karg
*);
674 #define XT_URI_ABOUT ("about:")
675 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
676 #define XT_URI_ABOUT_ABOUT ("about")
677 #define XT_URI_ABOUT_BLANK ("blank")
678 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
679 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
680 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
681 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
682 #define XT_URI_ABOUT_FAVORITES ("favorites")
683 #define XT_URI_ABOUT_HELP ("help")
684 #define XT_URI_ABOUT_HISTORY ("history")
685 #define XT_URI_ABOUT_JSWL ("jswl")
686 #define XT_URI_ABOUT_SET ("set")
687 #define XT_URI_ABOUT_STATS ("stats")
688 #define XT_URI_ABOUT_MARCO ("marco")
692 int (*func
)(struct tab
*, struct karg
*);
694 { XT_URI_ABOUT_ABOUT
, about
},
695 { XT_URI_ABOUT_BLANK
, blank
},
696 { XT_URI_ABOUT_COOKIEWL
, cookie_show_wl
},
697 { XT_URI_ABOUT_COOKIEJAR
, xtp_page_cl
},
698 { XT_URI_ABOUT_DOWNLOADS
, xtp_page_dl
},
699 { XT_URI_ABOUT_FAVORITES
, xtp_page_fl
},
700 { XT_URI_ABOUT_HELP
, help
},
701 { XT_URI_ABOUT_HISTORY
, xtp_page_hl
},
702 { XT_URI_ABOUT_JSWL
, js_show_wl
},
703 { XT_URI_ABOUT_SET
, set
},
704 { XT_URI_ABOUT_STATS
, stats
},
705 { XT_URI_ABOUT_MARCO
, marco
},
709 extern char *__progname
;
712 GtkWidget
*main_window
;
713 GtkNotebook
*notebook
;
714 GtkWidget
*arrow
, *abtn
;
715 struct tab_list tabs
;
716 struct history_list hl
;
717 struct download_list downloads
;
718 struct domain_list c_wl
;
719 struct domain_list js_wl
;
720 struct undo_tailq undos
;
721 struct keybinding_list kbl
;
723 int updating_dl_tabs
= 0;
724 int updating_hl_tabs
= 0;
725 int updating_cl_tabs
= 0;
726 int updating_fl_tabs
= 0;
728 uint64_t blocked_cookies
= 0;
729 char named_session
[PATH_MAX
];
730 void update_favicon(struct tab
*);
731 int icon_size_map(int);
733 GtkListStore
*completion_model
;
734 void completion_add(struct tab
*);
735 void completion_add_uri(const gchar
*);
740 int saved_errno
, status
;
745 while ((pid
= waitpid(WAIT_ANY
, &status
, WNOHANG
)) != 0) {
749 if (errno
!= ECHILD
) {
751 clog_warn("sigchild: waitpid:");
757 if (WIFEXITED(status
)) {
758 if (WEXITSTATUS(status
) != 0) {
760 clog_warnx("sigchild: child exit status: %d",
761 WEXITSTATUS(status));
766 clog_warnx("sigchild: child is terminated abnormally");
775 is_g_object_setting(GObject
*o
, char *str
)
777 guint n_props
= 0, i
;
778 GParamSpec
**proplist
;
780 if (! G_IS_OBJECT(o
))
783 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
786 for (i
=0; i
< n_props
; i
++) {
787 if (! strcmp(proplist
[i
]->name
, str
))
794 * Display a web page from a HTML string in memory, rather than from a URL
797 load_webkit_string(struct tab
*t
, const char *str
, gchar
*title
)
803 /* we set this to indicate we want to manually do navaction */
805 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
807 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, "");
808 #if GTK_CHECK_VERSION(2, 20, 0)
809 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
810 gtk_widget_hide(t
->spinner
);
814 uri
= g_strdup_printf("%s%s", XT_URI_ABOUT
, title
);
815 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
818 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[0]);
819 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
820 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
821 GTK_ENTRY_ICON_PRIMARY
, pb
);
822 gdk_pixbuf_unref(pb
);
827 set_status(struct tab
*t
, gchar
*s
, int status
)
835 case XT_STATUS_LOADING
:
836 type
= g_strdup_printf("Loading: %s", s
);
840 type
= g_strdup_printf("Link: %s", s
);
842 t
->status
= g_strdup(gtk_entry_get_text(GTK_ENTRY(t
->statusbar
)));
846 type
= g_strdup_printf("%s", s
);
848 t
->status
= g_strdup(type
);
852 t
->status
= g_strdup(s
);
854 case XT_STATUS_NOTHING
:
859 gtk_entry_set_text(GTK_ENTRY(t
->statusbar
), s
);
865 hide_oops(struct tab
*t
)
867 gtk_widget_hide(t
->oops
);
871 hide_cmd(struct tab
*t
)
873 gtk_widget_hide(t
->cmd
);
877 show_cmd(struct tab
*t
)
879 gtk_widget_hide(t
->oops
);
880 gtk_widget_show(t
->cmd
);
884 show_oops(struct tab
*t
, const char *fmt
, ...)
893 if (vasprintf(&msg
, fmt
, ap
) == -1)
894 errx(1, "show_oops failed");
897 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
898 gtk_widget_hide(t
->cmd
);
899 gtk_widget_show(t
->oops
);
902 /* XXX collapse with show_oops */
904 show_oops_s(const char *fmt
, ...)
908 struct tab
*ti
, *t
= NULL
;
913 TAILQ_FOREACH(ti
, &tabs
, entry
)
914 if (ti
->tab_id
== gtk_notebook_current_page(notebook
)) {
922 if (vasprintf(&msg
, fmt
, ap
) == -1)
923 errx(1, "show_oops_s failed");
926 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
927 gtk_widget_hide(t
->cmd
);
928 gtk_widget_show(t
->oops
);
932 get_as_string(struct settings
*s
)
943 warnx("get_as_string skip %s\n", s
->name
);
944 } else if (s
->type
== XT_S_INT
)
945 r
= g_strdup_printf("%d", *s
->ival
);
946 else if (s
->type
== XT_S_STR
)
947 r
= g_strdup(*s
->sval
);
948 else if (s
->type
== XT_S_FLOAT
)
949 r
= g_strdup_printf("%f", *s
->fval
);
951 r
= g_strdup_printf("INVALID TYPE");
957 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
962 for (i
= 0; i
< LENGTH(rs
); i
++) {
963 if (rs
[i
].s
&& rs
[i
].s
->walk
)
964 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
966 s
= get_as_string(&rs
[i
]);
967 cb(&rs
[i
], s
, cb_args
);
974 set_browser_mode(struct settings
*s
, char *val
)
976 if (!strcmp(val
, "whitelist")) {
977 browser_mode
= XT_BM_WHITELIST
;
978 allow_volatile_cookies
= 0;
979 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
981 enable_cookie_whitelist
= 1;
982 read_only_cookies
= 0;
983 save_rejected_cookies
= 0;
984 session_timeout
= 3600;
986 enable_js_whitelist
= 1;
987 } else if (!strcmp(val
, "normal")) {
988 browser_mode
= XT_BM_NORMAL
;
989 allow_volatile_cookies
= 0;
990 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
992 enable_cookie_whitelist
= 0;
993 read_only_cookies
= 0;
994 save_rejected_cookies
= 0;
995 session_timeout
= 3600;
997 enable_js_whitelist
= 0;
998 } else if (!strcmp(val
, "kiosk")) {
999 browser_mode
= XT_BM_KIOSK
;
1000 allow_volatile_cookies
= 0;
1001 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1002 cookies_enabled
= 1;
1003 enable_cookie_whitelist
= 0;
1004 read_only_cookies
= 0;
1005 save_rejected_cookies
= 0;
1006 session_timeout
= 3600;
1008 enable_js_whitelist
= 0;
1018 get_browser_mode(struct settings
*s
)
1022 if (browser_mode
== XT_BM_WHITELIST
)
1023 r
= g_strdup("whitelist");
1024 else if (browser_mode
== XT_BM_NORMAL
)
1025 r
= g_strdup("normal");
1026 else if (browser_mode
== XT_BM_KIOSK
)
1027 r
= g_strdup("kiosk");
1035 set_cookie_policy(struct settings
*s
, char *val
)
1037 if (!strcmp(val
, "no3rdparty"))
1038 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1039 else if (!strcmp(val
, "accept"))
1040 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1041 else if (!strcmp(val
, "reject"))
1042 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1050 get_cookie_policy(struct settings
*s
)
1054 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1055 r
= g_strdup("no3rdparty");
1056 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1057 r
= g_strdup("accept");
1058 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1059 r
= g_strdup("reject");
1067 get_download_dir(struct settings
*s
)
1069 if (download_dir
[0] == '\0')
1071 return (g_strdup(download_dir
));
1075 set_download_dir(struct settings
*s
, char *val
)
1078 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1079 pwd
->pw_dir
, &val
[1]);
1081 strlcpy(download_dir
, val
, sizeof download_dir
);
1088 * We use these to prevent people putting xxxt:// URLs on
1089 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1091 #define XT_XTP_SES_KEY_SZ 8
1092 #define XT_XTP_SES_KEY_HEX_FMT \
1093 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1094 char *dl_session_key
; /* downloads */
1095 char *hl_session_key
; /* history list */
1096 char *cl_session_key
; /* cookie list */
1097 char *fl_session_key
; /* favorites list */
1099 char work_dir
[PATH_MAX
];
1100 char certs_dir
[PATH_MAX
];
1101 char cache_dir
[PATH_MAX
];
1102 char sessions_dir
[PATH_MAX
];
1103 char cookie_file
[PATH_MAX
];
1104 SoupURI
*proxy_uri
= NULL
;
1105 SoupSession
*session
;
1106 SoupCookieJar
*s_cookiejar
;
1107 SoupCookieJar
*p_cookiejar
;
1108 char rc_fname
[PATH_MAX
];
1110 struct mime_type_list mtl
;
1111 struct alias_list aliases
;
1114 struct tab
*create_new_tab(char *, struct undo
*, int);
1115 void delete_tab(struct tab
*);
1116 void adjustfont_webkit(struct tab
*, int);
1117 int run_script(struct tab
*, char *);
1118 int download_rb_cmp(struct download
*, struct download
*);
1119 gboolean
cmd_execute(struct tab
*t
, char *str
);
1122 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1124 return (strcmp(h1
->uri
, h2
->uri
));
1126 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1129 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1131 return (strcmp(d1
->d
, d2
->d
));
1133 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1136 get_work_dir(struct settings
*s
)
1138 if (work_dir
[0] == '\0')
1140 return (g_strdup(work_dir
));
1144 set_work_dir(struct settings
*s
, char *val
)
1147 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1148 pwd
->pw_dir
, &val
[1]);
1150 strlcpy(work_dir
, val
, sizeof work_dir
);
1156 * generate a session key to secure xtp commands.
1157 * pass in a ptr to the key in question and it will
1158 * be modified in place.
1161 generate_xtp_session_key(char **key
)
1163 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1169 /* make a new one */
1170 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1171 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1172 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1173 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1175 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1179 * validate a xtp session key.
1183 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1185 if (strcmp(trusted
, untrusted
) != 0) {
1186 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1195 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1197 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1199 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1201 struct valid_url_types
{
1212 valid_url_type(char *url
)
1216 for (i
= 0; i
< LENGTH(vut
); i
++)
1217 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1224 print_cookie(char *msg
, SoupCookie
*c
)
1230 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1231 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1232 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1233 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1234 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1235 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1236 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1237 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1238 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1239 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1243 walk_alias(struct settings
*s
,
1244 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1249 if (s
== NULL
|| cb
== NULL
) {
1250 show_oops_s("walk_alias invalid parameters");
1254 TAILQ_FOREACH(a
, &aliases
, entry
) {
1255 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1256 cb(s
, str
, cb_args
);
1262 match_alias(char *url_in
)
1266 char *url_out
= NULL
, *search
, *enc_arg
;
1268 search
= g_strdup(url_in
);
1270 if (strsep(&arg
, " \t") == NULL
) {
1271 show_oops_s("match_alias: NULL URL");
1275 TAILQ_FOREACH(a
, &aliases
, entry
) {
1276 if (!strcmp(search
, a
->a_name
))
1281 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1284 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1285 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1288 url_out
= g_strdup(a
->a_uri
);
1296 guess_url_type(char *url_in
)
1299 char *url_out
= NULL
, *enc_search
= NULL
;
1301 url_out
= match_alias(url_in
);
1302 if (url_out
!= NULL
)
1307 * If there is no dot nor slash in the string and it isn't a
1308 * path to a local file and doesn't resolves to an IP, assume
1309 * that the user wants to search for the string.
1312 if (strchr(url_in
, '.') == NULL
&&
1313 strchr(url_in
, '/') == NULL
&&
1314 stat(url_in
, &sb
) != 0 &&
1315 gethostbyname(url_in
) == NULL
) {
1317 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1318 url_out
= g_strdup_printf(search_string
, enc_search
);
1324 /* XXX not sure about this heuristic */
1325 if (stat(url_in
, &sb
) == 0)
1326 url_out
= g_strdup_printf("file://%s", url_in
);
1328 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1330 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1336 load_uri(struct tab
*t
, gchar
*uri
)
1339 gchar
*newuri
= NULL
;
1345 /* Strip leading spaces. */
1346 while(*uri
&& isspace(*uri
))
1349 if (strlen(uri
) == 0) {
1354 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1355 for (i
= 0; i
< LENGTH(about_list
); i
++)
1356 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1357 bzero(&args
, sizeof args
);
1358 about_list
[i
].func(t
, &args
);
1361 show_oops(t
, "invalid about page");
1365 if (valid_url_type(uri
)) {
1366 newuri
= guess_url_type(uri
);
1370 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1371 webkit_web_view_load_uri(t
->wv
, uri
);
1378 get_uri(WebKitWebView
*wv
)
1380 WebKitWebFrame
*frame
;
1383 frame
= webkit_web_view_get_main_frame(wv
);
1384 uri
= webkit_web_frame_get_uri(frame
);
1386 if (uri
&& strlen(uri
) > 0)
1393 add_alias(struct settings
*s
, char *line
)
1396 struct alias
*a
= NULL
;
1398 if (s
== NULL
|| line
== NULL
) {
1399 show_oops_s("add_alias invalid parameters");
1404 a
= g_malloc(sizeof(*a
));
1406 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1407 show_oops_s("add_alias: incomplete alias definition");
1410 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1411 show_oops_s("add_alias: invalid alias definition");
1415 a
->a_name
= g_strdup(alias
);
1416 a
->a_uri
= g_strdup(l
);
1418 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1420 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1430 add_mime_type(struct settings
*s
, char *line
)
1434 struct mime_type
*m
= NULL
;
1435 int downloadfirst
= 0;
1437 /* XXX this could be smarter */
1439 if (line
== NULL
&& strlen(line
) == 0) {
1440 show_oops_s("add_mime_type invalid parameters");
1449 m
= g_malloc(sizeof(*m
));
1451 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1452 show_oops_s("add_mime_type: invalid mime_type");
1455 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1456 mime_type
[strlen(mime_type
) - 1] = '\0';
1461 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1462 show_oops_s("add_mime_type: invalid mime_type");
1466 m
->mt_type
= g_strdup(mime_type
);
1467 m
->mt_action
= g_strdup(l
);
1468 m
->mt_download
= downloadfirst
;
1470 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1471 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1473 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1483 find_mime_type(char *mime_type
)
1485 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1487 TAILQ_FOREACH(m
, &mtl
, entry
) {
1488 if (m
->mt_default
&&
1489 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1492 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1505 walk_mime_type(struct settings
*s
,
1506 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1508 struct mime_type
*m
;
1511 if (s
== NULL
|| cb
== NULL
)
1512 show_oops_s("walk_mime_type invalid parameters");
1514 TAILQ_FOREACH(m
, &mtl
, entry
) {
1515 str
= g_strdup_printf("%s%s --> %s",
1517 m
->mt_default
? "*" : "",
1519 cb(s
, str
, cb_args
);
1525 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1530 if (str
== NULL
|| wl
== NULL
)
1532 if (strlen(str
) < 2)
1535 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1537 /* treat *.moo.com the same as .moo.com */
1538 if (str
[0] == '*' && str
[1] == '.')
1540 else if (str
[0] == '.')
1545 d
= g_malloc(sizeof *d
);
1547 d
->d
= g_strdup_printf(".%s", str
);
1549 d
->d
= g_strdup(str
);
1552 if (RB_INSERT(domain_list
, wl
, d
))
1555 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1566 add_cookie_wl(struct settings
*s
, char *entry
)
1568 wl_add(entry
, &c_wl
, 1);
1573 walk_cookie_wl(struct settings
*s
,
1574 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1578 if (s
== NULL
|| cb
== NULL
) {
1579 show_oops_s("walk_cookie_wl invalid parameters");
1583 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1584 cb(s
, d
->d
, cb_args
);
1588 walk_js_wl(struct settings
*s
,
1589 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1593 if (s
== NULL
|| cb
== NULL
) {
1594 show_oops_s("walk_js_wl invalid parameters");
1598 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1599 cb(s
, d
->d
, cb_args
);
1603 add_js_wl(struct settings
*s
, char *entry
)
1605 wl_add(entry
, &js_wl
, 1 /* persistent */);
1610 wl_find(const gchar
*search
, struct domain_list
*wl
)
1613 struct domain
*d
= NULL
, dfind
;
1616 if (search
== NULL
|| wl
== NULL
)
1618 if (strlen(search
) < 2)
1621 if (search
[0] != '.')
1622 s
= g_strdup_printf(".%s", search
);
1624 s
= g_strdup(search
);
1626 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1629 d
= RB_FIND(domain_list
, wl
, &dfind
);
1643 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1649 if (s
== NULL
|| wl
== NULL
)
1652 if (!strncmp(s
, "http://", strlen("http://")))
1653 s
= &s
[strlen("http://")];
1654 else if (!strncmp(s
, "https://", strlen("https://")))
1655 s
= &s
[strlen("https://")];
1660 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1661 /* chop string at first slash */
1662 if (s
[i
] == '/' || s
[i
] == '\0') {
1665 r
= wl_find(ss
, wl
);
1674 get_toplevel_domain(char *domain
)
1681 if (strlen(domain
) < 2)
1684 s
= &domain
[strlen(domain
) - 1];
1685 while (s
!= domain
) {
1701 settings_add(char *var
, char *val
)
1708 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
1709 if (strcmp(var
, rs
[i
].name
))
1713 if (rs
[i
].s
->set(&rs
[i
], val
))
1714 errx(1, "invalid value for %s: %s", var
, val
);
1718 switch (rs
[i
].type
) {
1727 errx(1, "invalid sval for %s",
1741 errx(1, "invalid type for %s", var
);
1750 config_parse(char *filename
, int runtime
)
1753 char *line
, *cp
, *var
, *val
;
1754 size_t len
, lineno
= 0;
1756 char file
[PATH_MAX
];
1759 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
1761 if (filename
== NULL
)
1764 if (runtime
&& runtime_settings
[0] != '\0') {
1765 snprintf(file
, sizeof file
, "%s/%s",
1766 work_dir
, runtime_settings
);
1767 if (stat(file
, &sb
)) {
1768 warnx("runtime file doesn't exist, creating it");
1769 if ((f
= fopen(file
, "w")) == NULL
)
1771 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
1775 strlcpy(file
, filename
, sizeof file
);
1777 if ((config
= fopen(file
, "r")) == NULL
) {
1778 warn("config_parse: cannot open %s", filename
);
1783 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
1784 if (feof(config
) || ferror(config
))
1788 cp
+= (long)strspn(cp
, WS
);
1789 if (cp
[0] == '\0') {
1795 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
1796 errx(1, "invalid config file entry: %s", line
);
1798 cp
+= (long)strspn(cp
, WS
);
1800 if ((val
= strsep(&cp
, "\0")) == NULL
)
1803 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
1804 handled
= settings_add(var
, val
);
1806 errx(1, "invalid conf file entry: %s=%s", var
, val
);
1815 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1821 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1825 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1828 JSStringGetUTF8CString(jsref
, s
, l
);
1829 JSStringRelease(jsref
);
1835 disable_hints(struct tab
*t
)
1837 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1838 bzero(t
->hint_num
, sizeof t
->hint_num
);
1839 run_script(t
, "vimprobable_clear()");
1841 t
->hint_mode
= XT_HINT_NONE
;
1845 enable_hints(struct tab
*t
)
1847 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1848 run_script(t
, "vimprobable_show_hints()");
1850 t
->hint_mode
= XT_HINT_NONE
;
1853 #define XT_JS_OPEN ("open;")
1854 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1855 #define XT_JS_FIRE ("fire;")
1856 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1857 #define XT_JS_FOUND ("found;")
1858 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1861 run_script(struct tab
*t
, char *s
)
1863 JSGlobalContextRef ctx
;
1864 WebKitWebFrame
*frame
;
1866 JSValueRef val
, exception
;
1869 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
1870 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1872 frame
= webkit_web_view_get_main_frame(t
->wv
);
1873 ctx
= webkit_web_frame_get_global_context(frame
);
1875 str
= JSStringCreateWithUTF8CString(s
);
1876 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1877 NULL
, 0, &exception
);
1878 JSStringRelease(str
);
1880 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
1882 es
= js_ref_to_string(ctx
, exception
);
1883 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
1887 es
= js_ref_to_string(ctx
, val
);
1888 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
1890 /* handle return value right here */
1891 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
1893 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
1896 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
1897 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
1898 &es
[XT_JS_FIRE_LEN
]);
1903 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
1904 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
1915 hint(struct tab
*t
, struct karg
*args
)
1918 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
1920 if (t
->hints_on
== 0)
1929 apply_style(struct tab
*t
)
1931 g_object_set(G_OBJECT(t
->settings
),
1932 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
1936 userstyle(struct tab
*t
, struct karg
*args
)
1938 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
1942 g_object_set(G_OBJECT(t
->settings
),
1943 "user-stylesheet-uri", NULL
, (char *)NULL
);
1952 * Doesn't work fully, due to the following bug:
1953 * https://bugs.webkit.org/show_bug.cgi?id=51747
1956 restore_global_history(void)
1958 char file
[PATH_MAX
];
1964 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
1966 if ((f
= fopen(file
, "r")) == NULL
) {
1967 warnx("%s: fopen", __func__
);
1972 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1973 if (feof(f
) || ferror(f
))
1976 if ((title
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1977 if (feof(f
) || ferror(f
)) {
1979 warnx("%s: broken history file\n", __func__
);
1983 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
1984 webkit_web_history_item_new_with_data(uri
, title
);
1985 h
= g_malloc(sizeof(struct history
));
1986 h
->uri
= g_strdup(uri
);
1987 h
->title
= g_strdup(title
);
1988 RB_INSERT(history_list
, &hl
, h
);
1989 completion_add_uri(h
->uri
);
1991 warnx("%s: failed to restore history\n", __func__
);
2007 save_global_history_to_disk(struct tab
*t
)
2009 char file
[PATH_MAX
];
2013 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2015 if ((f
= fopen(file
, "w")) == NULL
) {
2016 show_oops(t
, "%s: global history file: %s",
2017 __func__
, strerror(errno
));
2021 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2022 if (h
->uri
&& h
->title
)
2023 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2032 quit(struct tab
*t
, struct karg
*args
)
2034 if (save_global_history
)
2035 save_global_history_to_disk(t
);
2043 open_tabs(struct tab
*t
, struct karg
*a
)
2045 char file
[PATH_MAX
];
2049 struct tab
*ti
, *tt
;
2054 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2055 if ((f
= fopen(file
, "r")) == NULL
)
2058 ti
= TAILQ_LAST(&tabs
, tab_list
);
2061 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2062 if (feof(f
) || ferror(f
))
2065 /* retrieve session name */
2066 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2067 strlcpy(named_session
,
2068 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2069 sizeof named_session
);
2073 if (uri
&& strlen(uri
))
2074 create_new_tab(uri
, NULL
, 1);
2080 /* close open tabs */
2081 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2083 tt
= TAILQ_FIRST(&tabs
);
2102 restore_saved_tabs(void)
2104 char file
[PATH_MAX
];
2105 int unlink_file
= 0;
2110 snprintf(file
, sizeof file
, "%s/%s",
2111 sessions_dir
, XT_RESTART_TABS_FILE
);
2112 if (stat(file
, &sb
) == -1)
2113 a
.s
= XT_SAVED_TABS_FILE
;
2116 a
.s
= XT_RESTART_TABS_FILE
;
2119 a
.i
= XT_SES_DONOTHING
;
2120 rv
= open_tabs(NULL
, &a
);
2129 save_tabs(struct tab
*t
, struct karg
*a
)
2131 char file
[PATH_MAX
];
2136 const gchar
**arr
= NULL
;
2141 snprintf(file
, sizeof file
, "%s/%s",
2142 sessions_dir
, named_session
);
2144 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2146 if ((f
= fopen(file
, "w")) == NULL
) {
2147 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2151 /* save session name */
2152 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2154 /* save tabs, in the order they are arranged in the notebook */
2155 TAILQ_FOREACH(ti
, &tabs
, entry
)
2158 arr
= g_malloc0(len
* sizeof(gchar
*));
2160 TAILQ_FOREACH(ti
, &tabs
, entry
) {
2161 if ((uri
= get_uri(ti
->wv
)) != NULL
)
2162 arr
[gtk_notebook_page_num(notebook
, ti
->vbox
)] = uri
;
2165 for (i
= 0; i
< len
; i
++)
2167 fprintf(f
, "%s\n", arr
[i
]);
2176 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2188 yank_uri(struct tab
*t
, struct karg
*args
)
2191 GtkClipboard
*clipboard
;
2193 if ((uri
= get_uri(t
->wv
)) == NULL
)
2196 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2197 gtk_clipboard_set_text(clipboard
, uri
, -1);
2208 paste_uri_cb(GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
2210 struct paste_args
*pap
;
2212 if (data
== NULL
|| text
== NULL
|| !strlen(text
))
2215 pap
= (struct paste_args
*)data
;
2218 case XT_PASTE_CURRENT_TAB
:
2219 load_uri(pap
->t
, (gchar
*)text
);
2221 case XT_PASTE_NEW_TAB
:
2222 create_new_tab((gchar
*)text
, NULL
, 1);
2230 paste_uri(struct tab
*t
, struct karg
*args
)
2232 GtkClipboard
*clipboard
;
2233 struct paste_args
*pap
;
2235 pap
= g_malloc(sizeof(struct paste_args
));
2240 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2241 gtk_clipboard_request_text(clipboard
, paste_uri_cb
, pap
);
2247 find_domain(const gchar
*s
, int add_dot
)
2250 char *r
= NULL
, *ss
= NULL
;
2255 if (!strncmp(s
, "http://", strlen("http://")))
2256 s
= &s
[strlen("http://")];
2257 else if (!strncmp(s
, "https://", strlen("https://")))
2258 s
= &s
[strlen("https://")];
2264 for (i
= 0; i
< strlen(ss
) + 1 /* yes er need this */; i
++)
2265 /* chop string at first slash */
2266 if (ss
[i
] == '/' || ss
[i
] == '\0') {
2269 r
= g_strdup_printf(".%s", ss
);
2280 toggle_cwl(struct tab
*t
, struct karg
*args
)
2284 char *dom
= NULL
, *dom_toggle
= NULL
;
2290 uri
= get_uri(t
->wv
);
2291 dom
= find_domain(uri
, 1);
2292 d
= wl_find(dom
, &c_wl
);
2299 if (args
->i
& XT_WL_TOGGLE
)
2301 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2303 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2306 if (args
->i
& XT_WL_TOPLEVEL
)
2307 dom_toggle
= get_toplevel_domain(dom
);
2312 /* enable cookies for domain */
2313 wl_add(dom_toggle
, &c_wl
, 0);
2315 /* disable cookies for domain */
2316 RB_REMOVE(domain_list
, &c_wl
, d
);
2318 webkit_web_view_reload(t
->wv
);
2325 toggle_js(struct tab
*t
, struct karg
*args
)
2330 char *dom
= NULL
, *dom_toggle
= NULL
;
2335 g_object_get(G_OBJECT(t
->settings
),
2336 "enable-scripts", &es
, (char *)NULL
);
2337 if (args
->i
& XT_WL_TOGGLE
)
2339 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2341 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2346 uri
= get_uri(t
->wv
);
2347 dom
= find_domain(uri
, 1);
2349 if (uri
== NULL
|| dom
== NULL
) {
2350 show_oops(t
, "Can't toggle domain in JavaScript white list");
2354 if (args
->i
& XT_WL_TOPLEVEL
)
2355 dom_toggle
= get_toplevel_domain(dom
);
2360 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2361 wl_add(dom_toggle
, &js_wl
, 0 /* session */);
2363 d
= wl_find(dom_toggle
, &js_wl
);
2365 RB_REMOVE(domain_list
, &js_wl
, d
);
2366 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2368 g_object_set(G_OBJECT(t
->settings
),
2369 "enable-scripts", es
, (char *)NULL
);
2370 g_object_set(G_OBJECT(t
->settings
),
2371 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2372 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2373 webkit_web_view_reload(t
->wv
);
2381 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2385 a
.i
= XT_WL_TOGGLE
| XT_WL_FQDN
;
2390 toggle_src(struct tab
*t
, struct karg
*args
)
2397 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2398 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2399 webkit_web_view_reload(t
->wv
);
2405 focus_webview(struct tab
*t
)
2410 /* only grab focus if we are visible */
2411 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2412 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2416 focus(struct tab
*t
, struct karg
*args
)
2418 if (t
== NULL
|| args
== NULL
)
2424 if (args
->i
== XT_FOCUS_URI
)
2425 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2426 else if (args
->i
== XT_FOCUS_SEARCH
)
2427 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2433 stats(struct tab
*t
, struct karg
*args
)
2435 char *stats
, *s
, line
[64 * 1024];
2436 uint64_t line_count
= 0;
2440 show_oops_s("stats invalid parameters");
2443 if (save_rejected_cookies
) {
2444 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2446 s
= fgets(line
, sizeof line
, r_cookie_f
);
2447 if (s
== NULL
|| feof(r_cookie_f
) ||
2453 snprintf(line
, sizeof line
,
2454 "<br>Cookies blocked(*) total: %llu", line_count
);
2456 show_oops(t
, "Can't open blocked cookies file: %s",
2460 stats
= g_strdup_printf(XT_DOCTYPE
2463 "<title>Statistics</title>"
2465 "<h1>Statistics</h1>"
2467 "Cookies blocked(*) this session: %llu"
2469 "<p><small><b>*</b> results vary based on settings"
2475 load_webkit_string(t
, stats
, XT_URI_ABOUT_STATS
);
2482 marco(struct tab
*t
, struct karg
*args
)
2484 char *message
, line
[64 * 1024];
2488 show_oops_s("marco invalid parameters");
2491 snprintf(line
, sizeof line
, "<br>%s", marco_message(&len
));
2493 message
= g_strdup_printf(XT_DOCTYPE
2496 "<title>Marco Sez...</title>"
2505 load_webkit_string(t
, message
, XT_URI_ABOUT_MARCO
);
2512 blank(struct tab
*t
, struct karg
*args
)
2515 show_oops_s("about invalid parameters");
2517 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2522 about(struct tab
*t
, struct karg
*args
)
2527 show_oops_s("about invalid parameters");
2529 about
= g_strdup_printf(XT_DOCTYPE
2532 "<title>About</title>"
2536 "<b>Version: %s</b><p>"
2539 "<li>Marco Peereboom <marco@peereboom.us></li>"
2540 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2541 "<li>Edd Barrett <vext01@gmail.com> </li>"
2542 "<li>Todd T. Fries <todd@fries.net> </li>"
2544 "Copyrights and licenses can be found on the XXXterm "
2545 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2551 load_webkit_string(t
, about
, XT_URI_ABOUT_ABOUT
);
2558 help(struct tab
*t
, struct karg
*args
)
2563 show_oops_s("help invalid parameters");
2568 "<title>XXXterm</title>"
2569 "<meta http-equiv=\"REFRESH\" content=\"0;"
2570 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2573 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2574 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2575 "cgi-bin/man-cgi?xxxterm</a>"
2580 load_webkit_string(t
, help
, XT_URI_ABOUT_HELP
);
2586 * update all favorite tabs apart from one. Pass NULL if
2587 * you want to update all.
2590 update_favorite_tabs(struct tab
*apart_from
)
2593 if (!updating_fl_tabs
) {
2594 updating_fl_tabs
= 1; /* stop infinite recursion */
2595 TAILQ_FOREACH(t
, &tabs
, entry
)
2596 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2597 && (t
!= apart_from
))
2598 xtp_page_fl(t
, NULL
);
2599 updating_fl_tabs
= 0;
2603 /* show a list of favorites (bookmarks) */
2605 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2607 char file
[PATH_MAX
];
2609 char *uri
= NULL
, *title
= NULL
;
2610 size_t len
, lineno
= 0;
2612 char *header
, *body
, *tmp
, *html
= NULL
;
2614 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2617 warn("%s: bad param", __func__
);
2619 /* mark tab as favorite list */
2620 t
->xtp_meaning
= XT_XTP_TAB_MEANING_FL
;
2622 /* new session key */
2623 if (!updating_fl_tabs
)
2624 generate_xtp_session_key(&fl_session_key
);
2626 /* open favorites */
2627 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
2628 if ((f
= fopen(file
, "r")) == NULL
) {
2629 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2634 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
2635 "<title>Favorites</title>\n"
2638 "<h1>Favorites</h1>\n",
2642 body
= g_strdup_printf("<div align='center'><table><tr>"
2643 "<th style='width: 4%%'>#</th><th>Link</th>"
2644 "<th style='width: 15%%'>Remove</th></tr>\n");
2647 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2648 if (feof(f
) || ferror(f
))
2656 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2657 if (feof(f
) || ferror(f
)) {
2658 show_oops(t
, "favorites file corrupt");
2664 body
= g_strdup_printf("%s<tr>"
2666 "<td><a href='%s'>%s</a></td>"
2667 "<td style='text-align: center'>"
2668 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2670 body
, i
, uri
, title
,
2671 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2683 /* if none, say so */
2686 body
= g_strdup_printf("%s<tr>"
2687 "<td colspan='3' style='text-align: center'>"
2688 "No favorites - To add one use the 'favadd' command."
2689 "</td></tr>", body
);
2700 html
= g_strdup_printf("%s%s</table></div></html>",
2702 load_webkit_string(t
, html
, XT_URI_ABOUT_FAVORITES
);
2705 update_favorite_tabs(t
);
2718 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2719 size_t cert_count
, char *title
)
2721 gnutls_datum_t cinfo
;
2722 char *tmp
, *header
, *body
, *footer
;
2725 header
= g_strdup_printf("<html><head><title>%s</title></head><body>", title
);
2726 footer
= g_strdup("</body></html>");
2727 body
= g_strdup("");
2729 for (i
= 0; i
< cert_count
; i
++) {
2730 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2735 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2736 body
, i
, cinfo
.data
);
2737 gnutls_free(cinfo
.data
);
2741 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
2745 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
2750 ca_cmd(struct tab
*t
, struct karg
*args
)
2753 int rv
= 1, certs
= 0, certs_read
;
2756 gnutls_x509_crt_t
*c
= NULL
;
2757 char *certs_buf
= NULL
, *s
;
2759 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
2760 show_oops(t
, "Can't open CA file: %s", strerror(errno
));
2764 if (fstat(fileno(f
), &sb
) == -1) {
2765 show_oops(t
, "Can't stat CA file: %s", strerror(errno
));
2769 certs_buf
= g_malloc(sb
.st_size
+ 1);
2770 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
2771 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
2774 certs_buf
[sb
.st_size
] = '\0';
2777 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
2779 s
+= strlen("BEGIN CERTIFICATE");
2782 bzero(&dt
, sizeof dt
);
2783 dt
.data
= certs_buf
;
2784 dt
.size
= sb
.st_size
;
2785 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
2786 certs_read
= gnutls_x509_crt_list_import(c
, &certs
, &dt
,
2787 GNUTLS_X509_FMT_PEM
, 0);
2788 if (certs_read
<= 0) {
2789 show_oops(t
, "No cert(s) available");
2792 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
2805 connect_socket_from_uri(const gchar
*uri
, char *domain
, size_t domain_sz
)
2808 struct addrinfo hints
, *res
= NULL
, *ai
;
2812 if (uri
&& !g_str_has_prefix(uri
, "https://"))
2815 su
= soup_uri_new(uri
);
2818 if (!SOUP_URI_VALID_FOR_HTTP(su
))
2821 snprintf(port
, sizeof port
, "%d", su
->port
);
2822 bzero(&hints
, sizeof(struct addrinfo
));
2823 hints
.ai_flags
= AI_CANONNAME
;
2824 hints
.ai_family
= AF_UNSPEC
;
2825 hints
.ai_socktype
= SOCK_STREAM
;
2827 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
2830 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
2831 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
2834 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
2837 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
2841 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
2846 strlcpy(domain
, su
->host
, domain_sz
);
2857 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
2860 gnutls_deinit(gsession
);
2862 gnutls_certificate_free_credentials(xcred
);
2868 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
2869 gnutls_certificate_credentials_t
*xc
)
2871 gnutls_certificate_credentials_t xcred
;
2872 gnutls_session_t gsession
;
2875 if (gs
== NULL
|| xc
== NULL
)
2881 gnutls_certificate_allocate_credentials(&xcred
);
2882 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
2883 GNUTLS_X509_FMT_PEM
);
2884 gnutls_init(&gsession
, GNUTLS_CLIENT
);
2885 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
2886 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
2887 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
2888 if ((rv
= gnutls_handshake(gsession
)) < 0) {
2889 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
2891 gnutls_error_is_fatal(rv
),
2892 gnutls_strerror_name(rv
));
2893 stop_tls(gsession
, xcred
);
2897 gnutls_credentials_type_t cred
;
2898 cred
= gnutls_auth_get_type(gsession
);
2899 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
2900 stop_tls(gsession
, xcred
);
2912 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
2916 const gnutls_datum_t
*cl
;
2917 gnutls_x509_crt_t
*all_certs
;
2920 if (certs
== NULL
|| cert_count
== NULL
)
2922 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
2924 cl
= gnutls_certificate_get_peers(gsession
, &len
);
2928 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
2929 for (i
= 0; i
< len
; i
++) {
2930 gnutls_x509_crt_init(&all_certs
[i
]);
2931 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
2932 GNUTLS_X509_FMT_PEM
< 0)) {
2946 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
2950 for (i
= 0; i
< cert_count
; i
++)
2951 gnutls_x509_crt_deinit(certs
[i
]);
2956 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2957 size_t cert_count
, char *domain
)
2960 char cert_buf
[64 * 1024], file
[PATH_MAX
];
2965 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
2968 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
2969 if ((f
= fopen(file
, "w")) == NULL
) {
2970 show_oops(t
, "Can't create cert file %s %s",
2971 file
, strerror(errno
));
2975 for (i
= 0; i
< cert_count
; i
++) {
2976 cert_buf_sz
= sizeof cert_buf
;
2977 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
2978 cert_buf
, &cert_buf_sz
)) {
2979 show_oops(t
, "gnutls_x509_crt_export failed");
2982 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
2983 show_oops(t
, "Can't write certs: %s", strerror(errno
));
2988 /* not the best spot but oh well */
2989 gdk_color_parse("lightblue", &color
);
2990 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
2991 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2992 gdk_color_parse(XT_COLOR_BLACK
, &color
);
2993 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
2999 load_compare_cert(struct tab
*t
, struct karg
*args
)
3002 char domain
[8182], file
[PATH_MAX
];
3003 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
3004 int s
= -1, rv
= 1, i
;
3008 gnutls_session_t gsession
;
3009 gnutls_x509_crt_t
*certs
;
3010 gnutls_certificate_credentials_t xcred
;
3015 if ((uri
= get_uri(t
->wv
)) == NULL
)
3018 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
3022 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3023 show_oops(t
, "Start TLS failed");
3028 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3029 show_oops(t
, "Can't get connection certificates");
3033 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3034 if ((f
= fopen(file
, "r")) == NULL
)
3037 for (i
= 0; i
< cert_count
; i
++) {
3038 cert_buf_sz
= sizeof cert_buf
;
3039 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3040 cert_buf
, &cert_buf_sz
)) {
3043 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3044 rv
= -1; /* critical */
3047 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3048 rv
= -1; /* critical */
3057 free_connection_certs(certs
, cert_count
);
3059 /* we close the socket first for speed */
3062 stop_tls(gsession
, xcred
);
3068 cert_cmd(struct tab
*t
, struct karg
*args
)
3074 gnutls_session_t gsession
;
3075 gnutls_x509_crt_t
*certs
;
3076 gnutls_certificate_credentials_t xcred
;
3081 if ((uri
= get_uri(t
->wv
)) == NULL
) {
3082 show_oops(t
, "Invalid URI");
3086 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
3087 show_oops(t
, "Invalid certidicate URI: %s", uri
);
3092 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3093 show_oops(t
, "Start TLS failed");
3098 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3099 show_oops(t
, "get_connection_certs failed");
3103 if (args
->i
& XT_SHOW
)
3104 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3105 else if (args
->i
& XT_SAVE
)
3106 save_certs(t
, certs
, cert_count
, domain
);
3108 free_connection_certs(certs
, cert_count
);
3110 /* we close the socket first for speed */
3113 stop_tls(gsession
, xcred
);
3119 remove_cookie(int index
)
3125 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3127 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3129 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3133 print_cookie("remove cookie", c
);
3134 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3139 soup_cookies_free(cf
);
3145 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3148 char *tmp
, *header
, *body
, *footer
;
3150 /* we set this to indicate we want to manually do navaction */
3151 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3153 header
= g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3155 footer
= g_strdup("</body></html>");
3156 body
= g_strdup("");
3159 if (args
->i
& XT_WL_PERSISTENT
) {
3161 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3163 RB_FOREACH(d
, domain_list
, wl
) {
3167 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3173 if (args
->i
& XT_WL_SESSION
) {
3175 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3177 RB_FOREACH(d
, domain_list
, wl
) {
3181 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
3186 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3191 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3193 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3199 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3201 char file
[PATH_MAX
];
3203 char *line
= NULL
, *lt
= NULL
;
3206 char *dom
= NULL
, *dom_save
= NULL
;
3212 if (t
== NULL
|| args
== NULL
)
3215 if (runtime_settings
[0] == '\0')
3218 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3219 if ((f
= fopen(file
, "r+")) == NULL
)
3222 uri
= get_uri(t
->wv
);
3223 dom
= find_domain(uri
, 1);
3224 if (uri
== NULL
|| dom
== NULL
) {
3225 show_oops(t
, "Can't add domain to %s white list",
3226 js
? "JavaScript" : "cookie");
3230 if (args
->i
& XT_WL_TOPLEVEL
) {
3232 if ((dom_save
= get_toplevel_domain(dom
)) == NULL
) {
3233 show_oops(t
, "invalid domain: %s", dom
);
3236 } else if (args
->i
& XT_WL_FQDN
) {
3242 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom_save
);
3245 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3248 if (!strcmp(line
, lt
))
3254 fprintf(f
, "%s\n", lt
);
3259 d
= wl_find(dom_save
, &js_wl
);
3261 settings_add("js_wl", dom_save
);
3262 d
= wl_find(dom_save
, &js_wl
);
3266 d
= wl_find(dom_save
, &c_wl
);
3268 settings_add("cookie_wl", dom_save
);
3269 d
= wl_find(dom_save
, &c_wl
);
3273 /* find and add to persistent jar */
3274 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3275 for (;cf
; cf
= cf
->next
) {
3277 if (!strcmp(dom_save
, ci
->domain
) ||
3278 !strcmp(&dom_save
[1], ci
->domain
)) /* deal with leading . */ {
3279 c
= soup_cookie_copy(ci
);
3280 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3283 soup_cookies_free(cf
);
3301 js_show_wl(struct tab
*t
, struct karg
*args
)
3303 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3304 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3310 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3312 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3313 wl_show(t
, args
, "Cookie White List", &c_wl
);
3319 cookie_cmd(struct tab
*t
, struct karg
*args
)
3321 if (args
->i
& XT_SHOW
)
3322 wl_show(t
, args
, "Cookie White List", &c_wl
);
3323 else if (args
->i
& XT_WL_TOGGLE
)
3324 toggle_cwl(t
, args
);
3325 else if (args
->i
& XT_SAVE
)
3326 wl_save(t
, args
, 0);
3327 else if (args
->i
& XT_DELETE
)
3328 show_oops(t
, "'cookie delete' currently unimplemented");
3334 js_cmd(struct tab
*t
, struct karg
*args
)
3336 if (args
->i
& XT_SHOW
)
3337 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3338 else if (args
->i
& XT_SAVE
)
3339 wl_save(t
, args
, 1);
3340 else if (args
->i
& XT_WL_TOGGLE
)
3342 else if (args
->i
& XT_DELETE
)
3343 show_oops(t
, "'js delete' currently unimplemented");
3349 add_favorite(struct tab
*t
, struct karg
*args
)
3351 char file
[PATH_MAX
];
3354 size_t urilen
, linelen
;
3355 const gchar
*uri
, *title
;
3360 /* don't allow adding of xtp pages to favorites */
3361 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3362 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3366 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3367 if ((f
= fopen(file
, "r+")) == NULL
) {
3368 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3372 title
= webkit_web_view_get_title(t
->wv
);
3373 uri
= get_uri(t
->wv
);
3378 if (title
== NULL
|| uri
== NULL
) {
3379 show_oops(t
, "can't add page to favorites");
3383 urilen
= strlen(uri
);
3386 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3387 if (feof(f
) || ferror(f
))
3390 if (linelen
== urilen
&& !strcmp(line
, uri
))
3397 fprintf(f
, "\n%s\n%s", title
, uri
);
3403 update_favorite_tabs(NULL
);
3409 navaction(struct tab
*t
, struct karg
*args
)
3411 WebKitWebHistoryItem
*item
;
3413 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3414 t
->tab_id
, args
->i
);
3417 if (args
->i
== XT_NAV_BACK
)
3418 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3420 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3422 return (XT_CB_PASSTHROUGH
);
3423 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
3425 return (XT_CB_PASSTHROUGH
);
3430 webkit_web_view_go_back(t
->wv
);
3432 case XT_NAV_FORWARD
:
3433 webkit_web_view_go_forward(t
->wv
);
3436 webkit_web_view_reload(t
->wv
);
3438 case XT_NAV_RELOAD_CACHE
:
3439 webkit_web_view_reload_bypass_cache(t
->wv
);
3442 return (XT_CB_PASSTHROUGH
);
3446 move(struct tab
*t
, struct karg
*args
)
3448 GtkAdjustment
*adjust
;
3449 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3454 case XT_MOVE_BOTTOM
:
3456 case XT_MOVE_PAGEDOWN
:
3457 case XT_MOVE_PAGEUP
:
3458 case XT_MOVE_HALFDOWN
:
3459 case XT_MOVE_HALFUP
:
3460 adjust
= t
->adjust_v
;
3463 adjust
= t
->adjust_h
;
3467 pos
= gtk_adjustment_get_value(adjust
);
3468 ps
= gtk_adjustment_get_page_size(adjust
);
3469 upper
= gtk_adjustment_get_upper(adjust
);
3470 lower
= gtk_adjustment_get_lower(adjust
);
3471 si
= gtk_adjustment_get_step_increment(adjust
);
3472 pi
= gtk_adjustment_get_page_increment(adjust
);
3475 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3476 "max %f si %f pi %f\n",
3477 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
3478 pos
, ps
, upper
, lower
, max
, si
, pi
);
3484 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3489 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3491 case XT_MOVE_BOTTOM
:
3492 case XT_MOVE_FARRIGHT
:
3493 gtk_adjustment_set_value(adjust
, max
);
3496 case XT_MOVE_FARLEFT
:
3497 gtk_adjustment_set_value(adjust
, lower
);
3499 case XT_MOVE_PAGEDOWN
:
3501 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3503 case XT_MOVE_PAGEUP
:
3505 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3507 case XT_MOVE_HALFDOWN
:
3509 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3511 case XT_MOVE_HALFUP
:
3513 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3516 return (XT_CB_PASSTHROUGH
);
3519 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3521 return (XT_CB_HANDLED
);
3525 url_set_visibility(void)
3529 TAILQ_FOREACH(t
, &tabs
, entry
) {
3530 if (show_url
== 0) {
3531 gtk_widget_hide(t
->toolbar
);
3534 gtk_widget_show(t
->toolbar
);
3539 notebook_tab_set_visibility(GtkNotebook
*notebook
)
3542 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3544 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3548 statusbar_set_visibility(void)
3552 TAILQ_FOREACH(t
, &tabs
, entry
) {
3553 if (show_statusbar
== 0) {
3554 gtk_widget_hide(t
->statusbar
);
3557 gtk_widget_show(t
->statusbar
);
3562 url_set(struct tab
*t
, int enable_url_entry
)
3567 show_url
= enable_url_entry
;
3569 if (enable_url_entry
) {
3570 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
3571 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3572 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
), 0);
3574 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
3575 GTK_ENTRY_ICON_PRIMARY
);
3577 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
3578 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
3579 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
3580 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
3586 fullscreen(struct tab
*t
, struct karg
*args
)
3588 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3591 return (XT_CB_PASSTHROUGH
);
3593 if (show_url
== 0) {
3601 url_set_visibility();
3602 notebook_tab_set_visibility(notebook
);
3604 return (XT_CB_HANDLED
);
3608 statusaction(struct tab
*t
, struct karg
*args
)
3610 int rv
= XT_CB_HANDLED
;
3612 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3615 return (XT_CB_PASSTHROUGH
);
3618 case XT_STATUSBAR_SHOW
:
3619 if (show_statusbar
== 0) {
3621 statusbar_set_visibility();
3624 case XT_STATUSBAR_HIDE
:
3625 if (show_statusbar
== 1) {
3627 statusbar_set_visibility();
3635 urlaction(struct tab
*t
, struct karg
*args
)
3637 int rv
= XT_CB_HANDLED
;
3639 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3642 return (XT_CB_PASSTHROUGH
);
3646 if (show_url
== 0) {
3648 url_set_visibility();
3652 if (show_url
== 1) {
3654 url_set_visibility();
3662 tabaction(struct tab
*t
, struct karg
*args
)
3664 int rv
= XT_CB_HANDLED
;
3665 char *url
= args
->s
;
3668 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
3671 return (XT_CB_PASSTHROUGH
);
3675 if (strlen(url
) > 0)
3676 create_new_tab(url
, NULL
, 1);
3678 create_new_tab(NULL
, NULL
, 1);
3683 case XT_TAB_DELQUIT
:
3684 if (gtk_notebook_get_n_pages(notebook
) > 1)
3690 if (strlen(url
) > 0)
3693 rv
= XT_CB_PASSTHROUGH
;
3699 if (show_tabs
== 0) {
3701 notebook_tab_set_visibility(notebook
);
3705 if (show_tabs
== 1) {
3707 notebook_tab_set_visibility(notebook
);
3710 case XT_TAB_UNDO_CLOSE
:
3711 if (undo_count
== 0) {
3712 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3716 u
= TAILQ_FIRST(&undos
);
3717 create_new_tab(u
->uri
, u
, 1);
3719 TAILQ_REMOVE(&undos
, u
, entry
);
3721 /* u->history is freed in create_new_tab() */
3726 rv
= XT_CB_PASSTHROUGH
;
3740 resizetab(struct tab
*t
, struct karg
*args
)
3742 if (t
== NULL
|| args
== NULL
) {
3743 show_oops_s("resizetab invalid parameters");
3744 return (XT_CB_PASSTHROUGH
);
3747 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
3748 t
->tab_id
, args
->i
);
3750 adjustfont_webkit(t
, args
->i
);
3752 return (XT_CB_HANDLED
);
3756 movetab(struct tab
*t
, struct karg
*args
)
3761 if (t
== NULL
|| args
== NULL
) {
3762 show_oops_s("movetab invalid parameters");
3763 return (XT_CB_PASSTHROUGH
);
3766 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
3767 t
->tab_id
, args
->i
);
3769 if (args
->i
== XT_TAB_INVALID
)
3770 return (XT_CB_PASSTHROUGH
);
3772 if (args
->i
< XT_TAB_INVALID
) {
3773 /* next or previous tab */
3774 if (TAILQ_EMPTY(&tabs
))
3775 return (XT_CB_PASSTHROUGH
);
3779 if (strlen(args
->s
) == 0) {
3780 /* if at the last page, loop around to the first */
3781 if (gtk_notebook_get_current_page(notebook
) ==
3782 gtk_notebook_get_n_pages(notebook
) - 1)
3783 gtk_notebook_set_current_page(notebook
, 0);
3785 gtk_notebook_next_page(notebook
);
3787 x
= atoi(args
->s
) - 1;
3789 return (XT_CB_PASSTHROUGH
);
3791 if (t
->tab_id
== x
) {
3792 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
3793 return (XT_CB_HANDLED
);
3795 TAILQ_FOREACH(tt
, &tabs
, entry
) {
3796 if (tt
->tab_id
== x
) {
3797 gtk_notebook_set_current_page(notebook
, x
);
3798 DNPRINTF(XT_D_TAB
, "movetab: going to %d\n", x
);
3806 /* if at the first page, loop around to the last */
3807 if (gtk_notebook_current_page(notebook
) == 0)
3808 gtk_notebook_set_current_page(notebook
,
3809 gtk_notebook_get_n_pages(notebook
) - 1);
3811 gtk_notebook_prev_page(notebook
);
3814 gtk_notebook_set_current_page(notebook
, 0);
3817 gtk_notebook_set_current_page(notebook
, -1);
3820 return (XT_CB_PASSTHROUGH
);
3823 return (XT_CB_HANDLED
);
3826 return (XT_CB_HANDLED
);
3830 command(struct tab
*t
, struct karg
*args
)
3832 char *s
= NULL
, *ss
= NULL
;
3836 if (t
== NULL
|| args
== NULL
) {
3837 show_oops_s("command invalid parameters");
3838 return (XT_CB_PASSTHROUGH
);
3857 case XT_CMD_OPEN_CURRENT
:
3860 case XT_CMD_TABNEW_CURRENT
:
3861 if (!s
) /* FALL THROUGH? */
3863 if ((uri
= get_uri(t
->wv
)) != NULL
) {
3864 ss
= g_strdup_printf("%s%s", s
, uri
);
3869 show_oops(t
, "command: invalid opcode %d", args
->i
);
3870 return (XT_CB_PASSTHROUGH
);
3873 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
3875 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
3876 gdk_color_parse(XT_COLOR_WHITE
, &color
);
3877 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
3879 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
3880 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
3885 return (XT_CB_HANDLED
);
3889 * Return a new string with a download row (in html)
3890 * appended. Old string is freed.
3893 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
3896 WebKitDownloadStatus stat
;
3897 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
3899 char cur_sz
[FMT_SCALED_STRSIZE
];
3900 char tot_sz
[FMT_SCALED_STRSIZE
];
3903 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
3905 /* All actions wil take this form:
3906 * xxxt://class/seskey
3908 xtp_prefix
= g_strdup_printf("%s%d/%s/",
3909 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
3911 stat
= webkit_download_get_status(dl
->download
);
3914 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
3915 status_html
= g_strdup_printf("Finished");
3916 cmd_html
= g_strdup_printf(
3917 "<a href='%s%d/%d'>Remove</a>",
3918 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3920 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
3921 /* gather size info */
3922 progress
= 100 * webkit_download_get_progress(dl
->download
);
3925 webkit_download_get_current_size(dl
->download
), cur_sz
);
3927 webkit_download_get_total_size(dl
->download
), tot_sz
);
3929 status_html
= g_strdup_printf(
3930 "<div style='width: 100%%' align='center'>"
3931 "<div class='progress-outer'>"
3932 "<div class='progress-inner' style='width: %.2f%%'>"
3933 "</div></div></div>"
3934 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3935 progress
, cur_sz
, tot_sz
, progress
);
3937 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3938 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3942 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
3943 status_html
= g_strdup_printf("Cancelled");
3944 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3945 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3947 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
3948 status_html
= g_strdup_printf("Error!");
3949 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3950 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3952 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
3953 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3954 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3955 status_html
= g_strdup_printf("Starting");
3958 show_oops(t
, "%s: unknown download status", __func__
);
3961 new_html
= g_strdup_printf(
3962 "%s\n<tr><td>%s</td><td>%s</td>"
3963 "<td style='text-align:center'>%s</td></tr>\n",
3964 html
, basename(webkit_download_get_destination_uri(dl
->download
)),
3965 status_html
, cmd_html
);
3969 g_free(status_html
);
3980 * update all download tabs apart from one. Pass NULL if
3981 * you want to update all.
3984 update_download_tabs(struct tab
*apart_from
)
3987 if (!updating_dl_tabs
) {
3988 updating_dl_tabs
= 1; /* stop infinite recursion */
3989 TAILQ_FOREACH(t
, &tabs
, entry
)
3990 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
3991 && (t
!= apart_from
))
3992 xtp_page_dl(t
, NULL
);
3993 updating_dl_tabs
= 0;
3998 * update all cookie tabs apart from one. Pass NULL if
3999 * you want to update all.
4002 update_cookie_tabs(struct tab
*apart_from
)
4005 if (!updating_cl_tabs
) {
4006 updating_cl_tabs
= 1; /* stop infinite recursion */
4007 TAILQ_FOREACH(t
, &tabs
, entry
)
4008 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
4009 && (t
!= apart_from
))
4010 xtp_page_cl(t
, NULL
);
4011 updating_cl_tabs
= 0;
4016 * update all history tabs apart from one. Pass NULL if
4017 * you want to update all.
4020 update_history_tabs(struct tab
*apart_from
)
4024 if (!updating_hl_tabs
) {
4025 updating_hl_tabs
= 1; /* stop infinite recursion */
4026 TAILQ_FOREACH(t
, &tabs
, entry
)
4027 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4028 && (t
!= apart_from
))
4029 xtp_page_hl(t
, NULL
);
4030 updating_hl_tabs
= 0;
4034 /* cookie management XTP page */
4036 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4038 char *header
, *body
, *footer
, *page
, *tmp
;
4039 int i
= 1; /* all ids start 1 */
4040 GSList
*sc
, *pc
, *pc_start
;
4042 char *type
, *table_headers
;
4043 char *last_domain
= strdup("");
4045 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4048 show_oops_s("%s invalid parameters", __func__
);
4051 /* mark this tab as cookie jar */
4052 t
->xtp_meaning
= XT_XTP_TAB_MEANING_CL
;
4054 /* Generate a new session key */
4055 if (!updating_cl_tabs
)
4056 generate_xtp_session_key(&cl_session_key
);
4059 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4060 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4061 "</head><body><h1>Cookie Jar</h1>\n");
4064 table_headers
= g_strdup_printf("<div align='center'><table><tr>"
4071 "<th>HTTP<br />only</th>"
4072 "<th>Rm</th></tr>\n");
4074 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4075 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4079 for (; sc
; sc
= sc
->next
) {
4082 if (strcmp(last_domain
, c
->domain
) != 0) {
4085 last_domain
= strdup(c
->domain
);
4089 body
= g_strdup_printf("%s</table></div>"
4091 body
, c
->domain
, table_headers
);
4095 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4096 c
->domain
, table_headers
);
4101 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4102 if (soup_cookie_equal(pc
->data
, c
)) {
4103 type
= "Session + Persistent";
4108 body
= g_strdup_printf(
4110 "<td style='width: text-align: center'>%s</td>"
4111 "<td style='width: 1px'>%s</td>"
4112 "<td style='width=70%%;overflow: visible'>"
4113 " <textarea rows='4'>%s</textarea>"
4117 "<td style='width: 1px; text-align: center'>%d</td>"
4118 "<td style='width: 1px; text-align: center'>%d</td>"
4119 "<td style='width: 1px; text-align: center'>"
4120 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4127 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4142 soup_cookies_free(sc
);
4143 soup_cookies_free(pc
);
4145 /* small message if there are none */
4147 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4148 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4152 footer
= g_strdup_printf("</table></div></body></html>");
4154 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4159 g_free(table_headers
);
4160 g_free(last_domain
);
4162 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4163 update_cookie_tabs(t
);
4171 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4173 char *header
, *body
, *footer
, *page
, *tmp
;
4175 int i
= 1; /* all ids start 1 */
4177 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4180 show_oops_s("%s invalid parameters", __func__
);
4184 /* mark this tab as history manager */
4185 t
->xtp_meaning
= XT_XTP_TAB_MEANING_HL
;
4187 /* Generate a new session key */
4188 if (!updating_hl_tabs
)
4189 generate_xtp_session_key(&hl_session_key
);
4192 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
4193 "<title>History</title>\n"
4196 "<h1>History</h1>\n",
4200 body
= g_strdup_printf("<div align='center'><table><tr>"
4201 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4203 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4205 body
= g_strdup_printf(
4207 "<td><a href='%s'>%s</a></td>"
4209 "<td style='text-align: center'>"
4210 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4211 body
, h
->uri
, h
->uri
, h
->title
,
4212 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4213 XT_XTP_HL_REMOVE
, i
);
4219 /* small message if there are none */
4222 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4223 "colspan='3'>No History</td></tr>\n", body
);
4228 footer
= g_strdup_printf("</table></div></body></html>");
4230 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4233 * update all history manager tabs as the xtp session
4234 * key has now changed. No need to update the current tab.
4235 * Already did that above.
4237 update_history_tabs(t
);
4243 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4250 * Generate a web page detailing the status of any downloads
4253 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4255 struct download
*dl
;
4256 char *header
, *body
, *footer
, *page
, *tmp
;
4260 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4263 show_oops_s("%s invalid parameters", __func__
);
4266 /* mark as a download manager tab */
4267 t
->xtp_meaning
= XT_XTP_TAB_MEANING_DL
;
4270 * Generate a new session key for next page instance.
4271 * This only happens for the top level call to xtp_page_dl()
4272 * in which case updating_dl_tabs is 0.
4274 if (!updating_dl_tabs
)
4275 generate_xtp_session_key(&dl_session_key
);
4277 /* header - with refresh so as to update */
4278 if (refresh_interval
>= 1)
4279 ref
= g_strdup_printf(
4280 "<meta http-equiv='refresh' content='%u"
4281 ";url=%s%d/%s/%d' />\n",
4291 header
= g_strdup_printf(
4293 "<title>Downloads</title>\n%s%s</head>\n",
4294 XT_DOCTYPE XT_HTML_TAG
,
4298 body
= g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4299 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4300 "</p><table><tr><th style='width: 60%%'>"
4301 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4302 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4304 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4305 body
= xtp_page_dl_row(t
, body
, dl
);
4309 /* message if no downloads in list */
4312 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4313 " style='text-align: center'>"
4314 "No downloads</td></tr>\n", body
);
4319 footer
= g_strdup_printf("</table></div></body></html>");
4321 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4325 * update all download manager tabs as the xtp session
4326 * key has now changed. No need to update the current tab.
4327 * Already did that above.
4329 update_download_tabs(t
);
4336 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4343 search(struct tab
*t
, struct karg
*args
)
4347 if (t
== NULL
|| args
== NULL
) {
4348 show_oops_s("search invalid parameters");
4351 if (t
->search_text
== NULL
) {
4352 if (global_search
== NULL
)
4353 return (XT_CB_PASSTHROUGH
);
4355 t
->search_text
= g_strdup(global_search
);
4356 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4357 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4361 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4362 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4365 case XT_SEARCH_NEXT
:
4366 d
= t
->search_forward
;
4368 case XT_SEARCH_PREV
:
4369 d
= !t
->search_forward
;
4372 return (XT_CB_PASSTHROUGH
);
4375 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4377 return (XT_CB_HANDLED
);
4380 struct settings_args
{
4386 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4389 struct settings_args
*sa
= cb_args
;
4394 if (s
->flags
& XT_SF_RUNTIME
)
4400 *sa
->body
= g_strdup_printf(
4402 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4403 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4415 set(struct tab
*t
, struct karg
*args
)
4417 char *header
, *body
, *footer
, *page
, *tmp
;
4419 struct settings_args sa
;
4421 bzero(&sa
, sizeof sa
);
4425 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4426 "\n<head><title>Settings</title>\n"
4427 "</head><body><h1>Settings</h1>\n");
4430 body
= g_strdup_printf("<div align='center'><table><tr>"
4431 "<th align='left'>Setting</th>"
4432 "<th align='left'>Value</th></tr>\n");
4434 settings_walk(print_setting
, &sa
);
4437 /* small message if there are none */
4440 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4441 "colspan='2'>No settings</td></tr>\n", body
);
4446 footer
= g_strdup_printf("</table></div></body></html>");
4448 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
4454 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4456 return (XT_CB_PASSTHROUGH
);
4460 session_save(struct tab
*t
, char *filename
)
4465 if (strlen(filename
) == 0)
4468 if (filename
[0] == '.' || filename
[0] == '/')
4472 if (save_tabs(t
, &a
))
4474 strlcpy(named_session
, filename
, sizeof named_session
);
4482 session_open(struct tab
*t
, char *filename
)
4487 if (strlen(filename
) == 0)
4490 if (filename
[0] == '.' || filename
[0] == '/')
4494 a
.i
= XT_SES_CLOSETABS
;
4495 if (open_tabs(t
, &a
))
4498 strlcpy(named_session
, filename
, sizeof named_session
);
4506 session_delete(struct tab
*t
, char *filename
)
4508 char file
[PATH_MAX
];
4511 if (strlen(filename
) == 0)
4514 if (filename
[0] == '.' || filename
[0] == '/')
4517 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
4521 if (!strcmp(filename
, named_session
))
4522 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
4523 sizeof named_session
);
4531 session_cmd(struct tab
*t
, struct karg
*args
)
4533 char *filename
= args
->s
;
4538 if (args
->i
& XT_SHOW
)
4539 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
4540 XT_SAVED_TABS_FILE
: named_session
);
4541 else if (args
->i
& XT_SAVE
) {
4542 if (session_save(t
, filename
)) {
4543 show_oops(t
, "Can't save session: %s",
4544 filename
? filename
: "INVALID");
4547 } else if (args
->i
& XT_OPEN
) {
4548 if (session_open(t
, filename
)) {
4549 show_oops(t
, "Can't open session: %s",
4550 filename
? filename
: "INVALID");
4553 } else if (args
->i
& XT_DELETE
) {
4554 if (session_delete(t
, filename
)) {
4555 show_oops(t
, "Can't delete session: %s",
4556 filename
? filename
: "INVALID");
4561 return (XT_CB_PASSTHROUGH
);
4565 * Make a hardcopy of the page
4568 print_page(struct tab
*t
, struct karg
*args
)
4570 WebKitWebFrame
*frame
;
4572 GtkPrintOperation
*op
;
4573 GtkPrintOperationAction action
;
4574 GtkPrintOperationResult print_res
;
4575 GError
*g_err
= NULL
;
4576 int marg_l
, marg_r
, marg_t
, marg_b
;
4578 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
4580 ps
= gtk_page_setup_new();
4581 op
= gtk_print_operation_new();
4582 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
4583 frame
= webkit_web_view_get_main_frame(t
->wv
);
4585 /* the default margins are too small, so we will bump them */
4586 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
4587 XT_PRINT_EXTRA_MARGIN
;
4588 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
4589 XT_PRINT_EXTRA_MARGIN
;
4590 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
4591 XT_PRINT_EXTRA_MARGIN
;
4592 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
4593 XT_PRINT_EXTRA_MARGIN
;
4596 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
4597 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
4598 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
4599 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
4601 gtk_print_operation_set_default_page_setup(op
, ps
);
4603 /* this appears to free 'op' and 'ps' */
4604 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
4606 /* check it worked */
4607 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
4608 show_oops_s("can't print: %s", g_err
->message
);
4609 g_error_free (g_err
);
4617 go_home(struct tab
*t
, struct karg
*args
)
4624 restart(struct tab
*t
, struct karg
*args
)
4628 a
.s
= XT_RESTART_TABS_FILE
;
4630 execvp(start_argv
[0], start_argv
);
4636 #define CTRL GDK_CONTROL_MASK
4637 #define MOD1 GDK_MOD1_MASK
4638 #define SHFT GDK_SHIFT_MASK
4640 /* inherent to GTK not all keys will be caught at all times */
4641 /* XXX sort key bindings */
4642 struct key_binding
{
4647 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
4649 { "cookiejar", MOD1
, 0, GDK_j
},
4650 { "downloadmgr", MOD1
, 0, GDK_d
},
4651 { "history", MOD1
, 0, GDK_h
},
4652 { "print", CTRL
, 0, GDK_p
},
4653 { "search", 0, 0, GDK_slash
},
4654 { "searchb", 0, 0, GDK_question
},
4655 { "command", 0, 0, GDK_colon
},
4656 { "qa", CTRL
, 0, GDK_q
},
4657 { "restart", MOD1
, 0, GDK_q
},
4658 { "js toggle", CTRL
, 0, GDK_j
},
4659 { "cookie toggle", MOD1
, 0, GDK_c
},
4660 { "togglesrc", CTRL
, 0, GDK_s
},
4661 { "yankuri", 0, 0, GDK_y
},
4662 { "pasteuricur", 0, 0, GDK_p
},
4663 { "pasteurinew", 0, 0, GDK_P
},
4666 { "searchnext", 0, 0, GDK_n
},
4667 { "searchprevious", 0, 0, GDK_N
},
4670 { "focusaddress", 0, 0, GDK_F6
},
4671 { "focussearch", 0, 0, GDK_F7
},
4674 { "hinting", 0, 0, GDK_f
},
4676 /* custom stylesheet */
4677 { "userstyle", 0, 0, GDK_i
},
4680 { "goback", 0, 0, GDK_BackSpace
},
4681 { "goback", MOD1
, 0, GDK_Left
},
4682 { "goforward", SHFT
, 0, GDK_BackSpace
},
4683 { "goforward", MOD1
, 0, GDK_Right
},
4684 { "reload", 0, 0, GDK_F5
},
4685 { "reload", CTRL
, 0, GDK_r
},
4686 { "reloadforce", CTRL
, 0, GDK_R
},
4687 { "reload", CTRL
, 0, GDK_l
},
4688 { "favorites", MOD1
, 1, GDK_f
},
4690 /* vertical movement */
4691 { "scrolldown", 0, 0, GDK_j
},
4692 { "scrolldown", 0, 0, GDK_Down
},
4693 { "scrollup", 0, 0, GDK_Up
},
4694 { "scrollup", 0, 0, GDK_k
},
4695 { "scrollbottom", 0, 0, GDK_G
},
4696 { "scrollbottom", 0, 0, GDK_End
},
4697 { "scrolltop", 0, 0, GDK_Home
},
4698 { "scrolltop", 0, 0, GDK_g
},
4699 { "scrollpagedown", 0, 0, GDK_space
},
4700 { "scrollpagedown", CTRL
, 0, GDK_f
},
4701 { "scrollhalfdown", CTRL
, 0, GDK_d
},
4702 { "scrollpagedown", 0, 0, GDK_Page_Down
},
4703 { "scrollpageup", 0, 0, GDK_Page_Up
},
4704 { "scrollpageup", CTRL
, 0, GDK_b
},
4705 { "scrollhalfup", CTRL
, 0, GDK_u
},
4706 /* horizontal movement */
4707 { "scrollright", 0, 0, GDK_l
},
4708 { "scrollright", 0, 0, GDK_Right
},
4709 { "scrollleft", 0, 0, GDK_Left
},
4710 { "scrollleft", 0, 0, GDK_h
},
4711 { "scrollfarright", 0, 0, GDK_dollar
},
4712 { "scrollfarleft", 0, 0, GDK_0
},
4715 { "tabnew", CTRL
, 0, GDK_t
},
4716 { "tabclose", CTRL
, 1, GDK_w
},
4717 { "tabundoclose", 0, 0, GDK_U
},
4718 { "tabnext 1", CTRL
, 0, GDK_1
},
4719 { "tabnext 2", CTRL
, 0, GDK_2
},
4720 { "tabnext 3", CTRL
, 0, GDK_3
},
4721 { "tabnext 4", CTRL
, 0, GDK_4
},
4722 { "tabnext 5", CTRL
, 0, GDK_5
},
4723 { "tabnext 6", CTRL
, 0, GDK_6
},
4724 { "tabnext 7", CTRL
, 0, GDK_7
},
4725 { "tabnext 8", CTRL
, 0, GDK_8
},
4726 { "tabnext 9", CTRL
, 0, GDK_9
},
4727 { "tabnext 10", CTRL
, 0, GDK_0
},
4728 { "tabfirst", CTRL
, 0, GDK_less
},
4729 { "tablast", CTRL
, 0, GDK_greater
},
4730 { "tabprevious", CTRL
, 0, GDK_Left
},
4731 { "tabnext", CTRL
, 0, GDK_Right
},
4732 { "focusout", CTRL
, 0, GDK_minus
},
4733 { "focusin", CTRL
, 0, GDK_plus
},
4734 { "focusin", CTRL
, 0, GDK_equal
},
4736 /* command aliases (handy when -S flag is used) */
4737 { "promptopen", 0, 0, GDK_F9
},
4738 { "promptopencurrent", 0, 0, GDK_F10
},
4739 { "prompttabnew", 0, 0, GDK_F11
},
4740 { "prompttabnewcurrent",0, 0, GDK_F12
},
4742 TAILQ_HEAD(keybinding_list
, key_binding
);
4745 walk_kb(struct settings
*s
,
4746 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
4748 struct key_binding
*k
;
4751 if (s
== NULL
|| cb
== NULL
) {
4752 show_oops_s("walk_kb invalid parameters");
4756 TAILQ_FOREACH(k
, &kbl
, entry
) {
4762 if (gdk_keyval_name(k
->key
) == NULL
)
4765 strlcat(str
, k
->cmd
, sizeof str
);
4766 strlcat(str
, ",", sizeof str
);
4768 if (k
->mask
& GDK_SHIFT_MASK
)
4769 strlcat(str
, "S-", sizeof str
);
4770 if (k
->mask
& GDK_CONTROL_MASK
)
4771 strlcat(str
, "C-", sizeof str
);
4772 if (k
->mask
& GDK_MOD1_MASK
)
4773 strlcat(str
, "M1-", sizeof str
);
4774 if (k
->mask
& GDK_MOD2_MASK
)
4775 strlcat(str
, "M2-", sizeof str
);
4776 if (k
->mask
& GDK_MOD3_MASK
)
4777 strlcat(str
, "M3-", sizeof str
);
4778 if (k
->mask
& GDK_MOD4_MASK
)
4779 strlcat(str
, "M4-", sizeof str
);
4780 if (k
->mask
& GDK_MOD5_MASK
)
4781 strlcat(str
, "M5-", sizeof str
);
4783 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
4784 cb(s
, str
, cb_args
);
4789 init_keybindings(void)
4792 struct key_binding
*k
;
4794 for (i
= 0; i
< LENGTH(keys
); i
++) {
4795 k
= g_malloc0(sizeof *k
);
4796 k
->cmd
= keys
[i
].cmd
;
4797 k
->mask
= keys
[i
].mask
;
4798 k
->use_in_entry
= keys
[i
].use_in_entry
;
4799 k
->key
= keys
[i
].key
;
4800 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4802 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
4803 k
->cmd
? k
->cmd
: "unnamed key");
4808 keybinding_clearall(void)
4810 struct key_binding
*k
, *next
;
4812 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
4813 next
= TAILQ_NEXT(k
, entry
);
4817 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
4818 k
->cmd
? k
->cmd
: "unnamed key");
4819 TAILQ_REMOVE(&kbl
, k
, entry
);
4825 keybinding_add(char *cmd
, char *key
, int use_in_entry
)
4827 struct key_binding
*k
;
4828 guint keyval
, mask
= 0;
4831 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s\n", cmd
, key
);
4833 /* Keys which are to be used in entry have been prefixed with an
4834 * exclamation mark. */
4838 /* find modifier keys */
4839 if (strstr(key
, "S-"))
4840 mask
|= GDK_SHIFT_MASK
;
4841 if (strstr(key
, "C-"))
4842 mask
|= GDK_CONTROL_MASK
;
4843 if (strstr(key
, "M1-"))
4844 mask
|= GDK_MOD1_MASK
;
4845 if (strstr(key
, "M2-"))
4846 mask
|= GDK_MOD2_MASK
;
4847 if (strstr(key
, "M3-"))
4848 mask
|= GDK_MOD3_MASK
;
4849 if (strstr(key
, "M4-"))
4850 mask
|= GDK_MOD4_MASK
;
4851 if (strstr(key
, "M5-"))
4852 mask
|= GDK_MOD5_MASK
;
4855 for (i
= strlen(key
) - 1; i
> 0; i
--)
4859 /* validate keyname */
4860 keyval
= gdk_keyval_from_name(key
);
4861 if (keyval
== GDK_VoidSymbol
) {
4862 warnx("invalid keybinding name %s", key
);
4865 /* must run this test too, gtk+ doesn't handle 10 for example */
4866 if (gdk_keyval_name(keyval
) == NULL
) {
4867 warnx("invalid keybinding name %s", key
);
4871 /* Remove eventual dupes. */
4872 TAILQ_FOREACH(k
, &kbl
, entry
)
4873 if (k
->key
== keyval
&& k
->mask
== mask
) {
4874 TAILQ_REMOVE(&kbl
, k
, entry
);
4880 k
= g_malloc0(sizeof *k
);
4881 k
->cmd
= g_strdup(cmd
);
4883 k
->use_in_entry
= use_in_entry
;
4886 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
4891 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
4892 k
->cmd
, gdk_keyval_name(keyval
));
4894 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4900 add_kb(struct settings
*s
, char *entry
)
4904 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
4906 /* clearall is special */
4907 if (!strcmp(entry
, "clearall")) {
4908 keybinding_clearall();
4912 kb
= strstr(entry
, ",");
4918 return (keybinding_add(entry
, key
, key
[0] == '!'));
4924 int (*func
)(struct tab
*, struct karg
*);
4926 bool userarg
; /* allow free text arg */
4928 { "command", 0, command
, {.i
= ':'}, FALSE
},
4929 { "search", 0, command
, {.i
= '/'}, FALSE
},
4930 { "searchb", 0, command
, {.i
= '?'}, FALSE
},
4931 { "togglesrc", 0, toggle_src
, {0}, FALSE
},
4933 /* yanking and pasting */
4934 { "yankuri", 0, yank_uri
, {0}, FALSE
},
4935 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4936 { "pasteuricur", 0, paste_uri
, {.i
= XT_PASTE_CURRENT_TAB
}, FALSE
},
4937 { "pasteurinew", 0, paste_uri
, {.i
= XT_PASTE_NEW_TAB
}, FALSE
},
4940 { "searchnext", 0, search
, {.i
= XT_SEARCH_NEXT
}, FALSE
},
4941 { "searchprevious", 0, search
, {.i
= XT_SEARCH_PREV
}, FALSE
},
4944 { "focusaddress", 0, focus
, {.i
= XT_FOCUS_URI
}, FALSE
},
4945 { "focussearch", 0, focus
, {.i
= XT_FOCUS_SEARCH
}, FALSE
},
4948 { "hinting", 0, hint
, {.i
= 0}, FALSE
},
4950 /* custom stylesheet */
4951 { "userstyle", 0, userstyle
, {.i
= 0 }, FALSE
},
4954 { "goback", 0, navaction
, {.i
= XT_NAV_BACK
}, FALSE
},
4955 { "goforward", 0, navaction
, {.i
= XT_NAV_FORWARD
}, FALSE
},
4956 { "reload", 0, navaction
, {.i
= XT_NAV_RELOAD
}, FALSE
},
4957 { "reloadforce", 0, navaction
, {.i
= XT_NAV_RELOAD_CACHE
}, FALSE
},
4959 /* vertical movement */
4960 { "scrolldown", 0, move
, {.i
= XT_MOVE_DOWN
}, FALSE
},
4961 { "scrollup", 0, move
, {.i
= XT_MOVE_UP
}, FALSE
},
4962 { "scrollbottom", 0, move
, {.i
= XT_MOVE_BOTTOM
}, FALSE
},
4963 { "scrolltop", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4964 { "1", 0, move
, {.i
= XT_MOVE_TOP
}, FALSE
},
4965 { "scrollhalfdown", 0, move
, {.i
= XT_MOVE_HALFDOWN
},FALSE
},
4966 { "scrollhalfup", 0, move
, {.i
= XT_MOVE_HALFUP
}, FALSE
},
4967 { "scrollpagedown", 0, move
, {.i
= XT_MOVE_PAGEDOWN
},FALSE
},
4968 { "scrollpageup", 0, move
, {.i
= XT_MOVE_PAGEUP
}, FALSE
},
4969 /* horizontal movement */
4970 { "scrollright", 0, move
, {.i
= XT_MOVE_RIGHT
}, FALSE
},
4971 { "scrollleft", 0, move
, {.i
= XT_MOVE_LEFT
}, FALSE
},
4972 { "scrollfarright", 0, move
, {.i
= XT_MOVE_FARRIGHT
},FALSE
},
4973 { "scrollfarleft", 0, move
, {.i
= XT_MOVE_FARLEFT
}, FALSE
},
4976 { "favorites", 0, xtp_page_fl
, {0}, FALSE
},
4977 { "fav", 0, xtp_page_fl
, {0}, FALSE
},
4978 { "favadd", 0, add_favorite
, {0}, FALSE
},
4980 { "qall", 0, quit
, {0}, FALSE
},
4981 { "quitall", 0, quit
, {0}, FALSE
},
4982 { "w", 0, save_tabs
, {0}, FALSE
},
4983 { "wq", 0, save_tabs_and_quit
, {0}, FALSE
},
4984 { "help", 0, help
, {0}, FALSE
},
4985 { "about", 0, about
, {0}, FALSE
},
4986 { "stats", 0, stats
, {0}, FALSE
},
4987 { "version", 0, about
, {0}, FALSE
},
4988 { "cookiejar", 0, xtp_page_cl
, {0}, FALSE
},
4991 { "js", 0, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4992 { "save", 1, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4993 { "domain", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
4994 { "fqdn", 2, js_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
4995 { "show", 1, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4996 { "all", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
4997 { "persistent", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
4998 { "session", 2, js_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
4999 { "toggle", 1, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5000 { "domain", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5001 { "fqdn", 2, js_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5003 /* cookie command */
5004 { "cookie", 0, cookie_cmd
, {0}, FALSE
},
5005 { "save", 1, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5006 { "domain", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_TOPLEVEL
}, FALSE
},
5007 { "fqdn", 2, cookie_cmd
, {.i
= XT_SAVE
| XT_WL_FQDN
}, FALSE
},
5008 { "show", 1, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5009 { "all", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
}, FALSE
},
5010 { "persistent", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_PERSISTENT
}, FALSE
},
5011 { "session", 2, cookie_cmd
, {.i
= XT_SHOW
| XT_WL_SESSION
}, FALSE
},
5012 { "toggle", 1, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5013 { "domain", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
}, FALSE
},
5014 { "fqdn", 2, cookie_cmd
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
}, FALSE
},
5017 { "cert", 0, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5018 { "save", 1, cert_cmd
, {.i
= XT_SAVE
}, FALSE
},
5019 { "show", 1, cert_cmd
, {.i
= XT_SHOW
}, FALSE
},
5021 { "ca", 0, ca_cmd
, {0}, FALSE
},
5022 { "downloadmgr", 0, xtp_page_dl
, {0}, FALSE
},
5023 { "dl", 0, xtp_page_dl
, {0}, FALSE
},
5024 { "h", 0, xtp_page_hl
, {0}, FALSE
},
5025 { "history", 0, xtp_page_hl
, {0}, FALSE
},
5026 { "home", 0, go_home
, {0}, FALSE
},
5027 { "restart", 0, restart
, {0}, FALSE
},
5028 { "urlhide", 0, urlaction
, {.i
= XT_URL_HIDE
}, FALSE
},
5029 { "urlshow", 0, urlaction
, {.i
= XT_URL_SHOW
}, FALSE
},
5030 { "statushide", 0, statusaction
, {.i
= XT_STATUSBAR_HIDE
}, FALSE
},
5031 { "statusshow", 0, statusaction
, {.i
= XT_STATUSBAR_SHOW
}, FALSE
},
5033 { "print", 0, print_page
, {0}, FALSE
},
5036 { "open", 0, tabaction
, {.i
= XT_TAB_OPEN
}, TRUE
},
5037 { "tabnew", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5038 { "tabedit", 0, tabaction
, {.i
= XT_TAB_NEW
}, TRUE
},
5039 { "tabclose", 0, tabaction
, {.i
= XT_TAB_DELETE
}, FALSE
},
5040 { "tabundoclose", 0, tabaction
, {.i
= XT_TAB_UNDO_CLOSE
} },
5041 { "tabshow", 0, tabaction
, {.i
= XT_TAB_SHOW
}, FALSE
},
5042 { "tabhide", 0, tabaction
, {.i
= XT_TAB_HIDE
}, FALSE
},
5043 { "quit", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5044 { "q", 0, tabaction
, {.i
= XT_TAB_DELQUIT
}, FALSE
},
5046 /* XXX add count to these commands */
5047 { "tabfirst", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5048 { "tabrewind", 0, movetab
, {.i
= XT_TAB_FIRST
}, FALSE
},
5049 { "tablast", 0, movetab
, {.i
= XT_TAB_LAST
}, FALSE
},
5050 { "tabprevious", 0, movetab
, {.i
= XT_TAB_PREV
}, FALSE
},
5051 { "tabnext", 0, movetab
, {.i
= XT_TAB_NEXT
}, TRUE
},
5052 { "focusout", 0, resizetab
, {.i
= -1}, FALSE
},
5053 { "focusin", 0, resizetab
, {.i
= 1}, FALSE
},
5055 /* command aliases (handy when -S flag is used) */
5056 { "promptopen", 0, command
, {.i
= XT_CMD_OPEN
}, FALSE
},
5057 { "promptopencurrent", 0, command
, {.i
= XT_CMD_OPEN_CURRENT
}, FALSE
},
5058 { "prompttabnew", 0, command
, {.i
= XT_CMD_TABNEW
}, FALSE
},
5059 { "prompttabnewcurrent",0, command
, {.i
= XT_CMD_TABNEW_CURRENT
}, FALSE
},
5062 { "set", 0, set
, {0}, FALSE
},
5063 { "fullscreen", 0, fullscreen
, {0}, FALSE
},
5064 { "f", 0, fullscreen
, {0}, FALSE
},
5067 { "session", 0, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5068 { "delete", 1, session_cmd
, {.i
= XT_DELETE
}, TRUE
},
5069 { "open", 1, session_cmd
, {.i
= XT_OPEN
}, TRUE
},
5070 { "save", 1, session_cmd
, {.i
= XT_SAVE
}, TRUE
},
5071 { "show", 1, session_cmd
, {.i
= XT_SHOW
}, FALSE
},
5078 } cmd_status
= {-1, 0};
5081 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5087 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5093 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5095 a
.i
= XT_NAV_FORWARD
;
5105 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5107 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5109 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5116 * cancel, remove, etc. downloads
5119 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5121 struct download find
, *d
= NULL
;
5123 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5125 /* some commands require a valid download id */
5126 if (cmd
!= XT_XTP_DL_LIST
) {
5127 /* lookup download in question */
5129 d
= RB_FIND(download_list
, &downloads
, &find
);
5132 show_oops(t
, "%s: no such download", __func__
);
5137 /* decide what to do */
5139 case XT_XTP_DL_CANCEL
:
5140 webkit_download_cancel(d
->download
);
5142 case XT_XTP_DL_REMOVE
:
5143 webkit_download_cancel(d
->download
); /* just incase */
5144 g_object_unref(d
->download
);
5145 RB_REMOVE(download_list
, &downloads
, d
);
5147 case XT_XTP_DL_LIST
:
5151 show_oops(t
, "%s: unknown command", __func__
);
5154 xtp_page_dl(t
, NULL
);
5158 * Actions on history, only does one thing for now, but
5159 * we provide the function for future actions
5162 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5164 struct history
*h
, *next
;
5168 case XT_XTP_HL_REMOVE
:
5169 /* walk backwards, as listed in reverse */
5170 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5171 next
= RB_PREV(history_list
, &hl
, h
);
5173 RB_REMOVE(history_list
, &hl
, h
);
5174 g_free((gpointer
) h
->title
);
5175 g_free((gpointer
) h
->uri
);
5182 case XT_XTP_HL_LIST
:
5183 /* Nothing - just xtp_page_hl() below */
5186 show_oops(t
, "%s: unknown command", __func__
);
5190 xtp_page_hl(t
, NULL
);
5193 /* remove a favorite */
5195 remove_favorite(struct tab
*t
, int index
)
5197 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5198 char *new_favs
, *tmp
;
5203 /* open favorites */
5204 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5206 if ((f
= fopen(file
, "r")) == NULL
) {
5207 show_oops(t
, "%s: can't open favorites: %s",
5208 __func__
, strerror(errno
));
5212 /* build a string which will become the new favroites file */
5213 new_favs
= g_strdup_printf("%s", "");
5216 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5217 if (feof(f
) || ferror(f
))
5219 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5226 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5227 if (feof(f
) || ferror(f
)) {
5228 show_oops(t
, "%s: can't parse favorites %s",
5229 __func__
, strerror(errno
));
5234 /* as long as this isn't the one we are deleting add to file */
5237 new_favs
= g_strdup_printf("%s%s\n%s\n",
5238 new_favs
, title
, uri
);
5250 /* write back new favorites file */
5251 if ((f
= fopen(file
, "w")) == NULL
) {
5252 show_oops(t
, "%s: can't open favorites: %s",
5253 __func__
, strerror(errno
));
5257 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5270 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5273 case XT_XTP_FL_LIST
:
5274 /* nothing, just the below call to xtp_page_fl() */
5276 case XT_XTP_FL_REMOVE
:
5277 remove_favorite(t
, arg
);
5280 show_oops(t
, "%s: invalid favorites command", __func__
);
5284 xtp_page_fl(t
, NULL
);
5288 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5291 case XT_XTP_CL_LIST
:
5292 /* nothing, just xtp_page_cl() */
5294 case XT_XTP_CL_REMOVE
:
5298 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5302 xtp_page_cl(t
, NULL
);
5305 /* link an XTP class to it's session key and handler function */
5306 struct xtp_despatch
{
5309 void (*handle_func
)(struct tab
*, uint8_t, int);
5312 struct xtp_despatch xtp_despatches
[] = {
5313 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5314 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5315 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5316 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5317 { XT_XTP_INVALID
, NULL
, NULL
}
5321 * is the url xtp protocol? (xxxt://)
5322 * if so, parse and despatch correct bahvior
5325 parse_xtp_url(struct tab
*t
, const char *url
)
5327 char *dup
= NULL
, *p
, *last
;
5328 uint8_t n_tokens
= 0;
5329 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5330 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5335 * tokens array meaning:
5337 * tokens[1] = session key
5338 * tokens[2] = action
5339 * tokens[3] = optional argument
5342 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5344 /*xtp tab meaning is normal unless proven special */
5345 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5347 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5350 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5352 /* split out the url */
5353 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5354 (p
= strtok_r(NULL
, "/", &last
))) {
5356 tokens
[n_tokens
++] = p
;
5359 /* should be atleast three fields 'class/seskey/command/arg' */
5363 dsp
= xtp_despatches
;
5364 req_class
= atoi(tokens
[0]);
5365 while (dsp
->xtp_class
) {
5366 if (dsp
->xtp_class
== req_class
) {
5373 /* did we find one atall? */
5374 if (dsp_match
== NULL
) {
5375 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
5379 /* check session key and call despatch function */
5380 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
5381 ret
= TRUE
; /* all is well, this was a valid xtp request */
5382 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
5395 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5397 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
5399 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
5402 show_oops_s("activate_uri_entry_cb invalid parameters");
5407 show_oops(t
, "activate_uri_entry_cb no uri");
5411 uri
+= strspn(uri
, "\t ");
5413 /* if xxxt:// treat specially */
5414 if (parse_xtp_url(t
, uri
))
5417 /* otherwise continue to load page normally */
5418 load_uri(t
, (gchar
*)uri
);
5423 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5425 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
5426 char *newuri
= NULL
;
5429 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
5432 show_oops_s("activate_search_entry_cb invalid parameters");
5436 if (search_string
== NULL
) {
5437 show_oops(t
, "no search_string");
5441 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
5442 newuri
= g_strdup_printf(search_string
, enc_search
);
5445 webkit_web_view_load_uri(t
->wv
, newuri
);
5453 check_and_set_js(const gchar
*uri
, struct tab
*t
)
5455 struct domain
*d
= NULL
;
5458 if (uri
== NULL
|| t
== NULL
)
5461 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5466 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
5467 es
? "enable" : "disable", uri
);
5469 g_object_set(G_OBJECT(t
->settings
),
5470 "enable-scripts", es
, (char *)NULL
);
5471 g_object_set(G_OBJECT(t
->settings
),
5472 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
5473 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5475 button_set_stockid(t
->js_toggle
,
5476 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
5480 show_ca_status(struct tab
*t
, const char *uri
)
5482 WebKitWebFrame
*frame
;
5483 WebKitWebDataSource
*source
;
5484 WebKitNetworkRequest
*request
;
5485 SoupMessage
*message
;
5487 gchar
*col_str
= XT_COLOR_WHITE
;
5490 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
5491 ssl_strict_certs
, ssl_ca_file
, uri
);
5495 if (ssl_ca_file
== NULL
) {
5496 if (g_str_has_prefix(uri
, "http://"))
5498 if (g_str_has_prefix(uri
, "https://")) {
5499 col_str
= XT_COLOR_RED
;
5504 if (g_str_has_prefix(uri
, "http://") ||
5505 !g_str_has_prefix(uri
, "https://"))
5508 frame
= webkit_web_view_get_main_frame(t
->wv
);
5509 source
= webkit_web_frame_get_data_source(frame
);
5510 request
= webkit_web_data_source_get_request(source
);
5511 message
= webkit_network_request_get_message(request
);
5513 if (message
&& (soup_message_get_flags(message
) &
5514 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
5515 col_str
= XT_COLOR_GREEN
;
5518 r
= load_compare_cert(t
, NULL
);
5520 col_str
= XT_COLOR_BLUE
;
5522 col_str
= XT_COLOR_YELLOW
;
5524 col_str
= XT_COLOR_RED
;
5529 gdk_color_parse(col_str
, &color
);
5530 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
5532 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
5533 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5535 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5536 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5539 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5541 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5542 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5549 free_favicon(struct tab
*t
)
5551 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p pix %p\n",
5552 __func__
, t
->icon_download
, t
->icon_request
, t
->icon_pixbuf
);
5554 if (t
->icon_request
)
5555 g_object_unref(t
->icon_request
);
5557 g_object_unref(t
->icon_pixbuf
);
5558 if (t
->icon_dest_uri
)
5559 g_free(t
->icon_dest_uri
);
5561 t
->icon_pixbuf
= NULL
;
5562 t
->icon_request
= NULL
;
5563 t
->icon_dest_uri
= NULL
;
5567 xt_icon_from_name(struct tab
*t
, gchar
*name
)
5569 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5570 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5572 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5573 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5575 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5576 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5580 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pixbuf
)
5582 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
5583 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5585 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
5586 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5588 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5589 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5593 is_valid_icon(char *file
)
5596 const char *mime_type
;
5600 gf
= g_file_new_for_path(file
);
5601 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5603 mime_type
= g_file_info_get_content_type(fi
);
5604 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
5605 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
5606 g_strcmp0(mime_type
, "image/png") == 0 ||
5607 g_strcmp0(mime_type
, "image/gif") == 0 ||
5608 g_strcmp0(mime_type
, "application/octet-stream") == 0;
5616 set_favicon_from_file(struct tab
*t
, char *file
)
5619 GdkPixbuf
*pixbuf
, *scaled
;
5622 if (t
== NULL
|| file
== NULL
)
5624 if (t
->icon_pixbuf
) {
5625 DNPRINTF(XT_D_DOWNLOAD
, "%s: icon already set\n", __func__
);
5629 if (g_str_has_prefix(file
, "file://"))
5630 file
+= strlen("file://");
5631 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
5633 if (!stat(file
, &sb
)) {
5634 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
5635 /* corrupt icon so trash it */
5636 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5639 /* no need to set icon to default here */
5644 pixbuf
= gdk_pixbuf_new_from_file(file
, NULL
);
5645 if (pixbuf
== NULL
) {
5646 xt_icon_from_name(t
, "text-html");
5650 g_object_get(pixbuf
, "width", &width
, "height", &height
,
5652 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d icon size %dx%d\n",
5653 __func__
, t
->tab_id
, width
, height
);
5655 if (width
> 16 || height
> 16) {
5656 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5657 GDK_INTERP_BILINEAR
);
5658 g_object_unref(pixbuf
);
5662 if (scaled
== NULL
) {
5663 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5664 GDK_INTERP_BILINEAR
);
5668 t
->icon_pixbuf
= scaled
;
5669 xt_icon_from_pixbuf(t
, t
->icon_pixbuf
);
5673 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5676 WebKitDownloadStatus status
= webkit_download_get_status(download
);
5677 struct tab
*tt
= NULL
, *t
= NULL
;
5680 * find the webview instead of passing in the tab as it could have been
5681 * deleted from underneath us.
5683 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5692 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
5693 __func__
, t
->tab_id
, status
);
5696 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
5698 t
->icon_download
= NULL
;
5701 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
5704 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
5707 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
5709 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
5710 __func__
, t
->tab_id
);
5711 t
->icon_download
= NULL
;
5714 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
5717 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
5718 __func__
, t
->icon_dest_uri
);
5719 set_favicon_from_file(t
, t
->icon_dest_uri
);
5720 /* these will be freed post callback */
5721 t
->icon_request
= NULL
;
5722 t
->icon_download
= NULL
;
5730 abort_favicon_download(struct tab
*t
)
5732 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
5734 if (t
->icon_download
) {
5735 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
5736 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5737 webkit_download_cancel(t
->icon_download
);
5738 t
->icon_download
= NULL
;
5742 xt_icon_from_name(t
, "text-html");
5746 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
5748 gchar
*name_hash
, file
[PATH_MAX
];
5751 DNPRINTF(XT_D_DOWNLOAD
, "notify_icon_loaded_cb %s\n", uri
);
5753 if (uri
== NULL
|| t
== NULL
)
5756 if (t
->icon_request
) {
5757 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
5761 /* check to see if we got the icon in cache */
5762 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
5763 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
5766 if (!stat(file
, &sb
)) {
5767 if (sb
.st_size
> 0) {
5768 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
5770 set_favicon_from_file(t
, file
);
5774 /* corrupt icon so trash it */
5775 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5780 /* create download for icon */
5781 t
->icon_request
= webkit_network_request_new(uri
);
5782 if (t
->icon_request
== NULL
) {
5783 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
5788 t
->icon_download
= webkit_download_new(t
->icon_request
);
5789 if (t
->icon_download
== NULL
) {
5790 fprintf(stderr
, "%s: icon_download", __func__
);
5794 /* we have to free icon_dest_uri later */
5795 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
5796 webkit_download_set_destination_uri(t
->icon_download
,
5799 if (webkit_download_get_status(t
->icon_download
) ==
5800 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5801 fprintf(stderr
, "%s: download failed to start", __func__
);
5802 g_object_unref(t
->icon_request
);
5803 g_free(t
->icon_dest_uri
);
5804 t
->icon_request
= NULL
;
5805 t
->icon_dest_uri
= NULL
;
5809 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
5810 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5812 webkit_download_start(t
->icon_download
);
5816 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5818 const gchar
*set
= NULL
, *uri
= NULL
, *title
= NULL
;
5819 struct history
*h
, find
;
5820 const gchar
*s_loading
;
5823 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
5824 webkit_web_view_get_load_status(wview
), get_uri(wview
) ? get_uri(wview
) : "NOTHING");
5827 show_oops_s("notify_load_status_cb invalid paramters");
5831 switch (webkit_web_view_get_load_status(wview
)) {
5832 case WEBKIT_LOAD_PROVISIONAL
:
5834 abort_favicon_download(t
);
5835 #if GTK_CHECK_VERSION(2, 20, 0)
5836 gtk_widget_show(t
->spinner
);
5837 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
5839 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
5841 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
5847 case WEBKIT_LOAD_COMMITTED
:
5849 if ((uri
= get_uri(wview
)) != NULL
) {
5850 if (strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
5851 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
5857 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
5860 /* check if js white listing is enabled */
5861 if (enable_js_whitelist
) {
5862 uri
= get_uri(wview
);
5863 check_and_set_js(uri
, t
);
5869 show_ca_status(t
, uri
);
5871 /* we know enough to autosave the session */
5872 if (session_autosave
) {
5878 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
5882 case WEBKIT_LOAD_FINISHED
:
5884 uri
= get_uri(wview
);
5888 if (!strncmp(uri
, "http://", strlen("http://")) ||
5889 !strncmp(uri
, "https://", strlen("https://")) ||
5890 !strncmp(uri
, "file://", strlen("file://"))) {
5892 h
= RB_FIND(history_list
, &hl
, &find
);
5894 title
= webkit_web_view_get_title(wview
);
5895 set
= title
? title
: uri
;
5896 h
= g_malloc(sizeof *h
);
5897 h
->uri
= g_strdup(uri
);
5898 h
->title
= g_strdup(set
);
5899 RB_INSERT(history_list
, &hl
, h
);
5900 completion_add_uri(h
->uri
);
5901 update_history_tabs(NULL
);
5905 set_status(t
, (char *)uri
, XT_STATUS_URI
);
5906 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5907 case WEBKIT_LOAD_FAILED
:
5910 #if GTK_CHECK_VERSION(2, 20, 0)
5911 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5912 gtk_widget_hide(t
->spinner
);
5914 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
5915 if (s_loading
&& !strcmp(s_loading
, "Loading"))
5916 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5918 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
5923 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
5925 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
5926 webkit_web_view_can_go_back(wview
));
5928 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
5929 webkit_web_view_can_go_forward(wview
));
5931 /* take focus if we are visible */
5936 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5938 const gchar
*set
= NULL
, *title
= NULL
;
5940 title
= webkit_web_view_get_title(wview
);
5941 set
= title
? title
: get_uri(wview
);
5942 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
5943 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
5947 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5949 run_script(t
, JS_HINTING
);
5953 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
5955 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
5956 progress
== 100 ? 0 : (double)progress
/ 100);
5957 if (show_url
== 0) {
5958 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
5959 progress
== 100 ? 0 : (double)progress
/ 100);
5964 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
5965 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
5966 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
5969 WebKitWebNavigationReason reason
;
5970 struct domain
*d
= NULL
;
5973 show_oops_s("webview_npd_cb invalid parameters");
5977 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
5979 webkit_network_request_get_uri(request
));
5981 uri
= (char *)webkit_network_request_get_uri(request
);
5983 /* if this is an xtp url, we don't load anything else */
5984 if (parse_xtp_url(t
, uri
))
5987 if (t
->ctrl_click
) {
5989 create_new_tab(uri
, NULL
, ctrl_click_focus
);
5990 webkit_web_policy_decision_ignore(pd
);
5991 return (TRUE
); /* we made the decission */
5995 * This is a little hairy but it comes down to this:
5996 * when we run in whitelist mode we have to assist the browser in
5997 * opening the URL that it would have opened in a new tab.
5999 reason
= webkit_web_navigation_action_get_reason(na
);
6000 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6001 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6002 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6005 webkit_web_policy_decision_use(pd
);
6006 return (TRUE
); /* we made the decission */
6013 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6016 struct domain
*d
= NULL
;
6018 WebKitWebView
*webview
= NULL
;
6020 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6021 webkit_web_view_get_uri(wv
));
6024 /* open in current tab */
6026 } else if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6027 uri
= webkit_web_view_get_uri(wv
);
6028 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6031 tt
= create_new_tab(NULL
, NULL
, 1);
6033 } else if (enable_scripts
== 1) {
6034 tt
= create_new_tab(NULL
, NULL
, 1);
6042 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6045 struct domain
*d
= NULL
;
6047 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6049 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6050 uri
= webkit_web_view_get_uri(wv
);
6051 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6055 } else if (enable_scripts
== 1)
6062 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6064 /* we can not eat the event without throwing gtk off so defer it */
6066 /* catch middle click */
6067 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6072 /* catch ctrl click */
6073 if (e
->type
== GDK_BUTTON_RELEASE
&&
6074 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6079 return (XT_CB_PASSTHROUGH
);
6083 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6085 struct mime_type
*m
;
6087 m
= find_mime_type(mime_type
);
6095 show_oops(t
, "can't fork mime handler");
6104 execlp(m
->mt_action
, m
->mt_action
,
6105 webkit_network_request_get_uri(request
), (void *)NULL
);
6114 get_mime_type(char *file
)
6116 const char *mime_type
;
6120 if (g_str_has_prefix(file
, "file://"))
6121 file
+= strlen("file://");
6123 gf
= g_file_new_for_path(file
);
6124 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6126 mime_type
= g_file_info_get_content_type(fi
);
6134 run_download_mimehandler(char *mime_type
, char *file
)
6136 struct mime_type
*m
;
6138 m
= find_mime_type(mime_type
);
6144 show_oops_s("can't fork download mime handler");
6153 if (g_str_has_prefix(file
, "file://"))
6154 file
+= strlen("file://");
6155 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6164 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6167 WebKitDownloadStatus status
;
6168 const gchar
*file
= NULL
, *mime
= NULL
;
6170 if (download
== NULL
)
6172 status
= webkit_download_get_status(download
);
6173 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6176 file
= webkit_download_get_destination_uri(download
);
6179 mime
= get_mime_type((char *)file
);
6183 run_download_mimehandler((char *)mime
, (char *)file
);
6187 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6188 WebKitNetworkRequest
*request
, char *mime_type
,
6189 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6192 show_oops_s("webview_mimetype_cb invalid parameters");
6196 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6197 t
->tab_id
, mime_type
);
6199 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6200 webkit_web_policy_decision_ignore(decision
);
6205 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6206 webkit_web_policy_decision_download(decision
);
6214 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6217 const gchar
*filename
;
6219 struct download
*download_entry
;
6222 if (wk_download
== NULL
|| t
== NULL
) {
6223 show_oops_s("%s invalid parameters", __func__
);
6227 filename
= webkit_download_get_suggested_filename(wk_download
);
6228 if (filename
== NULL
)
6229 return (FALSE
); /* abort download */
6231 uri
= g_strdup_printf("file://%s/%s", download_dir
, filename
);
6233 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6234 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6236 webkit_download_set_destination_uri(wk_download
, uri
);
6238 if (webkit_download_get_status(wk_download
) ==
6239 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6240 show_oops(t
, "%s: download failed to start", __func__
);
6242 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6244 /* connect "download first" mime handler */
6245 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6246 G_CALLBACK(download_status_changed_cb
), NULL
);
6248 download_entry
= g_malloc(sizeof(struct download
));
6249 download_entry
->download
= wk_download
;
6250 download_entry
->tab
= t
;
6251 download_entry
->id
= next_download_id
++;
6252 RB_INSERT(download_list
, &downloads
, download_entry
);
6253 /* get from history */
6254 g_object_ref(wk_download
);
6255 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6256 show_oops(t
, "Download of '%s' started...",
6257 basename(webkit_download_get_destination_uri(wk_download
)));
6263 /* sync other download manager tabs */
6264 update_download_tabs(NULL
);
6267 * NOTE: never redirect/render the current tab before this
6268 * function returns. This will cause the download to never start.
6270 return (ret
); /* start download */
6274 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6276 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6279 show_oops_s("webview_hover_cb");
6284 set_status(t
, uri
, XT_STATUS_LINK
);
6287 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6292 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6294 struct key_binding
*k
;
6296 TAILQ_FOREACH(k
, &kbl
, entry
)
6297 if (e
->keyval
== k
->key
&& (entry
? k
->use_in_entry
: 1)) {
6299 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6300 return (cmd_execute(t
, k
->cmd
));
6301 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6302 return (cmd_execute(t
, k
->cmd
));
6306 return (XT_CB_PASSTHROUGH
);
6310 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6312 char s
[2], buf
[128];
6313 const char *errstr
= NULL
;
6316 /* don't use w directly; use t->whatever instead */
6319 show_oops_s("wv_keypress_after_cb");
6320 return (XT_CB_PASSTHROUGH
);
6323 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6324 e
->keyval
, e
->state
, t
);
6328 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6330 return (XT_CB_HANDLED
);
6334 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6335 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6337 /* we have a string */
6339 /* we have a number */
6340 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6348 /* XXX unfuck this */
6349 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6350 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6351 /* last input was numerical */
6353 l
= strlen(t
->hint_num
);
6360 t
->hint_num
[l
] = '\0';
6364 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6365 /* last input was alphanumerical */
6367 l
= strlen(t
->hint_buf
);
6374 t
->hint_buf
[l
] = '\0';
6384 /* numerical input */
6385 if (CLEAN(e
->state
) == 0 &&
6386 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6387 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6388 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6389 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6392 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6394 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6397 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6399 t
->hint_mode
= XT_HINT_NUMERICAL
;
6403 /* empty the counter buffer */
6404 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6405 return (XT_CB_HANDLED
);
6408 /* alphanumerical input */
6410 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6411 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6412 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6413 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6414 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6415 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6416 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6419 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6422 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6424 t
->hint_mode
= XT_HINT_ALPHANUM
;
6427 /* empty the counter buffer */
6428 bzero(t
->hint_num
, sizeof t
->hint_num
);
6429 return (XT_CB_HANDLED
);
6432 return (XT_CB_HANDLED
);
6435 return (handle_keypress(t
, e
, 0));
6439 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6443 return (XT_CB_PASSTHROUGH
);
6447 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6449 const gchar
*c
= gtk_entry_get_text(w
);
6453 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6454 e
->keyval
, e
->state
, t
);
6457 show_oops_s("cmd_keyrelease_cb invalid parameters");
6458 return (XT_CB_PASSTHROUGH
);
6461 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6462 e
->keyval
, e
->state
, t
);
6466 if (strlen(c
) == 1) {
6467 webkit_web_view_unmark_text_matches(t
->wv
);
6473 else if (c
[0] == '?')
6479 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6481 /* not found, mark red */
6482 gdk_color_parse(XT_COLOR_RED
, &color
);
6483 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6484 /* unmark and remove selection */
6485 webkit_web_view_unmark_text_matches(t
->wv
);
6486 /* my kingdom for a way to unselect text in webview */
6488 /* found, highlight all */
6489 webkit_web_view_unmark_text_matches(t
->wv
);
6490 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6491 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6492 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6493 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6496 return (XT_CB_PASSTHROUGH
);
6500 match_uri(const gchar
*uri
, const gchar
*key
) {
6503 gboolean match
= FALSE
;
6507 if (!strncmp(key
, uri
, len
))
6510 voffset
= strstr(uri
, "/") + 2;
6511 if (!strncmp(key
, voffset
, len
))
6513 else if (g_str_has_prefix(voffset
, "www.")) {
6514 voffset
= voffset
+ strlen("www.");
6515 if (!strncmp(key
, voffset
, len
))
6524 cmd_getlist(int id
, char *key
)
6529 if (id
>= 0 && (cmds
[id
].userarg
&& (cmds
[id
].arg
.i
== XT_TAB_OPEN
|| cmds
[id
].arg
.i
== XT_TAB_NEW
))) {
6530 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6531 if (match_uri(h
->uri
, key
)) {
6532 cmd_status
.list
[c
] = (char *)h
->uri
;
6541 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6543 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6544 if(cmds
[i
].level
< dep
)
6546 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6547 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6555 cmd_getnext(int dir
)
6557 cmd_status
.index
+= dir
;
6559 if (cmd_status
.index
< 0)
6560 cmd_status
.index
= cmd_status
.len
- 1;
6561 else if (cmd_status
.index
>= cmd_status
.len
)
6562 cmd_status
.index
= 0;
6564 return cmd_status
.list
[cmd_status
.index
];
6568 cmd_tokenize(char *s
, char *tokens
[])
6572 size_t len
= strlen(s
);
6573 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6575 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6585 cmd_complete(struct tab
*t
, char *str
, int dir
)
6587 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6588 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1, matchcount
= 0;
6589 char *tok
, *match
, *s
= g_strdup(str
);
6591 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6593 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: complete %s\n", str
);
6595 levels
= cmd_tokenize(s
, tokens
);
6598 for (i
= 0; i
< levels
- 1; i
++) {
6601 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6602 if (cmds
[j
].level
< dep
)
6604 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, strlen(tok
))) {
6607 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
6614 if (matchcount
== 1) {
6615 strlcat(res
, tok
, sizeof res
);
6616 strlcat(res
, " ", sizeof res
);
6624 if (cmd_status
.index
== -1)
6625 cmd_getlist(parent
, tokens
[i
]);
6627 if (cmd_status
.len
> 0) {
6628 match
= cmd_getnext(dir
);
6629 strlcat(res
, match
, sizeof res
);
6630 gtk_entry_set_text(w
, res
);
6631 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6636 cmd_execute(struct tab
*t
, char *str
)
6638 struct cmd
*cmd
= NULL
;
6639 char *tok
, *last
, *s
= g_strdup(str
);
6640 int j
, len
, c
= 0, dep
= 0, matchcount
= 0;
6642 for (tok
= strtok_r(s
, " ", &last
); tok
;
6643 tok
= strtok_r(NULL
, " ", &last
)) {
6645 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6646 if (cmds
[j
].level
< dep
)
6648 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1: strlen(tok
);
6649 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, len
)) {
6653 if (len
== strlen(cmds
[j
].cmd
)) {
6659 if (matchcount
== 1) {
6664 show_oops(t
, "Invalid command: %s", str
);
6666 return (XT_CB_PASSTHROUGH
);
6671 cmd
->arg
.s
= last
? g_strdup(last
) : g_strdup("");
6673 cmd
->arg
.s
= g_strdup(tok
);
6675 /* arg->s contains last token */
6676 cmd
->func(t
, &cmd
->arg
);
6681 return (XT_CB_HANDLED
);
6685 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6688 show_oops_s("entry_key_cb invalid parameters");
6689 return (XT_CB_PASSTHROUGH
);
6692 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6693 e
->keyval
, e
->state
, t
);
6697 if (e
->keyval
== GDK_Escape
) {
6698 /* don't use focus_webview(t) because we want to type :cmds */
6699 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6702 return (handle_keypress(t
, e
, 1));
6706 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6708 int rv
= XT_CB_HANDLED
;
6709 const gchar
*c
= gtk_entry_get_text(w
);
6712 show_oops_s("cmd_keypress_cb parameters");
6713 return (XT_CB_PASSTHROUGH
);
6716 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6717 e
->keyval
, e
->state
, t
);
6721 e
->keyval
= GDK_Escape
;
6722 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6723 e
->keyval
= GDK_Escape
;
6725 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
6726 cmd_status
.index
= -1;
6728 switch (e
->keyval
) {
6731 cmd_complete(t
, (char *)&c
[1], 1);
6733 case GDK_ISO_Left_Tab
:
6735 cmd_complete(t
, (char *)&c
[1], -1);
6739 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
6747 if (c
[0] == '/' || c
[0] == '?')
6748 webkit_web_view_unmark_text_matches(t
->wv
);
6752 rv
= XT_CB_PASSTHROUGH
;
6758 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6761 show_oops_s("cmd_focusout_cb invalid parameters");
6762 return (XT_CB_PASSTHROUGH
);
6764 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
6769 if (show_url
== 0 || t
->focus_wv
)
6772 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6774 return (XT_CB_PASSTHROUGH
);
6778 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6781 const gchar
*c
= gtk_entry_get_text(entry
);
6784 show_oops_s("cmd_activate_cb invalid parameters");
6788 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6795 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6801 if (c
[0] == '/' || c
[0] == '?') {
6802 if (t
->search_text
) {
6803 g_free(t
->search_text
);
6804 t
->search_text
= NULL
;
6807 t
->search_text
= g_strdup(s
);
6809 g_free(global_search
);
6810 global_search
= g_strdup(s
);
6811 t
->search_forward
= c
[0] == '/';
6823 backward_cb(GtkWidget
*w
, struct tab
*t
)
6828 show_oops_s("backward_cb invalid parameters");
6832 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6839 forward_cb(GtkWidget
*w
, struct tab
*t
)
6844 show_oops_s("forward_cb invalid parameters");
6848 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6850 a
.i
= XT_NAV_FORWARD
;
6855 home_cb(GtkWidget
*w
, struct tab
*t
)
6858 show_oops_s("home_cb invalid parameters");
6862 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6868 stop_cb(GtkWidget
*w
, struct tab
*t
)
6870 WebKitWebFrame
*frame
;
6873 show_oops_s("stop_cb invalid parameters");
6877 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
6879 frame
= webkit_web_view_get_main_frame(t
->wv
);
6880 if (frame
== NULL
) {
6881 show_oops(t
, "stop_cb: no frame");
6885 webkit_web_frame_stop_loading(frame
);
6886 abort_favicon_download(t
);
6890 setup_webkit(struct tab
*t
)
6892 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
6893 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
6894 FALSE
, (char *)NULL
);
6896 warnx("webkit does not have \"enable-dns-prefetching\" property");
6897 g_object_set(G_OBJECT(t
->settings
),
6898 "user-agent", t
->user_agent
, (char *)NULL
);
6899 g_object_set(G_OBJECT(t
->settings
),
6900 "enable-scripts", enable_scripts
, (char *)NULL
);
6901 g_object_set(G_OBJECT(t
->settings
),
6902 "enable-plugins", enable_plugins
, (char *)NULL
);
6903 g_object_set(G_OBJECT(t
->settings
),
6904 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
6905 g_object_set(G_OBJECT(t
->wv
),
6906 "full-content-zoom", TRUE
, (char *)NULL
);
6907 adjustfont_webkit(t
, XT_FONT_SET
);
6909 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6913 create_browser(struct tab
*t
)
6919 show_oops_s("create_browser invalid parameters");
6923 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
6924 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
6925 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
6926 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
6928 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
6929 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
6930 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
6932 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
6933 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
6936 t
->settings
= webkit_web_settings_new();
6938 if (user_agent
== NULL
) {
6939 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
6941 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
6944 t
->user_agent
= g_strdup(user_agent
);
6946 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
6958 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
6959 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
6960 gtk_widget_set_name(w
, "xxxterm");
6961 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
6962 g_signal_connect(G_OBJECT(w
), "delete_event",
6963 G_CALLBACK (gtk_main_quit
), NULL
);
6969 create_kiosk_toolbar(struct tab
*t
)
6971 GtkWidget
*toolbar
= NULL
, *b
;
6973 b
= gtk_hbox_new(FALSE
, 0);
6975 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
6977 /* backward button */
6978 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
6979 gtk_widget_set_sensitive(t
->backward
, FALSE
);
6980 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
6981 G_CALLBACK(backward_cb
), t
);
6982 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
6984 /* forward button */
6985 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
6986 gtk_widget_set_sensitive(t
->forward
, FALSE
);
6987 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
6988 G_CALLBACK(forward_cb
), t
);
6989 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
6992 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
6993 gtk_widget_set_sensitive(t
->gohome
, true);
6994 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
6995 G_CALLBACK(home_cb
), t
);
6996 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
6998 /* create widgets but don't use them */
6999 t
->uri_entry
= gtk_entry_new();
7000 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7001 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7002 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7008 create_toolbar(struct tab
*t
)
7010 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7012 b
= gtk_hbox_new(FALSE
, 0);
7014 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7017 /* backward button */
7018 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7019 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7020 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7021 G_CALLBACK(backward_cb
), t
);
7022 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7024 /* forward button */
7025 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7026 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7027 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7028 G_CALLBACK(forward_cb
), t
);
7029 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7033 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7034 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7035 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7036 G_CALLBACK(stop_cb
), t
);
7037 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7041 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7042 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7043 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7044 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7045 G_CALLBACK(js_toggle_cb
), t
);
7046 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7049 t
->uri_entry
= gtk_entry_new();
7050 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7051 G_CALLBACK(activate_uri_entry_cb
), t
);
7052 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7053 G_CALLBACK(entry_key_cb
), t
);
7055 eb1
= gtk_hbox_new(FALSE
, 0);
7056 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7057 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7058 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7061 if (fancy_bar
&& search_string
) {
7063 t
->search_entry
= gtk_entry_new();
7064 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7065 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7066 G_CALLBACK(activate_search_entry_cb
), t
);
7067 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7068 G_CALLBACK(entry_key_cb
), t
);
7069 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7070 eb2
= gtk_hbox_new(FALSE
, 0);
7071 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7072 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7074 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7084 TAILQ_FOREACH(t
, &tabs
, entry
)
7085 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7089 undo_close_tab_save(struct tab
*t
)
7093 struct undo
*u1
, *u2
;
7095 WebKitWebHistoryItem
*item
;
7097 if ((uri
= get_uri(t
->wv
)) == NULL
)
7100 u1
= g_malloc0(sizeof(struct undo
));
7101 u1
->uri
= g_strdup(uri
);
7103 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7105 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7106 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7109 /* forward history */
7110 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7114 u1
->history
= g_list_prepend(u1
->history
,
7115 webkit_web_history_item_copy(item
));
7116 items
= g_list_next(items
);
7121 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7122 u1
->history
= g_list_prepend(u1
->history
,
7123 webkit_web_history_item_copy(item
));
7127 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7131 u1
->history
= g_list_prepend(u1
->history
,
7132 webkit_web_history_item_copy(item
));
7133 items
= g_list_next(items
);
7136 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7138 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7139 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7140 TAILQ_REMOVE(&undos
, u2
, entry
);
7142 g_list_free(u2
->history
);
7151 delete_tab(struct tab
*t
)
7155 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7160 TAILQ_REMOVE(&tabs
, t
, entry
);
7162 /* halt all webkit activity */
7163 abort_favicon_download(t
);
7164 webkit_web_view_stop_loading(t
->wv
);
7165 undo_close_tab_save(t
);
7167 if (browser_mode
== XT_BM_KIOSK
) {
7168 gtk_widget_destroy(t
->uri_entry
);
7169 gtk_widget_destroy(t
->stop
);
7170 gtk_widget_destroy(t
->js_toggle
);
7173 gtk_widget_destroy(t
->vbox
);
7174 g_free(t
->user_agent
);
7175 g_free(t
->stylesheet
);
7179 if (TAILQ_EMPTY(&tabs
)) {
7180 if (browser_mode
== XT_BM_KIOSK
)
7181 create_new_tab(home
, NULL
, 1);
7183 create_new_tab(NULL
, NULL
, 1);
7186 /* recreate session */
7187 if (session_autosave
) {
7194 adjustfont_webkit(struct tab
*t
, int adjust
)
7199 show_oops_s("adjustfont_webkit invalid parameters");
7203 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7204 if (adjust
== XT_FONT_SET
) {
7205 t
->font_size
= default_font_size
;
7206 zoom
= default_zoom_level
;
7207 t
->font_size
+= adjust
;
7208 g_object_set(G_OBJECT(t
->settings
), "default-font-size",
7209 t
->font_size
, (char *)NULL
);
7210 g_object_get(G_OBJECT(t
->settings
), "default-font-size",
7211 &t
->font_size
, (char *)NULL
);
7213 t
->font_size
+= adjust
;
7214 zoom
+= adjust
/25.0;
7219 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7220 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7224 append_tab(struct tab
*t
)
7229 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7230 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7234 create_new_tab(char *title
, struct undo
*u
, int focus
)
7237 int load
= 1, id
, notfound
;
7239 WebKitWebHistoryItem
*item
;
7243 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7245 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7246 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7250 t
= g_malloc0(sizeof *t
);
7252 if (title
== NULL
) {
7253 title
= "(untitled)";
7257 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7259 /* label + button for tab */
7260 b
= gtk_hbox_new(FALSE
, 0);
7263 #if GTK_CHECK_VERSION(2, 20, 0)
7264 t
->spinner
= gtk_spinner_new ();
7266 t
->label
= gtk_label_new(title
);
7267 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7268 gtk_widget_set_size_request(t
->label
, 100, 0);
7269 gtk_widget_set_size_request(b
, 130, 0);
7271 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7272 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7273 #if GTK_CHECK_VERSION(2, 20, 0)
7274 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7278 if (browser_mode
== XT_BM_KIOSK
)
7279 t
->toolbar
= create_kiosk_toolbar(t
);
7281 t
->toolbar
= create_toolbar(t
);
7283 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7286 t
->browser_win
= create_browser(t
);
7287 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7289 /* oops message for user feedback */
7290 t
->oops
= gtk_entry_new();
7291 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7292 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7293 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7294 gdk_color_parse(XT_COLOR_RED
, &color
);
7295 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7296 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7299 t
->cmd
= gtk_entry_new();
7300 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7301 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7302 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7305 t
->statusbar
= gtk_entry_new();
7306 gtk_entry_set_inner_border(GTK_ENTRY(t
->statusbar
), NULL
);
7307 gtk_entry_set_has_frame(GTK_ENTRY(t
->statusbar
), FALSE
);
7308 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7309 gdk_color_parse(XT_COLOR_BLACK
, &color
);
7310 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7311 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7312 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7313 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7315 /* xtp meaning is normal by default */
7316 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
7318 /* set empty favicon */
7319 xt_icon_from_name(t
, "text-html");
7321 /* and show it all */
7322 gtk_widget_show_all(b
);
7323 gtk_widget_show_all(t
->vbox
);
7325 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7329 id
= gtk_notebook_get_current_page(notebook
);
7330 TAILQ_FOREACH(tt
, &tabs
, entry
) {
7331 if (tt
->tab_id
== id
) {
7333 TAILQ_INSERT_AFTER(&tabs
, tt
, t
, entry
);
7334 gtk_notebook_insert_page(notebook
, t
->vbox
, b
,
7344 #if GTK_CHECK_VERSION(2, 20, 0)
7345 /* turn spinner off if we are a new tab without uri */
7347 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7348 gtk_widget_hide(t
->spinner
);
7351 /* make notebook tabs reorderable */
7352 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7354 g_object_connect(G_OBJECT(t
->cmd
),
7355 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7356 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7357 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7358 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7361 /* reuse wv_button_cb to hide oops */
7362 g_object_connect(G_OBJECT(t
->oops
),
7363 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7366 g_object_connect(G_OBJECT(t
->wv
),
7367 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7368 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7369 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7370 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7371 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7372 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7373 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7374 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7375 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7376 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7377 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
7378 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
7379 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7380 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7382 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7384 g_signal_connect(t
->wv
,
7385 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7386 g_signal_connect(t
->wv
,
7387 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7389 /* hijack the unused keys as if we were the browser */
7390 g_object_connect(G_OBJECT(t
->toolbar
),
7391 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7394 g_signal_connect(G_OBJECT(bb
), "button_press_event",
7395 G_CALLBACK(tab_close_cb
), t
);
7400 url_set_visibility();
7401 statusbar_set_visibility();
7404 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7405 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
7410 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
7414 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7419 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7420 /* restore the tab's history */
7421 if (u
&& u
->history
) {
7425 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
7426 items
= g_list_next(items
);
7429 item
= g_list_nth_data(u
->history
, u
->back
);
7431 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
7434 g_list_free(u
->history
);
7436 webkit_web_back_forward_list_clear(t
->bfl
);
7442 notebook_switchpage_cb(GtkNotebook
*nb
, GtkNotebookPage
*nbp
, guint pn
,
7448 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
7450 TAILQ_FOREACH(t
, &tabs
, entry
) {
7451 if (t
->tab_id
== pn
) {
7452 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
7455 uri
= webkit_web_view_get_title(t
->wv
);
7458 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
7470 menuitem_response(struct tab
*t
)
7472 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7476 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
7478 GtkWidget
*menu
, *menu_items
;
7479 GdkEventButton
*bevent
;
7483 if (event
->type
== GDK_BUTTON_PRESS
) {
7484 bevent
= (GdkEventButton
*) event
;
7485 menu
= gtk_menu_new();
7487 TAILQ_FOREACH(ti
, &tabs
, entry
) {
7488 if ((uri
= get_uri(ti
->wv
)) == NULL
)
7489 /* XXX make sure there is something to print */
7490 /* XXX add gui pages in here to look purdy */
7492 menu_items
= gtk_menu_item_new_with_label(uri
);
7493 gtk_menu_append(GTK_MENU (menu
), menu_items
);
7494 gtk_widget_show(menu_items
);
7496 gtk_signal_connect_object(GTK_OBJECT(menu_items
),
7497 "activate", GTK_SIGNAL_FUNC(menuitem_response
),
7501 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
7502 bevent
->button
, bevent
->time
);
7504 /* unref object so it'll free itself when popped down */
7505 g_object_ref_sink(menu
);
7506 g_object_unref(menu
);
7508 return (TRUE
/* eat event */);
7511 return (FALSE
/* propagate */);
7515 icon_size_map(int icon_size
)
7517 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
7518 icon_size
> GTK_ICON_SIZE_DIALOG
)
7519 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
7525 create_button(char *name
, char *stockid
, int size
)
7527 GtkWidget
*button
, *image
;
7531 rcstring
= g_strdup_printf(
7532 "style \"%s-style\"\n"
7534 " GtkWidget::focus-padding = 0\n"
7535 " GtkWidget::focus-line-width = 0\n"
7539 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
7540 gtk_rc_parse_string(rcstring
);
7542 button
= gtk_button_new();
7543 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
7544 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
7546 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
7547 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7548 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
7549 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
7550 gtk_widget_set_name(button
, name
);
7551 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
7557 button_set_stockid(GtkWidget
*button
, char *stockid
)
7561 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
7562 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7563 gtk_button_set_image(GTK_BUTTON(button
), image
);
7572 char file
[PATH_MAX
];
7575 vbox
= gtk_vbox_new(FALSE
, 0);
7576 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
7577 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
7578 gtk_notebook_set_tab_hborder(notebook
, 0);
7579 gtk_notebook_set_tab_vborder(notebook
, 0);
7580 gtk_notebook_set_scrollable(notebook
, TRUE
);
7581 notebook_tab_set_visibility(notebook
);
7582 gtk_notebook_set_show_border(notebook
, FALSE
);
7583 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
7585 abtn
= gtk_button_new();
7586 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
7587 gtk_widget_set_size_request(arrow
, -1, -1);
7588 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
7589 gtk_widget_set_size_request(abtn
, -1, 20);
7591 #if GTK_CHECK_VERSION(2, 20, 0)
7592 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
7594 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
7595 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
7596 gtk_widget_set_size_request(vbox
, -1, -1);
7598 g_object_connect(G_OBJECT(notebook
),
7599 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
7601 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
7602 G_CALLBACK(arrow_cb
), NULL
);
7604 main_window
= create_window();
7605 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
7606 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
7609 for (i
= 0; i
< LENGTH(icons
); i
++) {
7610 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
7611 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
7612 l
= g_list_append(l
, pb
);
7614 gtk_window_set_default_icon_list(l
);
7616 gtk_widget_show_all(abtn
);
7617 gtk_widget_show_all(main_window
);
7621 set_hook(void **hook
, char *name
)
7624 errx(1, "set_hook");
7626 if (*hook
== NULL
) {
7627 *hook
= dlsym(RTLD_NEXT
, name
);
7629 errx(1, "can't hook %s", name
);
7633 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7635 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
7637 g_return_val_if_fail(cookie1
, FALSE
);
7638 g_return_val_if_fail(cookie2
, FALSE
);
7640 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
7641 !strcmp (cookie1
->value
, cookie2
->value
) &&
7642 !strcmp (cookie1
->path
, cookie2
->path
) &&
7643 !strcmp (cookie1
->domain
, cookie2
->domain
));
7647 transfer_cookies(void)
7650 SoupCookie
*sc
, *pc
;
7652 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7654 for (;cf
; cf
= cf
->next
) {
7656 sc
= soup_cookie_copy(pc
);
7657 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
7660 soup_cookies_free(cf
);
7664 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
7669 print_cookie("soup_cookie_jar_delete_cookie", c
);
7671 if (cookies_enabled
== 0)
7674 if (jar
== NULL
|| c
== NULL
)
7677 /* find and remove from persistent jar */
7678 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7680 for (;cf
; cf
= cf
->next
) {
7682 if (soup_cookie_equal(ci
, c
)) {
7683 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
7688 soup_cookies_free(cf
);
7690 /* delete from session jar */
7691 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
7695 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
7697 struct domain
*d
= NULL
;
7701 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
7702 jar
, p_cookiejar
, s_cookiejar
);
7704 if (cookies_enabled
== 0)
7707 /* see if we are up and running */
7708 if (p_cookiejar
== NULL
) {
7709 _soup_cookie_jar_add_cookie(jar
, cookie
);
7712 /* disallow p_cookiejar adds, shouldn't happen */
7713 if (jar
== p_cookiejar
)
7717 if (jar
== NULL
|| cookie
== NULL
)
7720 if (enable_cookie_whitelist
&&
7721 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
7723 DNPRINTF(XT_D_COOKIE
,
7724 "soup_cookie_jar_add_cookie: reject %s\n",
7726 if (save_rejected_cookies
) {
7727 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
7728 show_oops_s("can't open reject cookie file");
7731 fseek(r_cookie_f
, 0, SEEK_END
);
7732 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7733 cookie
->http_only
? "#HttpOnly_" : "",
7735 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
7737 cookie
->secure
? "TRUE" : "FALSE",
7739 (gulong
)soup_date_to_time_t(cookie
->expires
) :
7746 if (!allow_volatile_cookies
)
7750 if (cookie
->expires
== NULL
&& session_timeout
) {
7751 soup_cookie_set_expires(cookie
,
7752 soup_date_new_from_now(session_timeout
));
7753 print_cookie("modified add cookie", cookie
);
7756 /* see if we are white listed for persistence */
7757 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
7758 /* add to persistent jar */
7759 c
= soup_cookie_copy(cookie
);
7760 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
7761 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
7764 /* add to session jar */
7765 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
7766 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
7772 char file
[PATH_MAX
];
7774 set_hook((void *)&_soup_cookie_jar_add_cookie
,
7775 "soup_cookie_jar_add_cookie");
7776 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
7777 "soup_cookie_jar_delete_cookie");
7779 if (cookies_enabled
== 0)
7783 * the following code is intricate due to overriding several libsoup
7785 * do not alter order of these operations.
7788 /* rejected cookies */
7789 if (save_rejected_cookies
)
7790 snprintf(rc_fname
, sizeof file
, "%s/rejected.txt", work_dir
);
7792 /* persistent cookies */
7793 snprintf(file
, sizeof file
, "%s/cookies.txt", work_dir
);
7794 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
7796 /* session cookies */
7797 s_cookiejar
= soup_cookie_jar_new();
7798 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
7799 cookie_policy
, (void *)NULL
);
7802 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
7806 setup_proxy(char *uri
)
7809 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
7810 soup_uri_free(proxy_uri
);
7814 if (http_proxy
!= uri
) {
7821 http_proxy
= g_strdup(uri
);
7822 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
7823 proxy_uri
= soup_uri_new(http_proxy
);
7824 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
7829 send_cmd_to_socket(char *cmd
)
7832 struct sockaddr_un sa
;
7834 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7835 warnx("%s: socket", __func__
);
7839 sa
.sun_family
= AF_UNIX
;
7840 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7841 work_dir
, XT_SOCKET_FILE
);
7844 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7845 warnx("%s: connect", __func__
);
7849 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
7850 warnx("%s: send", __func__
);
7861 socket_watcher(gpointer data
, gint fd
, GdkInputCondition cond
)
7864 char str
[XT_MAX_URL_LENGTH
];
7865 socklen_t t
= sizeof(struct sockaddr_un
);
7866 struct sockaddr_un sa
;
7872 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
7877 if (getpeereid(s
, &uid
, &gid
) == -1) {
7881 if (uid
!= getuid() || gid
!= getgid()) {
7882 warnx("unauthorized user");
7888 warnx("not a valid user");
7892 n
= recv(s
, str
, sizeof(str
), 0);
7896 tt
= TAILQ_LAST(&tabs
, tab_list
);
7897 cmd_execute(tt
, str
);
7904 struct sockaddr_un sa
;
7906 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7907 warn("is_running: socket");
7911 sa
.sun_family
= AF_UNIX
;
7912 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7913 work_dir
, XT_SOCKET_FILE
);
7916 /* connect to see if there is a listener */
7917 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
7918 rv
= 0; /* not running */
7920 rv
= 1; /* already running */
7931 struct sockaddr_un sa
;
7933 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7934 warn("build_socket: socket");
7938 sa
.sun_family
= AF_UNIX
;
7939 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
7940 work_dir
, XT_SOCKET_FILE
);
7943 /* connect to see if there is a listener */
7944 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7945 /* no listener so we will */
7946 unlink(sa
.sun_path
);
7948 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
7949 warn("build_socket: bind");
7953 if (listen(s
, 1) == -1) {
7954 warn("build_socket: listen");
7967 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
7968 GtkTreeIter
*iter
, struct tab
*t
)
7972 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
7979 completion_add_uri(const gchar
*uri
)
7983 /* add uri to list_store */
7984 gtk_list_store_append(completion_model
, &iter
);
7985 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
7989 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
7990 GtkTreeIter
*iter
, gpointer user_data
)
7993 gboolean match
= FALSE
;
7995 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8001 match
= match_uri(value
, key
);
8008 completion_add(struct tab
*t
)
8010 /* enable completion for tab */
8011 t
->completion
= gtk_entry_completion_new();
8012 gtk_entry_completion_set_text_column(t
->completion
, 0);
8013 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8014 gtk_entry_completion_set_model(t
->completion
,
8015 GTK_TREE_MODEL(completion_model
));
8016 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8018 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8019 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8020 G_CALLBACK(completion_select_cb
), t
);
8027 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8032 main(int argc
, char *argv
[])
8035 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8036 char conf
[PATH_MAX
] = { '\0' };
8037 char file
[PATH_MAX
];
8038 char *env_proxy
= NULL
;
8041 struct sigaction sact
;
8042 gchar
*priority
= g_strdup("NORMAL");
8046 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8048 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8057 errx(0 , "Version: %s", version
);
8060 strlcpy(conf
, optarg
, sizeof(conf
));
8063 strlcpy(named_session
, optarg
, sizeof(named_session
));
8084 RB_INIT(&downloads
);
8088 TAILQ_INIT(&aliases
);
8094 gnutls_global_init();
8096 /* generate session keys for xtp pages */
8097 generate_xtp_session_key(&dl_session_key
);
8098 generate_xtp_session_key(&hl_session_key
);
8099 generate_xtp_session_key(&cl_session_key
);
8100 generate_xtp_session_key(&fl_session_key
);
8103 gtk_init(&argc
, &argv
);
8104 if (!g_thread_supported())
8105 g_thread_init(NULL
);
8108 bzero(&sact
, sizeof(sact
));
8109 sigemptyset(&sact
.sa_mask
);
8110 sact
.sa_handler
= sigchild
;
8111 sact
.sa_flags
= SA_NOCLDSTOP
;
8112 sigaction(SIGCHLD
, &sact
, NULL
);
8114 /* set download dir */
8115 pwd
= getpwuid(getuid());
8117 errx(1, "invalid user %d", getuid());
8118 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8120 /* set default string settings */
8121 home
= g_strdup("http://www.peereboom.us");
8122 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8123 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
8124 strlcpy(runtime_settings
,"runtime", sizeof runtime_settings
);
8126 /* read config file */
8127 if (strlen(conf
) == 0)
8128 snprintf(conf
, sizeof conf
, "%s/.%s",
8129 pwd
->pw_dir
, XT_CONF_FILE
);
8130 config_parse(conf
, 0);
8132 /* working directory */
8133 if (strlen(work_dir
) == 0)
8134 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
8135 pwd
->pw_dir
, XT_DIR
);
8136 if (stat(work_dir
, &sb
)) {
8137 if (mkdir(work_dir
, S_IRWXU
) == -1)
8138 err(1, "mkdir work_dir");
8139 if (stat(work_dir
, &sb
))
8140 err(1, "stat work_dir");
8142 if (S_ISDIR(sb
.st_mode
) == 0)
8143 errx(1, "%s not a dir", work_dir
);
8144 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8145 warnx("fixing invalid permissions on %s", work_dir
);
8146 if (chmod(work_dir
, S_IRWXU
) == -1)
8150 /* icon cache dir */
8151 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
8152 if (stat(cache_dir
, &sb
)) {
8153 if (mkdir(cache_dir
, S_IRWXU
) == -1)
8154 err(1, "mkdir cache_dir");
8155 if (stat(cache_dir
, &sb
))
8156 err(1, "stat cache_dir");
8158 if (S_ISDIR(sb
.st_mode
) == 0)
8159 errx(1, "%s not a dir", cache_dir
);
8160 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8161 warnx("fixing invalid permissions on %s", cache_dir
);
8162 if (chmod(cache_dir
, S_IRWXU
) == -1)
8167 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
8168 if (stat(certs_dir
, &sb
)) {
8169 if (mkdir(certs_dir
, S_IRWXU
) == -1)
8170 err(1, "mkdir certs_dir");
8171 if (stat(certs_dir
, &sb
))
8172 err(1, "stat certs_dir");
8174 if (S_ISDIR(sb
.st_mode
) == 0)
8175 errx(1, "%s not a dir", certs_dir
);
8176 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8177 warnx("fixing invalid permissions on %s", certs_dir
);
8178 if (chmod(certs_dir
, S_IRWXU
) == -1)
8183 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
8184 work_dir
, XT_SESSIONS_DIR
);
8185 if (stat(sessions_dir
, &sb
)) {
8186 if (mkdir(sessions_dir
, S_IRWXU
) == -1)
8187 err(1, "mkdir sessions_dir");
8188 if (stat(sessions_dir
, &sb
))
8189 err(1, "stat sessions_dir");
8191 if (S_ISDIR(sb
.st_mode
) == 0)
8192 errx(1, "%s not a dir", sessions_dir
);
8193 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8194 warnx("fixing invalid permissions on %s", sessions_dir
);
8195 if (chmod(sessions_dir
, S_IRWXU
) == -1)
8198 /* runtime settings that can override config file */
8199 if (runtime_settings
[0] != '\0')
8200 config_parse(runtime_settings
, 1);
8203 if (!strcmp(download_dir
, pwd
->pw_dir
))
8204 strlcat(download_dir
, "/downloads", sizeof download_dir
);
8205 if (stat(download_dir
, &sb
)) {
8206 if (mkdir(download_dir
, S_IRWXU
) == -1)
8207 err(1, "mkdir download_dir");
8208 if (stat(download_dir
, &sb
))
8209 err(1, "stat download_dir");
8211 if (S_ISDIR(sb
.st_mode
) == 0)
8212 errx(1, "%s not a dir", download_dir
);
8213 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8214 warnx("fixing invalid permissions on %s", download_dir
);
8215 if (chmod(download_dir
, S_IRWXU
) == -1)
8219 /* favorites file */
8220 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
8221 if (stat(file
, &sb
)) {
8222 warnx("favorites file doesn't exist, creating it");
8223 if ((f
= fopen(file
, "w")) == NULL
)
8224 err(1, "favorites");
8229 session
= webkit_get_default_session();
8230 /* XXX ssl-priority property not quite available yet */
8231 if (is_g_object_setting(G_OBJECT(session
), "ssl-priority"))
8232 g_object_set(G_OBJECT(session
), "ssl-priority", priority
,
8235 warnx("session does not have \"ssl-priority\" property");
8240 if (stat(ssl_ca_file
, &sb
)) {
8241 warn("no CA file: %s", ssl_ca_file
);
8242 g_free(ssl_ca_file
);
8245 g_object_set(session
,
8246 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
8247 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8252 env_proxy
= getenv("http_proxy");
8254 setup_proxy(env_proxy
);
8256 setup_proxy(http_proxy
);
8259 send_cmd_to_socket(argv
[0]);
8263 /* set some connection parameters */
8264 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8265 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8268 /* see if there is already an xxxterm running */
8269 if (single_instance
&& is_running()) {
8271 warnx("already running");
8277 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8278 send_cmd_to_socket(cmd
);
8288 /* uri completion */
8289 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8294 if (save_global_history
)
8295 restore_global_history();
8297 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
8298 restore_saved_tabs();
8300 a
.s
= named_session
;
8301 a
.i
= XT_SES_DONOTHING
;
8302 open_tabs(NULL
, &a
);
8306 create_new_tab(argv
[0], NULL
, focus
);
8313 if (TAILQ_EMPTY(&tabs
))
8314 create_new_tab(home
, NULL
, 1);
8317 if ((s
= build_socket()) != -1)
8318 gdk_input_add(s
, GDK_INPUT_READ
, socket_watcher
, NULL
);
8322 gnutls_global_deinit();