3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * inverse color browsing
26 * multi letter commands
27 * pre and post counts for commands
28 * autocompletion on various inputs
29 * create privacy browsing
30 * - encrypted local data
44 #include <sys/types.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
50 #include "freebsd/util.h"
56 #include <sys/queue.h>
58 #include <sys/socket.h>
62 #include <gdk/gdkkeysyms.h>
63 #include <webkit/webkit.h>
64 #include <libsoup/soup.h>
65 #include <gnutls/gnutls.h>
66 #include <JavaScriptCore/JavaScript.h>
67 #include <gnutls/x509.h>
69 #include "javascript.h"
72 javascript.h borrowed from vimprobable2 under the following license:
74 Copyright (c) 2009 Leon Winter
75 Copyright (c) 2009 Hannes Schueller
76 Copyright (c) 2009 Matto Fransen
78 Permission is hereby granted, free of charge, to any person obtaining a copy
79 of this software and associated documentation files (the "Software"), to deal
80 in the Software without restriction, including without limitation the rights
81 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
82 copies of the Software, and to permit persons to whom the Software is
83 furnished to do so, subject to the following conditions:
85 The above copyright notice and this permission notice shall be included in
86 all copies or substantial portions of the Software.
88 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
89 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
90 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
91 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
92 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
93 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
97 static char *version
= "$xxxterm$";
99 /* hooked functions */
100 void (*_soup_cookie_jar_add_cookie
)(SoupCookieJar
*, SoupCookie
*);
101 void (*_soup_cookie_jar_delete_cookie
)(SoupCookieJar
*,
106 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
107 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
108 #define XT_D_MOVE 0x0001
109 #define XT_D_KEY 0x0002
110 #define XT_D_TAB 0x0004
111 #define XT_D_URL 0x0008
112 #define XT_D_CMD 0x0010
113 #define XT_D_NAV 0x0020
114 #define XT_D_DOWNLOAD 0x0040
115 #define XT_D_CONFIG 0x0080
116 #define XT_D_JS 0x0100
117 #define XT_D_FAVORITE 0x0200
118 #define XT_D_PRINTING 0x0400
119 #define XT_D_COOKIE 0x0800
120 u_int32_t swm_debug
= 0
135 #define DPRINTF(x...)
136 #define DNPRINTF(n,x...)
139 #define LENGTH(x) (sizeof x / sizeof x[0])
140 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
141 ~(GDK_BUTTON1_MASK) & \
142 ~(GDK_BUTTON2_MASK) & \
143 ~(GDK_BUTTON3_MASK) & \
144 ~(GDK_BUTTON4_MASK) & \
156 TAILQ_ENTRY(tab
) entry
;
158 GtkWidget
*tab_content
;
161 GtkWidget
*uri_entry
;
162 GtkWidget
*search_entry
;
164 GtkWidget
*browser_win
;
170 GtkWidget
*js_toggle
;
174 WebKitWebHistoryItem
*item
;
175 WebKitWebBackForwardList
*bfl
;
178 WebKitNetworkRequest
*icon_request
;
179 WebKitDownload
*icon_download
;
180 GdkPixbuf
*icon_pixbuf
;
181 gchar
*icon_dest_uri
;
183 /* adjustments for browser */
186 GtkAdjustment
*adjust_h
;
187 GtkAdjustment
*adjust_v
;
193 int xtp_meaning
; /* identifies dls/favorites */
198 #define XT_HINT_NONE (0)
199 #define XT_HINT_NUMERICAL (1)
200 #define XT_HINT_ALPHANUM (2)
209 WebKitWebSettings
*settings
;
213 TAILQ_HEAD(tab_list
, tab
);
216 RB_ENTRY(history
) entry
;
220 RB_HEAD(history_list
, history
);
223 RB_ENTRY(download
) entry
;
225 WebKitDownload
*download
;
228 RB_HEAD(download_list
, download
);
231 RB_ENTRY(domain
) entry
;
233 int handy
; /* app use */
235 RB_HEAD(domain_list
, domain
);
238 TAILQ_ENTRY(undo
) entry
;
241 int back
; /* Keeps track of how many back
242 * history items there are. */
244 TAILQ_HEAD(undo_tailq
, undo
);
246 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
247 int next_download_id
= 1;
255 #define XT_NAME ("XXXTerm")
256 #define XT_DIR (".xxxterm")
257 #define XT_CACHE_DIR ("cache")
258 #define XT_CERT_DIR ("certs/")
259 #define XT_SESSIONS_DIR ("sessions/")
260 #define XT_CONF_FILE ("xxxterm.conf")
261 #define XT_FAVS_FILE ("favorites")
262 #define XT_SAVED_TABS_FILE ("main_session")
263 #define XT_RESTART_TABS_FILE ("restart_tabs")
264 #define XT_SOCKET_FILE ("socket")
265 #define XT_HISTORY_FILE ("history")
266 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
267 #define XT_CB_HANDLED (TRUE)
268 #define XT_CB_PASSTHROUGH (FALSE)
269 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
270 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
271 #define XT_DLMAN_REFRESH "10"
272 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
273 "td {overflow: hidden;}\n" \
274 "th {background-color: #cccccc}" \
275 "table {width: 90%%; border: 1px black" \
276 " solid; table-layout: fixed}\n</style>\n\n"
277 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
278 #define XT_MAX_UNDO_CLOSE_TAB (32)
281 #define SZ_KB ((uint64_t) 1024)
282 #define SZ_MB (SZ_KB * SZ_KB)
283 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
284 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
287 * xxxterm "protocol" (xtp)
288 * We use this for managing stuff like downloads and favorites. They
289 * make magical HTML pages in memory which have xxxt:// links in order
290 * to communicate with xxxterm's internals. These links take the format:
291 * xxxt://class/session_key/action/arg
293 * Don't begin xtp class/actions as 0. atoi returns that on error.
295 * Typically we have not put addition of items in this framework, as
296 * adding items is either done via an ex-command or via a keybinding instead.
299 #define XT_XTP_STR "xxxt://"
301 /* XTP classes (xxxt://<class>) */
302 #define XT_XTP_DL 1 /* downloads */
303 #define XT_XTP_HL 2 /* history */
304 #define XT_XTP_CL 3 /* cookies */
305 #define XT_XTP_FL 4 /* favorites */
307 /* XTP download actions */
308 #define XT_XTP_DL_LIST 1
309 #define XT_XTP_DL_CANCEL 2
310 #define XT_XTP_DL_REMOVE 3
312 /* XTP history actions */
313 #define XT_XTP_HL_LIST 1
314 #define XT_XTP_HL_REMOVE 2
316 /* XTP cookie actions */
317 #define XT_XTP_CL_LIST 1
318 #define XT_XTP_CL_REMOVE 2
320 /* XTP cookie actions */
321 #define XT_XTP_FL_LIST 1
322 #define XT_XTP_FL_REMOVE 2
324 /* xtp tab meanings - identifies which tabs have xtp pages in */
325 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
326 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
327 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
328 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
329 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
332 #define XT_MOVE_INVALID (0)
333 #define XT_MOVE_DOWN (1)
334 #define XT_MOVE_UP (2)
335 #define XT_MOVE_BOTTOM (3)
336 #define XT_MOVE_TOP (4)
337 #define XT_MOVE_PAGEDOWN (5)
338 #define XT_MOVE_PAGEUP (6)
339 #define XT_MOVE_HALFDOWN (7)
340 #define XT_MOVE_HALFUP (8)
341 #define XT_MOVE_LEFT (9)
342 #define XT_MOVE_FARLEFT (10)
343 #define XT_MOVE_RIGHT (11)
344 #define XT_MOVE_FARRIGHT (12)
346 #define XT_TAB_LAST (-4)
347 #define XT_TAB_FIRST (-3)
348 #define XT_TAB_PREV (-2)
349 #define XT_TAB_NEXT (-1)
350 #define XT_TAB_INVALID (0)
351 #define XT_TAB_NEW (1)
352 #define XT_TAB_DELETE (2)
353 #define XT_TAB_DELQUIT (3)
354 #define XT_TAB_OPEN (4)
355 #define XT_TAB_UNDO_CLOSE (5)
356 #define XT_TAB_SHOW (6)
357 #define XT_TAB_HIDE (7)
359 #define XT_NAV_INVALID (0)
360 #define XT_NAV_BACK (1)
361 #define XT_NAV_FORWARD (2)
362 #define XT_NAV_RELOAD (3)
363 #define XT_NAV_RELOAD_CACHE (4)
365 #define XT_FOCUS_INVALID (0)
366 #define XT_FOCUS_URI (1)
367 #define XT_FOCUS_SEARCH (2)
369 #define XT_SEARCH_INVALID (0)
370 #define XT_SEARCH_NEXT (1)
371 #define XT_SEARCH_PREV (2)
373 #define XT_PASTE_CURRENT_TAB (0)
374 #define XT_PASTE_NEW_TAB (1)
376 #define XT_FONT_SET (0)
378 #define XT_URL_SHOW (1)
379 #define XT_URL_HIDE (2)
381 #define XT_WL_TOGGLE (1<<0)
382 #define XT_WL_ENABLE (1<<1)
383 #define XT_WL_DISABLE (1<<2)
384 #define XT_WL_FQDN (1<<3) /* default */
385 #define XT_WL_TOPLEVEL (1<<4)
387 #define XT_CMD_OPEN (0)
388 #define XT_CMD_OPEN_CURRENT (1)
389 #define XT_CMD_TABNEW (2)
390 #define XT_CMD_TABNEW_CURRENT (3)
392 #define XT_SES_DONOTHING (0)
393 #define XT_SES_CLOSETABS (1)
400 TAILQ_ENTRY(mime_type
) entry
;
402 TAILQ_HEAD(mime_type_list
, mime_type
);
408 TAILQ_ENTRY(alias
) entry
;
410 TAILQ_HEAD(alias_list
, alias
);
412 /* settings that require restart */
413 int show_tabs
= 1; /* show tabs on notebook */
414 int show_url
= 1; /* show url toolbar on notebook */
415 int tabless
= 0; /* allow only 1 tab */
416 int enable_socket
= 0;
417 int single_instance
= 0; /* only allow one xxxterm to run */
418 int fancy_bar
= 1; /* fancy toolbar */
420 /* runtime settings */
421 int ctrl_click_focus
= 0; /* ctrl click gets focus */
422 int cookies_enabled
= 1; /* enable cookies */
423 int read_only_cookies
= 0; /* enable to not write cookies */
424 int enable_scripts
= 0;
425 int enable_plugins
= 0;
426 int default_font_size
= 12;
427 int window_height
= 768;
428 int window_width
= 1024;
429 int icon_size
= 2; /* 1 = smallest, 2+ = bigger */
430 unsigned refresh_interval
= 10; /* download refresh interval */
431 int enable_cookie_whitelist
= 1;
432 int enable_js_whitelist
= 1;
433 time_t session_timeout
= 3600; /* cookie session timeout */
434 int cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
435 char *ssl_ca_file
= NULL
;
436 char *resource_dir
= NULL
;
437 gboolean ssl_strict_certs
= FALSE
;
438 int append_next
= 1; /* append tab after current tab */
440 char *search_string
= NULL
;
441 char *http_proxy
= NULL
;
442 char download_dir
[PATH_MAX
];
443 char runtime_settings
[PATH_MAX
]; /* override of settings */
444 int allow_volatile_cookies
= 0;
445 int save_global_history
= 0; /* save global history to disk */
446 char *user_agent
= NULL
;
447 int save_rejected_cookies
= 0;
448 time_t session_autosave
= 0;
451 int set_download_dir(struct settings
*, char *);
452 int set_runtime_dir(struct settings
*, char *);
453 int set_cookie_policy(struct settings
*, char *);
454 int add_alias(struct settings
*, char *);
455 int add_mime_type(struct settings
*, char *);
456 int add_cookie_wl(struct settings
*, char *);
457 int add_js_wl(struct settings
*, char *);
458 void button_set_stockid(GtkWidget
*, char *);
459 GtkWidget
* create_button(char *, char *, int);
461 char *get_cookie_policy(struct settings
*);
463 char *get_download_dir(struct settings
*);
464 char *get_runtime_dir(struct settings
*);
466 void walk_alias(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
467 void walk_cookie_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
468 void walk_js_wl(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
469 void walk_mime_type(struct settings
*, void (*)(struct settings
*, char *, void *), void *);
472 int (*set
)(struct settings
*, char *);
473 char *(*get
)(struct settings
*);
474 void (*walk
)(struct settings
*, void (*cb
)(struct settings
*, char *, void *), void *);
477 struct special s_cookie
= {
483 struct special s_alias
= {
489 struct special s_mime
= {
495 struct special s_js
= {
501 struct special s_cookie_wl
= {
507 struct special s_download_dir
= {
516 #define XT_S_INVALID (0)
520 #define XT_SF_RESTART (1<<0)
521 #define XT_SF_RUNTIME (1<<1)
526 { "append_next", XT_S_INT
, 0 , &append_next
, NULL
, NULL
},
527 { "allow_volatile_cookies", XT_S_INT
, 0 , &allow_volatile_cookies
, NULL
, NULL
},
528 { "cookies_enabled", XT_S_INT
, 0 , &cookies_enabled
, NULL
, NULL
},
529 { "cookie_policy", XT_S_INT
, 0 , NULL
, NULL
, &s_cookie
},
530 { "ctrl_click_focus", XT_S_INT
, 0 , &ctrl_click_focus
, NULL
, NULL
},
531 { "default_font_size", XT_S_INT
, 0 , &default_font_size
, NULL
, NULL
},
532 { "download_dir", XT_S_STR
, 0 , NULL
, NULL
, &s_download_dir
},
533 { "enable_cookie_whitelist", XT_S_INT
, 0 , &enable_cookie_whitelist
, NULL
, NULL
},
534 { "enable_js_whitelist", XT_S_INT
, 0 , &enable_js_whitelist
, NULL
, NULL
},
535 { "enable_plugins", XT_S_INT
, 0 , &enable_plugins
, NULL
, NULL
},
536 { "enable_scripts", XT_S_INT
, 0 , &enable_scripts
, NULL
, NULL
},
537 { "enable_socket", XT_S_INT
, XT_SF_RESTART
, &enable_socket
, NULL
, NULL
},
538 { "fancy_bar", XT_S_INT
, XT_SF_RESTART
, &fancy_bar
, NULL
, NULL
},
539 { "home", XT_S_STR
, 0 , NULL
, &home
, NULL
},
540 { "http_proxy", XT_S_STR
, 0 , NULL
, &http_proxy
, NULL
},
541 { "icon_size", XT_S_INT
, 0 , &icon_size
, NULL
, NULL
},
542 { "read_only_cookies", XT_S_INT
, 0 , &read_only_cookies
, NULL
, NULL
},
543 { "refresh_interval", XT_S_INT
, 0 , &refresh_interval
, NULL
, NULL
},
544 { "resource_dir", XT_S_STR
, 0 , NULL
, &resource_dir
, NULL
},
545 { "search_string", XT_S_STR
, 0 , NULL
, &search_string
, NULL
},
546 { "save_global_history", XT_S_INT
, XT_SF_RESTART
, &save_global_history
, NULL
, NULL
},
547 { "save_rejected_cookies", XT_S_INT
, XT_SF_RESTART
, &save_rejected_cookies
, NULL
, NULL
},
548 { "session_timeout", XT_S_INT
, 0 , &session_timeout
, NULL
, NULL
},
549 { "session_autosave", XT_S_INT
, 0 , &session_autosave
, NULL
, NULL
},
550 { "single_instance", XT_S_INT
, XT_SF_RESTART
, &single_instance
, NULL
, NULL
},
551 { "show_tabs", XT_S_INT
, 0, &show_tabs
, NULL
, NULL
},
552 { "show_url", XT_S_INT
, 0, &show_url
, NULL
, NULL
},
553 { "ssl_ca_file", XT_S_STR
, 0 , NULL
, &ssl_ca_file
, NULL
},
554 { "ssl_strict_certs", XT_S_INT
, 0 , &ssl_strict_certs
, NULL
, NULL
},
555 { "user_agent", XT_S_STR
, 0 , NULL
, &user_agent
, NULL
},
556 { "window_height", XT_S_INT
, 0 , &window_height
, NULL
, NULL
},
557 { "window_width", XT_S_INT
, 0 , &window_width
, NULL
, NULL
},
559 /* runtime settings */
560 { "alias", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_alias
},
561 { "cookie_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_cookie_wl
},
562 { "js_wl", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_js
},
563 { "mime_type", XT_S_STR
, XT_SF_RUNTIME
, NULL
, NULL
, &s_mime
},
567 extern char *__progname
;
570 GtkWidget
*main_window
;
571 GtkNotebook
*notebook
;
572 GtkWidget
*arrow
, *abtn
;
573 struct tab_list tabs
;
574 struct history_list hl
;
575 struct download_list downloads
;
576 struct domain_list c_wl
;
577 struct domain_list js_wl
;
578 struct undo_tailq undos
;
580 int updating_dl_tabs
= 0;
581 int updating_hl_tabs
= 0;
582 int updating_cl_tabs
= 0;
583 int updating_fl_tabs
= 0;
585 uint64_t blocked_cookies
= 0;
586 char named_session
[PATH_MAX
];
587 void update_favicon(struct tab
*);
588 int icon_size_map(int);
591 load_webkit_string(struct tab
*t
, const char *str
)
593 /* we set this to indicate we want to manually do navaction */
594 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
595 webkit_web_view_load_string(t
->wv
, str
, NULL
, NULL
, NULL
);
599 hide_oops(struct tab
*t
)
601 gtk_widget_hide(t
->oops
);
605 hide_cmd(struct tab
*t
)
607 gtk_widget_hide(t
->cmd
);
611 show_cmd(struct tab
*t
)
613 gtk_widget_hide(t
->oops
);
614 gtk_widget_show(t
->cmd
);
618 show_oops(struct tab
*t
, const char *fmt
, ...)
627 if (vasprintf(&msg
, fmt
, ap
) == -1)
631 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
632 gtk_widget_hide(t
->cmd
);
633 gtk_widget_show(t
->oops
);
636 get_as_string(struct settings
*s
)
647 warnx("get_as_string skip %s\n", s
->name
);
648 } else if (s
->type
== XT_S_INT
)
649 r
= g_strdup_printf("%d", *s
->ival
);
650 else if (s
->type
== XT_S_STR
)
651 r
= g_strdup(*s
->sval
);
653 r
= g_strdup_printf("INVALID TYPE");
659 settings_walk(void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
664 for (i
= 0; i
< LENGTH(rs
); i
++) {
665 if (rs
[i
].s
&& rs
[i
].s
->walk
)
666 rs
[i
].s
->walk(&rs
[i
], cb
, cb_args
);
668 s
= get_as_string(&rs
[i
]);
669 cb(&rs
[i
], s
, cb_args
);
676 set_cookie_policy(struct settings
*s
, char *val
)
678 if (!strcmp(val
, "no3rdparty"))
679 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
;
680 else if (!strcmp(val
, "accept"))
681 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_ALWAYS
;
682 else if (!strcmp(val
, "reject"))
683 cookie_policy
= SOUP_COOKIE_JAR_ACCEPT_NEVER
;
691 get_cookie_policy(struct settings
*s
)
695 if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY
)
696 r
= g_strdup("no3rdparty");
697 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_ALWAYS
)
698 r
= g_strdup("accept");
699 else if (cookie_policy
== SOUP_COOKIE_JAR_ACCEPT_NEVER
)
700 r
= g_strdup("reject");
708 get_download_dir(struct settings
*s
)
710 if (download_dir
[0] == '\0')
712 return (g_strdup(download_dir
));
716 set_download_dir(struct settings
*s
, char *val
)
719 snprintf(download_dir
, sizeof download_dir
, "%s/%s",
720 pwd
->pw_dir
, &val
[1]);
722 strlcpy(download_dir
, val
, sizeof download_dir
);
729 * We use these to prevent people putting xxxt:// URLs on
730 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
732 #define XT_XTP_SES_KEY_SZ 8
733 #define XT_XTP_SES_KEY_HEX_FMT \
734 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
735 char *dl_session_key
; /* downloads */
736 char *hl_session_key
; /* history list */
737 char *cl_session_key
; /* cookie list */
738 char *fl_session_key
; /* favorites list */
740 char work_dir
[PATH_MAX
];
741 char certs_dir
[PATH_MAX
];
742 char cache_dir
[PATH_MAX
];
743 char sessions_dir
[PATH_MAX
];
744 char cookie_file
[PATH_MAX
];
745 SoupURI
*proxy_uri
= NULL
;
746 SoupSession
*session
;
747 SoupCookieJar
*s_cookiejar
;
748 SoupCookieJar
*p_cookiejar
;
749 char rc_fname
[PATH_MAX
];
751 struct mime_type_list mtl
;
752 struct alias_list aliases
;
755 void create_new_tab(char *, struct undo
*, int);
756 void delete_tab(struct tab
*);
757 void adjustfont_webkit(struct tab
*, int);
758 int run_script(struct tab
*, char *);
759 int download_rb_cmp(struct download
*, struct download
*);
760 int xtp_page_hl(struct tab
*t
, struct karg
*args
);
761 int xtp_page_dl(struct tab
*t
, struct karg
*args
);
762 int xtp_page_cl(struct tab
*t
, struct karg
*args
);
763 int xtp_page_fl(struct tab
*t
, struct karg
*args
);
766 history_rb_cmp(struct history
*h1
, struct history
*h2
)
768 return (strcmp(h1
->uri
, h2
->uri
));
770 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
773 domain_rb_cmp(struct domain
*d1
, struct domain
*d2
)
775 return (strcmp(d1
->d
, d2
->d
));
777 RB_GENERATE(domain_list
, domain
, entry
, domain_rb_cmp
);
780 * generate a session key to secure xtp commands.
781 * pass in a ptr to the key in question and it will
782 * be modified in place.
785 generate_xtp_session_key(char **key
)
787 uint8_t rand_bytes
[XT_XTP_SES_KEY_SZ
];
794 arc4random_buf(rand_bytes
, XT_XTP_SES_KEY_SZ
);
795 *key
= g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT
,
796 rand_bytes
[0], rand_bytes
[1], rand_bytes
[2], rand_bytes
[3],
797 rand_bytes
[4], rand_bytes
[5], rand_bytes
[6], rand_bytes
[7]);
799 DNPRINTF(XT_D_DOWNLOAD
, "%s: new session key '%s'\n", __func__
, *key
);
803 * validate a xtp session key.
807 validate_xtp_session_key(struct tab
*t
, char *trusted
, char *untrusted
)
809 if (strcmp(trusted
, untrusted
) != 0) {
810 show_oops(t
, "%s: xtp session key mismatch possible spoof",
819 download_rb_cmp(struct download
*e1
, struct download
*e2
)
821 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
823 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
825 struct valid_url_types
{
836 valid_url_type(char *url
)
840 for (i
= 0; i
< LENGTH(vut
); i
++)
841 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
848 print_cookie(char *msg
, SoupCookie
*c
)
854 DNPRINTF(XT_D_COOKIE
, "%s\n", msg
);
855 DNPRINTF(XT_D_COOKIE
, "name : %s\n", c
->name
);
856 DNPRINTF(XT_D_COOKIE
, "value : %s\n", c
->value
);
857 DNPRINTF(XT_D_COOKIE
, "domain : %s\n", c
->domain
);
858 DNPRINTF(XT_D_COOKIE
, "path : %s\n", c
->path
);
859 DNPRINTF(XT_D_COOKIE
, "expires : %s\n",
860 c
->expires
? soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "");
861 DNPRINTF(XT_D_COOKIE
, "secure : %d\n", c
->secure
);
862 DNPRINTF(XT_D_COOKIE
, "http_only: %d\n", c
->http_only
);
863 DNPRINTF(XT_D_COOKIE
, "====================================\n");
867 walk_alias(struct settings
*s
,
868 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
873 if (s
== NULL
|| cb
== NULL
)
874 errx(1, "walk_alias");
876 TAILQ_FOREACH(a
, &aliases
, entry
) {
877 str
= g_strdup_printf("%s --> %s", a
->a_name
, a
->a_uri
);
884 match_alias(char *url_in
)
888 char *url_out
= NULL
, *search
;
890 search
= g_strdup(url_in
);
892 if (strsep(&arg
, " \t") == NULL
)
893 errx(1, "match_alias: NULL URL");
895 TAILQ_FOREACH(a
, &aliases
, entry
) {
896 if (!strcmp(search
, a
->a_name
))
901 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
904 url_out
= g_strdup_printf(a
->a_uri
, arg
);
906 url_out
= g_strdup(a
->a_uri
);
915 guess_url_type(char *url_in
)
918 char *url_out
= NULL
;
920 url_out
= match_alias(url_in
);
924 /* XXX not sure about this heuristic */
925 if (stat(url_in
, &sb
) == 0)
926 url_out
= g_strdup_printf("file://%s", url_in
);
928 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
930 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
936 add_alias(struct settings
*s
, char *line
)
942 errx(1, "add_alias");
945 a
= g_malloc(sizeof(*a
));
947 if ((alias
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
)
948 errx(1, "add_alias: incomplete alias definition");
950 if (strlen(alias
) == 0 || strlen(l
) == 0)
951 errx(1, "add_alias: invalid alias definition");
953 a
->a_name
= g_strdup(alias
);
954 a
->a_uri
= g_strdup(l
);
956 DNPRINTF(XT_D_CONFIG
, "add_alias: %s for %s\n", a
->a_name
, a
->a_uri
);
958 TAILQ_INSERT_TAIL(&aliases
, a
, entry
);
964 add_mime_type(struct settings
*s
, char *line
)
970 /* XXX this could be smarter */
973 errx(1, "add_mime_type");
976 m
= g_malloc(sizeof(*m
));
978 if ((mime_type
= strsep(&l
, " \t,")) == NULL
|| l
== NULL
)
979 errx(1, "add_mime_type: invalid mime_type");
981 if (mime_type
[strlen(mime_type
) - 1] == '*') {
982 mime_type
[strlen(mime_type
) - 1] = '\0';
987 if (strlen(mime_type
) == 0 || strlen(l
) == 0)
988 errx(1, "add_mime_type: invalid mime_type");
990 m
->mt_type
= g_strdup(mime_type
);
991 m
->mt_action
= g_strdup(l
);
993 DNPRINTF(XT_D_CONFIG
, "add_mime_type: type %s action %s default %d\n",
994 m
->mt_type
, m
->mt_action
, m
->mt_default
);
996 TAILQ_INSERT_TAIL(&mtl
, m
, entry
);
1002 find_mime_type(char *mime_type
)
1004 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
1006 TAILQ_FOREACH(m
, &mtl
, entry
) {
1007 if (m
->mt_default
&&
1008 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
1011 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
1024 walk_mime_type(struct settings
*s
,
1025 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1027 struct mime_type
*m
;
1030 if (s
== NULL
|| cb
== NULL
)
1031 errx(1, "walk_mime_type");
1033 TAILQ_FOREACH(m
, &mtl
, entry
) {
1034 str
= g_strdup_printf("%s%s --> %s",
1036 m
->mt_default
? "*" : "",
1038 cb(s
, str
, cb_args
);
1044 wl_add(char *str
, struct domain_list
*wl
, int handy
)
1049 if (str
== NULL
|| wl
== NULL
)
1051 if (strlen(str
) < 2)
1054 DNPRINTF(XT_D_COOKIE
, "wl_add in: %s\n", str
);
1056 /* treat *.moo.com the same as .moo.com */
1057 if (str
[0] == '*' && str
[1] == '.')
1059 else if (str
[0] == '.')
1064 d
= g_malloc(sizeof *d
);
1066 d
->d
= g_strdup_printf(".%s", str
);
1068 d
->d
= g_strdup(str
);
1071 if (RB_INSERT(domain_list
, wl
, d
))
1074 DNPRINTF(XT_D_COOKIE
, "wl_add: %s\n", d
->d
);
1085 add_cookie_wl(struct settings
*s
, char *entry
)
1087 wl_add(entry
, &c_wl
, 1);
1092 walk_cookie_wl(struct settings
*s
,
1093 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1097 if (s
== NULL
|| cb
== NULL
)
1098 errx(1, "walk_cookie_wl");
1100 RB_FOREACH_REVERSE(d
, domain_list
, &c_wl
)
1101 cb(s
, d
->d
, cb_args
);
1105 walk_js_wl(struct settings
*s
,
1106 void (*cb
)(struct settings
*, char *, void *), void *cb_args
)
1110 if (s
== NULL
|| cb
== NULL
)
1111 errx(1, "walk_js_wl");
1113 RB_FOREACH_REVERSE(d
, domain_list
, &js_wl
)
1114 cb(s
, d
->d
, cb_args
);
1118 add_js_wl(struct settings
*s
, char *entry
)
1120 wl_add(entry
, &js_wl
, 1 /* persistent */);
1125 wl_find(const gchar
*search
, struct domain_list
*wl
)
1128 struct domain
*d
= NULL
, dfind
;
1131 if (search
== NULL
|| wl
== NULL
)
1133 if (strlen(search
) < 2)
1136 if (search
[0] != '.')
1137 s
= g_strdup_printf(".%s", search
);
1139 s
= g_strdup(search
);
1141 for (i
= strlen(s
) - 1; i
>= 0; i
--) {
1144 d
= RB_FIND(domain_list
, wl
, &dfind
);
1158 wl_find_uri(const gchar
*s
, struct domain_list
*wl
)
1164 if (s
== NULL
|| wl
== NULL
)
1167 if (!strncmp(s
, "http://", strlen("http://")))
1168 s
= &s
[strlen("http://")];
1169 else if (!strncmp(s
, "https://", strlen("https://")))
1170 s
= &s
[strlen("https://")];
1175 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1176 /* chop string at first slash */
1177 if (s
[i
] == '/' || s
[i
] == '\0') {
1180 r
= wl_find(ss
, wl
);
1189 get_toplevel_domain(char *domain
)
1196 if (strlen(domain
) < 2)
1199 s
= &domain
[strlen(domain
) - 1];
1200 while (s
!= domain
) {
1217 config_parse(char *filename
, int runtime
)
1220 char *line
, *cp
, *var
, *val
;
1221 size_t len
, lineno
= 0;
1223 char **s
, file
[PATH_MAX
];
1226 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
1228 if (filename
== NULL
)
1231 if (runtime
&& runtime_settings
[0] != '\0') {
1232 snprintf(file
, sizeof file
, "%s/%s",
1233 work_dir
, runtime_settings
);
1234 if (stat(file
, &sb
)) {
1235 warnx("runtime file doesn't exist, creating it");
1236 if ((f
= fopen(file
, "w")) == NULL
)
1238 fprintf(f
, "# AUTO GENERATED, DO NOT EDIT\n");
1242 strlcpy(file
, filename
, sizeof file
);
1244 if ((config
= fopen(file
, "r")) == NULL
) {
1245 warn("config_parse: cannot open %s", filename
);
1250 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
1251 if (feof(config
) || ferror(config
))
1255 cp
+= (long)strspn(cp
, WS
);
1256 if (cp
[0] == '\0') {
1262 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
1265 cp
+= (long)strspn(cp
, WS
);
1267 if ((val
= strsep(&cp
, "\0")) == NULL
)
1270 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
1273 for (i
= 0, handled
= 0; i
< LENGTH(rs
); i
++) {
1274 if (strcmp(var
, rs
[i
].name
))
1278 if (rs
[i
].s
->set(&rs
[i
], val
))
1279 errx(1, "invalid value for %s", var
);
1283 switch (rs
[i
].type
) {
1292 errx(1, "invalid sval for %s",
1301 errx(1, "invalid type for %s", var
);
1306 errx(1, "invalid conf file entry: %s=%s", var
, val
);
1315 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1321 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1325 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1328 JSStringGetUTF8CString(jsref
, s
, l
);
1329 JSStringRelease(jsref
);
1335 disable_hints(struct tab
*t
)
1337 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1338 bzero(t
->hint_num
, sizeof t
->hint_num
);
1339 run_script(t
, "vimprobable_clear()");
1341 t
->hint_mode
= XT_HINT_NONE
;
1345 enable_hints(struct tab
*t
)
1347 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
1348 run_script(t
, "vimprobable_show_hints()");
1350 t
->hint_mode
= XT_HINT_NONE
;
1353 #define XT_JS_OPEN ("open;")
1354 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1355 #define XT_JS_FIRE ("fire;")
1356 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1357 #define XT_JS_FOUND ("found;")
1358 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1361 run_script(struct tab
*t
, char *s
)
1363 JSGlobalContextRef ctx
;
1364 WebKitWebFrame
*frame
;
1366 JSValueRef val
, exception
;
1369 DNPRINTF(XT_D_JS
, "run_script: tab %d %s\n",
1370 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1372 frame
= webkit_web_view_get_main_frame(t
->wv
);
1373 ctx
= webkit_web_frame_get_global_context(frame
);
1375 str
= JSStringCreateWithUTF8CString(s
);
1376 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1377 NULL
, 0, &exception
);
1378 JSStringRelease(str
);
1380 DNPRINTF(XT_D_JS
, "run_script: val %p\n", val
);
1382 es
= js_ref_to_string(ctx
, exception
);
1383 DNPRINTF(XT_D_JS
, "run_script: exception %s\n", es
);
1387 es
= js_ref_to_string(ctx
, val
);
1388 DNPRINTF(XT_D_JS
, "run_script: val %s\n", es
);
1390 /* handle return value right here */
1391 if (!strncmp(es
, XT_JS_OPEN
, XT_JS_OPEN_LEN
)) {
1393 webkit_web_view_load_uri(t
->wv
, &es
[XT_JS_OPEN_LEN
]);
1396 if (!strncmp(es
, XT_JS_FIRE
, XT_JS_FIRE_LEN
)) {
1397 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
1398 &es
[XT_JS_FIRE_LEN
]);
1403 if (!strncmp(es
, XT_JS_FOUND
, XT_JS_FOUND_LEN
)) {
1404 if (atoi(&es
[XT_JS_FOUND_LEN
]) == 0)
1415 hint(struct tab
*t
, struct karg
*args
)
1418 DNPRINTF(XT_D_JS
, "hint: tab %d\n", t
->tab_id
);
1420 if (t
->hints_on
== 0)
1428 /* Doesn't work fully, due to the following bug:
1429 * https://bugs.webkit.org/show_bug.cgi?id=51747
1432 restore_global_history(void)
1434 char file
[PATH_MAX
];
1440 snprintf(file
, sizeof file
, "%s/%s/%s",
1441 pwd
->pw_dir
, XT_DIR
, XT_HISTORY_FILE
);
1443 if ((f
= fopen(file
, "r")) == NULL
) {
1444 warnx("%s: fopen", __func__
);
1449 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1450 if (feof(f
) || ferror(f
))
1453 if ((title
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1454 if (feof(f
) || ferror(f
)) {
1456 warnx("%s: broken history file\n", __func__
);
1460 if (uri
&& strlen(uri
) && title
&& strlen(title
)) {
1461 webkit_web_history_item_new_with_data(uri
, title
);
1462 h
= g_malloc(sizeof(struct history
));
1463 h
->uri
= g_strdup(uri
);
1464 h
->title
= g_strdup(title
);
1465 RB_INSERT(history_list
, &hl
, h
);
1467 warnx("%s: failed to restore history\n", __func__
);
1483 save_global_history_to_disk(struct tab
*t
)
1485 char file
[PATH_MAX
];
1489 snprintf(file
, sizeof file
, "%s/%s/%s",
1490 pwd
->pw_dir
, XT_DIR
, XT_HISTORY_FILE
);
1492 if ((f
= fopen(file
, "w")) == NULL
) {
1493 show_oops(t
, "%s: global history file: %s",
1494 __func__
, strerror(errno
));
1498 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
1499 if (h
->uri
&& h
->title
)
1500 fprintf(f
, "%s\n%s\n", h
->uri
, h
->title
);
1509 quit(struct tab
*t
, struct karg
*args
)
1511 if (save_global_history
)
1512 save_global_history_to_disk(t
);
1520 open_tabs(struct tab
*t
, struct karg
*a
)
1522 char file
[PATH_MAX
];
1526 struct tab
*ti
, *tt
;
1531 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
1532 if ((f
= fopen(file
, "r")) == NULL
)
1535 ti
= TAILQ_LAST(&tabs
, tab_list
);
1538 if ((uri
= fparseln(f
, NULL
, NULL
, NULL
, 0)) == NULL
)
1539 if (feof(f
) || ferror(f
))
1542 /* retrieve session name */
1543 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
1544 strlcpy(named_session
,
1545 &uri
[strlen(XT_SAVE_SESSION_ID
)],
1546 sizeof named_session
);
1550 if (uri
&& strlen(uri
))
1551 create_new_tab(uri
, NULL
, 1);
1557 /* close open tabs */
1558 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
1560 tt
= TAILQ_FIRST(&tabs
);
1579 restore_saved_tabs(void)
1581 char file
[PATH_MAX
];
1582 int unlink_file
= 0;
1587 snprintf(file
, sizeof file
, "%s/%s",
1588 sessions_dir
, XT_RESTART_TABS_FILE
);
1589 if (stat(file
, &sb
) == -1)
1590 a
.s
= XT_SAVED_TABS_FILE
;
1593 a
.s
= XT_RESTART_TABS_FILE
;
1596 a
.i
= XT_SES_DONOTHING
;
1597 rv
= open_tabs(NULL
, &a
);
1606 save_tabs(struct tab
*t
, struct karg
*a
)
1608 char file
[PATH_MAX
];
1611 WebKitWebFrame
*frame
;
1619 snprintf(file
, sizeof file
, "%s/%s",
1620 sessions_dir
, named_session
);
1622 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, a
->s
);
1624 if ((f
= fopen(file
, "w")) == NULL
) {
1625 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
1629 /* save session name */
1630 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
1632 /* save tabs, in the order they are arranged in the notebook */
1633 TAILQ_FOREACH(ti
, &tabs
, entry
)
1636 arr
= g_malloc0(len
* sizeof(gchar
*));
1638 TAILQ_FOREACH(ti
, &tabs
, entry
) {
1639 frame
= webkit_web_view_get_main_frame(ti
->wv
);
1640 uri
= webkit_web_frame_get_uri(frame
);
1641 if (uri
&& strlen(uri
) > 0)
1642 arr
[gtk_notebook_page_num(notebook
, ti
->vbox
)] = (gchar
*)uri
;
1645 for (i
= 0; i
< len
; i
++)
1647 fprintf(f
, "%s\n", arr
[i
]);
1656 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
1668 yank_uri(struct tab
*t
, struct karg
*args
)
1670 WebKitWebFrame
*frame
;
1672 GtkClipboard
*clipboard
;
1674 frame
= webkit_web_view_get_main_frame(t
->wv
);
1675 uri
= webkit_web_frame_get_uri(frame
);
1679 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
1680 gtk_clipboard_set_text(clipboard
, uri
, -1);
1691 paste_uri_cb(GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
1693 struct paste_args
*pap
;
1698 pap
= (struct paste_args
*)data
;
1701 case XT_PASTE_CURRENT_TAB
:
1702 webkit_web_view_load_uri(pap
->t
->wv
, text
);
1704 case XT_PASTE_NEW_TAB
:
1705 create_new_tab((char *)text
, NULL
, 1);
1713 paste_uri(struct tab
*t
, struct karg
*args
)
1715 GtkClipboard
*clipboard
;
1716 struct paste_args
*pap
;
1718 pap
= g_malloc(sizeof(struct paste_args
));
1723 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
1724 gtk_clipboard_request_text(clipboard
, paste_uri_cb
, pap
);
1730 find_domain(const char *s
, int add_dot
)
1733 char *r
= NULL
, *ss
= NULL
;
1738 if (!strncmp(s
, "http://", strlen("http://")))
1739 s
= &s
[strlen("http://")];
1740 else if (!strncmp(s
, "https://", strlen("https://")))
1741 s
= &s
[strlen("https://")];
1747 for (i
= 0; i
< strlen(ss
) + 1 /* yes er need this */; i
++)
1748 /* chop string at first slash */
1749 if (ss
[i
] == '/' || ss
[i
] == '\0') {
1752 r
= g_strdup_printf(".%s", ss
);
1763 toggle_cwl(struct tab
*t
, struct karg
*args
)
1765 WebKitWebFrame
*frame
;
1768 char *dom
= NULL
, *dom_toggle
= NULL
;
1774 frame
= webkit_web_view_get_main_frame(t
->wv
);
1775 uri
= (char *)webkit_web_frame_get_uri(frame
);
1776 dom
= find_domain(uri
, 1);
1777 d
= wl_find(dom
, &c_wl
);
1783 if (args
->i
& XT_WL_TOGGLE
)
1785 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
1787 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
1790 if (args
->i
& XT_WL_TOPLEVEL
)
1791 dom_toggle
= get_toplevel_domain(dom
);
1796 /* enable cookies for domain */
1797 wl_add(dom_toggle
, &c_wl
, 0);
1799 /* disable cookies for domain */
1800 RB_REMOVE(domain_list
, &c_wl
, d
);
1802 webkit_web_view_reload(t
->wv
);
1809 toggle_js(struct tab
*t
, struct karg
*args
)
1812 WebKitWebFrame
*frame
;
1815 char *dom
= NULL
, *dom_toggle
= NULL
;
1820 g_object_get((GObject
*)t
->settings
,
1821 "enable-scripts", &es
, (char *)NULL
);
1822 if (args
->i
& XT_WL_TOGGLE
)
1824 else if ((args
->i
& XT_WL_ENABLE
) && es
!= 1)
1826 else if ((args
->i
& XT_WL_DISABLE
) && es
!= 0)
1831 frame
= webkit_web_view_get_main_frame(t
->wv
);
1832 uri
= (char *)webkit_web_frame_get_uri(frame
);
1833 dom
= find_domain(uri
, 1);
1834 if (uri
== NULL
|| dom
== NULL
) {
1835 show_oops(t
, "Can't toggle domain in JavaScript white list");
1839 if (args
->i
& XT_WL_TOPLEVEL
)
1840 dom_toggle
= get_toplevel_domain(dom
);
1845 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PLAY
);
1846 wl_add(dom_toggle
, &js_wl
, 0 /* session */);
1848 d
= wl_find(dom_toggle
, &js_wl
);
1850 RB_REMOVE(domain_list
, &js_wl
, d
);
1851 button_set_stockid(t
->js_toggle
, GTK_STOCK_MEDIA_PAUSE
);
1853 g_object_set((GObject
*)t
->settings
,
1854 "enable-scripts", es
, (char *)NULL
);
1855 webkit_web_view_set_settings(t
->wv
, t
->settings
);
1856 webkit_web_view_reload(t
->wv
);
1864 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
1868 a
.i
= XT_WL_TOGGLE
| XT_WL_FQDN
;
1873 toggle_src(struct tab
*t
, struct karg
*args
)
1880 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
1881 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
1882 webkit_web_view_reload(t
->wv
);
1888 focus(struct tab
*t
, struct karg
*args
)
1890 if (t
== NULL
|| args
== NULL
)
1896 if (args
->i
== XT_FOCUS_URI
)
1897 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
1898 else if (args
->i
== XT_FOCUS_SEARCH
)
1899 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
1905 stats(struct tab
*t
, struct karg
*args
)
1907 char *stats
, *s
, line
[64 * 1024];
1908 uint64_t line_count
= 0;
1915 if (save_rejected_cookies
) {
1916 if ((r_cookie_f
= fopen(rc_fname
, "r"))) {
1918 s
= fgets(line
, sizeof line
, r_cookie_f
);
1919 if (s
== NULL
|| feof(r_cookie_f
) ||
1925 snprintf(line
, sizeof line
,
1926 "<br>Cookies blocked(*) total: %llu", line_count
);
1928 show_oops(t
, "Can't open blocked cookies file: %s",
1932 stats
= g_strdup_printf(XT_DOCTYPE
1935 "<title>Statistics</title>"
1937 "<h1>Statistics</h1>"
1939 "Cookies blocked(*) this session: %llu"
1941 "<p><small><b>*</b> results vary based on settings"
1947 load_webkit_string(t
, stats
);
1954 about(struct tab
*t
, struct karg
*args
)
1961 about
= g_strdup_printf(XT_DOCTYPE
1964 "<title>About</title>"
1968 "<b>Version: %s</b><p>"
1971 "<li>Marco Peereboom <marco@peereboom.us></li>"
1972 "<li>Stevan Andjelkovic <stevan@student.chalmers.se></li>"
1973 "<li>Edd Barrett <vext01@gmail.com> </li>"
1974 "<li>Todd T. Fries <todd@fries.net> </li>"
1976 "Copyrights and licenses can be found on the XXXterm "
1977 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
1983 load_webkit_string(t
, about
);
1990 help(struct tab
*t
, struct karg
*args
)
2000 "<title>XXXterm</title>"
2001 "<meta http-equiv=\"REFRESH\" content=\"0;"
2002 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2005 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2006 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2007 "cgi-bin/man-cgi?xxxterm</a>"
2012 load_webkit_string(t
, help
);
2018 * update all favorite tabs apart from one. Pass NULL if
2019 * you want to update all.
2022 update_favorite_tabs(struct tab
*apart_from
)
2025 if (!updating_fl_tabs
) {
2026 updating_fl_tabs
= 1; /* stop infinite recursion */
2027 TAILQ_FOREACH(t
, &tabs
, entry
)
2028 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_FL
)
2029 && (t
!= apart_from
))
2030 xtp_page_fl(t
, NULL
);
2031 updating_fl_tabs
= 0;
2035 /* show a list of favorites (bookmarks) */
2037 xtp_page_fl(struct tab
*t
, struct karg
*args
)
2039 char file
[PATH_MAX
];
2041 char *uri
= NULL
, *title
= NULL
;
2042 size_t len
, lineno
= 0;
2044 char *header
, *body
, *tmp
, *html
= NULL
;
2046 DNPRINTF(XT_D_FAVORITE
, "%s:", __func__
);
2049 warn("%s: bad param", __func__
);
2051 /* mark tab as favorite list */
2052 t
->xtp_meaning
= XT_XTP_TAB_MEANING_FL
;
2054 /* new session key */
2055 if (!updating_fl_tabs
)
2056 generate_xtp_session_key(&fl_session_key
);
2058 /* open favorites */
2059 snprintf(file
, sizeof file
, "%s/%s/%s",
2060 pwd
->pw_dir
, XT_DIR
, XT_FAVS_FILE
);
2061 if ((f
= fopen(file
, "r")) == NULL
) {
2062 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2067 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
2068 "<title>Favorites</title>\n"
2071 "<h1>Favorites</h1>\n",
2075 body
= g_strdup_printf("<div align='center'><table><tr>"
2076 "<th style='width: 4%%'>#</th><th>Link</th>"
2077 "<th style='width: 15%%'>Remove</th></tr>\n");
2080 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2081 if (feof(f
) || ferror(f
))
2089 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
2090 if (feof(f
) || ferror(f
)) {
2091 errx(0, "%s: can't parse favorites\n",
2098 body
= g_strdup_printf("%s<tr>"
2100 "<td><a href='%s'>%s</a></td>"
2101 "<td style='text-align: center'>"
2102 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2104 body
, i
, uri
, title
,
2105 XT_XTP_STR
, XT_XTP_FL
, fl_session_key
, XT_XTP_FL_REMOVE
, i
);
2117 /* if none, say so */
2120 body
= g_strdup_printf("%s<tr>"
2121 "<td colspan='3' style='text-align: center'>"
2122 "No favorites - To add one use the 'favadd' command."
2123 "</td></tr>", body
);
2134 html
= g_strdup_printf("%s%s</table></div></html>",
2136 load_webkit_string(t
, html
);
2139 update_favorite_tabs(t
);
2152 getparams(char *cmd
, char *cmp
)
2157 if (!strncmp(cmd
, cmp
, strlen(cmp
))) {
2158 rv
= cmd
+ strlen(cmp
);
2161 if (strlen(rv
) == 0)
2170 show_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2171 size_t cert_count
, char *title
)
2173 gnutls_datum_t cinfo
;
2174 char *tmp
, *header
, *body
, *footer
;
2177 header
= g_strdup_printf("<title>%s</title><html><body>", title
);
2178 footer
= g_strdup("</body></html>");
2179 body
= g_strdup("");
2181 for (i
= 0; i
< cert_count
; i
++) {
2182 if (gnutls_x509_crt_print(certs
[i
], GNUTLS_CRT_PRINT_FULL
,
2187 body
= g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2188 body
, i
, cinfo
.data
);
2189 gnutls_free(cinfo
.data
);
2193 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
2197 load_webkit_string(t
, tmp
);
2202 ca_cmd(struct tab
*t
, struct karg
*args
)
2205 int rv
= 1, certs
= 0, certs_read
;
2208 gnutls_x509_crt_t
*c
= NULL
;
2209 char *certs_buf
= NULL
, *s
;
2211 /* yeah yeah stat race */
2212 if (stat(ssl_ca_file
, &sb
)) {
2213 show_oops(t
, "no CA file: %s", ssl_ca_file
);
2217 if ((f
= fopen(ssl_ca_file
, "r")) == NULL
) {
2218 show_oops(t
, "Can't open CA file: %s", strerror(errno
));
2222 certs_buf
= g_malloc(sb
.st_size
+ 1);
2223 if (fread(certs_buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
2224 show_oops(t
, "Can't read CA file: %s", strerror(errno
));
2227 certs_buf
[sb
.st_size
] = '\0';
2230 while ((s
= strstr(s
, "BEGIN CERTIFICATE"))) {
2232 s
+= strlen("BEGIN CERTIFICATE");
2235 bzero(&dt
, sizeof dt
);
2236 dt
.data
= certs_buf
;
2237 dt
.size
= sb
.st_size
;
2238 c
= g_malloc(sizeof(gnutls_x509_crt_t
) * certs
);
2239 certs_read
= gnutls_x509_crt_list_import(c
, &certs
, &dt
, GNUTLS_X509_FMT_PEM
, 0);
2240 if (certs_read
<= 0) {
2241 show_oops(t
, "No cert(s) available");
2244 show_certs(t
, c
, certs_read
, "Certificate Authority Certificates");
2257 connect_socket_from_uri(char *uri
, char *domain
, size_t domain_sz
)
2260 struct addrinfo hints
, *res
= NULL
, *ai
;
2264 if (uri
&& !g_str_has_prefix(uri
, "https://"))
2267 su
= soup_uri_new(uri
);
2270 if (!SOUP_URI_VALID_FOR_HTTP(su
))
2273 snprintf(port
, sizeof port
, "%d", su
->port
);
2274 bzero(&hints
, sizeof(struct addrinfo
));
2275 hints
.ai_flags
= AI_CANONNAME
;
2276 hints
.ai_family
= AF_UNSPEC
;
2277 hints
.ai_socktype
= SOCK_STREAM
;
2279 if (getaddrinfo(su
->host
, port
, &hints
, &res
))
2282 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
2283 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
2286 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
2289 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
2293 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) < 0)
2298 strlcpy(domain
, su
->host
, domain_sz
);
2309 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
2312 gnutls_deinit(gsession
);
2314 gnutls_certificate_free_credentials(xcred
);
2320 start_tls(struct tab
*t
, int s
, gnutls_session_t
*gs
,
2321 gnutls_certificate_credentials_t
*xc
)
2323 gnutls_certificate_credentials_t xcred
;
2324 gnutls_session_t gsession
;
2327 if (gs
== NULL
|| xc
== NULL
)
2333 gnutls_certificate_allocate_credentials(&xcred
);
2334 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
2335 GNUTLS_X509_FMT_PEM
);
2336 gnutls_init(&gsession
, GNUTLS_CLIENT
);
2337 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
2338 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
2339 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
2340 if ((rv
= gnutls_handshake(gsession
)) < 0) {
2341 show_oops(t
, "gnutls_handshake failed %d fatal %d %s",
2343 gnutls_error_is_fatal(rv
),
2344 gnutls_strerror_name(rv
));
2345 stop_tls(gsession
, xcred
);
2349 gnutls_credentials_type_t cred
;
2350 cred
= gnutls_auth_get_type(gsession
);
2351 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
2352 stop_tls(gsession
, xcred
);
2364 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
2368 const gnutls_datum_t
*cl
;
2369 gnutls_x509_crt_t
*all_certs
;
2372 if (certs
== NULL
|| cert_count
== NULL
)
2374 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
2376 cl
= gnutls_certificate_get_peers(gsession
, &len
);
2380 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
2381 for (i
= 0; i
< len
; i
++) {
2382 gnutls_x509_crt_init(&all_certs
[i
]);
2383 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
2384 GNUTLS_X509_FMT_PEM
< 0)) {
2398 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
2402 for (i
= 0; i
< cert_count
; i
++)
2403 gnutls_x509_crt_deinit(certs
[i
]);
2408 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
2409 size_t cert_count
, char *domain
)
2412 char cert_buf
[64 * 1024], file
[PATH_MAX
];
2417 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
2420 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
2421 if ((f
= fopen(file
, "w")) == NULL
) {
2422 show_oops(t
, "Can't create cert file %s %s",
2423 file
, strerror(errno
));
2427 for (i
= 0; i
< cert_count
; i
++) {
2428 cert_buf_sz
= sizeof cert_buf
;
2429 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
2430 cert_buf
, &cert_buf_sz
)) {
2431 show_oops(t
, "gnutls_x509_crt_export failed");
2434 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
2435 show_oops(t
, "Can't write certs: %s", strerror(errno
));
2440 /* not the best spot but oh well */
2441 gdk_color_parse("lightblue", &color
);
2442 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
2448 load_compare_cert(struct tab
*t
, struct karg
*args
)
2450 WebKitWebFrame
*frame
;
2451 char *uri
, domain
[8182], file
[PATH_MAX
];
2452 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
2453 int s
= -1, rv
= 1, i
;
2457 gnutls_session_t gsession
;
2458 gnutls_x509_crt_t
*certs
;
2459 gnutls_certificate_credentials_t xcred
;
2464 frame
= webkit_web_view_get_main_frame(t
->wv
);
2465 uri
= (char *)webkit_web_frame_get_uri(frame
);
2466 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1)
2470 if (start_tls(t
, s
, &gsession
, &xcred
)) {
2471 show_oops(t
, "Start TLS failed");
2476 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
2477 show_oops(t
, "Can't get connection certificates");
2481 snprintf(file
, sizeof file
, "%s/%s", certs_dir
, domain
);
2482 if ((f
= fopen(file
, "r")) == NULL
)
2485 for (i
= 0; i
< cert_count
; i
++) {
2486 cert_buf_sz
= sizeof cert_buf
;
2487 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
2488 cert_buf
, &cert_buf_sz
)) {
2491 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1) {
2492 rv
= -1; /* critical */
2495 if (bcmp(r_cert_buf
, cert_buf
, sizeof cert_buf_sz
)) {
2496 rv
= -1; /* critical */
2505 free_connection_certs(certs
, cert_count
);
2507 /* we close the socket first for speed */
2510 stop_tls(gsession
, xcred
);
2516 cert_cmd(struct tab
*t
, struct karg
*args
)
2518 WebKitWebFrame
*frame
;
2519 char *uri
, *action
, domain
[8182];
2522 gnutls_session_t gsession
;
2523 gnutls_x509_crt_t
*certs
;
2524 gnutls_certificate_credentials_t xcred
;
2529 if ((action
= getparams(args
->s
, "cert")))
2534 frame
= webkit_web_view_get_main_frame(t
->wv
);
2535 uri
= (char *)webkit_web_frame_get_uri(frame
);
2536 if (uri
&& strlen(uri
) == 0) {
2537 show_oops(t
, "Invalid URI");
2540 if ((s
= connect_socket_from_uri(uri
, domain
, sizeof domain
)) == -1) {
2541 show_oops(t
, "Invalid certidicate URI: %s", uri
);
2546 if (start_tls(t
, s
, &gsession
, &xcred
)) {
2547 show_oops(t
, "Start TLS failed");
2552 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
2553 show_oops(t
, "get_connection_certs failed");
2557 if (!strcmp(action
, "show"))
2558 show_certs(t
, certs
, cert_count
, "Certificate Chain");
2559 else if (!strcmp(action
, "save"))
2560 save_certs(t
, certs
, cert_count
, domain
);
2562 show_oops(t
, "Invalid command: %s", action
);
2564 free_connection_certs(certs
, cert_count
);
2566 /* we close the socket first for speed */
2569 stop_tls(gsession
, xcred
);
2575 remove_cookie(int index
)
2581 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
2583 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2585 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
2589 print_cookie("remove cookie", c
);
2590 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
2595 soup_cookies_free(cf
);
2601 wl_show(struct tab
*t
, char *args
, char *title
, struct domain_list
*wl
)
2604 char *tmp
, *header
, *body
, *footer
;
2605 int p_js
= 0, s_js
= 0;
2607 /* we set this to indicate we want to manually do navaction */
2608 t
->item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2610 if (g_str_has_prefix(args
, "show a") ||
2611 !strcmp(args
, "show")) {
2615 } else if (g_str_has_prefix(args
, "show p")) {
2616 /* show persistent */
2618 } else if (g_str_has_prefix(args
, "show s")) {
2624 header
= g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2626 footer
= g_strdup("</body></html>");
2627 body
= g_strdup("");
2632 body
= g_strdup_printf("%s<h2>Persistent</h2>", body
);
2634 RB_FOREACH(d
, domain_list
, wl
) {
2638 body
= g_strdup_printf("%s%s<br>", body
, d
->d
);
2646 body
= g_strdup_printf("%s<h2>Session</h2>", body
);
2648 RB_FOREACH(d
, domain_list
, wl
) {
2652 body
= g_strdup_printf("%s%s", body
, d
->d
);
2657 tmp
= g_strdup_printf("%s%s%s", header
, body
, footer
);
2661 load_webkit_string(t
, tmp
);
2667 wl_save(struct tab
*t
, struct karg
*args
, int js
)
2669 char file
[PATH_MAX
];
2671 char *line
= NULL
, *lt
= NULL
;
2673 WebKitWebFrame
*frame
;
2674 char *dom
= NULL
, *uri
, *dom_save
= NULL
;
2681 if (t
== NULL
|| args
== NULL
)
2684 if (runtime_settings
[0] == '\0')
2687 snprintf(file
, sizeof file
, "%s/%s", work_dir
, runtime_settings
);
2688 if ((f
= fopen(file
, "r+")) == NULL
)
2691 frame
= webkit_web_view_get_main_frame(t
->wv
);
2692 uri
= (char *)webkit_web_frame_get_uri(frame
);
2693 dom
= find_domain(uri
, 1);
2694 if (uri
== NULL
|| dom
== NULL
) {
2695 show_oops(t
, "Can't add domain to %s white list",
2696 js
? "JavaScript" : "cookie");
2700 if (g_str_has_prefix(args
->s
, "save d")) {
2702 if ((dom_save
= get_toplevel_domain(dom
)) == NULL
) {
2703 show_oops(t
, "invalid domain: %s", dom
);
2706 flags
= XT_WL_TOPLEVEL
;
2707 } else if (g_str_has_prefix(args
->s
, "save f") ||
2708 !strcmp(args
->s
, "save")) {
2713 show_oops(t
, "invalid command: %s", args
->s
);
2717 lt
= g_strdup_printf("%s=%s", js
? "js_wl" : "cookie_wl", dom_save
);
2720 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
2723 if (!strcmp(line
, lt
))
2729 fprintf(f
, "%s\n", lt
);
2734 d
= wl_find(dom_save
, &js_wl
);
2737 d
= wl_find(dom_save
, &c_wl
);
2740 /* find and add to persistent jar */
2741 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2742 for (;cf
; cf
= cf
->next
) {
2744 if (!strcmp(dom_save
, ci
->domain
) ||
2745 !strcmp(&dom_save
[1], ci
->domain
)) /* deal with leading . */ {
2746 c
= soup_cookie_copy(ci
);
2747 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
2750 soup_cookies_free(cf
);
2768 cookie_cmd(struct tab
*t
, struct karg
*args
)
2773 if ((cmd
= getparams(args
->s
, "cookie")))
2779 if (g_str_has_prefix(cmd
, "show")) {
2780 wl_show(t
, cmd
, "Cookie White List", &c_wl
);
2781 } else if (g_str_has_prefix(cmd
, "save")) {
2784 } else if (g_str_has_prefix(cmd
, "toggle")) {
2786 if (g_str_has_prefix(cmd
, "toggle d"))
2787 a
.i
|= XT_WL_TOPLEVEL
;
2791 } else if (g_str_has_prefix(cmd
, "delete")) {
2792 show_oops(t
, "'cookie delete' currently unimplemented");
2794 show_oops(t
, "unknown cookie command: %s", cmd
);
2800 js_cmd(struct tab
*t
, struct karg
*args
)
2805 if ((cmd
= getparams(args
->s
, "js")))
2810 if (g_str_has_prefix(cmd
, "show")) {
2811 wl_show(t
, cmd
, "JavaScript White List", &js_wl
);
2812 } else if (g_str_has_prefix(cmd
, "save")) {
2815 } else if (g_str_has_prefix(cmd
, "toggle")) {
2817 if (g_str_has_prefix(cmd
, "toggle d"))
2818 a
.i
|= XT_WL_TOPLEVEL
;
2822 } else if (g_str_has_prefix(cmd
, "delete")) {
2823 show_oops(t
, "'js delete' currently unimplemented");
2825 show_oops(t
, "unknown js command: %s", cmd
);
2831 add_favorite(struct tab
*t
, struct karg
*args
)
2833 char file
[PATH_MAX
];
2836 size_t urilen
, linelen
;
2837 WebKitWebFrame
*frame
;
2838 const gchar
*uri
, *title
;
2843 /* don't allow adding of xtp pages to favorites */
2844 if (t
->xtp_meaning
!= XT_XTP_TAB_MEANING_NORMAL
) {
2845 show_oops(t
, "%s: can't add xtp pages to favorites", __func__
);
2849 snprintf(file
, sizeof file
, "%s/%s/%s",
2850 pwd
->pw_dir
, XT_DIR
, XT_FAVS_FILE
);
2851 if ((f
= fopen(file
, "r+")) == NULL
) {
2852 show_oops(t
, "Can't open favorites file: %s", strerror(errno
));
2856 title
= webkit_web_view_get_title(t
->wv
);
2857 frame
= webkit_web_view_get_main_frame(t
->wv
);
2858 uri
= webkit_web_frame_get_uri(frame
);
2862 if (title
== NULL
|| uri
== NULL
) {
2863 show_oops(t
, "can't add page to favorites");
2867 urilen
= strlen(uri
);
2870 line
= fparseln(f
, &linelen
, NULL
, NULL
, 0);
2871 if (linelen
== urilen
&& !strcmp(line
, uri
))
2877 fprintf(f
, "\n%s\n%s", title
, uri
);
2883 update_favorite_tabs(NULL
);
2889 navaction(struct tab
*t
, struct karg
*args
)
2891 WebKitWebHistoryItem
*item
;
2893 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
2894 t
->tab_id
, args
->i
);
2897 if (args
->i
== XT_NAV_BACK
)
2898 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2900 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
2902 return (XT_CB_PASSTHROUGH
);;
2903 webkit_web_view_load_uri(t
->wv
, webkit_web_history_item_get_uri(item
));
2905 return (XT_CB_PASSTHROUGH
);
2910 webkit_web_view_go_back(t
->wv
);
2912 case XT_NAV_FORWARD
:
2913 webkit_web_view_go_forward(t
->wv
);
2916 webkit_web_view_reload(t
->wv
);
2918 case XT_NAV_RELOAD_CACHE
:
2919 webkit_web_view_reload_bypass_cache(t
->wv
);
2922 return (XT_CB_PASSTHROUGH
);
2926 move(struct tab
*t
, struct karg
*args
)
2928 GtkAdjustment
*adjust
;
2929 double pi
, si
, pos
, ps
, upper
, lower
, max
;
2934 case XT_MOVE_BOTTOM
:
2936 case XT_MOVE_PAGEDOWN
:
2937 case XT_MOVE_PAGEUP
:
2938 case XT_MOVE_HALFDOWN
:
2939 case XT_MOVE_HALFUP
:
2940 adjust
= t
->adjust_v
;
2943 adjust
= t
->adjust_h
;
2947 pos
= gtk_adjustment_get_value(adjust
);
2948 ps
= gtk_adjustment_get_page_size(adjust
);
2949 upper
= gtk_adjustment_get_upper(adjust
);
2950 lower
= gtk_adjustment_get_lower(adjust
);
2951 si
= gtk_adjustment_get_step_increment(adjust
);
2952 pi
= gtk_adjustment_get_page_increment(adjust
);
2955 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2956 "max %f si %f pi %f\n",
2957 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
2958 pos
, ps
, upper
, lower
, max
, si
, pi
);
2964 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2969 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2971 case XT_MOVE_BOTTOM
:
2972 case XT_MOVE_FARRIGHT
:
2973 gtk_adjustment_set_value(adjust
, max
);
2976 case XT_MOVE_FARLEFT
:
2977 gtk_adjustment_set_value(adjust
, lower
);
2979 case XT_MOVE_PAGEDOWN
:
2981 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2983 case XT_MOVE_PAGEUP
:
2985 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2987 case XT_MOVE_HALFDOWN
:
2989 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2991 case XT_MOVE_HALFUP
:
2993 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2996 return (XT_CB_PASSTHROUGH
);
2999 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
3001 return (XT_CB_HANDLED
);
3005 url_set_visibility(void)
3009 TAILQ_FOREACH(t
, &tabs
, entry
) {
3010 if (show_url
== 0) {
3011 gtk_widget_hide(t
->toolbar
);
3012 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
3014 gtk_widget_show(t
->toolbar
);
3019 notebook_tab_set_visibility(GtkNotebook
*notebook
)
3022 gtk_notebook_set_show_tabs(notebook
, FALSE
);
3024 gtk_notebook_set_show_tabs(notebook
, TRUE
);
3028 fullscreen(struct tab
*t
, struct karg
*args
)
3030 DNPRINTF(XT_D_TAB
, "urlaction: %p %d %d\n", t
, args
->i
, t
->focus_wv
);
3033 return (XT_CB_PASSTHROUGH
);
3036 show_url
= show_tabs
= 1;
3038 show_url
= show_tabs
= 0;
3040 url_set_visibility();
3041 notebook_tab_set_visibility(notebook
);
3043 return (XT_CB_HANDLED
);
3047 urlaction(struct tab
*t
, struct karg
*args
)
3049 int rv
= XT_CB_HANDLED
;
3051 DNPRINTF(XT_D_TAB
, "urlaction: %p %d %d\n", t
, args
->i
, t
->focus_wv
);
3054 return (XT_CB_PASSTHROUGH
);
3058 if (show_url
== 0) {
3060 url_set_visibility();
3064 if (show_url
== 1) {
3066 url_set_visibility();
3074 tabaction(struct tab
*t
, struct karg
*args
)
3076 int rv
= XT_CB_HANDLED
;
3077 char *url
= NULL
, *newuri
= NULL
;
3080 DNPRINTF(XT_D_TAB
, "tabaction: %p %d %d\n", t
, args
->i
, t
->focus_wv
);
3083 return (XT_CB_PASSTHROUGH
);
3087 if ((url
= getparams(args
->s
, "tabnew")))
3088 create_new_tab(url
, NULL
, 1);
3090 create_new_tab(NULL
, NULL
, 1);
3095 case XT_TAB_DELQUIT
:
3096 if (gtk_notebook_get_n_pages(notebook
) > 1)
3102 if ((url
= getparams(args
->s
, "open")) ||
3103 ((url
= getparams(args
->s
, "op"))) ||
3104 ((url
= getparams(args
->s
, "o"))))
3107 rv
= XT_CB_PASSTHROUGH
;
3111 if (valid_url_type(url
)) {
3112 newuri
= guess_url_type(url
);
3115 webkit_web_view_load_uri(t
->wv
, url
);
3120 if (show_tabs
== 0) {
3122 notebook_tab_set_visibility(notebook
);
3126 if (show_tabs
== 1) {
3128 notebook_tab_set_visibility(notebook
);
3131 case XT_TAB_UNDO_CLOSE
:
3132 if (undo_count
== 0) {
3133 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close", __func__
);
3137 u
= TAILQ_FIRST(&undos
);
3138 create_new_tab(u
->uri
, u
, 1);
3140 TAILQ_REMOVE(&undos
, u
, entry
);
3142 /* u->history is freed in create_new_tab() */
3147 rv
= XT_CB_PASSTHROUGH
;
3161 resizetab(struct tab
*t
, struct karg
*args
)
3163 if (t
== NULL
|| args
== NULL
)
3164 errx(1, "resizetab");
3166 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
3167 t
->tab_id
, args
->i
);
3169 adjustfont_webkit(t
, args
->i
);
3171 return (XT_CB_HANDLED
);
3175 movetab(struct tab
*t
, struct karg
*args
)
3180 if (t
== NULL
|| args
== NULL
)
3183 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
3184 t
->tab_id
, args
->i
);
3186 if (args
->i
== XT_TAB_INVALID
)
3187 return (XT_CB_PASSTHROUGH
);
3189 if (args
->i
< XT_TAB_INVALID
) {
3190 /* next or previous tab */
3191 if (TAILQ_EMPTY(&tabs
))
3192 return (XT_CB_PASSTHROUGH
);
3196 /* if at the last page, loop around to the first */
3197 if (gtk_notebook_get_current_page(notebook
) ==
3198 gtk_notebook_get_n_pages(notebook
) - 1)
3199 gtk_notebook_set_current_page(notebook
, 0);
3201 gtk_notebook_next_page(notebook
);
3204 /* if at the first page, loop around to the last */
3205 if (gtk_notebook_current_page(notebook
) == 0)
3206 gtk_notebook_set_current_page(notebook
,
3207 gtk_notebook_get_n_pages(notebook
) - 1);
3209 gtk_notebook_prev_page(notebook
);
3212 gtk_notebook_set_current_page(notebook
, 0);
3215 gtk_notebook_set_current_page(notebook
, -1);
3218 return (XT_CB_PASSTHROUGH
);
3221 return (XT_CB_HANDLED
);
3226 if (t
->tab_id
== x
) {
3227 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
3228 return (XT_CB_HANDLED
);
3231 TAILQ_FOREACH(tt
, &tabs
, entry
) {
3232 if (tt
->tab_id
== x
) {
3233 gtk_notebook_set_current_page(notebook
, x
);
3234 DNPRINTF(XT_D_TAB
, "movetab: going to %d\n", x
);
3236 gtk_widget_grab_focus(GTK_WIDGET(tt
->wv
));
3240 return (XT_CB_HANDLED
);
3244 command(struct tab
*t
, struct karg
*args
)
3246 WebKitWebFrame
*frame
;
3247 char *s
= NULL
, *ss
= NULL
;
3251 if (t
== NULL
|| args
== NULL
)
3270 case XT_CMD_OPEN_CURRENT
:
3273 case XT_CMD_TABNEW_CURRENT
:
3274 if (!s
) /* FALL THROUGH? */
3276 frame
= webkit_web_view_get_main_frame(t
->wv
);
3277 uri
= webkit_web_frame_get_uri(frame
);
3278 if (uri
&& strlen(uri
)) {
3279 ss
= g_strdup_printf("%s%s", s
, uri
);
3284 show_oops(t
, "command: invalid opcode %d", args
->i
);
3285 return (XT_CB_PASSTHROUGH
);
3288 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
3290 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
3291 gdk_color_parse("white", &color
);
3292 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
3294 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
3295 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
3300 return (XT_CB_HANDLED
);
3304 * Return a new string with a download row (in html)
3305 * appended. Old string is freed.
3308 xtp_page_dl_row(struct tab
*t
, char *html
, struct download
*dl
)
3311 WebKitDownloadStatus stat
;
3312 char *status_html
= NULL
, *cmd_html
= NULL
, *new_html
;
3314 char cur_sz
[FMT_SCALED_STRSIZE
];
3315 char tot_sz
[FMT_SCALED_STRSIZE
];
3318 DNPRINTF(XT_D_DOWNLOAD
, "%s: dl->id %d\n", __func__
, dl
->id
);
3320 /* All actions wil take this form:
3321 * xxxt://class/seskey
3323 xtp_prefix
= g_strdup_printf("%s%d/%s/",
3324 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
);
3326 stat
= webkit_download_get_status(dl
->download
);
3329 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
3330 status_html
= g_strdup_printf("Finished");
3331 cmd_html
= g_strdup_printf(
3332 "<a href='%s%d/%d'>Remove</a>",
3333 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3335 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
3336 /* gather size info */
3337 progress
= 100 * webkit_download_get_progress(dl
->download
);
3340 webkit_download_get_current_size(dl
->download
), cur_sz
);
3342 webkit_download_get_total_size(dl
->download
), tot_sz
);
3344 status_html
= g_strdup_printf("%s of %s (%.2f%%)", cur_sz
,
3346 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3347 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3351 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
3352 status_html
= g_strdup_printf("Cancelled");
3353 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3354 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3356 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
3357 status_html
= g_strdup_printf("Error!");
3358 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3359 xtp_prefix
, XT_XTP_DL_REMOVE
, dl
->id
);
3361 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
3362 cmd_html
= g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3363 xtp_prefix
, XT_XTP_DL_CANCEL
, dl
->id
);
3364 status_html
= g_strdup_printf("Starting");
3367 show_oops(t
, "%s: unknown download status", __func__
);
3370 new_html
= g_strdup_printf(
3371 "%s\n<tr><td>%s</td><td>%s</td>"
3372 "<td style='text-align:center'>%s</td></tr>\n",
3373 html
, webkit_download_get_uri(dl
->download
),
3374 status_html
, cmd_html
);
3378 g_free(status_html
);
3389 * update all download tabs apart from one. Pass NULL if
3390 * you want to update all.
3393 update_download_tabs(struct tab
*apart_from
)
3396 if (!updating_dl_tabs
) {
3397 updating_dl_tabs
= 1; /* stop infinite recursion */
3398 TAILQ_FOREACH(t
, &tabs
, entry
)
3399 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_DL
)
3400 && (t
!= apart_from
))
3401 xtp_page_dl(t
, NULL
);
3402 updating_dl_tabs
= 0;
3407 * update all cookie tabs apart from one. Pass NULL if
3408 * you want to update all.
3411 update_cookie_tabs(struct tab
*apart_from
)
3414 if (!updating_cl_tabs
) {
3415 updating_cl_tabs
= 1; /* stop infinite recursion */
3416 TAILQ_FOREACH(t
, &tabs
, entry
)
3417 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_CL
)
3418 && (t
!= apart_from
))
3419 xtp_page_cl(t
, NULL
);
3420 updating_cl_tabs
= 0;
3425 * update all history tabs apart from one. Pass NULL if
3426 * you want to update all.
3429 update_history_tabs(struct tab
*apart_from
)
3433 if (!updating_hl_tabs
) {
3434 updating_hl_tabs
= 1; /* stop infinite recursion */
3435 TAILQ_FOREACH(t
, &tabs
, entry
)
3436 if ((t
->xtp_meaning
== XT_XTP_TAB_MEANING_HL
)
3437 && (t
!= apart_from
))
3438 xtp_page_hl(t
, NULL
);
3439 updating_hl_tabs
= 0;
3443 /* cookie management XTP page */
3445 xtp_page_cl(struct tab
*t
, struct karg
*args
)
3447 char *header
, *body
, *footer
, *page
, *tmp
;
3448 int i
= 1; /* all ids start 1 */
3449 GSList
*sc
, *pc
, *pc_start
;
3453 DNPRINTF(XT_D_CMD
, "%s", __func__
);
3456 errx(1, "%s: null tab", __func__
);
3458 /* mark this tab as cookie jar */
3459 t
->xtp_meaning
= XT_XTP_TAB_MEANING_CL
;
3461 /* Generate a new session key */
3462 if (!updating_cl_tabs
)
3463 generate_xtp_session_key(&cl_session_key
);
3466 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3467 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3468 "</head><body><h1>Cookie Jar</h1>\n");
3471 body
= g_strdup_printf("<div align='center'><table><tr>"
3479 "<th>HTTP_only</th>"
3480 "<th>Remove</th></tr>\n");
3482 sc
= soup_cookie_jar_all_cookies(s_cookiejar
);
3483 pc
= soup_cookie_jar_all_cookies(p_cookiejar
);
3486 for (; sc
; sc
= sc
->next
) {
3490 for (pc
= pc_start
; pc
; pc
= pc
->next
)
3491 if (soup_cookie_equal(pc
->data
, c
)) {
3492 type
= "Session + Persistent";
3497 body
= g_strdup_printf(
3499 "<td style='width: 3%%; text-align: center'>%s</td>"
3500 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3501 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3502 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3503 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3504 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3505 "<td style='width: 3%%; text-align: center'>%d</td>"
3506 "<td style='width: 3%%; text-align: center'>%d</td>"
3507 "<td style='width: 3%%; text-align: center'>"
3508 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3516 soup_date_to_string(c
->expires
, SOUP_DATE_HTTP
) : "",
3531 soup_cookies_free(sc
);
3532 soup_cookies_free(pc
);
3534 /* small message if there are none */
3537 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
3538 "colspan='8'>No Cookies</td></tr>\n", body
);
3543 footer
= g_strdup_printf("</table></div></body></html>");
3545 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3551 load_webkit_string(t
, page
);
3552 update_cookie_tabs(t
);
3560 xtp_page_hl(struct tab
*t
, struct karg
*args
)
3562 char *header
, *body
, *footer
, *page
, *tmp
;
3564 int i
= 1; /* all ids start 1 */
3566 DNPRINTF(XT_D_CMD
, "%s", __func__
);
3569 errx(1, "%s: null tab", __func__
);
3571 /* mark this tab as history manager */
3572 t
->xtp_meaning
= XT_XTP_TAB_MEANING_HL
;
3574 /* Generate a new session key */
3575 if (!updating_hl_tabs
)
3576 generate_xtp_session_key(&hl_session_key
);
3579 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
"\n<head>"
3580 "<title>History</title>\n"
3583 "<h1>History</h1>\n",
3587 body
= g_strdup_printf("<div align='center'><table><tr>"
3588 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3590 RB_FOREACH_REVERSE(h
, history_list
, &hl
) {
3592 body
= g_strdup_printf(
3594 "<td><a href='%s'>%s</a></td>"
3596 "<td style='text-align: center'>"
3597 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3598 body
, h
->uri
, h
->uri
, h
->title
,
3599 XT_XTP_STR
, XT_XTP_HL
, hl_session_key
,
3600 XT_XTP_HL_REMOVE
, i
);
3606 /* small message if there are none */
3609 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
3610 "colspan='3'>No History</td></tr>\n", body
);
3615 footer
= g_strdup_printf("</table></div></body></html>");
3617 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3620 * update all history manager tabs as the xtp session
3621 * key has now changed. No need to update the current tab.
3622 * Already did that above.
3624 update_history_tabs(t
);
3630 load_webkit_string(t
, page
);
3637 * Generate a web page detailing the status of any downloads
3640 xtp_page_dl(struct tab
*t
, struct karg
*args
)
3642 struct download
*dl
;
3643 char *header
, *body
, *footer
, *page
, *tmp
;
3647 DNPRINTF(XT_D_DOWNLOAD
, "%s", __func__
);
3650 errx(1, "%s: null tab", __func__
);
3652 /* mark as a download manager tab */
3653 t
->xtp_meaning
= XT_XTP_TAB_MEANING_DL
;
3656 * Generate a new session key for next page instance.
3657 * This only happens for the top level call to xtp_page_dl()
3658 * in which case updating_dl_tabs is 0.
3660 if (!updating_dl_tabs
)
3661 generate_xtp_session_key(&dl_session_key
);
3663 /* header - with refresh so as to update */
3664 if (refresh_interval
>= 1)
3665 ref
= g_strdup_printf(
3666 "<meta http-equiv='refresh' content='%u"
3667 ";url=%s%d/%s/%d' />\n",
3677 header
= g_strdup_printf(
3679 "<title>Downloads</title>\n%s%s</head>\n",
3680 XT_DOCTYPE XT_HTML_TAG
,
3684 body
= g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
3685 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
3686 "</p><table><tr><th style='width: 60%%'>"
3687 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
3688 XT_XTP_STR
, XT_XTP_DL
, dl_session_key
, XT_XTP_DL_LIST
);
3690 RB_FOREACH_REVERSE(dl
, download_list
, &downloads
) {
3691 body
= xtp_page_dl_row(t
, body
, dl
);
3695 /* message if no downloads in list */
3698 body
= g_strdup_printf("%s\n<tr><td colspan='3'"
3699 " style='text-align: center'>"
3700 "No downloads</td></tr>\n", body
);
3705 footer
= g_strdup_printf("</table></div></body></html>");
3707 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3711 * update all download manager tabs as the xtp session
3712 * key has now changed. No need to update the current tab.
3713 * Already did that above.
3715 update_download_tabs(t
);
3722 load_webkit_string(t
, page
);
3729 search(struct tab
*t
, struct karg
*args
)
3733 if (t
== NULL
|| args
== NULL
)
3735 if (t
->search_text
== NULL
) {
3736 if (global_search
== NULL
)
3737 return (XT_CB_PASSTHROUGH
);
3739 t
->search_text
= g_strdup(global_search
);
3740 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
3741 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
3745 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
3746 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
3749 case XT_SEARCH_NEXT
:
3750 d
= t
->search_forward
;
3752 case XT_SEARCH_PREV
:
3753 d
= !t
->search_forward
;
3756 return (XT_CB_PASSTHROUGH
);
3759 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
3761 return (XT_CB_HANDLED
);
3764 struct settings_args
{
3770 print_setting(struct settings
*s
, char *val
, void *cb_args
)
3773 struct settings_args
*sa
= cb_args
;
3778 if (s
->flags
& XT_SF_RUNTIME
)
3784 *sa
->body
= g_strdup_printf(
3786 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
3787 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
3799 set(struct tab
*t
, struct karg
*args
)
3801 char *header
, *body
, *footer
, *page
, *tmp
, *pars
;
3803 struct settings_args sa
;
3805 if ((pars
= getparams(args
->s
, "set")) == NULL
) {
3806 bzero(&sa
, sizeof sa
);
3810 header
= g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3811 "\n<head><title>Settings</title>\n"
3812 "</head><body><h1>Settings</h1>\n");
3815 body
= g_strdup_printf("<div align='center'><table><tr>"
3816 "<th align='left'>Setting</th>"
3817 "<th align='left'>Value</th></tr>\n");
3819 settings_walk(print_setting
, &sa
);
3822 /* small message if there are none */
3825 body
= g_strdup_printf("%s\n<tr><td style='text-align:center'"
3826 "colspan='2'>No settings</td></tr>\n", body
);
3831 footer
= g_strdup_printf("</table></div></body></html>");
3833 page
= g_strdup_printf("%s%s%s", header
, body
, footer
);
3839 load_webkit_string(t
, page
);
3841 show_oops(t
, "Invalid command: %s", pars
);
3843 return (XT_CB_PASSTHROUGH
);
3847 session_save(struct tab
*t
, char *filename
, char **ret
)
3853 f
+= strlen("save");
3854 while (*f
== ' ' && *f
!= '\0')
3860 if (f
[0] == '.' || f
[0] == '/')
3864 if (save_tabs(t
, &a
))
3866 strlcpy(named_session
, f
, sizeof named_session
);
3874 session_open(struct tab
*t
, char *filename
, char **ret
)
3880 f
+= strlen("open");
3881 while (*f
== ' ' && *f
!= '\0')
3887 if (f
[0] == '.' || f
[0] == '/')
3891 a
.i
= XT_SES_CLOSETABS
;
3892 if (open_tabs(t
, &a
))
3895 strlcpy(named_session
, f
, sizeof named_session
);
3903 session_delete(struct tab
*t
, char *filename
, char **ret
)
3905 char file
[PATH_MAX
];
3909 f
+= strlen("delete");
3910 while (*f
== ' ' && *f
!= '\0')
3916 if (f
[0] == '.' || f
[0] == '/')
3919 snprintf(file
, sizeof file
, "%s/%s", sessions_dir
, f
);
3923 if (!strcmp(f
, named_session
))
3924 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
3925 sizeof named_session
);
3933 session_cmd(struct tab
*t
, struct karg
*args
)
3935 char *action
= NULL
;
3936 char *filename
= NULL
;
3941 if ((action
= getparams(args
->s
, "session")))
3946 if (!strcmp(action
, "show"))
3947 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
3948 XT_SAVED_TABS_FILE
: named_session
);
3949 else if (g_str_has_prefix(action
, "save ")) {
3950 if (session_save(t
, action
, &filename
)) {
3951 show_oops(t
, "Can't save session: %s",
3952 filename
? filename
: "INVALID");
3955 } else if (g_str_has_prefix(action
, "open ")) {
3956 if (session_open(t
, action
, &filename
)) {
3957 show_oops(t
, "Can't open session: %s",
3958 filename
? filename
: "INVALID");
3961 } else if (g_str_has_prefix(action
, "delete ")) {
3962 if (session_delete(t
, action
, &filename
)) {
3963 show_oops(t
, "Can't delete session: %s",
3964 filename
? filename
: "INVALID");
3968 show_oops(t
, "Invalid command: %s", action
);
3970 return (XT_CB_PASSTHROUGH
);
3974 * Make a hardcopy of the page
3977 print_page(struct tab
*t
, struct karg
*args
)
3979 WebKitWebFrame
*frame
;
3981 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
3984 * for now we just call the GTK print box,
3985 * but later we might decide to hook in a command.
3987 frame
= webkit_web_view_get_main_frame(t
->wv
);
3988 webkit_web_frame_print(frame
);
3994 go_home(struct tab
*t
, struct karg
*args
)
3998 newuri
= guess_url_type((char *)home
);
3999 webkit_web_view_load_uri(t
->wv
, newuri
);
4006 restart(struct tab
*t
, struct karg
*args
)
4010 a
.s
= XT_RESTART_TABS_FILE
;
4012 execvp(start_argv
[0], start_argv
);
4018 /* inherent to GTK not all keys will be caught at all times */
4019 /* XXX sort key bindings */
4020 struct key_bindings
{
4024 int (*func
)(struct tab
*, struct karg
*);
4027 { GDK_MOD1_MASK
, 0, GDK_d
, xtp_page_dl
, {0} },
4028 { GDK_MOD1_MASK
, 0, GDK_h
, xtp_page_hl
, {0} },
4029 { GDK_CONTROL_MASK
, 0, GDK_p
, print_page
, {0}},
4030 { 0, 0, GDK_slash
, command
, {.i
= '/'} },
4031 { GDK_SHIFT_MASK
, 0, GDK_question
, command
, {.i
= '?'} },
4032 { GDK_SHIFT_MASK
, 0, GDK_colon
, command
, {.i
= ':'} },
4033 { GDK_CONTROL_MASK
, 0, GDK_q
, quit
, {0} },
4034 { GDK_MOD1_MASK
, 0, GDK_q
, restart
, {0} },
4035 { GDK_CONTROL_MASK
, 0, GDK_j
, toggle_js
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
} },
4036 { GDK_MOD1_MASK
, 0, GDK_c
, toggle_cwl
, {.i
= XT_WL_TOGGLE
| XT_WL_FQDN
} },
4037 { GDK_CONTROL_MASK
, 0, GDK_s
, toggle_src
, {0} },
4038 { 0, 0, GDK_y
, yank_uri
, {0} },
4039 { 0, 0, GDK_p
, paste_uri
, {.i
= XT_PASTE_CURRENT_TAB
} },
4040 { GDK_SHIFT_MASK
, 0, GDK_P
, paste_uri
, {.i
= XT_PASTE_NEW_TAB
} },
4043 { 0, 0, GDK_n
, search
, {.i
= XT_SEARCH_NEXT
} },
4044 { GDK_SHIFT_MASK
, 0, GDK_N
, search
, {.i
= XT_SEARCH_PREV
} },
4047 { 0, 0, GDK_F6
, focus
, {.i
= XT_FOCUS_URI
} },
4048 { 0, 0, GDK_F7
, focus
, {.i
= XT_FOCUS_SEARCH
} },
4050 /* command aliases (handy when -S flag is used) */
4051 { 0, 0, GDK_F9
, command
, {.i
= XT_CMD_OPEN
} },
4052 { 0, 0, GDK_F10
, command
, {.i
= XT_CMD_OPEN_CURRENT
} },
4053 { 0, 0, GDK_F11
, command
, {.i
= XT_CMD_TABNEW
} },
4054 { 0, 0, GDK_F12
, command
, {.i
= XT_CMD_TABNEW_CURRENT
} },
4057 { 0, 0, GDK_f
, hint
, {.i
= 0} },
4060 { 0, 0, GDK_BackSpace
, navaction
, {.i
= XT_NAV_BACK
} },
4061 { GDK_MOD1_MASK
, 0, GDK_Left
, navaction
, {.i
= XT_NAV_BACK
} },
4062 { GDK_SHIFT_MASK
, 0, GDK_BackSpace
, navaction
, {.i
= XT_NAV_FORWARD
} },
4063 { GDK_MOD1_MASK
, 0, GDK_Right
, navaction
, {.i
= XT_NAV_FORWARD
} },
4064 { 0, 0, GDK_F5
, navaction
, {.i
= XT_NAV_RELOAD
} },
4065 { GDK_CONTROL_MASK
, 0, GDK_r
, navaction
, {.i
= XT_NAV_RELOAD
} },
4066 { GDK_CONTROL_MASK
|GDK_SHIFT_MASK
, 0, GDK_R
, navaction
, {.i
= XT_NAV_RELOAD_CACHE
} },
4067 { GDK_CONTROL_MASK
, 0, GDK_l
, navaction
, {.i
= XT_NAV_RELOAD
} },
4068 { GDK_MOD1_MASK
, 1, GDK_f
, xtp_page_fl
, {0} },
4070 /* vertical movement */
4071 { 0, 0, GDK_j
, move
, {.i
= XT_MOVE_DOWN
} },
4072 { 0, 0, GDK_Down
, move
, {.i
= XT_MOVE_DOWN
} },
4073 { 0, 0, GDK_Up
, move
, {.i
= XT_MOVE_UP
} },
4074 { 0, 0, GDK_k
, move
, {.i
= XT_MOVE_UP
} },
4075 { GDK_SHIFT_MASK
, 0, GDK_G
, move
, {.i
= XT_MOVE_BOTTOM
} },
4076 { 0, 0, GDK_End
, move
, {.i
= XT_MOVE_BOTTOM
} },
4077 { 0, 0, GDK_Home
, move
, {.i
= XT_MOVE_TOP
} },
4078 { 0, 0, GDK_g
, move
, {.i
= XT_MOVE_TOP
} }, /* XXX make this work */
4079 { 0, 0, GDK_space
, move
, {.i
= XT_MOVE_PAGEDOWN
} },
4080 { GDK_CONTROL_MASK
, 0, GDK_f
, move
, {.i
= XT_MOVE_PAGEDOWN
} },
4081 { GDK_CONTROL_MASK
, 0, GDK_d
, move
, {.i
= XT_MOVE_HALFDOWN
} },
4082 { 0, 0, GDK_Page_Down
, move
, {.i
= XT_MOVE_PAGEDOWN
} },
4083 { 0, 0, GDK_Page_Up
, move
, {.i
= XT_MOVE_PAGEUP
} },
4084 { GDK_CONTROL_MASK
, 0, GDK_b
, move
, {.i
= XT_MOVE_PAGEUP
} },
4085 { GDK_CONTROL_MASK
, 0, GDK_u
, move
, {.i
= XT_MOVE_HALFUP
} },
4086 /* horizontal movement */
4087 { 0, 0, GDK_l
, move
, {.i
= XT_MOVE_RIGHT
} },
4088 { 0, 0, GDK_Right
, move
, {.i
= XT_MOVE_RIGHT
} },
4089 { 0, 0, GDK_Left
, move
, {.i
= XT_MOVE_LEFT
} },
4090 { 0, 0, GDK_h
, move
, {.i
= XT_MOVE_LEFT
} },
4091 { GDK_SHIFT_MASK
, 0, GDK_dollar
, move
, {.i
= XT_MOVE_FARRIGHT
} },
4092 { 0, 0, GDK_0
, move
, {.i
= XT_MOVE_FARLEFT
} },
4095 { GDK_CONTROL_MASK
, 0, GDK_t
, tabaction
, {.i
= XT_TAB_NEW
} },
4096 { GDK_CONTROL_MASK
, 1, GDK_w
, tabaction
, {.i
= XT_TAB_DELETE
} },
4097 { GDK_SHIFT_MASK
, 0, GDK_U
, tabaction
, {.i
= XT_TAB_UNDO_CLOSE
} },
4098 { GDK_CONTROL_MASK
, 0, GDK_1
, movetab
, {.i
= 1} },
4099 { GDK_CONTROL_MASK
, 0, GDK_2
, movetab
, {.i
= 2} },
4100 { GDK_CONTROL_MASK
, 0, GDK_3
, movetab
, {.i
= 3} },
4101 { GDK_CONTROL_MASK
, 0, GDK_4
, movetab
, {.i
= 4} },
4102 { GDK_CONTROL_MASK
, 0, GDK_5
, movetab
, {.i
= 5} },
4103 { GDK_CONTROL_MASK
, 0, GDK_6
, movetab
, {.i
= 6} },
4104 { GDK_CONTROL_MASK
, 0, GDK_7
, movetab
, {.i
= 7} },
4105 { GDK_CONTROL_MASK
, 0, GDK_8
, movetab
, {.i
= 8} },
4106 { GDK_CONTROL_MASK
, 0, GDK_9
, movetab
, {.i
= 9} },
4107 { GDK_CONTROL_MASK
, 0, GDK_0
, movetab
, {.i
= 10} },
4108 { GDK_CONTROL_MASK
|GDK_SHIFT_MASK
, 0, GDK_less
, movetab
, {.i
= XT_TAB_FIRST
} },
4109 { GDK_CONTROL_MASK
|GDK_SHIFT_MASK
, 0, GDK_greater
, movetab
, {.i
= XT_TAB_LAST
} },
4110 { GDK_CONTROL_MASK
, 0, GDK_Left
, movetab
, {.i
= XT_TAB_PREV
} },
4111 { GDK_CONTROL_MASK
, 0, GDK_Right
, movetab
, {.i
= XT_TAB_NEXT
} },
4112 { GDK_CONTROL_MASK
, 0, GDK_minus
, resizetab
, {.i
= -1} },
4113 { GDK_CONTROL_MASK
|GDK_SHIFT_MASK
, 0, GDK_plus
, resizetab
, {.i
= 1} },
4114 { GDK_CONTROL_MASK
, 0, GDK_equal
, resizetab
, {.i
= 1} },
4120 int (*func
)(struct tab
*, struct karg
*);
4123 { "q!", 0, quit
, {0} },
4124 { "qa", 0, quit
, {0} },
4125 { "qa!", 0, quit
, {0} },
4126 { "w", 0, save_tabs
, {0} },
4127 { "wq", 0, save_tabs_and_quit
, {0} },
4128 { "wq!", 0, save_tabs_and_quit
, {0} },
4129 { "help", 0, help
, {0} },
4130 { "about", 0, about
, {0} },
4131 { "stats", 0, stats
, {0} },
4132 { "version", 0, about
, {0} },
4133 { "cookies", 0, xtp_page_cl
, {0} },
4134 { "fav", 0, xtp_page_fl
, {0} },
4135 { "favadd", 0, add_favorite
, {0} },
4136 { "js", 2, js_cmd
, {0} },
4137 { "cookie", 2, cookie_cmd
, {0} },
4138 { "cert", 1, cert_cmd
, {0} },
4139 { "ca", 0, ca_cmd
, {0} },
4140 { "dl" , 0, xtp_page_dl
, {0} },
4141 { "h" , 0, xtp_page_hl
, {0} },
4142 { "hist" , 0, xtp_page_hl
, {0} },
4143 { "history" , 0, xtp_page_hl
, {0} },
4144 { "home" , 0, go_home
, {0} },
4145 { "restart" , 0, restart
, {0} },
4146 { "urlhide", 0, urlaction
, {.i
= XT_URL_HIDE
} },
4147 { "urlh", 0, urlaction
, {.i
= XT_URL_HIDE
} },
4148 { "urlshow", 0, urlaction
, {.i
= XT_URL_SHOW
} },
4149 { "urls", 0, urlaction
, {.i
= XT_URL_SHOW
} },
4151 { "1", 0, move
, {.i
= XT_MOVE_TOP
} },
4152 { "print", 0, print_page
, {0} },
4155 { "o", 1, tabaction
, {.i
= XT_TAB_OPEN
} },
4156 { "op", 1, tabaction
, {.i
= XT_TAB_OPEN
} },
4157 { "open", 1, tabaction
, {.i
= XT_TAB_OPEN
} },
4158 { "tabnew", 1, tabaction
, {.i
= XT_TAB_NEW
} },
4159 { "tabedit", 1, tabaction
, {.i
= XT_TAB_NEW
} },
4160 { "tabe", 1, tabaction
, {.i
= XT_TAB_NEW
} },
4161 { "tabclose", 0, tabaction
, {.i
= XT_TAB_DELETE
} },
4162 { "tabc", 0, tabaction
, {.i
= XT_TAB_DELETE
} },
4163 { "tabshow", 1, tabaction
, {.i
= XT_TAB_SHOW
} },
4164 { "tabs", 1, tabaction
, {.i
= XT_TAB_SHOW
} },
4165 { "tabhide", 1, tabaction
, {.i
= XT_TAB_HIDE
} },
4166 { "tabh", 1, tabaction
, {.i
= XT_TAB_HIDE
} },
4167 { "quit", 0, tabaction
, {.i
= XT_TAB_DELQUIT
} },
4168 { "q", 0, tabaction
, {.i
= XT_TAB_DELQUIT
} },
4169 /* XXX add count to these commands */
4170 { "tabfirst", 0, movetab
, {.i
= XT_TAB_FIRST
} },
4171 { "tabfir", 0, movetab
, {.i
= XT_TAB_FIRST
} },
4172 { "tabrewind", 0, movetab
, {.i
= XT_TAB_FIRST
} },
4173 { "tabr", 0, movetab
, {.i
= XT_TAB_FIRST
} },
4174 { "tablast", 0, movetab
, {.i
= XT_TAB_LAST
} },
4175 { "tabl", 0, movetab
, {.i
= XT_TAB_LAST
} },
4176 { "tabprevious", 0, movetab
, {.i
= XT_TAB_PREV
} },
4177 { "tabp", 0, movetab
, {.i
= XT_TAB_PREV
} },
4178 { "tabnext", 0, movetab
, {.i
= XT_TAB_NEXT
} },
4179 { "tabn", 0, movetab
, {.i
= XT_TAB_NEXT
} },
4182 { "set", 1, set
, {0} },
4183 { "fullscreen", 0, fullscreen
, {0} },
4184 { "f", 0, fullscreen
, {0} },
4187 { "session", 1, session_cmd
, {0} },
4191 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
4199 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
4201 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
4203 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
4210 * cancel, remove, etc. downloads
4213 xtp_handle_dl(struct tab
*t
, uint8_t cmd
, int id
)
4215 struct download find
, *d
;
4217 DNPRINTF(XT_D_DOWNLOAD
, "download control: cmd %d, id %d\n", cmd
, id
);
4219 /* some commands require a valid download id */
4220 if (cmd
!= XT_XTP_DL_LIST
) {
4221 /* lookup download in question */
4223 d
= RB_FIND(download_list
, &downloads
, &find
);
4226 show_oops(t
, "%s: no such download", __func__
);
4231 /* decide what to do */
4233 case XT_XTP_DL_CANCEL
:
4234 webkit_download_cancel(d
->download
);
4236 case XT_XTP_DL_REMOVE
:
4237 webkit_download_cancel(d
->download
); /* just incase */
4238 g_object_unref(d
->download
);
4239 RB_REMOVE(download_list
, &downloads
, d
);
4241 case XT_XTP_DL_LIST
:
4245 show_oops(t
, "%s: unknown command", __func__
);
4248 xtp_page_dl(t
, NULL
);
4252 * Actions on history, only does one thing for now, but
4253 * we provide the function for future actions
4256 xtp_handle_hl(struct tab
*t
, uint8_t cmd
, int id
)
4258 struct history
*h
, *next
;
4262 case XT_XTP_HL_REMOVE
:
4263 /* walk backwards, as listed in reverse */
4264 for (h
= RB_MAX(history_list
, &hl
); h
!= NULL
; h
= next
) {
4265 next
= RB_PREV(history_list
, &hl
, h
);
4267 RB_REMOVE(history_list
, &hl
, h
);
4268 g_free((gpointer
) h
->title
);
4269 g_free((gpointer
) h
->uri
);
4276 case XT_XTP_HL_LIST
:
4277 /* Nothing - just xtp_page_hl() below */
4280 show_oops(t
, "%s: unknown command", __func__
);
4284 xtp_page_hl(t
, NULL
);
4287 /* remove a favorite */
4289 remove_favorite(struct tab
*t
, int index
)
4291 char file
[PATH_MAX
], *title
, *uri
;
4292 char *new_favs
, *tmp
;
4297 /* open favorites */
4298 snprintf(file
, sizeof file
, "%s/%s/%s",
4299 pwd
->pw_dir
, XT_DIR
, XT_FAVS_FILE
);
4301 if ((f
= fopen(file
, "r")) == NULL
) {
4302 show_oops(t
, "%s: can't open favorites: %s",
4303 __func__
, strerror(errno
));
4307 /* build a string which will become the new favroites file */
4308 new_favs
= g_strdup_printf("%s", "");
4311 if ((title
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
)
4312 if (feof(f
) || ferror(f
))
4314 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
4321 if ((uri
= fparseln(f
, &len
, &lineno
, NULL
, 0)) == NULL
) {
4322 if (feof(f
) || ferror(f
)) {
4323 show_oops(t
, "%s: can't parse favorites %s",
4324 __func__
, strerror(errno
));
4329 /* as long as this isn't the one we are deleting add to file */
4332 new_favs
= g_strdup_printf("%s%s\n%s\n",
4333 new_favs
, title
, uri
);
4345 /* write back new favorites file */
4346 if ((f
= fopen(file
, "w")) == NULL
) {
4347 show_oops(t
, "%s: can't open favorites: %s",
4348 __func__
, strerror(errno
));
4352 fwrite(new_favs
, strlen(new_favs
), 1, f
);
4365 xtp_handle_fl(struct tab
*t
, uint8_t cmd
, int arg
)
4368 case XT_XTP_FL_LIST
:
4369 /* nothing, just the below call to xtp_page_fl() */
4371 case XT_XTP_FL_REMOVE
:
4372 remove_favorite(t
, arg
);
4375 show_oops(t
, "%s: invalid favorites command", __func__
);
4379 xtp_page_fl(t
, NULL
);
4383 xtp_handle_cl(struct tab
*t
, uint8_t cmd
, int arg
)
4386 case XT_XTP_CL_LIST
:
4387 /* nothing, just xtp_page_cl() */
4389 case XT_XTP_CL_REMOVE
:
4393 show_oops(t
, "%s: unknown cookie xtp command", __func__
);
4397 xtp_page_cl(t
, NULL
);
4400 /* link an XTP class to it's session key and handler function */
4401 struct xtp_despatch
{
4404 void (*handle_func
)(struct tab
*, uint8_t, int);
4407 struct xtp_despatch xtp_despatches
[] = {
4408 { XT_XTP_DL
, &dl_session_key
, xtp_handle_dl
},
4409 { XT_XTP_HL
, &hl_session_key
, xtp_handle_hl
},
4410 { XT_XTP_FL
, &fl_session_key
, xtp_handle_fl
},
4411 { XT_XTP_CL
, &cl_session_key
, xtp_handle_cl
},
4412 { NULL
, NULL
, NULL
}
4416 * is the url xtp protocol? (xxxt://)
4417 * if so, parse and despatch correct bahvior
4420 parse_xtp_url(struct tab
*t
, const char *url
)
4422 char *dup
= NULL
, *p
, *last
;
4423 uint8_t n_tokens
= 0;
4424 char *tokens
[4] = {NULL
, NULL
, NULL
, ""};
4425 struct xtp_despatch
*dsp
, *dsp_match
= NULL
;
4429 * tokens array meaning:
4431 * tokens[1] = session key
4432 * tokens[2] = action
4433 * tokens[3] = optional argument
4436 DNPRINTF(XT_D_URL
, "%s: url %s\n", __func__
, url
);
4438 /*xtp tab meaning is normal unless proven special */
4439 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
4441 if (strncmp(url
, XT_XTP_STR
, strlen(XT_XTP_STR
)))
4444 dup
= g_strdup(url
+ strlen(XT_XTP_STR
));
4446 /* split out the url */
4447 for ((p
= strtok_r(dup
, "/", &last
)); p
;
4448 (p
= strtok_r(NULL
, "/", &last
))) {
4450 tokens
[n_tokens
++] = p
;
4453 /* should be atleast three fields 'class/seskey/command/arg' */
4457 dsp
= xtp_despatches
;
4458 req_class
= atoi(tokens
[0]);
4459 while (dsp
->xtp_class
!= NULL
) {
4460 if (dsp
->xtp_class
== req_class
) {
4467 /* did we find one atall? */
4468 if (dsp_match
== NULL
) {
4469 show_oops(t
, "%s: no matching xtp despatch found", __func__
);
4473 /* check session key and call despatch function */
4474 if (validate_xtp_session_key(t
, *(dsp_match
->session_key
), tokens
[1])) {
4475 dsp_match
->handle_func(t
, atoi(tokens
[2]), atoi(tokens
[3]));
4488 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
4490 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
4491 char *newuri
= NULL
;
4493 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
4496 errx(1, "activate_uri_entry_cb");
4501 uri
+= strspn(uri
, "\t ");
4503 /* if xxxt:// treat specially */
4504 if (!parse_xtp_url(t
, uri
)) {
4505 if (valid_url_type((char *)uri
)) {
4506 newuri
= guess_url_type((char *)uri
);
4510 webkit_web_view_load_uri(t
->wv
, uri
);
4511 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
4519 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
4521 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
4522 char *newuri
= NULL
;
4524 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
4527 errx(1, "activate_search_entry_cb");
4529 if (search_string
== NULL
) {
4530 show_oops(t
, "no search_string");
4534 newuri
= g_strdup_printf(search_string
, search
);
4536 webkit_web_view_load_uri(t
->wv
, newuri
);
4537 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
4544 check_and_set_js(gchar
*uri
, struct tab
*t
)
4546 struct domain
*d
= NULL
;
4549 if (uri
== NULL
|| t
== NULL
)
4552 if ((d
= wl_find_uri(uri
, &js_wl
)) == NULL
)
4557 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
4558 es
? "enable" : "disable", uri
);
4560 g_object_set((GObject
*)t
->settings
,
4561 "enable-scripts", es
, (char *)NULL
);
4562 webkit_web_view_set_settings(t
->wv
, t
->settings
);
4564 button_set_stockid(t
->js_toggle
,
4565 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
4569 show_ca_status(struct tab
*t
, const char *uri
)
4571 WebKitWebFrame
*frame
;
4572 WebKitWebDataSource
*source
;
4573 WebKitNetworkRequest
*request
;
4574 SoupMessage
*message
;
4576 gchar
*col_str
= "white";
4579 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
4580 ssl_strict_certs
, ssl_ca_file
, uri
);
4584 if (ssl_ca_file
== NULL
) {
4585 if (g_str_has_prefix(uri
, "http://"))
4587 if (g_str_has_prefix(uri
, "https://")) {
4593 if (g_str_has_prefix(uri
, "http://") ||
4594 !g_str_has_prefix(uri
, "https://"))
4597 frame
= webkit_web_view_get_main_frame(t
->wv
);
4598 source
= webkit_web_frame_get_data_source(frame
);
4599 request
= webkit_web_data_source_get_request(source
);
4600 message
= webkit_network_request_get_message(request
);
4602 if (message
&& (soup_message_get_flags(message
) &
4603 SOUP_MESSAGE_CERTIFICATE_TRUSTED
)) {
4607 r
= load_compare_cert(t
, NULL
);
4609 col_str
= "lightblue";
4618 gdk_color_parse(col_str
, &color
);
4619 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
4624 free_favicon(struct tab
*t
)
4626 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p pix %p\n",
4627 __func__
, t
->icon_download
, t
->icon_request
, t
->icon_pixbuf
);
4629 if (t
->icon_request
)
4630 g_object_unref(t
->icon_request
);
4632 g_object_unref(t
->icon_pixbuf
);
4633 if (t
->icon_dest_uri
)
4634 g_free(t
->icon_dest_uri
);
4636 t
->icon_pixbuf
= NULL
;
4637 t
->icon_request
= NULL
;
4638 t
->icon_dest_uri
= NULL
;
4642 abort_favicon_download(struct tab
*t
)
4644 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
4646 if (t
->icon_download
)
4647 webkit_download_cancel(t
->icon_download
);
4651 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
4652 GTK_ENTRY_ICON_PRIMARY
, "text-html");
4656 set_favicon_from_file(struct tab
*t
, char *file
)
4659 GdkPixbuf
*pixbuf
, *scaled
;
4662 if (t
== NULL
|| file
== NULL
)
4664 if (t
->icon_pixbuf
) {
4665 DNPRINTF(XT_D_DOWNLOAD
, "%s: icon already set\n", __func__
);
4669 if (g_str_has_prefix(file
, "file://"))
4670 file
+= strlen("file://");
4671 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
4673 if (!stat(file
, &sb
)) {
4674 if (sb
.st_size
== 0) {
4675 /* corrupt icon so trash it */
4676 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
4679 /* no need to set icon to default here */
4684 pixbuf
= gdk_pixbuf_new_from_file(file
, NULL
);
4685 if (pixbuf
== NULL
) {
4686 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
4687 GTK_ENTRY_ICON_PRIMARY
, "text-html");
4691 g_object_get(pixbuf
, "width", &width
, "height", &height
,
4693 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d icon size %dx%d\n",
4694 __func__
, t
->tab_id
, width
, height
);
4696 if (width
> 16 || height
> 16) {
4697 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
4698 GDK_INTERP_BILINEAR
);
4699 g_object_unref(pixbuf
);
4703 if (scaled
== NULL
) {
4704 scaled
= gdk_pixbuf_scale_simple(pixbuf
, 16, 16,
4705 GDK_INTERP_BILINEAR
);
4709 t
->icon_pixbuf
= scaled
;
4710 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
4711 GTK_ENTRY_ICON_PRIMARY
, t
->icon_pixbuf
);
4715 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
4718 WebKitDownloadStatus status
= webkit_download_get_status(download
);
4723 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
4724 __func__
, t
->tab_id
, status
);
4727 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
4729 t
->icon_download
= NULL
;
4732 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
4735 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
4738 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
4740 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
4741 __func__
, t
->tab_id
);
4742 t
->icon_download
= NULL
;
4745 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
4747 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
4748 __func__
, t
->icon_dest_uri
);
4749 set_favicon_from_file(t
, t
->icon_dest_uri
);
4750 /* these will be freed post callback */
4751 t
->icon_request
= NULL
;
4752 t
->icon_download
= NULL
;
4760 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
4762 gchar
*name_hash
, file
[PATH_MAX
];
4765 DNPRINTF(XT_D_DOWNLOAD
, "notify_icon_loaded_cb %s\n", uri
);
4767 if (uri
== NULL
|| t
== NULL
)
4770 if (t
->icon_request
) {
4771 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
4775 /* check to see if we got the icon in cache */
4776 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
4777 snprintf(file
, sizeof file
, "%s/%s.ico", cache_dir
, name_hash
);
4780 if (!stat(file
, &sb
)) {
4781 if (sb
.st_size
> 0) {
4782 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
4784 set_favicon_from_file(t
, file
);
4788 /* corrupt icon so trash it */
4789 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
4794 /* create download for icon */
4795 t
->icon_request
= webkit_network_request_new(uri
);
4796 if (t
->icon_request
== NULL
) {
4797 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
4802 t
->icon_download
= webkit_download_new(t
->icon_request
);
4804 /* we have to free icon_dest_uri later */
4805 t
->icon_dest_uri
= g_strdup_printf("file://%s", file
);
4806 webkit_download_set_destination_uri(t
->icon_download
,
4809 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
4810 G_CALLBACK(favicon_download_status_changed_cb
), t
);
4812 webkit_download_start(t
->icon_download
);
4816 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
4818 WebKitWebFrame
*frame
;
4819 const gchar
*set
= NULL
, *uri
= NULL
, *title
= NULL
;
4820 struct history
*h
, find
;
4822 const gchar
*s_loading
;
4825 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d\n",
4826 webkit_web_view_get_load_status(wview
));
4829 errx(1, "notify_load_status_cb");
4831 switch (webkit_web_view_get_load_status(wview
)) {
4832 case WEBKIT_LOAD_PROVISIONAL
:
4833 abort_favicon_download(t
);
4834 #if GTK_CHECK_VERSION(2, 20, 0)
4835 gtk_widget_show(t
->spinner
);
4836 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
4838 gtk_label_set_text(GTK_LABEL(t
->label
), "Loading");
4840 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
4843 /* take focus if we are visible */
4844 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
4845 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
4849 case WEBKIT_LOAD_COMMITTED
:
4850 frame
= webkit_web_view_get_main_frame(wview
);
4851 uri
= webkit_web_frame_get_uri(frame
);
4853 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
4855 /* check if js white listing is enabled */
4856 if (enable_js_whitelist
) {
4857 frame
= webkit_web_view_get_main_frame(wview
);
4858 uri
= webkit_web_frame_get_uri(frame
);
4859 check_and_set_js((gchar
*)uri
, t
);
4862 show_ca_status(t
, uri
);
4864 /* we know enough to autosave the session */
4865 if (session_autosave
) {
4871 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
4872 title
= webkit_web_view_get_title(wview
);
4873 frame
= webkit_web_view_get_main_frame(wview
);
4874 uri
= webkit_web_frame_get_uri(frame
);
4882 gtk_label_set_text(GTK_LABEL(t
->label
), set
);
4883 gtk_window_set_title(GTK_WINDOW(main_window
), set
);
4886 if (!strncmp(uri
, "http://", strlen("http://")) ||
4887 !strncmp(uri
, "https://", strlen("https://")) ||
4888 !strncmp(uri
, "file://", strlen("file://")))
4893 h
= RB_FIND(history_list
, &hl
, &find
);
4897 h
= g_malloc(sizeof *h
);
4898 h
->uri
= g_strdup(uri
);
4900 h
->title
= g_strdup(title
);
4902 h
->title
= g_strdup(uri
);
4903 RB_INSERT(history_list
, &hl
, h
);
4904 update_history_tabs(NULL
);
4909 case WEBKIT_LOAD_FINISHED
:
4910 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4911 case WEBKIT_LOAD_FAILED
:
4913 #if GTK_CHECK_VERSION(2, 20, 0)
4914 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
4915 gtk_widget_hide(t
->spinner
);
4917 s_loading
= gtk_label_get_text(GTK_LABEL(t
->label
));
4918 if (s_loading
&& !strcmp(s_loading
, "Loading"))
4919 gtk_label_set_text(GTK_LABEL(t
->label
), "(untitled)");
4921 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
4926 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), TRUE
);
4928 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
4929 webkit_web_view_can_go_back(wview
));
4931 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
4932 webkit_web_view_can_go_forward(wview
));
4936 webview_load_finished_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
4938 run_script(t
, JS_HINTING
);
4942 webview_progress_changed_cb(WebKitWebView
*wv
, int progress
, struct tab
*t
)
4944 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
4945 progress
== 100 ? 0 : (double)progress
/ 100);
4949 webview_nw_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
4950 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
4951 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
4956 errx(1, "webview_nw_cb");
4958 DNPRINTF(XT_D_NAV
, "webview_nw_cb: %s\n",
4959 webkit_network_request_get_uri(request
));
4961 /* open in current tab */
4962 uri
= (char *)webkit_network_request_get_uri(request
);
4963 webkit_web_view_load_uri(t
->wv
, uri
);
4964 webkit_web_policy_decision_ignore(pd
);
4966 return (TRUE
); /* we made the decission */
4970 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
4971 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
4972 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
4977 errx(1, "webview_npd_cb");
4979 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
4981 webkit_network_request_get_uri(request
));
4983 uri
= (char *)webkit_network_request_get_uri(request
);
4985 if ((!parse_xtp_url(t
, uri
) && (t
->ctrl_click
))) {
4987 create_new_tab(uri
, NULL
, ctrl_click_focus
);
4988 webkit_web_policy_decision_ignore(pd
);
4989 return (TRUE
); /* we made the decission */
4992 webkit_web_policy_decision_use(pd
);
4993 return (TRUE
); /* we made the decission */
4997 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5000 errx(1, "webview_cwv_cb");
5002 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
5003 webkit_web_view_get_uri(wv
));
5009 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
5011 /* we can not eat the event without throwing gtk off so defer it */
5013 /* catch middle click */
5014 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
5019 /* catch ctrl click */
5020 if (e
->type
== GDK_BUTTON_RELEASE
&&
5021 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
5026 return (XT_CB_PASSTHROUGH
);
5030 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
5032 struct mime_type
*m
;
5034 m
= find_mime_type(mime_type
);
5049 execlp(m
->mt_action
, m
->mt_action
,
5050 webkit_network_request_get_uri(request
), (void *)NULL
);
5059 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
5060 WebKitNetworkRequest
*request
, char *mime_type
,
5061 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
5064 errx(1, "webview_mimetype_cb");
5066 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
5067 t
->tab_id
, mime_type
);
5069 if (run_mimehandler(t
, mime_type
, request
) == 0) {
5070 webkit_web_policy_decision_ignore(decision
);
5071 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
5075 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
5076 webkit_web_policy_decision_download(decision
);
5084 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
, struct tab
*t
)
5086 const gchar
*filename
;
5088 struct download
*download_entry
;
5091 if (wk_download
== NULL
|| t
== NULL
)
5092 errx(1, "%s: invalid pointers", __func__
);
5094 filename
= webkit_download_get_suggested_filename(wk_download
);
5095 if (filename
== NULL
)
5096 return (FALSE
); /* abort download */
5098 uri
= g_strdup_printf("file://%s/%s", download_dir
, filename
);
5100 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
5101 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
5103 webkit_download_set_destination_uri(wk_download
, uri
);
5105 if (webkit_download_get_status(wk_download
) ==
5106 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5107 show_oops(t
, "%s: download failed to start", __func__
);
5109 gtk_label_set_text(GTK_LABEL(t
->label
), "Download Failed");
5111 download_entry
= g_malloc(sizeof(struct download
));
5112 download_entry
->download
= wk_download
;
5113 download_entry
->tab
= t
;
5114 download_entry
->id
= next_download_id
++;
5115 RB_INSERT(download_list
, &downloads
, download_entry
);
5116 /* get from history */
5117 g_object_ref(wk_download
);
5118 gtk_label_set_text(GTK_LABEL(t
->label
), "Downloading");
5124 /* sync other download manager tabs */
5125 update_download_tabs(NULL
);
5128 * NOTE: never redirect/render the current tab before this
5129 * function returns. This will cause the download to never start.
5131 return (ret
); /* start download */
5134 /* XXX currently unused */
5136 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
5138 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
5141 errx(1, "webview_hover_cb");
5148 t
->hover
= g_strdup(uri
);
5149 } else if (t
->hover
) {
5156 wv_keypress_after_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
5159 char s
[2], buf
[128];
5160 const char *errstr
= NULL
;
5163 /* don't use w directly; use t->whatever instead */
5166 errx(1, "wv_keypress_after_cb");
5168 DNPRINTF(XT_D_KEY
, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
5169 e
->keyval
, e
->state
, t
);
5173 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
5175 return (XT_CB_HANDLED
);
5179 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Return
) {
5180 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
5182 /* we have a string */
5184 /* we have a number */
5185 snprintf(buf
, sizeof buf
, "vimprobable_fire(%s)",
5193 /* XXX unfuck this */
5194 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_BackSpace
) {
5195 if (t
->hint_mode
== XT_HINT_NUMERICAL
) {
5196 /* last input was numerical */
5198 l
= strlen(t
->hint_num
);
5205 t
->hint_num
[l
] = '\0';
5209 } else if (t
->hint_mode
== XT_HINT_ALPHANUM
) {
5210 /* last input was alphanumerical */
5212 l
= strlen(t
->hint_buf
);
5219 t
->hint_buf
[l
] = '\0';
5229 /* numerical input */
5230 if (CLEAN(e
->state
) == 0 &&
5231 ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) || (e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
))) {
5232 snprintf(s
, sizeof s
, "%c", e
->keyval
);
5233 strlcat(t
->hint_num
, s
, sizeof t
->hint_num
);
5234 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: numerical %s\n",
5237 link
= strtonum(t
->hint_num
, 1, 1000, &errstr
);
5239 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: invalid link number\n");
5242 snprintf(buf
, sizeof buf
, "vimprobable_update_hints(%s)",
5244 t
->hint_mode
= XT_HINT_NUMERICAL
;
5248 /* empty the counter buffer */
5249 bzero(t
->hint_buf
, sizeof t
->hint_buf
);
5250 return (XT_CB_HANDLED
);
5253 /* alphanumerical input */
5255 (CLEAN(e
->state
) == 0 && e
->keyval
>= GDK_a
&& e
->keyval
<= GDK_z
) ||
5256 (CLEAN(e
->state
) == GDK_SHIFT_MASK
&& e
->keyval
>= GDK_A
&& e
->keyval
<= GDK_Z
) ||
5257 (CLEAN(e
->state
) == 0 && ((e
->keyval
>= GDK_0
&& e
->keyval
<= GDK_9
) ||
5258 ((e
->keyval
>= GDK_KP_0
&& e
->keyval
<= GDK_KP_9
) && (t
->hint_mode
!= XT_HINT_NUMERICAL
))))) {
5259 snprintf(s
, sizeof s
, "%c", e
->keyval
);
5260 strlcat(t
->hint_buf
, s
, sizeof t
->hint_buf
);
5261 DNPRINTF(XT_D_JS
, "wv_keypress_after_cb: alphanumerical %s\n",
5264 snprintf(buf
, sizeof buf
, "vimprobable_cleanup()");
5267 snprintf(buf
, sizeof buf
, "vimprobable_show_hints('%s')",
5269 t
->hint_mode
= XT_HINT_ALPHANUM
;
5272 /* empty the counter buffer */
5273 bzero(t
->hint_num
, sizeof t
->hint_num
);
5274 return (XT_CB_HANDLED
);
5277 return (XT_CB_HANDLED
);
5280 for (i
= 0; i
< LENGTH(keys
); i
++)
5281 if (e
->keyval
== keys
[i
].key
&& CLEAN(e
->state
) ==
5283 keys
[i
].func(t
, &keys
[i
].arg
);
5284 return (XT_CB_HANDLED
);
5288 return (XT_CB_PASSTHROUGH
);
5292 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
5296 return (XT_CB_PASSTHROUGH
);
5300 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
5302 const gchar
*c
= gtk_entry_get_text(w
);
5306 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5307 e
->keyval
, e
->state
, t
);
5310 errx(1, "cmd_keyrelease_cb");
5312 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5313 e
->keyval
, e
->state
, t
);
5317 if (strlen(c
) == 1) {
5318 webkit_web_view_unmark_text_matches(t
->wv
);
5324 else if (c
[0] == '?')
5330 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, forward
, TRUE
) ==
5332 /* not found, mark red */
5333 gdk_color_parse("red", &color
);
5334 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
5335 /* unmark and remove selection */
5336 webkit_web_view_unmark_text_matches(t
->wv
);
5337 /* my kingdom for a way to unselect text in webview */
5339 /* found, highlight all */
5340 webkit_web_view_unmark_text_matches(t
->wv
);
5341 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
5342 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
5343 gdk_color_parse("white", &color
);
5344 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
5347 return (XT_CB_PASSTHROUGH
);
5352 cmd_complete(struct tab
*t
, char *s
)
5355 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
5357 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: complete %s\n", s
);
5359 for (i
= 0; i
< LENGTH(cmds
); i
++) {
5360 if (!strncasecmp(cmds
[i
].cmd
, s
, strlen(s
))) {
5361 fprintf(stderr
, "match %s %d\n", cmds
[i
].cmd
, strcasecmp(cmds
[i
].cmd
, s
));
5363 gtk_entry_set_text(w
, ":");
5364 gtk_entry_append_text(w
, cmds
[i
].cmd
);
5365 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
5375 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
5380 errx(1, "entry_key_cb");
5382 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
5383 e
->keyval
, e
->state
, t
);
5387 if (e
->keyval
== GDK_Escape
)
5388 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
5390 for (i
= 0; i
< LENGTH(keys
); i
++)
5391 if (e
->keyval
== keys
[i
].key
&&
5392 CLEAN(e
->state
) == keys
[i
].mask
&&
5393 keys
[i
].use_in_entry
) {
5394 keys
[i
].func(t
, &keys
[i
].arg
);
5395 return (XT_CB_HANDLED
);
5398 return (XT_CB_PASSTHROUGH
);
5402 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
5404 int rv
= XT_CB_HANDLED
;
5405 const gchar
*c
= gtk_entry_get_text(w
);
5408 errx(1, "cmd_keypress_cb");
5410 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
5411 e
->keyval
, e
->state
, t
);
5415 e
->keyval
= GDK_Escape
;
5416 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
5417 e
->keyval
= GDK_Escape
;
5419 switch (e
->keyval
) {
5425 if (strchr (c
, ' ')) {
5426 /* par completion */
5427 fprintf(stderr
, "completeme par\n");
5431 cmd_complete(t
, (char *)&c
[1]);
5436 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?")))
5441 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
5444 if (c
[0] == '/' || c
[0] == '?')
5445 webkit_web_view_unmark_text_matches(t
->wv
);
5449 rv
= XT_CB_PASSTHROUGH
;
5455 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
5458 errx(1, "cmd_focusout_cb");
5460 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d focus_wv %d\n",
5461 t
->tab_id
, t
->focus_wv
);
5466 if (show_url
== 0 || t
->focus_wv
)
5467 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
5469 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
5471 return (XT_CB_PASSTHROUGH
);
5475 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
5479 const gchar
*c
= gtk_entry_get_text(entry
);
5482 errx(1, "cmd_activate_cb");
5484 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
5489 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?'))
5495 if (c
[0] == '/' || c
[0] == '?') {
5496 if (t
->search_text
) {
5497 g_free(t
->search_text
);
5498 t
->search_text
= NULL
;
5501 t
->search_text
= g_strdup(s
);
5503 g_free(global_search
);
5504 global_search
= g_strdup(s
);
5505 t
->search_forward
= c
[0] == '/';
5510 for (i
= 0; i
< LENGTH(cmds
); i
++)
5511 if (cmds
[i
].params
) {
5512 if (!strncmp(s
, cmds
[i
].cmd
, strlen(cmds
[i
].cmd
))) {
5513 cmds
[i
].arg
.s
= g_strdup(s
);
5514 goto execute_command
;
5517 if (!strcmp(s
, cmds
[i
].cmd
))
5518 goto execute_command
;
5520 show_oops(t
, "Invalid command: %s", s
);
5527 cmds
[i
].func(t
, &cmds
[i
].arg
);
5530 backward_cb(GtkWidget
*w
, struct tab
*t
)
5535 errx(1, "backward_cb");
5537 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
5544 forward_cb(GtkWidget
*w
, struct tab
*t
)
5549 errx(1, "forward_cb");
5551 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
5553 a
.i
= XT_NAV_FORWARD
;
5558 stop_cb(GtkWidget
*w
, struct tab
*t
)
5560 WebKitWebFrame
*frame
;
5565 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
5567 frame
= webkit_web_view_get_main_frame(t
->wv
);
5568 if (frame
== NULL
) {
5569 show_oops(t
, "stop_cb: no frame");
5573 webkit_web_frame_stop_loading(frame
);
5574 abort_favicon_download(t
);
5578 setup_webkit(struct tab
*t
)
5580 g_object_set((GObject
*)t
->settings
,
5581 "user-agent", t
->user_agent
, (char *)NULL
);
5582 g_object_set((GObject
*)t
->settings
,
5583 "enable-scripts", enable_scripts
, (char *)NULL
);
5584 g_object_set((GObject
*)t
->settings
,
5585 "enable-plugins", enable_plugins
, (char *)NULL
);
5586 adjustfont_webkit(t
, XT_FONT_SET
);
5588 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5592 create_browser(struct tab
*t
)
5598 errx(1, "create_browser");
5600 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
5601 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
5602 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
5603 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
5605 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
5606 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
5607 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
5609 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
5610 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
5613 t
->settings
= webkit_web_settings_new();
5615 if (user_agent
== NULL
) {
5616 g_object_get((GObject
*)t
->settings
, "user-agent", &strval
,
5618 t
->user_agent
= g_strdup_printf("%s %s+", strval
, version
);
5621 t
->user_agent
= g_strdup(user_agent
);
5634 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
5635 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
5636 gtk_widget_set_name(w
, "xxxterm");
5637 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
5638 g_signal_connect(G_OBJECT(w
), "delete_event",
5639 G_CALLBACK (gtk_main_quit
), NULL
);
5645 create_toolbar(struct tab
*t
)
5647 GtkWidget
*toolbar
= NULL
, *b
, *eb1
;
5649 b
= gtk_hbox_new(FALSE
, 0);
5651 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
5654 /* backward button */
5655 t
->backward
= create_button("GoBack", GTK_STOCK_GO_BACK
, 0);
5656 gtk_widget_set_sensitive(t
->backward
, FALSE
);
5657 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
5658 G_CALLBACK(backward_cb
), t
);
5659 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
5661 /* forward button */
5662 t
->forward
= create_button("GoForward",GTK_STOCK_GO_FORWARD
, 0);
5663 gtk_widget_set_sensitive(t
->forward
, FALSE
);
5664 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
5665 G_CALLBACK(forward_cb
), t
);
5666 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
,
5670 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
5671 gtk_widget_set_sensitive(t
->stop
, FALSE
);
5672 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
5673 G_CALLBACK(stop_cb
), t
);
5674 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
,
5678 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
5679 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
5680 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
5681 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
5682 G_CALLBACK(js_toggle_cb
), t
);
5683 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
5686 t
->uri_entry
= gtk_entry_new();
5687 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
5688 G_CALLBACK(activate_uri_entry_cb
), t
);
5689 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
5690 (GCallback
)entry_key_cb
, t
);
5691 eb1
= gtk_hbox_new(FALSE
, 0);
5692 gtk_container_set_border_width(GTK_CONTAINER(eb1
), 1);
5693 gtk_box_pack_start(GTK_BOX(eb1
), t
->uri_entry
, TRUE
, TRUE
, 0);
5694 gtk_box_pack_start(GTK_BOX(b
), eb1
, TRUE
, TRUE
, 0);
5696 /* set empty favicon */
5697 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
5698 GTK_ENTRY_ICON_PRIMARY
, "text-html");
5701 if (fancy_bar
&& search_string
) {
5703 t
->search_entry
= gtk_entry_new();
5704 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
5705 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
5706 G_CALLBACK(activate_search_entry_cb
), t
);
5707 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
5708 (GCallback
)entry_key_cb
, t
);
5709 gtk_widget_set_size_request(t
->search_entry
, -1, -1);
5710 eb2
= gtk_hbox_new(FALSE
, 0);
5711 gtk_container_set_border_width(GTK_CONTAINER(eb2
), 1);
5712 gtk_box_pack_start(GTK_BOX(eb2
), t
->search_entry
, TRUE
, TRUE
,
5714 gtk_box_pack_start(GTK_BOX(b
), eb2
, FALSE
, FALSE
, 0);
5724 TAILQ_FOREACH(t
, &tabs
, entry
)
5725 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
5729 undo_close_tab_save(struct tab
*t
)
5733 struct undo
*u1
, *u2
;
5734 WebKitWebFrame
*frame
;
5736 WebKitWebHistoryItem
*item
;
5738 frame
= webkit_web_view_get_main_frame(t
->wv
);
5739 uri
= webkit_web_frame_get_uri(frame
);
5741 if (uri
&& !strlen(uri
))
5744 u1
= g_malloc0(sizeof(struct undo
));
5745 u1
->uri
= g_strdup(uri
);
5747 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
5749 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
5750 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
5753 /* forward history */
5754 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
5758 u1
->history
= g_list_prepend(u1
->history
,
5759 webkit_web_history_item_copy(item
));
5760 items
= g_list_next(items
);
5765 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
5766 u1
->history
= g_list_prepend(u1
->history
,
5767 webkit_web_history_item_copy(item
));
5771 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
5775 u1
->history
= g_list_prepend(u1
->history
,
5776 webkit_web_history_item_copy(item
));
5777 items
= g_list_next(items
);
5780 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
5782 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
5783 u2
= TAILQ_LAST(&undos
, undo_tailq
);
5784 TAILQ_REMOVE(&undos
, u2
, entry
);
5786 g_list_free(u2
->history
);
5795 delete_tab(struct tab
*t
)
5799 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
5804 TAILQ_REMOVE(&tabs
, t
, entry
);
5806 /* halt all webkit activity */
5807 abort_favicon_download(t
);
5808 webkit_web_view_stop_loading(t
->wv
);
5809 undo_close_tab_save(t
);
5811 gtk_widget_destroy(t
->vbox
);
5812 g_free(t
->user_agent
);
5816 if (TAILQ_EMPTY(&tabs
))
5817 create_new_tab(NULL
, NULL
, 1);
5820 /* recreate session */
5821 if (session_autosave
) {
5828 adjustfont_webkit(struct tab
*t
, int adjust
)
5831 errx(1, "adjustfont_webkit");
5833 if (adjust
== XT_FONT_SET
)
5834 t
->font_size
= default_font_size
;
5836 t
->font_size
+= adjust
;
5837 g_object_set((GObject
*)t
->settings
, "default-font-size",
5838 t
->font_size
, (char *)NULL
);
5839 g_object_get((GObject
*)t
->settings
, "default-font-size",
5840 &t
->font_size
, (char *)NULL
);
5844 append_tab(struct tab
*t
)
5849 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
5850 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
5854 create_new_tab(char *title
, struct undo
*u
, int focus
)
5857 int load
= 1, id
, notfound
;
5858 char *newuri
= NULL
;
5860 WebKitWebHistoryItem
*item
;
5864 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
5866 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
5867 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
5871 t
= g_malloc0(sizeof *t
);
5873 if (title
== NULL
) {
5874 title
= "(untitled)";
5877 if (valid_url_type(title
)) {
5878 newuri
= guess_url_type(title
);
5883 t
->vbox
= gtk_vbox_new(FALSE
, 0);
5885 /* label + button for tab */
5886 b
= gtk_hbox_new(FALSE
, 0);
5889 #if GTK_CHECK_VERSION(2, 20, 0)
5890 t
->spinner
= gtk_spinner_new ();
5892 t
->label
= gtk_label_new(title
);
5893 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
5894 gtk_widget_set_size_request(t
->label
, 100, 0);
5895 gtk_widget_set_size_request(b
, 130, 0);
5897 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
5898 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
5899 #if GTK_CHECK_VERSION(2, 20, 0)
5900 gtk_box_pack_start(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
5904 t
->toolbar
= create_toolbar(t
);
5905 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
5908 t
->browser_win
= create_browser(t
);
5909 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
5911 /* oops message for user feedback */
5912 t
->oops
= gtk_entry_new();
5913 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
5914 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
5915 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
5916 gdk_color_parse("red", &color
);
5917 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
5918 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
5921 t
->cmd
= gtk_entry_new();
5922 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
5923 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
5924 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
5926 /* xtp meaning is normal by default */
5927 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
5929 /* and show it all */
5930 gtk_widget_show_all(b
);
5931 gtk_widget_show_all(t
->vbox
);
5933 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
5937 id
= gtk_notebook_get_current_page(notebook
);
5938 TAILQ_FOREACH(tt
, &tabs
, entry
) {
5939 if (tt
->tab_id
== id
) {
5941 TAILQ_INSERT_AFTER(&tabs
, tt
, t
, entry
);
5942 gtk_notebook_insert_page(notebook
, t
->vbox
, b
,
5952 #if GTK_CHECK_VERSION(2, 20, 0)
5953 /* turn spinner off if we are a new tab without uri */
5955 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
5956 gtk_widget_hide(t
->spinner
);
5959 /* make notebook tabs reorderable */
5960 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
5962 g_object_connect((GObject
*)t
->cmd
,
5963 "signal::key-press-event", (GCallback
)cmd_keypress_cb
, t
,
5964 "signal::key-release-event", (GCallback
)cmd_keyrelease_cb
, t
,
5965 "signal::focus-out-event", (GCallback
)cmd_focusout_cb
, t
,
5966 "signal::activate", (GCallback
)cmd_activate_cb
, t
,
5969 /* reuse wv_button_cb to hide oops */
5970 g_object_connect((GObject
*)t
->oops
,
5971 "signal::button_press_event", (GCallback
)wv_button_cb
, t
,
5974 g_object_connect((GObject
*)t
->wv
,
5975 "signal::key-press-event", (GCallback
)wv_keypress_cb
, t
,
5976 "signal-after::key-press-event", (GCallback
)wv_keypress_after_cb
, t
,
5977 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
5978 "signal::download-requested", (GCallback
)webview_download_cb
, t
,
5979 "signal::mime-type-policy-decision-requested", (GCallback
)webview_mimetype_cb
, t
,
5980 "signal::navigation-policy-decision-requested", (GCallback
)webview_npd_cb
, t
,
5981 "signal::new-window-policy-decision-requested", (GCallback
)webview_nw_cb
, t
,
5982 "signal::create-web-view", (GCallback
)webview_cwv_cb
, t
,
5983 "signal::event", (GCallback
)webview_event_cb
, t
,
5984 "signal::load-finished", (GCallback
)webview_load_finished_cb
, t
,
5985 "signal::load-progress-changed", (GCallback
)webview_progress_changed_cb
, t
,
5986 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5987 "signal::icon-loaded", (GCallback
)notify_icon_loaded_cb
, t
,
5989 "signal::button_press_event", (GCallback
)wv_button_cb
, t
,
5991 g_signal_connect(t
->wv
,
5992 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
5994 /* hijack the unused keys as if we were the browser */
5995 g_object_connect((GObject
*)t
->toolbar
,
5996 "signal-after::key-press-event", (GCallback
)wv_keypress_after_cb
, t
,
5999 g_signal_connect(G_OBJECT(bb
), "button_press_event",
6000 G_CALLBACK(tab_close_cb
), t
);
6005 url_set_visibility();
6008 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
6009 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
6014 webkit_web_view_load_uri(t
->wv
, title
);
6017 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6019 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6022 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
6023 /* restore the tab's history */
6024 if (u
&& u
->history
) {
6028 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
6029 items
= g_list_next(items
);
6032 item
= g_list_nth_data(u
->history
, u
->back
);
6034 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
6037 g_list_free(u
->history
);
6045 notebook_switchpage_cb(GtkNotebook
*nb
, GtkNotebookPage
*nbp
, guint pn
,
6051 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
6053 TAILQ_FOREACH(t
, &tabs
, entry
) {
6054 if (t
->tab_id
== pn
) {
6055 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
6058 uri
= webkit_web_view_get_title(t
->wv
);
6061 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
6067 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6073 menuitem_response(struct tab
*t
)
6075 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
6079 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
6081 GtkWidget
*menu
, *menu_items
;
6082 GdkEventButton
*bevent
;
6083 WebKitWebFrame
*frame
;
6087 if (event
->type
== GDK_BUTTON_PRESS
) {
6088 bevent
= (GdkEventButton
*) event
;
6089 menu
= gtk_menu_new();
6091 TAILQ_FOREACH(ti
, &tabs
, entry
) {
6092 frame
= webkit_web_view_get_main_frame(ti
->wv
);
6093 uri
= webkit_web_frame_get_uri(frame
);
6094 /* XXX make sure there is something to print */
6095 /* XXX add gui pages in here to look purdy */
6098 if (strlen(uri
) == 0)
6100 menu_items
= gtk_menu_item_new_with_label(uri
);
6101 gtk_menu_append(GTK_MENU (menu
), menu_items
);
6102 gtk_widget_show(menu_items
);
6104 gtk_signal_connect_object(GTK_OBJECT(menu_items
),
6105 "activate", GTK_SIGNAL_FUNC(menuitem_response
),
6109 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
6110 bevent
->button
, bevent
->time
);
6112 /* unref object so it'll free itself when popped down */
6113 g_object_ref_sink(menu
);
6114 g_object_unref(menu
);
6116 return (TRUE
/* eat event */);
6119 return (FALSE
/* propagate */);
6123 icon_size_map(int icon_size
)
6125 if (icon_size
<= GTK_ICON_SIZE_INVALID
||
6126 icon_size
> GTK_ICON_SIZE_DIALOG
)
6127 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
6133 create_button(char *name
, char *stockid
, int size
)
6135 GtkWidget
*button
, *image
;
6138 rcstring
= g_strdup_printf(
6139 "style \"%s-style\"\n"
6141 " GtkWidget::focus-padding = 0\n"
6142 " GtkWidget::focus-line-width = 0\n"
6146 "widget \"*.%s\" style \"%s-style\"",name
,name
,name
);
6147 gtk_rc_parse_string(rcstring
);
6149 button
= gtk_button_new();
6150 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
6151 gtk_icon_size
= icon_size_map(size
?size
:icon_size
);
6153 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
6154 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
6155 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
6156 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
6157 gtk_widget_set_name(button
, name
);
6158 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
6159 gtk_widget_set_tooltip_text(button
, name
);
6165 button_set_stockid(GtkWidget
*button
, char *stockid
)
6168 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
6169 gtk_widget_set_size_request(GTK_WIDGET(image
), -1, -1);
6170 gtk_button_set_image(GTK_BUTTON(button
), image
);
6179 char file
[PATH_MAX
];
6182 vbox
= gtk_vbox_new(FALSE
, 0);
6183 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
6184 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
6185 gtk_notebook_set_tab_hborder(notebook
, 0);
6186 gtk_notebook_set_tab_vborder(notebook
, 0);
6187 gtk_notebook_set_scrollable(notebook
, TRUE
);
6188 notebook_tab_set_visibility(notebook
);
6189 gtk_notebook_set_show_border(notebook
, FALSE
);
6190 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
6192 abtn
= gtk_button_new();
6193 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
6194 gtk_widget_set_size_request(arrow
, -1, -1);
6195 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
6196 gtk_widget_set_size_request(abtn
, -1, 20);
6197 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
6199 gtk_widget_set_size_request(GTK_WIDGET(notebook
), -1, -1);
6200 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
6201 gtk_widget_set_size_request(vbox
, -1, -1);
6203 g_object_connect((GObject
*)notebook
,
6204 "signal::switch-page", (GCallback
)notebook_switchpage_cb
, NULL
,
6206 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
6207 G_CALLBACK(arrow_cb
), NULL
);
6209 main_window
= create_window();
6210 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
6211 gtk_window_set_title(GTK_WINDOW(main_window
), XT_NAME
);
6214 for (i
= 0; i
< LENGTH(icons
); i
++) {
6215 snprintf(file
, sizeof file
, "%s/%s", resource_dir
, icons
[i
]);
6216 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
6217 l
= g_list_append(l
, pb
);
6219 gtk_window_set_default_icon_list(l
);
6221 gtk_widget_show_all(abtn
);
6222 gtk_widget_show_all(main_window
);
6226 set_hook(void **hook
, char *name
)
6229 errx(1, "set_hook");
6231 if (*hook
== NULL
) {
6232 *hook
= dlsym(RTLD_NEXT
, name
);
6234 errx(1, "can't hook %s", name
);
6238 /* override libsoup soup_cookie_equal because it doesn't look at domain */
6240 soup_cookie_equal(SoupCookie
*cookie1
, SoupCookie
*cookie2
)
6242 g_return_val_if_fail(cookie1
, FALSE
);
6243 g_return_val_if_fail(cookie2
, FALSE
);
6245 return (!strcmp (cookie1
->name
, cookie2
->name
) &&
6246 !strcmp (cookie1
->value
, cookie2
->value
) &&
6247 !strcmp (cookie1
->path
, cookie2
->path
) &&
6248 !strcmp (cookie1
->domain
, cookie2
->domain
));
6252 transfer_cookies(void)
6255 SoupCookie
*sc
, *pc
;
6257 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
6259 for (;cf
; cf
= cf
->next
) {
6261 sc
= soup_cookie_copy(pc
);
6262 _soup_cookie_jar_add_cookie(s_cookiejar
, sc
);
6265 soup_cookies_free(cf
);
6269 soup_cookie_jar_delete_cookie(SoupCookieJar
*jar
, SoupCookie
*c
)
6274 print_cookie("soup_cookie_jar_delete_cookie", c
);
6276 if (cookies_enabled
== 0)
6279 if (jar
== NULL
|| c
== NULL
)
6282 /* find and remove from persistent jar */
6283 cf
= soup_cookie_jar_all_cookies(p_cookiejar
);
6285 for (;cf
; cf
= cf
->next
) {
6287 if (soup_cookie_equal(ci
, c
)) {
6288 _soup_cookie_jar_delete_cookie(p_cookiejar
, ci
);
6293 soup_cookies_free(cf
);
6295 /* delete from session jar */
6296 _soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
6300 soup_cookie_jar_add_cookie(SoupCookieJar
*jar
, SoupCookie
*cookie
)
6306 DNPRINTF(XT_D_COOKIE
, "soup_cookie_jar_add_cookie: %p %p %p\n",
6307 jar
, p_cookiejar
, s_cookiejar
);
6309 if (cookies_enabled
== 0)
6312 /* see if we are up and running */
6313 if (p_cookiejar
== NULL
) {
6314 _soup_cookie_jar_add_cookie(jar
, cookie
);
6317 /* disallow p_cookiejar adds, shouldn't happen */
6318 if (jar
== p_cookiejar
)
6321 if ((d
= wl_find(cookie
->domain
, &c_wl
)) == NULL
) {
6323 DNPRINTF(XT_D_COOKIE
,
6324 "soup_cookie_jar_add_cookie: reject %s\n",
6326 if (save_rejected_cookies
) {
6327 if ((r_cookie_f
= fopen(rc_fname
, "a+")) == NULL
)
6328 err(1, "reject cookie file");
6329 fseek(r_cookie_f
, 0, SEEK_END
);
6330 fprintf(r_cookie_f
, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
6331 cookie
->http_only
? "#HttpOnly_" : "",
6333 *cookie
->domain
== '.' ? "TRUE" : "FALSE",
6335 cookie
->secure
? "TRUE" : "FALSE",
6337 (gulong
)soup_date_to_time_t(cookie
->expires
) :
6344 if (!allow_volatile_cookies
)
6348 if (cookie
->expires
== NULL
&& session_timeout
) {
6349 soup_cookie_set_expires(cookie
,
6350 soup_date_new_from_now(session_timeout
));
6351 print_cookie("modified add cookie", cookie
);
6354 /* see if we are white listed for persistence */
6355 if (d
&& d
->handy
) {
6356 /* add to persistent jar */
6357 c
= soup_cookie_copy(cookie
);
6358 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c
);
6359 _soup_cookie_jar_add_cookie(p_cookiejar
, c
);
6362 /* add to session jar */
6363 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie
);
6364 _soup_cookie_jar_add_cookie(s_cookiejar
, cookie
);
6370 char file
[PATH_MAX
];
6372 set_hook((void *)&_soup_cookie_jar_add_cookie
,
6373 "soup_cookie_jar_add_cookie");
6374 set_hook((void *)&_soup_cookie_jar_delete_cookie
,
6375 "soup_cookie_jar_delete_cookie");
6377 if (cookies_enabled
== 0)
6381 * the following code is intricate due to overriding several libsoup
6383 * do not alter order of these operations.
6386 /* rejected cookies */
6387 if (save_rejected_cookies
)
6388 snprintf(rc_fname
, sizeof file
, "%s/rejected.txt", work_dir
);
6390 /* persistent cookies */
6391 snprintf(file
, sizeof file
, "%s/cookies.txt", work_dir
);
6392 p_cookiejar
= soup_cookie_jar_text_new(file
, read_only_cookies
);
6394 /* session cookies */
6395 s_cookiejar
= soup_cookie_jar_new();
6396 g_object_set(G_OBJECT(s_cookiejar
), SOUP_COOKIE_JAR_ACCEPT_POLICY
,
6397 cookie_policy
, (void *)NULL
);
6400 soup_session_add_feature(session
, (SoupSessionFeature
*)s_cookiejar
);
6404 setup_proxy(char *uri
)
6407 g_object_set(session
, "proxy_uri", NULL
, (char *)NULL
);
6408 soup_uri_free(proxy_uri
);
6412 if (http_proxy
!= uri
) {
6419 http_proxy
= g_strdup(uri
);
6420 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
6421 proxy_uri
= soup_uri_new(http_proxy
);
6422 g_object_set(session
, "proxy-uri", proxy_uri
, (char *)NULL
);
6427 send_url_to_socket(char *url
)
6429 int s
, len
, rv
= -1;
6430 struct sockaddr_un sa
;
6432 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
6433 warnx("send_url_to_socket: socket");
6437 sa
.sun_family
= AF_UNIX
;
6438 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s/%s",
6439 pwd
->pw_dir
, XT_DIR
, XT_SOCKET_FILE
);
6442 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
6443 warnx("send_url_to_socket: connect");
6447 if (send(s
, url
, strlen(url
) + 1, 0) == -1) {
6448 warnx("send_url_to_socket: send");
6457 socket_watcher(gpointer data
, gint fd
, GdkInputCondition cond
)
6460 char str
[XT_MAX_URL_LENGTH
];
6461 socklen_t t
= sizeof(struct sockaddr_un
);
6462 struct sockaddr_un sa
;
6467 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
6468 warn("socket_watcher: accept");
6472 if (getpeereid(s
, &uid
, &gid
) == -1) {
6473 warn("socket_watcher: getpeereid");
6476 if (uid
!= getuid() || gid
!= getgid()) {
6477 warnx("socket_watcher: unauthorized user");
6483 warnx("socket_watcher: not a valid user");
6487 n
= recv(s
, str
, sizeof(str
), 0);
6491 create_new_tab(str
, NULL
, 1);
6498 struct sockaddr_un sa
;
6500 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
6501 warn("is_running: socket");
6505 sa
.sun_family
= AF_UNIX
;
6506 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s/%s",
6507 pwd
->pw_dir
, XT_DIR
, XT_SOCKET_FILE
);
6510 /* connect to see if there is a listener */
6511 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
6512 rv
= 0; /* not running */
6514 rv
= 1; /* already running */
6525 struct sockaddr_un sa
;
6527 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
6528 warn("build_socket: socket");
6532 sa
.sun_family
= AF_UNIX
;
6533 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s/%s/%s",
6534 pwd
->pw_dir
, XT_DIR
, XT_SOCKET_FILE
);
6537 /* connect to see if there is a listener */
6538 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
6539 /* no listener so we will */
6540 unlink(sa
.sun_path
);
6542 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
6543 warn("build_socket: bind");
6547 if (listen(s
, 1) == -1) {
6548 warn("build_socket: listen");
6564 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
6569 main(int argc
, char *argv
[])
6572 int c
, s
, optn
= 0, focus
= 1;
6573 char conf
[PATH_MAX
] = { '\0' };
6574 char file
[PATH_MAX
];
6575 char *env_proxy
= NULL
;
6581 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
6583 while ((c
= getopt(argc
, argv
, "STVf:s:tn")) != -1) {
6592 errx(0 , "Version: %s", version
);
6595 strlcpy(conf
, optarg
, sizeof(conf
));
6598 strlcpy(named_session
, optarg
, sizeof(named_session
));
6617 RB_INIT(&downloads
);
6619 TAILQ_INIT(&aliases
);
6622 gnutls_global_init();
6624 /* generate session keys for xtp pages */
6625 generate_xtp_session_key(&dl_session_key
);
6626 generate_xtp_session_key(&hl_session_key
);
6627 generate_xtp_session_key(&cl_session_key
);
6628 generate_xtp_session_key(&fl_session_key
);
6631 gtk_init(&argc
, &argv
);
6632 if (!g_thread_supported())
6633 g_thread_init(NULL
);
6635 pwd
= getpwuid(getuid());
6637 errx(1, "invalid user %d", getuid());
6639 /* set download dir */
6640 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
6642 /* set default string settings */
6643 home
= g_strdup("http://www.peereboom.us");
6644 resource_dir
= g_strdup("/usr/local/share/xxxterm/");
6645 strlcpy(runtime_settings
,"runtime", sizeof runtime_settings
);
6647 /* read config file */
6648 if (strlen(conf
) == 0)
6649 snprintf(conf
, sizeof conf
, "%s/.%s",
6650 pwd
->pw_dir
, XT_CONF_FILE
);
6651 config_parse(conf
, 0);
6653 /* working directory */
6654 snprintf(work_dir
, sizeof work_dir
, "%s/%s", pwd
->pw_dir
, XT_DIR
);
6655 if (stat(work_dir
, &sb
)) {
6656 if (mkdir(work_dir
, S_IRWXU
) == -1)
6657 err(1, "mkdir work_dir");
6658 if (stat(work_dir
, &sb
))
6659 err(1, "stat work_dir");
6661 if (S_ISDIR(sb
.st_mode
) == 0)
6662 errx(1, "%s not a dir", work_dir
);
6663 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
6664 warnx("fixing invalid permissions on %s", work_dir
);
6665 if (chmod(work_dir
, S_IRWXU
) == -1)
6669 /* icon cache dir */
6670 snprintf(cache_dir
, sizeof cache_dir
, "%s/%s/%s",
6671 pwd
->pw_dir
, XT_DIR
, XT_CACHE_DIR
);
6672 if (stat(cache_dir
, &sb
)) {
6673 if (mkdir(cache_dir
, S_IRWXU
) == -1)
6674 err(1, "mkdir cache_dir");
6675 if (stat(cache_dir
, &sb
))
6676 err(1, "stat cache_dir");
6678 if (S_ISDIR(sb
.st_mode
) == 0)
6679 errx(1, "%s not a dir", cache_dir
);
6680 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
6681 warnx("fixing invalid permissions on %s", cache_dir
);
6682 if (chmod(cache_dir
, S_IRWXU
) == -1)
6687 snprintf(certs_dir
, sizeof certs_dir
, "%s/%s/%s",
6688 pwd
->pw_dir
, XT_DIR
, XT_CERT_DIR
);
6689 if (stat(certs_dir
, &sb
)) {
6690 if (mkdir(certs_dir
, S_IRWXU
) == -1)
6691 err(1, "mkdir certs_dir");
6692 if (stat(certs_dir
, &sb
))
6693 err(1, "stat certs_dir");
6695 if (S_ISDIR(sb
.st_mode
) == 0)
6696 errx(1, "%s not a dir", certs_dir
);
6697 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
6698 warnx("fixing invalid permissions on %s", certs_dir
);
6699 if (chmod(certs_dir
, S_IRWXU
) == -1)
6704 snprintf(sessions_dir
, sizeof sessions_dir
, "%s/%s/%s",
6705 pwd
->pw_dir
, XT_DIR
, XT_SESSIONS_DIR
);
6706 if (stat(sessions_dir
, &sb
)) {
6707 if (mkdir(sessions_dir
, S_IRWXU
) == -1)
6708 err(1, "mkdir sessions_dir");
6709 if (stat(sessions_dir
, &sb
))
6710 err(1, "stat sessions_dir");
6712 if (S_ISDIR(sb
.st_mode
) == 0)
6713 errx(1, "%s not a dir", sessions_dir
);
6714 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
6715 warnx("fixing invalid permissions on %s", sessions_dir
);
6716 if (chmod(sessions_dir
, S_IRWXU
) == -1)
6719 /* runtime settings that can override config file */
6720 if (runtime_settings
[0] != '\0')
6721 config_parse(runtime_settings
, 1);
6724 if (!strcmp(download_dir
, pwd
->pw_dir
))
6725 strlcat(download_dir
, "/downloads", sizeof download_dir
);
6726 if (stat(download_dir
, &sb
)) {
6727 if (mkdir(download_dir
, S_IRWXU
) == -1)
6728 err(1, "mkdir download_dir");
6729 if (stat(download_dir
, &sb
))
6730 err(1, "stat download_dir");
6732 if (S_ISDIR(sb
.st_mode
) == 0)
6733 errx(1, "%s not a dir", download_dir
);
6734 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
6735 warnx("fixing invalid permissions on %s", download_dir
);
6736 if (chmod(download_dir
, S_IRWXU
) == -1)
6740 /* favorites file */
6741 snprintf(file
, sizeof file
, "%s/%s", work_dir
, XT_FAVS_FILE
);
6742 if (stat(file
, &sb
)) {
6743 warnx("favorites file doesn't exist, creating it");
6744 if ((f
= fopen(file
, "w")) == NULL
)
6745 err(1, "favorites");
6750 session
= webkit_get_default_session();
6755 if (stat(ssl_ca_file
, &sb
)) {
6756 warn("no CA file: %s", ssl_ca_file
);
6757 g_free(ssl_ca_file
);
6760 g_object_set(session
,
6761 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
6762 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
6767 env_proxy
= getenv("http_proxy");
6769 setup_proxy(env_proxy
);
6771 setup_proxy(http_proxy
);
6773 /* see if there is already an xxxterm running */
6774 if (single_instance
&& is_running()) {
6776 warnx("already running");
6781 send_url_to_socket(argv
[0]);
6792 if (save_global_history
)
6793 restore_global_history();
6795 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
6796 restore_saved_tabs();
6798 a
.s
= named_session
;
6799 a
.i
= XT_SES_DONOTHING
;
6800 open_tabs(NULL
, &a
);
6804 create_new_tab(argv
[0], NULL
, focus
);
6811 if (TAILQ_EMPTY(&tabs
))
6812 create_new_tab(home
, NULL
, 1);
6815 if ((s
= build_socket()) != -1)
6816 gdk_input_add(s
, GDK_INPUT_READ
, socket_watcher
, NULL
);
6820 gnutls_global_deinit();