3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
46 #include <sys/types.h>
48 #if defined(__linux__)
49 #include "linux/util.h"
50 #include "linux/tree.h"
51 #elif defined(__FreeBSD__)
53 #include "freebsd/util.h"
59 #include <sys/queue.h>
60 #include <sys/resource.h>
61 #include <sys/socket.h>
67 #include <gdk/gdkkeysyms.h>
69 #if GTK_CHECK_VERSION(3,0,0)
70 /* we still use GDK_* instead of GDK_KEY_* */
71 #include <gdk/gdkkeysyms-compat.h>
74 #include <webkit/webkit.h>
75 #include <libsoup/soup.h>
76 #include <gnutls/gnutls.h>
77 #include <JavaScriptCore/JavaScript.h>
78 #include <gnutls/x509.h>
80 #include "javascript.h"
83 javascript.h borrowed from vimprobable2 under the following license:
85 Copyright (c) 2009 Leon Winter
86 Copyright (c) 2009 Hannes Schueller
87 Copyright (c) 2009 Matto Fransen
89 Permission is hereby granted, free of charge, to any person obtaining a copy
90 of this software and associated documentation files (the "Software"), to deal
91 in the Software without restriction, including without limitation the rights
92 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
93 copies of the Software, and to permit persons to whom the Software is
94 furnished to do so, subject to the following conditions:
96 The above copyright notice and this permission notice shall be included in
97 all copies or substantial portions of the Software.
99 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
100 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
101 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
102 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
103 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
104 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
108 static char *version
= "$xxxterm$";
110 /* hooked functions */
111 void (*_soup_cookie_jar_add_cookie
)(SoupCookieJar
*, SoupCookie
*);
112 void (*_soup_cookie_jar_delete_cookie
)(SoupCookieJar
*,
117 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
118 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
119 #define XT_D_MOVE 0x0001
120 #define XT_D_KEY 0x0002
121 #define XT_D_TAB 0x0004
122 #define XT_D_URL 0x0008
123 #define XT_D_CMD 0x0010
124 #define XT_D_NAV 0x0020
125 #define XT_D_DOWNLOAD 0x0040
126 #define XT_D_CONFIG 0x0080
127 #define XT_D_JS 0x0100
128 #define XT_D_FAVORITE 0x0200
129 #define XT_D_PRINTING 0x0400
130 #define XT_D_COOKIE 0x0800
131 #define XT_D_KEYBINDING 0x1000
132 #define XT_D_CLIP 0x2000
133 #define XT_D_BUFFERCMD 0x4000
134 u_int32_t swm_debug
= 0
152 #define DPRINTF(x...)
153 #define DNPRINTF(n,x...)
156 #define LENGTH(x) (sizeof x / sizeof x[0])
157 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
158 ~(GDK_BUTTON1_MASK) & \
159 ~(GDK_BUTTON2_MASK) & \
160 ~(GDK_BUTTON3_MASK) & \
161 ~(GDK_BUTTON4_MASK) & \
173 TAILQ_ENTRY(tab
) entry
;
175 GtkWidget
*tab_content
;
184 GtkWidget
*uri_entry
;
185 GtkWidget
*search_entry
;
187 GtkWidget
*browser_win
;
188 GtkWidget
*statusbar_box
;
190 GtkWidget
*statusbar
;
191 GtkWidget
*buffercmd
;
201 GtkWidget
*js_toggle
;
202 GtkEntryCompletion
*completion
;
206 WebKitWebHistoryItem
*item
;
207 WebKitWebBackForwardList
*bfl
;
210 WebKitNetworkRequest
*icon_request
;
211 WebKitDownload
*icon_download
;
212 gchar
*icon_dest_uri
;
214 /* adjustments for browser */
217 GtkAdjustment
*adjust_h
;
218 GtkAdjustment
*adjust_v
;
224 int xtp_meaning
; /* identifies dls/favorites */
230 #define XT_HINT_NONE (0)
231 #define XT_HINT_NUMERICAL (1)
232 #define XT_HINT_ALPHANUM (2)
236 /* custom stylesheet */
245 WebKitWebSettings
*settings
;
248 TAILQ_HEAD(tab_list
, tab
);
251 RB_ENTRY(history
) entry
;
255 RB_HEAD(history_list
, history
);
258 RB_ENTRY(download
) entry
;
260 WebKitDownload
*download
;
263 RB_HEAD(download_list
, download
);
266 RB_ENTRY(domain
) entry
;
268 int handy
; /* app use */
270 RB_HEAD(domain_list
, domain
);
273 TAILQ_ENTRY(undo
) entry
;
276 int back
; /* Keeps track of how many back
277 * history items there are. */
279 TAILQ_HEAD(undo_tailq
, undo
);
281 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
282 int next_download_id
= 1;
291 #define XT_NAME ("XXXTerm")
292 #define XT_DIR (".xxxterm")
293 #define XT_CACHE_DIR ("cache")
294 #define XT_CERT_DIR ("certs/")
295 #define XT_SESSIONS_DIR ("sessions/")
296 #define XT_CONF_FILE ("xxxterm.conf")
297 #define XT_FAVS_FILE ("favorites")
298 #define XT_SAVED_TABS_FILE ("main_session")
299 #define XT_RESTART_TABS_FILE ("restart_tabs")
300 #define XT_SOCKET_FILE ("socket")
301 #define XT_HISTORY_FILE ("history")
302 #define XT_REJECT_FILE ("rejected.txt")
303 #define XT_COOKIE_FILE ("cookies.txt")
304 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
305 #define XT_CB_HANDLED (TRUE)
306 #define XT_CB_PASSTHROUGH (FALSE)
307 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
308 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
309 #define XT_DLMAN_REFRESH "10"
310 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
311 "td{overflow: hidden;" \
312 " padding: 2px 2px 2px 2px;" \
313 " border: 1px solid black;" \
314 " vertical-align:top;" \
315 " word-wrap: break-word}\n" \
316 "tr:hover{background: #ffff99}\n" \
317 "th{background-color: #cccccc;" \
318 " border: 1px solid black}\n" \
319 "table{width: 100%%;" \
320 " border: 1px black solid;" \
321 " border-collapse:collapse}\n" \
323 "border: 1px solid black;" \
326 ".progress-inner{float: left;" \
328 " background: green}\n" \
329 ".dlstatus{font-size: small;" \
330 " text-align: center}\n" \
332 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
333 #define XT_MAX_UNDO_CLOSE_TAB (32)
334 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
335 #define XT_PRINT_EXTRA_MARGIN 10
338 #define XT_COLOR_RED "#cc0000"
339 #define XT_COLOR_YELLOW "#ffff66"
340 #define XT_COLOR_BLUE "lightblue"
341 #define XT_COLOR_GREEN "#99ff66"
342 #define XT_COLOR_WHITE "white"
343 #define XT_COLOR_BLACK "black"
345 #define XT_COLOR_CT_BACKGROUND "#000000"
346 #define XT_COLOR_CT_INACTIVE "#dddddd"
347 #define XT_COLOR_CT_ACTIVE "#bbbb00"
348 #define XT_COLOR_CT_SEPARATOR "#555555"
350 #define XT_COLOR_SB_SEPARATOR "#555555"
353 * xxxterm "protocol" (xtp)
354 * We use this for managing stuff like downloads and favorites. They
355 * make magical HTML pages in memory which have xxxt:// links in order
356 * to communicate with xxxterm's internals. These links take the format:
357 * xxxt://class/session_key/action/arg
359 * Don't begin xtp class/actions as 0. atoi returns that on error.
361 * Typically we have not put addition of items in this framework, as
362 * adding items is either done via an ex-command or via a keybinding instead.
365 #define XT_XTP_STR "xxxt://"
367 /* XTP classes (xxxt://<class>) */
368 #define XT_XTP_INVALID 0 /* invalid */
369 #define XT_XTP_DL 1 /* downloads */
370 #define XT_XTP_HL 2 /* history */
371 #define XT_XTP_CL 3 /* cookies */
372 #define XT_XTP_FL 4 /* favorites */
374 /* XTP download actions */
375 #define XT_XTP_DL_LIST 1
376 #define XT_XTP_DL_CANCEL 2
377 #define XT_XTP_DL_REMOVE 3
379 /* XTP history actions */
380 #define XT_XTP_HL_LIST 1
381 #define XT_XTP_HL_REMOVE 2
383 /* XTP cookie actions */
384 #define XT_XTP_CL_LIST 1
385 #define XT_XTP_CL_REMOVE 2
387 /* XTP cookie actions */
388 #define XT_XTP_FL_LIST 1
389 #define XT_XTP_FL_REMOVE 2
392 #define XT_MOVE_INVALID (0)
393 #define XT_MOVE_DOWN (1)
394 #define XT_MOVE_UP (2)
395 #define XT_MOVE_BOTTOM (3)
396 #define XT_MOVE_TOP (4)
397 #define XT_MOVE_PAGEDOWN (5)
398 #define XT_MOVE_PAGEUP (6)
399 #define XT_MOVE_HALFDOWN (7)
400 #define XT_MOVE_HALFUP (8)
401 #define XT_MOVE_LEFT (9)
402 #define XT_MOVE_FARLEFT (10)
403 #define XT_MOVE_RIGHT (11)
404 #define XT_MOVE_FARRIGHT (12)
406 #define XT_TAB_LAST (-4)
407 #define XT_TAB_FIRST (-3)
408 #define XT_TAB_PREV (-2)
409 #define XT_TAB_NEXT (-1)
410 #define XT_TAB_INVALID (0)
411 #define XT_TAB_NEW (1)
412 #define XT_TAB_DELETE (2)
413 #define XT_TAB_DELQUIT (3)
414 #define XT_TAB_OPEN (4)
415 #define XT_TAB_UNDO_CLOSE (5)
416 #define XT_TAB_SHOW (6)
417 #define XT_TAB_HIDE (7)
418 #define XT_TAB_NEXTSTYLE (8)
420 #define XT_NAV_INVALID (0)
421 #define XT_NAV_BACK (1)
422 #define XT_NAV_FORWARD (2)
423 #define XT_NAV_RELOAD (3)
424 #define XT_NAV_RELOAD_CACHE (4)
426 #define XT_FOCUS_INVALID (0)
427 #define XT_FOCUS_URI (1)
428 #define XT_FOCUS_SEARCH (2)
430 #define XT_SEARCH_INVALID (0)
431 #define XT_SEARCH_NEXT (1)
432 #define XT_SEARCH_PREV (2)
434 #define XT_PASTE_CURRENT_TAB (0)
435 #define XT_PASTE_NEW_TAB (1)
437 #define XT_ZOOM_IN (-1)
438 #define XT_ZOOM_OUT (-2)
439 #define XT_ZOOM_NORMAL (100)
441 #define XT_URL_SHOW (1)
442 #define XT_URL_HIDE (2)
444 #define XT_WL_TOGGLE (1<<0)
445 #define XT_WL_ENABLE (1<<1)
446 #define XT_WL_DISABLE (1<<2)
447 #define XT_WL_FQDN (1<<3) /* default */
448 #define XT_WL_TOPLEVEL (1<<4)
449 #define XT_WL_PERSISTENT (1<<5)
450 #define XT_WL_SESSION (1<<6)
451 #define XT_WL_RELOAD (1<<7)
453 #define XT_SHOW (1<<7)
454 #define XT_DELETE (1<<8)
455 #define XT_SAVE (1<<9)
456 #define XT_OPEN (1<<10)
458 #define XT_CMD_OPEN (0)
459 #define XT_CMD_OPEN_CURRENT (1)
460 #define XT_CMD_TABNEW (2)
461 #define XT_CMD_TABNEW_CURRENT (3)
463 #define XT_STATUS_NOTHING (0)
464 #define XT_STATUS_LINK (1)
465 #define XT_STATUS_URI (2)
466 #define XT_STATUS_LOADING (3)
468 #define XT_SES_DONOTHING (0)
469 #define XT_SES_CLOSETABS (1)
471 #define XT_BM_NORMAL (0)
472 #define XT_BM_WHITELIST (1)
473 #define XT_BM_KIOSK (2)
475 #define XT_PREFIX (1<<0)
476 #define XT_USERARG (1<<1)
477 #define XT_URLARG (1<<2)
478 #define XT_INTARG (1<<3)
480 #define XT_TABS_NORMAL 0
481 #define XT_TABS_COMPACT 1
489 TAILQ_ENTRY(mime_type
) entry
;
491 TAILQ_HEAD(mime_type_list
, mime_type
);
497 TAILQ_ENTRY(alias
) entry
;
499 TAILQ_HEAD(alias_list
, alias
);
501 /* settings that require restart */
502 int tabless
= 0; /* allow only 1 tab */
503 int enable_socket
= 0;
504 int single_instance
= 0; /* only allow one xxxterm to run */
505 int fancy_bar
= 1; /* fancy toolbar */
506 int browser_mode
= XT_BM_NORMAL
;
507 int enable_localstorage
= 0;
508 char *statusbar_elems
= NULL
;
510 /* runtime settings */
511 int show_tabs
= 1; /* show tabs on notebook */
512 int tab_style
= XT_TABS_NORMAL
; /* tab bar style */
513 int show_url
= 1; /* show url toolbar on notebook */
514 int show_statusbar
= 0; /* vimperator style status bar */
515 int ctrl_click_focus
= 0; /* ctrl click gets focus */
516 int cookies_enabled
= 1; /* enable cookies */
517 int read_only_cookies
= 0; /* enable to not write cookies */
518 int enable_scripts
= 1;
519 int enable_plugins
= 0;
520 gfloat default_zoom_level
= 1.0;
521 char default_script
[PATH_MAX
];
522 int window_height
= 768;
523 int window_width
= 1024;
524 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
525 int refresh_interval
= 10; /* download refresh interval */
526 int enable_cookie_whitelist
= 0;
527 int enable_js_whitelist
= 0;
528 int session_timeout
= 3600; /* cookie session timeout */
529 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
530 char *ssl_ca_file
= NULL
;
531 char *resource_dir
= NULL
;
532 gboolean ssl_strict_certs
= FALSE
;
533 int append_next
= 1; /* append tab after current tab */
535 char *search_string
= NULL
;
536 char *http_proxy
= NULL
;
537 char download_dir
[PATH_MAX
];
538 char runtime_settings
[PATH_MAX
]; /* override of settings */
539 int allow_volatile_cookies
= 0;
540 int save_global_history
= 0; /* save global history to disk */
541 char *user_agent
= NULL
;
542 int save_rejected_cookies
= 0;
543 int session_autosave
= 0;
544 int guess_search
= 0;
545 int dns_prefetch
= FALSE
;
546 gint max_connections
= 25;
547 gint max_host_connections
= 5;
548 gint enable_spell_checking
= 0;
549 char *spell_check_languages
= NULL
;
551 char *cmd_font_name
= NULL
;
552 char *oops_font_name
= NULL
;
553 char *statusbar_font_name
= NULL
;
554 char *tabbar_font_name
= NULL
;
555 PangoFontDescription
*cmd_font
;
556 PangoFontDescription
*oops_font
;
557 PangoFontDescription
*statusbar_font
;
558 PangoFontDescription
*tabbar_font
;
560 int btn_down
; /* M1 down in any wv */
564 int set_browser_mode(struct settings
*, char *);
565 int set_cookie_policy(struct settings
*, char *);
566 int set_download_dir(struct settings
*, char *);
567 int set_default_script(struct settings
*, char *);
568 int set_runtime_dir(struct settings
*, char *);
569 int set_tab_style(struct settings
*, char *);
570 int set_work_dir(struct settings
*, char *);
571 int add_alias(struct settings
*, char *);
572 int add_mime_type(struct settings
*, char *);
573 int add_cookie_wl(struct settings
*, char *);
574 int add_js_wl(struct settings
*, char *);
575 int add_kb(struct settings
*, char *);
576 void button_set_stockid(GtkWidget
*, char *);
577 GtkWidget
* create_button(char *, char *, int);
579 char *get_browser_mode(struct settings
*);
580 char *get_cookie_policy(struct settings
*);
581 char *get_download_dir(struct settings
*);
582 char *get_default_script(struct settings
*);
583 char *get_runtime_dir(struct settings
*);
584 char *get_tab_style(struct settings
*);
585 char *get_work_dir(struct settings
*);
587 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
588 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
589 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
590 void walk_kb(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
591 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
593 void recalc_tabs(void);
594 void recolor_compact_tabs(void);
595 void set_current_tab(int page_num
);
596 gboolean
update_statusbar_position(GtkAdjustment
* adjustment
, gpointer data
);
599 int (*set
)(struct settings
*, char *);
600 char *(*get
)(struct settings
*);
601 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
604 struct special s_browser_mode
= {
610 struct special s_cookie
= {
616 struct special s_alias
= {
622 struct special s_mime
= {
628 struct special s_js
= {
634 struct special s_kb
= {
640 struct special s_cookie_wl
= {
646 struct special s_default_script
= {
652 struct special s_download_dir
= {
658 struct special s_work_dir
= {
664 struct special s_tab_style
= {
673 #define XT_S_INVALID (0)
676 #define XT_S_FLOAT (3)
678 #define XT_SF_RESTART (1<<0)
679 #define XT_SF_RUNTIME (1<<1)
685 { "append_next", XT_S_INT
, 0, &append_next
, NULL
, NULL
},
686 { "allow_volatile_cookies", XT_S_INT
, 0, &allow_volatile_cookies
, NULL
, NULL
},
687 { "browser_mode", XT_S_INT
, 0, NULL
, NULL
,&s_browser_mode
},
688 { "cookie_policy", XT_S_INT
, 0, NULL
, NULL
,&s_cookie
},
689 { "cookies_enabled", XT_S_INT
, 0, &cookies_enabled
, NULL
, NULL
},
690 { "ctrl_click_focus", XT_S_INT
, 0, &ctrl_click_focus
, NULL
, NULL
},
691 { "default_zoom_level", XT_S_FLOAT
, 0, NULL
, NULL
, NULL
, &default_zoom_level
},
692 { "default_script", XT_S_STR
, 0, NULL
, NULL
,&s_default_script
},
693 { "download_dir", XT_S_STR
, 0, NULL
, NULL
,&s_download_dir
},
694 { "enable_cookie_whitelist", XT_S_INT
, 0, &enable_cookie_whitelist
, NULL
, NULL
},
695 { "enable_js_whitelist", XT_S_INT
, 0, &enable_js_whitelist
, NULL
, NULL
},
696 { "enable_localstorage", XT_S_INT
, 0, &enable_localstorage
, NULL
, NULL
},
697 { "enable_plugins", XT_S_INT
, 0, &enable_plugins
, NULL
, NULL
},
698 { "enable_scripts", XT_S_INT
, 0, &enable_scripts
, NULL
, NULL
},
699 { "enable_socket", XT_S_INT
, XT_SF_RESTART
,&enable_socket
, NULL
, NULL
},
700 { "enable_spell_checking", XT_S_INT
, 0, &enable_spell_checking
, NULL
, NULL
},
701 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
,&fancy_bar
, NULL
, NULL
},
702 { "guess_search", XT_S_INT
, 0, &guess_search
, NULL
, NULL
},
703 { "home", XT_S_STR
, 0, NULL
, &home
, NULL
},
704 { "http_proxy", XT_S_STR
, 0, NULL
, &http_proxy
, NULL
},
705 { "icon_size", XT_S_INT
, 0, &icon_size
, NULL
, NULL
},
706 { "max_connections", XT_S_INT
, XT_SF_RESTART
,&max_connections
, NULL
, NULL
},
707 { "max_host_connections", XT_S_INT
, XT_SF_RESTART
,&max_host_connections
, NULL
, NULL
},
708 { "read_only_cookies", XT_S_INT
, 0, &read_only_cookies
, NULL
, NULL
},
709 { "refresh_interval", XT_S_INT
, 0, &refresh_interval
, NULL
, NULL
},
710 { "resource_dir", XT_S_STR
, 0, NULL
, &resource_dir
, NULL
},
711 { "search_string", XT_S_STR
, 0, NULL
, &search_string
, NULL
},
712 { "save_global_history", XT_S_INT
, XT_SF_RESTART
,&save_global_history
, NULL
, NULL
},
713 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
,&save_rejected_cookies
, NULL
, NULL
},
714 { "session_timeout", XT_S_INT
, 0, &session_timeout
, NULL
, NULL
},
715 { "session_autosave", XT_S_INT
, 0, &session_autosave
, NULL
, NULL
},
716 { "single_instance", XT_S_INT
, XT_SF_RESTART
,&single_instance
, NULL
, NULL
},
717 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
718 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
719 { "show_statusbar", XT_S_INT
, 0, &show_statusbar
, NULL
, NULL
},
720 { "spell_check_languages", XT_S_STR
, 0, NULL
, &spell_check_languages
, NULL
},
721 { "ssl_ca_file", XT_S_STR
, 0, NULL
, &ssl_ca_file
, NULL
},
722 { "ssl_strict_certs", XT_S_INT
, 0, &ssl_strict_certs
, NULL
, NULL
},
723 { "statusbar_elems", XT_S_STR
, 0, NULL
, &statusbar_elems
, NULL
},
724 { "tab_style", XT_S_STR
, 0, NULL
, NULL
,&s_tab_style
},
725 { "user_agent", XT_S_STR
, 0, NULL
, &user_agent
, NULL
},
726 { "window_height", XT_S_INT
, 0, &window_height
, NULL
, NULL
},
727 { "window_width", XT_S_INT
, 0, &window_width
, NULL
, NULL
},
728 { "work_dir", XT_S_STR
, 0, NULL
, NULL
,&s_work_dir
},
731 { "cmd_font", XT_S_STR
, 0, NULL
, &cmd_font_name
, NULL
},
732 { "oops_font", XT_S_STR
, 0, NULL
, &oops_font_name
, NULL
},
733 { "statusbar_font", XT_S_STR
, 0, NULL
, &statusbar_font_name
, NULL
},
734 { "tabbar_font", XT_S_STR
, 0, NULL
, &tabbar_font_name
, NULL
},
736 /* runtime settings */
737 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
738 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
739 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
740 { "keybinding", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_kb
},
741 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
744 int about(struct tab
*, struct karg
*);
745 int blank(struct tab
*, struct karg
*);
746 int ca_cmd(struct tab
*, struct karg
*);
747 int cookie_show_wl(struct tab
*, struct karg
*);
748 int js_show_wl(struct tab
*, struct karg
*);
749 int help(struct tab
*, struct karg
*);
750 int set(struct tab
*, struct karg
*);
751 int stats(struct tab
*, struct karg
*);
752 int marco(struct tab
*, struct karg
*);
753 const char * marco_message(int *);
754 int xtp_page_cl(struct tab
*, struct karg
*);
755 int xtp_page_dl(struct tab
*, struct karg
*);
756 int xtp_page_fl(struct tab
*, struct karg
*);
757 int xtp_page_hl(struct tab
*, struct karg
*);
758 void xt_icon_from_file(struct tab
*, char *);
759 const gchar
*get_uri(struct tab
*);
760 const gchar
*get_title(struct tab
*, bool);
762 #define XT_URI_ABOUT ("about:")
763 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
764 #define XT_URI_ABOUT_ABOUT ("about")
765 #define XT_URI_ABOUT_BLANK ("blank")
766 #define XT_URI_ABOUT_CERTS ("certs")
767 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
768 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
769 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
770 #define XT_URI_ABOUT_FAVORITES ("favorites")
771 #define XT_URI_ABOUT_HELP ("help")
772 #define XT_URI_ABOUT_HISTORY ("history")
773 #define XT_URI_ABOUT_JSWL ("jswl")
774 #define XT_URI_ABOUT_SET ("set")
775 #define XT_URI_ABOUT_STATS ("stats")
776 #define XT_URI_ABOUT_MARCO ("marco")
780 int (*func
)(struct tab
*, struct karg
*);
782 { XT_URI_ABOUT_ABOUT
, about
},
783 { XT_URI_ABOUT_BLANK
, blank
},
784 { XT_URI_ABOUT_CERTS
, ca_cmd
},
785 { XT_URI_ABOUT_COOKIEWL
, cookie_show_wl
},
786 { XT_URI_ABOUT_COOKIEJAR
, xtp_page_cl
},
787 { XT_URI_ABOUT_DOWNLOADS
, xtp_page_dl
},
788 { XT_URI_ABOUT_FAVORITES
, xtp_page_fl
},
789 { XT_URI_ABOUT_HELP
, help
},
790 { XT_URI_ABOUT_HISTORY
, xtp_page_hl
},
791 { XT_URI_ABOUT_JSWL
, js_show_wl
},
792 { XT_URI_ABOUT_SET
, set
},
793 { XT_URI_ABOUT_STATS
, stats
},
794 { XT_URI_ABOUT_MARCO
, marco
},
797 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
798 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
799 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
800 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
801 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
802 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
803 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
806 extern char *__progname
;
809 GtkWidget
*main_window
;
810 GtkNotebook
*notebook
;
812 GtkWidget
*arrow
, *abtn
;
813 struct tab_list tabs
;
814 struct history_list hl
;
815 struct download_list downloads
;
816 struct domain_list c_wl
;
817 struct domain_list js_wl
;
818 struct undo_tailq undos
;
819 struct keybinding_list kbl
;
821 int updating_dl_tabs
= 0;
822 int updating_hl_tabs
= 0;
823 int updating_cl_tabs
= 0;
824 int updating_fl_tabs
= 0;
826 uint64_t blocked_cookies
= 0;
827 char named_session
[PATH_MAX
];
828 int icon_size_map(int);
830 GtkListStore
*completion_model
;
831 void completion_add(struct tab
*);
832 void completion_add_uri(const gchar
*);
833 GtkListStore
*buffers_store
;
834 void xxx_dir(char *);
839 int saved_errno
, status
;
844 while ((pid
= waitpid(WAIT_ANY
, &status
, WNOHANG
)) != 0) {
848 if (errno
!= ECHILD
) {
850 clog_warn("sigchild: waitpid:");
856 if (WIFEXITED(status
)) {
857 if (WEXITSTATUS(status
) != 0) {
859 clog_warnx("sigchild: child exit status: %d",
860 WEXITSTATUS(status));
865 clog_warnx("sigchild: child is terminated abnormally");
874 is_g_object_setting(GObject
*o
, char *str
)
876 guint n_props
= 0, i
;
877 GParamSpec
**proplist
;
879 if (! G_IS_OBJECT(o
))
882 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
885 for (i
=0; i
< n_props
; i
++) {
886 if (! strcmp(proplist
[i
]->name
, str
))
893 get_html_page(gchar
*title
, gchar
*body
, gchar
*head
, bool addstyles
)
897 r
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
899 "<title>%s</title>\n"
908 addstyles
? XT_PAGE_STYLE
: "",
917 * Display a web page from a HTML string in memory, rather than from a URL
920 load_webkit_string(struct tab
*t
, const char *str
, gchar
*title
)
925 /* we set this to indicate we want to manually do navaction */
927 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
929 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
931 /* set t->xtp_meaning */
932 for (i
= 0; i
< LENGTH(about_list
); i
++)
933 if (!strcmp(title
, about_list
[i
].name
)) {
938 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, "file://");
939 #if GTK_CHECK_VERSION(2, 20, 0)
940 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
941 gtk_widget_hide(t
->spinner
);
943 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[0]);
944 xt_icon_from_file(t
, file
);
949 get_current_tab(void)
953 TAILQ_FOREACH(t
, &tabs
, entry
) {
954 if (t
->tab_id
== gtk_notebook_get_current_page(notebook
))
958 warnx("%s: no current tab", __func__
);
964 set_status(struct tab
*t
, gchar
*s
, int status
)
972 case XT_STATUS_LOADING
:
973 type
= g_strdup_printf("Loading: %s", s
);
977 type
= g_strdup_printf("Link: %s", s
);
979 t
->status
= g_strdup(gtk_entry_get_text(
980 GTK_ENTRY(t
->sbe
.statusbar
)));
984 type
= g_strdup_printf("%s", s
);
986 t
->status
= g_strdup(type
);
990 t
->status
= g_strdup(s
);
992 case XT_STATUS_NOTHING
:
997 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.statusbar
), s
);
1003 hide_cmd(struct tab
*t
)
1005 gtk_widget_hide(t
->cmd
);
1009 show_cmd(struct tab
*t
)
1011 gtk_widget_hide(t
->oops
);
1012 gtk_widget_show(t
->cmd
);
1016 hide_buffers(struct tab
*t
)
1018 gtk_widget_hide(t
->buffers
);
1019 gtk_list_store_clear(buffers_store
);
1029 sort_tabs_by_page_num(struct tab
***stabs
)
1034 num_tabs
= gtk_notebook_get_n_pages(notebook
);
1036 *stabs
= g_malloc0(num_tabs
* sizeof(struct tab
*));
1038 TAILQ_FOREACH(t
, &tabs
, entry
)
1039 (*stabs
)[gtk_notebook_page_num(notebook
, t
->vbox
)] = t
;
1045 buffers_make_list(void)
1048 const gchar
*title
= NULL
;
1050 struct tab
**stabs
= NULL
;
1052 num_tabs
= sort_tabs_by_page_num(&stabs
);
1054 for (i
= 0; i
< num_tabs
; i
++)
1056 gtk_list_store_append(buffers_store
, &iter
);
1057 title
= get_title(stabs
[i
], FALSE
);
1058 gtk_list_store_set(buffers_store
, &iter
,
1059 COL_ID
, i
+ 1, /* Enumerate the tabs starting from 1
1069 show_buffers(struct tab
*t
)
1071 buffers_make_list();
1072 gtk_widget_show(t
->buffers
);
1073 gtk_widget_grab_focus(GTK_WIDGET(t
->buffers
));
1077 toggle_buffers(struct tab
*t
)
1079 if (gtk_widget_get_visible(t
->buffers
))
1086 buffers(struct tab
*t
, struct karg
*args
)
1094 hide_oops(struct tab
*t
)
1096 gtk_widget_hide(t
->oops
);
1100 show_oops(struct tab
*at
, const char *fmt
, ...)
1104 struct tab
*t
= NULL
;
1110 if ((t
= get_current_tab()) == NULL
)
1116 if (vasprintf(&msg
, fmt
, ap
) == -1)
1117 errx(1, "show_oops failed");
1120 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
1121 gtk_widget_hide(t
->cmd
);
1122 gtk_widget_show(t
->oops
);
1126 get_as_string(struct settings
*s
)
1137 warnx("get_as_string skip %s\n", s
->name
);
1138 } else if (s
->type
== XT_S_INT
)
1139 r
= g_strdup_printf("%d", *s
->ival
);
1140 else if (s
->type
== XT_S_STR
)
1141 r
= g_strdup(*s
->sval
);
1142 else if (s
->type
== XT_S_FLOAT
)
1143 r
= g_strdup_printf("%f", *s
->fval
);
1145 r
= g_strdup_printf("INVALID TYPE");
1151 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1156 for (i
= 0; i
< LENGTH(rs
); i
++) {
1157 if (rs
[i
].s
&& rs
[i
].s
->walk
)
1158 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
1160 s
= get_as_string(&rs
[i
]);
1161 cb(&rs
[i
], s
, cb_args
);
1168 set_browser_mode(struct settings
*s
, char *val
)
1170 if (!strcmp(val
, "whitelist")) {
1171 browser_mode
= XT_BM_WHITELIST
;
1172 allow_volatile_cookies
= 0;
1173 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1174 cookies_enabled
= 1;
1175 enable_cookie_whitelist
= 1;
1176 read_only_cookies
= 0;
1177 save_rejected_cookies
= 0;
1178 session_timeout
= 3600;
1180 enable_js_whitelist
= 1;
1181 enable_localstorage
= 0;
1182 } else if (!strcmp(val
, "normal")) {
1183 browser_mode
= XT_BM_NORMAL
;
1184 allow_volatile_cookies
= 0;
1185 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1186 cookies_enabled
= 1;
1187 enable_cookie_whitelist
= 0;
1188 read_only_cookies
= 0;
1189 save_rejected_cookies
= 0;
1190 session_timeout
= 3600;
1192 enable_js_whitelist
= 0;
1193 enable_localstorage
= 1;
1194 } else if (!strcmp(val
, "kiosk")) {
1195 browser_mode
= XT_BM_KIOSK
;
1196 allow_volatile_cookies
= 0;
1197 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1198 cookies_enabled
= 1;
1199 enable_cookie_whitelist
= 0;
1200 read_only_cookies
= 0;
1201 save_rejected_cookies
= 0;
1202 session_timeout
= 3600;
1204 enable_js_whitelist
= 0;
1205 enable_localstorage
= 1;
1215 get_browser_mode(struct settings
*s
)
1219 if (browser_mode
== XT_BM_WHITELIST
)
1220 r
= g_strdup("whitelist");
1221 else if (browser_mode
== XT_BM_NORMAL
)
1222 r
= g_strdup("normal");
1223 else if (browser_mode
== XT_BM_KIOSK
)
1224 r
= g_strdup("kiosk");
1232 set_cookie_policy(struct settings
*s
, char *val
)
1234 if (!strcmp(val
, "no3rdparty"))
1235 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
1236 else if (!strcmp(val
, "accept"))
1237 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
1238 else if (!strcmp(val
, "reject"))
1239 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
1247 get_cookie_policy(struct settings
*s
)
1251 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
1252 r
= g_strdup("no3rdparty");
1253 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
1254 r
= g_strdup("accept");
1255 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
1256 r
= g_strdup("reject");
1264 get_default_script(struct settings
*s
)
1266 if (default_script
[0] == '\0')
1268 return (g_strdup(default_script
));
1272 set_default_script(struct settings
*s
, char *val
)
1275 snprintf(default_script
, sizeof default_script
, "%s/%s",
1276 pwd
->pw_dir
, &val
[1]);
1278 strlcpy(default_script
, val
, sizeof default_script
);
1284 get_download_dir(struct settings
*s
)
1286 if (download_dir
[0] == '\0')
1288 return (g_strdup(download_dir
));
1292 set_download_dir(struct settings
*s
, char *val
)
1295 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
1296 pwd
->pw_dir
, &val
[1]);
1298 strlcpy(download_dir
, val
, sizeof download_dir
);
1305 * We use these to prevent people putting xxxt:// URLs on
1306 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1308 #define XT_XTP_SES_KEY_SZ 8
1309 #define XT_XTP_SES_KEY_HEX_FMT \
1310 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1311 char *dl_session_key
; /* downloads */
1312 char *hl_session_key
; /* history list */
1313 char *cl_session_key
; /* cookie list */
1314 char *fl_session_key
; /* favorites list */
1316 char work_dir
[PATH_MAX
];
1317 char certs_dir
[PATH_MAX
];
1318 char cache_dir
[PATH_MAX
];
1319 char sessions_dir
[PATH_MAX
];
1320 char cookie_file
[PATH_MAX
];
1321 SoupURI
*proxy_uri
= NULL
;
1322 SoupSession
*session
;
1323 SoupCookieJar
*s_cookiejar
;
1324 SoupCookieJar
*p_cookiejar
;
1325 char rc_fname
[PATH_MAX
];
1327 struct mime_type_list mtl
;
1328 struct alias_list aliases
;
1331 struct tab
*create_new_tab(char *, struct undo
*, int, int);
1332 void delete_tab(struct tab
*);
1333 void setzoom_webkit(struct tab
*, int);
1334 int run_script(struct tab
*, char *);
1335 int download_rb_cmp(struct download
*, struct download
*);
1336 gboolean
cmd_execute(struct tab
*t
, char *str
);
1339 history_rb_cmp(struct history
*h1
, struct history
*h2
)
1341 return (strcmp(h1
->uri
, h2
->uri
));
1343 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
1346 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
1348 return (strcmp(d1
->d
, d2
->d
));
1350 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
1353 get_work_dir(struct settings
*s
)
1355 if (work_dir
[0] == '\0')
1357 return (g_strdup(work_dir
));
1361 set_work_dir(struct settings
*s
, char *val
)
1364 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
1365 pwd
->pw_dir
, &val
[1]);
1367 strlcpy(work_dir
, val
, sizeof work_dir
);
1373 get_tab_style(struct settings
*s
)
1375 if (tab_style
== XT_TABS_NORMAL
)
1376 return (g_strdup("normal"));
1378 return (g_strdup("compact"));
1382 set_tab_style(struct settings
*s
, char *val
)
1384 if (!strcmp(val
, "normal"))
1385 tab_style
= XT_TABS_NORMAL
;
1386 else if (!strcmp(val
, "compact"))
1387 tab_style
= XT_TABS_COMPACT
;
1395 * generate a session key to secure xtp commands.
1396 * pass in a ptr to the key in question and it will
1397 * be modified in place.
1400 generate_xtp_session_key(char **key
)
1402 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
1408 /* make a new one */
1409 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
1410 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
1411 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
1412 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
1414 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
1418 * validate a xtp session key.
1422 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
1424 if (strcmp(trusted
, untrusted
) != 0) {
1425 show_oops(t
, "%s: xtp session key mismatch possible spoof",
1434 download_rb_cmp(struct download
*e1
, struct download
*e2
)
1436 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
1438 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
1440 struct valid_url_types
{
1451 valid_url_type(char *url
)
1455 for (i
= 0; i
< LENGTH(vut
); i
++)
1456 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
1463 print_cookie(char *msg
, SoupCookie
*c
)
1469 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
1470 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
1471 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
1472 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
1473 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
1474 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
1475 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
1476 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
1477 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
1478 DNPRINTF(XT_D_COOKIE
, "====================================\n");
1482 walk_alias(struct settings
*s
,
1483 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1488 if (s
== NULL
|| cb
== NULL
) {
1489 show_oops(NULL
, "walk_alias invalid parameters");
1493 TAILQ_FOREACH(a
, &aliases
, entry
) {
1494 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
1495 cb(s
, str
, cb_args
);
1501 match_alias(char *url_in
)
1505 char *url_out
= NULL
, *search
, *enc_arg
;
1507 search
= g_strdup(url_in
);
1509 if (strsep(&arg
, " \t") == NULL
) {
1510 show_oops(NULL
, "match_alias: NULL URL");
1514 TAILQ_FOREACH(a
, &aliases
, entry
) {
1515 if (!strcmp(search
, a
->a_name
))
1520 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
1523 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
1524 url_out
= g_strdup_printf(a
->a_uri
, enc_arg
);
1527 url_out
= g_strdup_printf(a
->a_uri
, "");
1535 guess_url_type(char *url_in
)
1538 char *url_out
= NULL
, *enc_search
= NULL
;
1540 url_out
= match_alias(url_in
);
1541 if (url_out
!= NULL
)
1546 * If there is no dot nor slash in the string and it isn't a
1547 * path to a local file and doesn't resolves to an IP, assume
1548 * that the user wants to search for the string.
1551 if (strchr(url_in
, '.') == NULL
&&
1552 strchr(url_in
, '/') == NULL
&&
1553 stat(url_in
, &sb
) != 0 &&
1554 gethostbyname(url_in
) == NULL
) {
1556 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
1557 url_out
= g_strdup_printf(search_string
, enc_search
);
1563 /* XXX not sure about this heuristic */
1564 if (stat(url_in
, &sb
) == 0)
1565 url_out
= g_strdup_printf("file://%s", url_in
);
1567 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
1569 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
1575 load_uri(struct tab
*t
, gchar
*uri
)
1578 gchar
*newuri
= NULL
;
1584 /* Strip leading spaces. */
1585 while (*uri
&& isspace(*uri
))
1588 if (strlen(uri
) == 0) {
1593 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
1595 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
1596 for (i
= 0; i
< LENGTH(about_list
); i
++)
1597 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
)) {
1598 bzero(&args
, sizeof args
);
1599 about_list
[i
].func(t
, &args
);
1600 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
),
1604 show_oops(t
, "invalid about page");
1608 if (valid_url_type(uri
)) {
1609 newuri
= guess_url_type(uri
);
1613 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
1614 webkit_web_view_load_uri(t
->wv
, uri
);
1621 get_uri(struct tab
*t
)
1623 const gchar
*uri
= NULL
;
1625 if (webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
)
1627 if (t
->xtp_meaning
== XT_XTP_TAB_MEANING_NORMAL
) {
1628 uri
= webkit_web_view_get_uri(t
->wv
);
1630 /* use tmp_uri to make sure it is g_freed */
1633 t
->tmp_uri
= g_strdup_printf("%s%s", XT_URI_ABOUT
, about_list
[t
->xtp_meaning
].name
);
1640 get_title(struct tab
*t
, bool window
)
1642 const gchar
*set
= NULL
, *title
= NULL
;
1643 WebKitLoadStatus status
= webkit_web_view_get_load_status(t
->wv
);
1645 if (status
== WEBKIT_LOAD_PROVISIONAL
|| status
== WEBKIT_LOAD_FAILED
||
1646 t
->xtp_meaning
== XT_XTP_TAB_MEANING_BL
)
1649 title
= webkit_web_view_get_title(t
->wv
);
1650 if ((set
= title
? title
: get_uri(t
)))
1654 set
= window
? XT_NAME
: "(untitled)";
1660 add_alias(struct settings
*s
, char *line
)
1663 struct alias
*a
= NULL
;
1665 if (s
== NULL
|| line
== NULL
) {
1666 show_oops(NULL
, "add_alias invalid parameters");
1671 a
= g_malloc(sizeof(*a
));
1673 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1674 show_oops(NULL
, "add_alias: incomplete alias definition");
1677 if (strlen(alias
) == 0 || strlen(l
) == 0) {
1678 show_oops(NULL
, "add_alias: invalid alias definition");
1682 a
->a_name
= g_strdup(alias
);
1683 a
->a_uri
= g_strdup(l
);
1685 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
1687 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
1697 add_mime_type(struct settings
*s
, char *line
)
1701 struct mime_type
*m
= NULL
;
1702 int downloadfirst
= 0;
1704 /* XXX this could be smarter */
1706 if (line
== NULL
|| strlen(line
) == 0) {
1707 show_oops(NULL
, "add_mime_type invalid parameters");
1716 m
= g_malloc(sizeof(*m
));
1718 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
) {
1719 show_oops(NULL
, "add_mime_type: invalid mime_type");
1722 if (mime_type
[strlen(mime_type
) - 1] == '*') {
1723 mime_type
[strlen(mime_type
) - 1] = '\0';
1728 if (strlen(mime_type
) == 0 || strlen(l
) == 0) {
1729 show_oops(NULL
, "add_mime_type: invalid mime_type");
1733 m
->mt_type
= g_strdup(mime_type
);
1734 m
->mt_action
= g_strdup(l
);
1735 m
->mt_download
= downloadfirst
;
1737 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
1738 m
->mt_type
, m
->mt_action
, m
->mt_default
);
1740 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1750 find_mime_type(char *mime_type
)
1752 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1754 TAILQ_FOREACH(m
, &mtl
, entry
) {
1755 if (m
->mt_default
&&
1756 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1759 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1772 walk_mime_type(struct settings
*s
,
1773 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1775 struct mime_type
*m
;
1778 if (s
== NULL
|| cb
== NULL
) {
1779 show_oops(NULL
, "walk_mime_type invalid parameters");
1783 TAILQ_FOREACH(m
, &mtl
, entry
) {
1784 str
= g_strdup_printf("%s%s --> %s",
1786 m
->mt_default
? "*" : "",
1788 cb(s
, str
, cb_args
);
1794 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1799 if (str
== NULL
|| wl
== NULL
|| strlen(str
) < 2)
1802 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1804 /* treat *.moo.com the same as .moo.com */
1805 if (str
[0] == '*' && str
[1] == '.')
1807 else if (str
[0] == '.')
1812 d
= g_malloc(sizeof *d
);
1814 d
->d
= g_strdup_printf(".%s", str
);
1816 d
->d
= g_strdup(str
);
1819 if (RB_INSERT(domain_list
, wl
, d
))
1822 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1833 add_cookie_wl(struct settings
*s
, char *entry
)
1835 wl_add(entry
, &c_wl
, 1);
1840 walk_cookie_wl(struct settings
*s
,
1841 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1845 if (s
== NULL
|| cb
== NULL
) {
1846 show_oops(NULL
, "walk_cookie_wl invalid parameters");
1850 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1851 cb(s
, d
->d
, cb_args
);
1855 walk_js_wl(struct settings
*s
,
1856 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1860 if (s
== NULL
|| cb
== NULL
) {
1861 show_oops(NULL
, "walk_js_wl invalid parameters");
1865 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1866 cb(s
, d
->d
, cb_args
);
1870 add_js_wl(struct settings
*s
, char *entry
)
1872 wl_add(entry
, &js_wl
, 1 /* persistent */);
1877 wl_find(const gchar
*search
, struct domain_list
*wl
)
1880 struct domain
*d
= NULL
, dfind
;
1883 if (search
== NULL
|| wl
== NULL
)
1885 if (strlen(search
) < 2)
1888 if (search
[0] != '.')
1889 s
= g_strdup_printf(".%s", search
);
1891 s
= g_strdup(search
);
1893 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1896 d
= RB_FIND(domain_list
, wl
, &dfind
);
1910 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1916 if (s
== NULL
|| wl
== NULL
)
1919 if (!strncmp(s
, "http://", strlen("http://")))
1920 s
= &s
[strlen("http://")];
1921 else if (!strncmp(s
, "https://", strlen("https://")))
1922 s
= &s
[strlen("https://")];
1927 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1928 /* chop string at first slash */
1929 if (s
[i
] == '/' || s
[i
] == '\0') {
1932 r
= wl_find(ss
, wl
);
1941 settings_add(char *var
, char *val
)
1948 for (i
= 0, rv
= 0; i
< LENGTH(rs
); i
++) {
1949 if (strcmp(var
, rs
[i
].name
))
1953 if (rs
[i
].s
->set(&rs
[i
], val
))
1954 errx(1, "invalid value for %s: %s", var
, val
);
1958 switch (rs
[i
].type
) {
1967 errx(1, "invalid sval for %s",
1981 errx(1, "invalid type for %s", var
);
1990 config_parse(char *filename
, int runtime
)
1993 char *line
, *cp
, *var
, *val
;
1994 size_t len
, lineno
= 0;
1996 char file
[PATH_MAX
];
1999 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
2001 if (filename
== NULL
)
2004 if (runtime
&& runtime_settings
[0] != '\0') {
2005 snprintf(file
, sizeof file
, "%s/%s",
2006 work_dir
, runtime_settings
);
2007 if (stat(file
, &sb
)) {
2008 warnx("runtime file doesn't exist, creating it");
2009 if ((f
= fopen(file
, "w")) == NULL
)
2011 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
2015 strlcpy(file
, filename
, sizeof file
);
2017 if ((config
= fopen(file
, "r")) == NULL
) {
2018 warn("config_parse: cannot open %s", filename
);
2023 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
2024 if (feof(config
) || ferror(config
))
2028 cp
+= (long)strspn(cp
, WS
);
2029 if (cp
[0] == '\0') {
2035 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
2036 errx(1, "invalid config file entry: %s", line
);
2038 cp
+= (long)strspn(cp
, WS
);
2040 if ((val
= strsep(&cp
, "\0")) == NULL
)
2043 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n", var
, val
);
2044 handled
= settings_add(var
, val
);
2046 errx(1, "invalid conf file entry: %s=%s", var
, val
);
2055 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
2061 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
2065 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
2068 JSStringGetUTF8CString(jsref
, s
, l
);
2069 JSStringRelease(jsref
);
2075 disable_hints(struct tab
*t
)
2077 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
2078 bzero(t
->hint_num
, sizeof t
->hint_num
);
2079 run_script(t
, "vimprobable_clear()");
2081 t
->hint_mode
= XT_HINT_NONE
;
2085 enable_hints(struct tab
*t
)
2087 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
2088 run_script(t
, "vimprobable_show_hints()");
2090 t
->hint_mode
= XT_HINT_NONE
;
2093 #define XT_JS_OPEN ("open;")
2094 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2095 #define XT_JS_FIRE ("fire;")
2096 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2097 #define XT_JS_FOUND ("found;")
2098 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2101 run_script(struct tab
*t
, char *s
)
2103 JSGlobalContextRef ctx
;
2104 WebKitWebFrame
*frame
;
2106 JSValueRef val
, exception
;
2109 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
2110 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
2112 frame
= webkit_web_view_get_main_frame(t
->wv
);
2113 ctx
= webkit_web_frame_get_global_context(frame
);
2115 str
= JSStringCreateWithUTF8CString(s
);
2116 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
2117 NULL
, 0, &exception
);
2118 JSStringRelease(str
);
2120 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
2122 es
= js_ref_to_string(ctx
, exception
);
2123 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
2127 es
= js_ref_to_string(ctx
, val
);
2128 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
2130 /* handle return value right here */
2131 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
2133 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
2136 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
2137 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
2138 &es
[XT_JS_FIRE_LEN
]);
2143 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
2144 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
2155 hint(struct tab
*t
, struct karg
*args
)
2158 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
2160 if (t
->hints_on
== 0)
2169 apply_style(struct tab
*t
)
2171 g_object_set(G_OBJECT(t
->settings
),
2172 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
2176 userstyle(struct tab
*t
, struct karg
*args
)
2178 DNPRINTF(XT_D_JS
, "userstyle: tab %d\n", t
->tab_id
);
2182 g_object_set(G_OBJECT(t
->settings
),
2183 "user-stylesheet-uri", NULL
, (char *)NULL
);
2192 * Doesn't work fully, due to the following bug:
2193 * https://bugs.webkit.org/show_bug.cgi?id=51747
2196 restore_global_history(void)
2198 char file
[PATH_MAX
];
2203 const char delim
[3] = {'\\', '\\', '\0'};
2205 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2207 if ((f
= fopen(file
, "r")) == NULL
) {
2208 warnx("%s: fopen", __func__
);
2213 if ((uri
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2214 if (feof(f
) || ferror(f
))
2217 if ((title
= fparseln(f
, NULL
, NULL
, delim
, 0)) == NULL
)
2218 if (feof(f
) || ferror(f
)) {
2220 warnx("%s: broken history file\n", __func__
);
2224 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
2225 webkit_web_history_item_new_with_data(uri
, title
);
2226 h
= g_malloc(sizeof(struct history
));
2227 h
->uri
= g_strdup(uri
);
2228 h
->title
= g_strdup(title
);
2229 RB_INSERT(history_list
, &hl
, h
);
2230 completion_add_uri(h
->uri
);
2232 warnx("%s: failed to restore history\n", __func__
);
2248 save_global_history_to_disk(struct tab
*t
)
2250 char file
[PATH_MAX
];
2254 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_HISTORY_FILE
);
2256 if ((f
= fopen(file
, "w")) == NULL
) {
2257 show_oops(t
, "%s: global history file: %s",
2258 __func__
, strerror(errno
));
2262 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
2263 if (h
->uri
&& h
->title
)
2264 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
2273 quit(struct tab
*t
, struct karg
*args
)
2275 if (save_global_history
)
2276 save_global_history_to_disk(t
);
2284 open_tabs(struct tab
*t
, struct karg
*a
)
2286 char file
[PATH_MAX
];
2290 struct tab
*ti
, *tt
;
2295 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2296 if ((f
= fopen(file
, "r")) == NULL
)
2299 ti
= TAILQ_LAST(&tabs
, tab_list
);
2302 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
2303 if (feof(f
) || ferror(f
))
2306 /* retrieve session name */
2307 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
2308 strlcpy(named_session
,
2309 &uri
[strlen(XT_SAVE_SESSION_ID
)],
2310 sizeof named_session
);
2314 if (uri
&& strlen(uri
))
2315 create_new_tab(uri
, NULL
, 1, -1);
2321 /* close open tabs */
2322 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
2324 tt
= TAILQ_FIRST(&tabs
);
2344 restore_saved_tabs(void)
2346 char file
[PATH_MAX
];
2347 int unlink_file
= 0;
2352 snprintf(file
, sizeof file
, "%s/%s",
2353 sessions_dir
, XT_RESTART_TABS_FILE
);
2354 if (stat(file
, &sb
) == -1)
2355 a
.s
= XT_SAVED_TABS_FILE
;
2358 a
.s
= XT_RESTART_TABS_FILE
;
2361 a
.i
= XT_SES_DONOTHING
;
2362 rv
= open_tabs(NULL
, &a
);
2371 save_tabs(struct tab
*t
, struct karg
*a
)
2373 char file
[PATH_MAX
];
2375 int num_tabs
= 0, i
;
2376 struct tab
**stabs
= NULL
;
2381 snprintf(file
, sizeof file
, "%s/%s",
2382 sessions_dir
, named_session
);
2384 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
2386 if ((f
= fopen(file
, "w")) == NULL
) {
2387 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
2391 /* save session name */
2392 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
2394 /* Save tabs, in the order they are arranged in the notebook. */
2395 num_tabs
= sort_tabs_by_page_num(&stabs
);
2397 for (i
= 0; i
< num_tabs
; i
++)
2398 if (stabs
[i
] && get_uri(stabs
[i
]) != NULL
)
2399 fprintf(f
, "%s\n", get_uri(stabs
[i
]));
2403 /* try and make sure this gets to disk NOW. XXX Backup first? */
2404 if (fflush(f
) != 0 || fsync(fileno(f
)) != 0) {
2405 show_oops(t
, "May not have managed to save session: %s",
2415 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
2427 run_page_script(struct tab
*t
, struct karg
*args
)
2430 char *tmp
, script
[PATH_MAX
];
2432 tmp
= args
->s
!= NULL
&& strlen(args
->s
) > 0 ? args
->s
: default_script
;
2433 if (tmp
[0] == '\0') {
2434 show_oops(t
, "no script specified");
2438 if ((uri
= get_uri(t
)) == NULL
) {
2439 show_oops(t
, "tab is empty, not running script");
2444 snprintf(script
, sizeof script
, "%s/%s",
2445 pwd
->pw_dir
, &tmp
[1]);
2447 strlcpy(script
, tmp
, sizeof script
);
2451 show_oops(t
, "can't fork to run script");
2461 execlp(script
, script
, uri
, (void *)NULL
);
2471 yank_uri(struct tab
*t
, struct karg
*args
)
2474 GtkClipboard
*clipboard
;
2476 if ((uri
= get_uri(t
)) == NULL
)
2479 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2480 gtk_clipboard_set_text(clipboard
, uri
, -1);
2486 paste_uri(struct tab
*t
, struct karg
*args
)
2488 GtkClipboard
*clipboard
;
2489 GdkAtom atom
= gdk_atom_intern("CUT_BUFFER0", FALSE
);
2491 gchar
*p
= NULL
, *uri
;
2493 /* try primary clipboard first */
2494 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
2495 p
= gtk_clipboard_wait_for_text(clipboard
);
2497 /* if it failed get whatever text is in cut_buffer0 */
2499 if (gdk_property_get(gdk_get_default_root_window(),
2501 gdk_atom_intern("STRING", FALSE
),
2503 65536 /* picked out of my butt */,
2509 /* yes sir, we need to NUL the string */
2515 while (*uri
&& isspace(*uri
))
2517 if (strlen(uri
) == 0) {
2518 show_oops(t
, "empty paste buffer");
2521 if (guess_search
== 0 && valid_url_type(uri
)) {
2522 /* we can be clever and paste this in search box */
2523 show_oops(t
, "not a valid URL");
2527 if (args
->i
== XT_PASTE_CURRENT_TAB
)
2529 else if (args
->i
== XT_PASTE_NEW_TAB
)
2530 create_new_tab(uri
, NULL
, 1, -1);
2541 find_domain(const gchar
*s
, int toplevel
)
2549 uri
= soup_uri_new(s
);
2551 if (uri
== NULL
|| !SOUP_URI_VALID_FOR_HTTP(uri
)) {
2555 if (toplevel
&& !isdigit(uri
->host
[strlen(uri
->host
) - 1])) {
2556 if ((p
= strrchr(uri
->host
, '.')) != NULL
) {
2557 while(--p
>= uri
->host
&& *p
!= '.');
2564 if (uri
->port
== 80)
2565 ret
= g_strdup_printf(".%s", p
);
2567 ret
= g_strdup_printf(".%s:%d", p
, uri
->port
);
2575 toggle_cwl(struct tab
*t
, struct karg
*args
)
2586 dom
= find_domain(uri
, args
->i
& XT_WL_TOPLEVEL
);
2588 if (uri
== NULL
|| dom
== NULL
|| webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
) {
2589 show_oops(t
, "Can't toggle domain in cookie white list");
2592 d
= wl_find(dom
, &c_wl
);
2599 if (args
->i
& XT_WL_TOGGLE
)
2601 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2603 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2607 /* enable cookies for domain */
2608 wl_add(dom
, &c_wl
, 0);
2610 /* disable cookies for domain */
2611 RB_REMOVE(domain_list
, &c_wl
, d
);
2613 if (args
->i
& XT_WL_RELOAD
)
2614 webkit_web_view_reload(t
->wv
);
2622 toggle_js(struct tab
*t
, struct karg
*args
)
2632 g_object_get(G_OBJECT(t
->settings
),
2633 "enable-scripts", &es
, (char *)NULL
);
2634 if (args
->i
& XT_WL_TOGGLE
)
2636 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
2638 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
2644 dom
= find_domain(uri
, args
->i
& XT_WL_TOPLEVEL
);
2646 if (uri
== NULL
|| dom
== NULL
|| webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
) {
2647 show_oops(t
, "Can't toggle domain in JavaScript white list");
2652 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
2653 wl_add(dom
, &js_wl
, 0 /* session */);
2655 d
= wl_find(dom
, &js_wl
);
2657 RB_REMOVE(domain_list
, &js_wl
, d
);
2658 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
2660 g_object_set(G_OBJECT(t
->settings
),
2661 "enable-scripts", es
, (char *)NULL
);
2662 g_object_set(G_OBJECT(t
->settings
),
2663 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
2664 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2666 if (args
->i
& XT_WL_RELOAD
)
2667 webkit_web_view_reload(t
->wv
);
2675 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
2679 a
.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
;
2682 a
.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
;
2687 toggle_src(struct tab
*t
, struct karg
*args
)
2694 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
2695 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
2696 webkit_web_view_reload(t
->wv
);
2702 focus_webview(struct tab
*t
)
2707 /* only grab focus if we are visible */
2708 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
2709 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
2713 focus(struct tab
*t
, struct karg
*args
)
2715 if (t
== NULL
|| args
== NULL
)
2721 if (args
->i
== XT_FOCUS_URI
)
2722 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
2723 else if (args
->i
== XT_FOCUS_SEARCH
)
2724 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
2730 stats(struct tab
*t
, struct karg
*args
)
2732 char *page
, *body
, *s
, line
[64 * 1024];
2733 uint64_t line_count
= 0;
2737 show_oops(NULL
, "stats invalid parameters");
2740 if (save_rejected_cookies
) {
2741 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
2743 s
= fgets(line
, sizeof line
, r_cookie_f
);
2744 if (s
== NULL
|| feof(r_cookie_f
) ||
2750 snprintf(line
, sizeof line
,
2751 "<br/>Cookies blocked(*) total: %llu", line_count
);
2753 show_oops(t
, "Can't open blocked cookies file: %s",
2757 body
= g_strdup_printf(
2758 "Cookies blocked(*) this session: %llu"
2760 "<p><small><b>*</b> results vary based on settings</small></p>",
2764 page
= get_html_page("Statistics", body
, "", 0);
2767 load_webkit_string(t
, page
, XT_URI_ABOUT_STATS
);
2774 marco(struct tab
*t
, struct karg
*args
)
2776 char *page
, line
[64 * 1024];
2780 show_oops(NULL
, "marco invalid parameters");
2783 snprintf(line
, sizeof line
, "%s", marco_message(&len
));
2785 page
= get_html_page("Marco Sez...", line
, "", 0);
2787 load_webkit_string(t
, page
, XT_URI_ABOUT_MARCO
);
2794 blank(struct tab
*t
, struct karg
*args
)
2797 show_oops(NULL
, "blank invalid parameters");
2799 load_webkit_string(t
, "", XT_URI_ABOUT_BLANK
);
2804 about(struct tab
*t
, struct karg
*args
)
2809 show_oops(NULL
, "about invalid parameters");
2811 body
= g_strdup_printf("<b>Version: %s</b><p>"
2814 "<li>Marco Peereboom <marco@peereboom.us></li>"
2815 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
2816 "<li>Edd Barrett <vext01@gmail.com> </li>"
2817 "<li>Todd T. Fries <todd@fries.net> </li>"
2818 "<li>Raphael Graf <r@undefined.ch> </li>"
2820 "Copyrights and licenses can be found on the XXXTerm "
2821 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2825 page
= get_html_page("About", body
, "", 0);
2828 load_webkit_string(t
, page
, XT_URI_ABOUT_ABOUT
);
2835 help(struct tab
*t
, struct karg
*args
)
2837 char *page
, *head
, *body
;
2840 show_oops(NULL
, "help invalid parameters");
2842 head
= "<meta http-equiv=\"REFRESH\" content=\"0;"
2843 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2845 body
= "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2846 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2847 "cgi-bin/man-cgi?xxxterm</a>";
2849 page
= get_html_page(XT_NAME
, body
, head
, FALSE
);
2851 load_webkit_string(t
, page
, XT_URI_ABOUT_HELP
);
2858 * update all favorite tabs apart from one. Pass NULL if
2859 * you want to update all.
2862 update_favorite_tabs(struct tab
*apart_from
)
2865 if (!updating_fl_tabs
) {
2866 updating_fl_tabs
= 1; /* stop infinite recursion */
2867 TAILQ_FOREACH(t
, &tabs
, entry
)
2868 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2869 && (t
!= apart_from
))
2870 xtp_page_fl(t
, NULL
);
2871 updating_fl_tabs
= 0;
2875 /* show a list of favorites (bookmarks) */
2877 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2879 char file
[PATH_MAX
];
2881 char *uri
= NULL
, *title
= NULL
;
2882 size_t len
, lineno
= 0;
2884 char *body
, *tmp
, *page
= NULL
;
2885 const char delim
[3] = {'\\', '\\', '\0'};
2887 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2890 warn("%s: bad param", __func__
);
2892 /* new session key */
2893 if (!updating_fl_tabs
)
2894 generate_xtp_session_key(&fl_session_key
);
2896 /* open favorites */
2897 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
2898 if ((f
= fopen(file
, "r")) == NULL
) {
2899 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2904 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
2905 "<th style='width: 40px'>#</th><th>Link</th>"
2906 "<th style='width: 40px'>Rm</th></tr>\n");
2909 if ((title
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
2910 if (feof(f
) || ferror(f
))
2918 if ((uri
= fparseln(f
, &len
, &lineno
, delim
, 0)) == NULL
)
2919 if (feof(f
) || ferror(f
)) {
2920 show_oops(t
, "favorites file corrupt");
2926 body
= g_strdup_printf("%s<tr>"
2928 "<td><a href='%s'>%s</a></td>"
2929 "<td style='text-align: center'>"
2930 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2932 body
, i
, uri
, title
,
2933 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2945 /* if none, say so */
2948 body
= g_strdup_printf("%s<tr>"
2949 "<td colspan='3' style='text-align: center'>"
2950 "No favorites - To add one use the 'favadd' command."
2951 "</td></tr>", body
);
2956 body
= g_strdup_printf("%s</table>", body
);
2966 page
= get_html_page("Favorites", body
, "", 1);
2967 load_webkit_string(t
, page
, XT_URI_ABOUT_FAVORITES
);
2971 update_favorite_tabs(t
);
2980 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2981 size_t cert_count
, char *title
)
2983 gnutls_datum_t cinfo
;
2987 body
= g_strdup("");
2989 for (i
= 0; i
< cert_count
; i
++) {
2990 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2995 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2996 body
, i
, cinfo
.data
);
2997 gnutls_free(cinfo
.data
);
3001 tmp
= get_html_page(title
, body
, "", 0);
3004 load_webkit_string(t
, tmp
, XT_URI_ABOUT_CERTS
);
3009 ca_cmd(struct tab
*t
, struct karg
*args
)
3012 int rv
= 1, certs
= 0, certs_read
;
3015 gnutls_x509_crt_t
*c
= NULL
;
3016 char *certs_buf
= NULL
, *s
;
3018 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
3019 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
3023 if (fstat(fileno(f
), &sb
) == -1) {
3024 show_oops(t
, "Can't stat CA file: %s", ssl_ca_file
);
3028 certs_buf
= g_malloc(sb
.st_size
+ 1);
3029 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
3030 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
3033 certs_buf
[sb
.st_size
] = '\0';
3036 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
3038 s
+= strlen("BEGIN CERTIFICATE");
3041 bzero(&dt
, sizeof dt
);
3042 dt
.data
= (unsigned char *)certs_buf
;
3043 dt
.size
= sb
.st_size
;
3044 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
3045 certs_read
= gnutls_x509_crt_list_import(c
, (unsigned int *)&certs
, &dt
,
3046 GNUTLS_X509_FMT_PEM
, 0);
3047 if (certs_read
<= 0) {
3048 show_oops(t
, "No cert(s) available");
3051 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
3064 connect_socket_from_uri(const gchar
*uri
, char *domain
, size_t domain_sz
)
3067 struct addrinfo hints
, *res
= NULL
, *ai
;
3071 if (uri
&& !g_str_has_prefix(uri
, "https://"))
3074 su
= soup_uri_new(uri
);
3077 if (!SOUP_URI_VALID_FOR_HTTP(su
))
3080 snprintf(port
, sizeof port
, "%d", su
->port
);
3081 bzero(&hints
, sizeof(struct addrinfo
));
3082 hints
.ai_flags
= AI_CANONNAME
;
3083 hints
.ai_family
= AF_UNSPEC
;
3084 hints
.ai_socktype
= SOCK_STREAM
;
3086 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
3089 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
3090 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
3093 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
3096 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
3100 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
3105 strlcpy(domain
, su
->host
, domain_sz
);
3116 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
3119 gnutls_deinit(gsession
);
3121 gnutls_certificate_free_credentials(xcred
);
3127 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
3128 gnutls_certificate_credentials_t
*xc
)
3130 gnutls_certificate_credentials_t xcred
;
3131 gnutls_session_t gsession
;
3134 if (gs
== NULL
|| xc
== NULL
)
3140 gnutls_certificate_allocate_credentials(&xcred
);
3141 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
3142 GNUTLS_X509_FMT_PEM
);
3143 gnutls_init(&gsession
, GNUTLS_CLIENT
);
3144 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
3145 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
3146 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
3147 if ((rv
= gnutls_handshake(gsession
)) < 0) {
3148 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
3150 gnutls_error_is_fatal(rv
),
3151 gnutls_strerror_name(rv
));
3152 stop_tls(gsession
, xcred
);
3156 gnutls_credentials_type_t cred
;
3157 cred
= gnutls_auth_get_type(gsession
);
3158 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
3159 stop_tls(gsession
, xcred
);
3171 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
3175 const gnutls_datum_t
*cl
;
3176 gnutls_x509_crt_t
*all_certs
;
3179 if (certs
== NULL
|| cert_count
== NULL
)
3181 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
3183 cl
= gnutls_certificate_get_peers(gsession
, &len
);
3187 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
3188 for (i
= 0; i
< len
; i
++) {
3189 gnutls_x509_crt_init(&all_certs
[i
]);
3190 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
3191 GNUTLS_X509_FMT_PEM
< 0)) {
3205 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
3209 for (i
= 0; i
< cert_count
; i
++)
3210 gnutls_x509_crt_deinit(certs
[i
]);
3215 statusbar_modify_attr(struct tab
*t
, const char *text
, const char *base
)
3217 GdkColor c_text
, c_base
;
3219 gdk_color_parse(text
, &c_text
);
3220 gdk_color_parse(base
, &c_base
);
3222 gtk_widget_modify_text(t
->sbe
.statusbar
, GTK_STATE_NORMAL
, &c_text
);
3223 gtk_widget_modify_text(t
->sbe
.buffercmd
, GTK_STATE_NORMAL
, &c_text
);
3224 gtk_widget_modify_text(t
->sbe
.position
, GTK_STATE_NORMAL
, &c_text
);
3226 gtk_widget_modify_base(t
->sbe
.statusbar
, GTK_STATE_NORMAL
, &c_base
);
3227 gtk_widget_modify_base(t
->sbe
.buffercmd
, GTK_STATE_NORMAL
, &c_base
);
3228 gtk_widget_modify_base(t
->sbe
.position
, GTK_STATE_NORMAL
, &c_base
);
3232 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
3233 size_t cert_count
, char *domain
)
3236 char cert_buf
[64 * 1024], file
[PATH_MAX
];
3241 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
3244 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3245 if ((f
= fopen(file
, "w")) == NULL
) {
3246 show_oops(t
, "Can't create cert file %s %s",
3247 file
, strerror(errno
));
3251 for (i
= 0; i
< cert_count
; i
++) {
3252 cert_buf_sz
= sizeof cert_buf
;
3253 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3254 cert_buf
, &cert_buf_sz
)) {
3255 show_oops(t
, "gnutls_x509_crt_export failed");
3258 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3259 show_oops(t
, "Can't write certs: %s", strerror(errno
));
3264 /* not the best spot but oh well */
3265 gdk_color_parse("lightblue", &color
);
3266 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3267 statusbar_modify_attr(t
, XT_COLOR_BLACK
, "lightblue");
3273 load_compare_cert(struct tab
*t
, struct karg
*args
)
3276 char domain
[8182], file
[PATH_MAX
];
3277 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
3278 int s
= -1, rv
= 1, i
;
3282 gnutls_session_t gsession
;
3283 gnutls_x509_crt_t
*certs
;
3284 gnutls_certificate_credentials_t xcred
;
3289 if ((uri
= get_uri(t
)) == NULL
)
3292 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
3296 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3297 show_oops(t
, "Start TLS failed");
3302 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3303 show_oops(t
, "Can't get connection certificates");
3307 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
3308 if ((f
= fopen(file
, "r")) == NULL
)
3311 for (i
= 0; i
< cert_count
; i
++) {
3312 cert_buf_sz
= sizeof cert_buf
;
3313 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
3314 cert_buf
, &cert_buf_sz
)) {
3317 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
3318 rv
= -1; /* critical */
3321 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
3322 rv
= -1; /* critical */
3331 free_connection_certs(certs
, cert_count
);
3333 /* we close the socket first for speed */
3336 stop_tls(gsession
, xcred
);
3342 cert_cmd(struct tab
*t
, struct karg
*args
)
3348 gnutls_session_t gsession
;
3349 gnutls_x509_crt_t
*certs
;
3350 gnutls_certificate_credentials_t xcred
;
3355 if (ssl_ca_file
== NULL
) {
3356 show_oops(t
, "Can't open CA file: %s", ssl_ca_file
);
3360 if ((uri
= get_uri(t
)) == NULL
) {
3361 show_oops(t
, "Invalid URI");
3365 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
3366 show_oops(t
, "Invalid certificate URI: %s", uri
);
3371 if (start_tls(t
, s
, &gsession
, &xcred
)) {
3372 show_oops(t
, "Start TLS failed");
3377 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
3378 show_oops(t
, "get_connection_certs failed");
3382 if (args
->i
& XT_SHOW
)
3383 show_certs(t
, certs
, cert_count
, "Certificate Chain");
3384 else if (args
->i
& XT_SAVE
)
3385 save_certs(t
, certs
, cert_count
, domain
);
3387 free_connection_certs(certs
, cert_count
);
3389 /* we close the socket first for speed */
3392 stop_tls(gsession
, xcred
);
3398 remove_cookie(int index
)
3404 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
3406 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3408 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
3412 print_cookie("remove cookie", c
);
3413 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
3418 soup_cookies_free(cf
);
3424 wl_show(struct tab
*t
, struct karg
*args
, char *title
, struct domain_list
*wl
)
3429 body
= g_strdup("");
3432 if (args
->i
& XT_WL_PERSISTENT
) {
3434 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
3436 RB_FOREACH(d
, domain_list
, wl
) {
3440 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3446 if (args
->i
& XT_WL_SESSION
) {
3448 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
3450 RB_FOREACH(d
, domain_list
, wl
) {
3454 body
= g_strdup_printf("%s%s<br/>", body
, d
->d
);
3459 tmp
= get_html_page(title
, body
, "", 0);
3462 load_webkit_string(t
, tmp
, XT_URI_ABOUT_JSWL
);
3464 load_webkit_string(t
, tmp
, XT_URI_ABOUT_COOKIEWL
);
3470 wl_save(struct tab
*t
, struct karg
*args
, int js
)
3472 char file
[PATH_MAX
];
3474 char *line
= NULL
, *lt
= NULL
, *dom
= NULL
;
3482 if (t
== NULL
|| args
== NULL
)
3485 if (runtime_settings
[0] == '\0')
3488 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
3489 if ((f
= fopen(file
, "r+")) == NULL
)
3493 dom
= find_domain(uri
, args
->i
& XT_WL_TOPLEVEL
);
3494 if (uri
== NULL
|| dom
== NULL
|| webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
) {
3495 show_oops(t
, "Can't add domain to %s white list",
3496 js
? "JavaScript" : "cookie");
3500 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom
);
3503 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
3506 if (!strcmp(line
, lt
))
3512 fprintf(f
, "%s\n", lt
);
3517 d
= wl_find(dom
, &js_wl
);
3519 settings_add("js_wl", dom
);
3520 d
= wl_find(dom
, &js_wl
);
3524 d
= wl_find(dom
, &c_wl
);
3526 settings_add("cookie_wl", dom
);
3527 d
= wl_find(dom
, &c_wl
);
3531 /* find and add to persistent jar */
3532 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
3533 for (;cf
; cf
= cf
->next
) {
3535 if (!strcmp(dom
, ci
->domain
) ||
3536 !strcmp(&dom
[1], ci
->domain
)) /* deal with leading . */ {
3537 c
= soup_cookie_copy(ci
);
3538 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
3541 soup_cookies_free(cf
);
3559 js_show_wl(struct tab
*t
, struct karg
*args
)
3561 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3562 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3568 cookie_show_wl(struct tab
*t
, struct karg
*args
)
3570 args
->i
= XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
;
3571 wl_show(t
, args
, "Cookie White List", &c_wl
);
3577 cookie_cmd(struct tab
*t
, struct karg
*args
)
3579 if (args
->i
& XT_SHOW
)
3580 wl_show(t
, args
, "Cookie White List", &c_wl
);
3581 else if (args
->i
& XT_WL_TOGGLE
) {
3582 args
->i
|= XT_WL_RELOAD
;
3583 toggle_cwl(t
, args
);
3584 } else if (args
->i
& XT_SAVE
) {
3585 args
->i
|= XT_WL_RELOAD
;
3586 wl_save(t
, args
, 0);
3587 } else if (args
->i
& XT_DELETE
)
3588 show_oops(t
, "'cookie delete' currently unimplemented");
3594 js_cmd(struct tab
*t
, struct karg
*args
)
3596 if (args
->i
& XT_SHOW
)
3597 wl_show(t
, args
, "JavaScript White List", &js_wl
);
3598 else if (args
->i
& XT_SAVE
) {
3599 args
->i
|= XT_WL_RELOAD
;
3600 wl_save(t
, args
, 1);
3601 } else if (args
->i
& XT_WL_TOGGLE
) {
3602 args
->i
|= XT_WL_RELOAD
;
3604 } else if (args
->i
& XT_DELETE
)
3605 show_oops(t
, "'js delete' currently unimplemented");
3611 toplevel_cmd(struct tab
*t
, struct karg
*args
)
3613 js_toggle_cb(t
->js_toggle
, t
);
3619 add_favorite(struct tab
*t
, struct karg
*args
)
3621 char file
[PATH_MAX
];
3624 size_t urilen
, linelen
;
3625 const gchar
*uri
, *title
;
3630 /* don't allow adding of xtp pages to favorites */
3631 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
3632 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
3636 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
3637 if ((f
= fopen(file
, "r+")) == NULL
) {
3638 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
3642 title
= get_title(t
, FALSE
);
3645 if (title
== NULL
|| uri
== NULL
) {
3646 show_oops(t
, "can't add page to favorites");
3650 urilen
= strlen(uri
);
3653 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
3654 if (feof(f
) || ferror(f
))
3657 if (linelen
== urilen
&& !strcmp(line
, uri
))
3664 fprintf(f
, "\n%s\n%s", title
, uri
);
3670 update_favorite_tabs(NULL
);
3676 navaction(struct tab
*t
, struct karg
*args
)
3678 WebKitWebHistoryItem
*item
;
3680 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
3681 t
->tab_id
, args
->i
);
3683 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
3686 if (args
->i
== XT_NAV_BACK
)
3687 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
3689 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
3691 return (XT_CB_PASSTHROUGH
);
3692 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
3694 return (XT_CB_PASSTHROUGH
);
3699 webkit_web_view_go_back(t
->wv
);
3701 case XT_NAV_FORWARD
:
3702 webkit_web_view_go_forward(t
->wv
);
3705 webkit_web_view_reload(t
->wv
);
3707 case XT_NAV_RELOAD_CACHE
:
3708 webkit_web_view_reload_bypass_cache(t
->wv
);
3711 return (XT_CB_PASSTHROUGH
);
3715 move(struct tab
*t
, struct karg
*args
)
3717 GtkAdjustment
*adjust
;
3718 double pi
, si
, pos
, ps
, upper
, lower
, max
;
3723 case XT_MOVE_BOTTOM
:
3725 case XT_MOVE_PAGEDOWN
:
3726 case XT_MOVE_PAGEUP
:
3727 case XT_MOVE_HALFDOWN
:
3728 case XT_MOVE_HALFUP
:
3729 adjust
= t
->adjust_v
;
3732 adjust
= t
->adjust_h
;
3736 pos
= gtk_adjustment_get_value(adjust
);
3737 ps
= gtk_adjustment_get_page_size(adjust
);
3738 upper
= gtk_adjustment_get_upper(adjust
);
3739 lower
= gtk_adjustment_get_lower(adjust
);
3740 si
= gtk_adjustment_get_step_increment(adjust
);
3741 pi
= gtk_adjustment_get_page_increment(adjust
);
3744 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3745 "max %f si %f pi %f\n",
3746 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
3747 pos
, ps
, upper
, lower
, max
, si
, pi
);
3753 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3758 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3760 case XT_MOVE_BOTTOM
:
3761 case XT_MOVE_FARRIGHT
:
3762 gtk_adjustment_set_value(adjust
, max
);
3765 case XT_MOVE_FARLEFT
:
3766 gtk_adjustment_set_value(adjust
, lower
);
3768 case XT_MOVE_PAGEDOWN
:
3770 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3772 case XT_MOVE_PAGEUP
:
3774 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3776 case XT_MOVE_HALFDOWN
:
3778 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
3780 case XT_MOVE_HALFUP
:
3782 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
3785 return (XT_CB_PASSTHROUGH
);
3788 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3790 return (XT_CB_HANDLED
);
3794 url_set_visibility(void)
3798 TAILQ_FOREACH(t
, &tabs
, entry
)
3799 if (show_url
== 0) {
3800 gtk_widget_hide(t
->toolbar
);
3803 gtk_widget_show(t
->toolbar
);
3807 notebook_tab_set_visibility()
3809 if (show_tabs
== 0) {
3810 gtk_widget_hide(tab_bar
);
3811 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3813 if (tab_style
== XT_TABS_NORMAL
) {
3814 gtk_widget_hide(tab_bar
);
3815 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3816 } else if (tab_style
== XT_TABS_COMPACT
) {
3817 gtk_widget_show(tab_bar
);
3818 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3824 statusbar_set_visibility(void)
3828 TAILQ_FOREACH(t
, &tabs
, entry
)
3829 if (show_statusbar
== 0) {
3830 gtk_widget_hide(t
->statusbar_box
);
3833 gtk_widget_show(t
->statusbar_box
);
3837 url_set(struct tab
*t
, int enable_url_entry
)
3842 show_url
= enable_url_entry
;
3844 if (enable_url_entry
) {
3845 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
3846 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3847 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.statusbar
), 0);
3849 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
3850 GTK_ENTRY_ICON_PRIMARY
);
3852 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
3853 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.statusbar
),
3854 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
3855 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.statusbar
),
3861 fullscreen(struct tab
*t
, struct karg
*args
)
3863 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3866 return (XT_CB_PASSTHROUGH
);
3868 if (show_url
== 0) {
3876 url_set_visibility();
3877 notebook_tab_set_visibility();
3879 return (XT_CB_HANDLED
);
3883 statustoggle(struct tab
*t
, struct karg
*args
)
3885 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3887 if (show_statusbar
== 1) {
3889 statusbar_set_visibility();
3890 } else if (show_statusbar
== 0) {
3892 statusbar_set_visibility();
3894 return (XT_CB_HANDLED
);
3898 urlaction(struct tab
*t
, struct karg
*args
)
3900 int rv
= XT_CB_HANDLED
;
3902 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
3905 return (XT_CB_PASSTHROUGH
);
3909 if (show_url
== 0) {
3911 url_set_visibility();
3915 if (show_url
== 1) {
3917 url_set_visibility();
3925 tabaction(struct tab
*t
, struct karg
*args
)
3927 int rv
= XT_CB_HANDLED
;
3928 char *url
= args
->s
;
3932 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
3935 return (XT_CB_PASSTHROUGH
);
3939 if (strlen(url
) > 0)
3940 create_new_tab(url
, NULL
, 1, args
->p
);
3942 create_new_tab(NULL
, NULL
, 1, args
->p
);
3948 TAILQ_FOREACH(tt
, &tabs
, entry
)
3949 if (tt
->tab_id
== args
->p
- 1) {
3954 case XT_TAB_DELQUIT
:
3955 if (gtk_notebook_get_n_pages(notebook
) > 1)
3961 if (strlen(url
) > 0)
3964 rv
= XT_CB_PASSTHROUGH
;
3970 if (show_tabs
== 0) {
3972 notebook_tab_set_visibility();
3976 if (show_tabs
== 1) {
3978 notebook_tab_set_visibility();
3981 case XT_TAB_NEXTSTYLE
:
3982 if (tab_style
== XT_TABS_NORMAL
) {
3983 tab_style
= XT_TABS_COMPACT
;
3984 recolor_compact_tabs();
3987 tab_style
= XT_TABS_NORMAL
;
3988 notebook_tab_set_visibility();
3990 case XT_TAB_UNDO_CLOSE
:
3991 if (undo_count
== 0) {
3992 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3996 u
= TAILQ_FIRST(&undos
);
3997 create_new_tab(u
->uri
, u
, 1, -1);
3999 TAILQ_REMOVE(&undos
, u
, entry
);
4001 /* u->history is freed in create_new_tab() */
4006 rv
= XT_CB_PASSTHROUGH
;
4020 resizetab(struct tab
*t
, struct karg
*args
)
4022 if (t
== NULL
|| args
== NULL
) {
4023 show_oops(NULL
, "resizetab invalid parameters");
4024 return (XT_CB_PASSTHROUGH
);
4027 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
4028 t
->tab_id
, args
->i
);
4030 setzoom_webkit(t
, args
->i
);
4032 return (XT_CB_HANDLED
);
4036 movetab(struct tab
*t
, struct karg
*args
)
4040 if (t
== NULL
|| args
== NULL
) {
4041 show_oops(NULL
, "movetab invalid parameters");
4042 return (XT_CB_PASSTHROUGH
);
4045 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
4046 t
->tab_id
, args
->i
);
4048 if (args
->i
>= XT_TAB_INVALID
)
4049 return (XT_CB_PASSTHROUGH
);
4051 if (TAILQ_EMPTY(&tabs
))
4052 return (XT_CB_PASSTHROUGH
);
4054 n
= gtk_notebook_get_n_pages(notebook
);
4055 dest
= gtk_notebook_get_current_page(notebook
);
4060 dest
= dest
== n
- 1 ? 0 : dest
+ 1;
4069 dest
-= args
->p
% n
;
4082 return (XT_CB_PASSTHROUGH
);
4085 if (dest
< 0 || dest
>= n
)
4086 return (XT_CB_PASSTHROUGH
);
4087 if (t
->tab_id
== dest
) {
4088 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
4089 return (XT_CB_HANDLED
);
4092 set_current_tab(dest
);
4094 return (XT_CB_HANDLED
);
4100 command(struct tab
*t
, struct karg
*args
)
4102 char *s
= NULL
, *ss
= NULL
;
4106 if (t
== NULL
|| args
== NULL
) {
4107 show_oops(NULL
, "command invalid parameters");
4108 return (XT_CB_PASSTHROUGH
);
4119 if (cmd_prefix
== 0)
4122 ss
= g_strdup_printf(":%d", cmd_prefix
);
4133 case XT_CMD_OPEN_CURRENT
:
4136 case XT_CMD_TABNEW_CURRENT
:
4137 if (!s
) /* FALL THROUGH? */
4139 if ((uri
= get_uri(t
)) != NULL
) {
4140 ss
= g_strdup_printf("%s%s", s
, uri
);
4145 show_oops(t
, "command: invalid opcode %d", args
->i
);
4146 return (XT_CB_PASSTHROUGH
);
4149 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
4151 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
4152 gdk_color_parse(XT_COLOR_WHITE
, &color
);
4153 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
4155 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
4156 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
4161 return (XT_CB_HANDLED
);
4165 * Return a new string with a download row (in html)
4166 * appended. Old string is freed.
4169 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
4172 WebKitDownloadStatus stat
;
4173 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
4175 char cur_sz
[FMT_SCALED_STRSIZE
];
4176 char tot_sz
[FMT_SCALED_STRSIZE
];
4179 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
4181 /* All actions wil take this form:
4182 * xxxt://class/seskey
4184 xtp_prefix
= g_strdup_printf("%s%d/%s/",
4185 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
4187 stat
= webkit_download_get_status(dl
->download
);
4190 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
4191 status_html
= g_strdup_printf("Finished");
4192 cmd_html
= g_strdup_printf(
4193 "<a href='%s%d/%d'>Remove</a>",
4194 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4196 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
4197 /* gather size info */
4198 progress
= 100 * webkit_download_get_progress(dl
->download
);
4201 webkit_download_get_current_size(dl
->download
), cur_sz
);
4203 webkit_download_get_total_size(dl
->download
), tot_sz
);
4205 status_html
= g_strdup_printf(
4206 "<div style='width: 100%%' align='center'>"
4207 "<div class='progress-outer'>"
4208 "<div class='progress-inner' style='width: %.2f%%'>"
4209 "</div></div></div>"
4210 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4211 progress
, cur_sz
, tot_sz
, progress
);
4213 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4214 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
4218 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
4219 status_html
= g_strdup_printf("Cancelled");
4220 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4221 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4223 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
4224 status_html
= g_strdup_printf("Error!");
4225 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4226 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
4228 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
4229 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4230 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
4231 status_html
= g_strdup_printf("Starting");
4234 show_oops(t
, "%s: unknown download status", __func__
);
4237 new_html
= g_strdup_printf(
4238 "%s\n<tr><td>%s</td><td>%s</td>"
4239 "<td style='text-align:center'>%s</td></tr>\n",
4240 html
, basename((char *)webkit_download_get_destination_uri(dl
->download
)),
4241 status_html
, cmd_html
);
4245 g_free(status_html
);
4256 * update all download tabs apart from one. Pass NULL if
4257 * you want to update all.
4260 update_download_tabs(struct tab
*apart_from
)
4263 if (!updating_dl_tabs
) {
4264 updating_dl_tabs
= 1; /* stop infinite recursion */
4265 TAILQ_FOREACH(t
, &tabs
, entry
)
4266 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
4267 && (t
!= apart_from
))
4268 xtp_page_dl(t
, NULL
);
4269 updating_dl_tabs
= 0;
4274 * update all cookie tabs apart from one. Pass NULL if
4275 * you want to update all.
4278 update_cookie_tabs(struct tab
*apart_from
)
4281 if (!updating_cl_tabs
) {
4282 updating_cl_tabs
= 1; /* stop infinite recursion */
4283 TAILQ_FOREACH(t
, &tabs
, entry
)
4284 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
4285 && (t
!= apart_from
))
4286 xtp_page_cl(t
, NULL
);
4287 updating_cl_tabs
= 0;
4292 * update all history tabs apart from one. Pass NULL if
4293 * you want to update all.
4296 update_history_tabs(struct tab
*apart_from
)
4300 if (!updating_hl_tabs
) {
4301 updating_hl_tabs
= 1; /* stop infinite recursion */
4302 TAILQ_FOREACH(t
, &tabs
, entry
)
4303 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
4304 && (t
!= apart_from
))
4305 xtp_page_hl(t
, NULL
);
4306 updating_hl_tabs
= 0;
4310 /* cookie management XTP page */
4312 xtp_page_cl(struct tab
*t
, struct karg
*args
)
4314 char *body
, *page
, *tmp
;
4315 int i
= 1; /* all ids start 1 */
4316 GSList
*sc
, *pc
, *pc_start
;
4318 char *type
, *table_headers
, *last_domain
;
4320 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4323 show_oops(NULL
, "%s invalid parameters", __func__
);
4327 /* Generate a new session key */
4328 if (!updating_cl_tabs
)
4329 generate_xtp_session_key(&cl_session_key
);
4332 table_headers
= g_strdup_printf("<table><tr>"
4335 "<th style='width:200px'>Value</th>"
4339 "<th>HTTP<br />only</th>"
4340 "<th style='width:40px'>Rm</th></tr>\n");
4342 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
4343 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
4347 last_domain
= strdup("");
4348 for (; sc
; sc
= sc
->next
) {
4351 if (strcmp(last_domain
, c
->domain
) != 0) {
4354 last_domain
= strdup(c
->domain
);
4358 body
= g_strdup_printf("%s</table>"
4360 body
, c
->domain
, table_headers
);
4364 body
= g_strdup_printf("<h2>%s</h2>%s\n",
4365 c
->domain
, table_headers
);
4370 for (pc
= pc_start
; pc
; pc
= pc
->next
)
4371 if (soup_cookie_equal(pc
->data
, c
)) {
4372 type
= "Session + Persistent";
4377 body
= g_strdup_printf(
4380 "<td style='word-wrap:normal'>%s</td>"
4382 " <textarea rows='4'>%s</textarea>"
4388 "<td style='text-align:center'>"
4389 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4396 soup_date_to_string(c
->expires
, SOUP_DATE_COOKIE
) : "",
4411 soup_cookies_free(sc
);
4412 soup_cookies_free(pc
);
4414 /* small message if there are none */
4416 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4417 "colspan='8'>No Cookies</td></tr>\n", table_headers
);
4420 body
= g_strdup_printf("%s</table>", body
);
4423 page
= get_html_page("Cookie Jar", body
, "", TRUE
);
4425 g_free(table_headers
);
4426 g_free(last_domain
);
4428 load_webkit_string(t
, page
, XT_URI_ABOUT_COOKIEJAR
);
4429 update_cookie_tabs(t
);
4437 xtp_page_hl(struct tab
*t
, struct karg
*args
)
4439 char *body
, *page
, *tmp
;
4441 int i
= 1; /* all ids start 1 */
4443 DNPRINTF(XT_D_CMD
, "%s", __func__
);
4446 show_oops(NULL
, "%s invalid parameters", __func__
);
4450 /* Generate a new session key */
4451 if (!updating_hl_tabs
)
4452 generate_xtp_session_key(&hl_session_key
);
4455 body
= g_strdup_printf("<table style='table-layout:fixed'><tr>"
4456 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4458 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
4460 body
= g_strdup_printf(
4462 "<td><a href='%s'>%s</a></td>"
4464 "<td style='text-align: center'>"
4465 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4466 body
, h
->uri
, h
->uri
, h
->title
,
4467 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
4468 XT_XTP_HL_REMOVE
, i
);
4474 /* small message if there are none */
4477 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4478 "colspan='3'>No History</td></tr>\n", body
);
4483 body
= g_strdup_printf("%s</table>", body
);
4486 page
= get_html_page("History", body
, "", TRUE
);
4490 * update all history manager tabs as the xtp session
4491 * key has now changed. No need to update the current tab.
4492 * Already did that above.
4494 update_history_tabs(t
);
4496 load_webkit_string(t
, page
, XT_URI_ABOUT_HISTORY
);
4503 * Generate a web page detailing the status of any downloads
4506 xtp_page_dl(struct tab
*t
, struct karg
*args
)
4508 struct download
*dl
;
4509 char *body
, *page
, *tmp
;
4513 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
4516 show_oops(NULL
, "%s invalid parameters", __func__
);
4521 * Generate a new session key for next page instance.
4522 * This only happens for the top level call to xtp_page_dl()
4523 * in which case updating_dl_tabs is 0.
4525 if (!updating_dl_tabs
)
4526 generate_xtp_session_key(&dl_session_key
);
4528 /* header - with refresh so as to update */
4529 if (refresh_interval
>= 1)
4530 ref
= g_strdup_printf(
4531 "<meta http-equiv='refresh' content='%u"
4532 ";url=%s%d/%s/%d' />\n",
4541 body
= g_strdup_printf("<div align='center'>"
4542 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4543 "</p><table><tr><th style='width: 60%%'>"
4544 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4545 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
4547 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
4548 body
= xtp_page_dl_row(t
, body
, dl
);
4552 /* message if no downloads in list */
4555 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
4556 " style='text-align: center'>"
4557 "No downloads</td></tr>\n", body
);
4562 body
= g_strdup_printf("%s</table></div>", body
);
4565 page
= get_html_page("Downloads", body
, ref
, 1);
4570 * update all download manager tabs as the xtp session
4571 * key has now changed. No need to update the current tab.
4572 * Already did that above.
4574 update_download_tabs(t
);
4576 load_webkit_string(t
, page
, XT_URI_ABOUT_DOWNLOADS
);
4583 search(struct tab
*t
, struct karg
*args
)
4587 if (t
== NULL
|| args
== NULL
) {
4588 show_oops(NULL
, "search invalid parameters");
4591 if (t
->search_text
== NULL
) {
4592 if (global_search
== NULL
)
4593 return (XT_CB_PASSTHROUGH
);
4595 t
->search_text
= g_strdup(global_search
);
4596 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
4597 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
4601 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
4602 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
4605 case XT_SEARCH_NEXT
:
4606 d
= t
->search_forward
;
4608 case XT_SEARCH_PREV
:
4609 d
= !t
->search_forward
;
4612 return (XT_CB_PASSTHROUGH
);
4615 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
4617 return (XT_CB_HANDLED
);
4620 struct settings_args
{
4626 print_setting(struct settings
*s
, char *val
, void *cb_args
)
4629 struct settings_args
*sa
= cb_args
;
4634 if (s
->flags
& XT_SF_RUNTIME
)
4640 *sa
->body
= g_strdup_printf(
4642 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4643 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4655 set(struct tab
*t
, struct karg
*args
)
4657 char *body
, *page
, *tmp
;
4659 struct settings_args sa
;
4661 bzero(&sa
, sizeof sa
);
4665 body
= g_strdup_printf("<div align='center'><table><tr>"
4666 "<th align='left'>Setting</th>"
4667 "<th align='left'>Value</th></tr>\n");
4669 settings_walk(print_setting
, &sa
);
4672 /* small message if there are none */
4675 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
4676 "colspan='2'>No settings</td></tr>\n", body
);
4681 body
= g_strdup_printf("%s</table></div>", body
);
4684 page
= get_html_page("Settings", body
, "", 0);
4688 load_webkit_string(t
, page
, XT_URI_ABOUT_SET
);
4692 return (XT_CB_PASSTHROUGH
);
4696 session_save(struct tab
*t
, char *filename
)
4701 if (strlen(filename
) == 0)
4704 if (filename
[0] == '.' || filename
[0] == '/')
4708 if (save_tabs(t
, &a
))
4710 strlcpy(named_session
, filename
, sizeof named_session
);
4718 session_open(struct tab
*t
, char *filename
)
4723 if (strlen(filename
) == 0)
4726 if (filename
[0] == '.' || filename
[0] == '/')
4730 a
.i
= XT_SES_CLOSETABS
;
4731 if (open_tabs(t
, &a
))
4734 strlcpy(named_session
, filename
, sizeof named_session
);
4742 session_delete(struct tab
*t
, char *filename
)
4744 char file
[PATH_MAX
];
4747 if (strlen(filename
) == 0)
4750 if (filename
[0] == '.' || filename
[0] == '/')
4753 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, filename
);
4757 if (!strcmp(filename
, named_session
))
4758 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
4759 sizeof named_session
);
4767 session_cmd(struct tab
*t
, struct karg
*args
)
4769 char *filename
= args
->s
;
4774 if (args
->i
& XT_SHOW
)
4775 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
4776 XT_SAVED_TABS_FILE
: named_session
);
4777 else if (args
->i
& XT_SAVE
) {
4778 if (session_save(t
, filename
)) {
4779 show_oops(t
, "Can't save session: %s",
4780 filename
? filename
: "INVALID");
4783 } else if (args
->i
& XT_OPEN
) {
4784 if (session_open(t
, filename
)) {
4785 show_oops(t
, "Can't open session: %s",
4786 filename
? filename
: "INVALID");
4789 } else if (args
->i
& XT_DELETE
) {
4790 if (session_delete(t
, filename
)) {
4791 show_oops(t
, "Can't delete session: %s",
4792 filename
? filename
: "INVALID");
4797 return (XT_CB_PASSTHROUGH
);
4801 * Make a hardcopy of the page
4804 print_page(struct tab
*t
, struct karg
*args
)
4806 WebKitWebFrame
*frame
;
4808 GtkPrintOperation
*op
;
4809 GtkPrintOperationAction action
;
4810 GtkPrintOperationResult print_res
;
4811 GError
*g_err
= NULL
;
4812 int marg_l
, marg_r
, marg_t
, marg_b
;
4814 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
4816 ps
= gtk_page_setup_new();
4817 op
= gtk_print_operation_new();
4818 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
4819 frame
= webkit_web_view_get_main_frame(t
->wv
);
4821 /* the default margins are too small, so we will bump them */
4822 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
4823 XT_PRINT_EXTRA_MARGIN
;
4824 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
4825 XT_PRINT_EXTRA_MARGIN
;
4826 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
4827 XT_PRINT_EXTRA_MARGIN
;
4828 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
4829 XT_PRINT_EXTRA_MARGIN
;
4832 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
4833 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
4834 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
4835 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
4837 gtk_print_operation_set_default_page_setup(op
, ps
);
4839 /* this appears to free 'op' and 'ps' */
4840 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
4842 /* check it worked */
4843 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
4844 show_oops(NULL
, "can't print: %s", g_err
->message
);
4845 g_error_free (g_err
);
4853 go_home(struct tab
*t
, struct karg
*args
)
4860 restart(struct tab
*t
, struct karg
*args
)
4864 a
.s
= XT_RESTART_TABS_FILE
;
4866 execvp(start_argv
[0], start_argv
);
4872 #define CTRL GDK_CONTROL_MASK
4873 #define MOD1 GDK_MOD1_MASK
4874 #define SHFT GDK_SHIFT_MASK
4876 /* inherent to GTK not all keys will be caught at all times */
4877 /* XXX sort key bindings */
4878 struct key_binding
{
4883 TAILQ_ENTRY(key_binding
) entry
; /* in bss so no need to init */
4885 { "cookiejar", MOD1
, 0, GDK_j
},
4886 { "downloadmgr", MOD1
, 0, GDK_d
},
4887 { "history", MOD1
, 0, GDK_h
},
4888 { "print", CTRL
, 0, GDK_p
},
4889 { "search", 0, 0, GDK_slash
},
4890 { "searchb", 0, 0, GDK_question
},
4891 { "statustoggle", CTRL
, 0, GDK_n
},
4892 { "command", 0, 0, GDK_colon
},
4893 { "qa", CTRL
, 0, GDK_q
},
4894 { "restart", MOD1
, 0, GDK_q
},
4895 { "js toggle", CTRL
, 0, GDK_j
},
4896 { "cookie toggle", MOD1
, 0, GDK_c
},
4897 { "togglesrc", CTRL
, 0, GDK_s
},
4898 { "yankuri", 0, 0, GDK_y
},
4899 { "pasteuricur", 0, 0, GDK_p
},
4900 { "pasteurinew", 0, 0, GDK_P
},
4901 { "toplevel toggle", 0, 0, GDK_F4
},
4902 { "help", 0, 0, GDK_F1
},
4903 { "run_script", MOD1
, 0, GDK_r
},
4906 { "searchnext", 0, 0, GDK_n
},
4907 { "searchprevious", 0, 0, GDK_N
},
4910 { "focusaddress", 0, 0, GDK_F6
},
4911 { "focussearch", 0, 0, GDK_F7
},
4914 { "hinting", 0, 0, GDK_f
},
4916 /* custom stylesheet */
4917 { "userstyle", 0, 0, GDK_i
},
4920 { "goback", 0, 0, GDK_BackSpace
},
4921 { "goback", MOD1
, 0, GDK_Left
},
4922 { "goforward", SHFT
, 0, GDK_BackSpace
},
4923 { "goforward", MOD1
, 0, GDK_Right
},
4924 { "reload", 0, 0, GDK_F5
},
4925 { "reload", CTRL
, 0, GDK_r
},
4926 { "reloadforce", CTRL
, 0, GDK_R
},
4927 { "reload", CTRL
, 0, GDK_l
},
4928 { "favorites", MOD1
, 1, GDK_f
},
4930 /* vertical movement */
4931 { "scrolldown", 0, 0, GDK_j
},
4932 { "scrolldown", 0, 0, GDK_Down
},
4933 { "scrollup", 0, 0, GDK_Up
},
4934 { "scrollup", 0, 0, GDK_k
},
4935 { "scrollbottom", 0, 0, GDK_G
},
4936 { "scrollbottom", 0, 0, GDK_End
},
4937 { "scrolltop", 0, 0, GDK_Home
},
4938 { "scrollpagedown", 0, 0, GDK_space
},
4939 { "scrollpagedown", CTRL
, 0, GDK_f
},
4940 { "scrollhalfdown", CTRL
, 0, GDK_d
},
4941 { "scrollpagedown", 0, 0, GDK_Page_Down
},
4942 { "scrollpageup", 0, 0, GDK_Page_Up
},
4943 { "scrollpageup", CTRL
, 0, GDK_b
},
4944 { "scrollhalfup", CTRL
, 0, GDK_u
},
4945 /* horizontal movement */
4946 { "scrollright", 0, 0, GDK_l
},
4947 { "scrollright", 0, 0, GDK_Right
},
4948 { "scrollleft", 0, 0, GDK_Left
},
4949 { "scrollleft", 0, 0, GDK_h
},
4950 { "scrollfarright", 0, 0, GDK_dollar
},
4951 { "scrollfarleft", 0, 0, GDK_0
},
4954 { "tabnew", CTRL
, 0, GDK_t
},
4955 { "999tabnew", CTRL
, 0, GDK_T
},
4956 { "tabclose", CTRL
, 1, GDK_w
},
4957 { "tabundoclose", 0, 0, GDK_U
},
4958 { "tabnext 1", CTRL
, 0, GDK_1
},
4959 { "tabnext 2", CTRL
, 0, GDK_2
},
4960 { "tabnext 3", CTRL
, 0, GDK_3
},
4961 { "tabnext 4", CTRL
, 0, GDK_4
},
4962 { "tabnext 5", CTRL
, 0, GDK_5
},
4963 { "tabnext 6", CTRL
, 0, GDK_6
},
4964 { "tabnext 7", CTRL
, 0, GDK_7
},
4965 { "tabnext 8", CTRL
, 0, GDK_8
},
4966 { "tabnext 9", CTRL
, 0, GDK_9
},
4967 { "tabfirst", CTRL
, 0, GDK_less
},
4968 { "tablast", CTRL
, 0, GDK_greater
},
4969 { "tabprevious", CTRL
, 0, GDK_Left
},
4970 { "tabnext", CTRL
, 0, GDK_Right
},
4971 { "focusout", CTRL
, 0, GDK_minus
},
4972 { "focusin", CTRL
, 0, GDK_plus
},
4973 { "focusin", CTRL
, 0, GDK_equal
},
4974 { "focusreset", CTRL
, 0, GDK_0
},
4976 /* command aliases (handy when -S flag is used) */
4977 { "promptopen", 0, 0, GDK_F9
},
4978 { "promptopencurrent", 0, 0, GDK_F10
},
4979 { "prompttabnew", 0, 0, GDK_F11
},
4980 { "prompttabnewcurrent",0, 0, GDK_F12
},
4982 TAILQ_HEAD(keybinding_list
, key_binding
);
4985 walk_kb(struct settings
*s
,
4986 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
4988 struct key_binding
*k
;
4991 if (s
== NULL
|| cb
== NULL
) {
4992 show_oops(NULL
, "walk_kb invalid parameters");
4996 TAILQ_FOREACH(k
, &kbl
, entry
) {
5002 if (gdk_keyval_name(k
->key
) == NULL
)
5005 strlcat(str
, k
->cmd
, sizeof str
);
5006 strlcat(str
, ",", sizeof str
);
5008 if (k
->mask
& GDK_SHIFT_MASK
)
5009 strlcat(str
, "S-", sizeof str
);
5010 if (k
->mask
& GDK_CONTROL_MASK
)
5011 strlcat(str
, "C-", sizeof str
);
5012 if (k
->mask
& GDK_MOD1_MASK
)
5013 strlcat(str
, "M1-", sizeof str
);
5014 if (k
->mask
& GDK_MOD2_MASK
)
5015 strlcat(str
, "M2-", sizeof str
);
5016 if (k
->mask
& GDK_MOD3_MASK
)
5017 strlcat(str
, "M3-", sizeof str
);
5018 if (k
->mask
& GDK_MOD4_MASK
)
5019 strlcat(str
, "M4-", sizeof str
);
5020 if (k
->mask
& GDK_MOD5_MASK
)
5021 strlcat(str
, "M5-", sizeof str
);
5023 strlcat(str
, gdk_keyval_name(k
->key
), sizeof str
);
5024 cb(s
, str
, cb_args
);
5029 init_keybindings(void)
5032 struct key_binding
*k
;
5034 for (i
= 0; i
< LENGTH(keys
); i
++) {
5035 k
= g_malloc0(sizeof *k
);
5036 k
->cmd
= keys
[i
].cmd
;
5037 k
->mask
= keys
[i
].mask
;
5038 k
->use_in_entry
= keys
[i
].use_in_entry
;
5039 k
->key
= keys
[i
].key
;
5040 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
5042 DNPRINTF(XT_D_KEYBINDING
, "init_keybindings: added: %s\n",
5043 k
->cmd
? k
->cmd
: "unnamed key");
5048 keybinding_clearall(void)
5050 struct key_binding
*k
, *next
;
5052 for (k
= TAILQ_FIRST(&kbl
); k
; k
= next
) {
5053 next
= TAILQ_NEXT(k
, entry
);
5057 DNPRINTF(XT_D_KEYBINDING
, "keybinding_clearall: %s\n",
5058 k
->cmd
? k
->cmd
: "unnamed key");
5059 TAILQ_REMOVE(&kbl
, k
, entry
);
5065 keybinding_add(char *cmd
, char *key
, int use_in_entry
)
5067 struct key_binding
*k
;
5068 guint keyval
, mask
= 0;
5071 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s %s\n", cmd
, key
);
5073 /* Keys which are to be used in entry have been prefixed with an
5074 * exclamation mark. */
5078 /* find modifier keys */
5079 if (strstr(key
, "S-"))
5080 mask
|= GDK_SHIFT_MASK
;
5081 if (strstr(key
, "C-"))
5082 mask
|= GDK_CONTROL_MASK
;
5083 if (strstr(key
, "M1-"))
5084 mask
|= GDK_MOD1_MASK
;
5085 if (strstr(key
, "M2-"))
5086 mask
|= GDK_MOD2_MASK
;
5087 if (strstr(key
, "M3-"))
5088 mask
|= GDK_MOD3_MASK
;
5089 if (strstr(key
, "M4-"))
5090 mask
|= GDK_MOD4_MASK
;
5091 if (strstr(key
, "M5-"))
5092 mask
|= GDK_MOD5_MASK
;
5095 for (i
= strlen(key
) - 1; i
> 0; i
--)
5099 /* validate keyname */
5100 keyval
= gdk_keyval_from_name(key
);
5101 if (keyval
== GDK_VoidSymbol
) {
5102 warnx("invalid keybinding name %s", key
);
5105 /* must run this test too, gtk+ doesn't handle 10 for example */
5106 if (gdk_keyval_name(keyval
) == NULL
) {
5107 warnx("invalid keybinding name %s", key
);
5111 /* Remove eventual dupes. */
5112 TAILQ_FOREACH(k
, &kbl
, entry
)
5113 if (k
->key
== keyval
&& k
->mask
== mask
) {
5114 TAILQ_REMOVE(&kbl
, k
, entry
);
5120 k
= g_malloc0(sizeof *k
);
5121 k
->cmd
= g_strdup(cmd
);
5123 k
->use_in_entry
= use_in_entry
;
5126 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: %s 0x%x %d 0x%x\n",
5131 DNPRINTF(XT_D_KEYBINDING
, "keybinding_add: adding: %s %s\n",
5132 k
->cmd
, gdk_keyval_name(keyval
));
5134 TAILQ_INSERT_HEAD(&kbl
, k
, entry
);
5140 add_kb(struct settings
*s
, char *entry
)
5144 DNPRINTF(XT_D_KEYBINDING
, "add_kb: %s\n", entry
);
5146 /* clearall is special */
5147 if (!strcmp(entry
, "clearall")) {
5148 keybinding_clearall();
5152 kb
= strstr(entry
, ",");
5158 return (keybinding_add(entry
, key
, key
[0] == '!'));
5164 int (*func
)(struct tab
*, struct karg
*);
5168 { "command", 0, command
, ':', 0 },
5169 { "search", 0, command
, '/', 0 },
5170 { "searchb", 0, command
, '?', 0 },
5171 { "togglesrc", 0, toggle_src
, 0, 0 },
5173 /* yanking and pasting */
5174 { "yankuri", 0, yank_uri
, 0, 0 },
5175 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5176 { "pasteuricur", 0, paste_uri
, XT_PASTE_CURRENT_TAB
, 0 },
5177 { "pasteurinew", 0, paste_uri
, XT_PASTE_NEW_TAB
, 0 },
5180 { "searchnext", 0, search
, XT_SEARCH_NEXT
, 0 },
5181 { "searchprevious", 0, search
, XT_SEARCH_PREV
, 0 },
5184 { "focusaddress", 0, focus
, XT_FOCUS_URI
, 0 },
5185 { "focussearch", 0, focus
, XT_FOCUS_SEARCH
, 0 },
5188 { "hinting", 0, hint
, 0, 0 },
5190 /* custom stylesheet */
5191 { "userstyle", 0, userstyle
, 0, 0 },
5194 { "goback", 0, navaction
, XT_NAV_BACK
, 0 },
5195 { "goforward", 0, navaction
, XT_NAV_FORWARD
, 0 },
5196 { "reload", 0, navaction
, XT_NAV_RELOAD
, 0 },
5197 { "reloadforce", 0, navaction
, XT_NAV_RELOAD_CACHE
, 0 },
5199 /* vertical movement */
5200 { "scrolldown", 0, move
, XT_MOVE_DOWN
, 0 },
5201 { "scrollup", 0, move
, XT_MOVE_UP
, 0 },
5202 { "scrollbottom", 0, move
, XT_MOVE_BOTTOM
, 0 },
5203 { "scrolltop", 0, move
, XT_MOVE_TOP
, 0 },
5204 { "1", 0, move
, XT_MOVE_TOP
, 0 },
5205 { "scrollhalfdown", 0, move
, XT_MOVE_HALFDOWN
, 0 },
5206 { "scrollhalfup", 0, move
, XT_MOVE_HALFUP
, 0 },
5207 { "scrollpagedown", 0, move
, XT_MOVE_PAGEDOWN
, 0 },
5208 { "scrollpageup", 0, move
, XT_MOVE_PAGEUP
, 0 },
5209 /* horizontal movement */
5210 { "scrollright", 0, move
, XT_MOVE_RIGHT
, 0 },
5211 { "scrollleft", 0, move
, XT_MOVE_LEFT
, 0 },
5212 { "scrollfarright", 0, move
, XT_MOVE_FARRIGHT
, 0 },
5213 { "scrollfarleft", 0, move
, XT_MOVE_FARLEFT
, 0 },
5215 { "favorites", 0, xtp_page_fl
, 0, 0 },
5216 { "fav", 0, xtp_page_fl
, 0, 0 },
5217 { "favadd", 0, add_favorite
, 0, 0 },
5219 { "qall", 0, quit
, 0, 0 },
5220 { "quitall", 0, quit
, 0, 0 },
5221 { "w", 0, save_tabs
, 0, 0 },
5222 { "wq", 0, save_tabs_and_quit
, 0, 0 },
5223 { "help", 0, help
, 0, 0 },
5224 { "about", 0, about
, 0, 0 },
5225 { "stats", 0, stats
, 0, 0 },
5226 { "version", 0, about
, 0, 0 },
5229 { "js", 0, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5230 { "save", 1, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5231 { "domain", 2, js_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
5232 { "fqdn", 2, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5233 { "show", 1, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5234 { "all", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5235 { "persistent", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
5236 { "session", 2, js_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
5237 { "toggle", 1, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5238 { "domain", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5239 { "fqdn", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5241 /* cookie command */
5242 { "cookie", 0, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5243 { "save", 1, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5244 { "domain", 2, cookie_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
5245 { "fqdn", 2, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
5246 { "show", 1, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5247 { "all", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
5248 { "persistent", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
5249 { "session", 2, cookie_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
5250 { "toggle", 1, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5251 { "domain", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
5252 { "fqdn", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
5254 /* toplevel (domain) command */
5255 { "toplevel", 0, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
5256 { "toggle", 1, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
5259 { "cookiejar", 0, xtp_page_cl
, 0, 0 },
5262 { "cert", 0, cert_cmd
, XT_SHOW
, 0 },
5263 { "save", 1, cert_cmd
, XT_SAVE
, 0 },
5264 { "show", 1, cert_cmd
, XT_SHOW
, 0 },
5266 { "ca", 0, ca_cmd
, 0, 0 },
5267 { "downloadmgr", 0, xtp_page_dl
, 0, 0 },
5268 { "dl", 0, xtp_page_dl
, 0, 0 },
5269 { "h", 0, xtp_page_hl
, 0, 0 },
5270 { "history", 0, xtp_page_hl
, 0, 0 },
5271 { "home", 0, go_home
, 0, 0 },
5272 { "restart", 0, restart
, 0, 0 },
5273 { "urlhide", 0, urlaction
, XT_URL_HIDE
, 0 },
5274 { "urlshow", 0, urlaction
, XT_URL_SHOW
, 0 },
5275 { "statustoggle", 0, statustoggle
, 0, 0 },
5276 { "run_script", 0, run_page_script
, 0, XT_USERARG
},
5278 { "print", 0, print_page
, 0, 0 },
5281 { "focusin", 0, resizetab
, XT_ZOOM_IN
, 0 },
5282 { "focusout", 0, resizetab
, XT_ZOOM_OUT
, 0 },
5283 { "focusreset", 0, resizetab
, XT_ZOOM_NORMAL
, 0 },
5284 { "q", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5285 { "quit", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
5286 { "open", 0, tabaction
, XT_TAB_OPEN
, XT_URLARG
},
5287 { "tabclose", 0, tabaction
, XT_TAB_DELETE
, XT_PREFIX
| XT_INTARG
},
5288 { "tabedit", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5289 { "tabfirst", 0, movetab
, XT_TAB_FIRST
, 0 },
5290 { "tabhide", 0, tabaction
, XT_TAB_HIDE
, 0 },
5291 { "tablast", 0, movetab
, XT_TAB_LAST
, 0 },
5292 { "tabnew", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
5293 { "tabnext", 0, movetab
, XT_TAB_NEXT
, XT_PREFIX
| XT_INTARG
},
5294 { "tabnextstyle", 0, tabaction
, XT_TAB_NEXTSTYLE
, 0 },
5295 { "tabprevious", 0, movetab
, XT_TAB_PREV
, XT_PREFIX
| XT_INTARG
},
5296 { "tabrewind", 0, movetab
, XT_TAB_FIRST
, 0 },
5297 { "tabshow", 0, tabaction
, XT_TAB_SHOW
, 0 },
5298 { "tabundoclose", 0, tabaction
, XT_TAB_UNDO_CLOSE
, 0 },
5299 { "buffers", 0, buffers
, 0, 0 },
5300 { "ls", 0, buffers
, 0, 0 },
5301 { "tabs", 0, buffers
, 0, 0 },
5303 /* command aliases (handy when -S flag is used) */
5304 { "promptopen", 0, command
, XT_CMD_OPEN
, 0 },
5305 { "promptopencurrent", 0, command
, XT_CMD_OPEN_CURRENT
, 0 },
5306 { "prompttabnew", 0, command
, XT_CMD_TABNEW
, 0 },
5307 { "prompttabnewcurrent",0, command
, XT_CMD_TABNEW_CURRENT
, 0 },
5310 { "set", 0, set
, 0, 0 },
5311 { "fullscreen", 0, fullscreen
, 0, 0 },
5312 { "f", 0, fullscreen
, 0, 0 },
5315 { "session", 0, session_cmd
, XT_SHOW
, 0 },
5316 { "delete", 1, session_cmd
, XT_DELETE
, XT_USERARG
},
5317 { "open", 1, session_cmd
, XT_OPEN
, XT_USERARG
},
5318 { "save", 1, session_cmd
, XT_SAVE
, XT_USERARG
},
5319 { "show", 1, session_cmd
, XT_SHOW
, 0 },
5326 } cmd_status
= {-1, 0};
5329 wv_release_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5332 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 1)
5339 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5346 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5348 else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
5354 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
5356 a
.i
= XT_NAV_FORWARD
;
5366 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
5368 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
5370 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
5377 * cancel, remove, etc. downloads
5380 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
5382 struct download find
, *d
= NULL
;
5384 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
5386 /* some commands require a valid download id */
5387 if (cmd
!= XT_XTP_DL_LIST
) {
5388 /* lookup download in question */
5390 d
= RB_FIND(download_list
, &downloads
, &find
);
5393 show_oops(t
, "%s: no such download", __func__
);
5398 /* decide what to do */
5400 case XT_XTP_DL_CANCEL
:
5401 webkit_download_cancel(d
->download
);
5403 case XT_XTP_DL_REMOVE
:
5404 webkit_download_cancel(d
->download
); /* just incase */
5405 g_object_unref(d
->download
);
5406 RB_REMOVE(download_list
, &downloads
, d
);
5408 case XT_XTP_DL_LIST
:
5412 show_oops(t
, "%s: unknown command", __func__
);
5415 xtp_page_dl(t
, NULL
);
5419 * Actions on history, only does one thing for now, but
5420 * we provide the function for future actions
5423 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
5425 struct history
*h
, *next
;
5429 case XT_XTP_HL_REMOVE
:
5430 /* walk backwards, as listed in reverse */
5431 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
5432 next
= RB_PREV(history_list
, &hl
, h
);
5434 RB_REMOVE(history_list
, &hl
, h
);
5435 g_free((gpointer
) h
->title
);
5436 g_free((gpointer
) h
->uri
);
5443 case XT_XTP_HL_LIST
:
5444 /* Nothing - just xtp_page_hl() below */
5447 show_oops(t
, "%s: unknown command", __func__
);
5451 xtp_page_hl(t
, NULL
);
5454 /* remove a favorite */
5456 remove_favorite(struct tab
*t
, int index
)
5458 char file
[PATH_MAX
], *title
, *uri
= NULL
;
5459 char *new_favs
, *tmp
;
5464 /* open favorites */
5465 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
5467 if ((f
= fopen(file
, "r")) == NULL
) {
5468 show_oops(t
, "%s: can't open favorites: %s",
5469 __func__
, strerror(errno
));
5473 /* build a string which will become the new favroites file */
5474 new_favs
= g_strdup("");
5477 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
5478 if (feof(f
) || ferror(f
))
5480 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5487 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
5488 if (feof(f
) || ferror(f
)) {
5489 show_oops(t
, "%s: can't parse favorites %s",
5490 __func__
, strerror(errno
));
5495 /* as long as this isn't the one we are deleting add to file */
5498 new_favs
= g_strdup_printf("%s%s\n%s\n",
5499 new_favs
, title
, uri
);
5511 /* write back new favorites file */
5512 if ((f
= fopen(file
, "w")) == NULL
) {
5513 show_oops(t
, "%s: can't open favorites: %s",
5514 __func__
, strerror(errno
));
5518 fwrite(new_favs
, strlen(new_favs
), 1, f
);
5531 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
5534 case XT_XTP_FL_LIST
:
5535 /* nothing, just the below call to xtp_page_fl() */
5537 case XT_XTP_FL_REMOVE
:
5538 remove_favorite(t
, arg
);
5541 show_oops(t
, "%s: invalid favorites command", __func__
);
5545 xtp_page_fl(t
, NULL
);
5549 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
5552 case XT_XTP_CL_LIST
:
5553 /* nothing, just xtp_page_cl() */
5555 case XT_XTP_CL_REMOVE
:
5559 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
5563 xtp_page_cl(t
, NULL
);
5566 /* link an XTP class to it's session key and handler function */
5567 struct xtp_despatch
{
5570 void (*handle_func
)(struct tab
*, uint8_t, int);
5573 struct xtp_despatch xtp_despatches
[] = {
5574 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
5575 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
5576 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
5577 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
5578 { XT_XTP_INVALID
, NULL
, NULL
}
5582 * is the url xtp protocol? (xxxt://)
5583 * if so, parse and despatch correct bahvior
5586 parse_xtp_url(struct tab
*t
, const char *url
)
5588 char *dup
= NULL
, *p
, *last
;
5589 uint8_t n_tokens
= 0;
5590 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
5591 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
5596 * tokens array meaning:
5598 * tokens[1] = session key
5599 * tokens[2] = action
5600 * tokens[3] = optional argument
5603 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
5605 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
5608 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
5610 /* split out the url */
5611 for ((p
= strtok_r(dup
, "/", &last
)); p
;
5612 (p
= strtok_r(NULL
, "/", &last
))) {
5614 tokens
[n_tokens
++] = p
;
5617 /* should be atleast three fields 'class/seskey/command/arg' */
5621 dsp
= xtp_despatches
;
5622 req_class
= atoi(tokens
[0]);
5623 while (dsp
->xtp_class
) {
5624 if (dsp
->xtp_class
== req_class
) {
5631 /* did we find one atall? */
5632 if (dsp_match
== NULL
) {
5633 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
5637 /* check session key and call despatch function */
5638 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
5639 ret
= TRUE
; /* all is well, this was a valid xtp request */
5640 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
5653 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5655 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
5657 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
5660 show_oops(NULL
, "activate_uri_entry_cb invalid parameters");
5665 show_oops(t
, "activate_uri_entry_cb no uri");
5669 uri
+= strspn(uri
, "\t ");
5671 /* if xxxt:// treat specially */
5672 if (parse_xtp_url(t
, uri
))
5675 /* otherwise continue to load page normally */
5676 load_uri(t
, (gchar
*)uri
);
5681 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
5683 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
5684 char *newuri
= NULL
;
5687 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
5690 show_oops(NULL
, "activate_search_entry_cb invalid parameters");
5694 if (search_string
== NULL
) {
5695 show_oops(t
, "no search_string");
5699 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5701 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
5702 newuri
= g_strdup_printf(search_string
, enc_search
);
5705 webkit_web_view_load_uri(t
->wv
, newuri
);
5713 check_and_set_js(const gchar
*uri
, struct tab
*t
)
5715 struct domain
*d
= NULL
;
5718 if (uri
== NULL
|| t
== NULL
)
5721 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5726 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
5727 es
? "enable" : "disable", uri
);
5729 g_object_set(G_OBJECT(t
->settings
),
5730 "enable-scripts", es
, (char *)NULL
);
5731 g_object_set(G_OBJECT(t
->settings
),
5732 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
5733 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5735 button_set_stockid(t
->js_toggle
,
5736 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
5740 show_ca_status(struct tab
*t
, const char *uri
)
5742 WebKitWebFrame
*frame
;
5743 WebKitWebDataSource
*source
;
5744 WebKitNetworkRequest
*request
;
5745 SoupMessage
*message
;
5747 gchar
*col_str
= XT_COLOR_WHITE
;
5750 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
5751 ssl_strict_certs
, ssl_ca_file
, uri
);
5755 if (ssl_ca_file
== NULL
) {
5756 if (g_str_has_prefix(uri
, "http://"))
5758 if (g_str_has_prefix(uri
, "https://")) {
5759 col_str
= XT_COLOR_RED
;
5764 if (g_str_has_prefix(uri
, "http://") ||
5765 !g_str_has_prefix(uri
, "https://"))
5768 frame
= webkit_web_view_get_main_frame(t
->wv
);
5769 source
= webkit_web_frame_get_data_source(frame
);
5770 request
= webkit_web_data_source_get_request(source
);
5771 message
= webkit_network_request_get_message(request
);
5773 if (message
&& (soup_message_get_flags(message
) &
5774 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
5775 col_str
= XT_COLOR_GREEN
;
5778 r
= load_compare_cert(t
, NULL
);
5780 col_str
= XT_COLOR_BLUE
;
5782 col_str
= XT_COLOR_YELLOW
;
5784 col_str
= XT_COLOR_RED
;
5789 gdk_color_parse(col_str
, &color
);
5790 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
5792 if (!strcmp(col_str
, XT_COLOR_WHITE
))
5793 statusbar_modify_attr(t
, col_str
, XT_COLOR_BLACK
);
5795 statusbar_modify_attr(t
, XT_COLOR_BLACK
, col_str
);
5800 free_favicon(struct tab
*t
)
5802 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p\n",
5803 __func__
, t
->icon_download
, t
->icon_request
);
5805 if (t
->icon_request
)
5806 g_object_unref(t
->icon_request
);
5807 if (t
->icon_dest_uri
)
5808 g_free(t
->icon_dest_uri
);
5810 t
->icon_request
= NULL
;
5811 t
->icon_dest_uri
= NULL
;
5815 xt_icon_from_name(struct tab
*t
, gchar
*name
)
5817 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5818 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5820 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
5821 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5823 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
5824 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5828 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pb
)
5830 GdkPixbuf
*pb_scaled
;
5832 if (gdk_pixbuf_get_width(pb
) > 16 || gdk_pixbuf_get_height(pb
) > 16)
5833 pb_scaled
= gdk_pixbuf_scale_simple(pb
, 16, 16,
5834 GDK_INTERP_BILINEAR
);
5838 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
5839 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
5841 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.statusbar
),
5842 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
5844 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.statusbar
),
5845 GTK_ENTRY_ICON_PRIMARY
, NULL
);
5847 if (pb_scaled
!= pb
)
5848 g_object_unref(pb_scaled
);
5852 xt_icon_from_file(struct tab
*t
, char *file
)
5856 if (g_str_has_prefix(file
, "file://"))
5857 file
+= strlen("file://");
5859 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
5861 xt_icon_from_pixbuf(t
, pb
);
5864 xt_icon_from_name(t
, "text-html");
5868 is_valid_icon(char *file
)
5871 const char *mime_type
;
5875 gf
= g_file_new_for_path(file
);
5876 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5878 mime_type
= g_file_info_get_content_type(fi
);
5879 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
5880 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
5881 g_strcmp0(mime_type
, "image/png") == 0 ||
5882 g_strcmp0(mime_type
, "image/gif") == 0 ||
5883 g_strcmp0(mime_type
, "application/octet-stream") == 0;
5891 set_favicon_from_file(struct tab
*t
, char *file
)
5895 if (t
== NULL
|| file
== NULL
)
5898 if (g_str_has_prefix(file
, "file://"))
5899 file
+= strlen("file://");
5900 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
5902 if (!stat(file
, &sb
)) {
5903 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
5904 /* corrupt icon so trash it */
5905 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
5908 /* no need to set icon to default here */
5912 xt_icon_from_file(t
, file
);
5916 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5919 WebKitDownloadStatus status
= webkit_download_get_status(download
);
5920 struct tab
*tt
= NULL
, *t
= NULL
;
5923 * find the webview instead of passing in the tab as it could have been
5924 * deleted from underneath us.
5926 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5935 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
5936 __func__
, t
->tab_id
, status
);
5939 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
5941 t
->icon_download
= NULL
;
5944 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
5947 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
5950 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
5952 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
5953 __func__
, t
->tab_id
);
5954 t
->icon_download
= NULL
;
5957 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
5960 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
5961 __func__
, t
->icon_dest_uri
);
5962 set_favicon_from_file(t
, t
->icon_dest_uri
);
5963 /* these will be freed post callback */
5964 t
->icon_request
= NULL
;
5965 t
->icon_download
= NULL
;
5973 abort_favicon_download(struct tab
*t
)
5975 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
5977 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
5978 if (t
->icon_download
) {
5979 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
5980 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
5981 webkit_download_cancel(t
->icon_download
);
5982 t
->icon_download
= NULL
;
5987 xt_icon_from_name(t
, "text-html");
5991 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
5993 DNPRINTF(XT_D_DOWNLOAD
, "%s %s\n", __func__
, uri
);
5995 if (uri
== NULL
|| t
== NULL
)
5998 #if WEBKIT_CHECK_VERSION(1, 4, 0)
5999 /* take icon from WebKitIconDatabase */
6002 pb
= webkit_web_view_get_icon_pixbuf(wv
);
6004 xt_icon_from_pixbuf(t
, pb
);
6007 xt_icon_from_name(t
, "text-html");
6008 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6009 /* download icon to cache dir */
6010 gchar
*name_hash
, file
[PATH_MAX
];
6013 if (t
->icon_request
) {
6014 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
6018 /* check to see if we got the icon in cache */
6019 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
6020 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
6023 if (!stat(file
, &sb
)) {
6024 if (sb
.st_size
> 0) {
6025 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
6027 set_favicon_from_file(t
, file
);
6031 /* corrupt icon so trash it */
6032 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
6037 /* create download for icon */
6038 t
->icon_request
= webkit_network_request_new(uri
);
6039 if (t
->icon_request
== NULL
) {
6040 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
6045 t
->icon_download
= webkit_download_new(t
->icon_request
);
6046 if (t
->icon_download
== NULL
)
6049 /* we have to free icon_dest_uri later */
6050 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
6051 webkit_download_set_destination_uri(t
->icon_download
,
6054 if (webkit_download_get_status(t
->icon_download
) ==
6055 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6056 g_object_unref(t
->icon_request
);
6057 g_free(t
->icon_dest_uri
);
6058 t
->icon_request
= NULL
;
6059 t
->icon_dest_uri
= NULL
;
6063 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
6064 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
6066 webkit_download_start(t
->icon_download
);
6071 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
6073 const gchar
*uri
= NULL
, *title
= NULL
;
6074 struct history
*h
, find
;
6077 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
6078 webkit_web_view_get_load_status(wview
), get_uri(t
) ? get_uri(t
) : "NOTHING");
6081 show_oops(NULL
, "notify_load_status_cb invalid parameters");
6085 switch (webkit_web_view_get_load_status(wview
)) {
6086 case WEBKIT_LOAD_PROVISIONAL
:
6088 abort_favicon_download(t
);
6089 #if GTK_CHECK_VERSION(2, 20, 0)
6090 gtk_widget_show(t
->spinner
);
6091 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
6093 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
6095 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
6097 /* take focus if we are visible */
6103 case WEBKIT_LOAD_COMMITTED
:
6108 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
6114 set_status(t
, (char *)uri
, XT_STATUS_LOADING
);
6116 /* check if js white listing is enabled */
6117 if (enable_js_whitelist
) {
6118 check_and_set_js(uri
, t
);
6124 show_ca_status(t
, uri
);
6126 /* we know enough to autosave the session */
6127 if (session_autosave
) {
6133 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
6137 case WEBKIT_LOAD_FINISHED
:
6143 if (!strncmp(uri
, "http://", strlen("http://")) ||
6144 !strncmp(uri
, "https://", strlen("https://")) ||
6145 !strncmp(uri
, "file://", strlen("file://"))) {
6147 h
= RB_FIND(history_list
, &hl
, &find
);
6149 title
= get_title(t
, FALSE
);
6150 h
= g_malloc(sizeof *h
);
6151 h
->uri
= g_strdup(uri
);
6152 h
->title
= g_strdup(title
);
6153 RB_INSERT(history_list
, &hl
, h
);
6154 completion_add_uri(h
->uri
);
6155 update_history_tabs(NULL
);
6159 set_status(t
, (char *)uri
, XT_STATUS_URI
);
6160 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6161 case WEBKIT_LOAD_FAILED
:
6164 #if GTK_CHECK_VERSION(2, 20, 0)
6165 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
6166 gtk_widget_hide(t
->spinner
);
6169 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
6174 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
6176 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
6177 webkit_web_view_can_go_back(wview
));
6179 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
6180 webkit_web_view_can_go_forward(wview
));
6184 notify_load_error_cb(WebKitWebView
* wview
, WebKitWebFrame
*web_frame
, gchar
*uri
, gpointer web_error
,struct tab
*t
)
6188 t
->tmp_uri
= g_strdup(uri
);
6189 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
6190 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
6191 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
6192 set_status(t
, uri
, XT_STATUS_NOTHING
);
6198 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
6200 const gchar
*title
= NULL
, *win_title
= NULL
;
6202 title
= get_title(t
, FALSE
);
6203 win_title
= get_title(t
, TRUE
);
6204 gtk_label_set_text(GTK_LABEL(t
->label
), title
);
6205 gtk_label_set_text(GTK_LABEL(t
->tab_elems
.label
), title
);
6206 if (t
->tab_id
== gtk_notebook_get_current_page(notebook
))
6207 gtk_window_set_title(GTK_WINDOW(main_window
), win_title
);
6211 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6213 run_script(t
, JS_HINTING
);
6217 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
6219 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
6220 progress
== 100 ? 0 : (double)progress
/ 100);
6221 if (show_url
== 0) {
6222 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.statusbar
),
6223 progress
== 100 ? 0 : (double)progress
/ 100);
6226 update_statusbar_position(NULL
, NULL
);
6230 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
6231 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
6232 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
6235 WebKitWebNavigationReason reason
;
6236 struct domain
*d
= NULL
;
6239 show_oops(NULL
, "webview_npd_cb invalid parameters");
6243 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
6245 webkit_network_request_get_uri(request
));
6247 uri
= (char *)webkit_network_request_get_uri(request
);
6249 /* if this is an xtp url, we don't load anything else */
6250 if (parse_xtp_url(t
, uri
))
6253 if (t
->ctrl_click
) {
6255 create_new_tab(uri
, NULL
, ctrl_click_focus
, -1);
6256 webkit_web_policy_decision_ignore(pd
);
6257 return (TRUE
); /* we made the decission */
6261 * This is a little hairy but it comes down to this:
6262 * when we run in whitelist mode we have to assist the browser in
6263 * opening the URL that it would have opened in a new tab.
6265 reason
= webkit_web_navigation_action_get_reason(na
);
6266 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
6267 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
6268 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
6269 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6271 webkit_web_policy_decision_use(pd
);
6272 return (TRUE
); /* we made the decision */
6279 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
6282 struct domain
*d
= NULL
;
6284 WebKitWebView
*webview
= NULL
;
6286 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
6287 webkit_web_view_get_uri(wv
));
6290 /* open in current tab */
6292 } else if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6293 uri
= webkit_web_view_get_uri(wv
);
6294 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6297 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6299 } else if (enable_scripts
== 1) {
6300 tt
= create_new_tab(NULL
, NULL
, 1, -1);
6308 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
6311 struct domain
*d
= NULL
;
6313 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
6315 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
6316 uri
= webkit_web_view_get_uri(wv
);
6317 if (uri
&& (d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
6321 } else if (enable_scripts
== 1)
6328 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
6330 /* we can not eat the event without throwing gtk off so defer it */
6332 /* catch middle click */
6333 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
6338 /* catch ctrl click */
6339 if (e
->type
== GDK_BUTTON_RELEASE
&&
6340 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
6345 return (XT_CB_PASSTHROUGH
);
6349 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
6351 struct mime_type
*m
;
6353 m
= find_mime_type(mime_type
);
6361 show_oops(t
, "can't fork mime handler");
6371 execlp(m
->mt_action
, m
->mt_action
,
6372 webkit_network_request_get_uri(request
), (void *)NULL
);
6381 get_mime_type(char *file
)
6383 const char *mime_type
;
6387 if (g_str_has_prefix(file
, "file://"))
6388 file
+= strlen("file://");
6390 gf
= g_file_new_for_path(file
);
6391 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
6393 mime_type
= g_file_info_get_content_type(fi
);
6401 run_download_mimehandler(char *mime_type
, char *file
)
6403 struct mime_type
*m
;
6405 m
= find_mime_type(mime_type
);
6411 show_oops(NULL
, "can't fork download mime handler");
6421 if (g_str_has_prefix(file
, "file://"))
6422 file
+= strlen("file://");
6423 execlp(m
->mt_action
, m
->mt_action
, file
, (void *)NULL
);
6432 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
6435 WebKitDownloadStatus status
;
6436 const gchar
*file
= NULL
, *mime
= NULL
;
6438 if (download
== NULL
)
6440 status
= webkit_download_get_status(download
);
6441 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
6444 file
= webkit_download_get_destination_uri(download
);
6447 mime
= get_mime_type((char *)file
);
6451 run_download_mimehandler((char *)mime
, (char *)file
);
6455 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
6456 WebKitNetworkRequest
*request
, char *mime_type
,
6457 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
6460 show_oops(NULL
, "webview_mimetype_cb invalid parameters");
6464 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
6465 t
->tab_id
, mime_type
);
6467 if (run_mimehandler(t
, mime_type
, request
) == 0) {
6468 webkit_web_policy_decision_ignore(decision
);
6473 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
6474 webkit_web_policy_decision_download(decision
);
6482 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
6486 const gchar
*suggested_name
;
6487 gchar
*filename
= NULL
;
6489 struct download
*download_entry
;
6492 if (wk_download
== NULL
|| t
== NULL
) {
6493 show_oops(NULL
, "%s invalid parameters", __func__
);
6497 suggested_name
= webkit_download_get_suggested_filename(wk_download
);
6498 if (suggested_name
== NULL
)
6499 return (FALSE
); /* abort download */
6510 filename
= g_strdup_printf("%d%s", i
, suggested_name
);
6512 uri
= g_strdup_printf("file://%s/%s", download_dir
, i
?
6513 filename
: suggested_name
);
6515 } while (!stat(uri
+ strlen("file://"), &sb
));
6517 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
6518 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
6520 webkit_download_set_destination_uri(wk_download
, uri
);
6522 if (webkit_download_get_status(wk_download
) ==
6523 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
6524 show_oops(t
, "%s: download failed to start", __func__
);
6526 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
6528 /* connect "download first" mime handler */
6529 g_signal_connect(G_OBJECT(wk_download
), "notify::status",
6530 G_CALLBACK(download_status_changed_cb
), NULL
);
6532 download_entry
= g_malloc(sizeof(struct download
));
6533 download_entry
->download
= wk_download
;
6534 download_entry
->tab
= t
;
6535 download_entry
->id
= next_download_id
++;
6536 RB_INSERT(download_list
, &downloads
, download_entry
);
6537 /* get from history */
6538 g_object_ref(wk_download
);
6539 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
6540 show_oops(t
, "Download of '%s' started...",
6541 basename((char *)webkit_download_get_destination_uri(wk_download
)));
6550 /* sync other download manager tabs */
6551 update_download_tabs(NULL
);
6554 * NOTE: never redirect/render the current tab before this
6555 * function returns. This will cause the download to never start.
6557 return (ret
); /* start download */
6561 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
6563 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
6566 show_oops(NULL
, "webview_hover_cb");
6571 set_status(t
, uri
, XT_STATUS_LINK
);
6574 set_status(t
, t
->status
, XT_STATUS_NOTHING
);
6578 /* buffer commands receive the regex that triggered them in arg.s */
6582 int (*func
)(struct tab
*, struct karg
*);
6586 { "^gg$", move
, XT_MOVE_TOP
},
6587 { "^gG$", move
, XT_MOVE_BOTTOM
},
6588 { "^gh$", go_home
, 0 },
6589 { "^ZR$", restart
, 0 },
6590 { "^ZZ$", quit
, 0 },
6591 { "^zi$", resizetab
, XT_ZOOM_IN
},
6592 { "^zo$", resizetab
, XT_ZOOM_OUT
},
6593 { "^z0$", resizetab
, XT_ZOOM_NORMAL
},
6601 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
6602 regcomp(&buffercmds
[i
].cregex
, buffercmds
[i
].regex
, REG_EXTENDED
);
6606 buffercmd_abort(struct tab
*t
)
6610 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_abort: clearing buffer\n");
6611 for (i
= 0; i
< LENGTH(bcmd
); i
++)
6614 cmd_prefix
= 0; /* clear prefix for non-buffer commands */
6615 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.buffercmd
), bcmd
);
6619 buffercmd_execute(struct tab
*t
, struct buffercmd
*cmd
)
6621 struct karg arg
= {0, NULL
, -1};
6624 arg
.s
= g_strdup(bcmd
);
6626 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_execute: buffer \"%s\" "
6627 "matches regex \"%s\", executing\n", bcmd
, cmd
->regex
);
6637 buffercmd_addkey(struct tab
*t
, guint keyval
)
6641 if (keyval
== GDK_Escape
) {
6643 return XT_CB_HANDLED
;
6646 /* key with modifier or non-ascii character */
6647 if (!isascii(keyval
))
6648 return XT_CB_PASSTHROUGH
;
6650 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: adding key \"%c\" "
6651 "to buffer \"%s\"\n", keyval
, bcmd
);
6653 for (i
= 0; i
< LENGTH(bcmd
); i
++)
6654 if (bcmd
[i
] == '\0') {
6659 /* buffer full, ignore input */
6660 if (i
== LENGTH(bcmd
)) {
6661 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: buffer full\n");
6662 return XT_CB_HANDLED
;
6665 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.buffercmd
), bcmd
);
6667 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
6668 if (regexec(&buffercmds
[i
].cregex
, bcmd
,
6669 (size_t) 0, NULL
, 0) == 0) {
6670 buffercmd_execute(t
, &buffercmds
[i
]);
6674 return XT_CB_HANDLED
;
6678 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6680 struct key_binding
*k
;
6682 /* handle keybindings if buffercmd is empty.
6683 if not empty, allow commands like C-n */
6684 if (bcmd
[0] == '\0' || ((e
->state
& (CTRL
| MOD1
)) != 0))
6685 TAILQ_FOREACH(k
, &kbl
, entry
)
6686 if (e
->keyval
== k
->key
6687 && (entry
? k
->use_in_entry
: 1)) {
6689 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6690 return (cmd_execute(t
, k
->cmd
));
6691 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6692 return (cmd_execute(t
, k
->cmd
));
6696 if (!entry
&& ((e
->state
& (CTRL
| MOD1
)) == 0))
6697 return buffercmd_addkey(t
, e
->keyval
);
6699 return (XT_CB_PASSTHROUGH
);
6703 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
6705 char s
[2], buf
[128];
6706 const char *errstr
= NULL
;
6708 /* don't use w directly; use t->whatever instead */
6711 show_oops(NULL
, "wv_keypress_after_cb");
6712 return (XT_CB_PASSTHROUGH
);
6715 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6716 e
->keyval
, e
->state
, t
);
6720 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6722 return (XT_CB_HANDLED
);
6726 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
6728 /* we have a string */
6730 /* we have a number */
6731 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
6739 /* XXX unfuck this */
6740 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
6741 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
6742 /* last input was numerical */
6744 l
= strlen(t
->hint_num
);
6751 t
->hint_num
[l
] = '\0';
6755 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
6756 /* last input was alphanumerical */
6758 l
= strlen(t
->hint_buf
);
6765 t
->hint_buf
[l
] = '\0';
6775 /* numerical input */
6776 if (CLEAN(e
->state
) == 0 &&
6777 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
6778 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6779 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
6780 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
6784 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
6787 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
6789 t
->hint_mode
= XT_HINT_NUMERICAL
;
6793 /* empty the counter buffer */
6794 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
6795 return (XT_CB_HANDLED
);
6798 /* alphanumerical input */
6800 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
6801 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
6802 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
6803 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
6804 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6805 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
6806 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
6809 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
6812 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
6814 t
->hint_mode
= XT_HINT_ALPHANUM
;
6817 /* empty the counter buffer */
6818 bzero(t
->hint_num
, sizeof t
->hint_num
);
6819 return (XT_CB_HANDLED
);
6822 return (XT_CB_HANDLED
);
6825 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6826 if (CLEAN(e
->state
) == 0 && isdigit(s
[0]))
6827 cmd_prefix
= 10 * cmd_prefix
+ atoi(s
);
6830 return (handle_keypress(t
, e
, 0));
6834 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6838 /* Hide buffers, if they are visible, with escape. */
6839 if (gtk_widget_get_visible(GTK_WIDGET(t
->buffers
)) &&
6840 CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6841 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6843 return (XT_CB_HANDLED
);
6846 return (XT_CB_PASSTHROUGH
);
6850 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6852 const gchar
*c
= gtk_entry_get_text(w
);
6856 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6857 e
->keyval
, e
->state
, t
);
6860 show_oops(NULL
, "cmd_keyrelease_cb invalid parameters");
6861 return (XT_CB_PASSTHROUGH
);
6864 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6865 e
->keyval
, e
->state
, t
);
6869 if (strlen(c
) == 1) {
6870 webkit_web_view_unmark_text_matches(t
->wv
);
6876 else if (c
[0] == '?')
6882 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
6884 /* not found, mark red */
6885 gdk_color_parse(XT_COLOR_RED
, &color
);
6886 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6887 /* unmark and remove selection */
6888 webkit_web_view_unmark_text_matches(t
->wv
);
6889 /* my kingdom for a way to unselect text in webview */
6891 /* found, highlight all */
6892 webkit_web_view_unmark_text_matches(t
->wv
);
6893 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6894 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6895 gdk_color_parse(XT_COLOR_WHITE
, &color
);
6896 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6899 return (XT_CB_PASSTHROUGH
);
6903 match_uri(const gchar
*uri
, const gchar
*key
) {
6906 gboolean match
= FALSE
;
6910 if (!strncmp(key
, uri
, len
))
6913 voffset
= strstr(uri
, "/") + 2;
6914 if (!strncmp(key
, voffset
, len
))
6916 else if (g_str_has_prefix(voffset
, "www.")) {
6917 voffset
= voffset
+ strlen("www.");
6918 if (!strncmp(key
, voffset
, len
))
6927 cmd_getlist(int id
, char *key
)
6932 if (id
>= 0 && (cmds
[id
].type
& XT_URLARG
)) {
6933 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6934 if (match_uri(h
->uri
, key
)) {
6935 cmd_status
.list
[c
] = (char *)h
->uri
;
6944 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6946 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6947 if (cmds
[i
].level
< dep
)
6949 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
, strlen(key
)))
6950 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6958 cmd_getnext(int dir
)
6960 cmd_status
.index
+= dir
;
6962 if (cmd_status
.index
< 0)
6963 cmd_status
.index
= cmd_status
.len
- 1;
6964 else if (cmd_status
.index
>= cmd_status
.len
)
6965 cmd_status
.index
= 0;
6967 return cmd_status
.list
[cmd_status
.index
];
6971 cmd_tokenize(char *s
, char *tokens
[])
6975 size_t len
= strlen(s
);
6976 bool blank
= len
== 0 || (len
> 0 && s
[len
-1] == ' ');
6978 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3; tok
= strtok_r(NULL
, " ", &last
), i
++)
6988 cmd_complete(struct tab
*t
, char *str
, int dir
)
6990 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6991 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1, matchcount
= 0;
6992 char *tok
, *match
, *s
= g_strdup(str
);
6994 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6997 DNPRINTF(XT_D_CMD
, "%s: complete %s\n", __func__
, str
);
7000 for (i
= 0; isdigit(s
[i
]); i
++)
7003 for (; isspace(s
[i
]); i
++)
7008 levels
= cmd_tokenize(s
, tokens
);
7010 for (i
= 0; i
< levels
- 1; i
++) {
7013 for (j
= c
; j
< LENGTH(cmds
); j
++) {
7014 if (cmds
[j
].level
< dep
)
7016 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, strlen(tok
))) {
7019 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
7026 if (matchcount
== 1) {
7027 strlcat(res
, tok
, sizeof res
);
7028 strlcat(res
, " ", sizeof res
);
7038 if (cmd_status
.index
== -1)
7039 cmd_getlist(parent
, tokens
[i
]);
7041 if (cmd_status
.len
> 0) {
7042 match
= cmd_getnext(dir
);
7043 strlcat(res
, match
, sizeof res
);
7044 gtk_entry_set_text(w
, res
);
7045 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
7052 cmd_execute(struct tab
*t
, char *str
)
7054 struct cmd
*cmd
= NULL
;
7055 char *tok
, *last
, *s
= g_strdup(str
), *sc
, prefixstr
[4];
7056 int j
, len
, c
= 0, dep
= 0, matchcount
= 0, prefix
= -1;
7057 struct karg arg
= {0, NULL
, -1};
7058 int rv
= XT_CB_PASSTHROUGH
;
7063 for (j
= 0; j
<3 && isdigit(s
[j
]); j
++)
7069 while (isspace(s
[0]))
7072 if (strlen(s
) > 0 && strlen(prefixstr
) > 0)
7073 prefix
= atoi(prefixstr
);
7077 for (tok
= strtok_r(s
, " ", &last
); tok
;
7078 tok
= strtok_r(NULL
, " ", &last
)) {
7080 for (j
= c
; j
< LENGTH(cmds
); j
++) {
7081 if (cmds
[j
].level
< dep
)
7083 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1: strlen(tok
);
7084 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
, len
)) {
7088 if (len
== strlen(cmds
[j
].cmd
)) {
7094 if (matchcount
== 1) {
7099 show_oops(t
, "Invalid command: %s", str
);
7108 else if (cmd_prefix
> 0)
7111 if (j
> 0 && !(cmd
->type
& XT_PREFIX
) && arg
.p
> -1) {
7112 show_oops(t
, "No prefix allowed: %s", str
);
7116 arg
.s
= last
? g_strdup(last
) : g_strdup("");
7117 if (cmd
->type
& XT_INTARG
&& last
&& strlen(last
) > 0) {
7118 arg
.p
= atoi(arg
.s
);
7121 show_oops(t
, "Zero count");
7123 show_oops(t
, "Trailing characters");
7128 DNPRINTF(XT_D_CMD
, "%s: prefix %d arg %s\n", __func__
, arg
.p
, arg
.s
);
7144 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
7147 show_oops(NULL
, "entry_key_cb invalid parameters");
7148 return (XT_CB_PASSTHROUGH
);
7151 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7152 e
->keyval
, e
->state
, t
);
7156 if (e
->keyval
== GDK_Escape
) {
7157 /* don't use focus_webview(t) because we want to type :cmds */
7158 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7161 return (handle_keypress(t
, e
, 1));
7165 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
7167 int rv
= XT_CB_HANDLED
;
7168 const gchar
*c
= gtk_entry_get_text(w
);
7171 show_oops(NULL
, "cmd_keypress_cb parameters");
7172 return (XT_CB_PASSTHROUGH
);
7175 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7176 e
->keyval
, e
->state
, t
);
7180 e
->keyval
= GDK_Escape
;
7181 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
7182 e
->keyval
= GDK_Escape
;
7184 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&& e
->keyval
!= GDK_ISO_Left_Tab
)
7185 cmd_status
.index
= -1;
7187 switch (e
->keyval
) {
7190 cmd_complete(t
, (char *)&c
[1], 1);
7192 case GDK_ISO_Left_Tab
:
7194 cmd_complete(t
, (char *)&c
[1], -1);
7198 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
7206 if (c
!= NULL
&& (c
[0] == '/' || c
[0] == '?'))
7207 webkit_web_view_unmark_text_matches(t
->wv
);
7211 rv
= XT_CB_PASSTHROUGH
;
7217 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
7220 show_oops(NULL
, "cmd_focusout_cb invalid parameters");
7221 return (XT_CB_PASSTHROUGH
);
7223 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d\n", t
->tab_id
);
7228 if (show_url
== 0 || t
->focus_wv
)
7231 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
7233 return (XT_CB_PASSTHROUGH
);
7237 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
7240 const gchar
*c
= gtk_entry_get_text(entry
);
7243 show_oops(NULL
, "cmd_activate_cb invalid parameters");
7247 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
7254 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
7260 if (c
[0] == '/' || c
[0] == '?') {
7261 if (t
->search_text
) {
7262 g_free(t
->search_text
);
7263 t
->search_text
= NULL
;
7266 t
->search_text
= g_strdup(s
);
7268 g_free(global_search
);
7269 global_search
= g_strdup(s
);
7270 t
->search_forward
= c
[0] == '/';
7282 backward_cb(GtkWidget
*w
, struct tab
*t
)
7287 show_oops(NULL
, "backward_cb invalid parameters");
7291 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
7298 forward_cb(GtkWidget
*w
, struct tab
*t
)
7303 show_oops(NULL
, "forward_cb invalid parameters");
7307 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
7309 a
.i
= XT_NAV_FORWARD
;
7314 home_cb(GtkWidget
*w
, struct tab
*t
)
7317 show_oops(NULL
, "home_cb invalid parameters");
7321 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
7327 stop_cb(GtkWidget
*w
, struct tab
*t
)
7329 WebKitWebFrame
*frame
;
7332 show_oops(NULL
, "stop_cb invalid parameters");
7336 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
7338 frame
= webkit_web_view_get_main_frame(t
->wv
);
7339 if (frame
== NULL
) {
7340 show_oops(t
, "stop_cb: no frame");
7344 webkit_web_frame_stop_loading(frame
);
7345 abort_favicon_download(t
);
7349 setup_webkit(struct tab
*t
)
7351 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
7352 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
7353 FALSE
, (char *)NULL
);
7355 warnx("webkit does not have \"enable-dns-prefetching\" property");
7356 g_object_set(G_OBJECT(t
->settings
),
7357 "user-agent", t
->user_agent
, (char *)NULL
);
7358 g_object_set(G_OBJECT(t
->settings
),
7359 "enable-scripts", enable_scripts
, (char *)NULL
);
7360 g_object_set(G_OBJECT(t
->settings
),
7361 "enable-plugins", enable_plugins
, (char *)NULL
);
7362 g_object_set(G_OBJECT(t
->settings
),
7363 "javascript-can-open-windows-automatically", enable_scripts
, (char *)NULL
);
7364 g_object_set(G_OBJECT(t
->settings
),
7365 "enable-html5-database", FALSE
, (char *)NULL
);
7366 g_object_set(G_OBJECT(t
->settings
),
7367 "enable-html5-local-storage", enable_localstorage
, (char *)NULL
);
7368 g_object_set(G_OBJECT(t
->settings
),
7369 "enable_spell_checking", enable_spell_checking
, (char *)NULL
);
7370 g_object_set(G_OBJECT(t
->settings
),
7371 "spell_checking_languages", spell_check_languages
, (char *)NULL
);
7372 g_object_set(G_OBJECT(t
->wv
),
7373 "full-content-zoom", TRUE
, (char *)NULL
);
7375 webkit_web_view_set_settings(t
->wv
, t
->settings
);
7379 update_statusbar_position(GtkAdjustment
* adjustment
, gpointer data
)
7381 struct tab
*ti
, *t
= NULL
;
7382 gdouble view_size
, value
, max
;
7385 TAILQ_FOREACH(ti
, &tabs
, entry
)
7386 if (ti
->tab_id
== gtk_notebook_get_current_page(notebook
)) {
7394 if (adjustment
== NULL
)
7395 adjustment
= gtk_scrolled_window_get_vadjustment(
7396 GTK_SCROLLED_WINDOW(t
->browser_win
));
7398 view_size
= gtk_adjustment_get_page_size(adjustment
);
7399 value
= gtk_adjustment_get_value(adjustment
);
7400 max
= gtk_adjustment_get_upper(adjustment
) - view_size
;
7403 position
= g_strdup("All");
7404 else if (value
== max
)
7405 position
= g_strdup("Bot");
7406 else if (value
== 0)
7407 position
= g_strdup("Top");
7409 position
= g_strdup_printf("%d%%", (int) ((value
/ max
) * 100));
7411 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.position
), position
);
7418 create_browser(struct tab
*t
)
7422 GtkAdjustment
*adjustment
;
7425 show_oops(NULL
, "create_browser invalid parameters");
7429 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
7430 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
7431 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
7432 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
7434 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
7435 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
7436 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
7438 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
7439 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
7442 t
->settings
= webkit_web_settings_new();
7444 if (user_agent
== NULL
) {
7445 g_object_get(G_OBJECT(t
->settings
), "user-agent", &strval
,
7447 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
7450 t
->user_agent
= g_strdup(user_agent
);
7452 t
->stylesheet
= g_strdup_printf("file://%s/style.css", resource_dir
);
7455 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w
));
7456 g_signal_connect(G_OBJECT(adjustment
), "value-changed",
7457 G_CALLBACK(update_statusbar_position
), NULL
);
7469 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
7470 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
7471 gtk_widget_set_name(w
, "xxxterm");
7472 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
7473 g_signal_connect(G_OBJECT(w
), "delete_event",
7474 G_CALLBACK (gtk_main_quit
), NULL
);
7480 create_kiosk_toolbar(struct tab
*t
)
7482 GtkWidget
*toolbar
= NULL
, *b
;
7484 b
= gtk_hbox_new(FALSE
, 0);
7486 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7488 /* backward button */
7489 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7490 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7491 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7492 G_CALLBACK(backward_cb
), t
);
7493 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7495 /* forward button */
7496 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7497 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7498 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7499 G_CALLBACK(forward_cb
), t
);
7500 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7503 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7504 gtk_widget_set_sensitive(t
->gohome
, true);
7505 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7506 G_CALLBACK(home_cb
), t
);
7507 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7509 /* create widgets but don't use them */
7510 t
->uri_entry
= gtk_entry_new();
7511 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7512 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7513 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7519 create_toolbar(struct tab
*t
)
7521 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
7523 b
= gtk_hbox_new(FALSE
, 0);
7525 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7528 /* backward button */
7529 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7530 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7531 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7532 G_CALLBACK(backward_cb
), t
);
7533 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7535 /* forward button */
7536 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7537 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7538 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7539 G_CALLBACK(forward_cb
), t
);
7540 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
7544 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7545 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7546 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
7547 G_CALLBACK(stop_cb
), t
);
7548 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
7552 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7553 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7554 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7555 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7556 G_CALLBACK(js_toggle_cb
), t
);
7557 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7560 t
->uri_entry
= gtk_entry_new();
7561 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7562 G_CALLBACK(activate_uri_entry_cb
), t
);
7563 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7564 G_CALLBACK(entry_key_cb
), t
);
7566 eb1
= gtk_hbox_new(FALSE
, 0);
7567 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
7568 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
7569 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
7572 if (fancy_bar
&& search_string
) {
7574 t
->search_entry
= gtk_entry_new();
7575 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7576 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7577 G_CALLBACK(activate_search_entry_cb
), t
);
7578 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7579 G_CALLBACK(entry_key_cb
), t
);
7580 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
7581 eb2
= gtk_hbox_new(FALSE
, 0);
7582 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
7583 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
7585 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
7591 create_buffers(struct tab
*t
)
7593 GtkCellRenderer
*renderer
;
7596 view
= gtk_tree_view_new();
7598 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view
), FALSE
);
7600 renderer
= gtk_cell_renderer_text_new();
7601 gtk_tree_view_insert_column_with_attributes
7602 (GTK_TREE_VIEW(view
), -1, "Id", renderer
, "text", COL_ID
, NULL
);
7604 renderer
= gtk_cell_renderer_text_new();
7605 gtk_tree_view_insert_column_with_attributes
7606 (GTK_TREE_VIEW(view
), -1, "Title", renderer
, "text", COL_TITLE
, NULL
);
7608 gtk_tree_view_set_model
7609 (GTK_TREE_VIEW(view
), GTK_TREE_MODEL(buffers_store
));
7615 row_activated_cb(GtkTreeView
*view
, GtkTreePath
*path
,
7616 GtkTreeViewColumn
*col
, struct tab
*t
)
7621 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7623 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store
), &iter
, path
)) {
7625 (GTK_TREE_MODEL(buffers_store
), &iter
, COL_ID
, &id
, -1);
7626 set_current_tab(id
- 1);
7632 /* after tab reordering/creation/removal */
7639 TAILQ_FOREACH(t
, &tabs
, entry
) {
7640 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7641 if (t
->tab_id
> maxid
)
7644 gtk_widget_show(t
->tab_elems
.sep
);
7647 TAILQ_FOREACH(t
, &tabs
, entry
) {
7648 if (t
->tab_id
== maxid
) {
7649 gtk_widget_hide(t
->tab_elems
.sep
);
7655 /* after active tab change */
7657 recolor_compact_tabs(void)
7663 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color
);
7664 TAILQ_FOREACH(t
, &tabs
, entry
)
7665 gtk_widget_modify_fg(t
->tab_elems
.label
, GTK_STATE_NORMAL
, &color
);
7667 curid
= gtk_notebook_get_current_page(notebook
);
7668 TAILQ_FOREACH(t
, &tabs
, entry
)
7669 if (t
->tab_id
== curid
) {
7670 gdk_color_parse(XT_COLOR_CT_ACTIVE
, &color
);
7671 gtk_widget_modify_fg(t
->tab_elems
.label
, GTK_STATE_NORMAL
, &color
);
7677 set_current_tab(int page_num
)
7679 buffercmd_abort(get_current_tab());
7680 gtk_notebook_set_current_page(notebook
, page_num
);
7681 recolor_compact_tabs();
7685 undo_close_tab_save(struct tab
*t
)
7689 struct undo
*u1
, *u2
;
7691 WebKitWebHistoryItem
*item
;
7693 if ((uri
= get_uri(t
)) == NULL
)
7696 u1
= g_malloc0(sizeof(struct undo
));
7697 u1
->uri
= g_strdup(uri
);
7699 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7701 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7702 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7705 /* forward history */
7706 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7710 u1
->history
= g_list_prepend(u1
->history
,
7711 webkit_web_history_item_copy(item
));
7712 items
= g_list_next(items
);
7717 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7718 u1
->history
= g_list_prepend(u1
->history
,
7719 webkit_web_history_item_copy(item
));
7723 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7727 u1
->history
= g_list_prepend(u1
->history
,
7728 webkit_web_history_item_copy(item
));
7729 items
= g_list_next(items
);
7732 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7734 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7735 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7736 TAILQ_REMOVE(&undos
, u2
, entry
);
7738 g_list_free(u2
->history
);
7747 delete_tab(struct tab
*t
)
7751 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7756 TAILQ_REMOVE(&tabs
, t
, entry
);
7758 /* Halt all webkit activity. */
7759 abort_favicon_download(t
);
7760 webkit_web_view_stop_loading(t
->wv
);
7762 /* Save the tab, so we can undo the close. */
7763 undo_close_tab_save(t
);
7765 if (browser_mode
== XT_BM_KIOSK
) {
7766 gtk_widget_destroy(t
->uri_entry
);
7767 gtk_widget_destroy(t
->stop
);
7768 gtk_widget_destroy(t
->js_toggle
);
7771 gtk_widget_destroy(t
->tab_elems
.eventbox
);
7772 gtk_widget_destroy(t
->vbox
);
7774 g_free(t
->user_agent
);
7775 g_free(t
->stylesheet
);
7779 if (TAILQ_EMPTY(&tabs
)) {
7780 if (browser_mode
== XT_BM_KIOSK
)
7781 create_new_tab(home
, NULL
, 1, -1);
7783 create_new_tab(NULL
, NULL
, 1, -1);
7786 /* recreate session */
7787 if (session_autosave
) {
7793 recolor_compact_tabs();
7797 setzoom_webkit(struct tab
*t
, int adjust
)
7799 #define XT_ZOOMPERCENT 0.04
7804 show_oops(NULL
, "setzoom_webkit invalid parameters");
7808 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7809 if (adjust
== XT_ZOOM_IN
)
7810 zoom
+= XT_ZOOMPERCENT
;
7811 else if (adjust
== XT_ZOOM_OUT
)
7812 zoom
-= XT_ZOOMPERCENT
;
7813 else if (adjust
> 0)
7814 zoom
= default_zoom_level
+ adjust
/ 100.0 - 1.0;
7816 show_oops(t
, "setzoom_webkit invalid zoom value");
7820 if (zoom
< XT_ZOOMPERCENT
)
7821 zoom
= XT_ZOOMPERCENT
;
7822 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7826 tab_clicked_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
)
7828 struct tab
*t
= (struct tab
*) data
;
7830 DNPRINTF(XT_D_TAB
, "tab_clicked_cb: tab: %d\n", t
->tab_id
);
7832 switch (event
->button
) {
7834 set_current_tab(t
->tab_id
);
7845 append_tab(struct tab
*t
)
7850 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7851 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7855 create_new_tab(char *title
, struct undo
*u
, int focus
, int position
)
7860 WebKitWebHistoryItem
*item
;
7864 int sbe_p
= 0, sbe_b
= 0;
7866 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7868 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7869 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
7873 t
= g_malloc0(sizeof *t
);
7875 if (title
== NULL
) {
7876 title
= "(untitled)";
7880 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7882 /* label + button for tab */
7883 b
= gtk_hbox_new(FALSE
, 0);
7886 #if GTK_CHECK_VERSION(2, 20, 0)
7887 t
->spinner
= gtk_spinner_new();
7889 t
->label
= gtk_label_new(title
);
7890 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7891 gtk_widget_set_size_request(t
->label
, 100, 0);
7892 gtk_label_set_max_width_chars(GTK_LABEL(t
->label
), 20);
7893 gtk_label_set_ellipsize(GTK_LABEL(t
->label
), PANGO_ELLIPSIZE_END
);
7894 gtk_widget_set_size_request(b
, 130, 0);
7896 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7897 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7898 #if GTK_CHECK_VERSION(2, 20, 0)
7899 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7903 if (browser_mode
== XT_BM_KIOSK
)
7904 t
->toolbar
= create_kiosk_toolbar(t
);
7906 t
->toolbar
= create_toolbar(t
);
7908 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
7911 t
->browser_win
= create_browser(t
);
7912 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7914 /* oops message for user feedback */
7915 t
->oops
= gtk_entry_new();
7916 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7917 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7918 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7919 gdk_color_parse(XT_COLOR_RED
, &color
);
7920 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7921 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7922 gtk_widget_modify_font(GTK_WIDGET(t
->oops
), oops_font
);
7925 t
->cmd
= gtk_entry_new();
7926 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7927 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7928 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7929 gtk_widget_modify_font(GTK_WIDGET(t
->cmd
), cmd_font
);
7932 t
->statusbar_box
= gtk_hbox_new(FALSE
, 0);
7934 t
->sbe
.statusbar
= gtk_entry_new();
7935 gtk_entry_set_inner_border(GTK_ENTRY(t
->sbe
.statusbar
), NULL
);
7936 gtk_entry_set_has_frame(GTK_ENTRY(t
->sbe
.statusbar
), FALSE
);
7937 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.statusbar
), FALSE
);
7938 gtk_widget_modify_font(GTK_WIDGET(t
->sbe
.statusbar
), statusbar_font
);
7940 /* XXX create these widgets only if specified in statusbar_elems */
7941 t
->sbe
.position
= gtk_entry_new();
7942 gtk_entry_set_inner_border(GTK_ENTRY(t
->sbe
.position
), NULL
);
7943 gtk_entry_set_has_frame(GTK_ENTRY(t
->sbe
.position
), FALSE
);
7944 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.position
), FALSE
);
7945 gtk_widget_modify_font(GTK_WIDGET(t
->sbe
.position
), statusbar_font
);
7946 gtk_entry_set_alignment(GTK_ENTRY(t
->sbe
.position
), 1.0);
7947 gtk_widget_set_size_request(t
->sbe
.position
, 40, -1);
7949 t
->sbe
.buffercmd
= gtk_entry_new();
7950 gtk_entry_set_inner_border(GTK_ENTRY(t
->sbe
.buffercmd
), NULL
);
7951 gtk_entry_set_has_frame(GTK_ENTRY(t
->sbe
.buffercmd
), FALSE
);
7952 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.buffercmd
), FALSE
);
7953 gtk_widget_modify_font(GTK_WIDGET(t
->sbe
.buffercmd
), statusbar_font
);
7954 gtk_entry_set_alignment(GTK_ENTRY(t
->sbe
.buffercmd
), 1.0);
7955 gtk_widget_set_size_request(t
->sbe
.buffercmd
, 60, -1);
7957 statusbar_modify_attr(t
, XT_COLOR_WHITE
, XT_COLOR_BLACK
);
7959 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
), t
->sbe
.statusbar
, TRUE
,
7962 /* gtk widgets cannot be added to a box twice. sbe_* variables
7963 make sure of this */
7964 for (p
= statusbar_elems
; *p
!= '\0'; p
++) {
7968 GtkWidget
*sep
= gtk_vseparator_new();
7970 gdk_color_parse(XT_COLOR_SB_SEPARATOR
, &color
);
7971 gtk_widget_modify_bg(sep
, GTK_STATE_NORMAL
, &color
);
7972 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
), sep
, FALSE
,
7978 warnx("flag \"%c\" specified more than "
7979 "once in statusbar_elems\n", *p
);
7983 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
),
7984 t
->sbe
.position
, FALSE
, FALSE
, FALSE
);
7988 warnx("flag \"%c\" specified more than "
7989 "once in statusbar_elems\n", *p
);
7993 gtk_box_pack_start(GTK_BOX(t
->statusbar_box
),
7994 t
->sbe
.buffercmd
, FALSE
, FALSE
, FALSE
);
7997 warnx("illegal flag \"%c\" in statusbar_elems\n", *p
);
8002 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar_box
, FALSE
, FALSE
, 0);
8005 t
->buffers
= create_buffers(t
);
8006 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->buffers
, FALSE
, FALSE
, 0);
8008 /* xtp meaning is normal by default */
8009 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
8011 /* set empty favicon */
8012 xt_icon_from_name(t
, "text-html");
8014 /* and show it all */
8015 gtk_widget_show_all(b
);
8016 gtk_widget_show_all(t
->vbox
);
8018 /* compact tab bar */
8019 t
->tab_elems
.label
= gtk_label_new(title
);
8020 gtk_label_set_width_chars(GTK_LABEL(t
->tab_elems
.label
), 1.0);
8021 gtk_misc_set_alignment(GTK_MISC(t
->tab_elems
.label
), 0.0, 0.0);
8022 gtk_misc_set_padding(GTK_MISC(t
->tab_elems
.label
), 4.0, 4.0);
8023 gtk_widget_modify_font(GTK_WIDGET(t
->tab_elems
.label
), tabbar_font
);
8025 t
->tab_elems
.eventbox
= gtk_event_box_new();
8026 t
->tab_elems
.box
= gtk_hbox_new(FALSE
, 0);
8027 t
->tab_elems
.sep
= gtk_vseparator_new();
8029 gdk_color_parse(XT_COLOR_CT_BACKGROUND
, &color
);
8030 gtk_widget_modify_bg(t
->tab_elems
.eventbox
, GTK_STATE_NORMAL
, &color
);
8031 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color
);
8032 gtk_widget_modify_fg(t
->tab_elems
.label
, GTK_STATE_NORMAL
, &color
);
8033 gdk_color_parse(XT_COLOR_CT_SEPARATOR
, &color
);
8034 gtk_widget_modify_bg(t
->tab_elems
.sep
, GTK_STATE_NORMAL
, &color
);
8036 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.label
, TRUE
,
8038 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.sep
, FALSE
,
8040 gtk_container_add(GTK_CONTAINER(t
->tab_elems
.eventbox
),
8043 gtk_box_pack_start(GTK_BOX(tab_bar
), t
->tab_elems
.eventbox
, TRUE
,
8045 gtk_widget_show_all(t
->tab_elems
.eventbox
);
8047 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
8050 id
= position
>= 0 ? position
: gtk_notebook_get_current_page(notebook
) + 1;
8051 if (id
> gtk_notebook_get_n_pages(notebook
))
8054 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
8055 gtk_notebook_insert_page(notebook
, t
->vbox
, b
, id
);
8056 gtk_box_reorder_child(GTK_BOX(tab_bar
), t
->tab_elems
.eventbox
, id
);
8061 #if GTK_CHECK_VERSION(2, 20, 0)
8062 /* turn spinner off if we are a new tab without uri */
8064 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
8065 gtk_widget_hide(t
->spinner
);
8068 /* make notebook tabs reorderable */
8069 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
8071 /* compact tabs clickable */
8072 g_signal_connect(G_OBJECT(t
->tab_elems
.eventbox
),
8073 "button_press_event", G_CALLBACK(tab_clicked_cb
), t
);
8075 g_object_connect(G_OBJECT(t
->cmd
),
8076 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
8077 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
8078 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
8079 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
8082 /* reuse wv_button_cb to hide oops */
8083 g_object_connect(G_OBJECT(t
->oops
),
8084 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
8087 g_signal_connect(t
->buffers
,
8088 "row-activated", G_CALLBACK(row_activated_cb
), t
);
8089 g_object_connect(G_OBJECT(t
->buffers
),
8090 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
, NULL
);
8092 g_object_connect(G_OBJECT(t
->wv
),
8093 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
8094 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
8095 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
8096 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
8097 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
8098 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
8099 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
8100 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
8101 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
8102 "signal::event", G_CALLBACK(webview_event_cb
), t
,
8103 "signal::load-finished", G_CALLBACK(webview_load_finished_cb
), t
,
8104 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb
), t
,
8105 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
8106 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
8107 "signal::button_release_event", G_CALLBACK(wv_release_button_cb
), t
,
8109 g_signal_connect(t
->wv
,
8110 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
8111 g_signal_connect(t
->wv
,
8112 "load-error", G_CALLBACK(notify_load_error_cb
), t
);
8113 g_signal_connect(t
->wv
,
8114 "notify::title", G_CALLBACK(notify_title_cb
), t
);
8116 /* hijack the unused keys as if we were the browser */
8117 g_object_connect(G_OBJECT(t
->toolbar
),
8118 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb
), t
,
8121 g_signal_connect(G_OBJECT(bb
), "button_press_event",
8122 G_CALLBACK(tab_close_cb
), t
);
8128 url_set_visibility();
8129 statusbar_set_visibility();
8132 set_current_tab(t
->tab_id
);
8133 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
8138 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
8142 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
8147 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
8148 /* restore the tab's history */
8149 if (u
&& u
->history
) {
8153 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
8154 items
= g_list_next(items
);
8157 item
= g_list_nth_data(u
->history
, u
->back
);
8159 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
8162 g_list_free(u
->history
);
8164 webkit_web_back_forward_list_clear(t
->bfl
);
8166 recolor_compact_tabs();
8171 notebook_switchpage_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
8177 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
8179 if (gtk_notebook_get_current_page(notebook
) == -1)
8182 TAILQ_FOREACH(t
, &tabs
, entry
) {
8183 if (t
->tab_id
== pn
) {
8184 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
8187 uri
= get_title(t
, TRUE
);
8188 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
8194 /* can't use focus_webview here */
8195 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
8202 notebook_pagereordered_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
8205 struct tab
*t
= NULL
, *tt
;
8209 TAILQ_FOREACH(tt
, &tabs
, entry
)
8210 if (tt
->tab_id
== pn
) {
8215 DNPRINTF(XT_D_TAB
, "page_reordered_cb: tab: %d\n", t
->tab_id
);
8217 gtk_box_reorder_child(GTK_BOX(tab_bar
), t
->tab_elems
.eventbox
,
8222 menuitem_response(struct tab
*t
)
8224 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
8228 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
8230 GtkWidget
*menu
, *menu_items
;
8231 GdkEventButton
*bevent
;
8235 if (event
->type
== GDK_BUTTON_PRESS
) {
8236 bevent
= (GdkEventButton
*) event
;
8237 menu
= gtk_menu_new();
8239 TAILQ_FOREACH(ti
, &tabs
, entry
) {
8240 if ((uri
= get_uri(ti
)) == NULL
)
8241 /* XXX make sure there is something to print */
8242 /* XXX add gui pages in here to look purdy */
8244 menu_items
= gtk_menu_item_new_with_label(uri
);
8245 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_items
);
8246 gtk_widget_show(menu_items
);
8248 g_signal_connect_swapped((menu_items
),
8249 "activate", G_CALLBACK(menuitem_response
),
8253 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
8254 bevent
->button
, bevent
->time
);
8256 /* unref object so it'll free itself when popped down */
8257 #if !GTK_CHECK_VERSION(3, 0, 0)
8258 /* XXX does not need unref with gtk+3? */
8259 g_object_ref_sink(menu
);
8260 g_object_unref(menu
);
8263 return (TRUE
/* eat event */);
8266 return (FALSE
/* propagate */);
8270 icon_size_map(int icon_size
)
8272 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
8273 icon_size
> GTK_ICON_SIZE_DIALOG
)
8274 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
8280 create_button(char *name
, char *stockid
, int size
)
8282 GtkWidget
*button
, *image
;
8286 rcstring
= g_strdup_printf(
8287 "style \"%s-style\"\n"
8289 " GtkWidget::focus-padding = 0\n"
8290 " GtkWidget::focus-line-width = 0\n"
8294 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
8295 gtk_rc_parse_string(rcstring
);
8297 button
= gtk_button_new();
8298 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
8299 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
8301 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
8302 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
8303 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
8304 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
8305 gtk_widget_set_name(button
, name
);
8306 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
8312 button_set_stockid(GtkWidget
*button
, char *stockid
)
8316 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
8317 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
8318 gtk_button_set_image(GTK_BUTTON(button
), image
);
8322 clipb_primary_cb(GtkClipboard
*primary
, GdkEvent
*event
, gpointer notused
)
8324 GtkClipboard
*clipboard
;
8325 gchar
*p
= NULL
, *s
= NULL
;
8328 * This code is very aggressive!
8329 * It basically ensures that the primary and regular clipboard are
8330 * always set the same. This obviously messes with standard X protocol
8331 * but those clowns should have come up with something better.
8337 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
8338 p
= gtk_clipboard_wait_for_text(primary
);
8340 DNPRINTF(XT_D_CLIP
, "primary cleaned\n");
8341 p
= gtk_clipboard_wait_for_text(clipboard
);
8343 gtk_clipboard_set_text(primary
, p
, -1);
8345 DNPRINTF(XT_D_CLIP
, "primary got selection\n");
8346 s
= gtk_clipboard_wait_for_text(clipboard
);
8349 * if s and p are the same the string was set by
8350 * clipb_clipboard_cb so do nothing in that case
8351 * to prevent endless loop
8356 gtk_clipboard_set_text(clipboard
, p
, -1);
8366 clipb_clipboard_cb(GtkClipboard
*clipboard
, GdkEvent
*event
, gpointer notused
)
8368 GtkClipboard
*primary
;
8369 gchar
*p
= NULL
, *s
= NULL
;
8374 DNPRINTF(XT_D_CLIP
, "clipboard got content\n");
8376 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
8377 p
= gtk_clipboard_wait_for_text(clipboard
);
8379 s
= gtk_clipboard_wait_for_text(primary
);
8382 * if s and p are the same the string was set by
8383 * clipb_primary_cb so do nothing in that case
8384 * to prevent endless loop and deselection of text
8389 gtk_clipboard_set_text(primary
, p
, -1);
8404 char file
[PATH_MAX
];
8407 vbox
= gtk_vbox_new(FALSE
, 0);
8408 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
8409 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
8410 #if !GTK_CHECK_VERSION(3, 0, 0)
8411 /* XXX seems to be needed with gtk+2 */
8412 gtk_notebook_set_tab_hborder(notebook
, 0);
8413 gtk_notebook_set_tab_vborder(notebook
, 0);
8415 gtk_notebook_set_scrollable(notebook
, TRUE
);
8416 gtk_notebook_set_show_border(notebook
, FALSE
);
8417 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
8419 abtn
= gtk_button_new();
8420 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
8421 gtk_widget_set_size_request(arrow
, -1, -1);
8422 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
8423 gtk_widget_set_size_request(abtn
, -1, 20);
8425 #if GTK_CHECK_VERSION(2, 20, 0)
8426 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
8428 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
8430 /* compact tab bar */
8431 tab_bar
= gtk_hbox_new(TRUE
, 0);
8433 gtk_box_pack_start(GTK_BOX(vbox
), tab_bar
, FALSE
, FALSE
, 0);
8434 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
8435 gtk_widget_set_size_request(vbox
, -1, -1);
8437 g_object_connect(G_OBJECT(notebook
),
8438 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
8440 g_object_connect(G_OBJECT(notebook
),
8441 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb
), NULL
,
8443 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
8444 G_CALLBACK(arrow_cb
), NULL
);
8446 main_window
= create_window();
8447 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
8450 for (i
= 0; i
< LENGTH(icons
); i
++) {
8451 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
8452 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
8453 l
= g_list_append(l
, pb
);
8455 gtk_window_set_default_icon_list(l
);
8458 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY
)),
8459 "owner-change", G_CALLBACK(clipb_primary_cb
), NULL
);
8460 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
)),
8461 "owner-change", G_CALLBACK(clipb_clipboard_cb
), NULL
);
8463 gtk_widget_show_all(abtn
);
8464 gtk_widget_show_all(main_window
);
8465 notebook_tab_set_visibility();
8469 set_hook(void **hook
, char *name
)
8472 errx(1, "set_hook");
8474 if (*hook
== NULL
) {
8475 *hook
= dlsym(RTLD_NEXT
, name
);
8477 errx(1, "can't hook %s", name
);
8481 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8483 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
8485 g_return_val_if_fail(cookie1
, FALSE
);
8486 g_return_val_if_fail(cookie2
, FALSE
);
8488 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
8489 !strcmp (cookie1
->value
, cookie2
->value
) &&
8490 !strcmp (cookie1
->path
, cookie2
->path
) &&
8491 !strcmp (cookie1
->domain
, cookie2
->domain
));
8495 transfer_cookies(void)
8498 SoupCookie
*sc
, *pc
;
8500 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
8502 for (;cf
; cf
= cf
->next
) {
8504 sc
= soup_cookie_copy(pc
);
8505 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
8508 soup_cookies_free(cf
);
8512 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
8517 print_cookie("soup_cookie_jar_delete_cookie", c
);
8519 if (cookies_enabled
== 0)
8522 if (jar
== NULL
|| c
== NULL
)
8525 /* find and remove from persistent jar */
8526 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
8528 for (;cf
; cf
= cf
->next
) {
8530 if (soup_cookie_equal(ci
, c
)) {
8531 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
8536 soup_cookies_free(cf
);
8538 /* delete from session jar */
8539 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
8543 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
8545 struct domain
*d
= NULL
;
8549 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
8550 jar
, p_cookiejar
, s_cookiejar
);
8552 if (cookies_enabled
== 0)
8555 /* see if we are up and running */
8556 if (p_cookiejar
== NULL
) {
8557 _soup_cookie_jar_add_cookie(jar
, cookie
);
8560 /* disallow p_cookiejar adds, shouldn't happen */
8561 if (jar
== p_cookiejar
)
8565 if (jar
== NULL
|| cookie
== NULL
)
8568 if (enable_cookie_whitelist
&&
8569 (d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
8571 DNPRINTF(XT_D_COOKIE
,
8572 "soup_cookie_jar_add_cookie: reject %s\n",
8574 if (save_rejected_cookies
) {
8575 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
) {
8576 show_oops(NULL
, "can't open reject cookie file");
8579 fseek(r_cookie_f
, 0, SEEK_END
);
8580 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8581 cookie
->http_only
? "#HttpOnly_" : "",
8583 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
8585 cookie
->secure
? "TRUE" : "FALSE",
8587 (gulong
)soup_date_to_time_t(cookie
->expires
) :
8594 if (!allow_volatile_cookies
)
8598 if (cookie
->expires
== NULL
&& session_timeout
) {
8599 soup_cookie_set_expires(cookie
,
8600 soup_date_new_from_now(session_timeout
));
8601 print_cookie("modified add cookie", cookie
);
8604 /* see if we are white listed for persistence */
8605 if ((d
&& d
->handy
) || (enable_cookie_whitelist
== 0)) {
8606 /* add to persistent jar */
8607 c
= soup_cookie_copy(cookie
);
8608 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
8609 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
8612 /* add to session jar */
8613 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
8614 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
8620 char file
[PATH_MAX
];
8622 set_hook((void *)&_soup_cookie_jar_add_cookie
,
8623 "soup_cookie_jar_add_cookie");
8624 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
8625 "soup_cookie_jar_delete_cookie");
8627 if (cookies_enabled
== 0)
8631 * the following code is intricate due to overriding several libsoup
8633 * do not alter order of these operations.
8636 /* rejected cookies */
8637 if (save_rejected_cookies
)
8638 snprintf(rc_fname
, sizeof file
, "%s/%s", work_dir
, XT_REJECT_FILE
);
8640 /* persistent cookies */
8641 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_COOKIE_FILE
);
8642 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
8644 /* session cookies */
8645 s_cookiejar
= soup_cookie_jar_new();
8646 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
8647 cookie_policy
, (void *)NULL
);
8650 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
8654 setup_proxy(char *uri
)
8657 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
8658 soup_uri_free(proxy_uri
);
8662 if (http_proxy
!= uri
) {
8669 http_proxy
= g_strdup(uri
);
8670 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
8671 proxy_uri
= soup_uri_new(http_proxy
);
8672 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
8677 send_cmd_to_socket(char *cmd
)
8680 struct sockaddr_un sa
;
8682 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8683 warnx("%s: socket", __func__
);
8687 sa
.sun_family
= AF_UNIX
;
8688 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8689 work_dir
, XT_SOCKET_FILE
);
8692 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8693 warnx("%s: connect", __func__
);
8697 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
8698 warnx("%s: send", __func__
);
8709 socket_watcher(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
8712 char str
[XT_MAX_URL_LENGTH
];
8713 socklen_t t
= sizeof(struct sockaddr_un
);
8714 struct sockaddr_un sa
;
8719 gint fd
= g_io_channel_unix_get_fd(source
);
8721 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
8726 if (getpeereid(s
, &uid
, &gid
) == -1) {
8730 if (uid
!= getuid() || gid
!= getgid()) {
8731 warnx("unauthorized user");
8737 warnx("not a valid user");
8741 n
= recv(s
, str
, sizeof(str
), 0);
8745 tt
= TAILQ_LAST(&tabs
, tab_list
);
8746 cmd_execute(tt
, str
);
8754 struct sockaddr_un sa
;
8756 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8757 warn("is_running: socket");
8761 sa
.sun_family
= AF_UNIX
;
8762 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8763 work_dir
, XT_SOCKET_FILE
);
8766 /* connect to see if there is a listener */
8767 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
8768 rv
= 0; /* not running */
8770 rv
= 1; /* already running */
8781 struct sockaddr_un sa
;
8783 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8784 warn("build_socket: socket");
8788 sa
.sun_family
= AF_UNIX
;
8789 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s",
8790 work_dir
, XT_SOCKET_FILE
);
8793 /* connect to see if there is a listener */
8794 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8795 /* no listener so we will */
8796 unlink(sa
.sun_path
);
8798 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8799 warn("build_socket: bind");
8803 if (listen(s
, 1) == -1) {
8804 warn("build_socket: listen");
8817 completion_select_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8818 GtkTreeIter
*iter
, struct tab
*t
)
8822 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8830 completion_hover_cb(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
8831 GtkTreeIter
*iter
, struct tab
*t
)
8835 gtk_tree_model_get(model
, iter
, 0, &value
, -1);
8836 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), value
);
8837 gtk_editable_set_position(GTK_EDITABLE(t
->uri_entry
), -1);
8844 completion_add_uri(const gchar
*uri
)
8848 /* add uri to list_store */
8849 gtk_list_store_append(completion_model
, &iter
);
8850 gtk_list_store_set(completion_model
, &iter
, 0, uri
, -1);
8854 completion_match(GtkEntryCompletion
*completion
, const gchar
*key
,
8855 GtkTreeIter
*iter
, gpointer user_data
)
8858 gboolean match
= FALSE
;
8860 gtk_tree_model_get(GTK_TREE_MODEL(completion_model
), iter
, 0, &value
,
8866 match
= match_uri(value
, key
);
8873 completion_add(struct tab
*t
)
8875 /* enable completion for tab */
8876 t
->completion
= gtk_entry_completion_new();
8877 gtk_entry_completion_set_text_column(t
->completion
, 0);
8878 gtk_entry_set_completion(GTK_ENTRY(t
->uri_entry
), t
->completion
);
8879 gtk_entry_completion_set_model(t
->completion
,
8880 GTK_TREE_MODEL(completion_model
));
8881 gtk_entry_completion_set_match_func(t
->completion
, completion_match
,
8883 gtk_entry_completion_set_minimum_key_length(t
->completion
, 1);
8884 gtk_entry_completion_set_inline_selection(t
->completion
, TRUE
);
8885 g_signal_connect(G_OBJECT (t
->completion
), "match-selected",
8886 G_CALLBACK(completion_select_cb
), t
);
8887 g_signal_connect(G_OBJECT (t
->completion
), "cursor-on-match",
8888 G_CALLBACK(completion_hover_cb
), t
);
8896 if (stat(dir
, &sb
)) {
8897 if (mkdir(dir
, S_IRWXU
) == -1)
8898 err(1, "mkdir %s", dir
);
8900 err(1, "stat %s", dir
);
8902 if (S_ISDIR(sb
.st_mode
) == 0)
8903 errx(1, "%s not a dir", dir
);
8904 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8905 warnx("fixing invalid permissions on %s", dir
);
8906 if (chmod(dir
, S_IRWXU
) == -1)
8907 err(1, "chmod %s", dir
);
8915 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8921 main(int argc
, char *argv
[])
8924 int c
, s
, optn
= 0, opte
= 0, focus
= 1;
8925 char conf
[PATH_MAX
] = { '\0' };
8926 char file
[PATH_MAX
];
8927 char *env_proxy
= NULL
;
8930 struct sigaction sact
;
8931 GIOChannel
*channel
;
8936 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8938 /* fiddle with ulimits */
8939 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8942 /* just use them all */
8943 rlp
.rlim_cur
= rlp
.rlim_max
;
8944 if (setrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8946 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8948 else if (rlp
.rlim_cur
<= 256)
8949 warnx("%s requires at least 256 file descriptors",
8953 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8962 errx(0 , "Version: %s", version
);
8965 strlcpy(conf
, optarg
, sizeof(conf
));
8968 strlcpy(named_session
, optarg
, sizeof(named_session
));
8989 RB_INIT(&downloads
);
8993 TAILQ_INIT(&aliases
);
8999 gnutls_global_init();
9001 /* generate session keys for xtp pages */
9002 generate_xtp_session_key(&dl_session_key
);
9003 generate_xtp_session_key(&hl_session_key
);
9004 generate_xtp_session_key(&cl_session_key
);
9005 generate_xtp_session_key(&fl_session_key
);
9008 gtk_init(&argc
, &argv
);
9009 if (!g_thread_supported())
9010 g_thread_init(NULL
);
9013 bzero(&sact
, sizeof(sact
));
9014 sigemptyset(&sact
.sa_mask
);
9015 sact
.sa_handler
= sigchild
;
9016 sact
.sa_flags
= SA_NOCLDSTOP
;
9017 sigaction(SIGCHLD
, &sact
, NULL
);
9019 /* set download dir */
9020 pwd
= getpwuid(getuid());
9022 errx(1, "invalid user %d", getuid());
9023 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
9025 /* compile buffer command regexes */
9028 /* set default string settings */
9029 home
= g_strdup("https://www.cyphertite.com");
9030 search_string
= g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9031 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
9032 strlcpy(runtime_settings
, "runtime", sizeof runtime_settings
);
9033 cmd_font_name
= g_strdup("monospace normal 9");
9034 oops_font_name
= g_strdup("monospace normal 9");
9035 statusbar_font_name
= g_strdup("monospace normal 9");
9036 tabbar_font_name
= g_strdup("monospace normal 9");
9037 statusbar_elems
= g_strdup("BP");
9039 /* read config file */
9040 if (strlen(conf
) == 0)
9041 snprintf(conf
, sizeof conf
, "%s/.%s",
9042 pwd
->pw_dir
, XT_CONF_FILE
);
9043 config_parse(conf
, 0);
9046 cmd_font
= pango_font_description_from_string(cmd_font_name
);
9047 oops_font
= pango_font_description_from_string(oops_font_name
);
9048 statusbar_font
= pango_font_description_from_string(statusbar_font_name
);
9049 tabbar_font
= pango_font_description_from_string(tabbar_font_name
);
9051 /* working directory */
9052 if (strlen(work_dir
) == 0)
9053 snprintf(work_dir
, sizeof work_dir
, "%s/%s",
9054 pwd
->pw_dir
, XT_DIR
);
9057 /* icon cache dir */
9058 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s", work_dir
, XT_CACHE_DIR
);
9062 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s", work_dir
, XT_CERT_DIR
);
9066 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s",
9067 work_dir
, XT_SESSIONS_DIR
);
9068 xxx_dir(sessions_dir
);
9070 /* runtime settings that can override config file */
9071 if (runtime_settings
[0] != '\0')
9072 config_parse(runtime_settings
, 1);
9075 if (!strcmp(download_dir
, pwd
->pw_dir
))
9076 strlcat(download_dir
, "/downloads", sizeof download_dir
);
9077 xxx_dir(download_dir
);
9079 /* favorites file */
9080 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
9081 if (stat(file
, &sb
)) {
9082 warnx("favorites file doesn't exist, creating it");
9083 if ((f
= fopen(file
, "w")) == NULL
)
9084 err(1, "favorites");
9089 session
= webkit_get_default_session();
9094 if (stat(ssl_ca_file
, &sb
)) {
9095 warnx("no CA file: %s", ssl_ca_file
);
9096 g_free(ssl_ca_file
);
9099 g_object_set(session
,
9100 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
9101 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
9106 env_proxy
= getenv("http_proxy");
9108 setup_proxy(env_proxy
);
9110 setup_proxy(http_proxy
);
9113 send_cmd_to_socket(argv
[0]);
9117 /* set some connection parameters */
9118 /* XXX webkit 1.4.X overwrites these values! */
9119 /* https://bugs.webkit.org/show_bug.cgi?id=64355 */
9120 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
9121 g_object_set(session
, "max-conns-per-host", max_host_connections
,
9124 /* see if there is already an xxxterm running */
9125 if (single_instance
&& is_running()) {
9127 warnx("already running");
9133 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
9134 send_cmd_to_socket(cmd
);
9144 /* uri completion */
9145 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
9148 buffers_store
= gtk_list_store_new
9149 (NUM_COLS
, G_TYPE_UINT
, G_TYPE_STRING
);
9153 notebook_tab_set_visibility();
9155 if (save_global_history
)
9156 restore_global_history();
9158 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
9159 restore_saved_tabs();
9161 a
.s
= named_session
;
9162 a
.i
= XT_SES_DONOTHING
;
9163 open_tabs(NULL
, &a
);
9167 create_new_tab(argv
[0], NULL
, focus
, -1);
9174 if (TAILQ_EMPTY(&tabs
))
9175 create_new_tab(home
, NULL
, 1, -1);
9178 if ((s
= build_socket()) != -1) {
9179 channel
= g_io_channel_unix_new(s
);
9180 g_io_add_watch(channel
, G_IO_IN
, socket_watcher
, NULL
);
9185 gnutls_global_deinit();