handle 0 sized icons; i think this was a side effect of not canceling the
[xxxterm.git] / xxxterm.c
bloba419b947ec963796dce961c33aef05270989b2ab
1 /* $xxxterm$ */
2 /*
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>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 * TODO:
22 * inverse color browsing
23 * favs
24 * - store in sqlite
25 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <err.h>
35 #include <pwd.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <util.h>
39 #include <pthread.h>
40 #include <dlfcn.h>
41 #include <errno.h>
43 #ifdef __linux__
44 #include "linux/tree.h"
45 #else
46 #include <sys/tree.h>
47 #endif
48 #include <sys/queue.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <sys/socket.h>
52 #include <sys/un.h>
54 #include <gtk/gtk.h>
55 #include <gdk/gdkkeysyms.h>
56 #include <webkit/webkit.h>
57 #include <libsoup/soup.h>
58 #include <gnutls/gnutls.h>
59 #include <JavaScriptCore/JavaScript.h>
60 #include <gnutls/x509.h>
62 #include "javascript.h"
65 javascript.h borrowed from vimprobable2 under the following license:
67 Copyright (c) 2009 Leon Winter
68 Copyright (c) 2009 Hannes Schueller
69 Copyright (c) 2009 Matto Fransen
71 Permission is hereby granted, free of charge, to any person obtaining a copy
72 of this software and associated documentation files (the "Software"), to deal
73 in the Software without restriction, including without limitation the rights
74 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
75 copies of the Software, and to permit persons to whom the Software is
76 furnished to do so, subject to the following conditions:
78 The above copyright notice and this permission notice shall be included in
79 all copies or substantial portions of the Software.
81 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
82 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
83 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
84 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
85 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
86 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
87 THE SOFTWARE.
90 static char *version = "$xxxterm$";
92 /* hooked functions */
93 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
94 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
95 SoupCookie *);
97 /*#define XT_DEBUG*/
98 #ifdef XT_DEBUG
99 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
100 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
101 #define XT_D_MOVE 0x0001
102 #define XT_D_KEY 0x0002
103 #define XT_D_TAB 0x0004
104 #define XT_D_URL 0x0008
105 #define XT_D_CMD 0x0010
106 #define XT_D_NAV 0x0020
107 #define XT_D_DOWNLOAD 0x0040
108 #define XT_D_CONFIG 0x0080
109 #define XT_D_JS 0x0100
110 #define XT_D_FAVORITE 0x0200
111 #define XT_D_PRINTING 0x0400
112 #define XT_D_COOKIE 0x0800
113 u_int32_t swm_debug = 0
114 | XT_D_MOVE
115 | XT_D_KEY
116 | XT_D_TAB
117 | XT_D_URL
118 | XT_D_CMD
119 | XT_D_NAV
120 | XT_D_DOWNLOAD
121 | XT_D_CONFIG
122 | XT_D_JS
123 | XT_D_FAVORITE
124 | XT_D_PRINTING
125 | XT_D_COOKIE
127 #else
128 #define DPRINTF(x...)
129 #define DNPRINTF(n,x...)
130 #endif
132 #define LENGTH(x) (sizeof x / sizeof x[0])
133 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
134 ~(GDK_BUTTON1_MASK) & \
135 ~(GDK_BUTTON2_MASK) & \
136 ~(GDK_BUTTON3_MASK) & \
137 ~(GDK_BUTTON4_MASK) & \
138 ~(GDK_BUTTON5_MASK))
140 char *icons[] = {
141 "xxxtermicon16.png",
142 "xxxtermicon32.png",
143 "xxxtermicon48.png",
144 "xxxtermicon64.png",
145 "xxxtermicon128.png"
148 struct tab {
149 TAILQ_ENTRY(tab) entry;
150 GtkWidget *vbox;
151 GtkWidget *tab_content;
152 GtkWidget *label;
153 GtkWidget *spinner;
154 GtkWidget *uri_entry;
155 GtkWidget *search_entry;
156 GtkWidget *toolbar;
157 GtkWidget *browser_win;
158 GtkWidget *cmd;
159 GtkWidget *oops;
160 GtkWidget *backward;
161 GtkWidget *forward;
162 GtkWidget *stop;
163 GtkWidget *js_toggle;
164 GdkPixbuf *icon_pixbuf;
165 guint tab_id;
166 char *icon_uri;
167 WebKitDownload *icon_download;
168 WebKitWebView *wv;
170 WebKitWebHistoryItem *item;
171 WebKitWebBackForwardList *bfl;
173 /* adjustments for browser */
174 GtkScrollbar *sb_h;
175 GtkScrollbar *sb_v;
176 GtkAdjustment *adjust_h;
177 GtkAdjustment *adjust_v;
179 /* flags */
180 int focus_wv;
181 int ctrl_click;
182 gchar *hover;
183 int xtp_meaning; /* identifies dls/favorites */
185 /* hints */
186 int hints_on;
187 int hint_mode;
188 #define XT_HINT_NONE (0)
189 #define XT_HINT_NUMERICAL (1)
190 #define XT_HINT_ALPHANUM (2)
191 char hint_buf[128];
192 char hint_num[128];
194 /* search */
195 char *search_text;
196 int search_forward;
198 /* settings */
199 WebKitWebSettings *settings;
200 int font_size;
201 gchar *user_agent;
203 TAILQ_HEAD(tab_list, tab);
205 struct history {
206 RB_ENTRY(history) entry;
207 const gchar *uri;
208 const gchar *title;
210 RB_HEAD(history_list, history);
212 struct download {
213 RB_ENTRY(download) entry;
214 int id;
215 WebKitDownload *download;
216 struct tab *tab;
218 RB_HEAD(download_list, download);
220 struct domain {
221 RB_ENTRY(domain) entry;
222 gchar *d;
223 int handy; /* app use */
225 RB_HEAD(domain_list, domain);
227 struct undo {
228 TAILQ_ENTRY(undo) entry;
229 gchar *uri;
230 GList *history;
231 int back; /* Keeps track of how many back
232 * history items there are. */
234 TAILQ_HEAD(undo_tailq, undo);
236 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
237 int next_download_id = 1;
239 struct karg {
240 int i;
241 char *s;
244 /* defines */
245 #define XT_NAME ("XXXTerm")
246 #define XT_DIR (".xxxterm")
247 #define XT_CACHE_DIR ("cache")
248 #define XT_CERT_DIR ("certs/")
249 #define XT_SESSIONS_DIR ("sessions/")
250 #define XT_CONF_FILE ("xxxterm.conf")
251 #define XT_FAVS_FILE ("favorites")
252 #define XT_SAVED_TABS_FILE ("main_session")
253 #define XT_RESTART_TABS_FILE ("restart_tabs")
254 #define XT_SOCKET_FILE ("socket")
255 #define XT_HISTORY_FILE ("history")
256 #define XT_CB_HANDLED (TRUE)
257 #define XT_CB_PASSTHROUGH (FALSE)
258 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
259 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
260 #define XT_DLMAN_REFRESH "10"
261 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
262 "td {overflow: hidden;}\n" \
263 "th {background-color: #cccccc}" \
264 "table {width: 90%%; border: 1px black" \
265 " solid; table-layout: fixed}\n</style>\n\n"
266 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
267 #define XT_MAX_UNDO_CLOSE_TAB (32)
269 /* file sizes */
270 #define SZ_KB ((uint64_t) 1024)
271 #define SZ_MB (SZ_KB * SZ_KB)
272 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
273 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
276 * xxxterm "protocol" (xtp)
277 * We use this for managing stuff like downloads and favorites. They
278 * make magical HTML pages in memory which have xxxt:// links in order
279 * to communicate with xxxterm's internals. These links take the format:
280 * xxxt://class/session_key/action/arg
282 * Don't begin xtp class/actions as 0. atoi returns that on error.
284 * Typically we have not put addition of items in this framework, as
285 * adding items is either done via an ex-command or via a keybinding instead.
288 #define XT_XTP_STR "xxxt://"
290 /* XTP classes (xxxt://<class>) */
291 #define XT_XTP_DL 1 /* downloads */
292 #define XT_XTP_HL 2 /* history */
293 #define XT_XTP_CL 3 /* cookies */
294 #define XT_XTP_FL 4 /* favorites */
296 /* XTP download actions */
297 #define XT_XTP_DL_LIST 1
298 #define XT_XTP_DL_CANCEL 2
299 #define XT_XTP_DL_REMOVE 3
301 /* XTP history actions */
302 #define XT_XTP_HL_LIST 1
303 #define XT_XTP_HL_REMOVE 2
305 /* XTP cookie actions */
306 #define XT_XTP_CL_LIST 1
307 #define XT_XTP_CL_REMOVE 2
309 /* XTP cookie actions */
310 #define XT_XTP_FL_LIST 1
311 #define XT_XTP_FL_REMOVE 2
313 /* xtp tab meanings - identifies which tabs have xtp pages in */
314 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
315 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
316 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
317 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
318 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
320 /* actions */
321 #define XT_MOVE_INVALID (0)
322 #define XT_MOVE_DOWN (1)
323 #define XT_MOVE_UP (2)
324 #define XT_MOVE_BOTTOM (3)
325 #define XT_MOVE_TOP (4)
326 #define XT_MOVE_PAGEDOWN (5)
327 #define XT_MOVE_PAGEUP (6)
328 #define XT_MOVE_HALFDOWN (7)
329 #define XT_MOVE_HALFUP (8)
330 #define XT_MOVE_LEFT (9)
331 #define XT_MOVE_FARLEFT (10)
332 #define XT_MOVE_RIGHT (11)
333 #define XT_MOVE_FARRIGHT (12)
335 #define XT_TAB_LAST (-4)
336 #define XT_TAB_FIRST (-3)
337 #define XT_TAB_PREV (-2)
338 #define XT_TAB_NEXT (-1)
339 #define XT_TAB_INVALID (0)
340 #define XT_TAB_NEW (1)
341 #define XT_TAB_DELETE (2)
342 #define XT_TAB_DELQUIT (3)
343 #define XT_TAB_OPEN (4)
344 #define XT_TAB_UNDO_CLOSE (5)
346 #define XT_NAV_INVALID (0)
347 #define XT_NAV_BACK (1)
348 #define XT_NAV_FORWARD (2)
349 #define XT_NAV_RELOAD (3)
350 #define XT_NAV_RELOAD_CACHE (4)
352 #define XT_FOCUS_INVALID (0)
353 #define XT_FOCUS_URI (1)
354 #define XT_FOCUS_SEARCH (2)
356 #define XT_SEARCH_INVALID (0)
357 #define XT_SEARCH_NEXT (1)
358 #define XT_SEARCH_PREV (2)
360 #define XT_PASTE_CURRENT_TAB (0)
361 #define XT_PASTE_NEW_TAB (1)
363 #define XT_FONT_SET (0)
365 #define XT_WL_TOGGLE (1<<0)
366 #define XT_WL_ENABLE (1<<1)
367 #define XT_WL_DISABLE (1<<2)
368 #define XT_WL_FQDN (1<<3) /* default */
369 #define XT_WL_TOPLEVEL (1<<4)
371 #define XT_CMD_OPEN (0)
372 #define XT_CMD_OPEN_CURRENT (1)
373 #define XT_CMD_TABNEW (2)
374 #define XT_CMD_TABNEW_CURRENT (3)
376 /* mime types */
377 struct mime_type {
378 char *mt_type;
379 char *mt_action;
380 int mt_default;
381 TAILQ_ENTRY(mime_type) entry;
383 TAILQ_HEAD(mime_type_list, mime_type);
385 /* uri aliases */
386 struct alias {
387 char *a_name;
388 char *a_uri;
389 TAILQ_ENTRY(alias) entry;
391 TAILQ_HEAD(alias_list, alias);
393 /* settings that require restart */
394 int showtabs = 1; /* show tabs on notebook */
395 int showurl = 1; /* show url toolbar on notebook */
396 int tabless = 0; /* allow only 1 tab */
397 int enable_socket = 0;
398 int single_instance = 0; /* only allow one xxxterm to run */
399 int fancy_bar = 1; /* fancy toolbar */
401 /* runtime settings */
402 int ctrl_click_focus = 0; /* ctrl click gets focus */
403 int cookies_enabled = 1; /* enable cookies */
404 int read_only_cookies = 0; /* enable to not write cookies */
405 int enable_scripts = 0;
406 int enable_plugins = 0;
407 int default_font_size = 12;
408 int window_height = 768;
409 int window_width = 1024;
410 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
411 unsigned refresh_interval = 10; /* download refresh interval */
412 int enable_cookie_whitelist = 1;
413 int enable_js_whitelist = 1;
414 time_t session_timeout = 3600; /* cookie session timeout */
415 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
416 char *ssl_ca_file = NULL;
417 char *resource_dir = NULL;
418 gboolean ssl_strict_certs = FALSE;
419 int append_next = 1; /* append tab after current tab */
420 char *home = NULL;
421 char *search_string = NULL;
422 char *http_proxy = NULL;
423 char download_dir[PATH_MAX];
424 char runtime_settings[PATH_MAX]; /* override of settings */
425 int allow_volatile_cookies = 0;
426 int save_global_history = 0; /* save global history to disk */
427 char *user_agent = NULL;
428 int save_rejected_cookies = 0;
430 struct settings;
431 int set_download_dir(struct settings *, char *);
432 int set_runtime_dir(struct settings *, char *);
433 int set_cookie_policy(struct settings *, char *);
434 int add_alias(struct settings *, char *);
435 int add_mime_type(struct settings *, char *);
436 int add_cookie_wl(struct settings *, char *);
437 int add_js_wl(struct settings *, char *);
438 void button_set_stockid(GtkWidget *, char *);
439 GtkWidget * create_button(char *, char *, int);
441 char *get_cookie_policy(struct settings *);
443 char *get_download_dir(struct settings *);
444 char *get_runtime_dir(struct settings *);
446 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
447 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
448 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
449 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
451 struct special {
452 int (*set)(struct settings *, char *);
453 char *(*get)(struct settings *);
454 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
457 struct special s_cookie = {
458 set_cookie_policy,
459 get_cookie_policy,
460 NULL
463 struct special s_alias = {
464 add_alias,
465 NULL,
466 walk_alias
469 struct special s_mime = {
470 add_mime_type,
471 NULL,
472 walk_mime_type
475 struct special s_js = {
476 add_js_wl,
477 NULL,
478 walk_js_wl
481 struct special s_cookie_wl = {
482 add_cookie_wl,
483 NULL,
484 walk_cookie_wl
487 struct special s_download_dir = {
488 set_download_dir,
489 get_download_dir,
490 NULL
493 struct settings {
494 char *name;
495 int type;
496 #define XT_S_INVALID (0)
497 #define XT_S_INT (1)
498 #define XT_S_STR (2)
499 uint32_t flags;
500 #define XT_SF_RESTART (1<<0)
501 #define XT_SF_RUNTIME (1<<1)
502 int *ival;
503 char **sval;
504 struct special *s;
505 } rs[] = {
506 { "append_next", XT_S_INT, 0 , &append_next, NULL, NULL },
507 { "allow_volatile_cookies", XT_S_INT, 0 , &allow_volatile_cookies, NULL, NULL },
508 { "cookies_enabled", XT_S_INT, 0 , &cookies_enabled, NULL, NULL },
509 { "cookie_policy", XT_S_INT, 0 , NULL, NULL, &s_cookie },
510 { "ctrl_click_focus", XT_S_INT, 0 , &ctrl_click_focus, NULL, NULL },
511 { "default_font_size", XT_S_INT, 0 , &default_font_size, NULL, NULL },
512 { "download_dir", XT_S_STR, 0 , NULL, NULL, &s_download_dir },
513 { "enable_cookie_whitelist", XT_S_INT, 0 , &enable_cookie_whitelist, NULL, NULL },
514 { "enable_js_whitelist", XT_S_INT, 0 , &enable_js_whitelist, NULL, NULL },
515 { "enable_plugins", XT_S_INT, 0 , &enable_plugins, NULL, NULL },
516 { "enable_scripts", XT_S_INT, 0 , &enable_scripts, NULL, NULL },
517 { "enable_socket", XT_S_INT, XT_SF_RESTART , &enable_socket, NULL, NULL },
518 { "fancy_bar", XT_S_INT, XT_SF_RESTART , &fancy_bar, NULL, NULL },
519 { "home", XT_S_STR, 0 , NULL, &home, NULL },
520 { "http_proxy", XT_S_STR, 0 , NULL, &http_proxy, NULL },
521 { "icon_size", XT_S_INT, 0 , &icon_size, NULL, NULL },
522 { "read_only_cookies", XT_S_INT, 0 , &read_only_cookies, NULL, NULL },
523 { "refresh_interval", XT_S_INT, 0 , &refresh_interval, NULL, NULL },
524 { "resource_dir", XT_S_STR, 0 , NULL, &resource_dir, NULL },
525 { "search_string", XT_S_STR, 0 , NULL, &search_string, NULL },
526 { "session_timeout", XT_S_INT, 0 , &session_timeout, NULL, NULL },
527 { "save_global_history", XT_S_INT, XT_SF_RESTART , &save_global_history, NULL, NULL },
528 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART , &save_rejected_cookies, NULL, NULL },
529 { "single_instance", XT_S_INT, XT_SF_RESTART , &single_instance, NULL, NULL },
530 { "ssl_ca_file", XT_S_STR, 0 , NULL, &ssl_ca_file, NULL },
531 { "ssl_strict_certs", XT_S_INT, 0 , &ssl_strict_certs, NULL, NULL },
532 { "user_agent", XT_S_STR, 0 , NULL, &user_agent, NULL },
533 { "window_height", XT_S_INT, 0 , &window_height, NULL, NULL },
534 { "window_width", XT_S_INT, 0 , &window_width, NULL, NULL },
536 /* runtime settings */
537 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
538 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
539 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
540 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
543 /* globals */
544 extern char *__progname;
545 char **start_argv;
546 struct passwd *pwd;
547 GtkWidget *main_window;
548 GtkNotebook *notebook;
549 GtkWidget *arrow, *abtn;
550 struct tab_list tabs;
551 struct history_list hl;
552 struct download_list downloads;
553 struct domain_list c_wl;
554 struct domain_list js_wl;
555 struct undo_tailq undos;
556 int undo_count;
557 int updating_dl_tabs = 0;
558 int updating_hl_tabs = 0;
559 int updating_cl_tabs = 0;
560 int updating_fl_tabs = 0;
561 char *global_search;
562 uint64_t blocked_cookies = 0;
563 char named_session[PATH_MAX];
564 int notify_icon_loaded_cb(WebKitWebView *, char *,
565 struct tab *);
566 void update_favicon(struct tab *);
567 int icon_size_map(int);
569 void
570 check_favicon(struct tab *t)
572 const gchar *iconuri = webkit_web_view_get_icon_uri(t->wv);
574 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'\n",
575 __func__, t->tab_id, iconuri);
577 if (iconuri && strlen(iconuri) > 0)
578 notify_icon_loaded_cb(t->wv, (char *)iconuri, t);
579 else {
580 g_free(t->icon_uri);
581 t->icon_uri = NULL;
582 update_favicon(t);
586 void
587 update_favicon(struct tab *t)
589 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'\n",
590 __func__, t->tab_id, t->icon_uri);
592 if (t->icon_uri && strlen(t->icon_uri) > 0 && t->icon_pixbuf) {
593 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
594 GTK_ENTRY_ICON_PRIMARY, t->icon_pixbuf);
595 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'"
596 " (set)\n", __func__, t->tab_id,
597 t->icon_uri ? t->icon_uri : "(NULL)");
598 } else {
599 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
600 GTK_ENTRY_ICON_PRIMARY, "text-html");
601 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'"
602 " (!set)\n", __func__, t->tab_id,
603 (t->icon_uri && strlen(t->icon_uri) > 0) ?
604 t->icon_uri : "<empty>");
608 void
609 load_webkit_string(struct tab *t, const char *str)
611 /* we set this to indicate we want to manually do navaction */
612 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
613 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
616 void
617 hide_oops(struct tab *t)
619 gtk_widget_hide(t->oops);
622 void
623 hide_cmd(struct tab *t)
625 gtk_widget_hide(t->cmd);
628 void
629 show_cmd(struct tab *t)
631 gtk_widget_hide(t->oops);
632 gtk_widget_show(t->cmd);
635 void
636 show_oops(struct tab *t, const char *fmt, ...)
638 va_list ap;
639 char *msg;
641 if (fmt == NULL)
642 return;
644 va_start(ap, fmt);
645 if (vasprintf(&msg, fmt, ap) == -1)
646 errx(1, "moo");
647 va_end(ap);
649 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
650 gtk_widget_hide(t->cmd);
651 gtk_widget_show(t->oops);
653 char *
654 get_as_string(struct settings *s)
656 char *r = NULL;
658 if (s == NULL)
659 return (NULL);
661 if (s->s) {
662 if (s->s->get)
663 r = s->s->get(s);
664 else
665 warnx("get_as_string skip %s\n", s->name);
666 } else if (s->type == XT_S_INT)
667 r = g_strdup_printf("%d", *s->ival);
668 else if (s->type == XT_S_STR)
669 r = g_strdup(*s->sval);
670 else
671 r = g_strdup_printf("INVALID TYPE");
673 return (r);
676 void
677 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
679 int i;
680 char *s;
682 for (i = 0; i < LENGTH(rs); i++) {
683 if (rs[i].s && rs[i].s->walk)
684 rs[i].s->walk(&rs[i], cb, cb_args);
685 else {
686 s = get_as_string(&rs[i]);
687 cb(&rs[i], s, cb_args);
688 g_free(s);
694 set_cookie_policy(struct settings *s, char *val)
696 if (!strcmp(val, "no3rdparty"))
697 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
698 else if (!strcmp(val, "accept"))
699 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
700 else if (!strcmp(val, "reject"))
701 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
702 else
703 return (1);
705 return (0);
708 char *
709 get_cookie_policy(struct settings *s)
711 char *r = NULL;
713 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
714 r = g_strdup("no3rdparty");
715 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
716 r = g_strdup("accept");
717 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
718 r = g_strdup("reject");
719 else
720 return (NULL);
722 return (r);
725 char *
726 get_download_dir(struct settings *s)
728 if (download_dir[0] == '\0')
729 return (0);
730 return (g_strdup(download_dir));
734 set_download_dir(struct settings *s, char *val)
736 if (val[0] == '~')
737 snprintf(download_dir, sizeof download_dir, "%s/%s",
738 pwd->pw_dir, &val[1]);
739 else
740 strlcpy(download_dir, val, sizeof download_dir);
742 return (0);
746 * Session IDs.
747 * We use these to prevent people putting xxxt:// URLs on
748 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
750 #define XT_XTP_SES_KEY_SZ 8
751 #define XT_XTP_SES_KEY_HEX_FMT \
752 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
753 char *dl_session_key; /* downloads */
754 char *hl_session_key; /* history list */
755 char *cl_session_key; /* cookie list */
756 char *fl_session_key; /* favorites list */
758 char work_dir[PATH_MAX];
759 char certs_dir[PATH_MAX];
760 char cache_dir[PATH_MAX];
761 char sessions_dir[PATH_MAX];
762 char cookie_file[PATH_MAX];
763 SoupURI *proxy_uri = NULL;
764 SoupSession *session;
765 SoupCookieJar *s_cookiejar;
766 SoupCookieJar *p_cookiejar;
767 char rc_fname[PATH_MAX];
769 struct mime_type_list mtl;
770 struct alias_list aliases;
772 /* protos */
773 void create_new_tab(char *, struct undo *, int);
774 void delete_tab(struct tab *);
775 void adjustfont_webkit(struct tab *, int);
776 int run_script(struct tab *, char *);
777 int download_rb_cmp(struct download *, struct download *);
778 int xtp_page_hl(struct tab *t, struct karg *args);
779 int xtp_page_dl(struct tab *t, struct karg *args);
780 int xtp_page_cl(struct tab *t, struct karg *args);
781 int xtp_page_fl(struct tab *t, struct karg *args);
784 history_rb_cmp(struct history *h1, struct history *h2)
786 return (strcmp(h1->uri, h2->uri));
788 RB_GENERATE(history_list, history, entry, history_rb_cmp);
791 domain_rb_cmp(struct domain *d1, struct domain *d2)
793 return (strcmp(d1->d, d2->d));
795 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
798 * generate a session key to secure xtp commands.
799 * pass in a ptr to the key in question and it will
800 * be modified in place.
802 void
803 generate_xtp_session_key(char **key)
805 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
807 /* free old key */
808 if (*key)
809 g_free(*key);
811 /* make a new one */
812 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
813 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
814 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
815 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
817 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
821 * validate a xtp session key.
822 * return 1 if OK
825 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
827 if (strcmp(trusted, untrusted) != 0) {
828 show_oops(t, "%s: xtp session key mismatch possible spoof",
829 __func__);
830 return (0);
833 return (1);
837 download_rb_cmp(struct download *e1, struct download *e2)
839 return (e1->id < e2->id ? -1 : e1->id > e2->id);
841 RB_GENERATE(download_list, download, entry, download_rb_cmp);
843 struct valid_url_types {
844 char *type;
845 } vut[] = {
846 { "http://" },
847 { "https://" },
848 { "ftp://" },
849 { "file://" },
850 { XT_XTP_STR },
854 valid_url_type(char *url)
856 int i;
858 for (i = 0; i < LENGTH(vut); i++)
859 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
860 return (0);
862 return (1);
865 void
866 print_cookie(char *msg, SoupCookie *c)
868 if (c == NULL)
869 return;
871 if (msg)
872 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
873 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
874 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
875 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
876 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
877 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
878 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
879 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
880 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
881 DNPRINTF(XT_D_COOKIE, "====================================\n");
884 void
885 walk_alias(struct settings *s,
886 void (*cb)(struct settings *, char *, void *), void *cb_args)
888 struct alias *a;
889 char *str;
891 if (s == NULL || cb == NULL)
892 errx(1, "walk_alias");
894 TAILQ_FOREACH(a, &aliases, entry) {
895 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
896 cb(s, str, cb_args);
897 g_free(str);
901 char *
902 match_alias(char *url_in)
904 struct alias *a;
905 char *arg;
906 char *url_out = NULL;
908 arg = url_in;
909 if (strsep(&arg, " \t") == NULL)
910 errx(1, "match_alias: NULL URL");
912 TAILQ_FOREACH(a, &aliases, entry) {
913 if (!strcmp(url_in, a->a_name))
914 break;
917 if (a != NULL) {
918 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
919 a->a_name);
920 if (arg != NULL)
921 url_out = g_strdup_printf(a->a_uri, arg);
922 else
923 url_out = g_strdup(a->a_uri);
926 return (url_out);
929 char *
930 guess_url_type(char *url_in)
932 struct stat sb;
933 char *url_out = NULL;
935 url_out = match_alias(url_in);
936 if (url_out != NULL)
937 return (url_out);
939 /* XXX not sure about this heuristic */
940 if (stat(url_in, &sb) == 0)
941 url_out = g_strdup_printf("file://%s", url_in);
942 else
943 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
945 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
947 return (url_out);
951 add_alias(struct settings *s, char *line)
953 char *l, *alias;
954 struct alias *a;
956 if (line == NULL)
957 errx(1, "add_alias");
958 l = line;
960 a = g_malloc(sizeof(*a));
962 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL)
963 errx(1, "add_alias: incomplete alias definition");
965 if (strlen(alias) == 0 || strlen(l) == 0)
966 errx(1, "add_alias: invalid alias definition");
968 a->a_name = g_strdup(alias);
969 a->a_uri = g_strdup(l);
971 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
973 TAILQ_INSERT_TAIL(&aliases, a, entry);
975 return (0);
979 add_mime_type(struct settings *s, char *line)
981 char *mime_type;
982 char *l = NULL;
983 struct mime_type *m;
985 /* XXX this could be smarter */
987 if (line == NULL)
988 errx(1, "add_mime_type");
989 l = line;
991 m = g_malloc(sizeof(*m));
993 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
994 errx(1, "add_mime_type: invalid mime_type");
996 if (mime_type[strlen(mime_type) - 1] == '*') {
997 mime_type[strlen(mime_type) - 1] = '\0';
998 m->mt_default = 1;
999 } else
1000 m->mt_default = 0;
1002 if (strlen(mime_type) == 0 || strlen(l) == 0)
1003 errx(1, "add_mime_type: invalid mime_type");
1005 m->mt_type = g_strdup(mime_type);
1006 m->mt_action = g_strdup(l);
1008 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1009 m->mt_type, m->mt_action, m->mt_default);
1011 TAILQ_INSERT_TAIL(&mtl, m, entry);
1013 return (0);
1016 struct mime_type *
1017 find_mime_type(char *mime_type)
1019 struct mime_type *m, *def = NULL, *rv = NULL;
1021 TAILQ_FOREACH(m, &mtl, entry) {
1022 if (m->mt_default &&
1023 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1024 def = m;
1026 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1027 rv = m;
1028 break;
1032 if (rv == NULL)
1033 rv = def;
1035 return (rv);
1038 void
1039 walk_mime_type(struct settings *s,
1040 void (*cb)(struct settings *, char *, void *), void *cb_args)
1042 struct mime_type *m;
1043 char *str;
1045 if (s == NULL || cb == NULL)
1046 errx(1, "walk_mime_type");
1048 TAILQ_FOREACH(m, &mtl, entry) {
1049 str = g_strdup_printf("%s%s --> %s",
1050 m->mt_type,
1051 m->mt_default ? "*" : "",
1052 m->mt_action);
1053 cb(s, str, cb_args);
1054 g_free(str);
1058 void
1059 wl_add(char *str, struct domain_list *wl, int handy)
1061 struct domain *d;
1062 int add_dot = 0;
1064 if (str == NULL || wl == NULL)
1065 return;
1066 if (strlen(str) < 2)
1067 return;
1069 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1071 /* treat *.moo.com the same as .moo.com */
1072 if (str[0] == '*' && str[1] == '.')
1073 str = &str[1];
1074 else if (str[0] == '.')
1075 str = &str[0];
1076 else
1077 add_dot = 1;
1079 d = g_malloc(sizeof *d);
1080 if (add_dot)
1081 d->d = g_strdup_printf(".%s", str);
1082 else
1083 d->d = g_strdup(str);
1084 d->handy = handy;
1086 if (RB_INSERT(domain_list, wl, d))
1087 goto unwind;
1089 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1090 return;
1091 unwind:
1092 if (d) {
1093 if (d->d)
1094 g_free(d->d);
1095 g_free(d);
1100 add_cookie_wl(struct settings *s, char *entry)
1102 wl_add(entry, &c_wl, 1);
1103 return (0);
1106 void
1107 walk_cookie_wl(struct settings *s,
1108 void (*cb)(struct settings *, char *, void *), void *cb_args)
1110 struct domain *d;
1112 if (s == NULL || cb == NULL)
1113 errx(1, "walk_cookie_wl");
1115 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1116 cb(s, d->d, cb_args);
1119 void
1120 walk_js_wl(struct settings *s,
1121 void (*cb)(struct settings *, char *, void *), void *cb_args)
1123 struct domain *d;
1125 if (s == NULL || cb == NULL)
1126 errx(1, "walk_js_wl");
1128 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1129 cb(s, d->d, cb_args);
1133 add_js_wl(struct settings *s, char *entry)
1135 wl_add(entry, &js_wl, 1 /* persistent */);
1136 return (0);
1139 struct domain *
1140 wl_find(const gchar *search, struct domain_list *wl)
1142 int i;
1143 struct domain *d = NULL, dfind;
1144 gchar *s = NULL;
1146 if (search == NULL || wl == NULL)
1147 return (NULL);
1148 if (strlen(search) < 2)
1149 return (NULL);
1151 if (search[0] != '.')
1152 s = g_strdup_printf(".%s", search);
1153 else
1154 s = g_strdup(search);
1156 for (i = strlen(s) - 1; i >= 0; i--) {
1157 if (s[i] == '.') {
1158 dfind.d = &s[i];
1159 d = RB_FIND(domain_list, wl, &dfind);
1160 if (d)
1161 goto done;
1165 done:
1166 if (s)
1167 g_free(s);
1169 return (d);
1172 struct domain *
1173 wl_find_uri(const gchar *s, struct domain_list *wl)
1175 int i;
1176 char *ss;
1177 struct domain *r;
1179 if (s == NULL || wl == NULL)
1180 return (NULL);
1182 if (!strncmp(s, "http://", strlen("http://")))
1183 s = &s[strlen("http://")];
1184 else if (!strncmp(s, "https://", strlen("https://")))
1185 s = &s[strlen("https://")];
1187 if (strlen(s) < 2)
1188 return (NULL);
1190 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1191 /* chop string at first slash */
1192 if (s[i] == '/' || s[i] == '\0') {
1193 ss = g_strdup(s);
1194 ss[i] = '\0';
1195 r = wl_find(ss, wl);
1196 g_free(ss);
1197 return (r);
1200 return (NULL);
1203 char *
1204 get_toplevel_domain(char *domain)
1206 char *s;
1207 int found = 0;
1209 if (domain == NULL)
1210 return (NULL);
1211 if (strlen(domain) < 2)
1212 return (NULL);
1214 s = &domain[strlen(domain) - 1];
1215 while (s != domain) {
1216 if (*s == '.') {
1217 found++;
1218 if (found == 2)
1219 return (s);
1221 s--;
1224 if (found)
1225 return (domain);
1227 return (NULL);
1230 #define WS "\n= \t"
1231 void
1232 config_parse(char *filename, int runtime)
1234 FILE *config, *f;
1235 char *line, *cp, *var, *val;
1236 size_t len, lineno = 0;
1237 int i, handled, *p;
1238 char **s, file[PATH_MAX];
1239 struct stat sb;
1241 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1243 if (filename == NULL)
1244 return;
1246 if (runtime && runtime_settings[0] != '\0') {
1247 snprintf(file, sizeof file, "%s/%s",
1248 work_dir, runtime_settings);
1249 if (stat(file, &sb)) {
1250 warnx("runtime file doesn't exist, creating it");
1251 if ((f = fopen(file, "w")) == NULL)
1252 err(1, "runtime");
1253 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1254 fclose(f);
1256 } else
1257 strlcpy(file, filename, sizeof file);
1259 if ((config = fopen(file, "r")) == NULL) {
1260 warn("config_parse: cannot open %s", filename);
1261 return;
1264 for (;;) {
1265 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1266 if (feof(config) || ferror(config))
1267 break;
1269 cp = line;
1270 cp += (long)strspn(cp, WS);
1271 if (cp[0] == '\0') {
1272 /* empty line */
1273 free(line);
1274 continue;
1277 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1278 break;
1280 cp += (long)strspn(cp, WS);
1282 if ((val = strsep(&cp, "\0")) == NULL)
1283 break;
1285 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1287 /* get settings */
1288 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1289 if (strcmp(var, rs[i].name))
1290 continue;
1292 if (rs[i].s) {
1293 if (rs[i].s->set(&rs[i], val))
1294 errx(1, "invalid value for %s", var);
1295 handled = 1;
1296 break;
1297 } else {
1298 switch (rs[i].type) {
1299 case XT_S_INT:
1300 p = rs[i].ival;
1301 *p = atoi(val);
1302 handled = 1;
1303 break;
1304 case XT_S_STR:
1305 s = rs[i].sval;
1306 if (s == NULL)
1307 errx(1, "invalid sval for %s",
1308 rs[i].name);
1309 if (*s)
1310 g_free(*s);
1311 *s = g_strdup(val);
1312 handled = 1;
1313 break;
1314 case XT_S_INVALID:
1315 default:
1316 errx(1, "invalid type for %s", var);
1319 break;
1321 if (handled == 0)
1322 errx(1, "invalid conf file entry: %s=%s", var, val);
1324 free(line);
1327 fclose(config);
1330 char *
1331 js_ref_to_string(JSContextRef context, JSValueRef ref)
1333 char *s = NULL;
1334 size_t l;
1335 JSStringRef jsref;
1337 jsref = JSValueToStringCopy(context, ref, NULL);
1338 if (jsref == NULL)
1339 return (NULL);
1341 l = JSStringGetMaximumUTF8CStringSize(jsref);
1342 s = g_malloc(l);
1343 if (s)
1344 JSStringGetUTF8CString(jsref, s, l);
1345 JSStringRelease(jsref);
1347 return (s);
1350 void
1351 disable_hints(struct tab *t)
1353 bzero(t->hint_buf, sizeof t->hint_buf);
1354 bzero(t->hint_num, sizeof t->hint_num);
1355 run_script(t, "vimprobable_clear()");
1356 t->hints_on = 0;
1357 t->hint_mode = XT_HINT_NONE;
1360 void
1361 enable_hints(struct tab *t)
1363 bzero(t->hint_buf, sizeof t->hint_buf);
1364 run_script(t, "vimprobable_show_hints()");
1365 t->hints_on = 1;
1366 t->hint_mode = XT_HINT_NONE;
1369 #define XT_JS_OPEN ("open;")
1370 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1371 #define XT_JS_FIRE ("fire;")
1372 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1373 #define XT_JS_FOUND ("found;")
1374 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1377 run_script(struct tab *t, char *s)
1379 JSGlobalContextRef ctx;
1380 WebKitWebFrame *frame;
1381 JSStringRef str;
1382 JSValueRef val, exception;
1383 char *es, buf[128];
1385 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1386 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1388 frame = webkit_web_view_get_main_frame(t->wv);
1389 ctx = webkit_web_frame_get_global_context(frame);
1391 str = JSStringCreateWithUTF8CString(s);
1392 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1393 NULL, 0, &exception);
1394 JSStringRelease(str);
1396 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1397 if (val == NULL) {
1398 es = js_ref_to_string(ctx, exception);
1399 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1400 g_free(es);
1401 return (1);
1402 } else {
1403 es = js_ref_to_string(ctx, val);
1404 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1406 /* handle return value right here */
1407 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1408 disable_hints(t);
1409 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1412 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1413 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1414 &es[XT_JS_FIRE_LEN]);
1415 run_script(t, buf);
1416 disable_hints(t);
1419 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1420 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1421 disable_hints(t);
1424 g_free(es);
1427 return (0);
1431 hint(struct tab *t, struct karg *args)
1434 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1436 if (t->hints_on == 0)
1437 enable_hints(t);
1438 else
1439 disable_hints(t);
1441 return (0);
1444 /* Doesn't work fully, due to the following bug:
1445 * https://bugs.webkit.org/show_bug.cgi?id=51747
1448 restore_global_history(void)
1450 char file[PATH_MAX];
1451 FILE *f;
1452 struct history *h;
1453 gchar *uri;
1454 gchar *title;
1456 snprintf(file, sizeof file, "%s/%s/%s",
1457 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1459 if ((f = fopen(file, "r")) == NULL) {
1460 warnx("%s: fopen", __func__);
1461 return (1);
1464 for (;;) {
1465 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1466 if (feof(f) || ferror(f))
1467 break;
1469 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1470 if (feof(f) || ferror(f)) {
1471 free(uri);
1472 warnx("%s: broken history file\n", __func__);
1473 return (1);
1476 if (uri && strlen(uri) && title && strlen(title)) {
1477 webkit_web_history_item_new_with_data(uri, title);
1478 h = g_malloc(sizeof(struct history));
1479 h->uri = g_strdup(uri);
1480 h->title = g_strdup(title);
1481 RB_INSERT(history_list, &hl, h);
1482 } else {
1483 warnx("%s: failed to restore history\n", __func__);
1484 free(uri);
1485 free(title);
1486 return (1);
1489 free(uri);
1490 free(title);
1491 uri = NULL;
1492 title = NULL;
1495 return (0);
1499 save_global_history_to_disk(struct tab *t)
1501 char file[PATH_MAX];
1502 FILE *f;
1503 struct history *h;
1505 snprintf(file, sizeof file, "%s/%s/%s",
1506 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1508 if ((f = fopen(file, "w")) == NULL) {
1509 show_oops(t, "%s: global history file: %s",
1510 __func__, strerror(errno));
1511 return (1);
1514 RB_FOREACH_REVERSE(h, history_list, &hl) {
1515 if (h->uri && h->title)
1516 fprintf(f, "%s\n%s\n", h->uri, h->title);
1519 fclose(f);
1521 return (0);
1525 quit(struct tab *t, struct karg *args)
1527 if (save_global_history)
1528 save_global_history_to_disk(t);
1530 gtk_main_quit();
1532 return (1);
1536 open_tabs(struct tab *t, struct karg *a)
1538 char file[PATH_MAX];
1539 FILE *f = NULL;
1540 char *uri = NULL;
1541 int rv = 1;
1543 if (a == NULL)
1544 goto done;
1546 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1548 if ((f = fopen(file, "r")) == NULL)
1549 goto done;
1551 for (;;) {
1552 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1553 if (feof(f) || ferror(f))
1554 break;
1556 if (uri && strlen(uri))
1557 create_new_tab(uri, NULL, 1);
1559 free(uri);
1560 uri = NULL;
1563 rv = 0;
1564 done:
1565 if (f)
1566 fclose(f);
1568 return (rv);
1572 restore_saved_tabs(void)
1574 char file[PATH_MAX];
1575 int unlink_file = 0;
1576 struct stat sb;
1577 struct karg a;
1579 snprintf(file, sizeof file, "%s/%s",
1580 sessions_dir, XT_RESTART_TABS_FILE);
1581 if (stat(file, &sb) == -1)
1582 a.s = XT_SAVED_TABS_FILE;
1583 else {
1584 unlink_file = 1;
1585 a.s = XT_RESTART_TABS_FILE;
1588 open_tabs(NULL, &a);
1590 if (unlink_file)
1591 unlink(file);
1593 return (1);
1597 save_tabs(struct tab *t, struct karg *a)
1599 char file[PATH_MAX];
1600 FILE *f;
1601 struct tab *ti;
1602 WebKitWebFrame *frame;
1603 const gchar *uri;
1605 if (a == NULL)
1606 return (1);
1607 if (a->s == NULL)
1608 return (1);
1610 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1612 if ((f = fopen(file, "w")) == NULL) {
1613 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
1614 return (1);
1617 TAILQ_FOREACH(ti, &tabs, entry) {
1618 frame = webkit_web_view_get_main_frame(ti->wv);
1619 uri = webkit_web_frame_get_uri(frame);
1620 if (uri && strlen(uri) > 0)
1621 fprintf(f, "%s\n", uri);
1624 fclose(f);
1626 return (0);
1630 save_tabs_and_quit(struct tab *t, struct karg *args)
1632 struct karg a;
1634 a.s = XT_SAVED_TABS_FILE;
1635 save_tabs(t, &a);
1636 quit(t, NULL);
1638 return (1);
1642 yank_uri(struct tab *t, struct karg *args)
1644 WebKitWebFrame *frame;
1645 const gchar *uri;
1646 GtkClipboard *clipboard;
1648 frame = webkit_web_view_get_main_frame(t->wv);
1649 uri = webkit_web_frame_get_uri(frame);
1650 if (!uri)
1651 return (1);
1653 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1654 gtk_clipboard_set_text(clipboard, uri, -1);
1656 return (0);
1659 struct paste_args {
1660 struct tab *t;
1661 int i;
1664 void
1665 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
1667 struct paste_args *pap;
1669 if (data == NULL)
1670 return;
1672 pap = (struct paste_args *)data;
1674 switch(pap->i) {
1675 case XT_PASTE_CURRENT_TAB:
1676 webkit_web_view_load_uri(pap->t->wv, text);
1677 break;
1678 case XT_PASTE_NEW_TAB:
1679 create_new_tab((char *)text, NULL, 1);
1680 break;
1683 g_free(pap);
1687 paste_uri(struct tab *t, struct karg *args)
1689 GtkClipboard *clipboard;
1690 struct paste_args *pap;
1692 pap = g_malloc(sizeof(struct paste_args));
1694 pap->t = t;
1695 pap->i = args->i;
1697 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1698 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
1700 return (0);
1703 char *
1704 find_domain(const char *s, int add_dot)
1706 int i;
1707 char *r = NULL, *ss = NULL;
1709 if (s == NULL)
1710 return (NULL);
1712 if (!strncmp(s, "http://", strlen("http://")))
1713 s = &s[strlen("http://")];
1714 else if (!strncmp(s, "https://", strlen("https://")))
1715 s = &s[strlen("https://")];
1717 if (strlen(s) < 2)
1718 return (NULL);
1720 ss = g_strdup(s);
1721 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
1722 /* chop string at first slash */
1723 if (ss[i] == '/' || ss[i] == '\0') {
1724 ss[i] = '\0';
1725 if (add_dot)
1726 r = g_strdup_printf(".%s", ss);
1727 else
1728 r = g_strdup(ss);
1729 break;
1731 g_free(ss);
1733 return (r);
1737 toggle_cwl(struct tab *t, struct karg *args)
1739 WebKitWebFrame *frame;
1740 struct domain *d;
1741 char *uri;
1742 char *dom = NULL, *dom_toggle = NULL;
1743 int es;
1745 if (args == NULL)
1746 return (0);
1748 frame = webkit_web_view_get_main_frame(t->wv);
1749 uri = (char *)webkit_web_frame_get_uri(frame);
1750 dom = find_domain(uri, 1);
1751 d = wl_find(dom, &c_wl);
1752 if (d == NULL)
1753 es = 0;
1754 else
1755 es = 1;
1757 if (args->i & XT_WL_TOGGLE)
1758 es = !es;
1759 else if ((args->i & XT_WL_ENABLE) && es != 1)
1760 es = 1;
1761 else if ((args->i & XT_WL_DISABLE) && es != 0)
1762 es = 0;
1764 if (args->i & XT_WL_TOPLEVEL)
1765 dom_toggle = get_toplevel_domain(dom);
1766 else
1767 dom_toggle = dom;
1769 if (es) {
1770 /* enable cookies for domain */
1771 wl_add(dom_toggle, &c_wl, 0);
1772 } else {
1773 /* disable cookies for domain */
1774 RB_REMOVE(domain_list, &c_wl, d);
1777 webkit_web_view_reload(t->wv);
1779 g_free(dom);
1780 return (0);
1784 toggle_js(struct tab *t, struct karg *args)
1786 int es;
1787 WebKitWebFrame *frame;
1788 const gchar *uri;
1789 struct domain *d;
1790 char *dom = NULL, *dom_toggle = NULL;
1792 if (args == NULL)
1793 return (0);
1795 g_object_get((GObject *)t->settings,
1796 "enable-scripts", &es, (char *)NULL);
1797 if (args->i & XT_WL_TOGGLE)
1798 es = !es;
1799 else if ((args->i & XT_WL_ENABLE) && es != 1)
1800 es = 1;
1801 else if ((args->i & XT_WL_DISABLE) && es != 0)
1802 es = 0;
1803 else
1804 return (0);
1806 frame = webkit_web_view_get_main_frame(t->wv);
1807 uri = (char *)webkit_web_frame_get_uri(frame);
1808 dom = find_domain(uri, 1);
1809 if (uri == NULL || dom == NULL) {
1810 show_oops(t, "Can't toggle domain in JavaScript white list");
1811 goto done;
1814 if (args->i & XT_WL_TOPLEVEL)
1815 dom_toggle = get_toplevel_domain(dom);
1816 else
1817 dom_toggle = dom;
1819 if (es) {
1820 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
1821 wl_add(dom_toggle, &js_wl, 0 /* session */);
1822 } else {
1823 d = wl_find(dom_toggle, &js_wl);
1824 if (d)
1825 RB_REMOVE(domain_list, &js_wl, d);
1826 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
1828 g_object_set((GObject *)t->settings,
1829 "enable-scripts", es, (char *)NULL);
1830 webkit_web_view_set_settings(t->wv, t->settings);
1831 webkit_web_view_reload(t->wv);
1832 done:
1833 if (dom)
1834 g_free(dom);
1835 return (0);
1838 void
1839 js_toggle_cb(GtkWidget *w, struct tab *t)
1841 struct karg a;
1843 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
1844 toggle_js(t, &a);
1848 toggle_src(struct tab *t, struct karg *args)
1850 gboolean mode;
1852 if (t == NULL)
1853 return (0);
1855 mode = webkit_web_view_get_view_source_mode(t->wv);
1856 webkit_web_view_set_view_source_mode(t->wv, !mode);
1857 webkit_web_view_reload(t->wv);
1859 return (0);
1863 focus(struct tab *t, struct karg *args)
1865 if (t == NULL || args == NULL)
1866 errx(1, "focus");
1868 if (args->i == XT_FOCUS_URI)
1869 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1870 else if (args->i == XT_FOCUS_SEARCH)
1871 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
1873 return (0);
1877 stats(struct tab *t, struct karg *args)
1879 char *stats, *s, line[64 * 1024];
1880 uint64_t line_count = 0;
1881 FILE *r_cookie_f;
1883 if (t == NULL)
1884 errx(1, "stats");
1886 line[0] = '\0';
1887 if (save_rejected_cookies) {
1888 if ((r_cookie_f = fopen(rc_fname, "r"))) {
1889 for (;;) {
1890 s = fgets(line, sizeof line, r_cookie_f);
1891 if (s == NULL || feof(r_cookie_f) ||
1892 ferror(r_cookie_f))
1893 break;
1894 line_count++;
1896 fclose(r_cookie_f);
1897 snprintf(line, sizeof line,
1898 "<br>Cookies blocked(*) total: %llu", line_count);
1899 } else
1900 show_oops(t, "Can't open blocked cookies file: %s",
1901 strerror(errno));
1904 stats = g_strdup_printf(XT_DOCTYPE
1905 "<html>"
1906 "<head>"
1907 "<title>Statistics</title>"
1908 "</head>"
1909 "<h1>Statistics</h1>"
1910 "<body>"
1911 "Cookies blocked(*) this session: %llu"
1912 "%s"
1913 "<p><small><b>*</b> results vary based on settings"
1914 "</body>"
1915 "</html>",
1916 blocked_cookies,
1917 line);
1919 load_webkit_string(t, stats);
1920 g_free(stats);
1922 return (0);
1926 about(struct tab *t, struct karg *args)
1928 char *about;
1930 if (t == NULL)
1931 errx(1, "about");
1933 about = g_strdup_printf(XT_DOCTYPE
1934 "<html>"
1935 "<head>"
1936 "<title>About</title>"
1937 "</head>"
1938 "<h1>About</h1>"
1939 "<body>"
1940 "<b>Version: %s</b><p>"
1941 "Authors:"
1942 "<ul>"
1943 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1944 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1945 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
1946 "</ul>"
1947 "Copyrights and licenses can be found on the XXXterm "
1948 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
1949 "</body>"
1950 "</html>",
1951 version
1954 load_webkit_string(t, about);
1955 g_free(about);
1957 return (0);
1961 help(struct tab *t, struct karg *args)
1963 char *help;
1965 if (t == NULL)
1966 errx(1, "help");
1968 help = XT_DOCTYPE
1969 "<html>"
1970 "<head>"
1971 "<title>XXXterm</title>"
1972 "<meta http-equiv=\"REFRESH\" content=\"0;"
1973 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
1974 "</head>"
1975 "<body>"
1976 "XXXterm man page <a href=\"http://opensource.conformal.com/"
1977 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
1978 "cgi-bin/man-cgi?xxxterm</a>"
1979 "</body>"
1980 "</html>"
1983 load_webkit_string(t, help);
1985 return (0);
1989 * update all favorite tabs apart from one. Pass NULL if
1990 * you want to update all.
1992 void
1993 update_favorite_tabs(struct tab *apart_from)
1995 struct tab *t;
1996 if (!updating_fl_tabs) {
1997 updating_fl_tabs = 1; /* stop infinite recursion */
1998 TAILQ_FOREACH(t, &tabs, entry)
1999 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2000 && (t != apart_from))
2001 xtp_page_fl(t, NULL);
2002 updating_fl_tabs = 0;
2006 /* show a list of favorites (bookmarks) */
2008 xtp_page_fl(struct tab *t, struct karg *args)
2010 char file[PATH_MAX];
2011 FILE *f;
2012 char *uri = NULL, *title = NULL;
2013 size_t len, lineno = 0;
2014 int i, failed = 0;
2015 char *header, *body, *tmp, *html = NULL;
2017 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2019 if (t == NULL)
2020 warn("%s: bad param", __func__);
2022 /* mark tab as favorite list */
2023 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2025 /* new session key */
2026 if (!updating_fl_tabs)
2027 generate_xtp_session_key(&fl_session_key);
2029 /* open favorites */
2030 snprintf(file, sizeof file, "%s/%s/%s",
2031 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2032 if ((f = fopen(file, "r")) == NULL) {
2033 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2034 return (1);
2037 /* header */
2038 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2039 "<title>Favorites</title>\n"
2040 "%s"
2041 "</head>"
2042 "<h1>Favorites</h1>\n",
2043 XT_PAGE_STYLE);
2045 /* body */
2046 body = g_strdup_printf("<div align='center'><table><tr>"
2047 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2048 "<th style='width: 15%%'>Remove</th></tr>\n");
2050 for (i = 1;;) {
2051 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2052 if (feof(f) || ferror(f))
2053 break;
2054 if (len == 0) {
2055 free(title);
2056 title = NULL;
2057 continue;
2060 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2061 if (feof(f) || ferror(f)) {
2062 errx(0, "%s: can't parse favorites\n",
2063 __func__);
2064 failed = 1;
2065 break;
2068 tmp = body;
2069 body = g_strdup_printf("%s<tr>"
2070 "<td>%d</td>"
2071 "<td><a href='%s'>%s</a></td>"
2072 "<td style='text-align: center'>"
2073 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2074 "</tr>\n",
2075 body, i, uri, title,
2076 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2078 g_free(tmp);
2080 free(uri);
2081 uri = NULL;
2082 free(title);
2083 title = NULL;
2084 i++;
2086 fclose(f);
2088 /* if none, say so */
2089 if (i == 1) {
2090 tmp = body;
2091 body = g_strdup_printf("%s<tr>"
2092 "<td colspan='3' style='text-align: center'>"
2093 "No favorites - To add one use the 'favadd' command."
2094 "</td></tr>", body);
2095 g_free(tmp);
2098 if (uri)
2099 free(uri);
2100 if (title)
2101 free(title);
2103 /* render */
2104 if (!failed) {
2105 html = g_strdup_printf("%s%s</table></div></html>",
2106 header, body);
2107 load_webkit_string(t, html);
2110 update_favorite_tabs(t);
2112 if (header)
2113 g_free(header);
2114 if (body)
2115 g_free(body);
2116 if (html)
2117 g_free(html);
2119 return (failed);
2122 char *
2123 getparams(char *cmd, char *cmp)
2125 char *rv = NULL;
2127 if (cmd && cmp) {
2128 if (!strncmp(cmd, cmp, strlen(cmp))) {
2129 rv = cmd + strlen(cmp);
2130 while (*rv == ' ')
2131 rv++;
2132 if (strlen(rv) == 0)
2133 rv = NULL;
2137 return (rv);
2140 void
2141 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2142 size_t cert_count, char *title)
2144 gnutls_datum_t cinfo;
2145 char *tmp, *header, *body, *footer;
2146 int i;
2148 header = g_strdup_printf("<title>%s</title><html><body>", title);
2149 footer = g_strdup("</body></html>");
2150 body = g_strdup("");
2152 for (i = 0; i < cert_count; i++) {
2153 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2154 &cinfo))
2155 return;
2157 tmp = body;
2158 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2159 body, i, cinfo.data);
2160 gnutls_free(cinfo.data);
2161 g_free(tmp);
2164 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2165 g_free(header);
2166 g_free(body);
2167 g_free(footer);
2168 load_webkit_string(t, tmp);
2169 g_free(tmp);
2173 ca_cmd(struct tab *t, struct karg *args)
2175 FILE *f = NULL;
2176 int rv = 1, certs = 0, certs_read;
2177 struct stat sb;
2178 gnutls_datum dt;
2179 gnutls_x509_crt_t *c = NULL;
2180 char *certs_buf = NULL, *s;
2182 /* yeah yeah stat race */
2183 if (stat(ssl_ca_file, &sb)) {
2184 show_oops(t, "no CA file: %s", ssl_ca_file);
2185 goto done;
2188 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2189 show_oops(t, "Can't open CA file: %s", strerror(errno));
2190 return (1);
2193 certs_buf = g_malloc(sb.st_size + 1);
2194 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2195 show_oops(t, "Can't read CA file: %s", strerror(errno));
2196 goto done;
2198 certs_buf[sb.st_size] = '\0';
2200 s = certs_buf;
2201 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2202 certs++;
2203 s += strlen("BEGIN CERTIFICATE");
2206 bzero(&dt, sizeof dt);
2207 dt.data = certs_buf;
2208 dt.size = sb.st_size;
2209 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2210 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt, GNUTLS_X509_FMT_PEM, 0);
2211 if (certs_read <= 0) {
2212 show_oops(t, "No cert(s) available");
2213 goto done;
2215 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2216 done:
2217 if (c)
2218 g_free(c);
2219 if (certs_buf)
2220 g_free(certs_buf);
2221 if (f)
2222 fclose(f);
2224 return (rv);
2228 connect_socket_from_uri(char *uri, char *domain, size_t domain_sz)
2230 SoupURI *su = NULL;
2231 struct addrinfo hints, *res = NULL, *ai;
2232 int s = -1, on;
2233 char port[8];
2235 if (uri && !g_str_has_prefix(uri, "https://"))
2236 goto done;
2238 su = soup_uri_new(uri);
2239 if (su == NULL)
2240 goto done;
2241 if (!SOUP_URI_VALID_FOR_HTTP(su))
2242 goto done;
2244 snprintf(port, sizeof port, "%d", su->port);
2245 bzero(&hints, sizeof(struct addrinfo));
2246 hints.ai_flags = AI_CANONNAME;
2247 hints.ai_family = AF_UNSPEC;
2248 hints.ai_socktype = SOCK_STREAM;
2250 if (getaddrinfo(su->host, port, &hints, &res))
2251 goto done;
2253 for (ai = res; ai; ai = ai->ai_next) {
2254 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2255 continue;
2257 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2258 if (s < 0)
2259 goto done;
2260 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2261 sizeof(on)) == -1)
2262 goto done;
2264 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2265 goto done;
2268 if (domain)
2269 strlcpy(domain, su->host, domain_sz);
2270 done:
2271 if (su)
2272 soup_uri_free(su);
2273 if (res)
2274 freeaddrinfo(res);
2276 return (s);
2280 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2282 if (gsession)
2283 gnutls_deinit(gsession);
2284 if (xcred)
2285 gnutls_certificate_free_credentials(xcred);
2287 return (0);
2291 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2292 gnutls_certificate_credentials_t *xc)
2294 gnutls_certificate_credentials_t xcred;
2295 gnutls_session_t gsession;
2296 int rv = 1;
2298 if (gs == NULL || xc == NULL)
2299 goto done;
2301 *gs = NULL;
2302 *xc = NULL;
2304 gnutls_certificate_allocate_credentials(&xcred);
2305 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2306 GNUTLS_X509_FMT_PEM);
2307 gnutls_init(&gsession, GNUTLS_CLIENT);
2308 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2309 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2310 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2311 if ((rv = gnutls_handshake(gsession)) < 0) {
2312 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2314 gnutls_error_is_fatal(rv),
2315 gnutls_strerror_name(rv));
2316 stop_tls(gsession, xcred);
2317 goto done;
2320 gnutls_credentials_type_t cred;
2321 cred = gnutls_auth_get_type(gsession);
2322 if (cred != GNUTLS_CRD_CERTIFICATE) {
2323 stop_tls(gsession, xcred);
2324 goto done;
2327 *gs = gsession;
2328 *xc = xcred;
2329 rv = 0;
2330 done:
2331 return (rv);
2335 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2336 size_t *cert_count)
2338 unsigned int len;
2339 const gnutls_datum_t *cl;
2340 gnutls_x509_crt_t *all_certs;
2341 int i, rv = 1;
2343 if (certs == NULL || cert_count == NULL)
2344 goto done;
2345 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2346 goto done;
2347 cl = gnutls_certificate_get_peers(gsession, &len);
2348 if (len == 0)
2349 goto done;
2351 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2352 for (i = 0; i < len; i++) {
2353 gnutls_x509_crt_init(&all_certs[i]);
2354 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2355 GNUTLS_X509_FMT_PEM < 0)) {
2356 g_free(all_certs);
2357 goto done;
2361 *certs = all_certs;
2362 *cert_count = len;
2363 rv = 0;
2364 done:
2365 return (rv);
2368 void
2369 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2371 int i;
2373 for (i = 0; i < cert_count; i++)
2374 gnutls_x509_crt_deinit(certs[i]);
2375 g_free(certs);
2378 void
2379 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2380 size_t cert_count, char *domain)
2382 size_t cert_buf_sz;
2383 char cert_buf[64 * 1024], file[PATH_MAX];
2384 int i;
2385 FILE *f;
2386 GdkColor color;
2388 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2389 return;
2391 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2392 if ((f = fopen(file, "w")) == NULL) {
2393 show_oops(t, "Can't create cert file %s %s",
2394 file, strerror(errno));
2395 return;
2398 for (i = 0; i < cert_count; i++) {
2399 cert_buf_sz = sizeof cert_buf;
2400 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2401 cert_buf, &cert_buf_sz)) {
2402 show_oops(t, "gnutls_x509_crt_export failed");
2403 goto done;
2405 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2406 show_oops(t, "Can't write certs: %s", strerror(errno));
2407 goto done;
2411 /* not the best spot but oh well */
2412 gdk_color_parse("lightblue", &color);
2413 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2414 done:
2415 fclose(f);
2419 load_compare_cert(struct tab *t, struct karg *args)
2421 WebKitWebFrame *frame;
2422 char *uri, domain[8182], file[PATH_MAX];
2423 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2424 int s = -1, rv = 1, i;
2425 size_t cert_count;
2426 FILE *f = NULL;
2427 size_t cert_buf_sz;
2428 gnutls_session_t gsession;
2429 gnutls_x509_crt_t *certs;
2430 gnutls_certificate_credentials_t xcred;
2432 if (t == NULL)
2433 return (1);
2435 frame = webkit_web_view_get_main_frame(t->wv);
2436 uri = (char *)webkit_web_frame_get_uri(frame);
2437 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2438 return (1);
2440 /* go ssl/tls */
2441 if (start_tls(t, s, &gsession, &xcred)) {
2442 show_oops(t, "Start TLS failed");
2443 goto done;
2446 /* get certs */
2447 if (get_connection_certs(gsession, &certs, &cert_count)) {
2448 show_oops(t, "Can't get connection certificates");
2449 goto done;
2452 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2453 if ((f = fopen(file, "r")) == NULL)
2454 goto freeit;
2456 for (i = 0; i < cert_count; i++) {
2457 cert_buf_sz = sizeof cert_buf;
2458 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2459 cert_buf, &cert_buf_sz)) {
2460 goto freeit;
2462 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2463 rv = -1; /* critical */
2464 goto freeit;
2466 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2467 rv = -1; /* critical */
2468 goto freeit;
2472 rv = 0;
2473 freeit:
2474 if (f)
2475 fclose(f);
2476 free_connection_certs(certs, cert_count);
2477 done:
2478 /* we close the socket first for speed */
2479 if (s != -1)
2480 close(s);
2481 stop_tls(gsession, xcred);
2483 return (rv);
2487 cert_cmd(struct tab *t, struct karg *args)
2489 WebKitWebFrame *frame;
2490 char *uri, *action, domain[8182];
2491 int s = -1;
2492 size_t cert_count;
2493 gnutls_session_t gsession;
2494 gnutls_x509_crt_t *certs;
2495 gnutls_certificate_credentials_t xcred;
2497 if (t == NULL)
2498 return (1);
2500 if ((action = getparams(args->s, "cert")))
2502 else
2503 action = "show";
2505 frame = webkit_web_view_get_main_frame(t->wv);
2506 uri = (char *)webkit_web_frame_get_uri(frame);
2507 if (uri && strlen(uri) == 0) {
2508 show_oops(t, "Invalid URI");
2510 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2511 show_oops(t, "Invalid certidicate URI: %s", uri);
2512 return (1);
2515 /* go ssl/tls */
2516 if (start_tls(t, s, &gsession, &xcred)) {
2517 show_oops(t, "Start TLS failed");
2518 goto done;
2521 /* get certs */
2522 if (get_connection_certs(gsession, &certs, &cert_count)) {
2523 show_oops(t, "get_connection_certs failed");
2524 goto done;
2527 if (!strcmp(action, "show"))
2528 show_certs(t, certs, cert_count, "Certificate Chain");
2529 else if (!strcmp(action, "save"))
2530 save_certs(t, certs, cert_count, domain);
2531 else
2532 show_oops(t, "Invalid command: %s", action);
2534 free_connection_certs(certs, cert_count);
2535 done:
2536 /* we close the socket first for speed */
2537 if (s != -1)
2538 close(s);
2539 stop_tls(gsession, xcred);
2541 return (0);
2545 remove_cookie(int index)
2547 int i, rv = 1;
2548 GSList *cf;
2549 SoupCookie *c;
2551 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2553 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2555 for (i = 1; cf; cf = cf->next, i++) {
2556 if (i != index)
2557 continue;
2558 c = cf->data;
2559 print_cookie("remove cookie", c);
2560 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2561 rv = 0;
2562 break;
2565 soup_cookies_free(cf);
2567 return (rv);
2571 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2573 struct domain *d;
2574 char *tmp, *header, *body, *footer;
2575 int p_js = 0, s_js = 0;
2577 /* we set this to indicate we want to manually do navaction */
2578 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
2580 if (g_str_has_prefix(args, "show a") ||
2581 !strcmp(args, "show")) {
2582 /* show all */
2583 p_js = 1;
2584 s_js = 1;
2585 } else if (g_str_has_prefix(args, "show p")) {
2586 /* show persistent */
2587 p_js = 1;
2588 } else if (g_str_has_prefix(args, "show s")) {
2589 /* show session */
2590 s_js = 1;
2591 } else
2592 return (1);
2594 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2595 title, title);
2596 footer = g_strdup("</body></html>");
2597 body = g_strdup("");
2599 /* p list */
2600 if (p_js) {
2601 tmp = body;
2602 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
2603 g_free(tmp);
2604 RB_FOREACH(d, domain_list, wl) {
2605 if (d->handy == 0)
2606 continue;
2607 tmp = body;
2608 body = g_strdup_printf("%s%s<br>", body, d->d);
2609 g_free(tmp);
2613 /* s list */
2614 if (s_js) {
2615 tmp = body;
2616 body = g_strdup_printf("%s<h2>Session</h2>", body);
2617 g_free(tmp);
2618 RB_FOREACH(d, domain_list, wl) {
2619 if (d->handy == 1)
2620 continue;
2621 tmp = body;
2622 body = g_strdup_printf("%s%s", body, d->d);
2623 g_free(tmp);
2627 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2628 g_free(header);
2629 g_free(body);
2630 g_free(footer);
2631 load_webkit_string(t, tmp);
2632 g_free(tmp);
2633 return (0);
2637 wl_save(struct tab *t, struct karg *args, int js)
2639 char file[PATH_MAX];
2640 FILE *f;
2641 char *line = NULL, *lt = NULL;
2642 size_t linelen;
2643 WebKitWebFrame *frame;
2644 char *dom = NULL, *uri, *dom_save = NULL;
2645 struct karg a;
2646 struct domain *d;
2647 GSList *cf;
2648 SoupCookie *ci, *c;
2649 int flags;
2651 if (t == NULL || args == NULL)
2652 return (1);
2654 if (runtime_settings[0] == '\0')
2655 return (1);
2657 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
2658 if ((f = fopen(file, "r+")) == NULL)
2659 return (1);
2661 frame = webkit_web_view_get_main_frame(t->wv);
2662 uri = (char *)webkit_web_frame_get_uri(frame);
2663 dom = find_domain(uri, 1);
2664 if (uri == NULL || dom == NULL) {
2665 show_oops(t, "Can't add domain to %s white list",
2666 js ? "JavaScript" : "cookie");
2667 goto done;
2670 if (g_str_has_prefix(args->s, "save d")) {
2671 /* save domain */
2672 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
2673 show_oops(t, "invalid domain: %s", dom);
2674 goto done;
2676 flags = XT_WL_TOPLEVEL;
2677 } else if (g_str_has_prefix(args->s, "save f") ||
2678 !strcmp(args->s, "save")) {
2679 /* save fqdn */
2680 dom_save = dom;
2681 flags = XT_WL_FQDN;
2682 } else {
2683 show_oops(t, "invalid command: %s", args->s);
2684 goto done;
2687 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
2689 while (!feof(f)) {
2690 line = fparseln(f, &linelen, NULL, NULL, 0);
2691 if (line == NULL)
2692 continue;
2693 if (!strcmp(line, lt))
2694 goto done;
2695 free(line);
2696 line = NULL;
2699 fprintf(f, "%s\n", lt);
2701 a.i = XT_WL_ENABLE;
2702 a.i |= flags;
2703 if (js) {
2704 d = wl_find(dom_save, &js_wl);
2705 toggle_js(t, &a);
2706 } else {
2707 d = wl_find(dom_save, &c_wl);
2708 toggle_cwl(t, &a);
2710 /* find and add to persistent jar */
2711 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2712 for (;cf; cf = cf->next) {
2713 ci = cf->data;
2714 if (!strcmp(dom_save, ci->domain) ||
2715 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
2716 c = soup_cookie_copy(ci);
2717 _soup_cookie_jar_add_cookie(p_cookiejar, c);
2720 soup_cookies_free(cf);
2722 if (d)
2723 d->handy = 1;
2725 done:
2726 if (line)
2727 free(line);
2728 if (dom)
2729 g_free(dom);
2730 if (lt)
2731 g_free(lt);
2732 fclose(f);
2734 return (0);
2738 cookie_cmd(struct tab *t, struct karg *args)
2740 char *cmd;
2741 struct karg a;
2743 if ((cmd = getparams(args->s, "cookie")))
2745 else
2746 cmd = "show all";
2749 if (g_str_has_prefix(cmd, "show")) {
2750 wl_show(t, cmd, "Cookie White List", &c_wl);
2751 } else if (g_str_has_prefix(cmd, "save")) {
2752 a.s = cmd;
2753 wl_save(t, &a, 0);
2754 } else if (g_str_has_prefix(cmd, "toggle")) {
2755 a.i = XT_WL_TOGGLE;
2756 if (g_str_has_prefix(cmd, "toggle d"))
2757 a.i |= XT_WL_TOPLEVEL;
2758 else
2759 a.i |= XT_WL_FQDN;
2760 toggle_cwl(t, &a);
2761 } else if (g_str_has_prefix(cmd, "delete")) {
2762 show_oops(t, "'cookie delete' currently unimplemented");
2763 } else
2764 show_oops(t, "unknown cookie command: %s", cmd);
2766 return (0);
2770 js_cmd(struct tab *t, struct karg *args)
2772 char *cmd;
2773 struct karg a;
2775 if ((cmd = getparams(args->s, "js")))
2777 else
2778 cmd = "show all";
2780 if (g_str_has_prefix(cmd, "show")) {
2781 wl_show(t, cmd, "JavaScript White List", &js_wl);
2782 } else if (g_str_has_prefix(cmd, "save")) {
2783 a.s = cmd;
2784 wl_save(t, &a, 1);
2785 } else if (g_str_has_prefix(cmd, "toggle")) {
2786 a.i = XT_WL_TOGGLE;
2787 if (g_str_has_prefix(cmd, "toggle d"))
2788 a.i |= XT_WL_TOPLEVEL;
2789 else
2790 a.i |= XT_WL_FQDN;
2791 toggle_js(t, &a);
2792 } else if (g_str_has_prefix(cmd, "delete")) {
2793 show_oops(t, "'js delete' currently unimplemented");
2794 } else
2795 show_oops(t, "unknown js command: %s", cmd);
2797 return (0);
2801 add_favorite(struct tab *t, struct karg *args)
2803 char file[PATH_MAX];
2804 FILE *f;
2805 char *line = NULL;
2806 size_t urilen, linelen;
2807 WebKitWebFrame *frame;
2808 const gchar *uri, *title;
2810 if (t == NULL)
2811 return (1);
2813 /* don't allow adding of xtp pages to favorites */
2814 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
2815 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
2816 return (1);
2819 snprintf(file, sizeof file, "%s/%s/%s",
2820 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2821 if ((f = fopen(file, "r+")) == NULL) {
2822 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2823 return (1);
2826 title = webkit_web_view_get_title(t->wv);
2827 frame = webkit_web_view_get_main_frame(t->wv);
2828 uri = webkit_web_frame_get_uri(frame);
2829 if (title == NULL)
2830 title = uri;
2832 if (title == NULL || uri == NULL) {
2833 show_oops(t, "can't add page to favorites");
2834 goto done;
2837 urilen = strlen(uri);
2839 while (!feof(f)) {
2840 line = fparseln(f, &linelen, NULL, NULL, 0);
2841 if (linelen == urilen && !strcmp(line, uri))
2842 goto done;
2843 free(line);
2844 line = NULL;
2847 fprintf(f, "\n%s\n%s", title, uri);
2848 done:
2849 if (line)
2850 free(line);
2851 fclose(f);
2853 update_favorite_tabs(NULL);
2855 return (0);
2859 navaction(struct tab *t, struct karg *args)
2861 WebKitWebHistoryItem *item;
2863 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
2864 t->tab_id, args->i);
2866 if (t->item) {
2867 if (args->i == XT_NAV_BACK)
2868 item = webkit_web_back_forward_list_get_current_item(t->bfl);
2869 else
2870 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
2871 if (item == NULL)
2872 return (XT_CB_PASSTHROUGH);;
2873 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
2874 t->item = NULL;
2875 return (XT_CB_PASSTHROUGH);
2878 switch (args->i) {
2879 case XT_NAV_BACK:
2880 webkit_web_view_go_back(t->wv);
2881 break;
2882 case XT_NAV_FORWARD:
2883 webkit_web_view_go_forward(t->wv);
2884 break;
2885 case XT_NAV_RELOAD:
2886 webkit_web_view_reload(t->wv);
2887 break;
2888 case XT_NAV_RELOAD_CACHE:
2889 webkit_web_view_reload_bypass_cache(t->wv);
2890 break;
2892 return (XT_CB_PASSTHROUGH);
2896 move(struct tab *t, struct karg *args)
2898 GtkAdjustment *adjust;
2899 double pi, si, pos, ps, upper, lower, max;
2901 switch (args->i) {
2902 case XT_MOVE_DOWN:
2903 case XT_MOVE_UP:
2904 case XT_MOVE_BOTTOM:
2905 case XT_MOVE_TOP:
2906 case XT_MOVE_PAGEDOWN:
2907 case XT_MOVE_PAGEUP:
2908 case XT_MOVE_HALFDOWN:
2909 case XT_MOVE_HALFUP:
2910 adjust = t->adjust_v;
2911 break;
2912 default:
2913 adjust = t->adjust_h;
2914 break;
2917 pos = gtk_adjustment_get_value(adjust);
2918 ps = gtk_adjustment_get_page_size(adjust);
2919 upper = gtk_adjustment_get_upper(adjust);
2920 lower = gtk_adjustment_get_lower(adjust);
2921 si = gtk_adjustment_get_step_increment(adjust);
2922 pi = gtk_adjustment_get_page_increment(adjust);
2923 max = upper - ps;
2925 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2926 "max %f si %f pi %f\n",
2927 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
2928 pos, ps, upper, lower, max, si, pi);
2930 switch (args->i) {
2931 case XT_MOVE_DOWN:
2932 case XT_MOVE_RIGHT:
2933 pos += si;
2934 gtk_adjustment_set_value(adjust, MIN(pos, max));
2935 break;
2936 case XT_MOVE_UP:
2937 case XT_MOVE_LEFT:
2938 pos -= si;
2939 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2940 break;
2941 case XT_MOVE_BOTTOM:
2942 case XT_MOVE_FARRIGHT:
2943 gtk_adjustment_set_value(adjust, max);
2944 break;
2945 case XT_MOVE_TOP:
2946 case XT_MOVE_FARLEFT:
2947 gtk_adjustment_set_value(adjust, lower);
2948 break;
2949 case XT_MOVE_PAGEDOWN:
2950 pos += pi;
2951 gtk_adjustment_set_value(adjust, MIN(pos, max));
2952 break;
2953 case XT_MOVE_PAGEUP:
2954 pos -= pi;
2955 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2956 break;
2957 case XT_MOVE_HALFDOWN:
2958 pos += pi / 2;
2959 gtk_adjustment_set_value(adjust, MIN(pos, max));
2960 break;
2961 case XT_MOVE_HALFUP:
2962 pos -= pi / 2;
2963 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2964 break;
2965 default:
2966 return (XT_CB_PASSTHROUGH);
2969 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
2971 return (XT_CB_HANDLED);
2975 tabaction(struct tab *t, struct karg *args)
2977 int rv = XT_CB_HANDLED;
2978 char *url = NULL, *newuri = NULL;
2979 struct undo *u;
2981 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
2983 if (t == NULL)
2984 return (XT_CB_PASSTHROUGH);
2986 switch (args->i) {
2987 case XT_TAB_NEW:
2988 if ((url = getparams(args->s, "tabnew")))
2989 create_new_tab(url, NULL, 1);
2990 else
2991 create_new_tab(NULL, NULL, 1);
2992 break;
2993 case XT_TAB_DELETE:
2994 delete_tab(t);
2995 break;
2996 case XT_TAB_DELQUIT:
2997 if (gtk_notebook_get_n_pages(notebook) > 1)
2998 delete_tab(t);
2999 else
3000 quit(t, args);
3001 break;
3002 case XT_TAB_OPEN:
3003 if ((url = getparams(args->s, "open")) ||
3004 ((url = getparams(args->s, "op"))) ||
3005 ((url = getparams(args->s, "o"))))
3007 else {
3008 rv = XT_CB_PASSTHROUGH;
3009 goto done;
3012 if (valid_url_type(url)) {
3013 newuri = guess_url_type(url);
3014 url = newuri;
3016 webkit_web_view_load_uri(t->wv, url);
3017 if (newuri)
3018 g_free(newuri);
3019 break;
3020 case XT_TAB_UNDO_CLOSE:
3021 if (undo_count == 0) {
3022 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3023 goto done;
3024 } else {
3025 undo_count--;
3026 u = TAILQ_FIRST(&undos);
3027 create_new_tab(u->uri, u, 1);
3029 TAILQ_REMOVE(&undos, u, entry);
3030 g_free(u->uri);
3031 /* u->history is freed in create_new_tab() */
3032 g_free(u);
3034 break;
3035 default:
3036 rv = XT_CB_PASSTHROUGH;
3037 goto done;
3040 done:
3041 if (args->s) {
3042 g_free(args->s);
3043 args->s = NULL;
3046 return (rv);
3050 resizetab(struct tab *t, struct karg *args)
3052 if (t == NULL || args == NULL)
3053 errx(1, "resizetab");
3055 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3056 t->tab_id, args->i);
3058 adjustfont_webkit(t, args->i);
3060 return (XT_CB_HANDLED);
3064 movetab(struct tab *t, struct karg *args)
3066 struct tab *tt;
3067 int x;
3069 if (t == NULL || args == NULL)
3070 errx(1, "movetab");
3072 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3073 t->tab_id, args->i);
3075 if (args->i == XT_TAB_INVALID)
3076 return (XT_CB_PASSTHROUGH);
3078 if (args->i < XT_TAB_INVALID) {
3079 /* next or previous tab */
3080 if (TAILQ_EMPTY(&tabs))
3081 return (XT_CB_PASSTHROUGH);
3083 switch (args->i) {
3084 case XT_TAB_NEXT:
3085 /* if at the last page, loop around to the first */
3086 if (gtk_notebook_get_current_page(notebook) ==
3087 gtk_notebook_get_n_pages(notebook) - 1) {
3088 gtk_notebook_set_current_page(notebook, 0);
3089 } else {
3090 gtk_notebook_next_page(notebook);
3092 break;
3093 case XT_TAB_PREV:
3094 /* if at the first page, loop around to the last */
3095 if (gtk_notebook_current_page(notebook) == 0) {
3096 gtk_notebook_set_current_page(notebook,
3097 gtk_notebook_get_n_pages(notebook) - 1);
3098 } else {
3099 gtk_notebook_prev_page(notebook);
3101 break;
3102 case XT_TAB_FIRST:
3103 gtk_notebook_set_current_page(notebook, 0);
3104 break;
3105 case XT_TAB_LAST:
3106 gtk_notebook_set_current_page(notebook, -1);
3107 break;
3108 default:
3109 return (XT_CB_PASSTHROUGH);
3112 return (XT_CB_HANDLED);
3115 /* jump to tab */
3116 x = args->i - 1;
3117 if (t->tab_id == x) {
3118 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3119 return (XT_CB_HANDLED);
3122 TAILQ_FOREACH(tt, &tabs, entry) {
3123 if (tt->tab_id == x) {
3124 gtk_notebook_set_current_page(notebook, x);
3125 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3126 if (tt->focus_wv)
3127 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
3131 return (XT_CB_HANDLED);
3135 command(struct tab *t, struct karg *args)
3137 WebKitWebFrame *frame;
3138 char *s = NULL, *ss = NULL;
3139 GdkColor color;
3140 const gchar *uri;
3142 if (t == NULL || args == NULL)
3143 errx(1, "command");
3145 switch (args->i) {
3146 case '/':
3147 s = "/";
3148 break;
3149 case '?':
3150 s = "?";
3151 break;
3152 case ':':
3153 s = ":";
3154 break;
3155 case XT_CMD_OPEN:
3156 s = ":open ";
3157 break;
3158 case XT_CMD_TABNEW:
3159 s = ":tabnew ";
3160 break;
3161 case XT_CMD_OPEN_CURRENT:
3162 s = ":open ";
3163 /* FALL THROUGH */
3164 case XT_CMD_TABNEW_CURRENT:
3165 if (!s) /* FALL THROUGH? */
3166 s = ":tabnew ";
3167 frame = webkit_web_view_get_main_frame(t->wv);
3168 uri = webkit_web_frame_get_uri(frame);
3169 if (uri && strlen(uri)) {
3170 ss = g_strdup_printf("%s%s", s, uri);
3171 s = ss;
3173 break;
3174 default:
3175 show_oops(t, "command: invalid opcode %d", args->i);
3176 return (XT_CB_PASSTHROUGH);
3179 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3181 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3182 gdk_color_parse("white", &color);
3183 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3184 show_cmd(t);
3185 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3186 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3188 if (ss)
3189 g_free(ss);
3191 return (XT_CB_HANDLED);
3195 * Return a new string with a download row (in html)
3196 * appended. Old string is freed.
3198 char *
3199 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3202 WebKitDownloadStatus stat;
3203 char *status_html = NULL, *cmd_html = NULL, *new_html;
3204 gdouble progress;
3205 char cur_sz[FMT_SCALED_STRSIZE];
3206 char tot_sz[FMT_SCALED_STRSIZE];
3207 char *xtp_prefix;
3209 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3211 /* All actions wil take this form:
3212 * xxxt://class/seskey
3214 xtp_prefix = g_strdup_printf("%s%d/%s/",
3215 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3217 stat = webkit_download_get_status(dl->download);
3219 switch (stat) {
3220 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3221 status_html = g_strdup_printf("Finished");
3222 cmd_html = g_strdup_printf(
3223 "<a href='%s%d/%d'>Remove</a>",
3224 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3225 break;
3226 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3227 /* gather size info */
3228 progress = 100 * webkit_download_get_progress(dl->download);
3230 fmt_scaled(
3231 webkit_download_get_current_size(dl->download), cur_sz);
3232 fmt_scaled(
3233 webkit_download_get_total_size(dl->download), tot_sz);
3235 status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz,
3236 tot_sz, progress);
3237 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3238 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3240 break;
3241 /* LLL */
3242 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3243 status_html = g_strdup_printf("Cancelled");
3244 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3245 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3246 break;
3247 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3248 status_html = g_strdup_printf("Error!");
3249 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3250 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3251 break;
3252 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3253 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3254 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3255 status_html = g_strdup_printf("Starting");
3256 break;
3257 default:
3258 show_oops(t, "%s: unknown download status", __func__);
3261 new_html = g_strdup_printf(
3262 "%s\n<tr><td>%s</td><td>%s</td>"
3263 "<td style='text-align:center'>%s</td></tr>\n",
3264 html, webkit_download_get_uri(dl->download),
3265 status_html, cmd_html);
3266 g_free(html);
3268 if (status_html)
3269 g_free(status_html);
3271 if (cmd_html)
3272 g_free(cmd_html);
3274 g_free(xtp_prefix);
3276 return new_html;
3280 * update all download tabs apart from one. Pass NULL if
3281 * you want to update all.
3283 void
3284 update_download_tabs(struct tab *apart_from)
3286 struct tab *t;
3287 if (!updating_dl_tabs) {
3288 updating_dl_tabs = 1; /* stop infinite recursion */
3289 TAILQ_FOREACH(t, &tabs, entry)
3290 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3291 && (t != apart_from))
3292 xtp_page_dl(t, NULL);
3293 updating_dl_tabs = 0;
3298 * update all cookie tabs apart from one. Pass NULL if
3299 * you want to update all.
3301 void
3302 update_cookie_tabs(struct tab *apart_from)
3304 struct tab *t;
3305 if (!updating_cl_tabs) {
3306 updating_cl_tabs = 1; /* stop infinite recursion */
3307 TAILQ_FOREACH(t, &tabs, entry)
3308 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3309 && (t != apart_from))
3310 xtp_page_cl(t, NULL);
3311 updating_cl_tabs = 0;
3316 * update all history tabs apart from one. Pass NULL if
3317 * you want to update all.
3319 void
3320 update_history_tabs(struct tab *apart_from)
3322 struct tab *t;
3324 if (!updating_hl_tabs) {
3325 updating_hl_tabs = 1; /* stop infinite recursion */
3326 TAILQ_FOREACH(t, &tabs, entry)
3327 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3328 && (t != apart_from))
3329 xtp_page_hl(t, NULL);
3330 updating_hl_tabs = 0;
3334 /* cookie management XTP page */
3336 xtp_page_cl(struct tab *t, struct karg *args)
3338 char *header, *body, *footer, *page, *tmp;
3339 int i = 1; /* all ids start 1 */
3340 GSList *sc, *pc, *pc_start;
3341 SoupCookie *c;
3342 char *type;
3344 DNPRINTF(XT_D_CMD, "%s", __func__);
3346 if (t == NULL)
3347 errx(1, "%s: null tab", __func__);
3349 /* mark this tab as cookie jar */
3350 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3352 /* Generate a new session key */
3353 if (!updating_cl_tabs)
3354 generate_xtp_session_key(&cl_session_key);
3356 /* header */
3357 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3358 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3359 "</head><body><h1>Cookie Jar</h1>\n");
3361 /* body */
3362 body = g_strdup_printf("<div align='center'><table><tr>"
3363 "<th>Type</th>"
3364 "<th>Name</th>"
3365 "<th>Value</th>"
3366 "<th>Domain</th>"
3367 "<th>Path</th>"
3368 "<th>Expires</th>"
3369 "<th>Secure</th>"
3370 "<th>HTTP_only</th>"
3371 "<th>Remove</th></tr>\n");
3373 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3374 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3375 pc_start = pc;
3377 for (; sc; sc = sc->next) {
3378 c = sc->data;
3380 type = "Session";
3381 for (pc = pc_start; pc; pc = pc->next)
3382 if (soup_cookie_equal(pc->data, c)) {
3383 type = "Session + Persistent";
3384 break;
3387 tmp = body;
3388 body = g_strdup_printf(
3389 "%s\n<tr>"
3390 "<td style='width: 3%%; text-align: center'>%s</td>"
3391 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3392 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3393 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3394 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3395 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3396 "<td style='width: 3%%; text-align: center'>%d</td>"
3397 "<td style='width: 3%%; text-align: center'>%d</td>"
3398 "<td style='width: 3%%; text-align: center'>"
3399 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3400 body,
3401 type,
3402 c->name,
3403 c->value,
3404 c->domain,
3405 c->path,
3406 c->expires ?
3407 soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "",
3408 c->secure,
3409 c->http_only,
3411 XT_XTP_STR,
3412 XT_XTP_CL,
3413 cl_session_key,
3414 XT_XTP_CL_REMOVE,
3418 g_free(tmp);
3419 i++;
3422 soup_cookies_free(sc);
3423 soup_cookies_free(pc);
3425 /* small message if there are none */
3426 if (i == 1) {
3427 tmp = body;
3428 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3429 "colspan='8'>No Cookies</td></tr>\n", body);
3430 g_free(tmp);
3433 /* footer */
3434 footer = g_strdup_printf("</table></div></body></html>");
3436 page = g_strdup_printf("%s%s%s", header, body, footer);
3438 g_free(header);
3439 g_free(body);
3440 g_free(footer);
3442 load_webkit_string(t, page);
3443 update_cookie_tabs(t);
3445 g_free(page);
3447 return (0);
3451 xtp_page_hl(struct tab *t, struct karg *args)
3453 char *header, *body, *footer, *page, *tmp;
3454 struct history *h;
3455 int i = 1; /* all ids start 1 */
3457 DNPRINTF(XT_D_CMD, "%s", __func__);
3459 if (t == NULL)
3460 errx(1, "%s: null tab", __func__);
3462 /* mark this tab as history manager */
3463 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
3465 /* Generate a new session key */
3466 if (!updating_hl_tabs)
3467 generate_xtp_session_key(&hl_session_key);
3469 /* header */
3470 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
3471 "<title>History</title>\n"
3472 "%s"
3473 "</head>"
3474 "<h1>History</h1>\n",
3475 XT_PAGE_STYLE);
3477 /* body */
3478 body = g_strdup_printf("<div align='center'><table><tr>"
3479 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3481 RB_FOREACH_REVERSE(h, history_list, &hl) {
3482 tmp = body;
3483 body = g_strdup_printf(
3484 "%s\n<tr>"
3485 "<td><a href='%s'>%s</a></td>"
3486 "<td>%s</td>"
3487 "<td style='text-align: center'>"
3488 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3489 body, h->uri, h->uri, h->title,
3490 XT_XTP_STR, XT_XTP_HL, hl_session_key,
3491 XT_XTP_HL_REMOVE, i);
3493 g_free(tmp);
3494 i++;
3497 /* small message if there are none */
3498 if (i == 1) {
3499 tmp = body;
3500 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3501 "colspan='3'>No History</td></tr>\n", body);
3502 g_free(tmp);
3505 /* footer */
3506 footer = g_strdup_printf("</table></div></body></html>");
3508 page = g_strdup_printf("%s%s%s", header, body, footer);
3511 * update all history manager tabs as the xtp session
3512 * key has now changed. No need to update the current tab.
3513 * Already did that above.
3515 update_history_tabs(t);
3517 g_free(header);
3518 g_free(body);
3519 g_free(footer);
3521 load_webkit_string(t, page);
3522 g_free(page);
3524 return (0);
3528 * Generate a web page detailing the status of any downloads
3531 xtp_page_dl(struct tab *t, struct karg *args)
3533 struct download *dl;
3534 char *header, *body, *footer, *page, *tmp;
3535 char *ref;
3536 int n_dl = 1;
3538 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
3540 if (t == NULL)
3541 errx(1, "%s: null tab", __func__);
3543 /* mark as a download manager tab */
3544 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
3547 * Generate a new session key for next page instance.
3548 * This only happens for the top level call to xtp_page_dl()
3549 * in which case updating_dl_tabs is 0.
3551 if (!updating_dl_tabs)
3552 generate_xtp_session_key(&dl_session_key);
3554 /* header - with refresh so as to update */
3555 if (refresh_interval >= 1)
3556 ref = g_strdup_printf(
3557 "<meta http-equiv='refresh' content='%u"
3558 ";url=%s%d/%s/%d' />\n",
3559 refresh_interval,
3560 XT_XTP_STR,
3561 XT_XTP_DL,
3562 dl_session_key,
3563 XT_XTP_DL_LIST);
3564 else
3565 ref = g_strdup("");
3568 header = g_strdup_printf(
3569 "%s\n<head>"
3570 "<title>Downloads</title>\n%s%s</head>\n",
3571 XT_DOCTYPE XT_HTML_TAG,
3572 ref,
3573 XT_PAGE_STYLE);
3575 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
3576 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
3577 "</p><table><tr><th style='width: 60%%'>"
3578 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
3579 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
3581 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
3582 body = xtp_page_dl_row(t, body, dl);
3583 n_dl++;
3586 /* message if no downloads in list */
3587 if (n_dl == 1) {
3588 tmp = body;
3589 body = g_strdup_printf("%s\n<tr><td colspan='3'"
3590 " style='text-align: center'>"
3591 "No downloads</td></tr>\n", body);
3592 g_free(tmp);
3595 /* footer */
3596 footer = g_strdup_printf("</table></div></body></html>");
3598 page = g_strdup_printf("%s%s%s", header, body, footer);
3602 * update all download manager tabs as the xtp session
3603 * key has now changed. No need to update the current tab.
3604 * Already did that above.
3606 update_download_tabs(t);
3608 g_free(ref);
3609 g_free(header);
3610 g_free(body);
3611 g_free(footer);
3613 load_webkit_string(t, page);
3614 g_free(page);
3616 return (0);
3620 search(struct tab *t, struct karg *args)
3622 gboolean d;
3624 if (t == NULL || args == NULL)
3625 errx(1, "search");
3626 if (t->search_text == NULL) {
3627 if (global_search == NULL)
3628 return (XT_CB_PASSTHROUGH);
3629 else {
3630 t->search_text = g_strdup(global_search);
3631 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
3632 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
3636 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
3637 t->tab_id, args->i, t->search_forward, t->search_text);
3639 switch (args->i) {
3640 case XT_SEARCH_NEXT:
3641 d = t->search_forward;
3642 break;
3643 case XT_SEARCH_PREV:
3644 d = !t->search_forward;
3645 break;
3646 default:
3647 return (XT_CB_PASSTHROUGH);
3650 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
3652 return (XT_CB_HANDLED);
3655 struct settings_args {
3656 char **body;
3657 int i;
3660 void
3661 print_setting(struct settings *s, char *val, void *cb_args)
3663 char *tmp, *color;
3664 struct settings_args *sa = cb_args;
3666 if (sa == NULL)
3667 return;
3669 if (s->flags & XT_SF_RUNTIME)
3670 color = "#22cc22";
3671 else
3672 color = "#cccccc";
3674 tmp = *sa->body;
3675 *sa->body = g_strdup_printf(
3676 "%s\n<tr>"
3677 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
3678 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
3679 *sa->body,
3680 color,
3681 s->name,
3682 color,
3685 g_free(tmp);
3686 sa->i++;
3690 set(struct tab *t, struct karg *args)
3692 char *header, *body, *footer, *page, *tmp, *pars;
3693 int i = 1;
3694 struct settings_args sa;
3696 if ((pars = getparams(args->s, "set")) == NULL) {
3697 bzero(&sa, sizeof sa);
3698 sa.body = &body;
3700 /* header */
3701 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3702 "\n<head><title>Settings</title>\n"
3703 "</head><body><h1>Settings</h1>\n");
3705 /* body */
3706 body = g_strdup_printf("<div align='center'><table><tr>"
3707 "<th align='left'>Setting</th>"
3708 "<th align='left'>Value</th></tr>\n");
3710 settings_walk(print_setting, &sa);
3711 i = sa.i;
3713 /* small message if there are none */
3714 if (i == 1) {
3715 tmp = body;
3716 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3717 "colspan='2'>No settings</td></tr>\n", body);
3718 g_free(tmp);
3721 /* footer */
3722 footer = g_strdup_printf("</table></div></body></html>");
3724 page = g_strdup_printf("%s%s%s", header, body, footer);
3726 g_free(header);
3727 g_free(body);
3728 g_free(footer);
3730 load_webkit_string(t, page);
3731 } else {
3732 show_oops(t, "Invalid command: %s", pars);
3735 return (XT_CB_PASSTHROUGH);
3739 session_save(struct tab *t, char *filename, char **ret)
3741 struct karg a;
3742 char *f = filename;
3743 int rv = 1;
3745 f += strlen("save");
3746 while (*f == ' ' && *f != '\0')
3747 f++;
3748 if (strlen(f) == 0)
3749 goto done;
3751 a.s = f;
3752 save_tabs(t, &a);
3753 strlcpy(named_session, f, sizeof named_session);
3755 *ret = f;
3756 rv = 0;
3757 done:
3758 return (rv);
3762 session_open(struct tab *t, char *filename, char **ret)
3764 struct karg a;
3765 char *f = filename;
3766 int rv = 1;
3768 f += strlen("open");
3769 while (*f == ' ' && *f != '\0')
3770 f++;
3771 if (strlen(f) == 0)
3772 goto done;
3774 a.s = f;
3775 open_tabs(t, &a);
3776 strlcpy(named_session, f, sizeof named_session);
3778 *ret = f;
3779 rv = 0;
3780 done:
3781 return (rv);
3785 session_cmd(struct tab *t, struct karg *args)
3787 char *action = NULL;
3788 char *filename = NULL;
3790 if (t == NULL)
3791 return (1);
3793 if ((action = getparams(args->s, "session")))
3795 else
3796 action = "show";
3798 if (!strcmp(action, "show"))
3799 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
3800 XT_SAVED_TABS_FILE : named_session);
3801 else if (g_str_has_prefix(action, "save ")) {
3802 if (session_save(t, action, &filename)) {
3803 show_oops(t, "Can't save session: %s",
3804 filename ? filename : "INVALID");
3805 goto done;
3807 } else if (g_str_has_prefix(action, "open ")) {
3808 if (session_open(t, action, &filename)) {
3809 show_oops(t, "Can't open session: %s",
3810 filename ? filename : "INVALID");
3811 goto done;
3813 } else
3814 show_oops(t, "Invalid command: %s", action);
3815 done:
3816 return (XT_CB_PASSTHROUGH);
3820 * Make a hardcopy of the page
3823 print_page(struct tab *t, struct karg *args)
3825 WebKitWebFrame *frame;
3827 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
3830 * for now we just call the GTK print box,
3831 * but later we might decide to hook in a command.
3833 frame = webkit_web_view_get_main_frame(t->wv);
3834 webkit_web_frame_print(frame);
3836 return (0);
3840 go_home(struct tab *t, struct karg *args)
3842 char *newuri;
3844 newuri = guess_url_type((char *)home);
3845 webkit_web_view_load_uri(t->wv, newuri);
3846 free(newuri);
3848 return (0);
3852 restart(struct tab *t, struct karg *args)
3854 struct karg a;
3856 a.s = XT_RESTART_TABS_FILE;
3857 save_tabs(t, &a);
3858 execvp(start_argv[0], start_argv);
3859 /* NOTREACHED */
3861 return (0);
3864 /* inherent to GTK not all keys will be caught at all times */
3865 /* XXX sort key bindings */
3866 struct key_bindings {
3867 guint mask;
3868 guint use_in_entry;
3869 guint key;
3870 int (*func)(struct tab *, struct karg *);
3871 struct karg arg;
3872 } keys[] = {
3873 { GDK_MOD1_MASK, 0, GDK_d, xtp_page_dl, {0} },
3874 { GDK_MOD1_MASK, 0, GDK_h, xtp_page_hl, {0} },
3875 { GDK_CONTROL_MASK, 0, GDK_p, print_page, {0}},
3876 { 0, 0, GDK_slash, command, {.i = '/'} },
3877 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
3878 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
3879 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
3880 { GDK_MOD1_MASK, 0, GDK_q, restart, {0} },
3881 { GDK_CONTROL_MASK, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3882 { GDK_MOD1_MASK, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3883 { GDK_CONTROL_MASK, 0, GDK_s, toggle_src, {0} },
3884 { 0, 0, GDK_y, yank_uri, {0} },
3885 { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
3886 { GDK_SHIFT_MASK, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
3888 /* search */
3889 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
3890 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
3892 /* focus */
3893 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
3894 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
3896 /* command aliases (handy when -S flag is used) */
3897 { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
3898 { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
3899 { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
3900 { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
3902 /* hinting */
3903 { 0, 0, GDK_f, hint, {.i = 0} },
3905 /* navigation */
3906 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
3907 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
3908 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
3909 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
3910 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
3911 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
3912 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
3913 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
3914 { GDK_MOD1_MASK, 1, GDK_f, xtp_page_fl, {0} },
3916 /* vertical movement */
3917 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
3918 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
3919 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
3920 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
3921 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
3922 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
3923 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
3924 { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
3925 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
3926 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
3927 { GDK_CONTROL_MASK, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
3928 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
3929 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
3930 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
3931 { GDK_CONTROL_MASK, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
3932 /* horizontal movement */
3933 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
3934 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
3935 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
3936 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
3937 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
3938 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
3940 /* tabs */
3941 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
3942 { GDK_CONTROL_MASK, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
3943 { GDK_SHIFT_MASK, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
3944 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
3945 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
3946 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
3947 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
3948 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
3949 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
3950 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
3951 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
3952 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
3953 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
3954 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
3955 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
3956 { GDK_CONTROL_MASK, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
3957 { GDK_CONTROL_MASK, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
3958 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
3959 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
3960 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
3963 struct cmd {
3964 char *cmd;
3965 int params;
3966 int (*func)(struct tab *, struct karg *);
3967 struct karg arg;
3968 } cmds[] = {
3969 { "q!", 0, quit, {0} },
3970 { "qa", 0, quit, {0} },
3971 { "qa!", 0, quit, {0} },
3972 { "w", 0, save_tabs, {.s = XT_SAVED_TABS_FILE} },
3973 { "wq", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3974 { "wq!", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3975 { "help", 0, help, {0} },
3976 { "about", 0, about, {0} },
3977 { "stats", 0, stats, {0} },
3978 { "version", 0, about, {0} },
3979 { "cookies", 0, xtp_page_cl, {0} },
3980 { "fav", 0, xtp_page_fl, {0} },
3981 { "favadd", 0, add_favorite, {0} },
3982 { "js", 2, js_cmd, {0} },
3983 { "cookie", 2, cookie_cmd, {0} },
3984 { "cert", 1, cert_cmd, {0} },
3985 { "ca", 0, ca_cmd, {0} },
3986 { "dl" , 0, xtp_page_dl, {0} },
3987 { "h" , 0, xtp_page_hl, {0} },
3988 { "hist" , 0, xtp_page_hl, {0} },
3989 { "history" , 0, xtp_page_hl, {0} },
3990 { "home" , 0, go_home, {0} },
3991 { "restart" , 0, restart, {0} },
3993 { "1", 0, move, {.i = XT_MOVE_TOP} },
3994 { "print", 0, print_page, {0} },
3996 /* tabs */
3997 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
3998 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
3999 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4000 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4001 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4002 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4003 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4004 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4005 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4006 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4007 /* XXX add count to these commands */
4008 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4009 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4010 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4011 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4012 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4013 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4014 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4015 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4016 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4017 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4019 /* settings */
4020 { "set", 1, set, {0} },
4022 /* sessions */
4023 { "session", 1, session_cmd, {0} },
4026 gboolean
4027 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4029 hide_oops(t);
4031 return (FALSE);
4034 gboolean
4035 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4037 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4039 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4040 delete_tab(t);
4042 return (FALSE);
4046 * cancel, remove, etc. downloads
4048 void
4049 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4051 struct download find, *d;
4053 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4055 /* some commands require a valid download id */
4056 if (cmd != XT_XTP_DL_LIST) {
4057 /* lookup download in question */
4058 find.id = id;
4059 d = RB_FIND(download_list, &downloads, &find);
4061 if (d == NULL) {
4062 show_oops(t, "%s: no such download", __func__);
4063 return;
4067 /* decide what to do */
4068 switch (cmd) {
4069 case XT_XTP_DL_CANCEL:
4070 webkit_download_cancel(d->download);
4071 break;
4072 case XT_XTP_DL_REMOVE:
4073 webkit_download_cancel(d->download); /* just incase */
4074 g_object_unref(d->download);
4075 RB_REMOVE(download_list, &downloads, d);
4076 break;
4077 case XT_XTP_DL_LIST:
4078 /* Nothing */
4079 break;
4080 default:
4081 show_oops(t, "%s: unknown command", __func__);
4082 break;
4084 xtp_page_dl(t, NULL);
4088 * Actions on history, only does one thing for now, but
4089 * we provide the function for future actions
4091 void
4092 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
4094 struct history *h, *next;
4095 int i = 1;
4097 switch (cmd) {
4098 case XT_XTP_HL_REMOVE:
4099 /* walk backwards, as listed in reverse */
4100 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
4101 next = RB_PREV(history_list, &hl, h);
4102 if (id == i) {
4103 RB_REMOVE(history_list, &hl, h);
4104 g_free((gpointer) h->title);
4105 g_free((gpointer) h->uri);
4106 g_free(h);
4107 break;
4109 i++;
4111 break;
4112 case XT_XTP_HL_LIST:
4113 /* Nothing - just xtp_page_hl() below */
4114 break;
4115 default:
4116 show_oops(t, "%s: unknown command", __func__);
4117 break;
4120 xtp_page_hl(t, NULL);
4123 /* remove a favorite */
4124 void
4125 remove_favorite(struct tab *t, int index)
4127 char file[PATH_MAX], *title, *uri;
4128 char *new_favs, *tmp;
4129 FILE *f;
4130 int i;
4131 size_t len, lineno;
4133 /* open favorites */
4134 snprintf(file, sizeof file, "%s/%s/%s",
4135 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
4137 if ((f = fopen(file, "r")) == NULL) {
4138 show_oops(t, "%s: can't open favorites: %s",
4139 __func__, strerror(errno));
4140 return;
4143 /* build a string which will become the new favroites file */
4144 new_favs = g_strdup_printf("%s", "");
4146 for (i = 1;;) {
4147 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
4148 if (feof(f) || ferror(f))
4149 break;
4150 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
4151 if (len == 0) {
4152 free(title);
4153 title = NULL;
4154 continue;
4157 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
4158 if (feof(f) || ferror(f)) {
4159 show_oops(t, "%s: can't parse favorites %s",
4160 __func__, strerror(errno));
4161 goto clean;
4165 /* as long as this isn't the one we are deleting add to file */
4166 if (i != index) {
4167 tmp = new_favs;
4168 new_favs = g_strdup_printf("%s%s\n%s\n",
4169 new_favs, title, uri);
4170 g_free(tmp);
4173 free(uri);
4174 uri = NULL;
4175 free(title);
4176 title = NULL;
4177 i++;
4179 fclose(f);
4181 /* write back new favorites file */
4182 if ((f = fopen(file, "w")) == NULL) {
4183 show_oops(t, "%s: can't open favorites: %s",
4184 __func__, strerror(errno));
4185 goto clean;
4188 fwrite(new_favs, strlen(new_favs), 1, f);
4189 fclose(f);
4191 clean:
4192 if (uri)
4193 free(uri);
4194 if (title)
4195 free(title);
4197 g_free(new_favs);
4200 void
4201 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
4203 switch (cmd) {
4204 case XT_XTP_FL_LIST:
4205 /* nothing, just the below call to xtp_page_fl() */
4206 break;
4207 case XT_XTP_FL_REMOVE:
4208 remove_favorite(t, arg);
4209 break;
4210 default:
4211 show_oops(t, "%s: invalid favorites command", __func__);
4212 break;
4215 xtp_page_fl(t, NULL);
4218 void
4219 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
4221 switch (cmd) {
4222 case XT_XTP_CL_LIST:
4223 /* nothing, just xtp_page_cl() */
4224 break;
4225 case XT_XTP_CL_REMOVE:
4226 remove_cookie(arg);
4227 break;
4228 default:
4229 show_oops(t, "%s: unknown cookie xtp command", __func__);
4230 break;
4233 xtp_page_cl(t, NULL);
4236 /* link an XTP class to it's session key and handler function */
4237 struct xtp_despatch {
4238 uint8_t xtp_class;
4239 char **session_key;
4240 void (*handle_func)(struct tab *, uint8_t, int);
4243 struct xtp_despatch xtp_despatches[] = {
4244 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
4245 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
4246 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
4247 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
4248 { NULL, NULL, NULL }
4252 * is the url xtp protocol? (xxxt://)
4253 * if so, parse and despatch correct bahvior
4256 parse_xtp_url(struct tab *t, const char *url)
4258 char *dup = NULL, *p, *last;
4259 uint8_t n_tokens = 0;
4260 char *tokens[4] = {NULL, NULL, NULL, ""};
4261 struct xtp_despatch *dsp, *dsp_match = NULL;
4262 uint8_t req_class;
4265 * tokens array meaning:
4266 * tokens[0] = class
4267 * tokens[1] = session key
4268 * tokens[2] = action
4269 * tokens[3] = optional argument
4272 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
4274 /*xtp tab meaning is normal unless proven special */
4275 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4277 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
4278 return 0;
4280 dup = g_strdup(url + strlen(XT_XTP_STR));
4282 /* split out the url */
4283 for ((p = strtok_r(dup, "/", &last)); p;
4284 (p = strtok_r(NULL, "/", &last))) {
4285 if (n_tokens < 4)
4286 tokens[n_tokens++] = p;
4289 /* should be atleast three fields 'class/seskey/command/arg' */
4290 if (n_tokens < 3)
4291 goto clean;
4293 dsp = xtp_despatches;
4294 req_class = atoi(tokens[0]);
4295 while (dsp->xtp_class != NULL) {
4296 if (dsp->xtp_class == req_class) {
4297 dsp_match = dsp;
4298 break;
4300 dsp++;
4303 /* did we find one atall? */
4304 if (dsp_match == NULL) {
4305 show_oops(t, "%s: no matching xtp despatch found", __func__);
4306 goto clean;
4309 /* check session key and call despatch function */
4310 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
4311 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
4314 clean:
4315 if (dup)
4316 g_free(dup);
4318 return 1;
4323 void
4324 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
4326 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
4327 char *newuri = NULL;
4329 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
4331 if (t == NULL)
4332 errx(1, "activate_uri_entry_cb");
4334 if (uri == NULL)
4335 errx(1, "uri");
4337 uri += strspn(uri, "\t ");
4339 /* if xxxt:// treat specially */
4340 if (!parse_xtp_url(t, uri)) {
4341 if (valid_url_type((char *)uri)) {
4342 newuri = guess_url_type((char *)uri);
4343 uri = newuri;
4346 webkit_web_view_load_uri(t->wv, uri);
4347 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4350 if (newuri)
4351 g_free(newuri);
4354 void
4355 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
4357 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
4358 char *newuri = NULL;
4360 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
4362 if (t == NULL)
4363 errx(1, "activate_search_entry_cb");
4365 if (search_string == NULL) {
4366 show_oops(t, "no search_string");
4367 return;
4370 newuri = g_strdup_printf(search_string, search);
4372 webkit_web_view_load_uri(t->wv, newuri);
4373 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4375 if (newuri)
4376 g_free(newuri);
4379 void
4380 check_and_set_js(gchar *uri, struct tab *t)
4382 struct domain *d = NULL;
4383 int es = 0;
4385 if (uri == NULL || t == NULL)
4386 return;
4388 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
4389 es = 0;
4390 else
4391 es = 1;
4393 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
4394 es ? "enable" : "disable", uri);
4396 g_object_set((GObject *)t->settings,
4397 "enable-scripts", es, (char *)NULL);
4398 webkit_web_view_set_settings(t->wv, t->settings);
4400 button_set_stockid(t->js_toggle,
4401 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
4404 void
4405 show_ca_status(struct tab *t, const char *uri)
4407 WebKitWebFrame *frame;
4408 WebKitWebDataSource *source;
4409 WebKitNetworkRequest *request;
4410 SoupMessage *message;
4411 GdkColor color;
4412 gchar *col_str = "white";
4413 int r;
4415 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
4416 ssl_strict_certs, ssl_ca_file, uri);
4418 if (uri == NULL)
4419 goto done;
4420 if (ssl_ca_file == NULL) {
4421 if (g_str_has_prefix(uri, "http://"))
4422 goto done;
4423 if (g_str_has_prefix(uri, "https://")) {
4424 col_str = "red";
4425 goto done;
4427 return;
4429 if (g_str_has_prefix(uri, "http://") ||
4430 !g_str_has_prefix(uri, "https://"))
4431 goto done;
4433 frame = webkit_web_view_get_main_frame(t->wv);
4434 source = webkit_web_frame_get_data_source(frame);
4435 request = webkit_web_data_source_get_request(source);
4436 message = webkit_network_request_get_message(request);
4438 if (message && (soup_message_get_flags(message) &
4439 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
4440 col_str = "green";
4441 goto done;
4442 } else {
4443 r = load_compare_cert(t, NULL);
4444 if (r == 0)
4445 col_str = "lightblue";
4446 else if (r == 1)
4447 col_str = "yellow";
4448 else
4449 col_str = "red";
4450 goto done;
4452 done:
4453 if (col_str) {
4454 gdk_color_parse(col_str, &color);
4455 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
4459 void
4460 load_favicon(struct tab *t)
4462 gchar *name_hash;
4463 gint width, height;
4464 GdkPixbuf *pixbuf, *scaled;
4465 char file[PATH_MAX];
4467 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256,
4468 t->icon_uri, -1);
4469 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
4470 g_free(name_hash);
4471 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
4472 if (!pixbuf) {
4473 update_favicon(t);
4474 return;
4477 g_object_get(pixbuf, "width", &width, "height", &height, (char *)NULL);
4478 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
4479 __func__, t->tab_id, width, height);
4481 if (width > 16 || height > 16) {
4482 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
4483 GDK_INTERP_BILINEAR);
4484 g_object_unref(pixbuf);
4485 } else
4486 scaled = pixbuf;
4488 if (!scaled)
4489 update_favicon(t);
4491 if (t->icon_pixbuf)
4492 g_object_unref(t->icon_pixbuf);
4494 t->icon_pixbuf = scaled;
4496 update_favicon(t);
4499 void
4500 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
4501 struct tab *t)
4503 WebKitDownloadStatus status = webkit_download_get_status (download);
4505 switch (status) {
4506 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4507 load_favicon(t);
4508 g_object_unref(t->icon_download);
4509 break;
4510 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4511 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4512 g_object_unref(t->icon_download);
4513 break;
4514 default:
4515 break;
4520 notify_icon_loaded_cb(WebKitWebView *wv, char *uri, struct tab *t)
4522 WebKitNetworkRequest *request;
4523 gchar *name_hash, *dest_uri, file[PATH_MAX];
4524 struct stat sb;
4526 if (uri == NULL || t == NULL)
4527 return (FALSE);
4529 if (t->icon_uri) {
4530 if (!strcmp(t->icon_uri, uri))
4531 return (TRUE);
4532 g_free(t->icon_uri);
4535 t->icon_uri = g_strdup_printf("%s", uri);
4536 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri %s "
4537 "(dl!)\n", __func__, t->tab_id, uri);
4539 if (t->icon_download && G_IS_OBJECT(t->icon_download)
4540 && WEBKIT_IS_DOWNLOAD(t->icon_download)) {
4541 webkit_download_cancel(t->icon_download);
4542 g_object_unref(t->icon_download);
4545 request = webkit_network_request_new(t->icon_uri);
4546 if (!request) {
4547 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri %s "
4548 "(request failed)\n", __func__, t->tab_id, uri);
4549 return FALSE;
4551 t->icon_download = webkit_download_new(request);
4552 g_object_unref(request);
4554 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
4555 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
4556 g_free(name_hash);
4557 if (!stat(file, &sb)) {
4558 if (sb.st_size > 0) {
4559 load_favicon(t);
4560 return (TRUE);
4563 /* corrupt icon so trash it */
4564 unlink(file);
4567 dest_uri = g_strdup_printf("file://%s", file);
4568 webkit_download_set_destination_uri(t->icon_download, dest_uri);
4569 g_free(dest_uri);
4571 g_signal_connect(G_OBJECT (t->icon_download), "notify::status",
4572 G_CALLBACK (favicon_download_status_changed_cb), t);
4574 webkit_download_start(t->icon_download);
4576 return (TRUE);
4579 void
4580 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
4582 WebKitWebFrame *frame;
4583 const gchar *set = NULL, *uri = NULL, *title = NULL;
4584 struct history *h, find;
4585 int add = 0;
4586 const gchar *s_loading;
4588 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
4589 webkit_web_view_get_load_status(wview));
4591 if (t == NULL)
4592 errx(1, "notify_load_status_cb");
4594 switch (webkit_web_view_get_load_status(wview)) {
4595 case WEBKIT_LOAD_PROVISIONAL:
4596 #if GTK_CHECK_VERSION(2, 20, 0)
4597 gtk_widget_show(t->spinner);
4598 gtk_spinner_start(GTK_SPINNER(t->spinner));
4599 #endif
4600 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
4602 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
4603 t->focus_wv = 1;
4605 /* take focus if we are visible */
4606 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
4607 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4609 break;
4611 case WEBKIT_LOAD_COMMITTED:
4612 frame = webkit_web_view_get_main_frame(wview);
4613 uri = webkit_web_frame_get_uri(frame);
4614 if (uri)
4615 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
4617 /* check if js white listing is enabled */
4618 if (enable_js_whitelist) {
4619 frame = webkit_web_view_get_main_frame(wview);
4620 uri = webkit_web_frame_get_uri(frame);
4621 check_and_set_js((gchar *)uri, t);
4624 show_ca_status(t, uri);
4625 break;
4627 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
4628 title = webkit_web_view_get_title(wview);
4629 frame = webkit_web_view_get_main_frame(wview);
4630 uri = webkit_web_frame_get_uri(frame);
4631 if (title)
4632 set = title;
4633 else if (uri)
4634 set = uri;
4635 else
4636 break;
4638 gtk_label_set_text(GTK_LABEL(t->label), set);
4639 gtk_window_set_title(GTK_WINDOW(main_window), set);
4641 if (uri) {
4642 if (!strncmp(uri, "http://", strlen("http://")) ||
4643 !strncmp(uri, "https://", strlen("https://")) ||
4644 !strncmp(uri, "file://", strlen("file://")))
4645 add = 1;
4646 if (add == 0)
4647 break;
4648 find.uri = uri;
4649 h = RB_FIND(history_list, &hl, &find);
4650 if (h)
4651 break;
4653 h = g_malloc(sizeof *h);
4654 h->uri = g_strdup(uri);
4655 if (title)
4656 h->title = g_strdup(title);
4657 else
4658 h->title = g_strdup(uri);
4659 RB_INSERT(history_list, &hl, h);
4660 update_history_tabs(NULL);
4663 break;
4665 case WEBKIT_LOAD_FINISHED:
4666 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4667 case WEBKIT_LOAD_FAILED:
4668 #endif
4669 #if GTK_CHECK_VERSION(2, 20, 0)
4670 gtk_spinner_stop(GTK_SPINNER(t->spinner));
4671 gtk_widget_hide(t->spinner);
4672 #endif
4673 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
4674 if (s_loading && !strcmp(s_loading, "Loading"))
4675 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
4676 default:
4677 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
4678 break;
4680 check_favicon(t);
4682 if (t->item)
4683 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
4684 else
4685 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
4686 webkit_web_view_can_go_back(wview));
4688 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
4689 webkit_web_view_can_go_forward(wview));
4692 void
4693 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4695 run_script(t, JS_HINTING);
4698 void
4699 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
4701 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
4702 progress == 100 ? 0 : (double)progress / 100);
4706 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4707 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4708 WebKitWebPolicyDecision *pd, struct tab *t)
4710 char *uri;
4712 if (t == NULL)
4713 errx(1, "webview_nw_cb");
4715 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
4716 webkit_network_request_get_uri(request));
4718 /* open in current tab */
4719 uri = (char *)webkit_network_request_get_uri(request);
4720 webkit_web_view_load_uri(t->wv, uri);
4721 webkit_web_policy_decision_ignore(pd);
4723 return (TRUE); /* we made the decission */
4727 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4728 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4729 WebKitWebPolicyDecision *pd, struct tab *t)
4731 char *uri;
4733 if (t == NULL)
4734 errx(1, "webview_npd_cb");
4736 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
4737 t->ctrl_click,
4738 webkit_network_request_get_uri(request));
4740 uri = (char *)webkit_network_request_get_uri(request);
4742 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
4743 t->ctrl_click = 0;
4744 create_new_tab(uri, NULL, ctrl_click_focus);
4745 webkit_web_policy_decision_ignore(pd);
4746 return (TRUE); /* we made the decission */
4749 webkit_web_policy_decision_use(pd);
4750 return (TRUE); /* we made the decission */
4753 WebKitWebView *
4754 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4756 if (t == NULL)
4757 errx(1, "webview_cwv_cb");
4759 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
4760 webkit_web_view_get_uri(wv));
4762 return (wv);
4766 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
4768 /* we can not eat the event without throwing gtk off so defer it */
4770 /* catch middle click */
4771 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
4772 t->ctrl_click = 1;
4773 goto done;
4776 /* catch ctrl click */
4777 if (e->type == GDK_BUTTON_RELEASE &&
4778 CLEAN(e->state) == GDK_CONTROL_MASK)
4779 t->ctrl_click = 1;
4780 else
4781 t->ctrl_click = 0;
4782 done:
4783 return (XT_CB_PASSTHROUGH);
4787 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
4789 struct mime_type *m;
4791 m = find_mime_type(mime_type);
4792 if (m == NULL)
4793 return (1);
4795 switch (fork()) {
4796 case -1:
4797 err(1, "fork");
4798 /* NOTREACHED */
4799 case 0:
4800 break;
4801 default:
4802 return (0);
4805 /* child */
4806 execlp(m->mt_action, m->mt_action,
4807 webkit_network_request_get_uri(request), (void *)NULL);
4809 _exit(0);
4811 /* NOTREACHED */
4812 return (0);
4816 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
4817 WebKitNetworkRequest *request, char *mime_type,
4818 WebKitWebPolicyDecision *decision, struct tab *t)
4820 if (t == NULL)
4821 errx(1, "webview_mimetype_cb");
4823 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
4824 t->tab_id, mime_type);
4826 if (run_mimehandler(t, mime_type, request) == 0) {
4827 webkit_web_policy_decision_ignore(decision);
4828 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4829 return (TRUE);
4832 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
4833 webkit_web_policy_decision_download(decision);
4834 return (TRUE);
4837 return (FALSE);
4841 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t)
4843 const gchar *filename;
4844 char *uri = NULL;
4845 struct download *download_entry;
4846 int ret = TRUE;
4848 if (wk_download == NULL || t == NULL)
4849 errx(1, "%s: invalid pointers", __func__);
4851 filename = webkit_download_get_suggested_filename(wk_download);
4852 if (filename == NULL)
4853 return (FALSE); /* abort download */
4855 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
4857 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
4858 "local %s\n", __func__, t->tab_id, filename, uri);
4860 webkit_download_set_destination_uri(wk_download, uri);
4862 if (webkit_download_get_status(wk_download) ==
4863 WEBKIT_DOWNLOAD_STATUS_ERROR) {
4864 show_oops(t, "%s: download failed to start", __func__);
4865 ret = FALSE;
4866 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
4867 } else {
4868 download_entry = g_malloc(sizeof(struct download));
4869 download_entry->download = wk_download;
4870 download_entry->tab = t;
4871 download_entry->id = next_download_id++;
4872 RB_INSERT(download_list, &downloads, download_entry);
4873 /* get from history */
4874 g_object_ref(wk_download);
4875 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
4878 if (uri)
4879 g_free(uri);
4881 /* sync other download manager tabs */
4882 update_download_tabs(NULL);
4885 * NOTE: never redirect/render the current tab before this
4886 * function returns. This will cause the download to never start.
4888 return (ret); /* start download */
4891 /* XXX currently unused */
4892 void
4893 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
4895 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
4897 if (t == NULL)
4898 errx(1, "webview_hover_cb");
4900 if (uri) {
4901 if (t->hover) {
4902 g_free(t->hover);
4903 t->hover = NULL;
4905 t->hover = g_strdup(uri);
4906 } else if (t->hover) {
4907 g_free(t->hover);
4908 t->hover = NULL;
4913 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
4915 int i;
4916 char s[2], buf[128];
4917 const char *errstr = NULL;
4918 long long link;
4920 /* don't use w directly; use t->whatever instead */
4922 if (t == NULL)
4923 errx(1, "wv_keypress_after_cb");
4925 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
4926 e->keyval, e->state, t);
4928 if (t->hints_on) {
4929 /* ESC */
4930 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
4931 disable_hints(t);
4932 return (XT_CB_HANDLED);
4935 /* RETURN */
4936 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
4937 link = strtonum(t->hint_num, 1, 1000, &errstr);
4938 if (errstr) {
4939 /* we have a string */
4940 } else {
4941 /* we have a number */
4942 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
4943 t->hint_num);
4944 run_script(t, buf);
4946 disable_hints(t);
4949 /* BACKSPACE */
4950 /* XXX unfuck this */
4951 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
4952 if (t->hint_mode == XT_HINT_NUMERICAL) {
4953 /* last input was numerical */
4954 int l;
4955 l = strlen(t->hint_num);
4956 if (l > 0) {
4957 l--;
4958 if (l == 0) {
4959 disable_hints(t);
4960 enable_hints(t);
4961 } else {
4962 t->hint_num[l] = '\0';
4963 goto num;
4966 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
4967 /* last input was alphanumerical */
4968 int l;
4969 l = strlen(t->hint_buf);
4970 if (l > 0) {
4971 l--;
4972 if (l == 0) {
4973 disable_hints(t);
4974 enable_hints(t);
4975 } else {
4976 t->hint_buf[l] = '\0';
4977 goto anum;
4980 } else {
4981 /* bogus */
4982 disable_hints(t);
4986 /* numerical input */
4987 if (CLEAN(e->state) == 0 &&
4988 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
4989 snprintf(s, sizeof s, "%c", e->keyval);
4990 strlcat(t->hint_num, s, sizeof t->hint_num);
4991 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
4992 t->hint_num);
4993 num:
4994 link = strtonum(t->hint_num, 1, 1000, &errstr);
4995 if (errstr) {
4996 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
4997 disable_hints(t);
4998 } else {
4999 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
5000 t->hint_num);
5001 t->hint_mode = XT_HINT_NUMERICAL;
5002 run_script(t, buf);
5005 /* empty the counter buffer */
5006 bzero(t->hint_buf, sizeof t->hint_buf);
5007 return (XT_CB_HANDLED);
5010 /* alphanumerical input */
5011 if (
5012 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
5013 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
5014 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
5015 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
5016 snprintf(s, sizeof s, "%c", e->keyval);
5017 strlcat(t->hint_buf, s, sizeof t->hint_buf);
5018 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
5019 t->hint_buf);
5020 anum:
5021 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
5022 run_script(t, buf);
5024 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
5025 t->hint_buf);
5026 t->hint_mode = XT_HINT_ALPHANUM;
5027 run_script(t, buf);
5029 /* empty the counter buffer */
5030 bzero(t->hint_num, sizeof t->hint_num);
5031 return (XT_CB_HANDLED);
5034 return (XT_CB_HANDLED);
5037 for (i = 0; i < LENGTH(keys); i++)
5038 if (e->keyval == keys[i].key && CLEAN(e->state) ==
5039 keys[i].mask) {
5040 keys[i].func(t, &keys[i].arg);
5041 return (XT_CB_HANDLED);
5045 return (XT_CB_PASSTHROUGH);
5049 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5051 hide_oops(t);
5053 return (XT_CB_PASSTHROUGH);
5057 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5059 const gchar *c = gtk_entry_get_text(w);
5060 GdkColor color;
5061 int forward = TRUE;
5063 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5064 e->keyval, e->state, t);
5066 if (t == NULL)
5067 errx(1, "cmd_keyrelease_cb");
5069 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5070 e->keyval, e->state, t);
5072 if (c[0] == ':')
5073 goto done;
5074 if (strlen(c) == 1) {
5075 webkit_web_view_unmark_text_matches(t->wv);
5076 goto done;
5079 if (c[0] == '/')
5080 forward = TRUE;
5081 else if (c[0] == '?')
5082 forward = FALSE;
5083 else
5084 goto done;
5086 /* search */
5087 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
5088 FALSE) {
5089 /* not found, mark red */
5090 gdk_color_parse("red", &color);
5091 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5092 /* unmark and remove selection */
5093 webkit_web_view_unmark_text_matches(t->wv);
5094 /* my kingdom for a way to unselect text in webview */
5095 } else {
5096 /* found, highlight all */
5097 webkit_web_view_unmark_text_matches(t->wv);
5098 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
5099 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5100 gdk_color_parse("white", &color);
5101 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5103 done:
5104 return (XT_CB_PASSTHROUGH);
5107 #if 0
5109 cmd_complete(struct tab *t, char *s)
5111 int i;
5112 GtkEntry *w = GTK_ENTRY(t->cmd);
5114 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
5116 for (i = 0; i < LENGTH(cmds); i++) {
5117 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
5118 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
5119 #if 0
5120 gtk_entry_set_text(w, ":");
5121 gtk_entry_append_text(w, cmds[i].cmd);
5122 gtk_editable_set_position(GTK_EDITABLE(w), -1);
5123 #endif
5127 return (0);
5129 #endif
5132 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5134 int i;
5136 if (t == NULL)
5137 errx(1, "entry_key_cb");
5139 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
5140 e->keyval, e->state, t);
5142 hide_oops(t);
5144 if (e->keyval == GDK_Escape)
5145 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5147 for (i = 0; i < LENGTH(keys); i++)
5148 if (e->keyval == keys[i].key &&
5149 CLEAN(e->state) == keys[i].mask &&
5150 keys[i].use_in_entry) {
5151 keys[i].func(t, &keys[i].arg);
5152 return (XT_CB_HANDLED);
5155 return (XT_CB_PASSTHROUGH);
5159 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5161 int rv = XT_CB_HANDLED;
5162 const gchar *c = gtk_entry_get_text(w);
5164 if (t == NULL)
5165 errx(1, "cmd_keypress_cb");
5167 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
5168 e->keyval, e->state, t);
5170 /* sanity */
5171 if (c == NULL)
5172 e->keyval = GDK_Escape;
5173 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5174 e->keyval = GDK_Escape;
5176 switch (e->keyval) {
5177 #if 0
5178 case GDK_Tab:
5179 if (c[0] != ':')
5180 goto done;
5182 if (strchr (c, ' ')) {
5183 /* par completion */
5184 fprintf(stderr, "completeme par\n");
5185 goto done;
5188 cmd_complete(t, (char *)&c[1]);
5190 goto done;
5191 #endif
5192 case GDK_BackSpace:
5193 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
5194 break;
5195 /* FALLTHROUGH */
5196 case GDK_Escape:
5197 hide_cmd(t);
5198 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5200 /* cancel search */
5201 if (c[0] == '/' || c[0] == '?')
5202 webkit_web_view_unmark_text_matches(t->wv);
5203 goto done;
5206 rv = XT_CB_PASSTHROUGH;
5207 done:
5208 return (rv);
5212 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
5214 if (t == NULL)
5215 errx(1, "cmd_focusout_cb");
5217 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
5218 t->tab_id, t->focus_wv);
5220 hide_cmd(t);
5221 hide_oops(t);
5223 if (t->focus_wv)
5224 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5225 else
5226 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5228 return (XT_CB_PASSTHROUGH);
5231 void
5232 cmd_activate_cb(GtkEntry *entry, struct tab *t)
5234 int i;
5235 char *s;
5236 const gchar *c = gtk_entry_get_text(entry);
5238 if (t == NULL)
5239 errx(1, "cmd_activate_cb");
5241 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
5243 /* sanity */
5244 if (c == NULL)
5245 goto done;
5246 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5247 goto done;
5248 if (strlen(c) < 2)
5249 goto done;
5250 s = (char *)&c[1];
5252 if (c[0] == '/' || c[0] == '?') {
5253 if (t->search_text) {
5254 g_free(t->search_text);
5255 t->search_text = NULL;
5258 t->search_text = g_strdup(s);
5259 if (global_search)
5260 g_free(global_search);
5261 global_search = g_strdup(s);
5262 t->search_forward = c[0] == '/';
5264 goto done;
5267 for (i = 0; i < LENGTH(cmds); i++)
5268 if (cmds[i].params) {
5269 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
5270 cmds[i].arg.s = g_strdup(s);
5271 goto execute_command;
5273 } else {
5274 if (!strcmp(s, cmds[i].cmd))
5275 goto execute_command;
5277 show_oops(t, "Invalid command: %s", s);
5278 done:
5279 hide_cmd(t);
5280 return;
5282 execute_command:
5283 hide_cmd(t);
5284 cmds[i].func(t, &cmds[i].arg);
5286 void
5287 backward_cb(GtkWidget *w, struct tab *t)
5289 struct karg a;
5291 if (t == NULL)
5292 errx(1, "backward_cb");
5294 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
5296 a.i = XT_NAV_BACK;
5297 navaction(t, &a);
5300 void
5301 forward_cb(GtkWidget *w, struct tab *t)
5303 struct karg a;
5305 if (t == NULL)
5306 errx(1, "forward_cb");
5308 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
5310 a.i = XT_NAV_FORWARD;
5311 navaction(t, &a);
5314 void
5315 stop_cb(GtkWidget *w, struct tab *t)
5317 WebKitWebFrame *frame;
5319 if (t == NULL)
5320 errx(1, "stop_cb");
5322 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
5324 frame = webkit_web_view_get_main_frame(t->wv);
5325 if (frame == NULL) {
5326 show_oops(t, "stop_cb: no frame");
5327 return;
5330 webkit_web_frame_stop_loading(frame);
5333 void
5334 setup_webkit(struct tab *t)
5336 g_object_set((GObject *)t->settings,
5337 "user-agent", t->user_agent, (char *)NULL);
5338 g_object_set((GObject *)t->settings,
5339 "enable-scripts", enable_scripts, (char *)NULL);
5340 g_object_set((GObject *)t->settings,
5341 "enable-plugins", enable_plugins, (char *)NULL);
5342 adjustfont_webkit(t, XT_FONT_SET);
5344 webkit_web_view_set_settings(t->wv, t->settings);
5347 GtkWidget *
5348 create_browser(struct tab *t)
5350 GtkWidget *w;
5351 gchar *strval;
5353 if (t == NULL)
5354 errx(1, "create_browser");
5356 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
5357 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
5358 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
5359 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
5361 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
5362 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
5363 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5365 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
5366 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
5368 /* set defaults */
5369 t->settings = webkit_web_settings_new();
5371 if (user_agent == NULL) {
5372 g_object_get((GObject *)t->settings, "user-agent", &strval,
5373 (char *)NULL);
5374 t->user_agent = g_strdup_printf("%s %s+", strval, version);
5375 g_free(strval);
5376 } else {
5377 t->user_agent = g_strdup(user_agent);
5380 setup_webkit(t);
5382 return (w);
5385 GtkWidget *
5386 create_window(void)
5388 GtkWidget *w;
5390 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
5391 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
5392 gtk_widget_set_name(w, "xxxterm");
5393 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
5394 g_signal_connect(G_OBJECT(w), "delete_event",
5395 G_CALLBACK (gtk_main_quit), NULL);
5397 return (w);
5400 GtkWidget *
5401 create_toolbar(struct tab *t)
5403 GtkWidget *toolbar = NULL, *b, *eb1;
5405 b = gtk_hbox_new(FALSE, 0);
5406 toolbar = b;
5407 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
5409 if (fancy_bar) {
5410 /* backward button */
5411 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
5412 gtk_widget_set_sensitive(t->backward, FALSE);
5413 g_signal_connect(G_OBJECT(t->backward), "clicked",
5414 G_CALLBACK(backward_cb), t);
5415 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
5417 /* forward button */
5418 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
5419 gtk_widget_set_sensitive(t->forward, FALSE);
5420 g_signal_connect(G_OBJECT(t->forward), "clicked",
5421 G_CALLBACK(forward_cb), t);
5422 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
5423 FALSE, 0);
5425 /* stop button */
5426 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
5427 gtk_widget_set_sensitive(t->stop, FALSE);
5428 g_signal_connect(G_OBJECT(t->stop), "clicked",
5429 G_CALLBACK(stop_cb), t);
5430 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
5431 FALSE, 0);
5433 /* JS button */
5434 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
5435 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
5436 gtk_widget_set_sensitive(t->js_toggle, TRUE);
5437 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
5438 G_CALLBACK(js_toggle_cb), t);
5439 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
5442 t->uri_entry = gtk_entry_new();
5443 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
5444 G_CALLBACK(activate_uri_entry_cb), t);
5445 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
5446 (GCallback)entry_key_cb, t);
5447 eb1 = gtk_hbox_new(FALSE, 0);
5448 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
5449 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
5450 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
5452 /* search entry */
5453 if (fancy_bar && search_string) {
5454 GtkWidget *eb2;
5455 t->search_entry = gtk_entry_new();
5456 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
5457 g_signal_connect(G_OBJECT(t->search_entry), "activate",
5458 G_CALLBACK(activate_search_entry_cb), t);
5459 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
5460 (GCallback)entry_key_cb, t);
5461 gtk_widget_set_size_request(t->search_entry, -1, -1);
5462 eb2 = gtk_hbox_new(FALSE, 0);
5463 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
5464 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
5466 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
5468 return (toolbar);
5471 void
5472 recalc_tabs(void)
5474 struct tab *t;
5476 TAILQ_FOREACH(t, &tabs, entry)
5477 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
5481 undo_close_tab_save(struct tab *t)
5483 int m, n;
5484 const gchar *uri;
5485 struct undo *u1, *u2;
5486 WebKitWebFrame *frame;
5487 GList *items;
5488 WebKitWebHistoryItem *item;
5490 frame = webkit_web_view_get_main_frame(t->wv);
5491 uri = webkit_web_frame_get_uri(frame);
5493 if (uri && !strlen(uri))
5494 return (1);
5496 u1 = g_malloc0(sizeof(struct undo));
5497 u1->uri = g_strdup(uri);
5499 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
5501 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
5502 n = webkit_web_back_forward_list_get_back_length(t->bfl);
5503 u1->back = n;
5505 /* forward history */
5506 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
5508 while (items) {
5509 item = items->data;
5510 u1->history = g_list_prepend(u1->history,
5511 webkit_web_history_item_copy(item));
5512 items = g_list_next(items);
5515 /* current item */
5516 if (m) {
5517 item = webkit_web_back_forward_list_get_current_item(t->bfl);
5518 u1->history = g_list_prepend(u1->history,
5519 webkit_web_history_item_copy(item));
5522 /* back history */
5523 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
5525 while (items) {
5526 item = items->data;
5527 u1->history = g_list_prepend(u1->history,
5528 webkit_web_history_item_copy(item));
5529 items = g_list_next(items);
5532 TAILQ_INSERT_HEAD(&undos, u1, entry);
5534 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
5535 u2 = TAILQ_LAST(&undos, undo_tailq);
5536 TAILQ_REMOVE(&undos, u2, entry);
5537 g_free(u2->uri);
5538 g_list_free(u2->history);
5539 g_free(u2);
5540 } else
5541 undo_count++;
5543 return (0);
5546 void
5547 delete_tab(struct tab *t)
5549 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
5551 if (t == NULL)
5552 return;
5554 TAILQ_REMOVE(&tabs, t, entry);
5556 /* halt all webkit activity */
5557 webkit_web_view_stop_loading(t->wv);
5558 if (t->icon_download && G_IS_OBJECT(t->icon_download) &&
5559 WEBKIT_IS_DOWNLOAD(t->icon_download)) {
5560 webkit_download_cancel(t->icon_download);
5561 g_object_unref(t->icon_download);
5563 undo_close_tab_save(t);
5565 gtk_widget_destroy(t->vbox);
5566 g_free(t->user_agent);
5567 g_free(t);
5569 recalc_tabs();
5570 if (TAILQ_EMPTY(&tabs))
5571 create_new_tab(NULL, NULL, 1);
5574 void
5575 adjustfont_webkit(struct tab *t, int adjust)
5577 if (t == NULL)
5578 errx(1, "adjustfont_webkit");
5580 if (adjust == XT_FONT_SET)
5581 t->font_size = default_font_size;
5583 t->font_size += adjust;
5584 g_object_set((GObject *)t->settings, "default-font-size",
5585 t->font_size, (char *)NULL);
5586 g_object_get((GObject *)t->settings, "default-font-size",
5587 &t->font_size, (char *)NULL);
5590 void
5591 append_tab(struct tab *t)
5593 if (t == NULL)
5594 return;
5596 TAILQ_INSERT_TAIL(&tabs, t, entry);
5597 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
5600 void
5601 create_new_tab(char *title, struct undo *u, int focus)
5603 struct tab *t, *tt;
5604 int load = 1, id, notfound;
5605 char *newuri = NULL;
5606 GtkWidget *b, *bb;
5607 WebKitWebHistoryItem *item;
5608 GList *items;
5609 GdkColor color;
5611 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
5613 if (tabless && !TAILQ_EMPTY(&tabs)) {
5614 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
5615 return;
5618 t = g_malloc0(sizeof *t);
5620 if (title == NULL) {
5621 title = "(untitled)";
5622 load = 0;
5623 } else {
5624 if (valid_url_type(title)) {
5625 newuri = guess_url_type(title);
5626 title = newuri;
5630 t->vbox = gtk_vbox_new(FALSE, 0);
5632 /* label + button for tab */
5633 b = gtk_hbox_new(FALSE, 0);
5634 t->tab_content = b;
5636 #if GTK_CHECK_VERSION(2, 20, 0)
5637 t->spinner = gtk_spinner_new ();
5638 #endif
5639 t->label = gtk_label_new(title);
5640 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
5641 gtk_widget_set_size_request(t->label, 100, 0);
5642 gtk_widget_set_size_request(b, 130, 0);
5643 gtk_notebook_set_homogeneous_tabs(notebook, TRUE);
5645 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
5646 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
5647 #if GTK_CHECK_VERSION(2, 20, 0)
5648 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
5649 #endif
5651 /* toolbar */
5652 t->toolbar = create_toolbar(t);
5653 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
5655 /* browser */
5656 t->browser_win = create_browser(t);
5657 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
5659 /* oops message for user feedback */
5660 t->oops = gtk_entry_new();
5661 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
5662 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
5663 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
5664 gdk_color_parse("red", &color);
5665 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
5666 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
5668 /* command entry */
5669 t->cmd = gtk_entry_new();
5670 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
5671 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
5672 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
5674 /* xtp meaning is normal by default */
5675 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5677 /* and show it all */
5678 gtk_widget_show_all(b);
5679 gtk_widget_show_all(t->vbox);
5681 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
5682 append_tab(t);
5683 else {
5684 notfound = 1;
5685 id = gtk_notebook_get_current_page(notebook);
5686 TAILQ_FOREACH(tt, &tabs, entry) {
5687 if (tt->tab_id == id) {
5688 notfound = 0;
5689 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
5690 gtk_notebook_insert_page(notebook, t->vbox, b,
5691 id + 1);
5692 recalc_tabs();
5693 break;
5696 if (notfound)
5697 append_tab(t);
5700 #if GTK_CHECK_VERSION(2, 20, 0)
5701 /* turn spinner off if we are a new tab without uri */
5702 if (!load) {
5703 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5704 gtk_widget_hide(t->spinner);
5706 #endif
5707 /* make notebook tabs reorderable */
5708 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
5710 g_object_connect((GObject*)t->cmd,
5711 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
5712 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
5713 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
5714 "signal::activate", (GCallback)cmd_activate_cb, t,
5715 (char *)NULL);
5717 /* reuse wv_button_cb to hide oops */
5718 g_object_connect((GObject*)t->oops,
5719 "signal::button_press_event", (GCallback)wv_button_cb, t,
5720 (char *)NULL);
5722 g_object_connect((GObject*)t->wv,
5723 "signal::key-press-event", (GCallback)wv_keypress_cb, t,
5724 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5725 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
5726 "signal::download-requested", (GCallback)webview_download_cb, t,
5727 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
5728 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
5729 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
5730 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
5731 "signal::event", (GCallback)webview_event_cb, t,
5732 "signal::load-finished", (GCallback)webview_load_finished_cb, t,
5733 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t,
5734 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5735 "signal::icon-loaded", (GCallback)notify_icon_loaded_cb, t,
5736 #endif
5737 "signal::button_press_event", (GCallback)wv_button_cb, t,
5738 (char *)NULL);
5739 g_signal_connect(t->wv,
5740 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
5742 /* hijack the unused keys as if we were the browser */
5743 g_object_connect((GObject*)t->toolbar,
5744 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5745 (char *)NULL);
5747 g_signal_connect(G_OBJECT(bb), "button_press_event",
5748 G_CALLBACK(tab_close_cb), t);
5750 /* hide stuff */
5751 hide_cmd(t);
5752 hide_oops(t);
5753 if (showurl == 0)
5754 gtk_widget_hide(t->toolbar);
5756 if (focus) {
5757 gtk_notebook_set_current_page(notebook, t->tab_id);
5758 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
5759 t->tab_id);
5762 if (load)
5763 webkit_web_view_load_uri(t->wv, title);
5764 else {
5765 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5766 check_favicon(t);
5769 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
5770 /* restore the tab's history */
5771 if (u && u->history) {
5772 items = u->history;
5773 while (items) {
5774 item = items->data;
5775 webkit_web_back_forward_list_add_item(t->bfl, item);
5776 items = g_list_next(items);
5779 item = g_list_nth_data(u->history, u->back);
5780 if (item)
5781 webkit_web_view_go_to_back_forward_item(t->wv, item);
5783 g_list_free(items);
5784 g_list_free(u->history);
5787 if (newuri)
5788 g_free(newuri);
5791 void
5792 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
5793 gpointer *udata)
5795 struct tab *t;
5796 const gchar *uri;
5798 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
5800 TAILQ_FOREACH(t, &tabs, entry) {
5801 if (t->tab_id == pn) {
5802 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
5803 "%d\n", pn);
5805 uri = webkit_web_view_get_title(t->wv);
5806 if (uri == NULL)
5807 uri = XT_NAME;
5808 gtk_window_set_title(GTK_WINDOW(main_window), uri);
5810 hide_cmd(t);
5811 hide_oops(t);
5813 if (t->focus_wv)
5814 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5819 void
5820 menuitem_response(struct tab *t)
5822 gtk_notebook_set_current_page(notebook, t->tab_id);
5825 gboolean
5826 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
5828 GtkWidget *menu, *menu_items;
5829 GdkEventButton *bevent;
5830 WebKitWebFrame *frame;
5831 const gchar *uri;
5832 struct tab *ti;
5834 if (event->type == GDK_BUTTON_PRESS) {
5835 bevent = (GdkEventButton *) event;
5836 menu = gtk_menu_new();
5838 TAILQ_FOREACH(ti, &tabs, entry) {
5839 frame = webkit_web_view_get_main_frame(ti->wv);
5840 uri = webkit_web_frame_get_uri(frame);
5841 /* XXX make sure there is something to print */
5842 /* XXX add gui pages in here to look purdy */
5843 if (uri == NULL)
5844 uri = "(untitled)";
5845 if (strlen(uri) == 0)
5846 uri = "(untitled)";
5847 menu_items = gtk_menu_item_new_with_label(uri);
5848 gtk_menu_append(GTK_MENU (menu), menu_items);
5849 gtk_widget_show(menu_items);
5851 gtk_signal_connect_object(GTK_OBJECT(menu_items),
5852 "activate", GTK_SIGNAL_FUNC(menuitem_response),
5853 (gpointer)ti);
5856 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
5857 bevent->button, bevent->time);
5859 /* unref object so it'll free itself when popped down */
5860 g_object_ref_sink(menu);
5861 g_object_unref(menu);
5863 return (TRUE /* eat event */);
5866 return (FALSE /* propagate */);
5870 icon_size_map(int icon_size)
5872 if (icon_size <= GTK_ICON_SIZE_INVALID ||
5873 icon_size > GTK_ICON_SIZE_DIALOG)
5874 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
5876 return (icon_size);
5879 GtkWidget *
5880 create_button(char *name, char *stockid, int size)
5882 GtkWidget *button, *image;
5883 gchar *rcstring;
5884 int gtk_icon_size;
5885 rcstring = g_strdup_printf(
5886 "style \"%s-style\"\n"
5887 "{\n"
5888 " GtkWidget::focus-padding = 0\n"
5889 " GtkWidget::focus-line-width = 0\n"
5890 " xthickness = 0\n"
5891 " ythickness = 0\n"
5892 "}\n"
5893 "widget \"*.%s\" style \"%s-style\"",name,name,name);
5894 gtk_rc_parse_string(rcstring);
5895 g_free(rcstring);
5896 button = gtk_button_new();
5897 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
5898 gtk_icon_size = icon_size_map(size?size:icon_size);
5900 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
5901 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5902 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
5903 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
5904 gtk_widget_set_name(button, name);
5905 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
5906 gtk_widget_set_tooltip_text(button, name);
5908 return button;
5911 void
5912 button_set_stockid(GtkWidget *button, char *stockid)
5914 GtkWidget *image;
5915 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
5916 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5917 gtk_button_set_image(GTK_BUTTON(button), image);
5920 void
5921 create_canvas(void)
5923 GtkWidget *vbox;
5924 GList *l = NULL;
5925 GdkPixbuf *pb;
5926 char file[PATH_MAX];
5927 int i;
5929 vbox = gtk_vbox_new(FALSE, 0);
5930 gtk_box_set_spacing(GTK_BOX(vbox), 0);
5931 notebook = GTK_NOTEBOOK(gtk_notebook_new());
5932 if (showtabs == 0)
5933 gtk_notebook_set_show_tabs(notebook, FALSE);
5934 else {
5935 gtk_notebook_set_tab_hborder(notebook, 0);
5936 gtk_notebook_set_tab_vborder(notebook, 0);
5938 gtk_notebook_set_show_border(notebook, FALSE);
5939 gtk_notebook_set_scrollable(notebook, TRUE);
5940 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
5942 abtn = gtk_button_new();
5943 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
5944 gtk_widget_set_size_request(arrow, -1, -1);
5945 gtk_container_add(GTK_CONTAINER(abtn), arrow);
5946 gtk_widget_set_size_request(abtn, -1, 20);
5947 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
5949 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
5950 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
5951 gtk_widget_set_size_request(vbox, -1, -1);
5953 g_object_connect((GObject*)notebook,
5954 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
5955 (char *)NULL);
5956 g_signal_connect(G_OBJECT(abtn), "button_press_event",
5957 G_CALLBACK(arrow_cb), NULL);
5959 main_window = create_window();
5960 gtk_container_add(GTK_CONTAINER(main_window), vbox);
5961 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5963 /* icons */
5964 for (i = 0; i < LENGTH(icons); i++) {
5965 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
5966 pb = gdk_pixbuf_new_from_file(file, NULL);
5967 l = g_list_append(l, pb);
5969 gtk_window_set_default_icon_list(l);
5971 gtk_widget_show_all(abtn);
5972 gtk_widget_show_all(main_window);
5975 void
5976 set_hook(void **hook, char *name)
5978 if (hook == NULL)
5979 errx(1, "set_hook");
5981 if (*hook == NULL) {
5982 *hook = dlsym(RTLD_NEXT, name);
5983 if (*hook == NULL)
5984 errx(1, "can't hook %s", name);
5988 /* override libsoup soup_cookie_equal because it doesn't look at domain */
5989 gboolean
5990 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
5992 g_return_val_if_fail(cookie1, FALSE);
5993 g_return_val_if_fail(cookie2, FALSE);
5995 return (!strcmp (cookie1->name, cookie2->name) &&
5996 !strcmp (cookie1->value, cookie2->value) &&
5997 !strcmp (cookie1->path, cookie2->path) &&
5998 !strcmp (cookie1->domain, cookie2->domain));
6001 void
6002 transfer_cookies(void)
6004 GSList *cf;
6005 SoupCookie *sc, *pc;
6007 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6009 for (;cf; cf = cf->next) {
6010 pc = cf->data;
6011 sc = soup_cookie_copy(pc);
6012 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
6015 soup_cookies_free(cf);
6018 void
6019 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
6021 GSList *cf;
6022 SoupCookie *ci;
6024 print_cookie("soup_cookie_jar_delete_cookie", c);
6026 if (cookies_enabled == 0)
6027 return;
6029 if (jar == NULL || c == NULL)
6030 return;
6032 /* find and remove from persistent jar */
6033 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6035 for (;cf; cf = cf->next) {
6036 ci = cf->data;
6037 if (soup_cookie_equal(ci, c)) {
6038 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
6039 break;
6043 soup_cookies_free(cf);
6045 /* delete from session jar */
6046 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
6049 void
6050 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
6052 struct domain *d;
6053 SoupCookie *c;
6054 FILE *r_cookie_f;
6056 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
6057 jar, p_cookiejar, s_cookiejar);
6059 if (cookies_enabled == 0)
6060 return;
6062 /* see if we are up and running */
6063 if (p_cookiejar == NULL) {
6064 _soup_cookie_jar_add_cookie(jar, cookie);
6065 return;
6067 /* disallow p_cookiejar adds, shouldn't happen */
6068 if (jar == p_cookiejar)
6069 return;
6071 if ((d = wl_find(cookie->domain, &c_wl)) == NULL) {
6072 blocked_cookies++;
6073 DNPRINTF(XT_D_COOKIE,
6074 "soup_cookie_jar_add_cookie: reject %s\n",
6075 cookie->domain);
6076 if (save_rejected_cookies) {
6077 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL)
6078 err(1, "reject cookie file");
6079 fseek(r_cookie_f, 0, SEEK_END);
6080 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
6081 cookie->http_only ? "#HttpOnly_" : "",
6082 cookie->domain,
6083 *cookie->domain == '.' ? "TRUE" : "FALSE",
6084 cookie->path,
6085 cookie->secure ? "TRUE" : "FALSE",
6086 cookie->expires ?
6087 (gulong)soup_date_to_time_t(cookie->expires) :
6089 cookie->name,
6090 cookie->value);
6091 fflush(r_cookie_f);
6092 fclose(r_cookie_f);
6094 if (!allow_volatile_cookies)
6095 return;
6098 if (cookie->expires == NULL && session_timeout) {
6099 soup_cookie_set_expires(cookie,
6100 soup_date_new_from_now(session_timeout));
6101 print_cookie("modified add cookie", cookie);
6104 /* see if we are white listed for persistence */
6105 if (d && d->handy) {
6106 /* add to persistent jar */
6107 c = soup_cookie_copy(cookie);
6108 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
6109 _soup_cookie_jar_add_cookie(p_cookiejar, c);
6112 /* add to session jar */
6113 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
6114 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
6117 void
6118 setup_cookies(void)
6120 char file[PATH_MAX];
6122 set_hook((void *)&_soup_cookie_jar_add_cookie,
6123 "soup_cookie_jar_add_cookie");
6124 set_hook((void *)&_soup_cookie_jar_delete_cookie,
6125 "soup_cookie_jar_delete_cookie");
6127 if (cookies_enabled == 0)
6128 return;
6131 * the following code is intricate due to overriding several libsoup
6132 * functions.
6133 * do not alter order of these operations.
6136 /* rejected cookies */
6137 if (save_rejected_cookies)
6138 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
6140 /* persistent cookies */
6141 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
6142 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
6144 /* session cookies */
6145 s_cookiejar = soup_cookie_jar_new();
6146 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
6147 cookie_policy, (void *)NULL);
6148 transfer_cookies();
6150 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
6153 void
6154 setup_proxy(char *uri)
6156 if (proxy_uri) {
6157 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
6158 soup_uri_free(proxy_uri);
6159 proxy_uri = NULL;
6161 if (http_proxy) {
6162 if (http_proxy != uri) {
6163 g_free(http_proxy);
6164 http_proxy = NULL;
6168 if (uri) {
6169 http_proxy = g_strdup(uri);
6170 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
6171 proxy_uri = soup_uri_new(http_proxy);
6172 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
6177 send_url_to_socket(char *url)
6179 int s, len, rv = -1;
6180 struct sockaddr_un sa;
6182 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6183 warnx("send_url_to_socket: socket");
6184 return (-1);
6187 sa.sun_family = AF_UNIX;
6188 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6189 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6190 len = SUN_LEN(&sa);
6192 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6193 warnx("send_url_to_socket: connect");
6194 goto done;
6197 if (send(s, url, strlen(url) + 1, 0) == -1) {
6198 warnx("send_url_to_socket: send");
6199 goto done;
6201 done:
6202 close(s);
6203 return (rv);
6206 void
6207 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
6209 int s, n;
6210 char str[XT_MAX_URL_LENGTH];
6211 socklen_t t = sizeof(struct sockaddr_un);
6212 struct sockaddr_un sa;
6213 struct passwd *p;
6214 uid_t uid;
6215 gid_t gid;
6217 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
6218 warn("socket_watcher: accept");
6219 return;
6222 if (getpeereid(s, &uid, &gid) == -1) {
6223 warn("socket_watcher: getpeereid");
6224 return;
6226 if (uid != getuid() || gid != getgid()) {
6227 warnx("socket_watcher: unauthorized user");
6228 return;
6231 p = getpwuid(uid);
6232 if (p == NULL) {
6233 warnx("socket_watcher: not a valid user");
6234 return;
6237 n = recv(s, str, sizeof(str), 0);
6238 if (n <= 0)
6239 return;
6241 create_new_tab(str, NULL, 1);
6245 is_running(void)
6247 int s, len, rv = 1;
6248 struct sockaddr_un sa;
6250 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6251 warn("is_running: socket");
6252 return (-1);
6255 sa.sun_family = AF_UNIX;
6256 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6257 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6258 len = SUN_LEN(&sa);
6260 /* connect to see if there is a listener */
6261 if (connect(s, (struct sockaddr *)&sa, len) == -1)
6262 rv = 0; /* not running */
6263 else
6264 rv = 1; /* already running */
6266 close(s);
6268 return (rv);
6272 build_socket(void)
6274 int s, len;
6275 struct sockaddr_un sa;
6277 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6278 warn("build_socket: socket");
6279 return (-1);
6282 sa.sun_family = AF_UNIX;
6283 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6284 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6285 len = SUN_LEN(&sa);
6287 /* connect to see if there is a listener */
6288 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6289 /* no listener so we will */
6290 unlink(sa.sun_path);
6292 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
6293 warn("build_socket: bind");
6294 goto done;
6297 if (listen(s, 1) == -1) {
6298 warn("build_socket: listen");
6299 goto done;
6302 return (s);
6305 done:
6306 close(s);
6307 return (-1);
6310 void
6311 usage(void)
6313 fprintf(stderr,
6314 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
6315 exit(0);
6319 main(int argc, char *argv[])
6321 struct stat sb;
6322 int c, focus = 1, s, optn = 0;
6323 char conf[PATH_MAX] = { '\0' };
6324 char file[PATH_MAX];
6325 char *env_proxy = NULL;
6326 FILE *f = NULL;
6327 struct karg a;
6329 start_argv = argv;
6331 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
6333 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
6334 switch (c) {
6335 case 'S':
6336 showurl = 0;
6337 break;
6338 case 'T':
6339 showtabs = 0;
6340 break;
6341 case 'V':
6342 errx(0 , "Version: %s", version);
6343 break;
6344 case 'f':
6345 strlcpy(conf, optarg, sizeof(conf));
6346 break;
6347 case 's':
6348 strlcpy(named_session, optarg, sizeof(named_session));
6349 break;
6350 case 't':
6351 tabless = 1;
6352 break;
6353 case 'n':
6354 optn = 1;
6355 break;
6356 default:
6357 usage();
6358 /* NOTREACHED */
6361 argc -= optind;
6362 argv += optind;
6364 TAILQ_INIT(&tabs);
6365 RB_INIT(&hl);
6366 RB_INIT(&js_wl);
6367 RB_INIT(&downloads);
6368 TAILQ_INIT(&mtl);
6369 TAILQ_INIT(&aliases);
6370 TAILQ_INIT(&undos);
6372 gnutls_global_init();
6374 /* generate session keys for xtp pages */
6375 generate_xtp_session_key(&dl_session_key);
6376 generate_xtp_session_key(&hl_session_key);
6377 generate_xtp_session_key(&cl_session_key);
6378 generate_xtp_session_key(&fl_session_key);
6380 /* prepare gtk */
6381 gtk_init(&argc, &argv);
6382 if (!g_thread_supported())
6383 g_thread_init(NULL);
6385 pwd = getpwuid(getuid());
6386 if (pwd == NULL)
6387 errx(1, "invalid user %d", getuid());
6389 /* set download dir */
6390 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
6392 /* set default string settings */
6393 home = g_strdup("http://www.peereboom.us");
6394 resource_dir = g_strdup("/usr/local/share/xxxterm/");
6395 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
6397 /* read config file */
6398 if (strlen(conf) == 0)
6399 snprintf(conf, sizeof conf, "%s/.%s",
6400 pwd->pw_dir, XT_CONF_FILE);
6401 config_parse(conf, 0);
6403 /* working directory */
6404 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
6405 if (stat(work_dir, &sb)) {
6406 if (mkdir(work_dir, S_IRWXU) == -1)
6407 err(1, "mkdir work_dir");
6408 if (stat(work_dir, &sb))
6409 err(1, "stat work_dir");
6411 if (S_ISDIR(sb.st_mode) == 0)
6412 errx(1, "%s not a dir", work_dir);
6413 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6414 warnx("fixing invalid permissions on %s", work_dir);
6415 if (chmod(work_dir, S_IRWXU) == -1)
6416 err(1, "chmod");
6419 /* icon cache dir */
6420 snprintf(cache_dir, sizeof cache_dir, "%s/%s/%s",
6421 pwd->pw_dir, XT_DIR, XT_CACHE_DIR);
6422 if (stat(cache_dir, &sb)) {
6423 if (mkdir(cache_dir, S_IRWXU) == -1)
6424 err(1, "mkdir cache_dir");
6425 if (stat(cache_dir, &sb))
6426 err(1, "stat cache_dir");
6428 if (S_ISDIR(sb.st_mode) == 0)
6429 errx(1, "%s not a dir", cache_dir);
6430 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6431 warnx("fixing invalid permissions on %s", cache_dir);
6432 if (chmod(cache_dir, S_IRWXU) == -1)
6433 err(1, "chmod");
6436 /* certs dir */
6437 snprintf(certs_dir, sizeof certs_dir, "%s/%s/%s",
6438 pwd->pw_dir, XT_DIR, XT_CERT_DIR);
6439 if (stat(certs_dir, &sb)) {
6440 if (mkdir(certs_dir, S_IRWXU) == -1)
6441 err(1, "mkdir certs_dir");
6442 if (stat(certs_dir, &sb))
6443 err(1, "stat certs_dir");
6445 if (S_ISDIR(sb.st_mode) == 0)
6446 errx(1, "%s not a dir", certs_dir);
6447 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6448 warnx("fixing invalid permissions on %s", certs_dir);
6449 if (chmod(certs_dir, S_IRWXU) == -1)
6450 err(1, "chmod");
6453 /* sessions dir */
6454 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s/%s",
6455 pwd->pw_dir, XT_DIR, XT_SESSIONS_DIR);
6456 if (stat(sessions_dir, &sb)) {
6457 if (mkdir(sessions_dir, S_IRWXU) == -1)
6458 err(1, "mkdir sessions_dir");
6459 if (stat(sessions_dir, &sb))
6460 err(1, "stat sessions_dir");
6462 if (S_ISDIR(sb.st_mode) == 0)
6463 errx(1, "%s not a dir", sessions_dir);
6464 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6465 warnx("fixing invalid permissions on %s", sessions_dir);
6466 if (chmod(sessions_dir, S_IRWXU) == -1)
6467 err(1, "chmod");
6469 /* runtime settings that can override config file */
6470 if (runtime_settings[0] != '\0')
6471 config_parse(runtime_settings, 1);
6473 /* download dir */
6474 if (!strcmp(download_dir, pwd->pw_dir))
6475 strlcat(download_dir, "/downloads", sizeof download_dir);
6476 if (stat(download_dir, &sb)) {
6477 if (mkdir(download_dir, S_IRWXU) == -1)
6478 err(1, "mkdir download_dir");
6479 if (stat(download_dir, &sb))
6480 err(1, "stat download_dir");
6482 if (S_ISDIR(sb.st_mode) == 0)
6483 errx(1, "%s not a dir", download_dir);
6484 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6485 warnx("fixing invalid permissions on %s", download_dir);
6486 if (chmod(download_dir, S_IRWXU) == -1)
6487 err(1, "chmod");
6490 /* favorites file */
6491 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6492 if (stat(file, &sb)) {
6493 warnx("favorites file doesn't exist, creating it");
6494 if ((f = fopen(file, "w")) == NULL)
6495 err(1, "favorites");
6496 fclose(f);
6499 /* cookies */
6500 session = webkit_get_default_session();
6501 setup_cookies();
6503 /* certs */
6504 if (ssl_ca_file) {
6505 if (stat(ssl_ca_file, &sb)) {
6506 warn("no CA file: %s", ssl_ca_file);
6507 g_free(ssl_ca_file);
6508 ssl_ca_file = NULL;
6509 } else
6510 g_object_set(session,
6511 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
6512 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
6513 (void *)NULL);
6516 /* proxy */
6517 env_proxy = getenv("http_proxy");
6518 if (env_proxy)
6519 setup_proxy(env_proxy);
6520 else
6521 setup_proxy(http_proxy);
6523 /* see if there is already an xxxterm running */
6524 if (single_instance && is_running()) {
6525 optn = 1;
6526 warnx("already running");
6529 if (optn) {
6530 while (argc) {
6531 send_url_to_socket(argv[0]);
6533 argc--;
6534 argv++;
6536 exit(0);
6539 /* go graphical */
6540 create_canvas();
6542 if (save_global_history)
6543 restore_global_history();
6545 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
6546 focus = restore_saved_tabs();
6547 else {
6548 a.s = named_session;
6549 focus = open_tabs(NULL, &a);
6552 while (argc) {
6553 create_new_tab(argv[0], NULL, focus);
6554 focus = 0;
6556 argc--;
6557 argv++;
6559 if (focus == 1)
6560 create_new_tab(home, NULL, 1);
6562 if (enable_socket)
6563 if ((s = build_socket()) != -1)
6564 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
6566 gtk_main();
6568 gnutls_global_deinit();
6570 return (0);