3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 * create privacy browsing
26 * - encrypted local data
43 #include <sys/types.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
50 #include "freebsd/util.h"
56 #include <sys/queue.h>
57 #include <sys/resource.h>
58 #include <sys/socket.h>
64 #include <gdk/gdkkeysyms.h>
66 #if GTK_CHECK_VERSION(3,0,0)
67 /* we still use GDK_* instead of GDK_KEY_* */
68 #include <gdk/gdkkeysyms-compat.h>
71 #include <webkit/webkit.h>
72 #include <libsoup/soup.h>
73 #include <gnutls/gnutls.h>
74 #include <JavaScriptCore/JavaScript.h>
75 #include <gnutls/x509.h>
77 #include "javascript.h"
80 javascript.h borrowed from vimprobable2 under the following license:
82 Copyright (c) 2009 Leon Winter
83 Copyright (c) 2009 Hannes Schueller
84 Copyright (c) 2009 Matto Fransen
86 Permission is hereby granted, free of charge, to any person obtaining a copy
87 of this software and associated documentation files (the "Software"), to deal
88 in the Software without restriction, including without limitation the rights
89 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90 copies of the Software, and to permit persons to whom the Software is
91 furnished to do so, subject to the following conditions:
93 The above copyright notice and this permission notice shall be included in
94 all copies or substantial portions of the Software.
96 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
105 static char *version
= "$xxxterm$";
107 /* hooked functions */
108 void (*_soup_cookie_jar_add_cookie
)(SoupCookieJar
*, SoupCookie
*);
109 void (*_soup_cookie_jar_delete_cookie
)(SoupCookieJar
*,
114 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
115 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
116 #define XT_D_MOVE 0x0001
117 #define XT_D_KEY 0x0002
118 #define XT_D_TAB 0x0004
119 #define XT_D_URL 0x0008
120 #define XT_D_CMD 0x0010
121 #define XT_D_NAV 0x0020
122 #define XT_D_DOWNLOAD 0x0040
123 #define XT_D_CONFIG 0x0080
124 #define XT_D_JS 0x0100
125 #define XT_D_FAVORITE 0x0200
126 #define XT_D_PRINTING 0x0400
127 #define XT_D_COOKIE 0x0800
128 #define XT_D_KEYBINDING 0x1000
129 #define XT_D_CLIP 0x2000
130 #define XT_D_BUFFERCMD 0x4000
131 u_int32_t swm_debug
= 0
149 #define DPRINTF(x...)
150 #define DNPRINTF(n,x...)
153 #define LENGTH(x) (sizeof x / sizeof x[0])
154 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
155 ~(GDK_BUTTON1_MASK) & \
156 ~(GDK_BUTTON2_MASK) & \
157 ~(GDK_BUTTON3_MASK) & \
158 ~(GDK_BUTTON4_MASK) & \
161 #define XT_NOMARKS (('z' - 'a' + 1) * 2 + 10)
172 TAILQ_ENTRY(tab
) entry
;
174 GtkWidget
*tab_content
;
183 GtkWidget
*uri_entry
;
184 GtkWidget
*search_entry
;
186 GtkWidget
*browser_win
;
187 GtkWidget
*statusbar_box
;
189 GtkWidget
*statusbar
;
190 GtkWidget
*buffercmd
;
201 GtkWidget
*js_toggle
;
202 GtkEntryCompletion
*completion
;
206 WebKitWebHistoryItem
*item
;
207 WebKitWebBackForwardList
*bfl
;
210 WebKitNetworkRequest
*icon_request
;
211 WebKitDownload
*icon_download
;
212 gchar
*icon_dest_uri
;
214 /* adjustments for browser */
217 GtkAdjustment
*adjust_h
;
218 GtkAdjustment
*adjust_v
;
224 int xtp_meaning
; /* identifies dls/favorites */
226 int popup
; /* 1 if cmd_entry has popup visible */
231 #define XT_HINT_NONE (0)
232 #define XT_HINT_NUMERICAL (1)
233 #define XT_HINT_ALPHANUM (2)
237 /* custom stylesheet */
247 WebKitWebSettings
*settings
;
251 double mark
[XT_NOMARKS
];
253 TAILQ_HEAD(tab_list
, tab
);
256 RB_ENTRY(history
) entry
;
260 RB_HEAD(history_list
, history
);
263 RB_ENTRY(download
) entry
;
265 WebKitDownload
*download
;
268 RB_HEAD(download_list
, download
);
271 RB_ENTRY(domain
) entry
;
273 int handy
; /* app use */
275 RB_HEAD(domain_list
, domain
);
278 TAILQ_ENTRY(undo
) entry
;
281 int back
; /* Keeps track of how many back
282 * history items there are. */
284 TAILQ_HEAD(undo_tailq
, undo
);
288 TAILQ_ENTRY(sp
) entry
;
290 TAILQ_HEAD(sp_list
, sp
);
292 struct command_entry
{
294 TAILQ_ENTRY(command_entry
) entry
;
296 TAILQ_HEAD(command_list
, command_entry
);
298 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
299 int next_download_id
= 1;
308 #define XT_NAME ("XXXTerm")
309 #define XT_DIR (".xxxterm")
310 #define XT_CACHE_DIR ("cache")
311 #define XT_CERT_DIR ("certs/")
312 #define XT_SESSIONS_DIR ("sessions/")
313 #define XT_CONF_FILE ("xxxterm.conf")
314 #define XT_FAVS_FILE ("favorites")
315 #define XT_QMARKS_FILE ("quickmarks")
316 #define XT_SAVED_TABS_FILE ("main_session")
317 #define XT_RESTART_TABS_FILE ("restart_tabs")
318 #define XT_SOCKET_FILE ("socket")
319 #define XT_HISTORY_FILE ("history")
320 #define XT_REJECT_FILE ("rejected.txt")
321 #define XT_COOKIE_FILE ("cookies.txt")
322 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
323 #define XT_CB_HANDLED (TRUE)
324 #define XT_CB_PASSTHROUGH (FALSE)
325 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
326 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
327 #define XT_DLMAN_REFRESH "10"
328 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
329 "td{overflow: hidden;" \
330 " padding: 2px 2px 2px 2px;" \
331 " border: 1px solid black;" \
332 " vertical-align:top;" \
333 " word-wrap: break-word}\n" \
334 "tr:hover{background: #ffff99}\n" \
335 "th{background-color: #cccccc;" \
336 " border: 1px solid black}\n" \
337 "table{width: 100%%;" \
338 " border: 1px black solid;" \
339 " border-collapse:collapse}\n" \
341 "border: 1px solid black;" \
344 ".progress-inner{float: left;" \
346 " background: green}\n" \
347 ".dlstatus{font-size: small;" \
348 " text-align: center}\n" \
350 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
351 #define XT_MAX_UNDO_CLOSE_TAB (32)
352 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
353 #define XT_PRINT_EXTRA_MARGIN 10
354 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
355 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
358 #define XT_COLOR_RED "#cc0000"
359 #define XT_COLOR_YELLOW "#ffff66"
360 #define XT_COLOR_BLUE "lightblue"
361 #define XT_COLOR_GREEN "#99ff66"
362 #define XT_COLOR_WHITE "white"
363 #define XT_COLOR_BLACK "black"
365 #define XT_COLOR_CT_BACKGROUND "#000000"
366 #define XT_COLOR_CT_INACTIVE "#dddddd"
367 #define XT_COLOR_CT_ACTIVE "#bbbb00"
368 #define XT_COLOR_CT_SEPARATOR "#555555"
370 #define XT_COLOR_SB_SEPARATOR "#555555"
372 #define XT_PROTO_DELIM "://"
375 * xxxterm "protocol" (xtp)
376 * We use this for managing stuff like downloads and favorites. They
377 * make magical HTML pages in memory which have xxxt:// links in order
378 * to communicate with xxxterm's internals. These links take the format:
379 * xxxt://class/session_key/action/arg
381 * Don't begin xtp class/actions as 0. atoi returns that on error.
383 * Typically we have not put addition of items in this framework, as
384 * adding items is either done via an ex-command or via a keybinding instead.
387 #define XT_XTP_STR "xxxt://"
389 /* XTP classes (xxxt://<class>) */
390 #define XT_XTP_INVALID 0 /* invalid */
391 #define XT_XTP_DL 1 /* downloads */
392 #define XT_XTP_HL 2 /* history */
393 #define XT_XTP_CL 3 /* cookies */
394 #define XT_XTP_FL 4 /* favorites */
396 /* XTP download actions */
397 #define XT_XTP_DL_LIST 1
398 #define XT_XTP_DL_CANCEL 2
399 #define XT_XTP_DL_REMOVE 3
401 /* XTP history actions */
402 #define XT_XTP_HL_LIST 1
403 #define XT_XTP_HL_REMOVE 2
405 /* XTP cookie actions */
406 #define XT_XTP_CL_LIST 1
407 #define XT_XTP_CL_REMOVE 2
409 /* XTP cookie actions */
410 #define XT_XTP_FL_LIST 1
411 #define XT_XTP_FL_REMOVE 2
414 #define XT_MOVE_INVALID (0)
415 #define XT_MOVE_DOWN (1)
416 #define XT_MOVE_UP (2)
417 #define XT_MOVE_BOTTOM (3)
418 #define XT_MOVE_TOP (4)
419 #define XT_MOVE_PAGEDOWN (5)
420 #define XT_MOVE_PAGEUP (6)
421 #define XT_MOVE_HALFDOWN (7)
422 #define XT_MOVE_HALFUP (8)
423 #define XT_MOVE_LEFT (9)
424 #define XT_MOVE_FARLEFT (10)
425 #define XT_MOVE_RIGHT (11)
426 #define XT_MOVE_FARRIGHT (12)
427 #define XT_MOVE_PERCENT (13)
429 #define XT_QMARK_SET (0)
430 #define XT_QMARK_OPEN (1)
431 #define XT_QMARK_TAB (2)
433 #define XT_MARK_SET (0)
434 #define XT_MARK_GOTO (1)
436 #define XT_TAB_LAST (-4)
437 #define XT_TAB_FIRST (-3)
438 #define XT_TAB_PREV (-2)
439 #define XT_TAB_NEXT (-1)
440 #define XT_TAB_INVALID (0)
441 #define XT_TAB_NEW (1)
442 #define XT_TAB_DELETE (2)
443 #define XT_TAB_DELQUIT (3)
444 #define XT_TAB_OPEN (4)
445 #define XT_TAB_UNDO_CLOSE (5)
446 #define XT_TAB_SHOW (6)
447 #define XT_TAB_HIDE (7)
448 #define XT_TAB_NEXTSTYLE (8)
450 #define XT_NAV_INVALID (0)
451 #define XT_NAV_BACK (1)
452 #define XT_NAV_FORWARD (2)
453 #define XT_NAV_RELOAD (3)
455 #define XT_FOCUS_INVALID (0)
456 #define XT_FOCUS_URI (1)
457 #define XT_FOCUS_SEARCH (2)
459 #define XT_SEARCH_INVALID (0)
460 #define XT_SEARCH_NEXT (1)
461 #define XT_SEARCH_PREV (2)
463 #define XT_PASTE_CURRENT_TAB (0)
464 #define XT_PASTE_NEW_TAB (1)
466 #define XT_ZOOM_IN (-1)
467 #define XT_ZOOM_OUT (-2)
468 #define XT_ZOOM_NORMAL (100)
470 #define XT_URL_SHOW (1)
471 #define XT_URL_HIDE (2)
473 #define XT_WL_TOGGLE (1<<0)
474 #define XT_WL_ENABLE (1<<1)
475 #define XT_WL_DISABLE (1<<2)
476 #define XT_WL_FQDN (1<<3) /* default */
477 #define XT_WL_TOPLEVEL (1<<4)
478 #define XT_WL_PERSISTENT (1<<5)
479 #define XT_WL_SESSION (1<<6)
480 #define XT_WL_RELOAD (1<<7)
482 #define XT_SHOW (1<<7)
483 #define XT_DELETE (1<<8)
484 #define XT_SAVE (1<<9)
485 #define XT_OPEN (1<<10)
487 #define XT_CMD_OPEN (0)
488 #define XT_CMD_OPEN_CURRENT (1)
489 #define XT_CMD_TABNEW (2)
490 #define XT_CMD_TABNEW_CURRENT (3)
492 #define XT_STATUS_NOTHING (0)
493 #define XT_STATUS_LINK (1)
494 #define XT_STATUS_URI (2)
495 #define XT_STATUS_LOADING (3)
497 #define XT_SES_DONOTHING (0)
498 #define XT_SES_CLOSETABS (1)
500 #define XT_BM_NORMAL (0)
501 #define XT_BM_WHITELIST (1)
502 #define XT_BM_KIOSK (2)
504 #define XT_PREFIX (1<<0)
505 #define XT_USERARG (1<<1)
506 #define XT_URLARG (1<<2)
507 #define XT_INTARG (1<<3)
509 #define XT_TABS_NORMAL 0
510 #define XT_TABS_COMPACT 1
512 #define XT_BUFCMD_SZ (8)
520 TAILQ_ENTRY(mime_type
) entry
;
522 TAILQ_HEAD(mime_type_list
, mime_type
);
528 TAILQ_ENTRY(alias
) entry
;
530 TAILQ_HEAD(alias_list
, alias
);
532 /* settings that require restart */
533 int tabless
= 0; /* allow only 1 tab */
534 int enable_socket
= 0;
535 int single_instance
= 0; /* only allow one xxxterm to run */
536 int fancy_bar
= 1; /* fancy toolbar */
537 int browser_mode
= XT_BM_NORMAL
;
538 int enable_localstorage
= 0;
539 char *statusbar_elems
= NULL
;
541 /* runtime settings */
542 int show_tabs
= 1; /* show tabs on notebook */
543 int tab_style
= XT_TABS_NORMAL
; /* tab bar style */
544 int show_url
= 1; /* show url toolbar on notebook */
545 int show_statusbar
= 0; /* vimperator style status bar */
546 int ctrl_click_focus
= 0; /* ctrl click gets focus */
547 int cookies_enabled
= 1; /* enable cookies */
548 int read_only_cookies
= 0; /* enable to not write cookies */
549 int enable_scripts
= 1;
550 int enable_plugins
= 0;
551 gfloat default_zoom_level
= 1.0;
552 char default_script
[PATH_MAX
];
553 int window_height
= 768;
554 int window_width
= 1024;
555 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
556 int refresh_interval
= 10; /* download refresh interval */
557 int enable_cookie_whitelist
= 0;
558 int enable_js_whitelist
= 0;
559 int session_timeout
= 3600; /* cookie session timeout */
560 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
561 char *ssl_ca_file
= NULL
;
562 char *resource_dir
= NULL
;
563 gboolean ssl_strict_certs
= FALSE
;
564 int append_next
= 1; /* append tab after current tab */
566 char *search_string
= NULL
;
567 char *http_proxy
= NULL
;
568 char download_dir
[PATH_MAX
];
569 char runtime_settings
[PATH_MAX
]; /* override of settings */
570 int allow_volatile_cookies
= 0;
571 int save_global_history
= 0; /* save global history to disk */
572 char *user_agent
= NULL
;
573 int save_rejected_cookies
= 0;
574 int session_autosave
= 0;
575 int guess_search
= 0;
576 int dns_prefetch
= FALSE
;
577 gint max_connections
= 25;
578 gint max_host_connections
= 5;
579 gint enable_spell_checking
= 0;
580 char *spell_check_languages
= NULL
;
581 int xterm_workaround
= 0;
582 char *url_regex
= NULL
;
584 char *cmd_font_name
= NULL
;
585 char *oops_font_name
= NULL
;
586 char *statusbar_font_name
= NULL
;
587 char *tabbar_font_name
= NULL
;
588 PangoFontDescription
*cmd_font
;
589 PangoFontDescription
*oops_font
;
590 PangoFontDescription
*statusbar_font
;
591 PangoFontDescription
*tabbar_font
;
592 char *qmarks
[XT_NOMARKS
];
594 int btn_down
; /* M1 down in any wv */
595 regex_t url_re
; /* guess_search regex */
599 int set_browser_mode(struct settings
*, char *);
600 int set_cookie_policy(struct settings
*, char *);
601 int set_download_dir(struct settings
*, char *);
602 int set_default_script(struct settings
*, char *);
603 int set_runtime_dir(struct settings
*, char *);
604 int set_tab_style(struct settings
*, char *);
605 int set_work_dir(struct settings
*, char *);
606 int add_alias(struct settings
*, char *);
607 int add_mime_type(struct settings
*, char *);
608 int add_cookie_wl(struct settings
*, char *);
609 int add_js_wl(struct settings
*, char *);
610 int add_kb(struct settings
*, char *);
611 void button_set_stockid(GtkWidget
*, char *);
612 GtkWidget
* create_button(char *, char *, int);
614 char *get_browser_mode(struct settings
*);
615 char *get_cookie_policy(struct settings
*);
616 char *get_download_dir(struct settings
*);
617 char *get_default_script(struct settings
*);
618 char *get_runtime_dir(struct settings
*);
619 char *get_tab_style(struct settings
*);
620 char *get_work_dir(struct settings
*);
621 void startpage_add(const char *, ...);
623 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
624 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
625 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
626 void walk_kb(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
627 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
629 void recalc_tabs(void);
630 void recolor_compact_tabs(void);
631 void set_current_tab(int page_num
);
632 gboolean
update_statusbar_position(GtkAdjustment
* adjustment
, gpointer data
);
633 void marks_clear(struct tab
*t
);
635 int set_http_proxy(char *);
638 int (*set
)(struct settings
*, char *);
639 char *(*get
)(struct settings
*);
640 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
643 struct special s_browser_mode
= {
649 struct special s_cookie
= {
655 struct special s_alias
= {
661 struct special s_mime
= {
667 struct special s_js
= {
673 struct special s_kb
= {
679 struct special s_cookie_wl
= {
685 struct special s_default_script
= {
691 struct special s_download_dir
= {
697 struct special s_work_dir
= {
703 struct special s_tab_style
= {
712 #define XT_S_INVALID (0)
715 #define XT_S_FLOAT (3)
717 #define XT_SF_RESTART (1<<0)
718 #define XT_SF_RUNTIME (1<<1)
723 int (*activate
)(char *);
725 { "append_next", XT_S_INT
, 0, &append_next
, NULL
, NULL
},
726 { "allow_volatile_cookies", XT_S_INT
, 0, &allow_volatile_cookies
, NULL
, NULL
},
727 { "browser_mode", XT_S_INT
, 0, NULL
, NULL
,&s_browser_mode
},
728 { "cookie_policy", XT_S_INT
, 0, NULL
, NULL
,&s_cookie
},
729 { "cookies_enabled", XT_S_INT
, 0, &cookies_enabled
, NULL
, NULL
},
730 { "ctrl_click_focus", XT_S_INT
, 0, &ctrl_click_focus
, NULL
, NULL
},
731 { "default_zoom_level", XT_S_FLOAT
, 0, NULL
, NULL
, NULL
, &default_zoom_level
},
732 { "default_script", XT_S_STR
, 0, NULL
, NULL
,&s_default_script
},
733 { "download_dir", XT_S_STR
, 0, NULL
, NULL
,&s_download_dir
},
734 { "enable_cookie_whitelist", XT_S_INT
, 0, &enable_cookie_whitelist
, NULL
, NULL
},
735 { "enable_js_whitelist", XT_S_INT
, 0, &enable_js_whitelist
, NULL
, NULL
},
736 { "enable_localstorage", XT_S_INT
, 0, &enable_localstorage
, NULL
, NULL
},
737 { "enable_plugins", XT_S_INT
, 0, &enable_plugins
, NULL
, NULL
},
738 { "enable_scripts", XT_S_INT
, 0, &enable_scripts
, NULL
, NULL
},
739 { "enable_socket", XT_S_INT
, XT_SF_RESTART
,&enable_socket
, NULL
, NULL
},
740 { "enable_spell_checking", XT_S_INT
, 0, &enable_spell_checking
, NULL
, NULL
},
741 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
,&fancy_bar
, NULL
, NULL
},
742 { "guess_search", XT_S_INT
, 0, &guess_search
, NULL
, NULL
},
743 { "home", XT_S_STR
, 0, NULL
, &home
, NULL
},
744 { "http_proxy", XT_S_STR
, 0, NULL
, &http_proxy
, NULL
, NULL
, set_http_proxy
},
745 { "icon_size", XT_S_INT
, 0, &icon_size
, NULL
, NULL
},
746 { "max_connections", XT_S_INT
, XT_SF_RESTART
,&max_connections
, NULL
, NULL
},
747 { "max_host_connections", XT_S_INT
, XT_SF_RESTART
,&max_host_connections
, NULL
, NULL
},
748 { "read_only_cookies", XT_S_INT
, 0, &read_only_cookies
, NULL
, NULL
},
749 { "refresh_interval", XT_S_INT
, 0, &refresh_interval
, NULL
, NULL
},
750 { "resource_dir", XT_S_STR
, 0, NULL
, &resource_dir
, NULL
},
751 { "search_string", XT_S_STR
, 0, NULL
, &search_string
, NULL
},
752 { "save_global_history", XT_S_INT
, XT_SF_RESTART
,&save_global_history
, NULL
, NULL
},
753 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
,&save_rejected_cookies
, NULL
, NULL
},
754 { "session_timeout", XT_S_INT
, 0, &session_timeout
, NULL
, NULL
},
755 { "session_autosave", XT_S_INT
, 0, &session_autosave
, NULL
, NULL
},
756 { "single_instance", XT_S_INT
, XT_SF_RESTART
,&single_instance
, NULL
, NULL
},
757 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
758 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
759 { "show_statusbar", XT_S_INT
, 0, &show_statusbar
, NULL
, NULL
},
760 { "spell_check_languages", XT_S_STR
, 0, NULL
, &spell_check_languages
, NULL
},
761 { "ssl_ca_file", XT_S_STR
, 0, NULL
, &ssl_ca_file
, NULL
},
762 { "ssl_strict_certs", XT_S_INT
, 0, &ssl_strict_certs
, NULL
, NULL
},
763 { "statusbar_elems", XT_S_STR
, 0, NULL
, &statusbar_elems
, NULL
},
764 { "tab_style", XT_S_STR
, 0, NULL
, NULL
,&s_tab_style
},
765 { "url_regex", XT_S_STR
, 0, NULL
, &url_regex
, NULL
},
766 { "user_agent", XT_S_STR
, 0, NULL
, &user_agent
, NULL
},
767 { "window_height", XT_S_INT
, 0, &window_height
, NULL
, NULL
},
768 { "window_width", XT_S_INT
, 0, &window_width
, NULL
, NULL
},
769 { "work_dir", XT_S_STR
, 0, NULL
, NULL
,&s_work_dir
},
770 { "xterm_workaround", XT_S_INT
, 0, &xterm_workaround
, NULL
, NULL
},
773 { "cmd_font", XT_S_STR
, 0, NULL
, &cmd_font_name
, NULL
},
774 { "oops_font", XT_S_STR
, 0, NULL
, &oops_font_name
, NULL
},
775 { "statusbar_font", XT_S_STR
, 0, NULL
, &statusbar_font_name
, NULL
},
776 { "tabbar_font", XT_S_STR
, 0, NULL
, &tabbar_font_name
, NULL
},
778 /* runtime settings */
779 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
780 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
781 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
782 { "keybinding", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_kb
},
783 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
786 int about(struct tab
*, struct karg
*);
787 int blank(struct tab
*, struct karg
*);
788 int ca_cmd(struct tab
*, struct karg
*);
789 int cookie_show_wl(struct tab
*, struct karg
*);
790 int js_show_wl(struct tab
*, struct karg
*);
791 int help(struct tab
*, struct karg
*);
792 int set(struct tab
*, struct karg
*);
793 int stats(struct tab
*, struct karg
*);
794 int marco(struct tab
*, struct karg
*);
795 int startpage(struct tab
*, struct karg
*);
796 const char * marco_message(int *);
797 int xtp_page_cl(struct tab
*, struct karg
*);
798 int xtp_page_dl(struct tab
*, struct karg
*);
799 int xtp_page_fl(struct tab
*, struct karg
*);
800 int xtp_page_hl(struct tab
*, struct karg
*);
801 void xt_icon_from_file(struct tab
*, char *);
802 const gchar
*get_uri(struct tab
*);
803 const gchar
*get_title(struct tab
*, bool);
805 #define XT_URI_ABOUT ("about:")
806 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
807 #define XT_URI_ABOUT_ABOUT ("about")
808 #define XT_URI_ABOUT_BLANK ("blank")
809 #define XT_URI_ABOUT_CERTS ("certs")
810 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
811 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
812 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
813 #define XT_URI_ABOUT_FAVORITES ("favorites")
814 #define XT_URI_ABOUT_HELP ("help")
815 #define XT_URI_ABOUT_HISTORY ("history")
816 #define XT_URI_ABOUT_JSWL ("jswl")
817 #define XT_URI_ABOUT_SET ("set")
818 #define XT_URI_ABOUT_STATS ("stats")
819 #define XT_URI_ABOUT_MARCO ("marco")
820 #define XT_URI_ABOUT_STARTPAGE ("startpage")
824 int (*func
)(struct tab
*, struct karg
*);
826 { XT_URI_ABOUT_ABOUT
, about
},
827 { XT_URI_ABOUT_BLANK
, blank
},
828 { XT_URI_ABOUT_CERTS
, ca_cmd
},
829 { XT_URI_ABOUT_COOKIEWL
, cookie_show_wl
},
830 { XT_URI_ABOUT_COOKIEJAR
, xtp_page_cl
},
831 { XT_URI_ABOUT_DOWNLOADS
, xtp_page_dl
},
832 { XT_URI_ABOUT_FAVORITES
, xtp_page_fl
},
833 { XT_URI_ABOUT_HELP
, help
},
834 { XT_URI_ABOUT_HISTORY
, xtp_page_hl
},
835 { XT_URI_ABOUT_JSWL
, js_show_wl
},
836 { XT_URI_ABOUT_SET
, set
},
837 { XT_URI_ABOUT_STATS
, stats
},
838 { XT_URI_ABOUT_MARCO
, marco
},
839 { XT_URI_ABOUT_STARTPAGE
, startpage
},
842 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
843 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
844 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
845 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
846 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
847 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
848 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
851 extern char *__progname
;
854 GtkWidget
*main_window
;
855 GtkNotebook
*notebook
;
857 GtkWidget
*arrow
, *abtn
;
858 struct tab_list tabs
;
859 struct history_list hl
;
860 struct download_list downloads
;
861 struct domain_list c_wl
;
862 struct domain_list js_wl
;
863 struct undo_tailq undos
;
864 struct keybinding_list kbl
;
866 struct command_list chl
;
867 struct command_entry
*history_at
;
869 int updating_dl_tabs
= 0;
870 int updating_hl_tabs
= 0;
871 int updating_cl_tabs
= 0;
872 int updating_fl_tabs
= 0;
873 int cmd_history_count
= 0;
875 uint64_t blocked_cookies
= 0;
876 char named_session
[PATH_MAX
];
877 GtkListStore
*completion_model
;
878 GtkListStore
*buffers_store
;
880 void xxx_dir(char *);
881 int icon_size_map(int);
882 void completion_add(struct tab
*);
883 void completion_add_uri(const gchar
*);
886 cmd_history_delete(void)
888 struct command_entry
*c
;
890 c
= TAILQ_LAST(&chl
, command_list
);
894 TAILQ_REMOVE(&chl
, c
, entry
);
901 cmd_history_add(char *l
)
903 struct command_entry
*c
;
908 c
= g_malloc0(sizeof *c
);
909 c
->line
= g_strdup_printf(":%s", l
);
912 TAILQ_INSERT_HEAD(&chl
, c
, entry
);
914 if (cmd_history_count
> 1000)
915 cmd_history_delete();
918 /* marks and quickmarks array storage.
919 * first a-z, then A-Z, then 0-9 */
926 if (i
>= 0 && i
<= 'z' - 'a')
930 if (i
>= 0 && i
<= 'Z' - 'A')
945 if (m
>= 'a' && m
<= 'z')
946 return ret
+ m
- 'a';
948 ret
+= 'z' - 'a' + 1;
949 if (m
>= 'A' && m
<= 'Z')
950 return ret
+ m
- 'A';
952 ret
+= 'Z' - 'A' + 1;
953 if (m
>= '0' && m
<= '9')
954 return ret
+ m
- '0';
963 int saved_errno
, status
;
968 while ((pid
= waitpid(WAIT_ANY
, &status
, WNOHANG
)) != 0) {
972 if (errno
!= ECHILD
) {
974 clog_warn("sigchild: waitpid:");
980 if (WIFEXITED(status
)) {
981 if (WEXITSTATUS(status
) != 0) {
983 clog_warnx("sigchild: child exit status: %d",
984 WEXITSTATUS(status));
989 clog_warnx("sigchild: child is terminated abnormally");
998 is_g_object_setting(GObject
*o
, char *str
)
1000 guint n_props
= 0, i
;
1001 GParamSpec
**proplist
;
1003 if (! G_IS_OBJECT(o
))
1006 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
1009 for (i
=0; i
< n_props
; i
++) {
1010 if (! strcmp(proplist
[i
]->name
, str
))
1017 get_html_page(gchar
*title
, gchar
*body
, gchar
*head
, bool addstyles
)
1021 r
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
1023 "<title>%s</title>\n"
1032 addstyles
? XT_PAGE_STYLE
: "",
1041 * Display a web page from a HTML string in memory, rather than from a URL
1044 load_webkit_string(struct tab
*t
, const char *str
, gchar
*title
)
1046 char file
[PATH_MAX
];
1049 /* we set this to indicate we want to manually do navaction */
1051 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
1053 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
1055 /* set t->xtp_meaning */
1056 for (i
= 0; i
< LENGTH(about_list
); i
++)
1057 if (!strcmp(title
, about_list
[i
].name
)) {
1062 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, "file://");
1063 #if GTK_CHECK_VERSION(2, 20, 0)
1064 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
1065 gtk_widget_hide(t
->spinner
);
1067 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[0]);
1068 xt_icon_from_file(t
, file
);
1073 get_current_tab(void)
1077 TAILQ_FOREACH(t
, &tabs
, entry
) {
1078 if (t
->tab_id
== gtk_notebook_get_current_page(notebook
))
1082 warnx("%s: no current tab", __func__
);
1088 set_status(struct tab
*t
, gchar
*s
, int status
)
1096 case XT_STATUS_LOADING
:
1097 type
= g_strdup_printf("Loading: %s", s
);
1100 case XT_STATUS_LINK
:
1101 type
= g_strdup_printf("Link: %s", s
);
1103 t
->status
= g_strdup(gtk_entry_get_text(
1104 GTK_ENTRY(t
->sbe
.statusbar
)));
1108 type
= g_strdup_printf("%s", s
);
1110 t
->status
= g_strdup(type
);
1114 t
->status
= g_strdup(s
);
1116 case XT_STATUS_NOTHING
:
1121 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.statusbar
), s
);
1127 hide_cmd(struct tab
*t
)
1129 history_at
= NULL
; /* just in case */
1130 gtk_widget_hide(t
->cmd
);
1134 show_cmd(struct tab
*t
)
1138 gtk_widget_hide(t
->oops
);
1139 gtk_widget_show(t
->cmd
);
1143 hide_buffers(struct tab
*t
)
1145 gtk_widget_hide(t
->buffers
);
1146 gtk_list_store_clear(buffers_store
);
1156 sort_tabs_by_page_num(struct tab
***stabs
)
1161 num_tabs
= gtk_notebook_get_n_pages(notebook
);
1163 *stabs
= g_malloc0(num_tabs
* sizeof(struct tab
*));
1165 TAILQ_FOREACH(t
, &tabs
, entry
)
1166 (*stabs
)[gtk_notebook_page_num(notebook
, t
->vbox
)] = t
;
1172 buffers_make_list(void)
1175 const gchar
*title
= NULL
;
1177 struct tab
**stabs
= NULL
;
1179 num_tabs
= sort_tabs_by_page_num(&stabs
);
1181 for (i
= 0; i
< num_tabs
; i
++)
1183 gtk_list_store_append(buffers_store
, &iter
);
1184 title
= get_title(stabs
[i
], FALSE
);
1185 gtk_list_store_set(buffers_store
, &iter
,
1186 COL_ID
, i
+ 1, /* Enumerate the tabs starting from 1
1196 show_buffers(struct tab
*t
)
1198 buffers_make_list();
1199 gtk_widget_show(t
->buffers
);
1200 gtk_widget_grab_focus(GTK_WIDGET(t
->buffers
));
1204 toggle_buffers(struct tab
*t
)
1206 if (gtk_widget_get_visible(t
->buffers
))
1213 buffers(struct tab
*t
, struct karg
*args
)
1221 hide_oops(struct tab
*t
)
1223 gtk_widget_hide(t
->oops
);
1227 show_oops(struct tab
*at
, const char *fmt
, ...)
1231 struct tab
*t
= NULL
;
1237 if ((t
= get_current_tab()) == NULL
)
1243 if (vasprintf(&msg
, fmt
, ap
) == -1)
1244 errx(1, "show_oops failed");
1247 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
1248 gtk_widget_hide(t
->cmd
);
1249 gtk_widget_show(t
->oops
);
1256 get_as_string(struct settings
*s
)
1267 warnx("get_as_string skip %s\n", s
->name
);
1268 } else if (s
->type
== XT_S_INT
)
1269 r
= g_strdup_printf("%d", *s
->ival
);
1270 else if (s
->type
== XT_S_STR
)
1271 r
= g_strdup(*s
->sval
);
1272 else if (s
->type
== XT_S_FLOAT
)
1273 r
= g_strdup_printf("%f", *s
->fval
);
1275 r
= g_strdup_printf("INVALID TYPE");
1281 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1286 for (i
= 0; i
< LENGTH(rs
); i
++) {
1287 if (rs
[i
].s
&& rs
[i
].s
->walk
)
1288 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
1290 s
= get_as_string(&rs
[i
]);
1291 cb(&rs
[i
], s
, cb_args
);
1298 set_browser_mode(struct settings
*s
, char *val
)
1300 if (!strcmp(val
, "whitelist")) {
1301 browser_mode
= XT_BM_WHITELIST
;
1302 allow_volatile_cookies
= 0;
1303 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1304 cookies_enabled
= 1;
1305 enable_cookie_whitelist
= 1;
1306 read_only_cookies
= 0;
1307 save_rejected_cookies
= 0;
1308 session_timeout
= 3600;
1310 enable_js_whitelist
= 1;
1311 enable_localstorage
= 0;
1312 } else if (!strcmp(val
, "normal")) {
1313 browser_mode
= XT_BM_NORMAL
;
1314 allow_volatile_cookies
= 0;
1315 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1316 cookies_enabled
= 1;
1317 enable_cookie_whitelist
= 0;
1318 read_only_cookies
= 0;
1319 save_rejected_cookies
= 0;
1320 session_timeout
= 3600;
1322 enable_js_whitelist
= 0;
1323 enable_localstorage
= 1;
1324 } else if (!strcmp(val
, "kiosk")) {
1325 browser_mode
= XT_BM_KIOSK
;
1326 allow_volatile_cookies
= 0;
1327 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1328 cookies_enabled
= 1;
1329 enable_cookie_whitelist
= 0;
1330 read_only_cookies
= 0;
1331 save_rejected_cookies
= 0;
1332 session_timeout
= 3600;
1334 enable_js_whitelist
= 0;
1335 enable_localstorage
= 1;
1345 get_browser_mode(struct settings
*s
)
1349 if (browser_mode
== XT_BM_WHITELIST
)
1350 r
= g_strdup("whitelist");
1351 else if (browser_mode
== XT_BM_NORMAL
)
1352 r
= g_strdup("normal");
1353 else if (browser_mode
== XT_BM_KIOSK
)
1354 r
= g_strdup("kiosk");
1362 set_cookie_policy(struct settings
*s
, char *val
)
1364 if (!strcmp(val
, "no3rdparty"))
1365 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1366 else if (!strcmp(val
, "accept"))
1367 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1368 else if (!strcmp(val
, "reject"))
1369 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1377 get_cookie_policy(struct settings
*s
)
1381 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1382 r
= g_strdup("no3rdparty");
1383 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1384 r
= g_strdup("accept");
1385 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1386 r
= g_strdup("reject");
1394 get_default_script(struct settings
*s
)
1396 if (default_script
[0] == '\0')
1398 return (g_strdup(default_script
));
1402 set_default_script(struct settings
*s
, char *val
)
1405 snprintf(default_script
, sizeof default_script
, "%s/%s",
1406 pwd
->pw_dir
, &val
[1]);
1408 strlcpy(default_script
, val
, sizeof default_script
);
1414 get_download_dir(struct settings
*s
)
1416 if (download_dir
[0] == '\0')
1418 return (g_strdup(download_dir
));
1422 set_download_dir(struct settings
*s
, char *val
)
1425 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1426 pwd
->pw_dir
, &val
[1]);
1428 strlcpy(download_dir
, val
, sizeof download_dir
);
1435 * We use these to prevent people putting xxxt:// URLs on
1436 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1438 #define XT_XTP_SES_KEY_SZ 8
1439 #define XT_XTP_SES_KEY_HEX_FMT \
1440 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1441 char *dl_session_key
; /* downloads */
1442 char *hl_session_key
; /* history list */
1443 char *cl_session_key
; /* cookie list */
1444 char *fl_session_key
; /* favorites list */
1446 char work_dir
[PATH_MAX
];
1447 char certs_dir
[PATH_MAX
];
1448 char cache_dir
[PATH_MAX
];
1449 char sessions_dir
[PATH_MAX
];
1450 char cookie_file
[PATH_MAX
];
1451 SoupURI
*proxy_uri
= NULL
;
1452 SoupSession
*session
;
1453 SoupCookieJar
*s_cookiejar
;
1454 SoupCookieJar
*p_cookiejar
;
1455 char rc_fname
[PATH_MAX
];
1457 struct mime_type_list mtl
;
1458 struct alias_list aliases
;
1461 struct tab
*create_new_tab(char *, struct undo
*, int, int);
1462 void delete_tab(struct tab
*);
1463 void setzoom_webkit(struct tab
*, int);
1464 int run_script(struct tab
*, char *);
1465 int download_rb_cmp(struct download
*, struct download
*);
1466 gboolean
cmd_execute(struct tab
*t
, char *str
);
1469 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1471 return (strcmp(h1
->uri
, h2
->uri
));
1473 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1476 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1478 return (strcmp(d1
->d
, d2
->d
));
1480 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1483 get_work_dir(struct settings
*s
)
1485 if (work_dir
[0] == '\0')
1487 return (g_strdup(work_dir
));
1491 set_work_dir(struct settings
*s
, char *val
)
1494 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1495 pwd
->pw_dir
, &val
[1]);
1497 strlcpy(work_dir
, val
, sizeof work_dir
);
1503 get_tab_style(struct settings
*s
)
1505 if (tab_style
== XT_TABS_NORMAL
)
1506 return (g_strdup("normal"));
1508 return (g_strdup("compact"));
1512 set_tab_style(struct settings
*s
, char *val
)
1514 if (!strcmp(val
, "normal"))
1515 tab_style
= XT_TABS_NORMAL
;
1516 else if (!strcmp(val
, "compact"))
1517 tab_style
= XT_TABS_COMPACT
;
1525 * generate a session key to secure xtp commands.
1526 * pass in a ptr to the key in question and it will
1527 * be modified in place.
1530 generate_xtp_session_key(char **key
)
1532 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1538 /* make a new one */
1539 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1540 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1541 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1542 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1544 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1548 * validate a xtp session key.
1552 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1554 if (strcmp(trusted
, untrusted
) != 0) {
1555 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1564 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1566 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1568 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1570 struct valid_url_types
{
1581 valid_url_type(char *url
)
1585 for (i
= 0; i
< LENGTH(vut
); i
++)
1586 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1593 print_cookie(char *msg
, SoupCookie
*c
)
1599 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1600 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1601 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1602 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1603 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1604 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1605 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1606 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1607 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1608 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1612 walk_alias(struct settings
*s
,
1613 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1618 if (s
== NULL
|| cb
== NULL
) {
1619 show_oops(NULL
, "walk_alias invalid parameters");
1623 TAILQ_FOREACH(a
, &aliases
, entry
) {
1624 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1625 cb(s
, str
, cb_args
);
1631 match_alias(char *url_in
)
1635 char *url_out
= NULL
, *search
, *enc_arg
;
1637 search
= g_strdup(url_in
);
1639 if (strsep(&arg
, " \t") == NULL
) {
1640 show_oops(NULL
, "match_alias: NULL URL");
1644 TAILQ_FOREACH(a
, &aliases
, entry
) {
1645 if (!strcmp(search
, a
->a_name
))
1650 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1653 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1654 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1657 url_out
= g_strdup_printf(a
->a_uri
, "");
1665 guess_url_type(char *url_in
)
1668 char *url_out
= NULL
, *enc_search
= NULL
;
1670 url_out
= match_alias(url_in
);
1671 if (url_out
!= NULL
)
1674 if (guess_search
&& url_regex
&&
1675 !(g_str_has_prefix(url_in
, "http://") ||
1676 g_str_has_prefix(url_in
, "https://"))) {
1677 if (regexec(&url_re
, url_in
, 0, NULL
, 0)) {
1678 /* invalid URI so search instead */
1679 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1680 url_out
= g_strdup_printf(search_string
, enc_search
);
1686 /* XXX not sure about this heuristic */
1687 if (stat(url_in
, &sb
) == 0)
1688 url_out
= g_strdup_printf("file://%s", url_in
);
1690 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1692 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1698 load_uri(struct tab
*t
, gchar
*uri
)
1701 gchar
*newuri
= NULL
;
1707 /* Strip leading spaces. */
1708 while (*uri
&& isspace(*uri
))
1711 if (strlen(uri
) == 0) {
1716 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
1718 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1719 for (i
= 0; i
< LENGTH(about_list
); i
++)
1720 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1721 bzero(&args
, sizeof args
);
1722 about_list
[i
].func(t
, &args
);
1723 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
),
1727 show_oops(t
, "invalid about page");
1731 if (valid_url_type(uri
)) {
1732 newuri
= guess_url_type(uri
);
1736 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1738 webkit_web_view_load_uri(t
->wv
, uri
);
1745 get_uri(struct tab
*t
)
1747 const gchar
*uri
= NULL
;
1749 if (webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
)
1751 if (t
->xtp_meaning
== XT_XTP_TAB_MEANING_NORMAL
) {
1752 uri
= webkit_web_view_get_uri(t
->wv
);
1754 /* use tmp_uri to make sure it is g_freed */
1757 t
->tmp_uri
=g_strdup_printf("%s%s", XT_URI_ABOUT
,
1758 about_list
[t
->xtp_meaning
].name
);
1765 get_title(struct tab
*t
, bool window
)
1767 const gchar
*set
= NULL
, *title
= NULL
;
1768 WebKitLoadStatus status
= webkit_web_view_get_load_status(t
->wv
);
1770 if (status
== WEBKIT_LOAD_PROVISIONAL
|| status
== WEBKIT_LOAD_FAILED
||
1771 t
->xtp_meaning
== XT_XTP_TAB_MEANING_BL
)
1774 title
= webkit_web_view_get_title(t
->wv
);
1775 if ((set
= title
? title
: get_uri(t
)))
1779 set
= window
? XT_NAME
: "(untitled)";
1785 add_alias(struct settings
*s
, char *line
)
1788 struct alias
*a
= NULL
;
1790 if (s
== NULL
|| line
== NULL
) {
1791 show_oops(NULL
, "add_alias invalid parameters");
1796 a
= g_malloc(sizeof(*a
));
1798 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1799 show_oops(NULL
, "add_alias: incomplete alias definition");
1802 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1803 show_oops(NULL
, "add_alias: invalid alias definition");
1807 a
->a_name
= g_strdup(alias
);
1808 a
->a_uri
= g_strdup(l
);
1810 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1812 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1822 add_mime_type(struct settings
*s
, char *line
)
1826 struct mime_type
*m
= NULL
;
1827 int downloadfirst
= 0;
1829 /* XXX this could be smarter */
1831 if (line
== NULL
|| strlen(line
) == 0) {
1832 show_oops(NULL
, "add_mime_type invalid parameters");
1841 m
= g_malloc(sizeof(*m
));
1843 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1844 show_oops(NULL
, "add_mime_type: invalid mime_type");
1847 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1848 mime_type
[strlen(mime_type
) - 1] = '\0';
1853 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1854 show_oops(NULL
, "add_mime_type: invalid mime_type");
1858 m
->mt_type
= g_strdup(mime_type
);
1859 m
->mt_action
= g_strdup(l
);
1860 m
->mt_download
= downloadfirst
;
1862 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1863 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1865 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1875 find_mime_type(char *mime_type
)
1877 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1879 TAILQ_FOREACH(m
, &mtl
, entry
) {
1880 if (m
->mt_default
&&
1881 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1884 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1897 walk_mime_type(struct settings
*s
,
1898 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1900 struct mime_type
*m
;
1903 if (s
== NULL
|| cb
== NULL
) {
1904 show_oops(NULL
, "walk_mime_type invalid parameters");
1908 TAILQ_FOREACH(m
, &mtl
, entry
) {
1909 str
= g_strdup_printf("%s%s --> %s",
1911 m
->mt_default
? "*" : "",
1913 cb(s
, str
, cb_args
);
1919 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1925 if (str
== NULL
|| wl
== NULL
|| strlen(str
) < 2)
1928 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1930 /* treat *.moo.com the same as .moo.com */
1931 if (str
[0] == '*' && str
[1] == '.')
1933 else if (str
[0] == '.')
1938 /* slice off port number */
1939 p
= g_strrstr(str
, ":");
1943 d
= g_malloc(sizeof *d
);
1945 d
->d
= g_strdup_printf(".%s", str
);
1947 d
->d
= g_strdup(str
);
1950 if (RB_INSERT(domain_list
, wl
, d
))
1953 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1964 add_cookie_wl(struct settings
*s
, char *entry
)
1966 wl_add(entry
, &c_wl
, 1);
1971 walk_cookie_wl(struct settings
*s
,
1972 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1976 if (s
== NULL
|| cb
== NULL
) {
1977 show_oops(NULL
, "walk_cookie_wl invalid parameters");
1981 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1982 cb(s
, d
->d
, cb_args
);
1986 walk_js_wl(struct settings
*s
,
1987 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1991 if (s
== NULL
|| cb
== NULL
) {
1992 show_oops(NULL
, "walk_js_wl invalid parameters");
1996 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1997 cb(s
, d
->d
, cb_args
);
2001 add_js_wl(struct settings
*s
, char *entry
)
2003 wl_add(entry
, &js_wl
, 1 /* persistent */);
2008 wl_find(const gchar
*search
, struct domain_list
*wl
)
2011 struct domain
*d
= NULL
, dfind
;
2014 if (search
== NULL
|| wl
== NULL
)
2016 if (strlen(search
) < 2)
2019 if (search
[0] != '.')
2020 s
= g_strdup_printf(".%s", search
);
2022 s
= g_strdup(search
);
2024 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
2027 d
= RB_FIND(domain_list
, wl
, &dfind
);
2041 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
2047 if (s
== NULL
|| wl
== NULL
)
2050 if (!strncmp(s
, "http://", strlen("http://")))
2051 s
= &s
[strlen("http://")];
2052 else if (!strncmp(s
, "https://", strlen("https://")))
2053 s
= &s
[strlen("https://")];
2058 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
2059 /* chop string at first slash */
2060 if (s
[i
] == '/' || s
[i
] == '\0') {
2063 r
= wl_find(ss
, wl
);
2072 settings_add(char *var
, char *val
)
2079 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
2080 if (strcmp(var
, rs
[i
].name
))
2084 if (rs
[i
].s
->set(&rs
[i
], val
))
2085 errx(1, "invalid value for %s: %s", var
, val
);
2089 switch (rs
[i
].type
) {
2098 errx(1, "invalid sval for %s",
2112 errx(1, "invalid type for %s", var
);
2121 config_parse(char *filename
, int runtime
)
2124 char *line
, *cp
, *var
, *val
;
2125 size_t len
, lineno
= 0;
2127 char file
[PATH_MAX
];
2130 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
2132 if (filename
== NULL
)
2135 if (runtime
&& runtime_settings
[0] != '\0') {
2136 snprintf(file
, sizeof file
, "%s/%s",
2137 work_dir
, runtime_settings
);
2138 if (stat(file
, &sb
)) {
2139 warnx("runtime file doesn't exist, creating it");
2140 if ((f
= fopen(file
, "w")) == NULL
)
2142 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
2146 strlcpy(file
, filename
, sizeof file
);
2148 if ((config
= fopen(file
, "r")) == NULL
) {
2149 warn("config_parse: cannot open %s", filename
);
2154 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
2155 if (feof(config
) || ferror(config
))
2159 cp
+= (long)strspn(cp
, WS
);
2160 if (cp
[0] == '\0') {
2166 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
2167 startpage_add("invalid configuration file entry: %s",
2170 cp
+= (long)strspn(cp
, WS
);
2172 if ((val
= strsep(&cp
, "\0")) == NULL
)
2175 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n", var
, val
);
2176 handled
= settings_add(var
, val
);
2178 startpage_add("invalid configuration file entry: %s=%s",
2188 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
2194 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
2198 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
2201 JSStringGetUTF8CString(jsref
, s
, l
);
2202 JSStringRelease(jsref
);
2208 disable_hints(struct tab
*t
)
2210 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
2211 bzero(t
->hint_num
, sizeof t
->hint_num
);
2212 run_script(t
, "vimprobable_clear()");
2214 t
->hint_mode
= XT_HINT_NONE
;
2218 enable_hints(struct tab
*t
)
2220 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
2221 run_script(t
, "vimprobable_show_hints()");
2223 t
->hint_mode
= XT_HINT_NONE
;
2226 #define XT_JS_OPEN ("open;")
2227 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2228 #define XT_JS_FIRE ("fire;")
2229 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2230 #define XT_JS_FOUND ("found;")
2231 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2234 run_script(struct tab
*t
, char *s
)
2236 JSGlobalContextRef ctx
;
2237 WebKitWebFrame
*frame
;
2239 JSValueRef val
, exception
;
2242 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
2243 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
2245 frame
= webkit_web_view_get_main_frame(t
->wv
);
2246 ctx
= webkit_web_frame_get_global_context(frame
);
2248 str
= JSStringCreateWithUTF8CString(s
);
2249 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
2250 NULL
, 0, &exception
);
2251 JSStringRelease(str
);
2253 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
2255 es
= js_ref_to_string(ctx
, exception
);
2256 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
2260 es
= js_ref_to_string(ctx
, val
);
2261 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
2263 /* handle return value right here */
2264 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
2267 load_uri(t
, &es
[XT_JS_OPEN_LEN
]);
2270 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
2271 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
2272 &es
[XT_JS_FIRE_LEN
]);
2277 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
2278 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
2289 hint(struct tab
*t
, struct karg
*args
)
2292 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
2294 if (t
->hints_on
== 0)
2303 apply_style(struct tab
*t
)
2305 g_object_set(G_OBJECT(t
->settings
),
2306 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
2310 userstyle(struct tab
*t
, struct karg
*args
)
2312 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
2316 g_object_set(G_OBJECT(t
->settings
),
2317 "user-stylesheet-uri", NULL
, (char *)NULL
);
2326 * Doesn't work fully, due to the following bug:
2327 * https://bugs.webkit.org/show_bug.cgi?id=51747
2330 restore_global_history(void)
2332 char file
[PATH_MAX
];
2337 const char delim
[3] = {'\\', '\\', '\0'};
2339 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2341 if ((f
= fopen(file
, "r")) == NULL
) {
2342 warnx("%s: fopen", __func__
);
2347 if ((uri
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2348 if (feof(f
) || ferror(f
))
2351 if ((title
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2352 if (feof(f
) || ferror(f
)) {
2354 warnx("%s: broken history file\n", __func__
);
2358 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
2359 webkit_web_history_item_new_with_data(uri
, title
);
2360 h
= g_malloc(sizeof(struct history
));
2361 h
->uri
= g_strdup(uri
);
2362 h
->title
= g_strdup(title
);
2363 RB_INSERT(history_list
, &hl
, h
);
2364 completion_add_uri(h
->uri
);
2366 warnx("%s: failed to restore history\n", __func__
);
2382 save_global_history_to_disk(struct tab
*t
)
2384 char file
[PATH_MAX
];
2388 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2390 if ((f
= fopen(file
, "w")) == NULL
) {
2391 show_oops(t
, "%s: global history file: %s",
2392 __func__
, strerror(errno
));
2396 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2397 if (h
->uri
&& h
->title
)
2398 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2407 quit(struct tab
*t
, struct karg
*args
)
2409 if (save_global_history
)
2410 save_global_history_to_disk(t
);
2418 open_tabs(struct tab
*t
, struct karg
*a
)
2420 char file
[PATH_MAX
];
2424 struct tab
*ti
, *tt
;
2429 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2430 if ((f
= fopen(file
, "r")) == NULL
)
2433 ti
= TAILQ_LAST(&tabs
, tab_list
);
2436 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2437 if (feof(f
) || ferror(f
))
2440 /* retrieve session name */
2441 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2442 strlcpy(named_session
,
2443 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2444 sizeof named_session
);
2448 if (uri
&& strlen(uri
))
2449 create_new_tab(uri
, NULL
, 1, -1);
2455 /* close open tabs */
2456 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2458 tt
= TAILQ_FIRST(&tabs
);
2478 restore_saved_tabs(void)
2480 char file
[PATH_MAX
];
2481 int unlink_file
= 0;
2486 snprintf(file
, sizeof file
, "%s/%s",
2487 sessions_dir
, XT_RESTART_TABS_FILE
);
2488 if (stat(file
, &sb
) == -1)
2489 a
.s
= XT_SAVED_TABS_FILE
;
2492 a
.s
= XT_RESTART_TABS_FILE
;
2495 a
.i
= XT_SES_DONOTHING
;
2496 rv
= open_tabs(NULL
, &a
);
2505 save_tabs(struct tab
*t
, struct karg
*a
)
2507 char file
[PATH_MAX
];
2509 int num_tabs
= 0, i
;
2510 struct tab
**stabs
= NULL
;
2515 snprintf(file
, sizeof file
, "%s/%s",
2516 sessions_dir
, named_session
);
2518 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2520 if ((f
= fopen(file
, "w")) == NULL
) {
2521 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2525 /* save session name */
2526 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2528 /* Save tabs, in the order they are arranged in the notebook. */
2529 num_tabs
= sort_tabs_by_page_num(&stabs
);
2531 for (i
= 0; i
< num_tabs
; i
++)
2533 if (get_uri(stabs
[i
]) != NULL
)
2534 fprintf(f
, "%s\n", get_uri(stabs
[i
]));
2535 else if (gtk_entry_get_text(GTK_ENTRY(
2536 stabs
[i
]->uri_entry
)))
2537 fprintf(f
, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2538 stabs
[i
]->uri_entry
)));
2543 /* try and make sure this gets to disk NOW. XXX Backup first? */
2544 if (fflush(f
) != 0 || fsync(fileno(f
)) != 0) {
2545 show_oops(t
, "May not have managed to save session: %s",
2555 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2567 run_page_script(struct tab
*t
, struct karg
*args
)
2570 char *tmp
, script
[PATH_MAX
];
2572 tmp
= args
->s
!= NULL
&& strlen(args
->s
) > 0 ? args
->s
: default_script
;
2573 if (tmp
[0] == '\0') {
2574 show_oops(t
, "no script specified");
2578 if ((uri
= get_uri(t
)) == NULL
) {
2579 show_oops(t
, "tab is empty, not running script");
2584 snprintf(script
, sizeof script
, "%s/%s",
2585 pwd
->pw_dir
, &tmp
[1]);
2587 strlcpy(script
, tmp
, sizeof script
);
2591 show_oops(t
, "can't fork to run script");
2601 execlp(script
, script
, uri
, (void *)NULL
);
2611 yank_uri(struct tab
*t
, struct karg
*args
)
2614 GtkClipboard
*clipboard
;
2616 if ((uri
= get_uri(t
)) == NULL
)
2619 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2620 gtk_clipboard_set_text(clipboard
, uri
, -1);
2626 paste_uri(struct tab
*t
, struct karg
*args
)
2628 GtkClipboard
*clipboard
;
2629 GdkAtom atom
= gdk_atom_intern("CUT_BUFFER0", FALSE
);
2631 gchar
*p
= NULL
, *uri
;
2633 /* try primary clipboard first */
2634 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2635 p
= gtk_clipboard_wait_for_text(clipboard
);
2637 /* if it failed get whatever text is in cut_buffer0 */
2638 if (p
== NULL
&& xterm_workaround
)
2639 if (gdk_property_get(gdk_get_default_root_window(),
2641 gdk_atom_intern("STRING", FALSE
),
2643 1024 * 1024 /* picked out of my butt */,
2649 /* yes sir, we need to NUL the string */
2655 while (*uri
&& isspace(*uri
))
2657 if (strlen(uri
) == 0) {
2658 show_oops(t
, "empty paste buffer");
2661 if (guess_search
== 0 && valid_url_type(uri
)) {
2662 /* we can be clever and paste this in search box */
2663 show_oops(t
, "not a valid URL");
2667 if (args
->i
== XT_PASTE_CURRENT_TAB
)
2669 else if (args
->i
== XT_PASTE_NEW_TAB
)
2670 create_new_tab(uri
, NULL
, 1, -1);
2681 find_domain(const gchar
*s
, int toplevel
)
2689 uri
= soup_uri_new(s
);
2691 if (uri
== NULL
|| !SOUP_URI_VALID_FOR_HTTP(uri
)) {
2695 if (toplevel
&& !isdigit(uri
->host
[strlen(uri
->host
) - 1])) {
2696 if ((p
= strrchr(uri
->host
, '.')) != NULL
) {
2697 while(--p
>= uri
->host
&& *p
!= '.');
2704 if (uri
->port
== 80)
2705 ret
= g_strdup_printf(".%s", p
);
2707 ret
= g_strdup_printf(".%s:%d", p
, uri
->port
);
2715 toggle_cwl(struct tab
*t
, struct karg
*args
)
2726 dom
= find_domain(uri
, args
->i
& XT_WL_TOPLEVEL
);
2728 if (uri
== NULL
|| dom
== NULL
||
2729 webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
) {
2730 show_oops(t
, "Can't toggle domain in cookie white list");
2733 d
= wl_find(dom
, &c_wl
);
2740 if (args
->i
& XT_WL_TOGGLE
)
2742 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2744 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2748 /* enable cookies for domain */
2749 wl_add(dom
, &c_wl
, 0);
2751 /* disable cookies for domain */
2752 RB_REMOVE(domain_list
, &c_wl
, d
);
2754 if (args
->i
& XT_WL_RELOAD
)
2755 webkit_web_view_reload(t
->wv
);
2763 toggle_js(struct tab
*t
, struct karg
*args
)
2773 g_object_get(G_OBJECT(t
->settings
),
2774 "enable-scripts", &es
, (char *)NULL
);
2775 if (args
->i
& XT_WL_TOGGLE
)
2777 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2779 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2785 dom
= find_domain(uri
, args
->i
& XT_WL_TOPLEVEL
);
2787 if (uri
== NULL
|| dom
== NULL
||
2788 webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
) {
2789 show_oops(t
, "Can't toggle domain in JavaScript white list");
2794 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2795 wl_add(dom
, &js_wl
, 0 /* session */);
2797 d
= wl_find(dom
, &js_wl
);
2799 RB_REMOVE(domain_list
, &js_wl
, d
);
2800 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2802 g_object_set(G_OBJECT(t
->settings
),
2803 "enable-scripts", es
, (char *)NULL
);
2804 g_object_set(G_OBJECT(t
->settings
),
2805 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2806 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2808 if (args
->i
& XT_WL_RELOAD
)
2809 webkit_web_view_reload(t
->wv
);
2817 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2821 a
.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
;
2824 a
.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
;
2829 toggle_src(struct tab
*t
, struct karg
*args
)
2836 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2837 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2838 webkit_web_view_reload(t
->wv
);
2844 focus_webview(struct tab
*t
)
2849 /* only grab focus if we are visible */
2850 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2851 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2855 focus(struct tab
*t
, struct karg
*args
)
2857 if (t
== NULL
|| args
== NULL
)
2863 if (args
->i
== XT_FOCUS_URI
)
2864 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2865 else if (args
->i
== XT_FOCUS_SEARCH
)
2866 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2872 stats(struct tab
*t
, struct karg
*args
)
2874 char *page
, *body
, *s
, line
[64 * 1024];
2875 uint64_t line_count
= 0;
2879 show_oops(NULL
, "stats invalid parameters");
2882 if (save_rejected_cookies
) {
2883 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2885 s
= fgets(line
, sizeof line
, r_cookie_f
);
2886 if (s
== NULL
|| feof(r_cookie_f
) ||
2892 snprintf(line
, sizeof line
,
2893 "<br/>Cookies blocked(*) total: %llu", line_count
);
2895 show_oops(t
, "Can't open blocked cookies file: %s",
2899 body
= g_strdup_printf(
2900 "Cookies blocked(*) this session: %llu"
2902 "<p><small><b>*</b> results vary based on settings</small></p>",
2906 page
= get_html_page("Statistics", body
, "", 0);
2909 load_webkit_string(t
, page
, XT_URI_ABOUT_STATS
);
2916 marco(struct tab
*t
, struct karg
*args
)
2918 char *page
, line
[64 * 1024];
2922 show_oops(NULL
, "marco invalid parameters");
2925 snprintf(line
, sizeof line
, "%s", marco_message(&len
));
2927 page
= get_html_page("Marco Sez...", line
, "", 0);
2929 load_webkit_string(t
, page
, XT_URI_ABOUT_MARCO
);
2936 blank(struct tab
*t
, struct karg
*args
)
2939 show_oops(NULL
, "blank invalid parameters");
2941 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2947 about(struct tab
*t
, struct karg
*args
)
2952 show_oops(NULL
, "about invalid parameters");
2954 body
= g_strdup_printf("<b>Version: %s</b><p>"
2957 "<li>Marco Peereboom <marco@peereboom.us></li>"
2958 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2959 "<li>Edd Barrett <vext01@gmail.com> </li>"
2960 "<li>Todd T. Fries <todd@fries.net> </li>"
2961 "<li>Raphael Graf <r@undefined.ch> </li>"
2963 "Copyrights and licenses can be found on the XXXTerm "
2964 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2968 page
= get_html_page("About", body
, "", 0);
2971 load_webkit_string(t
, page
, XT_URI_ABOUT_ABOUT
);
2978 help(struct tab
*t
, struct karg
*args
)
2980 char *page
, *head
, *body
;
2983 show_oops(NULL
, "help invalid parameters");
2985 head
= "<meta http-equiv=\"REFRESH\" content=\"0;"
2986 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2988 body
= "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2989 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2990 "cgi-bin/man-cgi?xxxterm</a>";
2992 page
= get_html_page(XT_NAME
, body
, head
, FALSE
);
2994 load_webkit_string(t
, page
, XT_URI_ABOUT_HELP
);
3001 startpage(struct tab
*t
, struct karg
*args
)
3003 char *page
, *body
, *b
;
3007 show_oops(NULL
, "startpage invalid parameters");
3009 body
= g_strdup_printf("<b>Startup Exception(s):</b><p>");
3011 TAILQ_FOREACH(s
, &spl
, entry
) {
3013 body
= g_strdup_printf("%s%s<br>", body
, s
->line
);
3017 page
= get_html_page("Startup Exception", body
, "", 0);
3020 load_webkit_string(t
, page
, XT_URI_ABOUT_STARTPAGE
);
3027 startpage_add(const char *fmt
, ...)
3037 if (vasprintf(&msg
, fmt
, ap
) == -1)
3038 errx(1, "startpage_add failed");
3041 s
= g_malloc0(sizeof *s
);
3044 TAILQ_INSERT_TAIL(&spl
, s
, entry
);
3048 * update all favorite tabs apart from one. Pass NULL if
3049 * you want to update all.
3052 update_favorite_tabs(struct tab
*apart_from
)
3055 if (!updating_fl_tabs
) {
3056 updating_fl_tabs
= 1; /* stop infinite recursion */
3057 TAILQ_FOREACH(t
, &tabs
, entry
)
3058 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
3059 && (t
!= apart_from
))
3060 xtp_page_fl(t
, NULL
);
3061 updating_fl_tabs
= 0;
3065 /* show a list of favorites (bookmarks) */
3067 xtp_page_fl(struct tab
*t
, struct karg
*args
)
3069 char file
[PATH_MAX
];
3071 char *uri
= NULL
, *title
= NULL
;
3072 size_t len
, lineno
= 0;
3074 char *body
, *tmp
, *page
= NULL
;
3075 const char delim
[3] = {'\\', '\\', '\0'};
3077 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
3080 warn("%s: bad param", __func__
);
3082 /* new session key */
3083 if (!updating_fl_tabs
)
3084 generate_xtp_session_key(&fl_session_key
);
3086 /* open favorites */
3087 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3088 if ((f
= fopen(file
, "r")) == NULL
) {
3089 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3094 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
3095 "<th style='width: 40px'>#</th><th>Link</th>"
3096 "<th style='width: 40px'>Rm</th></tr>\n");
3099 if ((title
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
3100 if (feof(f
) || ferror(f
))
3102 if (strlen(title
) == 0 || title
[0] == '#') {
3108 if ((uri
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
3109 if (feof(f
) || ferror(f
)) {
3110 show_oops(t
, "favorites file corrupt");
3116 body
= g_strdup_printf("%s<tr>"
3118 "<td><a href='%s'>%s</a></td>"
3119 "<td style='text-align: center'>"
3120 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3122 body
, i
, uri
, title
,
3123 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
3135 /* if none, say so */
3138 body
= g_strdup_printf("%s<tr>"
3139 "<td colspan='3' style='text-align: center'>"
3140 "No favorites - To add one use the 'favadd' command."
3141 "</td></tr>", body
);
3146 body
= g_strdup_printf("%s</table>", body
);
3156 page
= get_html_page("Favorites", body
, "", 1);
3157 load_webkit_string(t
, page
, XT_URI_ABOUT_FAVORITES
);
3161 update_favorite_tabs(t
);
3170 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
3171 size_t cert_count
, char *title
)
3173 gnutls_datum_t cinfo
;
3177 body
= g_strdup("");
3179 for (i
= 0; i
< cert_count
; i
++) {
3180 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
3185 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3186 body
, i
, cinfo
.data
);
3187 gnutls_free(cinfo
.data
);
3191 tmp
= get_html_page(title
, body
, "", 0);
3194 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
3199 ca_cmd(struct tab
*t
, struct karg
*args
)
3202 int rv
= 1, certs
= 0, certs_read
;
3205 gnutls_x509_crt_t
*c
= NULL
;
3206 char *certs_buf
= NULL
, *s
;
3208 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
3209 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
3213 if (fstat(fileno(f
), &sb
) == -1) {
3214 show_oops(t
, "Can't stat CA file: %s", ssl_ca_file
);
3218 certs_buf
= g_malloc(sb
.st_size
+ 1);
3219 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
3220 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
3223 certs_buf
[sb
.st_size
] = '\0';
3226 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
3228 s
+= strlen("BEGIN CERTIFICATE");
3231 bzero(&dt
, sizeof dt
);
3232 dt
.data
= (unsigned char *)certs_buf
;
3233 dt
.size
= sb
.st_size
;
3234 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
3235 certs_read
= gnutls_x509_crt_list_import(c
, (unsigned int *)&certs
, &dt
,
3236 GNUTLS_X509_FMT_PEM
, 0);
3237 if (certs_read
<= 0) {
3238 show_oops(t
, "No cert(s) available");
3241 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
3254 connect_socket_from_uri(struct tab
*t
, const gchar
*uri
, char *domain
,
3258 struct addrinfo hints
, *res
= NULL
, *ai
;
3259 int rv
= -1, s
= -1, on
, error
;
3262 if (uri
&& !g_str_has_prefix(uri
, "https://")) {
3263 show_oops(t
, "invalid URI");
3267 su
= soup_uri_new(uri
);
3269 show_oops(t
, "invalid soup URI");
3272 if (!SOUP_URI_VALID_FOR_HTTP(su
)) {
3273 show_oops(t
, "invalid HTTPS URI");
3277 snprintf(port
, sizeof port
, "%d", su
->port
);
3278 bzero(&hints
, sizeof(struct addrinfo
));
3279 hints
.ai_flags
= AI_CANONNAME
;
3280 hints
.ai_family
= AF_UNSPEC
;
3281 hints
.ai_socktype
= SOCK_STREAM
;
3283 if ((error
= getaddrinfo(su
->host
, port
, &hints
, &res
))) {
3284 show_oops(t
, "getaddrinfo failed: %s", gai_strerror(errno
));
3288 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
3289 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
3292 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
3294 show_oops(t
, "socket failed: %s", strerror(errno
));
3297 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
3298 sizeof(on
)) == -1) {
3299 show_oops(t
, "setsockopt failed: %s", strerror(errno
));
3302 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) == -1) {
3303 show_oops(t
, "connect failed: %s", strerror(errno
));
3311 strlcpy(domain
, su
->host
, domain_sz
);
3318 if (rv
== -1 && s
!= -1)
3325 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
3328 gnutls_deinit(gsession
);
3330 gnutls_certificate_free_credentials(xcred
);
3336 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
3337 gnutls_certificate_credentials_t
*xc
)
3339 gnutls_certificate_credentials_t xcred
;
3340 gnutls_session_t gsession
;
3343 if (gs
== NULL
|| xc
== NULL
)
3349 gnutls_certificate_allocate_credentials(&xcred
);
3350 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
3351 GNUTLS_X509_FMT_PEM
);
3353 gnutls_init(&gsession
, GNUTLS_CLIENT
);
3354 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
3355 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
3356 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
3357 if ((rv
= gnutls_handshake(gsession
)) < 0) {
3358 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
3360 gnutls_error_is_fatal(rv
),
3361 gnutls_strerror_name(rv
));
3362 stop_tls(gsession
, xcred
);
3366 gnutls_credentials_type_t cred
;
3367 cred
= gnutls_auth_get_type(gsession
);
3368 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
3369 show_oops(t
, "gnutls_auth_get_type failed %d", (int)cred
);
3370 stop_tls(gsession
, xcred
);
3382 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
3386 const gnutls_datum_t
*cl
;
3387 gnutls_x509_crt_t
*all_certs
;
3390 if (certs
== NULL
|| cert_count
== NULL
)
3392 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
3394 cl
= gnutls_certificate_get_peers(gsession
, &len
);
3398 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
3399 for (i
= 0; i
< len
; i
++) {
3400 gnutls_x509_crt_init(&all_certs
[i
]);
3401 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
3402 GNUTLS_X509_FMT_PEM
< 0)) {
3416 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
3420 for (i
= 0; i
< cert_count
; i
++)
3421 gnutls_x509_crt_deinit(certs
[i
]);
3426 statusbar_modify_attr(struct tab
*t
, const char *text
, const char *base
)
3428 GdkColor c_text
, c_base
;
3430 gdk_color_parse(text
, &c_text
);
3431 gdk_color_parse(base
, &c_base
);
3433 gtk_widget_modify_text(t
->sbe
.statusbar
, GTK_STATE_NORMAL
, &c_text
);
3434 gtk_widget_modify_text(t
->sbe
.buffercmd
, GTK_STATE_NORMAL
, &c_text
);
3435 gtk_widget_modify_text(t
->sbe
.zoom
, GTK_STATE_NORMAL
, &c_text
);
3436 gtk_widget_modify_text(t
->sbe
.position
, GTK_STATE_NORMAL
, &c_text
);
3438 gtk_widget_modify_base(t
->sbe
.statusbar
, GTK_STATE_NORMAL
, &c_base
);
3439 gtk_widget_modify_base(t
->sbe
.buffercmd
, GTK_STATE_NORMAL
, &c_base
);
3440 gtk_widget_modify_base(t
->sbe
.zoom
, GTK_STATE_NORMAL
, &c_base
);
3441 gtk_widget_modify_base(t
->sbe
.position
, GTK_STATE_NORMAL
, &c_base
);
3445 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
3446 size_t cert_count
, char *domain
)
3449 char cert_buf
[64 * 1024], file
[PATH_MAX
];
3454 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
3457 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3458 if ((f
= fopen(file
, "w")) == NULL
) {
3459 show_oops(t
, "Can't create cert file %s %s",
3460 file
, strerror(errno
));
3464 for (i
= 0; i
< cert_count
; i
++) {
3465 cert_buf_sz
= sizeof cert_buf
;
3466 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3467 cert_buf
, &cert_buf_sz
)) {
3468 show_oops(t
, "gnutls_x509_crt_export failed");
3471 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3472 show_oops(t
, "Can't write certs: %s", strerror(errno
));
3477 /* not the best spot but oh well */
3478 gdk_color_parse("lightblue", &color
);
3479 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3480 statusbar_modify_attr(t
, XT_COLOR_BLACK
, "lightblue");
3493 load_compare_cert(struct tab
*t
, struct karg
*args
)
3496 char domain
[8182], file
[PATH_MAX
];
3497 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
3498 int s
= -1, i
, error
;
3500 size_t cert_buf_sz
, cert_count
;
3501 enum cert_trust rv
= CERT_UNTRUSTED
;
3503 gnutls_session_t gsession
;
3504 gnutls_x509_crt_t
*certs
;
3505 gnutls_certificate_credentials_t xcred
;
3507 DNPRINTF(XT_D_URL
, "%s: %p %p\n", __func__
, t
, args
);
3512 if ((uri
= get_uri(t
)) == NULL
)
3514 DNPRINTF(XT_D_URL
, "%s: %s\n", __func__
, uri
);
3516 if ((s
= connect_socket_from_uri(t
, uri
, domain
, sizeof domain
)) == -1)
3518 DNPRINTF(XT_D_URL
, "%s: fd %d\n", __func__
, s
);
3521 if (start_tls(t
, s
, &gsession
, &xcred
))
3523 DNPRINTF(XT_D_URL
, "%s: got tls\n", __func__
);
3525 /* verify certs in case cert file doesn't exist */
3526 if (gnutls_certificate_verify_peers2(gsession
, &error
) !=
3528 show_oops(t
, "Invalid certificates");
3533 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3534 show_oops(t
, "Can't get connection certificates");
3538 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3539 if ((f
= fopen(file
, "r")) == NULL
) {
3545 for (i
= 0; i
< cert_count
; i
++) {
3546 cert_buf_sz
= sizeof cert_buf
;
3547 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3548 cert_buf
, &cert_buf_sz
)) {
3551 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3552 rv
= CERT_BAD
; /* critical */
3555 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3556 rv
= CERT_BAD
; /* critical */
3565 free_connection_certs(certs
, cert_count
);
3567 /* we close the socket first for speed */
3571 /* only complain if we didn't save it locally */
3572 if (error
&& rv
!= CERT_LOCAL
) {
3573 strlcpy(serr
, "Certificate exception(s): ", sizeof serr
);
3574 if (error
& GNUTLS_CERT_INVALID
)
3575 strlcat(serr
, "invalid, ", sizeof serr
);
3576 if (error
& GNUTLS_CERT_REVOKED
)
3577 strlcat(serr
, "revoked, ", sizeof serr
);
3578 if (error
& GNUTLS_CERT_SIGNER_NOT_FOUND
)
3579 strlcat(serr
, "signer not found, ", sizeof serr
);
3580 if (error
& GNUTLS_CERT_SIGNER_NOT_CA
)
3581 strlcat(serr
, "not signed by CA, ", sizeof serr
);
3582 if (error
& GNUTLS_CERT_INSECURE_ALGORITHM
)
3583 strlcat(serr
, "insecure algorithm, ", sizeof serr
);
3584 if (error
& GNUTLS_CERT_NOT_ACTIVATED
)
3585 strlcat(serr
, "not activated, ", sizeof serr
);
3586 if (error
& GNUTLS_CERT_EXPIRED
)
3587 strlcat(serr
, "expired, ", sizeof serr
);
3588 for (i
= strlen(serr
) - 1; i
> 0; i
--)
3589 if (serr
[i
] == ',') {
3596 stop_tls(gsession
, xcred
);
3602 cert_cmd(struct tab
*t
, struct karg
*args
)
3608 gnutls_session_t gsession
;
3609 gnutls_x509_crt_t
*certs
;
3610 gnutls_certificate_credentials_t xcred
;
3615 if (ssl_ca_file
== NULL
) {
3616 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
3620 if ((uri
= get_uri(t
)) == NULL
) {
3621 show_oops(t
, "Invalid URI");
3625 if ((s
= connect_socket_from_uri(t
, uri
, domain
, sizeof domain
)) == -1) {
3626 show_oops(t
, "Invalid certificate URI: %s", uri
);
3631 if (start_tls(t
, s
, &gsession
, &xcred
))
3635 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3636 show_oops(t
, "get_connection_certs failed");
3640 if (args
->i
& XT_SHOW
)
3641 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3642 else if (args
->i
& XT_SAVE
)
3643 save_certs(t
, certs
, cert_count
, domain
);
3645 free_connection_certs(certs
, cert_count
);
3647 /* we close the socket first for speed */
3650 stop_tls(gsession
, xcred
);
3656 remove_cookie(int index
)
3662 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3664 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3666 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3670 print_cookie("remove cookie", c
);
3671 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3676 soup_cookies_free(cf
);
3682 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3687 body
= g_strdup("");
3690 if (args
->i
& XT_WL_PERSISTENT
) {
3692 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3694 RB_FOREACH(d
, domain_list
, wl
) {
3698 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3704 if (args
->i
& XT_WL_SESSION
) {
3706 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3708 RB_FOREACH(d
, domain_list
, wl
) {
3712 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3717 tmp
= get_html_page(title
, body
, "", 0);
3720 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3722 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3728 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3730 char file
[PATH_MAX
];
3732 char *line
= NULL
, *lt
= NULL
, *dom
= NULL
;
3741 if (t
== NULL
|| args
== NULL
)
3744 if (runtime_settings
[0] == '\0')
3747 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3748 if ((f
= fopen(file
, "r+")) == NULL
)
3752 dom
= find_domain(uri
, args
->i
& XT_WL_TOPLEVEL
);
3753 if (uri
== NULL
|| dom
== NULL
||
3754 webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
) {
3755 show_oops(t
, "Can't add domain to %s white list",
3756 js
? "JavaScript" : "cookie");
3760 /* we don't want to save :port number */
3761 p
= g_strrstr(dom
, ":");
3765 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom
);
3768 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3771 if (!strcmp(line
, lt
))
3777 fprintf(f
, "%s\n", lt
);
3782 d
= wl_find(dom
, &js_wl
);
3784 settings_add("js_wl", dom
);
3785 d
= wl_find(dom
, &js_wl
);
3789 d
= wl_find(dom
, &c_wl
);
3791 settings_add("cookie_wl", dom
);
3792 d
= wl_find(dom
, &c_wl
);
3796 /* find and add to persistent jar */
3797 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3798 for (;cf
; cf
= cf
->next
) {
3800 if (!strcmp(dom
, ci
->domain
) ||
3801 !strcmp(&dom
[1], ci
->domain
)) /* deal with leading . */ {
3802 c
= soup_cookie_copy(ci
);
3803 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3806 soup_cookies_free(cf
);
3824 js_show_wl(struct tab
*t
, struct karg
*args
)
3826 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3827 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3833 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3835 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3836 wl_show(t
, args
, "Cookie White List", &c_wl
);
3842 cookie_cmd(struct tab
*t
, struct karg
*args
)
3844 if (args
->i
& XT_SHOW
)
3845 wl_show(t
, args
, "Cookie White List", &c_wl
);
3846 else if (args
->i
& XT_WL_TOGGLE
) {
3847 args
->i
|= XT_WL_RELOAD
;
3848 toggle_cwl(t
, args
);
3849 } else if (args
->i
& XT_SAVE
) {
3850 args
->i
|= XT_WL_RELOAD
;
3851 wl_save(t
, args
, 0);
3852 } else if (args
->i
& XT_DELETE
)
3853 show_oops(t
, "'cookie delete' currently unimplemented");
3859 js_cmd(struct tab
*t
, struct karg
*args
)
3861 if (args
->i
& XT_SHOW
)
3862 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3863 else if (args
->i
& XT_SAVE
) {
3864 args
->i
|= XT_WL_RELOAD
;
3865 wl_save(t
, args
, 1);
3866 } else if (args
->i
& XT_WL_TOGGLE
) {
3867 args
->i
|= XT_WL_RELOAD
;
3869 } else if (args
->i
& XT_DELETE
)
3870 show_oops(t
, "'js delete' currently unimplemented");
3876 toplevel_cmd(struct tab
*t
, struct karg
*args
)
3878 js_toggle_cb(t
->js_toggle
, t
);
3884 add_favorite(struct tab
*t
, struct karg
*args
)
3886 char file
[PATH_MAX
];
3889 size_t urilen
, linelen
;
3890 const gchar
*uri
, *title
;
3895 /* don't allow adding of xtp pages to favorites */
3896 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3897 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3901 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3902 if ((f
= fopen(file
, "r+")) == NULL
) {
3903 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3907 title
= get_title(t
, FALSE
);
3910 if (title
== NULL
|| uri
== NULL
) {
3911 show_oops(t
, "can't add page to favorites");
3915 urilen
= strlen(uri
);
3918 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3919 if (feof(f
) || ferror(f
))
3922 if (linelen
== urilen
&& !strcmp(line
, uri
))
3929 fprintf(f
, "\n%s\n%s", title
, uri
);
3935 update_favorite_tabs(NULL
);
3941 navaction(struct tab
*t
, struct karg
*args
)
3943 WebKitWebHistoryItem
*item
;
3945 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3946 t
->tab_id
, args
->i
);
3948 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
3951 if (args
->i
== XT_NAV_BACK
)
3952 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3954 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3956 return (XT_CB_PASSTHROUGH
);
3957 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
3959 return (XT_CB_PASSTHROUGH
);
3965 item
= webkit_web_back_forward_list_get_back_item(t
->bfl
);
3967 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
3969 case XT_NAV_FORWARD
:
3971 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3973 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
3976 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3978 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
3981 return (XT_CB_PASSTHROUGH
);
3985 move(struct tab
*t
, struct karg
*args
)
3987 GtkAdjustment
*adjust
;
3988 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3994 case XT_MOVE_BOTTOM
:
3996 case XT_MOVE_PAGEDOWN
:
3997 case XT_MOVE_PAGEUP
:
3998 case XT_MOVE_HALFDOWN
:
3999 case XT_MOVE_HALFUP
:
4000 case XT_MOVE_PERCENT
:
4001 adjust
= t
->adjust_v
;
4004 adjust
= t
->adjust_h
;
4008 pos
= gtk_adjustment_get_value(adjust
);
4009 ps
= gtk_adjustment_get_page_size(adjust
);
4010 upper
= gtk_adjustment_get_upper(adjust
);
4011 lower
= gtk_adjustment_get_lower(adjust
);
4012 si
= gtk_adjustment_get_step_increment(adjust
);
4013 pi
= gtk_adjustment_get_page_increment(adjust
);
4016 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4017 "max %f si %f pi %f\n",
4018 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
4019 pos
, ps
, upper
, lower
, max
, si
, pi
);
4025 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
4030 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
4032 case XT_MOVE_BOTTOM
:
4033 case XT_MOVE_FARRIGHT
:
4034 gtk_adjustment_set_value(adjust
, max
);
4037 case XT_MOVE_FARLEFT
:
4038 gtk_adjustment_set_value(adjust
, lower
);
4040 case XT_MOVE_PAGEDOWN
:
4042 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
4044 case XT_MOVE_PAGEUP
:
4046 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
4048 case XT_MOVE_HALFDOWN
:
4050 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
4052 case XT_MOVE_HALFUP
:
4054 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
4056 case XT_MOVE_PERCENT
:
4057 percent
= atoi(args
->s
) / 100.0;
4058 pos
= max
* percent
;
4059 if (pos
< 0.0 || pos
> max
)
4061 gtk_adjustment_set_value(adjust
, pos
);
4064 return (XT_CB_PASSTHROUGH
);
4067 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
4069 return (XT_CB_HANDLED
);
4073 url_set_visibility(void)
4077 TAILQ_FOREACH(t
, &tabs
, entry
)
4078 if (show_url
== 0) {
4079 gtk_widget_hide(t
->toolbar
);
4082 gtk_widget_show(t
->toolbar
);
4086 notebook_tab_set_visibility(void)
4088 if (show_tabs
== 0) {
4089 gtk_widget_hide(tab_bar
);
4090 gtk_notebook_set_show_tabs(notebook
, FALSE
);
4092 if (tab_style
== XT_TABS_NORMAL
) {
4093 gtk_widget_hide(tab_bar
);
4094 gtk_notebook_set_show_tabs(notebook
, TRUE
);
4095 } else if (tab_style
== XT_TABS_COMPACT
) {
4096 gtk_widget_show(tab_bar
);
4097 gtk_notebook_set_show_tabs(notebook
, FALSE
);
4103 statusbar_set_visibility(void)
4107 TAILQ_FOREACH(t
, &tabs
, entry
)
4108 if (show_statusbar
== 0) {
4109 gtk_widget_hide(t
->statusbar_box
);
4112 gtk_widget_show(t
->statusbar_box
);
4116 url_set(struct tab
*t
, int enable_url_entry
)
4121 show_url
= enable_url_entry
;
4123 if (enable_url_entry
) {
4124 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
4125 GTK_ENTRY_ICON_PRIMARY
, NULL
);
4126 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.statusbar
), 0);
4128 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
4129 GTK_ENTRY_ICON_PRIMARY
);
4131 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
4132 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.statusbar
),
4133 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
4134 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.statusbar
),
4140 fullscreen(struct tab
*t
, struct karg
*args
)
4142 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
4145 return (XT_CB_PASSTHROUGH
);
4147 if (show_url
== 0) {
4155 url_set_visibility();
4156 notebook_tab_set_visibility();
4158 return (XT_CB_HANDLED
);
4162 statustoggle(struct tab
*t
, struct karg
*args
)
4164 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
4166 if (show_statusbar
== 1) {
4168 statusbar_set_visibility();
4169 } else if (show_statusbar
== 0) {
4171 statusbar_set_visibility();
4173 return (XT_CB_HANDLED
);
4177 urlaction(struct tab
*t
, struct karg
*args
)
4179 int rv
= XT_CB_HANDLED
;
4181 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
4184 return (XT_CB_PASSTHROUGH
);
4188 if (show_url
== 0) {
4190 url_set_visibility();
4194 if (show_url
== 1) {
4196 url_set_visibility();
4204 tabaction(struct tab
*t
, struct karg
*args
)
4206 int rv
= XT_CB_HANDLED
;
4207 char *url
= args
->s
;
4211 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
4214 return (XT_CB_PASSTHROUGH
);
4218 if (strlen(url
) > 0)
4219 create_new_tab(url
, NULL
, 1, args
->precount
);
4221 create_new_tab(NULL
, NULL
, 1, args
->precount
);
4224 if (args
->precount
< 0)
4227 TAILQ_FOREACH(tt
, &tabs
, entry
)
4228 if (tt
->tab_id
== args
->precount
- 1) {
4233 case XT_TAB_DELQUIT
:
4234 if (gtk_notebook_get_n_pages(notebook
) > 1)
4240 if (strlen(url
) > 0)
4243 rv
= XT_CB_PASSTHROUGH
;
4249 if (show_tabs
== 0) {
4251 notebook_tab_set_visibility();
4255 if (show_tabs
== 1) {
4257 notebook_tab_set_visibility();
4260 case XT_TAB_NEXTSTYLE
:
4261 if (tab_style
== XT_TABS_NORMAL
) {
4262 tab_style
= XT_TABS_COMPACT
;
4263 recolor_compact_tabs();
4266 tab_style
= XT_TABS_NORMAL
;
4267 notebook_tab_set_visibility();
4269 case XT_TAB_UNDO_CLOSE
:
4270 if (undo_count
== 0) {
4271 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close",
4276 u
= TAILQ_FIRST(&undos
);
4277 create_new_tab(u
->uri
, u
, 1, -1);
4279 TAILQ_REMOVE(&undos
, u
, entry
);
4281 /* u->history is freed in create_new_tab() */
4286 rv
= XT_CB_PASSTHROUGH
;
4300 resizetab(struct tab
*t
, struct karg
*args
)
4302 if (t
== NULL
|| args
== NULL
) {
4303 show_oops(NULL
, "resizetab invalid parameters");
4304 return (XT_CB_PASSTHROUGH
);
4307 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
4308 t
->tab_id
, args
->i
);
4310 setzoom_webkit(t
, args
->i
);
4312 return (XT_CB_HANDLED
);
4316 movetab(struct tab
*t
, struct karg
*args
)
4320 if (t
== NULL
|| args
== NULL
) {
4321 show_oops(NULL
, "movetab invalid parameters");
4322 return (XT_CB_PASSTHROUGH
);
4325 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
4326 t
->tab_id
, args
->i
);
4328 if (args
->i
>= XT_TAB_INVALID
)
4329 return (XT_CB_PASSTHROUGH
);
4331 if (TAILQ_EMPTY(&tabs
))
4332 return (XT_CB_PASSTHROUGH
);
4334 n
= gtk_notebook_get_n_pages(notebook
);
4335 dest
= gtk_notebook_get_current_page(notebook
);
4339 if (args
->precount
< 0)
4340 dest
= dest
== n
- 1 ? 0 : dest
+ 1;
4342 dest
= args
->precount
- 1;
4346 if (args
->precount
< 0)
4349 dest
-= args
->precount
% n
;
4362 return (XT_CB_PASSTHROUGH
);
4365 if (dest
< 0 || dest
>= n
)
4366 return (XT_CB_PASSTHROUGH
);
4367 if (t
->tab_id
== dest
) {
4368 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
4369 return (XT_CB_HANDLED
);
4372 set_current_tab(dest
);
4374 return (XT_CB_HANDLED
);
4380 command(struct tab
*t
, struct karg
*args
)
4382 char *s
= NULL
, *ss
= NULL
;
4386 if (t
== NULL
|| args
== NULL
) {
4387 show_oops(NULL
, "command invalid parameters");
4388 return (XT_CB_PASSTHROUGH
);
4399 if (cmd_prefix
== 0)
4402 ss
= g_strdup_printf(":%d", cmd_prefix
);
4413 case XT_CMD_OPEN_CURRENT
:
4416 case XT_CMD_TABNEW_CURRENT
:
4417 if (!s
) /* FALL THROUGH? */
4419 if ((uri
= get_uri(t
)) != NULL
) {
4420 ss
= g_strdup_printf("%s%s", s
, uri
);
4425 show_oops(t
, "command: invalid opcode %d", args
->i
);
4426 return (XT_CB_PASSTHROUGH
);
4429 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
4431 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
4432 gdk_color_parse(XT_COLOR_WHITE
, &color
);
4433 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
4435 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
4436 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
4441 return (XT_CB_HANDLED
);
4445 * Return a new string with a download row (in html)
4446 * appended. Old string is freed.
4449 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
4452 WebKitDownloadStatus stat
;
4453 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
4455 char cur_sz
[FMT_SCALED_STRSIZE
];
4456 char tot_sz
[FMT_SCALED_STRSIZE
];
4459 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
4461 /* All actions wil take this form:
4462 * xxxt://class/seskey
4464 xtp_prefix
= g_strdup_printf("%s%d/%s/",
4465 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
4467 stat
= webkit_download_get_status(dl
->download
);
4470 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
4471 status_html
= g_strdup_printf("Finished");
4472 cmd_html
= g_strdup_printf(
4473 "<a href='%s%d/%d'>Remove</a>",
4474 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4476 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
4477 /* gather size info */
4478 progress
= 100 * webkit_download_get_progress(dl
->download
);
4481 webkit_download_get_current_size(dl
->download
), cur_sz
);
4483 webkit_download_get_total_size(dl
->download
), tot_sz
);
4485 status_html
= g_strdup_printf(
4486 "<div style='width: 100%%' align='center'>"
4487 "<div class='progress-outer'>"
4488 "<div class='progress-inner' style='width: %.2f%%'>"
4489 "</div></div></div>"
4490 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4491 progress
, cur_sz
, tot_sz
, progress
);
4493 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4494 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
4498 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
4499 status_html
= g_strdup_printf("Cancelled");
4500 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4501 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4503 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
4504 status_html
= g_strdup_printf("Error!");
4505 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4506 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4508 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
4509 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4510 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
4511 status_html
= g_strdup_printf("Starting");
4514 show_oops(t
, "%s: unknown download status", __func__
);
4517 new_html
= g_strdup_printf(
4518 "%s\n<tr><td>%s</td><td>%s</td>"
4519 "<td style='text-align:center'>%s</td></tr>\n",
4520 html
, basename((char *)webkit_download_get_destination_uri(dl
->download
)),
4521 status_html
, cmd_html
);
4525 g_free(status_html
);
4536 * update all download tabs apart from one. Pass NULL if
4537 * you want to update all.
4540 update_download_tabs(struct tab
*apart_from
)
4543 if (!updating_dl_tabs
) {
4544 updating_dl_tabs
= 1; /* stop infinite recursion */
4545 TAILQ_FOREACH(t
, &tabs
, entry
)
4546 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
4547 && (t
!= apart_from
))
4548 xtp_page_dl(t
, NULL
);
4549 updating_dl_tabs
= 0;
4554 * update all cookie tabs apart from one. Pass NULL if
4555 * you want to update all.
4558 update_cookie_tabs(struct tab
*apart_from
)
4561 if (!updating_cl_tabs
) {
4562 updating_cl_tabs
= 1; /* stop infinite recursion */
4563 TAILQ_FOREACH(t
, &tabs
, entry
)
4564 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
4565 && (t
!= apart_from
))
4566 xtp_page_cl(t
, NULL
);
4567 updating_cl_tabs
= 0;
4572 * update all history tabs apart from one. Pass NULL if
4573 * you want to update all.
4576 update_history_tabs(struct tab
*apart_from
)
4580 if (!updating_hl_tabs
) {
4581 updating_hl_tabs
= 1; /* stop infinite recursion */
4582 TAILQ_FOREACH(t
, &tabs
, entry
)
4583 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4584 && (t
!= apart_from
))
4585 xtp_page_hl(t
, NULL
);
4586 updating_hl_tabs
= 0;
4590 /* cookie management XTP page */
4592 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4594 char *body
, *page
, *tmp
;
4595 int i
= 1; /* all ids start 1 */
4596 GSList
*sc
, *pc
, *pc_start
;
4598 char *type
, *table_headers
, *last_domain
;
4600 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4603 show_oops(NULL
, "%s invalid parameters", __func__
);
4607 /* Generate a new session key */
4608 if (!updating_cl_tabs
)
4609 generate_xtp_session_key(&cl_session_key
);
4612 table_headers
= g_strdup_printf("<table><tr>"
4615 "<th style='width:200px'>Value</th>"
4619 "<th>HTTP<br />only</th>"
4620 "<th style='width:40px'>Rm</th></tr>\n");
4622 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4623 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4627 last_domain
= strdup("");
4628 for (; sc
; sc
= sc
->next
) {
4631 if (strcmp(last_domain
, c
->domain
) != 0) {
4634 last_domain
= strdup(c
->domain
);
4638 body
= g_strdup_printf("%s</table>"
4640 body
, c
->domain
, table_headers
);
4644 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4645 c
->domain
, table_headers
);
4650 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4651 if (soup_cookie_equal(pc
->data
, c
)) {
4652 type
= "Session + Persistent";
4657 body
= g_strdup_printf(
4660 "<td style='word-wrap:normal'>%s</td>"
4662 " <textarea rows='4'>%s</textarea>"
4668 "<td style='text-align:center'>"
4669 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4676 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4691 soup_cookies_free(sc
);
4692 soup_cookies_free(pc
);
4694 /* small message if there are none */
4696 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4697 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4700 body
= g_strdup_printf("%s</table>", body
);
4703 page
= get_html_page("Cookie Jar", body
, "", TRUE
);
4705 g_free(table_headers
);
4706 g_free(last_domain
);
4708 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4709 update_cookie_tabs(t
);
4717 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4719 char *body
, *page
, *tmp
;
4721 int i
= 1; /* all ids start 1 */
4723 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4726 show_oops(NULL
, "%s invalid parameters", __func__
);
4730 /* Generate a new session key */
4731 if (!updating_hl_tabs
)
4732 generate_xtp_session_key(&hl_session_key
);
4735 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
4736 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4738 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4740 body
= g_strdup_printf(
4742 "<td><a href='%s'>%s</a></td>"
4744 "<td style='text-align: center'>"
4745 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4746 body
, h
->uri
, h
->uri
, h
->title
,
4747 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4748 XT_XTP_HL_REMOVE
, i
);
4754 /* small message if there are none */
4757 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4758 "colspan='3'>No History</td></tr>\n", body
);
4763 body
= g_strdup_printf("%s</table>", body
);
4766 page
= get_html_page("History", body
, "", TRUE
);
4770 * update all history manager tabs as the xtp session
4771 * key has now changed. No need to update the current tab.
4772 * Already did that above.
4774 update_history_tabs(t
);
4776 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4783 * Generate a web page detailing the status of any downloads
4786 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4788 struct download
*dl
;
4789 char *body
, *page
, *tmp
;
4793 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4796 show_oops(NULL
, "%s invalid parameters", __func__
);
4801 * Generate a new session key for next page instance.
4802 * This only happens for the top level call to xtp_page_dl()
4803 * in which case updating_dl_tabs is 0.
4805 if (!updating_dl_tabs
)
4806 generate_xtp_session_key(&dl_session_key
);
4808 /* header - with refresh so as to update */
4809 if (refresh_interval
>= 1)
4810 ref
= g_strdup_printf(
4811 "<meta http-equiv='refresh' content='%u"
4812 ";url=%s%d/%s/%d' />\n",
4821 body
= g_strdup_printf("<div align='center'>"
4822 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4823 "</p><table><tr><th style='width: 60%%'>"
4824 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4825 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4827 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4828 body
= xtp_page_dl_row(t
, body
, dl
);
4832 /* message if no downloads in list */
4835 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4836 " style='text-align: center'>"
4837 "No downloads</td></tr>\n", body
);
4842 body
= g_strdup_printf("%s</table></div>", body
);
4845 page
= get_html_page("Downloads", body
, ref
, 1);
4850 * update all download manager tabs as the xtp session
4851 * key has now changed. No need to update the current tab.
4852 * Already did that above.
4854 update_download_tabs(t
);
4856 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4863 search(struct tab
*t
, struct karg
*args
)
4867 if (t
== NULL
|| args
== NULL
) {
4868 show_oops(NULL
, "search invalid parameters");
4871 if (t
->search_text
== NULL
) {
4872 if (global_search
== NULL
)
4873 return (XT_CB_PASSTHROUGH
);
4875 t
->search_text
= g_strdup(global_search
);
4876 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4877 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4881 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4882 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4885 case XT_SEARCH_NEXT
:
4886 d
= t
->search_forward
;
4888 case XT_SEARCH_PREV
:
4889 d
= !t
->search_forward
;
4892 return (XT_CB_PASSTHROUGH
);
4895 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4897 return (XT_CB_HANDLED
);
4900 struct settings_args
{
4906 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4909 struct settings_args
*sa
= cb_args
;
4914 if (s
->flags
& XT_SF_RUNTIME
)
4920 *sa
->body
= g_strdup_printf(
4922 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4923 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4935 set_show(struct tab
*t
, struct karg
*args
)
4937 char *body
, *page
, *tmp
;
4939 struct settings_args sa
;
4941 bzero(&sa
, sizeof sa
);
4945 body
= g_strdup_printf("<div align='center'><table><tr>"
4946 "<th align='left'>Setting</th>"
4947 "<th align='left'>Value</th></tr>\n");
4949 settings_walk(print_setting
, &sa
);
4952 /* small message if there are none */
4955 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4956 "colspan='2'>No settings</td></tr>\n", body
);
4961 body
= g_strdup_printf("%s</table></div>", body
);
4964 page
= get_html_page("Settings", body
, "", 0);
4968 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4972 return (XT_CB_PASSTHROUGH
);
4976 set(struct tab
*t
, struct karg
*args
)
4981 if (args
== NULL
|| args
->s
== NULL
)
4982 return (set_show(t
, args
));
4985 p
= g_strstrip(args
->s
);
4988 return (set_show(t
, args
));
4990 /* we got some sort of string */
4991 val
= g_strrstr(p
, "=");
4994 val
= g_strchomp(val
);
4997 for (i
= 0; i
< LENGTH(rs
); i
++) {
4998 if (strcmp(rs
[i
].name
, p
))
5001 if (rs
[i
].activate
) {
5002 if (rs
[i
].activate(val
))
5003 show_oops(t
, "%s invalid value %s",
5006 show_oops(t
, ":set %s = %s", p
, val
);
5009 show_oops(t
, "not a runtime option: %s", p
);
5013 show_oops(t
, "unknown option: %s", p
);
5017 for (i
= 0; i
< LENGTH(rs
); i
++) {
5018 if (strcmp(rs
[i
].name
, p
))
5021 /* XXX this could use some cleanup */
5022 switch (rs
[i
].type
) {
5025 show_oops(t
, "%s = %d",
5026 rs
[i
].name
, *rs
[i
].ival
);
5027 else if (rs
[i
].s
&& rs
[i
].s
->get
)
5028 show_oops(t
, "%s = %s",
5030 rs
[i
].s
->get(&rs
[i
]));
5031 else if (rs
[i
].s
&& rs
[i
].s
->get
== NULL
)
5032 show_oops(t
, "%s = ...", rs
[i
].name
);
5034 show_oops(t
, "%s = ", rs
[i
].name
);
5038 show_oops(t
, "%s = %f",
5039 rs
[i
].name
, *rs
[i
].fval
);
5040 else if (rs
[i
].s
&& rs
[i
].s
->get
)
5041 show_oops(t
, "%s = %s",
5043 rs
[i
].s
->get(&rs
[i
]));
5044 else if (rs
[i
].s
&& rs
[i
].s
->get
== NULL
)
5045 show_oops(t
, "%s = ...", rs
[i
].name
);
5047 show_oops(t
, "%s = ", rs
[i
].name
);
5050 if (rs
[i
].sval
&& *rs
[i
].sval
)
5051 show_oops(t
, "%s = %s",
5052 rs
[i
].name
, *rs
[i
].sval
);
5053 else if (rs
[i
].s
&& rs
[i
].s
->get
)
5054 show_oops(t
, "%s = %s",
5056 rs
[i
].s
->get(&rs
[i
]));
5057 else if (rs
[i
].s
&& rs
[i
].s
->get
== NULL
)
5058 show_oops(t
, "%s = ...", rs
[i
].name
);
5060 show_oops(t
, "%s = ", rs
[i
].name
);
5063 show_oops(t
, "unknown type for %s", rs
[i
].name
);
5069 show_oops(t
, "unknown option: %s", p
);
5072 return (XT_CB_PASSTHROUGH
);
5076 session_save(struct tab
*t
, char *filename
)
5081 if (strlen(filename
) == 0)
5084 if (filename
[0] == '.' || filename
[0] == '/')
5088 if (save_tabs(t
, &a
))
5090 strlcpy(named_session
, filename
, sizeof named_session
);
5098 session_open(struct tab
*t
, char *filename
)
5103 if (strlen(filename
) == 0)
5106 if (filename
[0] == '.' || filename
[0] == '/')
5110 a
.i
= XT_SES_CLOSETABS
;
5111 if (open_tabs(t
, &a
))
5114 strlcpy(named_session
, filename
, sizeof named_session
);
5122 session_delete(struct tab
*t
, char *filename
)
5124 char file
[PATH_MAX
];
5127 if (strlen(filename
) == 0)
5130 if (filename
[0] == '.' || filename
[0] == '/')
5133 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
5137 if (!strcmp(filename
, named_session
))
5138 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
5139 sizeof named_session
);
5147 session_cmd(struct tab
*t
, struct karg
*args
)
5149 char *filename
= args
->s
;
5154 if (args
->i
& XT_SHOW
)
5155 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
5156 XT_SAVED_TABS_FILE
: named_session
);
5157 else if (args
->i
& XT_SAVE
) {
5158 if (session_save(t
, filename
)) {
5159 show_oops(t
, "Can't save session: %s",
5160 filename
? filename
: "INVALID");
5163 } else if (args
->i
& XT_OPEN
) {
5164 if (session_open(t
, filename
)) {
5165 show_oops(t
, "Can't open session: %s",
5166 filename
? filename
: "INVALID");
5169 } else if (args
->i
& XT_DELETE
) {
5170 if (session_delete(t
, filename
)) {
5171 show_oops(t
, "Can't delete session: %s",
5172 filename
? filename
: "INVALID");
5177 return (XT_CB_PASSTHROUGH
);
5181 * Make a hardcopy of the page
5184 print_page(struct tab
*t
, struct karg
*args
)
5186 WebKitWebFrame
*frame
;
5188 GtkPrintOperation
*op
;
5189 GtkPrintOperationAction action
;
5190 GtkPrintOperationResult print_res
;
5191 GError
*g_err
= NULL
;
5192 int marg_l
, marg_r
, marg_t
, marg_b
;
5194 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
5196 ps
= gtk_page_setup_new();
5197 op
= gtk_print_operation_new();
5198 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
5199 frame
= webkit_web_view_get_main_frame(t
->wv
);
5201 /* the default margins are too small, so we will bump them */
5202 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
5203 XT_PRINT_EXTRA_MARGIN
;
5204 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
5205 XT_PRINT_EXTRA_MARGIN
;
5206 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
5207 XT_PRINT_EXTRA_MARGIN
;
5208 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
5209 XT_PRINT_EXTRA_MARGIN
;
5212 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
5213 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
5214 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
5215 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
5217 gtk_print_operation_set_default_page_setup(op
, ps
);
5219 /* this appears to free 'op' and 'ps' */
5220 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
5222 /* check it worked */
5223 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
5224 show_oops(NULL
, "can't print: %s", g_err
->message
);
5225 g_error_free (g_err
);
5233 go_home(struct tab
*t
, struct karg
*args
)
5240 restart(struct tab
*t
, struct karg
*args
)
5244 a
.s
= XT_RESTART_TABS_FILE
;
5246 execvp(start_argv
[0], start_argv
);
5252 #define CTRL GDK_CONTROL_MASK
5253 #define MOD1 GDK_MOD1_MASK
5254 #define SHFT GDK_SHIFT_MASK
5256 /* inherent to GTK not all keys will be caught at all times */
5257 /* XXX sort key bindings */
5258 struct key_binding
{
5263 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
5265 { "cookiejar", MOD1
, 0, GDK_j
},
5266 { "downloadmgr", MOD1
, 0, GDK_d
},
5267 { "history", MOD1
, 0, GDK_h
},
5268 { "print", CTRL
, 0, GDK_p
},
5269 { "search", 0, 0, GDK_slash
},
5270 { "searchb", 0, 0, GDK_question
},
5271 { "statustoggle", CTRL
, 0, GDK_n
},
5272 { "command", 0, 0, GDK_colon
},
5273 { "qa", CTRL
, 0, GDK_q
},
5274 { "restart", MOD1
, 0, GDK_q
},
5275 { "js toggle", CTRL
, 0, GDK_j
},
5276 { "cookie toggle", MOD1
, 0, GDK_c
},
5277 { "togglesrc", CTRL
, 0, GDK_s
},
5278 { "yankuri", 0, 0, GDK_y
},
5279 { "pasteuricur", 0, 0, GDK_p
},
5280 { "pasteurinew", 0, 0, GDK_P
},
5281 { "toplevel toggle", 0, 0, GDK_F4
},
5282 { "help", 0, 0, GDK_F1
},
5283 { "run_script", MOD1
, 0, GDK_r
},
5286 { "searchnext", 0, 0, GDK_n
},
5287 { "searchprevious", 0, 0, GDK_N
},
5290 { "focusaddress", 0, 0, GDK_F6
},
5291 { "focussearch", 0, 0, GDK_F7
},
5294 { "hinting", 0, 0, GDK_f
},
5296 /* custom stylesheet */
5297 { "userstyle", 0, 0, GDK_i
},
5300 { "goback", 0, 0, GDK_BackSpace
},
5301 { "goback", MOD1
, 0, GDK_Left
},
5302 { "goforward", SHFT
, 0, GDK_BackSpace
},
5303 { "goforward", MOD1
, 0, GDK_Right
},
5304 { "reload", 0, 0, GDK_F5
},
5305 { "reload", CTRL
, 0, GDK_r
},
5306 { "reload", CTRL
, 0, GDK_l
},
5307 { "favorites", MOD1
, 1, GDK_f
},
5309 /* vertical movement */
5310 { "scrolldown", 0, 0, GDK_j
},
5311 { "scrolldown", 0, 0, GDK_Down
},
5312 { "scrollup", 0, 0, GDK_Up
},
5313 { "scrollup", 0, 0, GDK_k
},
5314 { "scrollbottom", 0, 0, GDK_G
},
5315 { "scrollbottom", 0, 0, GDK_End
},
5316 { "scrolltop", 0, 0, GDK_Home
},
5317 { "scrollpagedown", 0, 0, GDK_space
},
5318 { "scrollpagedown", CTRL
, 0, GDK_f
},
5319 { "scrollhalfdown", CTRL
, 0, GDK_d
},
5320 { "scrollpagedown", 0, 0, GDK_Page_Down
},
5321 { "scrollpageup", 0, 0, GDK_Page_Up
},
5322 { "scrollpageup", CTRL
, 0, GDK_b
},
5323 { "scrollhalfup", CTRL
, 0, GDK_u
},
5324 /* horizontal movement */
5325 { "scrollright", 0, 0, GDK_l
},
5326 { "scrollright", 0, 0, GDK_Right
},
5327 { "scrollleft", 0, 0, GDK_Left
},
5328 { "scrollleft", 0, 0, GDK_h
},
5329 { "scrollfarright", 0, 0, GDK_dollar
},
5330 { "scrollfarleft", 0, 0, GDK_0
},
5333 { "tabnew", CTRL
, 0, GDK_t
},
5334 { "999tabnew", CTRL
, 0, GDK_T
},
5335 { "tabclose", CTRL
, 1, GDK_w
},
5336 { "tabundoclose", 0, 0, GDK_U
},
5337 { "tabnext 1", CTRL
, 0, GDK_1
},
5338 { "tabnext 2", CTRL
, 0, GDK_2
},
5339 { "tabnext 3", CTRL
, 0, GDK_3
},
5340 { "tabnext 4", CTRL
, 0, GDK_4
},
5341 { "tabnext 5", CTRL
, 0, GDK_5
},
5342 { "tabnext 6", CTRL
, 0, GDK_6
},
5343 { "tabnext 7", CTRL
, 0, GDK_7
},
5344 { "tabnext 8", CTRL
, 0, GDK_8
},
5345 { "tabnext 9", CTRL
, 0, GDK_9
},
5346 { "tabfirst", CTRL
, 0, GDK_less
},
5347 { "tablast", CTRL
, 0, GDK_greater
},
5348 { "tabprevious", CTRL
, 0, GDK_Left
},
5349 { "tabnext", CTRL
, 0, GDK_Right
},
5350 { "focusout", CTRL
, 0, GDK_minus
},
5351 { "focusin", CTRL
, 0, GDK_plus
},
5352 { "focusin", CTRL
, 0, GDK_equal
},
5353 { "focusreset", CTRL
, 0, GDK_0
},
5355 /* command aliases (handy when -S flag is used) */
5356 { "promptopen", 0, 0, GDK_F9
},
5357 { "promptopencurrent", 0, 0, GDK_F10
},
5358 { "prompttabnew", 0, 0, GDK_F11
},
5359 { "prompttabnewcurrent",0, 0, GDK_F12
},
5361 TAILQ_HEAD(keybinding_list
, key_binding
);
5364 walk_kb(struct settings
*s
,
5365 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
5367 struct key_binding
*k
;
5370 if (s
== NULL
|| cb
== NULL
) {
5371 show_oops(NULL
, "walk_kb invalid parameters");
5375 TAILQ_FOREACH(k
, &kbl
, entry
) {
5381 if (gdk_keyval_name(k
->key
) == NULL
)
5384 strlcat(str
, k
->cmd
, sizeof str
);
5385 strlcat(str
, ",", sizeof str
);
5387 if (k
->mask
& GDK_SHIFT_MASK
)
5388 strlcat(str
, "S-", sizeof str
);
5389 if (k
->mask
& GDK_CONTROL_MASK
)
5390 strlcat(str
, "C-", sizeof str
);
5391 if (k
->mask
& GDK_MOD1_MASK
)
5392 strlcat(str
, "M1-", sizeof str
);
5393 if (k
->mask
& GDK_MOD2_MASK
)
5394 strlcat(str
, "M2-", sizeof str
);
5395 if (k
->mask
& GDK_MOD3_MASK
)
5396 strlcat(str
, "M3-", sizeof str
);
5397 if (k
->mask
& GDK_MOD4_MASK
)
5398 strlcat(str
, "M4-", sizeof str
);
5399 if (k
->mask
& GDK_MOD5_MASK
)
5400 strlcat(str
, "M5-", sizeof str
);
5402 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
5403 cb(s
, str
, cb_args
);
5408 init_keybindings(void)
5411 struct key_binding
*k
;
5413 for (i
= 0; i
< LENGTH(keys
); i
++) {
5414 k
= g_malloc0(sizeof *k
);
5415 k
->cmd
= keys
[i
].cmd
;
5416 k
->mask
= keys
[i
].mask
;
5417 k
->use_in_entry
= keys
[i
].use_in_entry
;
5418 k
->key
= keys
[i
].key
;
5419 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
5421 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
5422 k
->cmd
? k
->cmd
: "unnamed key");
5427 keybinding_clearall(void)
5429 struct key_binding
*k
, *next
;
5431 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
5432 next
= TAILQ_NEXT(k
, entry
);
5436 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
5437 k
->cmd
? k
->cmd
: "unnamed key");
5438 TAILQ_REMOVE(&kbl
, k
, entry
);
5444 keybinding_add(char *cmd
, char *key
, int use_in_entry
)
5446 struct key_binding
*k
;
5447 guint keyval
, mask
= 0;
5450 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s\n", cmd
, key
);
5452 /* Keys which are to be used in entry have been prefixed with an
5453 * exclamation mark. */
5457 /* find modifier keys */
5458 if (strstr(key
, "S-"))
5459 mask
|= GDK_SHIFT_MASK
;
5460 if (strstr(key
, "C-"))
5461 mask
|= GDK_CONTROL_MASK
;
5462 if (strstr(key
, "M1-"))
5463 mask
|= GDK_MOD1_MASK
;
5464 if (strstr(key
, "M2-"))
5465 mask
|= GDK_MOD2_MASK
;
5466 if (strstr(key
, "M3-"))
5467 mask
|= GDK_MOD3_MASK
;
5468 if (strstr(key
, "M4-"))
5469 mask
|= GDK_MOD4_MASK
;
5470 if (strstr(key
, "M5-"))
5471 mask
|= GDK_MOD5_MASK
;
5474 for (i
= strlen(key
) - 1; i
> 0; i
--)
5478 /* validate keyname */
5479 keyval
= gdk_keyval_from_name(key
);
5480 if (keyval
== GDK_VoidSymbol
) {
5481 warnx("invalid keybinding name %s", key
);
5484 /* must run this test too, gtk+ doesn't handle 10 for example */
5485 if (gdk_keyval_name(keyval
) == NULL
) {
5486 warnx("invalid keybinding name %s", key
);
5490 /* Remove eventual dupes. */
5491 TAILQ_FOREACH(k
, &kbl
, entry
)
5492 if (k
->key
== keyval
&& k
->mask
== mask
) {
5493 TAILQ_REMOVE(&kbl
, k
, entry
);
5499 k
= g_malloc0(sizeof *k
);
5500 k
->cmd
= g_strdup(cmd
);
5502 k
->use_in_entry
= use_in_entry
;
5505 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
5510 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
5511 k
->cmd
, gdk_keyval_name(keyval
));
5513 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
5519 add_kb(struct settings
*s
, char *entry
)
5523 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
5525 /* clearall is special */
5526 if (!strcmp(entry
, "clearall")) {
5527 keybinding_clearall();
5531 kb
= strstr(entry
, ",");
5537 return (keybinding_add(entry
, key
, key
[0] == '!'));
5543 int (*func
)(struct tab
*, struct karg
*);
5547 { "command", 0, command
, ':', 0 },
5548 { "search", 0, command
, '/', 0 },
5549 { "searchb", 0, command
, '?', 0 },
5550 { "togglesrc", 0, toggle_src
, 0, 0 },
5552 /* yanking and pasting */
5553 { "yankuri", 0, yank_uri
, 0, 0 },
5554 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5555 { "pasteuricur", 0, paste_uri
, XT_PASTE_CURRENT_TAB
, 0 },
5556 { "pasteurinew", 0, paste_uri
, XT_PASTE_NEW_TAB
, 0 },
5559 { "searchnext", 0, search
, XT_SEARCH_NEXT
, 0 },
5560 { "searchprevious", 0, search
, XT_SEARCH_PREV
, 0 },
5563 { "focusaddress", 0, focus
, XT_FOCUS_URI
, 0 },
5564 { "focussearch", 0, focus
, XT_FOCUS_SEARCH
, 0 },
5567 { "hinting", 0, hint
, 0, 0 },
5569 /* custom stylesheet */
5570 { "userstyle", 0, userstyle
, 0, 0 },
5573 { "goback", 0, navaction
, XT_NAV_BACK
, 0 },
5574 { "goforward", 0, navaction
, XT_NAV_FORWARD
, 0 },
5575 { "reload", 0, navaction
, XT_NAV_RELOAD
, 0 },
5577 /* vertical movement */
5578 { "scrolldown", 0, move
, XT_MOVE_DOWN
, 0 },
5579 { "scrollup", 0, move
, XT_MOVE_UP
, 0 },
5580 { "scrollbottom", 0, move
, XT_MOVE_BOTTOM
, 0 },
5581 { "scrolltop", 0, move
, XT_MOVE_TOP
, 0 },
5582 { "1", 0, move
, XT_MOVE_TOP
, 0 },
5583 { "scrollhalfdown", 0, move
, XT_MOVE_HALFDOWN
, 0 },
5584 { "scrollhalfup", 0, move
, XT_MOVE_HALFUP
, 0 },
5585 { "scrollpagedown", 0, move
, XT_MOVE_PAGEDOWN
, 0 },
5586 { "scrollpageup", 0, move
, XT_MOVE_PAGEUP
, 0 },
5587 /* horizontal movement */
5588 { "scrollright", 0, move
, XT_MOVE_RIGHT
, 0 },
5589 { "scrollleft", 0, move
, XT_MOVE_LEFT
, 0 },
5590 { "scrollfarright", 0, move
, XT_MOVE_FARRIGHT
, 0 },
5591 { "scrollfarleft", 0, move
, XT_MOVE_FARLEFT
, 0 },
5593 { "favorites", 0, xtp_page_fl
, 0, 0 },
5594 { "fav", 0, xtp_page_fl
, 0, 0 },
5595 { "favadd", 0, add_favorite
, 0, 0 },
5597 { "qall", 0, quit
, 0, 0 },
5598 { "quitall", 0, quit
, 0, 0 },
5599 { "w", 0, save_tabs
, 0, 0 },
5600 { "wq", 0, save_tabs_and_quit
, 0, 0 },
5601 { "help", 0, help
, 0, 0 },
5602 { "about", 0, about
, 0, 0 },
5603 { "stats", 0, stats
, 0, 0 },
5604 { "version", 0, about
, 0, 0 },
5607 { "js", 0, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5608 { "save", 1, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5609 { "domain", 2, js_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
5610 { "fqdn", 2, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5611 { "show", 1, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5612 { "all", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5613 { "persistent", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
5614 { "session", 2, js_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
5615 { "toggle", 1, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5616 { "domain", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5617 { "fqdn", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5619 /* cookie command */
5620 { "cookie", 0, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5621 { "save", 1, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5622 { "domain", 2, cookie_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
5623 { "fqdn", 2, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5624 { "show", 1, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5625 { "all", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5626 { "persistent", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
5627 { "session", 2, cookie_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
5628 { "toggle", 1, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5629 { "domain", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5630 { "fqdn", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5632 /* toplevel (domain) command */
5633 { "toplevel", 0, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
5634 { "toggle", 1, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
5637 { "cookiejar", 0, xtp_page_cl
, 0, 0 },
5640 { "cert", 0, cert_cmd
, XT_SHOW
, 0 },
5641 { "save", 1, cert_cmd
, XT_SAVE
, 0 },
5642 { "show", 1, cert_cmd
, XT_SHOW
, 0 },
5644 { "ca", 0, ca_cmd
, 0, 0 },
5645 { "downloadmgr", 0, xtp_page_dl
, 0, 0 },
5646 { "dl", 0, xtp_page_dl
, 0, 0 },
5647 { "h", 0, xtp_page_hl
, 0, 0 },
5648 { "history", 0, xtp_page_hl
, 0, 0 },
5649 { "home", 0, go_home
, 0, 0 },
5650 { "restart", 0, restart
, 0, 0 },
5651 { "urlhide", 0, urlaction
, XT_URL_HIDE
, 0 },
5652 { "urlshow", 0, urlaction
, XT_URL_SHOW
, 0 },
5653 { "statustoggle", 0, statustoggle
, 0, 0 },
5654 { "run_script", 0, run_page_script
, 0, XT_USERARG
},
5656 { "print", 0, print_page
, 0, 0 },
5659 { "focusin", 0, resizetab
, XT_ZOOM_IN
, 0 },
5660 { "focusout", 0, resizetab
, XT_ZOOM_OUT
, 0 },
5661 { "focusreset", 0, resizetab
, XT_ZOOM_NORMAL
, 0 },
5662 { "q", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5663 { "quit", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5664 { "open", 0, tabaction
, XT_TAB_OPEN
, XT_URLARG
},
5665 { "tabclose", 0, tabaction
, XT_TAB_DELETE
, XT_PREFIX
| XT_INTARG
},
5666 { "tabedit", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5667 { "tabfirst", 0, movetab
, XT_TAB_FIRST
, 0 },
5668 { "tabhide", 0, tabaction
, XT_TAB_HIDE
, 0 },
5669 { "tablast", 0, movetab
, XT_TAB_LAST
, 0 },
5670 { "tabnew", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5671 { "tabnext", 0, movetab
, XT_TAB_NEXT
, XT_PREFIX
| XT_INTARG
},
5672 { "tabnextstyle", 0, tabaction
, XT_TAB_NEXTSTYLE
, 0 },
5673 { "tabprevious", 0, movetab
, XT_TAB_PREV
, XT_PREFIX
| XT_INTARG
},
5674 { "tabrewind", 0, movetab
, XT_TAB_FIRST
, 0 },
5675 { "tabshow", 0, tabaction
, XT_TAB_SHOW
, 0 },
5676 { "tabundoclose", 0, tabaction
, XT_TAB_UNDO_CLOSE
, 0 },
5677 { "buffers", 0, buffers
, 0, 0 },
5678 { "ls", 0, buffers
, 0, 0 },
5679 { "tabs", 0, buffers
, 0, 0 },
5681 /* command aliases (handy when -S flag is used) */
5682 { "promptopen", 0, command
, XT_CMD_OPEN
, 0 },
5683 { "promptopencurrent", 0, command
, XT_CMD_OPEN_CURRENT
, 0 },
5684 { "prompttabnew", 0, command
, XT_CMD_TABNEW
, 0 },
5685 { "prompttabnewcurrent",0, command
, XT_CMD_TABNEW_CURRENT
, 0 },
5688 { "set", 0, set
, 0, XT_USERARG
},
5690 { "fullscreen", 0, fullscreen
, 0, 0 },
5691 { "f", 0, fullscreen
, 0, 0 },
5694 { "session", 0, session_cmd
, XT_SHOW
, 0 },
5695 { "delete", 1, session_cmd
, XT_DELETE
, XT_USERARG
},
5696 { "open", 1, session_cmd
, XT_OPEN
, XT_USERARG
},
5697 { "save", 1, session_cmd
, XT_SAVE
, XT_USERARG
},
5698 { "show", 1, session_cmd
, XT_SHOW
, 0 },
5705 } cmd_status
= {-1, 0};
5708 wv_release_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5711 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 1)
5718 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5725 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5727 else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5733 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5735 a
.i
= XT_NAV_FORWARD
;
5745 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5747 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5749 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5756 * cancel, remove, etc. downloads
5759 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5761 struct download find
, *d
= NULL
;
5763 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5765 /* some commands require a valid download id */
5766 if (cmd
!= XT_XTP_DL_LIST
) {
5767 /* lookup download in question */
5769 d
= RB_FIND(download_list
, &downloads
, &find
);
5772 show_oops(t
, "%s: no such download", __func__
);
5777 /* decide what to do */
5779 case XT_XTP_DL_CANCEL
:
5780 webkit_download_cancel(d
->download
);
5782 case XT_XTP_DL_REMOVE
:
5783 webkit_download_cancel(d
->download
); /* just incase */
5784 g_object_unref(d
->download
);
5785 RB_REMOVE(download_list
, &downloads
, d
);
5787 case XT_XTP_DL_LIST
:
5791 show_oops(t
, "%s: unknown command", __func__
);
5794 xtp_page_dl(t
, NULL
);
5798 * Actions on history, only does one thing for now, but
5799 * we provide the function for future actions
5802 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5804 struct history
*h
, *next
;
5808 case XT_XTP_HL_REMOVE
:
5809 /* walk backwards, as listed in reverse */
5810 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5811 next
= RB_PREV(history_list
, &hl
, h
);
5813 RB_REMOVE(history_list
, &hl
, h
);
5814 g_free((gpointer
) h
->title
);
5815 g_free((gpointer
) h
->uri
);
5822 case XT_XTP_HL_LIST
:
5823 /* Nothing - just xtp_page_hl() below */
5826 show_oops(t
, "%s: unknown command", __func__
);
5830 xtp_page_hl(t
, NULL
);
5833 /* remove a favorite */
5835 remove_favorite(struct tab
*t
, int index
)
5837 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5838 char *new_favs
, *tmp
;
5843 /* open favorites */
5844 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5846 if ((f
= fopen(file
, "r")) == NULL
) {
5847 show_oops(t
, "%s: can't open favorites: %s",
5848 __func__
, strerror(errno
));
5852 /* build a string which will become the new favroites file */
5853 new_favs
= g_strdup("");
5856 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5857 if (feof(f
) || ferror(f
))
5859 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5866 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5867 if (feof(f
) || ferror(f
)) {
5868 show_oops(t
, "%s: can't parse favorites %s",
5869 __func__
, strerror(errno
));
5874 /* as long as this isn't the one we are deleting add to file */
5877 new_favs
= g_strdup_printf("%s%s\n%s\n",
5878 new_favs
, title
, uri
);
5890 /* write back new favorites file */
5891 if ((f
= fopen(file
, "w")) == NULL
) {
5892 show_oops(t
, "%s: can't open favorites: %s",
5893 __func__
, strerror(errno
));
5897 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5910 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5913 case XT_XTP_FL_LIST
:
5914 /* nothing, just the below call to xtp_page_fl() */
5916 case XT_XTP_FL_REMOVE
:
5917 remove_favorite(t
, arg
);
5920 show_oops(t
, "%s: invalid favorites command", __func__
);
5924 xtp_page_fl(t
, NULL
);
5928 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5931 case XT_XTP_CL_LIST
:
5932 /* nothing, just xtp_page_cl() */
5934 case XT_XTP_CL_REMOVE
:
5938 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5942 xtp_page_cl(t
, NULL
);
5945 /* link an XTP class to it's session key and handler function */
5946 struct xtp_despatch
{
5949 void (*handle_func
)(struct tab
*, uint8_t, int);
5952 struct xtp_despatch xtp_despatches
[] = {
5953 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5954 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5955 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5956 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5957 { XT_XTP_INVALID
, NULL
, NULL
}
5961 * is the url xtp protocol? (xxxt://)
5962 * if so, parse and despatch correct bahvior
5965 parse_xtp_url(struct tab
*t
, const char *url
)
5967 char *dup
= NULL
, *p
, *last
;
5968 uint8_t n_tokens
= 0;
5969 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5970 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5975 * tokens array meaning:
5977 * tokens[1] = session key
5978 * tokens[2] = action
5979 * tokens[3] = optional argument
5982 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5984 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5987 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5989 /* split out the url */
5990 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5991 (p
= strtok_r(NULL
, "/", &last
))) {
5993 tokens
[n_tokens
++] = p
;
5996 /* should be atleast three fields 'class/seskey/command/arg' */
6000 dsp
= xtp_despatches
;
6001 req_class
= atoi(tokens
[0]);
6002 while (dsp
->xtp_class
) {
6003 if (dsp
->xtp_class
== req_class
) {
6010 /* did we find one atall? */
6011 if (dsp_match
== NULL
) {
6012 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
6016 /* check session key and call despatch function */
6017 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
6018 ret
= TRUE
; /* all is well, this was a valid xtp request */
6019 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
6032 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
6034 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
6036 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
6039 show_oops(NULL
, "activate_uri_entry_cb invalid parameters");
6044 show_oops(t
, "activate_uri_entry_cb no uri");
6048 uri
+= strspn(uri
, "\t ");
6050 /* if xxxt:// treat specially */
6051 if (parse_xtp_url(t
, uri
))
6054 /* otherwise continue to load page normally */
6055 load_uri(t
, (gchar
*)uri
);
6060 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
6062 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
6063 char *newuri
= NULL
;
6066 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
6069 show_oops(NULL
, "activate_search_entry_cb invalid parameters");
6073 if (search_string
== NULL
) {
6074 show_oops(t
, "no search_string");
6078 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
6080 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
6081 newuri
= g_strdup_printf(search_string
, enc_search
);
6085 webkit_web_view_load_uri(t
->wv
, newuri
);
6093 check_and_set_cookie(const gchar
*uri
, struct tab
*t
)
6095 struct domain
*d
= NULL
;
6098 if (uri
== NULL
|| t
== NULL
)
6101 if ((d
= wl_find_uri(uri
, &c_wl
)) == NULL
)
6106 DNPRINTF(XT_D_COOKIE
, "check_and_set_cookie: %s %s\n",
6107 es
? "enable" : "disable", uri
);
6109 g_object_set(G_OBJECT(t
->settings
),
6110 "enable-html5-local-storage", es
, (char *)NULL
);
6111 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6115 check_and_set_js(const gchar
*uri
, struct tab
*t
)
6117 struct domain
*d
= NULL
;
6120 if (uri
== NULL
|| t
== NULL
)
6123 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6128 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
6129 es
? "enable" : "disable", uri
);
6131 g_object_set(G_OBJECT(t
->settings
),
6132 "enable-scripts", es
, (char *)NULL
);
6133 g_object_set(G_OBJECT(t
->settings
),
6134 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
6135 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6137 button_set_stockid(t
->js_toggle
,
6138 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
6142 color_address_bar(gpointer p
)
6145 struct tab
*tt
, *t
= p
;
6146 gchar
*col_str
= XT_COLOR_YELLOW
;
6148 DNPRINTF(XT_D_URL
, "%s:\n", __func__
);
6150 /* make sure t still exists */
6153 TAILQ_FOREACH(tt
, &tabs
, entry
)
6159 switch (load_compare_cert(t
, NULL
)) {
6161 col_str
= XT_COLOR_BLUE
;
6164 col_str
= XT_COLOR_GREEN
;
6166 case CERT_UNTRUSTED
:
6167 col_str
= XT_COLOR_YELLOW
;
6170 col_str
= XT_COLOR_RED
;
6174 gdk_color_parse(col_str
, &color
);
6175 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
6177 if (!strcmp(col_str
, XT_COLOR_WHITE
))
6178 statusbar_modify_attr(t
, col_str
, XT_COLOR_BLACK
);
6180 statusbar_modify_attr(t
, XT_COLOR_BLACK
, col_str
);
6184 return (FALSE
/* kill thread */);
6188 show_ca_status(struct tab
*t
, const char *uri
)
6191 gchar
*col_str
= XT_COLOR_WHITE
;
6193 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
6194 ssl_strict_certs
, ssl_ca_file
, uri
);
6201 if (ssl_ca_file
== NULL
) {
6202 if (g_str_has_prefix(uri
, "http://"))
6204 if (g_str_has_prefix(uri
, "https://")) {
6205 col_str
= XT_COLOR_RED
;
6210 if (g_str_has_prefix(uri
, "http://") ||
6211 !g_str_has_prefix(uri
, "https://"))
6214 /* thread the coloring of the address bar */
6215 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE
,
6216 color_address_bar
, t
, NULL
);
6221 gdk_color_parse(col_str
, &color
);
6222 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
6224 if (!strcmp(col_str
, XT_COLOR_WHITE
))
6225 statusbar_modify_attr(t
, col_str
, XT_COLOR_BLACK
);
6227 statusbar_modify_attr(t
, XT_COLOR_BLACK
, col_str
);
6232 free_favicon(struct tab
*t
)
6234 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p\n",
6235 __func__
, t
->icon_download
, t
->icon_request
);
6237 if (t
->icon_request
)
6238 g_object_unref(t
->icon_request
);
6239 if (t
->icon_dest_uri
)
6240 g_free(t
->icon_dest_uri
);
6242 t
->icon_request
= NULL
;
6243 t
->icon_dest_uri
= NULL
;
6247 xt_icon_from_name(struct tab
*t
, gchar
*name
)
6249 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
6250 GTK_ENTRY_ICON_PRIMARY
, "text-html");
6252 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
6253 GTK_ENTRY_ICON_PRIMARY
, "text-html");
6255 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
6256 GTK_ENTRY_ICON_PRIMARY
, NULL
);
6260 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pb
)
6262 GdkPixbuf
*pb_scaled
;
6264 if (gdk_pixbuf_get_width(pb
) > 16 || gdk_pixbuf_get_height(pb
) > 16)
6265 pb_scaled
= gdk_pixbuf_scale_simple(pb
, 16, 16,
6266 GDK_INTERP_BILINEAR
);
6270 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
6271 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
6273 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.statusbar
),
6274 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
6276 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
6277 GTK_ENTRY_ICON_PRIMARY
, NULL
);
6279 if (pb_scaled
!= pb
)
6280 g_object_unref(pb_scaled
);
6284 xt_icon_from_file(struct tab
*t
, char *file
)
6288 if (g_str_has_prefix(file
, "file://"))
6289 file
+= strlen("file://");
6291 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
6293 xt_icon_from_pixbuf(t
, pb
);
6296 xt_icon_from_name(t
, "text-html");
6300 is_valid_icon(char *file
)
6303 const char *mime_type
;
6307 gf
= g_file_new_for_path(file
);
6308 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6310 mime_type
= g_file_info_get_content_type(fi
);
6311 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
6312 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
6313 g_strcmp0(mime_type
, "image/png") == 0 ||
6314 g_strcmp0(mime_type
, "image/gif") == 0 ||
6315 g_strcmp0(mime_type
, "application/octet-stream") == 0;
6323 set_favicon_from_file(struct tab
*t
, char *file
)
6327 if (t
== NULL
|| file
== NULL
)
6330 if (g_str_has_prefix(file
, "file://"))
6331 file
+= strlen("file://");
6332 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
6334 if (!stat(file
, &sb
)) {
6335 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
6336 /* corrupt icon so trash it */
6337 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
6340 /* no need to set icon to default here */
6344 xt_icon_from_file(t
, file
);
6348 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6351 WebKitDownloadStatus status
= webkit_download_get_status(download
);
6352 struct tab
*tt
= NULL
, *t
= NULL
;
6355 * find the webview instead of passing in the tab as it could have been
6356 * deleted from underneath us.
6358 TAILQ_FOREACH(tt
, &tabs
, entry
) {
6367 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
6368 __func__
, t
->tab_id
, status
);
6371 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
6373 t
->icon_download
= NULL
;
6376 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
6379 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
6382 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
6384 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
6385 __func__
, t
->tab_id
);
6386 t
->icon_download
= NULL
;
6389 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
6392 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
6393 __func__
, t
->icon_dest_uri
);
6394 set_favicon_from_file(t
, t
->icon_dest_uri
);
6395 /* these will be freed post callback */
6396 t
->icon_request
= NULL
;
6397 t
->icon_download
= NULL
;
6405 abort_favicon_download(struct tab
*t
)
6407 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
6409 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6410 if (t
->icon_download
) {
6411 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
6412 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
6413 webkit_download_cancel(t
->icon_download
);
6414 t
->icon_download
= NULL
;
6419 xt_icon_from_name(t
, "text-html");
6423 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
6425 DNPRINTF(XT_D_DOWNLOAD
, "%s %s\n", __func__
, uri
);
6427 if (uri
== NULL
|| t
== NULL
)
6430 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6431 /* take icon from WebKitIconDatabase */
6434 pb
= webkit_web_view_get_icon_pixbuf(wv
);
6436 xt_icon_from_pixbuf(t
, pb
);
6439 xt_icon_from_name(t
, "text-html");
6440 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6441 /* download icon to cache dir */
6442 gchar
*name_hash
, file
[PATH_MAX
];
6445 if (t
->icon_request
) {
6446 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
6450 /* check to see if we got the icon in cache */
6451 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
6452 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
6455 if (!stat(file
, &sb
)) {
6456 if (sb
.st_size
> 0) {
6457 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
6459 set_favicon_from_file(t
, file
);
6463 /* corrupt icon so trash it */
6464 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
6469 /* create download for icon */
6470 t
->icon_request
= webkit_network_request_new(uri
);
6471 if (t
->icon_request
== NULL
) {
6472 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
6477 t
->icon_download
= webkit_download_new(t
->icon_request
);
6478 if (t
->icon_download
== NULL
)
6481 /* we have to free icon_dest_uri later */
6482 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
6483 webkit_download_set_destination_uri(t
->icon_download
,
6486 if (webkit_download_get_status(t
->icon_download
) ==
6487 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6488 g_object_unref(t
->icon_request
);
6489 g_free(t
->icon_dest_uri
);
6490 t
->icon_request
= NULL
;
6491 t
->icon_dest_uri
= NULL
;
6495 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
6496 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
6498 webkit_download_start(t
->icon_download
);
6503 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
6505 const gchar
*uri
= NULL
, *title
= NULL
;
6506 struct history
*h
, find
;
6510 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
6511 webkit_web_view_get_load_status(wview
),
6512 get_uri(t
) ? get_uri(t
) : "NOTHING");
6515 show_oops(NULL
, "notify_load_status_cb invalid parameters");
6519 switch (webkit_web_view_get_load_status(wview
)) {
6520 case WEBKIT_LOAD_PROVISIONAL
:
6522 abort_favicon_download(t
);
6523 #if GTK_CHECK_VERSION(2, 20, 0)
6524 gtk_widget_show(t
->spinner
);
6525 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
6527 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
6529 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
6531 /* assume we are a new address */
6532 gdk_color_parse("white", &color
);
6533 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
6534 statusbar_modify_attr(t
, "white", XT_COLOR_BLACK
);
6536 /* take focus if we are visible */
6542 case WEBKIT_LOAD_COMMITTED
:
6547 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
6553 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
6555 /* check if js white listing is enabled */
6556 if (enable_cookie_whitelist
)
6557 check_and_set_cookie(uri
, t
);
6558 if (enable_js_whitelist
)
6559 check_and_set_js(uri
, t
);
6565 /* we know enough to autosave the session */
6566 if (session_autosave
) {
6571 show_ca_status(t
, uri
);
6574 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
6578 case WEBKIT_LOAD_FINISHED
:
6584 if (!strncmp(uri
, "http://", strlen("http://")) ||
6585 !strncmp(uri
, "https://", strlen("https://")) ||
6586 !strncmp(uri
, "file://", strlen("file://"))) {
6588 h
= RB_FIND(history_list
, &hl
, &find
);
6590 title
= get_title(t
, FALSE
);
6591 h
= g_malloc(sizeof *h
);
6592 h
->uri
= g_strdup(uri
);
6593 h
->title
= g_strdup(title
);
6594 RB_INSERT(history_list
, &hl
, h
);
6595 completion_add_uri(h
->uri
);
6596 update_history_tabs(NULL
);
6600 set_status(t
, (char *)uri
, XT_STATUS_URI
);
6601 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6602 case WEBKIT_LOAD_FAILED
:
6605 #if GTK_CHECK_VERSION(2, 20, 0)
6606 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
6607 gtk_widget_hide(t
->spinner
);
6610 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
6615 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
6617 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
6618 webkit_web_view_can_go_back(wview
));
6620 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
6621 webkit_web_view_can_go_forward(wview
));
6626 notify_load_error_cb(WebKitWebView
* wview
, WebKitWebFrame
*web_frame
,
6627 gchar
*uri
, gpointer web_error
,struct tab
*t
)
6630 * XXX this function is wrong
6631 * it overwrites perfectly good urls with garbage on load errors
6632 * those happen often when popups fail to resolve dns
6636 t
->tmp_uri
= g_strdup(uri
);
6637 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
6638 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
6639 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
6640 set_status(t
, uri
, XT_STATUS_NOTHING
);
6647 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
6649 const gchar
*title
= NULL
, *win_title
= NULL
;
6651 title
= get_title(t
, FALSE
);
6652 win_title
= get_title(t
, TRUE
);
6653 gtk_label_set_text(GTK_LABEL(t
->label
), title
);
6654 gtk_label_set_text(GTK_LABEL(t
->tab_elems
.label
), title
);
6655 if (t
->tab_id
== gtk_notebook_get_current_page(notebook
))
6656 gtk_window_set_title(GTK_WINDOW(main_window
), win_title
);
6660 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6662 run_script(t
, JS_HINTING
);
6666 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
6668 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
6669 progress
== 100 ? 0 : (double)progress
/ 100);
6670 if (show_url
== 0) {
6671 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.statusbar
),
6672 progress
== 100 ? 0 : (double)progress
/ 100);
6675 update_statusbar_position(NULL
, NULL
);
6679 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
6680 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
6681 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
6684 WebKitWebNavigationReason reason
;
6685 struct domain
*d
= NULL
;
6688 show_oops(NULL
, "webview_npd_cb invalid parameters");
6692 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
6694 webkit_network_request_get_uri(request
));
6696 uri
= (char *)webkit_network_request_get_uri(request
);
6698 /* if this is an xtp url, we don't load anything else */
6699 if (parse_xtp_url(t
, uri
))
6702 if (t
->ctrl_click
) {
6704 create_new_tab(uri
, NULL
, ctrl_click_focus
, -1);
6705 webkit_web_policy_decision_ignore(pd
);
6706 return (TRUE
); /* we made the decission */
6710 * This is a little hairy but it comes down to this:
6711 * when we run in whitelist mode we have to assist the browser in
6712 * opening the URL that it would have opened in a new tab.
6714 reason
= webkit_web_navigation_action_get_reason(na
);
6715 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6716 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
6717 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6718 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6720 webkit_web_policy_decision_use(pd
);
6721 return (TRUE
); /* we made the decision */
6728 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6731 struct domain
*d
= NULL
;
6733 WebKitWebView
*webview
= NULL
;
6735 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6736 webkit_web_view_get_uri(wv
));
6739 /* open in current tab */
6741 } else if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6742 uri
= webkit_web_view_get_uri(wv
);
6743 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6746 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6748 } else if (enable_scripts
== 1) {
6749 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6757 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6760 struct domain
*d
= NULL
;
6762 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6764 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6765 uri
= webkit_web_view_get_uri(wv
);
6766 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6770 } else if (enable_scripts
== 1)
6777 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6779 /* we can not eat the event without throwing gtk off so defer it */
6781 /* catch middle click */
6782 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6787 /* catch ctrl click */
6788 if (e
->type
== GDK_BUTTON_RELEASE
&&
6789 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6794 return (XT_CB_PASSTHROUGH
);
6798 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6800 struct mime_type
*m
;
6802 m
= find_mime_type(mime_type
);
6810 show_oops(t
, "can't fork mime handler");
6820 execlp(m
->mt_action
, m
->mt_action
,
6821 webkit_network_request_get_uri(request
), (void *)NULL
);
6830 get_mime_type(char *file
)
6832 const char *mime_type
;
6836 if (g_str_has_prefix(file
, "file://"))
6837 file
+= strlen("file://");
6839 gf
= g_file_new_for_path(file
);
6840 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6842 mime_type
= g_file_info_get_content_type(fi
);
6850 run_download_mimehandler(char *mime_type
, char *file
)
6852 struct mime_type
*m
;
6854 m
= find_mime_type(mime_type
);
6860 show_oops(NULL
, "can't fork download mime handler");
6870 if (g_str_has_prefix(file
, "file://"))
6871 file
+= strlen("file://");
6872 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6881 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6884 WebKitDownloadStatus status
;
6885 const gchar
*file
= NULL
, *mime
= NULL
;
6887 if (download
== NULL
)
6889 status
= webkit_download_get_status(download
);
6890 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6893 file
= webkit_download_get_destination_uri(download
);
6896 mime
= get_mime_type((char *)file
);
6900 run_download_mimehandler((char *)mime
, (char *)file
);
6904 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6905 WebKitNetworkRequest
*request
, char *mime_type
,
6906 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6909 show_oops(NULL
, "webview_mimetype_cb invalid parameters");
6913 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6914 t
->tab_id
, mime_type
);
6916 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6917 webkit_web_policy_decision_ignore(decision
);
6922 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6923 webkit_web_policy_decision_download(decision
);
6931 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6935 const gchar
*suggested_name
;
6936 gchar
*filename
= NULL
;
6938 struct download
*download_entry
;
6941 if (wk_download
== NULL
|| t
== NULL
) {
6942 show_oops(NULL
, "%s invalid parameters", __func__
);
6946 suggested_name
= webkit_download_get_suggested_filename(wk_download
);
6947 if (suggested_name
== NULL
)
6948 return (FALSE
); /* abort download */
6959 filename
= g_strdup_printf("%d%s", i
, suggested_name
);
6961 uri
= g_strdup_printf("file://%s/%s", download_dir
, i
?
6962 filename
: suggested_name
);
6964 } while (!stat(uri
+ strlen("file://"), &sb
));
6966 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6967 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6969 webkit_download_set_destination_uri(wk_download
, uri
);
6971 if (webkit_download_get_status(wk_download
) ==
6972 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6973 show_oops(t
, "%s: download failed to start", __func__
);
6975 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6977 /* connect "download first" mime handler */
6978 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6979 G_CALLBACK(download_status_changed_cb
), NULL
);
6981 download_entry
= g_malloc(sizeof(struct download
));
6982 download_entry
->download
= wk_download
;
6983 download_entry
->tab
= t
;
6984 download_entry
->id
= next_download_id
++;
6985 RB_INSERT(download_list
, &downloads
, download_entry
);
6986 /* get from history */
6987 g_object_ref(wk_download
);
6988 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6989 show_oops(t
, "Download of '%s' started...",
6990 basename((char *)webkit_download_get_destination_uri(wk_download
)));
6999 /* sync other download manager tabs */
7000 update_download_tabs(NULL
);
7003 * NOTE: never redirect/render the current tab before this
7004 * function returns. This will cause the download to never start.
7006 return (ret
); /* start download */
7010 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
7012 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
7015 show_oops(NULL
, "webview_hover_cb");
7020 set_status(t
, uri
, XT_STATUS_LINK
);
7023 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
7028 mark(struct tab
*t
, struct karg
*arg
)
7034 if ((index
= marktoindex(mark
)) == -1)
7037 if (arg
->i
== XT_MARK_SET
)
7038 t
->mark
[index
] = gtk_adjustment_get_value(t
->adjust_v
);
7039 else if (arg
->i
== XT_MARK_GOTO
) {
7040 if (t
->mark
[index
] == XT_INVALID_MARK
) {
7041 show_oops(t
, "mark '%c' does not exist", mark
);
7044 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7045 something changes the document size */
7046 gtk_adjustment_set_value(t
->adjust_v
, t
->mark
[index
]);
7053 marks_clear(struct tab
*t
)
7057 for (i
= 0; i
< LENGTH(t
->mark
); i
++)
7058 t
->mark
[i
] = XT_INVALID_MARK
;
7064 char file
[PATH_MAX
];
7065 char *line
= NULL
, *p
, mark
;
7070 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_QMARKS_FILE
);
7071 if ((f
= fopen(file
, "r+")) == NULL
) {
7072 show_oops(NULL
, "Can't open quickmarks file: %s", strerror(errno
));
7076 for (i
= 1; ; i
++) {
7077 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
7078 if (feof(f
) || ferror(f
))
7080 if (strlen(line
) == 0 || line
[0] == '#') {
7086 p
= strtok(line
, " \t");
7088 if (p
== NULL
|| strlen(p
) != 1 ||
7089 (index
= marktoindex(*p
)) == -1) {
7090 warnx("corrupt quickmarks file, line %d", i
);
7095 p
= strtok(NULL
, " \t");
7096 if (qmarks
[index
] != NULL
)
7097 g_free(qmarks
[index
]);
7098 qmarks
[index
] = g_strdup(p
);
7109 char file
[PATH_MAX
];
7113 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_QMARKS_FILE
);
7114 if ((f
= fopen(file
, "r+")) == NULL
) {
7115 show_oops(NULL
, "Can't open quickmarks file: %s", strerror(errno
));
7119 for (i
= 0; i
< XT_NOMARKS
; i
++)
7120 if (qmarks
[i
] != NULL
)
7121 fprintf(f
, "%c %s\n", indextomark(i
), qmarks
[i
]);
7129 qmark(struct tab
*t
, struct karg
*arg
)
7134 mark
= arg
->s
[strlen(arg
->s
)-1];
7135 index
= marktoindex(mark
);
7141 if (qmarks
[index
] != NULL
)
7142 g_free(qmarks
[index
]);
7144 qmarks_load(); /* sync if multiple instances */
7145 qmarks
[index
] = g_strdup(get_uri(t
));
7149 if (qmarks
[index
] != NULL
)
7150 load_uri(t
, qmarks
[index
]);
7152 show_oops(t
, "quickmark \"%c\" does not exist",
7158 if (qmarks
[index
] != NULL
)
7159 create_new_tab(qmarks
[index
], NULL
, 1, -1);
7161 show_oops(t
, "quickmark \"%c\" does not exist",
7172 go_up(struct tab
*t
, struct karg
*args
)
7178 levels
= atoi(args
->s
);
7182 uri
= g_strdup(webkit_web_view_get_uri(t
->wv
));
7183 if ((tmp
= strstr(uri
, XT_PROTO_DELIM
)) == NULL
)
7185 tmp
+= strlen(XT_PROTO_DELIM
);
7187 /* if an uri starts with a slash, leave it alone (for file:///) */
7194 p
= strrchr(tmp
, '/');
7208 gototab(struct tab
*t
, struct karg
*args
)
7211 struct karg arg
= {0, NULL
, -1};
7213 tab
= atoi(args
->s
);
7215 arg
.i
= XT_TAB_NEXT
;
7224 zoom_amount(struct tab
*t
, struct karg
*arg
)
7226 struct karg narg
= {0, NULL
, -1};
7228 narg
.i
= atoi(arg
->s
);
7229 resizetab(t
, &narg
);
7235 flip_colon(struct tab
*t
, struct karg
*arg
)
7237 struct karg narg
= {0, NULL
, -1};
7240 if (t
== NULL
|| arg
== NULL
)
7243 p
= strstr(arg
->s
, ":");
7255 /* buffer commands receive the regex that triggered them in arg.s */
7256 char bcmd
[XT_BUFCMD_SZ
];
7260 #define XT_PRE_NO (0)
7261 #define XT_PRE_YES (1)
7262 #define XT_PRE_MAYBE (2)
7264 int (*func
)(struct tab
*, struct karg
*);
7268 { "^[0-9]*gu$", XT_PRE_MAYBE
, "gu", go_up
, 0 },
7269 { "^gg$", XT_PRE_NO
, "gg", move
, XT_MOVE_TOP
},
7270 { "^gG$", XT_PRE_NO
, "gG", move
, XT_MOVE_BOTTOM
},
7271 { "^[0-9]+%$", XT_PRE_YES
, "%", move
, XT_MOVE_PERCENT
},
7272 { "^gh$", XT_PRE_NO
, "gh", go_home
, 0 },
7273 { "^m[a-zA-Z0-9]$", XT_PRE_NO
, "m", mark
, XT_MARK_SET
},
7274 { "^['][a-zA-Z0-9]$", XT_PRE_NO
, "'", mark
, XT_MARK_GOTO
},
7275 { "^[0-9]+t$", XT_PRE_YES
, "t", gototab
, 0 },
7276 { "^M[a-zA-Z0-9]$", XT_PRE_NO
, "M", qmark
, XT_QMARK_SET
},
7277 { "^go[a-zA-Z0-9]$", XT_PRE_NO
, "go", qmark
, XT_QMARK_OPEN
},
7278 { "^gn[a-zA-Z0-9]$", XT_PRE_NO
, "gn", qmark
, XT_QMARK_TAB
},
7279 { "^ZR$", XT_PRE_NO
, "ZR", restart
, 0 },
7280 { "^ZZ$", XT_PRE_NO
, "ZZ", quit
, 0 },
7281 { "^zi$", XT_PRE_NO
, "zi", resizetab
, XT_ZOOM_IN
},
7282 { "^zo$", XT_PRE_NO
, "zo", resizetab
, XT_ZOOM_OUT
},
7283 { "^z0$", XT_PRE_NO
, "z0", resizetab
, XT_ZOOM_NORMAL
},
7284 { "^[0-9]+Z$", XT_PRE_YES
, "Z", zoom_amount
, 0 },
7285 { "^[0-9]+:$", XT_PRE_YES
, ":", flip_colon
, 0 },
7289 buffercmd_init(void)
7293 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
7294 if (regcomp(&buffercmds
[i
].cregex
, buffercmds
[i
].regex
,
7295 REG_EXTENDED
| REG_NOSUB
))
7296 startpage_add("invalid buffercmd regex %s",
7297 buffercmds
[i
].regex
);
7301 buffercmd_abort(struct tab
*t
)
7305 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_abort: clearing buffer\n");
7306 for (i
= 0; i
< LENGTH(bcmd
); i
++)
7309 cmd_prefix
= 0; /* clear prefix for non-buffer commands */
7310 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.buffercmd
), bcmd
);
7314 buffercmd_execute(struct tab
*t
, struct buffercmd
*cmd
)
7316 struct karg arg
= {0, NULL
, -1};
7319 arg
.s
= g_strdup(bcmd
);
7321 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_execute: buffer \"%s\" "
7322 "matches regex \"%s\", executing\n", bcmd
, cmd
->regex
);
7332 buffercmd_addkey(struct tab
*t
, guint keyval
)
7335 char s
[XT_BUFCMD_SZ
];
7337 if (keyval
== GDK_Escape
) {
7339 return (XT_CB_HANDLED
);
7342 /* key with modifier or non-ascii character */
7343 if (!isascii(keyval
))
7344 return (XT_CB_PASSTHROUGH
);
7346 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: adding key \"%c\" "
7347 "to buffer \"%s\"\n", keyval
, bcmd
);
7349 for (i
= 0; i
< LENGTH(bcmd
); i
++)
7350 if (bcmd
[i
] == '\0') {
7355 /* buffer full, ignore input */
7356 if (i
>= LENGTH(bcmd
) -1) {
7357 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: buffer full\n");
7359 return (XT_CB_HANDLED
);
7362 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.buffercmd
), bcmd
);
7364 /* find exact match */
7365 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
7366 if (regexec(&buffercmds
[i
].cregex
, bcmd
,
7367 (size_t) 0, NULL
, 0) == 0) {
7368 buffercmd_execute(t
, &buffercmds
[i
]);
7372 /* find non exact matches to see if we need to abort ot not */
7373 for (i
= 0, match
= 0; i
< LENGTH(buffercmds
); i
++) {
7374 DNPRINTF(XT_D_BUFFERCMD
, "trying: %s\n", bcmd
);
7377 if (buffercmds
[i
].precount
== XT_PRE_MAYBE
) {
7378 if (isdigit(bcmd
[0])) {
7379 if (sscanf(bcmd
, "%d%s", &c
, s
) == 0)
7383 if (sscanf(bcmd
, "%s", s
) == 0)
7386 } else if (buffercmds
[i
].precount
== XT_PRE_YES
) {
7387 if (sscanf(bcmd
, "%d%s", &c
, s
) == 0)
7390 if (sscanf(bcmd
, "%s", s
) == 0)
7393 if (c
== -1 && buffercmds
[i
].precount
)
7395 if (!strncmp(s
, buffercmds
[i
].cmd
, strlen(s
)))
7398 DNPRINTF(XT_D_BUFFERCMD
, "got[%d] %d <%s>: %d %s\n",
7399 i
, match
, buffercmds
[i
].cmd
, c
, s
);
7402 DNPRINTF(XT_D_BUFFERCMD
, "aborting: %s\n", bcmd
);
7407 return (XT_CB_HANDLED
);
7411 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
7413 struct key_binding
*k
;
7415 /* handle keybindings if buffercmd is empty.
7416 if not empty, allow commands like C-n */
7417 if (bcmd
[0] == '\0' || ((e
->state
& (CTRL
| MOD1
)) != 0))
7418 TAILQ_FOREACH(k
, &kbl
, entry
)
7419 if (e
->keyval
== k
->key
7420 && (entry
? k
->use_in_entry
: 1)) {
7422 if ((e
->state
& (CTRL
| MOD1
)) == 0)
7423 return (cmd_execute(t
, k
->cmd
));
7424 } else if ((e
->state
& k
->mask
) == k
->mask
) {
7425 return (cmd_execute(t
, k
->cmd
));
7429 if (!entry
&& ((e
->state
& (CTRL
| MOD1
)) == 0))
7430 return buffercmd_addkey(t
, e
->keyval
);
7432 return (XT_CB_PASSTHROUGH
);
7436 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
7438 char s
[2], buf
[128];
7439 const char *errstr
= NULL
;
7441 /* don't use w directly; use t->whatever instead */
7444 show_oops(NULL
, "wv_keypress_after_cb");
7445 return (XT_CB_PASSTHROUGH
);
7448 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7449 e
->keyval
, e
->state
, t
);
7453 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
7455 return (XT_CB_HANDLED
);
7459 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
7461 /* we have a string */
7463 /* we have a number */
7464 snprintf(buf
, sizeof buf
,
7465 "vimprobable_fire(%s)", t
->hint_num
);
7472 /* XXX unfuck this */
7473 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
7474 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
7475 /* last input was numerical */
7477 l
= strlen(t
->hint_num
);
7484 t
->hint_num
[l
] = '\0';
7488 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
7489 /* last input was alphanumerical */
7491 l
= strlen(t
->hint_buf
);
7498 t
->hint_buf
[l
] = '\0';
7508 /* numerical input */
7509 if (CLEAN(e
->state
) == 0 &&
7510 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
7511 (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
7512 snprintf(s
, sizeof s
, "%c", e
->keyval
);
7513 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
7514 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: num %s\n",
7518 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: "
7519 "invalid link number\n");
7522 snprintf(buf
, sizeof buf
,
7523 "vimprobable_update_hints(%s)",
7525 t
->hint_mode
= XT_HINT_NUMERICAL
;
7529 /* empty the counter buffer */
7530 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
7531 return (XT_CB_HANDLED
);
7534 /* alphanumerical input */
7535 if ((CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&&
7536 e
->keyval
<= GDK_z
) ||
7537 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&&
7538 e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
7539 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&&
7540 e
->keyval
<= GDK_9
) ||
7541 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) &&
7542 (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
7543 snprintf(s
, sizeof s
, "%c", e
->keyval
);
7544 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
7545 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical"
7546 " %s\n", t
->hint_buf
);
7548 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
7551 snprintf(buf
, sizeof buf
,
7552 "vimprobable_show_hints('%s')", t
->hint_buf
);
7553 t
->hint_mode
= XT_HINT_ALPHANUM
;
7556 /* empty the counter buffer */
7557 bzero(t
->hint_num
, sizeof t
->hint_num
);
7558 return (XT_CB_HANDLED
);
7561 return (XT_CB_HANDLED
);
7564 snprintf(s
, sizeof s
, "%c", e
->keyval
);
7565 if (CLEAN(e
->state
) == 0 && isdigit(s
[0]))
7566 cmd_prefix
= 10 * cmd_prefix
+ atoi(s
);
7569 return (handle_keypress(t
, e
, 0));
7573 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
7577 /* Hide buffers, if they are visible, with escape. */
7578 if (gtk_widget_get_visible(GTK_WIDGET(t
->buffers
)) &&
7579 CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
7580 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7582 return (XT_CB_HANDLED
);
7585 return (XT_CB_PASSTHROUGH
);
7589 search_continue(struct tab
*t
)
7591 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
7592 gboolean rv
= FALSE
;
7596 if (strlen(c
) == 1) {
7597 webkit_web_view_unmark_text_matches(t
->wv
);
7602 t
->search_forward
= TRUE
;
7603 else if (c
[0] == '?')
7604 t
->search_forward
= FALSE
;
7614 search_cb(struct tab
*t
)
7616 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
7619 if (search_continue(t
) == FALSE
)
7623 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, t
->search_forward
,
7625 /* not found, mark red */
7626 gdk_color_parse(XT_COLOR_RED
, &color
);
7627 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
7628 /* unmark and remove selection */
7629 webkit_web_view_unmark_text_matches(t
->wv
);
7630 /* my kingdom for a way to unselect text in webview */
7632 /* found, highlight all */
7633 webkit_web_view_unmark_text_matches(t
->wv
);
7634 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
7635 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
7636 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7637 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
7645 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
7647 const gchar
*c
= gtk_entry_get_text(w
);
7650 show_oops(NULL
, "cmd_keyrelease_cb invalid parameters");
7651 return (XT_CB_PASSTHROUGH
);
7654 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
7655 e
->keyval
, e
->state
, t
);
7657 if (search_continue(t
) == FALSE
)
7660 /* if search length is > 4 then no longer play timeout games */
7661 if (strlen(c
) > 4) {
7663 g_source_remove(t
->search_id
);
7670 /* reestablish a new timer if the user types fast */
7672 g_source_remove(t
->search_id
);
7673 t
->search_id
= g_timeout_add(250, (GSourceFunc
)search_cb
, (gpointer
)t
);
7676 return (XT_CB_PASSTHROUGH
);
7680 match_uri(const gchar
*uri
, const gchar
*key
) {
7683 gboolean match
= FALSE
;
7687 if (!strncmp(key
, uri
, len
))
7690 voffset
= strstr(uri
, "/") + 2;
7691 if (!strncmp(key
, voffset
, len
))
7693 else if (g_str_has_prefix(voffset
, "www.")) {
7694 voffset
= voffset
+ strlen("www.");
7695 if (!strncmp(key
, voffset
, len
))
7704 cmd_getlist(int id
, char *key
)
7709 if (id
>= 0 && (cmds
[id
].type
& XT_URLARG
)) {
7710 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
7711 if (match_uri(h
->uri
, key
)) {
7712 cmd_status
.list
[c
] = (char *)h
->uri
;
7721 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
7723 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
7724 if (cmds
[i
].level
< dep
)
7726 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
,
7728 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
7736 cmd_getnext(int dir
)
7738 cmd_status
.index
+= dir
;
7740 if (cmd_status
.index
< 0)
7741 cmd_status
.index
= cmd_status
.len
- 1;
7742 else if (cmd_status
.index
>= cmd_status
.len
)
7743 cmd_status
.index
= 0;
7745 return cmd_status
.list
[cmd_status
.index
];
7749 cmd_tokenize(char *s
, char *tokens
[])
7753 size_t len
= strlen(s
);
7756 blank
= len
== 0 || (len
> 0 && s
[len
- 1] == ' ');
7757 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3;
7758 tok
= strtok_r(NULL
, " ", &last
), i
++)
7768 cmd_complete(struct tab
*t
, char *str
, int dir
)
7770 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
7771 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1;
7773 char *tok
, *match
, *s
= g_strdup(str
);
7775 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
7778 DNPRINTF(XT_D_CMD
, "%s: complete %s\n", __func__
, str
);
7781 for (i
= 0; isdigit(s
[i
]); i
++)
7784 for (; isspace(s
[i
]); i
++)
7789 levels
= cmd_tokenize(s
, tokens
);
7791 for (i
= 0; i
< levels
- 1; i
++) {
7794 for (j
= c
; j
< LENGTH(cmds
); j
++) {
7795 if (cmds
[j
].level
< dep
)
7797 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
,
7801 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
7808 if (matchcount
== 1) {
7809 strlcat(res
, tok
, sizeof res
);
7810 strlcat(res
, " ", sizeof res
);
7820 if (cmd_status
.index
== -1)
7821 cmd_getlist(parent
, tokens
[i
]);
7823 if (cmd_status
.len
> 0) {
7824 match
= cmd_getnext(dir
);
7825 strlcat(res
, match
, sizeof res
);
7826 gtk_entry_set_text(w
, res
);
7827 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
7834 cmd_execute(struct tab
*t
, char *str
)
7836 struct cmd
*cmd
= NULL
;
7837 char *tok
, *last
, *s
= g_strdup(str
), *sc
;
7839 int j
, len
, c
= 0, dep
= 0, matchcount
= 0;
7840 int prefix
= -1, rv
= XT_CB_PASSTHROUGH
;
7841 struct karg arg
= {0, NULL
, -1};
7846 for (j
= 0; j
<3 && isdigit(s
[j
]); j
++)
7852 while (isspace(s
[0]))
7855 if (strlen(s
) > 0 && strlen(prefixstr
) > 0)
7856 prefix
= atoi(prefixstr
);
7860 for (tok
= strtok_r(s
, " ", &last
); tok
;
7861 tok
= strtok_r(NULL
, " ", &last
)) {
7863 for (j
= c
; j
< LENGTH(cmds
); j
++) {
7864 if (cmds
[j
].level
< dep
)
7866 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1 :
7868 if (cmds
[j
].level
== dep
&&
7869 !strncmp(tok
, cmds
[j
].cmd
, len
)) {
7873 if (len
== strlen(cmds
[j
].cmd
)) {
7879 if (matchcount
== 1) {
7884 show_oops(t
, "Invalid command: %s", str
);
7892 arg
.precount
= prefix
;
7893 else if (cmd_prefix
> 0)
7894 arg
.precount
= cmd_prefix
;
7896 if (j
> 0 && !(cmd
->type
& XT_PREFIX
) && arg
.precount
> -1) {
7897 show_oops(t
, "No prefix allowed: %s", str
);
7901 arg
.s
= last
? g_strdup(last
) : g_strdup("");
7902 if (cmd
->type
& XT_INTARG
&& last
&& strlen(last
) > 0) {
7903 arg
.precount
= atoi(arg
.s
);
7904 if (arg
.precount
<= 0) {
7905 if (arg
.s
[0] == '0')
7906 show_oops(t
, "Zero count");
7908 show_oops(t
, "Trailing characters");
7913 DNPRINTF(XT_D_CMD
, "%s: prefix %d arg %s\n",
7914 __func__
, arg
.precount
, arg
.s
);
7930 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
7933 show_oops(NULL
, "entry_key_cb invalid parameters");
7934 return (XT_CB_PASSTHROUGH
);
7937 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7938 e
->keyval
, e
->state
, t
);
7942 if (e
->keyval
== GDK_Escape
) {
7943 /* don't use focus_webview(t) because we want to type :cmds */
7944 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7947 return (handle_keypress(t
, e
, 1));
7951 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
7953 int rv
= XT_CB_HANDLED
;
7954 const gchar
*c
= gtk_entry_get_text(w
);
7957 show_oops(NULL
, "cmd_keypress_cb parameters");
7958 return (XT_CB_PASSTHROUGH
);
7961 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7962 e
->keyval
, e
->state
, t
);
7966 e
->keyval
= GDK_Escape
;
7967 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
7968 e
->keyval
= GDK_Escape
;
7970 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&&
7971 e
->keyval
!= GDK_ISO_Left_Tab
)
7972 cmd_status
.index
= -1;
7974 switch (e
->keyval
) {
7977 cmd_complete(t
, (char *)&c
[1], 1);
7979 case GDK_ISO_Left_Tab
:
7981 cmd_complete(t
, (char *)&c
[1], -1);
7988 if (history_at
== NULL
)
7989 history_at
= TAILQ_LAST(&chl
, command_list
);
7991 history_at
= TAILQ_PREV(history_at
, command_list
,
7993 if (history_at
== NULL
)
7994 history_at
= TAILQ_LAST(&chl
, command_list
);
7998 gtk_entry_set_text(w
, history_at
->line
);
7999 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
8006 if (history_at
== NULL
)
8007 history_at
= TAILQ_FIRST(&chl
);
8009 history_at
= TAILQ_NEXT(history_at
, entry
);
8010 if (history_at
== NULL
)
8011 history_at
= TAILQ_FIRST(&chl
);
8015 gtk_entry_set_text(w
, history_at
->line
);
8016 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
8020 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
8028 if (c
!= NULL
&& (c
[0] == '/' || c
[0] == '?'))
8029 webkit_web_view_unmark_text_matches(t
->wv
);
8033 rv
= XT_CB_PASSTHROUGH
;
8039 cmd_popup_cb(GtkEntry
*entry
, GtkMenu
*menu
, struct tab
*t
)
8041 /* popup menu enabled */
8046 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
8049 show_oops(NULL
, "cmd_focusout_cb invalid parameters");
8050 return (XT_CB_PASSTHROUGH
);
8053 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d popup %d\n",
8054 t
->tab_id
, t
->popup
);
8056 /* if popup is enabled don't lose focus */
8059 return (XT_CB_PASSTHROUGH
);
8065 if (show_url
== 0 || t
->focus_wv
)
8068 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
8070 return (XT_CB_PASSTHROUGH
);
8074 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
8077 const gchar
*c
= gtk_entry_get_text(entry
);
8080 show_oops(NULL
, "cmd_activate_cb invalid parameters");
8084 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
8091 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
8097 if (c
[0] == '/' || c
[0] == '?') {
8098 /* see if there is a timer pending */
8100 g_source_remove(t
->search_id
);
8105 if (t
->search_text
) {
8106 g_free(t
->search_text
);
8107 t
->search_text
= NULL
;
8110 t
->search_text
= g_strdup(s
);
8112 g_free(global_search
);
8113 global_search
= g_strdup(s
);
8114 t
->search_forward
= c
[0] == '/';
8127 backward_cb(GtkWidget
*w
, struct tab
*t
)
8132 show_oops(NULL
, "backward_cb invalid parameters");
8136 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
8143 forward_cb(GtkWidget
*w
, struct tab
*t
)
8148 show_oops(NULL
, "forward_cb invalid parameters");
8152 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
8154 a
.i
= XT_NAV_FORWARD
;
8159 home_cb(GtkWidget
*w
, struct tab
*t
)
8162 show_oops(NULL
, "home_cb invalid parameters");
8166 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
8172 stop_cb(GtkWidget
*w
, struct tab
*t
)
8174 WebKitWebFrame
*frame
;
8177 show_oops(NULL
, "stop_cb invalid parameters");
8181 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
8183 frame
= webkit_web_view_get_main_frame(t
->wv
);
8184 if (frame
== NULL
) {
8185 show_oops(t
, "stop_cb: no frame");
8189 webkit_web_frame_stop_loading(frame
);
8190 abort_favicon_download(t
);
8194 setup_webkit(struct tab
*t
)
8196 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
8197 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
8198 FALSE
, (char *)NULL
);
8200 warnx("webkit does not have \"enable-dns-prefetching\" property");
8201 g_object_set(G_OBJECT(t
->settings
),
8202 "user-agent", t
->user_agent
, (char *)NULL
);
8203 g_object_set(G_OBJECT(t
->settings
),
8204 "enable-scripts", enable_scripts
, (char *)NULL
);
8205 g_object_set(G_OBJECT(t
->settings
),
8206 "enable-plugins", enable_plugins
, (char *)NULL
);
8207 g_object_set(G_OBJECT(t
->settings
),
8208 "javascript-can-open-windows-automatically", enable_scripts
,
8210 g_object_set(G_OBJECT(t
->settings
),
8211 "enable-html5-database", FALSE
, (char *)NULL
);
8212 g_object_set(G_OBJECT(t
->settings
),
8213 "enable-html5-local-storage", enable_localstorage
, (char *)NULL
);
8214 g_object_set(G_OBJECT(t
->settings
),
8215 "enable_spell_checking", enable_spell_checking
, (char *)NULL
);
8216 g_object_set(G_OBJECT(t
->settings
),
8217 "spell_checking_languages", spell_check_languages
, (char *)NULL
);
8218 g_object_set(G_OBJECT(t
->wv
),
8219 "full-content-zoom", TRUE
, (char *)NULL
);
8221 webkit_web_view_set_settings(t
->wv
, t
->settings
);
8225 update_statusbar_position(GtkAdjustment
* adjustment
, gpointer data
)
8227 struct tab
*ti
, *t
= NULL
;
8228 gdouble view_size
, value
, max
;
8231 TAILQ_FOREACH(ti
, &tabs
, entry
)
8232 if (ti
->tab_id
== gtk_notebook_get_current_page(notebook
)) {
8240 if (adjustment
== NULL
)
8241 adjustment
= gtk_scrolled_window_get_vadjustment(
8242 GTK_SCROLLED_WINDOW(t
->browser_win
));
8244 view_size
= gtk_adjustment_get_page_size(adjustment
);
8245 value
= gtk_adjustment_get_value(adjustment
);
8246 max
= gtk_adjustment_get_upper(adjustment
) - view_size
;
8249 position
= g_strdup("All");
8250 else if (value
== max
)
8251 position
= g_strdup("Bot");
8252 else if (value
== 0)
8253 position
= g_strdup("Top");
8255 position
= g_strdup_printf("%d%%", (int) ((value
/ max
) * 100));
8257 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.position
), position
);
8264 create_browser(struct tab
*t
)
8268 GtkAdjustment
*adjustment
;
8271 show_oops(NULL
, "create_browser invalid parameters");
8275 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
8276 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
8277 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
8278 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
8280 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
8281 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
8282 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
8284 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
8285 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
8288 t
->settings
= webkit_web_settings_new();
8290 if (user_agent
== NULL
) {
8291 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
8293 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
8296 t
->user_agent
= g_strdup(user_agent
);
8298 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
8301 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w
));
8302 g_signal_connect(G_OBJECT(adjustment
), "value-changed",
8303 G_CALLBACK(update_statusbar_position
), NULL
);
8315 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
8316 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
8317 gtk_widget_set_name(w
, "xxxterm");
8318 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
8319 g_signal_connect(G_OBJECT(w
), "delete_event",
8320 G_CALLBACK (gtk_main_quit
), NULL
);
8326 create_kiosk_toolbar(struct tab
*t
)
8328 GtkWidget
*toolbar
= NULL
, *b
;
8330 b
= gtk_hbox_new(FALSE
, 0);
8332 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
8334 /* backward button */
8335 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
8336 gtk_widget_set_sensitive(t
->backward
, FALSE
);
8337 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
8338 G_CALLBACK(backward_cb
), t
);
8339 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
8341 /* forward button */
8342 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
8343 gtk_widget_set_sensitive(t
->forward
, FALSE
);
8344 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
8345 G_CALLBACK(forward_cb
), t
);
8346 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
8349 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
8350 gtk_widget_set_sensitive(t
->gohome
, true);
8351 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
8352 G_CALLBACK(home_cb
), t
);
8353 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
8355 /* create widgets but don't use them */
8356 t
->uri_entry
= gtk_entry_new();
8357 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
8358 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
8359 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
8365 create_toolbar(struct tab
*t
)
8367 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
8369 b
= gtk_hbox_new(FALSE
, 0);
8371 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
8373 /* backward button */
8374 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
8375 gtk_widget_set_sensitive(t
->backward
, FALSE
);
8376 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
8377 G_CALLBACK(backward_cb
), t
);
8378 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
8380 /* forward button */
8381 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
8382 gtk_widget_set_sensitive(t
->forward
, FALSE
);
8383 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
8384 G_CALLBACK(forward_cb
), t
);
8385 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
8389 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
8390 gtk_widget_set_sensitive(t
->stop
, FALSE
);
8391 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
8392 G_CALLBACK(stop_cb
), t
);
8393 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
8397 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
8398 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
8399 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
8400 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
8401 G_CALLBACK(js_toggle_cb
), t
);
8402 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
8404 t
->uri_entry
= gtk_entry_new();
8405 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
8406 G_CALLBACK(activate_uri_entry_cb
), t
);
8407 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
8408 G_CALLBACK(entry_key_cb
), t
);
8410 eb1
= gtk_hbox_new(FALSE
, 0);
8411 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
8412 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
8413 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
8416 if (search_string
) {
8418 t
->search_entry
= gtk_entry_new();
8419 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
8420 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
8421 G_CALLBACK(activate_search_entry_cb
), t
);
8422 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
8423 G_CALLBACK(entry_key_cb
), t
);
8424 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
8425 eb2
= gtk_hbox_new(FALSE
, 0);
8426 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
8427 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
8429 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
8436 create_buffers(struct tab
*t
)
8438 GtkCellRenderer
*renderer
;
8441 view
= gtk_tree_view_new();
8443 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view
), FALSE
);
8445 renderer
= gtk_cell_renderer_text_new();
8446 gtk_tree_view_insert_column_with_attributes
8447 (GTK_TREE_VIEW(view
), -1, "Id", renderer
, "text", COL_ID
, NULL
);
8449 renderer
= gtk_cell_renderer_text_new();
8450 gtk_tree_view_insert_column_with_attributes
8451 (GTK_TREE_VIEW(view
), -1, "Title", renderer
, "text", COL_TITLE
,
8454 gtk_tree_view_set_model
8455 (GTK_TREE_VIEW(view
), GTK_TREE_MODEL(buffers_store
));
8461 row_activated_cb(GtkTreeView
*view
, GtkTreePath
*path
,
8462 GtkTreeViewColumn
*col
, struct tab
*t
)
8467 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
8469 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store
), &iter
,
8472 (GTK_TREE_MODEL(buffers_store
), &iter
, COL_ID
, &id
, -1);
8473 set_current_tab(id
- 1);
8479 /* after tab reordering/creation/removal */
8486 TAILQ_FOREACH(t
, &tabs
, entry
) {
8487 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
8488 if (t
->tab_id
> maxid
)
8491 gtk_widget_show(t
->tab_elems
.sep
);
8494 TAILQ_FOREACH(t
, &tabs
, entry
) {
8495 if (t
->tab_id
== maxid
) {
8496 gtk_widget_hide(t
->tab_elems
.sep
);
8502 /* after active tab change */
8504 recolor_compact_tabs(void)
8510 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color
);
8511 TAILQ_FOREACH(t
, &tabs
, entry
)
8512 gtk_widget_modify_fg(t
->tab_elems
.label
, GTK_STATE_NORMAL
,
8515 curid
= gtk_notebook_get_current_page(notebook
);
8516 TAILQ_FOREACH(t
, &tabs
, entry
)
8517 if (t
->tab_id
== curid
) {
8518 gdk_color_parse(XT_COLOR_CT_ACTIVE
, &color
);
8519 gtk_widget_modify_fg(t
->tab_elems
.label
,
8520 GTK_STATE_NORMAL
, &color
);
8526 set_current_tab(int page_num
)
8528 buffercmd_abort(get_current_tab());
8529 gtk_notebook_set_current_page(notebook
, page_num
);
8530 recolor_compact_tabs();
8534 undo_close_tab_save(struct tab
*t
)
8538 struct undo
*u1
, *u2
;
8540 WebKitWebHistoryItem
*item
;
8542 if ((uri
= get_uri(t
)) == NULL
)
8545 u1
= g_malloc0(sizeof(struct undo
));
8546 u1
->uri
= g_strdup(uri
);
8548 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
8550 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
8551 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
8554 /* forward history */
8555 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
8559 u1
->history
= g_list_prepend(u1
->history
,
8560 webkit_web_history_item_copy(item
));
8561 items
= g_list_next(items
);
8566 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
8567 u1
->history
= g_list_prepend(u1
->history
,
8568 webkit_web_history_item_copy(item
));
8572 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
8576 u1
->history
= g_list_prepend(u1
->history
,
8577 webkit_web_history_item_copy(item
));
8578 items
= g_list_next(items
);
8581 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
8583 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
8584 u2
= TAILQ_LAST(&undos
, undo_tailq
);
8585 TAILQ_REMOVE(&undos
, u2
, entry
);
8587 g_list_free(u2
->history
);
8596 delete_tab(struct tab
*t
)
8600 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
8606 TAILQ_REMOVE(&tabs
, t
, entry
);
8608 /* Halt all webkit activity. */
8609 abort_favicon_download(t
);
8610 webkit_web_view_stop_loading(t
->wv
);
8612 /* Save the tab, so we can undo the close. */
8613 undo_close_tab_save(t
);
8615 if (browser_mode
== XT_BM_KIOSK
) {
8616 gtk_widget_destroy(t
->uri_entry
);
8617 gtk_widget_destroy(t
->stop
);
8618 gtk_widget_destroy(t
->js_toggle
);
8621 gtk_widget_destroy(t
->tab_elems
.eventbox
);
8622 gtk_widget_destroy(t
->vbox
);
8626 g_source_remove(t
->search_id
);
8628 g_free(t
->user_agent
);
8629 g_free(t
->stylesheet
);
8633 if (TAILQ_EMPTY(&tabs
)) {
8634 if (browser_mode
== XT_BM_KIOSK
)
8635 create_new_tab(home
, NULL
, 1, -1);
8637 create_new_tab(NULL
, NULL
, 1, -1);
8640 /* recreate session */
8641 if (session_autosave
) {
8647 recolor_compact_tabs();
8651 update_statusbar_zoom(struct tab
*t
)
8654 char s
[16] = { '\0' };
8656 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
8657 if ((zoom
<= 0.99 || zoom
>= 1.01))
8658 snprintf(s
, sizeof s
, "%d%%", (int)(zoom
* 100));
8659 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.zoom
), s
);
8663 setzoom_webkit(struct tab
*t
, int adjust
)
8665 #define XT_ZOOMPERCENT 0.04
8670 show_oops(NULL
, "setzoom_webkit invalid parameters");
8674 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
8675 if (adjust
== XT_ZOOM_IN
)
8676 zoom
+= XT_ZOOMPERCENT
;
8677 else if (adjust
== XT_ZOOM_OUT
)
8678 zoom
-= XT_ZOOMPERCENT
;
8679 else if (adjust
> 0)
8680 zoom
= default_zoom_level
+ adjust
/ 100.0 - 1.0;
8682 show_oops(t
, "setzoom_webkit invalid zoom value");
8686 if (zoom
< XT_ZOOMPERCENT
)
8687 zoom
= XT_ZOOMPERCENT
;
8688 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
8689 update_statusbar_zoom(t
);
8693 tab_clicked_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
)
8695 struct tab
*t
= (struct tab
*) data
;
8697 DNPRINTF(XT_D_TAB
, "tab_clicked_cb: tab: %d\n", t
->tab_id
);
8699 switch (event
->button
) {
8701 set_current_tab(t
->tab_id
);
8712 append_tab(struct tab
*t
)
8717 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
8718 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
8722 create_sbe(int width
)
8726 sbe
= gtk_entry_new();
8727 gtk_entry_set_inner_border(GTK_ENTRY(sbe
), NULL
);
8728 gtk_entry_set_has_frame(GTK_ENTRY(sbe
), FALSE
);
8729 gtk_widget_set_can_focus(GTK_WIDGET(sbe
), FALSE
);
8730 gtk_widget_modify_font(GTK_WIDGET(sbe
), statusbar_font
);
8731 gtk_entry_set_alignment(GTK_ENTRY(sbe
), 1.0);
8732 gtk_widget_set_size_request(sbe
, width
, -1);
8738 create_new_tab(char *title
, struct undo
*u
, int focus
, int position
)
8743 WebKitWebHistoryItem
*item
;
8747 int sbe_p
= 0, sbe_b
= 0,
8750 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
8752 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
8753 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
8757 t
= g_malloc0(sizeof *t
);
8759 if (title
== NULL
) {
8760 title
= "(untitled)";
8764 t
->vbox
= gtk_vbox_new(FALSE
, 0);
8766 /* label + button for tab */
8767 b
= gtk_hbox_new(FALSE
, 0);
8770 #if GTK_CHECK_VERSION(2, 20, 0)
8771 t
->spinner
= gtk_spinner_new();
8773 t
->label
= gtk_label_new(title
);
8774 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
8775 gtk_widget_set_size_request(t
->label
, 100, 0);
8776 gtk_label_set_max_width_chars(GTK_LABEL(t
->label
), 20);
8777 gtk_label_set_ellipsize(GTK_LABEL(t
->label
), PANGO_ELLIPSIZE_END
);
8778 gtk_widget_set_size_request(b
, 130, 0);
8780 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
8781 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
8782 #if GTK_CHECK_VERSION(2, 20, 0)
8783 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
8787 if (browser_mode
== XT_BM_KIOSK
) {
8788 t
->toolbar
= create_kiosk_toolbar(t
);
8789 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
,
8792 t
->toolbar
= create_toolbar(t
);
8794 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
,
8802 t
->browser_win
= create_browser(t
);
8803 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
8805 /* oops message for user feedback */
8806 t
->oops
= gtk_entry_new();
8807 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
8808 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
8809 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
8810 gdk_color_parse(XT_COLOR_RED
, &color
);
8811 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
8812 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
8813 gtk_widget_modify_font(GTK_WIDGET(t
->oops
), oops_font
);
8816 t
->cmd
= gtk_entry_new();
8817 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
8818 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
8819 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
8820 gtk_widget_modify_font(GTK_WIDGET(t
->cmd
), cmd_font
);
8823 t
->statusbar_box
= gtk_hbox_new(FALSE
, 0);
8825 t
->sbe
.statusbar
= gtk_entry_new();
8826 gtk_entry_set_inner_border(GTK_ENTRY(t
->sbe
.statusbar
), NULL
);
8827 gtk_entry_set_has_frame(GTK_ENTRY(t
->sbe
.statusbar
), FALSE
);
8828 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.statusbar
), FALSE
);
8829 gtk_widget_modify_font(GTK_WIDGET(t
->sbe
.statusbar
), statusbar_font
);
8831 /* create these widgets only if specified in statusbar_elems */
8833 t
->sbe
.position
= create_sbe(40);
8834 t
->sbe
.zoom
= create_sbe(40);
8835 t
->sbe
.buffercmd
= create_sbe(60);
8837 statusbar_modify_attr(t
, XT_COLOR_WHITE
, XT_COLOR_BLACK
);
8839 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
), t
->sbe
.statusbar
, TRUE
,
8842 /* gtk widgets cannot be added to a box twice. sbe_* variables
8843 make sure of this */
8844 for (p
= statusbar_elems
; *p
!= '\0'; p
++) {
8848 GtkWidget
*sep
= gtk_vseparator_new();
8850 gdk_color_parse(XT_COLOR_SB_SEPARATOR
, &color
);
8851 gtk_widget_modify_bg(sep
, GTK_STATE_NORMAL
, &color
);
8852 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
), sep
,
8853 FALSE
, FALSE
, FALSE
);
8858 warnx("flag \"%c\" specified more than "
8859 "once in statusbar_elems\n", *p
);
8863 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
),
8864 t
->sbe
.position
, FALSE
, FALSE
, FALSE
);
8868 warnx("flag \"%c\" specified more than "
8869 "once in statusbar_elems\n", *p
);
8873 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
),
8874 t
->sbe
.buffercmd
, FALSE
, FALSE
, FALSE
);
8878 warnx("flag \"%c\" specified more than "
8879 "once in statusbar_elems\n", *p
);
8883 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
),
8884 t
->sbe
.zoom
, FALSE
, FALSE
, FALSE
);
8887 warnx("illegal flag \"%c\" in statusbar_elems\n", *p
);
8892 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar_box
, FALSE
, FALSE
, 0);
8895 t
->buffers
= create_buffers(t
);
8896 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->buffers
, FALSE
, FALSE
, 0);
8898 /* xtp meaning is normal by default */
8899 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
8901 /* set empty favicon */
8902 xt_icon_from_name(t
, "text-html");
8904 /* and show it all */
8905 gtk_widget_show_all(b
);
8906 gtk_widget_show_all(t
->vbox
);
8908 /* compact tab bar */
8909 t
->tab_elems
.label
= gtk_label_new(title
);
8910 gtk_label_set_width_chars(GTK_LABEL(t
->tab_elems
.label
), 1.0);
8911 gtk_misc_set_alignment(GTK_MISC(t
->tab_elems
.label
), 0.0, 0.0);
8912 gtk_misc_set_padding(GTK_MISC(t
->tab_elems
.label
), 4.0, 4.0);
8913 gtk_widget_modify_font(GTK_WIDGET(t
->tab_elems
.label
), tabbar_font
);
8915 t
->tab_elems
.eventbox
= gtk_event_box_new();
8916 t
->tab_elems
.box
= gtk_hbox_new(FALSE
, 0);
8917 t
->tab_elems
.sep
= gtk_vseparator_new();
8919 gdk_color_parse(XT_COLOR_CT_BACKGROUND
, &color
);
8920 gtk_widget_modify_bg(t
->tab_elems
.eventbox
, GTK_STATE_NORMAL
, &color
);
8921 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color
);
8922 gtk_widget_modify_fg(t
->tab_elems
.label
, GTK_STATE_NORMAL
, &color
);
8923 gdk_color_parse(XT_COLOR_CT_SEPARATOR
, &color
);
8924 gtk_widget_modify_bg(t
->tab_elems
.sep
, GTK_STATE_NORMAL
, &color
);
8926 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.label
, TRUE
,
8928 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.sep
, FALSE
,
8930 gtk_container_add(GTK_CONTAINER(t
->tab_elems
.eventbox
),
8933 gtk_box_pack_start(GTK_BOX(tab_bar
), t
->tab_elems
.eventbox
, TRUE
,
8935 gtk_widget_show_all(t
->tab_elems
.eventbox
);
8937 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
8940 id
= position
>= 0 ? position
:
8941 gtk_notebook_get_current_page(notebook
) + 1;
8942 if (id
> gtk_notebook_get_n_pages(notebook
))
8945 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
8946 gtk_notebook_insert_page(notebook
, t
->vbox
, b
, id
);
8947 gtk_box_reorder_child(GTK_BOX(tab_bar
),
8948 t
->tab_elems
.eventbox
, id
);
8953 #if GTK_CHECK_VERSION(2, 20, 0)
8954 /* turn spinner off if we are a new tab without uri */
8956 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
8957 gtk_widget_hide(t
->spinner
);
8960 /* make notebook tabs reorderable */
8961 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
8963 /* compact tabs clickable */
8964 g_signal_connect(G_OBJECT(t
->tab_elems
.eventbox
),
8965 "button_press_event", G_CALLBACK(tab_clicked_cb
), t
);
8967 g_object_connect(G_OBJECT(t
->cmd
),
8968 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
8969 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
8970 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
8971 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
8972 "signal::populate-popup", G_CALLBACK(cmd_popup_cb
), t
,
8975 /* reuse wv_button_cb to hide oops */
8976 g_object_connect(G_OBJECT(t
->oops
),
8977 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
8980 g_signal_connect(t
->buffers
,
8981 "row-activated", G_CALLBACK(row_activated_cb
), t
);
8982 g_object_connect(G_OBJECT(t
->buffers
),
8983 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
, NULL
);
8985 g_object_connect(G_OBJECT(t
->wv
),
8986 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
8987 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
8988 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
8989 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
8990 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
8991 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
8992 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
8993 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
8994 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
8995 "signal::event", G_CALLBACK(webview_event_cb
), t
,
8996 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
8997 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
8998 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
8999 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
9000 "signal::button_release_event", G_CALLBACK(wv_release_button_cb
), t
,
9002 g_signal_connect(t
->wv
,
9003 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
9005 * XXX this puts invalid url in uri_entry and that is undesirable
9008 g_signal_connect(t
->wv
,
9009 "load-error", G_CALLBACK(notify_load_error_cb
), t
);
9011 g_signal_connect(t
->wv
,
9012 "notify::title", G_CALLBACK(notify_title_cb
), t
);
9014 /* hijack the unused keys as if we were the browser */
9015 g_object_connect(G_OBJECT(t
->toolbar
),
9016 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
9019 g_signal_connect(G_OBJECT(bb
), "button_press_event",
9020 G_CALLBACK(tab_close_cb
), t
);
9026 url_set_visibility();
9027 statusbar_set_visibility();
9030 set_current_tab(t
->tab_id
);
9031 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
9036 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
9040 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
9045 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
9046 /* restore the tab's history */
9047 if (u
&& u
->history
) {
9051 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
9052 items
= g_list_next(items
);
9055 item
= g_list_nth_data(u
->history
, u
->back
);
9057 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
9060 g_list_free(u
->history
);
9062 webkit_web_back_forward_list_clear(t
->bfl
);
9064 recolor_compact_tabs();
9065 setzoom_webkit(t
, XT_ZOOM_NORMAL
);
9070 notebook_switchpage_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
9076 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
9078 if (gtk_notebook_get_current_page(notebook
) == -1)
9081 TAILQ_FOREACH(t
, &tabs
, entry
) {
9082 if (t
->tab_id
== pn
) {
9083 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
9086 uri
= get_title(t
, TRUE
);
9087 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
9093 /* can't use focus_webview here */
9094 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
9101 notebook_pagereordered_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
9104 struct tab
*t
= NULL
, *tt
;
9108 TAILQ_FOREACH(tt
, &tabs
, entry
)
9109 if (tt
->tab_id
== pn
) {
9114 DNPRINTF(XT_D_TAB
, "page_reordered_cb: tab: %d\n", t
->tab_id
);
9116 gtk_box_reorder_child(GTK_BOX(tab_bar
), t
->tab_elems
.eventbox
,
9121 menuitem_response(struct tab
*t
)
9123 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
9127 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
9129 GtkWidget
*menu
, *menu_items
;
9130 GdkEventButton
*bevent
;
9134 if (event
->type
== GDK_BUTTON_PRESS
) {
9135 bevent
= (GdkEventButton
*) event
;
9136 menu
= gtk_menu_new();
9138 TAILQ_FOREACH(ti
, &tabs
, entry
) {
9139 if ((uri
= get_uri(ti
)) == NULL
)
9140 /* XXX make sure there is something to print */
9141 /* XXX add gui pages in here to look purdy */
9143 menu_items
= gtk_menu_item_new_with_label(uri
);
9144 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_items
);
9145 gtk_widget_show(menu_items
);
9147 g_signal_connect_swapped((menu_items
),
9148 "activate", G_CALLBACK(menuitem_response
),
9152 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
9153 bevent
->button
, bevent
->time
);
9155 /* unref object so it'll free itself when popped down */
9156 #if !GTK_CHECK_VERSION(3, 0, 0)
9157 /* XXX does not need unref with gtk+3? */
9158 g_object_ref_sink(menu
);
9159 g_object_unref(menu
);
9162 return (TRUE
/* eat event */);
9165 return (FALSE
/* propagate */);
9169 icon_size_map(int icon_size
)
9171 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
9172 icon_size
> GTK_ICON_SIZE_DIALOG
)
9173 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
9179 create_button(char *name
, char *stockid
, int size
)
9181 GtkWidget
*button
, *image
;
9185 rcstring
= g_strdup_printf(
9186 "style \"%s-style\"\n"
9188 " GtkWidget::focus-padding = 0\n"
9189 " GtkWidget::focus-line-width = 0\n"
9193 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
9194 gtk_rc_parse_string(rcstring
);
9196 button
= gtk_button_new();
9197 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
9198 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
9200 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
9201 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
9202 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
9203 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
9204 gtk_widget_set_name(button
, name
);
9205 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
9211 button_set_stockid(GtkWidget
*button
, char *stockid
)
9215 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
9216 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
9217 gtk_button_set_image(GTK_BUTTON(button
), image
);
9221 clipb_primary_cb(GtkClipboard
*primary
, GdkEvent
*event
, gpointer notused
)
9224 GdkAtom atom
= gdk_atom_intern("CUT_BUFFER0", FALSE
);
9227 if (xterm_workaround
== 0)
9231 * xterm doesn't play nice with clipboards because it clears the
9232 * primary when clicked. We rely on primary being set to properly
9233 * handle middle mouse button clicks (paste). So when someone clears
9234 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9235 * other application behavior (as in DON'T clear primary).
9238 p
= gtk_clipboard_wait_for_text(primary
);
9240 if (gdk_property_get(gdk_get_default_root_window(),
9242 gdk_atom_intern("STRING", FALSE
),
9244 1024 * 1024 /* picked out of my butt */,
9250 /* yes sir, we need to NUL the string */
9252 gtk_clipboard_set_text(primary
, p
, -1);
9266 char file
[PATH_MAX
];
9269 vbox
= gtk_vbox_new(FALSE
, 0);
9270 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
9271 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
9272 #if !GTK_CHECK_VERSION(3, 0, 0)
9273 /* XXX seems to be needed with gtk+2 */
9274 gtk_notebook_set_tab_hborder(notebook
, 0);
9275 gtk_notebook_set_tab_vborder(notebook
, 0);
9277 gtk_notebook_set_scrollable(notebook
, TRUE
);
9278 gtk_notebook_set_show_border(notebook
, FALSE
);
9279 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
9281 abtn
= gtk_button_new();
9282 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
9283 gtk_widget_set_size_request(arrow
, -1, -1);
9284 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
9285 gtk_widget_set_size_request(abtn
, -1, 20);
9287 #if GTK_CHECK_VERSION(2, 20, 0)
9288 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
9290 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
9292 /* compact tab bar */
9293 tab_bar
= gtk_hbox_new(TRUE
, 0);
9295 gtk_box_pack_start(GTK_BOX(vbox
), tab_bar
, FALSE
, FALSE
, 0);
9296 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
9297 gtk_widget_set_size_request(vbox
, -1, -1);
9299 g_object_connect(G_OBJECT(notebook
),
9300 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
9302 g_object_connect(G_OBJECT(notebook
),
9303 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb
),
9304 NULL
, (char *)NULL
);
9305 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
9306 G_CALLBACK(arrow_cb
), NULL
);
9308 main_window
= create_window();
9309 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
9312 for (i
= 0; i
< LENGTH(icons
); i
++) {
9313 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
9314 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
9315 l
= g_list_append(l
, pb
);
9317 gtk_window_set_default_icon_list(l
);
9319 /* clipboard work around */
9320 if (xterm_workaround
)
9322 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY
)),
9323 "owner-change", G_CALLBACK(clipb_primary_cb
), NULL
);
9325 gtk_widget_show_all(abtn
);
9326 gtk_widget_show_all(main_window
);
9327 notebook_tab_set_visibility();
9331 set_hook(void **hook
, char *name
)
9334 errx(1, "set_hook");
9336 if (*hook
== NULL
) {
9337 *hook
= dlsym(RTLD_NEXT
, name
);
9339 errx(1, "can't hook %s", name
);
9343 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9345 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
9347 g_return_val_if_fail(cookie1
, FALSE
);
9348 g_return_val_if_fail(cookie2
, FALSE
);
9350 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
9351 !strcmp (cookie1
->value
, cookie2
->value
) &&
9352 !strcmp (cookie1
->path
, cookie2
->path
) &&
9353 !strcmp (cookie1
->domain
, cookie2
->domain
));
9357 transfer_cookies(void)
9360 SoupCookie
*sc
, *pc
;
9362 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
9364 for (;cf
; cf
= cf
->next
) {
9366 sc
= soup_cookie_copy(pc
);
9367 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
9370 soup_cookies_free(cf
);
9374 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
9379 print_cookie("soup_cookie_jar_delete_cookie", c
);
9381 if (cookies_enabled
== 0)
9384 if (jar
== NULL
|| c
== NULL
)
9387 /* find and remove from persistent jar */
9388 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
9390 for (;cf
; cf
= cf
->next
) {
9392 if (soup_cookie_equal(ci
, c
)) {
9393 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
9398 soup_cookies_free(cf
);
9400 /* delete from session jar */
9401 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
9405 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
9407 struct domain
*d
= NULL
;
9411 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
9412 jar
, p_cookiejar
, s_cookiejar
);
9414 if (cookies_enabled
== 0)
9417 /* see if we are up and running */
9418 if (p_cookiejar
== NULL
) {
9419 _soup_cookie_jar_add_cookie(jar
, cookie
);
9422 /* disallow p_cookiejar adds, shouldn't happen */
9423 if (jar
== p_cookiejar
)
9427 if (jar
== NULL
|| cookie
== NULL
)
9430 if (enable_cookie_whitelist
&&
9431 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
9433 DNPRINTF(XT_D_COOKIE
,
9434 "soup_cookie_jar_add_cookie: reject %s\n",
9436 if (save_rejected_cookies
) {
9437 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
9438 show_oops(NULL
, "can't open reject cookie file");
9441 fseek(r_cookie_f
, 0, SEEK_END
);
9442 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9443 cookie
->http_only
? "#HttpOnly_" : "",
9445 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
9447 cookie
->secure
? "TRUE" : "FALSE",
9449 (gulong
)soup_date_to_time_t(cookie
->expires
) :
9456 if (!allow_volatile_cookies
)
9460 if (cookie
->expires
== NULL
&& session_timeout
) {
9461 soup_cookie_set_expires(cookie
,
9462 soup_date_new_from_now(session_timeout
));
9463 print_cookie("modified add cookie", cookie
);
9466 /* see if we are white listed for persistence */
9467 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
9468 /* add to persistent jar */
9469 c
= soup_cookie_copy(cookie
);
9470 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
9471 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
9474 /* add to session jar */
9475 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
9476 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
9482 char file
[PATH_MAX
];
9484 set_hook((void *)&_soup_cookie_jar_add_cookie
,
9485 "soup_cookie_jar_add_cookie");
9486 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
9487 "soup_cookie_jar_delete_cookie");
9489 if (cookies_enabled
== 0)
9493 * the following code is intricate due to overriding several libsoup
9495 * do not alter order of these operations.
9498 /* rejected cookies */
9499 if (save_rejected_cookies
)
9500 snprintf(rc_fname
, sizeof file
, "%s/%s", work_dir
,
9503 /* persistent cookies */
9504 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_COOKIE_FILE
);
9505 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
9507 /* session cookies */
9508 s_cookiejar
= soup_cookie_jar_new();
9509 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
9510 cookie_policy
, (void *)NULL
);
9513 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
9517 setup_proxy(char *uri
)
9522 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
9523 soup_uri_free(proxy_uri
);
9527 if (http_proxy
!= uri
) {
9534 http_proxy
= g_strdup(uri
);
9535 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
9536 suri
= soup_uri_new(http_proxy
);
9537 if (!(suri
== NULL
|| !SOUP_URI_VALID_FOR_HTTP(suri
)))
9538 g_object_set(session
, "proxy-uri", proxy_uri
,
9541 soup_uri_free(suri
);
9546 set_http_proxy(char *proxy
)
9553 /* see if we need to clear it instead */
9554 if (strlen(proxy
) == 0) {
9559 uri
= soup_uri_new(proxy
);
9560 if (uri
== NULL
|| !SOUP_URI_VALID_FOR_HTTP(uri
))
9571 send_cmd_to_socket(char *cmd
)
9574 struct sockaddr_un sa
;
9576 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
9577 warnx("%s: socket", __func__
);
9581 sa
.sun_family
= AF_UNIX
;
9582 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
9583 work_dir
, XT_SOCKET_FILE
);
9586 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
9587 warnx("%s: connect", __func__
);
9591 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
9592 warnx("%s: send", __func__
);
9603 socket_watcher(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
9606 char str
[XT_MAX_URL_LENGTH
];
9607 socklen_t t
= sizeof(struct sockaddr_un
);
9608 struct sockaddr_un sa
;
9613 gint fd
= g_io_channel_unix_get_fd(source
);
9615 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
9620 if (getpeereid(s
, &uid
, &gid
) == -1) {
9624 if (uid
!= getuid() || gid
!= getgid()) {
9625 warnx("unauthorized user");
9631 warnx("not a valid user");
9635 n
= recv(s
, str
, sizeof(str
), 0);
9639 tt
= TAILQ_LAST(&tabs
, tab_list
);
9640 cmd_execute(tt
, str
);
9648 struct sockaddr_un sa
;
9650 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
9651 warn("is_running: socket");
9655 sa
.sun_family
= AF_UNIX
;
9656 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
9657 work_dir
, XT_SOCKET_FILE
);
9660 /* connect to see if there is a listener */
9661 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
9662 rv
= 0; /* not running */
9664 rv
= 1; /* already running */
9675 struct sockaddr_un sa
;
9677 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
9678 warn("build_socket: socket");
9682 sa
.sun_family
= AF_UNIX
;
9683 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
9684 work_dir
, XT_SOCKET_FILE
);
9687 /* connect to see if there is a listener */
9688 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
9689 /* no listener so we will */
9690 unlink(sa
.sun_path
);
9692 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
9693 warn("build_socket: bind");
9697 if (listen(s
, 1) == -1) {
9698 warn("build_socket: listen");
9711 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
9712 GtkTreeIter
*iter
, struct tab
*t
)
9716 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
9724 completion_hover_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
9725 GtkTreeIter
*iter
, struct tab
*t
)
9729 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
9730 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), value
);
9731 gtk_editable_set_position(GTK_EDITABLE(t
->uri_entry
), -1);
9738 completion_add_uri(const gchar
*uri
)
9742 /* add uri to list_store */
9743 gtk_list_store_append(completion_model
, &iter
);
9744 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
9748 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
9749 GtkTreeIter
*iter
, gpointer user_data
)
9752 gboolean match
= FALSE
;
9754 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
9760 match
= match_uri(value
, key
);
9767 completion_add(struct tab
*t
)
9769 /* enable completion for tab */
9770 t
->completion
= gtk_entry_completion_new();
9771 gtk_entry_completion_set_text_column(t
->completion
, 0);
9772 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
9773 gtk_entry_completion_set_model(t
->completion
,
9774 GTK_TREE_MODEL(completion_model
));
9775 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
9777 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
9778 gtk_entry_completion_set_inline_selection(t
->completion
, TRUE
);
9779 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
9780 G_CALLBACK(completion_select_cb
), t
);
9781 g_signal_connect(G_OBJECT (t
->completion
), "cursor-on-match",
9782 G_CALLBACK(completion_hover_cb
), t
);
9790 if (stat(dir
, &sb
)) {
9791 if (mkdir(dir
, S_IRWXU
) == -1)
9792 err(1, "mkdir %s", dir
);
9794 err(1, "stat %s", dir
);
9796 if (S_ISDIR(sb
.st_mode
) == 0)
9797 errx(1, "%s not a dir", dir
);
9798 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
9799 warnx("fixing invalid permissions on %s", dir
);
9800 if (chmod(dir
, S_IRWXU
) == -1)
9801 err(1, "chmod %s", dir
);
9809 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
9815 main(int argc
, char *argv
[])
9818 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
9819 char conf
[PATH_MAX
] = { '\0' };
9820 char file
[PATH_MAX
];
9821 char *env_proxy
= NULL
;
9825 struct sigaction sact
;
9826 GIOChannel
*channel
;
9831 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
9835 RB_INIT(&downloads
);
9839 TAILQ_INIT(&aliases
);
9845 /* fiddle with ulimits */
9846 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
9849 /* just use them all */
9850 rlp
.rlim_cur
= rlp
.rlim_max
;
9851 if (setrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
9853 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
9855 else if (rlp
.rlim_cur
<= 256)
9856 startpage_add("%s requires at least 256 file "
9857 "descriptors, currently it has up to %d available",
9858 __progname
, rlp
.rlim_cur
);
9861 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
9870 errx(0 , "Version: %s", version
);
9873 strlcpy(conf
, optarg
, sizeof(conf
));
9876 strlcpy(named_session
, optarg
, sizeof(named_session
));
9897 gnutls_global_init();
9899 /* generate session keys for xtp pages */
9900 generate_xtp_session_key(&dl_session_key
);
9901 generate_xtp_session_key(&hl_session_key
);
9902 generate_xtp_session_key(&cl_session_key
);
9903 generate_xtp_session_key(&fl_session_key
);
9906 if (!g_thread_supported()) {
9907 g_thread_init(NULL
);
9909 gdk_threads_enter();
9911 gtk_init(&argc
, &argv
);
9914 bzero(&sact
, sizeof(sact
));
9915 sigemptyset(&sact
.sa_mask
);
9916 sact
.sa_handler
= sigchild
;
9917 sact
.sa_flags
= SA_NOCLDSTOP
;
9918 sigaction(SIGCHLD
, &sact
, NULL
);
9920 /* set download dir */
9921 pwd
= getpwuid(getuid());
9923 errx(1, "invalid user %d", getuid());
9924 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
9926 /* compile buffer command regexes */
9929 /* set default string settings */
9930 home
= g_strdup("https://www.cyphertite.com");
9931 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9932 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
9933 strlcpy(runtime_settings
, "runtime", sizeof runtime_settings
);
9934 cmd_font_name
= g_strdup("monospace normal 9");
9935 oops_font_name
= g_strdup("monospace normal 9");
9936 statusbar_font_name
= g_strdup("monospace normal 9");
9937 tabbar_font_name
= g_strdup("monospace normal 9");
9938 statusbar_elems
= g_strdup("BP");
9940 /* read config file */
9941 if (strlen(conf
) == 0)
9942 snprintf(conf
, sizeof conf
, "%s/.%s",
9943 pwd
->pw_dir
, XT_CONF_FILE
);
9944 config_parse(conf
, 0);
9947 cmd_font
= pango_font_description_from_string(cmd_font_name
);
9948 oops_font
= pango_font_description_from_string(oops_font_name
);
9949 statusbar_font
= pango_font_description_from_string(statusbar_font_name
);
9950 tabbar_font
= pango_font_description_from_string(tabbar_font_name
);
9952 /* working directory */
9953 if (strlen(work_dir
) == 0)
9954 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
9955 pwd
->pw_dir
, XT_DIR
);
9958 /* icon cache dir */
9959 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
9963 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
9967 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
9968 work_dir
, XT_SESSIONS_DIR
);
9969 xxx_dir(sessions_dir
);
9971 /* runtime settings that can override config file */
9972 if (runtime_settings
[0] != '\0')
9973 config_parse(runtime_settings
, 1);
9976 if (!strcmp(download_dir
, pwd
->pw_dir
))
9977 strlcat(download_dir
, "/downloads", sizeof download_dir
);
9978 xxx_dir(download_dir
);
9980 /* favorites file */
9981 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
9982 if (stat(file
, &sb
)) {
9983 warnx("favorites file doesn't exist, creating it");
9984 if ((f
= fopen(file
, "w")) == NULL
)
9985 err(1, "favorites");
9989 /* quickmarks file */
9990 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_QMARKS_FILE
);
9991 if (stat(file
, &sb
)) {
9992 warnx("quickmarks file doesn't exist, creating it");
9993 if ((f
= fopen(file
, "w")) == NULL
)
9994 err(1, "quickmarks");
9999 session
= webkit_get_default_session();
10004 if (stat(ssl_ca_file
, &sb
)) {
10005 warnx("no CA file: %s", ssl_ca_file
);
10006 g_free(ssl_ca_file
);
10007 ssl_ca_file
= NULL
;
10009 g_object_set(session
,
10010 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
10011 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
10015 /* guess_search regex */
10016 if (url_regex
== NULL
)
10017 url_regex
= g_strdup(XT_URL_REGEX
);
10019 if (regcomp(&url_re
, url_regex
, REG_EXTENDED
| REG_NOSUB
))
10020 startpage_add("invalid url regex %s", url_regex
);
10023 env_proxy
= getenv("http_proxy");
10025 setup_proxy(env_proxy
);
10027 setup_proxy(http_proxy
);
10030 send_cmd_to_socket(argv
[0]);
10034 /* set some connection parameters */
10035 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
10036 g_object_set(session
, "max-conns-per-host", max_host_connections
,
10039 /* see if there is already an xxxterm running */
10040 if (single_instance
&& is_running()) {
10042 warnx("already running");
10047 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
10048 send_cmd_to_socket(cmd
);
10058 /* uri completion */
10059 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
10062 buffers_store
= gtk_list_store_new
10063 (NUM_COLS
, G_TYPE_UINT
, G_TYPE_STRING
);
10069 notebook_tab_set_visibility();
10071 if (save_global_history
)
10072 restore_global_history();
10074 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
10075 restore_saved_tabs();
10077 a
.s
= named_session
;
10078 a
.i
= XT_SES_DONOTHING
;
10079 open_tabs(NULL
, &a
);
10082 /* see if we have an exception */
10083 if (!TAILQ_EMPTY(&spl
)) {
10084 create_new_tab("about:startpage", NULL
, focus
, -1);
10089 create_new_tab(argv
[0], NULL
, focus
, -1);
10096 if (TAILQ_EMPTY(&tabs
))
10097 create_new_tab(home
, NULL
, 1, -1);
10100 if ((s
= build_socket()) != -1) {
10101 channel
= g_io_channel_unix_new(s
);
10102 g_io_add_watch(channel
, G_IO_IN
, socket_watcher
, NULL
);
10107 if (!g_thread_supported()) {
10108 gdk_threads_leave();
10111 gnutls_global_deinit();