3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
44 #include <sys/types.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
51 #include "freebsd/util.h"
57 #include <sys/queue.h>
59 #include <sys/socket.h>
63 #include <gdk/gdkkeysyms.h>
65 #if GTK_CHECK_VERSION(3,0,0)
66 /* we still use GDK_* instead of GDK_KEY_* */
67 #include <gdk/gdkkeysyms-compat.h>
70 #include <webkit/webkit.h>
71 #include <libsoup/soup.h>
72 #include <gnutls/gnutls.h>
73 #include <JavaScriptCore/JavaScript.h>
74 #include <gnutls/x509.h>
76 #include "javascript.h"
79 javascript.h borrowed from vimprobable2 under the following license:
81 Copyright (c) 2009 Leon Winter
82 Copyright (c) 2009 Hannes Schueller
83 Copyright (c) 2009 Matto Fransen
85 Permission is hereby granted, free of charge, to any person obtaining a copy
86 of this software and associated documentation files (the "Software"), to deal
87 in the Software without restriction, including without limitation the rights
88 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
89 copies of the Software, and to permit persons to whom the Software is
90 furnished to do so, subject to the following conditions:
92 The above copyright notice and this permission notice shall be included in
93 all copies or substantial portions of the Software.
95 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
96 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
97 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
98 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
99 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
100 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
104 static char *version
= "$xxxterm$";
106 /* hooked functions */
107 void (*_soup_cookie_jar_add_cookie
)(SoupCookieJar
*, SoupCookie
*);
108 void (*_soup_cookie_jar_delete_cookie
)(SoupCookieJar
*,
113 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
114 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
115 #define XT_D_MOVE 0x0001
116 #define XT_D_KEY 0x0002
117 #define XT_D_TAB 0x0004
118 #define XT_D_URL 0x0008
119 #define XT_D_CMD 0x0010
120 #define XT_D_NAV 0x0020
121 #define XT_D_DOWNLOAD 0x0040
122 #define XT_D_CONFIG 0x0080
123 #define XT_D_JS 0x0100
124 #define XT_D_FAVORITE 0x0200
125 #define XT_D_PRINTING 0x0400
126 #define XT_D_COOKIE 0x0800
127 #define XT_D_KEYBINDING 0x1000
128 #define XT_D_CLIP 0x2000
129 u_int32_t swm_debug
= 0
146 #define DPRINTF(x...)
147 #define DNPRINTF(n,x...)
150 #define LENGTH(x) (sizeof x / sizeof x[0])
151 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
152 ~(GDK_BUTTON1_MASK) & \
153 ~(GDK_BUTTON2_MASK) & \
154 ~(GDK_BUTTON3_MASK) & \
155 ~(GDK_BUTTON4_MASK) & \
167 TAILQ_ENTRY(tab
) entry
;
169 GtkWidget
*tab_content
;
172 GtkWidget
*uri_entry
;
173 GtkWidget
*search_entry
;
175 GtkWidget
*browser_win
;
176 GtkWidget
*statusbar
;
183 GtkWidget
*js_toggle
;
184 GtkEntryCompletion
*completion
;
188 WebKitWebHistoryItem
*item
;
189 WebKitWebBackForwardList
*bfl
;
192 WebKitNetworkRequest
*icon_request
;
193 WebKitDownload
*icon_download
;
194 GdkPixbuf
*icon_pixbuf
;
195 gchar
*icon_dest_uri
;
197 /* adjustments for browser */
200 GtkAdjustment
*adjust_h
;
201 GtkAdjustment
*adjust_v
;
207 int xtp_meaning
; /* identifies dls/favorites */
212 #define XT_HINT_NONE (0)
213 #define XT_HINT_NUMERICAL (1)
214 #define XT_HINT_ALPHANUM (2)
218 /* custom stylesheet */
227 WebKitWebSettings
*settings
;
231 TAILQ_HEAD(tab_list
, tab
);
234 RB_ENTRY(history
) entry
;
238 RB_HEAD(history_list
, history
);
241 RB_ENTRY(download
) entry
;
243 WebKitDownload
*download
;
246 RB_HEAD(download_list
, download
);
249 RB_ENTRY(domain
) entry
;
251 int handy
; /* app use */
253 RB_HEAD(domain_list
, domain
);
256 TAILQ_ENTRY(undo
) entry
;
259 int back
; /* Keeps track of how many back
260 * history items there are. */
262 TAILQ_HEAD(undo_tailq
, undo
);
264 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
265 int next_download_id
= 1;
274 #define XT_NAME ("XXXTerm")
275 #define XT_DIR (".xxxterm")
276 #define XT_CACHE_DIR ("cache")
277 #define XT_CERT_DIR ("certs/")
278 #define XT_SESSIONS_DIR ("sessions/")
279 #define XT_CONF_FILE ("xxxterm.conf")
280 #define XT_FAVS_FILE ("favorites")
281 #define XT_SAVED_TABS_FILE ("main_session")
282 #define XT_RESTART_TABS_FILE ("restart_tabs")
283 #define XT_SOCKET_FILE ("socket")
284 #define XT_HISTORY_FILE ("history")
285 #define XT_REJECT_FILE ("rejected.txt")
286 #define XT_COOKIE_FILE ("cookies.txt")
287 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
288 #define XT_CB_HANDLED (TRUE)
289 #define XT_CB_PASSTHROUGH (FALSE)
290 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
291 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
292 #define XT_DLMAN_REFRESH "10"
293 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
294 "td{overflow: hidden;" \
295 " padding: 2px 2px 2px 2px;" \
296 " border: 1px solid black;" \
297 " vertical-align:top;" \
298 " word-wrap: break-word}\n" \
299 "tr:hover{background: #ffff99}\n" \
300 "th{background-color: #cccccc;" \
301 " border: 1px solid black}\n" \
302 "table{width: 100%%;" \
303 " border: 1px black solid;" \
304 " border-collapse:collapse}\n" \
306 "border: 1px solid black;" \
309 ".progress-inner{float: left;" \
311 " background: green}\n" \
312 ".dlstatus{font-size: small;" \
313 " text-align: center}\n" \
315 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
316 #define XT_MAX_UNDO_CLOSE_TAB (32)
317 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
318 #define XT_PRINT_EXTRA_MARGIN 10
321 #define XT_COLOR_RED "#cc0000"
322 #define XT_COLOR_YELLOW "#ffff66"
323 #define XT_COLOR_BLUE "lightblue"
324 #define XT_COLOR_GREEN "#99ff66"
325 #define XT_COLOR_WHITE "white"
326 #define XT_COLOR_BLACK "black"
329 * xxxterm "protocol" (xtp)
330 * We use this for managing stuff like downloads and favorites. They
331 * make magical HTML pages in memory which have xxxt:// links in order
332 * to communicate with xxxterm's internals. These links take the format:
333 * xxxt://class/session_key/action/arg
335 * Don't begin xtp class/actions as 0. atoi returns that on error.
337 * Typically we have not put addition of items in this framework, as
338 * adding items is either done via an ex-command or via a keybinding instead.
341 #define XT_XTP_STR "xxxt://"
343 /* XTP classes (xxxt://<class>) */
344 #define XT_XTP_INVALID 0 /* invalid */
345 #define XT_XTP_DL 1 /* downloads */
346 #define XT_XTP_HL 2 /* history */
347 #define XT_XTP_CL 3 /* cookies */
348 #define XT_XTP_FL 4 /* favorites */
350 /* XTP download actions */
351 #define XT_XTP_DL_LIST 1
352 #define XT_XTP_DL_CANCEL 2
353 #define XT_XTP_DL_REMOVE 3
355 /* XTP history actions */
356 #define XT_XTP_HL_LIST 1
357 #define XT_XTP_HL_REMOVE 2
359 /* XTP cookie actions */
360 #define XT_XTP_CL_LIST 1
361 #define XT_XTP_CL_REMOVE 2
363 /* XTP cookie actions */
364 #define XT_XTP_FL_LIST 1
365 #define XT_XTP_FL_REMOVE 2
367 /* xtp tab meanings - identifies which tabs have xtp pages in */
368 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
369 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
370 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
371 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
372 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
375 #define XT_MOVE_INVALID (0)
376 #define XT_MOVE_DOWN (1)
377 #define XT_MOVE_UP (2)
378 #define XT_MOVE_BOTTOM (3)
379 #define XT_MOVE_TOP (4)
380 #define XT_MOVE_PAGEDOWN (5)
381 #define XT_MOVE_PAGEUP (6)
382 #define XT_MOVE_HALFDOWN (7)
383 #define XT_MOVE_HALFUP (8)
384 #define XT_MOVE_LEFT (9)
385 #define XT_MOVE_FARLEFT (10)
386 #define XT_MOVE_RIGHT (11)
387 #define XT_MOVE_FARRIGHT (12)
389 #define XT_TAB_LAST (-4)
390 #define XT_TAB_FIRST (-3)
391 #define XT_TAB_PREV (-2)
392 #define XT_TAB_NEXT (-1)
393 #define XT_TAB_INVALID (0)
394 #define XT_TAB_NEW (1)
395 #define XT_TAB_DELETE (2)
396 #define XT_TAB_DELQUIT (3)
397 #define XT_TAB_OPEN (4)
398 #define XT_TAB_UNDO_CLOSE (5)
399 #define XT_TAB_SHOW (6)
400 #define XT_TAB_HIDE (7)
402 #define XT_NAV_INVALID (0)
403 #define XT_NAV_BACK (1)
404 #define XT_NAV_FORWARD (2)
405 #define XT_NAV_RELOAD (3)
406 #define XT_NAV_RELOAD_CACHE (4)
408 #define XT_FOCUS_INVALID (0)
409 #define XT_FOCUS_URI (1)
410 #define XT_FOCUS_SEARCH (2)
412 #define XT_SEARCH_INVALID (0)
413 #define XT_SEARCH_NEXT (1)
414 #define XT_SEARCH_PREV (2)
416 #define XT_PASTE_CURRENT_TAB (0)
417 #define XT_PASTE_NEW_TAB (1)
419 #define XT_FONT_SET (0)
421 #define XT_URL_SHOW (1)
422 #define XT_URL_HIDE (2)
424 #define XT_STATUSBAR_SHOW (1)
425 #define XT_STATUSBAR_HIDE (2)
427 #define XT_WL_TOGGLE (1<<0)
428 #define XT_WL_ENABLE (1<<1)
429 #define XT_WL_DISABLE (1<<2)
430 #define XT_WL_FQDN (1<<3) /* default */
431 #define XT_WL_TOPLEVEL (1<<4)
432 #define XT_WL_PERSISTENT (1<<5)
433 #define XT_WL_SESSION (1<<6)
435 #define XT_SHOW (1<<7)
436 #define XT_DELETE (1<<8)
437 #define XT_SAVE (1<<9)
438 #define XT_OPEN (1<<10)
440 #define XT_CMD_OPEN (0)
441 #define XT_CMD_OPEN_CURRENT (1)
442 #define XT_CMD_TABNEW (2)
443 #define XT_CMD_TABNEW_CURRENT (3)
445 #define XT_STATUS_NOTHING (0)
446 #define XT_STATUS_LINK (1)
447 #define XT_STATUS_URI (2)
448 #define XT_STATUS_LOADING (3)
450 #define XT_SES_DONOTHING (0)
451 #define XT_SES_CLOSETABS (1)
453 #define XT_BM_NORMAL (0)
454 #define XT_BM_WHITELIST (1)
455 #define XT_BM_KIOSK (2)
457 #define XT_PREFIX (1<<0)
458 #define XT_USERARG (1<<1)
459 #define XT_URLARG (1<<2)
460 #define XT_INTARG (1<<3)
468 TAILQ_ENTRY(mime_type
) entry
;
470 TAILQ_HEAD(mime_type_list
, mime_type
);
476 TAILQ_ENTRY(alias
) entry
;
478 TAILQ_HEAD(alias_list
, alias
);
480 /* settings that require restart */
481 int tabless
= 0; /* allow only 1 tab */
482 int enable_socket
= 0;
483 int single_instance
= 0; /* only allow one xxxterm to run */
484 int fancy_bar
= 1; /* fancy toolbar */
485 int browser_mode
= XT_BM_NORMAL
;
487 /* runtime settings */
488 int show_tabs
= 1; /* show tabs on notebook */
489 int show_url
= 1; /* show url toolbar on notebook */
490 int show_statusbar
= 0; /* vimperator style status bar */
491 int ctrl_click_focus
= 0; /* ctrl click gets focus */
492 int cookies_enabled
= 1; /* enable cookies */
493 int read_only_cookies
= 0; /* enable to not write cookies */
494 int enable_scripts
= 1;
495 int enable_plugins
= 0;
496 int default_font_size
= 12;
497 gfloat default_zoom_level
= 1.0;
498 int window_height
= 768;
499 int window_width
= 1024;
500 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
501 unsigned refresh_interval
= 10; /* download refresh interval */
502 int enable_cookie_whitelist
= 0;
503 int enable_js_whitelist
= 0;
504 time_t session_timeout
= 3600; /* cookie session timeout */
505 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
506 char *ssl_ca_file
= NULL
;
507 char *resource_dir
= NULL
;
508 gboolean ssl_strict_certs
= FALSE
;
509 int append_next
= 1; /* append tab after current tab */
511 char *search_string
= NULL
;
512 char *http_proxy
= NULL
;
513 char download_dir
[PATH_MAX
];
514 char runtime_settings
[PATH_MAX
]; /* override of settings */
515 int allow_volatile_cookies
= 0;
516 int save_global_history
= 0; /* save global history to disk */
517 char *user_agent
= NULL
;
518 int save_rejected_cookies
= 0;
519 time_t session_autosave
= 0;
520 int guess_search
= 0;
521 int dns_prefetch
= FALSE
;
522 gint max_connections
= 25;
523 gint max_host_connections
= 5;
524 gint enable_spell_checking
= 0;
525 char *spell_check_languages
= NULL
;
529 int set_download_dir(struct settings
*, char *);
530 int set_work_dir(struct settings
*, char *);
531 int set_runtime_dir(struct settings
*, char *);
532 int set_browser_mode(struct settings
*, char *);
533 int set_cookie_policy(struct settings
*, char *);
534 int add_alias(struct settings
*, char *);
535 int add_mime_type(struct settings
*, char *);
536 int add_cookie_wl(struct settings
*, char *);
537 int add_js_wl(struct settings
*, char *);
538 int add_kb(struct settings
*, char *);
539 void button_set_stockid(GtkWidget
*, char *);
540 GtkWidget
* create_button(char *, char *, int);
542 char *get_browser_mode(struct settings
*);
543 char *get_cookie_policy(struct settings
*);
545 char *get_download_dir(struct settings
*);
546 char *get_work_dir(struct settings
*);
547 char *get_runtime_dir(struct settings
*);
549 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
550 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
551 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
552 void walk_kb(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
553 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
555 void recalc_tabs(void);
558 int (*set
)(struct settings
*, char *);
559 char *(*get
)(struct settings
*);
560 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
563 struct special s_browser_mode
= {
569 struct special s_cookie
= {
575 struct special s_alias
= {
581 struct special s_mime
= {
587 struct special s_js
= {
593 struct special s_kb
= {
599 struct special s_cookie_wl
= {
605 struct special s_download_dir
= {
611 struct special s_work_dir
= {
620 #define XT_S_INVALID (0)
623 #define XT_S_FLOAT (3)
625 #define XT_SF_RESTART (1<<0)
626 #define XT_SF_RUNTIME (1<<1)
632 { "append_next", XT_S_INT
, 0, &append_next
, NULL
, NULL
},
633 { "allow_volatile_cookies", XT_S_INT
, 0, &allow_volatile_cookies
, NULL
, NULL
},
634 { "browser_mode", XT_S_INT
, 0, NULL
, NULL
,&s_browser_mode
},
635 { "cookie_policy", XT_S_INT
, 0, NULL
, NULL
,&s_cookie
},
636 { "cookies_enabled", XT_S_INT
, 0, &cookies_enabled
, NULL
, NULL
},
637 { "ctrl_click_focus", XT_S_INT
, 0, &ctrl_click_focus
, NULL
, NULL
},
638 { "default_font_size", XT_S_INT
, 0, &default_font_size
, NULL
, NULL
},
639 { "default_zoom_level", XT_S_FLOAT
, 0, NULL
, NULL
, NULL
, &default_zoom_level
},
640 { "download_dir", XT_S_STR
, 0, NULL
, NULL
,&s_download_dir
},
641 { "enable_cookie_whitelist", XT_S_INT
, 0, &enable_cookie_whitelist
, NULL
, NULL
},
642 { "enable_js_whitelist", XT_S_INT
, 0, &enable_js_whitelist
, NULL
, NULL
},
643 { "enable_plugins", XT_S_INT
, 0, &enable_plugins
, NULL
, NULL
},
644 { "enable_scripts", XT_S_INT
, 0, &enable_scripts
, NULL
, NULL
},
645 { "enable_socket", XT_S_INT
, XT_SF_RESTART
,&enable_socket
, NULL
, NULL
},
646 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
,&fancy_bar
, NULL
, NULL
},
647 { "home", XT_S_STR
, 0, NULL
, &home
, NULL
},
648 { "http_proxy", XT_S_STR
, 0, NULL
, &http_proxy
, NULL
},
649 { "icon_size", XT_S_INT
, 0, &icon_size
, NULL
, NULL
},
650 { "max_connections", XT_S_INT
, XT_SF_RESTART
,&max_connections
, NULL
, NULL
},
651 { "max_host_connections", XT_S_INT
, XT_SF_RESTART
,&max_host_connections
, NULL
, NULL
},
652 { "read_only_cookies", XT_S_INT
, 0, &read_only_cookies
, NULL
, NULL
},
653 { "refresh_interval", XT_S_INT
, 0, &refresh_interval
, NULL
, NULL
},
654 { "resource_dir", XT_S_STR
, 0, NULL
, &resource_dir
, NULL
},
655 { "search_string", XT_S_STR
, 0, NULL
, &search_string
, NULL
},
656 { "save_global_history", XT_S_INT
, XT_SF_RESTART
,&save_global_history
, NULL
, NULL
},
657 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
,&save_rejected_cookies
, NULL
, NULL
},
658 { "session_timeout", XT_S_INT
, 0, &session_timeout
, NULL
, NULL
},
659 { "session_autosave", XT_S_INT
, 0, &session_autosave
, NULL
, NULL
},
660 { "single_instance", XT_S_INT
, XT_SF_RESTART
,&single_instance
, NULL
, NULL
},
661 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
662 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
663 { "guess_search", XT_S_INT
, 0, &guess_search
, NULL
, NULL
},
664 { "show_statusbar", XT_S_INT
, 0, &show_statusbar
, NULL
, NULL
},
665 { "ssl_ca_file", XT_S_STR
, 0, NULL
, &ssl_ca_file
, NULL
},
666 { "ssl_strict_certs", XT_S_INT
, 0, &ssl_strict_certs
, NULL
, NULL
},
667 { "user_agent", XT_S_STR
, 0, NULL
, &user_agent
, NULL
},
668 { "window_height", XT_S_INT
, 0, &window_height
, NULL
, NULL
},
669 { "window_width", XT_S_INT
, 0, &window_width
, NULL
, NULL
},
670 { "work_dir", XT_S_STR
, 0, NULL
, NULL
,&s_work_dir
},
671 { "enable_spell_checking", XT_S_INT
, 0, &enable_spell_checking
, NULL
, NULL
},
672 { "spell_check_languages", XT_S_STR
, 0, NULL
, &spell_check_languages
, NULL
},
674 /* runtime settings */
675 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
676 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
677 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
678 { "keybinding", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_kb
},
679 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
682 int about(struct tab
*, struct karg
*);
683 int blank(struct tab
*, struct karg
*);
684 int ca_cmd(struct tab
*, struct karg
*);
685 int cookie_show_wl(struct tab
*, struct karg
*);
686 int js_show_wl(struct tab
*, struct karg
*);
687 int help(struct tab
*, struct karg
*);
688 int set(struct tab
*, struct karg
*);
689 int stats(struct tab
*, struct karg
*);
690 int marco(struct tab
*, struct karg
*);
691 const char * marco_message(int *);
692 int xtp_page_cl(struct tab
*, struct karg
*);
693 int xtp_page_dl(struct tab
*, struct karg
*);
694 int xtp_page_fl(struct tab
*, struct karg
*);
695 int xtp_page_hl(struct tab
*, struct karg
*);
697 #define XT_URI_ABOUT ("about:")
698 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
699 #define XT_URI_ABOUT_ABOUT ("about")
700 #define XT_URI_ABOUT_BLANK ("blank")
701 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
702 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
703 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
704 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
705 #define XT_URI_ABOUT_FAVORITES ("favorites")
706 #define XT_URI_ABOUT_HELP ("help")
707 #define XT_URI_ABOUT_HISTORY ("history")
708 #define XT_URI_ABOUT_JSWL ("jswl")
709 #define XT_URI_ABOUT_SET ("set")
710 #define XT_URI_ABOUT_STATS ("stats")
711 #define XT_URI_ABOUT_MARCO ("marco")
715 int (*func
)(struct tab
*, struct karg
*);
717 { XT_URI_ABOUT_ABOUT
, about
},
718 { XT_URI_ABOUT_BLANK
, blank
},
719 { XT_URI_ABOUT_CERTS
, ca_cmd
},
720 { XT_URI_ABOUT_COOKIEWL
, cookie_show_wl
},
721 { XT_URI_ABOUT_COOKIEJAR
, xtp_page_cl
},
722 { XT_URI_ABOUT_DOWNLOADS
, xtp_page_dl
},
723 { XT_URI_ABOUT_FAVORITES
, xtp_page_fl
},
724 { XT_URI_ABOUT_HELP
, help
},
725 { XT_URI_ABOUT_HISTORY
, xtp_page_hl
},
726 { XT_URI_ABOUT_JSWL
, js_show_wl
},
727 { XT_URI_ABOUT_SET
, set
},
728 { XT_URI_ABOUT_STATS
, stats
},
729 { XT_URI_ABOUT_MARCO
, marco
},
733 extern char *__progname
;
736 GtkWidget
*main_window
;
737 GtkNotebook
*notebook
;
738 GtkWidget
*arrow
, *abtn
;
739 struct tab_list tabs
;
740 struct history_list hl
;
741 struct download_list downloads
;
742 struct domain_list c_wl
;
743 struct domain_list js_wl
;
744 struct undo_tailq undos
;
745 struct keybinding_list kbl
;
747 int updating_dl_tabs
= 0;
748 int updating_hl_tabs
= 0;
749 int updating_cl_tabs
= 0;
750 int updating_fl_tabs
= 0;
752 uint64_t blocked_cookies
= 0;
753 char named_session
[PATH_MAX
];
754 void update_favicon(struct tab
*);
755 int icon_size_map(int);
757 GtkListStore
*completion_model
;
758 void completion_add(struct tab
*);
759 void completion_add_uri(const gchar
*);
760 void xxx_dir(char *);
765 int saved_errno
, status
;
770 while ((pid
= waitpid(WAIT_ANY
, &status
, WNOHANG
)) != 0) {
774 if (errno
!= ECHILD
) {
776 clog_warn("sigchild: waitpid:");
782 if (WIFEXITED(status
)) {
783 if (WEXITSTATUS(status
) != 0) {
785 clog_warnx("sigchild: child exit status: %d",
786 WEXITSTATUS(status));
791 clog_warnx("sigchild: child is terminated abnormally");
800 is_g_object_setting(GObject
*o
, char *str
)
802 guint n_props
= 0, i
;
803 GParamSpec
**proplist
;
805 if (! G_IS_OBJECT(o
))
808 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
811 for (i
=0; i
< n_props
; i
++) {
812 if (! strcmp(proplist
[i
]->name
, str
))
819 get_html_page(gchar
*title
, gchar
*body
, gchar
*head
, bool addstyles
)
823 r
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
825 "<title>%s</title>\n"
834 addstyles
? XT_PAGE_STYLE
: "",
843 * Display a web page from a HTML string in memory, rather than from a URL
846 load_webkit_string(struct tab
*t
, const char *str
, gchar
*title
)
852 /* we set this to indicate we want to manually do navaction */
854 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
856 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, "");
857 #if GTK_CHECK_VERSION(2, 20, 0)
858 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
859 gtk_widget_hide(t
->spinner
);
863 uri
= g_strdup_printf("%s%s", XT_URI_ABOUT
, title
);
864 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
867 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[0]);
868 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
869 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
870 GTK_ENTRY_ICON_PRIMARY
, pb
);
871 gdk_pixbuf_unref(pb
);
876 set_status(struct tab
*t
, gchar
*s
, int status
)
884 case XT_STATUS_LOADING
:
885 type
= g_strdup_printf("Loading: %s", s
);
889 type
= g_strdup_printf("Link: %s", s
);
891 t
->status
= g_strdup(gtk_entry_get_text(GTK_ENTRY(t
->statusbar
)));
895 type
= g_strdup_printf("%s", s
);
897 t
->status
= g_strdup(type
);
901 t
->status
= g_strdup(s
);
903 case XT_STATUS_NOTHING
:
908 gtk_entry_set_text(GTK_ENTRY(t
->statusbar
), s
);
914 hide_oops(struct tab
*t
)
916 gtk_widget_hide(t
->oops
);
920 hide_cmd(struct tab
*t
)
922 gtk_widget_hide(t
->cmd
);
926 show_cmd(struct tab
*t
)
928 gtk_widget_hide(t
->oops
);
929 gtk_widget_show(t
->cmd
);
933 show_oops(struct tab
*t
, const char *fmt
, ...)
942 if (vasprintf(&msg
, fmt
, ap
) == -1)
943 errx(1, "show_oops failed");
946 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
947 gtk_widget_hide(t
->cmd
);
948 gtk_widget_show(t
->oops
);
951 /* XXX collapse with show_oops */
953 show_oops_s(const char *fmt
, ...)
957 struct tab
*ti
, *t
= NULL
;
962 TAILQ_FOREACH(ti
, &tabs
, entry
)
963 if (ti
->tab_id
== gtk_notebook_get_current_page(notebook
)) {
971 if (vasprintf(&msg
, fmt
, ap
) == -1)
972 errx(1, "show_oops_s failed");
975 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
976 gtk_widget_hide(t
->cmd
);
977 gtk_widget_show(t
->oops
);
981 get_as_string(struct settings
*s
)
992 warnx("get_as_string skip %s\n", s
->name
);
993 } else if (s
->type
== XT_S_INT
)
994 r
= g_strdup_printf("%d", *s
->ival
);
995 else if (s
->type
== XT_S_STR
)
996 r
= g_strdup(*s
->sval
);
997 else if (s
->type
== XT_S_FLOAT
)
998 r
= g_strdup_printf("%f", *s
->fval
);
1000 r
= g_strdup_printf("INVALID TYPE");
1006 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1011 for (i
= 0; i
< LENGTH(rs
); i
++) {
1012 if (rs
[i
].s
&& rs
[i
].s
->walk
)
1013 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
1015 s
= get_as_string(&rs
[i
]);
1016 cb(&rs
[i
], s
, cb_args
);
1023 set_browser_mode(struct settings
*s
, char *val
)
1025 if (!strcmp(val
, "whitelist")) {
1026 browser_mode
= XT_BM_WHITELIST
;
1027 allow_volatile_cookies
= 0;
1028 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1029 cookies_enabled
= 1;
1030 enable_cookie_whitelist
= 1;
1031 read_only_cookies
= 0;
1032 save_rejected_cookies
= 0;
1033 session_timeout
= 3600;
1035 enable_js_whitelist
= 1;
1036 } else if (!strcmp(val
, "normal")) {
1037 browser_mode
= XT_BM_NORMAL
;
1038 allow_volatile_cookies
= 0;
1039 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1040 cookies_enabled
= 1;
1041 enable_cookie_whitelist
= 0;
1042 read_only_cookies
= 0;
1043 save_rejected_cookies
= 0;
1044 session_timeout
= 3600;
1046 enable_js_whitelist
= 0;
1047 } else if (!strcmp(val
, "kiosk")) {
1048 browser_mode
= XT_BM_KIOSK
;
1049 allow_volatile_cookies
= 0;
1050 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1051 cookies_enabled
= 1;
1052 enable_cookie_whitelist
= 0;
1053 read_only_cookies
= 0;
1054 save_rejected_cookies
= 0;
1055 session_timeout
= 3600;
1057 enable_js_whitelist
= 0;
1067 get_browser_mode(struct settings
*s
)
1071 if (browser_mode
== XT_BM_WHITELIST
)
1072 r
= g_strdup("whitelist");
1073 else if (browser_mode
== XT_BM_NORMAL
)
1074 r
= g_strdup("normal");
1075 else if (browser_mode
== XT_BM_KIOSK
)
1076 r
= g_strdup("kiosk");
1084 set_cookie_policy(struct settings
*s
, char *val
)
1086 if (!strcmp(val
, "no3rdparty"))
1087 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1088 else if (!strcmp(val
, "accept"))
1089 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1090 else if (!strcmp(val
, "reject"))
1091 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1099 get_cookie_policy(struct settings
*s
)
1103 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1104 r
= g_strdup("no3rdparty");
1105 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1106 r
= g_strdup("accept");
1107 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1108 r
= g_strdup("reject");
1116 get_download_dir(struct settings
*s
)
1118 if (download_dir
[0] == '\0')
1120 return (g_strdup(download_dir
));
1124 set_download_dir(struct settings
*s
, char *val
)
1127 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1128 pwd
->pw_dir
, &val
[1]);
1130 strlcpy(download_dir
, val
, sizeof download_dir
);
1137 * We use these to prevent people putting xxxt:// URLs on
1138 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1140 #define XT_XTP_SES_KEY_SZ 8
1141 #define XT_XTP_SES_KEY_HEX_FMT \
1142 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1143 char *dl_session_key
; /* downloads */
1144 char *hl_session_key
; /* history list */
1145 char *cl_session_key
; /* cookie list */
1146 char *fl_session_key
; /* favorites list */
1148 char work_dir
[PATH_MAX
];
1149 char certs_dir
[PATH_MAX
];
1150 char cache_dir
[PATH_MAX
];
1151 char sessions_dir
[PATH_MAX
];
1152 char cookie_file
[PATH_MAX
];
1153 SoupURI
*proxy_uri
= NULL
;
1154 SoupSession
*session
;
1155 SoupCookieJar
*s_cookiejar
;
1156 SoupCookieJar
*p_cookiejar
;
1157 char rc_fname
[PATH_MAX
];
1159 struct mime_type_list mtl
;
1160 struct alias_list aliases
;
1163 struct tab
*create_new_tab(char *, struct undo
*, int, int);
1164 void delete_tab(struct tab
*);
1165 void adjustfont_webkit(struct tab
*, int);
1166 int run_script(struct tab
*, char *);
1167 int download_rb_cmp(struct download
*, struct download
*);
1168 gboolean
cmd_execute(struct tab
*t
, char *str
);
1171 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1173 return (strcmp(h1
->uri
, h2
->uri
));
1175 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1178 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1180 return (strcmp(d1
->d
, d2
->d
));
1182 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1185 get_work_dir(struct settings
*s
)
1187 if (work_dir
[0] == '\0')
1189 return (g_strdup(work_dir
));
1193 set_work_dir(struct settings
*s
, char *val
)
1196 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1197 pwd
->pw_dir
, &val
[1]);
1199 strlcpy(work_dir
, val
, sizeof work_dir
);
1205 * generate a session key to secure xtp commands.
1206 * pass in a ptr to the key in question and it will
1207 * be modified in place.
1210 generate_xtp_session_key(char **key
)
1212 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1218 /* make a new one */
1219 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1220 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1221 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1222 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1224 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1228 * validate a xtp session key.
1232 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1234 if (strcmp(trusted
, untrusted
) != 0) {
1235 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1244 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1246 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1248 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1250 struct valid_url_types
{
1261 valid_url_type(char *url
)
1265 for (i
= 0; i
< LENGTH(vut
); i
++)
1266 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1273 print_cookie(char *msg
, SoupCookie
*c
)
1279 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1280 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1281 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1282 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1283 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1284 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1285 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1286 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1287 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1288 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1292 walk_alias(struct settings
*s
,
1293 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1298 if (s
== NULL
|| cb
== NULL
) {
1299 show_oops_s("walk_alias invalid parameters");
1303 TAILQ_FOREACH(a
, &aliases
, entry
) {
1304 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1305 cb(s
, str
, cb_args
);
1311 match_alias(char *url_in
)
1315 char *url_out
= NULL
, *search
, *enc_arg
;
1317 search
= g_strdup(url_in
);
1319 if (strsep(&arg
, " \t") == NULL
) {
1320 show_oops_s("match_alias: NULL URL");
1324 TAILQ_FOREACH(a
, &aliases
, entry
) {
1325 if (!strcmp(search
, a
->a_name
))
1330 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1333 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1334 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1337 url_out
= g_strdup(a
->a_uri
);
1345 guess_url_type(char *url_in
)
1348 char *url_out
= NULL
, *enc_search
= NULL
;
1350 url_out
= match_alias(url_in
);
1351 if (url_out
!= NULL
)
1356 * If there is no dot nor slash in the string and it isn't a
1357 * path to a local file and doesn't resolves to an IP, assume
1358 * that the user wants to search for the string.
1361 if (strchr(url_in
, '.') == NULL
&&
1362 strchr(url_in
, '/') == NULL
&&
1363 stat(url_in
, &sb
) != 0 &&
1364 gethostbyname(url_in
) == NULL
) {
1366 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1367 url_out
= g_strdup_printf(search_string
, enc_search
);
1373 /* XXX not sure about this heuristic */
1374 if (stat(url_in
, &sb
) == 0)
1375 url_out
= g_strdup_printf("file://%s", url_in
);
1377 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1379 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1385 load_uri(struct tab
*t
, gchar
*uri
)
1388 gchar
*newuri
= NULL
;
1394 /* Strip leading spaces. */
1395 while(*uri
&& isspace(*uri
))
1398 if (strlen(uri
) == 0) {
1403 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1404 for (i
= 0; i
< LENGTH(about_list
); i
++)
1405 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1406 bzero(&args
, sizeof args
);
1407 about_list
[i
].func(t
, &args
);
1408 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
),
1412 show_oops(t
, "invalid about page");
1416 if (valid_url_type(uri
)) {
1417 newuri
= guess_url_type(uri
);
1421 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1422 webkit_web_view_load_uri(t
->wv
, uri
);
1429 get_uri(WebKitWebView
*wv
)
1433 uri
= webkit_web_view_get_uri(wv
);
1435 if (uri
&& strlen(uri
) > 0)
1442 add_alias(struct settings
*s
, char *line
)
1445 struct alias
*a
= NULL
;
1447 if (s
== NULL
|| line
== NULL
) {
1448 show_oops_s("add_alias invalid parameters");
1453 a
= g_malloc(sizeof(*a
));
1455 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1456 show_oops_s("add_alias: incomplete alias definition");
1459 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1460 show_oops_s("add_alias: invalid alias definition");
1464 a
->a_name
= g_strdup(alias
);
1465 a
->a_uri
= g_strdup(l
);
1467 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1469 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1479 add_mime_type(struct settings
*s
, char *line
)
1483 struct mime_type
*m
= NULL
;
1484 int downloadfirst
= 0;
1486 /* XXX this could be smarter */
1488 if (line
== NULL
&& strlen(line
) == 0) {
1489 show_oops_s("add_mime_type invalid parameters");
1498 m
= g_malloc(sizeof(*m
));
1500 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1501 show_oops_s("add_mime_type: invalid mime_type");
1504 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1505 mime_type
[strlen(mime_type
) - 1] = '\0';
1510 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1511 show_oops_s("add_mime_type: invalid mime_type");
1515 m
->mt_type
= g_strdup(mime_type
);
1516 m
->mt_action
= g_strdup(l
);
1517 m
->mt_download
= downloadfirst
;
1519 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1520 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1522 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1532 find_mime_type(char *mime_type
)
1534 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1536 TAILQ_FOREACH(m
, &mtl
, entry
) {
1537 if (m
->mt_default
&&
1538 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1541 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1554 walk_mime_type(struct settings
*s
,
1555 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1557 struct mime_type
*m
;
1560 if (s
== NULL
|| cb
== NULL
)
1561 show_oops_s("walk_mime_type invalid parameters");
1563 TAILQ_FOREACH(m
, &mtl
, entry
) {
1564 str
= g_strdup_printf("%s%s --> %s",
1566 m
->mt_default
? "*" : "",
1568 cb(s
, str
, cb_args
);
1574 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1579 if (str
== NULL
|| wl
== NULL
|| strlen(str
) < 2)
1582 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1584 /* treat *.moo.com the same as .moo.com */
1585 if (str
[0] == '*' && str
[1] == '.')
1587 else if (str
[0] == '.')
1592 d
= g_malloc(sizeof *d
);
1594 d
->d
= g_strdup_printf(".%s", str
);
1596 d
->d
= g_strdup(str
);
1599 if (RB_INSERT(domain_list
, wl
, d
))
1602 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1613 add_cookie_wl(struct settings
*s
, char *entry
)
1615 wl_add(entry
, &c_wl
, 1);
1620 walk_cookie_wl(struct settings
*s
,
1621 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1625 if (s
== NULL
|| cb
== NULL
) {
1626 show_oops_s("walk_cookie_wl invalid parameters");
1630 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1631 cb(s
, d
->d
, cb_args
);
1635 walk_js_wl(struct settings
*s
,
1636 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1640 if (s
== NULL
|| cb
== NULL
) {
1641 show_oops_s("walk_js_wl invalid parameters");
1645 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1646 cb(s
, d
->d
, cb_args
);
1650 add_js_wl(struct settings
*s
, char *entry
)
1652 wl_add(entry
, &js_wl
, 1 /* persistent */);
1657 wl_find(const gchar
*search
, struct domain_list
*wl
)
1660 struct domain
*d
= NULL
, dfind
;
1663 if (search
== NULL
|| wl
== NULL
)
1665 if (strlen(search
) < 2)
1668 if (search
[0] != '.')
1669 s
= g_strdup_printf(".%s", search
);
1671 s
= g_strdup(search
);
1673 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1676 d
= RB_FIND(domain_list
, wl
, &dfind
);
1690 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1696 if (s
== NULL
|| wl
== NULL
)
1699 if (!strncmp(s
, "http://", strlen("http://")))
1700 s
= &s
[strlen("http://")];
1701 else if (!strncmp(s
, "https://", strlen("https://")))
1702 s
= &s
[strlen("https://")];
1707 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1708 /* chop string at first slash */
1709 if (s
[i
] == '/' || s
[i
] == '\0') {
1712 r
= wl_find(ss
, wl
);
1721 get_toplevel_domain(char *domain
)
1728 if (strlen(domain
) < 2)
1731 s
= &domain
[strlen(domain
) - 1];
1732 while (s
!= domain
) {
1748 settings_add(char *var
, char *val
)
1755 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
1756 if (strcmp(var
, rs
[i
].name
))
1760 if (rs
[i
].s
->set(&rs
[i
], val
))
1761 errx(1, "invalid value for %s: %s", var
, val
);
1765 switch (rs
[i
].type
) {
1774 errx(1, "invalid sval for %s",
1788 errx(1, "invalid type for %s", var
);
1797 config_parse(char *filename
, int runtime
)
1800 char *line
, *cp
, *var
, *val
;
1801 size_t len
, lineno
= 0;
1803 char file
[PATH_MAX
];
1806 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
1808 if (filename
== NULL
)
1811 if (runtime
&& runtime_settings
[0] != '\0') {
1812 snprintf(file
, sizeof file
, "%s/%s",
1813 work_dir
, runtime_settings
);
1814 if (stat(file
, &sb
)) {
1815 warnx("runtime file doesn't exist, creating it");
1816 if ((f
= fopen(file
, "w")) == NULL
)
1818 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
1822 strlcpy(file
, filename
, sizeof file
);
1824 if ((config
= fopen(file
, "r")) == NULL
) {
1825 warn("config_parse: cannot open %s", filename
);
1830 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
1831 if (feof(config
) || ferror(config
))
1835 cp
+= (long)strspn(cp
, WS
);
1836 if (cp
[0] == '\0') {
1842 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
1843 errx(1, "invalid config file entry: %s", line
);
1845 cp
+= (long)strspn(cp
, WS
);
1847 if ((val
= strsep(&cp
, "\0")) == NULL
)
1850 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
1851 handled
= settings_add(var
, val
);
1853 errx(1, "invalid conf file entry: %s=%s", var
, val
);
1862 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1868 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1872 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1875 JSStringGetUTF8CString(jsref
, s
, l
);
1876 JSStringRelease(jsref
);
1882 disable_hints(struct tab
*t
)
1884 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1885 bzero(t
->hint_num
, sizeof t
->hint_num
);
1886 run_script(t
, "vimprobable_clear()");
1888 t
->hint_mode
= XT_HINT_NONE
;
1892 enable_hints(struct tab
*t
)
1894 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1895 run_script(t
, "vimprobable_show_hints()");
1897 t
->hint_mode
= XT_HINT_NONE
;
1900 #define XT_JS_OPEN ("open;")
1901 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1902 #define XT_JS_FIRE ("fire;")
1903 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1904 #define XT_JS_FOUND ("found;")
1905 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1908 run_script(struct tab
*t
, char *s
)
1910 JSGlobalContextRef ctx
;
1911 WebKitWebFrame
*frame
;
1913 JSValueRef val
, exception
;
1916 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
1917 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1919 frame
= webkit_web_view_get_main_frame(t
->wv
);
1920 ctx
= webkit_web_frame_get_global_context(frame
);
1922 str
= JSStringCreateWithUTF8CString(s
);
1923 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1924 NULL
, 0, &exception
);
1925 JSStringRelease(str
);
1927 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
1929 es
= js_ref_to_string(ctx
, exception
);
1930 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
1934 es
= js_ref_to_string(ctx
, val
);
1935 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
1937 /* handle return value right here */
1938 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
1940 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
1943 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
1944 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
1945 &es
[XT_JS_FIRE_LEN
]);
1950 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
1951 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
1962 hint(struct tab
*t
, struct karg
*args
)
1965 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
1967 if (t
->hints_on
== 0)
1976 apply_style(struct tab
*t
)
1978 g_object_set(G_OBJECT(t
->settings
),
1979 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
1983 userstyle(struct tab
*t
, struct karg
*args
)
1985 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
1989 g_object_set(G_OBJECT(t
->settings
),
1990 "user-stylesheet-uri", NULL
, (char *)NULL
);
1999 * Doesn't work fully, due to the following bug:
2000 * https://bugs.webkit.org/show_bug.cgi?id=51747
2003 restore_global_history(void)
2005 char file
[PATH_MAX
];
2010 const char delim
[3] = {'\\', '\\', '\0'};
2012 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2014 if ((f
= fopen(file
, "r")) == NULL
) {
2015 warnx("%s: fopen", __func__
);
2020 if ((uri
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2021 if (feof(f
) || ferror(f
))
2024 if ((title
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2025 if (feof(f
) || ferror(f
)) {
2027 warnx("%s: broken history file\n", __func__
);
2031 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
2032 webkit_web_history_item_new_with_data(uri
, title
);
2033 h
= g_malloc(sizeof(struct history
));
2034 h
->uri
= g_strdup(uri
);
2035 h
->title
= g_strdup(title
);
2036 RB_INSERT(history_list
, &hl
, h
);
2037 completion_add_uri(h
->uri
);
2039 warnx("%s: failed to restore history\n", __func__
);
2055 save_global_history_to_disk(struct tab
*t
)
2057 char file
[PATH_MAX
];
2061 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2063 if ((f
= fopen(file
, "w")) == NULL
) {
2064 show_oops(t
, "%s: global history file: %s",
2065 __func__
, strerror(errno
));
2069 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2070 if (h
->uri
&& h
->title
)
2071 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2080 quit(struct tab
*t
, struct karg
*args
)
2082 if (save_global_history
)
2083 save_global_history_to_disk(t
);
2091 open_tabs(struct tab
*t
, struct karg
*a
)
2093 char file
[PATH_MAX
];
2097 struct tab
*ti
, *tt
;
2102 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2103 if ((f
= fopen(file
, "r")) == NULL
)
2106 ti
= TAILQ_LAST(&tabs
, tab_list
);
2109 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2110 if (feof(f
) || ferror(f
))
2113 /* retrieve session name */
2114 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2115 strlcpy(named_session
,
2116 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2117 sizeof named_session
);
2121 if (uri
&& strlen(uri
))
2122 create_new_tab(uri
, NULL
, 1, -1);
2128 /* close open tabs */
2129 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2131 tt
= TAILQ_FIRST(&tabs
);
2150 restore_saved_tabs(void)
2152 char file
[PATH_MAX
];
2153 int unlink_file
= 0;
2158 snprintf(file
, sizeof file
, "%s/%s",
2159 sessions_dir
, XT_RESTART_TABS_FILE
);
2160 if (stat(file
, &sb
) == -1)
2161 a
.s
= XT_SAVED_TABS_FILE
;
2164 a
.s
= XT_RESTART_TABS_FILE
;
2167 a
.i
= XT_SES_DONOTHING
;
2168 rv
= open_tabs(NULL
, &a
);
2177 save_tabs(struct tab
*t
, struct karg
*a
)
2179 char file
[PATH_MAX
];
2184 const gchar
**arr
= NULL
;
2189 snprintf(file
, sizeof file
, "%s/%s",
2190 sessions_dir
, named_session
);
2192 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2194 if ((f
= fopen(file
, "w")) == NULL
) {
2195 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2199 /* save session name */
2200 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2202 /* save tabs, in the order they are arranged in the notebook */
2203 TAILQ_FOREACH(ti
, &tabs
, entry
)
2206 arr
= g_malloc0(len
* sizeof(gchar
*));
2208 TAILQ_FOREACH(ti
, &tabs
, entry
) {
2209 if ((uri
= get_uri(ti
->wv
)) != NULL
)
2210 arr
[gtk_notebook_page_num(notebook
, ti
->vbox
)] = uri
;
2213 for (i
= 0; i
< len
; i
++)
2215 fprintf(f
, "%s\n", arr
[i
]);
2218 /* try and make sure this gets to disk NOW. XXX Backup first? */
2219 if (fflush(f
) != 0 || fsync(fileno(f
)) != 0) {
2220 show_oops(t
, "May not have managed to save session: %s",
2230 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2242 yank_uri(struct tab
*t
, struct karg
*args
)
2245 GtkClipboard
*clipboard
;
2247 if ((uri
= get_uri(t
->wv
)) == NULL
)
2250 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2251 gtk_clipboard_set_text(clipboard
, uri
, -1);
2257 paste_uri(struct tab
*t
, struct karg
*args
)
2259 GtkClipboard
*clipboard
;
2260 GdkAtom atom
= gdk_atom_intern("CUT_BUFFER0", FALSE
);
2262 gchar
*p
= NULL
, *uri
;
2264 /* try primary clipboard first */
2265 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2266 p
= gtk_clipboard_wait_for_text(clipboard
);
2268 /* if it failed get whatever text is in cut_buffer0 */
2270 if (gdk_property_get(gdk_get_default_root_window(),
2272 gdk_atom_intern("STRING", FALSE
),
2274 65536 /* picked out of my butt */,
2280 /* yes sir, we need to NUL the string */
2286 while(*uri
&& isspace(*uri
))
2288 if (strlen(uri
) == 0) {
2289 show_oops(t
, "empty paste buffer");
2292 if (valid_url_type(uri
)) {
2293 /* we can be clever and paste this in search box */
2294 show_oops(t
, "not a valid URL");
2298 if (args
->i
== XT_PASTE_CURRENT_TAB
)
2300 else if (args
->i
== XT_PASTE_NEW_TAB
)
2301 create_new_tab(uri
, NULL
, 1, -1);
2312 find_domain(const gchar
*s
, int add_dot
)
2315 char *r
= NULL
, *ss
= NULL
;
2320 if (!strncmp(s
, "http://", strlen("http://")))
2321 s
= &s
[strlen("http://")];
2322 else if (!strncmp(s
, "https://", strlen("https://")))
2323 s
= &s
[strlen("https://")];
2329 for (i
= 0; i
< strlen(ss
) + 1 /* yes er need this */; i
++)
2330 /* chop string at first slash */
2331 if (ss
[i
] == '/' || ss
[i
] == '\0') {
2334 r
= g_strdup_printf(".%s", ss
);
2345 toggle_cwl(struct tab
*t
, struct karg
*args
)
2349 char *dom
= NULL
, *dom_toggle
= NULL
;
2355 uri
= get_uri(t
->wv
);
2356 dom
= find_domain(uri
, 1);
2357 d
= wl_find(dom
, &c_wl
);
2364 if (args
->i
& XT_WL_TOGGLE
)
2366 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2368 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2371 if (args
->i
& XT_WL_TOPLEVEL
)
2372 dom_toggle
= get_toplevel_domain(dom
);
2377 /* enable cookies for domain */
2378 wl_add(dom_toggle
, &c_wl
, 0);
2380 /* disable cookies for domain */
2381 RB_REMOVE(domain_list
, &c_wl
, d
);
2383 webkit_web_view_reload(t
->wv
);
2390 toggle_js(struct tab
*t
, struct karg
*args
)
2395 char *dom
= NULL
, *dom_toggle
= NULL
;
2400 g_object_get(G_OBJECT(t
->settings
),
2401 "enable-scripts", &es
, (char *)NULL
);
2402 if (args
->i
& XT_WL_TOGGLE
)
2404 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2406 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2411 uri
= get_uri(t
->wv
);
2412 dom
= find_domain(uri
, 1);
2414 if (uri
== NULL
|| dom
== NULL
) {
2415 show_oops(t
, "Can't toggle domain in JavaScript white list");
2419 if (args
->i
& XT_WL_TOPLEVEL
)
2420 dom_toggle
= get_toplevel_domain(dom
);
2425 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2426 wl_add(dom_toggle
, &js_wl
, 0 /* session */);
2428 d
= wl_find(dom_toggle
, &js_wl
);
2430 RB_REMOVE(domain_list
, &js_wl
, d
);
2431 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2433 g_object_set(G_OBJECT(t
->settings
),
2434 "enable-scripts", es
, (char *)NULL
);
2435 g_object_set(G_OBJECT(t
->settings
),
2436 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2437 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2438 webkit_web_view_reload(t
->wv
);
2446 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2450 a
.i
= XT_WL_TOGGLE
| XT_WL_FQDN
;
2455 toggle_src(struct tab
*t
, struct karg
*args
)
2462 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2463 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2464 webkit_web_view_reload(t
->wv
);
2470 focus_webview(struct tab
*t
)
2475 /* only grab focus if we are visible */
2476 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2477 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2481 focus(struct tab
*t
, struct karg
*args
)
2483 if (t
== NULL
|| args
== NULL
)
2489 if (args
->i
== XT_FOCUS_URI
)
2490 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2491 else if (args
->i
== XT_FOCUS_SEARCH
)
2492 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2498 stats(struct tab
*t
, struct karg
*args
)
2500 char *page
, *body
, *s
, line
[64 * 1024];
2501 uint64_t line_count
= 0;
2505 show_oops_s("stats invalid parameters");
2508 if (save_rejected_cookies
) {
2509 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2511 s
= fgets(line
, sizeof line
, r_cookie_f
);
2512 if (s
== NULL
|| feof(r_cookie_f
) ||
2518 snprintf(line
, sizeof line
,
2519 "<br/>Cookies blocked(*) total: %llu", line_count
);
2521 show_oops(t
, "Can't open blocked cookies file: %s",
2525 body
= g_strdup_printf(
2526 "Cookies blocked(*) this session: %llu"
2528 "<p><small><b>*</b> results vary based on settings</small></p>",
2532 page
= get_html_page("Statistics", body
, "", 0);
2535 load_webkit_string(t
, page
, XT_URI_ABOUT_STATS
);
2542 marco(struct tab
*t
, struct karg
*args
)
2544 char *page
, line
[64 * 1024];
2548 show_oops_s("marco invalid parameters");
2551 snprintf(line
, sizeof line
, "%s", marco_message(&len
));
2553 page
= get_html_page("Marco Sez...", line
, "", 0);
2555 load_webkit_string(t
, page
, XT_URI_ABOUT_MARCO
);
2562 blank(struct tab
*t
, struct karg
*args
)
2565 show_oops_s("blank invalid parameters");
2567 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2572 about(struct tab
*t
, struct karg
*args
)
2577 show_oops_s("about invalid parameters");
2579 body
= g_strdup_printf("<b>Version: %s</b><p>"
2582 "<li>Marco Peereboom <marco@peereboom.us></li>"
2583 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2584 "<li>Edd Barrett <vext01@gmail.com> </li>"
2585 "<li>Todd T. Fries <todd@fries.net> </li>"
2586 "<li>Raphael Graf <r@undefined.ch> </li>"
2588 "Copyrights and licenses can be found on the XXXterm "
2589 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2593 page
= get_html_page("About", body
, "", 0);
2596 load_webkit_string(t
, page
, XT_URI_ABOUT_ABOUT
);
2603 help(struct tab
*t
, struct karg
*args
)
2605 char *page
, *head
, *body
;
2608 show_oops_s("help invalid parameters");
2610 head
= "<meta http-equiv=\"REFRESH\" content=\"0;"
2611 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2613 body
= "XXXterm man page <a href=\"http://opensource.conformal.com/"
2614 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2615 "cgi-bin/man-cgi?xxxterm</a>";
2617 page
= get_html_page("XXXterm", body
, head
, FALSE
);
2619 load_webkit_string(t
, page
, XT_URI_ABOUT_HELP
);
2626 * update all favorite tabs apart from one. Pass NULL if
2627 * you want to update all.
2630 update_favorite_tabs(struct tab
*apart_from
)
2633 if (!updating_fl_tabs
) {
2634 updating_fl_tabs
= 1; /* stop infinite recursion */
2635 TAILQ_FOREACH(t
, &tabs
, entry
)
2636 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2637 && (t
!= apart_from
))
2638 xtp_page_fl(t
, NULL
);
2639 updating_fl_tabs
= 0;
2643 /* show a list of favorites (bookmarks) */
2645 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2647 char file
[PATH_MAX
];
2649 char *uri
= NULL
, *title
= NULL
;
2650 size_t len
, lineno
= 0;
2652 char *body
, *tmp
, *page
= NULL
;
2653 const char delim
[3] = {'\\', '\\', '\0'};
2655 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2658 warn("%s: bad param", __func__
);
2660 /* mark tab as favorite list */
2661 t
->xtp_meaning
= XT_XTP_TAB_MEANING_FL
;
2663 /* new session key */
2664 if (!updating_fl_tabs
)
2665 generate_xtp_session_key(&fl_session_key
);
2667 /* open favorites */
2668 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
2669 if ((f
= fopen(file
, "r")) == NULL
) {
2670 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2675 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
2676 "<th style='width: 40px'>#</th><th>Link</th>"
2677 "<th style='width: 40px'>Rm</th></tr>\n");
2680 if ((title
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
2681 if (feof(f
) || ferror(f
))
2689 if ((uri
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
2690 if (feof(f
) || ferror(f
)) {
2691 show_oops(t
, "favorites file corrupt");
2697 body
= g_strdup_printf("%s<tr>"
2699 "<td><a href='%s'>%s</a></td>"
2700 "<td style='text-align: center'>"
2701 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2703 body
, i
, uri
, title
,
2704 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2716 /* if none, say so */
2719 body
= g_strdup_printf("%s<tr>"
2720 "<td colspan='3' style='text-align: center'>"
2721 "No favorites - To add one use the 'favadd' command."
2722 "</td></tr>", body
);
2727 body
= g_strdup_printf("%s</table>", body
);
2737 page
= get_html_page("Favorites", body
, "", 1);
2738 load_webkit_string(t
, page
, XT_URI_ABOUT_FAVORITES
);
2742 update_favorite_tabs(t
);
2751 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2752 size_t cert_count
, char *title
)
2754 gnutls_datum_t cinfo
;
2758 body
= g_strdup("");
2760 for (i
= 0; i
< cert_count
; i
++) {
2761 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2766 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2767 body
, i
, cinfo
.data
);
2768 gnutls_free(cinfo
.data
);
2772 tmp
= get_html_page(title
, body
, "", 0);
2775 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
2780 ca_cmd(struct tab
*t
, struct karg
*args
)
2783 int rv
= 1, certs
= 0, certs_read
;
2786 gnutls_x509_crt_t
*c
= NULL
;
2787 char *certs_buf
= NULL
, *s
;
2789 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
2790 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
2794 if (fstat(fileno(f
), &sb
) == -1) {
2795 show_oops(t
, "Can't stat CA file: %s", ssl_ca_file
);
2799 certs_buf
= g_malloc(sb
.st_size
+ 1);
2800 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
2801 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
2804 certs_buf
[sb
.st_size
] = '\0';
2807 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
2809 s
+= strlen("BEGIN CERTIFICATE");
2812 bzero(&dt
, sizeof dt
);
2813 dt
.data
= certs_buf
;
2814 dt
.size
= sb
.st_size
;
2815 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
2816 certs_read
= gnutls_x509_crt_list_import(c
, &certs
, &dt
,
2817 GNUTLS_X509_FMT_PEM
, 0);
2818 if (certs_read
<= 0) {
2819 show_oops(t
, "No cert(s) available");
2822 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
2835 connect_socket_from_uri(const gchar
*uri
, char *domain
, size_t domain_sz
)
2838 struct addrinfo hints
, *res
= NULL
, *ai
;
2842 if (uri
&& !g_str_has_prefix(uri
, "https://"))
2845 su
= soup_uri_new(uri
);
2848 if (!SOUP_URI_VALID_FOR_HTTP(su
))
2851 snprintf(port
, sizeof port
, "%d", su
->port
);
2852 bzero(&hints
, sizeof(struct addrinfo
));
2853 hints
.ai_flags
= AI_CANONNAME
;
2854 hints
.ai_family
= AF_UNSPEC
;
2855 hints
.ai_socktype
= SOCK_STREAM
;
2857 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
2860 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
2861 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
2864 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
2867 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
2871 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
2876 strlcpy(domain
, su
->host
, domain_sz
);
2887 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
2890 gnutls_deinit(gsession
);
2892 gnutls_certificate_free_credentials(xcred
);
2898 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
2899 gnutls_certificate_credentials_t
*xc
)
2901 gnutls_certificate_credentials_t xcred
;
2902 gnutls_session_t gsession
;
2905 if (gs
== NULL
|| xc
== NULL
)
2911 gnutls_certificate_allocate_credentials(&xcred
);
2912 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
2913 GNUTLS_X509_FMT_PEM
);
2914 gnutls_init(&gsession
, GNUTLS_CLIENT
);
2915 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
2916 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
2917 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
2918 if ((rv
= gnutls_handshake(gsession
)) < 0) {
2919 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
2921 gnutls_error_is_fatal(rv
),
2922 gnutls_strerror_name(rv
));
2923 stop_tls(gsession
, xcred
);
2927 gnutls_credentials_type_t cred
;
2928 cred
= gnutls_auth_get_type(gsession
);
2929 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
2930 stop_tls(gsession
, xcred
);
2942 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
2946 const gnutls_datum_t
*cl
;
2947 gnutls_x509_crt_t
*all_certs
;
2950 if (certs
== NULL
|| cert_count
== NULL
)
2952 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
2954 cl
= gnutls_certificate_get_peers(gsession
, &len
);
2958 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
2959 for (i
= 0; i
< len
; i
++) {
2960 gnutls_x509_crt_init(&all_certs
[i
]);
2961 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
2962 GNUTLS_X509_FMT_PEM
< 0)) {
2976 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
2980 for (i
= 0; i
< cert_count
; i
++)
2981 gnutls_x509_crt_deinit(certs
[i
]);
2986 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2987 size_t cert_count
, char *domain
)
2990 char cert_buf
[64 * 1024], file
[PATH_MAX
];
2995 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
2998 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
2999 if ((f
= fopen(file
, "w")) == NULL
) {
3000 show_oops(t
, "Can't create cert file %s %s",
3001 file
, strerror(errno
));
3005 for (i
= 0; i
< cert_count
; i
++) {
3006 cert_buf_sz
= sizeof cert_buf
;
3007 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3008 cert_buf
, &cert_buf_sz
)) {
3009 show_oops(t
, "gnutls_x509_crt_export failed");
3012 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3013 show_oops(t
, "Can't write certs: %s", strerror(errno
));
3018 /* not the best spot but oh well */
3019 gdk_color_parse("lightblue", &color
);
3020 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3021 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
3022 gdk_color_parse(XT_COLOR_BLACK
, &color
);
3023 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
3029 load_compare_cert(struct tab
*t
, struct karg
*args
)
3032 char domain
[8182], file
[PATH_MAX
];
3033 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
3034 int s
= -1, rv
= 1, i
;
3038 gnutls_session_t gsession
;
3039 gnutls_x509_crt_t
*certs
;
3040 gnutls_certificate_credentials_t xcred
;
3045 if ((uri
= get_uri(t
->wv
)) == NULL
)
3048 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
3052 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3053 show_oops(t
, "Start TLS failed");
3058 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3059 show_oops(t
, "Can't get connection certificates");
3063 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3064 if ((f
= fopen(file
, "r")) == NULL
)
3067 for (i
= 0; i
< cert_count
; i
++) {
3068 cert_buf_sz
= sizeof cert_buf
;
3069 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3070 cert_buf
, &cert_buf_sz
)) {
3073 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3074 rv
= -1; /* critical */
3077 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3078 rv
= -1; /* critical */
3087 free_connection_certs(certs
, cert_count
);
3089 /* we close the socket first for speed */
3092 stop_tls(gsession
, xcred
);
3098 cert_cmd(struct tab
*t
, struct karg
*args
)
3104 gnutls_session_t gsession
;
3105 gnutls_x509_crt_t
*certs
;
3106 gnutls_certificate_credentials_t xcred
;
3111 if (ssl_ca_file
== NULL
) {
3112 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
3116 if ((uri
= get_uri(t
->wv
)) == NULL
) {
3117 show_oops(t
, "Invalid URI");
3121 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
3122 show_oops(t
, "Invalid certificate URI: %s", uri
);
3127 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3128 show_oops(t
, "Start TLS failed");
3133 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3134 show_oops(t
, "get_connection_certs failed");
3138 if (args
->i
& XT_SHOW
)
3139 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3140 else if (args
->i
& XT_SAVE
)
3141 save_certs(t
, certs
, cert_count
, domain
);
3143 free_connection_certs(certs
, cert_count
);
3145 /* we close the socket first for speed */
3148 stop_tls(gsession
, xcred
);
3154 remove_cookie(int index
)
3160 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3162 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3164 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3168 print_cookie("remove cookie", c
);
3169 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3174 soup_cookies_free(cf
);
3180 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3185 body
= g_strdup("");
3188 if (args
->i
& XT_WL_PERSISTENT
) {
3190 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3192 RB_FOREACH(d
, domain_list
, wl
) {
3196 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3202 if (args
->i
& XT_WL_SESSION
) {
3204 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3206 RB_FOREACH(d
, domain_list
, wl
) {
3210 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3215 tmp
= get_html_page(title
, body
, "", 0);
3218 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3220 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3226 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3228 char file
[PATH_MAX
];
3230 char *line
= NULL
, *lt
= NULL
;
3233 char *dom
= NULL
, *dom_save
= NULL
;
3239 if (t
== NULL
|| args
== NULL
)
3242 if (runtime_settings
[0] == '\0')
3245 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3246 if ((f
= fopen(file
, "r+")) == NULL
)
3249 uri
= get_uri(t
->wv
);
3250 dom
= find_domain(uri
, 1);
3251 if (uri
== NULL
|| dom
== NULL
) {
3252 show_oops(t
, "Can't add domain to %s white list",
3253 js
? "JavaScript" : "cookie");
3257 if (args
->i
& XT_WL_TOPLEVEL
) {
3259 if ((dom_save
= get_toplevel_domain(dom
)) == NULL
) {
3260 show_oops(t
, "invalid domain: %s", dom
);
3263 } else if (args
->i
& XT_WL_FQDN
) {
3269 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom_save
);
3272 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3275 if (!strcmp(line
, lt
))
3281 fprintf(f
, "%s\n", lt
);
3286 d
= wl_find(dom_save
, &js_wl
);
3288 settings_add("js_wl", dom_save
);
3289 d
= wl_find(dom_save
, &js_wl
);
3293 d
= wl_find(dom_save
, &c_wl
);
3295 settings_add("cookie_wl", dom_save
);
3296 d
= wl_find(dom_save
, &c_wl
);
3300 /* find and add to persistent jar */
3301 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3302 for (;cf
; cf
= cf
->next
) {
3304 if (!strcmp(dom_save
, ci
->domain
) ||
3305 !strcmp(&dom_save
[1], ci
->domain
)) /* deal with leading . */ {
3306 c
= soup_cookie_copy(ci
);
3307 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3310 soup_cookies_free(cf
);
3328 js_show_wl(struct tab
*t
, struct karg
*args
)
3330 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3331 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3337 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3339 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3340 wl_show(t
, args
, "Cookie White List", &c_wl
);
3346 cookie_cmd(struct tab
*t
, struct karg
*args
)
3348 if (args
->i
& XT_SHOW
)
3349 wl_show(t
, args
, "Cookie White List", &c_wl
);
3350 else if (args
->i
& XT_WL_TOGGLE
)
3351 toggle_cwl(t
, args
);
3352 else if (args
->i
& XT_SAVE
)
3353 wl_save(t
, args
, 0);
3354 else if (args
->i
& XT_DELETE
)
3355 show_oops(t
, "'cookie delete' currently unimplemented");
3361 js_cmd(struct tab
*t
, struct karg
*args
)
3363 if (args
->i
& XT_SHOW
)
3364 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3365 else if (args
->i
& XT_SAVE
)
3366 wl_save(t
, args
, 1);
3367 else if (args
->i
& XT_WL_TOGGLE
)
3369 else if (args
->i
& XT_DELETE
)
3370 show_oops(t
, "'js delete' currently unimplemented");
3376 add_favorite(struct tab
*t
, struct karg
*args
)
3378 char file
[PATH_MAX
];
3381 size_t urilen
, linelen
;
3382 const gchar
*uri
, *title
;
3387 /* don't allow adding of xtp pages to favorites */
3388 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3389 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3393 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3394 if ((f
= fopen(file
, "r+")) == NULL
) {
3395 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3399 title
= webkit_web_view_get_title(t
->wv
);
3400 uri
= get_uri(t
->wv
);
3405 if (title
== NULL
|| uri
== NULL
) {
3406 show_oops(t
, "can't add page to favorites");
3410 urilen
= strlen(uri
);
3413 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3414 if (feof(f
) || ferror(f
))
3417 if (linelen
== urilen
&& !strcmp(line
, uri
))
3424 fprintf(f
, "\n%s\n%s", title
, uri
);
3430 update_favorite_tabs(NULL
);
3436 navaction(struct tab
*t
, struct karg
*args
)
3438 WebKitWebHistoryItem
*item
;
3440 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3441 t
->tab_id
, args
->i
);
3444 if (args
->i
== XT_NAV_BACK
)
3445 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3447 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3449 return (XT_CB_PASSTHROUGH
);
3450 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
3452 return (XT_CB_PASSTHROUGH
);
3457 webkit_web_view_go_back(t
->wv
);
3459 case XT_NAV_FORWARD
:
3460 webkit_web_view_go_forward(t
->wv
);
3463 webkit_web_view_reload(t
->wv
);
3465 case XT_NAV_RELOAD_CACHE
:
3466 webkit_web_view_reload_bypass_cache(t
->wv
);
3469 return (XT_CB_PASSTHROUGH
);
3473 move(struct tab
*t
, struct karg
*args
)
3475 GtkAdjustment
*adjust
;
3476 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3481 case XT_MOVE_BOTTOM
:
3483 case XT_MOVE_PAGEDOWN
:
3484 case XT_MOVE_PAGEUP
:
3485 case XT_MOVE_HALFDOWN
:
3486 case XT_MOVE_HALFUP
:
3487 adjust
= t
->adjust_v
;
3490 adjust
= t
->adjust_h
;
3494 pos
= gtk_adjustment_get_value(adjust
);
3495 ps
= gtk_adjustment_get_page_size(adjust
);
3496 upper
= gtk_adjustment_get_upper(adjust
);
3497 lower
= gtk_adjustment_get_lower(adjust
);
3498 si
= gtk_adjustment_get_step_increment(adjust
);
3499 pi
= gtk_adjustment_get_page_increment(adjust
);
3502 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3503 "max %f si %f pi %f\n",
3504 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
3505 pos
, ps
, upper
, lower
, max
, si
, pi
);
3511 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3516 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3518 case XT_MOVE_BOTTOM
:
3519 case XT_MOVE_FARRIGHT
:
3520 gtk_adjustment_set_value(adjust
, max
);
3523 case XT_MOVE_FARLEFT
:
3524 gtk_adjustment_set_value(adjust
, lower
);
3526 case XT_MOVE_PAGEDOWN
:
3528 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3530 case XT_MOVE_PAGEUP
:
3532 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3534 case XT_MOVE_HALFDOWN
:
3536 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3538 case XT_MOVE_HALFUP
:
3540 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3543 return (XT_CB_PASSTHROUGH
);
3546 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3548 return (XT_CB_HANDLED
);
3552 url_set_visibility(void)
3556 TAILQ_FOREACH(t
, &tabs
, entry
) {
3557 if (show_url
== 0) {
3558 gtk_widget_hide(t
->toolbar
);
3561 gtk_widget_show(t
->toolbar
);
3566 notebook_tab_set_visibility(GtkNotebook
*notebook
)
3569 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3571 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3575 statusbar_set_visibility(void)
3579 TAILQ_FOREACH(t
, &tabs
, entry
) {
3580 if (show_statusbar
== 0) {
3581 gtk_widget_hide(t
->statusbar
);
3584 gtk_widget_show(t
->statusbar
);
3589 url_set(struct tab
*t
, int enable_url_entry
)
3594 show_url
= enable_url_entry
;
3596 if (enable_url_entry
) {
3597 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
3598 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3599 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
), 0);
3601 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
3602 GTK_ENTRY_ICON_PRIMARY
);
3604 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
3605 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
3606 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
3607 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
3613 fullscreen(struct tab
*t
, struct karg
*args
)
3615 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3618 return (XT_CB_PASSTHROUGH
);
3620 if (show_url
== 0) {
3628 url_set_visibility();
3629 notebook_tab_set_visibility(notebook
);
3631 return (XT_CB_HANDLED
);
3635 statusaction(struct tab
*t
, struct karg
*args
)
3637 int rv
= XT_CB_HANDLED
;
3639 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3642 return (XT_CB_PASSTHROUGH
);
3645 case XT_STATUSBAR_SHOW
:
3646 if (show_statusbar
== 0) {
3648 statusbar_set_visibility();
3651 case XT_STATUSBAR_HIDE
:
3652 if (show_statusbar
== 1) {
3654 statusbar_set_visibility();
3662 urlaction(struct tab
*t
, struct karg
*args
)
3664 int rv
= XT_CB_HANDLED
;
3666 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3669 return (XT_CB_PASSTHROUGH
);
3673 if (show_url
== 0) {
3675 url_set_visibility();
3679 if (show_url
== 1) {
3681 url_set_visibility();
3689 tabaction(struct tab
*t
, struct karg
*args
)
3691 int rv
= XT_CB_HANDLED
;
3692 char *url
= args
->s
;
3696 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
3699 return (XT_CB_PASSTHROUGH
);
3703 if (strlen(url
) > 0)
3704 create_new_tab(url
, NULL
, 1, args
->p
);
3706 create_new_tab(NULL
, NULL
, 1, args
->p
);
3712 TAILQ_FOREACH(tt
, &tabs
, entry
)
3713 if (tt
->tab_id
== args
->p
- 1) {
3719 case XT_TAB_DELQUIT
:
3720 if (gtk_notebook_get_n_pages(notebook
) > 1)
3726 if (strlen(url
) > 0)
3729 rv
= XT_CB_PASSTHROUGH
;
3735 if (show_tabs
== 0) {
3737 notebook_tab_set_visibility(notebook
);
3741 if (show_tabs
== 1) {
3743 notebook_tab_set_visibility(notebook
);
3746 case XT_TAB_UNDO_CLOSE
:
3747 if (undo_count
== 0) {
3748 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3752 u
= TAILQ_FIRST(&undos
);
3753 create_new_tab(u
->uri
, u
, 1, -1);
3755 TAILQ_REMOVE(&undos
, u
, entry
);
3757 /* u->history is freed in create_new_tab() */
3762 rv
= XT_CB_PASSTHROUGH
;
3776 resizetab(struct tab
*t
, struct karg
*args
)
3778 if (t
== NULL
|| args
== NULL
) {
3779 show_oops_s("resizetab invalid parameters");
3780 return (XT_CB_PASSTHROUGH
);
3783 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
3784 t
->tab_id
, args
->i
);
3786 adjustfont_webkit(t
, args
->i
);
3788 return (XT_CB_HANDLED
);
3792 movetab(struct tab
*t
, struct karg
*args
)
3796 if (t
== NULL
|| args
== NULL
) {
3797 show_oops_s("movetab invalid parameters");
3798 return (XT_CB_PASSTHROUGH
);
3801 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
3802 t
->tab_id
, args
->i
);
3804 if (args
->i
>= XT_TAB_INVALID
)
3805 return (XT_CB_PASSTHROUGH
);
3807 if (TAILQ_EMPTY(&tabs
))
3808 return (XT_CB_PASSTHROUGH
);
3810 n
= gtk_notebook_get_n_pages(notebook
);
3811 dest
= gtk_notebook_get_current_page(notebook
);
3816 dest
= dest
== n
- 1 ? 0 : dest
+ 1;
3825 dest
-= args
->p
% n
;
3838 return (XT_CB_PASSTHROUGH
);
3841 if (dest
< 0 || dest
>= n
)
3842 return (XT_CB_PASSTHROUGH
);
3843 if (t
->tab_id
== dest
) {
3844 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
3845 return (XT_CB_HANDLED
);
3848 gtk_notebook_set_current_page(notebook
, dest
);
3850 return (XT_CB_HANDLED
);
3856 command(struct tab
*t
, struct karg
*args
)
3858 char *s
= NULL
, *ss
= NULL
;
3862 if (t
== NULL
|| args
== NULL
) {
3863 show_oops_s("command invalid parameters");
3864 return (XT_CB_PASSTHROUGH
);
3875 if (cmd_prefix
== 0)
3878 ss
= g_strdup_printf(":%d", cmd_prefix
);
3889 case XT_CMD_OPEN_CURRENT
:
3892 case XT_CMD_TABNEW_CURRENT
:
3893 if (!s
) /* FALL THROUGH? */
3895 if ((uri
= get_uri(t
->wv
)) != NULL
) {
3896 ss
= g_strdup_printf("%s%s", s
, uri
);
3901 show_oops(t
, "command: invalid opcode %d", args
->i
);
3902 return (XT_CB_PASSTHROUGH
);
3905 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
3907 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
3908 gdk_color_parse(XT_COLOR_WHITE
, &color
);
3909 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
3911 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
3912 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
3917 return (XT_CB_HANDLED
);
3921 * Return a new string with a download row (in html)
3922 * appended. Old string is freed.
3925 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
3928 WebKitDownloadStatus stat
;
3929 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
3931 char cur_sz
[FMT_SCALED_STRSIZE
];
3932 char tot_sz
[FMT_SCALED_STRSIZE
];
3935 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
3937 /* All actions wil take this form:
3938 * xxxt://class/seskey
3940 xtp_prefix
= g_strdup_printf("%s%d/%s/",
3941 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
3943 stat
= webkit_download_get_status(dl
->download
);
3946 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
3947 status_html
= g_strdup_printf("Finished");
3948 cmd_html
= g_strdup_printf(
3949 "<a href='%s%d/%d'>Remove</a>",
3950 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3952 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
3953 /* gather size info */
3954 progress
= 100 * webkit_download_get_progress(dl
->download
);
3957 webkit_download_get_current_size(dl
->download
), cur_sz
);
3959 webkit_download_get_total_size(dl
->download
), tot_sz
);
3961 status_html
= g_strdup_printf(
3962 "<div style='width: 100%%' align='center'>"
3963 "<div class='progress-outer'>"
3964 "<div class='progress-inner' style='width: %.2f%%'>"
3965 "</div></div></div>"
3966 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3967 progress
, cur_sz
, tot_sz
, progress
);
3969 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3970 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3974 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
3975 status_html
= g_strdup_printf("Cancelled");
3976 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3977 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3979 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
3980 status_html
= g_strdup_printf("Error!");
3981 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3982 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3984 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
3985 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3986 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3987 status_html
= g_strdup_printf("Starting");
3990 show_oops(t
, "%s: unknown download status", __func__
);
3993 new_html
= g_strdup_printf(
3994 "%s\n<tr><td>%s</td><td>%s</td>"
3995 "<td style='text-align:center'>%s</td></tr>\n",
3996 html
, basename(webkit_download_get_destination_uri(dl
->download
)),
3997 status_html
, cmd_html
);
4001 g_free(status_html
);
4012 * update all download tabs apart from one. Pass NULL if
4013 * you want to update all.
4016 update_download_tabs(struct tab
*apart_from
)
4019 if (!updating_dl_tabs
) {
4020 updating_dl_tabs
= 1; /* stop infinite recursion */
4021 TAILQ_FOREACH(t
, &tabs
, entry
)
4022 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
4023 && (t
!= apart_from
))
4024 xtp_page_dl(t
, NULL
);
4025 updating_dl_tabs
= 0;
4030 * update all cookie tabs apart from one. Pass NULL if
4031 * you want to update all.
4034 update_cookie_tabs(struct tab
*apart_from
)
4037 if (!updating_cl_tabs
) {
4038 updating_cl_tabs
= 1; /* stop infinite recursion */
4039 TAILQ_FOREACH(t
, &tabs
, entry
)
4040 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
4041 && (t
!= apart_from
))
4042 xtp_page_cl(t
, NULL
);
4043 updating_cl_tabs
= 0;
4048 * update all history tabs apart from one. Pass NULL if
4049 * you want to update all.
4052 update_history_tabs(struct tab
*apart_from
)
4056 if (!updating_hl_tabs
) {
4057 updating_hl_tabs
= 1; /* stop infinite recursion */
4058 TAILQ_FOREACH(t
, &tabs
, entry
)
4059 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4060 && (t
!= apart_from
))
4061 xtp_page_hl(t
, NULL
);
4062 updating_hl_tabs
= 0;
4066 /* cookie management XTP page */
4068 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4070 char *body
, *page
, *tmp
;
4071 int i
= 1; /* all ids start 1 */
4072 GSList
*sc
, *pc
, *pc_start
;
4074 char *type
, *table_headers
;
4075 char *last_domain
= strdup("");
4077 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4080 show_oops_s("%s invalid parameters", __func__
);
4083 /* mark this tab as cookie jar */
4084 t
->xtp_meaning
= XT_XTP_TAB_MEANING_CL
;
4086 /* Generate a new session key */
4087 if (!updating_cl_tabs
)
4088 generate_xtp_session_key(&cl_session_key
);
4091 table_headers
= g_strdup_printf("<table><tr>"
4094 "<th style='width:200px'>Value</th>"
4098 "<th>HTTP<br />only</th>"
4099 "<th style='width:40px'>Rm</th></tr>\n");
4101 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4102 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4106 for (; sc
; sc
= sc
->next
) {
4109 if (strcmp(last_domain
, c
->domain
) != 0) {
4112 last_domain
= strdup(c
->domain
);
4116 body
= g_strdup_printf("%s</table>"
4118 body
, c
->domain
, table_headers
);
4122 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4123 c
->domain
, table_headers
);
4128 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4129 if (soup_cookie_equal(pc
->data
, c
)) {
4130 type
= "Session + Persistent";
4135 body
= g_strdup_printf(
4138 "<td style='word-wrap:normal'>%s</td>"
4140 " <textarea rows='4'>%s</textarea>"
4146 "<td style='text-align:center'>"
4147 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4154 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4169 soup_cookies_free(sc
);
4170 soup_cookies_free(pc
);
4172 /* small message if there are none */
4174 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4175 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4178 body
= g_strdup_printf("%s</table>", body
);
4181 page
= get_html_page("Cookie Jar", body
, "", TRUE
);
4183 g_free(table_headers
);
4184 g_free(last_domain
);
4186 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4187 update_cookie_tabs(t
);
4195 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4197 char *body
, *page
, *tmp
;
4199 int i
= 1; /* all ids start 1 */
4201 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4204 show_oops_s("%s invalid parameters", __func__
);
4208 /* mark this tab as history manager */
4209 t
->xtp_meaning
= XT_XTP_TAB_MEANING_HL
;
4211 /* Generate a new session key */
4212 if (!updating_hl_tabs
)
4213 generate_xtp_session_key(&hl_session_key
);
4216 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
4217 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4219 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4221 body
= g_strdup_printf(
4223 "<td><a href='%s'>%s</a></td>"
4225 "<td style='text-align: center'>"
4226 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4227 body
, h
->uri
, h
->uri
, h
->title
,
4228 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4229 XT_XTP_HL_REMOVE
, i
);
4235 /* small message if there are none */
4238 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4239 "colspan='3'>No History</td></tr>\n", body
);
4244 body
= g_strdup_printf("%s</table>", body
);
4247 page
= get_html_page("History", body
, "", TRUE
);
4251 * update all history manager tabs as the xtp session
4252 * key has now changed. No need to update the current tab.
4253 * Already did that above.
4255 update_history_tabs(t
);
4257 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4264 * Generate a web page detailing the status of any downloads
4267 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4269 struct download
*dl
;
4270 char *body
, *page
, *tmp
;
4274 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4277 show_oops_s("%s invalid parameters", __func__
);
4280 /* mark as a download manager tab */
4281 t
->xtp_meaning
= XT_XTP_TAB_MEANING_DL
;
4284 * Generate a new session key for next page instance.
4285 * This only happens for the top level call to xtp_page_dl()
4286 * in which case updating_dl_tabs is 0.
4288 if (!updating_dl_tabs
)
4289 generate_xtp_session_key(&dl_session_key
);
4291 /* header - with refresh so as to update */
4292 if (refresh_interval
>= 1)
4293 ref
= g_strdup_printf(
4294 "<meta http-equiv='refresh' content='%u"
4295 ";url=%s%d/%s/%d' />\n",
4304 body
= g_strdup_printf("<div align='center'>"
4305 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4306 "</p><table><tr><th style='width: 60%%'>"
4307 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4308 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4310 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4311 body
= xtp_page_dl_row(t
, body
, dl
);
4315 /* message if no downloads in list */
4318 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4319 " style='text-align: center'>"
4320 "No downloads</td></tr>\n", body
);
4325 body
= g_strdup_printf("%s</table></div>", body
);
4328 page
= get_html_page("Downloads", body
, ref
, 1);
4333 * update all download manager tabs as the xtp session
4334 * key has now changed. No need to update the current tab.
4335 * Already did that above.
4337 update_download_tabs(t
);
4339 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4346 search(struct tab
*t
, struct karg
*args
)
4350 if (t
== NULL
|| args
== NULL
) {
4351 show_oops_s("search invalid parameters");
4354 if (t
->search_text
== NULL
) {
4355 if (global_search
== NULL
)
4356 return (XT_CB_PASSTHROUGH
);
4358 t
->search_text
= g_strdup(global_search
);
4359 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4360 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4364 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4365 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4368 case XT_SEARCH_NEXT
:
4369 d
= t
->search_forward
;
4371 case XT_SEARCH_PREV
:
4372 d
= !t
->search_forward
;
4375 return (XT_CB_PASSTHROUGH
);
4378 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4380 return (XT_CB_HANDLED
);
4383 struct settings_args
{
4389 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4392 struct settings_args
*sa
= cb_args
;
4397 if (s
->flags
& XT_SF_RUNTIME
)
4403 *sa
->body
= g_strdup_printf(
4405 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4406 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4418 set(struct tab
*t
, struct karg
*args
)
4420 char *body
, *page
, *tmp
;
4422 struct settings_args sa
;
4424 bzero(&sa
, sizeof sa
);
4428 body
= g_strdup_printf("<div align='center'><table><tr>"
4429 "<th align='left'>Setting</th>"
4430 "<th align='left'>Value</th></tr>\n");
4432 settings_walk(print_setting
, &sa
);
4435 /* small message if there are none */
4438 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4439 "colspan='2'>No settings</td></tr>\n", body
);
4444 body
= g_strdup_printf("%s</table></div>", body
);
4447 page
= get_html_page("Settings", body
, "", 0);
4451 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4455 return (XT_CB_PASSTHROUGH
);
4459 session_save(struct tab
*t
, char *filename
)
4464 if (strlen(filename
) == 0)
4467 if (filename
[0] == '.' || filename
[0] == '/')
4471 if (save_tabs(t
, &a
))
4473 strlcpy(named_session
, filename
, sizeof named_session
);
4481 session_open(struct tab
*t
, char *filename
)
4486 if (strlen(filename
) == 0)
4489 if (filename
[0] == '.' || filename
[0] == '/')
4493 a
.i
= XT_SES_CLOSETABS
;
4494 if (open_tabs(t
, &a
))
4497 strlcpy(named_session
, filename
, sizeof named_session
);
4505 session_delete(struct tab
*t
, char *filename
)
4507 char file
[PATH_MAX
];
4510 if (strlen(filename
) == 0)
4513 if (filename
[0] == '.' || filename
[0] == '/')
4516 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
4520 if (!strcmp(filename
, named_session
))
4521 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
4522 sizeof named_session
);
4530 session_cmd(struct tab
*t
, struct karg
*args
)
4532 char *filename
= args
->s
;
4537 if (args
->i
& XT_SHOW
)
4538 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
4539 XT_SAVED_TABS_FILE
: named_session
);
4540 else if (args
->i
& XT_SAVE
) {
4541 if (session_save(t
, filename
)) {
4542 show_oops(t
, "Can't save session: %s",
4543 filename
? filename
: "INVALID");
4546 } else if (args
->i
& XT_OPEN
) {
4547 if (session_open(t
, filename
)) {
4548 show_oops(t
, "Can't open session: %s",
4549 filename
? filename
: "INVALID");
4552 } else if (args
->i
& XT_DELETE
) {
4553 if (session_delete(t
, filename
)) {
4554 show_oops(t
, "Can't delete session: %s",
4555 filename
? filename
: "INVALID");
4560 return (XT_CB_PASSTHROUGH
);
4564 * Make a hardcopy of the page
4567 print_page(struct tab
*t
, struct karg
*args
)
4569 WebKitWebFrame
*frame
;
4571 GtkPrintOperation
*op
;
4572 GtkPrintOperationAction action
;
4573 GtkPrintOperationResult print_res
;
4574 GError
*g_err
= NULL
;
4575 int marg_l
, marg_r
, marg_t
, marg_b
;
4577 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
4579 ps
= gtk_page_setup_new();
4580 op
= gtk_print_operation_new();
4581 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
4582 frame
= webkit_web_view_get_main_frame(t
->wv
);
4584 /* the default margins are too small, so we will bump them */
4585 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
4586 XT_PRINT_EXTRA_MARGIN
;
4587 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
4588 XT_PRINT_EXTRA_MARGIN
;
4589 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
4590 XT_PRINT_EXTRA_MARGIN
;
4591 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
4592 XT_PRINT_EXTRA_MARGIN
;
4595 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
4596 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
4597 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
4598 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
4600 gtk_print_operation_set_default_page_setup(op
, ps
);
4602 /* this appears to free 'op' and 'ps' */
4603 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
4605 /* check it worked */
4606 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
4607 show_oops_s("can't print: %s", g_err
->message
);
4608 g_error_free (g_err
);
4616 go_home(struct tab
*t
, struct karg
*args
)
4623 restart(struct tab
*t
, struct karg
*args
)
4627 a
.s
= XT_RESTART_TABS_FILE
;
4629 execvp(start_argv
[0], start_argv
);
4635 #define CTRL GDK_CONTROL_MASK
4636 #define MOD1 GDK_MOD1_MASK
4637 #define SHFT GDK_SHIFT_MASK
4639 /* inherent to GTK not all keys will be caught at all times */
4640 /* XXX sort key bindings */
4641 struct key_binding
{
4646 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
4648 { "cookiejar", MOD1
, 0, GDK_j
},
4649 { "downloadmgr", MOD1
, 0, GDK_d
},
4650 { "history", MOD1
, 0, GDK_h
},
4651 { "print", CTRL
, 0, GDK_p
},
4652 { "search", 0, 0, GDK_slash
},
4653 { "searchb", 0, 0, GDK_question
},
4654 { "command", 0, 0, GDK_colon
},
4655 { "qa", CTRL
, 0, GDK_q
},
4656 { "restart", MOD1
, 0, GDK_q
},
4657 { "js toggle", CTRL
, 0, GDK_j
},
4658 { "cookie toggle", MOD1
, 0, GDK_c
},
4659 { "togglesrc", CTRL
, 0, GDK_s
},
4660 { "yankuri", 0, 0, GDK_y
},
4661 { "pasteuricur", 0, 0, GDK_p
},
4662 { "pasteurinew", 0, 0, GDK_P
},
4665 { "searchnext", 0, 0, GDK_n
},
4666 { "searchprevious", 0, 0, GDK_N
},
4669 { "focusaddress", 0, 0, GDK_F6
},
4670 { "focussearch", 0, 0, GDK_F7
},
4673 { "hinting", 0, 0, GDK_f
},
4675 /* custom stylesheet */
4676 { "userstyle", 0, 0, GDK_i
},
4679 { "goback", 0, 0, GDK_BackSpace
},
4680 { "goback", MOD1
, 0, GDK_Left
},
4681 { "goforward", SHFT
, 0, GDK_BackSpace
},
4682 { "goforward", MOD1
, 0, GDK_Right
},
4683 { "reload", 0, 0, GDK_F5
},
4684 { "reload", CTRL
, 0, GDK_r
},
4685 { "reloadforce", CTRL
, 0, GDK_R
},
4686 { "reload", CTRL
, 0, GDK_l
},
4687 { "favorites", MOD1
, 1, GDK_f
},
4689 /* vertical movement */
4690 { "scrolldown", 0, 0, GDK_j
},
4691 { "scrolldown", 0, 0, GDK_Down
},
4692 { "scrollup", 0, 0, GDK_Up
},
4693 { "scrollup", 0, 0, GDK_k
},
4694 { "scrollbottom", 0, 0, GDK_G
},
4695 { "scrollbottom", 0, 0, GDK_End
},
4696 { "scrolltop", 0, 0, GDK_Home
},
4697 { "scrolltop", 0, 0, GDK_g
},
4698 { "scrollpagedown", 0, 0, GDK_space
},
4699 { "scrollpagedown", CTRL
, 0, GDK_f
},
4700 { "scrollhalfdown", CTRL
, 0, GDK_d
},
4701 { "scrollpagedown", 0, 0, GDK_Page_Down
},
4702 { "scrollpageup", 0, 0, GDK_Page_Up
},
4703 { "scrollpageup", CTRL
, 0, GDK_b
},
4704 { "scrollhalfup", CTRL
, 0, GDK_u
},
4705 /* horizontal movement */
4706 { "scrollright", 0, 0, GDK_l
},
4707 { "scrollright", 0, 0, GDK_Right
},
4708 { "scrollleft", 0, 0, GDK_Left
},
4709 { "scrollleft", 0, 0, GDK_h
},
4710 { "scrollfarright", 0, 0, GDK_dollar
},
4711 { "scrollfarleft", 0, 0, GDK_0
},
4714 { "tabnew", CTRL
, 0, GDK_t
},
4715 { "999tabnew", CTRL
, 0, GDK_T
},
4716 { "tabclose", CTRL
, 1, GDK_w
},
4717 { "tabundoclose", 0, 0, GDK_U
},
4718 { "tabnext 1", CTRL
, 0, GDK_1
},
4719 { "tabnext 2", CTRL
, 0, GDK_2
},
4720 { "tabnext 3", CTRL
, 0, GDK_3
},
4721 { "tabnext 4", CTRL
, 0, GDK_4
},
4722 { "tabnext 5", CTRL
, 0, GDK_5
},
4723 { "tabnext 6", CTRL
, 0, GDK_6
},
4724 { "tabnext 7", CTRL
, 0, GDK_7
},
4725 { "tabnext 8", CTRL
, 0, GDK_8
},
4726 { "tabnext 9", CTRL
, 0, GDK_9
},
4727 { "tabnext 10", CTRL
, 0, GDK_0
},
4728 { "tabfirst", CTRL
, 0, GDK_less
},
4729 { "tablast", CTRL
, 0, GDK_greater
},
4730 { "tabprevious", CTRL
, 0, GDK_Left
},
4731 { "tabnext", CTRL
, 0, GDK_Right
},
4732 { "focusout", CTRL
, 0, GDK_minus
},
4733 { "focusin", CTRL
, 0, GDK_plus
},
4734 { "focusin", CTRL
, 0, GDK_equal
},
4736 /* command aliases (handy when -S flag is used) */
4737 { "promptopen", 0, 0, GDK_F9
},
4738 { "promptopencurrent", 0, 0, GDK_F10
},
4739 { "prompttabnew", 0, 0, GDK_F11
},
4740 { "prompttabnewcurrent",0, 0, GDK_F12
},
4742 TAILQ_HEAD(keybinding_list
, key_binding
);
4745 walk_kb(struct settings
*s
,
4746 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
4748 struct key_binding
*k
;
4751 if (s
== NULL
|| cb
== NULL
) {
4752 show_oops_s("walk_kb invalid parameters");
4756 TAILQ_FOREACH(k
, &kbl
, entry
) {
4762 if (gdk_keyval_name(k
->key
) == NULL
)
4765 strlcat(str
, k
->cmd
, sizeof str
);
4766 strlcat(str
, ",", sizeof str
);
4768 if (k
->mask
& GDK_SHIFT_MASK
)
4769 strlcat(str
, "S-", sizeof str
);
4770 if (k
->mask
& GDK_CONTROL_MASK
)
4771 strlcat(str
, "C-", sizeof str
);
4772 if (k
->mask
& GDK_MOD1_MASK
)
4773 strlcat(str
, "M1-", sizeof str
);
4774 if (k
->mask
& GDK_MOD2_MASK
)
4775 strlcat(str
, "M2-", sizeof str
);
4776 if (k
->mask
& GDK_MOD3_MASK
)
4777 strlcat(str
, "M3-", sizeof str
);
4778 if (k
->mask
& GDK_MOD4_MASK
)
4779 strlcat(str
, "M4-", sizeof str
);
4780 if (k
->mask
& GDK_MOD5_MASK
)
4781 strlcat(str
, "M5-", sizeof str
);
4783 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
4784 cb(s
, str
, cb_args
);
4789 init_keybindings(void)
4792 struct key_binding
*k
;
4794 for (i
= 0; i
< LENGTH(keys
); i
++) {
4795 k
= g_malloc0(sizeof *k
);
4796 k
->cmd
= keys
[i
].cmd
;
4797 k
->mask
= keys
[i
].mask
;
4798 k
->use_in_entry
= keys
[i
].use_in_entry
;
4799 k
->key
= keys
[i
].key
;
4800 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4802 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
4803 k
->cmd
? k
->cmd
: "unnamed key");
4808 keybinding_clearall(void)
4810 struct key_binding
*k
, *next
;
4812 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
4813 next
= TAILQ_NEXT(k
, entry
);
4817 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
4818 k
->cmd
? k
->cmd
: "unnamed key");
4819 TAILQ_REMOVE(&kbl
, k
, entry
);
4825 keybinding_add(char *cmd
, char *key
, int use_in_entry
)
4827 struct key_binding
*k
;
4828 guint keyval
, mask
= 0;
4831 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s\n", cmd
, key
);
4833 /* Keys which are to be used in entry have been prefixed with an
4834 * exclamation mark. */
4838 /* find modifier keys */
4839 if (strstr(key
, "S-"))
4840 mask
|= GDK_SHIFT_MASK
;
4841 if (strstr(key
, "C-"))
4842 mask
|= GDK_CONTROL_MASK
;
4843 if (strstr(key
, "M1-"))
4844 mask
|= GDK_MOD1_MASK
;
4845 if (strstr(key
, "M2-"))
4846 mask
|= GDK_MOD2_MASK
;
4847 if (strstr(key
, "M3-"))
4848 mask
|= GDK_MOD3_MASK
;
4849 if (strstr(key
, "M4-"))
4850 mask
|= GDK_MOD4_MASK
;
4851 if (strstr(key
, "M5-"))
4852 mask
|= GDK_MOD5_MASK
;
4855 for (i
= strlen(key
) - 1; i
> 0; i
--)
4859 /* validate keyname */
4860 keyval
= gdk_keyval_from_name(key
);
4861 if (keyval
== GDK_VoidSymbol
) {
4862 warnx("invalid keybinding name %s", key
);
4865 /* must run this test too, gtk+ doesn't handle 10 for example */
4866 if (gdk_keyval_name(keyval
) == NULL
) {
4867 warnx("invalid keybinding name %s", key
);
4871 /* Remove eventual dupes. */
4872 TAILQ_FOREACH(k
, &kbl
, entry
)
4873 if (k
->key
== keyval
&& k
->mask
== mask
) {
4874 TAILQ_REMOVE(&kbl
, k
, entry
);
4880 k
= g_malloc0(sizeof *k
);
4881 k
->cmd
= g_strdup(cmd
);
4883 k
->use_in_entry
= use_in_entry
;
4886 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
4891 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
4892 k
->cmd
, gdk_keyval_name(keyval
));
4894 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
4900 add_kb(struct settings
*s
, char *entry
)
4904 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
4906 /* clearall is special */
4907 if (!strcmp(entry
, "clearall")) {
4908 keybinding_clearall();
4912 kb
= strstr(entry
, ",");
4918 return (keybinding_add(entry
, key
, key
[0] == '!'));
4924 int (*func
)(struct tab
*, struct karg
*);
4928 { "command", 0, command
, ':', 0 },
4929 { "search", 0, command
, '/', 0 },
4930 { "searchb", 0, command
, '?', 0 },
4931 { "togglesrc", 0, toggle_src
, 0, 0 },
4933 /* yanking and pasting */
4934 { "yankuri", 0, yank_uri
, 0, 0 },
4935 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4936 { "pasteuricur", 0, paste_uri
, XT_PASTE_CURRENT_TAB
, 0 },
4937 { "pasteurinew", 0, paste_uri
, XT_PASTE_NEW_TAB
, 0 },
4940 { "searchnext", 0, search
, XT_SEARCH_NEXT
, 0 },
4941 { "searchprevious", 0, search
, XT_SEARCH_PREV
, 0 },
4944 { "focusaddress", 0, focus
, XT_FOCUS_URI
, 0 },
4945 { "focussearch", 0, focus
, XT_FOCUS_SEARCH
, 0 },
4948 { "hinting", 0, hint
, 0, 0 },
4950 /* custom stylesheet */
4951 { "userstyle", 0, userstyle
, 0, 0 },
4954 { "goback", 0, navaction
, XT_NAV_BACK
, 0 },
4955 { "goforward", 0, navaction
, XT_NAV_FORWARD
, 0 },
4956 { "reload", 0, navaction
, XT_NAV_RELOAD
, 0 },
4957 { "reloadforce", 0, navaction
, XT_NAV_RELOAD_CACHE
, 0 },
4959 /* vertical movement */
4960 { "scrolldown", 0, move
, XT_MOVE_DOWN
, 0 },
4961 { "scrollup", 0, move
, XT_MOVE_UP
, 0 },
4962 { "scrollbottom", 0, move
, XT_MOVE_BOTTOM
, 0 },
4963 { "scrolltop", 0, move
, XT_MOVE_TOP
, 0 },
4964 { "1", 0, move
, XT_MOVE_TOP
, 0 },
4965 { "scrollhalfdown", 0, move
, XT_MOVE_HALFDOWN
, 0 },
4966 { "scrollhalfup", 0, move
, XT_MOVE_HALFUP
, 0 },
4967 { "scrollpagedown", 0, move
, XT_MOVE_PAGEDOWN
, 0 },
4968 { "scrollpageup", 0, move
, XT_MOVE_PAGEUP
, 0 },
4969 /* horizontal movement */
4970 { "scrollright", 0, move
, XT_MOVE_RIGHT
, 0 },
4971 { "scrollleft", 0, move
, XT_MOVE_LEFT
, 0 },
4972 { "scrollfarright", 0, move
, XT_MOVE_FARRIGHT
, 0 },
4973 { "scrollfarleft", 0, move
, XT_MOVE_FARLEFT
, 0 },
4976 { "favorites", 0, xtp_page_fl
, 0, 0 },
4977 { "fav", 0, xtp_page_fl
, 0, 0 },
4978 { "favadd", 0, add_favorite
, 0, 0 },
4980 { "qall", 0, quit
, 0, 0 },
4981 { "quitall", 0, quit
, 0, 0 },
4982 { "w", 0, save_tabs
, 0, 0 },
4983 { "wq", 0, save_tabs_and_quit
, 0, 0 },
4984 { "help", 0, help
, 0, 0 },
4985 { "about", 0, about
, 0, 0 },
4986 { "stats", 0, stats
, 0, 0 },
4987 { "version", 0, about
, 0, 0 },
4990 { "js", 0, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
4991 { "save", 1, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
4992 { "domain", 2, js_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
4993 { "fqdn", 2, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
4994 { "show", 1, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
4995 { "all", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
4996 { "persistent", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
4997 { "session", 2, js_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
4998 { "toggle", 1, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
4999 { "domain", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5000 { "fqdn", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5002 /* cookie command */
5003 { "cookie", 0, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5004 { "save", 1, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5005 { "domain", 2, cookie_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
5006 { "fqdn", 2, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5007 { "show", 1, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5008 { "all", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5009 { "persistent", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
5010 { "session", 2, cookie_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
5011 { "toggle", 1, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5012 { "domain", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5013 { "fqdn", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5016 { "cookiejar", 0, xtp_page_cl
, 0, 0 },
5019 { "cert", 0, cert_cmd
, XT_SHOW
, 0 },
5020 { "save", 1, cert_cmd
, XT_SAVE
, 0 },
5021 { "show", 1, cert_cmd
, XT_SHOW
, 0 },
5023 { "ca", 0, ca_cmd
, 0, 0 },
5024 { "downloadmgr", 0, xtp_page_dl
, 0, 0 },
5025 { "dl", 0, xtp_page_dl
, 0, 0 },
5026 { "h", 0, xtp_page_hl
, 0, 0 },
5027 { "history", 0, xtp_page_hl
, 0, 0 },
5028 { "home", 0, go_home
, 0, 0 },
5029 { "restart", 0, restart
, 0, 0 },
5030 { "urlhide", 0, urlaction
, XT_URL_HIDE
, 0 },
5031 { "urlshow", 0, urlaction
, XT_URL_SHOW
, 0 },
5032 { "statushide", 0, statusaction
, XT_STATUSBAR_HIDE
, 0 },
5033 { "statusshow", 0, statusaction
, XT_STATUSBAR_SHOW
, 0 },
5035 { "print", 0, print_page
, 0, 0 },
5038 { "focusin", 0, resizetab
, 1, 0 },
5039 { "focusout", 0, resizetab
, -1, 0 },
5040 { "q", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5041 { "quit", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5042 { "open", 0, tabaction
, XT_TAB_OPEN
, XT_URLARG
},
5043 { "tabclose", 0, tabaction
, XT_TAB_DELETE
, XT_PREFIX
| XT_INTARG
},
5044 { "tabedit", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5045 { "tabfirst", 0, movetab
, XT_TAB_FIRST
, 0 },
5046 { "tabhide", 0, tabaction
, XT_TAB_HIDE
, 0 },
5047 { "tablast", 0, movetab
, XT_TAB_LAST
, 0 },
5048 { "tabnew", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5049 { "tabnext", 0, movetab
, XT_TAB_NEXT
, XT_PREFIX
| XT_INTARG
},
5050 { "tabprevious", 0, movetab
, XT_TAB_PREV
, XT_PREFIX
| XT_INTARG
},
5051 { "tabrewind", 0, movetab
, XT_TAB_FIRST
, 0 },
5052 { "tabshow", 0, tabaction
, XT_TAB_SHOW
, 0 },
5053 { "tabundoclose", 0, tabaction
, XT_TAB_UNDO_CLOSE
, 0 },
5055 /* command aliases (handy when -S flag is used) */
5056 { "promptopen", 0, command
, XT_CMD_OPEN
, 0 },
5057 { "promptopencurrent", 0, command
, XT_CMD_OPEN_CURRENT
, 0 },
5058 { "prompttabnew", 0, command
, XT_CMD_TABNEW
, 0 },
5059 { "prompttabnewcurrent",0, command
, XT_CMD_TABNEW_CURRENT
, 0 },
5062 { "set", 0, set
, 0, 0 },
5063 { "fullscreen", 0, fullscreen
, 0, 0 },
5064 { "f", 0, fullscreen
, 0, 0 },
5067 { "session", 0, session_cmd
, XT_SHOW
, 0 },
5068 { "delete", 1, session_cmd
, XT_DELETE
, XT_USERARG
},
5069 { "open", 1, session_cmd
, XT_OPEN
, XT_USERARG
},
5070 { "save", 1, session_cmd
, XT_SAVE
, XT_USERARG
},
5071 { "show", 1, session_cmd
, XT_SHOW
, 0 },
5078 } cmd_status
= {-1, 0};
5081 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5087 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5093 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5095 a
.i
= XT_NAV_FORWARD
;
5105 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5107 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5109 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5116 * cancel, remove, etc. downloads
5119 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5121 struct download find
, *d
= NULL
;
5123 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5125 /* some commands require a valid download id */
5126 if (cmd
!= XT_XTP_DL_LIST
) {
5127 /* lookup download in question */
5129 d
= RB_FIND(download_list
, &downloads
, &find
);
5132 show_oops(t
, "%s: no such download", __func__
);
5137 /* decide what to do */
5139 case XT_XTP_DL_CANCEL
:
5140 webkit_download_cancel(d
->download
);
5142 case XT_XTP_DL_REMOVE
:
5143 webkit_download_cancel(d
->download
); /* just incase */
5144 g_object_unref(d
->download
);
5145 RB_REMOVE(download_list
, &downloads
, d
);
5147 case XT_XTP_DL_LIST
:
5151 show_oops(t
, "%s: unknown command", __func__
);
5154 xtp_page_dl(t
, NULL
);
5158 * Actions on history, only does one thing for now, but
5159 * we provide the function for future actions
5162 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5164 struct history
*h
, *next
;
5168 case XT_XTP_HL_REMOVE
:
5169 /* walk backwards, as listed in reverse */
5170 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5171 next
= RB_PREV(history_list
, &hl
, h
);
5173 RB_REMOVE(history_list
, &hl
, h
);
5174 g_free((gpointer
) h
->title
);
5175 g_free((gpointer
) h
->uri
);
5182 case XT_XTP_HL_LIST
:
5183 /* Nothing - just xtp_page_hl() below */
5186 show_oops(t
, "%s: unknown command", __func__
);
5190 xtp_page_hl(t
, NULL
);
5193 /* remove a favorite */
5195 remove_favorite(struct tab
*t
, int index
)
5197 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5198 char *new_favs
, *tmp
;
5203 /* open favorites */
5204 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5206 if ((f
= fopen(file
, "r")) == NULL
) {
5207 show_oops(t
, "%s: can't open favorites: %s",
5208 __func__
, strerror(errno
));
5212 /* build a string which will become the new favroites file */
5213 new_favs
= g_strdup("");
5216 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5217 if (feof(f
) || ferror(f
))
5219 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5226 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5227 if (feof(f
) || ferror(f
)) {
5228 show_oops(t
, "%s: can't parse favorites %s",
5229 __func__
, strerror(errno
));
5234 /* as long as this isn't the one we are deleting add to file */
5237 new_favs
= g_strdup_printf("%s%s\n%s\n",
5238 new_favs
, title
, uri
);
5250 /* write back new favorites file */
5251 if ((f
= fopen(file
, "w")) == NULL
) {
5252 show_oops(t
, "%s: can't open favorites: %s",
5253 __func__
, strerror(errno
));
5257 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5270 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5273 case XT_XTP_FL_LIST
:
5274 /* nothing, just the below call to xtp_page_fl() */
5276 case XT_XTP_FL_REMOVE
:
5277 remove_favorite(t
, arg
);
5280 show_oops(t
, "%s: invalid favorites command", __func__
);
5284 xtp_page_fl(t
, NULL
);
5288 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5291 case XT_XTP_CL_LIST
:
5292 /* nothing, just xtp_page_cl() */
5294 case XT_XTP_CL_REMOVE
:
5298 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5302 xtp_page_cl(t
, NULL
);
5305 /* link an XTP class to it's session key and handler function */
5306 struct xtp_despatch
{
5309 void (*handle_func
)(struct tab
*, uint8_t, int);
5312 struct xtp_despatch xtp_despatches
[] = {
5313 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5314 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5315 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5316 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5317 { XT_XTP_INVALID
, NULL
, NULL
}
5321 * is the url xtp protocol? (xxxt://)
5322 * if so, parse and despatch correct bahvior
5325 parse_xtp_url(struct tab
*t
, const char *url
)
5327 char *dup
= NULL
, *p
, *last
;
5328 uint8_t n_tokens
= 0;
5329 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5330 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5335 * tokens array meaning:
5337 * tokens[1] = session key
5338 * tokens[2] = action
5339 * tokens[3] = optional argument
5342 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5344 /*xtp tab meaning is normal unless proven special */
5345 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5347 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5350 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5352 /* split out the url */
5353 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5354 (p
= strtok_r(NULL
, "/", &last
))) {
5356 tokens
[n_tokens
++] = p
;
5359 /* should be atleast three fields 'class/seskey/command/arg' */
5363 dsp
= xtp_despatches
;
5364 req_class
= atoi(tokens
[0]);
5365 while (dsp
->xtp_class
) {
5366 if (dsp
->xtp_class
== req_class
) {
5373 /* did we find one atall? */
5374 if (dsp_match
== NULL
) {
5375 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
5379 /* check session key and call despatch function */
5380 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
5381 ret
= TRUE
; /* all is well, this was a valid xtp request */
5382 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
5395 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5397 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
5399 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
5402 show_oops_s("activate_uri_entry_cb invalid parameters");
5407 show_oops(t
, "activate_uri_entry_cb no uri");
5411 uri
+= strspn(uri
, "\t ");
5413 /* if xxxt:// treat specially */
5414 if (parse_xtp_url(t
, uri
))
5417 /* otherwise continue to load page normally */
5418 load_uri(t
, (gchar
*)uri
);
5423 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5425 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
5426 char *newuri
= NULL
;
5429 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
5432 show_oops_s("activate_search_entry_cb invalid parameters");
5436 if (search_string
== NULL
) {
5437 show_oops(t
, "no search_string");
5441 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
5442 newuri
= g_strdup_printf(search_string
, enc_search
);
5445 webkit_web_view_load_uri(t
->wv
, newuri
);
5453 check_and_set_js(const gchar
*uri
, struct tab
*t
)
5455 struct domain
*d
= NULL
;
5458 if (uri
== NULL
|| t
== NULL
)
5461 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5466 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
5467 es
? "enable" : "disable", uri
);
5469 g_object_set(G_OBJECT(t
->settings
),
5470 "enable-scripts", es
, (char *)NULL
);
5471 g_object_set(G_OBJECT(t
->settings
),
5472 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
5473 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5475 button_set_stockid(t
->js_toggle
,
5476 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
5480 show_ca_status(struct tab
*t
, const char *uri
)
5482 WebKitWebFrame
*frame
;
5483 WebKitWebDataSource
*source
;
5484 WebKitNetworkRequest
*request
;
5485 SoupMessage
*message
;
5487 gchar
*col_str
= XT_COLOR_WHITE
;
5490 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
5491 ssl_strict_certs
, ssl_ca_file
, uri
);
5495 if (ssl_ca_file
== NULL
) {
5496 if (g_str_has_prefix(uri
, "http://"))
5498 if (g_str_has_prefix(uri
, "https://")) {
5499 col_str
= XT_COLOR_RED
;
5504 if (g_str_has_prefix(uri
, "http://") ||
5505 !g_str_has_prefix(uri
, "https://"))
5508 frame
= webkit_web_view_get_main_frame(t
->wv
);
5509 source
= webkit_web_frame_get_data_source(frame
);
5510 request
= webkit_web_data_source_get_request(source
);
5511 message
= webkit_network_request_get_message(request
);
5513 if (message
&& (soup_message_get_flags(message
) &
5514 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
5515 col_str
= XT_COLOR_GREEN
;
5518 r
= load_compare_cert(t
, NULL
);
5520 col_str
= XT_COLOR_BLUE
;
5522 col_str
= XT_COLOR_YELLOW
;
5524 col_str
= XT_COLOR_RED
;
5529 gdk_color_parse(col_str
, &color
);
5530 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
5532 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
5533 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5535 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5536 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5539 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
,
5541 gdk_color_parse(XT_COLOR_BLACK
, &color
);
5542 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
,
5549 free_favicon(struct tab
*t
)
5551 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p pix %p\n",
5552 __func__
, t
->icon_download
, t
->icon_request
, t
->icon_pixbuf
);
5554 if (t
->icon_request
)
5555 g_object_unref(t
->icon_request
);
5557 g_object_unref(t
->icon_pixbuf
);
5558 if (t
->icon_dest_uri
)
5559 g_free(t
->icon_dest_uri
);
5561 t
->icon_pixbuf
= NULL
;
5562 t
->icon_request
= NULL
;
5563 t
->icon_dest_uri
= NULL
;
5567 xt_icon_from_name(struct tab
*t
, gchar
*name
)
5569 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5570 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5572 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5573 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5575 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5576 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5580 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pixbuf
)
5582 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
5583 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5585 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->statusbar
),
5586 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
5588 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->statusbar
),
5589 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5593 is_valid_icon(char *file
)
5596 const char *mime_type
;
5600 gf
= g_file_new_for_path(file
);
5601 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5603 mime_type
= g_file_info_get_content_type(fi
);
5604 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
5605 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
5606 g_strcmp0(mime_type
, "image/png") == 0 ||
5607 g_strcmp0(mime_type
, "image/gif") == 0 ||
5608 g_strcmp0(mime_type
, "application/octet-stream") == 0;
5616 set_favicon_from_file(struct tab
*t
, char *file
)
5619 GdkPixbuf
*pixbuf
, *scaled
;
5622 if (t
== NULL
|| file
== NULL
)
5624 if (t
->icon_pixbuf
) {
5625 DNPRINTF(XT_D_DOWNLOAD
, "%s: icon already set\n", __func__
);
5629 if (g_str_has_prefix(file
, "file://"))
5630 file
+= strlen("file://");
5631 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
5633 if (!stat(file
, &sb
)) {
5634 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
5635 /* corrupt icon so trash it */
5636 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5639 /* no need to set icon to default here */
5644 pixbuf
= gdk_pixbuf_new_from_file(file
, NULL
);
5645 if (pixbuf
== NULL
) {
5646 xt_icon_from_name(t
, "text-html");
5650 g_object_get(pixbuf
, "width", &width
, "height", &height
,
5652 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d icon size %dx%d\n",
5653 __func__
, t
->tab_id
, width
, height
);
5655 if (width
> 16 || height
> 16) {
5656 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5657 GDK_INTERP_BILINEAR
);
5658 g_object_unref(pixbuf
);
5662 if (scaled
== NULL
) {
5663 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
5664 GDK_INTERP_BILINEAR
);
5668 t
->icon_pixbuf
= scaled
;
5669 xt_icon_from_pixbuf(t
, t
->icon_pixbuf
);
5673 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5676 WebKitDownloadStatus status
= webkit_download_get_status(download
);
5677 struct tab
*tt
= NULL
, *t
= NULL
;
5680 * find the webview instead of passing in the tab as it could have been
5681 * deleted from underneath us.
5683 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5692 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
5693 __func__
, t
->tab_id
, status
);
5696 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
5698 t
->icon_download
= NULL
;
5701 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
5704 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
5707 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
5709 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
5710 __func__
, t
->tab_id
);
5711 t
->icon_download
= NULL
;
5714 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
5717 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
5718 __func__
, t
->icon_dest_uri
);
5719 set_favicon_from_file(t
, t
->icon_dest_uri
);
5720 /* these will be freed post callback */
5721 t
->icon_request
= NULL
;
5722 t
->icon_download
= NULL
;
5730 abort_favicon_download(struct tab
*t
)
5732 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
5734 if (t
->icon_download
) {
5735 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
5736 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5737 webkit_download_cancel(t
->icon_download
);
5738 t
->icon_download
= NULL
;
5742 xt_icon_from_name(t
, "text-html");
5746 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
5748 gchar
*name_hash
, file
[PATH_MAX
];
5751 DNPRINTF(XT_D_DOWNLOAD
, "notify_icon_loaded_cb %s\n", uri
);
5753 if (uri
== NULL
|| t
== NULL
)
5756 if (t
->icon_request
) {
5757 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
5761 /* check to see if we got the icon in cache */
5762 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
5763 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
5766 if (!stat(file
, &sb
)) {
5767 if (sb
.st_size
> 0) {
5768 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
5770 set_favicon_from_file(t
, file
);
5774 /* corrupt icon so trash it */
5775 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5780 /* create download for icon */
5781 t
->icon_request
= webkit_network_request_new(uri
);
5782 if (t
->icon_request
== NULL
) {
5783 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
5788 t
->icon_download
= webkit_download_new(t
->icon_request
);
5789 if (t
->icon_download
== NULL
) {
5790 fprintf(stderr
, "%s: icon_download", __func__
);
5794 /* we have to free icon_dest_uri later */
5795 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
5796 webkit_download_set_destination_uri(t
->icon_download
,
5799 if (webkit_download_get_status(t
->icon_download
) ==
5800 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5801 fprintf(stderr
, "%s: download failed to start", __func__
);
5802 g_object_unref(t
->icon_request
);
5803 g_free(t
->icon_dest_uri
);
5804 t
->icon_request
= NULL
;
5805 t
->icon_dest_uri
= NULL
;
5809 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
5810 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5812 webkit_download_start(t
->icon_download
);
5816 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5818 const gchar
*set
= NULL
, *uri
= NULL
, *title
= NULL
;
5819 struct history
*h
, find
;
5820 const gchar
*s_loading
;
5823 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
5824 webkit_web_view_get_load_status(wview
), get_uri(wview
) ? get_uri(wview
) : "NOTHING");
5827 show_oops_s("notify_load_status_cb invalid paramters");
5831 switch (webkit_web_view_get_load_status(wview
)) {
5832 case WEBKIT_LOAD_PROVISIONAL
:
5834 abort_favicon_download(t
);
5835 #if GTK_CHECK_VERSION(2, 20, 0)
5836 gtk_widget_show(t
->spinner
);
5837 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
5839 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
5841 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
5843 /* take focus if we are visible */
5849 case WEBKIT_LOAD_COMMITTED
:
5851 if ((uri
= get_uri(wview
)) != NULL
) {
5852 if (strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
5853 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
5859 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
5862 /* check if js white listing is enabled */
5863 if (enable_js_whitelist
) {
5864 uri
= get_uri(wview
);
5865 check_and_set_js(uri
, t
);
5871 show_ca_status(t
, uri
);
5873 /* we know enough to autosave the session */
5874 if (session_autosave
) {
5880 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
5884 case WEBKIT_LOAD_FINISHED
:
5886 uri
= get_uri(wview
);
5890 if (!strncmp(uri
, "http://", strlen("http://")) ||
5891 !strncmp(uri
, "https://", strlen("https://")) ||
5892 !strncmp(uri
, "file://", strlen("file://"))) {
5894 h
= RB_FIND(history_list
, &hl
, &find
);
5896 title
= webkit_web_view_get_title(wview
);
5897 set
= title
? title
: uri
;
5898 h
= g_malloc(sizeof *h
);
5899 h
->uri
= g_strdup(uri
);
5900 h
->title
= g_strdup(set
);
5901 RB_INSERT(history_list
, &hl
, h
);
5902 completion_add_uri(h
->uri
);
5903 update_history_tabs(NULL
);
5907 set_status(t
, (char *)uri
, XT_STATUS_URI
);
5908 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5909 case WEBKIT_LOAD_FAILED
:
5912 #if GTK_CHECK_VERSION(2, 20, 0)
5913 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5914 gtk_widget_hide(t
->spinner
);
5916 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
5917 if (s_loading
&& !strcmp(s_loading
, "Loading"))
5918 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5920 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
5925 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
5927 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
5928 webkit_web_view_can_go_back(wview
));
5930 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
5931 webkit_web_view_can_go_forward(wview
));
5935 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5937 const gchar
*set
= NULL
, *title
= NULL
;
5939 title
= webkit_web_view_get_title(wview
);
5940 set
= title
? title
: get_uri(wview
);
5942 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
5943 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
5945 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5946 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
5951 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5953 run_script(t
, JS_HINTING
);
5957 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
5959 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
5960 progress
== 100 ? 0 : (double)progress
/ 100);
5961 if (show_url
== 0) {
5962 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
5963 progress
== 100 ? 0 : (double)progress
/ 100);
5968 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
5969 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
5970 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
5973 WebKitWebNavigationReason reason
;
5974 struct domain
*d
= NULL
;
5977 show_oops_s("webview_npd_cb invalid parameters");
5981 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
5983 webkit_network_request_get_uri(request
));
5985 uri
= (char *)webkit_network_request_get_uri(request
);
5987 /* if this is an xtp url, we don't load anything else */
5988 if (parse_xtp_url(t
, uri
))
5991 if (t
->ctrl_click
) {
5993 create_new_tab(uri
, NULL
, ctrl_click_focus
, -1);
5994 webkit_web_policy_decision_ignore(pd
);
5995 return (TRUE
); /* we made the decission */
5999 * This is a little hairy but it comes down to this:
6000 * when we run in whitelist mode we have to assist the browser in
6001 * opening the URL that it would have opened in a new tab.
6003 reason
= webkit_web_navigation_action_get_reason(na
);
6004 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6005 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6006 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6009 webkit_web_policy_decision_use(pd
);
6010 return (TRUE
); /* we made the decission */
6017 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6020 struct domain
*d
= NULL
;
6022 WebKitWebView
*webview
= NULL
;
6024 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6025 webkit_web_view_get_uri(wv
));
6028 /* open in current tab */
6030 } else if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6031 uri
= webkit_web_view_get_uri(wv
);
6032 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6035 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6037 } else if (enable_scripts
== 1) {
6038 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6046 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6049 struct domain
*d
= NULL
;
6051 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6053 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6054 uri
= webkit_web_view_get_uri(wv
);
6055 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6059 } else if (enable_scripts
== 1)
6066 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6068 /* we can not eat the event without throwing gtk off so defer it */
6070 /* catch middle click */
6071 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6076 /* catch ctrl click */
6077 if (e
->type
== GDK_BUTTON_RELEASE
&&
6078 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6083 return (XT_CB_PASSTHROUGH
);
6087 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6089 struct mime_type
*m
;
6091 m
= find_mime_type(mime_type
);
6099 show_oops(t
, "can't fork mime handler");
6108 execlp(m
->mt_action
, m
->mt_action
,
6109 webkit_network_request_get_uri(request
), (void *)NULL
);
6118 get_mime_type(char *file
)
6120 const char *mime_type
;
6124 if (g_str_has_prefix(file
, "file://"))
6125 file
+= strlen("file://");
6127 gf
= g_file_new_for_path(file
);
6128 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6130 mime_type
= g_file_info_get_content_type(fi
);
6138 run_download_mimehandler(char *mime_type
, char *file
)
6140 struct mime_type
*m
;
6142 m
= find_mime_type(mime_type
);
6148 show_oops_s("can't fork download mime handler");
6157 if (g_str_has_prefix(file
, "file://"))
6158 file
+= strlen("file://");
6159 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6168 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6171 WebKitDownloadStatus status
;
6172 const gchar
*file
= NULL
, *mime
= NULL
;
6174 if (download
== NULL
)
6176 status
= webkit_download_get_status(download
);
6177 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6180 file
= webkit_download_get_destination_uri(download
);
6183 mime
= get_mime_type((char *)file
);
6187 run_download_mimehandler((char *)mime
, (char *)file
);
6191 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6192 WebKitNetworkRequest
*request
, char *mime_type
,
6193 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6196 show_oops_s("webview_mimetype_cb invalid parameters");
6200 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6201 t
->tab_id
, mime_type
);
6203 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6204 webkit_web_policy_decision_ignore(decision
);
6209 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6210 webkit_web_policy_decision_download(decision
);
6218 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6221 const gchar
*filename
;
6223 struct download
*download_entry
;
6226 if (wk_download
== NULL
|| t
== NULL
) {
6227 show_oops_s("%s invalid parameters", __func__
);
6231 filename
= webkit_download_get_suggested_filename(wk_download
);
6232 if (filename
== NULL
)
6233 return (FALSE
); /* abort download */
6235 uri
= g_strdup_printf("file://%s/%s", download_dir
, filename
);
6237 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6238 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6240 webkit_download_set_destination_uri(wk_download
, uri
);
6242 if (webkit_download_get_status(wk_download
) ==
6243 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6244 show_oops(t
, "%s: download failed to start", __func__
);
6246 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6248 /* connect "download first" mime handler */
6249 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6250 G_CALLBACK(download_status_changed_cb
), NULL
);
6252 download_entry
= g_malloc(sizeof(struct download
));
6253 download_entry
->download
= wk_download
;
6254 download_entry
->tab
= t
;
6255 download_entry
->id
= next_download_id
++;
6256 RB_INSERT(download_list
, &downloads
, download_entry
);
6257 /* get from history */
6258 g_object_ref(wk_download
);
6259 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6260 show_oops(t
, "Download of '%s' started...",
6261 basename(webkit_download_get_destination_uri(wk_download
)));
6267 /* sync other download manager tabs */
6268 update_download_tabs(NULL
);
6271 * NOTE: never redirect/render the current tab before this
6272 * function returns. This will cause the download to never start.
6274 return (ret
); /* start download */
6278 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6280 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6283 show_oops_s("webview_hover_cb");
6288 set_status(t
, uri
, XT_STATUS_LINK
);
6291 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6296 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6298 struct key_binding
*k
;
6300 TAILQ_FOREACH(k
, &kbl
, entry
)
6301 if (e
->keyval
== k
->key
&& (entry
? k
->use_in_entry
: 1)) {
6303 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6304 return (cmd_execute(t
, k
->cmd
));
6305 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6306 return (cmd_execute(t
, k
->cmd
));
6310 return (XT_CB_PASSTHROUGH
);
6314 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6316 char s
[2], buf
[128];
6317 const char *errstr
= NULL
;
6320 /* don't use w directly; use t->whatever instead */
6323 show_oops_s("wv_keypress_after_cb");
6324 return (XT_CB_PASSTHROUGH
);
6327 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6328 e
->keyval
, e
->state
, t
);
6332 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6334 return (XT_CB_HANDLED
);
6338 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6339 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6341 /* we have a string */
6343 /* we have a number */
6344 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6352 /* XXX unfuck this */
6353 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6354 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6355 /* last input was numerical */
6357 l
= strlen(t
->hint_num
);
6364 t
->hint_num
[l
] = '\0';
6368 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6369 /* last input was alphanumerical */
6371 l
= strlen(t
->hint_buf
);
6378 t
->hint_buf
[l
] = '\0';
6388 /* numerical input */
6389 if (CLEAN(e
->state
) == 0 &&
6390 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6391 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6392 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6393 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6396 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6398 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6401 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6403 t
->hint_mode
= XT_HINT_NUMERICAL
;
6407 /* empty the counter buffer */
6408 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6409 return (XT_CB_HANDLED
);
6412 /* alphanumerical input */
6414 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6415 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6416 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6417 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6418 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6419 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6420 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6423 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6426 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6428 t
->hint_mode
= XT_HINT_ALPHANUM
;
6431 /* empty the counter buffer */
6432 bzero(t
->hint_num
, sizeof t
->hint_num
);
6433 return (XT_CB_HANDLED
);
6436 return (XT_CB_HANDLED
);
6439 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6440 if (CLEAN(e
->state
) == 0 && isdigit(s
[0])) {
6441 cmd_prefix
= 10 * cmd_prefix
+ atoi(s
);
6445 return (handle_keypress(t
, e
, 0));
6449 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6453 return (XT_CB_PASSTHROUGH
);
6457 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6459 const gchar
*c
= gtk_entry_get_text(w
);
6463 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6464 e
->keyval
, e
->state
, t
);
6467 show_oops_s("cmd_keyrelease_cb invalid parameters");
6468 return (XT_CB_PASSTHROUGH
);
6471 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6472 e
->keyval
, e
->state
, t
);
6476 if (strlen(c
) == 1) {
6477 webkit_web_view_unmark_text_matches(t
->wv
);
6483 else if (c
[0] == '?')
6489 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6491 /* not found, mark red */
6492 gdk_color_parse(XT_COLOR_RED
, &color
);
6493 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6494 /* unmark and remove selection */
6495 webkit_web_view_unmark_text_matches(t
->wv
);
6496 /* my kingdom for a way to unselect text in webview */
6498 /* found, highlight all */
6499 webkit_web_view_unmark_text_matches(t
->wv
);
6500 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6501 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6502 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6503 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6506 return (XT_CB_PASSTHROUGH
);
6510 match_uri(const gchar
*uri
, const gchar
*key
) {
6513 gboolean match
= FALSE
;
6517 if (!strncmp(key
, uri
, len
))
6520 voffset
= strstr(uri
, "/") + 2;
6521 if (!strncmp(key
, voffset
, len
))
6523 else if (g_str_has_prefix(voffset
, "www.")) {
6524 voffset
= voffset
+ strlen("www.");
6525 if (!strncmp(key
, voffset
, len
))
6534 cmd_getlist(int id
, char *key
)
6539 if (id
>= 0 && (cmds
[id
].type
& XT_URLARG
)) {
6540 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6541 if (match_uri(h
->uri
, key
)) {
6542 cmd_status
.list
[c
] = (char *)h
->uri
;
6551 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6553 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6554 if(cmds
[i
].level
< dep
)
6556 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6557 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6565 cmd_getnext(int dir
)
6567 cmd_status
.index
+= dir
;
6569 if (cmd_status
.index
< 0)
6570 cmd_status
.index
= cmd_status
.len
- 1;
6571 else if (cmd_status
.index
>= cmd_status
.len
)
6572 cmd_status
.index
= 0;
6574 return cmd_status
.list
[cmd_status
.index
];
6578 cmd_tokenize(char *s
, char *tokens
[])
6582 size_t len
= strlen(s
);
6583 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6585 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6595 cmd_complete(struct tab
*t
, char *str
, int dir
)
6597 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6598 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1, matchcount
= 0;
6599 char *tok
, *match
, *s
= g_strdup(str
);
6601 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6604 DNPRINTF(XT_D_CMD
, "%s: complete %s\n", __func__
, str
);
6607 for (i
= 0; isdigit(s
[i
]); i
++)
6610 for (; isspace(s
[i
]); i
++)
6615 levels
= cmd_tokenize(s
, tokens
);
6617 for (i
= 0; i
< levels
- 1; i
++) {
6620 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6621 if (cmds
[j
].level
< dep
)
6623 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, strlen(tok
))) {
6626 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
6633 if (matchcount
== 1) {
6634 strlcat(res
, tok
, sizeof res
);
6635 strlcat(res
, " ", sizeof res
);
6645 if (cmd_status
.index
== -1)
6646 cmd_getlist(parent
, tokens
[i
]);
6648 if (cmd_status
.len
> 0) {
6649 match
= cmd_getnext(dir
);
6650 strlcat(res
, match
, sizeof res
);
6651 gtk_entry_set_text(w
, res
);
6652 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6659 cmd_execute(struct tab
*t
, char *str
)
6661 struct cmd
*cmd
= NULL
;
6662 char *tok
, *last
, *s
= g_strdup(str
), *sc
, prefixstr
[4];
6663 int j
, len
, c
= 0, dep
= 0, matchcount
= 0, prefix
= -1;
6664 struct karg arg
= {0, NULL
, -1};
6665 int rv
= XT_CB_PASSTHROUGH
;
6670 for (j
= 0; j
<3 && isdigit(s
[j
]); j
++)
6676 while (isspace(s
[0]))
6679 if (strlen(s
) > 0 && strlen(prefixstr
) > 0)
6680 prefix
= atoi(prefixstr
);
6684 for (tok
= strtok_r(s
, " ", &last
); tok
;
6685 tok
= strtok_r(NULL
, " ", &last
)) {
6687 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6688 if (cmds
[j
].level
< dep
)
6690 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1: strlen(tok
);
6691 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, len
)) {
6695 if (len
== strlen(cmds
[j
].cmd
)) {
6701 if (matchcount
== 1) {
6706 show_oops(t
, "Invalid command: %s", str
);
6715 else if (cmd_prefix
> 0)
6718 if (j
> 0 && !(cmd
->type
& XT_PREFIX
) && arg
.p
> -1) {
6719 show_oops(t
, "No prefix allowed: %s", str
);
6723 arg
.s
= last
? g_strdup(last
) : g_strdup("");
6724 if (cmd
->type
& XT_INTARG
&& last
&& strlen(last
) > 0) {
6725 arg
.p
= atoi(arg
.s
);
6728 show_oops(t
, "Zero count");
6730 show_oops(t
, "Trailing characters");
6735 DNPRINTF(XT_D_CMD
, "%s: prefix %d arg %s\n", __func__
, arg
.p
, arg
.s
);
6751 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6754 show_oops_s("entry_key_cb invalid parameters");
6755 return (XT_CB_PASSTHROUGH
);
6758 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6759 e
->keyval
, e
->state
, t
);
6763 if (e
->keyval
== GDK_Escape
) {
6764 /* don't use focus_webview(t) because we want to type :cmds */
6765 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6768 return (handle_keypress(t
, e
, 1));
6772 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6774 int rv
= XT_CB_HANDLED
;
6775 const gchar
*c
= gtk_entry_get_text(w
);
6778 show_oops_s("cmd_keypress_cb parameters");
6779 return (XT_CB_PASSTHROUGH
);
6782 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6783 e
->keyval
, e
->state
, t
);
6787 e
->keyval
= GDK_Escape
;
6788 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6789 e
->keyval
= GDK_Escape
;
6791 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
6792 cmd_status
.index
= -1;
6794 switch (e
->keyval
) {
6797 cmd_complete(t
, (char *)&c
[1], 1);
6799 case GDK_ISO_Left_Tab
:
6801 cmd_complete(t
, (char *)&c
[1], -1);
6805 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
6813 if (c
[0] == '/' || c
[0] == '?')
6814 webkit_web_view_unmark_text_matches(t
->wv
);
6818 rv
= XT_CB_PASSTHROUGH
;
6824 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6827 show_oops_s("cmd_focusout_cb invalid parameters");
6828 return (XT_CB_PASSTHROUGH
);
6830 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
6835 if (show_url
== 0 || t
->focus_wv
)
6838 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6840 return (XT_CB_PASSTHROUGH
);
6844 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6847 const gchar
*c
= gtk_entry_get_text(entry
);
6850 show_oops_s("cmd_activate_cb invalid parameters");
6854 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6861 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6867 if (c
[0] == '/' || c
[0] == '?') {
6868 if (t
->search_text
) {
6869 g_free(t
->search_text
);
6870 t
->search_text
= NULL
;
6873 t
->search_text
= g_strdup(s
);
6875 g_free(global_search
);
6876 global_search
= g_strdup(s
);
6877 t
->search_forward
= c
[0] == '/';
6889 backward_cb(GtkWidget
*w
, struct tab
*t
)
6894 show_oops_s("backward_cb invalid parameters");
6898 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6905 forward_cb(GtkWidget
*w
, struct tab
*t
)
6910 show_oops_s("forward_cb invalid parameters");
6914 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6916 a
.i
= XT_NAV_FORWARD
;
6921 home_cb(GtkWidget
*w
, struct tab
*t
)
6924 show_oops_s("home_cb invalid parameters");
6928 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6934 stop_cb(GtkWidget
*w
, struct tab
*t
)
6936 WebKitWebFrame
*frame
;
6939 show_oops_s("stop_cb invalid parameters");
6943 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
6945 frame
= webkit_web_view_get_main_frame(t
->wv
);
6946 if (frame
== NULL
) {
6947 show_oops(t
, "stop_cb: no frame");
6951 webkit_web_frame_stop_loading(frame
);
6952 abort_favicon_download(t
);
6956 setup_webkit(struct tab
*t
)
6958 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
6959 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
6960 FALSE
, (char *)NULL
);
6962 warnx("webkit does not have \"enable-dns-prefetching\" property");
6963 g_object_set(G_OBJECT(t
->settings
),
6964 "user-agent", t
->user_agent
, (char *)NULL
);
6965 g_object_set(G_OBJECT(t
->settings
),
6966 "enable-scripts", enable_scripts
, (char *)NULL
);
6967 g_object_set(G_OBJECT(t
->settings
),
6968 "enable-plugins", enable_plugins
, (char *)NULL
);
6969 g_object_set(G_OBJECT(t
->settings
),
6970 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
6971 g_object_set(G_OBJECT(t
->settings
),
6972 "enable_spell_checking", enable_spell_checking
, (char *)NULL
);
6973 g_object_set(G_OBJECT(t
->settings
),
6974 "spell_checking_languages", spell_check_languages
, (char *)NULL
);
6975 g_object_set(G_OBJECT(t
->wv
),
6976 "full-content-zoom", TRUE
, (char *)NULL
);
6977 adjustfont_webkit(t
, XT_FONT_SET
);
6979 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6983 create_browser(struct tab
*t
)
6989 show_oops_s("create_browser invalid parameters");
6993 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
6994 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
6995 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
6996 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
6998 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
6999 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
7000 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
7002 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
7003 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
7006 t
->settings
= webkit_web_settings_new();
7008 if (user_agent
== NULL
) {
7009 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
7011 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
7014 t
->user_agent
= g_strdup(user_agent
);
7016 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
7028 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
7029 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
7030 gtk_widget_set_name(w
, "xxxterm");
7031 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
7032 g_signal_connect(G_OBJECT(w
), "delete_event",
7033 G_CALLBACK (gtk_main_quit
), NULL
);
7039 create_kiosk_toolbar(struct tab
*t
)
7041 GtkWidget
*toolbar
= NULL
, *b
;
7043 b
= gtk_hbox_new(FALSE
, 0);
7045 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7047 /* backward button */
7048 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7049 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7050 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7051 G_CALLBACK(backward_cb
), t
);
7052 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7054 /* forward button */
7055 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7056 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7057 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7058 G_CALLBACK(forward_cb
), t
);
7059 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7062 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7063 gtk_widget_set_sensitive(t
->gohome
, true);
7064 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7065 G_CALLBACK(home_cb
), t
);
7066 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7068 /* create widgets but don't use them */
7069 t
->uri_entry
= gtk_entry_new();
7070 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7071 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7072 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7078 create_toolbar(struct tab
*t
)
7080 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7082 b
= gtk_hbox_new(FALSE
, 0);
7084 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7087 /* backward button */
7088 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7089 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7090 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7091 G_CALLBACK(backward_cb
), t
);
7092 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7094 /* forward button */
7095 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7096 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7097 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7098 G_CALLBACK(forward_cb
), t
);
7099 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7103 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7104 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7105 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7106 G_CALLBACK(stop_cb
), t
);
7107 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7111 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7112 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7113 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7114 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7115 G_CALLBACK(js_toggle_cb
), t
);
7116 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7119 t
->uri_entry
= gtk_entry_new();
7120 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7121 G_CALLBACK(activate_uri_entry_cb
), t
);
7122 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7123 G_CALLBACK(entry_key_cb
), t
);
7125 eb1
= gtk_hbox_new(FALSE
, 0);
7126 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7127 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7128 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7131 if (fancy_bar
&& search_string
) {
7133 t
->search_entry
= gtk_entry_new();
7134 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7135 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7136 G_CALLBACK(activate_search_entry_cb
), t
);
7137 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7138 G_CALLBACK(entry_key_cb
), t
);
7139 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7140 eb2
= gtk_hbox_new(FALSE
, 0);
7141 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7142 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7144 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7154 TAILQ_FOREACH(t
, &tabs
, entry
)
7155 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7159 undo_close_tab_save(struct tab
*t
)
7163 struct undo
*u1
, *u2
;
7165 WebKitWebHistoryItem
*item
;
7167 if ((uri
= get_uri(t
->wv
)) == NULL
)
7170 u1
= g_malloc0(sizeof(struct undo
));
7171 u1
->uri
= g_strdup(uri
);
7173 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7175 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7176 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7179 /* forward history */
7180 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7184 u1
->history
= g_list_prepend(u1
->history
,
7185 webkit_web_history_item_copy(item
));
7186 items
= g_list_next(items
);
7191 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7192 u1
->history
= g_list_prepend(u1
->history
,
7193 webkit_web_history_item_copy(item
));
7197 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7201 u1
->history
= g_list_prepend(u1
->history
,
7202 webkit_web_history_item_copy(item
));
7203 items
= g_list_next(items
);
7206 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7208 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7209 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7210 TAILQ_REMOVE(&undos
, u2
, entry
);
7212 g_list_free(u2
->history
);
7221 delete_tab(struct tab
*t
)
7225 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7230 TAILQ_REMOVE(&tabs
, t
, entry
);
7232 /* halt all webkit activity */
7233 abort_favicon_download(t
);
7234 webkit_web_view_stop_loading(t
->wv
);
7235 undo_close_tab_save(t
);
7237 if (browser_mode
== XT_BM_KIOSK
) {
7238 gtk_widget_destroy(t
->uri_entry
);
7239 gtk_widget_destroy(t
->stop
);
7240 gtk_widget_destroy(t
->js_toggle
);
7243 gtk_widget_destroy(t
->vbox
);
7244 g_free(t
->user_agent
);
7245 g_free(t
->stylesheet
);
7248 if (TAILQ_EMPTY(&tabs
)) {
7249 if (browser_mode
== XT_BM_KIOSK
)
7250 create_new_tab(home
, NULL
, 1, -1);
7252 create_new_tab(NULL
, NULL
, 1, -1);
7255 /* recreate session */
7256 if (session_autosave
) {
7263 adjustfont_webkit(struct tab
*t
, int adjust
)
7268 show_oops_s("adjustfont_webkit invalid parameters");
7272 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7273 if (adjust
== XT_FONT_SET
) {
7274 t
->font_size
= default_font_size
;
7275 zoom
= default_zoom_level
;
7276 t
->font_size
+= adjust
;
7277 g_object_set(G_OBJECT(t
->settings
), "default-font-size",
7278 t
->font_size
, (char *)NULL
);
7279 g_object_get(G_OBJECT(t
->settings
), "default-font-size",
7280 &t
->font_size
, (char *)NULL
);
7282 t
->font_size
+= adjust
;
7283 zoom
+= adjust
/25.0;
7288 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7289 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7293 append_tab(struct tab
*t
)
7298 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7299 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7303 create_new_tab(char *title
, struct undo
*u
, int focus
, int position
)
7308 WebKitWebHistoryItem
*item
;
7312 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7314 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7315 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7319 t
= g_malloc0(sizeof *t
);
7321 if (title
== NULL
) {
7322 title
= "(untitled)";
7326 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7328 /* label + button for tab */
7329 b
= gtk_hbox_new(FALSE
, 0);
7332 #if GTK_CHECK_VERSION(2, 20, 0)
7333 t
->spinner
= gtk_spinner_new ();
7335 t
->label
= gtk_label_new(title
);
7336 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7337 gtk_widget_set_size_request(t
->label
, 100, 0);
7338 gtk_label_set_max_width_chars(GTK_LABEL(t
->label
), 20);
7339 gtk_label_set_ellipsize(GTK_LABEL(t
->label
), PANGO_ELLIPSIZE_END
);
7340 gtk_widget_set_size_request(b
, 130, 0);
7342 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7343 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7344 #if GTK_CHECK_VERSION(2, 20, 0)
7345 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7349 if (browser_mode
== XT_BM_KIOSK
)
7350 t
->toolbar
= create_kiosk_toolbar(t
);
7352 t
->toolbar
= create_toolbar(t
);
7354 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7357 t
->browser_win
= create_browser(t
);
7358 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7360 /* oops message for user feedback */
7361 t
->oops
= gtk_entry_new();
7362 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7363 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7364 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7365 gdk_color_parse(XT_COLOR_RED
, &color
);
7366 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7367 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7370 t
->cmd
= gtk_entry_new();
7371 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7372 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7373 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7376 t
->statusbar
= gtk_entry_new();
7377 gtk_entry_set_inner_border(GTK_ENTRY(t
->statusbar
), NULL
);
7378 gtk_entry_set_has_frame(GTK_ENTRY(t
->statusbar
), FALSE
);
7379 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7380 gdk_color_parse(XT_COLOR_BLACK
, &color
);
7381 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7382 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7383 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7384 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7386 /* xtp meaning is normal by default */
7387 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
7389 /* set empty favicon */
7390 xt_icon_from_name(t
, "text-html");
7392 /* and show it all */
7393 gtk_widget_show_all(b
);
7394 gtk_widget_show_all(t
->vbox
);
7396 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7399 id
= position
>= 0 ? position
: gtk_notebook_get_current_page(notebook
) + 1;
7400 if (id
> gtk_notebook_get_n_pages(notebook
))
7403 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7404 gtk_notebook_insert_page(notebook
, t
->vbox
, b
, id
);
7409 #if GTK_CHECK_VERSION(2, 20, 0)
7410 /* turn spinner off if we are a new tab without uri */
7412 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7413 gtk_widget_hide(t
->spinner
);
7416 /* make notebook tabs reorderable */
7417 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7419 g_object_connect(G_OBJECT(t
->cmd
),
7420 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7421 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7422 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7423 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7426 /* reuse wv_button_cb to hide oops */
7427 g_object_connect(G_OBJECT(t
->oops
),
7428 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7431 g_object_connect(G_OBJECT(t
->wv
),
7432 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7433 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7434 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7435 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7436 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7437 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7438 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7439 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7440 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7441 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7442 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
7443 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
7444 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7445 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7447 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7449 g_signal_connect(t
->wv
,
7450 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7451 g_signal_connect(t
->wv
,
7452 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7454 /* hijack the unused keys as if we were the browser */
7455 g_object_connect(G_OBJECT(t
->toolbar
),
7456 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7459 g_signal_connect(G_OBJECT(bb
), "button_press_event",
7460 G_CALLBACK(tab_close_cb
), t
);
7465 url_set_visibility();
7466 statusbar_set_visibility();
7469 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7470 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
7475 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
7479 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7484 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7485 /* restore the tab's history */
7486 if (u
&& u
->history
) {
7490 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
7491 items
= g_list_next(items
);
7494 item
= g_list_nth_data(u
->history
, u
->back
);
7496 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
7499 g_list_free(u
->history
);
7501 webkit_web_back_forward_list_clear(t
->bfl
);
7507 notebook_switchpage_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
7513 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
7515 if (gtk_notebook_get_current_page(notebook
) == -1)
7518 TAILQ_FOREACH(t
, &tabs
, entry
) {
7519 if (t
->tab_id
== pn
) {
7520 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
7523 uri
= webkit_web_view_get_title(t
->wv
);
7526 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
7532 /* can't use focus_webview here */
7533 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7540 notebook_pagereordered_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
7547 menuitem_response(struct tab
*t
)
7549 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7553 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
7555 GtkWidget
*menu
, *menu_items
;
7556 GdkEventButton
*bevent
;
7560 if (event
->type
== GDK_BUTTON_PRESS
) {
7561 bevent
= (GdkEventButton
*) event
;
7562 menu
= gtk_menu_new();
7564 TAILQ_FOREACH(ti
, &tabs
, entry
) {
7565 if ((uri
= get_uri(ti
->wv
)) == NULL
)
7566 /* XXX make sure there is something to print */
7567 /* XXX add gui pages in here to look purdy */
7569 menu_items
= gtk_menu_item_new_with_label(uri
);
7570 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_items
);
7571 gtk_widget_show(menu_items
);
7573 g_signal_connect_swapped((menu_items
),
7574 "activate", G_CALLBACK(menuitem_response
),
7578 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
7579 bevent
->button
, bevent
->time
);
7581 /* unref object so it'll free itself when popped down */
7582 #if !GTK_CHECK_VERSION(3, 0, 0)
7583 /* XXX does not need unref with gtk+3? */
7584 g_object_ref_sink(menu
);
7585 g_object_unref(menu
);
7588 return (TRUE
/* eat event */);
7591 return (FALSE
/* propagate */);
7595 icon_size_map(int icon_size
)
7597 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
7598 icon_size
> GTK_ICON_SIZE_DIALOG
)
7599 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
7605 create_button(char *name
, char *stockid
, int size
)
7607 GtkWidget
*button
, *image
;
7611 rcstring
= g_strdup_printf(
7612 "style \"%s-style\"\n"
7614 " GtkWidget::focus-padding = 0\n"
7615 " GtkWidget::focus-line-width = 0\n"
7619 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
7620 gtk_rc_parse_string(rcstring
);
7622 button
= gtk_button_new();
7623 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
7624 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
7626 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
7627 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7628 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
7629 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
7630 gtk_widget_set_name(button
, name
);
7631 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
7637 button_set_stockid(GtkWidget
*button
, char *stockid
)
7641 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
7642 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7643 gtk_button_set_image(GTK_BUTTON(button
), image
);
7647 clipb_primary_cb(GtkClipboard
*primary
, GdkEvent
*event
, gpointer notused
)
7649 GtkClipboard
*clipboard
;
7650 gchar
*p
= NULL
, *s
= NULL
;
7653 * This code is very aggressive!
7654 * It basically ensures that the primary and regular clipboard are
7655 * always set the same. This obviously messes with standard X protocol
7656 * but those clowns should have come up with something better.
7659 /* XXX make this setting? */
7660 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
7661 p
= gtk_clipboard_wait_for_text(primary
);
7663 DNPRINTF(XT_D_CLIP
, "primary cleaned\n");
7664 p
= gtk_clipboard_wait_for_text(clipboard
);
7666 gtk_clipboard_set_text(primary
, p
, -1);
7668 DNPRINTF(XT_D_CLIP
, "primary got selection\n");
7669 s
= gtk_clipboard_wait_for_text(clipboard
);
7672 * if s and p are the same the string was set by
7673 * clipb_clipboard_cb so do nothing in that case
7674 * to prevent endless loop
7679 gtk_clipboard_set_text(clipboard
, p
, -1);
7689 clipb_clipboard_cb(GtkClipboard
*clipboard
, GdkEvent
*event
, gpointer notused
)
7691 GtkClipboard
*primary
;
7692 gchar
*p
= NULL
, *s
= NULL
;
7694 DNPRINTF(XT_D_CLIP
, "clipboard got content\n");
7696 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
7697 p
= gtk_clipboard_wait_for_text(clipboard
);
7699 s
= gtk_clipboard_wait_for_text(primary
);
7702 * if s and p are the same the string was set by
7703 * clipb_primary_cb so do nothing in that case
7704 * to prevent endless loop and deselection of text
7709 gtk_clipboard_set_text(primary
, p
, -1);
7724 char file
[PATH_MAX
];
7727 vbox
= gtk_vbox_new(FALSE
, 0);
7728 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
7729 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
7730 #if !GTK_CHECK_VERSION(3, 0, 0)
7731 /* XXX seems to be needed with gtk+2 */
7732 gtk_notebook_set_tab_hborder(notebook
, 0);
7733 gtk_notebook_set_tab_vborder(notebook
, 0);
7735 gtk_notebook_set_scrollable(notebook
, TRUE
);
7736 notebook_tab_set_visibility(notebook
);
7737 gtk_notebook_set_show_border(notebook
, FALSE
);
7738 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
7740 abtn
= gtk_button_new();
7741 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
7742 gtk_widget_set_size_request(arrow
, -1, -1);
7743 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
7744 gtk_widget_set_size_request(abtn
, -1, 20);
7746 #if GTK_CHECK_VERSION(2, 20, 0)
7747 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
7749 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
7750 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
7751 gtk_widget_set_size_request(vbox
, -1, -1);
7753 g_object_connect(G_OBJECT(notebook
),
7754 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
7756 g_object_connect(G_OBJECT(notebook
),
7757 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb
), NULL
,
7759 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
7760 G_CALLBACK(arrow_cb
), NULL
);
7762 main_window
= create_window();
7763 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
7764 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
7767 for (i
= 0; i
< LENGTH(icons
); i
++) {
7768 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
7769 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
7770 l
= g_list_append(l
, pb
);
7772 gtk_window_set_default_icon_list(l
);
7775 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY
)),
7776 "owner-change", G_CALLBACK(clipb_primary_cb
), NULL
);
7777 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
)),
7778 "owner-change", G_CALLBACK(clipb_clipboard_cb
), NULL
);
7780 gtk_widget_show_all(abtn
);
7781 gtk_widget_show_all(main_window
);
7785 set_hook(void **hook
, char *name
)
7788 errx(1, "set_hook");
7790 if (*hook
== NULL
) {
7791 *hook
= dlsym(RTLD_NEXT
, name
);
7793 errx(1, "can't hook %s", name
);
7797 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7799 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
7801 g_return_val_if_fail(cookie1
, FALSE
);
7802 g_return_val_if_fail(cookie2
, FALSE
);
7804 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
7805 !strcmp (cookie1
->value
, cookie2
->value
) &&
7806 !strcmp (cookie1
->path
, cookie2
->path
) &&
7807 !strcmp (cookie1
->domain
, cookie2
->domain
));
7811 transfer_cookies(void)
7814 SoupCookie
*sc
, *pc
;
7816 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7818 for (;cf
; cf
= cf
->next
) {
7820 sc
= soup_cookie_copy(pc
);
7821 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
7824 soup_cookies_free(cf
);
7828 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
7833 print_cookie("soup_cookie_jar_delete_cookie", c
);
7835 if (cookies_enabled
== 0)
7838 if (jar
== NULL
|| c
== NULL
)
7841 /* find and remove from persistent jar */
7842 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7844 for (;cf
; cf
= cf
->next
) {
7846 if (soup_cookie_equal(ci
, c
)) {
7847 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
7852 soup_cookies_free(cf
);
7854 /* delete from session jar */
7855 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
7859 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
7861 struct domain
*d
= NULL
;
7865 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
7866 jar
, p_cookiejar
, s_cookiejar
);
7868 if (cookies_enabled
== 0)
7871 /* see if we are up and running */
7872 if (p_cookiejar
== NULL
) {
7873 _soup_cookie_jar_add_cookie(jar
, cookie
);
7876 /* disallow p_cookiejar adds, shouldn't happen */
7877 if (jar
== p_cookiejar
)
7881 if (jar
== NULL
|| cookie
== NULL
)
7884 if (enable_cookie_whitelist
&&
7885 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
7887 DNPRINTF(XT_D_COOKIE
,
7888 "soup_cookie_jar_add_cookie: reject %s\n",
7890 if (save_rejected_cookies
) {
7891 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
7892 show_oops_s("can't open reject cookie file");
7895 fseek(r_cookie_f
, 0, SEEK_END
);
7896 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7897 cookie
->http_only
? "#HttpOnly_" : "",
7899 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
7901 cookie
->secure
? "TRUE" : "FALSE",
7903 (gulong
)soup_date_to_time_t(cookie
->expires
) :
7910 if (!allow_volatile_cookies
)
7914 if (cookie
->expires
== NULL
&& session_timeout
) {
7915 soup_cookie_set_expires(cookie
,
7916 soup_date_new_from_now(session_timeout
));
7917 print_cookie("modified add cookie", cookie
);
7920 /* see if we are white listed for persistence */
7921 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
7922 /* add to persistent jar */
7923 c
= soup_cookie_copy(cookie
);
7924 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
7925 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
7928 /* add to session jar */
7929 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
7930 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
7936 char file
[PATH_MAX
];
7938 set_hook((void *)&_soup_cookie_jar_add_cookie
,
7939 "soup_cookie_jar_add_cookie");
7940 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
7941 "soup_cookie_jar_delete_cookie");
7943 if (cookies_enabled
== 0)
7947 * the following code is intricate due to overriding several libsoup
7949 * do not alter order of these operations.
7952 /* rejected cookies */
7953 if (save_rejected_cookies
)
7954 snprintf(rc_fname
, sizeof file
, "%s/%s", work_dir
, XT_REJECT_FILE
);
7956 /* persistent cookies */
7957 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_COOKIE_FILE
);
7958 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
7960 /* session cookies */
7961 s_cookiejar
= soup_cookie_jar_new();
7962 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
7963 cookie_policy
, (void *)NULL
);
7966 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
7970 setup_proxy(char *uri
)
7973 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
7974 soup_uri_free(proxy_uri
);
7978 if (http_proxy
!= uri
) {
7985 http_proxy
= g_strdup(uri
);
7986 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
7987 proxy_uri
= soup_uri_new(http_proxy
);
7988 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
7993 send_cmd_to_socket(char *cmd
)
7996 struct sockaddr_un sa
;
7998 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7999 warnx("%s: socket", __func__
);
8003 sa
.sun_family
= AF_UNIX
;
8004 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8005 work_dir
, XT_SOCKET_FILE
);
8008 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8009 warnx("%s: connect", __func__
);
8013 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
8014 warnx("%s: send", __func__
);
8025 socket_watcher(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
8028 char str
[XT_MAX_URL_LENGTH
];
8029 socklen_t t
= sizeof(struct sockaddr_un
);
8030 struct sockaddr_un sa
;
8035 gint fd
= g_io_channel_unix_get_fd(source
);
8037 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
8042 if (getpeereid(s
, &uid
, &gid
) == -1) {
8046 if (uid
!= getuid() || gid
!= getgid()) {
8047 warnx("unauthorized user");
8053 warnx("not a valid user");
8057 n
= recv(s
, str
, sizeof(str
), 0);
8061 tt
= TAILQ_LAST(&tabs
, tab_list
);
8062 cmd_execute(tt
, str
);
8070 struct sockaddr_un sa
;
8072 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8073 warn("is_running: socket");
8077 sa
.sun_family
= AF_UNIX
;
8078 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8079 work_dir
, XT_SOCKET_FILE
);
8082 /* connect to see if there is a listener */
8083 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
8084 rv
= 0; /* not running */
8086 rv
= 1; /* already running */
8097 struct sockaddr_un sa
;
8099 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8100 warn("build_socket: socket");
8104 sa
.sun_family
= AF_UNIX
;
8105 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8106 work_dir
, XT_SOCKET_FILE
);
8109 /* connect to see if there is a listener */
8110 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8111 /* no listener so we will */
8112 unlink(sa
.sun_path
);
8114 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8115 warn("build_socket: bind");
8119 if (listen(s
, 1) == -1) {
8120 warn("build_socket: listen");
8133 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8134 GtkTreeIter
*iter
, struct tab
*t
)
8138 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8146 completion_hover_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8147 GtkTreeIter
*iter
, struct tab
*t
)
8151 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8152 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), value
);
8153 gtk_editable_set_position(GTK_EDITABLE(t
->uri_entry
), -1);
8160 completion_add_uri(const gchar
*uri
)
8164 /* add uri to list_store */
8165 gtk_list_store_append(completion_model
, &iter
);
8166 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
8170 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
8171 GtkTreeIter
*iter
, gpointer user_data
)
8174 gboolean match
= FALSE
;
8176 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8182 match
= match_uri(value
, key
);
8189 completion_add(struct tab
*t
)
8191 /* enable completion for tab */
8192 t
->completion
= gtk_entry_completion_new();
8193 gtk_entry_completion_set_text_column(t
->completion
, 0);
8194 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8195 gtk_entry_completion_set_model(t
->completion
,
8196 GTK_TREE_MODEL(completion_model
));
8197 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8199 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8200 gtk_entry_completion_set_inline_selection(t
->completion
, TRUE
);
8201 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8202 G_CALLBACK(completion_select_cb
), t
);
8203 g_signal_connect(G_OBJECT (t
->completion
), "cursor-on-match",
8204 G_CALLBACK(completion_hover_cb
), t
);
8212 if (stat(dir
, &sb
)) {
8213 if (mkdir(dir
, S_IRWXU
) == -1)
8214 err(1, "mkdir %s", dir
);
8216 err(1, "stat %s", dir
);
8218 if (S_ISDIR(sb
.st_mode
) == 0)
8219 errx(1, "%s not a dir", dir
);
8220 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8221 warnx("fixing invalid permissions on %s", dir
);
8222 if (chmod(dir
, S_IRWXU
) == -1)
8223 err(1, "chmod %s", dir
);
8231 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8237 main(int argc
, char *argv
[])
8240 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8241 char conf
[PATH_MAX
] = { '\0' };
8242 char file
[PATH_MAX
];
8243 char *env_proxy
= NULL
;
8246 struct sigaction sact
;
8247 gchar
*priority
= g_strdup("NORMAL");
8248 GIOChannel
*channel
;
8252 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8254 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8263 errx(0 , "Version: %s", version
);
8266 strlcpy(conf
, optarg
, sizeof(conf
));
8269 strlcpy(named_session
, optarg
, sizeof(named_session
));
8290 RB_INIT(&downloads
);
8294 TAILQ_INIT(&aliases
);
8300 gnutls_global_init();
8302 /* generate session keys for xtp pages */
8303 generate_xtp_session_key(&dl_session_key
);
8304 generate_xtp_session_key(&hl_session_key
);
8305 generate_xtp_session_key(&cl_session_key
);
8306 generate_xtp_session_key(&fl_session_key
);
8309 gtk_init(&argc
, &argv
);
8310 if (!g_thread_supported())
8311 g_thread_init(NULL
);
8314 bzero(&sact
, sizeof(sact
));
8315 sigemptyset(&sact
.sa_mask
);
8316 sact
.sa_handler
= sigchild
;
8317 sact
.sa_flags
= SA_NOCLDSTOP
;
8318 sigaction(SIGCHLD
, &sact
, NULL
);
8320 /* set download dir */
8321 pwd
= getpwuid(getuid());
8323 errx(1, "invalid user %d", getuid());
8324 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8326 /* set default string settings */
8327 home
= g_strdup("https://www.cyphertite.com");
8328 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8329 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
8330 strlcpy(runtime_settings
,"runtime", sizeof runtime_settings
);
8332 /* read config file */
8333 if (strlen(conf
) == 0)
8334 snprintf(conf
, sizeof conf
, "%s/.%s",
8335 pwd
->pw_dir
, XT_CONF_FILE
);
8336 config_parse(conf
, 0);
8338 /* working directory */
8339 if (strlen(work_dir
) == 0)
8340 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
8341 pwd
->pw_dir
, XT_DIR
);
8344 /* icon cache dir */
8345 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
8349 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
8353 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
8354 work_dir
, XT_SESSIONS_DIR
);
8355 xxx_dir(sessions_dir
);
8357 /* runtime settings that can override config file */
8358 if (runtime_settings
[0] != '\0')
8359 config_parse(runtime_settings
, 1);
8362 if (!strcmp(download_dir
, pwd
->pw_dir
))
8363 strlcat(download_dir
, "/downloads", sizeof download_dir
);
8364 xxx_dir(download_dir
);
8366 /* favorites file */
8367 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
8368 if (stat(file
, &sb
)) {
8369 warnx("favorites file doesn't exist, creating it");
8370 if ((f
= fopen(file
, "w")) == NULL
)
8371 err(1, "favorites");
8376 session
= webkit_get_default_session();
8377 /* XXX ssl-priority property not quite available yet */
8378 if (is_g_object_setting(G_OBJECT(session
), "ssl-priority"))
8379 g_object_set(G_OBJECT(session
), "ssl-priority", priority
,
8382 warnx("session does not have \"ssl-priority\" property");
8387 if (stat(ssl_ca_file
, &sb
)) {
8388 warnx("no CA file: %s", ssl_ca_file
);
8389 g_free(ssl_ca_file
);
8392 g_object_set(session
,
8393 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
8394 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8399 env_proxy
= getenv("http_proxy");
8401 setup_proxy(env_proxy
);
8403 setup_proxy(http_proxy
);
8406 send_cmd_to_socket(argv
[0]);
8410 /* set some connection parameters */
8411 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8412 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8415 /* see if there is already an xxxterm running */
8416 if (single_instance
&& is_running()) {
8418 warnx("already running");
8424 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8425 send_cmd_to_socket(cmd
);
8435 /* uri completion */
8436 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8441 if (save_global_history
)
8442 restore_global_history();
8444 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
8445 restore_saved_tabs();
8447 a
.s
= named_session
;
8448 a
.i
= XT_SES_DONOTHING
;
8449 open_tabs(NULL
, &a
);
8453 create_new_tab(argv
[0], NULL
, focus
, -1);
8460 if (TAILQ_EMPTY(&tabs
))
8461 create_new_tab(home
, NULL
, 1, -1);
8464 if ((s
= build_socket()) != -1) {
8465 channel
= g_io_channel_unix_new(s
);
8466 g_io_add_watch(channel
, G_IO_IN
, socket_watcher
, NULL
);
8471 gnutls_global_deinit();