3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
44 #include <sys/types.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
51 #include "freebsd/util.h"
57 #include <sys/queue.h>
59 #include <sys/socket.h>
62 #include <sys/resource.h>
65 #include <gdk/gdkkeysyms.h>
67 #if GTK_CHECK_VERSION(3,0,0)
68 /* we still use GDK_* instead of GDK_KEY_* */
69 #include <gdk/gdkkeysyms-compat.h>
72 #include <webkit/webkit.h>
73 #include <libsoup/soup.h>
74 #include <gnutls/gnutls.h>
75 #include <JavaScriptCore/JavaScript.h>
76 #include <gnutls/x509.h>
78 #include "javascript.h"
81 javascript.h borrowed from vimprobable2 under the following license:
83 Copyright (c) 2009 Leon Winter
84 Copyright (c) 2009 Hannes Schueller
85 Copyright (c) 2009 Matto Fransen
87 Permission is hereby granted, free of charge, to any person obtaining a copy
88 of this software and associated documentation files (the "Software"), to deal
89 in the Software without restriction, including without limitation the rights
90 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
91 copies of the Software, and to permit persons to whom the Software is
92 furnished to do so, subject to the following conditions:
94 The above copyright notice and this permission notice shall be included in
95 all copies or substantial portions of the Software.
97 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
98 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
99 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
100 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
101 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
102 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
106 static char *version
= "$xxxterm$";
108 /* hooked functions */
109 void (*_soup_cookie_jar_add_cookie
)(SoupCookieJar
*, SoupCookie
*);
110 void (*_soup_cookie_jar_delete_cookie
)(SoupCookieJar
*,
115 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
116 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
117 #define XT_D_MOVE 0x0001
118 #define XT_D_KEY 0x0002
119 #define XT_D_TAB 0x0004
120 #define XT_D_URL 0x0008
121 #define XT_D_CMD 0x0010
122 #define XT_D_NAV 0x0020
123 #define XT_D_DOWNLOAD 0x0040
124 #define XT_D_CONFIG 0x0080
125 #define XT_D_JS 0x0100
126 #define XT_D_FAVORITE 0x0200
127 #define XT_D_PRINTING 0x0400
128 #define XT_D_COOKIE 0x0800
129 #define XT_D_KEYBINDING 0x1000
130 #define XT_D_CLIP 0x2000
131 u_int32_t swm_debug
= 0
148 #define DPRINTF(x...)
149 #define DNPRINTF(n,x...)
152 #define LENGTH(x) (sizeof x / sizeof x[0])
153 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
154 ~(GDK_BUTTON1_MASK) & \
155 ~(GDK_BUTTON2_MASK) & \
156 ~(GDK_BUTTON3_MASK) & \
157 ~(GDK_BUTTON4_MASK) & \
169 TAILQ_ENTRY(tab
) entry
;
171 GtkWidget
*tab_content
;
174 GtkWidget
*uri_entry
;
175 GtkWidget
*search_entry
;
177 GtkWidget
*browser_win
;
178 GtkWidget
*statusbar
;
185 GtkWidget
*js_toggle
;
186 GtkEntryCompletion
*completion
;
190 WebKitWebHistoryItem
*item
;
191 WebKitWebBackForwardList
*bfl
;
194 WebKitNetworkRequest
*icon_request
;
195 WebKitDownload
*icon_download
;
196 gchar
*icon_dest_uri
;
198 /* adjustments for browser */
201 GtkAdjustment
*adjust_h
;
202 GtkAdjustment
*adjust_v
;
208 int xtp_meaning
; /* identifies dls/favorites */
213 #define XT_HINT_NONE (0)
214 #define XT_HINT_NUMERICAL (1)
215 #define XT_HINT_ALPHANUM (2)
219 /* custom stylesheet */
228 WebKitWebSettings
*settings
;
232 TAILQ_HEAD(tab_list
, tab
);
235 RB_ENTRY(history
) entry
;
239 RB_HEAD(history_list
, history
);
242 RB_ENTRY(download
) entry
;
244 WebKitDownload
*download
;
247 RB_HEAD(download_list
, download
);
250 RB_ENTRY(domain
) entry
;
252 int handy
; /* app use */
254 RB_HEAD(domain_list
, domain
);
257 TAILQ_ENTRY(undo
) entry
;
260 int back
; /* Keeps track of how many back
261 * history items there are. */
263 TAILQ_HEAD(undo_tailq
, undo
);
265 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
266 int next_download_id
= 1;
275 #define XT_NAME ("XXXTerm")
276 #define XT_DIR (".xxxterm")
277 #define XT_CACHE_DIR ("cache")
278 #define XT_CERT_DIR ("certs/")
279 #define XT_SESSIONS_DIR ("sessions/")
280 #define XT_CONF_FILE ("xxxterm.conf")
281 #define XT_FAVS_FILE ("favorites")
282 #define XT_SAVED_TABS_FILE ("main_session")
283 #define XT_RESTART_TABS_FILE ("restart_tabs")
284 #define XT_SOCKET_FILE ("socket")
285 #define XT_HISTORY_FILE ("history")
286 #define XT_REJECT_FILE ("rejected.txt")
287 #define XT_COOKIE_FILE ("cookies.txt")
288 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
289 #define XT_CB_HANDLED (TRUE)
290 #define XT_CB_PASSTHROUGH (FALSE)
291 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
292 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
293 #define XT_DLMAN_REFRESH "10"
294 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
295 "td{overflow: hidden;" \
296 " padding: 2px 2px 2px 2px;" \
297 " border: 1px solid black;" \
298 " vertical-align:top;" \
299 " word-wrap: break-word}\n" \
300 "tr:hover{background: #ffff99}\n" \
301 "th{background-color: #cccccc;" \
302 " border: 1px solid black}\n" \
303 "table{width: 100%%;" \
304 " border: 1px black solid;" \
305 " border-collapse:collapse}\n" \
307 "border: 1px solid black;" \
310 ".progress-inner{float: left;" \
312 " background: green}\n" \
313 ".dlstatus{font-size: small;" \
314 " text-align: center}\n" \
316 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
317 #define XT_MAX_UNDO_CLOSE_TAB (32)
318 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
319 #define XT_PRINT_EXTRA_MARGIN 10
322 #define XT_COLOR_RED "#cc0000"
323 #define XT_COLOR_YELLOW "#ffff66"
324 #define XT_COLOR_BLUE "lightblue"
325 #define XT_COLOR_GREEN "#99ff66"
326 #define XT_COLOR_WHITE "white"
327 #define XT_COLOR_BLACK "black"
330 * xxxterm "protocol" (xtp)
331 * We use this for managing stuff like downloads and favorites. They
332 * make magical HTML pages in memory which have xxxt:// links in order
333 * to communicate with xxxterm's internals. These links take the format:
334 * xxxt://class/session_key/action/arg
336 * Don't begin xtp class/actions as 0. atoi returns that on error.
338 * Typically we have not put addition of items in this framework, as
339 * adding items is either done via an ex-command or via a keybinding instead.
342 #define XT_XTP_STR "xxxt://"
344 /* XTP classes (xxxt://<class>) */
345 #define XT_XTP_INVALID 0 /* invalid */
346 #define XT_XTP_DL 1 /* downloads */
347 #define XT_XTP_HL 2 /* history */
348 #define XT_XTP_CL 3 /* cookies */
349 #define XT_XTP_FL 4 /* favorites */
351 /* XTP download actions */
352 #define XT_XTP_DL_LIST 1
353 #define XT_XTP_DL_CANCEL 2
354 #define XT_XTP_DL_REMOVE 3
356 /* XTP history actions */
357 #define XT_XTP_HL_LIST 1
358 #define XT_XTP_HL_REMOVE 2
360 /* XTP cookie actions */
361 #define XT_XTP_CL_LIST 1
362 #define XT_XTP_CL_REMOVE 2
364 /* XTP cookie actions */
365 #define XT_XTP_FL_LIST 1
366 #define XT_XTP_FL_REMOVE 2
368 /* xtp tab meanings - identifies which tabs have xtp pages in */
369 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
370 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
371 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
372 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
373 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
376 #define XT_MOVE_INVALID (0)
377 #define XT_MOVE_DOWN (1)
378 #define XT_MOVE_UP (2)
379 #define XT_MOVE_BOTTOM (3)
380 #define XT_MOVE_TOP (4)
381 #define XT_MOVE_PAGEDOWN (5)
382 #define XT_MOVE_PAGEUP (6)
383 #define XT_MOVE_HALFDOWN (7)
384 #define XT_MOVE_HALFUP (8)
385 #define XT_MOVE_LEFT (9)
386 #define XT_MOVE_FARLEFT (10)
387 #define XT_MOVE_RIGHT (11)
388 #define XT_MOVE_FARRIGHT (12)
390 #define XT_TAB_LAST (-4)
391 #define XT_TAB_FIRST (-3)
392 #define XT_TAB_PREV (-2)
393 #define XT_TAB_NEXT (-1)
394 #define XT_TAB_INVALID (0)
395 #define XT_TAB_NEW (1)
396 #define XT_TAB_DELETE (2)
397 #define XT_TAB_DELQUIT (3)
398 #define XT_TAB_OPEN (4)
399 #define XT_TAB_UNDO_CLOSE (5)
400 #define XT_TAB_SHOW (6)
401 #define XT_TAB_HIDE (7)
403 #define XT_NAV_INVALID (0)
404 #define XT_NAV_BACK (1)
405 #define XT_NAV_FORWARD (2)
406 #define XT_NAV_RELOAD (3)
407 #define XT_NAV_RELOAD_CACHE (4)
409 #define XT_FOCUS_INVALID (0)
410 #define XT_FOCUS_URI (1)
411 #define XT_FOCUS_SEARCH (2)
413 #define XT_SEARCH_INVALID (0)
414 #define XT_SEARCH_NEXT (1)
415 #define XT_SEARCH_PREV (2)
417 #define XT_PASTE_CURRENT_TAB (0)
418 #define XT_PASTE_NEW_TAB (1)
420 #define XT_FONT_SET (0)
422 #define XT_URL_SHOW (1)
423 #define XT_URL_HIDE (2)
425 #define XT_STATUSBAR_SHOW (1)
426 #define XT_STATUSBAR_HIDE (2)
428 #define XT_WL_TOGGLE (1<<0)
429 #define XT_WL_ENABLE (1<<1)
430 #define XT_WL_DISABLE (1<<2)
431 #define XT_WL_FQDN (1<<3) /* default */
432 #define XT_WL_TOPLEVEL (1<<4)
433 #define XT_WL_PERSISTENT (1<<5)
434 #define XT_WL_SESSION (1<<6)
435 #define XT_WL_RELOAD (1<<7)
437 #define XT_SHOW (1<<7)
438 #define XT_DELETE (1<<8)
439 #define XT_SAVE (1<<9)
440 #define XT_OPEN (1<<10)
442 #define XT_CMD_OPEN (0)
443 #define XT_CMD_OPEN_CURRENT (1)
444 #define XT_CMD_TABNEW (2)
445 #define XT_CMD_TABNEW_CURRENT (3)
447 #define XT_STATUS_NOTHING (0)
448 #define XT_STATUS_LINK (1)
449 #define XT_STATUS_URI (2)
450 #define XT_STATUS_LOADING (3)
452 #define XT_SES_DONOTHING (0)
453 #define XT_SES_CLOSETABS (1)
455 #define XT_BM_NORMAL (0)
456 #define XT_BM_WHITELIST (1)
457 #define XT_BM_KIOSK (2)
459 #define XT_PREFIX (1<<0)
460 #define XT_USERARG (1<<1)
461 #define XT_URLARG (1<<2)
462 #define XT_INTARG (1<<3)
470 TAILQ_ENTRY(mime_type
) entry
;
472 TAILQ_HEAD(mime_type_list
, mime_type
);
478 TAILQ_ENTRY(alias
) entry
;
480 TAILQ_HEAD(alias_list
, alias
);
482 /* settings that require restart */
483 int tabless
= 0; /* allow only 1 tab */
484 int enable_socket
= 0;
485 int single_instance
= 0; /* only allow one xxxterm to run */
486 int fancy_bar
= 1; /* fancy toolbar */
487 int browser_mode
= XT_BM_NORMAL
;
488 int enable_localstorage
= 0;
490 /* runtime settings */
491 int show_tabs
= 1; /* show tabs on notebook */
492 int show_url
= 1; /* show url toolbar on notebook */
493 int show_statusbar
= 0; /* vimperator style status bar */
494 int ctrl_click_focus
= 0; /* ctrl click gets focus */
495 int cookies_enabled
= 1; /* enable cookies */
496 int read_only_cookies
= 0; /* enable to not write cookies */
497 int enable_scripts
= 1;
498 int enable_plugins
= 0;
499 int default_font_size
= 12;
500 gfloat default_zoom_level
= 1.0;
501 int window_height
= 768;
502 int window_width
= 1024;
503 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
504 unsigned refresh_interval
= 10; /* download refresh interval */
505 int enable_cookie_whitelist
= 0;
506 int enable_js_whitelist
= 0;
507 time_t session_timeout
= 3600; /* cookie session timeout */
508 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
509 char *ssl_ca_file
= NULL
;
510 char *resource_dir
= NULL
;
511 gboolean ssl_strict_certs
= FALSE
;
512 int append_next
= 1; /* append tab after current tab */
514 char *search_string
= NULL
;
515 char *http_proxy
= NULL
;
516 char download_dir
[PATH_MAX
];
517 char runtime_settings
[PATH_MAX
]; /* override of settings */
518 int allow_volatile_cookies
= 0;
519 int save_global_history
= 0; /* save global history to disk */
520 char *user_agent
= NULL
;
521 int save_rejected_cookies
= 0;
522 time_t session_autosave
= 0;
523 int guess_search
= 0;
524 int dns_prefetch
= FALSE
;
525 gint max_connections
= 25;
526 gint max_host_connections
= 5;
527 gint enable_spell_checking
= 0;
528 char *spell_check_languages
= NULL
;
530 char *cmd_font_name
= NULL
;
531 char *statusbar_font_name
= NULL
;
532 PangoFontDescription
*cmd_font
;
533 PangoFontDescription
*statusbar_font
;
537 int set_download_dir(struct settings
*, char *);
538 int set_work_dir(struct settings
*, char *);
539 int set_runtime_dir(struct settings
*, char *);
540 int set_browser_mode(struct settings
*, char *);
541 int set_cookie_policy(struct settings
*, char *);
542 int add_alias(struct settings
*, char *);
543 int add_mime_type(struct settings
*, char *);
544 int add_cookie_wl(struct settings
*, char *);
545 int add_js_wl(struct settings
*, char *);
546 int add_kb(struct settings
*, char *);
547 void button_set_stockid(GtkWidget
*, char *);
548 GtkWidget
* create_button(char *, char *, int);
550 char *get_browser_mode(struct settings
*);
551 char *get_cookie_policy(struct settings
*);
553 char *get_download_dir(struct settings
*);
554 char *get_work_dir(struct settings
*);
555 char *get_runtime_dir(struct settings
*);
557 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
558 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
559 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
560 void walk_kb(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
561 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
563 void recalc_tabs(void);
566 int (*set
)(struct settings
*, char *);
567 char *(*get
)(struct settings
*);
568 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
571 struct special s_browser_mode
= {
577 struct special s_cookie
= {
583 struct special s_alias
= {
589 struct special s_mime
= {
595 struct special s_js
= {
601 struct special s_kb
= {
607 struct special s_cookie_wl
= {
613 struct special s_download_dir
= {
619 struct special s_work_dir
= {
628 #define XT_S_INVALID (0)
631 #define XT_S_FLOAT (3)
633 #define XT_SF_RESTART (1<<0)
634 #define XT_SF_RUNTIME (1<<1)
640 { "append_next", XT_S_INT
, 0, &append_next
, NULL
, NULL
},
641 { "allow_volatile_cookies", XT_S_INT
, 0, &allow_volatile_cookies
, NULL
, NULL
},
642 { "browser_mode", XT_S_INT
, 0, NULL
, NULL
,&s_browser_mode
},
643 { "cookie_policy", XT_S_INT
, 0, NULL
, NULL
,&s_cookie
},
644 { "cookies_enabled", XT_S_INT
, 0, &cookies_enabled
, NULL
, NULL
},
645 { "ctrl_click_focus", XT_S_INT
, 0, &ctrl_click_focus
, NULL
, NULL
},
646 { "default_font_size", XT_S_INT
, 0, &default_font_size
, NULL
, NULL
},
647 { "default_zoom_level", XT_S_FLOAT
, 0, NULL
, NULL
, NULL
, &default_zoom_level
},
648 { "download_dir", XT_S_STR
, 0, NULL
, NULL
,&s_download_dir
},
649 { "enable_cookie_whitelist", XT_S_INT
, 0, &enable_cookie_whitelist
, NULL
, NULL
},
650 { "enable_js_whitelist", XT_S_INT
, 0, &enable_js_whitelist
, NULL
, NULL
},
651 { "enable_plugins", XT_S_INT
, 0, &enable_plugins
, NULL
, NULL
},
652 { "enable_scripts", XT_S_INT
, 0, &enable_scripts
, NULL
, NULL
},
653 { "enable_localstorage", XT_S_INT
, 0, &enable_localstorage
, NULL
, NULL
},
654 { "enable_socket", XT_S_INT
, XT_SF_RESTART
,&enable_socket
, NULL
, NULL
},
655 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
,&fancy_bar
, NULL
, NULL
},
656 { "home", XT_S_STR
, 0, NULL
, &home
, NULL
},
657 { "http_proxy", XT_S_STR
, 0, NULL
, &http_proxy
, NULL
},
658 { "icon_size", XT_S_INT
, 0, &icon_size
, NULL
, NULL
},
659 { "max_connections", XT_S_INT
, XT_SF_RESTART
,&max_connections
, NULL
, NULL
},
660 { "max_host_connections", XT_S_INT
, XT_SF_RESTART
,&max_host_connections
, NULL
, NULL
},
661 { "read_only_cookies", XT_S_INT
, 0, &read_only_cookies
, NULL
, NULL
},
662 { "refresh_interval", XT_S_INT
, 0, &refresh_interval
, NULL
, NULL
},
663 { "resource_dir", XT_S_STR
, 0, NULL
, &resource_dir
, NULL
},
664 { "search_string", XT_S_STR
, 0, NULL
, &search_string
, NULL
},
665 { "save_global_history", XT_S_INT
, XT_SF_RESTART
,&save_global_history
, NULL
, NULL
},
666 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
,&save_rejected_cookies
, NULL
, NULL
},
667 { "session_timeout", XT_S_INT
, 0, &session_timeout
, NULL
, NULL
},
668 { "session_autosave", XT_S_INT
, 0, &session_autosave
, NULL
, NULL
},
669 { "single_instance", XT_S_INT
, XT_SF_RESTART
,&single_instance
, NULL
, NULL
},
670 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
671 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
672 { "guess_search", XT_S_INT
, 0, &guess_search
, NULL
, NULL
},
673 { "show_statusbar", XT_S_INT
, 0, &show_statusbar
, NULL
, NULL
},
674 { "ssl_ca_file", XT_S_STR
, 0, NULL
, &ssl_ca_file
, NULL
},
675 { "ssl_strict_certs", XT_S_INT
, 0, &ssl_strict_certs
, NULL
, NULL
},
676 { "user_agent", XT_S_STR
, 0, NULL
, &user_agent
, NULL
},
677 { "window_height", XT_S_INT
, 0, &window_height
, NULL
, NULL
},
678 { "window_width", XT_S_INT
, 0, &window_width
, NULL
, NULL
},
679 { "work_dir", XT_S_STR
, 0, NULL
, NULL
,&s_work_dir
},
680 { "enable_spell_checking", XT_S_INT
, 0, &enable_spell_checking
, NULL
, NULL
},
681 { "spell_check_languages", XT_S_STR
, 0, NULL
, &spell_check_languages
, NULL
},
684 { "cmd_font", XT_S_STR
, 0, NULL
, &cmd_font_name
, NULL
},
685 { "statusbar_font", XT_S_STR
, 0, NULL
, &statusbar_font_name
, NULL
},
687 /* runtime settings */
688 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
689 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
690 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
691 { "keybinding", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_kb
},
692 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
695 int about(struct tab
*, struct karg
*);
696 int blank(struct tab
*, struct karg
*);
697 int ca_cmd(struct tab
*, struct karg
*);
698 int cookie_show_wl(struct tab
*, struct karg
*);
699 int js_show_wl(struct tab
*, struct karg
*);
700 int help(struct tab
*, struct karg
*);
701 int set(struct tab
*, struct karg
*);
702 int stats(struct tab
*, struct karg
*);
703 int marco(struct tab
*, struct karg
*);
704 const char * marco_message(int *);
705 int xtp_page_cl(struct tab
*, struct karg
*);
706 int xtp_page_dl(struct tab
*, struct karg
*);
707 int xtp_page_fl(struct tab
*, struct karg
*);
708 int xtp_page_hl(struct tab
*, struct karg
*);
709 void xt_icon_from_file(struct tab
*, char *);
711 #define XT_URI_ABOUT ("about:")
712 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
713 #define XT_URI_ABOUT_ABOUT ("about")
714 #define XT_URI_ABOUT_BLANK ("blank")
715 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
716 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
717 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
718 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
719 #define XT_URI_ABOUT_FAVORITES ("favorites")
720 #define XT_URI_ABOUT_HELP ("help")
721 #define XT_URI_ABOUT_HISTORY ("history")
722 #define XT_URI_ABOUT_JSWL ("jswl")
723 #define XT_URI_ABOUT_SET ("set")
724 #define XT_URI_ABOUT_STATS ("stats")
725 #define XT_URI_ABOUT_MARCO ("marco")
729 int (*func
)(struct tab
*, struct karg
*);
731 { XT_URI_ABOUT_ABOUT
, about
},
732 { XT_URI_ABOUT_BLANK
, blank
},
733 { XT_URI_ABOUT_CERTS
, ca_cmd
},
734 { XT_URI_ABOUT_COOKIEWL
, cookie_show_wl
},
735 { XT_URI_ABOUT_COOKIEJAR
, xtp_page_cl
},
736 { XT_URI_ABOUT_DOWNLOADS
, xtp_page_dl
},
737 { XT_URI_ABOUT_FAVORITES
, xtp_page_fl
},
738 { XT_URI_ABOUT_HELP
, help
},
739 { XT_URI_ABOUT_HISTORY
, xtp_page_hl
},
740 { XT_URI_ABOUT_JSWL
, js_show_wl
},
741 { XT_URI_ABOUT_SET
, set
},
742 { XT_URI_ABOUT_STATS
, stats
},
743 { XT_URI_ABOUT_MARCO
, marco
},
747 extern char *__progname
;
750 GtkWidget
*main_window
;
751 GtkNotebook
*notebook
;
752 GtkWidget
*arrow
, *abtn
;
753 struct tab_list tabs
;
754 struct history_list hl
;
755 struct download_list downloads
;
756 struct domain_list c_wl
;
757 struct domain_list js_wl
;
758 struct undo_tailq undos
;
759 struct keybinding_list kbl
;
761 int updating_dl_tabs
= 0;
762 int updating_hl_tabs
= 0;
763 int updating_cl_tabs
= 0;
764 int updating_fl_tabs
= 0;
766 uint64_t blocked_cookies
= 0;
767 char named_session
[PATH_MAX
];
768 int icon_size_map(int);
770 GtkListStore
*completion_model
;
771 void completion_add(struct tab
*);
772 void completion_add_uri(const gchar
*);
773 void xxx_dir(char *);
778 int saved_errno
, status
;
783 while ((pid
= waitpid(WAIT_ANY
, &status
, WNOHANG
)) != 0) {
787 if (errno
!= ECHILD
) {
789 clog_warn("sigchild: waitpid:");
795 if (WIFEXITED(status
)) {
796 if (WEXITSTATUS(status
) != 0) {
798 clog_warnx("sigchild: child exit status: %d",
799 WEXITSTATUS(status));
804 clog_warnx("sigchild: child is terminated abnormally");
813 is_g_object_setting(GObject
*o
, char *str
)
815 guint n_props
= 0, i
;
816 GParamSpec
**proplist
;
818 if (! G_IS_OBJECT(o
))
821 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
824 for (i
=0; i
< n_props
; i
++) {
825 if (! strcmp(proplist
[i
]->name
, str
))
832 get_html_page(gchar
*title
, gchar
*body
, gchar
*head
, bool addstyles
)
836 r
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
838 "<title>%s</title>\n"
847 addstyles
? XT_PAGE_STYLE
: "",
856 * Display a web page from a HTML string in memory, rather than from a URL
859 load_webkit_string(struct tab
*t
, const char *str
, gchar
*title
)
864 /* we set this to indicate we want to manually do navaction */
866 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
868 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, "");
869 #if GTK_CHECK_VERSION(2, 20, 0)
870 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
871 gtk_widget_hide(t
->spinner
);
875 uri
= g_strdup_printf("%s%s", XT_URI_ABOUT
, title
);
876 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
879 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[0]);
880 xt_icon_from_file(t
, file
);
885 set_status(struct tab
*t
, gchar
*s
, int status
)
893 case XT_STATUS_LOADING
:
894 type
= g_strdup_printf("Loading: %s", s
);
898 type
= g_strdup_printf("Link: %s", s
);
900 t
->status
= g_strdup(gtk_entry_get_text(GTK_ENTRY(t
->statusbar
)));
904 type
= g_strdup_printf("%s", s
);
906 t
->status
= g_strdup(type
);
910 t
->status
= g_strdup(s
);
912 case XT_STATUS_NOTHING
:
917 gtk_entry_set_text(GTK_ENTRY(t
->statusbar
), s
);
923 hide_oops(struct tab
*t
)
925 gtk_widget_hide(t
->oops
);
929 hide_cmd(struct tab
*t
)
931 gtk_widget_hide(t
->cmd
);
935 show_cmd(struct tab
*t
)
937 gtk_widget_hide(t
->oops
);
938 gtk_widget_show(t
->cmd
);
942 show_oops(struct tab
*t
, const char *fmt
, ...)
951 if (vasprintf(&msg
, fmt
, ap
) == -1)
952 errx(1, "show_oops failed");
955 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
956 gtk_widget_hide(t
->cmd
);
957 gtk_widget_show(t
->oops
);
960 /* XXX collapse with show_oops */
962 show_oops_s(const char *fmt
, ...)
966 struct tab
*ti
, *t
= NULL
;
971 TAILQ_FOREACH(ti
, &tabs
, entry
)
972 if (ti
->tab_id
== gtk_notebook_get_current_page(notebook
)) {
980 if (vasprintf(&msg
, fmt
, ap
) == -1)
981 errx(1, "show_oops_s failed");
984 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
985 gtk_widget_hide(t
->cmd
);
986 gtk_widget_show(t
->oops
);
990 get_as_string(struct settings
*s
)
1001 warnx("get_as_string skip %s\n", s
->name
);
1002 } else if (s
->type
== XT_S_INT
)
1003 r
= g_strdup_printf("%d", *s
->ival
);
1004 else if (s
->type
== XT_S_STR
)
1005 r
= g_strdup(*s
->sval
);
1006 else if (s
->type
== XT_S_FLOAT
)
1007 r
= g_strdup_printf("%f", *s
->fval
);
1009 r
= g_strdup_printf("INVALID TYPE");
1015 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1020 for (i
= 0; i
< LENGTH(rs
); i
++) {
1021 if (rs
[i
].s
&& rs
[i
].s
->walk
)
1022 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
1024 s
= get_as_string(&rs
[i
]);
1025 cb(&rs
[i
], s
, cb_args
);
1032 set_browser_mode(struct settings
*s
, char *val
)
1034 if (!strcmp(val
, "whitelist")) {
1035 browser_mode
= XT_BM_WHITELIST
;
1036 allow_volatile_cookies
= 0;
1037 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1038 cookies_enabled
= 1;
1039 enable_cookie_whitelist
= 1;
1040 read_only_cookies
= 0;
1041 save_rejected_cookies
= 0;
1042 session_timeout
= 3600;
1044 enable_js_whitelist
= 1;
1045 enable_localstorage
= 0;
1046 } else if (!strcmp(val
, "normal")) {
1047 browser_mode
= XT_BM_NORMAL
;
1048 allow_volatile_cookies
= 0;
1049 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1050 cookies_enabled
= 1;
1051 enable_cookie_whitelist
= 0;
1052 read_only_cookies
= 0;
1053 save_rejected_cookies
= 0;
1054 session_timeout
= 3600;
1056 enable_js_whitelist
= 0;
1057 enable_localstorage
= 1;
1058 } else if (!strcmp(val
, "kiosk")) {
1059 browser_mode
= XT_BM_KIOSK
;
1060 allow_volatile_cookies
= 0;
1061 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1062 cookies_enabled
= 1;
1063 enable_cookie_whitelist
= 0;
1064 read_only_cookies
= 0;
1065 save_rejected_cookies
= 0;
1066 session_timeout
= 3600;
1068 enable_js_whitelist
= 0;
1069 enable_localstorage
= 1;
1079 get_browser_mode(struct settings
*s
)
1083 if (browser_mode
== XT_BM_WHITELIST
)
1084 r
= g_strdup("whitelist");
1085 else if (browser_mode
== XT_BM_NORMAL
)
1086 r
= g_strdup("normal");
1087 else if (browser_mode
== XT_BM_KIOSK
)
1088 r
= g_strdup("kiosk");
1096 set_cookie_policy(struct settings
*s
, char *val
)
1098 if (!strcmp(val
, "no3rdparty"))
1099 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1100 else if (!strcmp(val
, "accept"))
1101 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1102 else if (!strcmp(val
, "reject"))
1103 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1111 get_cookie_policy(struct settings
*s
)
1115 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1116 r
= g_strdup("no3rdparty");
1117 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1118 r
= g_strdup("accept");
1119 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1120 r
= g_strdup("reject");
1128 get_download_dir(struct settings
*s
)
1130 if (download_dir
[0] == '\0')
1132 return (g_strdup(download_dir
));
1136 set_download_dir(struct settings
*s
, char *val
)
1139 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1140 pwd
->pw_dir
, &val
[1]);
1142 strlcpy(download_dir
, val
, sizeof download_dir
);
1149 * We use these to prevent people putting xxxt:// URLs on
1150 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1152 #define XT_XTP_SES_KEY_SZ 8
1153 #define XT_XTP_SES_KEY_HEX_FMT \
1154 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1155 char *dl_session_key
; /* downloads */
1156 char *hl_session_key
; /* history list */
1157 char *cl_session_key
; /* cookie list */
1158 char *fl_session_key
; /* favorites list */
1160 char work_dir
[PATH_MAX
];
1161 char certs_dir
[PATH_MAX
];
1162 char cache_dir
[PATH_MAX
];
1163 char sessions_dir
[PATH_MAX
];
1164 char cookie_file
[PATH_MAX
];
1165 SoupURI
*proxy_uri
= NULL
;
1166 SoupSession
*session
;
1167 SoupCookieJar
*s_cookiejar
;
1168 SoupCookieJar
*p_cookiejar
;
1169 char rc_fname
[PATH_MAX
];
1171 struct mime_type_list mtl
;
1172 struct alias_list aliases
;
1175 struct tab
*create_new_tab(char *, struct undo
*, int, int);
1176 void delete_tab(struct tab
*);
1177 void adjustfont_webkit(struct tab
*, int);
1178 int run_script(struct tab
*, char *);
1179 int download_rb_cmp(struct download
*, struct download
*);
1180 gboolean
cmd_execute(struct tab
*t
, char *str
);
1183 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1185 return (strcmp(h1
->uri
, h2
->uri
));
1187 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1190 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1192 return (strcmp(d1
->d
, d2
->d
));
1194 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1197 get_work_dir(struct settings
*s
)
1199 if (work_dir
[0] == '\0')
1201 return (g_strdup(work_dir
));
1205 set_work_dir(struct settings
*s
, char *val
)
1208 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1209 pwd
->pw_dir
, &val
[1]);
1211 strlcpy(work_dir
, val
, sizeof work_dir
);
1217 * generate a session key to secure xtp commands.
1218 * pass in a ptr to the key in question and it will
1219 * be modified in place.
1222 generate_xtp_session_key(char **key
)
1224 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1230 /* make a new one */
1231 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1232 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1233 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1234 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1236 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1240 * validate a xtp session key.
1244 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1246 if (strcmp(trusted
, untrusted
) != 0) {
1247 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1256 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1258 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1260 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1262 struct valid_url_types
{
1273 valid_url_type(char *url
)
1277 for (i
= 0; i
< LENGTH(vut
); i
++)
1278 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1285 print_cookie(char *msg
, SoupCookie
*c
)
1291 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1292 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1293 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1294 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1295 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1296 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1297 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1298 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1299 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1300 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1304 walk_alias(struct settings
*s
,
1305 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1310 if (s
== NULL
|| cb
== NULL
) {
1311 show_oops_s("walk_alias invalid parameters");
1315 TAILQ_FOREACH(a
, &aliases
, entry
) {
1316 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1317 cb(s
, str
, cb_args
);
1323 match_alias(char *url_in
)
1327 char *url_out
= NULL
, *search
, *enc_arg
;
1329 search
= g_strdup(url_in
);
1331 if (strsep(&arg
, " \t") == NULL
) {
1332 show_oops_s("match_alias: NULL URL");
1336 TAILQ_FOREACH(a
, &aliases
, entry
) {
1337 if (!strcmp(search
, a
->a_name
))
1342 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1345 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1346 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1349 url_out
= g_strdup(a
->a_uri
);
1357 guess_url_type(char *url_in
)
1360 char *url_out
= NULL
, *enc_search
= NULL
;
1362 url_out
= match_alias(url_in
);
1363 if (url_out
!= NULL
)
1368 * If there is no dot nor slash in the string and it isn't a
1369 * path to a local file and doesn't resolves to an IP, assume
1370 * that the user wants to search for the string.
1373 if (strchr(url_in
, '.') == NULL
&&
1374 strchr(url_in
, '/') == NULL
&&
1375 stat(url_in
, &sb
) != 0 &&
1376 gethostbyname(url_in
) == NULL
) {
1378 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1379 url_out
= g_strdup_printf(search_string
, enc_search
);
1385 /* XXX not sure about this heuristic */
1386 if (stat(url_in
, &sb
) == 0)
1387 url_out
= g_strdup_printf("file://%s", url_in
);
1389 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1391 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1397 load_uri(struct tab
*t
, gchar
*uri
)
1400 gchar
*newuri
= NULL
;
1406 /* Strip leading spaces. */
1407 while(*uri
&& isspace(*uri
))
1410 if (strlen(uri
) == 0) {
1415 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1416 for (i
= 0; i
< LENGTH(about_list
); i
++)
1417 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1418 bzero(&args
, sizeof args
);
1419 about_list
[i
].func(t
, &args
);
1420 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
),
1424 show_oops(t
, "invalid about page");
1428 if (valid_url_type(uri
)) {
1429 newuri
= guess_url_type(uri
);
1433 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1434 webkit_web_view_load_uri(t
->wv
, uri
);
1441 get_uri(WebKitWebView
*wv
)
1445 uri
= webkit_web_view_get_uri(wv
);
1447 if (uri
&& strlen(uri
) > 0)
1454 add_alias(struct settings
*s
, char *line
)
1457 struct alias
*a
= NULL
;
1459 if (s
== NULL
|| line
== NULL
) {
1460 show_oops_s("add_alias invalid parameters");
1465 a
= g_malloc(sizeof(*a
));
1467 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1468 show_oops_s("add_alias: incomplete alias definition");
1471 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1472 show_oops_s("add_alias: invalid alias definition");
1476 a
->a_name
= g_strdup(alias
);
1477 a
->a_uri
= g_strdup(l
);
1479 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1481 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1491 add_mime_type(struct settings
*s
, char *line
)
1495 struct mime_type
*m
= NULL
;
1496 int downloadfirst
= 0;
1498 /* XXX this could be smarter */
1500 if (line
== NULL
&& strlen(line
) == 0) {
1501 show_oops_s("add_mime_type invalid parameters");
1510 m
= g_malloc(sizeof(*m
));
1512 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1513 show_oops_s("add_mime_type: invalid mime_type");
1516 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1517 mime_type
[strlen(mime_type
) - 1] = '\0';
1522 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1523 show_oops_s("add_mime_type: invalid mime_type");
1527 m
->mt_type
= g_strdup(mime_type
);
1528 m
->mt_action
= g_strdup(l
);
1529 m
->mt_download
= downloadfirst
;
1531 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1532 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1534 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1544 find_mime_type(char *mime_type
)
1546 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1548 TAILQ_FOREACH(m
, &mtl
, entry
) {
1549 if (m
->mt_default
&&
1550 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1553 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1566 walk_mime_type(struct settings
*s
,
1567 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1569 struct mime_type
*m
;
1572 if (s
== NULL
|| cb
== NULL
)
1573 show_oops_s("walk_mime_type invalid parameters");
1575 TAILQ_FOREACH(m
, &mtl
, entry
) {
1576 str
= g_strdup_printf("%s%s --> %s",
1578 m
->mt_default
? "*" : "",
1580 cb(s
, str
, cb_args
);
1586 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1591 if (str
== NULL
|| wl
== NULL
|| strlen(str
) < 2)
1594 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1596 /* treat *.moo.com the same as .moo.com */
1597 if (str
[0] == '*' && str
[1] == '.')
1599 else if (str
[0] == '.')
1604 d
= g_malloc(sizeof *d
);
1606 d
->d
= g_strdup_printf(".%s", str
);
1608 d
->d
= g_strdup(str
);
1611 if (RB_INSERT(domain_list
, wl
, d
))
1614 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1625 add_cookie_wl(struct settings
*s
, char *entry
)
1627 wl_add(entry
, &c_wl
, 1);
1632 walk_cookie_wl(struct settings
*s
,
1633 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1637 if (s
== NULL
|| cb
== NULL
) {
1638 show_oops_s("walk_cookie_wl invalid parameters");
1642 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1643 cb(s
, d
->d
, cb_args
);
1647 walk_js_wl(struct settings
*s
,
1648 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1652 if (s
== NULL
|| cb
== NULL
) {
1653 show_oops_s("walk_js_wl invalid parameters");
1657 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1658 cb(s
, d
->d
, cb_args
);
1662 add_js_wl(struct settings
*s
, char *entry
)
1664 wl_add(entry
, &js_wl
, 1 /* persistent */);
1669 wl_find(const gchar
*search
, struct domain_list
*wl
)
1672 struct domain
*d
= NULL
, dfind
;
1675 if (search
== NULL
|| wl
== NULL
)
1677 if (strlen(search
) < 2)
1680 if (search
[0] != '.')
1681 s
= g_strdup_printf(".%s", search
);
1683 s
= g_strdup(search
);
1685 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1688 d
= RB_FIND(domain_list
, wl
, &dfind
);
1702 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1708 if (s
== NULL
|| wl
== NULL
)
1711 if (!strncmp(s
, "http://", strlen("http://")))
1712 s
= &s
[strlen("http://")];
1713 else if (!strncmp(s
, "https://", strlen("https://")))
1714 s
= &s
[strlen("https://")];
1719 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1720 /* chop string at first slash */
1721 if (s
[i
] == '/' || s
[i
] == '\0') {
1724 r
= wl_find(ss
, wl
);
1733 get_toplevel_domain(char *domain
)
1740 if (strlen(domain
) < 2)
1743 s
= &domain
[strlen(domain
) - 1];
1744 while (s
!= domain
) {
1760 settings_add(char *var
, char *val
)
1767 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
1768 if (strcmp(var
, rs
[i
].name
))
1772 if (rs
[i
].s
->set(&rs
[i
], val
))
1773 errx(1, "invalid value for %s: %s", var
, val
);
1777 switch (rs
[i
].type
) {
1786 errx(1, "invalid sval for %s",
1800 errx(1, "invalid type for %s", var
);
1809 config_parse(char *filename
, int runtime
)
1812 char *line
, *cp
, *var
, *val
;
1813 size_t len
, lineno
= 0;
1815 char file
[PATH_MAX
];
1818 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
1820 if (filename
== NULL
)
1823 if (runtime
&& runtime_settings
[0] != '\0') {
1824 snprintf(file
, sizeof file
, "%s/%s",
1825 work_dir
, runtime_settings
);
1826 if (stat(file
, &sb
)) {
1827 warnx("runtime file doesn't exist, creating it");
1828 if ((f
= fopen(file
, "w")) == NULL
)
1830 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
1834 strlcpy(file
, filename
, sizeof file
);
1836 if ((config
= fopen(file
, "r")) == NULL
) {
1837 warn("config_parse: cannot open %s", filename
);
1842 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
1843 if (feof(config
) || ferror(config
))
1847 cp
+= (long)strspn(cp
, WS
);
1848 if (cp
[0] == '\0') {
1854 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
1855 errx(1, "invalid config file entry: %s", line
);
1857 cp
+= (long)strspn(cp
, WS
);
1859 if ((val
= strsep(&cp
, "\0")) == NULL
)
1862 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n", var
, val
);
1863 handled
= settings_add(var
, val
);
1865 errx(1, "invalid conf file entry: %s=%s", var
, val
);
1874 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1880 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1884 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1887 JSStringGetUTF8CString(jsref
, s
, l
);
1888 JSStringRelease(jsref
);
1894 disable_hints(struct tab
*t
)
1896 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1897 bzero(t
->hint_num
, sizeof t
->hint_num
);
1898 run_script(t
, "vimprobable_clear()");
1900 t
->hint_mode
= XT_HINT_NONE
;
1904 enable_hints(struct tab
*t
)
1906 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1907 run_script(t
, "vimprobable_show_hints()");
1909 t
->hint_mode
= XT_HINT_NONE
;
1912 #define XT_JS_OPEN ("open;")
1913 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1914 #define XT_JS_FIRE ("fire;")
1915 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1916 #define XT_JS_FOUND ("found;")
1917 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1920 run_script(struct tab
*t
, char *s
)
1922 JSGlobalContextRef ctx
;
1923 WebKitWebFrame
*frame
;
1925 JSValueRef val
, exception
;
1928 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
1929 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1931 frame
= webkit_web_view_get_main_frame(t
->wv
);
1932 ctx
= webkit_web_frame_get_global_context(frame
);
1934 str
= JSStringCreateWithUTF8CString(s
);
1935 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1936 NULL
, 0, &exception
);
1937 JSStringRelease(str
);
1939 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
1941 es
= js_ref_to_string(ctx
, exception
);
1942 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
1946 es
= js_ref_to_string(ctx
, val
);
1947 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
1949 /* handle return value right here */
1950 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
1952 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
1955 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
1956 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
1957 &es
[XT_JS_FIRE_LEN
]);
1962 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
1963 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
1974 hint(struct tab
*t
, struct karg
*args
)
1977 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
1979 if (t
->hints_on
== 0)
1988 apply_style(struct tab
*t
)
1990 g_object_set(G_OBJECT(t
->settings
),
1991 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
1995 userstyle(struct tab
*t
, struct karg
*args
)
1997 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
2001 g_object_set(G_OBJECT(t
->settings
),
2002 "user-stylesheet-uri", NULL
, (char *)NULL
);
2011 * Doesn't work fully, due to the following bug:
2012 * https://bugs.webkit.org/show_bug.cgi?id=51747
2015 restore_global_history(void)
2017 char file
[PATH_MAX
];
2022 const char delim
[3] = {'\\', '\\', '\0'};
2024 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2026 if ((f
= fopen(file
, "r")) == NULL
) {
2027 warnx("%s: fopen", __func__
);
2032 if ((uri
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2033 if (feof(f
) || ferror(f
))
2036 if ((title
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2037 if (feof(f
) || ferror(f
)) {
2039 warnx("%s: broken history file\n", __func__
);
2043 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
2044 webkit_web_history_item_new_with_data(uri
, title
);
2045 h
= g_malloc(sizeof(struct history
));
2046 h
->uri
= g_strdup(uri
);
2047 h
->title
= g_strdup(title
);
2048 RB_INSERT(history_list
, &hl
, h
);
2049 completion_add_uri(h
->uri
);
2051 warnx("%s: failed to restore history\n", __func__
);
2067 save_global_history_to_disk(struct tab
*t
)
2069 char file
[PATH_MAX
];
2073 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2075 if ((f
= fopen(file
, "w")) == NULL
) {
2076 show_oops(t
, "%s: global history file: %s",
2077 __func__
, strerror(errno
));
2081 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2082 if (h
->uri
&& h
->title
)
2083 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2092 quit(struct tab
*t
, struct karg
*args
)
2094 if (save_global_history
)
2095 save_global_history_to_disk(t
);
2103 open_tabs(struct tab
*t
, struct karg
*a
)
2105 char file
[PATH_MAX
];
2109 struct tab
*ti
, *tt
;
2114 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2115 if ((f
= fopen(file
, "r")) == NULL
)
2118 ti
= TAILQ_LAST(&tabs
, tab_list
);
2121 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2122 if (feof(f
) || ferror(f
))
2125 /* retrieve session name */
2126 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2127 strlcpy(named_session
,
2128 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2129 sizeof named_session
);
2133 if (uri
&& strlen(uri
))
2134 create_new_tab(uri
, NULL
, 1, -1);
2140 /* close open tabs */
2141 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2143 tt
= TAILQ_FIRST(&tabs
);
2162 restore_saved_tabs(void)
2164 char file
[PATH_MAX
];
2165 int unlink_file
= 0;
2170 snprintf(file
, sizeof file
, "%s/%s",
2171 sessions_dir
, XT_RESTART_TABS_FILE
);
2172 if (stat(file
, &sb
) == -1)
2173 a
.s
= XT_SAVED_TABS_FILE
;
2176 a
.s
= XT_RESTART_TABS_FILE
;
2179 a
.i
= XT_SES_DONOTHING
;
2180 rv
= open_tabs(NULL
, &a
);
2189 save_tabs(struct tab
*t
, struct karg
*a
)
2191 char file
[PATH_MAX
];
2196 const gchar
**arr
= NULL
;
2201 snprintf(file
, sizeof file
, "%s/%s",
2202 sessions_dir
, named_session
);
2204 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2206 if ((f
= fopen(file
, "w")) == NULL
) {
2207 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2211 /* save session name */
2212 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2214 /* save tabs, in the order they are arranged in the notebook */
2215 TAILQ_FOREACH(ti
, &tabs
, entry
)
2218 arr
= g_malloc0(len
* sizeof(gchar
*));
2220 TAILQ_FOREACH(ti
, &tabs
, entry
) {
2221 if ((uri
= get_uri(ti
->wv
)) != NULL
)
2222 arr
[gtk_notebook_page_num(notebook
, ti
->vbox
)] = uri
;
2225 for (i
= 0; i
< len
; i
++)
2227 fprintf(f
, "%s\n", arr
[i
]);
2230 /* try and make sure this gets to disk NOW. XXX Backup first? */
2231 if (fflush(f
) != 0 || fsync(fileno(f
)) != 0) {
2232 show_oops(t
, "May not have managed to save session: %s",
2242 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2254 yank_uri(struct tab
*t
, struct karg
*args
)
2257 GtkClipboard
*clipboard
;
2259 if ((uri
= get_uri(t
->wv
)) == NULL
)
2262 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2263 gtk_clipboard_set_text(clipboard
, uri
, -1);
2269 paste_uri(struct tab
*t
, struct karg
*args
)
2271 GtkClipboard
*clipboard
;
2272 GdkAtom atom
= gdk_atom_intern("CUT_BUFFER0", FALSE
);
2274 gchar
*p
= NULL
, *uri
;
2276 /* try primary clipboard first */
2277 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2278 p
= gtk_clipboard_wait_for_text(clipboard
);
2280 /* if it failed get whatever text is in cut_buffer0 */
2282 if (gdk_property_get(gdk_get_default_root_window(),
2284 gdk_atom_intern("STRING", FALSE
),
2286 65536 /* picked out of my butt */,
2292 /* yes sir, we need to NUL the string */
2298 while(*uri
&& isspace(*uri
))
2300 if (strlen(uri
) == 0) {
2301 show_oops(t
, "empty paste buffer");
2304 if (valid_url_type(uri
)) {
2305 /* we can be clever and paste this in search box */
2306 show_oops(t
, "not a valid URL");
2310 if (args
->i
== XT_PASTE_CURRENT_TAB
)
2312 else if (args
->i
== XT_PASTE_NEW_TAB
)
2313 create_new_tab(uri
, NULL
, 1, -1);
2324 find_domain(const gchar
*s
, int add_dot
)
2327 char *r
= NULL
, *ss
= NULL
;
2332 if (!strncmp(s
, "http://", strlen("http://")))
2333 s
= &s
[strlen("http://")];
2334 else if (!strncmp(s
, "https://", strlen("https://")))
2335 s
= &s
[strlen("https://")];
2341 for (i
= 0; i
< strlen(ss
) + 1 /* yes er need this */; i
++)
2342 /* chop string at first slash */
2343 if (ss
[i
] == '/' || ss
[i
] == '\0') {
2346 r
= g_strdup_printf(".%s", ss
);
2357 toggle_cwl(struct tab
*t
, struct karg
*args
)
2361 char *dom
= NULL
, *dom_toggle
= NULL
;
2367 uri
= get_uri(t
->wv
);
2368 dom
= find_domain(uri
, 1);
2369 d
= wl_find(dom
, &c_wl
);
2376 if (args
->i
& XT_WL_TOGGLE
)
2378 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2380 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2383 if (args
->i
& XT_WL_TOPLEVEL
)
2384 dom_toggle
= get_toplevel_domain(dom
);
2389 /* enable cookies for domain */
2390 wl_add(dom_toggle
, &c_wl
, 0);
2392 /* disable cookies for domain */
2393 RB_REMOVE(domain_list
, &c_wl
, d
);
2395 if (args
->i
& XT_WL_RELOAD
)
2396 webkit_web_view_reload(t
->wv
);
2403 toggle_js(struct tab
*t
, struct karg
*args
)
2408 char *dom
= NULL
, *dom_toggle
= NULL
;
2413 g_object_get(G_OBJECT(t
->settings
),
2414 "enable-scripts", &es
, (char *)NULL
);
2415 if (args
->i
& XT_WL_TOGGLE
)
2417 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2419 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2424 uri
= get_uri(t
->wv
);
2425 dom
= find_domain(uri
, 1);
2427 if (uri
== NULL
|| dom
== NULL
) {
2428 show_oops(t
, "Can't toggle domain in JavaScript white list");
2432 if (args
->i
& XT_WL_TOPLEVEL
)
2433 dom_toggle
= get_toplevel_domain(dom
);
2438 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2439 wl_add(dom_toggle
, &js_wl
, 0 /* session */);
2441 d
= wl_find(dom_toggle
, &js_wl
);
2443 RB_REMOVE(domain_list
, &js_wl
, d
);
2444 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2446 g_object_set(G_OBJECT(t
->settings
),
2447 "enable-scripts", es
, (char *)NULL
);
2448 g_object_set(G_OBJECT(t
->settings
),
2449 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2450 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2452 if (args
->i
& XT_WL_RELOAD
)
2453 webkit_web_view_reload(t
->wv
);
2461 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2465 a
.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
;
2468 a
.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
;
2473 toggle_src(struct tab
*t
, struct karg
*args
)
2480 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2481 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2482 webkit_web_view_reload(t
->wv
);
2488 focus_webview(struct tab
*t
)
2493 /* only grab focus if we are visible */
2494 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2495 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2499 focus(struct tab
*t
, struct karg
*args
)
2501 if (t
== NULL
|| args
== NULL
)
2507 if (args
->i
== XT_FOCUS_URI
)
2508 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2509 else if (args
->i
== XT_FOCUS_SEARCH
)
2510 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2516 stats(struct tab
*t
, struct karg
*args
)
2518 char *page
, *body
, *s
, line
[64 * 1024];
2519 uint64_t line_count
= 0;
2523 show_oops_s("stats invalid parameters");
2526 if (save_rejected_cookies
) {
2527 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2529 s
= fgets(line
, sizeof line
, r_cookie_f
);
2530 if (s
== NULL
|| feof(r_cookie_f
) ||
2536 snprintf(line
, sizeof line
,
2537 "<br/>Cookies blocked(*) total: %llu", line_count
);
2539 show_oops(t
, "Can't open blocked cookies file: %s",
2543 body
= g_strdup_printf(
2544 "Cookies blocked(*) this session: %llu"
2546 "<p><small><b>*</b> results vary based on settings</small></p>",
2550 page
= get_html_page("Statistics", body
, "", 0);
2553 load_webkit_string(t
, page
, XT_URI_ABOUT_STATS
);
2560 marco(struct tab
*t
, struct karg
*args
)
2562 char *page
, line
[64 * 1024];
2566 show_oops_s("marco invalid parameters");
2569 snprintf(line
, sizeof line
, "%s", marco_message(&len
));
2571 page
= get_html_page("Marco Sez...", line
, "", 0);
2573 load_webkit_string(t
, page
, XT_URI_ABOUT_MARCO
);
2580 blank(struct tab
*t
, struct karg
*args
)
2583 show_oops_s("blank invalid parameters");
2585 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2590 about(struct tab
*t
, struct karg
*args
)
2595 show_oops_s("about invalid parameters");
2597 body
= g_strdup_printf("<b>Version: %s</b><p>"
2600 "<li>Marco Peereboom <marco@peereboom.us></li>"
2601 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2602 "<li>Edd Barrett <vext01@gmail.com> </li>"
2603 "<li>Todd T. Fries <todd@fries.net> </li>"
2604 "<li>Raphael Graf <r@undefined.ch> </li>"
2606 "Copyrights and licenses can be found on the XXXterm "
2607 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2611 page
= get_html_page("About", body
, "", 0);
2614 load_webkit_string(t
, page
, XT_URI_ABOUT_ABOUT
);
2621 help(struct tab
*t
, struct karg
*args
)
2623 char *page
, *head
, *body
;
2626 show_oops_s("help invalid parameters");
2628 head
= "<meta http-equiv=\"REFRESH\" content=\"0;"
2629 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2631 body
= "XXXterm man page <a href=\"http://opensource.conformal.com/"
2632 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2633 "cgi-bin/man-cgi?xxxterm</a>";
2635 page
= get_html_page("XXXterm", body
, head
, FALSE
);
2637 load_webkit_string(t
, page
, XT_URI_ABOUT_HELP
);
2644 * update all favorite tabs apart from one. Pass NULL if
2645 * you want to update all.
2648 update_favorite_tabs(struct tab
*apart_from
)
2651 if (!updating_fl_tabs
) {
2652 updating_fl_tabs
= 1; /* stop infinite recursion */
2653 TAILQ_FOREACH(t
, &tabs
, entry
)
2654 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2655 && (t
!= apart_from
))
2656 xtp_page_fl(t
, NULL
);
2657 updating_fl_tabs
= 0;
2661 /* show a list of favorites (bookmarks) */
2663 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2665 char file
[PATH_MAX
];
2667 char *uri
= NULL
, *title
= NULL
;
2668 size_t len
, lineno
= 0;
2670 char *body
, *tmp
, *page
= NULL
;
2671 const char delim
[3] = {'\\', '\\', '\0'};
2673 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2676 warn("%s: bad param", __func__
);
2678 /* mark tab as favorite list */
2679 t
->xtp_meaning
= XT_XTP_TAB_MEANING_FL
;
2681 /* new session key */
2682 if (!updating_fl_tabs
)
2683 generate_xtp_session_key(&fl_session_key
);
2685 /* open favorites */
2686 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
2687 if ((f
= fopen(file
, "r")) == NULL
) {
2688 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2693 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
2694 "<th style='width: 40px'>#</th><th>Link</th>"
2695 "<th style='width: 40px'>Rm</th></tr>\n");
2698 if ((title
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
2699 if (feof(f
) || ferror(f
))
2707 if ((uri
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
2708 if (feof(f
) || ferror(f
)) {
2709 show_oops(t
, "favorites file corrupt");
2715 body
= g_strdup_printf("%s<tr>"
2717 "<td><a href='%s'>%s</a></td>"
2718 "<td style='text-align: center'>"
2719 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2721 body
, i
, uri
, title
,
2722 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2734 /* if none, say so */
2737 body
= g_strdup_printf("%s<tr>"
2738 "<td colspan='3' style='text-align: center'>"
2739 "No favorites - To add one use the 'favadd' command."
2740 "</td></tr>", body
);
2745 body
= g_strdup_printf("%s</table>", body
);
2755 page
= get_html_page("Favorites", body
, "", 1);
2756 load_webkit_string(t
, page
, XT_URI_ABOUT_FAVORITES
);
2760 update_favorite_tabs(t
);
2769 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2770 size_t cert_count
, char *title
)
2772 gnutls_datum_t cinfo
;
2776 body
= g_strdup("");
2778 for (i
= 0; i
< cert_count
; i
++) {
2779 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2784 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2785 body
, i
, cinfo
.data
);
2786 gnutls_free(cinfo
.data
);
2790 tmp
= get_html_page(title
, body
, "", 0);
2793 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
2798 ca_cmd(struct tab
*t
, struct karg
*args
)
2801 int rv
= 1, certs
= 0, certs_read
;
2804 gnutls_x509_crt_t
*c
= NULL
;
2805 char *certs_buf
= NULL
, *s
;
2807 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
2808 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
2812 if (fstat(fileno(f
), &sb
) == -1) {
2813 show_oops(t
, "Can't stat CA file: %s", ssl_ca_file
);
2817 certs_buf
= g_malloc(sb
.st_size
+ 1);
2818 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
2819 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
2822 certs_buf
[sb
.st_size
] = '\0';
2825 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
2827 s
+= strlen("BEGIN CERTIFICATE");
2830 bzero(&dt
, sizeof dt
);
2831 dt
.data
= certs_buf
;
2832 dt
.size
= sb
.st_size
;
2833 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
2834 certs_read
= gnutls_x509_crt_list_import(c
, &certs
, &dt
,
2835 GNUTLS_X509_FMT_PEM
, 0);
2836 if (certs_read
<= 0) {
2837 show_oops(t
, "No cert(s) available");
2840 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
2853 connect_socket_from_uri(const gchar
*uri
, char *domain
, size_t domain_sz
)
2856 struct addrinfo hints
, *res
= NULL
, *ai
;
2860 if (uri
&& !g_str_has_prefix(uri
, "https://"))
2863 su
= soup_uri_new(uri
);
2866 if (!SOUP_URI_VALID_FOR_HTTP(su
))
2869 snprintf(port
, sizeof port
, "%d", su
->port
);
2870 bzero(&hints
, sizeof(struct addrinfo
));
2871 hints
.ai_flags
= AI_CANONNAME
;
2872 hints
.ai_family
= AF_UNSPEC
;
2873 hints
.ai_socktype
= SOCK_STREAM
;
2875 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
2878 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
2879 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
2882 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
2885 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
2889 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
2894 strlcpy(domain
, su
->host
, domain_sz
);
2905 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
2908 gnutls_deinit(gsession
);
2910 gnutls_certificate_free_credentials(xcred
);
2916 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
2917 gnutls_certificate_credentials_t
*xc
)
2919 gnutls_certificate_credentials_t xcred
;
2920 gnutls_session_t gsession
;
2923 if (gs
== NULL
|| xc
== NULL
)
2929 gnutls_certificate_allocate_credentials(&xcred
);
2930 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
2931 GNUTLS_X509_FMT_PEM
);
2932 gnutls_init(&gsession
, GNUTLS_CLIENT
);
2933 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
2934 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
2935 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
2936 if ((rv
= gnutls_handshake(gsession
)) < 0) {
2937 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
2939 gnutls_error_is_fatal(rv
),
2940 gnutls_strerror_name(rv
));
2941 stop_tls(gsession
, xcred
);
2945 gnutls_credentials_type_t cred
;
2946 cred
= gnutls_auth_get_type(gsession
);
2947 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
2948 stop_tls(gsession
, xcred
);
2960 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
2964 const gnutls_datum_t
*cl
;
2965 gnutls_x509_crt_t
*all_certs
;
2968 if (certs
== NULL
|| cert_count
== NULL
)
2970 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
2972 cl
= gnutls_certificate_get_peers(gsession
, &len
);
2976 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
2977 for (i
= 0; i
< len
; i
++) {
2978 gnutls_x509_crt_init(&all_certs
[i
]);
2979 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
2980 GNUTLS_X509_FMT_PEM
< 0)) {
2994 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
2998 for (i
= 0; i
< cert_count
; i
++)
2999 gnutls_x509_crt_deinit(certs
[i
]);
3004 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
3005 size_t cert_count
, char *domain
)
3008 char cert_buf
[64 * 1024], file
[PATH_MAX
];
3013 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
3016 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3017 if ((f
= fopen(file
, "w")) == NULL
) {
3018 show_oops(t
, "Can't create cert file %s %s",
3019 file
, strerror(errno
));
3023 for (i
= 0; i
< cert_count
; i
++) {
3024 cert_buf_sz
= sizeof cert_buf
;
3025 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3026 cert_buf
, &cert_buf_sz
)) {
3027 show_oops(t
, "gnutls_x509_crt_export failed");
3030 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3031 show_oops(t
, "Can't write certs: %s", strerror(errno
));
3036 /* not the best spot but oh well */
3037 gdk_color_parse("lightblue", &color
);
3038 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3039 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
3040 gdk_color_parse(XT_COLOR_BLACK
, &color
);
3041 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
3047 load_compare_cert(struct tab
*t
, struct karg
*args
)
3050 char domain
[8182], file
[PATH_MAX
];
3051 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
3052 int s
= -1, rv
= 1, i
;
3056 gnutls_session_t gsession
;
3057 gnutls_x509_crt_t
*certs
;
3058 gnutls_certificate_credentials_t xcred
;
3063 if ((uri
= get_uri(t
->wv
)) == NULL
)
3066 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
3070 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3071 show_oops(t
, "Start TLS failed");
3076 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3077 show_oops(t
, "Can't get connection certificates");
3081 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3082 if ((f
= fopen(file
, "r")) == NULL
)
3085 for (i
= 0; i
< cert_count
; i
++) {
3086 cert_buf_sz
= sizeof cert_buf
;
3087 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3088 cert_buf
, &cert_buf_sz
)) {
3091 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3092 rv
= -1; /* critical */
3095 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3096 rv
= -1; /* critical */
3105 free_connection_certs(certs
, cert_count
);
3107 /* we close the socket first for speed */
3110 stop_tls(gsession
, xcred
);
3116 cert_cmd(struct tab
*t
, struct karg
*args
)
3122 gnutls_session_t gsession
;
3123 gnutls_x509_crt_t
*certs
;
3124 gnutls_certificate_credentials_t xcred
;
3129 if (ssl_ca_file
== NULL
) {
3130 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
3134 if ((uri
= get_uri(t
->wv
)) == NULL
) {
3135 show_oops(t
, "Invalid URI");
3139 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
3140 show_oops(t
, "Invalid certificate URI: %s", uri
);
3145 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3146 show_oops(t
, "Start TLS failed");
3151 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3152 show_oops(t
, "get_connection_certs failed");
3156 if (args
->i
& XT_SHOW
)
3157 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3158 else if (args
->i
& XT_SAVE
)
3159 save_certs(t
, certs
, cert_count
, domain
);
3161 free_connection_certs(certs
, cert_count
);
3163 /* we close the socket first for speed */
3166 stop_tls(gsession
, xcred
);
3172 remove_cookie(int index
)
3178 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3180 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3182 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3186 print_cookie("remove cookie", c
);
3187 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3192 soup_cookies_free(cf
);
3198 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3203 body
= g_strdup("");
3206 if (args
->i
& XT_WL_PERSISTENT
) {
3208 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3210 RB_FOREACH(d
, domain_list
, wl
) {
3214 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3220 if (args
->i
& XT_WL_SESSION
) {
3222 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3224 RB_FOREACH(d
, domain_list
, wl
) {
3228 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3233 tmp
= get_html_page(title
, body
, "", 0);
3236 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3238 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3244 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3246 char file
[PATH_MAX
];
3248 char *line
= NULL
, *lt
= NULL
;
3251 char *dom
= NULL
, *dom_save
= NULL
;
3257 if (t
== NULL
|| args
== NULL
)
3260 if (runtime_settings
[0] == '\0')
3263 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3264 if ((f
= fopen(file
, "r+")) == NULL
)
3267 uri
= get_uri(t
->wv
);
3268 dom
= find_domain(uri
, 1);
3269 if (uri
== NULL
|| dom
== NULL
) {
3270 show_oops(t
, "Can't add domain to %s white list",
3271 js
? "JavaScript" : "cookie");
3275 if (args
->i
& XT_WL_TOPLEVEL
) {
3277 if ((dom_save
= get_toplevel_domain(dom
)) == NULL
) {
3278 show_oops(t
, "invalid domain: %s", dom
);
3281 } else if (args
->i
& XT_WL_FQDN
) {
3287 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom_save
);
3290 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3293 if (!strcmp(line
, lt
))
3299 fprintf(f
, "%s\n", lt
);
3304 d
= wl_find(dom_save
, &js_wl
);
3306 settings_add("js_wl", dom_save
);
3307 d
= wl_find(dom_save
, &js_wl
);
3311 d
= wl_find(dom_save
, &c_wl
);
3313 settings_add("cookie_wl", dom_save
);
3314 d
= wl_find(dom_save
, &c_wl
);
3318 /* find and add to persistent jar */
3319 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3320 for (;cf
; cf
= cf
->next
) {
3322 if (!strcmp(dom_save
, ci
->domain
) ||
3323 !strcmp(&dom_save
[1], ci
->domain
)) /* deal with leading . */ {
3324 c
= soup_cookie_copy(ci
);
3325 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3328 soup_cookies_free(cf
);
3346 js_show_wl(struct tab
*t
, struct karg
*args
)
3348 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3349 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3355 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3357 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3358 wl_show(t
, args
, "Cookie White List", &c_wl
);
3364 cookie_cmd(struct tab
*t
, struct karg
*args
)
3366 if (args
->i
& XT_SHOW
)
3367 wl_show(t
, args
, "Cookie White List", &c_wl
);
3368 else if (args
->i
& XT_WL_TOGGLE
) {
3369 args
->i
|= XT_WL_RELOAD
;
3370 toggle_cwl(t
, args
);
3371 } else if (args
->i
& XT_SAVE
) {
3372 args
->i
|= XT_WL_RELOAD
;
3373 wl_save(t
, args
, 0);
3374 } else if (args
->i
& XT_DELETE
)
3375 show_oops(t
, "'cookie delete' currently unimplemented");
3381 js_cmd(struct tab
*t
, struct karg
*args
)
3383 if (args
->i
& XT_SHOW
)
3384 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3385 else if (args
->i
& XT_SAVE
) {
3386 args
->i
|= XT_WL_RELOAD
;
3387 wl_save(t
, args
, 1);
3388 } else if (args
->i
& XT_WL_TOGGLE
) {
3389 args
->i
|= XT_WL_RELOAD
;
3391 } else if (args
->i
& XT_DELETE
)
3392 show_oops(t
, "'js delete' currently unimplemented");
3398 toplevel_cmd(struct tab
*t
, struct karg
*args
)
3400 js_toggle_cb(t
->js_toggle
, t
);
3406 add_favorite(struct tab
*t
, struct karg
*args
)
3408 char file
[PATH_MAX
];
3411 size_t urilen
, linelen
;
3412 const gchar
*uri
, *title
;
3417 /* don't allow adding of xtp pages to favorites */
3418 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3419 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3423 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3424 if ((f
= fopen(file
, "r+")) == NULL
) {
3425 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3429 title
= webkit_web_view_get_title(t
->wv
);
3430 uri
= get_uri(t
->wv
);
3435 if (title
== NULL
|| uri
== NULL
) {
3436 show_oops(t
, "can't add page to favorites");
3440 urilen
= strlen(uri
);
3443 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3444 if (feof(f
) || ferror(f
))
3447 if (linelen
== urilen
&& !strcmp(line
, uri
))
3454 fprintf(f
, "\n%s\n%s", title
, uri
);
3460 update_favorite_tabs(NULL
);
3466 navaction(struct tab
*t
, struct karg
*args
)
3468 WebKitWebHistoryItem
*item
;
3470 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3471 t
->tab_id
, args
->i
);
3474 if (args
->i
== XT_NAV_BACK
)
3475 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3477 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3479 return (XT_CB_PASSTHROUGH
);
3480 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
3482 return (XT_CB_PASSTHROUGH
);
3487 webkit_web_view_go_back(t
->wv
);
3489 case XT_NAV_FORWARD
:
3490 webkit_web_view_go_forward(t
->wv
);
3493 webkit_web_view_reload(t
->wv
);
3495 case XT_NAV_RELOAD_CACHE
:
3496 webkit_web_view_reload_bypass_cache(t
->wv
);
3499 return (XT_CB_PASSTHROUGH
);
3503 move(struct tab
*t
, struct karg
*args
)
3505 GtkAdjustment
*adjust
;
3506 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3511 case XT_MOVE_BOTTOM
:
3513 case XT_MOVE_PAGEDOWN
:
3514 case XT_MOVE_PAGEUP
:
3515 case XT_MOVE_HALFDOWN
:
3516 case XT_MOVE_HALFUP
:
3517 adjust
= t
->adjust_v
;
3520 adjust
= t
->adjust_h
;
3524 pos
= gtk_adjustment_get_value(adjust
);
3525 ps
= gtk_adjustment_get_page_size(adjust
);
3526 upper
= gtk_adjustment_get_upper(adjust
);
3527 lower
= gtk_adjustment_get_lower(adjust
);
3528 si
= gtk_adjustment_get_step_increment(adjust
);
3529 pi
= gtk_adjustment_get_page_increment(adjust
);
3532 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3533 "max %f si %f pi %f\n",
3534 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
3535 pos
, ps
, upper
, lower
, max
, si
, pi
);
3541 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3546 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3548 case XT_MOVE_BOTTOM
:
3549 case XT_MOVE_FARRIGHT
:
3550 gtk_adjustment_set_value(adjust
, max
);
3553 case XT_MOVE_FARLEFT
:
3554 gtk_adjustment_set_value(adjust
, lower
);
3556 case XT_MOVE_PAGEDOWN
:
3558 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3560 case XT_MOVE_PAGEUP
:
3562 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3564 case XT_MOVE_HALFDOWN
:
3566 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3568 case XT_MOVE_HALFUP
:
3570 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3573 return (XT_CB_PASSTHROUGH
);
3576 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3578 return (XT_CB_HANDLED
);
3582 url_set_visibility(void)
3586 TAILQ_FOREACH(t
, &tabs
, entry
) {
3587 if (show_url
== 0) {
3588 gtk_widget_hide(t
->toolbar
);
3591 gtk_widget_show(t
->toolbar
);
3596 notebook_tab_set_visibility(GtkNotebook
*notebook
)
3599 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3601 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3605 statusbar_set_visibility(void)
3609 TAILQ_FOREACH(t
, &tabs
, entry
) {
3610 if (show_statusbar
== 0) {
3611 gtk_widget_hide(t
->statusbar
);
3614 gtk_widget_show(t
->statusbar
);
3619 url_set(struct tab
*t
, int enable_url_entry
)
3624 show_url
= enable_url_entry
;
3626 if (enable_url_entry
) {
3627 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
3628 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3629 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
), 0);
3631 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
3632 GTK_ENTRY_ICON_PRIMARY
);
3634 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
3635 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
3636 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
3637 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
3643 fullscreen(struct tab
*t
, struct karg
*args
)
3645 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3648 return (XT_CB_PASSTHROUGH
);
3650 if (show_url
== 0) {
3658 url_set_visibility();
3659 notebook_tab_set_visibility(notebook
);
3661 return (XT_CB_HANDLED
);
3665 statusaction(struct tab
*t
, struct karg
*args
)
3667 int rv
= XT_CB_HANDLED
;
3669 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3672 return (XT_CB_PASSTHROUGH
);
3675 case XT_STATUSBAR_SHOW
:
3676 if (show_statusbar
== 0) {
3678 statusbar_set_visibility();
3681 case XT_STATUSBAR_HIDE
:
3682 if (show_statusbar
== 1) {
3684 statusbar_set_visibility();
3692 urlaction(struct tab
*t
, struct karg
*args
)
3694 int rv
= XT_CB_HANDLED
;
3696 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3699 return (XT_CB_PASSTHROUGH
);
3703 if (show_url
== 0) {
3705 url_set_visibility();
3709 if (show_url
== 1) {
3711 url_set_visibility();
3719 tabaction(struct tab
*t
, struct karg
*args
)
3721 int rv
= XT_CB_HANDLED
;
3722 char *url
= args
->s
;
3726 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
3729 return (XT_CB_PASSTHROUGH
);
3733 if (strlen(url
) > 0)
3734 create_new_tab(url
, NULL
, 1, args
->p
);
3736 create_new_tab(NULL
, NULL
, 1, args
->p
);
3742 TAILQ_FOREACH(tt
, &tabs
, entry
)
3743 if (tt
->tab_id
== args
->p
- 1) {
3749 case XT_TAB_DELQUIT
:
3750 if (gtk_notebook_get_n_pages(notebook
) > 1)
3756 if (strlen(url
) > 0)
3759 rv
= XT_CB_PASSTHROUGH
;
3765 if (show_tabs
== 0) {
3767 notebook_tab_set_visibility(notebook
);
3771 if (show_tabs
== 1) {
3773 notebook_tab_set_visibility(notebook
);
3776 case XT_TAB_UNDO_CLOSE
:
3777 if (undo_count
== 0) {
3778 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3782 u
= TAILQ_FIRST(&undos
);
3783 create_new_tab(u
->uri
, u
, 1, -1);
3785 TAILQ_REMOVE(&undos
, u
, entry
);
3787 /* u->history is freed in create_new_tab() */
3792 rv
= XT_CB_PASSTHROUGH
;
3806 resizetab(struct tab
*t
, struct karg
*args
)
3808 if (t
== NULL
|| args
== NULL
) {
3809 show_oops_s("resizetab invalid parameters");
3810 return (XT_CB_PASSTHROUGH
);
3813 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
3814 t
->tab_id
, args
->i
);
3816 adjustfont_webkit(t
, args
->i
);
3818 return (XT_CB_HANDLED
);
3822 movetab(struct tab
*t
, struct karg
*args
)
3826 if (t
== NULL
|| args
== NULL
) {
3827 show_oops_s("movetab invalid parameters");
3828 return (XT_CB_PASSTHROUGH
);
3831 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
3832 t
->tab_id
, args
->i
);
3834 if (args
->i
>= XT_TAB_INVALID
)
3835 return (XT_CB_PASSTHROUGH
);
3837 if (TAILQ_EMPTY(&tabs
))
3838 return (XT_CB_PASSTHROUGH
);
3840 n
= gtk_notebook_get_n_pages(notebook
);
3841 dest
= gtk_notebook_get_current_page(notebook
);
3846 dest
= dest
== n
- 1 ? 0 : dest
+ 1;
3855 dest
-= args
->p
% n
;
3868 return (XT_CB_PASSTHROUGH
);
3871 if (dest
< 0 || dest
>= n
)
3872 return (XT_CB_PASSTHROUGH
);
3873 if (t
->tab_id
== dest
) {
3874 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
3875 return (XT_CB_HANDLED
);
3878 gtk_notebook_set_current_page(notebook
, dest
);
3880 return (XT_CB_HANDLED
);
3886 command(struct tab
*t
, struct karg
*args
)
3888 char *s
= NULL
, *ss
= NULL
;
3892 if (t
== NULL
|| args
== NULL
) {
3893 show_oops_s("command invalid parameters");
3894 return (XT_CB_PASSTHROUGH
);
3905 if (cmd_prefix
== 0)
3908 ss
= g_strdup_printf(":%d", cmd_prefix
);
3919 case XT_CMD_OPEN_CURRENT
:
3922 case XT_CMD_TABNEW_CURRENT
:
3923 if (!s
) /* FALL THROUGH? */
3925 if ((uri
= get_uri(t
->wv
)) != NULL
) {
3926 ss
= g_strdup_printf("%s%s", s
, uri
);
3931 show_oops(t
, "command: invalid opcode %d", args
->i
);
3932 return (XT_CB_PASSTHROUGH
);
3935 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
3937 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
3938 gdk_color_parse(XT_COLOR_WHITE
, &color
);
3939 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
3941 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
3942 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
3947 return (XT_CB_HANDLED
);
3951 * Return a new string with a download row (in html)
3952 * appended. Old string is freed.
3955 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
3958 WebKitDownloadStatus stat
;
3959 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
3961 char cur_sz
[FMT_SCALED_STRSIZE
];
3962 char tot_sz
[FMT_SCALED_STRSIZE
];
3965 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
3967 /* All actions wil take this form:
3968 * xxxt://class/seskey
3970 xtp_prefix
= g_strdup_printf("%s%d/%s/",
3971 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
3973 stat
= webkit_download_get_status(dl
->download
);
3976 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
3977 status_html
= g_strdup_printf("Finished");
3978 cmd_html
= g_strdup_printf(
3979 "<a href='%s%d/%d'>Remove</a>",
3980 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3982 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
3983 /* gather size info */
3984 progress
= 100 * webkit_download_get_progress(dl
->download
);
3987 webkit_download_get_current_size(dl
->download
), cur_sz
);
3989 webkit_download_get_total_size(dl
->download
), tot_sz
);
3991 status_html
= g_strdup_printf(
3992 "<div style='width: 100%%' align='center'>"
3993 "<div class='progress-outer'>"
3994 "<div class='progress-inner' style='width: %.2f%%'>"
3995 "</div></div></div>"
3996 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3997 progress
, cur_sz
, tot_sz
, progress
);
3999 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4000 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
4004 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
4005 status_html
= g_strdup_printf("Cancelled");
4006 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4007 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4009 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
4010 status_html
= g_strdup_printf("Error!");
4011 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4012 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4014 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
4015 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4016 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
4017 status_html
= g_strdup_printf("Starting");
4020 show_oops(t
, "%s: unknown download status", __func__
);
4023 new_html
= g_strdup_printf(
4024 "%s\n<tr><td>%s</td><td>%s</td>"
4025 "<td style='text-align:center'>%s</td></tr>\n",
4026 html
, basename(webkit_download_get_destination_uri(dl
->download
)),
4027 status_html
, cmd_html
);
4031 g_free(status_html
);
4042 * update all download tabs apart from one. Pass NULL if
4043 * you want to update all.
4046 update_download_tabs(struct tab
*apart_from
)
4049 if (!updating_dl_tabs
) {
4050 updating_dl_tabs
= 1; /* stop infinite recursion */
4051 TAILQ_FOREACH(t
, &tabs
, entry
)
4052 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
4053 && (t
!= apart_from
))
4054 xtp_page_dl(t
, NULL
);
4055 updating_dl_tabs
= 0;
4060 * update all cookie tabs apart from one. Pass NULL if
4061 * you want to update all.
4064 update_cookie_tabs(struct tab
*apart_from
)
4067 if (!updating_cl_tabs
) {
4068 updating_cl_tabs
= 1; /* stop infinite recursion */
4069 TAILQ_FOREACH(t
, &tabs
, entry
)
4070 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
4071 && (t
!= apart_from
))
4072 xtp_page_cl(t
, NULL
);
4073 updating_cl_tabs
= 0;
4078 * update all history tabs apart from one. Pass NULL if
4079 * you want to update all.
4082 update_history_tabs(struct tab
*apart_from
)
4086 if (!updating_hl_tabs
) {
4087 updating_hl_tabs
= 1; /* stop infinite recursion */
4088 TAILQ_FOREACH(t
, &tabs
, entry
)
4089 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4090 && (t
!= apart_from
))
4091 xtp_page_hl(t
, NULL
);
4092 updating_hl_tabs
= 0;
4096 /* cookie management XTP page */
4098 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4100 char *body
, *page
, *tmp
;
4101 int i
= 1; /* all ids start 1 */
4102 GSList
*sc
, *pc
, *pc_start
;
4104 char *type
, *table_headers
;
4105 char *last_domain
= strdup("");
4107 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4110 show_oops_s("%s invalid parameters", __func__
);
4113 /* mark this tab as cookie jar */
4114 t
->xtp_meaning
= XT_XTP_TAB_MEANING_CL
;
4116 /* Generate a new session key */
4117 if (!updating_cl_tabs
)
4118 generate_xtp_session_key(&cl_session_key
);
4121 table_headers
= g_strdup_printf("<table><tr>"
4124 "<th style='width:200px'>Value</th>"
4128 "<th>HTTP<br />only</th>"
4129 "<th style='width:40px'>Rm</th></tr>\n");
4131 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4132 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4136 for (; sc
; sc
= sc
->next
) {
4139 if (strcmp(last_domain
, c
->domain
) != 0) {
4142 last_domain
= strdup(c
->domain
);
4146 body
= g_strdup_printf("%s</table>"
4148 body
, c
->domain
, table_headers
);
4152 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4153 c
->domain
, table_headers
);
4158 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4159 if (soup_cookie_equal(pc
->data
, c
)) {
4160 type
= "Session + Persistent";
4165 body
= g_strdup_printf(
4168 "<td style='word-wrap:normal'>%s</td>"
4170 " <textarea rows='4'>%s</textarea>"
4176 "<td style='text-align:center'>"
4177 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4184 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4199 soup_cookies_free(sc
);
4200 soup_cookies_free(pc
);
4202 /* small message if there are none */
4204 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4205 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4208 body
= g_strdup_printf("%s</table>", body
);
4211 page
= get_html_page("Cookie Jar", body
, "", TRUE
);
4213 g_free(table_headers
);
4214 g_free(last_domain
);
4216 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4217 update_cookie_tabs(t
);
4225 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4227 char *body
, *page
, *tmp
;
4229 int i
= 1; /* all ids start 1 */
4231 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4234 show_oops_s("%s invalid parameters", __func__
);
4238 /* mark this tab as history manager */
4239 t
->xtp_meaning
= XT_XTP_TAB_MEANING_HL
;
4241 /* Generate a new session key */
4242 if (!updating_hl_tabs
)
4243 generate_xtp_session_key(&hl_session_key
);
4246 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
4247 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4249 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4251 body
= g_strdup_printf(
4253 "<td><a href='%s'>%s</a></td>"
4255 "<td style='text-align: center'>"
4256 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4257 body
, h
->uri
, h
->uri
, h
->title
,
4258 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4259 XT_XTP_HL_REMOVE
, i
);
4265 /* small message if there are none */
4268 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4269 "colspan='3'>No History</td></tr>\n", body
);
4274 body
= g_strdup_printf("%s</table>", body
);
4277 page
= get_html_page("History", body
, "", TRUE
);
4281 * update all history manager tabs as the xtp session
4282 * key has now changed. No need to update the current tab.
4283 * Already did that above.
4285 update_history_tabs(t
);
4287 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4294 * Generate a web page detailing the status of any downloads
4297 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4299 struct download
*dl
;
4300 char *body
, *page
, *tmp
;
4304 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4307 show_oops_s("%s invalid parameters", __func__
);
4310 /* mark as a download manager tab */
4311 t
->xtp_meaning
= XT_XTP_TAB_MEANING_DL
;
4314 * Generate a new session key for next page instance.
4315 * This only happens for the top level call to xtp_page_dl()
4316 * in which case updating_dl_tabs is 0.
4318 if (!updating_dl_tabs
)
4319 generate_xtp_session_key(&dl_session_key
);
4321 /* header - with refresh so as to update */
4322 if (refresh_interval
>= 1)
4323 ref
= g_strdup_printf(
4324 "<meta http-equiv='refresh' content='%u"
4325 ";url=%s%d/%s/%d' />\n",
4334 body
= g_strdup_printf("<div align='center'>"
4335 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4336 "</p><table><tr><th style='width: 60%%'>"
4337 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4338 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4340 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4341 body
= xtp_page_dl_row(t
, body
, dl
);
4345 /* message if no downloads in list */
4348 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4349 " style='text-align: center'>"
4350 "No downloads</td></tr>\n", body
);
4355 body
= g_strdup_printf("%s</table></div>", body
);
4358 page
= get_html_page("Downloads", body
, ref
, 1);
4363 * update all download manager tabs as the xtp session
4364 * key has now changed. No need to update the current tab.
4365 * Already did that above.
4367 update_download_tabs(t
);
4369 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4376 search(struct tab
*t
, struct karg
*args
)
4380 if (t
== NULL
|| args
== NULL
) {
4381 show_oops_s("search invalid parameters");
4384 if (t
->search_text
== NULL
) {
4385 if (global_search
== NULL
)
4386 return (XT_CB_PASSTHROUGH
);
4388 t
->search_text
= g_strdup(global_search
);
4389 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4390 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4394 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4395 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4398 case XT_SEARCH_NEXT
:
4399 d
= t
->search_forward
;
4401 case XT_SEARCH_PREV
:
4402 d
= !t
->search_forward
;
4405 return (XT_CB_PASSTHROUGH
);
4408 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4410 return (XT_CB_HANDLED
);
4413 struct settings_args
{
4419 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4422 struct settings_args
*sa
= cb_args
;
4427 if (s
->flags
& XT_SF_RUNTIME
)
4433 *sa
->body
= g_strdup_printf(
4435 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4436 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4448 set(struct tab
*t
, struct karg
*args
)
4450 char *body
, *page
, *tmp
;
4452 struct settings_args sa
;
4454 bzero(&sa
, sizeof sa
);
4458 body
= g_strdup_printf("<div align='center'><table><tr>"
4459 "<th align='left'>Setting</th>"
4460 "<th align='left'>Value</th></tr>\n");
4462 settings_walk(print_setting
, &sa
);
4465 /* small message if there are none */
4468 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4469 "colspan='2'>No settings</td></tr>\n", body
);
4474 body
= g_strdup_printf("%s</table></div>", body
);
4477 page
= get_html_page("Settings", body
, "", 0);
4481 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4485 return (XT_CB_PASSTHROUGH
);
4489 session_save(struct tab
*t
, char *filename
)
4494 if (strlen(filename
) == 0)
4497 if (filename
[0] == '.' || filename
[0] == '/')
4501 if (save_tabs(t
, &a
))
4503 strlcpy(named_session
, filename
, sizeof named_session
);
4511 session_open(struct tab
*t
, char *filename
)
4516 if (strlen(filename
) == 0)
4519 if (filename
[0] == '.' || filename
[0] == '/')
4523 a
.i
= XT_SES_CLOSETABS
;
4524 if (open_tabs(t
, &a
))
4527 strlcpy(named_session
, filename
, sizeof named_session
);
4535 session_delete(struct tab
*t
, char *filename
)
4537 char file
[PATH_MAX
];
4540 if (strlen(filename
) == 0)
4543 if (filename
[0] == '.' || filename
[0] == '/')
4546 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
4550 if (!strcmp(filename
, named_session
))
4551 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
4552 sizeof named_session
);
4560 session_cmd(struct tab
*t
, struct karg
*args
)
4562 char *filename
= args
->s
;
4567 if (args
->i
& XT_SHOW
)
4568 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
4569 XT_SAVED_TABS_FILE
: named_session
);
4570 else if (args
->i
& XT_SAVE
) {
4571 if (session_save(t
, filename
)) {
4572 show_oops(t
, "Can't save session: %s",
4573 filename
? filename
: "INVALID");
4576 } else if (args
->i
& XT_OPEN
) {
4577 if (session_open(t
, filename
)) {
4578 show_oops(t
, "Can't open session: %s",
4579 filename
? filename
: "INVALID");
4582 } else if (args
->i
& XT_DELETE
) {
4583 if (session_delete(t
, filename
)) {
4584 show_oops(t
, "Can't delete session: %s",
4585 filename
? filename
: "INVALID");
4590 return (XT_CB_PASSTHROUGH
);
4594 * Make a hardcopy of the page
4597 print_page(struct tab
*t
, struct karg
*args
)
4599 WebKitWebFrame
*frame
;
4601 GtkPrintOperation
*op
;
4602 GtkPrintOperationAction action
;
4603 GtkPrintOperationResult print_res
;
4604 GError
*g_err
= NULL
;
4605 int marg_l
, marg_r
, marg_t
, marg_b
;
4607 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
4609 ps
= gtk_page_setup_new();
4610 op
= gtk_print_operation_new();
4611 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
4612 frame
= webkit_web_view_get_main_frame(t
->wv
);
4614 /* the default margins are too small, so we will bump them */
4615 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
4616 XT_PRINT_EXTRA_MARGIN
;
4617 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
4618 XT_PRINT_EXTRA_MARGIN
;
4619 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
4620 XT_PRINT_EXTRA_MARGIN
;
4621 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
4622 XT_PRINT_EXTRA_MARGIN
;
4625 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
4626 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
4627 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
4628 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
4630 gtk_print_operation_set_default_page_setup(op
, ps
);
4632 /* this appears to free 'op' and 'ps' */
4633 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
4635 /* check it worked */
4636 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
4637 show_oops_s("can't print: %s", g_err
->message
);
4638 g_error_free (g_err
);
4646 go_home(struct tab
*t
, struct karg
*args
)
4653 restart(struct tab
*t
, struct karg
*args
)
4657 a
.s
= XT_RESTART_TABS_FILE
;
4659 execvp(start_argv
[0], start_argv
);
4665 #define CTRL GDK_CONTROL_MASK
4666 #define MOD1 GDK_MOD1_MASK
4667 #define SHFT GDK_SHIFT_MASK
4669 /* inherent to GTK not all keys will be caught at all times */
4670 /* XXX sort key bindings */
4671 struct key_binding
{
4676 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
4678 { "cookiejar", MOD1
, 0, GDK_j
},
4679 { "downloadmgr", MOD1
, 0, GDK_d
},
4680 { "history", MOD1
, 0, GDK_h
},
4681 { "print", CTRL
, 0, GDK_p
},
4682 { "search", 0, 0, GDK_slash
},
4683 { "searchb", 0, 0, GDK_question
},
4684 { "command", 0, 0, GDK_colon
},
4685 { "qa", CTRL
, 0, GDK_q
},
4686 { "restart", MOD1
, 0, GDK_q
},
4687 { "js toggle", CTRL
, 0, GDK_j
},
4688 { "cookie toggle", MOD1
, 0, GDK_c
},
4689 { "togglesrc", CTRL
, 0, GDK_s
},
4690 { "yankuri", 0, 0, GDK_y
},
4691 { "pasteuricur", 0, 0, GDK_p
},
4692 { "pasteurinew", 0, 0, GDK_P
},
4693 { "toplevel toggle", 0, 0, GDK_F4
},
4694 { "help", 0, 0, GDK_F1
},
4697 { "searchnext", 0, 0, GDK_n
},
4698 { "searchprevious", 0, 0, GDK_N
},
4701 { "focusaddress", 0, 0, GDK_F6
},
4702 { "focussearch", 0, 0, GDK_F7
},
4705 { "hinting", 0, 0, GDK_f
},
4707 /* custom stylesheet */
4708 { "userstyle", 0, 0, GDK_i
},
4711 { "goback", 0, 0, GDK_BackSpace
},
4712 { "goback", MOD1
, 0, GDK_Left
},
4713 { "goforward", SHFT
, 0, GDK_BackSpace
},
4714 { "goforward", MOD1
, 0, GDK_Right
},
4715 { "reload", 0, 0, GDK_F5
},
4716 { "reload", CTRL
, 0, GDK_r
},
4717 { "reloadforce", CTRL
, 0, GDK_R
},
4718 { "reload", CTRL
, 0, GDK_l
},
4719 { "favorites", MOD1
, 1, GDK_f
},
4721 /* vertical movement */
4722 { "scrolldown", 0, 0, GDK_j
},
4723 { "scrolldown", 0, 0, GDK_Down
},
4724 { "scrollup", 0, 0, GDK_Up
},
4725 { "scrollup", 0, 0, GDK_k
},
4726 { "scrollbottom", 0, 0, GDK_G
},
4727 { "scrollbottom", 0, 0, GDK_End
},
4728 { "scrolltop", 0, 0, GDK_Home
},
4729 { "scrolltop", 0, 0, GDK_g
},
4730 { "scrollpagedown", 0, 0, GDK_space
},
4731 { "scrollpagedown", CTRL
, 0, GDK_f
},
4732 { "scrollhalfdown", CTRL
, 0, GDK_d
},
4733 { "scrollpagedown", 0, 0, GDK_Page_Down
},
4734 { "scrollpageup", 0, 0, GDK_Page_Up
},
4735 { "scrollpageup", CTRL
, 0, GDK_b
},
4736 { "scrollhalfup", CTRL
, 0, GDK_u
},
4737 /* horizontal movement */
4738 { "scrollright", 0, 0, GDK_l
},
4739 { "scrollright", 0, 0, GDK_Right
},
4740 { "scrollleft", 0, 0, GDK_Left
},
4741 { "scrollleft", 0, 0, GDK_h
},
4742 { "scrollfarright", 0, 0, GDK_dollar
},
4743 { "scrollfarleft", 0, 0, GDK_0
},
4746 { "tabnew", CTRL
, 0, GDK_t
},
4747 { "999tabnew", CTRL
, 0, GDK_T
},
4748 { "tabclose", CTRL
, 1, GDK_w
},
4749 { "tabundoclose", 0, 0, GDK_U
},
4750 { "tabnext 1", CTRL
, 0, GDK_1
},
4751 { "tabnext 2", CTRL
, 0, GDK_2
},
4752 { "tabnext 3", CTRL
, 0, GDK_3
},
4753 { "tabnext 4", CTRL
, 0, GDK_4
},
4754 { "tabnext 5", CTRL
, 0, GDK_5
},
4755 { "tabnext 6", CTRL
, 0, GDK_6
},
4756 { "tabnext 7", CTRL
, 0, GDK_7
},
4757 { "tabnext 8", CTRL
, 0, GDK_8
},
4758 { "tabnext 9", CTRL
, 0, GDK_9
},
4759 { "tabnext 10", CTRL
, 0, GDK_0
},
4760 { "tabfirst", CTRL
, 0, GDK_less
},
4761 { "tablast", CTRL
, 0, GDK_greater
},
4762 { "tabprevious", CTRL
, 0, GDK_Left
},
4763 { "tabnext", CTRL
, 0, GDK_Right
},
4764 { "focusout", CTRL
, 0, GDK_minus
},
4765 { "focusin", CTRL
, 0, GDK_plus
},
4766 { "focusin", CTRL
, 0, GDK_equal
},
4768 /* command aliases (handy when -S flag is used) */
4769 { "promptopen", 0, 0, GDK_F9
},
4770 { "promptopencurrent", 0, 0, GDK_F10
},
4771 { "prompttabnew", 0, 0, GDK_F11
},
4772 { "prompttabnewcurrent",0, 0, GDK_F12
},
4774 TAILQ_HEAD(keybinding_list
, key_binding
);
4777 walk_kb(struct settings
*s
,
4778 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
4780 struct key_binding
*k
;
4783 if (s
== NULL
|| cb
== NULL
) {
4784 show_oops_s("walk_kb invalid parameters");
4788 TAILQ_FOREACH(k
, &kbl
, entry
) {
4794 if (gdk_keyval_name(k
->key
) == NULL
)
4797 strlcat(str
, k
->cmd
, sizeof str
);
4798 strlcat(str
, ",", sizeof str
);
4800 if (k
->mask
& GDK_SHIFT_MASK
)
4801 strlcat(str
, "S-", sizeof str
);
4802 if (k
->mask
& GDK_CONTROL_MASK
)
4803 strlcat(str
, "C-", sizeof str
);
4804 if (k
->mask
& GDK_MOD1_MASK
)
4805 strlcat(str
, "M1-", sizeof str
);
4806 if (k
->mask
& GDK_MOD2_MASK
)
4807 strlcat(str
, "M2-", sizeof str
);
4808 if (k
->mask
& GDK_MOD3_MASK
)
4809 strlcat(str
, "M3-", sizeof str
);
4810 if (k
->mask
& GDK_MOD4_MASK
)
4811 strlcat(str
, "M4-", sizeof str
);
4812 if (k
->mask
& GDK_MOD5_MASK
)
4813 strlcat(str
, "M5-", sizeof str
);
4815 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
4816 cb(s
, str
, cb_args
);
4821 init_keybindings(void)
4824 struct key_binding
*k
;
4826 for (i
= 0; i
< LENGTH(keys
); i
++) {
4827 k
= g_malloc0(sizeof *k
);
4828 k
->cmd
= keys
[i
].cmd
;
4829 k
->mask
= keys
[i
].mask
;
4830 k
->use_in_entry
= keys
[i
].use_in_entry
;
4831 k
->key
= keys
[i
].key
;
4832 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4834 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
4835 k
->cmd
? k
->cmd
: "unnamed key");
4840 keybinding_clearall(void)
4842 struct key_binding
*k
, *next
;
4844 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
4845 next
= TAILQ_NEXT(k
, entry
);
4849 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
4850 k
->cmd
? k
->cmd
: "unnamed key");
4851 TAILQ_REMOVE(&kbl
, k
, entry
);
4857 keybinding_add(char *cmd
, char *key
, int use_in_entry
)
4859 struct key_binding
*k
;
4860 guint keyval
, mask
= 0;
4863 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s\n", cmd
, key
);
4865 /* Keys which are to be used in entry have been prefixed with an
4866 * exclamation mark. */
4870 /* find modifier keys */
4871 if (strstr(key
, "S-"))
4872 mask
|= GDK_SHIFT_MASK
;
4873 if (strstr(key
, "C-"))
4874 mask
|= GDK_CONTROL_MASK
;
4875 if (strstr(key
, "M1-"))
4876 mask
|= GDK_MOD1_MASK
;
4877 if (strstr(key
, "M2-"))
4878 mask
|= GDK_MOD2_MASK
;
4879 if (strstr(key
, "M3-"))
4880 mask
|= GDK_MOD3_MASK
;
4881 if (strstr(key
, "M4-"))
4882 mask
|= GDK_MOD4_MASK
;
4883 if (strstr(key
, "M5-"))
4884 mask
|= GDK_MOD5_MASK
;
4887 for (i
= strlen(key
) - 1; i
> 0; i
--)
4891 /* validate keyname */
4892 keyval
= gdk_keyval_from_name(key
);
4893 if (keyval
== GDK_VoidSymbol
) {
4894 warnx("invalid keybinding name %s", key
);
4897 /* must run this test too, gtk+ doesn't handle 10 for example */
4898 if (gdk_keyval_name(keyval
) == NULL
) {
4899 warnx("invalid keybinding name %s", key
);
4903 /* Remove eventual dupes. */
4904 TAILQ_FOREACH(k
, &kbl
, entry
)
4905 if (k
->key
== keyval
&& k
->mask
== mask
) {
4906 TAILQ_REMOVE(&kbl
, k
, entry
);
4912 k
= g_malloc0(sizeof *k
);
4913 k
->cmd
= g_strdup(cmd
);
4915 k
->use_in_entry
= use_in_entry
;
4918 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
4923 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
4924 k
->cmd
, gdk_keyval_name(keyval
));
4926 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4932 add_kb(struct settings
*s
, char *entry
)
4936 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
4938 /* clearall is special */
4939 if (!strcmp(entry
, "clearall")) {
4940 keybinding_clearall();
4944 kb
= strstr(entry
, ",");
4950 return (keybinding_add(entry
, key
, key
[0] == '!'));
4956 int (*func
)(struct tab
*, struct karg
*);
4960 { "command", 0, command
, ':', 0 },
4961 { "search", 0, command
, '/', 0 },
4962 { "searchb", 0, command
, '?', 0 },
4963 { "togglesrc", 0, toggle_src
, 0, 0 },
4965 /* yanking and pasting */
4966 { "yankuri", 0, yank_uri
, 0, 0 },
4967 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4968 { "pasteuricur", 0, paste_uri
, XT_PASTE_CURRENT_TAB
, 0 },
4969 { "pasteurinew", 0, paste_uri
, XT_PASTE_NEW_TAB
, 0 },
4972 { "searchnext", 0, search
, XT_SEARCH_NEXT
, 0 },
4973 { "searchprevious", 0, search
, XT_SEARCH_PREV
, 0 },
4976 { "focusaddress", 0, focus
, XT_FOCUS_URI
, 0 },
4977 { "focussearch", 0, focus
, XT_FOCUS_SEARCH
, 0 },
4980 { "hinting", 0, hint
, 0, 0 },
4982 /* custom stylesheet */
4983 { "userstyle", 0, userstyle
, 0, 0 },
4986 { "goback", 0, navaction
, XT_NAV_BACK
, 0 },
4987 { "goforward", 0, navaction
, XT_NAV_FORWARD
, 0 },
4988 { "reload", 0, navaction
, XT_NAV_RELOAD
, 0 },
4989 { "reloadforce", 0, navaction
, XT_NAV_RELOAD_CACHE
, 0 },
4991 /* vertical movement */
4992 { "scrolldown", 0, move
, XT_MOVE_DOWN
, 0 },
4993 { "scrollup", 0, move
, XT_MOVE_UP
, 0 },
4994 { "scrollbottom", 0, move
, XT_MOVE_BOTTOM
, 0 },
4995 { "scrolltop", 0, move
, XT_MOVE_TOP
, 0 },
4996 { "1", 0, move
, XT_MOVE_TOP
, 0 },
4997 { "scrollhalfdown", 0, move
, XT_MOVE_HALFDOWN
, 0 },
4998 { "scrollhalfup", 0, move
, XT_MOVE_HALFUP
, 0 },
4999 { "scrollpagedown", 0, move
, XT_MOVE_PAGEDOWN
, 0 },
5000 { "scrollpageup", 0, move
, XT_MOVE_PAGEUP
, 0 },
5001 /* horizontal movement */
5002 { "scrollright", 0, move
, XT_MOVE_RIGHT
, 0 },
5003 { "scrollleft", 0, move
, XT_MOVE_LEFT
, 0 },
5004 { "scrollfarright", 0, move
, XT_MOVE_FARRIGHT
, 0 },
5005 { "scrollfarleft", 0, move
, XT_MOVE_FARLEFT
, 0 },
5008 { "favorites", 0, xtp_page_fl
, 0, 0 },
5009 { "fav", 0, xtp_page_fl
, 0, 0 },
5010 { "favadd", 0, add_favorite
, 0, 0 },
5012 { "qall", 0, quit
, 0, 0 },
5013 { "quitall", 0, quit
, 0, 0 },
5014 { "w", 0, save_tabs
, 0, 0 },
5015 { "wq", 0, save_tabs_and_quit
, 0, 0 },
5016 { "help", 0, help
, 0, 0 },
5017 { "about", 0, about
, 0, 0 },
5018 { "stats", 0, stats
, 0, 0 },
5019 { "version", 0, about
, 0, 0 },
5022 { "js", 0, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5023 { "save", 1, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5024 { "domain", 2, js_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
5025 { "fqdn", 2, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5026 { "show", 1, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5027 { "all", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5028 { "persistent", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
5029 { "session", 2, js_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
5030 { "toggle", 1, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5031 { "domain", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5032 { "fqdn", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5034 /* cookie command */
5035 { "cookie", 0, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5036 { "save", 1, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5037 { "domain", 2, cookie_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
5038 { "fqdn", 2, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5039 { "show", 1, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5040 { "all", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5041 { "persistent", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
5042 { "session", 2, cookie_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
5043 { "toggle", 1, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5044 { "domain", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5045 { "fqdn", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5047 /* toplevel (domain) command */
5048 { "toplevel", 0, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
5049 { "toggle", 1, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
5052 { "cookiejar", 0, xtp_page_cl
, 0, 0 },
5055 { "cert", 0, cert_cmd
, XT_SHOW
, 0 },
5056 { "save", 1, cert_cmd
, XT_SAVE
, 0 },
5057 { "show", 1, cert_cmd
, XT_SHOW
, 0 },
5059 { "ca", 0, ca_cmd
, 0, 0 },
5060 { "downloadmgr", 0, xtp_page_dl
, 0, 0 },
5061 { "dl", 0, xtp_page_dl
, 0, 0 },
5062 { "h", 0, xtp_page_hl
, 0, 0 },
5063 { "history", 0, xtp_page_hl
, 0, 0 },
5064 { "home", 0, go_home
, 0, 0 },
5065 { "restart", 0, restart
, 0, 0 },
5066 { "urlhide", 0, urlaction
, XT_URL_HIDE
, 0 },
5067 { "urlshow", 0, urlaction
, XT_URL_SHOW
, 0 },
5068 { "statushide", 0, statusaction
, XT_STATUSBAR_HIDE
, 0 },
5069 { "statusshow", 0, statusaction
, XT_STATUSBAR_SHOW
, 0 },
5071 { "print", 0, print_page
, 0, 0 },
5074 { "focusin", 0, resizetab
, 1, 0 },
5075 { "focusout", 0, resizetab
, -1, 0 },
5076 { "q", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5077 { "quit", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5078 { "open", 0, tabaction
, XT_TAB_OPEN
, XT_URLARG
},
5079 { "tabclose", 0, tabaction
, XT_TAB_DELETE
, XT_PREFIX
| XT_INTARG
},
5080 { "tabedit", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5081 { "tabfirst", 0, movetab
, XT_TAB_FIRST
, 0 },
5082 { "tabhide", 0, tabaction
, XT_TAB_HIDE
, 0 },
5083 { "tablast", 0, movetab
, XT_TAB_LAST
, 0 },
5084 { "tabnew", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5085 { "tabnext", 0, movetab
, XT_TAB_NEXT
, XT_PREFIX
| XT_INTARG
},
5086 { "tabprevious", 0, movetab
, XT_TAB_PREV
, XT_PREFIX
| XT_INTARG
},
5087 { "tabrewind", 0, movetab
, XT_TAB_FIRST
, 0 },
5088 { "tabshow", 0, tabaction
, XT_TAB_SHOW
, 0 },
5089 { "tabundoclose", 0, tabaction
, XT_TAB_UNDO_CLOSE
, 0 },
5091 /* command aliases (handy when -S flag is used) */
5092 { "promptopen", 0, command
, XT_CMD_OPEN
, 0 },
5093 { "promptopencurrent", 0, command
, XT_CMD_OPEN_CURRENT
, 0 },
5094 { "prompttabnew", 0, command
, XT_CMD_TABNEW
, 0 },
5095 { "prompttabnewcurrent",0, command
, XT_CMD_TABNEW_CURRENT
, 0 },
5098 { "set", 0, set
, 0, 0 },
5099 { "fullscreen", 0, fullscreen
, 0, 0 },
5100 { "f", 0, fullscreen
, 0, 0 },
5103 { "session", 0, session_cmd
, XT_SHOW
, 0 },
5104 { "delete", 1, session_cmd
, XT_DELETE
, XT_USERARG
},
5105 { "open", 1, session_cmd
, XT_OPEN
, XT_USERARG
},
5106 { "save", 1, session_cmd
, XT_SAVE
, XT_USERARG
},
5107 { "show", 1, session_cmd
, XT_SHOW
, 0 },
5114 } cmd_status
= {-1, 0};
5117 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5123 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5129 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5131 a
.i
= XT_NAV_FORWARD
;
5141 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5143 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5145 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5152 * cancel, remove, etc. downloads
5155 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5157 struct download find
, *d
= NULL
;
5159 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5161 /* some commands require a valid download id */
5162 if (cmd
!= XT_XTP_DL_LIST
) {
5163 /* lookup download in question */
5165 d
= RB_FIND(download_list
, &downloads
, &find
);
5168 show_oops(t
, "%s: no such download", __func__
);
5173 /* decide what to do */
5175 case XT_XTP_DL_CANCEL
:
5176 webkit_download_cancel(d
->download
);
5178 case XT_XTP_DL_REMOVE
:
5179 webkit_download_cancel(d
->download
); /* just incase */
5180 g_object_unref(d
->download
);
5181 RB_REMOVE(download_list
, &downloads
, d
);
5183 case XT_XTP_DL_LIST
:
5187 show_oops(t
, "%s: unknown command", __func__
);
5190 xtp_page_dl(t
, NULL
);
5194 * Actions on history, only does one thing for now, but
5195 * we provide the function for future actions
5198 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5200 struct history
*h
, *next
;
5204 case XT_XTP_HL_REMOVE
:
5205 /* walk backwards, as listed in reverse */
5206 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5207 next
= RB_PREV(history_list
, &hl
, h
);
5209 RB_REMOVE(history_list
, &hl
, h
);
5210 g_free((gpointer
) h
->title
);
5211 g_free((gpointer
) h
->uri
);
5218 case XT_XTP_HL_LIST
:
5219 /* Nothing - just xtp_page_hl() below */
5222 show_oops(t
, "%s: unknown command", __func__
);
5226 xtp_page_hl(t
, NULL
);
5229 /* remove a favorite */
5231 remove_favorite(struct tab
*t
, int index
)
5233 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5234 char *new_favs
, *tmp
;
5239 /* open favorites */
5240 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5242 if ((f
= fopen(file
, "r")) == NULL
) {
5243 show_oops(t
, "%s: can't open favorites: %s",
5244 __func__
, strerror(errno
));
5248 /* build a string which will become the new favroites file */
5249 new_favs
= g_strdup("");
5252 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5253 if (feof(f
) || ferror(f
))
5255 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5262 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5263 if (feof(f
) || ferror(f
)) {
5264 show_oops(t
, "%s: can't parse favorites %s",
5265 __func__
, strerror(errno
));
5270 /* as long as this isn't the one we are deleting add to file */
5273 new_favs
= g_strdup_printf("%s%s\n%s\n",
5274 new_favs
, title
, uri
);
5286 /* write back new favorites file */
5287 if ((f
= fopen(file
, "w")) == NULL
) {
5288 show_oops(t
, "%s: can't open favorites: %s",
5289 __func__
, strerror(errno
));
5293 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5306 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5309 case XT_XTP_FL_LIST
:
5310 /* nothing, just the below call to xtp_page_fl() */
5312 case XT_XTP_FL_REMOVE
:
5313 remove_favorite(t
, arg
);
5316 show_oops(t
, "%s: invalid favorites command", __func__
);
5320 xtp_page_fl(t
, NULL
);
5324 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5327 case XT_XTP_CL_LIST
:
5328 /* nothing, just xtp_page_cl() */
5330 case XT_XTP_CL_REMOVE
:
5334 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5338 xtp_page_cl(t
, NULL
);
5341 /* link an XTP class to it's session key and handler function */
5342 struct xtp_despatch
{
5345 void (*handle_func
)(struct tab
*, uint8_t, int);
5348 struct xtp_despatch xtp_despatches
[] = {
5349 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5350 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5351 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5352 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5353 { XT_XTP_INVALID
, NULL
, NULL
}
5357 * is the url xtp protocol? (xxxt://)
5358 * if so, parse and despatch correct bahvior
5361 parse_xtp_url(struct tab
*t
, const char *url
)
5363 char *dup
= NULL
, *p
, *last
;
5364 uint8_t n_tokens
= 0;
5365 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5366 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5371 * tokens array meaning:
5373 * tokens[1] = session key
5374 * tokens[2] = action
5375 * tokens[3] = optional argument
5378 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5380 /*xtp tab meaning is normal unless proven special */
5381 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5383 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5386 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5388 /* split out the url */
5389 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5390 (p
= strtok_r(NULL
, "/", &last
))) {
5392 tokens
[n_tokens
++] = p
;
5395 /* should be atleast three fields 'class/seskey/command/arg' */
5399 dsp
= xtp_despatches
;
5400 req_class
= atoi(tokens
[0]);
5401 while (dsp
->xtp_class
) {
5402 if (dsp
->xtp_class
== req_class
) {
5409 /* did we find one atall? */
5410 if (dsp_match
== NULL
) {
5411 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
5415 /* check session key and call despatch function */
5416 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
5417 ret
= TRUE
; /* all is well, this was a valid xtp request */
5418 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
5431 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5433 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
5435 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
5438 show_oops_s("activate_uri_entry_cb invalid parameters");
5443 show_oops(t
, "activate_uri_entry_cb no uri");
5447 uri
+= strspn(uri
, "\t ");
5449 /* if xxxt:// treat specially */
5450 if (parse_xtp_url(t
, uri
))
5453 /* otherwise continue to load page normally */
5454 load_uri(t
, (gchar
*)uri
);
5459 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5461 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
5462 char *newuri
= NULL
;
5465 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
5468 show_oops_s("activate_search_entry_cb invalid parameters");
5472 if (search_string
== NULL
) {
5473 show_oops(t
, "no search_string");
5477 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
5478 newuri
= g_strdup_printf(search_string
, enc_search
);
5481 webkit_web_view_load_uri(t
->wv
, newuri
);
5489 check_and_set_js(const gchar
*uri
, struct tab
*t
)
5491 struct domain
*d
= NULL
;
5494 if (uri
== NULL
|| t
== NULL
)
5497 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5502 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
5503 es
? "enable" : "disable", uri
);
5505 g_object_set(G_OBJECT(t
->settings
),
5506 "enable-scripts", es
, (char *)NULL
);
5507 g_object_set(G_OBJECT(t
->settings
),
5508 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
5509 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5511 button_set_stockid(t
->js_toggle
,
5512 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
5516 show_ca_status(struct tab
*t
, const char *uri
)
5518 WebKitWebFrame
*frame
;
5519 WebKitWebDataSource
*source
;
5520 WebKitNetworkRequest
*request
;
5521 SoupMessage
*message
;
5523 gchar
*col_str
= XT_COLOR_WHITE
;
5526 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
5527 ssl_strict_certs
, ssl_ca_file
, uri
);
5531 if (ssl_ca_file
== NULL
) {
5532 if (g_str_has_prefix(uri
, "http://"))
5534 if (g_str_has_prefix(uri
, "https://")) {
5535 col_str
= XT_COLOR_RED
;
5540 if (g_str_has_prefix(uri
, "http://") ||
5541 !g_str_has_prefix(uri
, "https://"))
5544 frame
= webkit_web_view_get_main_frame(t
->wv
);
5545 source
= webkit_web_frame_get_data_source(frame
);
5546 request
= webkit_web_data_source_get_request(source
);
5547 message
= webkit_network_request_get_message(request
);
5549 if (message
&& (soup_message_get_flags(message
) &
5550 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
5551 col_str
= XT_COLOR_GREEN
;
5554 r
= load_compare_cert(t
, NULL
);
5556 col_str
= XT_COLOR_BLUE
;
5558 col_str
= XT_COLOR_YELLOW
;
5560 col_str
= XT_COLOR_RED
;
5565 gdk_color_parse(col_str
, &color
);
5566 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
5568 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
5569 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5571 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5572 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5575 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5577 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5578 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5585 free_favicon(struct tab
*t
)
5587 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p\n",
5588 __func__
, t
->icon_download
, t
->icon_request
);
5590 if (t
->icon_request
)
5591 g_object_unref(t
->icon_request
);
5592 if (t
->icon_dest_uri
)
5593 g_free(t
->icon_dest_uri
);
5595 t
->icon_request
= NULL
;
5596 t
->icon_dest_uri
= NULL
;
5600 xt_icon_from_name(struct tab
*t
, gchar
*name
)
5602 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5603 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5605 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5606 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5608 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5609 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5613 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pb
)
5615 GdkPixbuf
*pb_scaled
;
5617 if (gdk_pixbuf_get_width(pb
) > 16 || gdk_pixbuf_get_height(pb
) > 16)
5618 pb_scaled
= gdk_pixbuf_scale_simple(pb
, 16, 16, GDK_INTERP_BILINEAR
);
5622 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
5623 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
5625 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
5626 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
5628 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5629 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5631 if (pb_scaled
!= pb
)
5632 g_object_unref(pb_scaled
);
5636 xt_icon_from_file(struct tab
*t
, char *file
)
5640 if (g_str_has_prefix(file
, "file://"))
5641 file
+= strlen("file://");
5643 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
5645 xt_icon_from_pixbuf(t
, pb
);
5648 xt_icon_from_name(t
, "text-html");
5652 is_valid_icon(char *file
)
5655 const char *mime_type
;
5659 gf
= g_file_new_for_path(file
);
5660 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5662 mime_type
= g_file_info_get_content_type(fi
);
5663 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
5664 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
5665 g_strcmp0(mime_type
, "image/png") == 0 ||
5666 g_strcmp0(mime_type
, "image/gif") == 0 ||
5667 g_strcmp0(mime_type
, "application/octet-stream") == 0;
5675 set_favicon_from_file(struct tab
*t
, char *file
)
5679 if (t
== NULL
|| file
== NULL
)
5682 if (g_str_has_prefix(file
, "file://"))
5683 file
+= strlen("file://");
5684 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
5686 if (!stat(file
, &sb
)) {
5687 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
5688 /* corrupt icon so trash it */
5689 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5692 /* no need to set icon to default here */
5696 xt_icon_from_file(t
, file
);
5700 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5703 WebKitDownloadStatus status
= webkit_download_get_status(download
);
5704 struct tab
*tt
= NULL
, *t
= NULL
;
5707 * find the webview instead of passing in the tab as it could have been
5708 * deleted from underneath us.
5710 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5719 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
5720 __func__
, t
->tab_id
, status
);
5723 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
5725 t
->icon_download
= NULL
;
5728 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
5731 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
5734 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
5736 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
5737 __func__
, t
->tab_id
);
5738 t
->icon_download
= NULL
;
5741 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
5744 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
5745 __func__
, t
->icon_dest_uri
);
5746 set_favicon_from_file(t
, t
->icon_dest_uri
);
5747 /* these will be freed post callback */
5748 t
->icon_request
= NULL
;
5749 t
->icon_download
= NULL
;
5757 abort_favicon_download(struct tab
*t
)
5759 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
5761 if (!WEBKIT_CHECK_VERSION(1, 4, 0)) {
5762 if (t
->icon_download
) {
5763 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
5764 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5765 webkit_download_cancel(t
->icon_download
);
5766 t
->icon_download
= NULL
;
5771 xt_icon_from_name(t
, "text-html");
5775 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
5778 gchar
*name_hash
, file
[PATH_MAX
];
5781 DNPRINTF(XT_D_DOWNLOAD
, "%s %s\n", __func__
, uri
);
5783 if (uri
== NULL
|| t
== NULL
)
5786 if (WEBKIT_CHECK_VERSION(1, 4, 0)) {
5787 /* take icon from WebKitIconDatabase */
5788 pb
= webkit_web_view_get_icon_pixbuf(wv
);
5790 xt_icon_from_pixbuf(t
, pb
);
5793 xt_icon_from_name(t
, "text-html");
5795 } else if (WEBKIT_CHECK_VERSION(1, 1, 18)) {
5796 /* download icon to cache dir */
5797 if (t
->icon_request
) {
5798 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
5802 /* check to see if we got the icon in cache */
5803 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
5804 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
5807 if (!stat(file
, &sb
)) {
5808 if (sb
.st_size
> 0) {
5809 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
5811 set_favicon_from_file(t
, file
);
5815 /* corrupt icon so trash it */
5816 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5821 /* create download for icon */
5822 t
->icon_request
= webkit_network_request_new(uri
);
5823 if (t
->icon_request
== NULL
) {
5824 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
5829 t
->icon_download
= webkit_download_new(t
->icon_request
);
5830 if (t
->icon_download
== NULL
)
5833 /* we have to free icon_dest_uri later */
5834 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
5835 webkit_download_set_destination_uri(t
->icon_download
,
5838 if (webkit_download_get_status(t
->icon_download
) ==
5839 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5840 g_object_unref(t
->icon_request
);
5841 g_free(t
->icon_dest_uri
);
5842 t
->icon_request
= NULL
;
5843 t
->icon_dest_uri
= NULL
;
5847 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
5848 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5850 webkit_download_start(t
->icon_download
);
5855 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5857 const gchar
*set
= NULL
, *uri
= NULL
, *title
= NULL
;
5858 struct history
*h
, find
;
5859 const gchar
*s_loading
;
5862 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
5863 webkit_web_view_get_load_status(wview
), get_uri(wview
) ? get_uri(wview
) : "NOTHING");
5866 show_oops_s("notify_load_status_cb invalid parameters");
5870 switch (webkit_web_view_get_load_status(wview
)) {
5871 case WEBKIT_LOAD_PROVISIONAL
:
5873 abort_favicon_download(t
);
5874 #if GTK_CHECK_VERSION(2, 20, 0)
5875 gtk_widget_show(t
->spinner
);
5876 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
5878 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
5880 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
5882 /* take focus if we are visible */
5888 case WEBKIT_LOAD_COMMITTED
:
5890 if ((uri
= get_uri(wview
)) != NULL
) {
5891 if (strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
5892 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
5898 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
5901 /* check if js white listing is enabled */
5902 if (enable_js_whitelist
) {
5903 uri
= get_uri(wview
);
5904 check_and_set_js(uri
, t
);
5910 show_ca_status(t
, uri
);
5912 /* we know enough to autosave the session */
5913 if (session_autosave
) {
5919 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
5923 case WEBKIT_LOAD_FINISHED
:
5925 uri
= get_uri(wview
);
5929 if (!strncmp(uri
, "http://", strlen("http://")) ||
5930 !strncmp(uri
, "https://", strlen("https://")) ||
5931 !strncmp(uri
, "file://", strlen("file://"))) {
5933 h
= RB_FIND(history_list
, &hl
, &find
);
5935 title
= webkit_web_view_get_title(wview
);
5936 set
= title
? title
: uri
;
5937 h
= g_malloc(sizeof *h
);
5938 h
->uri
= g_strdup(uri
);
5939 h
->title
= g_strdup(set
);
5940 RB_INSERT(history_list
, &hl
, h
);
5941 completion_add_uri(h
->uri
);
5942 update_history_tabs(NULL
);
5946 set_status(t
, (char *)uri
, XT_STATUS_URI
);
5947 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5948 case WEBKIT_LOAD_FAILED
:
5951 #if GTK_CHECK_VERSION(2, 20, 0)
5952 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5953 gtk_widget_hide(t
->spinner
);
5955 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
5956 if (s_loading
&& !strcmp(s_loading
, "Loading"))
5957 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5959 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
5964 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
5966 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
5967 webkit_web_view_can_go_back(wview
));
5969 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
5970 webkit_web_view_can_go_forward(wview
));
5974 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5976 const gchar
*set
= NULL
, *title
= NULL
;
5978 title
= webkit_web_view_get_title(wview
);
5979 set
= title
? title
: get_uri(wview
);
5981 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
5982 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
5984 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5985 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
5990 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5992 run_script(t
, JS_HINTING
);
5996 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
5998 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
5999 progress
== 100 ? 0 : (double)progress
/ 100);
6000 if (show_url
== 0) {
6001 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
6002 progress
== 100 ? 0 : (double)progress
/ 100);
6007 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
6008 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
6009 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
6012 WebKitWebNavigationReason reason
;
6013 struct domain
*d
= NULL
;
6016 show_oops_s("webview_npd_cb invalid parameters");
6020 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
6022 webkit_network_request_get_uri(request
));
6024 uri
= (char *)webkit_network_request_get_uri(request
);
6026 /* if this is an xtp url, we don't load anything else */
6027 if (parse_xtp_url(t
, uri
))
6030 if (t
->ctrl_click
) {
6032 create_new_tab(uri
, NULL
, ctrl_click_focus
, -1);
6033 webkit_web_policy_decision_ignore(pd
);
6034 return (TRUE
); /* we made the decission */
6038 * This is a little hairy but it comes down to this:
6039 * when we run in whitelist mode we have to assist the browser in
6040 * opening the URL that it would have opened in a new tab.
6042 reason
= webkit_web_navigation_action_get_reason(na
);
6043 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6044 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6045 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6048 webkit_web_policy_decision_use(pd
);
6049 return (TRUE
); /* we made the decission */
6056 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6059 struct domain
*d
= NULL
;
6061 WebKitWebView
*webview
= NULL
;
6063 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6064 webkit_web_view_get_uri(wv
));
6067 /* open in current tab */
6069 } else if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6070 uri
= webkit_web_view_get_uri(wv
);
6071 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6074 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6076 } else if (enable_scripts
== 1) {
6077 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6085 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6088 struct domain
*d
= NULL
;
6090 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6092 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6093 uri
= webkit_web_view_get_uri(wv
);
6094 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6098 } else if (enable_scripts
== 1)
6105 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6107 /* we can not eat the event without throwing gtk off so defer it */
6109 /* catch middle click */
6110 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6115 /* catch ctrl click */
6116 if (e
->type
== GDK_BUTTON_RELEASE
&&
6117 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6122 return (XT_CB_PASSTHROUGH
);
6126 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6128 struct mime_type
*m
;
6130 m
= find_mime_type(mime_type
);
6138 show_oops(t
, "can't fork mime handler");
6147 execlp(m
->mt_action
, m
->mt_action
,
6148 webkit_network_request_get_uri(request
), (void *)NULL
);
6157 get_mime_type(char *file
)
6159 const char *mime_type
;
6163 if (g_str_has_prefix(file
, "file://"))
6164 file
+= strlen("file://");
6166 gf
= g_file_new_for_path(file
);
6167 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6169 mime_type
= g_file_info_get_content_type(fi
);
6177 run_download_mimehandler(char *mime_type
, char *file
)
6179 struct mime_type
*m
;
6181 m
= find_mime_type(mime_type
);
6187 show_oops_s("can't fork download mime handler");
6196 if (g_str_has_prefix(file
, "file://"))
6197 file
+= strlen("file://");
6198 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6207 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6210 WebKitDownloadStatus status
;
6211 const gchar
*file
= NULL
, *mime
= NULL
;
6213 if (download
== NULL
)
6215 status
= webkit_download_get_status(download
);
6216 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6219 file
= webkit_download_get_destination_uri(download
);
6222 mime
= get_mime_type((char *)file
);
6226 run_download_mimehandler((char *)mime
, (char *)file
);
6230 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6231 WebKitNetworkRequest
*request
, char *mime_type
,
6232 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6235 show_oops_s("webview_mimetype_cb invalid parameters");
6239 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6240 t
->tab_id
, mime_type
);
6242 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6243 webkit_web_policy_decision_ignore(decision
);
6248 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6249 webkit_web_policy_decision_download(decision
);
6257 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6261 const gchar
*suggested_name
;
6262 gchar
*filename
= NULL
;
6264 struct download
*download_entry
;
6267 if (wk_download
== NULL
|| t
== NULL
) {
6268 show_oops_s("%s invalid parameters", __func__
);
6272 suggested_name
= webkit_download_get_suggested_filename(wk_download
);
6273 if (suggested_name
== NULL
)
6274 return (FALSE
); /* abort download */
6285 filename
= g_strdup_printf("%d%s", i
, suggested_name
);
6287 uri
= g_strdup_printf("file://%s/%s", download_dir
, i
?
6288 filename
: suggested_name
);
6290 } while (!stat(uri
+ strlen("file://"), &sb
));
6292 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6293 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6295 webkit_download_set_destination_uri(wk_download
, uri
);
6297 if (webkit_download_get_status(wk_download
) ==
6298 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6299 show_oops(t
, "%s: download failed to start", __func__
);
6301 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6303 /* connect "download first" mime handler */
6304 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6305 G_CALLBACK(download_status_changed_cb
), NULL
);
6307 download_entry
= g_malloc(sizeof(struct download
));
6308 download_entry
->download
= wk_download
;
6309 download_entry
->tab
= t
;
6310 download_entry
->id
= next_download_id
++;
6311 RB_INSERT(download_list
, &downloads
, download_entry
);
6312 /* get from history */
6313 g_object_ref(wk_download
);
6314 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6315 show_oops(t
, "Download of '%s' started...",
6316 basename(webkit_download_get_destination_uri(wk_download
)));
6325 /* sync other download manager tabs */
6326 update_download_tabs(NULL
);
6329 * NOTE: never redirect/render the current tab before this
6330 * function returns. This will cause the download to never start.
6332 return (ret
); /* start download */
6336 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6338 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6341 show_oops_s("webview_hover_cb");
6346 set_status(t
, uri
, XT_STATUS_LINK
);
6349 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6354 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6356 struct key_binding
*k
;
6358 TAILQ_FOREACH(k
, &kbl
, entry
)
6359 if (e
->keyval
== k
->key
&& (entry
? k
->use_in_entry
: 1)) {
6361 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6362 return (cmd_execute(t
, k
->cmd
));
6363 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6364 return (cmd_execute(t
, k
->cmd
));
6368 return (XT_CB_PASSTHROUGH
);
6372 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6374 char s
[2], buf
[128];
6375 const char *errstr
= NULL
;
6378 /* don't use w directly; use t->whatever instead */
6381 show_oops_s("wv_keypress_after_cb");
6382 return (XT_CB_PASSTHROUGH
);
6385 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6386 e
->keyval
, e
->state
, t
);
6390 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6392 return (XT_CB_HANDLED
);
6396 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6397 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6399 /* we have a string */
6401 /* we have a number */
6402 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6410 /* XXX unfuck this */
6411 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6412 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6413 /* last input was numerical */
6415 l
= strlen(t
->hint_num
);
6422 t
->hint_num
[l
] = '\0';
6426 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6427 /* last input was alphanumerical */
6429 l
= strlen(t
->hint_buf
);
6436 t
->hint_buf
[l
] = '\0';
6446 /* numerical input */
6447 if (CLEAN(e
->state
) == 0 &&
6448 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6449 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6450 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6451 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6454 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6456 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6459 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6461 t
->hint_mode
= XT_HINT_NUMERICAL
;
6465 /* empty the counter buffer */
6466 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6467 return (XT_CB_HANDLED
);
6470 /* alphanumerical input */
6472 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6473 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6474 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6475 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6476 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6477 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6478 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6481 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6484 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6486 t
->hint_mode
= XT_HINT_ALPHANUM
;
6489 /* empty the counter buffer */
6490 bzero(t
->hint_num
, sizeof t
->hint_num
);
6491 return (XT_CB_HANDLED
);
6494 return (XT_CB_HANDLED
);
6497 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6498 if (CLEAN(e
->state
) == 0 && isdigit(s
[0])) {
6499 cmd_prefix
= 10 * cmd_prefix
+ atoi(s
);
6503 return (handle_keypress(t
, e
, 0));
6507 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6511 return (XT_CB_PASSTHROUGH
);
6515 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6517 const gchar
*c
= gtk_entry_get_text(w
);
6521 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6522 e
->keyval
, e
->state
, t
);
6525 show_oops_s("cmd_keyrelease_cb invalid parameters");
6526 return (XT_CB_PASSTHROUGH
);
6529 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6530 e
->keyval
, e
->state
, t
);
6534 if (strlen(c
) == 1) {
6535 webkit_web_view_unmark_text_matches(t
->wv
);
6541 else if (c
[0] == '?')
6547 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6549 /* not found, mark red */
6550 gdk_color_parse(XT_COLOR_RED
, &color
);
6551 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6552 /* unmark and remove selection */
6553 webkit_web_view_unmark_text_matches(t
->wv
);
6554 /* my kingdom for a way to unselect text in webview */
6556 /* found, highlight all */
6557 webkit_web_view_unmark_text_matches(t
->wv
);
6558 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6559 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6560 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6561 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6564 return (XT_CB_PASSTHROUGH
);
6568 match_uri(const gchar
*uri
, const gchar
*key
) {
6571 gboolean match
= FALSE
;
6575 if (!strncmp(key
, uri
, len
))
6578 voffset
= strstr(uri
, "/") + 2;
6579 if (!strncmp(key
, voffset
, len
))
6581 else if (g_str_has_prefix(voffset
, "www.")) {
6582 voffset
= voffset
+ strlen("www.");
6583 if (!strncmp(key
, voffset
, len
))
6592 cmd_getlist(int id
, char *key
)
6597 if (id
>= 0 && (cmds
[id
].type
& XT_URLARG
)) {
6598 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6599 if (match_uri(h
->uri
, key
)) {
6600 cmd_status
.list
[c
] = (char *)h
->uri
;
6609 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6611 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6612 if(cmds
[i
].level
< dep
)
6614 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6615 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6623 cmd_getnext(int dir
)
6625 cmd_status
.index
+= dir
;
6627 if (cmd_status
.index
< 0)
6628 cmd_status
.index
= cmd_status
.len
- 1;
6629 else if (cmd_status
.index
>= cmd_status
.len
)
6630 cmd_status
.index
= 0;
6632 return cmd_status
.list
[cmd_status
.index
];
6636 cmd_tokenize(char *s
, char *tokens
[])
6640 size_t len
= strlen(s
);
6641 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6643 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6653 cmd_complete(struct tab
*t
, char *str
, int dir
)
6655 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6656 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1, matchcount
= 0;
6657 char *tok
, *match
, *s
= g_strdup(str
);
6659 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6662 DNPRINTF(XT_D_CMD
, "%s: complete %s\n", __func__
, str
);
6665 for (i
= 0; isdigit(s
[i
]); i
++)
6668 for (; isspace(s
[i
]); i
++)
6673 levels
= cmd_tokenize(s
, tokens
);
6675 for (i
= 0; i
< levels
- 1; i
++) {
6678 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6679 if (cmds
[j
].level
< dep
)
6681 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, strlen(tok
))) {
6684 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
6691 if (matchcount
== 1) {
6692 strlcat(res
, tok
, sizeof res
);
6693 strlcat(res
, " ", sizeof res
);
6703 if (cmd_status
.index
== -1)
6704 cmd_getlist(parent
, tokens
[i
]);
6706 if (cmd_status
.len
> 0) {
6707 match
= cmd_getnext(dir
);
6708 strlcat(res
, match
, sizeof res
);
6709 gtk_entry_set_text(w
, res
);
6710 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6717 cmd_execute(struct tab
*t
, char *str
)
6719 struct cmd
*cmd
= NULL
;
6720 char *tok
, *last
, *s
= g_strdup(str
), *sc
, prefixstr
[4];
6721 int j
, len
, c
= 0, dep
= 0, matchcount
= 0, prefix
= -1;
6722 struct karg arg
= {0, NULL
, -1};
6723 int rv
= XT_CB_PASSTHROUGH
;
6728 for (j
= 0; j
<3 && isdigit(s
[j
]); j
++)
6734 while (isspace(s
[0]))
6737 if (strlen(s
) > 0 && strlen(prefixstr
) > 0)
6738 prefix
= atoi(prefixstr
);
6742 for (tok
= strtok_r(s
, " ", &last
); tok
;
6743 tok
= strtok_r(NULL
, " ", &last
)) {
6745 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6746 if (cmds
[j
].level
< dep
)
6748 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1: strlen(tok
);
6749 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, len
)) {
6753 if (len
== strlen(cmds
[j
].cmd
)) {
6759 if (matchcount
== 1) {
6764 show_oops(t
, "Invalid command: %s", str
);
6773 else if (cmd_prefix
> 0)
6776 if (j
> 0 && !(cmd
->type
& XT_PREFIX
) && arg
.p
> -1) {
6777 show_oops(t
, "No prefix allowed: %s", str
);
6781 arg
.s
= last
? g_strdup(last
) : g_strdup("");
6782 if (cmd
->type
& XT_INTARG
&& last
&& strlen(last
) > 0) {
6783 arg
.p
= atoi(arg
.s
);
6786 show_oops(t
, "Zero count");
6788 show_oops(t
, "Trailing characters");
6793 DNPRINTF(XT_D_CMD
, "%s: prefix %d arg %s\n", __func__
, arg
.p
, arg
.s
);
6809 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6812 show_oops_s("entry_key_cb invalid parameters");
6813 return (XT_CB_PASSTHROUGH
);
6816 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6817 e
->keyval
, e
->state
, t
);
6821 if (e
->keyval
== GDK_Escape
) {
6822 /* don't use focus_webview(t) because we want to type :cmds */
6823 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6826 return (handle_keypress(t
, e
, 1));
6830 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6832 int rv
= XT_CB_HANDLED
;
6833 const gchar
*c
= gtk_entry_get_text(w
);
6836 show_oops_s("cmd_keypress_cb parameters");
6837 return (XT_CB_PASSTHROUGH
);
6840 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6841 e
->keyval
, e
->state
, t
);
6845 e
->keyval
= GDK_Escape
;
6846 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6847 e
->keyval
= GDK_Escape
;
6849 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
6850 cmd_status
.index
= -1;
6852 switch (e
->keyval
) {
6855 cmd_complete(t
, (char *)&c
[1], 1);
6857 case GDK_ISO_Left_Tab
:
6859 cmd_complete(t
, (char *)&c
[1], -1);
6863 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
6871 if (c
[0] == '/' || c
[0] == '?')
6872 webkit_web_view_unmark_text_matches(t
->wv
);
6876 rv
= XT_CB_PASSTHROUGH
;
6882 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6885 show_oops_s("cmd_focusout_cb invalid parameters");
6886 return (XT_CB_PASSTHROUGH
);
6888 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
6893 if (show_url
== 0 || t
->focus_wv
)
6896 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6898 return (XT_CB_PASSTHROUGH
);
6902 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6905 const gchar
*c
= gtk_entry_get_text(entry
);
6908 show_oops_s("cmd_activate_cb invalid parameters");
6912 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6919 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6925 if (c
[0] == '/' || c
[0] == '?') {
6926 if (t
->search_text
) {
6927 g_free(t
->search_text
);
6928 t
->search_text
= NULL
;
6931 t
->search_text
= g_strdup(s
);
6933 g_free(global_search
);
6934 global_search
= g_strdup(s
);
6935 t
->search_forward
= c
[0] == '/';
6947 backward_cb(GtkWidget
*w
, struct tab
*t
)
6952 show_oops_s("backward_cb invalid parameters");
6956 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6963 forward_cb(GtkWidget
*w
, struct tab
*t
)
6968 show_oops_s("forward_cb invalid parameters");
6972 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6974 a
.i
= XT_NAV_FORWARD
;
6979 home_cb(GtkWidget
*w
, struct tab
*t
)
6982 show_oops_s("home_cb invalid parameters");
6986 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6992 stop_cb(GtkWidget
*w
, struct tab
*t
)
6994 WebKitWebFrame
*frame
;
6997 show_oops_s("stop_cb invalid parameters");
7001 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
7003 frame
= webkit_web_view_get_main_frame(t
->wv
);
7004 if (frame
== NULL
) {
7005 show_oops(t
, "stop_cb: no frame");
7009 webkit_web_frame_stop_loading(frame
);
7010 abort_favicon_download(t
);
7014 setup_webkit(struct tab
*t
)
7016 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
7017 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
7018 FALSE
, (char *)NULL
);
7020 warnx("webkit does not have \"enable-dns-prefetching\" property");
7021 g_object_set(G_OBJECT(t
->settings
),
7022 "user-agent", t
->user_agent
, (char *)NULL
);
7023 g_object_set(G_OBJECT(t
->settings
),
7024 "enable-scripts", enable_scripts
, (char *)NULL
);
7025 g_object_set(G_OBJECT(t
->settings
),
7026 "enable-plugins", enable_plugins
, (char *)NULL
);
7027 g_object_set(G_OBJECT(t
->settings
),
7028 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
7029 g_object_set(G_OBJECT(t
->settings
),
7030 "enable-html5-database", FALSE
, (char *)NULL
);
7031 g_object_set(G_OBJECT(t
->settings
),
7032 "enable-html5-local-storage", enable_localstorage
, (char *)NULL
);
7033 g_object_set(G_OBJECT(t
->settings
),
7034 "enable_spell_checking", enable_spell_checking
, (char *)NULL
);
7035 g_object_set(G_OBJECT(t
->settings
),
7036 "spell_checking_languages", spell_check_languages
, (char *)NULL
);
7037 g_object_set(G_OBJECT(t
->wv
),
7038 "full-content-zoom", TRUE
, (char *)NULL
);
7039 adjustfont_webkit(t
, XT_FONT_SET
);
7041 webkit_web_view_set_settings(t
->wv
, t
->settings
);
7045 create_browser(struct tab
*t
)
7051 show_oops_s("create_browser invalid parameters");
7055 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
7056 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
7057 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
7058 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
7060 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
7061 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
7062 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
7064 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
7065 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
7068 t
->settings
= webkit_web_settings_new();
7070 if (user_agent
== NULL
) {
7071 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
7073 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
7076 t
->user_agent
= g_strdup(user_agent
);
7078 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
7090 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
7091 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
7092 gtk_widget_set_name(w
, "xxxterm");
7093 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
7094 g_signal_connect(G_OBJECT(w
), "delete_event",
7095 G_CALLBACK (gtk_main_quit
), NULL
);
7101 create_kiosk_toolbar(struct tab
*t
)
7103 GtkWidget
*toolbar
= NULL
, *b
;
7105 b
= gtk_hbox_new(FALSE
, 0);
7107 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7109 /* backward button */
7110 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7111 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7112 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7113 G_CALLBACK(backward_cb
), t
);
7114 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7116 /* forward button */
7117 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7118 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7119 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7120 G_CALLBACK(forward_cb
), t
);
7121 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7124 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7125 gtk_widget_set_sensitive(t
->gohome
, true);
7126 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7127 G_CALLBACK(home_cb
), t
);
7128 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7130 /* create widgets but don't use them */
7131 t
->uri_entry
= gtk_entry_new();
7132 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7133 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7134 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7140 create_toolbar(struct tab
*t
)
7142 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7144 b
= gtk_hbox_new(FALSE
, 0);
7146 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7149 /* backward button */
7150 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7151 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7152 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7153 G_CALLBACK(backward_cb
), t
);
7154 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7156 /* forward button */
7157 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7158 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7159 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7160 G_CALLBACK(forward_cb
), t
);
7161 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7165 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7166 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7167 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7168 G_CALLBACK(stop_cb
), t
);
7169 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7173 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7174 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7175 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7176 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7177 G_CALLBACK(js_toggle_cb
), t
);
7178 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7181 t
->uri_entry
= gtk_entry_new();
7182 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7183 G_CALLBACK(activate_uri_entry_cb
), t
);
7184 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7185 G_CALLBACK(entry_key_cb
), t
);
7187 eb1
= gtk_hbox_new(FALSE
, 0);
7188 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7189 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7190 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7193 if (fancy_bar
&& search_string
) {
7195 t
->search_entry
= gtk_entry_new();
7196 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7197 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7198 G_CALLBACK(activate_search_entry_cb
), t
);
7199 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7200 G_CALLBACK(entry_key_cb
), t
);
7201 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7202 eb2
= gtk_hbox_new(FALSE
, 0);
7203 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7204 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7206 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7216 TAILQ_FOREACH(t
, &tabs
, entry
)
7217 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7221 undo_close_tab_save(struct tab
*t
)
7225 struct undo
*u1
, *u2
;
7227 WebKitWebHistoryItem
*item
;
7229 if ((uri
= get_uri(t
->wv
)) == NULL
)
7232 u1
= g_malloc0(sizeof(struct undo
));
7233 u1
->uri
= g_strdup(uri
);
7235 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7237 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7238 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7241 /* forward history */
7242 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7246 u1
->history
= g_list_prepend(u1
->history
,
7247 webkit_web_history_item_copy(item
));
7248 items
= g_list_next(items
);
7253 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7254 u1
->history
= g_list_prepend(u1
->history
,
7255 webkit_web_history_item_copy(item
));
7259 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7263 u1
->history
= g_list_prepend(u1
->history
,
7264 webkit_web_history_item_copy(item
));
7265 items
= g_list_next(items
);
7268 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7270 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7271 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7272 TAILQ_REMOVE(&undos
, u2
, entry
);
7274 g_list_free(u2
->history
);
7283 delete_tab(struct tab
*t
)
7287 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7292 TAILQ_REMOVE(&tabs
, t
, entry
);
7294 /* halt all webkit activity */
7295 abort_favicon_download(t
);
7296 webkit_web_view_stop_loading(t
->wv
);
7297 undo_close_tab_save(t
);
7299 if (browser_mode
== XT_BM_KIOSK
) {
7300 gtk_widget_destroy(t
->uri_entry
);
7301 gtk_widget_destroy(t
->stop
);
7302 gtk_widget_destroy(t
->js_toggle
);
7305 gtk_widget_destroy(t
->vbox
);
7306 g_free(t
->user_agent
);
7307 g_free(t
->stylesheet
);
7310 if (TAILQ_EMPTY(&tabs
)) {
7311 if (browser_mode
== XT_BM_KIOSK
)
7312 create_new_tab(home
, NULL
, 1, -1);
7314 create_new_tab(NULL
, NULL
, 1, -1);
7317 /* recreate session */
7318 if (session_autosave
) {
7325 adjustfont_webkit(struct tab
*t
, int adjust
)
7330 show_oops_s("adjustfont_webkit invalid parameters");
7334 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7335 if (adjust
== XT_FONT_SET
) {
7336 t
->font_size
= default_font_size
;
7337 zoom
= default_zoom_level
;
7338 t
->font_size
+= adjust
;
7339 g_object_set(G_OBJECT(t
->settings
), "default-font-size",
7340 t
->font_size
, (char *)NULL
);
7341 g_object_get(G_OBJECT(t
->settings
), "default-font-size",
7342 &t
->font_size
, (char *)NULL
);
7344 t
->font_size
+= adjust
;
7345 zoom
+= adjust
/25.0;
7350 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7351 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7355 append_tab(struct tab
*t
)
7360 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7361 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7365 create_new_tab(char *title
, struct undo
*u
, int focus
, int position
)
7370 WebKitWebHistoryItem
*item
;
7374 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7376 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7377 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7381 t
= g_malloc0(sizeof *t
);
7383 if (title
== NULL
) {
7384 title
= "(untitled)";
7388 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7390 /* label + button for tab */
7391 b
= gtk_hbox_new(FALSE
, 0);
7394 #if GTK_CHECK_VERSION(2, 20, 0)
7395 t
->spinner
= gtk_spinner_new ();
7397 t
->label
= gtk_label_new(title
);
7398 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7399 gtk_widget_set_size_request(t
->label
, 100, 0);
7400 gtk_label_set_max_width_chars(GTK_LABEL(t
->label
), 20);
7401 gtk_label_set_ellipsize(GTK_LABEL(t
->label
), PANGO_ELLIPSIZE_END
);
7402 gtk_widget_set_size_request(b
, 130, 0);
7404 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7405 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7406 #if GTK_CHECK_VERSION(2, 20, 0)
7407 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7411 if (browser_mode
== XT_BM_KIOSK
)
7412 t
->toolbar
= create_kiosk_toolbar(t
);
7414 t
->toolbar
= create_toolbar(t
);
7416 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7419 t
->browser_win
= create_browser(t
);
7420 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7422 /* oops message for user feedback */
7423 t
->oops
= gtk_entry_new();
7424 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7425 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7426 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7427 gdk_color_parse(XT_COLOR_RED
, &color
);
7428 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7429 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7432 t
->cmd
= gtk_entry_new();
7433 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7434 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7435 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7436 gtk_widget_modify_font(GTK_WIDGET(t
->cmd
), cmd_font
);
7439 t
->statusbar
= gtk_entry_new();
7440 gtk_entry_set_inner_border(GTK_ENTRY(t
->statusbar
), NULL
);
7441 gtk_entry_set_has_frame(GTK_ENTRY(t
->statusbar
), FALSE
);
7442 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7443 gdk_color_parse(XT_COLOR_BLACK
, &color
);
7444 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7445 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7446 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7447 gtk_widget_modify_font(GTK_WIDGET(t
->statusbar
), statusbar_font
);
7448 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7450 /* xtp meaning is normal by default */
7451 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
7453 /* set empty favicon */
7454 xt_icon_from_name(t
, "text-html");
7456 /* and show it all */
7457 gtk_widget_show_all(b
);
7458 gtk_widget_show_all(t
->vbox
);
7460 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7463 id
= position
>= 0 ? position
: gtk_notebook_get_current_page(notebook
) + 1;
7464 if (id
> gtk_notebook_get_n_pages(notebook
))
7467 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7468 gtk_notebook_insert_page(notebook
, t
->vbox
, b
, id
);
7473 #if GTK_CHECK_VERSION(2, 20, 0)
7474 /* turn spinner off if we are a new tab without uri */
7476 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7477 gtk_widget_hide(t
->spinner
);
7480 /* make notebook tabs reorderable */
7481 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7483 g_object_connect(G_OBJECT(t
->cmd
),
7484 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7485 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7486 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7487 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7490 /* reuse wv_button_cb to hide oops */
7491 g_object_connect(G_OBJECT(t
->oops
),
7492 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7495 g_object_connect(G_OBJECT(t
->wv
),
7496 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7497 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7498 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7499 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7500 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7501 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7502 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7503 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7504 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7505 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7506 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
7507 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
7508 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7509 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7511 g_signal_connect(t
->wv
,
7512 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7513 g_signal_connect(t
->wv
,
7514 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7516 /* hijack the unused keys as if we were the browser */
7517 g_object_connect(G_OBJECT(t
->toolbar
),
7518 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7521 g_signal_connect(G_OBJECT(bb
), "button_press_event",
7522 G_CALLBACK(tab_close_cb
), t
);
7527 url_set_visibility();
7528 statusbar_set_visibility();
7531 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7532 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
7537 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
7541 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7546 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7547 /* restore the tab's history */
7548 if (u
&& u
->history
) {
7552 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
7553 items
= g_list_next(items
);
7556 item
= g_list_nth_data(u
->history
, u
->back
);
7558 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
7561 g_list_free(u
->history
);
7563 webkit_web_back_forward_list_clear(t
->bfl
);
7569 notebook_switchpage_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
7575 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
7577 if (gtk_notebook_get_current_page(notebook
) == -1)
7580 TAILQ_FOREACH(t
, &tabs
, entry
) {
7581 if (t
->tab_id
== pn
) {
7582 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
7585 uri
= webkit_web_view_get_title(t
->wv
);
7588 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
7594 /* can't use focus_webview here */
7595 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7602 notebook_pagereordered_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
7609 menuitem_response(struct tab
*t
)
7611 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7615 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
7617 GtkWidget
*menu
, *menu_items
;
7618 GdkEventButton
*bevent
;
7622 if (event
->type
== GDK_BUTTON_PRESS
) {
7623 bevent
= (GdkEventButton
*) event
;
7624 menu
= gtk_menu_new();
7626 TAILQ_FOREACH(ti
, &tabs
, entry
) {
7627 if ((uri
= get_uri(ti
->wv
)) == NULL
)
7628 /* XXX make sure there is something to print */
7629 /* XXX add gui pages in here to look purdy */
7631 menu_items
= gtk_menu_item_new_with_label(uri
);
7632 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_items
);
7633 gtk_widget_show(menu_items
);
7635 g_signal_connect_swapped((menu_items
),
7636 "activate", G_CALLBACK(menuitem_response
),
7640 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
7641 bevent
->button
, bevent
->time
);
7643 /* unref object so it'll free itself when popped down */
7644 #if !GTK_CHECK_VERSION(3, 0, 0)
7645 /* XXX does not need unref with gtk+3? */
7646 g_object_ref_sink(menu
);
7647 g_object_unref(menu
);
7650 return (TRUE
/* eat event */);
7653 return (FALSE
/* propagate */);
7657 icon_size_map(int icon_size
)
7659 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
7660 icon_size
> GTK_ICON_SIZE_DIALOG
)
7661 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
7667 create_button(char *name
, char *stockid
, int size
)
7669 GtkWidget
*button
, *image
;
7673 rcstring
= g_strdup_printf(
7674 "style \"%s-style\"\n"
7676 " GtkWidget::focus-padding = 0\n"
7677 " GtkWidget::focus-line-width = 0\n"
7681 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
7682 gtk_rc_parse_string(rcstring
);
7684 button
= gtk_button_new();
7685 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
7686 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
7688 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
7689 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7690 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
7691 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
7692 gtk_widget_set_name(button
, name
);
7693 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
7699 button_set_stockid(GtkWidget
*button
, char *stockid
)
7703 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
7704 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7705 gtk_button_set_image(GTK_BUTTON(button
), image
);
7709 clipb_primary_cb(GtkClipboard
*primary
, GdkEvent
*event
, gpointer notused
)
7711 GtkClipboard
*clipboard
;
7712 gchar
*p
= NULL
, *s
= NULL
;
7715 * This code is very aggressive!
7716 * It basically ensures that the primary and regular clipboard are
7717 * always set the same. This obviously messes with standard X protocol
7718 * but those clowns should have come up with something better.
7721 /* XXX make this setting? */
7722 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
7723 p
= gtk_clipboard_wait_for_text(primary
);
7725 DNPRINTF(XT_D_CLIP
, "primary cleaned\n");
7726 p
= gtk_clipboard_wait_for_text(clipboard
);
7728 gtk_clipboard_set_text(primary
, p
, -1);
7730 DNPRINTF(XT_D_CLIP
, "primary got selection\n");
7731 s
= gtk_clipboard_wait_for_text(clipboard
);
7734 * if s and p are the same the string was set by
7735 * clipb_clipboard_cb so do nothing in that case
7736 * to prevent endless loop
7741 gtk_clipboard_set_text(clipboard
, p
, -1);
7751 clipb_clipboard_cb(GtkClipboard
*clipboard
, GdkEvent
*event
, gpointer notused
)
7753 GtkClipboard
*primary
;
7754 gchar
*p
= NULL
, *s
= NULL
;
7756 DNPRINTF(XT_D_CLIP
, "clipboard got content\n");
7758 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
7759 p
= gtk_clipboard_wait_for_text(clipboard
);
7761 s
= gtk_clipboard_wait_for_text(primary
);
7764 * if s and p are the same the string was set by
7765 * clipb_primary_cb so do nothing in that case
7766 * to prevent endless loop and deselection of text
7771 gtk_clipboard_set_text(primary
, p
, -1);
7786 char file
[PATH_MAX
];
7789 vbox
= gtk_vbox_new(FALSE
, 0);
7790 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
7791 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
7792 #if !GTK_CHECK_VERSION(3, 0, 0)
7793 /* XXX seems to be needed with gtk+2 */
7794 gtk_notebook_set_tab_hborder(notebook
, 0);
7795 gtk_notebook_set_tab_vborder(notebook
, 0);
7797 gtk_notebook_set_scrollable(notebook
, TRUE
);
7798 notebook_tab_set_visibility(notebook
);
7799 gtk_notebook_set_show_border(notebook
, FALSE
);
7800 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
7802 abtn
= gtk_button_new();
7803 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
7804 gtk_widget_set_size_request(arrow
, -1, -1);
7805 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
7806 gtk_widget_set_size_request(abtn
, -1, 20);
7808 #if GTK_CHECK_VERSION(2, 20, 0)
7809 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
7811 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
7812 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
7813 gtk_widget_set_size_request(vbox
, -1, -1);
7815 g_object_connect(G_OBJECT(notebook
),
7816 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
7818 g_object_connect(G_OBJECT(notebook
),
7819 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb
), NULL
,
7821 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
7822 G_CALLBACK(arrow_cb
), NULL
);
7824 main_window
= create_window();
7825 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
7826 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
7829 for (i
= 0; i
< LENGTH(icons
); i
++) {
7830 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
7831 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
7832 l
= g_list_append(l
, pb
);
7834 gtk_window_set_default_icon_list(l
);
7837 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY
)),
7838 "owner-change", G_CALLBACK(clipb_primary_cb
), NULL
);
7839 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
)),
7840 "owner-change", G_CALLBACK(clipb_clipboard_cb
), NULL
);
7842 gtk_widget_show_all(abtn
);
7843 gtk_widget_show_all(main_window
);
7847 set_hook(void **hook
, char *name
)
7850 errx(1, "set_hook");
7852 if (*hook
== NULL
) {
7853 *hook
= dlsym(RTLD_NEXT
, name
);
7855 errx(1, "can't hook %s", name
);
7859 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7861 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
7863 g_return_val_if_fail(cookie1
, FALSE
);
7864 g_return_val_if_fail(cookie2
, FALSE
);
7866 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
7867 !strcmp (cookie1
->value
, cookie2
->value
) &&
7868 !strcmp (cookie1
->path
, cookie2
->path
) &&
7869 !strcmp (cookie1
->domain
, cookie2
->domain
));
7873 transfer_cookies(void)
7876 SoupCookie
*sc
, *pc
;
7878 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7880 for (;cf
; cf
= cf
->next
) {
7882 sc
= soup_cookie_copy(pc
);
7883 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
7886 soup_cookies_free(cf
);
7890 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
7895 print_cookie("soup_cookie_jar_delete_cookie", c
);
7897 if (cookies_enabled
== 0)
7900 if (jar
== NULL
|| c
== NULL
)
7903 /* find and remove from persistent jar */
7904 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7906 for (;cf
; cf
= cf
->next
) {
7908 if (soup_cookie_equal(ci
, c
)) {
7909 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
7914 soup_cookies_free(cf
);
7916 /* delete from session jar */
7917 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
7921 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
7923 struct domain
*d
= NULL
;
7927 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
7928 jar
, p_cookiejar
, s_cookiejar
);
7930 if (cookies_enabled
== 0)
7933 /* see if we are up and running */
7934 if (p_cookiejar
== NULL
) {
7935 _soup_cookie_jar_add_cookie(jar
, cookie
);
7938 /* disallow p_cookiejar adds, shouldn't happen */
7939 if (jar
== p_cookiejar
)
7943 if (jar
== NULL
|| cookie
== NULL
)
7946 if (enable_cookie_whitelist
&&
7947 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
7949 DNPRINTF(XT_D_COOKIE
,
7950 "soup_cookie_jar_add_cookie: reject %s\n",
7952 if (save_rejected_cookies
) {
7953 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
7954 show_oops_s("can't open reject cookie file");
7957 fseek(r_cookie_f
, 0, SEEK_END
);
7958 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7959 cookie
->http_only
? "#HttpOnly_" : "",
7961 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
7963 cookie
->secure
? "TRUE" : "FALSE",
7965 (gulong
)soup_date_to_time_t(cookie
->expires
) :
7972 if (!allow_volatile_cookies
)
7976 if (cookie
->expires
== NULL
&& session_timeout
) {
7977 soup_cookie_set_expires(cookie
,
7978 soup_date_new_from_now(session_timeout
));
7979 print_cookie("modified add cookie", cookie
);
7982 /* see if we are white listed for persistence */
7983 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
7984 /* add to persistent jar */
7985 c
= soup_cookie_copy(cookie
);
7986 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
7987 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
7990 /* add to session jar */
7991 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
7992 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
7998 char file
[PATH_MAX
];
8000 set_hook((void *)&_soup_cookie_jar_add_cookie
,
8001 "soup_cookie_jar_add_cookie");
8002 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
8003 "soup_cookie_jar_delete_cookie");
8005 if (cookies_enabled
== 0)
8009 * the following code is intricate due to overriding several libsoup
8011 * do not alter order of these operations.
8014 /* rejected cookies */
8015 if (save_rejected_cookies
)
8016 snprintf(rc_fname
, sizeof file
, "%s/%s", work_dir
, XT_REJECT_FILE
);
8018 /* persistent cookies */
8019 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_COOKIE_FILE
);
8020 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
8022 /* session cookies */
8023 s_cookiejar
= soup_cookie_jar_new();
8024 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
8025 cookie_policy
, (void *)NULL
);
8028 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
8032 setup_proxy(char *uri
)
8035 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
8036 soup_uri_free(proxy_uri
);
8040 if (http_proxy
!= uri
) {
8047 http_proxy
= g_strdup(uri
);
8048 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
8049 proxy_uri
= soup_uri_new(http_proxy
);
8050 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
8055 send_cmd_to_socket(char *cmd
)
8058 struct sockaddr_un sa
;
8060 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8061 warnx("%s: socket", __func__
);
8065 sa
.sun_family
= AF_UNIX
;
8066 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8067 work_dir
, XT_SOCKET_FILE
);
8070 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8071 warnx("%s: connect", __func__
);
8075 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
8076 warnx("%s: send", __func__
);
8087 socket_watcher(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
8090 char str
[XT_MAX_URL_LENGTH
];
8091 socklen_t t
= sizeof(struct sockaddr_un
);
8092 struct sockaddr_un sa
;
8097 gint fd
= g_io_channel_unix_get_fd(source
);
8099 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
8104 if (getpeereid(s
, &uid
, &gid
) == -1) {
8108 if (uid
!= getuid() || gid
!= getgid()) {
8109 warnx("unauthorized user");
8115 warnx("not a valid user");
8119 n
= recv(s
, str
, sizeof(str
), 0);
8123 tt
= TAILQ_LAST(&tabs
, tab_list
);
8124 cmd_execute(tt
, str
);
8132 struct sockaddr_un sa
;
8134 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8135 warn("is_running: socket");
8139 sa
.sun_family
= AF_UNIX
;
8140 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8141 work_dir
, XT_SOCKET_FILE
);
8144 /* connect to see if there is a listener */
8145 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
8146 rv
= 0; /* not running */
8148 rv
= 1; /* already running */
8159 struct sockaddr_un sa
;
8161 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8162 warn("build_socket: socket");
8166 sa
.sun_family
= AF_UNIX
;
8167 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8168 work_dir
, XT_SOCKET_FILE
);
8171 /* connect to see if there is a listener */
8172 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8173 /* no listener so we will */
8174 unlink(sa
.sun_path
);
8176 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8177 warn("build_socket: bind");
8181 if (listen(s
, 1) == -1) {
8182 warn("build_socket: listen");
8195 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8196 GtkTreeIter
*iter
, struct tab
*t
)
8200 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8208 completion_hover_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8209 GtkTreeIter
*iter
, struct tab
*t
)
8213 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8214 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), value
);
8215 gtk_editable_set_position(GTK_EDITABLE(t
->uri_entry
), -1);
8222 completion_add_uri(const gchar
*uri
)
8226 /* add uri to list_store */
8227 gtk_list_store_append(completion_model
, &iter
);
8228 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
8232 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
8233 GtkTreeIter
*iter
, gpointer user_data
)
8236 gboolean match
= FALSE
;
8238 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8244 match
= match_uri(value
, key
);
8251 completion_add(struct tab
*t
)
8253 /* enable completion for tab */
8254 t
->completion
= gtk_entry_completion_new();
8255 gtk_entry_completion_set_text_column(t
->completion
, 0);
8256 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8257 gtk_entry_completion_set_model(t
->completion
,
8258 GTK_TREE_MODEL(completion_model
));
8259 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8261 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8262 gtk_entry_completion_set_inline_selection(t
->completion
, TRUE
);
8263 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8264 G_CALLBACK(completion_select_cb
), t
);
8265 g_signal_connect(G_OBJECT (t
->completion
), "cursor-on-match",
8266 G_CALLBACK(completion_hover_cb
), t
);
8274 if (stat(dir
, &sb
)) {
8275 if (mkdir(dir
, S_IRWXU
) == -1)
8276 err(1, "mkdir %s", dir
);
8278 err(1, "stat %s", dir
);
8280 if (S_ISDIR(sb
.st_mode
) == 0)
8281 errx(1, "%s not a dir", dir
);
8282 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8283 warnx("fixing invalid permissions on %s", dir
);
8284 if (chmod(dir
, S_IRWXU
) == -1)
8285 err(1, "chmod %s", dir
);
8293 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8299 main(int argc
, char *argv
[])
8302 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8303 char conf
[PATH_MAX
] = { '\0' };
8304 char file
[PATH_MAX
];
8305 char *env_proxy
= NULL
;
8308 struct sigaction sact
;
8309 GIOChannel
*channel
;
8314 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8316 /* fiddle with ulimits */
8317 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8320 /* just use them all */
8321 rlp
.rlim_cur
= rlp
.rlim_max
;
8322 if (setrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8324 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8326 else if (rlp
.rlim_cur
<= 256)
8327 warnx("%s requires at least 256 file descriptors",
8331 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8340 errx(0 , "Version: %s", version
);
8343 strlcpy(conf
, optarg
, sizeof(conf
));
8346 strlcpy(named_session
, optarg
, sizeof(named_session
));
8367 RB_INIT(&downloads
);
8371 TAILQ_INIT(&aliases
);
8377 gnutls_global_init();
8379 /* generate session keys for xtp pages */
8380 generate_xtp_session_key(&dl_session_key
);
8381 generate_xtp_session_key(&hl_session_key
);
8382 generate_xtp_session_key(&cl_session_key
);
8383 generate_xtp_session_key(&fl_session_key
);
8386 gtk_init(&argc
, &argv
);
8387 if (!g_thread_supported())
8388 g_thread_init(NULL
);
8391 bzero(&sact
, sizeof(sact
));
8392 sigemptyset(&sact
.sa_mask
);
8393 sact
.sa_handler
= sigchild
;
8394 sact
.sa_flags
= SA_NOCLDSTOP
;
8395 sigaction(SIGCHLD
, &sact
, NULL
);
8397 /* set download dir */
8398 pwd
= getpwuid(getuid());
8400 errx(1, "invalid user %d", getuid());
8401 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8403 /* set default string settings */
8404 home
= g_strdup("https://www.cyphertite.com");
8405 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8406 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
8407 strlcpy(runtime_settings
, "runtime", sizeof runtime_settings
);
8408 cmd_font_name
= g_strdup("monospace normal 9");
8409 statusbar_font_name
= g_strdup("monospace normal 9");
8412 /* read config file */
8413 if (strlen(conf
) == 0)
8414 snprintf(conf
, sizeof conf
, "%s/.%s",
8415 pwd
->pw_dir
, XT_CONF_FILE
);
8416 config_parse(conf
, 0);
8419 cmd_font
= pango_font_description_from_string(cmd_font_name
);
8420 statusbar_font
= pango_font_description_from_string(statusbar_font_name
);
8422 /* working directory */
8423 if (strlen(work_dir
) == 0)
8424 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
8425 pwd
->pw_dir
, XT_DIR
);
8428 /* icon cache dir */
8429 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
8433 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
8437 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
8438 work_dir
, XT_SESSIONS_DIR
);
8439 xxx_dir(sessions_dir
);
8441 /* runtime settings that can override config file */
8442 if (runtime_settings
[0] != '\0')
8443 config_parse(runtime_settings
, 1);
8446 if (!strcmp(download_dir
, pwd
->pw_dir
))
8447 strlcat(download_dir
, "/downloads", sizeof download_dir
);
8448 xxx_dir(download_dir
);
8450 /* favorites file */
8451 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
8452 if (stat(file
, &sb
)) {
8453 warnx("favorites file doesn't exist, creating it");
8454 if ((f
= fopen(file
, "w")) == NULL
)
8455 err(1, "favorites");
8460 session
= webkit_get_default_session();
8465 if (stat(ssl_ca_file
, &sb
)) {
8466 warnx("no CA file: %s", ssl_ca_file
);
8467 g_free(ssl_ca_file
);
8470 g_object_set(session
,
8471 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
8472 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8477 env_proxy
= getenv("http_proxy");
8479 setup_proxy(env_proxy
);
8481 setup_proxy(http_proxy
);
8484 send_cmd_to_socket(argv
[0]);
8488 /* set some connection parameters */
8489 /* XXX webkit 1.4.X overwrites these values! */
8490 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8491 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8494 /* see if there is already an xxxterm running */
8495 if (single_instance
&& is_running()) {
8497 warnx("already running");
8503 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8504 send_cmd_to_socket(cmd
);
8514 /* uri completion */
8515 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8520 if (save_global_history
)
8521 restore_global_history();
8523 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
8524 restore_saved_tabs();
8526 a
.s
= named_session
;
8527 a
.i
= XT_SES_DONOTHING
;
8528 open_tabs(NULL
, &a
);
8532 create_new_tab(argv
[0], NULL
, focus
, -1);
8539 if (TAILQ_EMPTY(&tabs
))
8540 create_new_tab(home
, NULL
, 1, -1);
8543 if ((s
= build_socket()) != -1) {
8544 channel
= g_io_channel_unix_new(s
);
8545 g_io_add_watch(channel
, G_IO_IN
, socket_watcher
, NULL
);
8550 gnutls_global_deinit();