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