Fix socket code that raphael broke and obviously didn't test.
[xxxterm.git] / xxxterm.c
blob71e6c14f2e21355fdcedcae44240f80962b087e9
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, 2011 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
8 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * TODO:
25 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
32 #include <ctype.h>
33 #include <dlfcn.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <libgen.h>
37 #include <pthread.h>
38 #include <pwd.h>
39 #include <regex.h>
40 #include <signal.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.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/resource.h>
61 #include <sys/socket.h>
62 #include <sys/stat.h>
63 #include <sys/time.h>
64 #include <sys/un.h>
66 #include <gtk/gtk.h>
67 #include <gdk/gdkkeysyms.h>
69 #if GTK_CHECK_VERSION(3,0,0)
70 /* we still use GDK_* instead of GDK_KEY_* */
71 #include <gdk/gdkkeysyms-compat.h>
72 #endif
74 #include <webkit/webkit.h>
75 #include <libsoup/soup.h>
76 #include <gnutls/gnutls.h>
77 #include <JavaScriptCore/JavaScript.h>
78 #include <gnutls/x509.h>
80 #include "javascript.h"
83 javascript.h borrowed from vimprobable2 under the following license:
85 Copyright (c) 2009 Leon Winter
86 Copyright (c) 2009 Hannes Schueller
87 Copyright (c) 2009 Matto Fransen
89 Permission is hereby granted, free of charge, to any person obtaining a copy
90 of this software and associated documentation files (the "Software"), to deal
91 in the Software without restriction, including without limitation the rights
92 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
93 copies of the Software, and to permit persons to whom the Software is
94 furnished to do so, subject to the following conditions:
96 The above copyright notice and this permission notice shall be included in
97 all copies or substantial portions of the Software.
99 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
100 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
101 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
102 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
103 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
104 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
105 THE SOFTWARE.
108 static char *version = "$xxxterm$";
110 /* hooked functions */
111 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
112 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
113 SoupCookie *);
115 /*#define XT_DEBUG*/
116 #ifdef XT_DEBUG
117 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
118 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
119 #define XT_D_MOVE 0x0001
120 #define XT_D_KEY 0x0002
121 #define XT_D_TAB 0x0004
122 #define XT_D_URL 0x0008
123 #define XT_D_CMD 0x0010
124 #define XT_D_NAV 0x0020
125 #define XT_D_DOWNLOAD 0x0040
126 #define XT_D_CONFIG 0x0080
127 #define XT_D_JS 0x0100
128 #define XT_D_FAVORITE 0x0200
129 #define XT_D_PRINTING 0x0400
130 #define XT_D_COOKIE 0x0800
131 #define XT_D_KEYBINDING 0x1000
132 #define XT_D_CLIP 0x2000
133 #define XT_D_BUFFERCMD 0x4000
134 u_int32_t swm_debug = 0
135 | XT_D_MOVE
136 | XT_D_KEY
137 | XT_D_TAB
138 | XT_D_URL
139 | XT_D_CMD
140 | XT_D_NAV
141 | XT_D_DOWNLOAD
142 | XT_D_CONFIG
143 | XT_D_JS
144 | XT_D_FAVORITE
145 | XT_D_PRINTING
146 | XT_D_COOKIE
147 | XT_D_KEYBINDING
148 | XT_D_CLIP
149 | XT_D_BUFFERCMD
151 #else
152 #define DPRINTF(x...)
153 #define DNPRINTF(n,x...)
154 #endif
156 #define LENGTH(x) (sizeof x / sizeof x[0])
157 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
158 ~(GDK_BUTTON1_MASK) & \
159 ~(GDK_BUTTON2_MASK) & \
160 ~(GDK_BUTTON3_MASK) & \
161 ~(GDK_BUTTON4_MASK) & \
162 ~(GDK_BUTTON5_MASK))
164 char *icons[] = {
165 "xxxtermicon16.png",
166 "xxxtermicon32.png",
167 "xxxtermicon48.png",
168 "xxxtermicon64.png",
169 "xxxtermicon128.png"
172 struct tab {
173 TAILQ_ENTRY(tab) entry;
174 GtkWidget *vbox;
175 GtkWidget *tab_content;
176 struct {
177 GtkWidget *label;
178 GtkWidget *eventbox;
179 GtkWidget *box;
180 GtkWidget *sep;
181 } tab_elems;
182 GtkWidget *label;
183 GtkWidget *spinner;
184 GtkWidget *uri_entry;
185 GtkWidget *search_entry;
186 GtkWidget *toolbar;
187 GtkWidget *browser_win;
188 GtkWidget *statusbar_box;
189 struct {
190 GtkWidget *statusbar;
191 GtkWidget *buffercmd;
192 GtkWidget *position;
193 } sbe;
194 GtkWidget *cmd;
195 GtkWidget *buffers;
196 GtkWidget *oops;
197 GtkWidget *backward;
198 GtkWidget *forward;
199 GtkWidget *stop;
200 GtkWidget *gohome;
201 GtkWidget *js_toggle;
202 GtkEntryCompletion *completion;
203 guint tab_id;
204 WebKitWebView *wv;
206 WebKitWebHistoryItem *item;
207 WebKitWebBackForwardList *bfl;
209 /* favicon */
210 WebKitNetworkRequest *icon_request;
211 WebKitDownload *icon_download;
212 gchar *icon_dest_uri;
214 /* adjustments for browser */
215 GtkScrollbar *sb_h;
216 GtkScrollbar *sb_v;
217 GtkAdjustment *adjust_h;
218 GtkAdjustment *adjust_v;
220 /* flags */
221 int focus_wv;
222 int ctrl_click;
223 gchar *status;
224 int xtp_meaning; /* identifies dls/favorites */
225 gchar *tmp_uri;
227 /* hints */
228 int hints_on;
229 int hint_mode;
230 #define XT_HINT_NONE (0)
231 #define XT_HINT_NUMERICAL (1)
232 #define XT_HINT_ALPHANUM (2)
233 char hint_buf[128];
234 char hint_num[128];
236 /* custom stylesheet */
237 int styled;
238 char *stylesheet;
240 /* search */
241 char *search_text;
242 int search_forward;
244 /* settings */
245 WebKitWebSettings *settings;
246 gchar *user_agent;
248 TAILQ_HEAD(tab_list, tab);
250 struct history {
251 RB_ENTRY(history) entry;
252 const gchar *uri;
253 const gchar *title;
255 RB_HEAD(history_list, history);
257 struct download {
258 RB_ENTRY(download) entry;
259 int id;
260 WebKitDownload *download;
261 struct tab *tab;
263 RB_HEAD(download_list, download);
265 struct domain {
266 RB_ENTRY(domain) entry;
267 gchar *d;
268 int handy; /* app use */
270 RB_HEAD(domain_list, domain);
272 struct undo {
273 TAILQ_ENTRY(undo) entry;
274 gchar *uri;
275 GList *history;
276 int back; /* Keeps track of how many back
277 * history items there are. */
279 TAILQ_HEAD(undo_tailq, undo);
281 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
282 int next_download_id = 1;
284 struct karg {
285 int i;
286 char *s;
287 int p;
290 /* defines */
291 #define XT_NAME ("XXXTerm")
292 #define XT_DIR (".xxxterm")
293 #define XT_CACHE_DIR ("cache")
294 #define XT_CERT_DIR ("certs/")
295 #define XT_SESSIONS_DIR ("sessions/")
296 #define XT_CONF_FILE ("xxxterm.conf")
297 #define XT_FAVS_FILE ("favorites")
298 #define XT_SAVED_TABS_FILE ("main_session")
299 #define XT_RESTART_TABS_FILE ("restart_tabs")
300 #define XT_SOCKET_FILE ("socket")
301 #define XT_HISTORY_FILE ("history")
302 #define XT_REJECT_FILE ("rejected.txt")
303 #define XT_COOKIE_FILE ("cookies.txt")
304 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
305 #define XT_CB_HANDLED (TRUE)
306 #define XT_CB_PASSTHROUGH (FALSE)
307 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
308 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
309 #define XT_DLMAN_REFRESH "10"
310 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
311 "td{overflow: hidden;" \
312 " padding: 2px 2px 2px 2px;" \
313 " border: 1px solid black;" \
314 " vertical-align:top;" \
315 " word-wrap: break-word}\n" \
316 "tr:hover{background: #ffff99}\n" \
317 "th{background-color: #cccccc;" \
318 " border: 1px solid black}\n" \
319 "table{width: 100%%;" \
320 " border: 1px black solid;" \
321 " border-collapse:collapse}\n" \
322 ".progress-outer{" \
323 "border: 1px solid black;" \
324 " height: 8px;" \
325 " width: 90%%}\n" \
326 ".progress-inner{float: left;" \
327 " height: 8px;" \
328 " background: green}\n" \
329 ".dlstatus{font-size: small;" \
330 " text-align: center}\n" \
331 "</style>\n"
332 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
333 #define XT_MAX_UNDO_CLOSE_TAB (32)
334 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
335 #define XT_PRINT_EXTRA_MARGIN 10
337 /* colors */
338 #define XT_COLOR_RED "#cc0000"
339 #define XT_COLOR_YELLOW "#ffff66"
340 #define XT_COLOR_BLUE "lightblue"
341 #define XT_COLOR_GREEN "#99ff66"
342 #define XT_COLOR_WHITE "white"
343 #define XT_COLOR_BLACK "black"
345 #define XT_COLOR_CT_BACKGROUND "#000000"
346 #define XT_COLOR_CT_INACTIVE "#dddddd"
347 #define XT_COLOR_CT_ACTIVE "#bbbb00"
348 #define XT_COLOR_CT_SEPARATOR "#555555"
350 #define XT_COLOR_SB_SEPARATOR "#555555"
353 * xxxterm "protocol" (xtp)
354 * We use this for managing stuff like downloads and favorites. They
355 * make magical HTML pages in memory which have xxxt:// links in order
356 * to communicate with xxxterm's internals. These links take the format:
357 * xxxt://class/session_key/action/arg
359 * Don't begin xtp class/actions as 0. atoi returns that on error.
361 * Typically we have not put addition of items in this framework, as
362 * adding items is either done via an ex-command or via a keybinding instead.
365 #define XT_XTP_STR "xxxt://"
367 /* XTP classes (xxxt://<class>) */
368 #define XT_XTP_INVALID 0 /* invalid */
369 #define XT_XTP_DL 1 /* downloads */
370 #define XT_XTP_HL 2 /* history */
371 #define XT_XTP_CL 3 /* cookies */
372 #define XT_XTP_FL 4 /* favorites */
374 /* XTP download actions */
375 #define XT_XTP_DL_LIST 1
376 #define XT_XTP_DL_CANCEL 2
377 #define XT_XTP_DL_REMOVE 3
379 /* XTP history actions */
380 #define XT_XTP_HL_LIST 1
381 #define XT_XTP_HL_REMOVE 2
383 /* XTP cookie actions */
384 #define XT_XTP_CL_LIST 1
385 #define XT_XTP_CL_REMOVE 2
387 /* XTP cookie actions */
388 #define XT_XTP_FL_LIST 1
389 #define XT_XTP_FL_REMOVE 2
391 /* actions */
392 #define XT_MOVE_INVALID (0)
393 #define XT_MOVE_DOWN (1)
394 #define XT_MOVE_UP (2)
395 #define XT_MOVE_BOTTOM (3)
396 #define XT_MOVE_TOP (4)
397 #define XT_MOVE_PAGEDOWN (5)
398 #define XT_MOVE_PAGEUP (6)
399 #define XT_MOVE_HALFDOWN (7)
400 #define XT_MOVE_HALFUP (8)
401 #define XT_MOVE_LEFT (9)
402 #define XT_MOVE_FARLEFT (10)
403 #define XT_MOVE_RIGHT (11)
404 #define XT_MOVE_FARRIGHT (12)
406 #define XT_TAB_LAST (-4)
407 #define XT_TAB_FIRST (-3)
408 #define XT_TAB_PREV (-2)
409 #define XT_TAB_NEXT (-1)
410 #define XT_TAB_INVALID (0)
411 #define XT_TAB_NEW (1)
412 #define XT_TAB_DELETE (2)
413 #define XT_TAB_DELQUIT (3)
414 #define XT_TAB_OPEN (4)
415 #define XT_TAB_UNDO_CLOSE (5)
416 #define XT_TAB_SHOW (6)
417 #define XT_TAB_HIDE (7)
418 #define XT_TAB_NEXTSTYLE (8)
420 #define XT_NAV_INVALID (0)
421 #define XT_NAV_BACK (1)
422 #define XT_NAV_FORWARD (2)
423 #define XT_NAV_RELOAD (3)
424 #define XT_NAV_RELOAD_CACHE (4)
426 #define XT_FOCUS_INVALID (0)
427 #define XT_FOCUS_URI (1)
428 #define XT_FOCUS_SEARCH (2)
430 #define XT_SEARCH_INVALID (0)
431 #define XT_SEARCH_NEXT (1)
432 #define XT_SEARCH_PREV (2)
434 #define XT_PASTE_CURRENT_TAB (0)
435 #define XT_PASTE_NEW_TAB (1)
437 #define XT_ZOOM_IN (-1)
438 #define XT_ZOOM_OUT (-2)
439 #define XT_ZOOM_NORMAL (100)
441 #define XT_URL_SHOW (1)
442 #define XT_URL_HIDE (2)
444 #define XT_WL_TOGGLE (1<<0)
445 #define XT_WL_ENABLE (1<<1)
446 #define XT_WL_DISABLE (1<<2)
447 #define XT_WL_FQDN (1<<3) /* default */
448 #define XT_WL_TOPLEVEL (1<<4)
449 #define XT_WL_PERSISTENT (1<<5)
450 #define XT_WL_SESSION (1<<6)
451 #define XT_WL_RELOAD (1<<7)
453 #define XT_SHOW (1<<7)
454 #define XT_DELETE (1<<8)
455 #define XT_SAVE (1<<9)
456 #define XT_OPEN (1<<10)
458 #define XT_CMD_OPEN (0)
459 #define XT_CMD_OPEN_CURRENT (1)
460 #define XT_CMD_TABNEW (2)
461 #define XT_CMD_TABNEW_CURRENT (3)
463 #define XT_STATUS_NOTHING (0)
464 #define XT_STATUS_LINK (1)
465 #define XT_STATUS_URI (2)
466 #define XT_STATUS_LOADING (3)
468 #define XT_SES_DONOTHING (0)
469 #define XT_SES_CLOSETABS (1)
471 #define XT_BM_NORMAL (0)
472 #define XT_BM_WHITELIST (1)
473 #define XT_BM_KIOSK (2)
475 #define XT_PREFIX (1<<0)
476 #define XT_USERARG (1<<1)
477 #define XT_URLARG (1<<2)
478 #define XT_INTARG (1<<3)
480 #define XT_TABS_NORMAL 0
481 #define XT_TABS_COMPACT 1
483 /* mime types */
484 struct mime_type {
485 char *mt_type;
486 char *mt_action;
487 int mt_default;
488 int mt_download;
489 TAILQ_ENTRY(mime_type) entry;
491 TAILQ_HEAD(mime_type_list, mime_type);
493 /* uri aliases */
494 struct alias {
495 char *a_name;
496 char *a_uri;
497 TAILQ_ENTRY(alias) entry;
499 TAILQ_HEAD(alias_list, alias);
501 /* settings that require restart */
502 int tabless = 0; /* allow only 1 tab */
503 int enable_socket = 0;
504 int single_instance = 0; /* only allow one xxxterm to run */
505 int fancy_bar = 1; /* fancy toolbar */
506 int browser_mode = XT_BM_NORMAL;
507 int enable_localstorage = 0;
508 char *statusbar_elems = NULL;
510 /* runtime settings */
511 int show_tabs = 1; /* show tabs on notebook */
512 int tab_style = XT_TABS_NORMAL; /* tab bar style */
513 int show_url = 1; /* show url toolbar on notebook */
514 int show_statusbar = 0; /* vimperator style status bar */
515 int ctrl_click_focus = 0; /* ctrl click gets focus */
516 int cookies_enabled = 1; /* enable cookies */
517 int read_only_cookies = 0; /* enable to not write cookies */
518 int enable_scripts = 1;
519 int enable_plugins = 0;
520 gfloat default_zoom_level = 1.0;
521 char default_script[PATH_MAX];
522 int window_height = 768;
523 int window_width = 1024;
524 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
525 int refresh_interval = 10; /* download refresh interval */
526 int enable_cookie_whitelist = 0;
527 int enable_js_whitelist = 0;
528 int session_timeout = 3600; /* cookie session timeout */
529 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
530 char *ssl_ca_file = NULL;
531 char *resource_dir = NULL;
532 gboolean ssl_strict_certs = FALSE;
533 int append_next = 1; /* append tab after current tab */
534 char *home = NULL;
535 char *search_string = NULL;
536 char *http_proxy = NULL;
537 char download_dir[PATH_MAX];
538 char runtime_settings[PATH_MAX]; /* override of settings */
539 int allow_volatile_cookies = 0;
540 int save_global_history = 0; /* save global history to disk */
541 char *user_agent = NULL;
542 int save_rejected_cookies = 0;
543 int session_autosave = 0;
544 int guess_search = 0;
545 int dns_prefetch = FALSE;
546 gint max_connections = 25;
547 gint max_host_connections = 5;
548 gint enable_spell_checking = 0;
549 char *spell_check_languages = NULL;
551 char *cmd_font_name = NULL;
552 char *oops_font_name = NULL;
553 char *statusbar_font_name = NULL;
554 char *tabbar_font_name = NULL;
555 PangoFontDescription *cmd_font;
556 PangoFontDescription *oops_font;
557 PangoFontDescription *statusbar_font;
558 PangoFontDescription *tabbar_font;
560 int btn_down; /* M1 down in any wv */
562 struct settings;
563 struct key_binding;
564 int set_browser_mode(struct settings *, char *);
565 int set_cookie_policy(struct settings *, char *);
566 int set_download_dir(struct settings *, char *);
567 int set_default_script(struct settings *, char *);
568 int set_runtime_dir(struct settings *, char *);
569 int set_tab_style(struct settings *, char *);
570 int set_work_dir(struct settings *, char *);
571 int add_alias(struct settings *, char *);
572 int add_mime_type(struct settings *, char *);
573 int add_cookie_wl(struct settings *, char *);
574 int add_js_wl(struct settings *, char *);
575 int add_kb(struct settings *, char *);
576 void button_set_stockid(GtkWidget *, char *);
577 GtkWidget * create_button(char *, char *, int);
579 char *get_browser_mode(struct settings *);
580 char *get_cookie_policy(struct settings *);
581 char *get_download_dir(struct settings *);
582 char *get_default_script(struct settings *);
583 char *get_runtime_dir(struct settings *);
584 char *get_tab_style(struct settings *);
585 char *get_work_dir(struct settings *);
587 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
588 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
589 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
590 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
591 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
593 void recalc_tabs(void);
594 void recolor_compact_tabs(void);
595 void set_current_tab(int page_num);
596 gboolean update_statusbar_position(GtkAdjustment* adjustment, gpointer data);
598 struct special {
599 int (*set)(struct settings *, char *);
600 char *(*get)(struct settings *);
601 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
604 struct special s_browser_mode = {
605 set_browser_mode,
606 get_browser_mode,
607 NULL
610 struct special s_cookie = {
611 set_cookie_policy,
612 get_cookie_policy,
613 NULL
616 struct special s_alias = {
617 add_alias,
618 NULL,
619 walk_alias
622 struct special s_mime = {
623 add_mime_type,
624 NULL,
625 walk_mime_type
628 struct special s_js = {
629 add_js_wl,
630 NULL,
631 walk_js_wl
634 struct special s_kb = {
635 add_kb,
636 NULL,
637 walk_kb
640 struct special s_cookie_wl = {
641 add_cookie_wl,
642 NULL,
643 walk_cookie_wl
646 struct special s_default_script = {
647 set_default_script,
648 get_default_script,
649 NULL
652 struct special s_download_dir = {
653 set_download_dir,
654 get_download_dir,
655 NULL
658 struct special s_work_dir = {
659 set_work_dir,
660 get_work_dir,
661 NULL
664 struct special s_tab_style = {
665 set_tab_style,
666 get_tab_style,
667 NULL
670 struct settings {
671 char *name;
672 int type;
673 #define XT_S_INVALID (0)
674 #define XT_S_INT (1)
675 #define XT_S_STR (2)
676 #define XT_S_FLOAT (3)
677 uint32_t flags;
678 #define XT_SF_RESTART (1<<0)
679 #define XT_SF_RUNTIME (1<<1)
680 int *ival;
681 char **sval;
682 struct special *s;
683 gfloat *fval;
684 } rs[] = {
685 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
686 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
687 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
688 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
689 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
690 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
691 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
692 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
693 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
694 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
695 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
696 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
697 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
698 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
699 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
700 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
701 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
702 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
703 { "home", XT_S_STR, 0, NULL, &home, NULL },
704 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
705 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
706 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
707 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
708 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
709 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
710 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
711 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
712 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
713 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
714 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
715 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
716 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
717 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
718 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
719 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
720 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
721 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
722 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
723 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
724 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
725 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
726 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
727 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
728 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
730 /* font settings */
731 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
732 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
733 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
734 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
736 /* runtime settings */
737 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
738 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
739 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
740 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
741 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
744 int about(struct tab *, struct karg *);
745 int blank(struct tab *, struct karg *);
746 int ca_cmd(struct tab *, struct karg *);
747 int cookie_show_wl(struct tab *, struct karg *);
748 int js_show_wl(struct tab *, struct karg *);
749 int help(struct tab *, struct karg *);
750 int set(struct tab *, struct karg *);
751 int stats(struct tab *, struct karg *);
752 int marco(struct tab *, struct karg *);
753 const char * marco_message(int *);
754 int xtp_page_cl(struct tab *, struct karg *);
755 int xtp_page_dl(struct tab *, struct karg *);
756 int xtp_page_fl(struct tab *, struct karg *);
757 int xtp_page_hl(struct tab *, struct karg *);
758 void xt_icon_from_file(struct tab *, char *);
759 const gchar *get_uri(struct tab *);
760 const gchar *get_title(struct tab *, bool);
762 #define XT_URI_ABOUT ("about:")
763 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
764 #define XT_URI_ABOUT_ABOUT ("about")
765 #define XT_URI_ABOUT_BLANK ("blank")
766 #define XT_URI_ABOUT_CERTS ("certs")
767 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
768 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
769 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
770 #define XT_URI_ABOUT_FAVORITES ("favorites")
771 #define XT_URI_ABOUT_HELP ("help")
772 #define XT_URI_ABOUT_HISTORY ("history")
773 #define XT_URI_ABOUT_JSWL ("jswl")
774 #define XT_URI_ABOUT_SET ("set")
775 #define XT_URI_ABOUT_STATS ("stats")
776 #define XT_URI_ABOUT_MARCO ("marco")
778 struct about_type {
779 char *name;
780 int (*func)(struct tab *, struct karg *);
781 } about_list[] = {
782 { XT_URI_ABOUT_ABOUT, about },
783 { XT_URI_ABOUT_BLANK, blank },
784 { XT_URI_ABOUT_CERTS, ca_cmd },
785 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
786 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
787 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
788 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
789 { XT_URI_ABOUT_HELP, help },
790 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
791 { XT_URI_ABOUT_JSWL, js_show_wl },
792 { XT_URI_ABOUT_SET, set },
793 { XT_URI_ABOUT_STATS, stats },
794 { XT_URI_ABOUT_MARCO, marco },
797 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
798 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
799 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
800 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
801 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
802 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
803 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
805 /* globals */
806 extern char *__progname;
807 char **start_argv;
808 struct passwd *pwd;
809 GtkWidget *main_window;
810 GtkNotebook *notebook;
811 GtkWidget *tab_bar;
812 GtkWidget *arrow, *abtn;
813 struct tab_list tabs;
814 struct history_list hl;
815 struct download_list downloads;
816 struct domain_list c_wl;
817 struct domain_list js_wl;
818 struct undo_tailq undos;
819 struct keybinding_list kbl;
820 int undo_count;
821 int updating_dl_tabs = 0;
822 int updating_hl_tabs = 0;
823 int updating_cl_tabs = 0;
824 int updating_fl_tabs = 0;
825 char *global_search;
826 uint64_t blocked_cookies = 0;
827 char named_session[PATH_MAX];
828 int icon_size_map(int);
830 GtkListStore *completion_model;
831 void completion_add(struct tab *);
832 void completion_add_uri(const gchar *);
833 GtkListStore *buffers_store;
834 void xxx_dir(char *);
836 void
837 sigchild(int sig)
839 int saved_errno, status;
840 pid_t pid;
842 saved_errno = errno;
844 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
845 if (pid == -1) {
846 if (errno == EINTR)
847 continue;
848 if (errno != ECHILD) {
850 clog_warn("sigchild: waitpid:");
853 break;
856 if (WIFEXITED(status)) {
857 if (WEXITSTATUS(status) != 0) {
859 clog_warnx("sigchild: child exit status: %d",
860 WEXITSTATUS(status));
863 } else {
865 clog_warnx("sigchild: child is terminated abnormally");
870 errno = saved_errno;
874 is_g_object_setting(GObject *o, char *str)
876 guint n_props = 0, i;
877 GParamSpec **proplist;
879 if (! G_IS_OBJECT(o))
880 return (0);
882 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
883 &n_props);
885 for (i=0; i < n_props; i++) {
886 if (! strcmp(proplist[i]->name, str))
887 return (1);
889 return (0);
892 gchar *
893 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
895 gchar *r;
897 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
898 "<head>\n"
899 "<title>%s</title>\n"
900 "%s"
901 "%s"
902 "</head>\n"
903 "<body>\n"
904 "<h1>%s</h1>\n"
905 "%s\n</body>\n"
906 "</html>",
907 title,
908 addstyles ? XT_PAGE_STYLE : "",
909 head,
910 title,
911 body);
913 return r;
917 * Display a web page from a HTML string in memory, rather than from a URL
919 void
920 load_webkit_string(struct tab *t, const char *str, gchar *title)
922 char file[PATH_MAX];
923 int i;
925 /* we set this to indicate we want to manually do navaction */
926 if (t->bfl)
927 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
929 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
930 if (title) {
931 /* set t->xtp_meaning */
932 for (i = 0; i < LENGTH(about_list); i++)
933 if (!strcmp(title, about_list[i].name)) {
934 t->xtp_meaning = i;
935 break;
938 webkit_web_view_load_string(t->wv, str, NULL, NULL, "file://");
939 #if GTK_CHECK_VERSION(2, 20, 0)
940 gtk_spinner_stop(GTK_SPINNER(t->spinner));
941 gtk_widget_hide(t->spinner);
942 #endif
943 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
944 xt_icon_from_file(t, file);
948 struct tab *
949 get_current_tab(void)
951 struct tab *t;
953 TAILQ_FOREACH(t, &tabs, entry) {
954 if (t->tab_id == gtk_notebook_get_current_page(notebook))
955 return (t);
958 warnx("%s: no current tab", __func__);
960 return (NULL);
963 void
964 set_status(struct tab *t, gchar *s, int status)
966 gchar *type = NULL;
968 if (s == NULL)
969 return;
971 switch (status) {
972 case XT_STATUS_LOADING:
973 type = g_strdup_printf("Loading: %s", s);
974 s = type;
975 break;
976 case XT_STATUS_LINK:
977 type = g_strdup_printf("Link: %s", s);
978 if (!t->status)
979 t->status = g_strdup(gtk_entry_get_text(
980 GTK_ENTRY(t->sbe.statusbar)));
981 s = type;
982 break;
983 case XT_STATUS_URI:
984 type = g_strdup_printf("%s", s);
985 if (!t->status) {
986 t->status = g_strdup(type);
988 s = type;
989 if (!t->status)
990 t->status = g_strdup(s);
991 break;
992 case XT_STATUS_NOTHING:
993 /* FALL THROUGH */
994 default:
995 break;
997 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
998 if (type)
999 g_free(type);
1002 void
1003 hide_cmd(struct tab *t)
1005 gtk_widget_hide(t->cmd);
1008 void
1009 show_cmd(struct tab *t)
1011 gtk_widget_hide(t->oops);
1012 gtk_widget_show(t->cmd);
1015 void
1016 hide_buffers(struct tab *t)
1018 gtk_widget_hide(t->buffers);
1019 gtk_list_store_clear(buffers_store);
1022 enum {
1023 COL_ID = 0,
1024 COL_TITLE,
1025 NUM_COLS
1029 sort_tabs_by_page_num(struct tab ***stabs)
1031 int num_tabs = 0;
1032 struct tab *t;
1034 num_tabs = gtk_notebook_get_n_pages(notebook);
1036 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1038 TAILQ_FOREACH(t, &tabs, entry)
1039 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1041 return (num_tabs);
1044 void
1045 buffers_make_list(void)
1047 int i, num_tabs;
1048 const gchar *title = NULL;
1049 GtkTreeIter iter;
1050 struct tab **stabs = NULL;
1052 num_tabs = sort_tabs_by_page_num(&stabs);
1054 for (i = 0; i < num_tabs; i++)
1055 if (stabs[i]) {
1056 gtk_list_store_append(buffers_store, &iter);
1057 title = get_title(stabs[i], FALSE);
1058 gtk_list_store_set(buffers_store, &iter,
1059 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1060 * rather than 0. */
1061 COL_TITLE, title,
1062 -1);
1065 g_free(stabs);
1068 void
1069 show_buffers(struct tab *t)
1071 buffers_make_list();
1072 gtk_widget_show(t->buffers);
1073 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1076 void
1077 toggle_buffers(struct tab *t)
1079 if (gtk_widget_get_visible(t->buffers))
1080 hide_buffers(t);
1081 else
1082 show_buffers(t);
1086 buffers(struct tab *t, struct karg *args)
1088 show_buffers(t);
1090 return (0);
1093 void
1094 hide_oops(struct tab *t)
1096 gtk_widget_hide(t->oops);
1099 void
1100 show_oops(struct tab *at, const char *fmt, ...)
1102 va_list ap;
1103 char *msg;
1104 struct tab *t = NULL;
1106 if (fmt == NULL)
1107 return;
1109 if (at == NULL) {
1110 if ((t = get_current_tab()) == NULL)
1111 return;
1112 } else
1113 t = at;
1115 va_start(ap, fmt);
1116 if (vasprintf(&msg, fmt, ap) == -1)
1117 errx(1, "show_oops failed");
1118 va_end(ap);
1120 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1121 gtk_widget_hide(t->cmd);
1122 gtk_widget_show(t->oops);
1125 char *
1126 get_as_string(struct settings *s)
1128 char *r = NULL;
1130 if (s == NULL)
1131 return (NULL);
1133 if (s->s) {
1134 if (s->s->get)
1135 r = s->s->get(s);
1136 else
1137 warnx("get_as_string skip %s\n", s->name);
1138 } else if (s->type == XT_S_INT)
1139 r = g_strdup_printf("%d", *s->ival);
1140 else if (s->type == XT_S_STR)
1141 r = g_strdup(*s->sval);
1142 else if (s->type == XT_S_FLOAT)
1143 r = g_strdup_printf("%f", *s->fval);
1144 else
1145 r = g_strdup_printf("INVALID TYPE");
1147 return (r);
1150 void
1151 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1153 int i;
1154 char *s;
1156 for (i = 0; i < LENGTH(rs); i++) {
1157 if (rs[i].s && rs[i].s->walk)
1158 rs[i].s->walk(&rs[i], cb, cb_args);
1159 else {
1160 s = get_as_string(&rs[i]);
1161 cb(&rs[i], s, cb_args);
1162 g_free(s);
1168 set_browser_mode(struct settings *s, char *val)
1170 if (!strcmp(val, "whitelist")) {
1171 browser_mode = XT_BM_WHITELIST;
1172 allow_volatile_cookies = 0;
1173 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1174 cookies_enabled = 1;
1175 enable_cookie_whitelist = 1;
1176 read_only_cookies = 0;
1177 save_rejected_cookies = 0;
1178 session_timeout = 3600;
1179 enable_scripts = 0;
1180 enable_js_whitelist = 1;
1181 enable_localstorage = 0;
1182 } else if (!strcmp(val, "normal")) {
1183 browser_mode = XT_BM_NORMAL;
1184 allow_volatile_cookies = 0;
1185 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1186 cookies_enabled = 1;
1187 enable_cookie_whitelist = 0;
1188 read_only_cookies = 0;
1189 save_rejected_cookies = 0;
1190 session_timeout = 3600;
1191 enable_scripts = 1;
1192 enable_js_whitelist = 0;
1193 enable_localstorage = 1;
1194 } else if (!strcmp(val, "kiosk")) {
1195 browser_mode = XT_BM_KIOSK;
1196 allow_volatile_cookies = 0;
1197 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1198 cookies_enabled = 1;
1199 enable_cookie_whitelist = 0;
1200 read_only_cookies = 0;
1201 save_rejected_cookies = 0;
1202 session_timeout = 3600;
1203 enable_scripts = 1;
1204 enable_js_whitelist = 0;
1205 enable_localstorage = 1;
1206 show_tabs = 0;
1207 tabless = 1;
1208 } else
1209 return (1);
1211 return (0);
1214 char *
1215 get_browser_mode(struct settings *s)
1217 char *r = NULL;
1219 if (browser_mode == XT_BM_WHITELIST)
1220 r = g_strdup("whitelist");
1221 else if (browser_mode == XT_BM_NORMAL)
1222 r = g_strdup("normal");
1223 else if (browser_mode == XT_BM_KIOSK)
1224 r = g_strdup("kiosk");
1225 else
1226 return (NULL);
1228 return (r);
1232 set_cookie_policy(struct settings *s, char *val)
1234 if (!strcmp(val, "no3rdparty"))
1235 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1236 else if (!strcmp(val, "accept"))
1237 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1238 else if (!strcmp(val, "reject"))
1239 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1240 else
1241 return (1);
1243 return (0);
1246 char *
1247 get_cookie_policy(struct settings *s)
1249 char *r = NULL;
1251 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1252 r = g_strdup("no3rdparty");
1253 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1254 r = g_strdup("accept");
1255 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1256 r = g_strdup("reject");
1257 else
1258 return (NULL);
1260 return (r);
1263 char *
1264 get_default_script(struct settings *s)
1266 if (default_script[0] == '\0')
1267 return (0);
1268 return (g_strdup(default_script));
1272 set_default_script(struct settings *s, char *val)
1274 if (val[0] == '~')
1275 snprintf(default_script, sizeof default_script, "%s/%s",
1276 pwd->pw_dir, &val[1]);
1277 else
1278 strlcpy(default_script, val, sizeof default_script);
1280 return (0);
1283 char *
1284 get_download_dir(struct settings *s)
1286 if (download_dir[0] == '\0')
1287 return (0);
1288 return (g_strdup(download_dir));
1292 set_download_dir(struct settings *s, char *val)
1294 if (val[0] == '~')
1295 snprintf(download_dir, sizeof download_dir, "%s/%s",
1296 pwd->pw_dir, &val[1]);
1297 else
1298 strlcpy(download_dir, val, sizeof download_dir);
1300 return (0);
1304 * Session IDs.
1305 * We use these to prevent people putting xxxt:// URLs on
1306 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1308 #define XT_XTP_SES_KEY_SZ 8
1309 #define XT_XTP_SES_KEY_HEX_FMT \
1310 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1311 char *dl_session_key; /* downloads */
1312 char *hl_session_key; /* history list */
1313 char *cl_session_key; /* cookie list */
1314 char *fl_session_key; /* favorites list */
1316 char work_dir[PATH_MAX];
1317 char certs_dir[PATH_MAX];
1318 char cache_dir[PATH_MAX];
1319 char sessions_dir[PATH_MAX];
1320 char cookie_file[PATH_MAX];
1321 SoupURI *proxy_uri = NULL;
1322 SoupSession *session;
1323 SoupCookieJar *s_cookiejar;
1324 SoupCookieJar *p_cookiejar;
1325 char rc_fname[PATH_MAX];
1327 struct mime_type_list mtl;
1328 struct alias_list aliases;
1330 /* protos */
1331 struct tab *create_new_tab(char *, struct undo *, int, int);
1332 void delete_tab(struct tab *);
1333 void setzoom_webkit(struct tab *, int);
1334 int run_script(struct tab *, char *);
1335 int download_rb_cmp(struct download *, struct download *);
1336 gboolean cmd_execute(struct tab *t, char *str);
1339 history_rb_cmp(struct history *h1, struct history *h2)
1341 return (strcmp(h1->uri, h2->uri));
1343 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1346 domain_rb_cmp(struct domain *d1, struct domain *d2)
1348 return (strcmp(d1->d, d2->d));
1350 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1352 char *
1353 get_work_dir(struct settings *s)
1355 if (work_dir[0] == '\0')
1356 return (0);
1357 return (g_strdup(work_dir));
1361 set_work_dir(struct settings *s, char *val)
1363 if (val[0] == '~')
1364 snprintf(work_dir, sizeof work_dir, "%s/%s",
1365 pwd->pw_dir, &val[1]);
1366 else
1367 strlcpy(work_dir, val, sizeof work_dir);
1369 return (0);
1372 char *
1373 get_tab_style(struct settings *s)
1375 if (tab_style == XT_TABS_NORMAL)
1376 return (g_strdup("normal"));
1377 else
1378 return (g_strdup("compact"));
1382 set_tab_style(struct settings *s, char *val)
1384 if (!strcmp(val, "normal"))
1385 tab_style = XT_TABS_NORMAL;
1386 else if (!strcmp(val, "compact"))
1387 tab_style = XT_TABS_COMPACT;
1388 else
1389 return (1);
1391 return (0);
1395 * generate a session key to secure xtp commands.
1396 * pass in a ptr to the key in question and it will
1397 * be modified in place.
1399 void
1400 generate_xtp_session_key(char **key)
1402 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1404 /* free old key */
1405 if (*key)
1406 g_free(*key);
1408 /* make a new one */
1409 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1410 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1411 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1412 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1414 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1418 * validate a xtp session key.
1419 * return 1 if OK
1422 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1424 if (strcmp(trusted, untrusted) != 0) {
1425 show_oops(t, "%s: xtp session key mismatch possible spoof",
1426 __func__);
1427 return (0);
1430 return (1);
1434 download_rb_cmp(struct download *e1, struct download *e2)
1436 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1438 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1440 struct valid_url_types {
1441 char *type;
1442 } vut[] = {
1443 { "http://" },
1444 { "https://" },
1445 { "ftp://" },
1446 { "file://" },
1447 { XT_XTP_STR },
1451 valid_url_type(char *url)
1453 int i;
1455 for (i = 0; i < LENGTH(vut); i++)
1456 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1457 return (0);
1459 return (1);
1462 void
1463 print_cookie(char *msg, SoupCookie *c)
1465 if (c == NULL)
1466 return;
1468 if (msg)
1469 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1470 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1471 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1472 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1473 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1474 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1475 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1476 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1477 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1478 DNPRINTF(XT_D_COOKIE, "====================================\n");
1481 void
1482 walk_alias(struct settings *s,
1483 void (*cb)(struct settings *, char *, void *), void *cb_args)
1485 struct alias *a;
1486 char *str;
1488 if (s == NULL || cb == NULL) {
1489 show_oops(NULL, "walk_alias invalid parameters");
1490 return;
1493 TAILQ_FOREACH(a, &aliases, entry) {
1494 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1495 cb(s, str, cb_args);
1496 g_free(str);
1500 char *
1501 match_alias(char *url_in)
1503 struct alias *a;
1504 char *arg;
1505 char *url_out = NULL, *search, *enc_arg;
1507 search = g_strdup(url_in);
1508 arg = search;
1509 if (strsep(&arg, " \t") == NULL) {
1510 show_oops(NULL, "match_alias: NULL URL");
1511 goto done;
1514 TAILQ_FOREACH(a, &aliases, entry) {
1515 if (!strcmp(search, a->a_name))
1516 break;
1519 if (a != NULL) {
1520 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1521 a->a_name);
1522 if (arg != NULL) {
1523 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1524 url_out = g_strdup_printf(a->a_uri, enc_arg);
1525 g_free(enc_arg);
1526 } else
1527 url_out = g_strdup_printf(a->a_uri, "");
1529 done:
1530 g_free(search);
1531 return (url_out);
1534 char *
1535 guess_url_type(char *url_in)
1537 struct stat sb;
1538 char *url_out = NULL, *enc_search = NULL;
1540 url_out = match_alias(url_in);
1541 if (url_out != NULL)
1542 return (url_out);
1544 if (guess_search) {
1546 * If there is no dot nor slash in the string and it isn't a
1547 * path to a local file and doesn't resolves to an IP, assume
1548 * that the user wants to search for the string.
1551 if (strchr(url_in, '.') == NULL &&
1552 strchr(url_in, '/') == NULL &&
1553 stat(url_in, &sb) != 0 &&
1554 gethostbyname(url_in) == NULL) {
1556 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1557 url_out = g_strdup_printf(search_string, enc_search);
1558 g_free(enc_search);
1559 return (url_out);
1563 /* XXX not sure about this heuristic */
1564 if (stat(url_in, &sb) == 0)
1565 url_out = g_strdup_printf("file://%s", url_in);
1566 else
1567 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1569 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1571 return (url_out);
1574 void
1575 load_uri(struct tab *t, gchar *uri)
1577 struct karg args;
1578 gchar *newuri = NULL;
1579 int i;
1581 if (uri == NULL)
1582 return;
1584 /* Strip leading spaces. */
1585 while (*uri && isspace(*uri))
1586 uri++;
1588 if (strlen(uri) == 0) {
1589 blank(t, NULL);
1590 return;
1593 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1595 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1596 for (i = 0; i < LENGTH(about_list); i++)
1597 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1598 bzero(&args, sizeof args);
1599 about_list[i].func(t, &args);
1600 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1601 FALSE);
1602 return;
1604 show_oops(t, "invalid about page");
1605 return;
1608 if (valid_url_type(uri)) {
1609 newuri = guess_url_type(uri);
1610 uri = newuri;
1613 set_status(t, (char *)uri, XT_STATUS_LOADING);
1614 webkit_web_view_load_uri(t->wv, uri);
1616 if (newuri)
1617 g_free(newuri);
1620 const gchar *
1621 get_uri(struct tab *t)
1623 const gchar *uri = NULL;
1625 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1626 return t->tmp_uri;
1627 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1628 uri = webkit_web_view_get_uri(t->wv);
1629 } else {
1630 /* use tmp_uri to make sure it is g_freed */
1631 if (t->tmp_uri)
1632 g_free(t->tmp_uri);
1633 t->tmp_uri = g_strdup_printf("%s%s", XT_URI_ABOUT, about_list[t->xtp_meaning].name);
1634 uri = t->tmp_uri;
1636 return uri;
1639 const gchar *
1640 get_title(struct tab *t, bool window)
1642 const gchar *set = NULL, *title = NULL;
1643 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1645 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1646 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1647 goto notitle;
1649 title = webkit_web_view_get_title(t->wv);
1650 if ((set = title ? title : get_uri(t)))
1651 return set;
1653 notitle:
1654 set = window ? XT_NAME : "(untitled)";
1656 return set;
1660 add_alias(struct settings *s, char *line)
1662 char *l, *alias;
1663 struct alias *a = NULL;
1665 if (s == NULL || line == NULL) {
1666 show_oops(NULL, "add_alias invalid parameters");
1667 return (1);
1670 l = line;
1671 a = g_malloc(sizeof(*a));
1673 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1674 show_oops(NULL, "add_alias: incomplete alias definition");
1675 goto bad;
1677 if (strlen(alias) == 0 || strlen(l) == 0) {
1678 show_oops(NULL, "add_alias: invalid alias definition");
1679 goto bad;
1682 a->a_name = g_strdup(alias);
1683 a->a_uri = g_strdup(l);
1685 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1687 TAILQ_INSERT_TAIL(&aliases, a, entry);
1689 return (0);
1690 bad:
1691 if (a)
1692 g_free(a);
1693 return (1);
1697 add_mime_type(struct settings *s, char *line)
1699 char *mime_type;
1700 char *l;
1701 struct mime_type *m = NULL;
1702 int downloadfirst = 0;
1704 /* XXX this could be smarter */
1706 if (line == NULL || strlen(line) == 0) {
1707 show_oops(NULL, "add_mime_type invalid parameters");
1708 return (1);
1711 l = line;
1712 if (*l == '@') {
1713 downloadfirst = 1;
1714 l++;
1716 m = g_malloc(sizeof(*m));
1718 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1719 show_oops(NULL, "add_mime_type: invalid mime_type");
1720 goto bad;
1722 if (mime_type[strlen(mime_type) - 1] == '*') {
1723 mime_type[strlen(mime_type) - 1] = '\0';
1724 m->mt_default = 1;
1725 } else
1726 m->mt_default = 0;
1728 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1729 show_oops(NULL, "add_mime_type: invalid mime_type");
1730 goto bad;
1733 m->mt_type = g_strdup(mime_type);
1734 m->mt_action = g_strdup(l);
1735 m->mt_download = downloadfirst;
1737 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1738 m->mt_type, m->mt_action, m->mt_default);
1740 TAILQ_INSERT_TAIL(&mtl, m, entry);
1742 return (0);
1743 bad:
1744 if (m)
1745 g_free(m);
1746 return (1);
1749 struct mime_type *
1750 find_mime_type(char *mime_type)
1752 struct mime_type *m, *def = NULL, *rv = NULL;
1754 TAILQ_FOREACH(m, &mtl, entry) {
1755 if (m->mt_default &&
1756 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1757 def = m;
1759 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1760 rv = m;
1761 break;
1765 if (rv == NULL)
1766 rv = def;
1768 return (rv);
1771 void
1772 walk_mime_type(struct settings *s,
1773 void (*cb)(struct settings *, char *, void *), void *cb_args)
1775 struct mime_type *m;
1776 char *str;
1778 if (s == NULL || cb == NULL) {
1779 show_oops(NULL, "walk_mime_type invalid parameters");
1780 return;
1783 TAILQ_FOREACH(m, &mtl, entry) {
1784 str = g_strdup_printf("%s%s --> %s",
1785 m->mt_type,
1786 m->mt_default ? "*" : "",
1787 m->mt_action);
1788 cb(s, str, cb_args);
1789 g_free(str);
1793 void
1794 wl_add(char *str, struct domain_list *wl, int handy)
1796 struct domain *d;
1797 int add_dot = 0;
1799 if (str == NULL || wl == NULL || strlen(str) < 2)
1800 return;
1802 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1804 /* treat *.moo.com the same as .moo.com */
1805 if (str[0] == '*' && str[1] == '.')
1806 str = &str[1];
1807 else if (str[0] == '.')
1808 str = &str[0];
1809 else
1810 add_dot = 1;
1812 d = g_malloc(sizeof *d);
1813 if (add_dot)
1814 d->d = g_strdup_printf(".%s", str);
1815 else
1816 d->d = g_strdup(str);
1817 d->handy = handy;
1819 if (RB_INSERT(domain_list, wl, d))
1820 goto unwind;
1822 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1823 return;
1824 unwind:
1825 if (d) {
1826 if (d->d)
1827 g_free(d->d);
1828 g_free(d);
1833 add_cookie_wl(struct settings *s, char *entry)
1835 wl_add(entry, &c_wl, 1);
1836 return (0);
1839 void
1840 walk_cookie_wl(struct settings *s,
1841 void (*cb)(struct settings *, char *, void *), void *cb_args)
1843 struct domain *d;
1845 if (s == NULL || cb == NULL) {
1846 show_oops(NULL, "walk_cookie_wl invalid parameters");
1847 return;
1850 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1851 cb(s, d->d, cb_args);
1854 void
1855 walk_js_wl(struct settings *s,
1856 void (*cb)(struct settings *, char *, void *), void *cb_args)
1858 struct domain *d;
1860 if (s == NULL || cb == NULL) {
1861 show_oops(NULL, "walk_js_wl invalid parameters");
1862 return;
1865 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1866 cb(s, d->d, cb_args);
1870 add_js_wl(struct settings *s, char *entry)
1872 wl_add(entry, &js_wl, 1 /* persistent */);
1873 return (0);
1876 struct domain *
1877 wl_find(const gchar *search, struct domain_list *wl)
1879 int i;
1880 struct domain *d = NULL, dfind;
1881 gchar *s = NULL;
1883 if (search == NULL || wl == NULL)
1884 return (NULL);
1885 if (strlen(search) < 2)
1886 return (NULL);
1888 if (search[0] != '.')
1889 s = g_strdup_printf(".%s", search);
1890 else
1891 s = g_strdup(search);
1893 for (i = strlen(s) - 1; i >= 0; i--) {
1894 if (s[i] == '.') {
1895 dfind.d = &s[i];
1896 d = RB_FIND(domain_list, wl, &dfind);
1897 if (d)
1898 goto done;
1902 done:
1903 if (s)
1904 g_free(s);
1906 return (d);
1909 struct domain *
1910 wl_find_uri(const gchar *s, struct domain_list *wl)
1912 int i;
1913 char *ss;
1914 struct domain *r;
1916 if (s == NULL || wl == NULL)
1917 return (NULL);
1919 if (!strncmp(s, "http://", strlen("http://")))
1920 s = &s[strlen("http://")];
1921 else if (!strncmp(s, "https://", strlen("https://")))
1922 s = &s[strlen("https://")];
1924 if (strlen(s) < 2)
1925 return (NULL);
1927 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1928 /* chop string at first slash */
1929 if (s[i] == '/' || s[i] == '\0') {
1930 ss = g_strdup(s);
1931 ss[i] = '\0';
1932 r = wl_find(ss, wl);
1933 g_free(ss);
1934 return (r);
1937 return (NULL);
1941 settings_add(char *var, char *val)
1943 int i, rv, *p;
1944 gfloat *f;
1945 char **s;
1947 /* get settings */
1948 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1949 if (strcmp(var, rs[i].name))
1950 continue;
1952 if (rs[i].s) {
1953 if (rs[i].s->set(&rs[i], val))
1954 errx(1, "invalid value for %s: %s", var, val);
1955 rv = 1;
1956 break;
1957 } else
1958 switch (rs[i].type) {
1959 case XT_S_INT:
1960 p = rs[i].ival;
1961 *p = atoi(val);
1962 rv = 1;
1963 break;
1964 case XT_S_STR:
1965 s = rs[i].sval;
1966 if (s == NULL)
1967 errx(1, "invalid sval for %s",
1968 rs[i].name);
1969 if (*s)
1970 g_free(*s);
1971 *s = g_strdup(val);
1972 rv = 1;
1973 break;
1974 case XT_S_FLOAT:
1975 f = rs[i].fval;
1976 *f = atof(val);
1977 rv = 1;
1978 break;
1979 case XT_S_INVALID:
1980 default:
1981 errx(1, "invalid type for %s", var);
1983 break;
1985 return (rv);
1988 #define WS "\n= \t"
1989 void
1990 config_parse(char *filename, int runtime)
1992 FILE *config, *f;
1993 char *line, *cp, *var, *val;
1994 size_t len, lineno = 0;
1995 int handled;
1996 char file[PATH_MAX];
1997 struct stat sb;
1999 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2001 if (filename == NULL)
2002 return;
2004 if (runtime && runtime_settings[0] != '\0') {
2005 snprintf(file, sizeof file, "%s/%s",
2006 work_dir, runtime_settings);
2007 if (stat(file, &sb)) {
2008 warnx("runtime file doesn't exist, creating it");
2009 if ((f = fopen(file, "w")) == NULL)
2010 err(1, "runtime");
2011 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2012 fclose(f);
2014 } else
2015 strlcpy(file, filename, sizeof file);
2017 if ((config = fopen(file, "r")) == NULL) {
2018 warn("config_parse: cannot open %s", filename);
2019 return;
2022 for (;;) {
2023 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2024 if (feof(config) || ferror(config))
2025 break;
2027 cp = line;
2028 cp += (long)strspn(cp, WS);
2029 if (cp[0] == '\0') {
2030 /* empty line */
2031 free(line);
2032 continue;
2035 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2036 errx(1, "invalid config file entry: %s", line);
2038 cp += (long)strspn(cp, WS);
2040 if ((val = strsep(&cp, "\0")) == NULL)
2041 break;
2043 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n", var, val);
2044 handled = settings_add(var, val);
2045 if (handled == 0)
2046 errx(1, "invalid conf file entry: %s=%s", var, val);
2048 free(line);
2051 fclose(config);
2054 char *
2055 js_ref_to_string(JSContextRef context, JSValueRef ref)
2057 char *s = NULL;
2058 size_t l;
2059 JSStringRef jsref;
2061 jsref = JSValueToStringCopy(context, ref, NULL);
2062 if (jsref == NULL)
2063 return (NULL);
2065 l = JSStringGetMaximumUTF8CStringSize(jsref);
2066 s = g_malloc(l);
2067 if (s)
2068 JSStringGetUTF8CString(jsref, s, l);
2069 JSStringRelease(jsref);
2071 return (s);
2074 void
2075 disable_hints(struct tab *t)
2077 bzero(t->hint_buf, sizeof t->hint_buf);
2078 bzero(t->hint_num, sizeof t->hint_num);
2079 run_script(t, "vimprobable_clear()");
2080 t->hints_on = 0;
2081 t->hint_mode = XT_HINT_NONE;
2084 void
2085 enable_hints(struct tab *t)
2087 bzero(t->hint_buf, sizeof t->hint_buf);
2088 run_script(t, "vimprobable_show_hints()");
2089 t->hints_on = 1;
2090 t->hint_mode = XT_HINT_NONE;
2093 #define XT_JS_OPEN ("open;")
2094 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
2095 #define XT_JS_FIRE ("fire;")
2096 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
2097 #define XT_JS_FOUND ("found;")
2098 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
2101 run_script(struct tab *t, char *s)
2103 JSGlobalContextRef ctx;
2104 WebKitWebFrame *frame;
2105 JSStringRef str;
2106 JSValueRef val, exception;
2107 char *es, buf[128];
2109 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2110 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2112 frame = webkit_web_view_get_main_frame(t->wv);
2113 ctx = webkit_web_frame_get_global_context(frame);
2115 str = JSStringCreateWithUTF8CString(s);
2116 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2117 NULL, 0, &exception);
2118 JSStringRelease(str);
2120 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2121 if (val == NULL) {
2122 es = js_ref_to_string(ctx, exception);
2123 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2124 g_free(es);
2125 return (1);
2126 } else {
2127 es = js_ref_to_string(ctx, val);
2128 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2130 /* handle return value right here */
2131 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
2132 disable_hints(t);
2133 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
2136 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
2137 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
2138 &es[XT_JS_FIRE_LEN]);
2139 run_script(t, buf);
2140 disable_hints(t);
2143 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
2144 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
2145 disable_hints(t);
2148 g_free(es);
2151 return (0);
2155 hint(struct tab *t, struct karg *args)
2158 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
2160 if (t->hints_on == 0)
2161 enable_hints(t);
2162 else
2163 disable_hints(t);
2165 return (0);
2168 void
2169 apply_style(struct tab *t)
2171 g_object_set(G_OBJECT(t->settings),
2172 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2176 userstyle(struct tab *t, struct karg *args)
2178 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2180 if (t->styled) {
2181 t->styled = 0;
2182 g_object_set(G_OBJECT(t->settings),
2183 "user-stylesheet-uri", NULL, (char *)NULL);
2184 } else {
2185 t->styled = 1;
2186 apply_style(t);
2188 return (0);
2192 * Doesn't work fully, due to the following bug:
2193 * https://bugs.webkit.org/show_bug.cgi?id=51747
2196 restore_global_history(void)
2198 char file[PATH_MAX];
2199 FILE *f;
2200 struct history *h;
2201 gchar *uri;
2202 gchar *title;
2203 const char delim[3] = {'\\', '\\', '\0'};
2205 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2207 if ((f = fopen(file, "r")) == NULL) {
2208 warnx("%s: fopen", __func__);
2209 return (1);
2212 for (;;) {
2213 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2214 if (feof(f) || ferror(f))
2215 break;
2217 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2218 if (feof(f) || ferror(f)) {
2219 free(uri);
2220 warnx("%s: broken history file\n", __func__);
2221 return (1);
2224 if (uri && strlen(uri) && title && strlen(title)) {
2225 webkit_web_history_item_new_with_data(uri, title);
2226 h = g_malloc(sizeof(struct history));
2227 h->uri = g_strdup(uri);
2228 h->title = g_strdup(title);
2229 RB_INSERT(history_list, &hl, h);
2230 completion_add_uri(h->uri);
2231 } else {
2232 warnx("%s: failed to restore history\n", __func__);
2233 free(uri);
2234 free(title);
2235 return (1);
2238 free(uri);
2239 free(title);
2240 uri = NULL;
2241 title = NULL;
2244 return (0);
2248 save_global_history_to_disk(struct tab *t)
2250 char file[PATH_MAX];
2251 FILE *f;
2252 struct history *h;
2254 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2256 if ((f = fopen(file, "w")) == NULL) {
2257 show_oops(t, "%s: global history file: %s",
2258 __func__, strerror(errno));
2259 return (1);
2262 RB_FOREACH_REVERSE(h, history_list, &hl) {
2263 if (h->uri && h->title)
2264 fprintf(f, "%s\n%s\n", h->uri, h->title);
2267 fclose(f);
2269 return (0);
2273 quit(struct tab *t, struct karg *args)
2275 if (save_global_history)
2276 save_global_history_to_disk(t);
2278 gtk_main_quit();
2280 return (1);
2284 open_tabs(struct tab *t, struct karg *a)
2286 char file[PATH_MAX];
2287 FILE *f = NULL;
2288 char *uri = NULL;
2289 int rv = 1;
2290 struct tab *ti, *tt;
2292 if (a == NULL)
2293 goto done;
2295 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2296 if ((f = fopen(file, "r")) == NULL)
2297 goto done;
2299 ti = TAILQ_LAST(&tabs, tab_list);
2301 for (;;) {
2302 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2303 if (feof(f) || ferror(f))
2304 break;
2306 /* retrieve session name */
2307 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2308 strlcpy(named_session,
2309 &uri[strlen(XT_SAVE_SESSION_ID)],
2310 sizeof named_session);
2311 continue;
2314 if (uri && strlen(uri))
2315 create_new_tab(uri, NULL, 1, -1);
2317 free(uri);
2318 uri = NULL;
2321 /* close open tabs */
2322 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2323 for (;;) {
2324 tt = TAILQ_FIRST(&tabs);
2325 if (tt != ti) {
2326 delete_tab(tt);
2327 continue;
2329 delete_tab(tt);
2330 break;
2332 recalc_tabs();
2335 rv = 0;
2336 done:
2337 if (f)
2338 fclose(f);
2340 return (rv);
2344 restore_saved_tabs(void)
2346 char file[PATH_MAX];
2347 int unlink_file = 0;
2348 struct stat sb;
2349 struct karg a;
2350 int rv = 0;
2352 snprintf(file, sizeof file, "%s/%s",
2353 sessions_dir, XT_RESTART_TABS_FILE);
2354 if (stat(file, &sb) == -1)
2355 a.s = XT_SAVED_TABS_FILE;
2356 else {
2357 unlink_file = 1;
2358 a.s = XT_RESTART_TABS_FILE;
2361 a.i = XT_SES_DONOTHING;
2362 rv = open_tabs(NULL, &a);
2364 if (unlink_file)
2365 unlink(file);
2367 return (rv);
2371 save_tabs(struct tab *t, struct karg *a)
2373 char file[PATH_MAX];
2374 FILE *f;
2375 int num_tabs = 0, i;
2376 struct tab **stabs = NULL;
2378 if (a == NULL)
2379 return (1);
2380 if (a->s == NULL)
2381 snprintf(file, sizeof file, "%s/%s",
2382 sessions_dir, named_session);
2383 else
2384 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2386 if ((f = fopen(file, "w")) == NULL) {
2387 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2388 return (1);
2391 /* save session name */
2392 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2394 /* Save tabs, in the order they are arranged in the notebook. */
2395 num_tabs = sort_tabs_by_page_num(&stabs);
2397 for (i = 0; i < num_tabs; i++)
2398 if (stabs[i] && get_uri(stabs[i]) != NULL)
2399 fprintf(f, "%s\n", get_uri(stabs[i]));
2401 g_free(stabs);
2403 /* try and make sure this gets to disk NOW. XXX Backup first? */
2404 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2405 show_oops(t, "May not have managed to save session: %s",
2406 strerror(errno));
2409 fclose(f);
2411 return (0);
2415 save_tabs_and_quit(struct tab *t, struct karg *args)
2417 struct karg a;
2419 a.s = NULL;
2420 save_tabs(t, &a);
2421 quit(t, NULL);
2423 return (1);
2427 run_page_script(struct tab *t, struct karg *args)
2429 const gchar *uri;
2430 char *tmp, script[PATH_MAX];
2432 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2433 if (tmp[0] == '\0') {
2434 show_oops(t, "no script specified");
2435 return (1);
2438 if ((uri = get_uri(t)) == NULL) {
2439 show_oops(t, "tab is empty, not running script");
2440 return (1);
2443 if (tmp[0] == '~')
2444 snprintf(script, sizeof script, "%s/%s",
2445 pwd->pw_dir, &tmp[1]);
2446 else
2447 strlcpy(script, tmp, sizeof script);
2449 switch (fork()) {
2450 case -1:
2451 show_oops(t, "can't fork to run script");
2452 return (1);
2453 /* NOTREACHED */
2454 case 0:
2455 break;
2456 default:
2457 return (0);
2460 /* child */
2461 execlp(script, script, uri, (void *)NULL);
2463 _exit(0);
2465 /* NOTREACHED */
2467 return (0);
2471 yank_uri(struct tab *t, struct karg *args)
2473 const gchar *uri;
2474 GtkClipboard *clipboard;
2476 if ((uri = get_uri(t)) == NULL)
2477 return (1);
2479 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2480 gtk_clipboard_set_text(clipboard, uri, -1);
2482 return (0);
2486 paste_uri(struct tab *t, struct karg *args)
2488 GtkClipboard *clipboard;
2489 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2490 gint len;
2491 gchar *p = NULL, *uri;
2493 /* try primary clipboard first */
2494 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2495 p = gtk_clipboard_wait_for_text(clipboard);
2497 /* if it failed get whatever text is in cut_buffer0 */
2498 if (p == NULL)
2499 if (gdk_property_get(gdk_get_default_root_window(),
2500 atom,
2501 gdk_atom_intern("STRING", FALSE),
2503 65536 /* picked out of my butt */,
2504 FALSE,
2505 NULL,
2506 NULL,
2507 &len,
2508 (guchar **)&p)) {
2509 /* yes sir, we need to NUL the string */
2510 p[len] = '\0';
2513 if (p) {
2514 uri = p;
2515 while (*uri && isspace(*uri))
2516 uri++;
2517 if (strlen(uri) == 0) {
2518 show_oops(t, "empty paste buffer");
2519 goto done;
2521 if (guess_search == 0 && valid_url_type(uri)) {
2522 /* we can be clever and paste this in search box */
2523 show_oops(t, "not a valid URL");
2524 goto done;
2527 if (args->i == XT_PASTE_CURRENT_TAB)
2528 load_uri(t, uri);
2529 else if (args->i == XT_PASTE_NEW_TAB)
2530 create_new_tab(uri, NULL, 1, -1);
2533 done:
2534 if (p)
2535 g_free(p);
2537 return (0);
2540 gchar *
2541 find_domain(const gchar *s, int toplevel)
2543 SoupURI *uri;
2544 gchar *ret, *p;
2546 if (s == NULL)
2547 return (NULL);
2549 uri = soup_uri_new(s);
2551 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2552 return (NULL);
2555 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2556 if ((p = strrchr(uri->host, '.')) != NULL) {
2557 while(--p >= uri->host && *p != '.');
2558 p++;
2559 } else
2560 p = uri->host;
2561 } else
2562 p = uri->host;
2564 if (uri->port == 80)
2565 ret = g_strdup_printf(".%s", p);
2566 else
2567 ret = g_strdup_printf(".%s:%d", p, uri->port);
2569 soup_uri_free(uri);
2571 return ret;
2575 toggle_cwl(struct tab *t, struct karg *args)
2577 struct domain *d;
2578 const gchar *uri;
2579 char *dom = NULL;
2580 int es;
2582 if (args == NULL)
2583 return (1);
2585 uri = get_uri(t);
2586 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2588 if (uri == NULL || dom == NULL || webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2589 show_oops(t, "Can't toggle domain in cookie white list");
2590 goto done;
2592 d = wl_find(dom, &c_wl);
2594 if (d == NULL)
2595 es = 0;
2596 else
2597 es = 1;
2599 if (args->i & XT_WL_TOGGLE)
2600 es = !es;
2601 else if ((args->i & XT_WL_ENABLE) && es != 1)
2602 es = 1;
2603 else if ((args->i & XT_WL_DISABLE) && es != 0)
2604 es = 0;
2606 if (es)
2607 /* enable cookies for domain */
2608 wl_add(dom, &c_wl, 0);
2609 else
2610 /* disable cookies for domain */
2611 RB_REMOVE(domain_list, &c_wl, d);
2613 if (args->i & XT_WL_RELOAD)
2614 webkit_web_view_reload(t->wv);
2616 done:
2617 g_free(dom);
2618 return (0);
2622 toggle_js(struct tab *t, struct karg *args)
2624 int es;
2625 const gchar *uri;
2626 struct domain *d;
2627 char *dom = NULL;
2629 if (args == NULL)
2630 return (1);
2632 g_object_get(G_OBJECT(t->settings),
2633 "enable-scripts", &es, (char *)NULL);
2634 if (args->i & XT_WL_TOGGLE)
2635 es = !es;
2636 else if ((args->i & XT_WL_ENABLE) && es != 1)
2637 es = 1;
2638 else if ((args->i & XT_WL_DISABLE) && es != 0)
2639 es = 0;
2640 else
2641 return (1);
2643 uri = get_uri(t);
2644 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2646 if (uri == NULL || dom == NULL || webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2647 show_oops(t, "Can't toggle domain in JavaScript white list");
2648 goto done;
2651 if (es) {
2652 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2653 wl_add(dom, &js_wl, 0 /* session */);
2654 } else {
2655 d = wl_find(dom, &js_wl);
2656 if (d)
2657 RB_REMOVE(domain_list, &js_wl, d);
2658 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2660 g_object_set(G_OBJECT(t->settings),
2661 "enable-scripts", es, (char *)NULL);
2662 g_object_set(G_OBJECT(t->settings),
2663 "javascript-can-open-windows-automatically", es, (char *)NULL);
2664 webkit_web_view_set_settings(t->wv, t->settings);
2666 if (args->i & XT_WL_RELOAD)
2667 webkit_web_view_reload(t->wv);
2668 done:
2669 if (dom)
2670 g_free(dom);
2671 return (0);
2674 void
2675 js_toggle_cb(GtkWidget *w, struct tab *t)
2677 struct karg a;
2679 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2680 toggle_cwl(t, &a);
2682 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2683 toggle_js(t, &a);
2687 toggle_src(struct tab *t, struct karg *args)
2689 gboolean mode;
2691 if (t == NULL)
2692 return (0);
2694 mode = webkit_web_view_get_view_source_mode(t->wv);
2695 webkit_web_view_set_view_source_mode(t->wv, !mode);
2696 webkit_web_view_reload(t->wv);
2698 return (0);
2701 void
2702 focus_webview(struct tab *t)
2704 if (t == NULL)
2705 return;
2707 /* only grab focus if we are visible */
2708 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2709 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2713 focus(struct tab *t, struct karg *args)
2715 if (t == NULL || args == NULL)
2716 return (1);
2718 if (show_url == 0)
2719 return (0);
2721 if (args->i == XT_FOCUS_URI)
2722 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2723 else if (args->i == XT_FOCUS_SEARCH)
2724 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2726 return (0);
2730 stats(struct tab *t, struct karg *args)
2732 char *page, *body, *s, line[64 * 1024];
2733 uint64_t line_count = 0;
2734 FILE *r_cookie_f;
2736 if (t == NULL)
2737 show_oops(NULL, "stats invalid parameters");
2739 line[0] = '\0';
2740 if (save_rejected_cookies) {
2741 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2742 for (;;) {
2743 s = fgets(line, sizeof line, r_cookie_f);
2744 if (s == NULL || feof(r_cookie_f) ||
2745 ferror(r_cookie_f))
2746 break;
2747 line_count++;
2749 fclose(r_cookie_f);
2750 snprintf(line, sizeof line,
2751 "<br/>Cookies blocked(*) total: %llu", line_count);
2752 } else
2753 show_oops(t, "Can't open blocked cookies file: %s",
2754 strerror(errno));
2757 body = g_strdup_printf(
2758 "Cookies blocked(*) this session: %llu"
2759 "%s"
2760 "<p><small><b>*</b> results vary based on settings</small></p>",
2761 blocked_cookies,
2762 line);
2764 page = get_html_page("Statistics", body, "", 0);
2765 g_free(body);
2767 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2768 g_free(page);
2770 return (0);
2774 marco(struct tab *t, struct karg *args)
2776 char *page, line[64 * 1024];
2777 int len;
2779 if (t == NULL)
2780 show_oops(NULL, "marco invalid parameters");
2782 line[0] = '\0';
2783 snprintf(line, sizeof line, "%s", marco_message(&len));
2785 page = get_html_page("Marco Sez...", line, "", 0);
2787 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2788 g_free(page);
2790 return (0);
2794 blank(struct tab *t, struct karg *args)
2796 if (t == NULL)
2797 show_oops(NULL, "blank invalid parameters");
2799 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2801 return (0);
2804 about(struct tab *t, struct karg *args)
2806 char *page, *body;
2808 if (t == NULL)
2809 show_oops(NULL, "about invalid parameters");
2811 body = g_strdup_printf("<b>Version: %s</b><p>"
2812 "Authors:"
2813 "<ul>"
2814 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2815 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2816 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2817 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2818 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2819 "</ul>"
2820 "Copyrights and licenses can be found on the XXXTerm "
2821 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2822 version
2825 page = get_html_page("About", body, "", 0);
2826 g_free(body);
2828 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2829 g_free(page);
2831 return (0);
2835 help(struct tab *t, struct karg *args)
2837 char *page, *head, *body;
2839 if (t == NULL)
2840 show_oops(NULL, "help invalid parameters");
2842 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2843 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2844 "</head>\n";
2845 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
2846 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2847 "cgi-bin/man-cgi?xxxterm</a>";
2849 page = get_html_page(XT_NAME, body, head, FALSE);
2851 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2852 g_free(page);
2854 return (0);
2858 * update all favorite tabs apart from one. Pass NULL if
2859 * you want to update all.
2861 void
2862 update_favorite_tabs(struct tab *apart_from)
2864 struct tab *t;
2865 if (!updating_fl_tabs) {
2866 updating_fl_tabs = 1; /* stop infinite recursion */
2867 TAILQ_FOREACH(t, &tabs, entry)
2868 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2869 && (t != apart_from))
2870 xtp_page_fl(t, NULL);
2871 updating_fl_tabs = 0;
2875 /* show a list of favorites (bookmarks) */
2877 xtp_page_fl(struct tab *t, struct karg *args)
2879 char file[PATH_MAX];
2880 FILE *f;
2881 char *uri = NULL, *title = NULL;
2882 size_t len, lineno = 0;
2883 int i, failed = 0;
2884 char *body, *tmp, *page = NULL;
2885 const char delim[3] = {'\\', '\\', '\0'};
2887 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2889 if (t == NULL)
2890 warn("%s: bad param", __func__);
2892 /* new session key */
2893 if (!updating_fl_tabs)
2894 generate_xtp_session_key(&fl_session_key);
2896 /* open favorites */
2897 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2898 if ((f = fopen(file, "r")) == NULL) {
2899 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2900 return (1);
2903 /* body */
2904 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2905 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2906 "<th style='width: 40px'>Rm</th></tr>\n");
2908 for (i = 1;;) {
2909 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2910 if (feof(f) || ferror(f))
2911 break;
2912 if (len == 0) {
2913 free(title);
2914 title = NULL;
2915 continue;
2918 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2919 if (feof(f) || ferror(f)) {
2920 show_oops(t, "favorites file corrupt");
2921 failed = 1;
2922 break;
2925 tmp = body;
2926 body = g_strdup_printf("%s<tr>"
2927 "<td>%d</td>"
2928 "<td><a href='%s'>%s</a></td>"
2929 "<td style='text-align: center'>"
2930 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2931 "</tr>\n",
2932 body, i, uri, title,
2933 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2935 g_free(tmp);
2937 free(uri);
2938 uri = NULL;
2939 free(title);
2940 title = NULL;
2941 i++;
2943 fclose(f);
2945 /* if none, say so */
2946 if (i == 1) {
2947 tmp = body;
2948 body = g_strdup_printf("%s<tr>"
2949 "<td colspan='3' style='text-align: center'>"
2950 "No favorites - To add one use the 'favadd' command."
2951 "</td></tr>", body);
2952 g_free(tmp);
2955 tmp = body;
2956 body = g_strdup_printf("%s</table>", body);
2957 g_free(tmp);
2959 if (uri)
2960 free(uri);
2961 if (title)
2962 free(title);
2964 /* render */
2965 if (!failed) {
2966 page = get_html_page("Favorites", body, "", 1);
2967 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2968 g_free(page);
2971 update_favorite_tabs(t);
2973 if (body)
2974 g_free(body);
2976 return (failed);
2979 void
2980 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2981 size_t cert_count, char *title)
2983 gnutls_datum_t cinfo;
2984 char *tmp, *body;
2985 int i;
2987 body = g_strdup("");
2989 for (i = 0; i < cert_count; i++) {
2990 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2991 &cinfo))
2992 return;
2994 tmp = body;
2995 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2996 body, i, cinfo.data);
2997 gnutls_free(cinfo.data);
2998 g_free(tmp);
3001 tmp = get_html_page(title, body, "", 0);
3002 g_free(body);
3004 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3005 g_free(tmp);
3009 ca_cmd(struct tab *t, struct karg *args)
3011 FILE *f = NULL;
3012 int rv = 1, certs = 0, certs_read;
3013 struct stat sb;
3014 gnutls_datum_t dt;
3015 gnutls_x509_crt_t *c = NULL;
3016 char *certs_buf = NULL, *s;
3018 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3019 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3020 return (1);
3023 if (fstat(fileno(f), &sb) == -1) {
3024 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3025 goto done;
3028 certs_buf = g_malloc(sb.st_size + 1);
3029 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3030 show_oops(t, "Can't read CA file: %s", strerror(errno));
3031 goto done;
3033 certs_buf[sb.st_size] = '\0';
3035 s = certs_buf;
3036 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3037 certs++;
3038 s += strlen("BEGIN CERTIFICATE");
3041 bzero(&dt, sizeof dt);
3042 dt.data = (unsigned char *)certs_buf;
3043 dt.size = sb.st_size;
3044 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3045 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3046 GNUTLS_X509_FMT_PEM, 0);
3047 if (certs_read <= 0) {
3048 show_oops(t, "No cert(s) available");
3049 goto done;
3051 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3052 done:
3053 if (c)
3054 g_free(c);
3055 if (certs_buf)
3056 g_free(certs_buf);
3057 if (f)
3058 fclose(f);
3060 return (rv);
3064 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
3066 SoupURI *su = NULL;
3067 struct addrinfo hints, *res = NULL, *ai;
3068 int s = -1, on;
3069 char port[8];
3071 if (uri && !g_str_has_prefix(uri, "https://"))
3072 goto done;
3074 su = soup_uri_new(uri);
3075 if (su == NULL)
3076 goto done;
3077 if (!SOUP_URI_VALID_FOR_HTTP(su))
3078 goto done;
3080 snprintf(port, sizeof port, "%d", su->port);
3081 bzero(&hints, sizeof(struct addrinfo));
3082 hints.ai_flags = AI_CANONNAME;
3083 hints.ai_family = AF_UNSPEC;
3084 hints.ai_socktype = SOCK_STREAM;
3086 if (getaddrinfo(su->host, port, &hints, &res))
3087 goto done;
3089 for (ai = res; ai; ai = ai->ai_next) {
3090 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3091 continue;
3093 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3094 if (s < 0)
3095 goto done;
3096 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3097 sizeof(on)) == -1)
3098 goto done;
3100 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
3101 goto done;
3104 if (domain)
3105 strlcpy(domain, su->host, domain_sz);
3106 done:
3107 if (su)
3108 soup_uri_free(su);
3109 if (res)
3110 freeaddrinfo(res);
3112 return (s);
3116 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3118 if (gsession)
3119 gnutls_deinit(gsession);
3120 if (xcred)
3121 gnutls_certificate_free_credentials(xcred);
3123 return (0);
3127 start_tls(struct tab *t, int s, gnutls_session_t *gs,
3128 gnutls_certificate_credentials_t *xc)
3130 gnutls_certificate_credentials_t xcred;
3131 gnutls_session_t gsession;
3132 int rv = 1;
3134 if (gs == NULL || xc == NULL)
3135 goto done;
3137 *gs = NULL;
3138 *xc = NULL;
3140 gnutls_certificate_allocate_credentials(&xcred);
3141 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3142 GNUTLS_X509_FMT_PEM);
3143 gnutls_init(&gsession, GNUTLS_CLIENT);
3144 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3145 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3146 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3147 if ((rv = gnutls_handshake(gsession)) < 0) {
3148 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
3150 gnutls_error_is_fatal(rv),
3151 gnutls_strerror_name(rv));
3152 stop_tls(gsession, xcred);
3153 goto done;
3156 gnutls_credentials_type_t cred;
3157 cred = gnutls_auth_get_type(gsession);
3158 if (cred != GNUTLS_CRD_CERTIFICATE) {
3159 stop_tls(gsession, xcred);
3160 goto done;
3163 *gs = gsession;
3164 *xc = xcred;
3165 rv = 0;
3166 done:
3167 return (rv);
3171 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3172 size_t *cert_count)
3174 unsigned int len;
3175 const gnutls_datum_t *cl;
3176 gnutls_x509_crt_t *all_certs;
3177 int i, rv = 1;
3179 if (certs == NULL || cert_count == NULL)
3180 goto done;
3181 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3182 goto done;
3183 cl = gnutls_certificate_get_peers(gsession, &len);
3184 if (len == 0)
3185 goto done;
3187 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3188 for (i = 0; i < len; i++) {
3189 gnutls_x509_crt_init(&all_certs[i]);
3190 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3191 GNUTLS_X509_FMT_PEM < 0)) {
3192 g_free(all_certs);
3193 goto done;
3197 *certs = all_certs;
3198 *cert_count = len;
3199 rv = 0;
3200 done:
3201 return (rv);
3204 void
3205 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3207 int i;
3209 for (i = 0; i < cert_count; i++)
3210 gnutls_x509_crt_deinit(certs[i]);
3211 g_free(certs);
3214 void
3215 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3217 GdkColor c_text, c_base;
3219 gdk_color_parse(text, &c_text);
3220 gdk_color_parse(base, &c_base);
3222 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3223 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3224 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3226 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3227 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3228 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3231 void
3232 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3233 size_t cert_count, char *domain)
3235 size_t cert_buf_sz;
3236 char cert_buf[64 * 1024], file[PATH_MAX];
3237 int i;
3238 FILE *f;
3239 GdkColor color;
3241 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3242 return;
3244 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3245 if ((f = fopen(file, "w")) == NULL) {
3246 show_oops(t, "Can't create cert file %s %s",
3247 file, strerror(errno));
3248 return;
3251 for (i = 0; i < cert_count; i++) {
3252 cert_buf_sz = sizeof cert_buf;
3253 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3254 cert_buf, &cert_buf_sz)) {
3255 show_oops(t, "gnutls_x509_crt_export failed");
3256 goto done;
3258 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3259 show_oops(t, "Can't write certs: %s", strerror(errno));
3260 goto done;
3264 /* not the best spot but oh well */
3265 gdk_color_parse("lightblue", &color);
3266 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3267 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3268 done:
3269 fclose(f);
3273 load_compare_cert(struct tab *t, struct karg *args)
3275 const gchar *uri;
3276 char domain[8182], file[PATH_MAX];
3277 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3278 int s = -1, rv = 1, i;
3279 size_t cert_count;
3280 FILE *f = NULL;
3281 size_t cert_buf_sz;
3282 gnutls_session_t gsession;
3283 gnutls_x509_crt_t *certs;
3284 gnutls_certificate_credentials_t xcred;
3286 if (t == NULL)
3287 return (1);
3289 if ((uri = get_uri(t)) == NULL)
3290 return (1);
3292 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3293 return (1);
3295 /* go ssl/tls */
3296 if (start_tls(t, s, &gsession, &xcred)) {
3297 show_oops(t, "Start TLS failed");
3298 goto done;
3301 /* get certs */
3302 if (get_connection_certs(gsession, &certs, &cert_count)) {
3303 show_oops(t, "Can't get connection certificates");
3304 goto done;
3307 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3308 if ((f = fopen(file, "r")) == NULL)
3309 goto freeit;
3311 for (i = 0; i < cert_count; i++) {
3312 cert_buf_sz = sizeof cert_buf;
3313 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3314 cert_buf, &cert_buf_sz)) {
3315 goto freeit;
3317 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3318 rv = -1; /* critical */
3319 goto freeit;
3321 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3322 rv = -1; /* critical */
3323 goto freeit;
3327 rv = 0;
3328 freeit:
3329 if (f)
3330 fclose(f);
3331 free_connection_certs(certs, cert_count);
3332 done:
3333 /* we close the socket first for speed */
3334 if (s != -1)
3335 close(s);
3336 stop_tls(gsession, xcred);
3338 return (rv);
3342 cert_cmd(struct tab *t, struct karg *args)
3344 const gchar *uri;
3345 char domain[8182];
3346 int s = -1;
3347 size_t cert_count;
3348 gnutls_session_t gsession;
3349 gnutls_x509_crt_t *certs;
3350 gnutls_certificate_credentials_t xcred;
3352 if (t == NULL)
3353 return (1);
3355 if (ssl_ca_file == NULL) {
3356 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3357 return (1);
3360 if ((uri = get_uri(t)) == NULL) {
3361 show_oops(t, "Invalid URI");
3362 return (1);
3365 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3366 show_oops(t, "Invalid certificate URI: %s", uri);
3367 return (1);
3370 /* go ssl/tls */
3371 if (start_tls(t, s, &gsession, &xcred)) {
3372 show_oops(t, "Start TLS failed");
3373 goto done;
3376 /* get certs */
3377 if (get_connection_certs(gsession, &certs, &cert_count)) {
3378 show_oops(t, "get_connection_certs failed");
3379 goto done;
3382 if (args->i & XT_SHOW)
3383 show_certs(t, certs, cert_count, "Certificate Chain");
3384 else if (args->i & XT_SAVE)
3385 save_certs(t, certs, cert_count, domain);
3387 free_connection_certs(certs, cert_count);
3388 done:
3389 /* we close the socket first for speed */
3390 if (s != -1)
3391 close(s);
3392 stop_tls(gsession, xcred);
3394 return (0);
3398 remove_cookie(int index)
3400 int i, rv = 1;
3401 GSList *cf;
3402 SoupCookie *c;
3404 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3406 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3408 for (i = 1; cf; cf = cf->next, i++) {
3409 if (i != index)
3410 continue;
3411 c = cf->data;
3412 print_cookie("remove cookie", c);
3413 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3414 rv = 0;
3415 break;
3418 soup_cookies_free(cf);
3420 return (rv);
3424 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3426 struct domain *d;
3427 char *tmp, *body;
3429 body = g_strdup("");
3431 /* p list */
3432 if (args->i & XT_WL_PERSISTENT) {
3433 tmp = body;
3434 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3435 g_free(tmp);
3436 RB_FOREACH(d, domain_list, wl) {
3437 if (d->handy == 0)
3438 continue;
3439 tmp = body;
3440 body = g_strdup_printf("%s%s<br/>", body, d->d);
3441 g_free(tmp);
3445 /* s list */
3446 if (args->i & XT_WL_SESSION) {
3447 tmp = body;
3448 body = g_strdup_printf("%s<h2>Session</h2>", body);
3449 g_free(tmp);
3450 RB_FOREACH(d, domain_list, wl) {
3451 if (d->handy == 1)
3452 continue;
3453 tmp = body;
3454 body = g_strdup_printf("%s%s<br/>", body, d->d);
3455 g_free(tmp);
3459 tmp = get_html_page(title, body, "", 0);
3460 g_free(body);
3461 if (wl == &js_wl)
3462 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3463 else
3464 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3465 g_free(tmp);
3466 return (0);
3470 wl_save(struct tab *t, struct karg *args, int js)
3472 char file[PATH_MAX];
3473 FILE *f;
3474 char *line = NULL, *lt = NULL, *dom = NULL;
3475 size_t linelen;
3476 const gchar *uri;
3477 struct karg a;
3478 struct domain *d;
3479 GSList *cf;
3480 SoupCookie *ci, *c;
3482 if (t == NULL || args == NULL)
3483 return (1);
3485 if (runtime_settings[0] == '\0')
3486 return (1);
3488 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3489 if ((f = fopen(file, "r+")) == NULL)
3490 return (1);
3492 uri = get_uri(t);
3493 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3494 if (uri == NULL || dom == NULL || webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3495 show_oops(t, "Can't add domain to %s white list",
3496 js ? "JavaScript" : "cookie");
3497 goto done;
3500 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom);
3502 while (!feof(f)) {
3503 line = fparseln(f, &linelen, NULL, NULL, 0);
3504 if (line == NULL)
3505 continue;
3506 if (!strcmp(line, lt))
3507 goto done;
3508 free(line);
3509 line = NULL;
3512 fprintf(f, "%s\n", lt);
3514 a.i = XT_WL_ENABLE;
3515 a.i |= args->i;
3516 if (js) {
3517 d = wl_find(dom, &js_wl);
3518 if (!d) {
3519 settings_add("js_wl", dom);
3520 d = wl_find(dom, &js_wl);
3522 toggle_js(t, &a);
3523 } else {
3524 d = wl_find(dom, &c_wl);
3525 if (!d) {
3526 settings_add("cookie_wl", dom);
3527 d = wl_find(dom, &c_wl);
3529 toggle_cwl(t, &a);
3531 /* find and add to persistent jar */
3532 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3533 for (;cf; cf = cf->next) {
3534 ci = cf->data;
3535 if (!strcmp(dom, ci->domain) ||
3536 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3537 c = soup_cookie_copy(ci);
3538 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3541 soup_cookies_free(cf);
3543 if (d)
3544 d->handy = 1;
3546 done:
3547 if (line)
3548 free(line);
3549 if (dom)
3550 g_free(dom);
3551 if (lt)
3552 g_free(lt);
3553 fclose(f);
3555 return (0);
3559 js_show_wl(struct tab *t, struct karg *args)
3561 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3562 wl_show(t, args, "JavaScript White List", &js_wl);
3564 return (0);
3568 cookie_show_wl(struct tab *t, struct karg *args)
3570 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3571 wl_show(t, args, "Cookie White List", &c_wl);
3573 return (0);
3577 cookie_cmd(struct tab *t, struct karg *args)
3579 if (args->i & XT_SHOW)
3580 wl_show(t, args, "Cookie White List", &c_wl);
3581 else if (args->i & XT_WL_TOGGLE) {
3582 args->i |= XT_WL_RELOAD;
3583 toggle_cwl(t, args);
3584 } else if (args->i & XT_SAVE) {
3585 args->i |= XT_WL_RELOAD;
3586 wl_save(t, args, 0);
3587 } else if (args->i & XT_DELETE)
3588 show_oops(t, "'cookie delete' currently unimplemented");
3590 return (0);
3594 js_cmd(struct tab *t, struct karg *args)
3596 if (args->i & XT_SHOW)
3597 wl_show(t, args, "JavaScript White List", &js_wl);
3598 else if (args->i & XT_SAVE) {
3599 args->i |= XT_WL_RELOAD;
3600 wl_save(t, args, 1);
3601 } else if (args->i & XT_WL_TOGGLE) {
3602 args->i |= XT_WL_RELOAD;
3603 toggle_js(t, args);
3604 } else if (args->i & XT_DELETE)
3605 show_oops(t, "'js delete' currently unimplemented");
3607 return (0);
3611 toplevel_cmd(struct tab *t, struct karg *args)
3613 js_toggle_cb(t->js_toggle, t);
3615 return (0);
3619 add_favorite(struct tab *t, struct karg *args)
3621 char file[PATH_MAX];
3622 FILE *f;
3623 char *line = NULL;
3624 size_t urilen, linelen;
3625 const gchar *uri, *title;
3627 if (t == NULL)
3628 return (1);
3630 /* don't allow adding of xtp pages to favorites */
3631 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3632 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3633 return (1);
3636 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3637 if ((f = fopen(file, "r+")) == NULL) {
3638 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3639 return (1);
3642 title = get_title(t, FALSE);
3643 uri = get_uri(t);
3645 if (title == NULL || uri == NULL) {
3646 show_oops(t, "can't add page to favorites");
3647 goto done;
3650 urilen = strlen(uri);
3652 for (;;) {
3653 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3654 if (feof(f) || ferror(f))
3655 break;
3657 if (linelen == urilen && !strcmp(line, uri))
3658 goto done;
3660 free(line);
3661 line = NULL;
3664 fprintf(f, "\n%s\n%s", title, uri);
3665 done:
3666 if (line)
3667 free(line);
3668 fclose(f);
3670 update_favorite_tabs(NULL);
3672 return (0);
3676 navaction(struct tab *t, struct karg *args)
3678 WebKitWebHistoryItem *item;
3680 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3681 t->tab_id, args->i);
3683 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3685 if (t->item) {
3686 if (args->i == XT_NAV_BACK)
3687 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3688 else
3689 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3690 if (item == NULL)
3691 return (XT_CB_PASSTHROUGH);
3692 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3693 t->item = NULL;
3694 return (XT_CB_PASSTHROUGH);
3697 switch (args->i) {
3698 case XT_NAV_BACK:
3699 webkit_web_view_go_back(t->wv);
3700 break;
3701 case XT_NAV_FORWARD:
3702 webkit_web_view_go_forward(t->wv);
3703 break;
3704 case XT_NAV_RELOAD:
3705 webkit_web_view_reload(t->wv);
3706 break;
3707 case XT_NAV_RELOAD_CACHE:
3708 webkit_web_view_reload_bypass_cache(t->wv);
3709 break;
3711 return (XT_CB_PASSTHROUGH);
3715 move(struct tab *t, struct karg *args)
3717 GtkAdjustment *adjust;
3718 double pi, si, pos, ps, upper, lower, max;
3720 switch (args->i) {
3721 case XT_MOVE_DOWN:
3722 case XT_MOVE_UP:
3723 case XT_MOVE_BOTTOM:
3724 case XT_MOVE_TOP:
3725 case XT_MOVE_PAGEDOWN:
3726 case XT_MOVE_PAGEUP:
3727 case XT_MOVE_HALFDOWN:
3728 case XT_MOVE_HALFUP:
3729 adjust = t->adjust_v;
3730 break;
3731 default:
3732 adjust = t->adjust_h;
3733 break;
3736 pos = gtk_adjustment_get_value(adjust);
3737 ps = gtk_adjustment_get_page_size(adjust);
3738 upper = gtk_adjustment_get_upper(adjust);
3739 lower = gtk_adjustment_get_lower(adjust);
3740 si = gtk_adjustment_get_step_increment(adjust);
3741 pi = gtk_adjustment_get_page_increment(adjust);
3742 max = upper - ps;
3744 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3745 "max %f si %f pi %f\n",
3746 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3747 pos, ps, upper, lower, max, si, pi);
3749 switch (args->i) {
3750 case XT_MOVE_DOWN:
3751 case XT_MOVE_RIGHT:
3752 pos += si;
3753 gtk_adjustment_set_value(adjust, MIN(pos, max));
3754 break;
3755 case XT_MOVE_UP:
3756 case XT_MOVE_LEFT:
3757 pos -= si;
3758 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3759 break;
3760 case XT_MOVE_BOTTOM:
3761 case XT_MOVE_FARRIGHT:
3762 gtk_adjustment_set_value(adjust, max);
3763 break;
3764 case XT_MOVE_TOP:
3765 case XT_MOVE_FARLEFT:
3766 gtk_adjustment_set_value(adjust, lower);
3767 break;
3768 case XT_MOVE_PAGEDOWN:
3769 pos += pi;
3770 gtk_adjustment_set_value(adjust, MIN(pos, max));
3771 break;
3772 case XT_MOVE_PAGEUP:
3773 pos -= pi;
3774 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3775 break;
3776 case XT_MOVE_HALFDOWN:
3777 pos += pi / 2;
3778 gtk_adjustment_set_value(adjust, MIN(pos, max));
3779 break;
3780 case XT_MOVE_HALFUP:
3781 pos -= pi / 2;
3782 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3783 break;
3784 default:
3785 return (XT_CB_PASSTHROUGH);
3788 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3790 return (XT_CB_HANDLED);
3793 void
3794 url_set_visibility(void)
3796 struct tab *t;
3798 TAILQ_FOREACH(t, &tabs, entry)
3799 if (show_url == 0) {
3800 gtk_widget_hide(t->toolbar);
3801 focus_webview(t);
3802 } else
3803 gtk_widget_show(t->toolbar);
3806 void
3807 notebook_tab_set_visibility()
3809 if (show_tabs == 0) {
3810 gtk_widget_hide(tab_bar);
3811 gtk_notebook_set_show_tabs(notebook, FALSE);
3812 } else {
3813 if (tab_style == XT_TABS_NORMAL) {
3814 gtk_widget_hide(tab_bar);
3815 gtk_notebook_set_show_tabs(notebook, TRUE);
3816 } else if (tab_style == XT_TABS_COMPACT) {
3817 gtk_widget_show(tab_bar);
3818 gtk_notebook_set_show_tabs(notebook, FALSE);
3823 void
3824 statusbar_set_visibility(void)
3826 struct tab *t;
3828 TAILQ_FOREACH(t, &tabs, entry)
3829 if (show_statusbar == 0) {
3830 gtk_widget_hide(t->statusbar_box);
3831 focus_webview(t);
3832 } else
3833 gtk_widget_show(t->statusbar_box);
3836 void
3837 url_set(struct tab *t, int enable_url_entry)
3839 GdkPixbuf *pixbuf;
3840 int progress;
3842 show_url = enable_url_entry;
3844 if (enable_url_entry) {
3845 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
3846 GTK_ENTRY_ICON_PRIMARY, NULL);
3847 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
3848 } else {
3849 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3850 GTK_ENTRY_ICON_PRIMARY);
3851 progress =
3852 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3853 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
3854 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3855 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
3856 progress);
3861 fullscreen(struct tab *t, struct karg *args)
3863 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3865 if (t == NULL)
3866 return (XT_CB_PASSTHROUGH);
3868 if (show_url == 0) {
3869 url_set(t, 1);
3870 show_tabs = 1;
3871 } else {
3872 url_set(t, 0);
3873 show_tabs = 0;
3876 url_set_visibility();
3877 notebook_tab_set_visibility();
3879 return (XT_CB_HANDLED);
3883 statustoggle(struct tab *t, struct karg *args)
3885 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3887 if (show_statusbar == 1) {
3888 show_statusbar = 0;
3889 statusbar_set_visibility();
3890 } else if (show_statusbar == 0) {
3891 show_statusbar = 1;
3892 statusbar_set_visibility();
3894 return (XT_CB_HANDLED);
3898 urlaction(struct tab *t, struct karg *args)
3900 int rv = XT_CB_HANDLED;
3902 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3904 if (t == NULL)
3905 return (XT_CB_PASSTHROUGH);
3907 switch (args->i) {
3908 case XT_URL_SHOW:
3909 if (show_url == 0) {
3910 url_set(t, 1);
3911 url_set_visibility();
3913 break;
3914 case XT_URL_HIDE:
3915 if (show_url == 1) {
3916 url_set(t, 0);
3917 url_set_visibility();
3919 break;
3921 return (rv);
3925 tabaction(struct tab *t, struct karg *args)
3927 int rv = XT_CB_HANDLED;
3928 char *url = args->s;
3929 struct undo *u;
3930 struct tab *tt;
3932 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3934 if (t == NULL)
3935 return (XT_CB_PASSTHROUGH);
3937 switch (args->i) {
3938 case XT_TAB_NEW:
3939 if (strlen(url) > 0)
3940 create_new_tab(url, NULL, 1, args->p);
3941 else
3942 create_new_tab(NULL, NULL, 1, args->p);
3943 break;
3944 case XT_TAB_DELETE:
3945 if (args->p < 0)
3946 delete_tab(t);
3947 else
3948 TAILQ_FOREACH(tt, &tabs, entry)
3949 if (tt->tab_id == args->p - 1) {
3950 delete_tab(tt);
3951 break;
3953 break;
3954 case XT_TAB_DELQUIT:
3955 if (gtk_notebook_get_n_pages(notebook) > 1)
3956 delete_tab(t);
3957 else
3958 quit(t, args);
3959 break;
3960 case XT_TAB_OPEN:
3961 if (strlen(url) > 0)
3963 else {
3964 rv = XT_CB_PASSTHROUGH;
3965 goto done;
3967 load_uri(t, url);
3968 break;
3969 case XT_TAB_SHOW:
3970 if (show_tabs == 0) {
3971 show_tabs = 1;
3972 notebook_tab_set_visibility();
3974 break;
3975 case XT_TAB_HIDE:
3976 if (show_tabs == 1) {
3977 show_tabs = 0;
3978 notebook_tab_set_visibility();
3980 break;
3981 case XT_TAB_NEXTSTYLE:
3982 if (tab_style == XT_TABS_NORMAL) {
3983 tab_style = XT_TABS_COMPACT;
3984 recolor_compact_tabs();
3986 else
3987 tab_style = XT_TABS_NORMAL;
3988 notebook_tab_set_visibility();
3989 break;
3990 case XT_TAB_UNDO_CLOSE:
3991 if (undo_count == 0) {
3992 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3993 goto done;
3994 } else {
3995 undo_count--;
3996 u = TAILQ_FIRST(&undos);
3997 create_new_tab(u->uri, u, 1, -1);
3999 TAILQ_REMOVE(&undos, u, entry);
4000 g_free(u->uri);
4001 /* u->history is freed in create_new_tab() */
4002 g_free(u);
4004 break;
4005 default:
4006 rv = XT_CB_PASSTHROUGH;
4007 goto done;
4010 done:
4011 if (args->s) {
4012 g_free(args->s);
4013 args->s = NULL;
4016 return (rv);
4020 resizetab(struct tab *t, struct karg *args)
4022 if (t == NULL || args == NULL) {
4023 show_oops(NULL, "resizetab invalid parameters");
4024 return (XT_CB_PASSTHROUGH);
4027 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4028 t->tab_id, args->i);
4030 setzoom_webkit(t, args->i);
4032 return (XT_CB_HANDLED);
4036 movetab(struct tab *t, struct karg *args)
4038 int n, dest;
4040 if (t == NULL || args == NULL) {
4041 show_oops(NULL, "movetab invalid parameters");
4042 return (XT_CB_PASSTHROUGH);
4045 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4046 t->tab_id, args->i);
4048 if (args->i >= XT_TAB_INVALID)
4049 return (XT_CB_PASSTHROUGH);
4051 if (TAILQ_EMPTY(&tabs))
4052 return (XT_CB_PASSTHROUGH);
4054 n = gtk_notebook_get_n_pages(notebook);
4055 dest = gtk_notebook_get_current_page(notebook);
4057 switch (args->i) {
4058 case XT_TAB_NEXT:
4059 if (args->p < 0)
4060 dest = dest == n - 1 ? 0 : dest + 1;
4061 else
4062 dest = args->p - 1;
4064 break;
4065 case XT_TAB_PREV:
4066 if (args->p < 0)
4067 dest -= 1;
4068 else
4069 dest -= args->p % n;
4071 if (dest < 0)
4072 dest += n;
4074 break;
4075 case XT_TAB_FIRST:
4076 dest = 0;
4077 break;
4078 case XT_TAB_LAST:
4079 dest = n - 1;
4080 break;
4081 default:
4082 return (XT_CB_PASSTHROUGH);
4085 if (dest < 0 || dest >= n)
4086 return (XT_CB_PASSTHROUGH);
4087 if (t->tab_id == dest) {
4088 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4089 return (XT_CB_HANDLED);
4092 set_current_tab(dest);
4094 return (XT_CB_HANDLED);
4097 int cmd_prefix = 0;
4100 command(struct tab *t, struct karg *args)
4102 char *s = NULL, *ss = NULL;
4103 GdkColor color;
4104 const gchar *uri;
4106 if (t == NULL || args == NULL) {
4107 show_oops(NULL, "command invalid parameters");
4108 return (XT_CB_PASSTHROUGH);
4111 switch (args->i) {
4112 case '/':
4113 s = "/";
4114 break;
4115 case '?':
4116 s = "?";
4117 break;
4118 case ':':
4119 if (cmd_prefix == 0)
4120 s = ":";
4121 else {
4122 ss = g_strdup_printf(":%d", cmd_prefix);
4123 s = ss;
4124 cmd_prefix = 0;
4126 break;
4127 case XT_CMD_OPEN:
4128 s = ":open ";
4129 break;
4130 case XT_CMD_TABNEW:
4131 s = ":tabnew ";
4132 break;
4133 case XT_CMD_OPEN_CURRENT:
4134 s = ":open ";
4135 /* FALL THROUGH */
4136 case XT_CMD_TABNEW_CURRENT:
4137 if (!s) /* FALL THROUGH? */
4138 s = ":tabnew ";
4139 if ((uri = get_uri(t)) != NULL) {
4140 ss = g_strdup_printf("%s%s", s, uri);
4141 s = ss;
4143 break;
4144 default:
4145 show_oops(t, "command: invalid opcode %d", args->i);
4146 return (XT_CB_PASSTHROUGH);
4149 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4151 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4152 gdk_color_parse(XT_COLOR_WHITE, &color);
4153 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4154 show_cmd(t);
4155 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4156 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4158 if (ss)
4159 g_free(ss);
4161 return (XT_CB_HANDLED);
4165 * Return a new string with a download row (in html)
4166 * appended. Old string is freed.
4168 char *
4169 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4172 WebKitDownloadStatus stat;
4173 char *status_html = NULL, *cmd_html = NULL, *new_html;
4174 gdouble progress;
4175 char cur_sz[FMT_SCALED_STRSIZE];
4176 char tot_sz[FMT_SCALED_STRSIZE];
4177 char *xtp_prefix;
4179 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4181 /* All actions wil take this form:
4182 * xxxt://class/seskey
4184 xtp_prefix = g_strdup_printf("%s%d/%s/",
4185 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4187 stat = webkit_download_get_status(dl->download);
4189 switch (stat) {
4190 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4191 status_html = g_strdup_printf("Finished");
4192 cmd_html = g_strdup_printf(
4193 "<a href='%s%d/%d'>Remove</a>",
4194 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4195 break;
4196 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4197 /* gather size info */
4198 progress = 100 * webkit_download_get_progress(dl->download);
4200 fmt_scaled(
4201 webkit_download_get_current_size(dl->download), cur_sz);
4202 fmt_scaled(
4203 webkit_download_get_total_size(dl->download), tot_sz);
4205 status_html = g_strdup_printf(
4206 "<div style='width: 100%%' align='center'>"
4207 "<div class='progress-outer'>"
4208 "<div class='progress-inner' style='width: %.2f%%'>"
4209 "</div></div></div>"
4210 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4211 progress, cur_sz, tot_sz, progress);
4213 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4214 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4216 break;
4217 /* LLL */
4218 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4219 status_html = g_strdup_printf("Cancelled");
4220 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4221 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4222 break;
4223 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4224 status_html = g_strdup_printf("Error!");
4225 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4226 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4227 break;
4228 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4229 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4230 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4231 status_html = g_strdup_printf("Starting");
4232 break;
4233 default:
4234 show_oops(t, "%s: unknown download status", __func__);
4237 new_html = g_strdup_printf(
4238 "%s\n<tr><td>%s</td><td>%s</td>"
4239 "<td style='text-align:center'>%s</td></tr>\n",
4240 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4241 status_html, cmd_html);
4242 g_free(html);
4244 if (status_html)
4245 g_free(status_html);
4247 if (cmd_html)
4248 g_free(cmd_html);
4250 g_free(xtp_prefix);
4252 return new_html;
4256 * update all download tabs apart from one. Pass NULL if
4257 * you want to update all.
4259 void
4260 update_download_tabs(struct tab *apart_from)
4262 struct tab *t;
4263 if (!updating_dl_tabs) {
4264 updating_dl_tabs = 1; /* stop infinite recursion */
4265 TAILQ_FOREACH(t, &tabs, entry)
4266 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4267 && (t != apart_from))
4268 xtp_page_dl(t, NULL);
4269 updating_dl_tabs = 0;
4274 * update all cookie tabs apart from one. Pass NULL if
4275 * you want to update all.
4277 void
4278 update_cookie_tabs(struct tab *apart_from)
4280 struct tab *t;
4281 if (!updating_cl_tabs) {
4282 updating_cl_tabs = 1; /* stop infinite recursion */
4283 TAILQ_FOREACH(t, &tabs, entry)
4284 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4285 && (t != apart_from))
4286 xtp_page_cl(t, NULL);
4287 updating_cl_tabs = 0;
4292 * update all history tabs apart from one. Pass NULL if
4293 * you want to update all.
4295 void
4296 update_history_tabs(struct tab *apart_from)
4298 struct tab *t;
4300 if (!updating_hl_tabs) {
4301 updating_hl_tabs = 1; /* stop infinite recursion */
4302 TAILQ_FOREACH(t, &tabs, entry)
4303 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4304 && (t != apart_from))
4305 xtp_page_hl(t, NULL);
4306 updating_hl_tabs = 0;
4310 /* cookie management XTP page */
4312 xtp_page_cl(struct tab *t, struct karg *args)
4314 char *body, *page, *tmp;
4315 int i = 1; /* all ids start 1 */
4316 GSList *sc, *pc, *pc_start;
4317 SoupCookie *c;
4318 char *type, *table_headers, *last_domain;
4320 DNPRINTF(XT_D_CMD, "%s", __func__);
4322 if (t == NULL) {
4323 show_oops(NULL, "%s invalid parameters", __func__);
4324 return (1);
4327 /* Generate a new session key */
4328 if (!updating_cl_tabs)
4329 generate_xtp_session_key(&cl_session_key);
4331 /* table headers */
4332 table_headers = g_strdup_printf("<table><tr>"
4333 "<th>Type</th>"
4334 "<th>Name</th>"
4335 "<th style='width:200px'>Value</th>"
4336 "<th>Path</th>"
4337 "<th>Expires</th>"
4338 "<th>Secure</th>"
4339 "<th>HTTP<br />only</th>"
4340 "<th style='width:40px'>Rm</th></tr>\n");
4342 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4343 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4344 pc_start = pc;
4346 body = NULL;
4347 last_domain = strdup("");
4348 for (; sc; sc = sc->next) {
4349 c = sc->data;
4351 if (strcmp(last_domain, c->domain) != 0) {
4352 /* new domain */
4353 free(last_domain);
4354 last_domain = strdup(c->domain);
4356 if (body != NULL) {
4357 tmp = body;
4358 body = g_strdup_printf("%s</table>"
4359 "<h2>%s</h2>%s\n",
4360 body, c->domain, table_headers);
4361 g_free(tmp);
4362 } else {
4363 /* first domain */
4364 body = g_strdup_printf("<h2>%s</h2>%s\n",
4365 c->domain, table_headers);
4369 type = "Session";
4370 for (pc = pc_start; pc; pc = pc->next)
4371 if (soup_cookie_equal(pc->data, c)) {
4372 type = "Session + Persistent";
4373 break;
4376 tmp = body;
4377 body = g_strdup_printf(
4378 "%s\n<tr>"
4379 "<td>%s</td>"
4380 "<td style='word-wrap:normal'>%s</td>"
4381 "<td>"
4382 " <textarea rows='4'>%s</textarea>"
4383 "</td>"
4384 "<td>%s</td>"
4385 "<td>%s</td>"
4386 "<td>%d</td>"
4387 "<td>%d</td>"
4388 "<td style='text-align:center'>"
4389 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4390 body,
4391 type,
4392 c->name,
4393 c->value,
4394 c->path,
4395 c->expires ?
4396 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4397 c->secure,
4398 c->http_only,
4400 XT_XTP_STR,
4401 XT_XTP_CL,
4402 cl_session_key,
4403 XT_XTP_CL_REMOVE,
4407 g_free(tmp);
4408 i++;
4411 soup_cookies_free(sc);
4412 soup_cookies_free(pc);
4414 /* small message if there are none */
4415 if (i == 1) {
4416 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4417 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4419 tmp = body;
4420 body = g_strdup_printf("%s</table>", body);
4421 g_free(tmp);
4423 page = get_html_page("Cookie Jar", body, "", TRUE);
4424 g_free(body);
4425 g_free(table_headers);
4426 g_free(last_domain);
4428 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4429 update_cookie_tabs(t);
4431 g_free(page);
4433 return (0);
4437 xtp_page_hl(struct tab *t, struct karg *args)
4439 char *body, *page, *tmp;
4440 struct history *h;
4441 int i = 1; /* all ids start 1 */
4443 DNPRINTF(XT_D_CMD, "%s", __func__);
4445 if (t == NULL) {
4446 show_oops(NULL, "%s invalid parameters", __func__);
4447 return (1);
4450 /* Generate a new session key */
4451 if (!updating_hl_tabs)
4452 generate_xtp_session_key(&hl_session_key);
4454 /* body */
4455 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4456 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4458 RB_FOREACH_REVERSE(h, history_list, &hl) {
4459 tmp = body;
4460 body = g_strdup_printf(
4461 "%s\n<tr>"
4462 "<td><a href='%s'>%s</a></td>"
4463 "<td>%s</td>"
4464 "<td style='text-align: center'>"
4465 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4466 body, h->uri, h->uri, h->title,
4467 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4468 XT_XTP_HL_REMOVE, i);
4470 g_free(tmp);
4471 i++;
4474 /* small message if there are none */
4475 if (i == 1) {
4476 tmp = body;
4477 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4478 "colspan='3'>No History</td></tr>\n", body);
4479 g_free(tmp);
4482 tmp = body;
4483 body = g_strdup_printf("%s</table>", body);
4484 g_free(tmp);
4486 page = get_html_page("History", body, "", TRUE);
4487 g_free(body);
4490 * update all history manager tabs as the xtp session
4491 * key has now changed. No need to update the current tab.
4492 * Already did that above.
4494 update_history_tabs(t);
4496 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4497 g_free(page);
4499 return (0);
4503 * Generate a web page detailing the status of any downloads
4506 xtp_page_dl(struct tab *t, struct karg *args)
4508 struct download *dl;
4509 char *body, *page, *tmp;
4510 char *ref;
4511 int n_dl = 1;
4513 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4515 if (t == NULL) {
4516 show_oops(NULL, "%s invalid parameters", __func__);
4517 return (1);
4521 * Generate a new session key for next page instance.
4522 * This only happens for the top level call to xtp_page_dl()
4523 * in which case updating_dl_tabs is 0.
4525 if (!updating_dl_tabs)
4526 generate_xtp_session_key(&dl_session_key);
4528 /* header - with refresh so as to update */
4529 if (refresh_interval >= 1)
4530 ref = g_strdup_printf(
4531 "<meta http-equiv='refresh' content='%u"
4532 ";url=%s%d/%s/%d' />\n",
4533 refresh_interval,
4534 XT_XTP_STR,
4535 XT_XTP_DL,
4536 dl_session_key,
4537 XT_XTP_DL_LIST);
4538 else
4539 ref = g_strdup("");
4541 body = g_strdup_printf("<div align='center'>"
4542 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4543 "</p><table><tr><th style='width: 60%%'>"
4544 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4545 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4547 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4548 body = xtp_page_dl_row(t, body, dl);
4549 n_dl++;
4552 /* message if no downloads in list */
4553 if (n_dl == 1) {
4554 tmp = body;
4555 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4556 " style='text-align: center'>"
4557 "No downloads</td></tr>\n", body);
4558 g_free(tmp);
4561 tmp = body;
4562 body = g_strdup_printf("%s</table></div>", body);
4563 g_free(tmp);
4565 page = get_html_page("Downloads", body, ref, 1);
4566 g_free(ref);
4567 g_free(body);
4570 * update all download manager tabs as the xtp session
4571 * key has now changed. No need to update the current tab.
4572 * Already did that above.
4574 update_download_tabs(t);
4576 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4577 g_free(page);
4579 return (0);
4583 search(struct tab *t, struct karg *args)
4585 gboolean d;
4587 if (t == NULL || args == NULL) {
4588 show_oops(NULL, "search invalid parameters");
4589 return (1);
4591 if (t->search_text == NULL) {
4592 if (global_search == NULL)
4593 return (XT_CB_PASSTHROUGH);
4594 else {
4595 t->search_text = g_strdup(global_search);
4596 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4597 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4601 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4602 t->tab_id, args->i, t->search_forward, t->search_text);
4604 switch (args->i) {
4605 case XT_SEARCH_NEXT:
4606 d = t->search_forward;
4607 break;
4608 case XT_SEARCH_PREV:
4609 d = !t->search_forward;
4610 break;
4611 default:
4612 return (XT_CB_PASSTHROUGH);
4615 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4617 return (XT_CB_HANDLED);
4620 struct settings_args {
4621 char **body;
4622 int i;
4625 void
4626 print_setting(struct settings *s, char *val, void *cb_args)
4628 char *tmp, *color;
4629 struct settings_args *sa = cb_args;
4631 if (sa == NULL)
4632 return;
4634 if (s->flags & XT_SF_RUNTIME)
4635 color = "#22cc22";
4636 else
4637 color = "#cccccc";
4639 tmp = *sa->body;
4640 *sa->body = g_strdup_printf(
4641 "%s\n<tr>"
4642 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4643 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4644 *sa->body,
4645 color,
4646 s->name,
4647 color,
4650 g_free(tmp);
4651 sa->i++;
4655 set(struct tab *t, struct karg *args)
4657 char *body, *page, *tmp;
4658 int i = 1;
4659 struct settings_args sa;
4661 bzero(&sa, sizeof sa);
4662 sa.body = &body;
4664 /* body */
4665 body = g_strdup_printf("<div align='center'><table><tr>"
4666 "<th align='left'>Setting</th>"
4667 "<th align='left'>Value</th></tr>\n");
4669 settings_walk(print_setting, &sa);
4670 i = sa.i;
4672 /* small message if there are none */
4673 if (i == 1) {
4674 tmp = body;
4675 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4676 "colspan='2'>No settings</td></tr>\n", body);
4677 g_free(tmp);
4680 tmp = body;
4681 body = g_strdup_printf("%s</table></div>", body);
4682 g_free(tmp);
4684 page = get_html_page("Settings", body, "", 0);
4686 g_free(body);
4688 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4690 g_free(page);
4692 return (XT_CB_PASSTHROUGH);
4696 session_save(struct tab *t, char *filename)
4698 struct karg a;
4699 int rv = 1;
4701 if (strlen(filename) == 0)
4702 goto done;
4704 if (filename[0] == '.' || filename[0] == '/')
4705 goto done;
4707 a.s = filename;
4708 if (save_tabs(t, &a))
4709 goto done;
4710 strlcpy(named_session, filename, sizeof named_session);
4712 rv = 0;
4713 done:
4714 return (rv);
4718 session_open(struct tab *t, char *filename)
4720 struct karg a;
4721 int rv = 1;
4723 if (strlen(filename) == 0)
4724 goto done;
4726 if (filename[0] == '.' || filename[0] == '/')
4727 goto done;
4729 a.s = filename;
4730 a.i = XT_SES_CLOSETABS;
4731 if (open_tabs(t, &a))
4732 goto done;
4734 strlcpy(named_session, filename, sizeof named_session);
4736 rv = 0;
4737 done:
4738 return (rv);
4742 session_delete(struct tab *t, char *filename)
4744 char file[PATH_MAX];
4745 int rv = 1;
4747 if (strlen(filename) == 0)
4748 goto done;
4750 if (filename[0] == '.' || filename[0] == '/')
4751 goto done;
4753 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4754 if (unlink(file))
4755 goto done;
4757 if (!strcmp(filename, named_session))
4758 strlcpy(named_session, XT_SAVED_TABS_FILE,
4759 sizeof named_session);
4761 rv = 0;
4762 done:
4763 return (rv);
4767 session_cmd(struct tab *t, struct karg *args)
4769 char *filename = args->s;
4771 if (t == NULL)
4772 return (1);
4774 if (args->i & XT_SHOW)
4775 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4776 XT_SAVED_TABS_FILE : named_session);
4777 else if (args->i & XT_SAVE) {
4778 if (session_save(t, filename)) {
4779 show_oops(t, "Can't save session: %s",
4780 filename ? filename : "INVALID");
4781 goto done;
4783 } else if (args->i & XT_OPEN) {
4784 if (session_open(t, filename)) {
4785 show_oops(t, "Can't open session: %s",
4786 filename ? filename : "INVALID");
4787 goto done;
4789 } else if (args->i & XT_DELETE) {
4790 if (session_delete(t, filename)) {
4791 show_oops(t, "Can't delete session: %s",
4792 filename ? filename : "INVALID");
4793 goto done;
4796 done:
4797 return (XT_CB_PASSTHROUGH);
4801 * Make a hardcopy of the page
4804 print_page(struct tab *t, struct karg *args)
4806 WebKitWebFrame *frame;
4807 GtkPageSetup *ps;
4808 GtkPrintOperation *op;
4809 GtkPrintOperationAction action;
4810 GtkPrintOperationResult print_res;
4811 GError *g_err = NULL;
4812 int marg_l, marg_r, marg_t, marg_b;
4814 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4816 ps = gtk_page_setup_new();
4817 op = gtk_print_operation_new();
4818 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4819 frame = webkit_web_view_get_main_frame(t->wv);
4821 /* the default margins are too small, so we will bump them */
4822 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4823 XT_PRINT_EXTRA_MARGIN;
4824 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4825 XT_PRINT_EXTRA_MARGIN;
4826 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4827 XT_PRINT_EXTRA_MARGIN;
4828 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4829 XT_PRINT_EXTRA_MARGIN;
4831 /* set margins */
4832 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4833 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4834 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4835 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4837 gtk_print_operation_set_default_page_setup(op, ps);
4839 /* this appears to free 'op' and 'ps' */
4840 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4842 /* check it worked */
4843 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4844 show_oops(NULL, "can't print: %s", g_err->message);
4845 g_error_free (g_err);
4846 return (1);
4849 return (0);
4853 go_home(struct tab *t, struct karg *args)
4855 load_uri(t, home);
4856 return (0);
4860 restart(struct tab *t, struct karg *args)
4862 struct karg a;
4864 a.s = XT_RESTART_TABS_FILE;
4865 save_tabs(t, &a);
4866 execvp(start_argv[0], start_argv);
4867 /* NOTREACHED */
4869 return (0);
4872 #define CTRL GDK_CONTROL_MASK
4873 #define MOD1 GDK_MOD1_MASK
4874 #define SHFT GDK_SHIFT_MASK
4876 /* inherent to GTK not all keys will be caught at all times */
4877 /* XXX sort key bindings */
4878 struct key_binding {
4879 char *cmd;
4880 guint mask;
4881 guint use_in_entry;
4882 guint key;
4883 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4884 } keys[] = {
4885 { "cookiejar", MOD1, 0, GDK_j },
4886 { "downloadmgr", MOD1, 0, GDK_d },
4887 { "history", MOD1, 0, GDK_h },
4888 { "print", CTRL, 0, GDK_p },
4889 { "search", 0, 0, GDK_slash },
4890 { "searchb", 0, 0, GDK_question },
4891 { "statustoggle", CTRL, 0, GDK_n },
4892 { "command", 0, 0, GDK_colon },
4893 { "qa", CTRL, 0, GDK_q },
4894 { "restart", MOD1, 0, GDK_q },
4895 { "js toggle", CTRL, 0, GDK_j },
4896 { "cookie toggle", MOD1, 0, GDK_c },
4897 { "togglesrc", CTRL, 0, GDK_s },
4898 { "yankuri", 0, 0, GDK_y },
4899 { "pasteuricur", 0, 0, GDK_p },
4900 { "pasteurinew", 0, 0, GDK_P },
4901 { "toplevel toggle", 0, 0, GDK_F4 },
4902 { "help", 0, 0, GDK_F1 },
4903 { "run_script", MOD1, 0, GDK_r },
4905 /* search */
4906 { "searchnext", 0, 0, GDK_n },
4907 { "searchprevious", 0, 0, GDK_N },
4909 /* focus */
4910 { "focusaddress", 0, 0, GDK_F6 },
4911 { "focussearch", 0, 0, GDK_F7 },
4913 /* hinting */
4914 { "hinting", 0, 0, GDK_f },
4916 /* custom stylesheet */
4917 { "userstyle", 0, 0, GDK_i },
4919 /* navigation */
4920 { "goback", 0, 0, GDK_BackSpace },
4921 { "goback", MOD1, 0, GDK_Left },
4922 { "goforward", SHFT, 0, GDK_BackSpace },
4923 { "goforward", MOD1, 0, GDK_Right },
4924 { "reload", 0, 0, GDK_F5 },
4925 { "reload", CTRL, 0, GDK_r },
4926 { "reloadforce", CTRL, 0, GDK_R },
4927 { "reload", CTRL, 0, GDK_l },
4928 { "favorites", MOD1, 1, GDK_f },
4930 /* vertical movement */
4931 { "scrolldown", 0, 0, GDK_j },
4932 { "scrolldown", 0, 0, GDK_Down },
4933 { "scrollup", 0, 0, GDK_Up },
4934 { "scrollup", 0, 0, GDK_k },
4935 { "scrollbottom", 0, 0, GDK_G },
4936 { "scrollbottom", 0, 0, GDK_End },
4937 { "scrolltop", 0, 0, GDK_Home },
4938 { "scrollpagedown", 0, 0, GDK_space },
4939 { "scrollpagedown", CTRL, 0, GDK_f },
4940 { "scrollhalfdown", CTRL, 0, GDK_d },
4941 { "scrollpagedown", 0, 0, GDK_Page_Down },
4942 { "scrollpageup", 0, 0, GDK_Page_Up },
4943 { "scrollpageup", CTRL, 0, GDK_b },
4944 { "scrollhalfup", CTRL, 0, GDK_u },
4945 /* horizontal movement */
4946 { "scrollright", 0, 0, GDK_l },
4947 { "scrollright", 0, 0, GDK_Right },
4948 { "scrollleft", 0, 0, GDK_Left },
4949 { "scrollleft", 0, 0, GDK_h },
4950 { "scrollfarright", 0, 0, GDK_dollar },
4951 { "scrollfarleft", 0, 0, GDK_0 },
4953 /* tabs */
4954 { "tabnew", CTRL, 0, GDK_t },
4955 { "999tabnew", CTRL, 0, GDK_T },
4956 { "tabclose", CTRL, 1, GDK_w },
4957 { "tabundoclose", 0, 0, GDK_U },
4958 { "tabnext 1", CTRL, 0, GDK_1 },
4959 { "tabnext 2", CTRL, 0, GDK_2 },
4960 { "tabnext 3", CTRL, 0, GDK_3 },
4961 { "tabnext 4", CTRL, 0, GDK_4 },
4962 { "tabnext 5", CTRL, 0, GDK_5 },
4963 { "tabnext 6", CTRL, 0, GDK_6 },
4964 { "tabnext 7", CTRL, 0, GDK_7 },
4965 { "tabnext 8", CTRL, 0, GDK_8 },
4966 { "tabnext 9", CTRL, 0, GDK_9 },
4967 { "tabfirst", CTRL, 0, GDK_less },
4968 { "tablast", CTRL, 0, GDK_greater },
4969 { "tabprevious", CTRL, 0, GDK_Left },
4970 { "tabnext", CTRL, 0, GDK_Right },
4971 { "focusout", CTRL, 0, GDK_minus },
4972 { "focusin", CTRL, 0, GDK_plus },
4973 { "focusin", CTRL, 0, GDK_equal },
4974 { "focusreset", CTRL, 0, GDK_0 },
4976 /* command aliases (handy when -S flag is used) */
4977 { "promptopen", 0, 0, GDK_F9 },
4978 { "promptopencurrent", 0, 0, GDK_F10 },
4979 { "prompttabnew", 0, 0, GDK_F11 },
4980 { "prompttabnewcurrent",0, 0, GDK_F12 },
4982 TAILQ_HEAD(keybinding_list, key_binding);
4984 void
4985 walk_kb(struct settings *s,
4986 void (*cb)(struct settings *, char *, void *), void *cb_args)
4988 struct key_binding *k;
4989 char str[1024];
4991 if (s == NULL || cb == NULL) {
4992 show_oops(NULL, "walk_kb invalid parameters");
4993 return;
4996 TAILQ_FOREACH(k, &kbl, entry) {
4997 if (k->cmd == NULL)
4998 continue;
4999 str[0] = '\0';
5001 /* sanity */
5002 if (gdk_keyval_name(k->key) == NULL)
5003 continue;
5005 strlcat(str, k->cmd, sizeof str);
5006 strlcat(str, ",", sizeof str);
5008 if (k->mask & GDK_SHIFT_MASK)
5009 strlcat(str, "S-", sizeof str);
5010 if (k->mask & GDK_CONTROL_MASK)
5011 strlcat(str, "C-", sizeof str);
5012 if (k->mask & GDK_MOD1_MASK)
5013 strlcat(str, "M1-", sizeof str);
5014 if (k->mask & GDK_MOD2_MASK)
5015 strlcat(str, "M2-", sizeof str);
5016 if (k->mask & GDK_MOD3_MASK)
5017 strlcat(str, "M3-", sizeof str);
5018 if (k->mask & GDK_MOD4_MASK)
5019 strlcat(str, "M4-", sizeof str);
5020 if (k->mask & GDK_MOD5_MASK)
5021 strlcat(str, "M5-", sizeof str);
5023 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5024 cb(s, str, cb_args);
5028 void
5029 init_keybindings(void)
5031 int i;
5032 struct key_binding *k;
5034 for (i = 0; i < LENGTH(keys); i++) {
5035 k = g_malloc0(sizeof *k);
5036 k->cmd = keys[i].cmd;
5037 k->mask = keys[i].mask;
5038 k->use_in_entry = keys[i].use_in_entry;
5039 k->key = keys[i].key;
5040 TAILQ_INSERT_HEAD(&kbl, k, entry);
5042 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5043 k->cmd ? k->cmd : "unnamed key");
5047 void
5048 keybinding_clearall(void)
5050 struct key_binding *k, *next;
5052 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5053 next = TAILQ_NEXT(k, entry);
5054 if (k->cmd == NULL)
5055 continue;
5057 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5058 k->cmd ? k->cmd : "unnamed key");
5059 TAILQ_REMOVE(&kbl, k, entry);
5060 g_free(k);
5065 keybinding_add(char *cmd, char *key, int use_in_entry)
5067 struct key_binding *k;
5068 guint keyval, mask = 0;
5069 int i;
5071 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5073 /* Keys which are to be used in entry have been prefixed with an
5074 * exclamation mark. */
5075 if (use_in_entry)
5076 key++;
5078 /* find modifier keys */
5079 if (strstr(key, "S-"))
5080 mask |= GDK_SHIFT_MASK;
5081 if (strstr(key, "C-"))
5082 mask |= GDK_CONTROL_MASK;
5083 if (strstr(key, "M1-"))
5084 mask |= GDK_MOD1_MASK;
5085 if (strstr(key, "M2-"))
5086 mask |= GDK_MOD2_MASK;
5087 if (strstr(key, "M3-"))
5088 mask |= GDK_MOD3_MASK;
5089 if (strstr(key, "M4-"))
5090 mask |= GDK_MOD4_MASK;
5091 if (strstr(key, "M5-"))
5092 mask |= GDK_MOD5_MASK;
5094 /* find keyname */
5095 for (i = strlen(key) - 1; i > 0; i--)
5096 if (key[i] == '-')
5097 key = &key[i + 1];
5099 /* validate keyname */
5100 keyval = gdk_keyval_from_name(key);
5101 if (keyval == GDK_VoidSymbol) {
5102 warnx("invalid keybinding name %s", key);
5103 return (1);
5105 /* must run this test too, gtk+ doesn't handle 10 for example */
5106 if (gdk_keyval_name(keyval) == NULL) {
5107 warnx("invalid keybinding name %s", key);
5108 return (1);
5111 /* Remove eventual dupes. */
5112 TAILQ_FOREACH(k, &kbl, entry)
5113 if (k->key == keyval && k->mask == mask) {
5114 TAILQ_REMOVE(&kbl, k, entry);
5115 g_free(k);
5116 break;
5119 /* add keyname */
5120 k = g_malloc0(sizeof *k);
5121 k->cmd = g_strdup(cmd);
5122 k->mask = mask;
5123 k->use_in_entry = use_in_entry;
5124 k->key = keyval;
5126 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5127 k->cmd,
5128 k->mask,
5129 k->use_in_entry,
5130 k->key);
5131 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5132 k->cmd, gdk_keyval_name(keyval));
5134 TAILQ_INSERT_HEAD(&kbl, k, entry);
5136 return (0);
5140 add_kb(struct settings *s, char *entry)
5142 char *kb, *key;
5144 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5146 /* clearall is special */
5147 if (!strcmp(entry, "clearall")) {
5148 keybinding_clearall();
5149 return (0);
5152 kb = strstr(entry, ",");
5153 if (kb == NULL)
5154 return (1);
5155 *kb = '\0';
5156 key = kb + 1;
5158 return (keybinding_add(entry, key, key[0] == '!'));
5161 struct cmd {
5162 char *cmd;
5163 int level;
5164 int (*func)(struct tab *, struct karg *);
5165 int arg;
5166 int type;
5167 } cmds[] = {
5168 { "command", 0, command, ':', 0 },
5169 { "search", 0, command, '/', 0 },
5170 { "searchb", 0, command, '?', 0 },
5171 { "togglesrc", 0, toggle_src, 0, 0 },
5173 /* yanking and pasting */
5174 { "yankuri", 0, yank_uri, 0, 0 },
5175 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5176 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5177 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5179 /* search */
5180 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5181 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5183 /* focus */
5184 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5185 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5187 /* hinting */
5188 { "hinting", 0, hint, 0, 0 },
5190 /* custom stylesheet */
5191 { "userstyle", 0, userstyle, 0, 0 },
5193 /* navigation */
5194 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5195 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5196 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5197 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
5199 /* vertical movement */
5200 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5201 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5202 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5203 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5204 { "1", 0, move, XT_MOVE_TOP, 0 },
5205 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5206 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5207 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5208 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5209 /* horizontal movement */
5210 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5211 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5212 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5213 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5215 { "favorites", 0, xtp_page_fl, 0, 0 },
5216 { "fav", 0, xtp_page_fl, 0, 0 },
5217 { "favadd", 0, add_favorite, 0, 0 },
5219 { "qall", 0, quit, 0, 0 },
5220 { "quitall", 0, quit, 0, 0 },
5221 { "w", 0, save_tabs, 0, 0 },
5222 { "wq", 0, save_tabs_and_quit, 0, 0 },
5223 { "help", 0, help, 0, 0 },
5224 { "about", 0, about, 0, 0 },
5225 { "stats", 0, stats, 0, 0 },
5226 { "version", 0, about, 0, 0 },
5228 /* js command */
5229 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5230 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5231 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5232 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5233 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5234 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5235 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5236 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5237 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5238 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5239 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5241 /* cookie command */
5242 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5243 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5244 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5245 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5246 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5247 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5248 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5249 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5250 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5251 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5252 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5254 /* toplevel (domain) command */
5255 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5256 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5258 /* cookie jar */
5259 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5261 /* cert command */
5262 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5263 { "save", 1, cert_cmd, XT_SAVE, 0 },
5264 { "show", 1, cert_cmd, XT_SHOW, 0 },
5266 { "ca", 0, ca_cmd, 0, 0 },
5267 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5268 { "dl", 0, xtp_page_dl, 0, 0 },
5269 { "h", 0, xtp_page_hl, 0, 0 },
5270 { "history", 0, xtp_page_hl, 0, 0 },
5271 { "home", 0, go_home, 0, 0 },
5272 { "restart", 0, restart, 0, 0 },
5273 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5274 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5275 { "statustoggle", 0, statustoggle, 0, 0 },
5276 { "run_script", 0, run_page_script, 0, XT_USERARG },
5278 { "print", 0, print_page, 0, 0 },
5280 /* tabs */
5281 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5282 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5283 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5284 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5285 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5286 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5287 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5288 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5289 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5290 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5291 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5292 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5293 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5294 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5295 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5296 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5297 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5298 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5299 { "buffers", 0, buffers, 0, 0 },
5300 { "ls", 0, buffers, 0, 0 },
5301 { "tabs", 0, buffers, 0, 0 },
5303 /* command aliases (handy when -S flag is used) */
5304 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5305 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5306 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5307 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5309 /* settings */
5310 { "set", 0, set, 0, 0 },
5311 { "fullscreen", 0, fullscreen, 0, 0 },
5312 { "f", 0, fullscreen, 0, 0 },
5314 /* sessions */
5315 { "session", 0, session_cmd, XT_SHOW, 0 },
5316 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5317 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5318 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5319 { "show", 1, session_cmd, XT_SHOW, 0 },
5322 struct {
5323 int index;
5324 int len;
5325 gchar *list[256];
5326 } cmd_status = {-1, 0};
5328 gboolean
5329 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5332 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5333 btn_down = 0;
5335 return (FALSE);
5338 gboolean
5339 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5341 struct karg a;
5343 hide_oops(t);
5344 hide_buffers(t);
5346 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5347 btn_down = 1;
5348 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5349 /* go backward */
5350 a.i = XT_NAV_BACK;
5351 navaction(t, &a);
5353 return (TRUE);
5354 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5355 /* go forward */
5356 a.i = XT_NAV_FORWARD;
5357 navaction(t, &a);
5359 return (TRUE);
5362 return (FALSE);
5365 gboolean
5366 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5368 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5370 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5371 delete_tab(t);
5373 return (FALSE);
5377 * cancel, remove, etc. downloads
5379 void
5380 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5382 struct download find, *d = NULL;
5384 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5386 /* some commands require a valid download id */
5387 if (cmd != XT_XTP_DL_LIST) {
5388 /* lookup download in question */
5389 find.id = id;
5390 d = RB_FIND(download_list, &downloads, &find);
5392 if (d == NULL) {
5393 show_oops(t, "%s: no such download", __func__);
5394 return;
5398 /* decide what to do */
5399 switch (cmd) {
5400 case XT_XTP_DL_CANCEL:
5401 webkit_download_cancel(d->download);
5402 break;
5403 case XT_XTP_DL_REMOVE:
5404 webkit_download_cancel(d->download); /* just incase */
5405 g_object_unref(d->download);
5406 RB_REMOVE(download_list, &downloads, d);
5407 break;
5408 case XT_XTP_DL_LIST:
5409 /* Nothing */
5410 break;
5411 default:
5412 show_oops(t, "%s: unknown command", __func__);
5413 break;
5415 xtp_page_dl(t, NULL);
5419 * Actions on history, only does one thing for now, but
5420 * we provide the function for future actions
5422 void
5423 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5425 struct history *h, *next;
5426 int i = 1;
5428 switch (cmd) {
5429 case XT_XTP_HL_REMOVE:
5430 /* walk backwards, as listed in reverse */
5431 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5432 next = RB_PREV(history_list, &hl, h);
5433 if (id == i) {
5434 RB_REMOVE(history_list, &hl, h);
5435 g_free((gpointer) h->title);
5436 g_free((gpointer) h->uri);
5437 g_free(h);
5438 break;
5440 i++;
5442 break;
5443 case XT_XTP_HL_LIST:
5444 /* Nothing - just xtp_page_hl() below */
5445 break;
5446 default:
5447 show_oops(t, "%s: unknown command", __func__);
5448 break;
5451 xtp_page_hl(t, NULL);
5454 /* remove a favorite */
5455 void
5456 remove_favorite(struct tab *t, int index)
5458 char file[PATH_MAX], *title, *uri = NULL;
5459 char *new_favs, *tmp;
5460 FILE *f;
5461 int i;
5462 size_t len, lineno;
5464 /* open favorites */
5465 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5467 if ((f = fopen(file, "r")) == NULL) {
5468 show_oops(t, "%s: can't open favorites: %s",
5469 __func__, strerror(errno));
5470 return;
5473 /* build a string which will become the new favroites file */
5474 new_favs = g_strdup("");
5476 for (i = 1;;) {
5477 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5478 if (feof(f) || ferror(f))
5479 break;
5480 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5481 if (len == 0) {
5482 free(title);
5483 title = NULL;
5484 continue;
5487 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5488 if (feof(f) || ferror(f)) {
5489 show_oops(t, "%s: can't parse favorites %s",
5490 __func__, strerror(errno));
5491 goto clean;
5495 /* as long as this isn't the one we are deleting add to file */
5496 if (i != index) {
5497 tmp = new_favs;
5498 new_favs = g_strdup_printf("%s%s\n%s\n",
5499 new_favs, title, uri);
5500 g_free(tmp);
5503 free(uri);
5504 uri = NULL;
5505 free(title);
5506 title = NULL;
5507 i++;
5509 fclose(f);
5511 /* write back new favorites file */
5512 if ((f = fopen(file, "w")) == NULL) {
5513 show_oops(t, "%s: can't open favorites: %s",
5514 __func__, strerror(errno));
5515 goto clean;
5518 fwrite(new_favs, strlen(new_favs), 1, f);
5519 fclose(f);
5521 clean:
5522 if (uri)
5523 free(uri);
5524 if (title)
5525 free(title);
5527 g_free(new_favs);
5530 void
5531 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5533 switch (cmd) {
5534 case XT_XTP_FL_LIST:
5535 /* nothing, just the below call to xtp_page_fl() */
5536 break;
5537 case XT_XTP_FL_REMOVE:
5538 remove_favorite(t, arg);
5539 break;
5540 default:
5541 show_oops(t, "%s: invalid favorites command", __func__);
5542 break;
5545 xtp_page_fl(t, NULL);
5548 void
5549 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5551 switch (cmd) {
5552 case XT_XTP_CL_LIST:
5553 /* nothing, just xtp_page_cl() */
5554 break;
5555 case XT_XTP_CL_REMOVE:
5556 remove_cookie(arg);
5557 break;
5558 default:
5559 show_oops(t, "%s: unknown cookie xtp command", __func__);
5560 break;
5563 xtp_page_cl(t, NULL);
5566 /* link an XTP class to it's session key and handler function */
5567 struct xtp_despatch {
5568 uint8_t xtp_class;
5569 char **session_key;
5570 void (*handle_func)(struct tab *, uint8_t, int);
5573 struct xtp_despatch xtp_despatches[] = {
5574 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5575 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5576 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5577 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5578 { XT_XTP_INVALID, NULL, NULL }
5582 * is the url xtp protocol? (xxxt://)
5583 * if so, parse and despatch correct bahvior
5586 parse_xtp_url(struct tab *t, const char *url)
5588 char *dup = NULL, *p, *last;
5589 uint8_t n_tokens = 0;
5590 char *tokens[4] = {NULL, NULL, NULL, ""};
5591 struct xtp_despatch *dsp, *dsp_match = NULL;
5592 uint8_t req_class;
5593 int ret = FALSE;
5596 * tokens array meaning:
5597 * tokens[0] = class
5598 * tokens[1] = session key
5599 * tokens[2] = action
5600 * tokens[3] = optional argument
5603 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5605 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5606 goto clean;
5608 dup = g_strdup(url + strlen(XT_XTP_STR));
5610 /* split out the url */
5611 for ((p = strtok_r(dup, "/", &last)); p;
5612 (p = strtok_r(NULL, "/", &last))) {
5613 if (n_tokens < 4)
5614 tokens[n_tokens++] = p;
5617 /* should be atleast three fields 'class/seskey/command/arg' */
5618 if (n_tokens < 3)
5619 goto clean;
5621 dsp = xtp_despatches;
5622 req_class = atoi(tokens[0]);
5623 while (dsp->xtp_class) {
5624 if (dsp->xtp_class == req_class) {
5625 dsp_match = dsp;
5626 break;
5628 dsp++;
5631 /* did we find one atall? */
5632 if (dsp_match == NULL) {
5633 show_oops(t, "%s: no matching xtp despatch found", __func__);
5634 goto clean;
5637 /* check session key and call despatch function */
5638 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5639 ret = TRUE; /* all is well, this was a valid xtp request */
5640 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5643 clean:
5644 if (dup)
5645 g_free(dup);
5647 return (ret);
5652 void
5653 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5655 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5657 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5659 if (t == NULL) {
5660 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5661 return;
5664 if (uri == NULL) {
5665 show_oops(t, "activate_uri_entry_cb no uri");
5666 return;
5669 uri += strspn(uri, "\t ");
5671 /* if xxxt:// treat specially */
5672 if (parse_xtp_url(t, uri))
5673 return;
5675 /* otherwise continue to load page normally */
5676 load_uri(t, (gchar *)uri);
5677 focus_webview(t);
5680 void
5681 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5683 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5684 char *newuri = NULL;
5685 gchar *enc_search;
5687 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5689 if (t == NULL) {
5690 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5691 return;
5694 if (search_string == NULL) {
5695 show_oops(t, "no search_string");
5696 return;
5699 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5701 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5702 newuri = g_strdup_printf(search_string, enc_search);
5703 g_free(enc_search);
5705 webkit_web_view_load_uri(t->wv, newuri);
5706 focus_webview(t);
5708 if (newuri)
5709 g_free(newuri);
5712 void
5713 check_and_set_js(const gchar *uri, struct tab *t)
5715 struct domain *d = NULL;
5716 int es = 0;
5718 if (uri == NULL || t == NULL)
5719 return;
5721 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5722 es = 0;
5723 else
5724 es = 1;
5726 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5727 es ? "enable" : "disable", uri);
5729 g_object_set(G_OBJECT(t->settings),
5730 "enable-scripts", es, (char *)NULL);
5731 g_object_set(G_OBJECT(t->settings),
5732 "javascript-can-open-windows-automatically", es, (char *)NULL);
5733 webkit_web_view_set_settings(t->wv, t->settings);
5735 button_set_stockid(t->js_toggle,
5736 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5739 void
5740 show_ca_status(struct tab *t, const char *uri)
5742 WebKitWebFrame *frame;
5743 WebKitWebDataSource *source;
5744 WebKitNetworkRequest *request;
5745 SoupMessage *message;
5746 GdkColor color;
5747 gchar *col_str = XT_COLOR_WHITE;
5748 int r;
5750 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5751 ssl_strict_certs, ssl_ca_file, uri);
5753 if (uri == NULL)
5754 goto done;
5755 if (ssl_ca_file == NULL) {
5756 if (g_str_has_prefix(uri, "http://"))
5757 goto done;
5758 if (g_str_has_prefix(uri, "https://")) {
5759 col_str = XT_COLOR_RED;
5760 goto done;
5762 return;
5764 if (g_str_has_prefix(uri, "http://") ||
5765 !g_str_has_prefix(uri, "https://"))
5766 goto done;
5768 frame = webkit_web_view_get_main_frame(t->wv);
5769 source = webkit_web_frame_get_data_source(frame);
5770 request = webkit_web_data_source_get_request(source);
5771 message = webkit_network_request_get_message(request);
5773 if (message && (soup_message_get_flags(message) &
5774 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5775 col_str = XT_COLOR_GREEN;
5776 goto done;
5777 } else {
5778 r = load_compare_cert(t, NULL);
5779 if (r == 0)
5780 col_str = XT_COLOR_BLUE;
5781 else if (r == 1)
5782 col_str = XT_COLOR_YELLOW;
5783 else
5784 col_str = XT_COLOR_RED;
5785 goto done;
5787 done:
5788 if (col_str) {
5789 gdk_color_parse(col_str, &color);
5790 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5792 if (!strcmp(col_str, XT_COLOR_WHITE))
5793 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5794 else
5795 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5799 void
5800 free_favicon(struct tab *t)
5802 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5803 __func__, t->icon_download, t->icon_request);
5805 if (t->icon_request)
5806 g_object_unref(t->icon_request);
5807 if (t->icon_dest_uri)
5808 g_free(t->icon_dest_uri);
5810 t->icon_request = NULL;
5811 t->icon_dest_uri = NULL;
5814 void
5815 xt_icon_from_name(struct tab *t, gchar *name)
5817 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5818 GTK_ENTRY_ICON_PRIMARY, "text-html");
5819 if (show_url == 0)
5820 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5821 GTK_ENTRY_ICON_PRIMARY, "text-html");
5822 else
5823 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5824 GTK_ENTRY_ICON_PRIMARY, NULL);
5827 void
5828 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5830 GdkPixbuf *pb_scaled;
5832 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5833 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
5834 GDK_INTERP_BILINEAR);
5835 else
5836 pb_scaled = pb;
5838 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5839 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5840 if (show_url == 0)
5841 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
5842 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5843 else
5844 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5845 GTK_ENTRY_ICON_PRIMARY, NULL);
5847 if (pb_scaled != pb)
5848 g_object_unref(pb_scaled);
5851 void
5852 xt_icon_from_file(struct tab *t, char *file)
5854 GdkPixbuf *pb;
5856 if (g_str_has_prefix(file, "file://"))
5857 file += strlen("file://");
5859 pb = gdk_pixbuf_new_from_file(file, NULL);
5860 if (pb) {
5861 xt_icon_from_pixbuf(t, pb);
5862 g_object_unref(pb);
5863 } else
5864 xt_icon_from_name(t, "text-html");
5867 gboolean
5868 is_valid_icon(char *file)
5870 gboolean valid = 0;
5871 const char *mime_type;
5872 GFileInfo *fi;
5873 GFile *gf;
5875 gf = g_file_new_for_path(file);
5876 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5877 NULL, NULL);
5878 mime_type = g_file_info_get_content_type(fi);
5879 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5880 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5881 g_strcmp0(mime_type, "image/png") == 0 ||
5882 g_strcmp0(mime_type, "image/gif") == 0 ||
5883 g_strcmp0(mime_type, "application/octet-stream") == 0;
5884 g_object_unref(fi);
5885 g_object_unref(gf);
5887 return (valid);
5890 void
5891 set_favicon_from_file(struct tab *t, char *file)
5893 struct stat sb;
5895 if (t == NULL || file == NULL)
5896 return;
5898 if (g_str_has_prefix(file, "file://"))
5899 file += strlen("file://");
5900 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5902 if (!stat(file, &sb)) {
5903 if (sb.st_size == 0 || !is_valid_icon(file)) {
5904 /* corrupt icon so trash it */
5905 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5906 __func__, file);
5907 unlink(file);
5908 /* no need to set icon to default here */
5909 return;
5912 xt_icon_from_file(t, file);
5915 void
5916 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5917 WebKitWebView *wv)
5919 WebKitDownloadStatus status = webkit_download_get_status(download);
5920 struct tab *tt = NULL, *t = NULL;
5923 * find the webview instead of passing in the tab as it could have been
5924 * deleted from underneath us.
5926 TAILQ_FOREACH(tt, &tabs, entry) {
5927 if (tt->wv == wv) {
5928 t = tt;
5929 break;
5932 if (t == NULL)
5933 return;
5935 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5936 __func__, t->tab_id, status);
5938 switch (status) {
5939 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5940 /* -1 */
5941 t->icon_download = NULL;
5942 free_favicon(t);
5943 break;
5944 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5945 /* 0 */
5946 break;
5947 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5948 /* 1 */
5949 break;
5950 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5951 /* 2 */
5952 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5953 __func__, t->tab_id);
5954 t->icon_download = NULL;
5955 free_favicon(t);
5956 break;
5957 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5958 /* 3 */
5960 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5961 __func__, t->icon_dest_uri);
5962 set_favicon_from_file(t, t->icon_dest_uri);
5963 /* these will be freed post callback */
5964 t->icon_request = NULL;
5965 t->icon_download = NULL;
5966 break;
5967 default:
5968 break;
5972 void
5973 abort_favicon_download(struct tab *t)
5975 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5977 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
5978 if (t->icon_download) {
5979 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5980 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5981 webkit_download_cancel(t->icon_download);
5982 t->icon_download = NULL;
5983 } else
5984 free_favicon(t);
5985 #endif
5987 xt_icon_from_name(t, "text-html");
5990 void
5991 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5993 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5995 if (uri == NULL || t == NULL)
5996 return;
5998 #if WEBKIT_CHECK_VERSION(1, 4, 0)
5999 /* take icon from WebKitIconDatabase */
6000 GdkPixbuf *pb;
6002 pb = webkit_web_view_get_icon_pixbuf(wv);
6003 if (pb) {
6004 xt_icon_from_pixbuf(t, pb);
6005 g_object_unref(pb);
6006 } else
6007 xt_icon_from_name(t, "text-html");
6008 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6009 /* download icon to cache dir */
6010 gchar *name_hash, file[PATH_MAX];
6011 struct stat sb;
6013 if (t->icon_request) {
6014 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6015 return;
6018 /* check to see if we got the icon in cache */
6019 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6020 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6021 g_free(name_hash);
6023 if (!stat(file, &sb)) {
6024 if (sb.st_size > 0) {
6025 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6026 __func__, file);
6027 set_favicon_from_file(t, file);
6028 return;
6031 /* corrupt icon so trash it */
6032 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6033 __func__, file);
6034 unlink(file);
6037 /* create download for icon */
6038 t->icon_request = webkit_network_request_new(uri);
6039 if (t->icon_request == NULL) {
6040 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6041 __func__, uri);
6042 return;
6045 t->icon_download = webkit_download_new(t->icon_request);
6046 if (t->icon_download == NULL)
6047 return;
6049 /* we have to free icon_dest_uri later */
6050 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6051 webkit_download_set_destination_uri(t->icon_download,
6052 t->icon_dest_uri);
6054 if (webkit_download_get_status(t->icon_download) ==
6055 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6056 g_object_unref(t->icon_request);
6057 g_free(t->icon_dest_uri);
6058 t->icon_request = NULL;
6059 t->icon_dest_uri = NULL;
6060 return;
6063 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6064 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6066 webkit_download_start(t->icon_download);
6067 #endif
6070 void
6071 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6073 const gchar *uri = NULL, *title = NULL;
6074 struct history *h, find;
6075 struct karg a;
6077 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6078 webkit_web_view_get_load_status(wview), get_uri(t) ? get_uri(t) : "NOTHING");
6080 if (t == NULL) {
6081 show_oops(NULL, "notify_load_status_cb invalid parameters");
6082 return;
6085 switch (webkit_web_view_get_load_status(wview)) {
6086 case WEBKIT_LOAD_PROVISIONAL:
6087 /* 0 */
6088 abort_favicon_download(t);
6089 #if GTK_CHECK_VERSION(2, 20, 0)
6090 gtk_widget_show(t->spinner);
6091 gtk_spinner_start(GTK_SPINNER(t->spinner));
6092 #endif
6093 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6095 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6097 /* take focus if we are visible */
6098 focus_webview(t);
6099 t->focus_wv = 1;
6101 break;
6103 case WEBKIT_LOAD_COMMITTED:
6104 /* 1 */
6105 uri = get_uri(t);
6106 if (uri == NULL)
6107 return;
6108 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6110 if (t->status) {
6111 g_free(t->status);
6112 t->status = NULL;
6114 set_status(t, (char *)uri, XT_STATUS_LOADING);
6116 /* check if js white listing is enabled */
6117 if (enable_js_whitelist) {
6118 check_and_set_js(uri, t);
6121 if (t->styled)
6122 apply_style(t);
6124 show_ca_status(t, uri);
6126 /* we know enough to autosave the session */
6127 if (session_autosave) {
6128 a.s = NULL;
6129 save_tabs(t, &a);
6131 break;
6133 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
6134 /* 3 */
6135 break;
6137 case WEBKIT_LOAD_FINISHED:
6138 /* 2 */
6139 uri = get_uri(t);
6140 if (uri == NULL)
6141 return;
6143 if (!strncmp(uri, "http://", strlen("http://")) ||
6144 !strncmp(uri, "https://", strlen("https://")) ||
6145 !strncmp(uri, "file://", strlen("file://"))) {
6146 find.uri = uri;
6147 h = RB_FIND(history_list, &hl, &find);
6148 if (!h) {
6149 title = get_title(t, FALSE);
6150 h = g_malloc(sizeof *h);
6151 h->uri = g_strdup(uri);
6152 h->title = g_strdup(title);
6153 RB_INSERT(history_list, &hl, h);
6154 completion_add_uri(h->uri);
6155 update_history_tabs(NULL);
6159 set_status(t, (char *)uri, XT_STATUS_URI);
6160 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6161 case WEBKIT_LOAD_FAILED:
6162 /* 4 */
6163 #endif
6164 #if GTK_CHECK_VERSION(2, 20, 0)
6165 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6166 gtk_widget_hide(t->spinner);
6167 #endif
6168 default:
6169 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
6170 break;
6173 if (t->item)
6174 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
6175 else
6176 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
6177 webkit_web_view_can_go_back(wview));
6179 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
6180 webkit_web_view_can_go_forward(wview));
6183 gboolean
6184 notify_load_error_cb(WebKitWebView* wview, WebKitWebFrame *web_frame, gchar *uri, gpointer web_error,struct tab *t)
6186 if (t->tmp_uri)
6187 g_free(t->tmp_uri);
6188 t->tmp_uri = g_strdup(uri);
6189 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
6190 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
6191 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6192 set_status(t, uri, XT_STATUS_NOTHING);
6194 return FALSE;
6197 void
6198 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6200 const gchar *title = NULL, *win_title = NULL;
6202 title = get_title(t, FALSE);
6203 win_title = get_title(t, TRUE);
6204 gtk_label_set_text(GTK_LABEL(t->label), title);
6205 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
6206 if (t->tab_id == gtk_notebook_get_current_page(notebook))
6207 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
6210 void
6211 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6213 run_script(t, JS_HINTING);
6216 void
6217 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
6219 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
6220 progress == 100 ? 0 : (double)progress / 100);
6221 if (show_url == 0) {
6222 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
6223 progress == 100 ? 0 : (double)progress / 100);
6226 update_statusbar_position(NULL, NULL);
6230 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
6231 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
6232 WebKitWebPolicyDecision *pd, struct tab *t)
6234 char *uri;
6235 WebKitWebNavigationReason reason;
6236 struct domain *d = NULL;
6238 if (t == NULL) {
6239 show_oops(NULL, "webview_npd_cb invalid parameters");
6240 return (FALSE);
6243 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6244 t->ctrl_click,
6245 webkit_network_request_get_uri(request));
6247 uri = (char *)webkit_network_request_get_uri(request);
6249 /* if this is an xtp url, we don't load anything else */
6250 if (parse_xtp_url(t, uri))
6251 return (TRUE);
6253 if (t->ctrl_click) {
6254 t->ctrl_click = 0;
6255 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6256 webkit_web_policy_decision_ignore(pd);
6257 return (TRUE); /* we made the decission */
6261 * This is a little hairy but it comes down to this:
6262 * when we run in whitelist mode we have to assist the browser in
6263 * opening the URL that it would have opened in a new tab.
6265 reason = webkit_web_navigation_action_get_reason(na);
6266 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6267 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6268 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6269 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6270 load_uri(t, uri);
6271 webkit_web_policy_decision_use(pd);
6272 return (TRUE); /* we made the decision */
6275 return (FALSE);
6278 WebKitWebView *
6279 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6281 struct tab *tt;
6282 struct domain *d = NULL;
6283 const gchar *uri;
6284 WebKitWebView *webview = NULL;
6286 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6287 webkit_web_view_get_uri(wv));
6289 if (tabless) {
6290 /* open in current tab */
6291 webview = t->wv;
6292 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6293 uri = webkit_web_view_get_uri(wv);
6294 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6295 return (NULL);
6297 tt = create_new_tab(NULL, NULL, 1, -1);
6298 webview = tt->wv;
6299 } else if (enable_scripts == 1) {
6300 tt = create_new_tab(NULL, NULL, 1, -1);
6301 webview = tt->wv;
6304 return (webview);
6307 gboolean
6308 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6310 const gchar *uri;
6311 struct domain *d = NULL;
6313 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6315 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6316 uri = webkit_web_view_get_uri(wv);
6317 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6318 return (FALSE);
6320 delete_tab(t);
6321 } else if (enable_scripts == 1)
6322 delete_tab(t);
6324 return (TRUE);
6328 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6330 /* we can not eat the event without throwing gtk off so defer it */
6332 /* catch middle click */
6333 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6334 t->ctrl_click = 1;
6335 goto done;
6338 /* catch ctrl click */
6339 if (e->type == GDK_BUTTON_RELEASE &&
6340 CLEAN(e->state) == GDK_CONTROL_MASK)
6341 t->ctrl_click = 1;
6342 else
6343 t->ctrl_click = 0;
6344 done:
6345 return (XT_CB_PASSTHROUGH);
6349 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6351 struct mime_type *m;
6353 m = find_mime_type(mime_type);
6354 if (m == NULL)
6355 return (1);
6356 if (m->mt_download)
6357 return (1);
6359 switch (fork()) {
6360 case -1:
6361 show_oops(t, "can't fork mime handler");
6362 return (1);
6363 /* NOTREACHED */
6364 case 0:
6365 break;
6366 default:
6367 return (0);
6370 /* child */
6371 execlp(m->mt_action, m->mt_action,
6372 webkit_network_request_get_uri(request), (void *)NULL);
6374 _exit(0);
6376 /* NOTREACHED */
6377 return (0);
6380 const gchar *
6381 get_mime_type(char *file)
6383 const char *mime_type;
6384 GFileInfo *fi;
6385 GFile *gf;
6387 if (g_str_has_prefix(file, "file://"))
6388 file += strlen("file://");
6390 gf = g_file_new_for_path(file);
6391 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6392 NULL, NULL);
6393 mime_type = g_file_info_get_content_type(fi);
6394 g_object_unref(fi);
6395 g_object_unref(gf);
6397 return (mime_type);
6401 run_download_mimehandler(char *mime_type, char *file)
6403 struct mime_type *m;
6405 m = find_mime_type(mime_type);
6406 if (m == NULL)
6407 return (1);
6409 switch (fork()) {
6410 case -1:
6411 show_oops(NULL, "can't fork download mime handler");
6412 return (1);
6413 /* NOTREACHED */
6414 case 0:
6415 break;
6416 default:
6417 return (0);
6420 /* child */
6421 if (g_str_has_prefix(file, "file://"))
6422 file += strlen("file://");
6423 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6425 _exit(0);
6427 /* NOTREACHED */
6428 return (0);
6431 void
6432 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6433 WebKitWebView *wv)
6435 WebKitDownloadStatus status;
6436 const gchar *file = NULL, *mime = NULL;
6438 if (download == NULL)
6439 return;
6440 status = webkit_download_get_status(download);
6441 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6442 return;
6444 file = webkit_download_get_destination_uri(download);
6445 if (file == NULL)
6446 return;
6447 mime = get_mime_type((char *)file);
6448 if (mime == NULL)
6449 return;
6451 run_download_mimehandler((char *)mime, (char *)file);
6455 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6456 WebKitNetworkRequest *request, char *mime_type,
6457 WebKitWebPolicyDecision *decision, struct tab *t)
6459 if (t == NULL) {
6460 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6461 return (FALSE);
6464 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6465 t->tab_id, mime_type);
6467 if (run_mimehandler(t, mime_type, request) == 0) {
6468 webkit_web_policy_decision_ignore(decision);
6469 focus_webview(t);
6470 return (TRUE);
6473 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6474 webkit_web_policy_decision_download(decision);
6475 return (TRUE);
6478 return (FALSE);
6482 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6483 struct tab *t)
6485 struct stat sb;
6486 const gchar *suggested_name;
6487 gchar *filename = NULL;
6488 char *uri = NULL;
6489 struct download *download_entry;
6490 int i, ret = TRUE;
6492 if (wk_download == NULL || t == NULL) {
6493 show_oops(NULL, "%s invalid parameters", __func__);
6494 return (FALSE);
6497 suggested_name = webkit_download_get_suggested_filename(wk_download);
6498 if (suggested_name == NULL)
6499 return (FALSE); /* abort download */
6501 i = 0;
6502 do {
6503 if (filename) {
6504 g_free(filename);
6505 filename = NULL;
6507 if (i) {
6508 g_free(uri);
6509 uri = NULL;
6510 filename = g_strdup_printf("%d%s", i, suggested_name);
6512 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6513 filename : suggested_name);
6514 i++;
6515 } while (!stat(uri + strlen("file://"), &sb));
6517 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6518 "local %s\n", __func__, t->tab_id, filename, uri);
6520 webkit_download_set_destination_uri(wk_download, uri);
6522 if (webkit_download_get_status(wk_download) ==
6523 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6524 show_oops(t, "%s: download failed to start", __func__);
6525 ret = FALSE;
6526 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6527 } else {
6528 /* connect "download first" mime handler */
6529 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6530 G_CALLBACK(download_status_changed_cb), NULL);
6532 download_entry = g_malloc(sizeof(struct download));
6533 download_entry->download = wk_download;
6534 download_entry->tab = t;
6535 download_entry->id = next_download_id++;
6536 RB_INSERT(download_list, &downloads, download_entry);
6537 /* get from history */
6538 g_object_ref(wk_download);
6539 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6540 show_oops(t, "Download of '%s' started...",
6541 basename((char *)webkit_download_get_destination_uri(wk_download)));
6544 if (uri)
6545 g_free(uri);
6547 if (filename)
6548 g_free(filename);
6550 /* sync other download manager tabs */
6551 update_download_tabs(NULL);
6554 * NOTE: never redirect/render the current tab before this
6555 * function returns. This will cause the download to never start.
6557 return (ret); /* start download */
6560 void
6561 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6563 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6565 if (t == NULL) {
6566 show_oops(NULL, "webview_hover_cb");
6567 return;
6570 if (uri)
6571 set_status(t, uri, XT_STATUS_LINK);
6572 else {
6573 if (t->status)
6574 set_status(t, t->status, XT_STATUS_NOTHING);
6578 /* buffer commands receive the regex that triggered them in arg.s */
6579 char bcmd[8];
6580 struct buffercmd {
6581 char *regex;
6582 int (*func)(struct tab *, struct karg *);
6583 int arg;
6584 regex_t cregex;
6585 } buffercmds[] = {
6586 { "^gg$", move, XT_MOVE_TOP },
6587 { "^gG$", move, XT_MOVE_BOTTOM },
6588 { "^gh$", go_home, 0 },
6589 { "^ZR$", restart, 0 },
6590 { "^ZZ$", quit, 0 },
6591 { "^zi$", resizetab, XT_ZOOM_IN },
6592 { "^zo$", resizetab, XT_ZOOM_OUT },
6593 { "^z0$", resizetab, XT_ZOOM_NORMAL },
6596 void
6597 buffercmd_init()
6599 int i;
6601 for (i = 0; i < LENGTH(buffercmds); i++)
6602 regcomp(&buffercmds[i].cregex, buffercmds[i].regex, REG_EXTENDED);
6605 void
6606 buffercmd_abort(struct tab *t)
6608 int i;
6610 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
6611 for (i = 0; i < LENGTH(bcmd); i++)
6612 bcmd[i] = '\0';
6614 cmd_prefix = 0; /* clear prefix for non-buffer commands */
6615 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6618 void
6619 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
6621 struct karg arg = {0, NULL, -1};
6623 arg.i = cmd->arg;
6624 arg.s = g_strdup(bcmd);
6626 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
6627 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
6628 cmd->func(t, &arg);
6630 if (arg.s)
6631 g_free(arg.s);
6633 buffercmd_abort(t);
6636 gboolean
6637 buffercmd_addkey(struct tab *t, guint keyval)
6639 int i;
6641 if (keyval == GDK_Escape) {
6642 buffercmd_abort(t);
6643 return XT_CB_HANDLED;
6646 /* key with modifier or non-ascii character */
6647 if (!isascii(keyval))
6648 return XT_CB_PASSTHROUGH;
6650 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
6651 "to buffer \"%s\"\n", keyval, bcmd);
6653 for (i = 0; i < LENGTH(bcmd); i++)
6654 if (bcmd[i] == '\0') {
6655 bcmd[i] = keyval;
6656 break;
6659 /* buffer full, ignore input */
6660 if (i == LENGTH(bcmd)) {
6661 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
6662 return XT_CB_HANDLED;
6665 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6667 for (i = 0; i < LENGTH(buffercmds); i++)
6668 if (regexec(&buffercmds[i].cregex, bcmd,
6669 (size_t) 0, NULL, 0) == 0) {
6670 buffercmd_execute(t, &buffercmds[i]);
6671 break;
6674 return XT_CB_HANDLED;
6677 gboolean
6678 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6680 struct key_binding *k;
6682 /* handle keybindings if buffercmd is empty.
6683 if not empty, allow commands like C-n */
6684 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
6685 TAILQ_FOREACH(k, &kbl, entry)
6686 if (e->keyval == k->key
6687 && (entry ? k->use_in_entry : 1)) {
6688 if (k->mask == 0) {
6689 if ((e->state & (CTRL | MOD1)) == 0)
6690 return (cmd_execute(t, k->cmd));
6691 } else if ((e->state & k->mask) == k->mask) {
6692 return (cmd_execute(t, k->cmd));
6696 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
6697 return buffercmd_addkey(t, e->keyval);
6699 return (XT_CB_PASSTHROUGH);
6703 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6705 char s[2], buf[128];
6706 const char *errstr = NULL;
6708 /* don't use w directly; use t->whatever instead */
6710 if (t == NULL) {
6711 show_oops(NULL, "wv_keypress_after_cb");
6712 return (XT_CB_PASSTHROUGH);
6715 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6716 e->keyval, e->state, t);
6718 if (t->hints_on) {
6719 /* ESC */
6720 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6721 disable_hints(t);
6722 return (XT_CB_HANDLED);
6725 /* RETURN */
6726 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6727 if (errstr) {
6728 /* we have a string */
6729 } else {
6730 /* we have a number */
6731 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6732 t->hint_num);
6733 run_script(t, buf);
6735 disable_hints(t);
6738 /* BACKSPACE */
6739 /* XXX unfuck this */
6740 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6741 if (t->hint_mode == XT_HINT_NUMERICAL) {
6742 /* last input was numerical */
6743 int l;
6744 l = strlen(t->hint_num);
6745 if (l > 0) {
6746 l--;
6747 if (l == 0) {
6748 disable_hints(t);
6749 enable_hints(t);
6750 } else {
6751 t->hint_num[l] = '\0';
6752 goto num;
6755 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6756 /* last input was alphanumerical */
6757 int l;
6758 l = strlen(t->hint_buf);
6759 if (l > 0) {
6760 l--;
6761 if (l == 0) {
6762 disable_hints(t);
6763 enable_hints(t);
6764 } else {
6765 t->hint_buf[l] = '\0';
6766 goto anum;
6769 } else {
6770 /* bogus */
6771 disable_hints(t);
6775 /* numerical input */
6776 if (CLEAN(e->state) == 0 &&
6777 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6778 snprintf(s, sizeof s, "%c", e->keyval);
6779 strlcat(t->hint_num, s, sizeof t->hint_num);
6780 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6781 t->hint_num);
6782 num:
6783 if (errstr) {
6784 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6785 disable_hints(t);
6786 } else {
6787 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6788 t->hint_num);
6789 t->hint_mode = XT_HINT_NUMERICAL;
6790 run_script(t, buf);
6793 /* empty the counter buffer */
6794 bzero(t->hint_buf, sizeof t->hint_buf);
6795 return (XT_CB_HANDLED);
6798 /* alphanumerical input */
6799 if (
6800 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6801 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6802 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6803 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6804 snprintf(s, sizeof s, "%c", e->keyval);
6805 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6806 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6807 t->hint_buf);
6808 anum:
6809 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6810 run_script(t, buf);
6812 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6813 t->hint_buf);
6814 t->hint_mode = XT_HINT_ALPHANUM;
6815 run_script(t, buf);
6817 /* empty the counter buffer */
6818 bzero(t->hint_num, sizeof t->hint_num);
6819 return (XT_CB_HANDLED);
6822 return (XT_CB_HANDLED);
6823 } else {
6824 /* prefix input*/
6825 snprintf(s, sizeof s, "%c", e->keyval);
6826 if (CLEAN(e->state) == 0 && isdigit(s[0]))
6827 cmd_prefix = 10 * cmd_prefix + atoi(s);
6830 return (handle_keypress(t, e, 0));
6834 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6836 hide_oops(t);
6838 /* Hide buffers, if they are visible, with escape. */
6839 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
6840 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6841 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6842 hide_buffers(t);
6843 return (XT_CB_HANDLED);
6846 return (XT_CB_PASSTHROUGH);
6850 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6852 const gchar *c = gtk_entry_get_text(w);
6853 GdkColor color;
6854 int forward = TRUE;
6856 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6857 e->keyval, e->state, t);
6859 if (t == NULL) {
6860 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
6861 return (XT_CB_PASSTHROUGH);
6864 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6865 e->keyval, e->state, t);
6867 if (c[0] == ':')
6868 goto done;
6869 if (strlen(c) == 1) {
6870 webkit_web_view_unmark_text_matches(t->wv);
6871 goto done;
6874 if (c[0] == '/')
6875 forward = TRUE;
6876 else if (c[0] == '?')
6877 forward = FALSE;
6878 else
6879 goto done;
6881 /* search */
6882 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6883 FALSE) {
6884 /* not found, mark red */
6885 gdk_color_parse(XT_COLOR_RED, &color);
6886 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6887 /* unmark and remove selection */
6888 webkit_web_view_unmark_text_matches(t->wv);
6889 /* my kingdom for a way to unselect text in webview */
6890 } else {
6891 /* found, highlight all */
6892 webkit_web_view_unmark_text_matches(t->wv);
6893 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6894 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6895 gdk_color_parse(XT_COLOR_WHITE, &color);
6896 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6898 done:
6899 return (XT_CB_PASSTHROUGH);
6902 gboolean
6903 match_uri(const gchar *uri, const gchar *key) {
6904 gchar *voffset;
6905 size_t len;
6906 gboolean match = FALSE;
6908 len = strlen(key);
6910 if (!strncmp(key, uri, len))
6911 match = TRUE;
6912 else {
6913 voffset = strstr(uri, "/") + 2;
6914 if (!strncmp(key, voffset, len))
6915 match = TRUE;
6916 else if (g_str_has_prefix(voffset, "www.")) {
6917 voffset = voffset + strlen("www.");
6918 if (!strncmp(key, voffset, len))
6919 match = TRUE;
6923 return (match);
6926 void
6927 cmd_getlist(int id, char *key)
6929 int i, dep, c = 0;
6930 struct history *h;
6932 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6933 RB_FOREACH_REVERSE(h, history_list, &hl)
6934 if (match_uri(h->uri, key)) {
6935 cmd_status.list[c] = (char *)h->uri;
6936 if (++c > 255)
6937 break;
6940 cmd_status.len = c;
6941 return;
6944 dep = (id == -1) ? 0 : cmds[id].level + 1;
6946 for (i = id + 1; i < LENGTH(cmds); i++) {
6947 if (cmds[i].level < dep)
6948 break;
6949 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6950 cmd_status.list[c++] = cmds[i].cmd;
6954 cmd_status.len = c;
6957 char *
6958 cmd_getnext(int dir)
6960 cmd_status.index += dir;
6962 if (cmd_status.index < 0)
6963 cmd_status.index = cmd_status.len - 1;
6964 else if (cmd_status.index >= cmd_status.len)
6965 cmd_status.index = 0;
6967 return cmd_status.list[cmd_status.index];
6971 cmd_tokenize(char *s, char *tokens[])
6973 int i = 0;
6974 char *tok, *last;
6975 size_t len = strlen(s);
6976 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6978 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6979 tokens[i] = tok;
6981 if (blank && i < 3)
6982 tokens[i++] = "";
6984 return (i);
6987 void
6988 cmd_complete(struct tab *t, char *str, int dir)
6990 GtkEntry *w = GTK_ENTRY(t->cmd);
6991 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6992 char *tok, *match, *s = g_strdup(str);
6993 char *tokens[3];
6994 char res[XT_MAX_URL_LENGTH + 32] = ":";
6995 char *sc = s;
6997 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6999 /* copy prefix*/
7000 for (i = 0; isdigit(s[i]); i++)
7001 res[i + 1] = s[i];
7003 for (; isspace(s[i]); i++)
7004 res[i + 1] = s[i];
7006 s += i;
7008 levels = cmd_tokenize(s, tokens);
7010 for (i = 0; i < levels - 1; i++) {
7011 tok = tokens[i];
7012 matchcount = 0;
7013 for (j = c; j < LENGTH(cmds); j++) {
7014 if (cmds[j].level < dep)
7015 break;
7016 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
7017 matchcount++;
7018 c = j + 1;
7019 if (strlen(tok) == strlen(cmds[j].cmd)) {
7020 matchcount = 1;
7021 break;
7026 if (matchcount == 1) {
7027 strlcat(res, tok, sizeof res);
7028 strlcat(res, " ", sizeof res);
7029 dep++;
7030 } else {
7031 g_free(sc);
7032 return;
7035 parent = c - 1;
7038 if (cmd_status.index == -1)
7039 cmd_getlist(parent, tokens[i]);
7041 if (cmd_status.len > 0) {
7042 match = cmd_getnext(dir);
7043 strlcat(res, match, sizeof res);
7044 gtk_entry_set_text(w, res);
7045 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7048 g_free(sc);
7051 gboolean
7052 cmd_execute(struct tab *t, char *str)
7054 struct cmd *cmd = NULL;
7055 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
7056 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
7057 struct karg arg = {0, NULL, -1};
7058 int rv = XT_CB_PASSTHROUGH;
7060 sc = s;
7062 /* copy prefix*/
7063 for (j = 0; j<3 && isdigit(s[j]); j++)
7064 prefixstr[j]=s[j];
7066 prefixstr[j]='\0';
7068 s += j;
7069 while (isspace(s[0]))
7070 s++;
7072 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7073 prefix = atoi(prefixstr);
7074 else
7075 s = sc;
7077 for (tok = strtok_r(s, " ", &last); tok;
7078 tok = strtok_r(NULL, " ", &last)) {
7079 matchcount = 0;
7080 for (j = c; j < LENGTH(cmds); j++) {
7081 if (cmds[j].level < dep)
7082 break;
7083 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
7084 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
7085 matchcount++;
7086 c = j + 1;
7087 cmd = &cmds[j];
7088 if (len == strlen(cmds[j].cmd)) {
7089 matchcount = 1;
7090 break;
7094 if (matchcount == 1) {
7095 if (cmd->type > 0)
7096 goto execute_cmd;
7097 dep++;
7098 } else {
7099 show_oops(t, "Invalid command: %s", str);
7100 goto done;
7103 execute_cmd:
7104 arg.i = cmd->arg;
7106 if (prefix != -1)
7107 arg.p = prefix;
7108 else if (cmd_prefix > 0)
7109 arg.p = cmd_prefix;
7111 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
7112 show_oops(t, "No prefix allowed: %s", str);
7113 goto done;
7115 if (cmd->type > 1)
7116 arg.s = last ? g_strdup(last) : g_strdup("");
7117 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7118 arg.p = atoi(arg.s);
7119 if (arg.p <= 0) {
7120 if (arg.s[0]=='0')
7121 show_oops(t, "Zero count");
7122 else
7123 show_oops(t, "Trailing characters");
7124 goto done;
7128 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
7130 cmd->func(t, &arg);
7132 rv = XT_CB_HANDLED;
7133 done:
7134 if (j > 0)
7135 cmd_prefix = 0;
7136 g_free(sc);
7137 if (arg.s)
7138 g_free(arg.s);
7140 return (rv);
7144 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7146 if (t == NULL) {
7147 show_oops(NULL, "entry_key_cb invalid parameters");
7148 return (XT_CB_PASSTHROUGH);
7151 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7152 e->keyval, e->state, t);
7154 hide_oops(t);
7156 if (e->keyval == GDK_Escape) {
7157 /* don't use focus_webview(t) because we want to type :cmds */
7158 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7161 return (handle_keypress(t, e, 1));
7165 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7167 int rv = XT_CB_HANDLED;
7168 const gchar *c = gtk_entry_get_text(w);
7170 if (t == NULL) {
7171 show_oops(NULL, "cmd_keypress_cb parameters");
7172 return (XT_CB_PASSTHROUGH);
7175 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7176 e->keyval, e->state, t);
7178 /* sanity */
7179 if (c == NULL)
7180 e->keyval = GDK_Escape;
7181 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7182 e->keyval = GDK_Escape;
7184 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
7185 cmd_status.index = -1;
7187 switch (e->keyval) {
7188 case GDK_Tab:
7189 if (c[0] == ':')
7190 cmd_complete(t, (char *)&c[1], 1);
7191 goto done;
7192 case GDK_ISO_Left_Tab:
7193 if (c[0] == ':')
7194 cmd_complete(t, (char *)&c[1], -1);
7196 goto done;
7197 case GDK_BackSpace:
7198 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
7199 break;
7200 /* FALLTHROUGH */
7201 case GDK_Escape:
7202 hide_cmd(t);
7203 focus_webview(t);
7205 /* cancel search */
7206 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7207 webkit_web_view_unmark_text_matches(t->wv);
7208 goto done;
7211 rv = XT_CB_PASSTHROUGH;
7212 done:
7213 return (rv);
7217 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7219 if (t == NULL) {
7220 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7221 return (XT_CB_PASSTHROUGH);
7223 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
7225 hide_cmd(t);
7226 hide_oops(t);
7228 if (show_url == 0 || t->focus_wv)
7229 focus_webview(t);
7230 else
7231 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7233 return (XT_CB_PASSTHROUGH);
7236 void
7237 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7239 char *s;
7240 const gchar *c = gtk_entry_get_text(entry);
7242 if (t == NULL) {
7243 show_oops(NULL, "cmd_activate_cb invalid parameters");
7244 return;
7247 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7249 hide_cmd(t);
7251 /* sanity */
7252 if (c == NULL)
7253 goto done;
7254 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
7255 goto done;
7256 if (strlen(c) < 2)
7257 goto done;
7258 s = (char *)&c[1];
7260 if (c[0] == '/' || c[0] == '?') {
7261 if (t->search_text) {
7262 g_free(t->search_text);
7263 t->search_text = NULL;
7266 t->search_text = g_strdup(s);
7267 if (global_search)
7268 g_free(global_search);
7269 global_search = g_strdup(s);
7270 t->search_forward = c[0] == '/';
7272 goto done;
7275 cmd_execute(t, s);
7277 done:
7278 return;
7281 void
7282 backward_cb(GtkWidget *w, struct tab *t)
7284 struct karg a;
7286 if (t == NULL) {
7287 show_oops(NULL, "backward_cb invalid parameters");
7288 return;
7291 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7293 a.i = XT_NAV_BACK;
7294 navaction(t, &a);
7297 void
7298 forward_cb(GtkWidget *w, struct tab *t)
7300 struct karg a;
7302 if (t == NULL) {
7303 show_oops(NULL, "forward_cb invalid parameters");
7304 return;
7307 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7309 a.i = XT_NAV_FORWARD;
7310 navaction(t, &a);
7313 void
7314 home_cb(GtkWidget *w, struct tab *t)
7316 if (t == NULL) {
7317 show_oops(NULL, "home_cb invalid parameters");
7318 return;
7321 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7323 load_uri(t, home);
7326 void
7327 stop_cb(GtkWidget *w, struct tab *t)
7329 WebKitWebFrame *frame;
7331 if (t == NULL) {
7332 show_oops(NULL, "stop_cb invalid parameters");
7333 return;
7336 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7338 frame = webkit_web_view_get_main_frame(t->wv);
7339 if (frame == NULL) {
7340 show_oops(t, "stop_cb: no frame");
7341 return;
7344 webkit_web_frame_stop_loading(frame);
7345 abort_favicon_download(t);
7348 void
7349 setup_webkit(struct tab *t)
7351 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7352 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7353 FALSE, (char *)NULL);
7354 else
7355 warnx("webkit does not have \"enable-dns-prefetching\" property");
7356 g_object_set(G_OBJECT(t->settings),
7357 "user-agent", t->user_agent, (char *)NULL);
7358 g_object_set(G_OBJECT(t->settings),
7359 "enable-scripts", enable_scripts, (char *)NULL);
7360 g_object_set(G_OBJECT(t->settings),
7361 "enable-plugins", enable_plugins, (char *)NULL);
7362 g_object_set(G_OBJECT(t->settings),
7363 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
7364 g_object_set(G_OBJECT(t->settings),
7365 "enable-html5-database", FALSE, (char *)NULL);
7366 g_object_set(G_OBJECT(t->settings),
7367 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7368 g_object_set(G_OBJECT(t->settings),
7369 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7370 g_object_set(G_OBJECT(t->settings),
7371 "spell_checking_languages", spell_check_languages, (char *)NULL);
7372 g_object_set(G_OBJECT(t->wv),
7373 "full-content-zoom", TRUE, (char *)NULL);
7375 webkit_web_view_set_settings(t->wv, t->settings);
7378 gboolean
7379 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7381 struct tab *ti, *t = NULL;
7382 gdouble view_size, value, max;
7383 gchar *position;
7385 TAILQ_FOREACH(ti, &tabs, entry)
7386 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7387 t = ti;
7388 break;
7391 if (t == NULL)
7392 return FALSE;
7394 if (adjustment == NULL)
7395 adjustment = gtk_scrolled_window_get_vadjustment(
7396 GTK_SCROLLED_WINDOW(t->browser_win));
7398 view_size = gtk_adjustment_get_page_size(adjustment);
7399 value = gtk_adjustment_get_value(adjustment);
7400 max = gtk_adjustment_get_upper(adjustment) - view_size;
7402 if (max == 0)
7403 position = g_strdup("All");
7404 else if (value == max)
7405 position = g_strdup("Bot");
7406 else if (value == 0)
7407 position = g_strdup("Top");
7408 else
7409 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7411 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7412 g_free(position);
7414 return (TRUE);
7417 GtkWidget *
7418 create_browser(struct tab *t)
7420 GtkWidget *w;
7421 gchar *strval;
7422 GtkAdjustment *adjustment;
7424 if (t == NULL) {
7425 show_oops(NULL, "create_browser invalid parameters");
7426 return (NULL);
7429 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7430 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7431 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7432 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7434 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7435 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7436 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7438 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7439 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7441 /* set defaults */
7442 t->settings = webkit_web_settings_new();
7444 if (user_agent == NULL) {
7445 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7446 (char *)NULL);
7447 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7448 g_free(strval);
7449 } else
7450 t->user_agent = g_strdup(user_agent);
7452 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7454 adjustment =
7455 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7456 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7457 G_CALLBACK(update_statusbar_position), NULL);
7459 setup_webkit(t);
7461 return (w);
7464 GtkWidget *
7465 create_window(void)
7467 GtkWidget *w;
7469 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7470 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7471 gtk_widget_set_name(w, "xxxterm");
7472 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7473 g_signal_connect(G_OBJECT(w), "delete_event",
7474 G_CALLBACK (gtk_main_quit), NULL);
7476 return (w);
7479 GtkWidget *
7480 create_kiosk_toolbar(struct tab *t)
7482 GtkWidget *toolbar = NULL, *b;
7484 b = gtk_hbox_new(FALSE, 0);
7485 toolbar = b;
7486 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7488 /* backward button */
7489 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7490 gtk_widget_set_sensitive(t->backward, FALSE);
7491 g_signal_connect(G_OBJECT(t->backward), "clicked",
7492 G_CALLBACK(backward_cb), t);
7493 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7495 /* forward button */
7496 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7497 gtk_widget_set_sensitive(t->forward, FALSE);
7498 g_signal_connect(G_OBJECT(t->forward), "clicked",
7499 G_CALLBACK(forward_cb), t);
7500 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7502 /* home button */
7503 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7504 gtk_widget_set_sensitive(t->gohome, true);
7505 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7506 G_CALLBACK(home_cb), t);
7507 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7509 /* create widgets but don't use them */
7510 t->uri_entry = gtk_entry_new();
7511 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7512 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7513 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7515 return (toolbar);
7518 GtkWidget *
7519 create_toolbar(struct tab *t)
7521 GtkWidget *toolbar = NULL, *b, *eb1;
7523 b = gtk_hbox_new(FALSE, 0);
7524 toolbar = b;
7525 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7527 if (fancy_bar) {
7528 /* backward button */
7529 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7530 gtk_widget_set_sensitive(t->backward, FALSE);
7531 g_signal_connect(G_OBJECT(t->backward), "clicked",
7532 G_CALLBACK(backward_cb), t);
7533 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7535 /* forward button */
7536 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7537 gtk_widget_set_sensitive(t->forward, FALSE);
7538 g_signal_connect(G_OBJECT(t->forward), "clicked",
7539 G_CALLBACK(forward_cb), t);
7540 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7541 FALSE, 0);
7543 /* stop button */
7544 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7545 gtk_widget_set_sensitive(t->stop, FALSE);
7546 g_signal_connect(G_OBJECT(t->stop), "clicked",
7547 G_CALLBACK(stop_cb), t);
7548 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7549 FALSE, 0);
7551 /* JS button */
7552 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7553 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7554 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7555 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7556 G_CALLBACK(js_toggle_cb), t);
7557 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7560 t->uri_entry = gtk_entry_new();
7561 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7562 G_CALLBACK(activate_uri_entry_cb), t);
7563 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7564 G_CALLBACK(entry_key_cb), t);
7565 completion_add(t);
7566 eb1 = gtk_hbox_new(FALSE, 0);
7567 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7568 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7569 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7571 /* search entry */
7572 if (fancy_bar && search_string) {
7573 GtkWidget *eb2;
7574 t->search_entry = gtk_entry_new();
7575 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7576 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7577 G_CALLBACK(activate_search_entry_cb), t);
7578 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7579 G_CALLBACK(entry_key_cb), t);
7580 gtk_widget_set_size_request(t->search_entry, -1, -1);
7581 eb2 = gtk_hbox_new(FALSE, 0);
7582 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7583 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7585 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7587 return (toolbar);
7590 GtkWidget *
7591 create_buffers(struct tab *t)
7593 GtkCellRenderer *renderer;
7594 GtkWidget *view;
7596 view = gtk_tree_view_new();
7598 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7600 renderer = gtk_cell_renderer_text_new();
7601 gtk_tree_view_insert_column_with_attributes
7602 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, NULL);
7604 renderer = gtk_cell_renderer_text_new();
7605 gtk_tree_view_insert_column_with_attributes
7606 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE, NULL);
7608 gtk_tree_view_set_model
7609 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7611 return view;
7614 void
7615 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7616 GtkTreeViewColumn *col, struct tab *t)
7618 GtkTreeIter iter;
7619 guint id;
7621 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7623 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path)) {
7624 gtk_tree_model_get
7625 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7626 set_current_tab(id - 1);
7629 hide_buffers(t);
7632 /* after tab reordering/creation/removal */
7633 void
7634 recalc_tabs(void)
7636 struct tab *t;
7637 int maxid = 0;
7639 TAILQ_FOREACH(t, &tabs, entry) {
7640 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7641 if (t->tab_id > maxid)
7642 maxid = t->tab_id;
7644 gtk_widget_show(t->tab_elems.sep);
7647 TAILQ_FOREACH(t, &tabs, entry) {
7648 if (t->tab_id == maxid) {
7649 gtk_widget_hide(t->tab_elems.sep);
7650 break;
7655 /* after active tab change */
7656 void
7657 recolor_compact_tabs(void)
7659 struct tab *t;
7660 int curid = 0;
7661 GdkColor color;
7663 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7664 TAILQ_FOREACH(t, &tabs, entry)
7665 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7667 curid = gtk_notebook_get_current_page(notebook);
7668 TAILQ_FOREACH(t, &tabs, entry)
7669 if (t->tab_id == curid) {
7670 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7671 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
7672 break;
7676 void
7677 set_current_tab(int page_num)
7679 buffercmd_abort(get_current_tab());
7680 gtk_notebook_set_current_page(notebook, page_num);
7681 recolor_compact_tabs();
7685 undo_close_tab_save(struct tab *t)
7687 int m, n;
7688 const gchar *uri;
7689 struct undo *u1, *u2;
7690 GList *items;
7691 WebKitWebHistoryItem *item;
7693 if ((uri = get_uri(t)) == NULL)
7694 return (1);
7696 u1 = g_malloc0(sizeof(struct undo));
7697 u1->uri = g_strdup(uri);
7699 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7701 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7702 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7703 u1->back = n;
7705 /* forward history */
7706 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7708 while (items) {
7709 item = items->data;
7710 u1->history = g_list_prepend(u1->history,
7711 webkit_web_history_item_copy(item));
7712 items = g_list_next(items);
7715 /* current item */
7716 if (m) {
7717 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7718 u1->history = g_list_prepend(u1->history,
7719 webkit_web_history_item_copy(item));
7722 /* back history */
7723 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7725 while (items) {
7726 item = items->data;
7727 u1->history = g_list_prepend(u1->history,
7728 webkit_web_history_item_copy(item));
7729 items = g_list_next(items);
7732 TAILQ_INSERT_HEAD(&undos, u1, entry);
7734 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7735 u2 = TAILQ_LAST(&undos, undo_tailq);
7736 TAILQ_REMOVE(&undos, u2, entry);
7737 g_free(u2->uri);
7738 g_list_free(u2->history);
7739 g_free(u2);
7740 } else
7741 undo_count++;
7743 return (0);
7746 void
7747 delete_tab(struct tab *t)
7749 struct karg a;
7751 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7753 if (t == NULL)
7754 return;
7756 TAILQ_REMOVE(&tabs, t, entry);
7758 /* Halt all webkit activity. */
7759 abort_favicon_download(t);
7760 webkit_web_view_stop_loading(t->wv);
7762 /* Save the tab, so we can undo the close. */
7763 undo_close_tab_save(t);
7765 if (browser_mode == XT_BM_KIOSK) {
7766 gtk_widget_destroy(t->uri_entry);
7767 gtk_widget_destroy(t->stop);
7768 gtk_widget_destroy(t->js_toggle);
7771 gtk_widget_destroy(t->tab_elems.eventbox);
7772 gtk_widget_destroy(t->vbox);
7774 g_free(t->user_agent);
7775 g_free(t->stylesheet);
7776 g_free(t->tmp_uri);
7777 g_free(t);
7779 if (TAILQ_EMPTY(&tabs)) {
7780 if (browser_mode == XT_BM_KIOSK)
7781 create_new_tab(home, NULL, 1, -1);
7782 else
7783 create_new_tab(NULL, NULL, 1, -1);
7786 /* recreate session */
7787 if (session_autosave) {
7788 a.s = NULL;
7789 save_tabs(t, &a);
7792 recalc_tabs();
7793 recolor_compact_tabs();
7796 void
7797 setzoom_webkit(struct tab *t, int adjust)
7799 #define XT_ZOOMPERCENT 0.04
7801 gfloat zoom;
7803 if (t == NULL) {
7804 show_oops(NULL, "setzoom_webkit invalid parameters");
7805 return;
7808 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7809 if (adjust == XT_ZOOM_IN)
7810 zoom += XT_ZOOMPERCENT;
7811 else if (adjust == XT_ZOOM_OUT)
7812 zoom -= XT_ZOOMPERCENT;
7813 else if (adjust > 0)
7814 zoom = default_zoom_level + adjust / 100.0 - 1.0;
7815 else {
7816 show_oops(t, "setzoom_webkit invalid zoom value");
7817 return;
7820 if (zoom < XT_ZOOMPERCENT)
7821 zoom = XT_ZOOMPERCENT;
7822 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7825 gboolean
7826 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
7828 struct tab *t = (struct tab *) data;
7830 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
7832 switch (event->button) {
7833 case 1:
7834 set_current_tab(t->tab_id);
7835 break;
7836 case 2:
7837 delete_tab(t);
7838 break;
7841 return TRUE;
7844 void
7845 append_tab(struct tab *t)
7847 if (t == NULL)
7848 return;
7850 TAILQ_INSERT_TAIL(&tabs, t, entry);
7851 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7854 struct tab *
7855 create_new_tab(char *title, struct undo *u, int focus, int position)
7857 struct tab *t;
7858 int load = 1, id;
7859 GtkWidget *b, *bb;
7860 WebKitWebHistoryItem *item;
7861 GList *items;
7862 GdkColor color;
7863 char *p;
7864 int sbe_p = 0, sbe_b = 0;
7866 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7868 if (tabless && !TAILQ_EMPTY(&tabs)) {
7869 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7870 return (NULL);
7873 t = g_malloc0(sizeof *t);
7875 if (title == NULL) {
7876 title = "(untitled)";
7877 load = 0;
7880 t->vbox = gtk_vbox_new(FALSE, 0);
7882 /* label + button for tab */
7883 b = gtk_hbox_new(FALSE, 0);
7884 t->tab_content = b;
7886 #if GTK_CHECK_VERSION(2, 20, 0)
7887 t->spinner = gtk_spinner_new();
7888 #endif
7889 t->label = gtk_label_new(title);
7890 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7891 gtk_widget_set_size_request(t->label, 100, 0);
7892 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7893 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7894 gtk_widget_set_size_request(b, 130, 0);
7896 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7897 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7898 #if GTK_CHECK_VERSION(2, 20, 0)
7899 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7900 #endif
7902 /* toolbar */
7903 if (browser_mode == XT_BM_KIOSK)
7904 t->toolbar = create_kiosk_toolbar(t);
7905 else
7906 t->toolbar = create_toolbar(t);
7908 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7910 /* browser */
7911 t->browser_win = create_browser(t);
7912 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7914 /* oops message for user feedback */
7915 t->oops = gtk_entry_new();
7916 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7917 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7918 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7919 gdk_color_parse(XT_COLOR_RED, &color);
7920 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7921 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7922 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
7924 /* command entry */
7925 t->cmd = gtk_entry_new();
7926 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7927 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7928 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7929 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
7931 /* status bar */
7932 t->statusbar_box = gtk_hbox_new(FALSE, 0);
7934 t->sbe.statusbar = gtk_entry_new();
7935 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
7936 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
7937 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
7938 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
7940 /* XXX create these widgets only if specified in statusbar_elems */
7941 t->sbe.position = gtk_entry_new();
7942 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.position), NULL);
7943 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.position), FALSE);
7944 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.position), FALSE);
7945 gtk_widget_modify_font(GTK_WIDGET(t->sbe.position), statusbar_font);
7946 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.position), 1.0);
7947 gtk_widget_set_size_request(t->sbe.position, 40, -1);
7949 t->sbe.buffercmd = gtk_entry_new();
7950 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.buffercmd), NULL);
7951 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.buffercmd), FALSE);
7952 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.buffercmd), FALSE);
7953 gtk_widget_modify_font(GTK_WIDGET(t->sbe.buffercmd), statusbar_font);
7954 gtk_entry_set_alignment(GTK_ENTRY(t->sbe.buffercmd), 1.0);
7955 gtk_widget_set_size_request(t->sbe.buffercmd, 60, -1);
7957 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
7959 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
7960 TRUE, FALSE);
7962 /* gtk widgets cannot be added to a box twice. sbe_* variables
7963 make sure of this */
7964 for (p = statusbar_elems; *p != '\0'; p++) {
7965 switch (*p) {
7966 case '|':
7968 GtkWidget *sep = gtk_vseparator_new();
7970 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
7971 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
7972 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep, FALSE,
7973 FALSE, FALSE);
7974 break;
7976 case 'P':
7977 if (sbe_p) {
7978 warnx("flag \"%c\" specified more than "
7979 "once in statusbar_elems\n", *p);
7980 break;
7982 sbe_p = 1;
7983 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
7984 t->sbe.position, FALSE, FALSE, FALSE);
7985 break;
7986 case 'B':
7987 if (sbe_b) {
7988 warnx("flag \"%c\" specified more than "
7989 "once in statusbar_elems\n", *p);
7990 break;
7992 sbe_b = 1;
7993 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
7994 t->sbe.buffercmd, FALSE, FALSE, FALSE);
7995 break;
7996 default:
7997 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
7998 break;
8002 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8004 /* buffer list */
8005 t->buffers = create_buffers(t);
8006 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8008 /* xtp meaning is normal by default */
8009 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8011 /* set empty favicon */
8012 xt_icon_from_name(t, "text-html");
8014 /* and show it all */
8015 gtk_widget_show_all(b);
8016 gtk_widget_show_all(t->vbox);
8018 /* compact tab bar */
8019 t->tab_elems.label = gtk_label_new(title);
8020 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8021 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8022 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8023 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8025 t->tab_elems.eventbox = gtk_event_box_new();
8026 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8027 t->tab_elems.sep = gtk_vseparator_new();
8029 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8030 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8031 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8032 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8033 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8034 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8036 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8037 TRUE, 0);
8038 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8039 FALSE, 0);
8040 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8041 t->tab_elems.box);
8043 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8044 TRUE, 0);
8045 gtk_widget_show_all(t->tab_elems.eventbox);
8047 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8048 append_tab(t);
8049 else {
8050 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
8051 if (id > gtk_notebook_get_n_pages(notebook))
8052 append_tab(t);
8053 else {
8054 TAILQ_INSERT_TAIL(&tabs, t, entry);
8055 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8056 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox, id);
8057 recalc_tabs();
8061 #if GTK_CHECK_VERSION(2, 20, 0)
8062 /* turn spinner off if we are a new tab without uri */
8063 if (!load) {
8064 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8065 gtk_widget_hide(t->spinner);
8067 #endif
8068 /* make notebook tabs reorderable */
8069 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8071 /* compact tabs clickable */
8072 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8073 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8075 g_object_connect(G_OBJECT(t->cmd),
8076 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8077 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8078 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8079 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8080 (char *)NULL);
8082 /* reuse wv_button_cb to hide oops */
8083 g_object_connect(G_OBJECT(t->oops),
8084 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8085 (char *)NULL);
8087 g_signal_connect(t->buffers,
8088 "row-activated", G_CALLBACK(row_activated_cb), t);
8089 g_object_connect(G_OBJECT(t->buffers),
8090 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, NULL);
8092 g_object_connect(G_OBJECT(t->wv),
8093 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8094 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8095 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8096 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8097 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8098 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8099 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8100 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8101 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8102 "signal::event", G_CALLBACK(webview_event_cb), t,
8103 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8104 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8105 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8106 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8107 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8108 (char *)NULL);
8109 g_signal_connect(t->wv,
8110 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8111 g_signal_connect(t->wv,
8112 "load-error", G_CALLBACK(notify_load_error_cb), t);
8113 g_signal_connect(t->wv,
8114 "notify::title", G_CALLBACK(notify_title_cb), t);
8116 /* hijack the unused keys as if we were the browser */
8117 g_object_connect(G_OBJECT(t->toolbar),
8118 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8119 (char *)NULL);
8121 g_signal_connect(G_OBJECT(bb), "button_press_event",
8122 G_CALLBACK(tab_close_cb), t);
8124 /* hide stuff */
8125 hide_cmd(t);
8126 hide_oops(t);
8127 hide_buffers(t);
8128 url_set_visibility();
8129 statusbar_set_visibility();
8131 if (focus) {
8132 set_current_tab(t->tab_id);
8133 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8134 t->tab_id);
8137 if (load) {
8138 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8139 load_uri(t, title);
8140 } else {
8141 if (show_url == 1)
8142 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8143 else
8144 focus_webview(t);
8147 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8148 /* restore the tab's history */
8149 if (u && u->history) {
8150 items = u->history;
8151 while (items) {
8152 item = items->data;
8153 webkit_web_back_forward_list_add_item(t->bfl, item);
8154 items = g_list_next(items);
8157 item = g_list_nth_data(u->history, u->back);
8158 if (item)
8159 webkit_web_view_go_to_back_forward_item(t->wv, item);
8161 g_list_free(items);
8162 g_list_free(u->history);
8163 } else
8164 webkit_web_back_forward_list_clear(t->bfl);
8166 recolor_compact_tabs();
8167 return (t);
8170 void
8171 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8172 gpointer *udata)
8174 struct tab *t;
8175 const gchar *uri;
8177 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8179 if (gtk_notebook_get_current_page(notebook) == -1)
8180 recalc_tabs();
8182 TAILQ_FOREACH(t, &tabs, entry) {
8183 if (t->tab_id == pn) {
8184 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8185 "%d\n", pn);
8187 uri = get_title(t, TRUE);
8188 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8190 hide_cmd(t);
8191 hide_oops(t);
8193 if (t->focus_wv) {
8194 /* can't use focus_webview here */
8195 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8201 void
8202 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8203 gpointer *udata)
8205 struct tab *t = NULL, *tt;
8207 recalc_tabs();
8209 TAILQ_FOREACH(tt, &tabs, entry)
8210 if (tt->tab_id == pn) {
8211 t = tt;
8212 break;
8215 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8217 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8218 t->tab_id);
8221 void
8222 menuitem_response(struct tab *t)
8224 gtk_notebook_set_current_page(notebook, t->tab_id);
8227 gboolean
8228 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8230 GtkWidget *menu, *menu_items;
8231 GdkEventButton *bevent;
8232 const gchar *uri;
8233 struct tab *ti;
8235 if (event->type == GDK_BUTTON_PRESS) {
8236 bevent = (GdkEventButton *) event;
8237 menu = gtk_menu_new();
8239 TAILQ_FOREACH(ti, &tabs, entry) {
8240 if ((uri = get_uri(ti)) == NULL)
8241 /* XXX make sure there is something to print */
8242 /* XXX add gui pages in here to look purdy */
8243 uri = "(untitled)";
8244 menu_items = gtk_menu_item_new_with_label(uri);
8245 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8246 gtk_widget_show(menu_items);
8248 g_signal_connect_swapped((menu_items),
8249 "activate", G_CALLBACK(menuitem_response),
8250 (gpointer)ti);
8253 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8254 bevent->button, bevent->time);
8256 /* unref object so it'll free itself when popped down */
8257 #if !GTK_CHECK_VERSION(3, 0, 0)
8258 /* XXX does not need unref with gtk+3? */
8259 g_object_ref_sink(menu);
8260 g_object_unref(menu);
8261 #endif
8263 return (TRUE /* eat event */);
8266 return (FALSE /* propagate */);
8270 icon_size_map(int icon_size)
8272 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8273 icon_size > GTK_ICON_SIZE_DIALOG)
8274 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8276 return (icon_size);
8279 GtkWidget *
8280 create_button(char *name, char *stockid, int size)
8282 GtkWidget *button, *image;
8283 gchar *rcstring;
8284 int gtk_icon_size;
8286 rcstring = g_strdup_printf(
8287 "style \"%s-style\"\n"
8288 "{\n"
8289 " GtkWidget::focus-padding = 0\n"
8290 " GtkWidget::focus-line-width = 0\n"
8291 " xthickness = 0\n"
8292 " ythickness = 0\n"
8293 "}\n"
8294 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8295 gtk_rc_parse_string(rcstring);
8296 g_free(rcstring);
8297 button = gtk_button_new();
8298 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8299 gtk_icon_size = icon_size_map(size ? size : icon_size);
8301 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8302 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8303 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8304 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8305 gtk_widget_set_name(button, name);
8306 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8308 return (button);
8311 void
8312 button_set_stockid(GtkWidget *button, char *stockid)
8314 GtkWidget *image;
8316 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8317 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8318 gtk_button_set_image(GTK_BUTTON(button), image);
8321 void
8322 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8324 GtkClipboard *clipboard;
8325 gchar *p = NULL, *s = NULL;
8328 * This code is very aggressive!
8329 * It basically ensures that the primary and regular clipboard are
8330 * always set the same. This obviously messes with standard X protocol
8331 * but those clowns should have come up with something better.
8334 if (btn_down)
8335 return;
8337 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
8338 p = gtk_clipboard_wait_for_text(primary);
8339 if (p == NULL) {
8340 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
8341 p = gtk_clipboard_wait_for_text(clipboard);
8342 if (p)
8343 gtk_clipboard_set_text(primary, p, -1);
8344 } else {
8345 DNPRINTF(XT_D_CLIP, "primary got selection\n");
8346 s = gtk_clipboard_wait_for_text(clipboard);
8347 if (s) {
8349 * if s and p are the same the string was set by
8350 * clipb_clipboard_cb so do nothing in that case
8351 * to prevent endless loop
8353 if (!strcmp(s, p))
8354 goto done;
8356 gtk_clipboard_set_text(clipboard, p, -1);
8358 done:
8359 if (p)
8360 g_free(p);
8361 if (s)
8362 g_free(s);
8365 void
8366 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
8368 GtkClipboard *primary;
8369 gchar *p = NULL, *s = NULL;
8371 if (btn_down)
8372 return;
8374 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
8376 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8377 p = gtk_clipboard_wait_for_text(clipboard);
8378 if (p) {
8379 s = gtk_clipboard_wait_for_text(primary);
8380 if (s) {
8382 * if s and p are the same the string was set by
8383 * clipb_primary_cb so do nothing in that case
8384 * to prevent endless loop and deselection of text
8386 if (!strcmp(s, p))
8387 goto done;
8389 gtk_clipboard_set_text(primary, p, -1);
8391 done:
8392 if (p)
8393 g_free(p);
8394 if (s)
8395 g_free(s);
8398 void
8399 create_canvas(void)
8401 GtkWidget *vbox;
8402 GList *l = NULL;
8403 GdkPixbuf *pb;
8404 char file[PATH_MAX];
8405 int i;
8407 vbox = gtk_vbox_new(FALSE, 0);
8408 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8409 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8410 #if !GTK_CHECK_VERSION(3, 0, 0)
8411 /* XXX seems to be needed with gtk+2 */
8412 gtk_notebook_set_tab_hborder(notebook, 0);
8413 gtk_notebook_set_tab_vborder(notebook, 0);
8414 #endif
8415 gtk_notebook_set_scrollable(notebook, TRUE);
8416 gtk_notebook_set_show_border(notebook, FALSE);
8417 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8419 abtn = gtk_button_new();
8420 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8421 gtk_widget_set_size_request(arrow, -1, -1);
8422 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8423 gtk_widget_set_size_request(abtn, -1, 20);
8425 #if GTK_CHECK_VERSION(2, 20, 0)
8426 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8427 #endif
8428 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8430 /* compact tab bar */
8431 tab_bar = gtk_hbox_new(TRUE, 0);
8433 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8434 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8435 gtk_widget_set_size_request(vbox, -1, -1);
8437 g_object_connect(G_OBJECT(notebook),
8438 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8439 (char *)NULL);
8440 g_object_connect(G_OBJECT(notebook),
8441 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
8442 (char *)NULL);
8443 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8444 G_CALLBACK(arrow_cb), NULL);
8446 main_window = create_window();
8447 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8449 /* icons */
8450 for (i = 0; i < LENGTH(icons); i++) {
8451 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8452 pb = gdk_pixbuf_new_from_file(file, NULL);
8453 l = g_list_append(l, pb);
8455 gtk_window_set_default_icon_list(l);
8457 /* clipboard */
8458 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8459 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8460 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
8461 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
8463 gtk_widget_show_all(abtn);
8464 gtk_widget_show_all(main_window);
8465 notebook_tab_set_visibility();
8468 void
8469 set_hook(void **hook, char *name)
8471 if (hook == NULL)
8472 errx(1, "set_hook");
8474 if (*hook == NULL) {
8475 *hook = dlsym(RTLD_NEXT, name);
8476 if (*hook == NULL)
8477 errx(1, "can't hook %s", name);
8481 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8482 gboolean
8483 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8485 g_return_val_if_fail(cookie1, FALSE);
8486 g_return_val_if_fail(cookie2, FALSE);
8488 return (!strcmp (cookie1->name, cookie2->name) &&
8489 !strcmp (cookie1->value, cookie2->value) &&
8490 !strcmp (cookie1->path, cookie2->path) &&
8491 !strcmp (cookie1->domain, cookie2->domain));
8494 void
8495 transfer_cookies(void)
8497 GSList *cf;
8498 SoupCookie *sc, *pc;
8500 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8502 for (;cf; cf = cf->next) {
8503 pc = cf->data;
8504 sc = soup_cookie_copy(pc);
8505 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8508 soup_cookies_free(cf);
8511 void
8512 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8514 GSList *cf;
8515 SoupCookie *ci;
8517 print_cookie("soup_cookie_jar_delete_cookie", c);
8519 if (cookies_enabled == 0)
8520 return;
8522 if (jar == NULL || c == NULL)
8523 return;
8525 /* find and remove from persistent jar */
8526 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8528 for (;cf; cf = cf->next) {
8529 ci = cf->data;
8530 if (soup_cookie_equal(ci, c)) {
8531 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8532 break;
8536 soup_cookies_free(cf);
8538 /* delete from session jar */
8539 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8542 void
8543 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8545 struct domain *d = NULL;
8546 SoupCookie *c;
8547 FILE *r_cookie_f;
8549 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8550 jar, p_cookiejar, s_cookiejar);
8552 if (cookies_enabled == 0)
8553 return;
8555 /* see if we are up and running */
8556 if (p_cookiejar == NULL) {
8557 _soup_cookie_jar_add_cookie(jar, cookie);
8558 return;
8560 /* disallow p_cookiejar adds, shouldn't happen */
8561 if (jar == p_cookiejar)
8562 return;
8564 /* sanity */
8565 if (jar == NULL || cookie == NULL)
8566 return;
8568 if (enable_cookie_whitelist &&
8569 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8570 blocked_cookies++;
8571 DNPRINTF(XT_D_COOKIE,
8572 "soup_cookie_jar_add_cookie: reject %s\n",
8573 cookie->domain);
8574 if (save_rejected_cookies) {
8575 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8576 show_oops(NULL, "can't open reject cookie file");
8577 return;
8579 fseek(r_cookie_f, 0, SEEK_END);
8580 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8581 cookie->http_only ? "#HttpOnly_" : "",
8582 cookie->domain,
8583 *cookie->domain == '.' ? "TRUE" : "FALSE",
8584 cookie->path,
8585 cookie->secure ? "TRUE" : "FALSE",
8586 cookie->expires ?
8587 (gulong)soup_date_to_time_t(cookie->expires) :
8589 cookie->name,
8590 cookie->value);
8591 fflush(r_cookie_f);
8592 fclose(r_cookie_f);
8594 if (!allow_volatile_cookies)
8595 return;
8598 if (cookie->expires == NULL && session_timeout) {
8599 soup_cookie_set_expires(cookie,
8600 soup_date_new_from_now(session_timeout));
8601 print_cookie("modified add cookie", cookie);
8604 /* see if we are white listed for persistence */
8605 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8606 /* add to persistent jar */
8607 c = soup_cookie_copy(cookie);
8608 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8609 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8612 /* add to session jar */
8613 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8614 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8617 void
8618 setup_cookies(void)
8620 char file[PATH_MAX];
8622 set_hook((void *)&_soup_cookie_jar_add_cookie,
8623 "soup_cookie_jar_add_cookie");
8624 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8625 "soup_cookie_jar_delete_cookie");
8627 if (cookies_enabled == 0)
8628 return;
8631 * the following code is intricate due to overriding several libsoup
8632 * functions.
8633 * do not alter order of these operations.
8636 /* rejected cookies */
8637 if (save_rejected_cookies)
8638 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
8640 /* persistent cookies */
8641 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8642 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8644 /* session cookies */
8645 s_cookiejar = soup_cookie_jar_new();
8646 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8647 cookie_policy, (void *)NULL);
8648 transfer_cookies();
8650 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8653 void
8654 setup_proxy(char *uri)
8656 if (proxy_uri) {
8657 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8658 soup_uri_free(proxy_uri);
8659 proxy_uri = NULL;
8661 if (http_proxy) {
8662 if (http_proxy != uri) {
8663 g_free(http_proxy);
8664 http_proxy = NULL;
8668 if (uri) {
8669 http_proxy = g_strdup(uri);
8670 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8671 proxy_uri = soup_uri_new(http_proxy);
8672 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8677 send_cmd_to_socket(char *cmd)
8679 int s, len, rv = 1;
8680 struct sockaddr_un sa;
8682 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8683 warnx("%s: socket", __func__);
8684 return (rv);
8687 sa.sun_family = AF_UNIX;
8688 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8689 work_dir, XT_SOCKET_FILE);
8690 len = SUN_LEN(&sa);
8692 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8693 warnx("%s: connect", __func__);
8694 goto done;
8697 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8698 warnx("%s: send", __func__);
8699 goto done;
8702 rv = 0;
8703 done:
8704 close(s);
8705 return (rv);
8708 gboolean
8709 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8711 int s, n;
8712 char str[XT_MAX_URL_LENGTH];
8713 socklen_t t = sizeof(struct sockaddr_un);
8714 struct sockaddr_un sa;
8715 struct passwd *p;
8716 uid_t uid;
8717 gid_t gid;
8718 struct tab *tt;
8719 gint fd = g_io_channel_unix_get_fd(source);
8721 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8722 warn("accept");
8723 return (FALSE);
8726 if (getpeereid(s, &uid, &gid) == -1) {
8727 warn("getpeereid");
8728 return (FALSE);
8730 if (uid != getuid() || gid != getgid()) {
8731 warnx("unauthorized user");
8732 return (FALSE);
8735 p = getpwuid(uid);
8736 if (p == NULL) {
8737 warnx("not a valid user");
8738 return (FALSE);
8741 n = recv(s, str, sizeof(str), 0);
8742 if (n <= 0)
8743 return (TRUE);
8745 tt = TAILQ_LAST(&tabs, tab_list);
8746 cmd_execute(tt, str);
8747 return (TRUE);
8751 is_running(void)
8753 int s, len, rv = 1;
8754 struct sockaddr_un sa;
8756 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8757 warn("is_running: socket");
8758 return (-1);
8761 sa.sun_family = AF_UNIX;
8762 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8763 work_dir, XT_SOCKET_FILE);
8764 len = SUN_LEN(&sa);
8766 /* connect to see if there is a listener */
8767 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8768 rv = 0; /* not running */
8769 else
8770 rv = 1; /* already running */
8772 close(s);
8774 return (rv);
8778 build_socket(void)
8780 int s, len;
8781 struct sockaddr_un sa;
8783 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8784 warn("build_socket: socket");
8785 return (-1);
8788 sa.sun_family = AF_UNIX;
8789 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8790 work_dir, XT_SOCKET_FILE);
8791 len = SUN_LEN(&sa);
8793 /* connect to see if there is a listener */
8794 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8795 /* no listener so we will */
8796 unlink(sa.sun_path);
8798 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8799 warn("build_socket: bind");
8800 goto done;
8803 if (listen(s, 1) == -1) {
8804 warn("build_socket: listen");
8805 goto done;
8808 return (s);
8811 done:
8812 close(s);
8813 return (-1);
8816 gboolean
8817 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8818 GtkTreeIter *iter, struct tab *t)
8820 gchar *value;
8822 gtk_tree_model_get(model, iter, 0, &value, -1);
8823 load_uri(t, value);
8824 g_free(value);
8826 return (FALSE);
8829 gboolean
8830 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8831 GtkTreeIter *iter, struct tab *t)
8833 gchar *value;
8835 gtk_tree_model_get(model, iter, 0, &value, -1);
8836 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8837 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8838 g_free(value);
8840 return (TRUE);
8843 void
8844 completion_add_uri(const gchar *uri)
8846 GtkTreeIter iter;
8848 /* add uri to list_store */
8849 gtk_list_store_append(completion_model, &iter);
8850 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8853 gboolean
8854 completion_match(GtkEntryCompletion *completion, const gchar *key,
8855 GtkTreeIter *iter, gpointer user_data)
8857 gchar *value;
8858 gboolean match = FALSE;
8860 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8861 -1);
8863 if (value == NULL)
8864 return FALSE;
8866 match = match_uri(value, key);
8868 g_free(value);
8869 return (match);
8872 void
8873 completion_add(struct tab *t)
8875 /* enable completion for tab */
8876 t->completion = gtk_entry_completion_new();
8877 gtk_entry_completion_set_text_column(t->completion, 0);
8878 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8879 gtk_entry_completion_set_model(t->completion,
8880 GTK_TREE_MODEL(completion_model));
8881 gtk_entry_completion_set_match_func(t->completion, completion_match,
8882 NULL, NULL);
8883 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8884 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8885 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8886 G_CALLBACK(completion_select_cb), t);
8887 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8888 G_CALLBACK(completion_hover_cb), t);
8891 void
8892 xxx_dir(char *dir)
8894 struct stat sb;
8896 if (stat(dir, &sb)) {
8897 if (mkdir(dir, S_IRWXU) == -1)
8898 err(1, "mkdir %s", dir);
8899 if (stat(dir, &sb))
8900 err(1, "stat %s", dir);
8902 if (S_ISDIR(sb.st_mode) == 0)
8903 errx(1, "%s not a dir", dir);
8904 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8905 warnx("fixing invalid permissions on %s", dir);
8906 if (chmod(dir, S_IRWXU) == -1)
8907 err(1, "chmod %s", dir);
8911 void
8912 usage(void)
8914 fprintf(stderr,
8915 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8916 exit(0);
8921 main(int argc, char *argv[])
8923 struct stat sb;
8924 int c, s, optn = 0, opte = 0, focus = 1;
8925 char conf[PATH_MAX] = { '\0' };
8926 char file[PATH_MAX];
8927 char *env_proxy = NULL;
8928 FILE *f = NULL;
8929 struct karg a;
8930 struct sigaction sact;
8931 GIOChannel *channel;
8932 struct rlimit rlp;
8934 start_argv = argv;
8936 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8938 /* fiddle with ulimits */
8939 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8940 warn("getrlimit");
8941 else {
8942 /* just use them all */
8943 rlp.rlim_cur = rlp.rlim_max;
8944 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
8945 warn("setrlimit");
8946 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
8947 warn("getrlimit");
8948 else if (rlp.rlim_cur <= 256)
8949 warnx("%s requires at least 256 file descriptors",
8950 __progname);
8953 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8954 switch (c) {
8955 case 'S':
8956 show_url = 0;
8957 break;
8958 case 'T':
8959 show_tabs = 0;
8960 break;
8961 case 'V':
8962 errx(0 , "Version: %s", version);
8963 break;
8964 case 'f':
8965 strlcpy(conf, optarg, sizeof(conf));
8966 break;
8967 case 's':
8968 strlcpy(named_session, optarg, sizeof(named_session));
8969 break;
8970 case 't':
8971 tabless = 1;
8972 break;
8973 case 'n':
8974 optn = 1;
8975 break;
8976 case 'e':
8977 opte = 1;
8978 break;
8979 default:
8980 usage();
8981 /* NOTREACHED */
8984 argc -= optind;
8985 argv += optind;
8987 RB_INIT(&hl);
8988 RB_INIT(&js_wl);
8989 RB_INIT(&downloads);
8991 TAILQ_INIT(&tabs);
8992 TAILQ_INIT(&mtl);
8993 TAILQ_INIT(&aliases);
8994 TAILQ_INIT(&undos);
8995 TAILQ_INIT(&kbl);
8997 init_keybindings();
8999 gnutls_global_init();
9001 /* generate session keys for xtp pages */
9002 generate_xtp_session_key(&dl_session_key);
9003 generate_xtp_session_key(&hl_session_key);
9004 generate_xtp_session_key(&cl_session_key);
9005 generate_xtp_session_key(&fl_session_key);
9007 /* prepare gtk */
9008 gtk_init(&argc, &argv);
9009 if (!g_thread_supported())
9010 g_thread_init(NULL);
9012 /* signals */
9013 bzero(&sact, sizeof(sact));
9014 sigemptyset(&sact.sa_mask);
9015 sact.sa_handler = sigchild;
9016 sact.sa_flags = SA_NOCLDSTOP;
9017 sigaction(SIGCHLD, &sact, NULL);
9019 /* set download dir */
9020 pwd = getpwuid(getuid());
9021 if (pwd == NULL)
9022 errx(1, "invalid user %d", getuid());
9023 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9025 /* compile buffer command regexes */
9026 buffercmd_init();
9028 /* set default string settings */
9029 home = g_strdup("https://www.cyphertite.com");
9030 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9031 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9032 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9033 cmd_font_name = g_strdup("monospace normal 9");
9034 oops_font_name = g_strdup("monospace normal 9");
9035 statusbar_font_name = g_strdup("monospace normal 9");
9036 tabbar_font_name = g_strdup("monospace normal 9");
9037 statusbar_elems = g_strdup("BP");
9039 /* read config file */
9040 if (strlen(conf) == 0)
9041 snprintf(conf, sizeof conf, "%s/.%s",
9042 pwd->pw_dir, XT_CONF_FILE);
9043 config_parse(conf, 0);
9045 /* init fonts */
9046 cmd_font = pango_font_description_from_string(cmd_font_name);
9047 oops_font = pango_font_description_from_string(oops_font_name);
9048 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9049 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9051 /* working directory */
9052 if (strlen(work_dir) == 0)
9053 snprintf(work_dir, sizeof work_dir, "%s/%s",
9054 pwd->pw_dir, XT_DIR);
9055 xxx_dir(work_dir);
9057 /* icon cache dir */
9058 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9059 xxx_dir(cache_dir);
9061 /* certs dir */
9062 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9063 xxx_dir(certs_dir);
9065 /* sessions dir */
9066 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9067 work_dir, XT_SESSIONS_DIR);
9068 xxx_dir(sessions_dir);
9070 /* runtime settings that can override config file */
9071 if (runtime_settings[0] != '\0')
9072 config_parse(runtime_settings, 1);
9074 /* download dir */
9075 if (!strcmp(download_dir, pwd->pw_dir))
9076 strlcat(download_dir, "/downloads", sizeof download_dir);
9077 xxx_dir(download_dir);
9079 /* favorites file */
9080 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9081 if (stat(file, &sb)) {
9082 warnx("favorites file doesn't exist, creating it");
9083 if ((f = fopen(file, "w")) == NULL)
9084 err(1, "favorites");
9085 fclose(f);
9088 /* cookies */
9089 session = webkit_get_default_session();
9090 setup_cookies();
9092 /* certs */
9093 if (ssl_ca_file) {
9094 if (stat(ssl_ca_file, &sb)) {
9095 warnx("no CA file: %s", ssl_ca_file);
9096 g_free(ssl_ca_file);
9097 ssl_ca_file = NULL;
9098 } else
9099 g_object_set(session,
9100 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9101 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9102 (void *)NULL);
9105 /* proxy */
9106 env_proxy = getenv("http_proxy");
9107 if (env_proxy)
9108 setup_proxy(env_proxy);
9109 else
9110 setup_proxy(http_proxy);
9112 if (opte) {
9113 send_cmd_to_socket(argv[0]);
9114 exit(0);
9117 /* set some connection parameters */
9118 /* XXX webkit 1.4.X overwrites these values! */
9119 /* https://bugs.webkit.org/show_bug.cgi?id=64355 */
9120 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9121 g_object_set(session, "max-conns-per-host", max_host_connections,
9122 (char *)NULL);
9124 /* see if there is already an xxxterm running */
9125 if (single_instance && is_running()) {
9126 optn = 1;
9127 warnx("already running");
9130 char *cmd = NULL;
9131 if (optn) {
9132 while (argc) {
9133 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9134 send_cmd_to_socket(cmd);
9135 if (cmd)
9136 g_free(cmd);
9138 argc--;
9139 argv++;
9141 exit(0);
9144 /* uri completion */
9145 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9147 /* buffers */
9148 buffers_store = gtk_list_store_new
9149 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9151 /* go graphical */
9152 create_canvas();
9153 notebook_tab_set_visibility();
9155 if (save_global_history)
9156 restore_global_history();
9158 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9159 restore_saved_tabs();
9160 else {
9161 a.s = named_session;
9162 a.i = XT_SES_DONOTHING;
9163 open_tabs(NULL, &a);
9166 while (argc) {
9167 create_new_tab(argv[0], NULL, focus, -1);
9168 focus = 0;
9170 argc--;
9171 argv++;
9174 if (TAILQ_EMPTY(&tabs))
9175 create_new_tab(home, NULL, 1, -1);
9177 if (enable_socket)
9178 if ((s = build_socket()) != -1) {
9179 channel = g_io_channel_unix_new(s);
9180 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9183 gtk_main();
9185 gnutls_global_deinit();
9187 return (0);