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
);
5847 case WEBKIT_LOAD_COMMITTED
:
5849 if ((uri
= get_uri(wview
)) != NULL
) {
5850 if (strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
5851 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
5857 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
5860 /* check if js white listing is enabled */
5861 if (enable_js_whitelist
) {
5862 uri
= get_uri(wview
);
5863 check_and_set_js(uri
, t
);
5869 show_ca_status(t
, uri
);
5871 /* we know enough to autosave the session */
5872 if (session_autosave
) {
5878 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
5882 case WEBKIT_LOAD_FINISHED
:
5884 uri
= get_uri(wview
);
5888 if (!strncmp(uri
, "http://", strlen("http://")) ||
5889 !strncmp(uri
, "https://", strlen("https://")) ||
5890 !strncmp(uri
, "file://", strlen("file://"))) {
5892 h
= RB_FIND(history_list
, &hl
, &find
);
5894 title
= webkit_web_view_get_title(wview
);
5895 set
= title
? title
: uri
;
5896 h
= g_malloc(sizeof *h
);
5897 h
->uri
= g_strdup(uri
);
5898 h
->title
= g_strdup(set
);
5899 RB_INSERT(history_list
, &hl
, h
);
5900 completion_add_uri(h
->uri
);
5901 update_history_tabs(NULL
);
5905 set_status(t
, (char *)uri
, XT_STATUS_URI
);
5906 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5907 case WEBKIT_LOAD_FAILED
:
5910 #if GTK_CHECK_VERSION(2, 20, 0)
5911 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5912 gtk_widget_hide(t
->spinner
);
5914 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
5915 if (s_loading
&& !strcmp(s_loading
, "Loading"))
5916 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5918 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
5923 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
5925 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
5926 webkit_web_view_can_go_back(wview
));
5928 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
5929 webkit_web_view_can_go_forward(wview
));
5931 /* take focus if we are visible */
5936 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
5938 const gchar
*set
= NULL
, *title
= NULL
;
5940 title
= webkit_web_view_get_title(wview
);
5941 set
= title
? title
: get_uri(wview
);
5943 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
5944 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
5946 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
5947 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
5952 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5954 run_script(t
, JS_HINTING
);
5958 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
5960 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
5961 progress
== 100 ? 0 : (double)progress
/ 100);
5962 if (show_url
== 0) {
5963 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->statusbar
),
5964 progress
== 100 ? 0 : (double)progress
/ 100);
5969 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
5970 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
5971 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
5974 WebKitWebNavigationReason reason
;
5975 struct domain
*d
= NULL
;
5978 show_oops_s("webview_npd_cb invalid parameters");
5982 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
5984 webkit_network_request_get_uri(request
));
5986 uri
= (char *)webkit_network_request_get_uri(request
);
5988 /* if this is an xtp url, we don't load anything else */
5989 if (parse_xtp_url(t
, uri
))
5992 if (t
->ctrl_click
) {
5994 create_new_tab(uri
, NULL
, ctrl_click_focus
, -1);
5995 webkit_web_policy_decision_ignore(pd
);
5996 return (TRUE
); /* we made the decission */
6000 * This is a little hairy but it comes down to this:
6001 * when we run in whitelist mode we have to assist the browser in
6002 * opening the URL that it would have opened in a new tab.
6004 reason
= webkit_web_navigation_action_get_reason(na
);
6005 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6006 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6007 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6010 webkit_web_policy_decision_use(pd
);
6011 return (TRUE
); /* we made the decission */
6018 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6021 struct domain
*d
= NULL
;
6023 WebKitWebView
*webview
= NULL
;
6025 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6026 webkit_web_view_get_uri(wv
));
6029 /* open in current tab */
6031 } else if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6032 uri
= webkit_web_view_get_uri(wv
);
6033 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6036 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6038 } else if (enable_scripts
== 1) {
6039 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6047 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6050 struct domain
*d
= NULL
;
6052 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6054 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6055 uri
= webkit_web_view_get_uri(wv
);
6056 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6060 } else if (enable_scripts
== 1)
6067 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6069 /* we can not eat the event without throwing gtk off so defer it */
6071 /* catch middle click */
6072 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6077 /* catch ctrl click */
6078 if (e
->type
== GDK_BUTTON_RELEASE
&&
6079 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6084 return (XT_CB_PASSTHROUGH
);
6088 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6090 struct mime_type
*m
;
6092 m
= find_mime_type(mime_type
);
6100 show_oops(t
, "can't fork mime handler");
6109 execlp(m
->mt_action
, m
->mt_action
,
6110 webkit_network_request_get_uri(request
), (void *)NULL
);
6119 get_mime_type(char *file
)
6121 const char *mime_type
;
6125 if (g_str_has_prefix(file
, "file://"))
6126 file
+= strlen("file://");
6128 gf
= g_file_new_for_path(file
);
6129 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6131 mime_type
= g_file_info_get_content_type(fi
);
6139 run_download_mimehandler(char *mime_type
, char *file
)
6141 struct mime_type
*m
;
6143 m
= find_mime_type(mime_type
);
6149 show_oops_s("can't fork download mime handler");
6158 if (g_str_has_prefix(file
, "file://"))
6159 file
+= strlen("file://");
6160 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6169 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6172 WebKitDownloadStatus status
;
6173 const gchar
*file
= NULL
, *mime
= NULL
;
6175 if (download
== NULL
)
6177 status
= webkit_download_get_status(download
);
6178 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6181 file
= webkit_download_get_destination_uri(download
);
6184 mime
= get_mime_type((char *)file
);
6188 run_download_mimehandler((char *)mime
, (char *)file
);
6192 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6193 WebKitNetworkRequest
*request
, char *mime_type
,
6194 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6197 show_oops_s("webview_mimetype_cb invalid parameters");
6201 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6202 t
->tab_id
, mime_type
);
6204 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6205 webkit_web_policy_decision_ignore(decision
);
6210 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6211 webkit_web_policy_decision_download(decision
);
6219 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6222 const gchar
*filename
;
6224 struct download
*download_entry
;
6227 if (wk_download
== NULL
|| t
== NULL
) {
6228 show_oops_s("%s invalid parameters", __func__
);
6232 filename
= webkit_download_get_suggested_filename(wk_download
);
6233 if (filename
== NULL
)
6234 return (FALSE
); /* abort download */
6236 uri
= g_strdup_printf("file://%s/%s", download_dir
, filename
);
6238 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6239 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6241 webkit_download_set_destination_uri(wk_download
, uri
);
6243 if (webkit_download_get_status(wk_download
) ==
6244 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6245 show_oops(t
, "%s: download failed to start", __func__
);
6247 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6249 /* connect "download first" mime handler */
6250 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6251 G_CALLBACK(download_status_changed_cb
), NULL
);
6253 download_entry
= g_malloc(sizeof(struct download
));
6254 download_entry
->download
= wk_download
;
6255 download_entry
->tab
= t
;
6256 download_entry
->id
= next_download_id
++;
6257 RB_INSERT(download_list
, &downloads
, download_entry
);
6258 /* get from history */
6259 g_object_ref(wk_download
);
6260 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6261 show_oops(t
, "Download of '%s' started...",
6262 basename(webkit_download_get_destination_uri(wk_download
)));
6268 /* sync other download manager tabs */
6269 update_download_tabs(NULL
);
6272 * NOTE: never redirect/render the current tab before this
6273 * function returns. This will cause the download to never start.
6275 return (ret
); /* start download */
6279 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6281 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6284 show_oops_s("webview_hover_cb");
6289 set_status(t
, uri
, XT_STATUS_LINK
);
6292 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6297 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6299 struct key_binding
*k
;
6301 TAILQ_FOREACH(k
, &kbl
, entry
)
6302 if (e
->keyval
== k
->key
&& (entry
? k
->use_in_entry
: 1)) {
6304 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6305 return (cmd_execute(t
, k
->cmd
));
6306 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6307 return (cmd_execute(t
, k
->cmd
));
6311 return (XT_CB_PASSTHROUGH
);
6315 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6317 char s
[2], buf
[128];
6318 const char *errstr
= NULL
;
6321 /* don't use w directly; use t->whatever instead */
6324 show_oops_s("wv_keypress_after_cb");
6325 return (XT_CB_PASSTHROUGH
);
6328 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6329 e
->keyval
, e
->state
, t
);
6333 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6335 return (XT_CB_HANDLED
);
6339 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6340 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6342 /* we have a string */
6344 /* we have a number */
6345 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6353 /* XXX unfuck this */
6354 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6355 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6356 /* last input was numerical */
6358 l
= strlen(t
->hint_num
);
6365 t
->hint_num
[l
] = '\0';
6369 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6370 /* last input was alphanumerical */
6372 l
= strlen(t
->hint_buf
);
6379 t
->hint_buf
[l
] = '\0';
6389 /* numerical input */
6390 if (CLEAN(e
->state
) == 0 &&
6391 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6392 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6393 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6394 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6397 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
6399 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6402 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6404 t
->hint_mode
= XT_HINT_NUMERICAL
;
6408 /* empty the counter buffer */
6409 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6410 return (XT_CB_HANDLED
);
6413 /* alphanumerical input */
6415 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6416 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6417 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6418 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6419 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6420 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6421 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6424 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6427 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6429 t
->hint_mode
= XT_HINT_ALPHANUM
;
6432 /* empty the counter buffer */
6433 bzero(t
->hint_num
, sizeof t
->hint_num
);
6434 return (XT_CB_HANDLED
);
6437 return (XT_CB_HANDLED
);
6440 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6441 if (CLEAN(e
->state
) == 0 && isdigit(s
[0])) {
6442 cmd_prefix
= 10 * cmd_prefix
+ atoi(s
);
6446 return (handle_keypress(t
, e
, 0));
6450 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6454 return (XT_CB_PASSTHROUGH
);
6458 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6460 const gchar
*c
= gtk_entry_get_text(w
);
6464 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6465 e
->keyval
, e
->state
, t
);
6468 show_oops_s("cmd_keyrelease_cb invalid parameters");
6469 return (XT_CB_PASSTHROUGH
);
6472 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6473 e
->keyval
, e
->state
, t
);
6477 if (strlen(c
) == 1) {
6478 webkit_web_view_unmark_text_matches(t
->wv
);
6484 else if (c
[0] == '?')
6490 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6492 /* not found, mark red */
6493 gdk_color_parse(XT_COLOR_RED
, &color
);
6494 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6495 /* unmark and remove selection */
6496 webkit_web_view_unmark_text_matches(t
->wv
);
6497 /* my kingdom for a way to unselect text in webview */
6499 /* found, highlight all */
6500 webkit_web_view_unmark_text_matches(t
->wv
);
6501 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6502 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6503 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6504 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6507 return (XT_CB_PASSTHROUGH
);
6511 match_uri(const gchar
*uri
, const gchar
*key
) {
6514 gboolean match
= FALSE
;
6518 if (!strncmp(key
, uri
, len
))
6521 voffset
= strstr(uri
, "/") + 2;
6522 if (!strncmp(key
, voffset
, len
))
6524 else if (g_str_has_prefix(voffset
, "www.")) {
6525 voffset
= voffset
+ strlen("www.");
6526 if (!strncmp(key
, voffset
, len
))
6535 cmd_getlist(int id
, char *key
)
6540 if (id
>= 0 && (cmds
[id
].type
& XT_URLARG
)) {
6541 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6542 if (match_uri(h
->uri
, key
)) {
6543 cmd_status
.list
[c
] = (char *)h
->uri
;
6552 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6554 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6555 if(cmds
[i
].level
< dep
)
6557 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6558 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6566 cmd_getnext(int dir
)
6568 cmd_status
.index
+= dir
;
6570 if (cmd_status
.index
< 0)
6571 cmd_status
.index
= cmd_status
.len
- 1;
6572 else if (cmd_status
.index
>= cmd_status
.len
)
6573 cmd_status
.index
= 0;
6575 return cmd_status
.list
[cmd_status
.index
];
6579 cmd_tokenize(char *s
, char *tokens
[])
6583 size_t len
= strlen(s
);
6584 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6586 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6596 cmd_complete(struct tab
*t
, char *str
, int dir
)
6598 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6599 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1, matchcount
= 0;
6600 char *tok
, *match
, *s
= g_strdup(str
);
6602 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6605 DNPRINTF(XT_D_CMD
, "%s: complete %s\n", __func__
, str
);
6608 for (i
= 0; isdigit(s
[i
]); i
++)
6611 for (; isspace(s
[i
]); i
++)
6616 levels
= cmd_tokenize(s
, tokens
);
6618 for (i
= 0; i
< levels
- 1; i
++) {
6621 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6622 if (cmds
[j
].level
< dep
)
6624 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, strlen(tok
))) {
6627 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
6634 if (matchcount
== 1) {
6635 strlcat(res
, tok
, sizeof res
);
6636 strlcat(res
, " ", sizeof res
);
6646 if (cmd_status
.index
== -1)
6647 cmd_getlist(parent
, tokens
[i
]);
6649 if (cmd_status
.len
> 0) {
6650 match
= cmd_getnext(dir
);
6651 strlcat(res
, match
, sizeof res
);
6652 gtk_entry_set_text(w
, res
);
6653 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6660 cmd_execute(struct tab
*t
, char *str
)
6662 struct cmd
*cmd
= NULL
;
6663 char *tok
, *last
, *s
= g_strdup(str
), *sc
, prefixstr
[4];
6664 int j
, len
, c
= 0, dep
= 0, matchcount
= 0, prefix
= -1;
6665 struct karg arg
= {0, NULL
, -1};
6666 int rv
= XT_CB_PASSTHROUGH
;
6671 for (j
= 0; j
<3 && isdigit(s
[j
]); j
++)
6677 while (isspace(s
[0]))
6680 if (strlen(s
) > 0 && strlen(prefixstr
) > 0)
6681 prefix
= atoi(prefixstr
);
6685 for (tok
= strtok_r(s
, " ", &last
); tok
;
6686 tok
= strtok_r(NULL
, " ", &last
)) {
6688 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6689 if (cmds
[j
].level
< dep
)
6691 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1: strlen(tok
);
6692 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, len
)) {
6696 if (len
== strlen(cmds
[j
].cmd
)) {
6702 if (matchcount
== 1) {
6707 show_oops(t
, "Invalid command: %s", str
);
6716 else if (cmd_prefix
> 0)
6719 if (j
> 0 && !(cmd
->type
& XT_PREFIX
) && arg
.p
> -1) {
6720 show_oops(t
, "No prefix allowed: %s", str
);
6724 arg
.s
= last
? g_strdup(last
) : g_strdup("");
6725 if (cmd
->type
& XT_INTARG
&& last
&& strlen(last
) > 0) {
6726 arg
.p
= atoi(arg
.s
);
6729 show_oops(t
, "Zero count");
6731 show_oops(t
, "Trailing characters");
6736 DNPRINTF(XT_D_CMD
, "%s: prefix %d arg %s\n", __func__
, arg
.p
, arg
.s
);
6752 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6755 show_oops_s("entry_key_cb invalid parameters");
6756 return (XT_CB_PASSTHROUGH
);
6759 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6760 e
->keyval
, e
->state
, t
);
6764 if (e
->keyval
== GDK_Escape
) {
6765 /* don't use focus_webview(t) because we want to type :cmds */
6766 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6769 return (handle_keypress(t
, e
, 1));
6773 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6775 int rv
= XT_CB_HANDLED
;
6776 const gchar
*c
= gtk_entry_get_text(w
);
6779 show_oops_s("cmd_keypress_cb parameters");
6780 return (XT_CB_PASSTHROUGH
);
6783 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6784 e
->keyval
, e
->state
, t
);
6788 e
->keyval
= GDK_Escape
;
6789 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6790 e
->keyval
= GDK_Escape
;
6792 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
6793 cmd_status
.index
= -1;
6795 switch (e
->keyval
) {
6798 cmd_complete(t
, (char *)&c
[1], 1);
6800 case GDK_ISO_Left_Tab
:
6802 cmd_complete(t
, (char *)&c
[1], -1);
6806 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
6814 if (c
[0] == '/' || c
[0] == '?')
6815 webkit_web_view_unmark_text_matches(t
->wv
);
6819 rv
= XT_CB_PASSTHROUGH
;
6825 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6828 show_oops_s("cmd_focusout_cb invalid parameters");
6829 return (XT_CB_PASSTHROUGH
);
6831 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
6836 if (show_url
== 0 || t
->focus_wv
)
6839 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6841 return (XT_CB_PASSTHROUGH
);
6845 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6848 const gchar
*c
= gtk_entry_get_text(entry
);
6851 show_oops_s("cmd_activate_cb invalid parameters");
6855 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6862 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
6868 if (c
[0] == '/' || c
[0] == '?') {
6869 if (t
->search_text
) {
6870 g_free(t
->search_text
);
6871 t
->search_text
= NULL
;
6874 t
->search_text
= g_strdup(s
);
6876 g_free(global_search
);
6877 global_search
= g_strdup(s
);
6878 t
->search_forward
= c
[0] == '/';
6890 backward_cb(GtkWidget
*w
, struct tab
*t
)
6895 show_oops_s("backward_cb invalid parameters");
6899 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6906 forward_cb(GtkWidget
*w
, struct tab
*t
)
6911 show_oops_s("forward_cb invalid parameters");
6915 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6917 a
.i
= XT_NAV_FORWARD
;
6922 home_cb(GtkWidget
*w
, struct tab
*t
)
6925 show_oops_s("home_cb invalid parameters");
6929 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6935 stop_cb(GtkWidget
*w
, struct tab
*t
)
6937 WebKitWebFrame
*frame
;
6940 show_oops_s("stop_cb invalid parameters");
6944 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
6946 frame
= webkit_web_view_get_main_frame(t
->wv
);
6947 if (frame
== NULL
) {
6948 show_oops(t
, "stop_cb: no frame");
6952 webkit_web_frame_stop_loading(frame
);
6953 abort_favicon_download(t
);
6957 setup_webkit(struct tab
*t
)
6959 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
6960 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
6961 FALSE
, (char *)NULL
);
6963 warnx("webkit does not have \"enable-dns-prefetching\" property");
6964 g_object_set(G_OBJECT(t
->settings
),
6965 "user-agent", t
->user_agent
, (char *)NULL
);
6966 g_object_set(G_OBJECT(t
->settings
),
6967 "enable-scripts", enable_scripts
, (char *)NULL
);
6968 g_object_set(G_OBJECT(t
->settings
),
6969 "enable-plugins", enable_plugins
, (char *)NULL
);
6970 g_object_set(G_OBJECT(t
->settings
),
6971 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
6972 g_object_set(G_OBJECT(t
->settings
),
6973 "enable_spell_checking", enable_spell_checking
, (char *)NULL
);
6974 g_object_set(G_OBJECT(t
->settings
),
6975 "spell_checking_languages", spell_check_languages
, (char *)NULL
);
6976 g_object_set(G_OBJECT(t
->wv
),
6977 "full-content-zoom", TRUE
, (char *)NULL
);
6978 adjustfont_webkit(t
, XT_FONT_SET
);
6980 webkit_web_view_set_settings(t
->wv
, t
->settings
);
6984 create_browser(struct tab
*t
)
6990 show_oops_s("create_browser invalid parameters");
6994 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
6995 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
6996 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
6997 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
6999 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
7000 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
7001 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
7003 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
7004 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
7007 t
->settings
= webkit_web_settings_new();
7009 if (user_agent
== NULL
) {
7010 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
7012 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
7015 t
->user_agent
= g_strdup(user_agent
);
7017 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
7029 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
7030 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
7031 gtk_widget_set_name(w
, "xxxterm");
7032 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
7033 g_signal_connect(G_OBJECT(w
), "delete_event",
7034 G_CALLBACK (gtk_main_quit
), NULL
);
7040 create_kiosk_toolbar(struct tab
*t
)
7042 GtkWidget
*toolbar
= NULL
, *b
;
7044 b
= gtk_hbox_new(FALSE
, 0);
7046 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7048 /* backward button */
7049 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7050 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7051 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7052 G_CALLBACK(backward_cb
), t
);
7053 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7055 /* forward button */
7056 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7057 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7058 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7059 G_CALLBACK(forward_cb
), t
);
7060 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7063 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7064 gtk_widget_set_sensitive(t
->gohome
, true);
7065 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7066 G_CALLBACK(home_cb
), t
);
7067 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7069 /* create widgets but don't use them */
7070 t
->uri_entry
= gtk_entry_new();
7071 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7072 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7073 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7079 create_toolbar(struct tab
*t
)
7081 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7083 b
= gtk_hbox_new(FALSE
, 0);
7085 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7088 /* backward button */
7089 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7090 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7091 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7092 G_CALLBACK(backward_cb
), t
);
7093 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7095 /* forward button */
7096 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7097 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7098 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7099 G_CALLBACK(forward_cb
), t
);
7100 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7104 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7105 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7106 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7107 G_CALLBACK(stop_cb
), t
);
7108 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7112 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7113 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7114 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7115 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7116 G_CALLBACK(js_toggle_cb
), t
);
7117 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7120 t
->uri_entry
= gtk_entry_new();
7121 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7122 G_CALLBACK(activate_uri_entry_cb
), t
);
7123 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7124 G_CALLBACK(entry_key_cb
), t
);
7126 eb1
= gtk_hbox_new(FALSE
, 0);
7127 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7128 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7129 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7132 if (fancy_bar
&& search_string
) {
7134 t
->search_entry
= gtk_entry_new();
7135 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7136 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7137 G_CALLBACK(activate_search_entry_cb
), t
);
7138 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7139 G_CALLBACK(entry_key_cb
), t
);
7140 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7141 eb2
= gtk_hbox_new(FALSE
, 0);
7142 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7143 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7145 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7155 TAILQ_FOREACH(t
, &tabs
, entry
)
7156 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7160 undo_close_tab_save(struct tab
*t
)
7164 struct undo
*u1
, *u2
;
7166 WebKitWebHistoryItem
*item
;
7168 if ((uri
= get_uri(t
->wv
)) == NULL
)
7171 u1
= g_malloc0(sizeof(struct undo
));
7172 u1
->uri
= g_strdup(uri
);
7174 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7176 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7177 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7180 /* forward history */
7181 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7185 u1
->history
= g_list_prepend(u1
->history
,
7186 webkit_web_history_item_copy(item
));
7187 items
= g_list_next(items
);
7192 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7193 u1
->history
= g_list_prepend(u1
->history
,
7194 webkit_web_history_item_copy(item
));
7198 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7202 u1
->history
= g_list_prepend(u1
->history
,
7203 webkit_web_history_item_copy(item
));
7204 items
= g_list_next(items
);
7207 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7209 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7210 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7211 TAILQ_REMOVE(&undos
, u2
, entry
);
7213 g_list_free(u2
->history
);
7222 delete_tab(struct tab
*t
)
7226 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7231 TAILQ_REMOVE(&tabs
, t
, entry
);
7233 /* halt all webkit activity */
7234 abort_favicon_download(t
);
7235 webkit_web_view_stop_loading(t
->wv
);
7236 undo_close_tab_save(t
);
7238 if (browser_mode
== XT_BM_KIOSK
) {
7239 gtk_widget_destroy(t
->uri_entry
);
7240 gtk_widget_destroy(t
->stop
);
7241 gtk_widget_destroy(t
->js_toggle
);
7244 gtk_widget_destroy(t
->vbox
);
7245 g_free(t
->user_agent
);
7246 g_free(t
->stylesheet
);
7249 if (TAILQ_EMPTY(&tabs
)) {
7250 if (browser_mode
== XT_BM_KIOSK
)
7251 create_new_tab(home
, NULL
, 1, -1);
7253 create_new_tab(NULL
, NULL
, 1, -1);
7256 /* recreate session */
7257 if (session_autosave
) {
7264 adjustfont_webkit(struct tab
*t
, int adjust
)
7269 show_oops_s("adjustfont_webkit invalid parameters");
7273 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7274 if (adjust
== XT_FONT_SET
) {
7275 t
->font_size
= default_font_size
;
7276 zoom
= default_zoom_level
;
7277 t
->font_size
+= adjust
;
7278 g_object_set(G_OBJECT(t
->settings
), "default-font-size",
7279 t
->font_size
, (char *)NULL
);
7280 g_object_get(G_OBJECT(t
->settings
), "default-font-size",
7281 &t
->font_size
, (char *)NULL
);
7283 t
->font_size
+= adjust
;
7284 zoom
+= adjust
/25.0;
7289 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7290 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7294 append_tab(struct tab
*t
)
7299 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7300 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7304 create_new_tab(char *title
, struct undo
*u
, int focus
, int position
)
7309 WebKitWebHistoryItem
*item
;
7313 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7315 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7316 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7320 t
= g_malloc0(sizeof *t
);
7322 if (title
== NULL
) {
7323 title
= "(untitled)";
7327 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7329 /* label + button for tab */
7330 b
= gtk_hbox_new(FALSE
, 0);
7333 #if GTK_CHECK_VERSION(2, 20, 0)
7334 t
->spinner
= gtk_spinner_new ();
7336 t
->label
= gtk_label_new(title
);
7337 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7338 gtk_widget_set_size_request(t
->label
, 100, 0);
7339 gtk_label_set_max_width_chars(GTK_LABEL(t
->label
), 20);
7340 gtk_label_set_ellipsize(GTK_LABEL(t
->label
), PANGO_ELLIPSIZE_END
);
7341 gtk_widget_set_size_request(b
, 130, 0);
7343 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7344 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7345 #if GTK_CHECK_VERSION(2, 20, 0)
7346 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7350 if (browser_mode
== XT_BM_KIOSK
)
7351 t
->toolbar
= create_kiosk_toolbar(t
);
7353 t
->toolbar
= create_toolbar(t
);
7355 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7358 t
->browser_win
= create_browser(t
);
7359 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7361 /* oops message for user feedback */
7362 t
->oops
= gtk_entry_new();
7363 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7364 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7365 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7366 gdk_color_parse(XT_COLOR_RED
, &color
);
7367 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7368 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7371 t
->cmd
= gtk_entry_new();
7372 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7373 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7374 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7377 t
->statusbar
= gtk_entry_new();
7378 gtk_entry_set_inner_border(GTK_ENTRY(t
->statusbar
), NULL
);
7379 gtk_entry_set_has_frame(GTK_ENTRY(t
->statusbar
), FALSE
);
7380 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7381 gdk_color_parse(XT_COLOR_BLACK
, &color
);
7382 gtk_widget_modify_base(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7383 gdk_color_parse(XT_COLOR_WHITE
, &color
);
7384 gtk_widget_modify_text(t
->statusbar
, GTK_STATE_NORMAL
, &color
);
7385 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7387 /* xtp meaning is normal by default */
7388 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
7390 /* set empty favicon */
7391 xt_icon_from_name(t
, "text-html");
7393 /* and show it all */
7394 gtk_widget_show_all(b
);
7395 gtk_widget_show_all(t
->vbox
);
7397 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7400 id
= position
>= 0 ? position
: gtk_notebook_get_current_page(notebook
) + 1;
7401 if (id
> gtk_notebook_get_n_pages(notebook
))
7404 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7405 gtk_notebook_insert_page(notebook
, t
->vbox
, b
, id
);
7410 #if GTK_CHECK_VERSION(2, 20, 0)
7411 /* turn spinner off if we are a new tab without uri */
7413 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7414 gtk_widget_hide(t
->spinner
);
7417 /* make notebook tabs reorderable */
7418 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7420 g_object_connect(G_OBJECT(t
->cmd
),
7421 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7422 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7423 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7424 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7427 /* reuse wv_button_cb to hide oops */
7428 g_object_connect(G_OBJECT(t
->oops
),
7429 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7432 g_object_connect(G_OBJECT(t
->wv
),
7433 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7434 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7435 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7436 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7437 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7438 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7439 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7440 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7441 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7442 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7443 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
7444 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
7445 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7446 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7448 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7450 g_signal_connect(t
->wv
,
7451 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7452 g_signal_connect(t
->wv
,
7453 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7455 /* hijack the unused keys as if we were the browser */
7456 g_object_connect(G_OBJECT(t
->toolbar
),
7457 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
7460 g_signal_connect(G_OBJECT(bb
), "button_press_event",
7461 G_CALLBACK(tab_close_cb
), t
);
7466 url_set_visibility();
7467 statusbar_set_visibility();
7470 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7471 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
7476 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
7480 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7485 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7486 /* restore the tab's history */
7487 if (u
&& u
->history
) {
7491 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
7492 items
= g_list_next(items
);
7495 item
= g_list_nth_data(u
->history
, u
->back
);
7497 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
7500 g_list_free(u
->history
);
7502 webkit_web_back_forward_list_clear(t
->bfl
);
7508 notebook_switchpage_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
7514 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
7516 if (gtk_notebook_get_current_page(notebook
) == -1)
7519 TAILQ_FOREACH(t
, &tabs
, entry
) {
7520 if (t
->tab_id
== pn
) {
7521 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
7524 uri
= webkit_web_view_get_title(t
->wv
);
7527 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
7539 notebook_pagereordered_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
7546 menuitem_response(struct tab
*t
)
7548 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
7552 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
7554 GtkWidget
*menu
, *menu_items
;
7555 GdkEventButton
*bevent
;
7559 if (event
->type
== GDK_BUTTON_PRESS
) {
7560 bevent
= (GdkEventButton
*) event
;
7561 menu
= gtk_menu_new();
7563 TAILQ_FOREACH(ti
, &tabs
, entry
) {
7564 if ((uri
= get_uri(ti
->wv
)) == NULL
)
7565 /* XXX make sure there is something to print */
7566 /* XXX add gui pages in here to look purdy */
7568 menu_items
= gtk_menu_item_new_with_label(uri
);
7569 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_items
);
7570 gtk_widget_show(menu_items
);
7572 g_signal_connect_swapped((menu_items
),
7573 "activate", G_CALLBACK(menuitem_response
),
7577 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
7578 bevent
->button
, bevent
->time
);
7580 /* unref object so it'll free itself when popped down */
7581 #if !GTK_CHECK_VERSION(3, 0, 0)
7582 /* XXX does not need unref with gtk+3? */
7583 g_object_ref_sink(menu
);
7584 g_object_unref(menu
);
7587 return (TRUE
/* eat event */);
7590 return (FALSE
/* propagate */);
7594 icon_size_map(int icon_size
)
7596 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
7597 icon_size
> GTK_ICON_SIZE_DIALOG
)
7598 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
7604 create_button(char *name
, char *stockid
, int size
)
7606 GtkWidget
*button
, *image
;
7610 rcstring
= g_strdup_printf(
7611 "style \"%s-style\"\n"
7613 " GtkWidget::focus-padding = 0\n"
7614 " GtkWidget::focus-line-width = 0\n"
7618 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
7619 gtk_rc_parse_string(rcstring
);
7621 button
= gtk_button_new();
7622 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
7623 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
7625 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
7626 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7627 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
7628 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
7629 gtk_widget_set_name(button
, name
);
7630 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
7636 button_set_stockid(GtkWidget
*button
, char *stockid
)
7640 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
7641 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
7642 gtk_button_set_image(GTK_BUTTON(button
), image
);
7646 clipb_primary_cb(GtkClipboard
*primary
, GdkEvent
*event
, gpointer notused
)
7648 GtkClipboard
*clipboard
;
7649 gchar
*p
= NULL
, *s
= NULL
;
7652 * This code is very aggressive!
7653 * It basically ensures that the primary and regular clipboard are
7654 * always set the same. This obviously messes with standard X protocol
7655 * but those clowns should have come up with something better.
7658 /* XXX make this setting? */
7659 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
7660 p
= gtk_clipboard_wait_for_text(primary
);
7662 DNPRINTF(XT_D_CLIP
, "primary cleaned\n");
7663 p
= gtk_clipboard_wait_for_text(clipboard
);
7665 gtk_clipboard_set_text(primary
, p
, -1);
7667 DNPRINTF(XT_D_CLIP
, "primary got selection\n");
7668 s
= gtk_clipboard_wait_for_text(clipboard
);
7671 * if s and p are the same the string was set by
7672 * clipb_clipboard_cb so do nothing in that case
7673 * to prevent endless loop
7678 gtk_clipboard_set_text(clipboard
, p
, -1);
7688 clipb_clipboard_cb(GtkClipboard
*clipboard
, GdkEvent
*event
, gpointer notused
)
7690 GtkClipboard
*primary
;
7691 gchar
*p
= NULL
, *s
= NULL
;
7693 DNPRINTF(XT_D_CLIP
, "clipboard got content\n");
7695 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
7696 p
= gtk_clipboard_wait_for_text(clipboard
);
7698 s
= gtk_clipboard_wait_for_text(primary
);
7701 * if s and p are the same the string was set by
7702 * clipb_primary_cb so do nothing in that case
7703 * to prevent endless loop and deselection of text
7708 gtk_clipboard_set_text(primary
, p
, -1);
7723 char file
[PATH_MAX
];
7726 vbox
= gtk_vbox_new(FALSE
, 0);
7727 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
7728 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
7729 #if !GTK_CHECK_VERSION(3, 0, 0)
7730 /* XXX seems to be needed with gtk+2 */
7731 gtk_notebook_set_tab_hborder(notebook
, 0);
7732 gtk_notebook_set_tab_vborder(notebook
, 0);
7734 gtk_notebook_set_scrollable(notebook
, TRUE
);
7735 notebook_tab_set_visibility(notebook
);
7736 gtk_notebook_set_show_border(notebook
, FALSE
);
7737 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
7739 abtn
= gtk_button_new();
7740 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
7741 gtk_widget_set_size_request(arrow
, -1, -1);
7742 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
7743 gtk_widget_set_size_request(abtn
, -1, 20);
7745 #if GTK_CHECK_VERSION(2, 20, 0)
7746 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
7748 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
7749 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
7750 gtk_widget_set_size_request(vbox
, -1, -1);
7752 g_object_connect(G_OBJECT(notebook
),
7753 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
7755 g_object_connect(G_OBJECT(notebook
),
7756 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb
), NULL
,
7758 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
7759 G_CALLBACK(arrow_cb
), NULL
);
7761 main_window
= create_window();
7762 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
7763 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
7766 for (i
= 0; i
< LENGTH(icons
); i
++) {
7767 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
7768 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
7769 l
= g_list_append(l
, pb
);
7771 gtk_window_set_default_icon_list(l
);
7774 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY
)),
7775 "owner-change", G_CALLBACK(clipb_primary_cb
), NULL
);
7776 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
)),
7777 "owner-change", G_CALLBACK(clipb_clipboard_cb
), NULL
);
7779 gtk_widget_show_all(abtn
);
7780 gtk_widget_show_all(main_window
);
7784 set_hook(void **hook
, char *name
)
7787 errx(1, "set_hook");
7789 if (*hook
== NULL
) {
7790 *hook
= dlsym(RTLD_NEXT
, name
);
7792 errx(1, "can't hook %s", name
);
7796 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7798 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
7800 g_return_val_if_fail(cookie1
, FALSE
);
7801 g_return_val_if_fail(cookie2
, FALSE
);
7803 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
7804 !strcmp (cookie1
->value
, cookie2
->value
) &&
7805 !strcmp (cookie1
->path
, cookie2
->path
) &&
7806 !strcmp (cookie1
->domain
, cookie2
->domain
));
7810 transfer_cookies(void)
7813 SoupCookie
*sc
, *pc
;
7815 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7817 for (;cf
; cf
= cf
->next
) {
7819 sc
= soup_cookie_copy(pc
);
7820 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
7823 soup_cookies_free(cf
);
7827 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
7832 print_cookie("soup_cookie_jar_delete_cookie", c
);
7834 if (cookies_enabled
== 0)
7837 if (jar
== NULL
|| c
== NULL
)
7840 /* find and remove from persistent jar */
7841 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
7843 for (;cf
; cf
= cf
->next
) {
7845 if (soup_cookie_equal(ci
, c
)) {
7846 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
7851 soup_cookies_free(cf
);
7853 /* delete from session jar */
7854 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
7858 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
7860 struct domain
*d
= NULL
;
7864 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
7865 jar
, p_cookiejar
, s_cookiejar
);
7867 if (cookies_enabled
== 0)
7870 /* see if we are up and running */
7871 if (p_cookiejar
== NULL
) {
7872 _soup_cookie_jar_add_cookie(jar
, cookie
);
7875 /* disallow p_cookiejar adds, shouldn't happen */
7876 if (jar
== p_cookiejar
)
7880 if (jar
== NULL
|| cookie
== NULL
)
7883 if (enable_cookie_whitelist
&&
7884 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
7886 DNPRINTF(XT_D_COOKIE
,
7887 "soup_cookie_jar_add_cookie: reject %s\n",
7889 if (save_rejected_cookies
) {
7890 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
7891 show_oops_s("can't open reject cookie file");
7894 fseek(r_cookie_f
, 0, SEEK_END
);
7895 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7896 cookie
->http_only
? "#HttpOnly_" : "",
7898 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
7900 cookie
->secure
? "TRUE" : "FALSE",
7902 (gulong
)soup_date_to_time_t(cookie
->expires
) :
7909 if (!allow_volatile_cookies
)
7913 if (cookie
->expires
== NULL
&& session_timeout
) {
7914 soup_cookie_set_expires(cookie
,
7915 soup_date_new_from_now(session_timeout
));
7916 print_cookie("modified add cookie", cookie
);
7919 /* see if we are white listed for persistence */
7920 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
7921 /* add to persistent jar */
7922 c
= soup_cookie_copy(cookie
);
7923 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
7924 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
7927 /* add to session jar */
7928 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
7929 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
7935 char file
[PATH_MAX
];
7937 set_hook((void *)&_soup_cookie_jar_add_cookie
,
7938 "soup_cookie_jar_add_cookie");
7939 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
7940 "soup_cookie_jar_delete_cookie");
7942 if (cookies_enabled
== 0)
7946 * the following code is intricate due to overriding several libsoup
7948 * do not alter order of these operations.
7951 /* rejected cookies */
7952 if (save_rejected_cookies
)
7953 snprintf(rc_fname
, sizeof file
, "%s/%s", work_dir
, XT_REJECT_FILE
);
7955 /* persistent cookies */
7956 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_COOKIE_FILE
);
7957 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
7959 /* session cookies */
7960 s_cookiejar
= soup_cookie_jar_new();
7961 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
7962 cookie_policy
, (void *)NULL
);
7965 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
7969 setup_proxy(char *uri
)
7972 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
7973 soup_uri_free(proxy_uri
);
7977 if (http_proxy
!= uri
) {
7984 http_proxy
= g_strdup(uri
);
7985 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
7986 proxy_uri
= soup_uri_new(http_proxy
);
7987 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
7992 send_cmd_to_socket(char *cmd
)
7995 struct sockaddr_un sa
;
7997 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
7998 warnx("%s: socket", __func__
);
8002 sa
.sun_family
= AF_UNIX
;
8003 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8004 work_dir
, XT_SOCKET_FILE
);
8007 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8008 warnx("%s: connect", __func__
);
8012 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
8013 warnx("%s: send", __func__
);
8024 socket_watcher(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
8027 char str
[XT_MAX_URL_LENGTH
];
8028 socklen_t t
= sizeof(struct sockaddr_un
);
8029 struct sockaddr_un sa
;
8034 gint fd
= g_io_channel_unix_get_fd(source
);
8036 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
8041 if (getpeereid(s
, &uid
, &gid
) == -1) {
8045 if (uid
!= getuid() || gid
!= getgid()) {
8046 warnx("unauthorized user");
8052 warnx("not a valid user");
8056 n
= recv(s
, str
, sizeof(str
), 0);
8060 tt
= TAILQ_LAST(&tabs
, tab_list
);
8061 cmd_execute(tt
, str
);
8069 struct sockaddr_un sa
;
8071 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8072 warn("is_running: socket");
8076 sa
.sun_family
= AF_UNIX
;
8077 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8078 work_dir
, XT_SOCKET_FILE
);
8081 /* connect to see if there is a listener */
8082 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
8083 rv
= 0; /* not running */
8085 rv
= 1; /* already running */
8096 struct sockaddr_un sa
;
8098 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8099 warn("build_socket: socket");
8103 sa
.sun_family
= AF_UNIX
;
8104 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8105 work_dir
, XT_SOCKET_FILE
);
8108 /* connect to see if there is a listener */
8109 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8110 /* no listener so we will */
8111 unlink(sa
.sun_path
);
8113 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8114 warn("build_socket: bind");
8118 if (listen(s
, 1) == -1) {
8119 warn("build_socket: listen");
8132 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8133 GtkTreeIter
*iter
, struct tab
*t
)
8137 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8145 completion_hover_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8146 GtkTreeIter
*iter
, struct tab
*t
)
8150 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8151 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), value
);
8152 gtk_editable_set_position(GTK_EDITABLE(t
->uri_entry
), -1);
8159 completion_add_uri(const gchar
*uri
)
8163 /* add uri to list_store */
8164 gtk_list_store_append(completion_model
, &iter
);
8165 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
8169 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
8170 GtkTreeIter
*iter
, gpointer user_data
)
8173 gboolean match
= FALSE
;
8175 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8181 match
= match_uri(value
, key
);
8188 completion_add(struct tab
*t
)
8190 /* enable completion for tab */
8191 t
->completion
= gtk_entry_completion_new();
8192 gtk_entry_completion_set_text_column(t
->completion
, 0);
8193 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8194 gtk_entry_completion_set_model(t
->completion
,
8195 GTK_TREE_MODEL(completion_model
));
8196 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8198 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8199 gtk_entry_completion_set_inline_selection(t
->completion
, TRUE
);
8200 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8201 G_CALLBACK(completion_select_cb
), t
);
8202 g_signal_connect(G_OBJECT (t
->completion
), "cursor-on-match",
8203 G_CALLBACK(completion_hover_cb
), t
);
8211 if (stat(dir
, &sb
)) {
8212 if (mkdir(dir
, S_IRWXU
) == -1)
8213 err(1, "mkdir %s", dir
);
8215 err(1, "stat %s", dir
);
8217 if (S_ISDIR(sb
.st_mode
) == 0)
8218 errx(1, "%s not a dir", dir
);
8219 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8220 warnx("fixing invalid permissions on %s", dir
);
8221 if (chmod(dir
, S_IRWXU
) == -1)
8222 err(1, "chmod %s", dir
);
8230 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8236 main(int argc
, char *argv
[])
8239 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8240 char conf
[PATH_MAX
] = { '\0' };
8241 char file
[PATH_MAX
];
8242 char *env_proxy
= NULL
;
8245 struct sigaction sact
;
8246 gchar
*priority
= g_strdup("NORMAL");
8247 GIOChannel
*channel
;
8251 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8253 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8262 errx(0 , "Version: %s", version
);
8265 strlcpy(conf
, optarg
, sizeof(conf
));
8268 strlcpy(named_session
, optarg
, sizeof(named_session
));
8289 RB_INIT(&downloads
);
8293 TAILQ_INIT(&aliases
);
8299 gnutls_global_init();
8301 /* generate session keys for xtp pages */
8302 generate_xtp_session_key(&dl_session_key
);
8303 generate_xtp_session_key(&hl_session_key
);
8304 generate_xtp_session_key(&cl_session_key
);
8305 generate_xtp_session_key(&fl_session_key
);
8308 gtk_init(&argc
, &argv
);
8309 if (!g_thread_supported())
8310 g_thread_init(NULL
);
8313 bzero(&sact
, sizeof(sact
));
8314 sigemptyset(&sact
.sa_mask
);
8315 sact
.sa_handler
= sigchild
;
8316 sact
.sa_flags
= SA_NOCLDSTOP
;
8317 sigaction(SIGCHLD
, &sact
, NULL
);
8319 /* set download dir */
8320 pwd
= getpwuid(getuid());
8322 errx(1, "invalid user %d", getuid());
8323 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8325 /* set default string settings */
8326 home
= g_strdup("https://www.cyphertite.com");
8327 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8328 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
8329 strlcpy(runtime_settings
,"runtime", sizeof runtime_settings
);
8331 /* read config file */
8332 if (strlen(conf
) == 0)
8333 snprintf(conf
, sizeof conf
, "%s/.%s",
8334 pwd
->pw_dir
, XT_CONF_FILE
);
8335 config_parse(conf
, 0);
8337 /* working directory */
8338 if (strlen(work_dir
) == 0)
8339 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
8340 pwd
->pw_dir
, XT_DIR
);
8343 /* icon cache dir */
8344 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
8348 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
8352 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
8353 work_dir
, XT_SESSIONS_DIR
);
8354 xxx_dir(sessions_dir
);
8356 /* runtime settings that can override config file */
8357 if (runtime_settings
[0] != '\0')
8358 config_parse(runtime_settings
, 1);
8361 if (!strcmp(download_dir
, pwd
->pw_dir
))
8362 strlcat(download_dir
, "/downloads", sizeof download_dir
);
8363 xxx_dir(download_dir
);
8365 /* favorites file */
8366 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
8367 if (stat(file
, &sb
)) {
8368 warnx("favorites file doesn't exist, creating it");
8369 if ((f
= fopen(file
, "w")) == NULL
)
8370 err(1, "favorites");
8375 session
= webkit_get_default_session();
8376 /* XXX ssl-priority property not quite available yet */
8377 if (is_g_object_setting(G_OBJECT(session
), "ssl-priority"))
8378 g_object_set(G_OBJECT(session
), "ssl-priority", priority
,
8381 warnx("session does not have \"ssl-priority\" property");
8386 if (stat(ssl_ca_file
, &sb
)) {
8387 warnx("no CA file: %s", ssl_ca_file
);
8388 g_free(ssl_ca_file
);
8391 g_object_set(session
,
8392 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
8393 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8398 env_proxy
= getenv("http_proxy");
8400 setup_proxy(env_proxy
);
8402 setup_proxy(http_proxy
);
8405 send_cmd_to_socket(argv
[0]);
8409 /* set some connection parameters */
8410 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8411 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8414 /* see if there is already an xxxterm running */
8415 if (single_instance
&& is_running()) {
8417 warnx("already running");
8423 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8424 send_cmd_to_socket(cmd
);
8434 /* uri completion */
8435 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8440 if (save_global_history
)
8441 restore_global_history();
8443 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
8444 restore_saved_tabs();
8446 a
.s
= named_session
;
8447 a
.i
= XT_SES_DONOTHING
;
8448 open_tabs(NULL
, &a
);
8452 create_new_tab(argv
[0], NULL
, focus
, -1);
8459 if (TAILQ_EMPTY(&tabs
))
8460 create_new_tab(home
, NULL
, 1, -1);
8463 if ((s
= build_socket()) != -1) {
8464 channel
= g_io_channel_unix_new(s
);
8465 g_io_add_watch(channel
, G_IO_IN
, socket_watcher
, NULL
);
8470 gnutls_global_deinit();