i have noticed that pages containing framesets have no title, no label and
[xxxterm.git] / xxxterm.c
blob0befe97e95c9f8674b80ba70be77b898f101dfa6
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>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 * TODO:
23 * inverse color browsing
24 * favs
25 * - store in sqlite
26 * multi letter commands
27 * pre and post counts for commands
28 * autocompletion on various inputs
29 * create privacy browsing
30 * - encrypted local data
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <err.h>
36 #include <pwd.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <pthread.h>
40 #include <dlfcn.h>
41 #include <errno.h>
42 #include <signal.h>
43 #include <libgen.h>
44 #include <ctype.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #if defined(__linux__)
49 #include "linux/util.h"
50 #include "linux/tree.h"
51 #elif defined(__FreeBSD__)
52 #include <libutil.h>
53 #include "freebsd/util.h"
54 #include <sys/tree.h>
55 #else /* OpenBSD */
56 #include <util.h>
57 #include <sys/tree.h>
58 #endif
59 #include <sys/queue.h>
60 #include <sys/stat.h>
61 #include <sys/socket.h>
62 #include <sys/un.h>
64 #include <gtk/gtk.h>
65 #include <gdk/gdkkeysyms.h>
66 #include <webkit/webkit.h>
67 #include <libsoup/soup.h>
68 #include <gnutls/gnutls.h>
69 #include <JavaScriptCore/JavaScript.h>
70 #include <gnutls/x509.h>
72 #include "javascript.h"
75 javascript.h borrowed from vimprobable2 under the following license:
77 Copyright (c) 2009 Leon Winter
78 Copyright (c) 2009 Hannes Schueller
79 Copyright (c) 2009 Matto Fransen
81 Permission is hereby granted, free of charge, to any person obtaining a copy
82 of this software and associated documentation files (the "Software"), to deal
83 in the Software without restriction, including without limitation the rights
84 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
85 copies of the Software, and to permit persons to whom the Software is
86 furnished to do so, subject to the following conditions:
88 The above copyright notice and this permission notice shall be included in
89 all copies or substantial portions of the Software.
91 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
92 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
93 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
94 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
95 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
96 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
97 THE SOFTWARE.
100 static char *version = "$xxxterm$";
102 /* hooked functions */
103 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
104 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
105 SoupCookie *);
107 /*#define XT_DEBUG*/
108 #ifdef XT_DEBUG
109 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
110 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
111 #define XT_D_MOVE 0x0001
112 #define XT_D_KEY 0x0002
113 #define XT_D_TAB 0x0004
114 #define XT_D_URL 0x0008
115 #define XT_D_CMD 0x0010
116 #define XT_D_NAV 0x0020
117 #define XT_D_DOWNLOAD 0x0040
118 #define XT_D_CONFIG 0x0080
119 #define XT_D_JS 0x0100
120 #define XT_D_FAVORITE 0x0200
121 #define XT_D_PRINTING 0x0400
122 #define XT_D_COOKIE 0x0800
123 #define XT_D_KEYBINDING 0x1000
124 u_int32_t swm_debug = 0
125 | XT_D_MOVE
126 | XT_D_KEY
127 | XT_D_TAB
128 | XT_D_URL
129 | XT_D_CMD
130 | XT_D_NAV
131 | XT_D_DOWNLOAD
132 | XT_D_CONFIG
133 | XT_D_JS
134 | XT_D_FAVORITE
135 | XT_D_PRINTING
136 | XT_D_COOKIE
137 | XT_D_KEYBINDING
139 #else
140 #define DPRINTF(x...)
141 #define DNPRINTF(n,x...)
142 #endif
144 #define LENGTH(x) (sizeof x / sizeof x[0])
145 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
146 ~(GDK_BUTTON1_MASK) & \
147 ~(GDK_BUTTON2_MASK) & \
148 ~(GDK_BUTTON3_MASK) & \
149 ~(GDK_BUTTON4_MASK) & \
150 ~(GDK_BUTTON5_MASK))
152 char *icons[] = {
153 "xxxtermicon16.png",
154 "xxxtermicon32.png",
155 "xxxtermicon48.png",
156 "xxxtermicon64.png",
157 "xxxtermicon128.png"
160 struct tab {
161 TAILQ_ENTRY(tab) entry;
162 GtkWidget *vbox;
163 GtkWidget *tab_content;
164 GtkWidget *label;
165 GtkWidget *spinner;
166 GtkWidget *uri_entry;
167 GtkWidget *search_entry;
168 GtkWidget *toolbar;
169 GtkWidget *browser_win;
170 GtkWidget *statusbar;
171 GtkWidget *cmd;
172 GtkWidget *oops;
173 GtkWidget *backward;
174 GtkWidget *forward;
175 GtkWidget *stop;
176 GtkWidget *js_toggle;
177 GtkEntryCompletion *completion;
178 guint tab_id;
179 WebKitWebView *wv;
181 WebKitWebHistoryItem *item;
182 WebKitWebBackForwardList *bfl;
184 /* favicon */
185 WebKitNetworkRequest *icon_request;
186 WebKitDownload *icon_download;
187 GdkPixbuf *icon_pixbuf;
188 gchar *icon_dest_uri;
190 /* adjustments for browser */
191 GtkScrollbar *sb_h;
192 GtkScrollbar *sb_v;
193 GtkAdjustment *adjust_h;
194 GtkAdjustment *adjust_v;
196 /* flags */
197 int focus_wv;
198 int ctrl_click;
199 gchar *status;
200 int xtp_meaning; /* identifies dls/favorites */
202 /* hints */
203 int hints_on;
204 int hint_mode;
205 #define XT_HINT_NONE (0)
206 #define XT_HINT_NUMERICAL (1)
207 #define XT_HINT_ALPHANUM (2)
208 char hint_buf[128];
209 char hint_num[128];
211 /* search */
212 char *search_text;
213 int search_forward;
215 /* settings */
216 WebKitWebSettings *settings;
217 int font_size;
218 gchar *user_agent;
220 TAILQ_HEAD(tab_list, tab);
222 struct history {
223 RB_ENTRY(history) entry;
224 const gchar *uri;
225 const gchar *title;
227 RB_HEAD(history_list, history);
229 struct download {
230 RB_ENTRY(download) entry;
231 int id;
232 WebKitDownload *download;
233 struct tab *tab;
235 RB_HEAD(download_list, download);
237 struct domain {
238 RB_ENTRY(domain) entry;
239 gchar *d;
240 int handy; /* app use */
242 RB_HEAD(domain_list, domain);
244 struct undo {
245 TAILQ_ENTRY(undo) entry;
246 gchar *uri;
247 GList *history;
248 int back; /* Keeps track of how many back
249 * history items there are. */
251 TAILQ_HEAD(undo_tailq, undo);
253 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
254 int next_download_id = 1;
256 struct karg {
257 int i;
258 char *s;
261 /* defines */
262 #define XT_NAME ("XXXTerm")
263 #define XT_DIR (".xxxterm")
264 #define XT_CACHE_DIR ("cache")
265 #define XT_CERT_DIR ("certs/")
266 #define XT_SESSIONS_DIR ("sessions/")
267 #define XT_CONF_FILE ("xxxterm.conf")
268 #define XT_FAVS_FILE ("favorites")
269 #define XT_SAVED_TABS_FILE ("main_session")
270 #define XT_RESTART_TABS_FILE ("restart_tabs")
271 #define XT_SOCKET_FILE ("socket")
272 #define XT_HISTORY_FILE ("history")
273 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
274 #define XT_CB_HANDLED (TRUE)
275 #define XT_CB_PASSTHROUGH (FALSE)
276 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
277 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
278 #define XT_DLMAN_REFRESH "10"
279 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
280 "td {overflow: hidden;" \
281 " padding: 2px 2px 2px 2px;" \
282 " border: 1px solid black}\n" \
283 "tr:hover {background: #ffff99 ;}\n" \
284 "th {background-color: #cccccc;" \
285 " border: 1px solid black}" \
286 "table {border-spacing: 0; " \
287 " width: 90%%;" \
288 " border: 1px black solid;}\n" \
289 ".progress-outer{" \
290 " border: 1px solid black;" \
291 " height: 8px;" \
292 " width: 90%%;}" \
293 ".progress-inner{" \
294 " float: left;" \
295 " height: 8px;" \
296 " background: green;}" \
297 ".dlstatus{" \
298 " font-size: small;" \
299 " text-align: center;}" \
300 "</style>\n\n"
301 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
302 #define XT_MAX_UNDO_CLOSE_TAB (32)
303 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
304 #define XT_PRINT_EXTRA_MARGIN 10
306 /* file sizes */
307 #define SZ_KB ((uint64_t) 1024)
308 #define SZ_MB (SZ_KB * SZ_KB)
309 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
310 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
313 * xxxterm "protocol" (xtp)
314 * We use this for managing stuff like downloads and favorites. They
315 * make magical HTML pages in memory which have xxxt:// links in order
316 * to communicate with xxxterm's internals. These links take the format:
317 * xxxt://class/session_key/action/arg
319 * Don't begin xtp class/actions as 0. atoi returns that on error.
321 * Typically we have not put addition of items in this framework, as
322 * adding items is either done via an ex-command or via a keybinding instead.
325 #define XT_XTP_STR "xxxt://"
327 /* XTP classes (xxxt://<class>) */
328 #define XT_XTP_DL 1 /* downloads */
329 #define XT_XTP_HL 2 /* history */
330 #define XT_XTP_CL 3 /* cookies */
331 #define XT_XTP_FL 4 /* favorites */
333 /* XTP download actions */
334 #define XT_XTP_DL_LIST 1
335 #define XT_XTP_DL_CANCEL 2
336 #define XT_XTP_DL_REMOVE 3
338 /* XTP history actions */
339 #define XT_XTP_HL_LIST 1
340 #define XT_XTP_HL_REMOVE 2
342 /* XTP cookie actions */
343 #define XT_XTP_CL_LIST 1
344 #define XT_XTP_CL_REMOVE 2
346 /* XTP cookie actions */
347 #define XT_XTP_FL_LIST 1
348 #define XT_XTP_FL_REMOVE 2
350 /* xtp tab meanings - identifies which tabs have xtp pages in */
351 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
352 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
353 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
354 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
355 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
357 /* actions */
358 #define XT_MOVE_INVALID (0)
359 #define XT_MOVE_DOWN (1)
360 #define XT_MOVE_UP (2)
361 #define XT_MOVE_BOTTOM (3)
362 #define XT_MOVE_TOP (4)
363 #define XT_MOVE_PAGEDOWN (5)
364 #define XT_MOVE_PAGEUP (6)
365 #define XT_MOVE_HALFDOWN (7)
366 #define XT_MOVE_HALFUP (8)
367 #define XT_MOVE_LEFT (9)
368 #define XT_MOVE_FARLEFT (10)
369 #define XT_MOVE_RIGHT (11)
370 #define XT_MOVE_FARRIGHT (12)
372 #define XT_TAB_LAST (-4)
373 #define XT_TAB_FIRST (-3)
374 #define XT_TAB_PREV (-2)
375 #define XT_TAB_NEXT (-1)
376 #define XT_TAB_INVALID (0)
377 #define XT_TAB_NEW (1)
378 #define XT_TAB_DELETE (2)
379 #define XT_TAB_DELQUIT (3)
380 #define XT_TAB_OPEN (4)
381 #define XT_TAB_UNDO_CLOSE (5)
382 #define XT_TAB_SHOW (6)
383 #define XT_TAB_HIDE (7)
385 #define XT_NAV_INVALID (0)
386 #define XT_NAV_BACK (1)
387 #define XT_NAV_FORWARD (2)
388 #define XT_NAV_RELOAD (3)
389 #define XT_NAV_RELOAD_CACHE (4)
391 #define XT_FOCUS_INVALID (0)
392 #define XT_FOCUS_URI (1)
393 #define XT_FOCUS_SEARCH (2)
395 #define XT_SEARCH_INVALID (0)
396 #define XT_SEARCH_NEXT (1)
397 #define XT_SEARCH_PREV (2)
399 #define XT_PASTE_CURRENT_TAB (0)
400 #define XT_PASTE_NEW_TAB (1)
402 #define XT_FONT_SET (0)
404 #define XT_URL_SHOW (1)
405 #define XT_URL_HIDE (2)
407 #define XT_STATUSBAR_SHOW (1)
408 #define XT_STATUSBAR_HIDE (2)
410 #define XT_WL_TOGGLE (1<<0)
411 #define XT_WL_ENABLE (1<<1)
412 #define XT_WL_DISABLE (1<<2)
413 #define XT_WL_FQDN (1<<3) /* default */
414 #define XT_WL_TOPLEVEL (1<<4)
416 #define XT_CMD_OPEN (0)
417 #define XT_CMD_OPEN_CURRENT (1)
418 #define XT_CMD_TABNEW (2)
419 #define XT_CMD_TABNEW_CURRENT (3)
421 #define XT_STATUS_NOTHING (0)
422 #define XT_STATUS_LINK (1)
423 #define XT_STATUS_URI (2)
424 #define XT_STATUS_LOADING (3)
426 #define XT_SES_DONOTHING (0)
427 #define XT_SES_CLOSETABS (1)
429 #define XT_COOKIE_NORMAL (0)
430 #define XT_COOKIE_WHITELIST (1)
432 /* mime types */
433 struct mime_type {
434 char *mt_type;
435 char *mt_action;
436 int mt_default;
437 TAILQ_ENTRY(mime_type) entry;
439 TAILQ_HEAD(mime_type_list, mime_type);
441 /* uri aliases */
442 struct alias {
443 char *a_name;
444 char *a_uri;
445 TAILQ_ENTRY(alias) entry;
447 TAILQ_HEAD(alias_list, alias);
449 /* settings that require restart */
450 int tabless = 0; /* allow only 1 tab */
451 int enable_socket = 0;
452 int single_instance = 0; /* only allow one xxxterm to run */
453 int fancy_bar = 1; /* fancy toolbar */
454 int browser_mode = XT_COOKIE_NORMAL;
456 /* runtime settings */
457 int show_tabs = 1; /* show tabs on notebook */
458 int show_url = 1; /* show url toolbar on notebook */
459 int show_statusbar = 0; /* vimperator style status bar */
460 int ctrl_click_focus = 0; /* ctrl click gets focus */
461 int cookies_enabled = 1; /* enable cookies */
462 int read_only_cookies = 0; /* enable to not write cookies */
463 int enable_scripts = 1;
464 int enable_plugins = 0;
465 int default_font_size = 12;
466 gfloat default_zoom_level = 1.0;
467 int window_height = 768;
468 int window_width = 1024;
469 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
470 unsigned refresh_interval = 10; /* download refresh interval */
471 int enable_cookie_whitelist = 0;
472 int enable_js_whitelist = 0;
473 time_t session_timeout = 3600; /* cookie session timeout */
474 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
475 char *ssl_ca_file = NULL;
476 char *resource_dir = NULL;
477 gboolean ssl_strict_certs = FALSE;
478 int append_next = 1; /* append tab after current tab */
479 char *home = NULL;
480 char *search_string = NULL;
481 char *http_proxy = NULL;
482 char download_dir[PATH_MAX];
483 char runtime_settings[PATH_MAX]; /* override of settings */
484 int allow_volatile_cookies = 0;
485 int save_global_history = 0; /* save global history to disk */
486 char *user_agent = NULL;
487 int save_rejected_cookies = 0;
488 time_t session_autosave = 0;
489 int guess_search = 0;
491 struct settings;
492 struct key_binding;
493 int set_download_dir(struct settings *, char *);
494 int set_work_dir(struct settings *, char *);
495 int set_runtime_dir(struct settings *, char *);
496 int set_browser_mode(struct settings *, char *);
497 int set_cookie_policy(struct settings *, char *);
498 int add_alias(struct settings *, char *);
499 int add_mime_type(struct settings *, char *);
500 int add_cookie_wl(struct settings *, char *);
501 int add_js_wl(struct settings *, char *);
502 int add_kb(struct settings *, char *);
503 void button_set_stockid(GtkWidget *, char *);
504 GtkWidget * create_button(char *, char *, int);
506 char *get_browser_mode(struct settings *);
507 char *get_cookie_policy(struct settings *);
509 char *get_download_dir(struct settings *);
510 char *get_work_dir(struct settings *);
511 char *get_runtime_dir(struct settings *);
513 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
514 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
515 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
516 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
517 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
519 struct special {
520 int (*set)(struct settings *, char *);
521 char *(*get)(struct settings *);
522 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
525 struct special s_browser_mode = {
526 set_browser_mode,
527 get_browser_mode,
528 NULL
531 struct special s_cookie = {
532 set_cookie_policy,
533 get_cookie_policy,
534 NULL
537 struct special s_alias = {
538 add_alias,
539 NULL,
540 walk_alias
543 struct special s_mime = {
544 add_mime_type,
545 NULL,
546 walk_mime_type
549 struct special s_js = {
550 add_js_wl,
551 NULL,
552 walk_js_wl
555 struct special s_kb = {
556 add_kb,
557 NULL,
558 walk_kb
561 struct special s_cookie_wl = {
562 add_cookie_wl,
563 NULL,
564 walk_cookie_wl
567 struct special s_download_dir = {
568 set_download_dir,
569 get_download_dir,
570 NULL
573 struct special s_work_dir = {
574 set_work_dir,
575 get_work_dir,
576 NULL
579 struct settings {
580 char *name;
581 int type;
582 #define XT_S_INVALID (0)
583 #define XT_S_INT (1)
584 #define XT_S_STR (2)
585 #define XT_S_FLOAT (3)
586 uint32_t flags;
587 #define XT_SF_RESTART (1<<0)
588 #define XT_SF_RUNTIME (1<<1)
589 int *ival;
590 char **sval;
591 struct special *s;
592 gfloat *fval;
593 } rs[] = {
594 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
595 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
596 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
597 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
598 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
599 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
600 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
601 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
602 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
603 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
604 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
605 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
606 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
607 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
608 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
609 { "home", XT_S_STR, 0, NULL, &home, NULL },
610 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
611 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
612 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
613 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
614 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
615 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
616 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
617 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
618 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
619 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
620 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
621 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
622 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
623 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
624 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
625 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
626 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
627 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
628 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
629 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
630 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
632 /* runtime settings */
633 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
634 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
635 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
636 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
637 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
640 int about(struct tab *, struct karg *);
641 int blank(struct tab *, struct karg *);
642 int cookie_show_wl(struct tab *, struct karg *);
643 int js_show_wl(struct tab *, struct karg *);
644 int help(struct tab *, struct karg *);
645 int set(struct tab *, struct karg *);
646 int stats(struct tab *, struct karg *);
647 int xtp_page_cl(struct tab *, struct karg *);
648 int xtp_page_dl(struct tab *, struct karg *);
649 int xtp_page_fl(struct tab *, struct karg *);
650 int xtp_page_hl(struct tab *, struct karg *);
652 #define XT_URI_ABOUT ("about:")
653 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
654 #define XT_URI_ABOUT_ABOUT ("about")
655 #define XT_URI_ABOUT_BLANK ("blank")
656 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
657 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
658 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
659 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
660 #define XT_URI_ABOUT_FAVORITES ("favorites")
661 #define XT_URI_ABOUT_HELP ("help")
662 #define XT_URI_ABOUT_HISTORY ("history")
663 #define XT_URI_ABOUT_JSWL ("jswl")
664 #define XT_URI_ABOUT_SET ("set")
665 #define XT_URI_ABOUT_STATS ("stats")
667 struct about_type {
668 char *name;
669 int (*func)(struct tab *, struct karg *);
670 } about_list[] = {
671 { XT_URI_ABOUT_ABOUT, about },
672 { XT_URI_ABOUT_BLANK, blank },
673 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
674 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
675 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
676 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
677 { XT_URI_ABOUT_HELP, help },
678 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
679 { XT_URI_ABOUT_JSWL, js_show_wl },
680 { XT_URI_ABOUT_SET, set },
681 { XT_URI_ABOUT_STATS, stats },
684 /* globals */
685 extern char *__progname;
686 char **start_argv;
687 struct passwd *pwd;
688 GtkWidget *main_window;
689 GtkNotebook *notebook;
690 GtkWidget *arrow, *abtn;
691 struct tab_list tabs;
692 struct history_list hl;
693 struct download_list downloads;
694 struct domain_list c_wl;
695 struct domain_list js_wl;
696 struct undo_tailq undos;
697 struct keybinding_list kbl;
698 int undo_count;
699 int updating_dl_tabs = 0;
700 int updating_hl_tabs = 0;
701 int updating_cl_tabs = 0;
702 int updating_fl_tabs = 0;
703 char *global_search;
704 uint64_t blocked_cookies = 0;
705 char named_session[PATH_MAX];
706 void update_favicon(struct tab *);
707 int icon_size_map(int);
709 GtkListStore *completion_model;
710 void completion_add(struct tab *);
711 void completion_add_uri(const gchar *);
713 void
714 sigchild(int sig)
716 int saved_errno, status;
717 pid_t pid;
719 saved_errno = errno;
721 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
722 if (pid == -1) {
723 if (errno == EINTR)
724 continue;
725 if (errno != ECHILD) {
727 clog_warn("sigchild: waitpid:");
730 break;
733 if (WIFEXITED(status)) {
734 if (WEXITSTATUS(status) != 0) {
736 clog_warnx("sigchild: child exit status: %d",
737 WEXITSTATUS(status));
740 } else {
742 clog_warnx("sigchild: child is terminated abnormally");
747 errno = saved_errno;
750 void
751 load_webkit_string(struct tab *t, const char *str, gchar *title)
753 gchar *uri;
754 char file[PATH_MAX];
755 GdkPixbuf *pb;
757 /* we set this to indicate we want to manually do navaction */
758 if (t->bfl)
759 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
760 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
762 if (title) {
763 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
764 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
765 g_free(uri);
767 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
768 pb = gdk_pixbuf_new_from_file(file, NULL);
769 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
770 GTK_ENTRY_ICON_PRIMARY, pb);
771 gdk_pixbuf_unref(pb);
775 void
776 set_status(struct tab *t, gchar *s, int status)
778 gchar *type = NULL;
780 if (s == NULL)
781 return;
783 switch (status) {
784 case XT_STATUS_LOADING:
785 type = g_strdup_printf("Loading: %s", s);
786 s = type;
787 break;
788 case XT_STATUS_LINK:
789 type = g_strdup_printf("Link: %s", s);
790 if (!t->status)
791 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
792 s = type;
793 break;
794 case XT_STATUS_URI:
795 type = g_strdup_printf("%s", s);
796 if (!t->status) {
797 t->status = g_strdup(type);
799 s = type;
800 if (!t->status)
801 t->status = g_strdup(s);
802 break;
803 case XT_STATUS_NOTHING:
804 /* FALL THROUGH */
805 default:
806 break;
808 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
809 if (type)
810 g_free(type);
813 void
814 hide_oops(struct tab *t)
816 gtk_widget_hide(t->oops);
819 void
820 hide_cmd(struct tab *t)
822 gtk_widget_hide(t->cmd);
825 void
826 show_cmd(struct tab *t)
828 gtk_widget_hide(t->oops);
829 gtk_widget_show(t->cmd);
832 void
833 show_oops(struct tab *t, const char *fmt, ...)
835 va_list ap;
836 char *msg;
838 if (fmt == NULL)
839 return;
841 va_start(ap, fmt);
842 if (vasprintf(&msg, fmt, ap) == -1)
843 errx(1, "show_oops failed");
844 va_end(ap);
846 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
847 gtk_widget_hide(t->cmd);
848 gtk_widget_show(t->oops);
851 /* XXX collapse with show_oops */
852 void
853 show_oops_s(const char *fmt, ...)
855 va_list ap;
856 char *msg;
857 struct tab *ti, *t = NULL;
859 if (fmt == NULL)
860 return;
862 TAILQ_FOREACH(ti, &tabs, entry)
863 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
864 t = ti;
865 break;
867 if (t == NULL)
868 return;
870 va_start(ap, fmt);
871 if (vasprintf(&msg, fmt, ap) == -1)
872 errx(1, "show_oops_s failed");
873 va_end(ap);
875 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
876 gtk_widget_hide(t->cmd);
877 gtk_widget_show(t->oops);
880 char *
881 get_as_string(struct settings *s)
883 char *r = NULL;
885 if (s == NULL)
886 return (NULL);
888 if (s->s) {
889 if (s->s->get)
890 r = s->s->get(s);
891 else
892 warnx("get_as_string skip %s\n", s->name);
893 } else if (s->type == XT_S_INT)
894 r = g_strdup_printf("%d", *s->ival);
895 else if (s->type == XT_S_STR)
896 r = g_strdup(*s->sval);
897 else if (s->type == XT_S_FLOAT)
898 r = g_strdup_printf("%f", *s->fval);
899 else
900 r = g_strdup_printf("INVALID TYPE");
902 return (r);
905 void
906 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
908 int i;
909 char *s;
911 for (i = 0; i < LENGTH(rs); i++) {
912 if (rs[i].s && rs[i].s->walk)
913 rs[i].s->walk(&rs[i], cb, cb_args);
914 else {
915 s = get_as_string(&rs[i]);
916 cb(&rs[i], s, cb_args);
917 g_free(s);
923 set_browser_mode(struct settings *s, char *val)
925 if (!strcmp(val, "whitelist")) {
926 browser_mode = XT_COOKIE_WHITELIST;
927 allow_volatile_cookies = 0;
928 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
929 cookies_enabled = 1;
930 enable_cookie_whitelist = 1;
931 read_only_cookies = 0;
932 save_rejected_cookies = 0;
933 session_timeout = 3600;
934 enable_scripts = 0;
935 enable_js_whitelist = 1;
936 } else if (!strcmp(val, "normal")) {
937 browser_mode = XT_COOKIE_NORMAL;
938 allow_volatile_cookies = 0;
939 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
940 cookies_enabled = 1;
941 enable_cookie_whitelist = 0;
942 read_only_cookies = 0;
943 save_rejected_cookies = 0;
944 session_timeout = 3600;
945 enable_scripts = 1;
946 enable_js_whitelist = 0;
947 } else
948 return (1);
950 return (0);
953 char *
954 get_browser_mode(struct settings *s)
956 char *r = NULL;
958 if (browser_mode == XT_COOKIE_WHITELIST)
959 r = g_strdup("whitelist");
960 else if (browser_mode == XT_COOKIE_NORMAL)
961 r = g_strdup("normal");
962 else
963 return (NULL);
965 return (r);
969 set_cookie_policy(struct settings *s, char *val)
971 if (!strcmp(val, "no3rdparty"))
972 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
973 else if (!strcmp(val, "accept"))
974 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
975 else if (!strcmp(val, "reject"))
976 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
977 else
978 return (1);
980 return (0);
983 char *
984 get_cookie_policy(struct settings *s)
986 char *r = NULL;
988 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
989 r = g_strdup("no3rdparty");
990 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
991 r = g_strdup("accept");
992 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
993 r = g_strdup("reject");
994 else
995 return (NULL);
997 return (r);
1000 char *
1001 get_download_dir(struct settings *s)
1003 if (download_dir[0] == '\0')
1004 return (0);
1005 return (g_strdup(download_dir));
1009 set_download_dir(struct settings *s, char *val)
1011 if (val[0] == '~')
1012 snprintf(download_dir, sizeof download_dir, "%s/%s",
1013 pwd->pw_dir, &val[1]);
1014 else
1015 strlcpy(download_dir, val, sizeof download_dir);
1017 return (0);
1021 * Session IDs.
1022 * We use these to prevent people putting xxxt:// URLs on
1023 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1025 #define XT_XTP_SES_KEY_SZ 8
1026 #define XT_XTP_SES_KEY_HEX_FMT \
1027 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1028 char *dl_session_key; /* downloads */
1029 char *hl_session_key; /* history list */
1030 char *cl_session_key; /* cookie list */
1031 char *fl_session_key; /* favorites list */
1033 char work_dir[PATH_MAX];
1034 char certs_dir[PATH_MAX];
1035 char cache_dir[PATH_MAX];
1036 char sessions_dir[PATH_MAX];
1037 char cookie_file[PATH_MAX];
1038 SoupURI *proxy_uri = NULL;
1039 SoupSession *session;
1040 SoupCookieJar *s_cookiejar;
1041 SoupCookieJar *p_cookiejar;
1042 char rc_fname[PATH_MAX];
1044 struct mime_type_list mtl;
1045 struct alias_list aliases;
1047 /* protos */
1048 struct tab *create_new_tab(char *, struct undo *, int);
1049 void delete_tab(struct tab *);
1050 void adjustfont_webkit(struct tab *, int);
1051 int run_script(struct tab *, char *);
1052 int download_rb_cmp(struct download *, struct download *);
1055 history_rb_cmp(struct history *h1, struct history *h2)
1057 return (strcmp(h1->uri, h2->uri));
1059 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1062 domain_rb_cmp(struct domain *d1, struct domain *d2)
1064 return (strcmp(d1->d, d2->d));
1066 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1068 char *
1069 get_work_dir(struct settings *s)
1071 if (work_dir[0] == '\0')
1072 return (0);
1073 return (g_strdup(work_dir));
1077 set_work_dir(struct settings *s, char *val)
1079 if (val[0] == '~')
1080 snprintf(work_dir, sizeof work_dir, "%s/%s",
1081 pwd->pw_dir, &val[1]);
1082 else
1083 strlcpy(work_dir, val, sizeof work_dir);
1085 return (0);
1089 * generate a session key to secure xtp commands.
1090 * pass in a ptr to the key in question and it will
1091 * be modified in place.
1093 void
1094 generate_xtp_session_key(char **key)
1096 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1098 /* free old key */
1099 if (*key)
1100 g_free(*key);
1102 /* make a new one */
1103 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1104 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1105 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1106 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1108 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1112 * validate a xtp session key.
1113 * return 1 if OK
1116 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1118 if (strcmp(trusted, untrusted) != 0) {
1119 show_oops(t, "%s: xtp session key mismatch possible spoof",
1120 __func__);
1121 return (0);
1124 return (1);
1128 download_rb_cmp(struct download *e1, struct download *e2)
1130 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1132 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1134 struct valid_url_types {
1135 char *type;
1136 } vut[] = {
1137 { "http://" },
1138 { "https://" },
1139 { "ftp://" },
1140 { "file://" },
1141 { XT_XTP_STR },
1145 valid_url_type(char *url)
1147 int i;
1149 for (i = 0; i < LENGTH(vut); i++)
1150 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1151 return (0);
1153 return (1);
1156 void
1157 print_cookie(char *msg, SoupCookie *c)
1159 if (c == NULL)
1160 return;
1162 if (msg)
1163 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1164 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1165 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1166 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1167 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1168 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1169 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1170 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1171 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1172 DNPRINTF(XT_D_COOKIE, "====================================\n");
1175 void
1176 walk_alias(struct settings *s,
1177 void (*cb)(struct settings *, char *, void *), void *cb_args)
1179 struct alias *a;
1180 char *str;
1182 if (s == NULL || cb == NULL) {
1183 show_oops_s("walk_alias invalid parameters");
1184 return;
1187 TAILQ_FOREACH(a, &aliases, entry) {
1188 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1189 cb(s, str, cb_args);
1190 g_free(str);
1194 char *
1195 match_alias(char *url_in)
1197 struct alias *a;
1198 char *arg;
1199 char *url_out = NULL, *search, *enc_arg;
1201 search = g_strdup(url_in);
1202 arg = search;
1203 if (strsep(&arg, " \t") == NULL) {
1204 show_oops_s("match_alias: NULL URL");
1205 goto done;
1208 TAILQ_FOREACH(a, &aliases, entry) {
1209 if (!strcmp(search, a->a_name))
1210 break;
1213 if (a != NULL) {
1214 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1215 a->a_name);
1216 if (arg != NULL) {
1217 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1218 url_out = g_strdup_printf(a->a_uri, enc_arg);
1219 g_free(enc_arg);
1220 } else
1221 url_out = g_strdup(a->a_uri);
1223 done:
1224 g_free(search);
1225 return (url_out);
1228 char *
1229 guess_url_type(char *url_in)
1231 struct stat sb;
1232 char *url_out = NULL, *enc_search = NULL;
1234 url_out = match_alias(url_in);
1235 if (url_out != NULL)
1236 return (url_out);
1238 if (guess_search) {
1240 * If there is no dot nor slash in the string and it isn't a
1241 * path to a local file and doesn't resolves to an IP, assume
1242 * that the user wants to search for the string.
1245 if (strchr(url_in, '.') == NULL &&
1246 strchr(url_in, '/') == NULL &&
1247 stat(url_in, &sb) != 0 &&
1248 gethostbyname(url_in) == NULL) {
1250 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1251 url_out = g_strdup_printf(search_string, enc_search);
1252 g_free(enc_search);
1253 return (url_out);
1257 /* XXX not sure about this heuristic */
1258 if (stat(url_in, &sb) == 0)
1259 url_out = g_strdup_printf("file://%s", url_in);
1260 else
1261 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1263 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1265 return (url_out);
1268 void
1269 load_uri(struct tab *t, gchar *uri)
1271 struct karg args;
1272 gchar *newuri = NULL;
1273 int i;
1275 if (uri == NULL)
1276 return;
1278 /* Strip leading spaces. */
1279 while(*uri && isspace(*uri))
1280 uri++;
1282 if (strlen(uri) == 0) {
1283 blank(t, NULL);
1284 return;
1287 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1288 for (i = 0; i < LENGTH(about_list); i++)
1289 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1290 bzero(&args, sizeof args);
1291 about_list[i].func(t, &args);
1292 return;
1294 show_oops(t, "invalid about page");
1295 return;
1298 if (valid_url_type(uri)) {
1299 newuri = guess_url_type(uri);
1300 uri = newuri;
1303 set_status(t, (char *)uri, XT_STATUS_LOADING);
1304 webkit_web_view_load_uri(t->wv, uri);
1306 if (newuri)
1307 g_free(newuri);
1310 const gchar *
1311 get_uri(WebKitWebView *wv)
1313 WebKitWebFrame *frame;
1314 const gchar *uri;
1316 frame = webkit_web_view_get_main_frame(wv);
1317 uri = webkit_web_frame_get_uri(frame);
1319 if (uri && strlen(uri) > 0)
1320 return (uri);
1321 else
1322 return (NULL);
1326 add_alias(struct settings *s, char *line)
1328 char *l, *alias;
1329 struct alias *a = NULL;
1331 if (s == NULL || line == NULL) {
1332 show_oops_s("add_alias invalid parameters");
1333 return (1);
1336 l = line;
1337 a = g_malloc(sizeof(*a));
1339 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1340 show_oops_s("add_alias: incomplete alias definition");
1341 goto bad;
1343 if (strlen(alias) == 0 || strlen(l) == 0) {
1344 show_oops_s("add_alias: invalid alias definition");
1345 goto bad;
1348 a->a_name = g_strdup(alias);
1349 a->a_uri = g_strdup(l);
1351 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1353 TAILQ_INSERT_TAIL(&aliases, a, entry);
1355 return (0);
1356 bad:
1357 if (a)
1358 g_free(a);
1359 return (1);
1363 add_mime_type(struct settings *s, char *line)
1365 char *mime_type;
1366 char *l;
1367 struct mime_type *m = NULL;
1369 /* XXX this could be smarter */
1371 if (line == NULL) {
1372 show_oops_s("add_mime_type invalid parameters");
1373 return (1);
1376 l = line;
1377 m = g_malloc(sizeof(*m));
1379 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1380 show_oops_s("add_mime_type: invalid mime_type");
1381 goto bad;
1383 if (mime_type[strlen(mime_type) - 1] == '*') {
1384 mime_type[strlen(mime_type) - 1] = '\0';
1385 m->mt_default = 1;
1386 } else
1387 m->mt_default = 0;
1389 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1390 show_oops_s("add_mime_type: invalid mime_type");
1391 goto bad;
1394 m->mt_type = g_strdup(mime_type);
1395 m->mt_action = g_strdup(l);
1397 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1398 m->mt_type, m->mt_action, m->mt_default);
1400 TAILQ_INSERT_TAIL(&mtl, m, entry);
1402 return (0);
1403 bad:
1404 if (m)
1405 g_free(m);
1406 return (1);
1409 struct mime_type *
1410 find_mime_type(char *mime_type)
1412 struct mime_type *m, *def = NULL, *rv = NULL;
1414 TAILQ_FOREACH(m, &mtl, entry) {
1415 if (m->mt_default &&
1416 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1417 def = m;
1419 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1420 rv = m;
1421 break;
1425 if (rv == NULL)
1426 rv = def;
1428 return (rv);
1431 void
1432 walk_mime_type(struct settings *s,
1433 void (*cb)(struct settings *, char *, void *), void *cb_args)
1435 struct mime_type *m;
1436 char *str;
1438 if (s == NULL || cb == NULL)
1439 show_oops_s("walk_mime_type invalid parameters");
1441 TAILQ_FOREACH(m, &mtl, entry) {
1442 str = g_strdup_printf("%s%s --> %s",
1443 m->mt_type,
1444 m->mt_default ? "*" : "",
1445 m->mt_action);
1446 cb(s, str, cb_args);
1447 g_free(str);
1451 void
1452 wl_add(char *str, struct domain_list *wl, int handy)
1454 struct domain *d;
1455 int add_dot = 0;
1457 if (str == NULL || wl == NULL)
1458 return;
1459 if (strlen(str) < 2)
1460 return;
1462 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1464 /* treat *.moo.com the same as .moo.com */
1465 if (str[0] == '*' && str[1] == '.')
1466 str = &str[1];
1467 else if (str[0] == '.')
1468 str = &str[0];
1469 else
1470 add_dot = 1;
1472 d = g_malloc(sizeof *d);
1473 if (add_dot)
1474 d->d = g_strdup_printf(".%s", str);
1475 else
1476 d->d = g_strdup(str);
1477 d->handy = handy;
1479 if (RB_INSERT(domain_list, wl, d))
1480 goto unwind;
1482 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1483 return;
1484 unwind:
1485 if (d) {
1486 if (d->d)
1487 g_free(d->d);
1488 g_free(d);
1493 add_cookie_wl(struct settings *s, char *entry)
1495 wl_add(entry, &c_wl, 1);
1496 return (0);
1499 void
1500 walk_cookie_wl(struct settings *s,
1501 void (*cb)(struct settings *, char *, void *), void *cb_args)
1503 struct domain *d;
1505 if (s == NULL || cb == NULL) {
1506 show_oops_s("walk_cookie_wl invalid parameters");
1507 return;
1510 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1511 cb(s, d->d, cb_args);
1514 void
1515 walk_js_wl(struct settings *s,
1516 void (*cb)(struct settings *, char *, void *), void *cb_args)
1518 struct domain *d;
1520 if (s == NULL || cb == NULL) {
1521 show_oops_s("walk_js_wl invalid parameters");
1522 return;
1525 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1526 cb(s, d->d, cb_args);
1530 add_js_wl(struct settings *s, char *entry)
1532 wl_add(entry, &js_wl, 1 /* persistent */);
1533 return (0);
1536 struct domain *
1537 wl_find(const gchar *search, struct domain_list *wl)
1539 int i;
1540 struct domain *d = NULL, dfind;
1541 gchar *s = NULL;
1543 if (search == NULL || wl == NULL)
1544 return (NULL);
1545 if (strlen(search) < 2)
1546 return (NULL);
1548 if (search[0] != '.')
1549 s = g_strdup_printf(".%s", search);
1550 else
1551 s = g_strdup(search);
1553 for (i = strlen(s) - 1; i >= 0; i--) {
1554 if (s[i] == '.') {
1555 dfind.d = &s[i];
1556 d = RB_FIND(domain_list, wl, &dfind);
1557 if (d)
1558 goto done;
1562 done:
1563 if (s)
1564 g_free(s);
1566 return (d);
1569 struct domain *
1570 wl_find_uri(const gchar *s, struct domain_list *wl)
1572 int i;
1573 char *ss;
1574 struct domain *r;
1576 if (s == NULL || wl == NULL)
1577 return (NULL);
1579 if (!strncmp(s, "http://", strlen("http://")))
1580 s = &s[strlen("http://")];
1581 else if (!strncmp(s, "https://", strlen("https://")))
1582 s = &s[strlen("https://")];
1584 if (strlen(s) < 2)
1585 return (NULL);
1587 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1588 /* chop string at first slash */
1589 if (s[i] == '/' || s[i] == '\0') {
1590 ss = g_strdup(s);
1591 ss[i] = '\0';
1592 r = wl_find(ss, wl);
1593 g_free(ss);
1594 return (r);
1597 return (NULL);
1600 char *
1601 get_toplevel_domain(char *domain)
1603 char *s;
1604 int found = 0;
1606 if (domain == NULL)
1607 return (NULL);
1608 if (strlen(domain) < 2)
1609 return (NULL);
1611 s = &domain[strlen(domain) - 1];
1612 while (s != domain) {
1613 if (*s == '.') {
1614 found++;
1615 if (found == 2)
1616 return (s);
1618 s--;
1621 if (found)
1622 return (domain);
1624 return (NULL);
1628 settings_add(char *var, char *val)
1630 int i, rv, *p;
1631 gfloat *f;
1632 char **s;
1634 /* get settings */
1635 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1636 if (strcmp(var, rs[i].name))
1637 continue;
1639 if (rs[i].s) {
1640 if (rs[i].s->set(&rs[i], val))
1641 errx(1, "invalid value for %s", var);
1642 rv = 1;
1643 break;
1644 } else
1645 switch (rs[i].type) {
1646 case XT_S_INT:
1647 p = rs[i].ival;
1648 *p = atoi(val);
1649 rv = 1;
1650 break;
1651 case XT_S_STR:
1652 s = rs[i].sval;
1653 if (s == NULL)
1654 errx(1, "invalid sval for %s",
1655 rs[i].name);
1656 if (*s)
1657 g_free(*s);
1658 *s = g_strdup(val);
1659 rv = 1;
1660 break;
1661 case XT_S_FLOAT:
1662 f = rs[i].fval;
1663 *f = atof(val);
1664 rv = 1;
1665 break;
1666 case XT_S_INVALID:
1667 default:
1668 errx(1, "invalid type for %s", var);
1670 break;
1672 return (rv);
1675 #define WS "\n= \t"
1676 void
1677 config_parse(char *filename, int runtime)
1679 FILE *config, *f;
1680 char *line, *cp, *var, *val;
1681 size_t len, lineno = 0;
1682 int handled;
1683 char file[PATH_MAX];
1684 struct stat sb;
1686 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1688 if (filename == NULL)
1689 return;
1691 if (runtime && runtime_settings[0] != '\0') {
1692 snprintf(file, sizeof file, "%s/%s",
1693 work_dir, runtime_settings);
1694 if (stat(file, &sb)) {
1695 warnx("runtime file doesn't exist, creating it");
1696 if ((f = fopen(file, "w")) == NULL)
1697 err(1, "runtime");
1698 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1699 fclose(f);
1701 } else
1702 strlcpy(file, filename, sizeof file);
1704 if ((config = fopen(file, "r")) == NULL) {
1705 warn("config_parse: cannot open %s", filename);
1706 return;
1709 for (;;) {
1710 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1711 if (feof(config) || ferror(config))
1712 break;
1714 cp = line;
1715 cp += (long)strspn(cp, WS);
1716 if (cp[0] == '\0') {
1717 /* empty line */
1718 free(line);
1719 continue;
1722 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1723 errx(1, "invalid config file entry: %s", line);
1725 cp += (long)strspn(cp, WS);
1727 if ((val = strsep(&cp, "\0")) == NULL)
1728 break;
1730 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1731 handled = settings_add(var, val);
1732 if (handled == 0)
1733 errx(1, "invalid conf file entry: %s=%s", var, val);
1735 free(line);
1738 fclose(config);
1741 char *
1742 js_ref_to_string(JSContextRef context, JSValueRef ref)
1744 char *s = NULL;
1745 size_t l;
1746 JSStringRef jsref;
1748 jsref = JSValueToStringCopy(context, ref, NULL);
1749 if (jsref == NULL)
1750 return (NULL);
1752 l = JSStringGetMaximumUTF8CStringSize(jsref);
1753 s = g_malloc(l);
1754 if (s)
1755 JSStringGetUTF8CString(jsref, s, l);
1756 JSStringRelease(jsref);
1758 return (s);
1761 void
1762 disable_hints(struct tab *t)
1764 bzero(t->hint_buf, sizeof t->hint_buf);
1765 bzero(t->hint_num, sizeof t->hint_num);
1766 run_script(t, "vimprobable_clear()");
1767 t->hints_on = 0;
1768 t->hint_mode = XT_HINT_NONE;
1771 void
1772 enable_hints(struct tab *t)
1774 bzero(t->hint_buf, sizeof t->hint_buf);
1775 run_script(t, "vimprobable_show_hints()");
1776 t->hints_on = 1;
1777 t->hint_mode = XT_HINT_NONE;
1780 #define XT_JS_OPEN ("open;")
1781 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1782 #define XT_JS_FIRE ("fire;")
1783 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1784 #define XT_JS_FOUND ("found;")
1785 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1788 run_script(struct tab *t, char *s)
1790 JSGlobalContextRef ctx;
1791 WebKitWebFrame *frame;
1792 JSStringRef str;
1793 JSValueRef val, exception;
1794 char *es, buf[128];
1796 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1797 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1799 frame = webkit_web_view_get_main_frame(t->wv);
1800 ctx = webkit_web_frame_get_global_context(frame);
1802 str = JSStringCreateWithUTF8CString(s);
1803 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1804 NULL, 0, &exception);
1805 JSStringRelease(str);
1807 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1808 if (val == NULL) {
1809 es = js_ref_to_string(ctx, exception);
1810 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1811 g_free(es);
1812 return (1);
1813 } else {
1814 es = js_ref_to_string(ctx, val);
1815 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1817 /* handle return value right here */
1818 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1819 disable_hints(t);
1820 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1823 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1824 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1825 &es[XT_JS_FIRE_LEN]);
1826 run_script(t, buf);
1827 disable_hints(t);
1830 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1831 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1832 disable_hints(t);
1835 g_free(es);
1838 return (0);
1842 hint(struct tab *t, struct karg *args)
1845 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1847 if (t->hints_on == 0)
1848 enable_hints(t);
1849 else
1850 disable_hints(t);
1852 return (0);
1856 * Doesn't work fully, due to the following bug:
1857 * https://bugs.webkit.org/show_bug.cgi?id=51747
1860 restore_global_history(void)
1862 char file[PATH_MAX];
1863 FILE *f;
1864 struct history *h;
1865 gchar *uri;
1866 gchar *title;
1868 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1870 if ((f = fopen(file, "r")) == NULL) {
1871 warnx("%s: fopen", __func__);
1872 return (1);
1875 for (;;) {
1876 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1877 if (feof(f) || ferror(f))
1878 break;
1880 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1881 if (feof(f) || ferror(f)) {
1882 free(uri);
1883 warnx("%s: broken history file\n", __func__);
1884 return (1);
1887 if (uri && strlen(uri) && title && strlen(title)) {
1888 webkit_web_history_item_new_with_data(uri, title);
1889 h = g_malloc(sizeof(struct history));
1890 h->uri = g_strdup(uri);
1891 h->title = g_strdup(title);
1892 RB_INSERT(history_list, &hl, h);
1893 completion_add_uri(h->uri);
1894 } else {
1895 warnx("%s: failed to restore history\n", __func__);
1896 free(uri);
1897 free(title);
1898 return (1);
1901 free(uri);
1902 free(title);
1903 uri = NULL;
1904 title = NULL;
1907 return (0);
1911 save_global_history_to_disk(struct tab *t)
1913 char file[PATH_MAX];
1914 FILE *f;
1915 struct history *h;
1917 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1919 if ((f = fopen(file, "w")) == NULL) {
1920 show_oops(t, "%s: global history file: %s",
1921 __func__, strerror(errno));
1922 return (1);
1925 RB_FOREACH_REVERSE(h, history_list, &hl) {
1926 if (h->uri && h->title)
1927 fprintf(f, "%s\n%s\n", h->uri, h->title);
1930 fclose(f);
1932 return (0);
1936 quit(struct tab *t, struct karg *args)
1938 if (save_global_history)
1939 save_global_history_to_disk(t);
1941 gtk_main_quit();
1943 return (1);
1947 open_tabs(struct tab *t, struct karg *a)
1949 char file[PATH_MAX];
1950 FILE *f = NULL;
1951 char *uri = NULL;
1952 int rv = 1;
1953 struct tab *ti, *tt;
1955 if (a == NULL)
1956 goto done;
1958 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1959 if ((f = fopen(file, "r")) == NULL)
1960 goto done;
1962 ti = TAILQ_LAST(&tabs, tab_list);
1964 for (;;) {
1965 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1966 if (feof(f) || ferror(f))
1967 break;
1969 /* retrieve session name */
1970 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
1971 strlcpy(named_session,
1972 &uri[strlen(XT_SAVE_SESSION_ID)],
1973 sizeof named_session);
1974 continue;
1977 if (uri && strlen(uri))
1978 create_new_tab(uri, NULL, 1);
1980 free(uri);
1981 uri = NULL;
1984 /* close open tabs */
1985 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
1986 for (;;) {
1987 tt = TAILQ_FIRST(&tabs);
1988 if (tt != ti) {
1989 delete_tab(tt);
1990 continue;
1992 delete_tab(tt);
1993 break;
1997 rv = 0;
1998 done:
1999 if (f)
2000 fclose(f);
2002 return (rv);
2006 restore_saved_tabs(void)
2008 char file[PATH_MAX];
2009 int unlink_file = 0;
2010 struct stat sb;
2011 struct karg a;
2012 int rv = 0;
2014 snprintf(file, sizeof file, "%s/%s",
2015 sessions_dir, XT_RESTART_TABS_FILE);
2016 if (stat(file, &sb) == -1)
2017 a.s = XT_SAVED_TABS_FILE;
2018 else {
2019 unlink_file = 1;
2020 a.s = XT_RESTART_TABS_FILE;
2023 a.i = XT_SES_DONOTHING;
2024 rv = open_tabs(NULL, &a);
2026 if (unlink_file)
2027 unlink(file);
2029 return (rv);
2033 save_tabs(struct tab *t, struct karg *a)
2035 char file[PATH_MAX];
2036 FILE *f;
2037 struct tab *ti;
2038 const gchar *uri;
2039 int len = 0, i;
2040 const gchar **arr = NULL;
2042 if (a == NULL)
2043 return (1);
2044 if (a->s == NULL)
2045 snprintf(file, sizeof file, "%s/%s",
2046 sessions_dir, named_session);
2047 else
2048 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2050 if ((f = fopen(file, "w")) == NULL) {
2051 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2052 return (1);
2055 /* save session name */
2056 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2058 /* save tabs, in the order they are arranged in the notebook */
2059 TAILQ_FOREACH(ti, &tabs, entry)
2060 len++;
2062 arr = g_malloc0(len * sizeof(gchar *));
2064 TAILQ_FOREACH(ti, &tabs, entry) {
2065 if ((uri = get_uri(ti->wv)) != NULL)
2066 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2069 for (i = 0; i < len; i++)
2070 if (arr[i])
2071 fprintf(f, "%s\n", arr[i]);
2073 g_free(arr);
2074 fclose(f);
2076 return (0);
2080 save_tabs_and_quit(struct tab *t, struct karg *args)
2082 struct karg a;
2084 a.s = NULL;
2085 save_tabs(t, &a);
2086 quit(t, NULL);
2088 return (1);
2092 yank_uri(struct tab *t, struct karg *args)
2094 const gchar *uri;
2095 GtkClipboard *clipboard;
2097 if ((uri = get_uri(t->wv)) == NULL)
2098 return (1);
2100 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2101 gtk_clipboard_set_text(clipboard, uri, -1);
2103 return (0);
2106 struct paste_args {
2107 struct tab *t;
2108 int i;
2111 void
2112 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2114 struct paste_args *pap;
2116 if (data == NULL || text == NULL || !strlen(text))
2117 return;
2119 pap = (struct paste_args *)data;
2121 switch(pap->i) {
2122 case XT_PASTE_CURRENT_TAB:
2123 load_uri(pap->t, (gchar *)text);
2124 break;
2125 case XT_PASTE_NEW_TAB:
2126 create_new_tab((gchar *)text, NULL, 1);
2127 break;
2130 g_free(pap);
2134 paste_uri(struct tab *t, struct karg *args)
2136 GtkClipboard *clipboard;
2137 struct paste_args *pap;
2139 pap = g_malloc(sizeof(struct paste_args));
2141 pap->t = t;
2142 pap->i = args->i;
2144 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2145 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2147 return (0);
2150 char *
2151 find_domain(const gchar *s, int add_dot)
2153 int i;
2154 char *r = NULL, *ss = NULL;
2156 if (s == NULL)
2157 return (NULL);
2159 if (!strncmp(s, "http://", strlen("http://")))
2160 s = &s[strlen("http://")];
2161 else if (!strncmp(s, "https://", strlen("https://")))
2162 s = &s[strlen("https://")];
2164 if (strlen(s) < 2)
2165 return (NULL);
2167 ss = g_strdup(s);
2168 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2169 /* chop string at first slash */
2170 if (ss[i] == '/' || ss[i] == '\0') {
2171 ss[i] = '\0';
2172 if (add_dot)
2173 r = g_strdup_printf(".%s", ss);
2174 else
2175 r = g_strdup(ss);
2176 break;
2178 g_free(ss);
2180 return (r);
2184 toggle_cwl(struct tab *t, struct karg *args)
2186 struct domain *d;
2187 const gchar *uri;
2188 char *dom = NULL, *dom_toggle = NULL;
2189 int es;
2191 if (args == NULL)
2192 return (1);
2194 uri = get_uri(t->wv);
2195 dom = find_domain(uri, 1);
2196 d = wl_find(dom, &c_wl);
2198 if (d == NULL)
2199 es = 0;
2200 else
2201 es = 1;
2203 if (args->i & XT_WL_TOGGLE)
2204 es = !es;
2205 else if ((args->i & XT_WL_ENABLE) && es != 1)
2206 es = 1;
2207 else if ((args->i & XT_WL_DISABLE) && es != 0)
2208 es = 0;
2210 if (args->i & XT_WL_TOPLEVEL)
2211 dom_toggle = get_toplevel_domain(dom);
2212 else
2213 dom_toggle = dom;
2215 if (es)
2216 /* enable cookies for domain */
2217 wl_add(dom_toggle, &c_wl, 0);
2218 else
2219 /* disable cookies for domain */
2220 RB_REMOVE(domain_list, &c_wl, d);
2222 webkit_web_view_reload(t->wv);
2224 g_free(dom);
2225 return (0);
2229 toggle_js(struct tab *t, struct karg *args)
2231 int es;
2232 const gchar *uri;
2233 struct domain *d;
2234 char *dom = NULL, *dom_toggle = NULL;
2236 if (args == NULL)
2237 return (1);
2239 g_object_get(G_OBJECT(t->settings),
2240 "enable-scripts", &es, (char *)NULL);
2241 if (args->i & XT_WL_TOGGLE)
2242 es = !es;
2243 else if ((args->i & XT_WL_ENABLE) && es != 1)
2244 es = 1;
2245 else if ((args->i & XT_WL_DISABLE) && es != 0)
2246 es = 0;
2247 else
2248 return (1);
2250 uri = get_uri(t->wv);
2251 dom = find_domain(uri, 1);
2253 if (uri == NULL || dom == NULL) {
2254 show_oops(t, "Can't toggle domain in JavaScript white list");
2255 goto done;
2258 if (args->i & XT_WL_TOPLEVEL)
2259 dom_toggle = get_toplevel_domain(dom);
2260 else
2261 dom_toggle = dom;
2263 if (es) {
2264 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2265 wl_add(dom_toggle, &js_wl, 0 /* session */);
2266 } else {
2267 d = wl_find(dom_toggle, &js_wl);
2268 if (d)
2269 RB_REMOVE(domain_list, &js_wl, d);
2270 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2272 g_object_set(G_OBJECT(t->settings),
2273 "enable-scripts", es, (char *)NULL);
2274 webkit_web_view_set_settings(t->wv, t->settings);
2275 webkit_web_view_reload(t->wv);
2276 done:
2277 if (dom)
2278 g_free(dom);
2279 return (0);
2282 void
2283 js_toggle_cb(GtkWidget *w, struct tab *t)
2285 struct karg a;
2287 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2288 toggle_js(t, &a);
2292 toggle_src(struct tab *t, struct karg *args)
2294 gboolean mode;
2296 if (t == NULL)
2297 return (0);
2299 mode = webkit_web_view_get_view_source_mode(t->wv);
2300 webkit_web_view_set_view_source_mode(t->wv, !mode);
2301 webkit_web_view_reload(t->wv);
2303 return (0);
2306 void
2307 focus_webview(struct tab *t)
2309 if (t == NULL)
2310 return;
2312 /* only grab focus if we are visible */
2313 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2314 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2318 focus(struct tab *t, struct karg *args)
2320 if (t == NULL || args == NULL)
2321 return (1);
2323 if (show_url == 0)
2324 return (0);
2326 if (args->i == XT_FOCUS_URI)
2327 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2328 else if (args->i == XT_FOCUS_SEARCH)
2329 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2331 return (0);
2335 stats(struct tab *t, struct karg *args)
2337 char *stats, *s, line[64 * 1024];
2338 uint64_t line_count = 0;
2339 FILE *r_cookie_f;
2341 if (t == NULL)
2342 show_oops_s("stats invalid parameters");
2344 line[0] = '\0';
2345 if (save_rejected_cookies) {
2346 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2347 for (;;) {
2348 s = fgets(line, sizeof line, r_cookie_f);
2349 if (s == NULL || feof(r_cookie_f) ||
2350 ferror(r_cookie_f))
2351 break;
2352 line_count++;
2354 fclose(r_cookie_f);
2355 snprintf(line, sizeof line,
2356 "<br>Cookies blocked(*) total: %llu", line_count);
2357 } else
2358 show_oops(t, "Can't open blocked cookies file: %s",
2359 strerror(errno));
2362 stats = g_strdup_printf(XT_DOCTYPE
2363 "<html>"
2364 "<head>"
2365 "<title>Statistics</title>"
2366 "</head>"
2367 "<h1>Statistics</h1>"
2368 "<body>"
2369 "Cookies blocked(*) this session: %llu"
2370 "%s"
2371 "<p><small><b>*</b> results vary based on settings"
2372 "</body>"
2373 "</html>",
2374 blocked_cookies,
2375 line);
2377 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2378 g_free(stats);
2380 return (0);
2384 blank(struct tab *t, struct karg *args)
2386 if (t == NULL)
2387 show_oops_s("about invalid parameters");
2389 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2391 return (0);
2394 about(struct tab *t, struct karg *args)
2396 char *about;
2398 if (t == NULL)
2399 show_oops_s("about invalid parameters");
2401 about = g_strdup_printf(XT_DOCTYPE
2402 "<html>"
2403 "<head>"
2404 "<title>About</title>"
2405 "</head>"
2406 "<h1>About</h1>"
2407 "<body>"
2408 "<b>Version: %s</b><p>"
2409 "Authors:"
2410 "<ul>"
2411 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2412 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2413 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2414 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2415 "</ul>"
2416 "Copyrights and licenses can be found on the XXXterm "
2417 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2418 "</body>"
2419 "</html>",
2420 version
2423 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2424 g_free(about);
2426 return (0);
2430 help(struct tab *t, struct karg *args)
2432 char *help;
2434 if (t == NULL)
2435 show_oops_s("help invalid parameters");
2437 help = XT_DOCTYPE
2438 "<html>"
2439 "<head>"
2440 "<title>XXXterm</title>"
2441 "<meta http-equiv=\"REFRESH\" content=\"0;"
2442 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2443 "</head>"
2444 "<body>"
2445 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2446 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2447 "cgi-bin/man-cgi?xxxterm</a>"
2448 "</body>"
2449 "</html>"
2452 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2454 return (0);
2458 * update all favorite tabs apart from one. Pass NULL if
2459 * you want to update all.
2461 void
2462 update_favorite_tabs(struct tab *apart_from)
2464 struct tab *t;
2465 if (!updating_fl_tabs) {
2466 updating_fl_tabs = 1; /* stop infinite recursion */
2467 TAILQ_FOREACH(t, &tabs, entry)
2468 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2469 && (t != apart_from))
2470 xtp_page_fl(t, NULL);
2471 updating_fl_tabs = 0;
2475 /* show a list of favorites (bookmarks) */
2477 xtp_page_fl(struct tab *t, struct karg *args)
2479 char file[PATH_MAX];
2480 FILE *f;
2481 char *uri = NULL, *title = NULL;
2482 size_t len, lineno = 0;
2483 int i, failed = 0;
2484 char *header, *body, *tmp, *html = NULL;
2486 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2488 if (t == NULL)
2489 warn("%s: bad param", __func__);
2491 /* mark tab as favorite list */
2492 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2494 /* new session key */
2495 if (!updating_fl_tabs)
2496 generate_xtp_session_key(&fl_session_key);
2498 /* open favorites */
2499 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2500 if ((f = fopen(file, "r")) == NULL) {
2501 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2502 return (1);
2505 /* header */
2506 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2507 "<title>Favorites</title>\n"
2508 "%s"
2509 "</head>"
2510 "<h1>Favorites</h1>\n",
2511 XT_PAGE_STYLE);
2513 /* body */
2514 body = g_strdup_printf("<div align='center'><table><tr>"
2515 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2516 "<th style='width: 15%%'>Remove</th></tr>\n");
2518 for (i = 1;;) {
2519 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2520 if (feof(f) || ferror(f))
2521 break;
2522 if (len == 0) {
2523 free(title);
2524 title = NULL;
2525 continue;
2528 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2529 if (feof(f) || ferror(f)) {
2530 show_oops(t, "favorites file corrupt");
2531 failed = 1;
2532 break;
2535 tmp = body;
2536 body = g_strdup_printf("%s<tr>"
2537 "<td>%d</td>"
2538 "<td><a href='%s'>%s</a></td>"
2539 "<td style='text-align: center'>"
2540 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2541 "</tr>\n",
2542 body, i, uri, title,
2543 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2545 g_free(tmp);
2547 free(uri);
2548 uri = NULL;
2549 free(title);
2550 title = NULL;
2551 i++;
2553 fclose(f);
2555 /* if none, say so */
2556 if (i == 1) {
2557 tmp = body;
2558 body = g_strdup_printf("%s<tr>"
2559 "<td colspan='3' style='text-align: center'>"
2560 "No favorites - To add one use the 'favadd' command."
2561 "</td></tr>", body);
2562 g_free(tmp);
2565 if (uri)
2566 free(uri);
2567 if (title)
2568 free(title);
2570 /* render */
2571 if (!failed) {
2572 html = g_strdup_printf("%s%s</table></div></html>",
2573 header, body);
2574 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2577 update_favorite_tabs(t);
2579 if (header)
2580 g_free(header);
2581 if (body)
2582 g_free(body);
2583 if (html)
2584 g_free(html);
2586 return (failed);
2589 char *
2590 getparams(char *cmd, char *cmp)
2592 char *rv = NULL;
2594 if (cmd && cmp) {
2595 if (!strncmp(cmd, cmp, strlen(cmp))) {
2596 rv = cmd + strlen(cmp);
2597 while (*rv == ' ')
2598 rv++;
2599 if (strlen(rv) == 0)
2600 rv = NULL;
2604 return (rv);
2607 void
2608 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2609 size_t cert_count, char *title)
2611 gnutls_datum_t cinfo;
2612 char *tmp, *header, *body, *footer;
2613 int i;
2615 header = g_strdup_printf("<title>%s</title><html><body>", title);
2616 footer = g_strdup("</body></html>");
2617 body = g_strdup("");
2619 for (i = 0; i < cert_count; i++) {
2620 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2621 &cinfo))
2622 return;
2624 tmp = body;
2625 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2626 body, i, cinfo.data);
2627 gnutls_free(cinfo.data);
2628 g_free(tmp);
2631 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2632 g_free(header);
2633 g_free(body);
2634 g_free(footer);
2635 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2636 g_free(tmp);
2640 ca_cmd(struct tab *t, struct karg *args)
2642 FILE *f = NULL;
2643 int rv = 1, certs = 0, certs_read;
2644 struct stat sb;
2645 gnutls_datum dt;
2646 gnutls_x509_crt_t *c = NULL;
2647 char *certs_buf = NULL, *s;
2649 /* yeah yeah stat race */
2650 if (stat(ssl_ca_file, &sb)) {
2651 show_oops(t, "no CA file: %s", ssl_ca_file);
2652 goto done;
2655 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2656 show_oops(t, "Can't open CA file: %s", strerror(errno));
2657 return (1);
2660 certs_buf = g_malloc(sb.st_size + 1);
2661 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2662 show_oops(t, "Can't read CA file: %s", strerror(errno));
2663 goto done;
2665 certs_buf[sb.st_size] = '\0';
2667 s = certs_buf;
2668 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2669 certs++;
2670 s += strlen("BEGIN CERTIFICATE");
2673 bzero(&dt, sizeof dt);
2674 dt.data = certs_buf;
2675 dt.size = sb.st_size;
2676 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2677 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2678 GNUTLS_X509_FMT_PEM, 0);
2679 if (certs_read <= 0) {
2680 show_oops(t, "No cert(s) available");
2681 goto done;
2683 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2684 done:
2685 if (c)
2686 g_free(c);
2687 if (certs_buf)
2688 g_free(certs_buf);
2689 if (f)
2690 fclose(f);
2692 return (rv);
2696 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2698 SoupURI *su = NULL;
2699 struct addrinfo hints, *res = NULL, *ai;
2700 int s = -1, on;
2701 char port[8];
2703 if (uri && !g_str_has_prefix(uri, "https://"))
2704 goto done;
2706 su = soup_uri_new(uri);
2707 if (su == NULL)
2708 goto done;
2709 if (!SOUP_URI_VALID_FOR_HTTP(su))
2710 goto done;
2712 snprintf(port, sizeof port, "%d", su->port);
2713 bzero(&hints, sizeof(struct addrinfo));
2714 hints.ai_flags = AI_CANONNAME;
2715 hints.ai_family = AF_UNSPEC;
2716 hints.ai_socktype = SOCK_STREAM;
2718 if (getaddrinfo(su->host, port, &hints, &res))
2719 goto done;
2721 for (ai = res; ai; ai = ai->ai_next) {
2722 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2723 continue;
2725 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2726 if (s < 0)
2727 goto done;
2728 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2729 sizeof(on)) == -1)
2730 goto done;
2732 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2733 goto done;
2736 if (domain)
2737 strlcpy(domain, su->host, domain_sz);
2738 done:
2739 if (su)
2740 soup_uri_free(su);
2741 if (res)
2742 freeaddrinfo(res);
2744 return (s);
2748 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2750 if (gsession)
2751 gnutls_deinit(gsession);
2752 if (xcred)
2753 gnutls_certificate_free_credentials(xcred);
2755 return (0);
2759 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2760 gnutls_certificate_credentials_t *xc)
2762 gnutls_certificate_credentials_t xcred;
2763 gnutls_session_t gsession;
2764 int rv = 1;
2766 if (gs == NULL || xc == NULL)
2767 goto done;
2769 *gs = NULL;
2770 *xc = NULL;
2772 gnutls_certificate_allocate_credentials(&xcred);
2773 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2774 GNUTLS_X509_FMT_PEM);
2775 gnutls_init(&gsession, GNUTLS_CLIENT);
2776 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2777 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2778 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2779 if ((rv = gnutls_handshake(gsession)) < 0) {
2780 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2782 gnutls_error_is_fatal(rv),
2783 gnutls_strerror_name(rv));
2784 stop_tls(gsession, xcred);
2785 goto done;
2788 gnutls_credentials_type_t cred;
2789 cred = gnutls_auth_get_type(gsession);
2790 if (cred != GNUTLS_CRD_CERTIFICATE) {
2791 stop_tls(gsession, xcred);
2792 goto done;
2795 *gs = gsession;
2796 *xc = xcred;
2797 rv = 0;
2798 done:
2799 return (rv);
2803 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2804 size_t *cert_count)
2806 unsigned int len;
2807 const gnutls_datum_t *cl;
2808 gnutls_x509_crt_t *all_certs;
2809 int i, rv = 1;
2811 if (certs == NULL || cert_count == NULL)
2812 goto done;
2813 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2814 goto done;
2815 cl = gnutls_certificate_get_peers(gsession, &len);
2816 if (len == 0)
2817 goto done;
2819 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2820 for (i = 0; i < len; i++) {
2821 gnutls_x509_crt_init(&all_certs[i]);
2822 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2823 GNUTLS_X509_FMT_PEM < 0)) {
2824 g_free(all_certs);
2825 goto done;
2829 *certs = all_certs;
2830 *cert_count = len;
2831 rv = 0;
2832 done:
2833 return (rv);
2836 void
2837 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2839 int i;
2841 for (i = 0; i < cert_count; i++)
2842 gnutls_x509_crt_deinit(certs[i]);
2843 g_free(certs);
2846 void
2847 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2848 size_t cert_count, char *domain)
2850 size_t cert_buf_sz;
2851 char cert_buf[64 * 1024], file[PATH_MAX];
2852 int i;
2853 FILE *f;
2854 GdkColor color;
2856 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2857 return;
2859 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2860 if ((f = fopen(file, "w")) == NULL) {
2861 show_oops(t, "Can't create cert file %s %s",
2862 file, strerror(errno));
2863 return;
2866 for (i = 0; i < cert_count; i++) {
2867 cert_buf_sz = sizeof cert_buf;
2868 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2869 cert_buf, &cert_buf_sz)) {
2870 show_oops(t, "gnutls_x509_crt_export failed");
2871 goto done;
2873 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2874 show_oops(t, "Can't write certs: %s", strerror(errno));
2875 goto done;
2879 /* not the best spot but oh well */
2880 gdk_color_parse("lightblue", &color);
2881 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2882 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2883 gdk_color_parse("black", &color);
2884 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2885 done:
2886 fclose(f);
2890 load_compare_cert(struct tab *t, struct karg *args)
2892 const gchar *uri;
2893 char domain[8182], file[PATH_MAX];
2894 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2895 int s = -1, rv = 1, i;
2896 size_t cert_count;
2897 FILE *f = NULL;
2898 size_t cert_buf_sz;
2899 gnutls_session_t gsession;
2900 gnutls_x509_crt_t *certs;
2901 gnutls_certificate_credentials_t xcred;
2903 if (t == NULL)
2904 return (1);
2906 if ((uri = get_uri(t->wv)) == NULL)
2907 return (1);
2909 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2910 return (1);
2912 /* go ssl/tls */
2913 if (start_tls(t, s, &gsession, &xcred)) {
2914 show_oops(t, "Start TLS failed");
2915 goto done;
2918 /* get certs */
2919 if (get_connection_certs(gsession, &certs, &cert_count)) {
2920 show_oops(t, "Can't get connection certificates");
2921 goto done;
2924 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2925 if ((f = fopen(file, "r")) == NULL)
2926 goto freeit;
2928 for (i = 0; i < cert_count; i++) {
2929 cert_buf_sz = sizeof cert_buf;
2930 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2931 cert_buf, &cert_buf_sz)) {
2932 goto freeit;
2934 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2935 rv = -1; /* critical */
2936 goto freeit;
2938 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2939 rv = -1; /* critical */
2940 goto freeit;
2944 rv = 0;
2945 freeit:
2946 if (f)
2947 fclose(f);
2948 free_connection_certs(certs, cert_count);
2949 done:
2950 /* we close the socket first for speed */
2951 if (s != -1)
2952 close(s);
2953 stop_tls(gsession, xcred);
2955 return (rv);
2959 cert_cmd(struct tab *t, struct karg *args)
2961 const gchar *uri;
2962 char *action, domain[8182];
2963 int s = -1;
2964 size_t cert_count;
2965 gnutls_session_t gsession;
2966 gnutls_x509_crt_t *certs;
2967 gnutls_certificate_credentials_t xcred;
2969 if (t == NULL)
2970 return (1);
2972 if ((action = getparams(args->s, "cert")))
2974 else
2975 action = "show";
2977 if ((uri = get_uri(t->wv)) == NULL) {
2978 show_oops(t, "Invalid URI");
2979 return (1);
2982 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2983 show_oops(t, "Invalid certidicate URI: %s", uri);
2984 return (1);
2987 /* go ssl/tls */
2988 if (start_tls(t, s, &gsession, &xcred)) {
2989 show_oops(t, "Start TLS failed");
2990 goto done;
2993 /* get certs */
2994 if (get_connection_certs(gsession, &certs, &cert_count)) {
2995 show_oops(t, "get_connection_certs failed");
2996 goto done;
2999 if (!strcmp(action, "show"))
3000 show_certs(t, certs, cert_count, "Certificate Chain");
3001 else if (!strcmp(action, "save"))
3002 save_certs(t, certs, cert_count, domain);
3003 else
3004 show_oops(t, "Invalid command: %s", action);
3006 free_connection_certs(certs, cert_count);
3007 done:
3008 /* we close the socket first for speed */
3009 if (s != -1)
3010 close(s);
3011 stop_tls(gsession, xcred);
3013 return (0);
3017 remove_cookie(int index)
3019 int i, rv = 1;
3020 GSList *cf;
3021 SoupCookie *c;
3023 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3025 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3027 for (i = 1; cf; cf = cf->next, i++) {
3028 if (i != index)
3029 continue;
3030 c = cf->data;
3031 print_cookie("remove cookie", c);
3032 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3033 rv = 0;
3034 break;
3037 soup_cookies_free(cf);
3039 return (rv);
3043 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
3045 struct domain *d;
3046 char *tmp, *header, *body, *footer;
3047 int p_js = 0, s_js = 0;
3049 /* we set this to indicate we want to manually do navaction */
3050 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3052 if (g_str_has_prefix(args, "show a") ||
3053 !strcmp(args, "show")) {
3054 /* show all */
3055 p_js = 1;
3056 s_js = 1;
3057 } else if (g_str_has_prefix(args, "show p")) {
3058 /* show persistent */
3059 p_js = 1;
3060 } else if (g_str_has_prefix(args, "show s")) {
3061 /* show session */
3062 s_js = 1;
3063 } else
3064 return (1);
3066 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3067 title, title);
3068 footer = g_strdup("</body></html>");
3069 body = g_strdup("");
3071 /* p list */
3072 if (p_js) {
3073 tmp = body;
3074 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3075 g_free(tmp);
3076 RB_FOREACH(d, domain_list, wl) {
3077 if (d->handy == 0)
3078 continue;
3079 tmp = body;
3080 body = g_strdup_printf("%s%s<br>", body, d->d);
3081 g_free(tmp);
3085 /* s list */
3086 if (s_js) {
3087 tmp = body;
3088 body = g_strdup_printf("%s<h2>Session</h2>", body);
3089 g_free(tmp);
3090 RB_FOREACH(d, domain_list, wl) {
3091 if (d->handy == 1)
3092 continue;
3093 tmp = body;
3094 body = g_strdup_printf("%s%s<br>", body, d->d);
3095 g_free(tmp);
3099 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3100 g_free(header);
3101 g_free(body);
3102 g_free(footer);
3103 if (wl == &js_wl)
3104 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3105 else
3106 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3107 g_free(tmp);
3108 return (0);
3112 wl_save(struct tab *t, struct karg *args, int js)
3114 char file[PATH_MAX];
3115 FILE *f;
3116 char *line = NULL, *lt = NULL;
3117 size_t linelen;
3118 const gchar *uri;
3119 char *dom = NULL, *dom_save = NULL;
3120 struct karg a;
3121 struct domain *d;
3122 GSList *cf;
3123 SoupCookie *ci, *c;
3124 int flags;
3126 if (t == NULL || args == NULL)
3127 return (1);
3129 if (runtime_settings[0] == '\0')
3130 return (1);
3132 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3133 if ((f = fopen(file, "r+")) == NULL)
3134 return (1);
3136 uri = get_uri(t->wv);
3137 dom = find_domain(uri, 1);
3138 if (uri == NULL || dom == NULL) {
3139 show_oops(t, "Can't add domain to %s white list",
3140 js ? "JavaScript" : "cookie");
3141 goto done;
3144 if (g_str_has_prefix(args->s, "save d")) {
3145 /* save domain */
3146 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3147 show_oops(t, "invalid domain: %s", dom);
3148 goto done;
3150 flags = XT_WL_TOPLEVEL;
3151 } else if (g_str_has_prefix(args->s, "save f") ||
3152 !strcmp(args->s, "save")) {
3153 /* save fqdn */
3154 dom_save = dom;
3155 flags = XT_WL_FQDN;
3156 } else {
3157 show_oops(t, "invalid command: %s", args->s);
3158 goto done;
3161 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3163 while (!feof(f)) {
3164 line = fparseln(f, &linelen, NULL, NULL, 0);
3165 if (line == NULL)
3166 continue;
3167 if (!strcmp(line, lt))
3168 goto done;
3169 free(line);
3170 line = NULL;
3173 fprintf(f, "%s\n", lt);
3175 a.i = XT_WL_ENABLE;
3176 a.i |= flags;
3177 if (js) {
3178 d = wl_find(dom_save, &js_wl);
3179 if (!d) {
3180 settings_add("js_wl", dom_save);
3181 d = wl_find(dom_save, &js_wl);
3183 toggle_js(t, &a);
3184 } else {
3185 d = wl_find(dom_save, &c_wl);
3186 if (!d) {
3187 settings_add("cookie_wl", dom_save);
3188 d = wl_find(dom_save, &c_wl);
3190 toggle_cwl(t, &a);
3192 /* find and add to persistent jar */
3193 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3194 for (;cf; cf = cf->next) {
3195 ci = cf->data;
3196 if (!strcmp(dom_save, ci->domain) ||
3197 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3198 c = soup_cookie_copy(ci);
3199 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3202 soup_cookies_free(cf);
3204 if (d)
3205 d->handy = 1;
3207 done:
3208 if (line)
3209 free(line);
3210 if (dom)
3211 g_free(dom);
3212 if (lt)
3213 g_free(lt);
3214 fclose(f);
3216 return (0);
3220 js_show_wl(struct tab *t, struct karg *args)
3222 wl_show(t, "show all", "JavaScript White List", &js_wl);
3224 return (0);
3228 cookie_show_wl(struct tab *t, struct karg *args)
3230 wl_show(t, "show all", "Cookie White List", &c_wl);
3232 return (0);
3236 cookie_cmd(struct tab *t, struct karg *args)
3238 char *cmd;
3239 struct karg a;
3241 if ((cmd = getparams(args->s, "cookie")))
3243 else
3244 cmd = "show all";
3247 if (g_str_has_prefix(cmd, "show")) {
3248 wl_show(t, cmd, "Cookie White List", &c_wl);
3249 } else if (g_str_has_prefix(cmd, "save")) {
3250 a.s = cmd;
3251 wl_save(t, &a, 0);
3252 } else if (g_str_has_prefix(cmd, "toggle")) {
3253 a.i = XT_WL_TOGGLE;
3254 if (g_str_has_prefix(cmd, "toggle d"))
3255 a.i |= XT_WL_TOPLEVEL;
3256 else
3257 a.i |= XT_WL_FQDN;
3258 toggle_cwl(t, &a);
3259 } else if (g_str_has_prefix(cmd, "delete")) {
3260 show_oops(t, "'cookie delete' currently unimplemented");
3261 } else
3262 show_oops(t, "unknown cookie command: %s", cmd);
3264 return (0);
3268 js_cmd(struct tab *t, struct karg *args)
3270 char *cmd;
3271 struct karg a;
3273 if ((cmd = getparams(args->s, "js")))
3275 else
3276 cmd = "show all";
3278 if (g_str_has_prefix(cmd, "show")) {
3279 wl_show(t, cmd, "JavaScript White List", &js_wl);
3280 } else if (g_str_has_prefix(cmd, "save")) {
3281 a.s = cmd;
3282 wl_save(t, &a, 1);
3283 } else if (g_str_has_prefix(cmd, "toggle")) {
3284 a.i = XT_WL_TOGGLE;
3285 if (g_str_has_prefix(cmd, "toggle d"))
3286 a.i |= XT_WL_TOPLEVEL;
3287 else
3288 a.i |= XT_WL_FQDN;
3289 toggle_js(t, &a);
3290 } else if (g_str_has_prefix(cmd, "delete")) {
3291 show_oops(t, "'js delete' currently unimplemented");
3292 } else
3293 show_oops(t, "unknown js command: %s", cmd);
3295 return (0);
3299 add_favorite(struct tab *t, struct karg *args)
3301 char file[PATH_MAX];
3302 FILE *f;
3303 char *line = NULL;
3304 size_t urilen, linelen;
3305 const gchar *uri, *title;
3307 if (t == NULL)
3308 return (1);
3310 /* don't allow adding of xtp pages to favorites */
3311 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3312 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3313 return (1);
3316 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3317 if ((f = fopen(file, "r+")) == NULL) {
3318 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3319 return (1);
3322 title = webkit_web_view_get_title(t->wv);
3323 uri = get_uri(t->wv);
3325 if (title == NULL)
3326 title = uri;
3328 if (title == NULL || uri == NULL) {
3329 show_oops(t, "can't add page to favorites");
3330 goto done;
3333 urilen = strlen(uri);
3335 for (;;) {
3336 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3337 if (feof(f) || ferror(f))
3338 break;
3340 if (linelen == urilen && !strcmp(line, uri))
3341 goto done;
3343 free(line);
3344 line = NULL;
3347 fprintf(f, "\n%s\n%s", title, uri);
3348 done:
3349 if (line)
3350 free(line);
3351 fclose(f);
3353 update_favorite_tabs(NULL);
3355 return (0);
3359 navaction(struct tab *t, struct karg *args)
3361 WebKitWebHistoryItem *item;
3363 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3364 t->tab_id, args->i);
3366 if (t->item) {
3367 if (args->i == XT_NAV_BACK)
3368 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3369 else
3370 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3371 if (item == NULL)
3372 return (XT_CB_PASSTHROUGH);
3373 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3374 t->item = NULL;
3375 return (XT_CB_PASSTHROUGH);
3378 switch (args->i) {
3379 case XT_NAV_BACK:
3380 webkit_web_view_go_back(t->wv);
3381 break;
3382 case XT_NAV_FORWARD:
3383 webkit_web_view_go_forward(t->wv);
3384 break;
3385 case XT_NAV_RELOAD:
3386 webkit_web_view_reload(t->wv);
3387 break;
3388 case XT_NAV_RELOAD_CACHE:
3389 webkit_web_view_reload_bypass_cache(t->wv);
3390 break;
3392 return (XT_CB_PASSTHROUGH);
3396 move(struct tab *t, struct karg *args)
3398 GtkAdjustment *adjust;
3399 double pi, si, pos, ps, upper, lower, max;
3401 switch (args->i) {
3402 case XT_MOVE_DOWN:
3403 case XT_MOVE_UP:
3404 case XT_MOVE_BOTTOM:
3405 case XT_MOVE_TOP:
3406 case XT_MOVE_PAGEDOWN:
3407 case XT_MOVE_PAGEUP:
3408 case XT_MOVE_HALFDOWN:
3409 case XT_MOVE_HALFUP:
3410 adjust = t->adjust_v;
3411 break;
3412 default:
3413 adjust = t->adjust_h;
3414 break;
3417 pos = gtk_adjustment_get_value(adjust);
3418 ps = gtk_adjustment_get_page_size(adjust);
3419 upper = gtk_adjustment_get_upper(adjust);
3420 lower = gtk_adjustment_get_lower(adjust);
3421 si = gtk_adjustment_get_step_increment(adjust);
3422 pi = gtk_adjustment_get_page_increment(adjust);
3423 max = upper - ps;
3425 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3426 "max %f si %f pi %f\n",
3427 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3428 pos, ps, upper, lower, max, si, pi);
3430 switch (args->i) {
3431 case XT_MOVE_DOWN:
3432 case XT_MOVE_RIGHT:
3433 pos += si;
3434 gtk_adjustment_set_value(adjust, MIN(pos, max));
3435 break;
3436 case XT_MOVE_UP:
3437 case XT_MOVE_LEFT:
3438 pos -= si;
3439 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3440 break;
3441 case XT_MOVE_BOTTOM:
3442 case XT_MOVE_FARRIGHT:
3443 gtk_adjustment_set_value(adjust, max);
3444 break;
3445 case XT_MOVE_TOP:
3446 case XT_MOVE_FARLEFT:
3447 gtk_adjustment_set_value(adjust, lower);
3448 break;
3449 case XT_MOVE_PAGEDOWN:
3450 pos += pi;
3451 gtk_adjustment_set_value(adjust, MIN(pos, max));
3452 break;
3453 case XT_MOVE_PAGEUP:
3454 pos -= pi;
3455 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3456 break;
3457 case XT_MOVE_HALFDOWN:
3458 pos += pi / 2;
3459 gtk_adjustment_set_value(adjust, MIN(pos, max));
3460 break;
3461 case XT_MOVE_HALFUP:
3462 pos -= pi / 2;
3463 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3464 break;
3465 default:
3466 return (XT_CB_PASSTHROUGH);
3469 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3471 return (XT_CB_HANDLED);
3474 void
3475 url_set_visibility(void)
3477 struct tab *t;
3479 TAILQ_FOREACH(t, &tabs, entry) {
3480 if (show_url == 0) {
3481 gtk_widget_hide(t->toolbar);
3482 focus_webview(t);
3483 } else
3484 gtk_widget_show(t->toolbar);
3488 void
3489 notebook_tab_set_visibility(GtkNotebook *notebook)
3491 if (show_tabs == 0)
3492 gtk_notebook_set_show_tabs(notebook, FALSE);
3493 else
3494 gtk_notebook_set_show_tabs(notebook, TRUE);
3497 void
3498 statusbar_set_visibility(void)
3500 struct tab *t;
3502 TAILQ_FOREACH(t, &tabs, entry) {
3503 if (show_statusbar == 0) {
3504 gtk_widget_hide(t->statusbar);
3505 focus_webview(t);
3506 } else
3507 gtk_widget_show(t->statusbar);
3511 void
3512 url_set(struct tab *t, int enable_url_entry)
3514 GdkPixbuf *pixbuf;
3515 int progress;
3517 show_url = enable_url_entry;
3519 if (enable_url_entry) {
3520 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3521 GTK_ENTRY_ICON_PRIMARY, NULL);
3522 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3523 } else {
3524 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3525 GTK_ENTRY_ICON_PRIMARY);
3526 progress =
3527 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3528 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3529 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3530 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3531 progress);
3536 fullscreen(struct tab *t, struct karg *args)
3538 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3540 if (t == NULL)
3541 return (XT_CB_PASSTHROUGH);
3543 if (show_url == 0) {
3544 url_set(t, 1);
3545 show_tabs = 1;
3546 } else {
3547 url_set(t, 0);
3548 show_tabs = 0;
3551 url_set_visibility();
3552 notebook_tab_set_visibility(notebook);
3554 return (XT_CB_HANDLED);
3558 statusaction(struct tab *t, struct karg *args)
3560 int rv = XT_CB_HANDLED;
3562 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3564 if (t == NULL)
3565 return (XT_CB_PASSTHROUGH);
3567 switch (args->i) {
3568 case XT_STATUSBAR_SHOW:
3569 if (show_statusbar == 0) {
3570 show_statusbar = 1;
3571 statusbar_set_visibility();
3573 break;
3574 case XT_STATUSBAR_HIDE:
3575 if (show_statusbar == 1) {
3576 show_statusbar = 0;
3577 statusbar_set_visibility();
3579 break;
3581 return (rv);
3585 urlaction(struct tab *t, struct karg *args)
3587 int rv = XT_CB_HANDLED;
3589 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3591 if (t == NULL)
3592 return (XT_CB_PASSTHROUGH);
3594 switch (args->i) {
3595 case XT_URL_SHOW:
3596 if (show_url == 0) {
3597 url_set(t, 1);
3598 url_set_visibility();
3600 break;
3601 case XT_URL_HIDE:
3602 if (show_url == 1) {
3603 url_set(t, 0);
3604 url_set_visibility();
3606 break;
3608 return (rv);
3612 tabaction(struct tab *t, struct karg *args)
3614 int rv = XT_CB_HANDLED;
3615 char *url = NULL;
3616 struct undo *u;
3618 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3620 if (t == NULL)
3621 return (XT_CB_PASSTHROUGH);
3623 switch (args->i) {
3624 case XT_TAB_NEW:
3625 if ((url = getparams(args->s, "tabnew")))
3626 create_new_tab(url, NULL, 1);
3627 else
3628 create_new_tab(NULL, NULL, 1);
3629 break;
3630 case XT_TAB_DELETE:
3631 delete_tab(t);
3632 break;
3633 case XT_TAB_DELQUIT:
3634 if (gtk_notebook_get_n_pages(notebook) > 1)
3635 delete_tab(t);
3636 else
3637 quit(t, args);
3638 break;
3639 case XT_TAB_OPEN:
3640 if ((url = getparams(args->s, "open")) ||
3641 ((url = getparams(args->s, "op"))) ||
3642 ((url = getparams(args->s, "o"))))
3644 else {
3645 rv = XT_CB_PASSTHROUGH;
3646 goto done;
3648 load_uri(t, url);
3649 break;
3650 case XT_TAB_SHOW:
3651 if (show_tabs == 0) {
3652 show_tabs = 1;
3653 notebook_tab_set_visibility(notebook);
3655 break;
3656 case XT_TAB_HIDE:
3657 if (show_tabs == 1) {
3658 show_tabs = 0;
3659 notebook_tab_set_visibility(notebook);
3661 break;
3662 case XT_TAB_UNDO_CLOSE:
3663 if (undo_count == 0) {
3664 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3665 goto done;
3666 } else {
3667 undo_count--;
3668 u = TAILQ_FIRST(&undos);
3669 create_new_tab(u->uri, u, 1);
3671 TAILQ_REMOVE(&undos, u, entry);
3672 g_free(u->uri);
3673 /* u->history is freed in create_new_tab() */
3674 g_free(u);
3676 break;
3677 default:
3678 rv = XT_CB_PASSTHROUGH;
3679 goto done;
3682 done:
3683 if (args->s) {
3684 g_free(args->s);
3685 args->s = NULL;
3688 return (rv);
3692 resizetab(struct tab *t, struct karg *args)
3694 if (t == NULL || args == NULL) {
3695 show_oops_s("resizetab invalid parameters");
3696 return (XT_CB_PASSTHROUGH);
3699 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3700 t->tab_id, args->i);
3702 adjustfont_webkit(t, args->i);
3704 return (XT_CB_HANDLED);
3708 movetab(struct tab *t, struct karg *args)
3710 struct tab *tt;
3711 int x;
3713 if (t == NULL || args == NULL) {
3714 show_oops_s("movetab invalid parameters");
3715 return (XT_CB_PASSTHROUGH);
3718 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3719 t->tab_id, args->i);
3721 if (args->i == XT_TAB_INVALID)
3722 return (XT_CB_PASSTHROUGH);
3724 if (args->i < XT_TAB_INVALID) {
3725 /* next or previous tab */
3726 if (TAILQ_EMPTY(&tabs))
3727 return (XT_CB_PASSTHROUGH);
3729 switch (args->i) {
3730 case XT_TAB_NEXT:
3731 /* if at the last page, loop around to the first */
3732 if (gtk_notebook_get_current_page(notebook) ==
3733 gtk_notebook_get_n_pages(notebook) - 1)
3734 gtk_notebook_set_current_page(notebook, 0);
3735 else
3736 gtk_notebook_next_page(notebook);
3737 break;
3738 case XT_TAB_PREV:
3739 /* if at the first page, loop around to the last */
3740 if (gtk_notebook_current_page(notebook) == 0)
3741 gtk_notebook_set_current_page(notebook,
3742 gtk_notebook_get_n_pages(notebook) - 1);
3743 else
3744 gtk_notebook_prev_page(notebook);
3745 break;
3746 case XT_TAB_FIRST:
3747 gtk_notebook_set_current_page(notebook, 0);
3748 break;
3749 case XT_TAB_LAST:
3750 gtk_notebook_set_current_page(notebook, -1);
3751 break;
3752 default:
3753 return (XT_CB_PASSTHROUGH);
3756 return (XT_CB_HANDLED);
3759 /* jump to tab */
3760 x = args->i - 1;
3761 if (t->tab_id == x) {
3762 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3763 return (XT_CB_HANDLED);
3766 TAILQ_FOREACH(tt, &tabs, entry) {
3767 if (tt->tab_id == x) {
3768 gtk_notebook_set_current_page(notebook, x);
3769 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3770 if (tt->focus_wv)
3771 focus_webview(tt);
3775 return (XT_CB_HANDLED);
3779 command(struct tab *t, struct karg *args)
3781 char *s = NULL, *ss = NULL;
3782 GdkColor color;
3783 const gchar *uri;
3785 if (t == NULL || args == NULL) {
3786 show_oops_s("command invalid parameters");
3787 return (XT_CB_PASSTHROUGH);
3790 switch (args->i) {
3791 case '/':
3792 s = "/";
3793 break;
3794 case '?':
3795 s = "?";
3796 break;
3797 case ':':
3798 s = ":";
3799 break;
3800 case XT_CMD_OPEN:
3801 s = ":open ";
3802 break;
3803 case XT_CMD_TABNEW:
3804 s = ":tabnew ";
3805 break;
3806 case XT_CMD_OPEN_CURRENT:
3807 s = ":open ";
3808 /* FALL THROUGH */
3809 case XT_CMD_TABNEW_CURRENT:
3810 if (!s) /* FALL THROUGH? */
3811 s = ":tabnew ";
3812 if ((uri = get_uri(t->wv)) != NULL) {
3813 ss = g_strdup_printf("%s%s", s, uri);
3814 s = ss;
3816 break;
3817 default:
3818 show_oops(t, "command: invalid opcode %d", args->i);
3819 return (XT_CB_PASSTHROUGH);
3822 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3824 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3825 gdk_color_parse("white", &color);
3826 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3827 show_cmd(t);
3828 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3829 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3831 if (ss)
3832 g_free(ss);
3834 return (XT_CB_HANDLED);
3838 * Return a new string with a download row (in html)
3839 * appended. Old string is freed.
3841 char *
3842 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3845 WebKitDownloadStatus stat;
3846 char *status_html = NULL, *cmd_html = NULL, *new_html;
3847 gdouble progress;
3848 char cur_sz[FMT_SCALED_STRSIZE];
3849 char tot_sz[FMT_SCALED_STRSIZE];
3850 char *xtp_prefix;
3852 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3854 /* All actions wil take this form:
3855 * xxxt://class/seskey
3857 xtp_prefix = g_strdup_printf("%s%d/%s/",
3858 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3860 stat = webkit_download_get_status(dl->download);
3862 switch (stat) {
3863 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3864 status_html = g_strdup_printf("Finished");
3865 cmd_html = g_strdup_printf(
3866 "<a href='%s%d/%d'>Remove</a>",
3867 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3868 break;
3869 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3870 /* gather size info */
3871 progress = 100 * webkit_download_get_progress(dl->download);
3873 fmt_scaled(
3874 webkit_download_get_current_size(dl->download), cur_sz);
3875 fmt_scaled(
3876 webkit_download_get_total_size(dl->download), tot_sz);
3878 status_html = g_strdup_printf(
3879 "<div style='width: 100%%' align='center'>"
3880 "<div class='progress-outer'>"
3881 "<div class='progress-inner' style='width: %.2f%%'>"
3882 "</div></div></div>"
3883 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3884 progress, cur_sz, tot_sz, progress);
3886 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3887 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3889 break;
3890 /* LLL */
3891 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3892 status_html = g_strdup_printf("Cancelled");
3893 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3894 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3895 break;
3896 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3897 status_html = g_strdup_printf("Error!");
3898 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3899 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3900 break;
3901 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3902 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3903 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3904 status_html = g_strdup_printf("Starting");
3905 break;
3906 default:
3907 show_oops(t, "%s: unknown download status", __func__);
3910 new_html = g_strdup_printf(
3911 "%s\n<tr><td>%s</td><td>%s</td>"
3912 "<td style='text-align:center'>%s</td></tr>\n",
3913 html, basename(webkit_download_get_uri(dl->download)),
3914 status_html, cmd_html);
3915 g_free(html);
3917 if (status_html)
3918 g_free(status_html);
3920 if (cmd_html)
3921 g_free(cmd_html);
3923 g_free(xtp_prefix);
3925 return new_html;
3929 * update all download tabs apart from one. Pass NULL if
3930 * you want to update all.
3932 void
3933 update_download_tabs(struct tab *apart_from)
3935 struct tab *t;
3936 if (!updating_dl_tabs) {
3937 updating_dl_tabs = 1; /* stop infinite recursion */
3938 TAILQ_FOREACH(t, &tabs, entry)
3939 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3940 && (t != apart_from))
3941 xtp_page_dl(t, NULL);
3942 updating_dl_tabs = 0;
3947 * update all cookie tabs apart from one. Pass NULL if
3948 * you want to update all.
3950 void
3951 update_cookie_tabs(struct tab *apart_from)
3953 struct tab *t;
3954 if (!updating_cl_tabs) {
3955 updating_cl_tabs = 1; /* stop infinite recursion */
3956 TAILQ_FOREACH(t, &tabs, entry)
3957 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3958 && (t != apart_from))
3959 xtp_page_cl(t, NULL);
3960 updating_cl_tabs = 0;
3965 * update all history tabs apart from one. Pass NULL if
3966 * you want to update all.
3968 void
3969 update_history_tabs(struct tab *apart_from)
3971 struct tab *t;
3973 if (!updating_hl_tabs) {
3974 updating_hl_tabs = 1; /* stop infinite recursion */
3975 TAILQ_FOREACH(t, &tabs, entry)
3976 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3977 && (t != apart_from))
3978 xtp_page_hl(t, NULL);
3979 updating_hl_tabs = 0;
3983 /* cookie management XTP page */
3985 xtp_page_cl(struct tab *t, struct karg *args)
3987 char *header, *body, *footer, *page, *tmp;
3988 int i = 1; /* all ids start 1 */
3989 GSList *sc, *pc, *pc_start;
3990 SoupCookie *c;
3991 char *type, *table_headers;
3992 char *last_domain = strdup("");
3994 DNPRINTF(XT_D_CMD, "%s", __func__);
3996 if (t == NULL) {
3997 show_oops_s("%s invalid parameters", __func__);
3998 return (1);
4000 /* mark this tab as cookie jar */
4001 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4003 /* Generate a new session key */
4004 if (!updating_cl_tabs)
4005 generate_xtp_session_key(&cl_session_key);
4007 /* header */
4008 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4009 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
4010 "</head><body><h1>Cookie Jar</h1>\n");
4012 /* table headers */
4013 table_headers = g_strdup_printf("<div align='center'><table><tr>"
4014 "<th>Type</th>"
4015 "<th>Name</th>"
4016 "<th>Value</th>"
4017 "<th>Path</th>"
4018 "<th>Expires</th>"
4019 "<th>Secure</th>"
4020 "<th>HTTP<br />only</th>"
4021 "<th>Rm</th></tr>\n");
4023 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4024 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4025 pc_start = pc;
4027 body = NULL;
4028 for (; sc; sc = sc->next) {
4029 c = sc->data;
4031 if (strcmp(last_domain, c->domain) != 0) {
4032 /* new domain */
4033 free(last_domain);
4034 last_domain = strdup(c->domain);
4036 if (body != NULL) {
4037 tmp = body;
4038 body = g_strdup_printf("%s</table></div>"
4039 "<h2>%s</h2>%s\n",
4040 body, c->domain, table_headers);
4041 g_free(tmp);
4042 } else {
4043 /* first domain */
4044 body = g_strdup_printf("<h2>%s</h2>%s\n",
4045 c->domain, table_headers);
4049 type = "Session";
4050 for (pc = pc_start; pc; pc = pc->next)
4051 if (soup_cookie_equal(pc->data, c)) {
4052 type = "Session + Persistent";
4053 break;
4056 tmp = body;
4057 body = g_strdup_printf(
4058 "%s\n<tr>"
4059 "<td style='width: text-align: center'>%s</td>"
4060 "<td style='width: 1px'>%s</td>"
4061 "<td style='width=70%%;overflow: visible'>"
4062 " <textarea rows='4'>%s</textarea>"
4063 "</td>"
4064 "<td>%s</td>"
4065 "<td>%s</td>"
4066 "<td style='width: 1px; text-align: center'>%d</td>"
4067 "<td style='width: 1px; text-align: center'>%d</td>"
4068 "<td style='width: 1px; text-align: center'>"
4069 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4070 body,
4071 type,
4072 c->name,
4073 c->value,
4074 c->path,
4075 c->expires ?
4076 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4077 c->secure,
4078 c->http_only,
4080 XT_XTP_STR,
4081 XT_XTP_CL,
4082 cl_session_key,
4083 XT_XTP_CL_REMOVE,
4087 g_free(tmp);
4088 i++;
4091 soup_cookies_free(sc);
4092 soup_cookies_free(pc);
4094 /* small message if there are none */
4095 if (i == 1) {
4096 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4097 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4100 /* footer */
4101 footer = g_strdup_printf("</table></div></body></html>");
4103 page = g_strdup_printf("%s%s%s", header, body, footer);
4105 g_free(header);
4106 g_free(body);
4107 g_free(footer);
4108 g_free(table_headers);
4109 g_free(last_domain);
4111 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4112 update_cookie_tabs(t);
4114 g_free(page);
4116 return (0);
4120 xtp_page_hl(struct tab *t, struct karg *args)
4122 char *header, *body, *footer, *page, *tmp;
4123 struct history *h;
4124 int i = 1; /* all ids start 1 */
4126 DNPRINTF(XT_D_CMD, "%s", __func__);
4128 if (t == NULL) {
4129 show_oops_s("%s invalid parameters", __func__);
4130 return (1);
4133 /* mark this tab as history manager */
4134 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4136 /* Generate a new session key */
4137 if (!updating_hl_tabs)
4138 generate_xtp_session_key(&hl_session_key);
4140 /* header */
4141 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4142 "<title>History</title>\n"
4143 "%s"
4144 "</head>"
4145 "<h1>History</h1>\n",
4146 XT_PAGE_STYLE);
4148 /* body */
4149 body = g_strdup_printf("<div align='center'><table><tr>"
4150 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4152 RB_FOREACH_REVERSE(h, history_list, &hl) {
4153 tmp = body;
4154 body = g_strdup_printf(
4155 "%s\n<tr>"
4156 "<td><a href='%s'>%s</a></td>"
4157 "<td>%s</td>"
4158 "<td style='text-align: center'>"
4159 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4160 body, h->uri, h->uri, h->title,
4161 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4162 XT_XTP_HL_REMOVE, i);
4164 g_free(tmp);
4165 i++;
4168 /* small message if there are none */
4169 if (i == 1) {
4170 tmp = body;
4171 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4172 "colspan='3'>No History</td></tr>\n", body);
4173 g_free(tmp);
4176 /* footer */
4177 footer = g_strdup_printf("</table></div></body></html>");
4179 page = g_strdup_printf("%s%s%s", header, body, footer);
4182 * update all history manager tabs as the xtp session
4183 * key has now changed. No need to update the current tab.
4184 * Already did that above.
4186 update_history_tabs(t);
4188 g_free(header);
4189 g_free(body);
4190 g_free(footer);
4192 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4193 g_free(page);
4195 return (0);
4199 * Generate a web page detailing the status of any downloads
4202 xtp_page_dl(struct tab *t, struct karg *args)
4204 struct download *dl;
4205 char *header, *body, *footer, *page, *tmp;
4206 char *ref;
4207 int n_dl = 1;
4209 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4211 if (t == NULL) {
4212 show_oops_s("%s invalid parameters", __func__);
4213 return (1);
4215 /* mark as a download manager tab */
4216 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4219 * Generate a new session key for next page instance.
4220 * This only happens for the top level call to xtp_page_dl()
4221 * in which case updating_dl_tabs is 0.
4223 if (!updating_dl_tabs)
4224 generate_xtp_session_key(&dl_session_key);
4226 /* header - with refresh so as to update */
4227 if (refresh_interval >= 1)
4228 ref = g_strdup_printf(
4229 "<meta http-equiv='refresh' content='%u"
4230 ";url=%s%d/%s/%d' />\n",
4231 refresh_interval,
4232 XT_XTP_STR,
4233 XT_XTP_DL,
4234 dl_session_key,
4235 XT_XTP_DL_LIST);
4236 else
4237 ref = g_strdup("");
4240 header = g_strdup_printf(
4241 "%s\n<head>"
4242 "<title>Downloads</title>\n%s%s</head>\n",
4243 XT_DOCTYPE XT_HTML_TAG,
4244 ref,
4245 XT_PAGE_STYLE);
4247 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4248 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4249 "</p><table><tr><th style='width: 60%%'>"
4250 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4251 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4253 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4254 body = xtp_page_dl_row(t, body, dl);
4255 n_dl++;
4258 /* message if no downloads in list */
4259 if (n_dl == 1) {
4260 tmp = body;
4261 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4262 " style='text-align: center'>"
4263 "No downloads</td></tr>\n", body);
4264 g_free(tmp);
4267 /* footer */
4268 footer = g_strdup_printf("</table></div></body></html>");
4270 page = g_strdup_printf("%s%s%s", header, body, footer);
4274 * update all download manager tabs as the xtp session
4275 * key has now changed. No need to update the current tab.
4276 * Already did that above.
4278 update_download_tabs(t);
4280 g_free(ref);
4281 g_free(header);
4282 g_free(body);
4283 g_free(footer);
4285 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4286 g_free(page);
4288 return (0);
4292 search(struct tab *t, struct karg *args)
4294 gboolean d;
4296 if (t == NULL || args == NULL) {
4297 show_oops_s("search invalid parameters");
4298 return (1);
4300 if (t->search_text == NULL) {
4301 if (global_search == NULL)
4302 return (XT_CB_PASSTHROUGH);
4303 else {
4304 t->search_text = g_strdup(global_search);
4305 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4306 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4310 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4311 t->tab_id, args->i, t->search_forward, t->search_text);
4313 switch (args->i) {
4314 case XT_SEARCH_NEXT:
4315 d = t->search_forward;
4316 break;
4317 case XT_SEARCH_PREV:
4318 d = !t->search_forward;
4319 break;
4320 default:
4321 return (XT_CB_PASSTHROUGH);
4324 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4326 return (XT_CB_HANDLED);
4329 struct settings_args {
4330 char **body;
4331 int i;
4334 void
4335 print_setting(struct settings *s, char *val, void *cb_args)
4337 char *tmp, *color;
4338 struct settings_args *sa = cb_args;
4340 if (sa == NULL)
4341 return;
4343 if (s->flags & XT_SF_RUNTIME)
4344 color = "#22cc22";
4345 else
4346 color = "#cccccc";
4348 tmp = *sa->body;
4349 *sa->body = g_strdup_printf(
4350 "%s\n<tr>"
4351 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4352 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4353 *sa->body,
4354 color,
4355 s->name,
4356 color,
4359 g_free(tmp);
4360 sa->i++;
4364 set(struct tab *t, struct karg *args)
4366 char *header, *body, *footer, *page, *tmp, *pars;
4367 int i = 1;
4368 struct settings_args sa;
4370 if ((pars = getparams(args->s, "set")) == NULL) {
4371 bzero(&sa, sizeof sa);
4372 sa.body = &body;
4374 /* header */
4375 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4376 "\n<head><title>Settings</title>\n"
4377 "</head><body><h1>Settings</h1>\n");
4379 /* body */
4380 body = g_strdup_printf("<div align='center'><table><tr>"
4381 "<th align='left'>Setting</th>"
4382 "<th align='left'>Value</th></tr>\n");
4384 settings_walk(print_setting, &sa);
4385 i = sa.i;
4387 /* small message if there are none */
4388 if (i == 1) {
4389 tmp = body;
4390 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4391 "colspan='2'>No settings</td></tr>\n", body);
4392 g_free(tmp);
4395 /* footer */
4396 footer = g_strdup_printf("</table></div></body></html>");
4398 page = g_strdup_printf("%s%s%s", header, body, footer);
4400 g_free(header);
4401 g_free(body);
4402 g_free(footer);
4404 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4405 } else
4406 show_oops(t, "Invalid command: %s", pars);
4408 return (XT_CB_PASSTHROUGH);
4412 session_save(struct tab *t, char *filename, char **ret)
4414 struct karg a;
4415 char *f = filename;
4416 int rv = 1;
4418 f += strlen("save");
4419 while (*f == ' ' && *f != '\0')
4420 f++;
4421 if (strlen(f) == 0)
4422 goto done;
4424 *ret = f;
4425 if (f[0] == '.' || f[0] == '/')
4426 goto done;
4428 a.s = f;
4429 if (save_tabs(t, &a))
4430 goto done;
4431 strlcpy(named_session, f, sizeof named_session);
4433 rv = 0;
4434 done:
4435 return (rv);
4439 session_open(struct tab *t, char *filename, char **ret)
4441 struct karg a;
4442 char *f = filename;
4443 int rv = 1;
4445 f += strlen("open");
4446 while (*f == ' ' && *f != '\0')
4447 f++;
4448 if (strlen(f) == 0)
4449 goto done;
4451 *ret = f;
4452 if (f[0] == '.' || f[0] == '/')
4453 goto done;
4455 a.s = f;
4456 a.i = XT_SES_CLOSETABS;
4457 if (open_tabs(t, &a))
4458 goto done;
4460 strlcpy(named_session, f, sizeof named_session);
4462 rv = 0;
4463 done:
4464 return (rv);
4468 session_delete(struct tab *t, char *filename, char **ret)
4470 char file[PATH_MAX];
4471 char *f = filename;
4472 int rv = 1;
4474 f += strlen("delete");
4475 while (*f == ' ' && *f != '\0')
4476 f++;
4477 if (strlen(f) == 0)
4478 goto done;
4480 *ret = f;
4481 if (f[0] == '.' || f[0] == '/')
4482 goto done;
4484 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
4485 if (unlink(file))
4486 goto done;
4488 if (!strcmp(f, named_session))
4489 strlcpy(named_session, XT_SAVED_TABS_FILE,
4490 sizeof named_session);
4492 rv = 0;
4493 done:
4494 return (rv);
4498 session_cmd(struct tab *t, struct karg *args)
4500 char *action = NULL;
4501 char *filename = NULL;
4503 if (t == NULL)
4504 return (1);
4506 if ((action = getparams(args->s, "session")))
4508 else
4509 action = "show";
4511 if (!strcmp(action, "show"))
4512 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4513 XT_SAVED_TABS_FILE : named_session);
4514 else if (g_str_has_prefix(action, "save ")) {
4515 if (session_save(t, action, &filename)) {
4516 show_oops(t, "Can't save session: %s",
4517 filename ? filename : "INVALID");
4518 goto done;
4520 } else if (g_str_has_prefix(action, "open ")) {
4521 if (session_open(t, action, &filename)) {
4522 show_oops(t, "Can't open session: %s",
4523 filename ? filename : "INVALID");
4524 goto done;
4526 } else if (g_str_has_prefix(action, "delete ")) {
4527 if (session_delete(t, action, &filename)) {
4528 show_oops(t, "Can't delete session: %s",
4529 filename ? filename : "INVALID");
4530 goto done;
4532 } else
4533 show_oops(t, "Invalid command: %s", action);
4534 done:
4535 return (XT_CB_PASSTHROUGH);
4539 * Make a hardcopy of the page
4542 print_page(struct tab *t, struct karg *args)
4544 WebKitWebFrame *frame;
4545 GtkPageSetup *ps;
4546 GtkPrintOperation *op;
4547 GtkPrintOperationAction action;
4548 GtkPrintOperationResult print_res;
4549 GError *g_err = NULL;
4550 int marg_l, marg_r, marg_t, marg_b;
4552 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4554 ps = gtk_page_setup_new();
4555 op = gtk_print_operation_new();
4556 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4557 frame = webkit_web_view_get_main_frame(t->wv);
4559 /* the default margins are too small, so we will bump them */
4560 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4561 XT_PRINT_EXTRA_MARGIN;
4562 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4563 XT_PRINT_EXTRA_MARGIN;
4564 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4565 XT_PRINT_EXTRA_MARGIN;
4566 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4567 XT_PRINT_EXTRA_MARGIN;
4569 /* set margins */
4570 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4571 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4572 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4573 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4575 gtk_print_operation_set_default_page_setup(op, ps);
4577 /* this appears to free 'op' and 'ps' */
4578 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4580 /* check it worked */
4581 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4582 show_oops_s("can't print: %s", g_err->message);
4583 g_error_free (g_err);
4584 return (1);
4587 return (0);
4591 go_home(struct tab *t, struct karg *args)
4593 load_uri(t, home);
4594 return (0);
4598 restart(struct tab *t, struct karg *args)
4600 struct karg a;
4602 a.s = XT_RESTART_TABS_FILE;
4603 save_tabs(t, &a);
4604 execvp(start_argv[0], start_argv);
4605 /* NOTREACHED */
4607 return (0);
4610 #define CTRL GDK_CONTROL_MASK
4611 #define MOD1 GDK_MOD1_MASK
4612 #define SHFT GDK_SHIFT_MASK
4614 /* inherent to GTK not all keys will be caught at all times */
4615 /* XXX sort key bindings */
4616 struct key_binding {
4617 char *name;
4618 guint mask;
4619 guint use_in_entry;
4620 guint key;
4621 int (*func)(struct tab *, struct karg *);
4622 struct karg arg;
4623 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4624 } keys[] = {
4625 { "cookiejar", MOD1, 0, GDK_j, xtp_page_cl, {0} },
4626 { "downloadmgr", MOD1, 0, GDK_d, xtp_page_dl, {0} },
4627 { "history", MOD1, 0, GDK_h, xtp_page_hl, {0} },
4628 { "print", CTRL, 0, GDK_p, print_page, {0}},
4629 { NULL, 0, 0, GDK_slash, command, {.i = '/'} },
4630 { NULL, 0, 0, GDK_question, command, {.i = '?'} },
4631 { NULL, 0, 0, GDK_colon, command, {.i = ':'} },
4632 { "quit", CTRL, 0, GDK_q, quit, {0} },
4633 { "restart", MOD1, 0, GDK_q, restart, {0} },
4634 { "togglejs", CTRL, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4635 { "togglecookie", MOD1, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4636 { "togglesrc", CTRL, 0, GDK_s, toggle_src, {0} },
4637 { "yankuri", 0, 0, GDK_y, yank_uri, {0} },
4638 { "pasteuricur", 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
4639 { "pasteurinew", 0, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
4641 /* search */
4642 { "searchnext", 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
4643 { "searchprev", 0, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
4645 /* focus */
4646 { "focusaddress", 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
4647 { "focussearch", 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
4649 /* command aliases (handy when -S flag is used) */
4650 { NULL, 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
4651 { NULL, 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
4652 { NULL, 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
4653 { NULL, 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
4655 /* hinting */
4656 { "hinting", 0, 0, GDK_f, hint, {.i = 0} },
4658 /* navigation */
4659 { "goback", 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
4660 { "goback", MOD1, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
4661 { "goforward", SHFT, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
4662 { "goforward", MOD1, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
4663 { "reload", 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
4664 { "reload", CTRL, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
4665 { "reloadforce", CTRL, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
4666 { "reload" , CTRL, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
4667 { "favorites", MOD1, 1, GDK_f, xtp_page_fl, {0} },
4669 /* vertical movement */
4670 { "scrolldown", 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
4671 { "scrolldown", 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
4672 { "scrollup", 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
4673 { "scrollup", 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
4674 { "scrollbottom", 0, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
4675 { "scrollbottom", 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
4676 { "scrolltop", 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
4677 { "scrolltop", 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} },
4678 { "scrollpagedown", 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
4679 { "scrollpagedown", CTRL, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
4680 { "scrollhalfdown", CTRL, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
4681 { "scrollpagedown", 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
4682 { "scrollpageup", 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
4683 { "scrollpageup", CTRL, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
4684 { "scrollhalfup", CTRL, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
4685 /* horizontal movement */
4686 { "scrollright", 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
4687 { "scrollright", 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
4688 { "scrollleft", 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
4689 { "scrollleft", 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
4690 { "scrollfarright", 0, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
4691 { "scrollfarleft", 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
4693 /* tabs */
4694 { "tabnew", CTRL, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
4695 { "tabclose", CTRL, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
4696 { "tabundoclose", 0, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
4697 { "tabgoto1", CTRL, 0, GDK_1, movetab, {.i = 1} },
4698 { "tabgoto2", CTRL, 0, GDK_2, movetab, {.i = 2} },
4699 { "tabgoto3", CTRL, 0, GDK_3, movetab, {.i = 3} },
4700 { "tabgoto4", CTRL, 0, GDK_4, movetab, {.i = 4} },
4701 { "tabgoto5", CTRL, 0, GDK_5, movetab, {.i = 5} },
4702 { "tabgoto6", CTRL, 0, GDK_6, movetab, {.i = 6} },
4703 { "tabgoto7", CTRL, 0, GDK_7, movetab, {.i = 7} },
4704 { "tabgoto8", CTRL, 0, GDK_8, movetab, {.i = 8} },
4705 { "tabgoto9", CTRL, 0, GDK_9, movetab, {.i = 9} },
4706 { "tabgoto10", CTRL, 0, GDK_0, movetab, {.i = 10} },
4707 { "tabgotofirst", CTRL, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
4708 { "tabgotolast", CTRL, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
4709 { "tabgotoprev", CTRL, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
4710 { "tabgotonext", CTRL, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
4711 { "focusout", CTRL, 0, GDK_minus, resizetab, {.i = -1} },
4712 { "focusin", CTRL, 0, GDK_plus, resizetab, {.i = 1} },
4713 { "focusin", CTRL, 0, GDK_equal, resizetab, {.i = 1} },
4715 TAILQ_HEAD(keybinding_list, key_binding);
4717 void
4718 walk_kb(struct settings *s,
4719 void (*cb)(struct settings *, char *, void *), void *cb_args)
4721 struct key_binding *k;
4722 char str[1024];
4724 if (s == NULL || cb == NULL) {
4725 show_oops_s("walk_kb invalid parameters");
4726 return;
4729 TAILQ_FOREACH(k, &kbl, entry) {
4730 if (k->name == NULL)
4731 continue;
4732 str[0] = '\0';
4734 /* sanity */
4735 if (gdk_keyval_name(k->key) == NULL)
4736 continue;
4738 strlcat(str, k->name, sizeof str);
4739 strlcat(str, ",", sizeof str);
4741 if (k->mask & GDK_SHIFT_MASK)
4742 strlcat(str, "S-", sizeof str);
4743 if (k->mask & GDK_CONTROL_MASK)
4744 strlcat(str, "C-", sizeof str);
4745 if (k->mask & GDK_MOD1_MASK)
4746 strlcat(str, "M1-", sizeof str);
4747 if (k->mask & GDK_MOD2_MASK)
4748 strlcat(str, "M2-", sizeof str);
4749 if (k->mask & GDK_MOD3_MASK)
4750 strlcat(str, "M3-", sizeof str);
4751 if (k->mask & GDK_MOD4_MASK)
4752 strlcat(str, "M4-", sizeof str);
4753 if (k->mask & GDK_MOD5_MASK)
4754 strlcat(str, "M5-", sizeof str);
4756 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4757 cb(s, str, cb_args);
4760 void
4761 init_keybindings(void)
4763 int i;
4764 struct key_binding *k;
4766 for (i = 0; i < LENGTH(keys); i++) {
4767 k = g_malloc0(sizeof *k);
4768 k->name = keys[i].name;
4769 k->mask = keys[i].mask;
4770 k->use_in_entry = keys[i].use_in_entry;
4771 k->key = keys[i].key;
4772 k->func = keys[i].func;
4773 bcopy(&keys[i].arg, &k->arg, sizeof k->arg);
4774 TAILQ_INSERT_HEAD(&kbl, k, entry);
4776 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4777 k->name ? k->name : "unnamed key");
4781 void
4782 keybinding_clearall(void)
4784 struct key_binding *k, *next;
4786 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4787 next = TAILQ_NEXT(k, entry);
4788 if (k->name == NULL)
4789 continue;
4791 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4792 k->name ? k->name : "unnamed key");
4793 TAILQ_REMOVE(&kbl, k, entry);
4794 g_free(k);
4799 keybinding_add(char *kb, char *value, struct key_binding *orig)
4801 struct key_binding *k;
4802 guint keyval, mask = 0;
4803 int i;
4805 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s %s\n", kb, value, orig->name);
4807 if (orig == NULL)
4808 return (1);
4809 if (strcmp(kb, orig->name))
4810 return (1);
4812 /* find modifier keys */
4813 if (strstr(value, "S-"))
4814 mask |= GDK_SHIFT_MASK;
4815 if (strstr(value, "C-"))
4816 mask |= GDK_CONTROL_MASK;
4817 if (strstr(value, "M1-"))
4818 mask |= GDK_MOD1_MASK;
4819 if (strstr(value, "M2-"))
4820 mask |= GDK_MOD2_MASK;
4821 if (strstr(value, "M3-"))
4822 mask |= GDK_MOD3_MASK;
4823 if (strstr(value, "M4-"))
4824 mask |= GDK_MOD4_MASK;
4825 if (strstr(value, "M5-"))
4826 mask |= GDK_MOD5_MASK;
4828 /* find keyname */
4829 for (i = strlen(value) - 1; i > 0; i--)
4830 if (value[i] == '-')
4831 value = &value[i + 1];
4833 /* validate keyname */
4834 keyval = gdk_keyval_from_name(value);
4835 if (keyval == GDK_VoidSymbol) {
4836 warnx("invalid keybinding name %s", value);
4837 return (1);
4839 /* must run this test too, gtk+ doesn't handle 10 for example */
4840 if (gdk_keyval_name(keyval) == NULL) {
4841 warnx("invalid keybinding name %s", value);
4842 return (1);
4845 /* make sure it isn't a dupe */
4846 TAILQ_FOREACH(k, &kbl, entry)
4847 if (k->key == keyval && k->mask == mask) {
4848 warnx("duplicate keybinding for %s", value);
4849 return (1);
4852 /* add keyname */
4853 k = g_malloc0(sizeof *k);
4854 k->name = orig->name;
4855 k->mask = mask;
4856 k->use_in_entry = orig->use_in_entry;
4857 k->key = keyval;
4858 k->func = orig->func;
4859 bcopy(&orig->arg, &k->arg, sizeof k->arg);
4861 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4862 k->name,
4863 k->mask,
4864 k->use_in_entry,
4865 k->key);
4866 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4867 k->name, gdk_keyval_name(keyval));
4869 TAILQ_INSERT_HEAD(&kbl, k, entry);
4871 return (0);
4875 add_kb(struct settings *s, char *entry)
4877 int i;
4878 char *kb, *value;
4880 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4882 /* clearall is special */
4883 if (!strcmp(entry, "clearall")) {
4884 keybinding_clearall();
4885 return (0);
4888 kb = strstr(entry, ",");
4889 if (kb == NULL)
4890 return (1);
4891 *kb = '\0';
4892 value = kb + 1;
4894 /* make sure it is a valid keybinding */
4895 for (i = 0; i < LENGTH(keys); i++)
4896 if (keys[i].name && !strcmp(entry, keys[i].name)) {
4897 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s 0x%x %d 0x%x\n",
4898 keys[i].name,
4899 keys[i].mask,
4900 keys[i].use_in_entry,
4901 keys[i].key);
4903 return (keybinding_add(entry, value, &keys[i]));
4906 return (1);
4909 struct cmd {
4910 char *cmd;
4911 int params;
4912 int (*func)(struct tab *, struct karg *);
4913 struct karg arg;
4914 } cmds[] = {
4915 { "q!", 0, quit, {0} },
4916 { "qa", 0, quit, {0} },
4917 { "qa!", 0, quit, {0} },
4918 { "w", 0, save_tabs, {0} },
4919 { "wq", 0, save_tabs_and_quit, {0} },
4920 { "wq!", 0, save_tabs_and_quit, {0} },
4921 { "help", 0, help, {0} },
4922 { "about", 0, about, {0} },
4923 { "stats", 0, stats, {0} },
4924 { "version", 0, about, {0} },
4925 { "cookies", 0, xtp_page_cl, {0} },
4926 { "fav", 0, xtp_page_fl, {0} },
4927 { "favadd", 0, add_favorite, {0} },
4928 { "js", 2, js_cmd, {0} },
4929 { "cookie", 2, cookie_cmd, {0} },
4930 { "cert", 1, cert_cmd, {0} },
4931 { "ca", 0, ca_cmd, {0} },
4932 { "dl", 0, xtp_page_dl, {0} },
4933 { "h", 0, xtp_page_hl, {0} },
4934 { "hist", 0, xtp_page_hl, {0} },
4935 { "history", 0, xtp_page_hl, {0} },
4936 { "home", 0, go_home, {0} },
4937 { "restart", 0, restart, {0} },
4938 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE} },
4939 { "urlh", 0, urlaction, {.i = XT_URL_HIDE} },
4940 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW} },
4941 { "urls", 0, urlaction, {.i = XT_URL_SHOW} },
4942 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4943 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4944 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4945 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4947 { "1", 0, move, {.i = XT_MOVE_TOP} },
4948 { "print", 0, print_page, {0} },
4950 /* tabs */
4951 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
4952 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
4953 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4954 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4955 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4956 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4957 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4958 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4959 { "tabshow", 1, tabaction, {.i = XT_TAB_SHOW} },
4960 { "tabs", 1, tabaction, {.i = XT_TAB_SHOW} },
4961 { "tabhide", 1, tabaction, {.i = XT_TAB_HIDE} },
4962 { "tabh", 1, tabaction, {.i = XT_TAB_HIDE} },
4963 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4964 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4965 /* XXX add count to these commands */
4966 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4967 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4968 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4969 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4970 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4971 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4972 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4973 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4974 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4975 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4977 /* settings */
4978 { "set", 1, set, {0} },
4979 { "fullscreen", 0, fullscreen, {0} },
4980 { "f", 0, fullscreen, {0} },
4982 /* sessions */
4983 { "session", 1, session_cmd, {0} },
4986 gboolean
4987 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4989 hide_oops(t);
4991 return (FALSE);
4994 gboolean
4995 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4997 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4999 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5000 delete_tab(t);
5002 return (FALSE);
5006 * cancel, remove, etc. downloads
5008 void
5009 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5011 struct download find, *d;
5013 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5015 /* some commands require a valid download id */
5016 if (cmd != XT_XTP_DL_LIST) {
5017 /* lookup download in question */
5018 find.id = id;
5019 d = RB_FIND(download_list, &downloads, &find);
5021 if (d == NULL) {
5022 show_oops(t, "%s: no such download", __func__);
5023 return;
5027 /* decide what to do */
5028 switch (cmd) {
5029 case XT_XTP_DL_CANCEL:
5030 webkit_download_cancel(d->download);
5031 break;
5032 case XT_XTP_DL_REMOVE:
5033 webkit_download_cancel(d->download); /* just incase */
5034 g_object_unref(d->download);
5035 RB_REMOVE(download_list, &downloads, d);
5036 break;
5037 case XT_XTP_DL_LIST:
5038 /* Nothing */
5039 break;
5040 default:
5041 show_oops(t, "%s: unknown command", __func__);
5042 break;
5044 xtp_page_dl(t, NULL);
5048 * Actions on history, only does one thing for now, but
5049 * we provide the function for future actions
5051 void
5052 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5054 struct history *h, *next;
5055 int i = 1;
5057 switch (cmd) {
5058 case XT_XTP_HL_REMOVE:
5059 /* walk backwards, as listed in reverse */
5060 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5061 next = RB_PREV(history_list, &hl, h);
5062 if (id == i) {
5063 RB_REMOVE(history_list, &hl, h);
5064 g_free((gpointer) h->title);
5065 g_free((gpointer) h->uri);
5066 g_free(h);
5067 break;
5069 i++;
5071 break;
5072 case XT_XTP_HL_LIST:
5073 /* Nothing - just xtp_page_hl() below */
5074 break;
5075 default:
5076 show_oops(t, "%s: unknown command", __func__);
5077 break;
5080 xtp_page_hl(t, NULL);
5083 /* remove a favorite */
5084 void
5085 remove_favorite(struct tab *t, int index)
5087 char file[PATH_MAX], *title, *uri;
5088 char *new_favs, *tmp;
5089 FILE *f;
5090 int i;
5091 size_t len, lineno;
5093 /* open favorites */
5094 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5096 if ((f = fopen(file, "r")) == NULL) {
5097 show_oops(t, "%s: can't open favorites: %s",
5098 __func__, strerror(errno));
5099 return;
5102 /* build a string which will become the new favroites file */
5103 new_favs = g_strdup_printf("%s", "");
5105 for (i = 1;;) {
5106 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5107 if (feof(f) || ferror(f))
5108 break;
5109 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5110 if (len == 0) {
5111 free(title);
5112 title = NULL;
5113 continue;
5116 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5117 if (feof(f) || ferror(f)) {
5118 show_oops(t, "%s: can't parse favorites %s",
5119 __func__, strerror(errno));
5120 goto clean;
5124 /* as long as this isn't the one we are deleting add to file */
5125 if (i != index) {
5126 tmp = new_favs;
5127 new_favs = g_strdup_printf("%s%s\n%s\n",
5128 new_favs, title, uri);
5129 g_free(tmp);
5132 free(uri);
5133 uri = NULL;
5134 free(title);
5135 title = NULL;
5136 i++;
5138 fclose(f);
5140 /* write back new favorites file */
5141 if ((f = fopen(file, "w")) == NULL) {
5142 show_oops(t, "%s: can't open favorites: %s",
5143 __func__, strerror(errno));
5144 goto clean;
5147 fwrite(new_favs, strlen(new_favs), 1, f);
5148 fclose(f);
5150 clean:
5151 if (uri)
5152 free(uri);
5153 if (title)
5154 free(title);
5156 g_free(new_favs);
5159 void
5160 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5162 switch (cmd) {
5163 case XT_XTP_FL_LIST:
5164 /* nothing, just the below call to xtp_page_fl() */
5165 break;
5166 case XT_XTP_FL_REMOVE:
5167 remove_favorite(t, arg);
5168 break;
5169 default:
5170 show_oops(t, "%s: invalid favorites command", __func__);
5171 break;
5174 xtp_page_fl(t, NULL);
5177 void
5178 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5180 switch (cmd) {
5181 case XT_XTP_CL_LIST:
5182 /* nothing, just xtp_page_cl() */
5183 break;
5184 case XT_XTP_CL_REMOVE:
5185 remove_cookie(arg);
5186 break;
5187 default:
5188 show_oops(t, "%s: unknown cookie xtp command", __func__);
5189 break;
5192 xtp_page_cl(t, NULL);
5195 /* link an XTP class to it's session key and handler function */
5196 struct xtp_despatch {
5197 uint8_t xtp_class;
5198 char **session_key;
5199 void (*handle_func)(struct tab *, uint8_t, int);
5202 struct xtp_despatch xtp_despatches[] = {
5203 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5204 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5205 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5206 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5207 { NULL, NULL, NULL }
5211 * is the url xtp protocol? (xxxt://)
5212 * if so, parse and despatch correct bahvior
5215 parse_xtp_url(struct tab *t, const char *url)
5217 char *dup = NULL, *p, *last;
5218 uint8_t n_tokens = 0;
5219 char *tokens[4] = {NULL, NULL, NULL, ""};
5220 struct xtp_despatch *dsp, *dsp_match = NULL;
5221 uint8_t req_class;
5224 * tokens array meaning:
5225 * tokens[0] = class
5226 * tokens[1] = session key
5227 * tokens[2] = action
5228 * tokens[3] = optional argument
5231 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5233 /*xtp tab meaning is normal unless proven special */
5234 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5236 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5237 return 0;
5239 dup = g_strdup(url + strlen(XT_XTP_STR));
5241 /* split out the url */
5242 for ((p = strtok_r(dup, "/", &last)); p;
5243 (p = strtok_r(NULL, "/", &last))) {
5244 if (n_tokens < 4)
5245 tokens[n_tokens++] = p;
5248 /* should be atleast three fields 'class/seskey/command/arg' */
5249 if (n_tokens < 3)
5250 goto clean;
5252 dsp = xtp_despatches;
5253 req_class = atoi(tokens[0]);
5254 while (dsp->xtp_class != NULL) {
5255 if (dsp->xtp_class == req_class) {
5256 dsp_match = dsp;
5257 break;
5259 dsp++;
5262 /* did we find one atall? */
5263 if (dsp_match == NULL) {
5264 show_oops(t, "%s: no matching xtp despatch found", __func__);
5265 goto clean;
5268 /* check session key and call despatch function */
5269 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5270 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5273 clean:
5274 if (dup)
5275 g_free(dup);
5277 return 1;
5282 void
5283 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5285 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5287 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5289 if (t == NULL) {
5290 show_oops_s("activate_uri_entry_cb invalid parameters");
5291 return;
5294 if (uri == NULL) {
5295 show_oops(t, "activate_uri_entry_cb no uri");
5296 return;
5299 uri += strspn(uri, "\t ");
5301 /* if xxxt:// treat specially */
5302 if (!parse_xtp_url(t, uri)) {
5303 load_uri(t, (gchar *)uri);
5304 focus_webview(t);
5308 void
5309 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5311 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5312 char *newuri = NULL;
5313 gchar *enc_search;
5315 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5317 if (t == NULL) {
5318 show_oops_s("activate_search_entry_cb invalid parameters");
5319 return;
5322 if (search_string == NULL) {
5323 show_oops(t, "no search_string");
5324 return;
5327 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5328 newuri = g_strdup_printf(search_string, enc_search);
5329 g_free(enc_search);
5331 webkit_web_view_load_uri(t->wv, newuri);
5332 focus_webview(t);
5334 if (newuri)
5335 g_free(newuri);
5338 void
5339 check_and_set_js(const gchar *uri, struct tab *t)
5341 struct domain *d = NULL;
5342 int es = 0;
5344 if (uri == NULL || t == NULL)
5345 return;
5347 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5348 es = 0;
5349 else
5350 es = 1;
5352 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5353 es ? "enable" : "disable", uri);
5355 g_object_set(G_OBJECT(t->settings),
5356 "enable-scripts", es, (char *)NULL);
5357 webkit_web_view_set_settings(t->wv, t->settings);
5359 button_set_stockid(t->js_toggle,
5360 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5363 void
5364 show_ca_status(struct tab *t, const char *uri)
5366 WebKitWebFrame *frame;
5367 WebKitWebDataSource *source;
5368 WebKitNetworkRequest *request;
5369 SoupMessage *message;
5370 GdkColor color;
5371 gchar *col_str = "white";
5372 int r;
5374 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5375 ssl_strict_certs, ssl_ca_file, uri);
5377 if (uri == NULL)
5378 goto done;
5379 if (ssl_ca_file == NULL) {
5380 if (g_str_has_prefix(uri, "http://"))
5381 goto done;
5382 if (g_str_has_prefix(uri, "https://")) {
5383 col_str = "red";
5384 goto done;
5386 return;
5388 if (g_str_has_prefix(uri, "http://") ||
5389 !g_str_has_prefix(uri, "https://"))
5390 goto done;
5392 frame = webkit_web_view_get_main_frame(t->wv);
5393 source = webkit_web_frame_get_data_source(frame);
5394 request = webkit_web_data_source_get_request(source);
5395 message = webkit_network_request_get_message(request);
5397 if (message && (soup_message_get_flags(message) &
5398 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5399 col_str = "green";
5400 goto done;
5401 } else {
5402 r = load_compare_cert(t, NULL);
5403 if (r == 0)
5404 col_str = "lightblue";
5405 else if (r == 1)
5406 col_str = "yellow";
5407 else
5408 col_str = "red";
5409 goto done;
5411 done:
5412 if (col_str) {
5413 gdk_color_parse(col_str, &color);
5414 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5416 if (!strcmp(col_str, "white")) {
5417 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5418 &color);
5419 gdk_color_parse("black", &color);
5420 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5421 &color);
5422 } else {
5423 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5424 &color);
5425 gdk_color_parse("black", &color);
5426 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5427 &color);
5432 void
5433 free_favicon(struct tab *t)
5435 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5436 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5438 if (t->icon_request)
5439 g_object_unref(t->icon_request);
5440 if (t->icon_pixbuf)
5441 g_object_unref(t->icon_pixbuf);
5442 if (t->icon_dest_uri)
5443 g_free(t->icon_dest_uri);
5445 t->icon_pixbuf = NULL;
5446 t->icon_request = NULL;
5447 t->icon_dest_uri = NULL;
5450 void
5451 xt_icon_from_name(struct tab *t, gchar *name)
5453 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5454 GTK_ENTRY_ICON_PRIMARY, "text-html");
5455 if (show_url == 0)
5456 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5457 GTK_ENTRY_ICON_PRIMARY, "text-html");
5458 else
5459 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5460 GTK_ENTRY_ICON_PRIMARY, NULL);
5463 void
5464 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5466 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5467 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5468 if (show_url == 0)
5469 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5470 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5471 else
5472 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5473 GTK_ENTRY_ICON_PRIMARY, NULL);
5476 gboolean
5477 is_valid_icon(char *file)
5479 gboolean valid = 0;
5480 const char *mime_type;
5481 GFileInfo *fi;
5482 GFile *gf;
5484 gf = g_file_new_for_path(file);
5485 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5486 NULL, NULL);
5487 mime_type = g_file_info_get_content_type(fi);
5488 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5489 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5490 g_strcmp0(mime_type, "image/png") == 0 ||
5491 g_strcmp0(mime_type, "image/gif") == 0 ||
5492 g_strcmp0(mime_type, "application/octet-stream") == 0;
5493 g_object_unref(fi);
5494 g_object_unref(gf);
5496 return (valid);
5499 void
5500 set_favicon_from_file(struct tab *t, char *file)
5502 gint width, height;
5503 GdkPixbuf *pixbuf, *scaled;
5504 struct stat sb;
5506 if (t == NULL || file == NULL)
5507 return;
5508 if (t->icon_pixbuf) {
5509 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5510 return;
5513 if (g_str_has_prefix(file, "file://"))
5514 file += strlen("file://");
5515 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5517 if (!stat(file, &sb)) {
5518 if (sb.st_size == 0 || !is_valid_icon(file)) {
5519 /* corrupt icon so trash it */
5520 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5521 __func__, file);
5522 unlink(file);
5523 /* no need to set icon to default here */
5524 return;
5528 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5529 if (pixbuf == NULL) {
5530 xt_icon_from_name(t, "text-html");
5531 return;
5534 g_object_get(pixbuf, "width", &width, "height", &height,
5535 (char *)NULL);
5536 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5537 __func__, t->tab_id, width, height);
5539 if (width > 16 || height > 16) {
5540 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5541 GDK_INTERP_BILINEAR);
5542 g_object_unref(pixbuf);
5543 } else
5544 scaled = pixbuf;
5546 if (scaled == NULL) {
5547 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5548 GDK_INTERP_BILINEAR);
5549 return;
5552 t->icon_pixbuf = scaled;
5553 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5556 void
5557 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5558 WebKitWebView *wv)
5560 WebKitDownloadStatus status = webkit_download_get_status(download);
5561 struct tab *tt = NULL, *t = NULL;
5564 * find the webview instead of passing in the tab as it could have been
5565 * deleted from underneath us.
5567 TAILQ_FOREACH(tt, &tabs, entry) {
5568 if (tt->wv == wv) {
5569 t = tt;
5570 break;
5573 if (t == NULL)
5574 return;
5576 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5577 __func__, t->tab_id, status);
5579 switch (status) {
5580 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5581 /* -1 */
5582 t->icon_download = NULL;
5583 free_favicon(t);
5584 break;
5585 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5586 /* 0 */
5587 break;
5588 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5589 /* 1 */
5590 break;
5591 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5592 /* 2 */
5593 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5594 __func__, t->tab_id);
5595 t->icon_download = NULL;
5596 free_favicon(t);
5597 break;
5598 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5599 /* 3 */
5601 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5602 __func__, t->icon_dest_uri);
5603 set_favicon_from_file(t, t->icon_dest_uri);
5604 /* these will be freed post callback */
5605 t->icon_request = NULL;
5606 t->icon_download = NULL;
5607 break;
5608 default:
5609 break;
5613 void
5614 abort_favicon_download(struct tab *t)
5616 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5618 if (t->icon_download) {
5619 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5620 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5621 webkit_download_cancel(t->icon_download);
5622 t->icon_download = NULL;
5623 } else
5624 free_favicon(t);
5626 xt_icon_from_name(t, "text-html");
5629 void
5630 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5632 gchar *name_hash, file[PATH_MAX];
5633 struct stat sb;
5635 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5637 if (uri == NULL || t == NULL)
5638 return;
5640 if (t->icon_request) {
5641 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5642 return;
5645 /* check to see if we got the icon in cache */
5646 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5647 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5648 g_free(name_hash);
5650 if (!stat(file, &sb)) {
5651 if (sb.st_size > 0) {
5652 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5653 __func__, file);
5654 set_favicon_from_file(t, file);
5655 return;
5658 /* corrupt icon so trash it */
5659 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5660 __func__, file);
5661 unlink(file);
5664 /* create download for icon */
5665 t->icon_request = webkit_network_request_new(uri);
5666 if (t->icon_request == NULL) {
5667 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5668 __func__, uri);
5669 return;
5672 t->icon_download = webkit_download_new(t->icon_request);
5673 if (t->icon_download == NULL) {
5674 fprintf(stderr, "%s: icon_download", __func__);
5675 return;
5678 /* we have to free icon_dest_uri later */
5679 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5680 webkit_download_set_destination_uri(t->icon_download,
5681 t->icon_dest_uri);
5683 if (webkit_download_get_status(t->icon_download) ==
5684 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5685 fprintf(stderr, "%s: download failed to start", __func__);
5686 g_object_unref(t->icon_request);
5687 g_free(t->icon_dest_uri);
5688 t->icon_request = NULL;
5689 t->icon_dest_uri = NULL;
5690 return;
5693 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5694 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5696 webkit_download_start(t->icon_download);
5699 void
5700 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5702 const gchar *set = NULL, *uri = NULL, *title = NULL;
5703 struct history *h, find;
5704 const gchar *s_loading;
5705 struct karg a;
5707 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
5708 webkit_web_view_get_load_status(wview));
5710 if (t == NULL) {
5711 show_oops_s("notify_load_status_cb invalid paramters");
5712 return;
5715 switch (webkit_web_view_get_load_status(wview)) {
5716 case WEBKIT_LOAD_PROVISIONAL:
5717 /* 0 */
5718 abort_favicon_download(t);
5719 #if GTK_CHECK_VERSION(2, 20, 0)
5720 gtk_widget_show(t->spinner);
5721 gtk_spinner_start(GTK_SPINNER(t->spinner));
5722 #endif
5723 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5725 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5727 t->focus_wv = 1;
5729 break;
5731 case WEBKIT_LOAD_COMMITTED:
5732 /* 1 */
5733 if ((uri = get_uri(wview)) != NULL) {
5734 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5735 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5737 if (t->status) {
5738 g_free(t->status);
5739 t->status = NULL;
5741 set_status(t, (char *)uri, XT_STATUS_LOADING);
5744 /* check if js white listing is enabled */
5745 if (enable_js_whitelist) {
5746 uri = get_uri(wview);
5747 check_and_set_js(uri, t);
5750 show_ca_status(t, uri);
5752 /* we know enough to autosave the session */
5753 if (session_autosave) {
5754 a.s = NULL;
5755 save_tabs(t, &a);
5757 break;
5759 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5760 /* 3 */
5761 break;
5763 case WEBKIT_LOAD_FINISHED:
5764 /* 2 */
5765 uri = get_uri(wview);
5767 if (!strncmp(uri, "http://", strlen("http://")) ||
5768 !strncmp(uri, "https://", strlen("https://")) ||
5769 !strncmp(uri, "file://", strlen("file://"))) {
5770 find.uri = uri;
5771 h = RB_FIND(history_list, &hl, &find);
5772 if (!h) {
5773 title = webkit_web_view_get_title(wview);
5774 set = title ? title: uri;
5775 h = g_malloc(sizeof *h);
5776 h->uri = g_strdup(uri);
5777 h->title = g_strdup(set);
5778 RB_INSERT(history_list, &hl, h);
5779 completion_add_uri(h->uri);
5780 update_history_tabs(NULL);
5784 set_status(t, (char *)uri, XT_STATUS_URI);
5785 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5786 case WEBKIT_LOAD_FAILED:
5787 /* 4 */
5788 #endif
5789 #if GTK_CHECK_VERSION(2, 20, 0)
5790 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5791 gtk_widget_hide(t->spinner);
5792 #endif
5793 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5794 if (s_loading && !strcmp(s_loading, "Loading"))
5795 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5796 default:
5797 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5798 break;
5801 if (t->item)
5802 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5803 else
5804 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5805 webkit_web_view_can_go_back(wview));
5807 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5808 webkit_web_view_can_go_forward(wview));
5810 /* take focus if we are visible */
5811 focus_webview(t);
5814 void
5815 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5817 const gchar *set = NULL, *title = NULL;
5819 title = webkit_web_view_get_title(wview);
5820 set = title ? title: get_uri(wview);
5821 gtk_label_set_text(GTK_LABEL(t->label), set);
5822 gtk_window_set_title(GTK_WINDOW(main_window), set);
5825 void
5826 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5828 run_script(t, JS_HINTING);
5831 void
5832 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5834 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5835 progress == 100 ? 0 : (double)progress / 100);
5836 if (show_url == 0) {
5837 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5838 progress == 100 ? 0 : (double)progress / 100);
5843 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5844 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5845 WebKitWebPolicyDecision *pd, struct tab *t)
5847 char *uri;
5849 if (t == NULL) {
5850 show_oops_s("webview_nw_cb invalid paramters");
5851 return (FALSE);
5854 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
5855 webkit_network_request_get_uri(request));
5857 /* open in current tab */
5858 uri = (char *)webkit_network_request_get_uri(request);
5859 webkit_web_view_load_uri(t->wv, uri);
5860 webkit_web_policy_decision_ignore(pd);
5862 return (TRUE); /* we made the decission */
5866 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5867 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5868 WebKitWebPolicyDecision *pd, struct tab *t)
5870 char *uri;
5872 if (t == NULL) {
5873 show_oops_s("webview_npd_cb invalid parameters");
5874 return (FALSE);
5877 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5878 t->ctrl_click,
5879 webkit_network_request_get_uri(request));
5881 uri = (char *)webkit_network_request_get_uri(request);
5883 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
5884 t->ctrl_click = 0;
5885 create_new_tab(uri, NULL, ctrl_click_focus);
5886 webkit_web_policy_decision_ignore(pd);
5887 return (TRUE); /* we made the decission */
5890 webkit_web_policy_decision_use(pd);
5891 return (TRUE); /* we made the decission */
5894 WebKitWebView *
5895 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5897 struct tab *tt;
5898 struct domain *d = NULL;
5899 const gchar *uri;
5900 WebKitWebView *webview = NULL;
5902 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5903 webkit_web_view_get_uri(wv));
5905 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
5906 uri = webkit_web_view_get_uri(wv);
5907 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
5908 return (NULL);
5910 tt = create_new_tab(NULL, NULL, 1);
5911 webview = tt->wv;
5912 } else if (enable_scripts == 1) {
5913 tt = create_new_tab(NULL, NULL, 1);
5914 webview = tt->wv;
5917 return (webview);
5920 gboolean
5921 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
5923 const gchar *uri;
5924 struct domain *d = NULL;
5926 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
5928 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
5929 uri = webkit_web_view_get_uri(wv);
5930 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
5931 return (FALSE);
5933 delete_tab(t);
5934 } else if (enable_scripts == 1)
5935 delete_tab(t);
5937 return (TRUE);
5941 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
5943 /* we can not eat the event without throwing gtk off so defer it */
5945 /* catch middle click */
5946 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
5947 t->ctrl_click = 1;
5948 goto done;
5951 /* catch ctrl click */
5952 if (e->type == GDK_BUTTON_RELEASE &&
5953 CLEAN(e->state) == GDK_CONTROL_MASK)
5954 t->ctrl_click = 1;
5955 else
5956 t->ctrl_click = 0;
5957 done:
5958 return (XT_CB_PASSTHROUGH);
5962 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
5964 struct mime_type *m;
5966 m = find_mime_type(mime_type);
5967 if (m == NULL)
5968 return (1);
5970 switch (fork()) {
5971 case -1:
5972 show_oops(t, "can't fork mime handler");
5973 /* NOTREACHED */
5974 case 0:
5975 break;
5976 default:
5977 return (0);
5980 /* child */
5981 execlp(m->mt_action, m->mt_action,
5982 webkit_network_request_get_uri(request), (void *)NULL);
5984 _exit(0);
5986 /* NOTREACHED */
5987 return (0);
5991 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
5992 WebKitNetworkRequest *request, char *mime_type,
5993 WebKitWebPolicyDecision *decision, struct tab *t)
5995 if (t == NULL) {
5996 show_oops_s("webview_mimetype_cb invalid parameters");
5997 return (FALSE);
6000 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6001 t->tab_id, mime_type);
6003 if (run_mimehandler(t, mime_type, request) == 0) {
6004 webkit_web_policy_decision_ignore(decision);
6005 focus_webview(t);
6006 return (TRUE);
6009 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6010 webkit_web_policy_decision_download(decision);
6011 return (TRUE);
6014 return (FALSE);
6018 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6019 struct tab *t)
6021 const gchar *filename;
6022 char *uri = NULL;
6023 struct download *download_entry;
6024 int ret = TRUE;
6026 if (wk_download == NULL || t == NULL) {
6027 show_oops_s("%s invalid parameters", __func__);
6028 return (FALSE);
6031 filename = webkit_download_get_suggested_filename(wk_download);
6032 if (filename == NULL)
6033 return (FALSE); /* abort download */
6035 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6037 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6038 "local %s\n", __func__, t->tab_id, filename, uri);
6040 webkit_download_set_destination_uri(wk_download, uri);
6042 if (webkit_download_get_status(wk_download) ==
6043 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6044 show_oops(t, "%s: download failed to start", __func__);
6045 ret = FALSE;
6046 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6047 } else {
6048 download_entry = g_malloc(sizeof(struct download));
6049 download_entry->download = wk_download;
6050 download_entry->tab = t;
6051 download_entry->id = next_download_id++;
6052 RB_INSERT(download_list, &downloads, download_entry);
6053 /* get from history */
6054 g_object_ref(wk_download);
6055 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6058 if (uri)
6059 g_free(uri);
6061 /* sync other download manager tabs */
6062 update_download_tabs(NULL);
6065 * NOTE: never redirect/render the current tab before this
6066 * function returns. This will cause the download to never start.
6068 return (ret); /* start download */
6071 void
6072 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6074 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6076 if (t == NULL) {
6077 show_oops_s("webview_hover_cb");
6078 return;
6081 if (uri)
6082 set_status(t, uri, XT_STATUS_LINK);
6083 else {
6084 if (t->status)
6085 set_status(t, t->status, XT_STATUS_NOTHING);
6090 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6092 char s[2], buf[128];
6093 const char *errstr = NULL;
6094 long long link;
6096 /* don't use w directly; use t->whatever instead */
6098 if (t == NULL) {
6099 show_oops_s("wv_keypress_after_cb");
6100 return (XT_CB_PASSTHROUGH);
6103 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6104 e->keyval, e->state, t);
6106 if (t->hints_on) {
6107 /* ESC */
6108 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6109 disable_hints(t);
6110 return (XT_CB_HANDLED);
6113 /* RETURN */
6114 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6115 link = strtonum(t->hint_num, 1, 1000, &errstr);
6116 if (errstr) {
6117 /* we have a string */
6118 } else {
6119 /* we have a number */
6120 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6121 t->hint_num);
6122 run_script(t, buf);
6124 disable_hints(t);
6127 /* BACKSPACE */
6128 /* XXX unfuck this */
6129 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6130 if (t->hint_mode == XT_HINT_NUMERICAL) {
6131 /* last input was numerical */
6132 int l;
6133 l = strlen(t->hint_num);
6134 if (l > 0) {
6135 l--;
6136 if (l == 0) {
6137 disable_hints(t);
6138 enable_hints(t);
6139 } else {
6140 t->hint_num[l] = '\0';
6141 goto num;
6144 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6145 /* last input was alphanumerical */
6146 int l;
6147 l = strlen(t->hint_buf);
6148 if (l > 0) {
6149 l--;
6150 if (l == 0) {
6151 disable_hints(t);
6152 enable_hints(t);
6153 } else {
6154 t->hint_buf[l] = '\0';
6155 goto anum;
6158 } else {
6159 /* bogus */
6160 disable_hints(t);
6164 /* numerical input */
6165 if (CLEAN(e->state) == 0 &&
6166 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6167 snprintf(s, sizeof s, "%c", e->keyval);
6168 strlcat(t->hint_num, s, sizeof t->hint_num);
6169 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6170 t->hint_num);
6171 num:
6172 link = strtonum(t->hint_num, 1, 1000, &errstr);
6173 if (errstr) {
6174 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6175 disable_hints(t);
6176 } else {
6177 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6178 t->hint_num);
6179 t->hint_mode = XT_HINT_NUMERICAL;
6180 run_script(t, buf);
6183 /* empty the counter buffer */
6184 bzero(t->hint_buf, sizeof t->hint_buf);
6185 return (XT_CB_HANDLED);
6188 /* alphanumerical input */
6189 if (
6190 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6191 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6192 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6193 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6194 snprintf(s, sizeof s, "%c", e->keyval);
6195 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6196 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6197 t->hint_buf);
6198 anum:
6199 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6200 run_script(t, buf);
6202 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6203 t->hint_buf);
6204 t->hint_mode = XT_HINT_ALPHANUM;
6205 run_script(t, buf);
6207 /* empty the counter buffer */
6208 bzero(t->hint_num, sizeof t->hint_num);
6209 return (XT_CB_HANDLED);
6212 return (XT_CB_HANDLED);
6215 struct key_binding *k;
6216 TAILQ_FOREACH(k, &kbl, entry)
6217 if (e->keyval == k->key) {
6218 if (k->mask == 0) {
6219 if ((e->state & (CTRL | MOD1)) == 0) {
6220 k->func(t, &k->arg);
6221 return (XT_CB_HANDLED);
6224 else if ((e->state & k->mask) == k->mask) {
6225 k->func(t, &k->arg);
6226 return (XT_CB_HANDLED);
6230 return (XT_CB_PASSTHROUGH);
6234 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6236 hide_oops(t);
6238 return (XT_CB_PASSTHROUGH);
6242 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6244 const gchar *c = gtk_entry_get_text(w);
6245 GdkColor color;
6246 int forward = TRUE;
6248 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6249 e->keyval, e->state, t);
6251 if (t == NULL) {
6252 show_oops_s("cmd_keyrelease_cb invalid parameters");
6253 return (XT_CB_PASSTHROUGH);
6256 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6257 e->keyval, e->state, t);
6259 if (c[0] == ':')
6260 goto done;
6261 if (strlen(c) == 1) {
6262 webkit_web_view_unmark_text_matches(t->wv);
6263 goto done;
6266 if (c[0] == '/')
6267 forward = TRUE;
6268 else if (c[0] == '?')
6269 forward = FALSE;
6270 else
6271 goto done;
6273 /* search */
6274 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6275 FALSE) {
6276 /* not found, mark red */
6277 gdk_color_parse("red", &color);
6278 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6279 /* unmark and remove selection */
6280 webkit_web_view_unmark_text_matches(t->wv);
6281 /* my kingdom for a way to unselect text in webview */
6282 } else {
6283 /* found, highlight all */
6284 webkit_web_view_unmark_text_matches(t->wv);
6285 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6286 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6287 gdk_color_parse("white", &color);
6288 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6290 done:
6291 return (XT_CB_PASSTHROUGH);
6294 #if 0
6296 cmd_complete(struct tab *t, char *s)
6298 int i;
6299 GtkEntry *w = GTK_ENTRY(t->cmd);
6301 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
6303 for (i = 0; i < LENGTH(cmds); i++) {
6304 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
6305 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
6306 #if 0
6307 gtk_entry_set_text(w, ":");
6308 gtk_entry_append_text(w, cmds[i].cmd);
6309 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6310 #endif
6314 return (0);
6316 #endif
6319 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6321 if (t == NULL) {
6322 show_oops_s("entry_key_cb invalid parameters");
6323 return (XT_CB_PASSTHROUGH);
6326 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6327 e->keyval, e->state, t);
6329 hide_oops(t);
6331 if (e->keyval == GDK_Escape) {
6332 /* don't use focus_webview(t) because we want to type :cmds */
6333 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6336 struct key_binding *k;
6337 TAILQ_FOREACH(k, &kbl, entry)
6338 if (e->keyval == k->key && k->use_in_entry) {
6339 if (k->mask == 0) {
6340 if ((e->state & (CTRL | MOD1)) == 0) {
6341 k->func(t, &k->arg);
6342 return (XT_CB_HANDLED);
6345 else if ((e->state & k->mask) == k->mask) {
6346 k->func(t, &k->arg);
6347 return (XT_CB_HANDLED);
6351 return (XT_CB_PASSTHROUGH);
6355 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6357 int rv = XT_CB_HANDLED;
6358 const gchar *c = gtk_entry_get_text(w);
6360 if (t == NULL) {
6361 show_oops_s("cmd_keypress_cb parameters");
6362 return (XT_CB_PASSTHROUGH);
6365 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6366 e->keyval, e->state, t);
6368 /* sanity */
6369 if (c == NULL)
6370 e->keyval = GDK_Escape;
6371 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6372 e->keyval = GDK_Escape;
6374 switch (e->keyval) {
6375 #if 0
6376 case GDK_Tab:
6377 if (c[0] != ':')
6378 goto done;
6380 if (strchr (c, ' ')) {
6381 /* par completion */
6382 fprintf(stderr, "completeme par\n");
6383 goto done;
6386 cmd_complete(t, (char *)&c[1]);
6388 goto done;
6389 #endif
6390 case GDK_BackSpace:
6391 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6392 break;
6393 /* FALLTHROUGH */
6394 case GDK_Escape:
6395 hide_cmd(t);
6396 focus_webview(t);
6398 /* cancel search */
6399 if (c[0] == '/' || c[0] == '?')
6400 webkit_web_view_unmark_text_matches(t->wv);
6401 goto done;
6404 rv = XT_CB_PASSTHROUGH;
6405 done:
6406 return (rv);
6410 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6412 if (t == NULL) {
6413 show_oops_s("cmd_focusout_cb invalid parameters");
6414 return (XT_CB_PASSTHROUGH);
6416 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6418 hide_cmd(t);
6419 hide_oops(t);
6421 if (show_url == 0 || t->focus_wv)
6422 focus_webview(t);
6423 else
6424 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6426 return (XT_CB_PASSTHROUGH);
6429 void
6430 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6432 int i;
6433 char *s;
6434 const gchar *c = gtk_entry_get_text(entry);
6436 if (t == NULL) {
6437 show_oops_s("cmd_activate_cb invalid parameters");
6438 return;
6441 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6443 /* sanity */
6444 if (c == NULL)
6445 goto done;
6446 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6447 goto done;
6448 if (strlen(c) < 2)
6449 goto done;
6450 s = (char *)&c[1];
6452 if (c[0] == '/' || c[0] == '?') {
6453 if (t->search_text) {
6454 g_free(t->search_text);
6455 t->search_text = NULL;
6458 t->search_text = g_strdup(s);
6459 if (global_search)
6460 g_free(global_search);
6461 global_search = g_strdup(s);
6462 t->search_forward = c[0] == '/';
6464 goto done;
6467 for (i = 0; i < LENGTH(cmds); i++)
6468 if (cmds[i].params) {
6469 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
6470 cmds[i].arg.s = g_strdup(s);
6471 goto execute_command;
6473 } else {
6474 if (!strcmp(s, cmds[i].cmd))
6475 goto execute_command;
6477 show_oops(t, "Invalid command: %s", s);
6478 done:
6479 hide_cmd(t);
6480 return;
6482 execute_command:
6483 hide_cmd(t);
6484 cmds[i].func(t, &cmds[i].arg);
6486 void
6487 backward_cb(GtkWidget *w, struct tab *t)
6489 struct karg a;
6491 if (t == NULL) {
6492 show_oops_s("backward_cb invalid parameters");
6493 return;
6496 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6498 a.i = XT_NAV_BACK;
6499 navaction(t, &a);
6502 void
6503 forward_cb(GtkWidget *w, struct tab *t)
6505 struct karg a;
6507 if (t == NULL) {
6508 show_oops_s("forward_cb invalid parameters");
6509 return;
6512 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6514 a.i = XT_NAV_FORWARD;
6515 navaction(t, &a);
6518 void
6519 stop_cb(GtkWidget *w, struct tab *t)
6521 WebKitWebFrame *frame;
6523 if (t == NULL) {
6524 show_oops_s("stop_cb invalid parameters");
6525 return;
6528 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6530 frame = webkit_web_view_get_main_frame(t->wv);
6531 if (frame == NULL) {
6532 show_oops(t, "stop_cb: no frame");
6533 return;
6536 webkit_web_frame_stop_loading(frame);
6537 abort_favicon_download(t);
6540 void
6541 setup_webkit(struct tab *t)
6543 g_object_set(G_OBJECT(t->settings),
6544 "user-agent", t->user_agent, (char *)NULL);
6545 g_object_set(G_OBJECT(t->settings),
6546 "enable-scripts", enable_scripts, (char *)NULL);
6547 g_object_set(G_OBJECT(t->settings),
6548 "enable-plugins", enable_plugins, (char *)NULL);
6549 g_object_set(G_OBJECT(t->wv),
6550 "full-content-zoom", TRUE, (char *)NULL);
6551 adjustfont_webkit(t, XT_FONT_SET);
6553 webkit_web_view_set_settings(t->wv, t->settings);
6556 GtkWidget *
6557 create_browser(struct tab *t)
6559 GtkWidget *w;
6560 gchar *strval;
6562 if (t == NULL) {
6563 show_oops_s("create_browser invalid parameters");
6564 return (NULL);
6567 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6568 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6569 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6570 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6572 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6573 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6574 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6576 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6577 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6579 /* set defaults */
6580 t->settings = webkit_web_settings_new();
6582 if (user_agent == NULL) {
6583 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6584 (char *)NULL);
6585 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6586 g_free(strval);
6587 } else {
6588 t->user_agent = g_strdup(user_agent);
6591 setup_webkit(t);
6593 return (w);
6596 GtkWidget *
6597 create_window(void)
6599 GtkWidget *w;
6601 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6602 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6603 gtk_widget_set_name(w, "xxxterm");
6604 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6605 g_signal_connect(G_OBJECT(w), "delete_event",
6606 G_CALLBACK (gtk_main_quit), NULL);
6608 return (w);
6611 GtkWidget *
6612 create_toolbar(struct tab *t)
6614 GtkWidget *toolbar = NULL, *b, *eb1;
6616 b = gtk_hbox_new(FALSE, 0);
6617 toolbar = b;
6618 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6620 if (fancy_bar) {
6621 /* backward button */
6622 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
6623 gtk_widget_set_sensitive(t->backward, FALSE);
6624 g_signal_connect(G_OBJECT(t->backward), "clicked",
6625 G_CALLBACK(backward_cb), t);
6626 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
6628 /* forward button */
6629 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
6630 gtk_widget_set_sensitive(t->forward, FALSE);
6631 g_signal_connect(G_OBJECT(t->forward), "clicked",
6632 G_CALLBACK(forward_cb), t);
6633 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
6634 FALSE, 0);
6636 /* stop button */
6637 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
6638 gtk_widget_set_sensitive(t->stop, FALSE);
6639 g_signal_connect(G_OBJECT(t->stop), "clicked",
6640 G_CALLBACK(stop_cb), t);
6641 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
6642 FALSE, 0);
6644 /* JS button */
6645 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
6646 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
6647 gtk_widget_set_sensitive(t->js_toggle, TRUE);
6648 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
6649 G_CALLBACK(js_toggle_cb), t);
6650 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
6653 t->uri_entry = gtk_entry_new();
6654 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
6655 G_CALLBACK(activate_uri_entry_cb), t);
6656 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
6657 G_CALLBACK(entry_key_cb), t);
6658 completion_add(t);
6659 eb1 = gtk_hbox_new(FALSE, 0);
6660 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
6661 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
6662 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
6664 /* search entry */
6665 if (fancy_bar && search_string) {
6666 GtkWidget *eb2;
6667 t->search_entry = gtk_entry_new();
6668 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
6669 g_signal_connect(G_OBJECT(t->search_entry), "activate",
6670 G_CALLBACK(activate_search_entry_cb), t);
6671 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
6672 G_CALLBACK(entry_key_cb), t);
6673 gtk_widget_set_size_request(t->search_entry, -1, -1);
6674 eb2 = gtk_hbox_new(FALSE, 0);
6675 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
6676 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
6678 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
6680 return (toolbar);
6683 void
6684 recalc_tabs(void)
6686 struct tab *t;
6688 TAILQ_FOREACH(t, &tabs, entry)
6689 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
6693 undo_close_tab_save(struct tab *t)
6695 int m, n;
6696 const gchar *uri;
6697 struct undo *u1, *u2;
6698 GList *items;
6699 WebKitWebHistoryItem *item;
6701 if ((uri = get_uri(t->wv)) == NULL)
6702 return (1);
6704 u1 = g_malloc0(sizeof(struct undo));
6705 u1->uri = g_strdup(uri);
6707 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6709 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
6710 n = webkit_web_back_forward_list_get_back_length(t->bfl);
6711 u1->back = n;
6713 /* forward history */
6714 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
6716 while (items) {
6717 item = items->data;
6718 u1->history = g_list_prepend(u1->history,
6719 webkit_web_history_item_copy(item));
6720 items = g_list_next(items);
6723 /* current item */
6724 if (m) {
6725 item = webkit_web_back_forward_list_get_current_item(t->bfl);
6726 u1->history = g_list_prepend(u1->history,
6727 webkit_web_history_item_copy(item));
6730 /* back history */
6731 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
6733 while (items) {
6734 item = items->data;
6735 u1->history = g_list_prepend(u1->history,
6736 webkit_web_history_item_copy(item));
6737 items = g_list_next(items);
6740 TAILQ_INSERT_HEAD(&undos, u1, entry);
6742 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
6743 u2 = TAILQ_LAST(&undos, undo_tailq);
6744 TAILQ_REMOVE(&undos, u2, entry);
6745 g_free(u2->uri);
6746 g_list_free(u2->history);
6747 g_free(u2);
6748 } else
6749 undo_count++;
6751 return (0);
6754 void
6755 delete_tab(struct tab *t)
6757 struct karg a;
6759 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
6761 if (t == NULL)
6762 return;
6764 TAILQ_REMOVE(&tabs, t, entry);
6766 /* halt all webkit activity */
6767 abort_favicon_download(t);
6768 webkit_web_view_stop_loading(t->wv);
6769 undo_close_tab_save(t);
6771 gtk_widget_destroy(t->vbox);
6772 g_free(t->user_agent);
6773 g_free(t);
6775 recalc_tabs();
6776 if (TAILQ_EMPTY(&tabs))
6777 create_new_tab(NULL, NULL, 1);
6780 /* recreate session */
6781 if (session_autosave) {
6782 a.s = NULL;
6783 save_tabs(t, &a);
6787 void
6788 adjustfont_webkit(struct tab *t, int adjust)
6790 gfloat zoom;
6791 if (t == NULL) {
6792 show_oops_s("adjustfont_webkit invalid parameters");
6793 return;
6796 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
6797 if (adjust == XT_FONT_SET) {
6798 t->font_size = default_font_size;
6799 zoom = default_zoom_level;
6800 t->font_size += adjust;
6801 g_object_set(G_OBJECT(t->settings), "default-font-size",
6802 t->font_size, (char *)NULL);
6803 g_object_get(G_OBJECT(t->settings), "default-font-size",
6804 &t->font_size, (char *)NULL);
6805 } else {
6806 t->font_size += adjust;
6807 zoom += adjust/25.0;
6808 if (zoom < 0.0) {
6809 zoom = 0.04;
6812 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
6813 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
6816 void
6817 append_tab(struct tab *t)
6819 if (t == NULL)
6820 return;
6822 TAILQ_INSERT_TAIL(&tabs, t, entry);
6823 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
6826 struct tab *
6827 create_new_tab(char *title, struct undo *u, int focus)
6829 struct tab *t, *tt;
6830 int load = 1, id, notfound;
6831 GtkWidget *b, *bb;
6832 WebKitWebHistoryItem *item;
6833 GList *items;
6834 GdkColor color;
6836 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
6838 if (tabless && !TAILQ_EMPTY(&tabs)) {
6839 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
6840 return (NULL);
6843 t = g_malloc0(sizeof *t);
6845 if (title == NULL) {
6846 title = "(untitled)";
6847 load = 0;
6850 t->vbox = gtk_vbox_new(FALSE, 0);
6852 /* label + button for tab */
6853 b = gtk_hbox_new(FALSE, 0);
6854 t->tab_content = b;
6856 #if GTK_CHECK_VERSION(2, 20, 0)
6857 t->spinner = gtk_spinner_new ();
6858 #endif
6859 t->label = gtk_label_new(title);
6860 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
6861 gtk_widget_set_size_request(t->label, 100, 0);
6862 gtk_widget_set_size_request(b, 130, 0);
6864 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
6865 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
6866 #if GTK_CHECK_VERSION(2, 20, 0)
6867 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
6868 #endif
6870 /* toolbar */
6871 t->toolbar = create_toolbar(t);
6872 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
6874 /* browser */
6875 t->browser_win = create_browser(t);
6876 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
6878 /* oops message for user feedback */
6879 t->oops = gtk_entry_new();
6880 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
6881 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
6882 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
6883 gdk_color_parse("red", &color);
6884 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
6885 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
6887 /* command entry */
6888 t->cmd = gtk_entry_new();
6889 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
6890 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
6891 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
6893 /* status bar */
6894 t->statusbar = gtk_entry_new();
6895 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
6896 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
6897 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
6898 gdk_color_parse("black", &color);
6899 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
6900 gdk_color_parse("white", &color);
6901 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
6902 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
6904 /* xtp meaning is normal by default */
6905 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6907 /* set empty favicon */
6908 xt_icon_from_name(t, "text-html");
6910 /* and show it all */
6911 gtk_widget_show_all(b);
6912 gtk_widget_show_all(t->vbox);
6914 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
6915 append_tab(t);
6916 else {
6917 notfound = 1;
6918 id = gtk_notebook_get_current_page(notebook);
6919 TAILQ_FOREACH(tt, &tabs, entry) {
6920 if (tt->tab_id == id) {
6921 notfound = 0;
6922 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
6923 gtk_notebook_insert_page(notebook, t->vbox, b,
6924 id + 1);
6925 recalc_tabs();
6926 break;
6929 if (notfound)
6930 append_tab(t);
6933 #if GTK_CHECK_VERSION(2, 20, 0)
6934 /* turn spinner off if we are a new tab without uri */
6935 if (!load) {
6936 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6937 gtk_widget_hide(t->spinner);
6939 #endif
6940 /* make notebook tabs reorderable */
6941 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
6943 g_object_connect(G_OBJECT(t->cmd),
6944 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
6945 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
6946 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
6947 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
6948 (char *)NULL);
6950 /* reuse wv_button_cb to hide oops */
6951 g_object_connect(G_OBJECT(t->oops),
6952 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6953 (char *)NULL);
6955 g_object_connect(G_OBJECT(t->wv),
6956 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
6957 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6958 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
6959 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
6960 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
6961 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
6962 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_nw_cb), t,
6963 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
6964 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
6965 "signal::event", G_CALLBACK(webview_event_cb), t,
6966 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
6967 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
6968 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6969 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
6970 #endif
6971 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6972 (char *)NULL);
6973 g_signal_connect(t->wv,
6974 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
6975 g_signal_connect(t->wv,
6976 "notify::title", G_CALLBACK(notify_title_cb), t);
6978 /* hijack the unused keys as if we were the browser */
6979 g_object_connect(G_OBJECT(t->toolbar),
6980 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6981 (char *)NULL);
6983 g_signal_connect(G_OBJECT(bb), "button_press_event",
6984 G_CALLBACK(tab_close_cb), t);
6986 /* hide stuff */
6987 hide_cmd(t);
6988 hide_oops(t);
6989 url_set_visibility();
6990 statusbar_set_visibility();
6992 if (focus) {
6993 gtk_notebook_set_current_page(notebook, t->tab_id);
6994 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
6995 t->tab_id);
6998 if (load) {
6999 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7000 load_uri(t, title);
7001 } else {
7002 if (show_url == 1)
7003 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7004 else
7005 focus_webview(t);
7008 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7009 /* restore the tab's history */
7010 if (u && u->history) {
7011 items = u->history;
7012 while (items) {
7013 item = items->data;
7014 webkit_web_back_forward_list_add_item(t->bfl, item);
7015 items = g_list_next(items);
7018 item = g_list_nth_data(u->history, u->back);
7019 if (item)
7020 webkit_web_view_go_to_back_forward_item(t->wv, item);
7022 g_list_free(items);
7023 g_list_free(u->history);
7024 } else
7025 webkit_web_back_forward_list_clear(t->bfl);
7027 return (t);
7030 void
7031 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
7032 gpointer *udata)
7034 struct tab *t;
7035 const gchar *uri;
7037 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7039 TAILQ_FOREACH(t, &tabs, entry) {
7040 if (t->tab_id == pn) {
7041 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7042 "%d\n", pn);
7044 uri = webkit_web_view_get_title(t->wv);
7045 if (uri == NULL)
7046 uri = XT_NAME;
7047 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7049 hide_cmd(t);
7050 hide_oops(t);
7052 if (t->focus_wv)
7053 focus_webview(t);
7058 void
7059 menuitem_response(struct tab *t)
7061 gtk_notebook_set_current_page(notebook, t->tab_id);
7064 gboolean
7065 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7067 GtkWidget *menu, *menu_items;
7068 GdkEventButton *bevent;
7069 const gchar *uri;
7070 struct tab *ti;
7072 if (event->type == GDK_BUTTON_PRESS) {
7073 bevent = (GdkEventButton *) event;
7074 menu = gtk_menu_new();
7076 TAILQ_FOREACH(ti, &tabs, entry) {
7077 if ((uri = get_uri(ti->wv)) == NULL)
7078 /* XXX make sure there is something to print */
7079 /* XXX add gui pages in here to look purdy */
7080 uri = "(untitled)";
7081 menu_items = gtk_menu_item_new_with_label(uri);
7082 gtk_menu_append(GTK_MENU (menu), menu_items);
7083 gtk_widget_show(menu_items);
7085 gtk_signal_connect_object(GTK_OBJECT(menu_items),
7086 "activate", GTK_SIGNAL_FUNC(menuitem_response),
7087 (gpointer)ti);
7090 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7091 bevent->button, bevent->time);
7093 /* unref object so it'll free itself when popped down */
7094 g_object_ref_sink(menu);
7095 g_object_unref(menu);
7097 return (TRUE /* eat event */);
7100 return (FALSE /* propagate */);
7104 icon_size_map(int icon_size)
7106 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7107 icon_size > GTK_ICON_SIZE_DIALOG)
7108 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7110 return (icon_size);
7113 GtkWidget *
7114 create_button(char *name, char *stockid, int size)
7116 GtkWidget *button, *image;
7117 gchar *rcstring;
7118 int gtk_icon_size;
7119 rcstring = g_strdup_printf(
7120 "style \"%s-style\"\n"
7121 "{\n"
7122 " GtkWidget::focus-padding = 0\n"
7123 " GtkWidget::focus-line-width = 0\n"
7124 " xthickness = 0\n"
7125 " ythickness = 0\n"
7126 "}\n"
7127 "widget \"*.%s\" style \"%s-style\"",name,name,name);
7128 gtk_rc_parse_string(rcstring);
7129 g_free(rcstring);
7130 button = gtk_button_new();
7131 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7132 gtk_icon_size = icon_size_map(size?size:icon_size);
7134 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7135 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7136 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7137 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7138 gtk_widget_set_name(button, name);
7139 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7140 gtk_widget_set_tooltip_text(button, name);
7142 return button;
7145 void
7146 button_set_stockid(GtkWidget *button, char *stockid)
7148 GtkWidget *image;
7149 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7150 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7151 gtk_button_set_image(GTK_BUTTON(button), image);
7154 void
7155 create_canvas(void)
7157 GtkWidget *vbox;
7158 GList *l = NULL;
7159 GdkPixbuf *pb;
7160 char file[PATH_MAX];
7161 int i;
7163 vbox = gtk_vbox_new(FALSE, 0);
7164 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7165 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7166 gtk_notebook_set_tab_hborder(notebook, 0);
7167 gtk_notebook_set_tab_vborder(notebook, 0);
7168 gtk_notebook_set_scrollable(notebook, TRUE);
7169 notebook_tab_set_visibility(notebook);
7170 gtk_notebook_set_show_border(notebook, FALSE);
7171 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7173 abtn = gtk_button_new();
7174 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7175 gtk_widget_set_size_request(arrow, -1, -1);
7176 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7177 gtk_widget_set_size_request(abtn, -1, 20);
7178 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7180 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7181 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7182 gtk_widget_set_size_request(vbox, -1, -1);
7184 g_object_connect(G_OBJECT(notebook),
7185 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7186 (char *)NULL);
7187 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7188 G_CALLBACK(arrow_cb), NULL);
7190 main_window = create_window();
7191 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7192 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7194 /* icons */
7195 for (i = 0; i < LENGTH(icons); i++) {
7196 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7197 pb = gdk_pixbuf_new_from_file(file, NULL);
7198 l = g_list_append(l, pb);
7200 gtk_window_set_default_icon_list(l);
7202 gtk_widget_show_all(abtn);
7203 gtk_widget_show_all(main_window);
7206 void
7207 set_hook(void **hook, char *name)
7209 if (hook == NULL)
7210 errx(1, "set_hook");
7212 if (*hook == NULL) {
7213 *hook = dlsym(RTLD_NEXT, name);
7214 if (*hook == NULL)
7215 errx(1, "can't hook %s", name);
7219 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7220 gboolean
7221 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7223 g_return_val_if_fail(cookie1, FALSE);
7224 g_return_val_if_fail(cookie2, FALSE);
7226 return (!strcmp (cookie1->name, cookie2->name) &&
7227 !strcmp (cookie1->value, cookie2->value) &&
7228 !strcmp (cookie1->path, cookie2->path) &&
7229 !strcmp (cookie1->domain, cookie2->domain));
7232 void
7233 transfer_cookies(void)
7235 GSList *cf;
7236 SoupCookie *sc, *pc;
7238 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7240 for (;cf; cf = cf->next) {
7241 pc = cf->data;
7242 sc = soup_cookie_copy(pc);
7243 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7246 soup_cookies_free(cf);
7249 void
7250 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7252 GSList *cf;
7253 SoupCookie *ci;
7255 print_cookie("soup_cookie_jar_delete_cookie", c);
7257 if (cookies_enabled == 0)
7258 return;
7260 if (jar == NULL || c == NULL)
7261 return;
7263 /* find and remove from persistent jar */
7264 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7266 for (;cf; cf = cf->next) {
7267 ci = cf->data;
7268 if (soup_cookie_equal(ci, c)) {
7269 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7270 break;
7274 soup_cookies_free(cf);
7276 /* delete from session jar */
7277 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7280 void
7281 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7283 struct domain *d = NULL;
7284 SoupCookie *c;
7285 FILE *r_cookie_f;
7287 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7288 jar, p_cookiejar, s_cookiejar);
7290 if (cookies_enabled == 0)
7291 return;
7293 /* see if we are up and running */
7294 if (p_cookiejar == NULL) {
7295 _soup_cookie_jar_add_cookie(jar, cookie);
7296 return;
7298 /* disallow p_cookiejar adds, shouldn't happen */
7299 if (jar == p_cookiejar)
7300 return;
7302 if (enable_cookie_whitelist &&
7303 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7304 blocked_cookies++;
7305 DNPRINTF(XT_D_COOKIE,
7306 "soup_cookie_jar_add_cookie: reject %s\n",
7307 cookie->domain);
7308 if (save_rejected_cookies) {
7309 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7310 show_oops_s("can't open reject cookie file");
7311 return;
7313 fseek(r_cookie_f, 0, SEEK_END);
7314 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7315 cookie->http_only ? "#HttpOnly_" : "",
7316 cookie->domain,
7317 *cookie->domain == '.' ? "TRUE" : "FALSE",
7318 cookie->path,
7319 cookie->secure ? "TRUE" : "FALSE",
7320 cookie->expires ?
7321 (gulong)soup_date_to_time_t(cookie->expires) :
7323 cookie->name,
7324 cookie->value);
7325 fflush(r_cookie_f);
7326 fclose(r_cookie_f);
7328 if (!allow_volatile_cookies)
7329 return;
7332 if (cookie->expires == NULL && session_timeout) {
7333 soup_cookie_set_expires(cookie,
7334 soup_date_new_from_now(session_timeout));
7335 print_cookie("modified add cookie", cookie);
7338 /* see if we are white listed for persistence */
7339 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7340 /* add to persistent jar */
7341 c = soup_cookie_copy(cookie);
7342 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7343 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7346 /* add to session jar */
7347 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7348 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7351 void
7352 setup_cookies(void)
7354 char file[PATH_MAX];
7356 set_hook((void *)&_soup_cookie_jar_add_cookie,
7357 "soup_cookie_jar_add_cookie");
7358 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7359 "soup_cookie_jar_delete_cookie");
7361 if (cookies_enabled == 0)
7362 return;
7365 * the following code is intricate due to overriding several libsoup
7366 * functions.
7367 * do not alter order of these operations.
7370 /* rejected cookies */
7371 if (save_rejected_cookies)
7372 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7374 /* persistent cookies */
7375 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7376 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7378 /* session cookies */
7379 s_cookiejar = soup_cookie_jar_new();
7380 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7381 cookie_policy, (void *)NULL);
7382 transfer_cookies();
7384 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7387 void
7388 setup_proxy(char *uri)
7390 if (proxy_uri) {
7391 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7392 soup_uri_free(proxy_uri);
7393 proxy_uri = NULL;
7395 if (http_proxy) {
7396 if (http_proxy != uri) {
7397 g_free(http_proxy);
7398 http_proxy = NULL;
7402 if (uri) {
7403 http_proxy = g_strdup(uri);
7404 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7405 proxy_uri = soup_uri_new(http_proxy);
7406 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7411 send_url_to_socket(char *url)
7413 int s, len, rv = -1;
7414 struct sockaddr_un sa;
7416 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7417 warnx("send_url_to_socket: socket");
7418 return (-1);
7421 sa.sun_family = AF_UNIX;
7422 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7423 work_dir, XT_SOCKET_FILE);
7424 len = SUN_LEN(&sa);
7426 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7427 warnx("send_url_to_socket: connect");
7428 goto done;
7431 if (send(s, url, strlen(url) + 1, 0) == -1) {
7432 warnx("send_url_to_socket: send");
7433 goto done;
7435 done:
7436 close(s);
7437 return (rv);
7440 void
7441 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7443 int s, n;
7444 char str[XT_MAX_URL_LENGTH];
7445 socklen_t t = sizeof(struct sockaddr_un);
7446 struct sockaddr_un sa;
7447 struct passwd *p;
7448 uid_t uid;
7449 gid_t gid;
7451 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7452 warn("socket_watcher: accept");
7453 return;
7456 if (getpeereid(s, &uid, &gid) == -1) {
7457 warn("socket_watcher: getpeereid");
7458 return;
7460 if (uid != getuid() || gid != getgid()) {
7461 warnx("socket_watcher: unauthorized user");
7462 return;
7465 p = getpwuid(uid);
7466 if (p == NULL) {
7467 warnx("socket_watcher: not a valid user");
7468 return;
7471 n = recv(s, str, sizeof(str), 0);
7472 if (n <= 0)
7473 return;
7475 create_new_tab(str, NULL, 1);
7479 is_running(void)
7481 int s, len, rv = 1;
7482 struct sockaddr_un sa;
7484 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7485 warn("is_running: socket");
7486 return (-1);
7489 sa.sun_family = AF_UNIX;
7490 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7491 work_dir, XT_SOCKET_FILE);
7492 len = SUN_LEN(&sa);
7494 /* connect to see if there is a listener */
7495 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7496 rv = 0; /* not running */
7497 else
7498 rv = 1; /* already running */
7500 close(s);
7502 return (rv);
7506 build_socket(void)
7508 int s, len;
7509 struct sockaddr_un sa;
7511 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7512 warn("build_socket: socket");
7513 return (-1);
7516 sa.sun_family = AF_UNIX;
7517 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7518 work_dir, XT_SOCKET_FILE);
7519 len = SUN_LEN(&sa);
7521 /* connect to see if there is a listener */
7522 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7523 /* no listener so we will */
7524 unlink(sa.sun_path);
7526 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7527 warn("build_socket: bind");
7528 goto done;
7531 if (listen(s, 1) == -1) {
7532 warn("build_socket: listen");
7533 goto done;
7536 return (s);
7539 done:
7540 close(s);
7541 return (-1);
7544 static gboolean
7545 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
7546 GtkTreeIter *iter, struct tab *t)
7548 gchar *value;
7550 gtk_tree_model_get(model, iter, 0, &value, -1);
7551 load_uri(t, value);
7553 return (FALSE);
7556 void
7557 completion_add_uri(const gchar *uri)
7559 GtkTreeIter iter;
7561 /* add uri to list_store */
7562 gtk_list_store_append(completion_model, &iter);
7563 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
7566 gboolean
7567 completion_match(GtkEntryCompletion *completion, const gchar *key,
7568 GtkTreeIter *iter, gpointer user_data)
7570 gchar *value, *voffset;
7571 size_t len;
7572 gboolean match = FALSE;
7574 len = strlen(key);
7576 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
7577 -1);
7579 if (value == NULL)
7580 return FALSE;
7582 if (!strncmp(key, value, len))
7583 match = TRUE;
7584 else {
7585 voffset = strstr(value, "/") + 2;
7586 if (!strncmp(key, voffset, len))
7587 match = TRUE;
7588 else if (g_str_has_prefix(voffset, "www.")) {
7589 voffset = voffset + strlen("www.");
7590 if (!strncmp(key, voffset, len))
7591 match = TRUE;
7595 g_free(value);
7596 return (match);
7599 void
7600 completion_add(struct tab *t)
7602 /* enable completion for tab */
7603 t->completion = gtk_entry_completion_new();
7604 gtk_entry_completion_set_text_column(t->completion, 0);
7605 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
7606 gtk_entry_completion_set_model(t->completion,
7607 GTK_TREE_MODEL(completion_model));
7608 gtk_entry_completion_set_match_func(t->completion, completion_match,
7609 NULL, NULL);
7610 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
7611 g_signal_connect(G_OBJECT (t->completion), "match-selected",
7612 G_CALLBACK(completion_select_cb), t);
7615 void
7616 usage(void)
7618 fprintf(stderr,
7619 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
7620 exit(0);
7624 main(int argc, char *argv[])
7626 struct stat sb;
7627 int c, s, optn = 0, focus = 1;
7628 char conf[PATH_MAX] = { '\0' };
7629 char file[PATH_MAX];
7630 char *env_proxy = NULL;
7631 FILE *f = NULL;
7632 struct karg a;
7633 struct sigaction sact;
7635 start_argv = argv;
7637 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
7639 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
7640 switch (c) {
7641 case 'S':
7642 show_url = 0;
7643 break;
7644 case 'T':
7645 show_tabs = 0;
7646 break;
7647 case 'V':
7648 errx(0 , "Version: %s", version);
7649 break;
7650 case 'f':
7651 strlcpy(conf, optarg, sizeof(conf));
7652 break;
7653 case 's':
7654 strlcpy(named_session, optarg, sizeof(named_session));
7655 break;
7656 case 't':
7657 tabless = 1;
7658 break;
7659 case 'n':
7660 optn = 1;
7661 break;
7662 default:
7663 usage();
7664 /* NOTREACHED */
7667 argc -= optind;
7668 argv += optind;
7670 RB_INIT(&hl);
7671 RB_INIT(&js_wl);
7672 RB_INIT(&downloads);
7674 TAILQ_INIT(&tabs);
7675 TAILQ_INIT(&mtl);
7676 TAILQ_INIT(&aliases);
7677 TAILQ_INIT(&undos);
7678 TAILQ_INIT(&kbl);
7680 init_keybindings();
7682 gnutls_global_init();
7684 /* generate session keys for xtp pages */
7685 generate_xtp_session_key(&dl_session_key);
7686 generate_xtp_session_key(&hl_session_key);
7687 generate_xtp_session_key(&cl_session_key);
7688 generate_xtp_session_key(&fl_session_key);
7690 /* prepare gtk */
7691 gtk_init(&argc, &argv);
7692 if (!g_thread_supported())
7693 g_thread_init(NULL);
7695 /* signals */
7696 bzero(&sact, sizeof(sact));
7697 sigemptyset(&sact.sa_mask);
7698 sact.sa_handler = sigchild;
7699 sact.sa_flags = SA_NOCLDSTOP;
7700 sigaction(SIGCHLD, &sact, NULL);
7702 /* set download dir */
7703 pwd = getpwuid(getuid());
7704 if (pwd == NULL)
7705 errx(1, "invalid user %d", getuid());
7706 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
7708 /* set default string settings */
7709 home = g_strdup("http://www.peereboom.us");
7710 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
7711 resource_dir = g_strdup("/usr/local/share/xxxterm/");
7712 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
7714 /* read config file */
7715 if (strlen(conf) == 0)
7716 snprintf(conf, sizeof conf, "%s/.%s",
7717 pwd->pw_dir, XT_CONF_FILE);
7718 config_parse(conf, 0);
7720 /* working directory */
7721 if (strlen(work_dir) == 0)
7722 snprintf(work_dir, sizeof work_dir, "%s/%s",
7723 pwd->pw_dir, XT_DIR);
7724 if (stat(work_dir, &sb)) {
7725 if (mkdir(work_dir, S_IRWXU) == -1)
7726 err(1, "mkdir work_dir");
7727 if (stat(work_dir, &sb))
7728 err(1, "stat work_dir");
7730 if (S_ISDIR(sb.st_mode) == 0)
7731 errx(1, "%s not a dir", work_dir);
7732 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7733 warnx("fixing invalid permissions on %s", work_dir);
7734 if (chmod(work_dir, S_IRWXU) == -1)
7735 err(1, "chmod");
7738 /* icon cache dir */
7739 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
7740 if (stat(cache_dir, &sb)) {
7741 if (mkdir(cache_dir, S_IRWXU) == -1)
7742 err(1, "mkdir cache_dir");
7743 if (stat(cache_dir, &sb))
7744 err(1, "stat cache_dir");
7746 if (S_ISDIR(sb.st_mode) == 0)
7747 errx(1, "%s not a dir", cache_dir);
7748 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7749 warnx("fixing invalid permissions on %s", cache_dir);
7750 if (chmod(cache_dir, S_IRWXU) == -1)
7751 err(1, "chmod");
7754 /* certs dir */
7755 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
7756 if (stat(certs_dir, &sb)) {
7757 if (mkdir(certs_dir, S_IRWXU) == -1)
7758 err(1, "mkdir certs_dir");
7759 if (stat(certs_dir, &sb))
7760 err(1, "stat certs_dir");
7762 if (S_ISDIR(sb.st_mode) == 0)
7763 errx(1, "%s not a dir", certs_dir);
7764 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7765 warnx("fixing invalid permissions on %s", certs_dir);
7766 if (chmod(certs_dir, S_IRWXU) == -1)
7767 err(1, "chmod");
7770 /* sessions dir */
7771 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
7772 work_dir, XT_SESSIONS_DIR);
7773 if (stat(sessions_dir, &sb)) {
7774 if (mkdir(sessions_dir, S_IRWXU) == -1)
7775 err(1, "mkdir sessions_dir");
7776 if (stat(sessions_dir, &sb))
7777 err(1, "stat sessions_dir");
7779 if (S_ISDIR(sb.st_mode) == 0)
7780 errx(1, "%s not a dir", sessions_dir);
7781 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7782 warnx("fixing invalid permissions on %s", sessions_dir);
7783 if (chmod(sessions_dir, S_IRWXU) == -1)
7784 err(1, "chmod");
7786 /* runtime settings that can override config file */
7787 if (runtime_settings[0] != '\0')
7788 config_parse(runtime_settings, 1);
7790 /* download dir */
7791 if (!strcmp(download_dir, pwd->pw_dir))
7792 strlcat(download_dir, "/downloads", sizeof download_dir);
7793 if (stat(download_dir, &sb)) {
7794 if (mkdir(download_dir, S_IRWXU) == -1)
7795 err(1, "mkdir download_dir");
7796 if (stat(download_dir, &sb))
7797 err(1, "stat download_dir");
7799 if (S_ISDIR(sb.st_mode) == 0)
7800 errx(1, "%s not a dir", download_dir);
7801 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7802 warnx("fixing invalid permissions on %s", download_dir);
7803 if (chmod(download_dir, S_IRWXU) == -1)
7804 err(1, "chmod");
7807 /* favorites file */
7808 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
7809 if (stat(file, &sb)) {
7810 warnx("favorites file doesn't exist, creating it");
7811 if ((f = fopen(file, "w")) == NULL)
7812 err(1, "favorites");
7813 fclose(f);
7816 /* cookies */
7817 session = webkit_get_default_session();
7818 setup_cookies();
7820 /* certs */
7821 if (ssl_ca_file) {
7822 if (stat(ssl_ca_file, &sb)) {
7823 warn("no CA file: %s", ssl_ca_file);
7824 g_free(ssl_ca_file);
7825 ssl_ca_file = NULL;
7826 } else
7827 g_object_set(session,
7828 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
7829 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
7830 (void *)NULL);
7833 /* proxy */
7834 env_proxy = getenv("http_proxy");
7835 if (env_proxy)
7836 setup_proxy(env_proxy);
7837 else
7838 setup_proxy(http_proxy);
7840 /* see if there is already an xxxterm running */
7841 if (single_instance && is_running()) {
7842 optn = 1;
7843 warnx("already running");
7846 if (optn) {
7847 while (argc) {
7848 send_url_to_socket(argv[0]);
7850 argc--;
7851 argv++;
7853 exit(0);
7856 /* uri completion */
7857 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
7859 /* go graphical */
7860 create_canvas();
7862 if (save_global_history)
7863 restore_global_history();
7865 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
7866 restore_saved_tabs();
7867 else {
7868 a.s = named_session;
7869 a.i = XT_SES_DONOTHING;
7870 open_tabs(NULL, &a);
7873 while (argc) {
7874 create_new_tab(argv[0], NULL, focus);
7875 focus = 0;
7877 argc--;
7878 argv++;
7881 if (TAILQ_EMPTY(&tabs))
7882 create_new_tab(home, NULL, 1);
7884 if (enable_socket)
7885 if ((s = build_socket()) != -1)
7886 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
7888 gtk_main();
7890 gnutls_global_deinit();
7892 return (0);