surf

Surf web browser.
git clone git://r-36.net/surf
Log | Files | Refs | README | LICENSE

surf.c (51025B)


      1 /* See LICENSE file for copyright and license details.
      2  *
      3  * To understand surf, start reading main().
      4  */
      5 #include <signal.h>
      6 #include <X11/X.h>
      7 #include <X11/Xatom.h>
      8 #include <gtk/gtk.h>
      9 #include <gdk/gdkx.h>
     10 #include <gdk/gdk.h>
     11 #include <gdk/gdkkeysyms.h>
     12 #include <string.h>
     13 #include <sys/types.h>
     14 #include <sys/wait.h>
     15 #include <fcntl.h>
     16 #include <unistd.h>
     17 #include <limits.h>
     18 #include <stdlib.h>
     19 #include <stdio.h>
     20 #include <webkit/webkit.h>
     21 #include <JavaScriptCore/JavaScript.h>
     22 #include <sys/file.h>
     23 #include <libgen.h>
     24 #include <stdarg.h>
     25 #include <regex.h>
     26 #include <pwd.h>
     27 #include <glib.h>
     28 #include <glib/gstdio.h>
     29 
     30 #include "arg.h"
     31 
     32 char *argv0;
     33 
     34 #define LENGTH(x)               (sizeof(x) / sizeof(x[0]))
     35 #define CLEANMASK(mask)         (mask & (MODKEY|GDK_SHIFT_MASK))
     36 #define COOKIEJAR_TYPE          (cookiejar_get_type ())
     37 #define COOKIEJAR(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), COOKIEJAR_TYPE, CookieJar))
     38 
     39 enum { AtomFind, AtomGo, AtomUri, AtomUA, AtomLast };
     40 enum {
     41 	ClkDoc   = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
     42 	ClkLink  = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
     43 	ClkImg   = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
     44 	ClkMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
     45 	ClkSel   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
     46 	ClkEdit  = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
     47 	ClkAny   = ClkDoc | ClkLink | ClkImg | ClkMedia | ClkSel | ClkEdit,
     48 };
     49 
     50 typedef union Arg Arg;
     51 union Arg {
     52 	gboolean b;
     53 	gint i;
     54 	const void *v;
     55 };
     56 
     57 typedef struct Client {
     58 	GtkWidget *win, *scroll, *vbox, *pane;
     59 	WebKitWebView *view;
     60 	WebKitWebInspector *inspector;
     61 	char *title, *linkhover;
     62 	const char *needle;
     63 	gint progress;
     64 	struct Client *next;
     65 	gboolean zoomed, fullscreen, isinspecting, sslfailed;
     66 } Client;
     67 
     68 typedef struct {
     69 	char *key;
     70 	char *value;
     71 } HttpHeader;
     72 
     73 typedef struct {
     74 	guint mod;
     75 	guint keyval;
     76 	void (*func)(Client *c, const Arg *arg);
     77 	const Arg arg;
     78 } Key;
     79 
     80 typedef struct {
     81 	unsigned int click;
     82 	unsigned int mask;
     83 	guint button;
     84 	void (*func)(Client *c, const Arg *arg);
     85 	const Arg arg;
     86 } Button;
     87 
     88 typedef struct {
     89 	SoupCookieJarText parent_instance;
     90 	int lock;
     91 } CookieJar;
     92 
     93 typedef struct {
     94 	SoupCookieJarTextClass parent_class;
     95 } CookieJarClass;
     96 
     97 G_DEFINE_TYPE(CookieJar, cookiejar, SOUP_TYPE_COOKIE_JAR_TEXT)
     98 
     99 typedef struct {
    100 	char *regex;
    101 	char *style;
    102 	regex_t re;
    103 } SiteStyle;
    104 
    105 static Display *dpy;
    106 static Atom atoms[AtomLast];
    107 static Client *clients = NULL;
    108 static GdkNativeWindow embed = 0;
    109 static gboolean showxid = FALSE;
    110 static char winid[64];
    111 static gboolean usingproxy = 0;
    112 static char togglestat[12];
    113 static char pagestat[3];
    114 static GTlsDatabase *tlsdb;
    115 static int policysel = 0;
    116 static char *stylefile = NULL;
    117 static SoupCache *diskcache = NULL;
    118 
    119 static void addaccelgroup(Client *c);
    120 static void beforerequest(WebKitWebView *w, WebKitWebFrame *f,
    121                           WebKitWebResource *r, WebKitNetworkRequest *req,
    122                           WebKitNetworkResponse *resp, Client *c);
    123 static const char *getuserhomedir(const char *user);
    124 static const char *getcurrentuserhomedir(void);
    125 static char *buildfile(const char *path);
    126 static char *buildpath(const char *path);
    127 static gboolean buttonrelease(WebKitWebView *web, GdkEventButton *e, Client *c);
    128 static void cleanup(void);
    129 static void clipboard(Client *c, const Arg *arg);
    130 
    131 /* Cookiejar implementation */
    132 static void cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie,
    133                               SoupCookie *new_cookie);
    134 static void cookiejar_finalize(GObject *self);
    135 static SoupCookieJarAcceptPolicy cookiepolicy_get(void);
    136 static SoupCookieJar *cookiejar_new(const char *filename, gboolean read_only,
    137                                     SoupCookieJarAcceptPolicy policy);
    138 static void cookiejar_set_property(GObject *self, guint prop_id,
    139     const GValue *value, GParamSpec *pspec);
    140 static char cookiepolicy_set(const SoupCookieJarAcceptPolicy p);
    141 
    142 static char *copystr(char **str, const char *src);
    143 static WebKitWebView *createwindow(WebKitWebView *v, WebKitWebFrame *f,
    144                                    Client *c);
    145 static gboolean decidedownload(WebKitWebView *v, WebKitWebFrame *f,
    146                                WebKitNetworkRequest *r, gchar *m,
    147                                WebKitWebPolicyDecision *p, Client *c);
    148 static gboolean decidewindow(WebKitWebView *v, WebKitWebFrame *f,
    149                              WebKitNetworkRequest *r, WebKitWebNavigationAction
    150                              *n, WebKitWebPolicyDecision *p, Client *c);
    151 static gboolean deletion_interface(WebKitWebView *view,
    152                                    WebKitDOMHTMLElement *arg1, Client *c);
    153 static void destroyclient(Client *c);
    154 static void destroywin(GtkWidget* w, Client *c);
    155 static void die(const char *errstr, ...);
    156 static void eval(Client *c, const Arg *arg);
    157 static void find(Client *c, const Arg *arg);
    158 static void fullscreen(Client *c, const Arg *arg);
    159 static void geopolicyrequested(WebKitWebView *v, WebKitWebFrame *f,
    160                                WebKitGeolocationPolicyDecision *d, Client *c);
    161 static const char *getatom(Client *c, int a);
    162 static void gettogglestat(Client *c);
    163 static void getpagestat(Client *c);
    164 static char *geturi(Client *c);
    165 static const gchar *getstyle(const char *uri);
    166 static void setstyle(Client *c, const char *style);
    167 
    168 static void handleplumb(Client *c, WebKitWebView *w, const gchar *uri);
    169 
    170 static gboolean initdownload(WebKitWebView *v, WebKitDownload *o, Client *c);
    171 
    172 static void inspector(Client *c, const Arg *arg);
    173 static WebKitWebView *inspector_new(WebKitWebInspector *i, WebKitWebView *v,
    174                                     Client *c);
    175 static gboolean inspector_show(WebKitWebInspector *i, Client *c);
    176 static gboolean inspector_close(WebKitWebInspector *i, Client *c);
    177 static void inspector_finished(WebKitWebInspector *i, Client *c);
    178 
    179 static gboolean keypress(GtkAccelGroup *group, GObject *obj, guint key,
    180                          GdkModifierType mods, Client *c);
    181 static void linkhover(WebKitWebView *v, const char* t, const char* l,
    182                       Client *c);
    183 static void loadstatuschange(WebKitWebView *view, GParamSpec *pspec,
    184                              Client *c);
    185 static void loaduri(Client *c, const Arg *arg);
    186 static void navigate(Client *c, const Arg *arg);
    187 static Client *newclient(void);
    188 static void newwindow(Client *c, const Arg *arg, gboolean noembed);
    189 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
    190 static gboolean contextmenu(WebKitWebView *view, GtkWidget *menu,
    191                             WebKitHitTestResult *target, gboolean keyboard,
    192                             Client *c);
    193 static void menuactivate(GtkMenuItem *item, Client *c);
    194 static void print(Client *c, const Arg *arg);
    195 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
    196                                 gpointer d);
    197 static void progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c);
    198 static void linkopen(Client *c, const Arg *arg);
    199 static void linkopenembed(Client *c, const Arg *arg);
    200 static void reload(Client *c, const Arg *arg);
    201 static void scroll_h(Client *c, const Arg *arg);
    202 static void scroll_v(Client *c, const Arg *arg);
    203 static void scroll(GtkAdjustment *a, const Arg *arg);
    204 static void setatom(Client *c, int a, const char *v);
    205 static void setup(void);
    206 static void setup_proxy(void);
    207 static void sigchld(int unused);
    208 static void sighup(int unused);
    209 static void source(Client *c, const Arg *arg);
    210 static void spawn(Client *c, const Arg *arg);
    211 static void stop(Client *c, const Arg *arg);
    212 static void titlechange(WebKitWebView *view, GParamSpec *pspec, Client *c);
    213 static void titlechangeleave(void *a, void *b, Client *c);
    214 static void toggle(Client *c, const Arg *arg);
    215 static void togglehelper(Client *c, const Arg *arg, int reload);
    216 static void togglecookiepolicy(Client *c, const Arg *arg);
    217 static void toggleinsecurecontent(Client *c, const Arg *arg);
    218 static void togglegeolocation(Client *c, const Arg *arg);
    219 static void toggleproxy(Client *c, const Arg *arg);
    220 static void togglescrollbars(Client *c, const Arg *arg);
    221 static void togglesoup(Client *c, const Arg *arg);
    222 static void togglestyle(Client *c, const Arg *arg);
    223 static void updatetitle(Client *c);
    224 static void updatewinid(Client *c);
    225 static void usage(void);
    226 static void windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame,
    227                                 JSContextRef js, JSObjectRef win, Client *c);
    228 static void zoom(Client *c, const Arg *arg);
    229 
    230 /* configuration, allows nested code to access above variables */
    231 #include "config.h"
    232 
    233 void
    234 addaccelgroup(Client *c)
    235 {
    236 	int i;
    237 	GtkAccelGroup *group = gtk_accel_group_new();
    238 	GClosure *closure;
    239 
    240 	for (i = 0; i < LENGTH(keys); i++) {
    241 		closure = g_cclosure_new(G_CALLBACK(keypress), c, NULL);
    242 		gtk_accel_group_connect(group, keys[i].keyval, keys[i].mod, 0,
    243 		                        closure);
    244 	}
    245 	gtk_window_add_accel_group(GTK_WINDOW(c->win), group);
    246 }
    247 
    248 void
    249 beforerequest(WebKitWebView *w, WebKitWebFrame *f, WebKitWebResource *r,
    250               WebKitNetworkRequest *req, WebKitNetworkResponse *resp,
    251               Client *c)
    252 {
    253 	SoupMessage *msg;
    254 	SoupMessageHeaders *hdrs;
    255 	const gchar *uri = webkit_network_request_get_uri(req);
    256 	int i, isascii = 1;
    257 
    258 	if (g_str_has_suffix(uri, "/favicon.ico"))
    259 		webkit_network_request_set_uri(req, "about:blank");
    260 
    261 	if (!g_str_has_prefix(uri, "http://")
    262 			&& !g_str_has_prefix(uri, "https://")
    263 			&& !g_str_has_prefix(uri, "about:")
    264 			&& !g_str_has_prefix(uri, "file://")
    265 			&& !g_str_has_prefix(uri, "data:")
    266 			&& !g_str_has_prefix(uri, "blob:")
    267 			&& strlen(uri) > 0) {
    268 		for (i = 0; i < strlen(uri); i++) {
    269 			if (!g_ascii_isprint(uri[i])) {
    270 				isascii = 0;
    271 				break;
    272 			}
    273 		}
    274 		if (isascii)
    275 			handleplumb(c, w, uri);
    276 
    277 		return;
    278 	}
    279 	if (g_str_has_prefix(uri, "http://")
    280 			|| g_str_has_prefix(uri, "https://")) {
    281 		msg = webkit_network_request_get_message(req);
    282 		g_object_get(G_OBJECT(msg), "request-headers", &hdrs,
    283 				NULL);
    284 		if (hdrs != NULL) {
    285 			for (i = 0; i < LENGTH(customheaders); i++) {
    286 				soup_message_headers_replace(hdrs,
    287 						customheaders[i].key,
    288 						customheaders[i].value);
    289 			}
    290 		}
    291 	}
    292 }
    293 
    294 char *
    295 buildfile(const char *path)
    296 {
    297 	char *dname, *bname, *bpath, *fpath;
    298 	FILE *f;
    299 
    300 	dname = g_path_get_dirname(path);
    301 	bname = g_path_get_basename(path);
    302 
    303 	bpath = buildpath(dname);
    304 	g_free(dname);
    305 
    306 	fpath = g_build_filename(bpath, bname, NULL);
    307 	g_free(bpath);
    308 	g_free(bname);
    309 
    310 	if (!(f = fopen(fpath, "a")))
    311 		die("Could not open file: %s\n", fpath);
    312 
    313 	g_chmod(fpath, 0600); /* always */
    314 	fclose(f);
    315 
    316 	return fpath;
    317 }
    318 
    319 static const char*
    320 getuserhomedir(const char *user)
    321 {
    322 	struct passwd *pw = getpwnam(user);
    323 
    324 	if (!pw)
    325 		die("Can't get user %s login information.\n", user);
    326 
    327 	return pw->pw_dir;
    328 }
    329 
    330 static const char*
    331 getcurrentuserhomedir(void)
    332 {
    333 	const char *homedir;
    334 	const char *user;
    335 	struct passwd *pw;
    336 
    337 	homedir = getenv("HOME");
    338 	if (homedir)
    339 		return homedir;
    340 
    341 	user = getenv("USER");
    342 	if (user)
    343 		return getuserhomedir(user);
    344 
    345 	pw = getpwuid(getuid());
    346 	if (!pw)
    347 		die("Can't get current user home directory\n");
    348 
    349 	return pw->pw_dir;
    350 }
    351 
    352 char *
    353 buildpath(const char *path)
    354 {
    355 	char *apath, *name, *p, *fpath;
    356 	const char *homedir;
    357 
    358 	if (path[0] == '~') {
    359 		if (path[1] == '/' || path[1] == '\0') {
    360 			p = (char *)&path[1];
    361 			homedir = getcurrentuserhomedir();
    362 		} else {
    363 			if ((p = strchr(path, '/'))) {
    364 				name = g_strndup(&path[1], --p - path);
    365 			} else {
    366 				name = g_strdup(&path[1]);
    367 			}
    368 
    369 			homedir = getuserhomedir(name);
    370 			g_free(name);
    371 		}
    372 		apath = g_build_filename(homedir, p, NULL);
    373 	} else {
    374 		apath = g_strdup(path);
    375 	}
    376 
    377 	/* creating directory */
    378 	if (g_mkdir_with_parents(apath, 0700) < 0)
    379 		die("Could not access directory: %s\n", apath);
    380 
    381 	fpath = realpath(apath, NULL);
    382 	g_free(apath);
    383 
    384 	return fpath;
    385 }
    386 
    387 gboolean
    388 buttonrelease(WebKitWebView *web, GdkEventButton *e, Client *c)
    389 {
    390 	WebKitHitTestResultContext context;
    391 	WebKitHitTestResult *result;
    392 	Arg arg;
    393 	unsigned int i;
    394 
    395 	result = webkit_web_view_get_hit_test_result(web, e);
    396 	g_object_get(result, "context", &context, NULL);
    397 	g_object_get(result, "link-uri", &arg.v, NULL);
    398 	for (i = 0; i < LENGTH(buttons); i++) {
    399 		if (context & buttons[i].click
    400 		    && e->button == buttons[i].button
    401 		    && CLEANMASK(e->state) == CLEANMASK(buttons[i].mask)
    402 		    && buttons[i].func) {
    403 			buttons[i].func(c, buttons[i].click == ClkLink
    404 			                && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg);
    405 			return true;
    406 		}
    407 	}
    408 	return false;
    409 }
    410 
    411 void
    412 cleanup(void)
    413 {
    414 	if (diskcache) {
    415 		soup_cache_flush(diskcache);
    416 		soup_cache_dump(diskcache);
    417 	}
    418 	while (clients)
    419 		destroyclient(clients);
    420 	g_free(cookiefile);
    421 	g_free(scriptfile);
    422 	g_free(stylefile);
    423 }
    424 
    425 void
    426 cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie,
    427                   SoupCookie *new_cookie)
    428 {
    429 	flock(COOKIEJAR(self)->lock, LOCK_EX);
    430 	if (new_cookie && !new_cookie->expires && sessiontime) {
    431 		soup_cookie_set_expires(new_cookie,
    432 		                        soup_date_new_from_now(sessiontime));
    433 	}
    434 	SOUP_COOKIE_JAR_CLASS(cookiejar_parent_class)->changed(self,
    435 	                                                       old_cookie,
    436 	                                                       new_cookie);
    437 	flock(COOKIEJAR(self)->lock, LOCK_UN);
    438 }
    439 
    440 void
    441 cookiejar_class_init(CookieJarClass *klass)
    442 {
    443 	SOUP_COOKIE_JAR_CLASS(klass)->changed = cookiejar_changed;
    444 	G_OBJECT_CLASS(klass)->get_property =
    445 	    G_OBJECT_CLASS(cookiejar_parent_class)->get_property;
    446 	G_OBJECT_CLASS(klass)->set_property = cookiejar_set_property;
    447 	G_OBJECT_CLASS(klass)->finalize = cookiejar_finalize;
    448 	g_object_class_override_property(G_OBJECT_CLASS(klass), 1, "filename");
    449 }
    450 
    451 void
    452 cookiejar_finalize(GObject *self)
    453 {
    454 	close(COOKIEJAR(self)->lock);
    455 	G_OBJECT_CLASS(cookiejar_parent_class)->finalize(self);
    456 }
    457 
    458 void
    459 cookiejar_init(CookieJar *self)
    460 {
    461 	self->lock = open(cookiefile, 0);
    462 }
    463 
    464 SoupCookieJar *
    465 cookiejar_new(const char *filename, gboolean read_only,
    466               SoupCookieJarAcceptPolicy policy)
    467 {
    468 	return g_object_new(COOKIEJAR_TYPE,
    469 	                    SOUP_COOKIE_JAR_TEXT_FILENAME, filename,
    470 	                    SOUP_COOKIE_JAR_READ_ONLY, read_only,
    471 	                    SOUP_COOKIE_JAR_ACCEPT_POLICY, policy, NULL);
    472 }
    473 
    474 void
    475 cookiejar_set_property(GObject *self, guint prop_id, const GValue *value,
    476                        GParamSpec *pspec)
    477 {
    478 	flock(COOKIEJAR(self)->lock, LOCK_SH);
    479 	G_OBJECT_CLASS(cookiejar_parent_class)->set_property(self, prop_id,
    480 	                                                     value, pspec);
    481 	flock(COOKIEJAR(self)->lock, LOCK_UN);
    482 }
    483 
    484 SoupCookieJarAcceptPolicy
    485 cookiepolicy_get(void)
    486 {
    487 	switch (cookiepolicies[policysel]) {
    488 	case 'a':
    489 		return SOUP_COOKIE_JAR_ACCEPT_NEVER;
    490 	case '@':
    491 		return SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
    492 	case 'A':
    493 	default:
    494 		break;
    495 	}
    496 
    497 	return SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
    498 }
    499 
    500 char
    501 cookiepolicy_set(const SoupCookieJarAcceptPolicy ep)
    502 {
    503 	switch (ep) {
    504 	case SOUP_COOKIE_JAR_ACCEPT_NEVER:
    505 		return 'a';
    506 	case SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY:
    507 		return '@';
    508 	case SOUP_COOKIE_JAR_ACCEPT_ALWAYS:
    509 	default:
    510 		break;
    511 	}
    512 
    513 	return 'A';
    514 }
    515 
    516 void
    517 evalscript(JSContextRef js, char *script, char *scriptname)
    518 {
    519 	JSStringRef jsscript, jsscriptname;
    520 	JSValueRef exception = NULL;
    521 
    522 	jsscript = JSStringCreateWithUTF8CString(script);
    523 	jsscriptname = JSStringCreateWithUTF8CString(scriptname);
    524 	JSEvaluateScript(js, jsscript, JSContextGetGlobalObject(js),
    525 	                 jsscriptname, 0, &exception);
    526 	JSStringRelease(jsscript);
    527 	JSStringRelease(jsscriptname);
    528 }
    529 
    530 void
    531 runscript(WebKitWebFrame *frame)
    532 {
    533 	char *script;
    534 	GError *error;
    535 
    536 	if (g_file_get_contents(scriptfile, &script, NULL, &error)) {
    537 		evalscript(webkit_web_frame_get_global_context(frame), script,
    538 		           scriptfile);
    539 	}
    540 }
    541 
    542 void
    543 clipboard(Client *c, const Arg *arg)
    544 {
    545 	gboolean paste = *(gboolean *)arg;
    546 
    547 	if (paste) {
    548 		gtk_clipboard_request_text(gtk_clipboard_get(
    549 		                           GDK_SELECTION_PRIMARY),
    550 		                           pasteuri, c);
    551 	} else {
    552 		gtk_clipboard_set_text(gtk_clipboard_get(
    553 		                       GDK_SELECTION_PRIMARY), c->linkhover
    554 		                       ? c->linkhover : geturi(c), -1);
    555 	}
    556 }
    557 
    558 char *
    559 copystr(char **str, const char *src)
    560 {
    561 	char *tmp;
    562 	tmp = g_strdup(src);
    563 
    564 	if (str && *str) {
    565 		g_free(*str);
    566 		*str = tmp;
    567 	}
    568 	return tmp;
    569 }
    570 
    571 WebKitWebView *
    572 createwindow(WebKitWebView  *v, WebKitWebFrame *f, Client *c)
    573 {
    574 	Client *n = newclient();
    575 	return n->view;
    576 }
    577 
    578 gboolean
    579 decidedownload(WebKitWebView *v, WebKitWebFrame *f, WebKitNetworkRequest *r,
    580                gchar *m,  WebKitWebPolicyDecision *p, Client *c)
    581 {
    582 	if (!webkit_web_view_can_show_mime_type(v, m)) {
    583 		webkit_web_policy_decision_download(p);
    584 		return TRUE;
    585 	}
    586 	return FALSE;
    587 }
    588 
    589 gboolean
    590 decidewindow(WebKitWebView *view, WebKitWebFrame *f, WebKitNetworkRequest *r,
    591              WebKitWebNavigationAction *n, WebKitWebPolicyDecision *p,
    592              Client *c)
    593 {
    594 	Arg arg;
    595 
    596 	if (webkit_web_navigation_action_get_reason(n)
    597 	    == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
    598 		webkit_web_policy_decision_ignore(p);
    599 		arg.v = (void *)webkit_network_request_get_uri(r);
    600 		newwindow(NULL, &arg, 0);
    601 		return TRUE;
    602 	}
    603 	return FALSE;
    604 }
    605 
    606 gboolean
    607 deletion_interface(WebKitWebView *view, WebKitDOMHTMLElement *arg1, Client *c)
    608 {
    609 	return FALSE;
    610 }
    611 
    612 void
    613 destroyclient(Client *c)
    614 {
    615 	Client *p;
    616 
    617 	webkit_web_view_stop_loading(c->view);
    618 	gtk_widget_destroy(GTK_WIDGET(c->view));
    619 	gtk_widget_destroy(c->scroll);
    620 	gtk_widget_destroy(c->vbox);
    621 	gtk_widget_destroy(c->win);
    622 
    623 	for (p = clients; p && p->next != c; p = p->next)
    624 		;
    625 	if (p)
    626 		p->next = c->next;
    627 	else
    628 		clients = c->next;
    629 	free(c);
    630 	if (clients == NULL)
    631 		gtk_main_quit();
    632 }
    633 
    634 void
    635 destroywin(GtkWidget* w, Client *c)
    636 {
    637 	destroyclient(c);
    638 }
    639 
    640 void
    641 die(const char *errstr, ...)
    642 {
    643 	va_list ap;
    644 
    645 	va_start(ap, errstr);
    646 	vfprintf(stderr, errstr, ap);
    647 	va_end(ap);
    648 	exit(EXIT_FAILURE);
    649 }
    650 
    651 void
    652 find(Client *c, const Arg *arg)
    653 {
    654 	const char *s;
    655 
    656 	s = getatom(c, AtomFind);
    657 	gboolean forward = *(gboolean *)arg;
    658 	webkit_web_view_search_text(c->view, s, FALSE, forward, TRUE);
    659 }
    660 
    661 void
    662 fullscreen(Client *c, const Arg *arg)
    663 {
    664 	if (c->fullscreen)
    665 		gtk_window_unfullscreen(GTK_WINDOW(c->win));
    666 	else
    667 		gtk_window_fullscreen(GTK_WINDOW(c->win));
    668 	c->fullscreen = !c->fullscreen;
    669 }
    670 
    671 void
    672 geopolicyrequested(WebKitWebView *v, WebKitWebFrame *f,
    673                    WebKitGeolocationPolicyDecision *d, Client *c)
    674 {
    675 	if (allowgeolocation)
    676 		webkit_geolocation_policy_allow(d);
    677 	else
    678 		webkit_geolocation_policy_deny(d);
    679 }
    680 
    681 const char *
    682 getatom(Client *c, int a)
    683 {
    684 	static char buf[BUFSIZ];
    685 	Atom adummy;
    686 	int idummy;
    687 	unsigned long ldummy;
    688 	unsigned char *p = NULL;
    689 
    690 	XGetWindowProperty(dpy, GDK_WINDOW_XID(GTK_WIDGET(c->win)->window),
    691 	                   atoms[a], 0L, BUFSIZ, False, AnyPropertyType,
    692 	                   &adummy, &idummy, &ldummy, &ldummy, &p);
    693 	if (p)
    694 		strncpy(buf, (char *)p, LENGTH(buf)-1);
    695 	else
    696 		buf[0] = '\0';
    697 	XFree(p);
    698 
    699 	return buf;
    700 }
    701 
    702 char *
    703 geturi(Client *c)
    704 {
    705 	char *uri;
    706 
    707 	if (!(uri = (char *)webkit_web_view_get_uri(c->view)))
    708 		uri = "about:blank";
    709 	return uri;
    710 }
    711 
    712 const gchar *
    713 getstyle(const char *uri)
    714 {
    715 	int i;
    716 
    717 	if (stylefile != NULL)
    718 		return stylefile;
    719 
    720 	for (i = 0; i < LENGTH(styles); i++) {
    721 		if (styles[i].regex && !regexec(&(styles[i].re), uri, 0,
    722 		    NULL, 0))
    723 			return styles[i].style;
    724 	}
    725 
    726 	return "";
    727 }
    728 
    729 void
    730 setstyle(Client *c, const char *style)
    731 {
    732 	WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
    733 
    734 	g_object_set(G_OBJECT(settings), "user-stylesheet-uri", style, NULL);
    735 }
    736 
    737 void
    738 handleplumb(Client *c, WebKitWebView *w, const gchar *uri)
    739 {
    740 	Arg arg;
    741 
    742 	webkit_web_view_stop_loading(w);
    743 	arg = (Arg)PLUMB((char *)uri);
    744 	spawn(c, &arg);
    745 }
    746 
    747 gboolean
    748 initdownload(WebKitWebView *view, WebKitDownload *o, Client *c)
    749 {
    750 	Arg arg;
    751 
    752 	updatewinid(c);
    753 	arg = (Arg)DOWNLOAD((char *)webkit_download_get_uri(o), geturi(c));
    754 	spawn(c, &arg);
    755 	return FALSE;
    756 }
    757 
    758 void
    759 inspector(Client *c, const Arg *arg)
    760 {
    761 	if (enableinspector) {
    762 		if (c->isinspecting)
    763 			webkit_web_inspector_close(c->inspector);
    764 		else
    765 			webkit_web_inspector_show(c->inspector);
    766 	}
    767 }
    768 
    769 WebKitWebView *
    770 inspector_new(WebKitWebInspector *i, WebKitWebView *v, Client *c)
    771 {
    772 	return WEBKIT_WEB_VIEW(webkit_web_view_new());
    773 }
    774 
    775 gboolean
    776 inspector_show(WebKitWebInspector *i, Client *c)
    777 {
    778 	WebKitWebView *w;
    779 
    780 	if (c->isinspecting)
    781 		return false;
    782 
    783 	w = webkit_web_inspector_get_web_view(i);
    784 	gtk_paned_pack2(GTK_PANED(c->pane), GTK_WIDGET(w), TRUE, TRUE);
    785 	gtk_widget_show(GTK_WIDGET(w));
    786 	c->isinspecting = true;
    787 
    788 	return true;
    789 }
    790 
    791 gboolean
    792 inspector_close(WebKitWebInspector *i, Client *c)
    793 {
    794 	GtkWidget *w;
    795 
    796 	if (!c->isinspecting)
    797 		return false;
    798 
    799 	w = GTK_WIDGET(webkit_web_inspector_get_web_view(i));
    800 	gtk_widget_hide(w);
    801 	gtk_widget_destroy(w);
    802 	c->isinspecting = false;
    803 
    804 	return true;
    805 }
    806 
    807 void
    808 inspector_finished(WebKitWebInspector *i, Client *c)
    809 {
    810 	g_free(c->inspector);
    811 }
    812 
    813 gboolean
    814 keypress(GtkAccelGroup *group, GObject *obj, guint key, GdkModifierType mods,
    815          Client *c)
    816 {
    817 	guint i;
    818 	gboolean processed = FALSE;
    819 
    820 	mods = CLEANMASK(mods);
    821 	key = gdk_keyval_to_lower(key);
    822 	updatewinid(c);
    823 	for (i = 0; i < LENGTH(keys); i++) {
    824 		if (key == keys[i].keyval
    825 		    && mods == keys[i].mod
    826 		    && keys[i].func) {
    827 			keys[i].func(c, &(keys[i].arg));
    828 			processed = TRUE;
    829 		}
    830 	}
    831 
    832 	return processed;
    833 }
    834 
    835 void
    836 linkhover(WebKitWebView *v, const char* t, const char* l, Client *c)
    837 {
    838 	if (l) {
    839 		c->linkhover = copystr(&c->linkhover, l);
    840 	} else if (c->linkhover) {
    841 		free(c->linkhover);
    842 		c->linkhover = NULL;
    843 	}
    844 	updatetitle(c);
    845 }
    846 
    847 void
    848 loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c)
    849 {
    850 	WebKitWebFrame *frame;
    851 	WebKitWebDataSource *src;
    852 	WebKitNetworkRequest *request;
    853 	SoupMessage *msg;
    854 	char *uri;
    855 
    856 	switch (webkit_web_view_get_load_status (c->view)) {
    857 	case WEBKIT_LOAD_COMMITTED:
    858 		uri = geturi(c);
    859 		if (strstr(uri, "https://") == uri) {
    860 			frame = webkit_web_view_get_main_frame(c->view);
    861 			src = webkit_web_frame_get_data_source(frame);
    862 			request = webkit_web_data_source_get_request(src);
    863 			msg = webkit_network_request_get_message(request);
    864 			c->sslfailed = !(soup_message_get_flags(msg)
    865 			                 & SOUP_MESSAGE_CERTIFICATE_TRUSTED);
    866 		}
    867 		setatom(c, AtomUri, uri);
    868 		c->title = copystr(&c->title, uri);
    869 
    870 		if (enablestyle)
    871 			setstyle(c, getstyle(uri));
    872 		break;
    873 	case WEBKIT_LOAD_FINISHED:
    874 		c->progress = 100;
    875 		updatetitle(c);
    876 		if (diskcache) {
    877 			soup_cache_flush(diskcache);
    878 			soup_cache_dump(diskcache);
    879 		}
    880 		break;
    881 	default:
    882 		break;
    883 	}
    884 }
    885 
    886 void
    887 loaduri(Client *c, const Arg *arg)
    888 {
    889 	char *u = NULL, *rp;
    890 	const char *uri = (char *)arg->v;
    891 	Arg a = { .b = FALSE };
    892 	struct stat st;
    893 
    894 	if (strcmp(uri, "") == 0)
    895 		return;
    896 
    897 	/* In case it's a file path. */
    898 	if (stat(uri, &st) == 0) {
    899 		rp = realpath(uri, NULL);
    900 		u = g_strdup_printf("file://%s", rp);
    901 		free(rp);
    902 	} else {
    903 		u = g_strrstr(uri, "://") || g_str_has_prefix(uri, "about:") ? g_strdup(uri)
    904 		    : g_strdup_printf("http://%s", uri);
    905 	}
    906 
    907 	setatom(c, AtomUri, uri);
    908 
    909 
    910 	/* prevents endless loop */
    911 	if (strcmp(u, geturi(c)) == 0) {
    912 		reload(c, &a);
    913 	} else {
    914 		webkit_web_view_load_uri(c->view, u);
    915 		c->progress = 0;
    916 		c->title = copystr(&c->title, u);
    917 		updatetitle(c);
    918 	}
    919 	g_free(u);
    920 }
    921 
    922 void
    923 navigate(Client *c, const Arg *arg)
    924 {
    925 	int steps = *(int *)arg;
    926 	webkit_web_view_go_back_or_forward(c->view, steps);
    927 }
    928 
    929 Client *
    930 newclient(void)
    931 {
    932 	Client *c;
    933 	WebKitWebSettings *settings;
    934 	WebKitWebFrame *frame;
    935 	GdkGeometry hints = { 1, 1 };
    936 	GdkScreen *screen;
    937 	gdouble dpi;
    938 	char *ua;
    939 
    940 	if (!(c = calloc(1, sizeof(Client))))
    941 		die("Cannot malloc!\n");
    942 
    943 	c->title = NULL;
    944 	c->progress = 100;
    945 
    946 	/* Window */
    947 	if (embed) {
    948 		c->win = gtk_plug_new(embed);
    949 	} else {
    950 		c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    951 
    952 		/* TA:  20091214:  Despite what the GNOME docs say, the ICCCM
    953 		 * is always correct, so we should still call this function.
    954 		 * But when doing so, we *must* differentiate between a
    955 		 * WM_CLASS and a resource on the window.  By convention, the
    956 		 * window class (WM_CLASS) is capped, while the resource is in
    957 		 * lowercase.   Both these values come as a pair.
    958 		 */
    959 		gtk_window_set_wmclass(GTK_WINDOW(c->win), "surf", "Surf");
    960 
    961 		/* TA:  20091214:  And set the role here as well -- so that
    962 		 * sessions can pick this up.
    963 		 */
    964 		gtk_window_set_role(GTK_WINDOW(c->win), "Surf");
    965 	}
    966 	gtk_window_set_default_size(GTK_WINDOW(c->win), 800, 600);
    967 	g_signal_connect(G_OBJECT(c->win),
    968 	                 "destroy",
    969 	                 G_CALLBACK(destroywin), c);
    970 	g_signal_connect(G_OBJECT(c->win),
    971 	                 "leave_notify_event",
    972 	                 G_CALLBACK(titlechangeleave), c);
    973 
    974 	if (!kioskmode)
    975 		addaccelgroup(c);
    976 
    977 	/* Pane */
    978 	c->pane = gtk_vpaned_new();
    979 
    980 	/* VBox */
    981 	c->vbox = gtk_vbox_new(FALSE, 0);
    982 	gtk_paned_pack1(GTK_PANED(c->pane), c->vbox, TRUE, TRUE);
    983 
    984 	/* Webview */
    985 	c->view = WEBKIT_WEB_VIEW(webkit_web_view_new());
    986 
    987 	g_signal_connect(G_OBJECT(c->view),
    988 	                 "notify::title",
    989 	                 G_CALLBACK(titlechange), c);
    990 	g_signal_connect(G_OBJECT(c->view),
    991 	                 "hovering-over-link",
    992 	                 G_CALLBACK(linkhover), c);
    993 	g_signal_connect(G_OBJECT(c->view),
    994 	                 "geolocation-policy-decision-requested",
    995 	                 G_CALLBACK(geopolicyrequested), c);
    996 	g_signal_connect(G_OBJECT(c->view),
    997 	                 "create-web-view",
    998 	                 G_CALLBACK(createwindow), c);
    999 	g_signal_connect(G_OBJECT(c->view),
   1000 	                 "new-window-policy-decision-requested",
   1001 	                 G_CALLBACK(decidewindow), c);
   1002 	g_signal_connect(G_OBJECT(c->view),
   1003 	                 "mime-type-policy-decision-requested",
   1004 	                 G_CALLBACK(decidedownload), c);
   1005 	g_signal_connect(G_OBJECT(c->view),
   1006 	                 "window-object-cleared",
   1007 	                 G_CALLBACK(windowobjectcleared), c);
   1008 	g_signal_connect(G_OBJECT(c->view),
   1009 	                 "notify::load-status",
   1010 	                 G_CALLBACK(loadstatuschange), c);
   1011 	g_signal_connect(G_OBJECT(c->view),
   1012 	                 "notify::progress",
   1013 	                 G_CALLBACK(progresschange), c);
   1014 	g_signal_connect(G_OBJECT(c->view),
   1015 	                 "download-requested",
   1016 	                 G_CALLBACK(initdownload), c);
   1017 	g_signal_connect(G_OBJECT(c->view),
   1018 	                 "button-release-event",
   1019 	                 G_CALLBACK(buttonrelease), c);
   1020 	g_signal_connect(G_OBJECT(c->view),
   1021 	                 "context-menu",
   1022 	                 G_CALLBACK(contextmenu), c);
   1023 	g_signal_connect(G_OBJECT(c->view),
   1024 	                 "resource-request-starting",
   1025 	                 G_CALLBACK(beforerequest), c);
   1026 	g_signal_connect(G_OBJECT(c->view),
   1027 	                 "should-show-delete-interface-for-element",
   1028 	                 G_CALLBACK(deletion_interface), c);
   1029 
   1030 	/* Scrolled Window */
   1031 	c->scroll = gtk_scrolled_window_new(NULL, NULL);
   1032 
   1033 	frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(c->view));
   1034 	g_signal_connect(G_OBJECT(frame), "scrollbars-policy-changed",
   1035 	                 G_CALLBACK(gtk_true), NULL);
   1036 
   1037 	if (!enablescrollbars) {
   1038 		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
   1039 		                               GTK_POLICY_NEVER,
   1040 		                               GTK_POLICY_NEVER);
   1041 	} else {
   1042 		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
   1043 		                               GTK_POLICY_AUTOMATIC,
   1044 		                               GTK_POLICY_AUTOMATIC);
   1045 	}
   1046 
   1047 	/* Arranging */
   1048 	gtk_container_add(GTK_CONTAINER(c->scroll), GTK_WIDGET(c->view));
   1049 	gtk_container_add(GTK_CONTAINER(c->win), c->pane);
   1050 	gtk_container_add(GTK_CONTAINER(c->vbox), c->scroll);
   1051 
   1052 	/* Setup */
   1053 	gtk_box_set_child_packing(GTK_BOX(c->vbox), c->scroll, TRUE, TRUE, 0,
   1054 	                          GTK_PACK_START);
   1055 	gtk_widget_grab_focus(GTK_WIDGET(c->view));
   1056 	gtk_widget_show(c->pane);
   1057 	gtk_widget_show(c->vbox);
   1058 	gtk_widget_show(c->scroll);
   1059 	gtk_widget_show(GTK_WIDGET(c->view));
   1060 	gtk_widget_show(c->win);
   1061 	gtk_window_set_geometry_hints(GTK_WINDOW(c->win), NULL, &hints,
   1062 	                              GDK_HINT_MIN_SIZE);
   1063 	gdk_window_set_events(GTK_WIDGET(c->win)->window, GDK_ALL_EVENTS_MASK);
   1064 	gdk_window_add_filter(GTK_WIDGET(c->win)->window, processx, c);
   1065 	webkit_web_view_set_full_content_zoom(c->view, TRUE);
   1066 
   1067 	runscript(frame);
   1068 
   1069 	settings = webkit_web_view_get_settings(c->view);
   1070 	g_object_set(G_OBJECT(settings), "html5-local-storage-database-path",
   1071 			dbfolder, NULL);
   1072 	g_object_set(G_OBJECT(settings),
   1073 			"enable-offline-web-application-cache",
   1074 			offlineappcache, NULL);
   1075 	g_object_set(G_OBJECT(settings),
   1076 			"enable-page-cache", enablepagecache, NULL);
   1077 	g_object_set(G_OBJECT(settings),
   1078 			"enable-private-browsing", privatebrowsing, NULL);
   1079 	g_object_set(G_OBJECT(settings), "enable-dns-prefetching",
   1080 			dnsprefetching, NULL);
   1081 
   1082 	if (!(ua = getenv("SURF_USERAGENT")))
   1083 		ua = useragent;
   1084 	g_object_set(G_OBJECT(settings), "user-agent", ua, NULL);
   1085 	setatom(c, AtomUA, ua);
   1086 
   1087 	g_object_set(G_OBJECT(settings),
   1088 	             "auto-load-images", loadimages, NULL);
   1089 	g_object_set(G_OBJECT(settings),
   1090 	             "enable-plugins", enableplugins, NULL);
   1091 	g_object_set(G_OBJECT(settings),
   1092 	             "enable-scripts", enablescripts, NULL);
   1093 	g_object_set(G_OBJECT(settings),
   1094 	             "enable-spatial-navigation", enablespatialbrowsing, NULL);
   1095 	g_object_set(G_OBJECT(settings),
   1096 	             "enable-spell-checking", enablespellchecking, NULL);
   1097 	g_object_set(G_OBJECT(settings),
   1098 	             "media-playback-allows-inline", inlineplayback, NULL);
   1099 	g_object_set(G_OBJECT(settings),
   1100 	             "media-playback-requires-user-gesture", inlinegestures, NULL);
   1101 	g_object_set(G_OBJECT(settings),
   1102 	             "enable-webaudio", enablewebaudio, NULL);
   1103 	g_object_set(G_OBJECT(settings),
   1104 	             "enable-webgl", enablewebgl, NULL);
   1105 	g_object_set(G_OBJECT(settings),
   1106 	             "enable-developer-extras", enableinspector, NULL);
   1107 	g_object_set(G_OBJECT(settings),
   1108 	             "enable-default-context-menu", kioskmode ^ 1, NULL);
   1109 	g_object_set(G_OBJECT(settings),
   1110 	             "default-font-size", defaultfontsize, NULL);
   1111 	g_object_set(G_OBJECT(settings),
   1112 	             "default-monospace-font-size", defaultmonofontsize, NULL);
   1113 	g_object_set(G_OBJECT(settings),
   1114 	             "resizable-text-areas", 1, NULL);
   1115 	g_object_set(G_OBJECT(settings),
   1116 		     "default-encoding", defaultencoding, NULL);
   1117 	g_object_set(G_OBJECT(settings),
   1118 	             "enable-accelerated-compositing", accelrendering, NULL);
   1119 	g_object_set(G_OBJECT(settings),
   1120 	             "enable-display-of-insecure-content", insecureresources,
   1121 		     NULL);
   1122 	g_object_set(G_OBJECT(settings),
   1123 	             "enable-running-of-insecure-content", insecureresources,
   1124 		     NULL);
   1125 	g_object_set(G_OBJECT(settings),
   1126 	             "enable-html5-database", enablehtml5db, NULL);
   1127 	g_object_set(G_OBJECT(settings),
   1128 	             "enable-html5-local-storage", enablehtml5local, NULL);
   1129 	g_object_set(G_OBJECT(settings),
   1130 	             "enable-java-applet", enablejava, NULL);
   1131 	g_object_set(G_OBJECT(settings),
   1132 	             "enable-media-stream", enablemediastream, NULL);
   1133 	g_object_set(G_OBJECT(settings),
   1134 	             "enable-mediasource", enablemediasource, NULL);
   1135 	if (enablestyle)
   1136 		setstyle(c, getstyle("about:blank"));
   1137 
   1138 	/*
   1139 	 * While stupid, CSS specifies that a pixel represents 1/96 of an inch.
   1140 	 * This ensures websites are not unusably small with a high DPI screen.
   1141 	 * It is equivalent to firefox's "layout.css.devPixelsPerPx" setting.
   1142 	 */
   1143 	if (zoomto96dpi) {
   1144 		screen = gdk_window_get_screen(GTK_WIDGET(c->win)->window);
   1145 		dpi = gdk_screen_get_resolution(screen);
   1146 		if (dpi != -1) {
   1147 			g_object_set(G_OBJECT(settings),
   1148 			             "enforce-96-dpi", true, NULL);
   1149 			webkit_web_view_set_zoom_level(c->view, dpi/96);
   1150 		}
   1151 	}
   1152 	/* This might conflict with _zoomto96dpi_. */
   1153 	if (zoomlevel != 1.0)
   1154 		webkit_web_view_set_zoom_level(c->view, zoomlevel);
   1155 
   1156 	if (enableinspector) {
   1157 		c->inspector = webkit_web_view_get_inspector(c->view);
   1158 		g_signal_connect(G_OBJECT(c->inspector), "inspect-web-view",
   1159 		                 G_CALLBACK(inspector_new), c);
   1160 		g_signal_connect(G_OBJECT(c->inspector), "show-window",
   1161 		                 G_CALLBACK(inspector_show), c);
   1162 		g_signal_connect(G_OBJECT(c->inspector), "close-window",
   1163 		                 G_CALLBACK(inspector_close), c);
   1164 		g_signal_connect(G_OBJECT(c->inspector), "finished",
   1165 		                 G_CALLBACK(inspector_finished), c);
   1166 		c->isinspecting = false;
   1167 	}
   1168 
   1169 	if (runinfullscreen)
   1170 		fullscreen(c, NULL);
   1171 
   1172 	setatom(c, AtomFind, "");
   1173 	setatom(c, AtomUri, "about:blank");
   1174 	if (hidebackground)
   1175 		webkit_web_view_set_transparent(c->view, TRUE);
   1176 
   1177 	c->next = clients;
   1178 	clients = c;
   1179 
   1180 	if (showxid) {
   1181 		gdk_display_sync(gtk_widget_get_display(c->win));
   1182 		printf("%u\n",
   1183 		       (guint)GDK_WINDOW_XID(GTK_WIDGET(c->win)->window));
   1184 		fflush(NULL);
   1185 		if (fclose(stdout) != 0)
   1186 			die("Error closing stdout");
   1187 	}
   1188 
   1189 	return c;
   1190 }
   1191 
   1192 void
   1193 newwindow(Client *c, const Arg *arg, gboolean noembed)
   1194 {
   1195 	guint i = 0;
   1196 	const char *cmd[30], *uri;
   1197 	const Arg a = { .v = (void *)cmd };
   1198 	char tmp[64], ztmp[6];
   1199 
   1200 	cmd[i++] = argv0;
   1201 
   1202 	if (cookiepolicies != NULL) {
   1203 		cmd[i++] = "-a";
   1204 		cmd[i++] = cookiepolicies;
   1205 	}
   1206 
   1207 	if (enablescrollbars)
   1208 		cmd[i++] = "-B";
   1209 	else
   1210 		cmd[i++] = "-b";
   1211 
   1212 	if (cookiefile != NULL) {
   1213 		cmd[i++] = "-c";
   1214 		cmd[i++] = cookiefile;
   1215 	}
   1216 
   1217 	if (enablediskcache)
   1218 		cmd[i++] = "-D";
   1219 	else
   1220 		cmd[i++] = "-d";
   1221 
   1222 	if (embed && !noembed) {
   1223 		cmd[i++] = "-e";
   1224 		snprintf(tmp, LENGTH(tmp), "%u", (int)embed);
   1225 		cmd[i++] = tmp;
   1226 	}
   1227 
   1228 	if (runinfullscreen)
   1229 		cmd[i++] = "-F";
   1230 	else
   1231 		cmd[i++] = "-f";
   1232 
   1233 	if (allowgeolocation)
   1234 		cmd[i++] = "-G";
   1235 	else
   1236 		cmd[i++] = "-g";
   1237 
   1238 	if (loadimages)
   1239 		cmd[i++] = "-I";
   1240 	else
   1241 		cmd[i++] = "-i";
   1242 
   1243 	if (kioskmode)
   1244 		cmd[i++] = "-K";
   1245 	else
   1246 		cmd[i++] = "-k";
   1247 
   1248 	if (insecureresources)
   1249 		cmd[i++] = "-L";
   1250 	else
   1251 		cmd[i++] = "-l";
   1252 
   1253 	if (enablestyle)
   1254 		cmd[i++] = "-M";
   1255 	else
   1256 		cmd[i++] = "-m";
   1257 
   1258 	if (enableinspector)
   1259 		cmd[i++] = "-N";
   1260 	else
   1261 		cmd[i++] = "-n";
   1262 
   1263 	if (enableplugins)
   1264 		cmd[i++] = "-P";
   1265 	else
   1266 		cmd[i++] = "-p";
   1267 
   1268 	if (scriptfile != NULL) {
   1269 		cmd[i++] = "-r";
   1270 		cmd[i++] = scriptfile;
   1271 	}
   1272 
   1273 	if (enablescripts)
   1274 		cmd[i++] = "-S";
   1275 	else
   1276 		cmd[i++] = "-s";
   1277 
   1278 	if (strictssl)
   1279 		cmd[i++] = "-T";
   1280 	else
   1281 		cmd[i++] = "-t";
   1282 
   1283 	if (useragent != NULL) {
   1284 		cmd[i++] = "-u";
   1285 		cmd[i++] = useragent;
   1286 	}
   1287 
   1288 	if (privatebrowsing)
   1289 		cmd[i++] = "-W";
   1290 	else
   1291 		cmd[i++] = "-w";
   1292 
   1293 
   1294 	if (showxid)
   1295 		cmd[i++] = "-x";
   1296 
   1297 	if (zoomlevel != 1.0) {
   1298 		cmd[i++] = "-z";
   1299 		snprintf(ztmp, LENGTH(ztmp), "%.1f", zoomlevel);
   1300 		cmd[i++] = ztmp;
   1301 	}
   1302 
   1303 	cmd[i++] = "--";
   1304 	uri = arg->v? (char *)arg->v : c->linkhover;
   1305 	if (uri)
   1306 		cmd[i++] = uri;
   1307 	cmd[i++] = NULL;
   1308 	spawn(NULL, &a);
   1309 }
   1310 
   1311 gboolean
   1312 contextmenu(WebKitWebView *view, GtkWidget *menu, WebKitHitTestResult *target,
   1313             gboolean keyboard, Client *c)
   1314 {
   1315 	GList *items = gtk_container_get_children(GTK_CONTAINER(GTK_MENU(menu)));
   1316 
   1317 	for (GList *l = items; l; l = l->next)
   1318 		g_signal_connect(l->data, "activate", G_CALLBACK(menuactivate), c);
   1319 
   1320 	g_list_free(items);
   1321 	return FALSE;
   1322 }
   1323 
   1324 void
   1325 menuactivate(GtkMenuItem *item, Client *c)
   1326 {
   1327 	/*
   1328 	 * context-menu-action-2000 open link
   1329 	 * context-menu-action-1    open link in window
   1330 	 * context-menu-action-2    download linked file
   1331 	 * context-menu-action-3    copy link location
   1332 	 * context-menu-action-7    copy image address
   1333 	 * context-menu-action-13   reload
   1334 	 * context-menu-action-10   back
   1335 	 * context-menu-action-11   forward
   1336 	 * context-menu-action-12   stop
   1337 	 */
   1338 
   1339 	GtkAction *a = NULL;
   1340 	const char *name, *uri;
   1341 	GtkClipboard *prisel, *clpbrd;
   1342 
   1343 	a = gtk_activatable_get_related_action(GTK_ACTIVATABLE(item));
   1344 	if (a == NULL)
   1345 		return;
   1346 
   1347 	name = gtk_action_get_name(a);
   1348 	if (!g_strcmp0(name, "context-menu-action-3")) {
   1349 		prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
   1350 		gtk_clipboard_set_text(prisel, c->linkhover, -1);
   1351 	} else if (!g_strcmp0(name, "context-menu-action-7")) {
   1352 		prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
   1353 		clpbrd = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
   1354 		uri = gtk_clipboard_wait_for_text(clpbrd);
   1355 		if (uri)
   1356 			gtk_clipboard_set_text(prisel, uri, -1);
   1357 	}
   1358 }
   1359 
   1360 void
   1361 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
   1362 {
   1363 	Arg arg = {.v = text };
   1364 	if (text != NULL)
   1365 		loaduri((Client *) d, &arg);
   1366 }
   1367 
   1368 void
   1369 print(Client *c, const Arg *arg)
   1370 {
   1371 	webkit_web_frame_print(webkit_web_view_get_main_frame(c->view));
   1372 }
   1373 
   1374 GdkFilterReturn
   1375 processx(GdkXEvent *e, GdkEvent *event, gpointer d)
   1376 {
   1377 	Client *c = (Client *)d;
   1378 	WebKitWebSettings *settings;
   1379 	XPropertyEvent *ev;
   1380 	Arg arg;
   1381 
   1382 	if (((XEvent *)e)->type == PropertyNotify) {
   1383 		ev = &((XEvent *)e)->xproperty;
   1384 		if (ev->state == PropertyNewValue) {
   1385 			if (ev->atom == atoms[AtomFind]) {
   1386 				arg.b = TRUE;
   1387 				find(c, &arg);
   1388 				return GDK_FILTER_REMOVE;
   1389 			} else if (ev->atom == atoms[AtomGo]) {
   1390 				arg.v = getatom(c, AtomGo);
   1391 				loaduri(c, &arg);
   1392 				return GDK_FILTER_REMOVE;
   1393 			} else if (ev->atom == atoms[AtomUA]) {
   1394 				settings = webkit_web_view_get_settings(c->view);
   1395 				g_object_set(G_OBJECT(settings), "user-agent",
   1396 						getatom(c, AtomUA), NULL);
   1397 				return GDK_FILTER_REMOVE;
   1398 			}
   1399 		}
   1400 	}
   1401 	return GDK_FILTER_CONTINUE;
   1402 }
   1403 
   1404 void
   1405 progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c)
   1406 {
   1407 	c->progress = webkit_web_view_get_progress(c->view) * 100;
   1408 	updatetitle(c);
   1409 }
   1410 
   1411 void
   1412 linkopen(Client *c, const Arg *arg)
   1413 {
   1414 	newwindow(NULL, arg, 1);
   1415 }
   1416 
   1417 void
   1418 linkopenembed(Client *c, const Arg *arg)
   1419 {
   1420 	newwindow(NULL, arg, 0);
   1421 }
   1422 
   1423 void
   1424 reload(Client *c, const Arg *arg)
   1425 {
   1426 	gboolean nocache = *(gboolean *)arg;
   1427 	if (nocache)
   1428 		webkit_web_view_reload_bypass_cache(c->view);
   1429 	else
   1430 		webkit_web_view_reload(c->view);
   1431 }
   1432 
   1433 void
   1434 scroll_h(Client *c, const Arg *arg)
   1435 {
   1436 	scroll(gtk_scrolled_window_get_hadjustment(
   1437 	       GTK_SCROLLED_WINDOW(c->scroll)), arg);
   1438 }
   1439 
   1440 void
   1441 scroll_v(Client *c, const Arg *arg)
   1442 {
   1443 	scroll(gtk_scrolled_window_get_vadjustment(
   1444 	       GTK_SCROLLED_WINDOW(c->scroll)), arg);
   1445 }
   1446 
   1447 void
   1448 scroll(GtkAdjustment *a, const Arg *arg)
   1449 {
   1450 	gdouble v;
   1451 
   1452 	v = gtk_adjustment_get_value(a);
   1453 	switch (arg->i) {
   1454 	case +10000:
   1455 	case -10000:
   1456 		v += gtk_adjustment_get_page_increment(a) * (arg->i / 10000);
   1457 		break;
   1458 	case +20000:
   1459 	case -20000:
   1460 	default:
   1461 		v += gtk_adjustment_get_step_increment(a) * arg->i;
   1462 	}
   1463 
   1464 	v = MAX(v, 0.0);
   1465 	v = MIN(v, gtk_adjustment_get_upper(a) -
   1466 	        gtk_adjustment_get_page_size(a));
   1467 	gtk_adjustment_set_value(a, v);
   1468 }
   1469 
   1470 void
   1471 setatom(Client *c, int a, const char *v)
   1472 {
   1473 	XSync(dpy, False);
   1474 	XChangeProperty(dpy, GDK_WINDOW_XID(GTK_WIDGET(c->win)->window),
   1475 	                atoms[a], XA_STRING, 8, PropModeReplace,
   1476 	                (unsigned char *)v, strlen(v) + 1);
   1477 	XSync(dpy, False);
   1478 }
   1479 
   1480 void
   1481 setup(void)
   1482 {
   1483 	int i;
   1484 	char *styledirfile, *stylepath;
   1485 	SoupSession *s;
   1486 	GError *error = NULL;
   1487 
   1488 	/* clean up any zombies immediately */
   1489 	sigchld(0);
   1490 	if (signal(SIGHUP, sighup) == SIG_ERR)
   1491 		die("Can't install SIGHUP handler");
   1492 	gtk_init(NULL, NULL);
   1493 
   1494 	dpy = GDK_DISPLAY();
   1495 
   1496 	/* atoms */
   1497 	atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
   1498 	atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
   1499 	atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
   1500 	atoms[AtomUA] = XInternAtom(dpy, "_SURF_UA", False);
   1501 
   1502 	/* dirs and files */
   1503 	cookiefile = buildfile(cookiefile);
   1504 	scriptfile = buildfile(scriptfile);
   1505 	cachefolder = buildpath(cachefolder);
   1506 	dbfolder = buildpath(dbfolder);
   1507 	if (stylefile == NULL) {
   1508 		styledir = buildpath(styledir);
   1509 		for (i = 0; i < LENGTH(styles); i++) {
   1510 			if (regcomp(&(styles[i].re), styles[i].regex,
   1511 			    REG_EXTENDED)) {
   1512 				fprintf(stderr,
   1513 				        "Could not compile regex: %s\n",
   1514 				        styles[i].regex);
   1515 				styles[i].regex = NULL;
   1516 			}
   1517 			styledirfile    = g_strconcat(styledir, "/",
   1518 			                              styles[i].style, NULL);
   1519 			stylepath       = buildfile(styledirfile);
   1520 			styles[i].style = g_strconcat("file://", stylepath,
   1521 			                              NULL);
   1522 			g_free(styledirfile);
   1523 			g_free(stylepath);
   1524 		}
   1525 		g_free(styledir);
   1526 	} else {
   1527 		stylepath = buildfile(stylefile);
   1528 		stylefile = g_strconcat("file://", stylepath, NULL);
   1529 		g_free(stylepath);
   1530 	}
   1531 
   1532 	/* request handler */
   1533 	s = webkit_get_default_session();
   1534 
   1535 	/* cookie jar */
   1536 	soup_session_add_feature(s,
   1537 	                         SOUP_SESSION_FEATURE(cookiejar_new(cookiefile,
   1538 	                         FALSE, cookiepolicy_get())));
   1539 
   1540 	/* disk cache */
   1541 	if (enablediskcache) {
   1542 		diskcache = soup_cache_new(cachefolder,
   1543 		                           SOUP_CACHE_SINGLE_USER);
   1544 		soup_cache_set_max_size(diskcache, diskcachebytes);
   1545 		soup_cache_load(diskcache);
   1546 		soup_session_add_feature(s, SOUP_SESSION_FEATURE(diskcache));
   1547 	}
   1548 
   1549 	/* ssl */
   1550 	tlsdb = g_tls_file_database_new(cafile, &error);
   1551 	if (error) {
   1552 		g_warning("Error loading SSL database %s: %s", cafile,
   1553 		          error->message);
   1554 		g_error_free(error);
   1555 	}
   1556 	g_object_set(G_OBJECT(s), "tls-database", tlsdb, NULL);
   1557 	g_object_set(G_OBJECT(s), "ssl-strict", strictssl, NULL);
   1558 
   1559 	setup_proxy();
   1560 }
   1561 
   1562 void
   1563 setup_proxy(void)
   1564 {
   1565 	char *proxy, *new_proxy, *no_proxy, **new_no_proxy;
   1566 	GProxyResolver *pr;
   1567 	SoupSession *s;
   1568 
   1569 	/* request handler */
   1570 	s = webkit_get_default_session();
   1571 
   1572 	/* proxy */
   1573 	if ((proxy = getenv("http_proxy")) && strcmp(proxy, "")) {
   1574 		new_proxy = g_strrstr(proxy, "http://")
   1575 		            || g_strrstr(proxy, "https://")
   1576 		            || g_strrstr(proxy, "socks://")
   1577 		            || g_strrstr(proxy, "socks4://")
   1578 		            || g_strrstr(proxy, "socks4a://")
   1579 		            || g_strrstr(proxy, "socks5://")
   1580 		            ? g_strdup(proxy)
   1581 		            : g_strdup_printf("http://%s", proxy);
   1582 		new_no_proxy = ((no_proxy = getenv("no_proxy")) && strcmp(no_proxy, ""))
   1583 		               ? g_strsplit(no_proxy, ",", -1) : NULL;
   1584 		pr = g_simple_proxy_resolver_new(new_proxy, new_no_proxy);
   1585 		g_object_set(G_OBJECT(s), "proxy-resolver", pr, NULL);
   1586 		g_free(new_proxy);
   1587 		g_strfreev(new_no_proxy);
   1588 		usingproxy = 1;
   1589 	} else {
   1590 		usingproxy = 0;
   1591 	}
   1592 }
   1593 
   1594 void
   1595 sigchld(int unused)
   1596 {
   1597 	if (signal(SIGCHLD, sigchld) == SIG_ERR)
   1598 		die("Can't install SIGCHLD handler");
   1599 	while (0 < waitpid(-1, NULL, WNOHANG));
   1600 }
   1601 
   1602 void
   1603 sighup(int unused)
   1604 {
   1605 	Arg a = { .b = FALSE };
   1606 	Client *c;
   1607 
   1608 	for (c = clients; c; c = c->next)
   1609 		reload(c, &a);
   1610 }
   1611 
   1612 void
   1613 source(Client *c, const Arg *arg)
   1614 {
   1615 	Arg a = { .b = FALSE };
   1616 	gboolean s;
   1617 
   1618 	s = webkit_web_view_get_view_source_mode(c->view);
   1619 	webkit_web_view_set_view_source_mode(c->view, !s);
   1620 	reload(c, &a);
   1621 }
   1622 
   1623 void
   1624 spawn(Client *c, const Arg *arg)
   1625 {
   1626 	if (fork() == 0) {
   1627 		if (dpy)
   1628 			close(ConnectionNumber(dpy));
   1629 		setsid();
   1630 		execvp(((char **)arg->v)[0], (char **)arg->v);
   1631 		fprintf(stderr, "surf: execvp %s", ((char **)arg->v)[0]);
   1632 		perror(" failed");
   1633 		exit(0);
   1634 	}
   1635 }
   1636 
   1637 void
   1638 eval(Client *c, const Arg *arg)
   1639 {
   1640 	WebKitWebFrame *frame = webkit_web_view_get_main_frame(c->view);
   1641 	evalscript(webkit_web_frame_get_global_context(frame),
   1642 	           ((char **)arg->v)[0], "");
   1643 }
   1644 
   1645 void
   1646 stop(Client *c, const Arg *arg)
   1647 {
   1648 	webkit_web_view_stop_loading(c->view);
   1649 }
   1650 
   1651 void
   1652 titlechange(WebKitWebView *view, GParamSpec *pspec, Client *c)
   1653 {
   1654 	const gchar *t = webkit_web_view_get_title(view);
   1655 
   1656 	if (t) {
   1657 		c->title = copystr(&c->title, t);
   1658 		updatetitle(c);
   1659 	}
   1660 }
   1661 
   1662 void
   1663 titlechangeleave(void *a, void *b, Client *c)
   1664 {
   1665 	c->linkhover = NULL;
   1666 	updatetitle(c);
   1667 }
   1668 
   1669 void
   1670 togglehelper(Client *c, const Arg *arg, int doreload)
   1671 {
   1672 	WebKitWebSettings *settings;
   1673 	char *name = (char *)arg->v;
   1674 	gboolean value;
   1675 	Arg a = { .b = FALSE };
   1676 
   1677 	settings = webkit_web_view_get_settings(c->view);
   1678 	g_object_get(G_OBJECT(settings), name, &value, NULL);
   1679 	g_object_set(G_OBJECT(settings), name, !value, NULL);
   1680 
   1681 	if (doreload)
   1682 		reload(c, &a);
   1683 }
   1684 
   1685 void
   1686 toggle(Client *c, const Arg *arg)
   1687 {
   1688 	togglehelper(c, arg, 1);
   1689 }
   1690 
   1691 void
   1692 togglecookiepolicy(Client *c, const Arg *arg)
   1693 {
   1694 	SoupCookieJar *jar;
   1695 	SoupCookieJarAcceptPolicy policy;
   1696 
   1697 	jar = SOUP_COOKIE_JAR(soup_session_get_feature(
   1698 	                      webkit_get_default_session(),
   1699 	                      SOUP_TYPE_COOKIE_JAR));
   1700 	g_object_get(G_OBJECT(jar), "accept-policy", &policy, NULL);
   1701 
   1702 	policysel++;
   1703 	if (policysel >= strlen(cookiepolicies))
   1704 		policysel = 0;
   1705 
   1706 	g_object_set(G_OBJECT(jar), "accept-policy", cookiepolicy_get(), NULL);
   1707 
   1708 	updatetitle(c);
   1709 	/* Do not reload. */
   1710 }
   1711 
   1712 void
   1713 toggleinsecurecontent(Client *c, const Arg *arg)
   1714 {
   1715 	Arg a;
   1716 
   1717 	a.v = "enable-running-of-insecure-content";
   1718 	togglehelper(c, &a, 0);
   1719 	a.v = "enable-display-of-insecure-content";
   1720 	togglehelper(c, &a, 1);
   1721 }
   1722 
   1723 void
   1724 togglegeolocation(Client *c, const Arg *arg)
   1725 {
   1726 	Arg a = { .b = FALSE };
   1727 
   1728 	allowgeolocation ^= 1;
   1729 	reload(c, &a);
   1730 }
   1731 
   1732 void
   1733 togglesoup(Client *c, const Arg *arg)
   1734 {
   1735 	SoupSession *s;
   1736 	char *name = (char *)arg->v;
   1737 	gboolean value;
   1738 	Arg a = { .b = FALSE };
   1739 
   1740 	/* request handler */
   1741 	s = webkit_get_default_session();
   1742 	g_object_get(G_OBJECT(s), name, &value, NULL);
   1743 	g_object_set(G_OBJECT(s), name, !value, NULL);
   1744 
   1745 	reload(c, &a);
   1746 }
   1747 
   1748 void
   1749 twitch(Client *c, const Arg *arg)
   1750 {
   1751 	GtkAdjustment *a;
   1752 	gdouble v;
   1753 
   1754 	a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(
   1755 	                                        c->scroll));
   1756 
   1757 	v = gtk_adjustment_get_value(a);
   1758 
   1759 	v += arg->i;
   1760 
   1761 	v = MAX(v, 0.0);
   1762 	v = MIN(v, gtk_adjustment_get_upper(a) -
   1763 	        gtk_adjustment_get_page_size(a));
   1764 	gtk_adjustment_set_value(a, v);
   1765 }
   1766 
   1767 void
   1768 toggleproxy(Client *c, const Arg *arg)
   1769 {
   1770 	SoupSession *s;
   1771 	GProxyResolver *pr;
   1772 
   1773 	/* request handler */
   1774 	s = webkit_get_default_session();
   1775 
   1776 	if (usingproxy) {
   1777 		pr = NULL;
   1778 		g_object_get(G_OBJECT(s), "proxy-resolver", &pr, NULL);
   1779 		if (pr != NULL)
   1780 			g_object_unref(pr);
   1781 
   1782 		g_object_set(G_OBJECT(s), "proxy-resolver", NULL, NULL);
   1783 		usingproxy = 0;
   1784 	} else {
   1785 		setup_proxy();
   1786 	}
   1787 
   1788 	updatetitle(c);
   1789 	/* Do not reload. */
   1790 }
   1791 
   1792 void
   1793 togglescrollbars(Client *c, const Arg *arg)
   1794 {
   1795 	GtkPolicyType vspolicy;
   1796 	Arg a;
   1797 
   1798 	gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(c->scroll), NULL,
   1799 	                               &vspolicy);
   1800 
   1801 	if (vspolicy == GTK_POLICY_AUTOMATIC) {
   1802 		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
   1803 		                               GTK_POLICY_NEVER,
   1804 		                               GTK_POLICY_NEVER);
   1805 	} else {
   1806 		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
   1807 		                               GTK_POLICY_AUTOMATIC,
   1808 		                               GTK_POLICY_AUTOMATIC);
   1809 		a.i = +1;
   1810 		twitch(c, &a);
   1811 		a.i = -1;
   1812 		twitch(c, &a);
   1813 	}
   1814 }
   1815 
   1816 void
   1817 togglestyle(Client *c, const Arg *arg)
   1818 {
   1819 	enablestyle = !enablestyle;
   1820 	setstyle(c, enablestyle ? getstyle(geturi(c)) : "");
   1821 	updatetitle(c);
   1822 }
   1823 
   1824 void
   1825 gettogglestat(Client *c)
   1826 {
   1827 	gboolean value;
   1828 	int p = 0;
   1829 	WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
   1830 	SoupSession *s = webkit_get_default_session();
   1831 
   1832 	togglestat[p++] = cookiepolicy_set(cookiepolicy_get());
   1833 
   1834 	g_object_get(G_OBJECT(settings), "enable-caret-browsing", &value,
   1835 	             NULL);
   1836 	togglestat[p++] = value? 'C': 'c';
   1837 
   1838 	togglestat[p++] = enablediskcache? 'D': 'd';
   1839 
   1840 	togglestat[p++] = allowgeolocation? 'G': 'g';
   1841 
   1842 	g_object_get(G_OBJECT(settings), "auto-load-images", &value, NULL);
   1843 	togglestat[p++] = value? 'I': 'i';
   1844 
   1845 	g_object_get(G_OBJECT(settings),
   1846 			"enable-display-of-insecure-content", &value, NULL);
   1847 	togglestat[p++] = value? 'L': 'l';
   1848 
   1849 	togglestat[p++] = enablestyle ? 'M': 'm';
   1850 
   1851 	g_object_get(G_OBJECT(settings), "enable-scripts", &value, NULL);
   1852 	togglestat[p++] = value? 'S': 's';
   1853 
   1854 	g_object_get(G_OBJECT(s), "ssl-strict", &value, NULL);
   1855 	togglestat[p++] = value? 'T': 't';
   1856 
   1857 	g_object_get(G_OBJECT(settings), "enable-plugins", &value, NULL);
   1858 	togglestat[p++] = value? 'V': 'v';
   1859 
   1860 	g_object_get(G_OBJECT(settings), "enable-private-browsing", &value,
   1861 			NULL);
   1862 	togglestat[p++] = value? 'W': 'w';
   1863 
   1864 	togglestat[p] = '\0';
   1865 }
   1866 
   1867 void
   1868 getpagestat(Client *c)
   1869 {
   1870 	const char *uri = geturi(c);
   1871 
   1872 	if (strstr(uri, "https://") == uri)
   1873 		pagestat[0] = c->sslfailed ? 'U' : 'T';
   1874 	else
   1875 		pagestat[0] = '-';
   1876 
   1877 	pagestat[1] = usingproxy ? 'P' : '-';
   1878 	pagestat[2] = '\0';
   1879 }
   1880 
   1881 void
   1882 updatetitle(Client *c)
   1883 {
   1884 	char *t;
   1885 
   1886 	if (showindicators) {
   1887 		gettogglestat(c);
   1888 		getpagestat(c);
   1889 
   1890 		if (c->linkhover) {
   1891 			t = g_strdup_printf("%s:%s | %s", togglestat, pagestat,
   1892 			                    c->linkhover);
   1893 		} else if (c->progress != 100) {
   1894 			t = g_strdup_printf("[%i%%] %s:%s | %s", c->progress,
   1895 			                    togglestat, pagestat,
   1896 			                    c->title == NULL ? "" : c->title);
   1897 		} else {
   1898 			t = g_strdup_printf("%s:%s | %s", togglestat, pagestat,
   1899 			                    c->title == NULL ? "" : c->title);
   1900 		}
   1901 
   1902 		gtk_window_set_title(GTK_WINDOW(c->win), t);
   1903 		g_free(t);
   1904 	} else {
   1905 		gtk_window_set_title(GTK_WINDOW(c->win), (c->title == NULL) ?
   1906 		                     "" : c->title);
   1907 	}
   1908 }
   1909 
   1910 void
   1911 updatewinid(Client *c)
   1912 {
   1913 	snprintf(winid, LENGTH(winid), "%u",
   1914 	         (int)GDK_WINDOW_XID(GTK_WIDGET(c->win)->window));
   1915 }
   1916 
   1917 void
   1918 usage(void)
   1919 {
   1920 	die("usage: %s [-bBdDfFgGiIkKlLmMnNpPsStTvwWx] [-a cookiepolicies ] "
   1921 	    "[-c cookiefile] [-e xid] [-r scriptfile] [-y stylefile] "
   1922 	    "[-u useragent] [-z zoomlevel] [uri]\n", basename(argv0));
   1923 }
   1924 
   1925 void
   1926 windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame, JSContextRef js,
   1927                     JSObjectRef win, Client *c)
   1928 {
   1929 	runscript(frame);
   1930 }
   1931 
   1932 void
   1933 zoom(Client *c, const Arg *arg)
   1934 {
   1935 	c->zoomed = TRUE;
   1936 	if (arg->i < 0) {
   1937 		/* zoom out */
   1938 		webkit_web_view_zoom_out(c->view);
   1939 	} else if (arg->i > 0) {
   1940 		/* zoom in */
   1941 		webkit_web_view_zoom_in(c->view);
   1942 	} else {
   1943 		/* reset */
   1944 		c->zoomed = FALSE;
   1945 		webkit_web_view_set_zoom_level(c->view, 1.0);
   1946 	}
   1947 }
   1948 
   1949 int
   1950 main(int argc, char *argv[])
   1951 {
   1952 	Arg arg;
   1953 	Client *c;
   1954 
   1955 	memset(&arg, 0, sizeof(arg));
   1956 
   1957 	/* command line args */
   1958 	ARGBEGIN {
   1959 	case 'a':
   1960 		cookiepolicies = EARGF(usage());
   1961 		break;
   1962 	case 'b':
   1963 		enablescrollbars = 0;
   1964 		break;
   1965 	case 'B':
   1966 		enablescrollbars = 1;
   1967 		break;
   1968 	case 'c':
   1969 		cookiefile = EARGF(usage());
   1970 		break;
   1971 	case 'd':
   1972 		enablediskcache = 0;
   1973 		break;
   1974 	case 'D':
   1975 		enablediskcache = 1;
   1976 		break;
   1977 	case 'e':
   1978 		embed = strtol(EARGF(usage()), NULL, 0);
   1979 		break;
   1980 	case 'f':
   1981 		runinfullscreen = 0;
   1982 		break;
   1983 	case 'F':
   1984 		runinfullscreen = 1;
   1985 		break;
   1986 	case 'g':
   1987 		allowgeolocation = 0;
   1988 		break;
   1989 	case 'G':
   1990 		allowgeolocation = 1;
   1991 		break;
   1992 	case 'i':
   1993 		loadimages = 0;
   1994 		break;
   1995 	case 'I':
   1996 		loadimages = 1;
   1997 		break;
   1998 	case 'k':
   1999 		kioskmode = 0;
   2000 		break;
   2001 	case 'K':
   2002 		kioskmode = 1;
   2003 		break;
   2004 	case 'L':
   2005 		insecureresources = 1;
   2006 		break;
   2007 	case 'l':
   2008 		insecureresources = 0;
   2009 		break;
   2010 	case 'm':
   2011 		enablestyle = 0;
   2012 		break;
   2013 	case 'M':
   2014 		enablestyle = 1;
   2015 		break;
   2016 	case 'n':
   2017 		enableinspector = 0;
   2018 		break;
   2019 	case 'N':
   2020 		enableinspector = 1;
   2021 		break;
   2022 	case 'p':
   2023 		enableplugins = 0;
   2024 		break;
   2025 	case 'P':
   2026 		enableplugins = 1;
   2027 		break;
   2028 	case 'r':
   2029 		scriptfile = EARGF(usage());
   2030 		break;
   2031 	case 's':
   2032 		enablescripts = 0;
   2033 		break;
   2034 	case 'S':
   2035 		enablescripts = 1;
   2036 		break;
   2037 	case 't':
   2038 		strictssl = 0;
   2039 		break;
   2040 	case 'T':
   2041 		strictssl = 1;
   2042 		break;
   2043 	case 'y':
   2044 		stylefile = EARGF(usage());
   2045 		break;
   2046 	case 'u':
   2047 		useragent = EARGF(usage());
   2048 		break;
   2049 	case 'v':
   2050 		die("surf-"VERSION", ©2009-2016 surf engineers, "
   2051 		    "see LICENSE for details\n");
   2052 	case 'w':
   2053 		privatebrowsing = 0;
   2054 		break;
   2055 	case 'W':
   2056 		privatebrowsing = 1;
   2057 		break;
   2058 	case 'x':
   2059 		showxid = TRUE;
   2060 		break;
   2061 	case 'z':
   2062 		zoomlevel = strtof(EARGF(usage()), NULL);
   2063 		break;
   2064 	default:
   2065 		usage();
   2066 	} ARGEND;
   2067 	if (argc > 0)
   2068 		arg.v = argv[0];
   2069 
   2070 	setup();
   2071 	c = newclient();
   2072 	if (arg.v)
   2073 		loaduri(clients, &arg);
   2074 	else
   2075 		updatetitle(c);
   2076 
   2077 	gtk_main();
   2078 	cleanup();
   2079 
   2080 	return EXIT_SUCCESS;
   2081 }
   2082