43b15316a547a8fd80dca5d6757bef78ff22455a
[surf.git] / surf.c
1 /* See LICENSE file for copyright and license details.
2  *
3  * To understand surf, start reading main().
4  */
5 #include <sys/file.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8 #include <libgen.h>
9 #include <limits.h>
10 #include <pwd.h>
11 #include <regex.h>
12 #include <signal.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18
19 #include <gdk/gdk.h>
20 #include <gdk/gdkkeysyms.h>
21 #include <gdk/gdkx.h>
22 #include <glib/gstdio.h>
23 #include <gtk/gtk.h>
24 #include <gtk/gtkx.h>
25 #include <JavaScriptCore/JavaScript.h>
26 #include <webkit2/webkit2.h>
27 #include <X11/X.h>
28 #include <X11/Xatom.h>
29
30 #include "arg.h"
31
32 #define LENGTH(x)               (sizeof(x) / sizeof(x[0]))
33 #define CLEANMASK(mask)         (mask & (MODKEY|GDK_SHIFT_MASK))
34 #define SETB(p, s)              [p] = (Parameter){ { .b = s }, }
35 #define SETI(p, s)              [p] = (Parameter){ { .i = s }, }
36 #define SETV(p, s)              [p] = (Parameter){ { .v = s }, }
37 #define SETF(p, s)              [p] = (Parameter){ { .f = s }, }
38 #define FSETB(p, s)             [p] = (Parameter){ { .b = s }, 1 }
39 #define FSETI(p, s)             [p] = (Parameter){ { .i = s }, 1 }
40 #define FSETV(p, s)             [p] = (Parameter){ { .v = s }, 1 }
41 #define FSETF(p, s)             [p] = (Parameter){ { .f = s }, 1 }
42
43 enum { AtomFind, AtomGo, AtomUri, AtomLast };
44
45 typedef enum {
46         CaretBrowsing,
47         CookiePolicies,
48         DiskCache,
49         DNSPrefetch,
50         FontSize,
51         FrameFlattening,
52         Geolocation,
53         HideBackground,
54         Inspector,
55         JavaScript,
56         KioskMode,
57         LoadImages,
58         Plugins,
59         PreferredLanguages,
60         RunInFullscreen,
61         ScrollBars,
62         ShowIndicators,
63         SpellChecking,
64         SpellLanguages,
65         StrictSSL,
66         Style,
67         ZoomLevel,
68         ParameterLast,
69 } ParamName;
70
71 enum {
72         OnDoc   = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
73         OnLink  = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
74         OnImg   = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
75         OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
76         OnEdit  = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
77         OnBar   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR,
78         OnSel   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
79         OnAny   = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | OnSel,
80 };
81
82 typedef union {
83         int b;
84         int i;
85         float f;
86         const void *v;
87 } Arg;
88
89 typedef struct {
90         Arg val;
91         int force;
92 } Parameter;
93
94 typedef struct Client {
95         GtkWidget *win;
96         WebKitWebView *view;
97         WebKitWebInspector *inspector;
98         WebKitFindController *finder;
99         WebKitHitTestResult *mousepos;
100         GTlsCertificateFlags tlsflags;
101         Window xid;
102         int progress, fullscreen;
103         const char *title, *overtitle, *targeturi;
104         const char *needle;
105         struct Client *next;
106 } Client;
107
108 typedef struct {
109         guint mod;
110         guint keyval;
111         void (*func)(Client *c, const Arg *a);
112         const Arg arg;
113 } Key;
114
115 typedef struct {
116         unsigned int target;
117         unsigned int mask;
118         guint button;
119         void (*func)(Client *c, const Arg *a, WebKitHitTestResult *h);
120         const Arg arg;
121         unsigned int stopevent;
122 } Button;
123
124 typedef struct {
125         const char *uri;
126         Parameter config[ParameterLast];
127         regex_t re;
128 } UriParameters;
129
130 typedef struct {
131         char *regex;
132         char *style;
133         regex_t re;
134 } SiteStyle;
135
136 /* Surf */
137 static void usage(void);
138 static void die(const char *errstr, ...);
139 static void setup(void);
140 static void sigchld(int unused);
141 static char *buildfile(const char *path);
142 static char *buildpath(const char *path);
143 static Client *newclient(Client *c);
144 static void loaduri(Client *c, const Arg *a);
145 static const char *geturi(Client *c);
146 static void setatom(Client *c, int a, const char *v);
147 static const char *getatom(Client *c, int a);
148 static void updatetitle(Client *c);
149 static void gettogglestats(Client *c);
150 static void getpagestats(Client *c);
151 static WebKitCookieAcceptPolicy cookiepolicy_get(void);
152 static char cookiepolicy_set(const WebKitCookieAcceptPolicy p);
153 static const char *getstyle(const char *uri);
154 static void setstyle(Client *c, const char *stylefile);
155 static void runscript(Client *c);
156 static void evalscript(Client *c, const char *jsstr, ...);
157 static void updatewinid(Client *c);
158 static void handleplumb(Client *c, const char *uri);
159 static void newwindow(Client *c, const Arg *a, int noembed);
160 static void spawn(Client *c, const Arg *a);
161 static void destroyclient(Client *c);
162 static void cleanup(void);
163
164 /* GTK/WebKit */
165 static GdkDevice *getkbdevice(void);
166 static WebKitWebView *newview(Client *c, WebKitWebView *rv);
167 static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a,
168                              Client *c);
169 static gboolean buttonreleased(GtkWidget *w, GdkEvent *e, Client *c);
170 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
171                                 gpointer d);
172 static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c);
173 static void showview(WebKitWebView *v, Client *c);
174 static GtkWidget *createwindow(Client *c);
175 static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c);
176 static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c);
177 static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c);
178 static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
179                                guint modifiers, Client *c);
180 static gboolean permissionrequested(WebKitWebView *v,
181                                     WebKitPermissionRequest *r, Client *c);
182 static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
183                              WebKitPolicyDecisionType dt, Client *c);
184 static void decidenavigation(WebKitPolicyDecision *d, Client *c);
185 static void decidenewwindow(WebKitPolicyDecision *d, Client *c);
186 static void decideresource(WebKitPolicyDecision *d, Client *c);
187 static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d,
188                             Client *c);
189 static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c);
190 static void download(Client *c, WebKitURIResponse *r);
191 static void closeview(WebKitWebView *v, Client *c);
192 static void destroywin(GtkWidget* w, Client *c);
193
194 /* Hotkeys */
195 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
196 static void reload(Client *c, const Arg *a);
197 static void print(Client *c, const Arg *a);
198 static void clipboard(Client *c, const Arg *a);
199 static void zoom(Client *c, const Arg *a);
200 static void scroll(Client *c, const Arg *a);
201 static void navigate(Client *c, const Arg *a);
202 static void stop(Client *c, const Arg *a);
203 static void toggle(Client *c, const Arg *a);
204 static void togglefullscreen(Client *c, const Arg *a);
205 static void togglecookiepolicy(Client *c, const Arg *a);
206 static void togglestyle(Client *c, const Arg *a);
207 static void toggleinspector(Client *c, const Arg *a);
208 static void find(Client *c, const Arg *a);
209
210 /* Buttons */
211 static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h);
212 static void clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h);
213 static void clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h);
214
215 static char winid[64];
216 static char togglestats[10];
217 static char pagestats[2];
218 static Atom atoms[AtomLast];
219 static Window embed;
220 static int showxid;
221 static int cookiepolicy;
222 static Display *dpy;
223 static Client *clients;
224 static GdkDevice *gdkkb;
225 static char *stylefile;
226 static const char *useragent;
227 char *argv0;
228
229 /* configuration, allows nested code to access above variables */
230 #include "config.h"
231
232 void
233 usage(void)
234 {
235         die("usage: %s [-bBdDfFgGiIkKmMnNpPsSvx] [-a cookiepolicies ] "
236             "[-c cookiefile] [-e xid] [-r scriptfile] [-t stylefile] "
237             "[-u useragent] [-z zoomlevel] [uri]\n", basename(argv0));
238 }
239
240 void
241 die(const char *errstr, ...)
242 {
243         va_list ap;
244
245         va_start(ap, errstr);
246         vfprintf(stderr, errstr, ap);
247         va_end(ap);
248         exit(1);
249 }
250
251 void
252 setup(void)
253 {
254         int i;
255
256         /* clean up any zombies immediately */
257         sigchld(0);
258         gtk_init(NULL, NULL);
259
260         dpy = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
261
262         /* atoms */
263         atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
264         atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
265         atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
266
267         /* dirs and files */
268         cookiefile = buildfile(cookiefile);
269         scriptfile = buildfile(scriptfile);
270         cachedir   = buildpath(cachedir);
271
272         gdkkb = getkbdevice();
273
274         if (!stylefile) {
275                 styledir = buildpath(styledir);
276                 for (i = 0; i < LENGTH(styles); ++i) {
277                         if (regcomp(&(styles[i].re), styles[i].regex,
278                             REG_EXTENDED)) {
279                                 fprintf(stderr,
280                                         "Could not compile regex: %s\n",
281                                         styles[i].regex);
282                                 styles[i].regex = NULL;
283                         }
284                         styles[i].style = g_strconcat(styledir, "/",
285                                                       styles[i].style, NULL);
286                 }
287                 g_free(styledir);
288         } else {
289                 stylefile = buildfile(stylefile);
290         }
291 }
292
293 void
294 sigchld(int unused)
295 {
296         if (signal(SIGCHLD, sigchld) == SIG_ERR)
297                 die("Can't install SIGCHLD handler");
298         while (waitpid(-1, NULL, WNOHANG) > 0)
299                 ;
300 }
301
302 char *
303 buildfile(const char *path)
304 {
305         char *dname, *bname, *bpath, *fpath;
306         FILE *f;
307
308         dname = g_path_get_dirname(path);
309         bname = g_path_get_basename(path);
310
311         bpath = buildpath(dname);
312         g_free(dname);
313
314         fpath = g_build_filename(bpath, bname, NULL);
315         g_free(bpath);
316         g_free(bname);
317
318         if (!(f = fopen(fpath, "a")))
319                 die("Could not open file: %s\n", fpath);
320
321         g_chmod(fpath, 0600); /* always */
322         fclose(f);
323
324         return fpath;
325 }
326
327 char *
328 buildpath(const char *path)
329 {
330         struct passwd *pw;
331         char *apath, *name, *p, *fpath;
332
333         if (path[0] == '~') {
334                 if (path[1] == '/' || path[1] == '\0') {
335                         p = (char *)&path[1];
336                         pw = getpwuid(getuid());
337                 } else {
338                         if ((p = strchr(path, '/')))
339                                 name = g_strndup(&path[1], --p - path);
340                         else
341                                 name = g_strdup(&path[1]);
342
343                         if (!(pw = getpwnam(name))) {
344                                 die("Can't get user %s home directory: %s.\n",
345                                     name, path);
346                         }
347                         g_free(name);
348                 }
349                 apath = g_build_filename(pw->pw_dir, p, NULL);
350         } else {
351                 apath = g_strdup(path);
352         }
353
354         /* creating directory */
355         if (g_mkdir_with_parents(apath, 0700) < 0)
356                 die("Could not access directory: %s\n", apath);
357
358         fpath = realpath(apath, NULL);
359         g_free(apath);
360
361         return fpath;
362 }
363
364 Client *
365 newclient(Client *rc)
366 {
367         Client *c;
368
369         if (!(c = calloc(1, sizeof(Client))))
370                 die("Cannot malloc!\n");
371
372         c->next = clients;
373         clients = c;
374
375         c->progress = 100;
376         c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
377         c->view = newview(c, rc ? rc->view : NULL);
378
379         return c;
380 }
381
382 void
383 loaduri(Client *c, const Arg *a)
384 {
385         struct stat st;
386         char *url, *path;
387         const char *uri = a->v;
388
389         if (g_strcmp0(uri, "") == 0)
390                 return;
391
392         if (g_strrstr(uri, "://") || g_str_has_prefix(uri, "about:")) {
393                 url = g_strdup(uri);
394         } else if (!stat(uri, &st) && (path = realpath(uri, NULL))) {
395                 url = g_strdup_printf("file://%s", path);
396                 free(path);
397         } else {
398                 url = g_strdup_printf("http://%s", uri);
399         }
400
401         setatom(c, AtomUri, url);
402
403         if (strcmp(url, geturi(c)) == 0) {
404                 reload(c, a);
405         } else {
406                 webkit_web_view_load_uri(c->view, url);
407                 updatetitle(c);
408         }
409
410         g_free(url);
411 }
412
413 const char *
414 geturi(Client *c)
415 {
416         const char *uri;
417
418         if (!(uri = webkit_web_view_get_uri(c->view)))
419                 uri = "about:blank";
420         return uri;
421 }
422
423 void
424 setatom(Client *c, int a, const char *v)
425 {
426         XSync(dpy, False);
427         XChangeProperty(dpy, c->xid,
428                         atoms[a], XA_STRING, 8, PropModeReplace,
429                         (unsigned char *)v, strlen(v) + 1);
430 }
431
432 const char *
433 getatom(Client *c, int a)
434 {
435         static char buf[BUFSIZ];
436         Atom adummy;
437         int idummy;
438         unsigned long ldummy;
439         unsigned char *p = NULL;
440
441         XGetWindowProperty(dpy, c->xid, atoms[a], 0L, BUFSIZ, False, XA_STRING,
442                            &adummy, &idummy, &ldummy, &ldummy, &p);
443         if (p)
444                 strncpy(buf, (char *)p, LENGTH(buf) - 1);
445         else
446                 buf[0] = '\0';
447         XFree(p);
448
449         return buf;
450 }
451
452 void
453 updatetitle(Client *c)
454 {
455         char *title;
456         const char *name = c->overtitle ? c->overtitle :
457                            c->title ? c->title : "";
458
459         if (showindicators) {
460                 gettogglestats(c);
461                 getpagestats(c);
462
463                 if (c->progress != 100)
464                         title = g_strdup_printf("[%i%%] %s:%s | %s",
465                                 c->progress, togglestats, pagestats, name);
466                 else
467                         title = g_strdup_printf("%s:%s | %s",
468                                 togglestats, pagestats, name);
469
470                 gtk_window_set_title(GTK_WINDOW(c->win), title);
471                 g_free(title);
472         } else {
473                 gtk_window_set_title(GTK_WINDOW(c->win), name);
474         }
475 }
476
477 void
478 gettogglestats(Client *c)
479 {
480         togglestats[0] = cookiepolicy_set(cookiepolicy_get());
481         togglestats[1] = enablecaretbrowsing ?   'C' : 'c';
482         togglestats[2] = allowgeolocation ?      'G' : 'g';
483         togglestats[3] = enablecache ?           'D' : 'd';
484         togglestats[4] = loadimages ?            'I' : 'i';
485         togglestats[5] = enablescripts ?         'S' : 's';
486         togglestats[6] = enableplugins ?         'V' : 'v';
487         togglestats[7] = enablestyle ?           'M' : 'm';
488         togglestats[8] = enableframeflattening ? 'F' : 'f';
489         togglestats[9] = '\0';
490 }
491
492 void
493 getpagestats(Client *c)
494 {
495         pagestats[0] = c->tlsflags > G_TLS_CERTIFICATE_VALIDATE_ALL ? '-' :
496                        c->tlsflags > 0 ? 'U' : 'T';
497         pagestats[1] = '\0';
498 }
499
500 WebKitCookieAcceptPolicy
501 cookiepolicy_get(void)
502 {
503         switch (cookiepolicies[cookiepolicy]) {
504         case 'a':
505                 return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
506         case '@':
507                 return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
508         default: /* fallthrough */
509         case 'A':
510                 return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
511         }
512
513 }
514
515 char
516 cookiepolicy_set(const WebKitCookieAcceptPolicy p)
517 {
518         switch (p) {
519         case WEBKIT_COOKIE_POLICY_ACCEPT_NEVER:
520                 return 'a';
521         case WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY:
522                 return '@';
523         default: /* fallthrough */
524         case WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS:
525                 return 'A';
526         }
527
528 }
529
530 const char *
531 getstyle(const char *uri)
532 {
533         int i;
534
535         if (stylefile)
536                 return stylefile;
537
538         for (i = 0; i < LENGTH(styles); ++i) {
539                 if (styles[i].regex &&
540                     !regexec(&(styles[i].re), uri, 0, NULL, 0))
541                         return styles[i].style;
542         }
543
544         return "";
545 }
546
547 void
548 setstyle(Client *c, const char *stylefile)
549 {
550         gchar *style;
551
552         if (!g_file_get_contents(stylefile, &style, NULL, NULL)) {
553                 fprintf(stderr, "Could not read style file: %s\n", stylefile);
554                 return;
555         }
556
557         webkit_user_content_manager_add_style_sheet(
558             webkit_web_view_get_user_content_manager(c->view),
559             webkit_user_style_sheet_new(style,
560             WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
561             WEBKIT_USER_STYLE_LEVEL_USER,
562             NULL, NULL));
563
564         g_free(style);
565 }
566
567 void
568 runscript(Client *c)
569 {
570         gchar *script;
571         gsize l;
572
573         if (g_file_get_contents(scriptfile, &script, &l, NULL) && l)
574                 evalscript(c, script);
575         g_free(script);
576 }
577
578 void
579 evalscript(Client *c, const char *jsstr, ...)
580 {
581         va_list ap;
582         gchar *script;
583
584         va_start(ap, jsstr);
585         script = g_strdup_vprintf(jsstr, ap);
586         va_end(ap);
587
588         webkit_web_view_run_javascript(c->view, script, NULL, NULL, NULL);
589         g_free(script);
590 }
591
592 void
593 updatewinid(Client *c)
594 {
595         snprintf(winid, LENGTH(winid), "%lu", c->xid);
596 }
597
598 void
599 handleplumb(Client *c, const char *uri)
600 {
601         Arg a = (Arg)PLUMB(uri);
602         spawn(c, &a);
603 }
604
605 void
606 newwindow(Client *c, const Arg *a, int noembed)
607 {
608         int i = 0;
609         char tmp[64];
610         const char *cmd[26], *uri;
611         const Arg arg = { .v = cmd };
612
613         cmd[i++] = argv0;
614         cmd[i++] = "-a";
615         cmd[i++] = cookiepolicies;
616         cmd[i++] = enablescrollbars ? "-B" : "-b";
617         if (cookiefile && g_strcmp0(cookiefile, "")) {
618                 cmd[i++] = "-c";
619                 cmd[i++] = cookiefile;
620         }
621         cmd[i++] = enablecache ? "-D" : "-d";
622         if (embed && !noembed) {
623                 cmd[i++] = "-e";
624                 snprintf(tmp, LENGTH(tmp), "%lu", embed);
625                 cmd[i++] = tmp;
626         }
627         cmd[i++] = runinfullscreen ? "-F" : "-f";
628         cmd[i++] = allowgeolocation ? "-G" : "-g";
629         cmd[i++] = loadimages ? "-I" : "-i";
630         cmd[i++] = kioskmode ? "-K" : "-k";
631         cmd[i++] = enablestyle ? "-M" : "-m";
632         cmd[i++] = enableinspector ? "-N" : "-n";
633         cmd[i++] = enableplugins ? "-P" : "-p";
634         if (scriptfile && g_strcmp0(scriptfile, "")) {
635                 cmd[i++] = "-r";
636                 cmd[i++] = scriptfile;
637         }
638         cmd[i++] = enablescripts ? "-S" : "-s";
639         if (stylefile && g_strcmp0(stylefile, "")) {
640                 cmd[i++] = "-t";
641                 cmd[i++] = stylefile;
642         }
643         if (fulluseragent && g_strcmp0(fulluseragent, "")) {
644                 cmd[i++] = "-u";
645                 cmd[i++] = fulluseragent;
646         }
647         if (showxid)
648                 cmd[i++] = "-x";
649         /* do not keep zoom level */
650         cmd[i++] = "--";
651         if ((uri = a->v))
652                 cmd[i++] = uri;
653         cmd[i] = NULL;
654
655         spawn(c, &arg);
656 }
657
658 void
659 spawn(Client *c, const Arg *a)
660 {
661         if (fork() == 0) {
662                 if (dpy)
663                         close(ConnectionNumber(dpy));
664                 setsid();
665                 execvp(((char **)a->v)[0], (char **)a->v);
666                 fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[0]);
667                 perror(" failed");
668                 exit(1);
669         }
670 }
671
672 void
673 destroyclient(Client *c)
674 {
675         Client *p;
676
677         webkit_web_view_stop_loading(c->view);
678         /* Not needed, has already been called
679         gtk_widget_destroy(c->win);
680          */
681
682         for (p = clients; p && p->next != c; p = p->next)
683                 ;
684         if (p)
685                 p->next = c->next;
686         else
687                 clients = c->next;
688         free(c);
689 }
690
691 void
692 cleanup(void)
693 {
694         while (clients)
695                 destroyclient(clients);
696         g_free(cookiefile);
697         g_free(scriptfile);
698         g_free(stylefile);
699         g_free(cachedir);
700 }
701
702 static GdkDevice *
703 getkbdevice(void)
704 {
705         GList *l, *gdl = gdk_device_manager_list_devices(
706                    gdk_display_get_device_manager(gdk_display_get_default()),
707                    GDK_DEVICE_TYPE_MASTER);
708         GdkDevice *gd = NULL;
709
710         for (l = gdl; l != NULL; l = l->next)
711                 if (gdk_device_get_source(l->data) == GDK_SOURCE_KEYBOARD)
712                         gd = l->data;
713
714         g_list_free(gdl);
715         return gd;
716 }
717
718 WebKitWebView *
719 newview(Client *c, WebKitWebView *rv)
720 {
721         WebKitWebView *v;
722         WebKitSettings *settings;
723         WebKitUserContentManager *contentmanager;
724         WebKitWebContext *context;
725
726         /* Webview */
727         if (rv) {
728                 v = WEBKIT_WEB_VIEW(
729                     webkit_web_view_new_with_related_view(rv));
730         } else {
731                 settings = webkit_settings_new_with_settings(
732                            "auto-load-images", loadimages,
733                            "default-font-size", defaultfontsize,
734                            "enable-caret-browsing", enablecaretbrowsing,
735                            "enable-developer-extras", enableinspector,
736                            "enable-dns-prefetching", enablednsprefetching,
737                            "enable-frame-flattening", enableframeflattening,
738                            "enable-html5-database", enablecache,
739                            "enable-html5-local-storage", enablecache,
740                            "enable-javascript", enablescripts,
741                            "enable-plugins", enableplugins,
742                            NULL);
743 /* For mor interesting settings, have a look at
744  * http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html */
745
746                 if (strcmp(fulluseragent, "")) {
747                         webkit_settings_set_user_agent(settings, fulluseragent);
748                 } else if (surfuseragent) {
749                         webkit_settings_set_user_agent_with_application_details(
750                             settings, "Surf", VERSION);
751                 }
752                 useragent = webkit_settings_get_user_agent(settings);
753
754                 contentmanager = webkit_user_content_manager_new();
755
756                 context = webkit_web_context_new_with_website_data_manager(
757                           webkit_website_data_manager_new(
758                           "base-cache-directory", cachedir,
759                           "base-data-directory", cachedir,
760                           NULL));
761
762                 /* rendering process model, can be a shared unique one
763                  * or one for each view */
764                 webkit_web_context_set_process_model(context,
765                     WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
766                 /* ssl */
767                 webkit_web_context_set_tls_errors_policy(context, strictssl ?
768                     WEBKIT_TLS_ERRORS_POLICY_FAIL :
769                     WEBKIT_TLS_ERRORS_POLICY_IGNORE);
770                 /* disk cache */
771                 webkit_web_context_set_cache_model(context, enablecache ?
772                     WEBKIT_CACHE_MODEL_WEB_BROWSER :
773                     WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
774
775                 /* Currently only works with text file to be compatible with curl */
776                 webkit_cookie_manager_set_persistent_storage(
777                     webkit_web_context_get_cookie_manager(context), cookiefile,
778                     WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
779                 /* cookie policy */
780                 webkit_cookie_manager_set_accept_policy(
781                     webkit_web_context_get_cookie_manager(context),
782                     cookiepolicy_get());
783                 /* languages */
784                 webkit_web_context_set_preferred_languages(context,
785                                                            preferedlanguages);
786                 webkit_web_context_set_spell_checking_languages(context,
787                     spellinglanguages);
788                 webkit_web_context_set_spell_checking_enabled(context,
789                     enablespellchecking);
790
791                 g_signal_connect(G_OBJECT(context), "download-started",
792                                  G_CALLBACK(downloadstarted), c);
793
794                 v = g_object_new(WEBKIT_TYPE_WEB_VIEW,
795                     "settings", settings,
796                     "user-content-manager", contentmanager,
797                     "web-context", context,
798                     NULL);
799         }
800
801         g_signal_connect(G_OBJECT(v), "notify::estimated-load-progress",
802                          G_CALLBACK(progresschanged), c);
803         g_signal_connect(G_OBJECT(v), "notify::title",
804                          G_CALLBACK(titlechanged), c);
805         g_signal_connect(G_OBJECT(v), "button-release-event",
806                          G_CALLBACK(buttonreleased), c);
807         g_signal_connect(G_OBJECT(v), "close",
808                         G_CALLBACK(closeview), c);
809         g_signal_connect(G_OBJECT(v), "create",
810                          G_CALLBACK(createview), c);
811         g_signal_connect(G_OBJECT(v), "decide-policy",
812                          G_CALLBACK(decidepolicy), c);
813         g_signal_connect(G_OBJECT(v), "load-changed",
814                          G_CALLBACK(loadchanged), c);
815         g_signal_connect(G_OBJECT(v), "mouse-target-changed",
816                          G_CALLBACK(mousetargetchanged), c);
817         g_signal_connect(G_OBJECT(v), "permission-request",
818                          G_CALLBACK(permissionrequested), c);
819         g_signal_connect(G_OBJECT(v), "ready-to-show",
820                          G_CALLBACK(showview), c);
821
822         return v;
823 }
824
825 GtkWidget *
826 createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c)
827 {
828         Client *n;
829
830         switch (webkit_navigation_action_get_navigation_type(a)) {
831         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
832                 /*
833                  * popup windows of type “other” are almost always triggered
834                  * by user gesture, so inverse the logic here
835                  */
836 /* instead of this, compare destination uri to mouse-over uri for validating window */
837                 if (webkit_navigation_action_is_user_gesture(a))
838                         return NULL;
839         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
840         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
841         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
842         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
843         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
844                 n = newclient(c);
845                 break;
846         default:
847                 return NULL;
848         }
849
850         return GTK_WIDGET(n->view);
851 }
852
853 gboolean
854 buttonreleased(GtkWidget *w, GdkEvent *e, Client *c)
855 {
856         WebKitHitTestResultContext element;
857         int i;
858
859         element = webkit_hit_test_result_get_context(c->mousepos);
860
861         for (i = 0; i < LENGTH(buttons); ++i) {
862                 if (element & buttons[i].target &&
863                     e->button.button == buttons[i].button &&
864                     CLEANMASK(e->button.state) == CLEANMASK(buttons[i].mask) &&
865                     buttons[i].func) {
866                         buttons[i].func(c, &buttons[i].arg, c->mousepos);
867                         return buttons[i].stopevent;
868                 }
869         }
870
871         return FALSE;
872 }
873
874 GdkFilterReturn
875 processx(GdkXEvent *e, GdkEvent *event, gpointer d)
876 {
877         Client *c = (Client *)d;
878         XPropertyEvent *ev;
879         Arg a;
880
881         if (((XEvent *)e)->type == PropertyNotify) {
882                 ev = &((XEvent *)e)->xproperty;
883                 if (ev->state == PropertyNewValue) {
884                         if (ev->atom == atoms[AtomFind]) {
885                                 find(c, NULL);
886
887                                 return GDK_FILTER_REMOVE;
888                         } else if (ev->atom == atoms[AtomGo]) {
889                                 a.v = getatom(c, AtomGo);
890                                 loaduri(c, &a);
891
892                                 return GDK_FILTER_REMOVE;
893                         }
894                 }
895         }
896         return GDK_FILTER_CONTINUE;
897 }
898
899 gboolean
900 winevent(GtkWidget *w, GdkEvent *e, Client *c)
901 {
902         int i;
903
904         switch (e->type) {
905         case GDK_ENTER_NOTIFY:
906                 c->overtitle = c->targeturi;
907                 updatetitle(c);
908                 break;
909         case GDK_KEY_PRESS:
910                 if (!kioskmode) {
911                         for (i = 0; i < LENGTH(keys); ++i) {
912                                 if (gdk_keyval_to_lower(e->key.keyval) ==
913                                     keys[i].keyval &&
914                                     CLEANMASK(e->key.state) == keys[i].mod &&
915                                     keys[i].func) {
916                                         updatewinid(c);
917                                         keys[i].func(c, &(keys[i].arg));
918                                         return TRUE;
919                                 }
920                         }
921                 }
922         case GDK_LEAVE_NOTIFY:
923                 c->overtitle = NULL;
924                 updatetitle(c);
925                 break;
926         case GDK_WINDOW_STATE:
927                 if (e->window_state.changed_mask ==
928                     GDK_WINDOW_STATE_FULLSCREEN)
929                         c->fullscreen = e->window_state.new_window_state &
930                                         GDK_WINDOW_STATE_FULLSCREEN;
931                 break;
932         default:
933                 break;
934         }
935
936         return FALSE;
937 }
938
939 void
940 showview(WebKitWebView *v, Client *c)
941 {
942         GdkRGBA bgcolor = { 0 };
943         GdkWindow *gwin;
944
945         c->finder = webkit_web_view_get_find_controller(c->view);
946         if (enableinspector)
947                 c->inspector = webkit_web_view_get_inspector(c->view);
948
949         c->win = createwindow(c);
950
951         gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
952         gtk_widget_show_all(c->win);
953         gtk_widget_grab_focus(GTK_WIDGET(c->view));
954
955         gwin = gtk_widget_get_window(GTK_WIDGET(c->win));
956         c->xid = gdk_x11_window_get_xid(gwin);
957         updatewinid(c);
958         if (showxid) {
959                 gdk_display_sync(gtk_widget_get_display(c->win));
960                 puts(winid);
961         }
962
963         if (hidebackground)
964                 webkit_web_view_set_background_color(c->view, &bgcolor);
965
966         if (!kioskmode) {
967                 gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK);
968                 gdk_window_add_filter(gwin, processx, c);
969         }
970
971         if (runinfullscreen)
972                 togglefullscreen(c, NULL);
973
974         if (zoomlevel != 1.0)
975                 webkit_web_view_set_zoom_level(c->view, zoomlevel);
976
977         setatom(c, AtomFind, "");
978         setatom(c, AtomUri, "about:blank");
979 }
980
981 GtkWidget *
982 createwindow(Client *c)
983 {
984         char *wmstr;
985         GtkWidget *w;
986
987         if (embed) {
988                 w = gtk_plug_new(embed);
989         } else {
990                 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
991
992                 wmstr = g_path_get_basename(argv0);
993                 gtk_window_set_wmclass(GTK_WINDOW(w), wmstr, "Surf");
994                 g_free(wmstr);
995
996                 wmstr = g_strdup_printf("%s[%lu]", "Surf",
997                         webkit_web_view_get_page_id(c->view));
998                 gtk_window_set_role(GTK_WINDOW(w), wmstr);
999                 g_free(wmstr);
1000
1001                 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
1002         }
1003
1004         g_signal_connect(G_OBJECT(w), "destroy",
1005                          G_CALLBACK(destroywin), c);
1006         g_signal_connect(G_OBJECT(w), "enter-notify-event",
1007                          G_CALLBACK(winevent), c);
1008         g_signal_connect(G_OBJECT(w), "key-press-event",
1009                          G_CALLBACK(winevent), c);
1010         g_signal_connect(G_OBJECT(w), "leave-notify-event",
1011                          G_CALLBACK(winevent), c);
1012         g_signal_connect(G_OBJECT(w), "window-state-event",
1013                          G_CALLBACK(winevent), c);
1014
1015         return w;
1016 }
1017
1018 void
1019 loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
1020 {
1021         const char *title = geturi(c);
1022
1023         switch (e) {
1024         case WEBKIT_LOAD_STARTED:
1025                 setatom(c, AtomUri, title);
1026                 c->title = title;
1027                 c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
1028                 break;
1029         case WEBKIT_LOAD_REDIRECTED:
1030                 setatom(c, AtomUri, title);
1031                 c->title = title;
1032                 break;
1033         case WEBKIT_LOAD_COMMITTED:
1034                 setatom(c, AtomUri, title);
1035                 c->title = title;
1036                 if (!webkit_web_view_get_tls_info(c->view, NULL,
1037                     &(c->tlsflags)))
1038                         c->tlsflags = G_TLS_CERTIFICATE_VALIDATE_ALL + 1;
1039
1040                 if (enablestyle)
1041                         setstyle(c, getstyle(geturi(c)));
1042                 break;
1043         case WEBKIT_LOAD_FINISHED:
1044                 /* Disabled until we write some WebKitWebExtension for
1045                  * manipulating the DOM directly.
1046                 evalscript(c, "document.documentElement.style.overflow = '%s'",
1047                     enablescrollbars ? "auto" : "hidden");
1048                 */
1049                 runscript(c);
1050                 break;
1051         }
1052         updatetitle(c);
1053 }
1054
1055 void
1056 progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c)
1057 {
1058         c->progress = webkit_web_view_get_estimated_load_progress(c->view) *
1059                       100;
1060         updatetitle(c);
1061 }
1062
1063 void
1064 titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c)
1065 {
1066         c->title = webkit_web_view_get_title(c->view);
1067         updatetitle(c);
1068 }
1069
1070 void
1071 mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modifiers,
1072     Client *c)
1073 {
1074         WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h);
1075
1076         /* Keep the hit test to know where is the pointer on the next click */
1077         c->mousepos = h;
1078
1079         if (hc & OnLink)
1080                 c->targeturi = webkit_hit_test_result_get_link_uri(h);
1081         else if (hc & OnImg)
1082                 c->targeturi = webkit_hit_test_result_get_image_uri(h);
1083         else if (hc & OnMedia)
1084                 c->targeturi = webkit_hit_test_result_get_media_uri(h);
1085         else
1086                 c->targeturi = NULL;
1087
1088         c->overtitle = c->targeturi;
1089         updatetitle(c);
1090 }
1091
1092 gboolean
1093 permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client *c)
1094 {
1095         if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
1096                 if (allowgeolocation)
1097                         webkit_permission_request_allow(r);
1098                 else
1099                         webkit_permission_request_deny(r);
1100                 return TRUE;
1101         }
1102
1103         return FALSE;
1104 }
1105
1106 gboolean
1107 decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
1108     WebKitPolicyDecisionType dt, Client *c)
1109 {
1110         switch (dt) {
1111         case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
1112                 decidenavigation(d, c);
1113                 break;
1114         case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
1115                 decidenewwindow(d, c);
1116                 break;
1117         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
1118                 decideresource(d, c);
1119                 break;
1120         default:
1121                 webkit_policy_decision_ignore(d);
1122                 break;
1123         }
1124         return TRUE;
1125 }
1126
1127 void
1128 decidenavigation(WebKitPolicyDecision *d, Client *c)
1129 {
1130         WebKitNavigationAction *a =
1131             webkit_navigation_policy_decision_get_navigation_action(
1132             WEBKIT_NAVIGATION_POLICY_DECISION(d));
1133
1134         switch (webkit_navigation_action_get_navigation_type(a)) {
1135         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1136         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1137         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1138         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1139         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: /* fallthrough */
1140         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1141         default:
1142                 /* Do not navigate to links with a "_blank" target (popup) */
1143                 if (webkit_navigation_policy_decision_get_frame_name(
1144                     WEBKIT_NAVIGATION_POLICY_DECISION(d))) {
1145                         webkit_policy_decision_ignore(d);
1146                 } else {
1147                         /* Filter out navigation to different domain ? */
1148                         /* get action→urirequest, copy and load in new window+view
1149                          * on Ctrl+Click ? */
1150                         webkit_policy_decision_use(d);
1151                 }
1152                 break;
1153         }
1154 }
1155
1156 void
1157 decidenewwindow(WebKitPolicyDecision *d, Client *c)
1158 {
1159         Arg arg;
1160         WebKitNavigationAction *a =
1161             webkit_navigation_policy_decision_get_navigation_action(
1162             WEBKIT_NAVIGATION_POLICY_DECISION(d));
1163
1164
1165         switch (webkit_navigation_action_get_navigation_type(a)) {
1166         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1167         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1168         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1169         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1170         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1171                 /* Filter domains here */
1172 /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
1173  * test for link clicked but no button ? */
1174                 arg.v = webkit_uri_request_get_uri(
1175                         webkit_navigation_action_get_request(a));
1176                 newwindow(c, &arg, 0);
1177                 break;
1178         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1179         default:
1180                 break;
1181         }
1182
1183         webkit_policy_decision_ignore(d);
1184 }
1185
1186 void
1187 decideresource(WebKitPolicyDecision *d, Client *c)
1188 {
1189         int i, isascii = 1;
1190         WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISION(d);
1191         WebKitURIResponse *res =
1192             webkit_response_policy_decision_get_response(r);
1193         const gchar *uri = webkit_uri_response_get_uri(res);
1194
1195         if (g_str_has_suffix(uri, "/favicon.ico")) {
1196                 webkit_policy_decision_ignore(d);
1197                 return;
1198         }
1199
1200         if (!g_str_has_prefix(uri, "http://")
1201             && !g_str_has_prefix(uri, "https://")
1202             && !g_str_has_prefix(uri, "about:")
1203             && !g_str_has_prefix(uri, "file://")
1204             && !g_str_has_prefix(uri, "data:")
1205             && !g_str_has_prefix(uri, "blob:")
1206             && strlen(uri) > 0) {
1207                 for (i = 0; i < strlen(uri); i++) {
1208                         if (!g_ascii_isprint(uri[i])) {
1209                                 isascii = 0;
1210                                 break;
1211                         }
1212                 }
1213                 if (isascii) {
1214                         handleplumb(c, uri);
1215                         webkit_policy_decision_ignore(d);
1216                         return;
1217                 }
1218         }
1219
1220         if (webkit_response_policy_decision_is_mime_type_supported(r)) {
1221                 webkit_policy_decision_use(d);
1222         } else {
1223                 webkit_policy_decision_ignore(d);
1224                 download(c, res);
1225         }
1226 }
1227
1228 void
1229 downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c)
1230 {
1231         g_signal_connect(G_OBJECT(d), "notify::response",
1232                          G_CALLBACK(responsereceived), c);
1233 }
1234
1235 void
1236 responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c)
1237 {
1238         download(c, webkit_download_get_response(d));
1239         webkit_download_cancel(d);
1240 }
1241
1242 void
1243 download(Client *c, WebKitURIResponse *r)
1244 {
1245         Arg a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
1246         spawn(c, &a);
1247 }
1248
1249 void
1250 closeview(WebKitWebView *v, Client *c)
1251 {
1252         gtk_widget_destroy(c->win);
1253 }
1254
1255 void
1256 destroywin(GtkWidget* w, Client *c)
1257 {
1258         destroyclient(c);
1259         if (!clients)
1260                 gtk_main_quit();
1261 }
1262
1263 void
1264 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
1265 {
1266         Arg a = {.v = text };
1267         if (text)
1268                 loaduri((Client *) d, &a);
1269 }
1270
1271 void
1272 reload(Client *c, const Arg *a)
1273 {
1274         if (a->b)
1275                 webkit_web_view_reload_bypass_cache(c->view);
1276         else
1277                 webkit_web_view_reload(c->view);
1278 }
1279
1280 void
1281 print(Client *c, const Arg *a)
1282 {
1283         webkit_print_operation_run_dialog(webkit_print_operation_new(c->view),
1284                                           GTK_WINDOW(c->win));
1285 }
1286
1287 void
1288 clipboard(Client *c, const Arg *a)
1289 {
1290         if (a->b) { /* load clipboard uri */
1291                 gtk_clipboard_request_text(gtk_clipboard_get(
1292                                            GDK_SELECTION_PRIMARY),
1293                                            pasteuri, c);
1294         } else { /* copy uri */
1295                 gtk_clipboard_set_text(gtk_clipboard_get(
1296                                        GDK_SELECTION_PRIMARY), c->targeturi
1297                                        ? c->targeturi : geturi(c), -1);
1298         }
1299 }
1300
1301 void
1302 zoom(Client *c, const Arg *a)
1303 {
1304         if (a->i > 0)
1305                 webkit_web_view_set_zoom_level(c->view, zoomlevel + 0.1);
1306         else if (a->i < 0)
1307                 webkit_web_view_set_zoom_level(c->view, zoomlevel - 0.1);
1308         else
1309                 webkit_web_view_set_zoom_level(c->view, 1.0);
1310
1311         zoomlevel = webkit_web_view_get_zoom_level(c->view);
1312 }
1313
1314 void
1315 scroll(Client *c, const Arg *a)
1316 {
1317         GdkEvent *ev = gdk_event_new(GDK_KEY_PRESS);
1318
1319         gdk_event_set_device(ev, gdkkb);
1320 //      gdk_event_set_screen(ev, gdk_screen_get_default());
1321         ev->key.window = gtk_widget_get_window(GTK_WIDGET(c->win));
1322         ev->key.state = GDK_CONTROL_MASK;
1323         ev->key.time = GDK_CURRENT_TIME;
1324
1325         switch (a->i) {
1326         case 'd':
1327                 ev->key.keyval = GDK_KEY_Down;
1328                 break;
1329         case 'D':
1330                 ev->key.keyval = GDK_KEY_Page_Down;
1331                 break;
1332         case 'l':
1333                 ev->key.keyval = GDK_KEY_Left;
1334                 break;
1335         case 'r':
1336                 ev->key.keyval = GDK_KEY_Right;
1337                 break;
1338         case 'U':
1339                 ev->key.keyval = GDK_KEY_Page_Up;
1340                 break;
1341         case 'u':
1342                 ev->key.keyval = GDK_KEY_Up;
1343                 break;
1344         }
1345
1346         gdk_event_put(ev);
1347 }
1348
1349 void
1350 navigate(Client *c, const Arg *a)
1351 {
1352         if (a->i < 0)
1353                 webkit_web_view_go_back(c->view);
1354         else if (a->i > 0)
1355                 webkit_web_view_go_forward(c->view);
1356 }
1357
1358 void
1359 stop(Client *c, const Arg *a)
1360 {
1361         webkit_web_view_stop_loading(c->view);
1362 }
1363
1364 void
1365 toggle(Client *c, const Arg *a)
1366 {
1367         WebKitSettings *s = webkit_web_view_get_settings(c->view);
1368
1369         switch ((unsigned int)a->i) {
1370         case CaretBrowsing:
1371                 enablecaretbrowsing = !enablecaretbrowsing;
1372                 webkit_settings_set_enable_caret_browsing(s,
1373                     enablecaretbrowsing);
1374                 updatetitle(c);
1375                 return; /* do not reload */
1376                 break;
1377         case FrameFlattening:
1378                 enableframeflattening = !enableframeflattening;
1379                 webkit_settings_set_enable_frame_flattening(s,
1380                     enableframeflattening);
1381                 break;
1382         case Geolocation:
1383                 allowgeolocation = !allowgeolocation;
1384                 break;
1385         case JavaScript:
1386                 enablescripts = !enablescripts;
1387                 webkit_settings_set_enable_javascript(s, enablescripts);
1388                 break;
1389         case LoadImages:
1390                 loadimages = !loadimages;
1391                 webkit_settings_set_auto_load_images(s, loadimages);
1392                 break;
1393         case Plugins:
1394                 enableplugins = !enableplugins;
1395                 webkit_settings_set_enable_plugins(s, enableplugins);
1396                 break;
1397         case ScrollBars:
1398                 /* Disabled until we write some WebKitWebExtension for
1399                  * manipulating the DOM directly.
1400                 enablescrollbars = !enablescrollbars;
1401                 evalscript(c, "document.documentElement.style.overflow = '%s'",
1402                     enablescrollbars ? "auto" : "hidden");
1403                 */
1404                 return; /* do not reload */
1405                 break;
1406         default:
1407                 break;
1408         }
1409         reload(c, a);
1410 }
1411
1412 void
1413 togglefullscreen(Client *c, const Arg *a)
1414 {
1415         /* toggling value is handled in winevent() */
1416         if (c->fullscreen)
1417                 gtk_window_unfullscreen(GTK_WINDOW(c->win));
1418         else
1419                 gtk_window_fullscreen(GTK_WINDOW(c->win));
1420 }
1421
1422 void
1423 togglecookiepolicy(Client *c, const Arg *a)
1424 {
1425         ++cookiepolicy;
1426         cookiepolicy %= strlen(cookiepolicies);
1427
1428         webkit_cookie_manager_set_accept_policy(
1429             webkit_web_context_get_cookie_manager(
1430             webkit_web_view_get_context(c->view)),
1431             cookiepolicy_get());
1432
1433         updatetitle(c);
1434         /* Do not reload. */
1435 }
1436
1437 void
1438 togglestyle(Client *c, const Arg *a)
1439 {
1440         enablestyle = !enablestyle;
1441
1442         if (enablestyle)
1443                 setstyle(c, getstyle(geturi(c)));
1444         else
1445                 webkit_user_content_manager_remove_all_style_sheets(
1446                     webkit_web_view_get_user_content_manager(c->view));
1447
1448         updatetitle(c);
1449 }
1450
1451 void
1452 toggleinspector(Client *c, const Arg *a)
1453 {
1454         if (enableinspector) {
1455                 if (webkit_web_inspector_is_attached(c->inspector))
1456                         webkit_web_inspector_close(c->inspector);
1457                 else
1458                         webkit_web_inspector_show(c->inspector);
1459         }
1460 }
1461
1462 void
1463 find(Client *c, const Arg *a)
1464 {
1465         const char *s, *f;
1466
1467         if (a && a->i) {
1468                 if (a->i > 0)
1469                         webkit_find_controller_search_next(c->finder);
1470                 else
1471                         webkit_find_controller_search_previous(c->finder);
1472         } else {
1473                 s = getatom(c, AtomFind);
1474                 f = webkit_find_controller_get_search_text(c->finder);
1475
1476                 if (g_strcmp0(f, s) == 0) /* reset search */
1477                         webkit_find_controller_search(c->finder, "", findopts,
1478                                                       G_MAXUINT);
1479
1480                 webkit_find_controller_search(c->finder, s, findopts,
1481                                               G_MAXUINT);
1482
1483                 if (strcmp(s, "") == 0)
1484                         webkit_find_controller_search_finish(c->finder);
1485         }
1486 }
1487
1488 void
1489 clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h)
1490 {
1491         navigate(c, a);
1492 }
1493
1494 void
1495 clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h)
1496 {
1497         Arg arg;
1498
1499         arg.v = webkit_hit_test_result_get_link_uri(h);
1500         newwindow(c, &arg, a->b);
1501 }
1502
1503 void
1504 clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h)
1505 {
1506         Arg arg;
1507
1508         arg = (Arg)VIDEOPLAY(webkit_hit_test_result_get_media_uri(h));
1509         spawn(c, &arg);
1510 }
1511
1512 int
1513 main(int argc, char *argv[])
1514 {
1515         Arg arg;
1516         Client *c;
1517
1518         memset(&arg, 0, sizeof(arg));
1519
1520         /* command line args */
1521         ARGBEGIN {
1522         case 'a':
1523                 cookiepolicies = EARGF(usage());
1524                 break;
1525         case 'b':
1526                 enablescrollbars = 0;
1527                 break;
1528         case 'B':
1529                 enablescrollbars = 1;
1530                 break;
1531         case 'c':
1532                 cookiefile = EARGF(usage());
1533                 break;
1534         case 'd':
1535                 enablecache = 0;
1536                 break;
1537         case 'D':
1538                 enablecache = 1;
1539                 break;
1540         case 'e':
1541                 embed = strtol(EARGF(usage()), NULL, 0);
1542                 break;
1543         case 'f':
1544                 runinfullscreen = 0;
1545                 break;
1546         case 'F':
1547                 runinfullscreen = 1;
1548                 break;
1549         case 'g':
1550                 allowgeolocation = 0;
1551                 break;
1552         case 'G':
1553                 allowgeolocation = 1;
1554                 break;
1555         case 'i':
1556                 loadimages = 0;
1557                 break;
1558         case 'I':
1559                 loadimages = 1;
1560                 break;
1561         case 'k':
1562                 kioskmode = 0;
1563                 break;
1564         case 'K':
1565                 kioskmode = 1;
1566                 break;
1567         case 'm':
1568                 enablestyle = 0;
1569                 break;
1570         case 'M':
1571                 enablestyle = 1;
1572                 break;
1573         case 'n':
1574                 enableinspector = 0;
1575                 break;
1576         case 'N':
1577                 enableinspector = 1;
1578                 break;
1579         case 'p':
1580                 enableplugins = 0;
1581                 break;
1582         case 'P':
1583                 enableplugins = 1;
1584                 break;
1585         case 'r':
1586                 scriptfile = EARGF(usage());
1587                 break;
1588         case 's':
1589                 enablescripts = 0;
1590                 break;
1591         case 'S':
1592                 enablescripts = 1;
1593                 break;
1594         case 't':
1595                 stylefile = EARGF(usage());
1596                 break;
1597         case 'u':
1598                 fulluseragent = EARGF(usage());
1599                 break;
1600         case 'v':
1601                 die("surf-"VERSION", ©2009-2015 surf engineers, "
1602                     "see LICENSE for details\n");
1603         case 'x':
1604                 showxid = 1;
1605                 break;
1606         case 'z':
1607                 zoomlevel = strtof(EARGF(usage()), NULL);
1608                 break;
1609         default:
1610                 usage();
1611         } ARGEND;
1612         if (argc > 0)
1613                 arg.v = argv[0];
1614         else
1615                 arg.v = "about:blank";
1616
1617         setup();
1618         c = newclient(NULL);
1619         showview(NULL, c);
1620
1621         loaduri(c, &arg);
1622         updatetitle(c);
1623
1624         gtk_main();
1625         cleanup();
1626
1627         return 0;
1628 }