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