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