readme
[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 <glib.h>
9 #include <libgen.h>
10 #include <limits.h>
11 #include <pwd.h>
12 #include <regex.h>
13 #include <signal.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 <gcr/gcr.h>
26 #include <JavaScriptCore/JavaScript.h>
27 #include <webkit2/webkit2.h>
28 #include <X11/X.h>
29 #include <X11/Xatom.h>
30 #include <glib.h>
31
32 #include "arg.h"
33 #include "common.h"
34
35 #define LENGTH(x)               (sizeof(x) / sizeof(x[0]))
36 #define CLEANMASK(mask)         (mask & (MODKEY|GDK_SHIFT_MASK))
37
38 enum { AtomFind, AtomGo, AtomUri, AtomLast };
39
40 enum {
41         OnDoc   = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
42         OnLink  = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
43         OnImg   = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
44         OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
45         OnEdit  = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
46         OnBar   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR,
47         OnSel   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
48         OnAny   = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | OnSel,
49 };
50
51 typedef enum {
52         AcceleratedCanvas,
53         AccessMicrophone,
54         AccessWebcam,
55         CaretBrowsing,
56         Certificate,
57         CookiePolicies,
58         DiskCache,
59         DefaultCharset,
60         DNSPrefetch,
61         FileURLsCrossAccess,
62         FontSize,
63         FrameFlattening,
64         Geolocation,
65         HideBackground,
66         Inspector,
67         Java,
68         JavaScript,
69         KioskMode,
70         LoadImages,
71         MediaManualPlay,
72         Plugins,
73         PreferredLanguages,
74         RunInFullscreen,
75         ScrollBars,
76         ShowIndicators,
77         SiteQuirks,
78         SmoothScrolling,
79         SpellChecking,
80         SpellLanguages,
81         StrictTLS,
82         Style,
83         WebGL,
84         ZoomLevel,
85         ParameterLast
86 } ParamName;
87
88 typedef union {
89         int i;
90         float f;
91         const void *v;
92 } Arg;
93
94 typedef struct {
95         Arg val;
96         int prio;
97 } Parameter;
98
99 typedef struct Client {
100         GtkWidget *win;
101         WebKitWebView *view;
102         WebKitWebInspector *inspector;
103         WebKitFindController *finder;
104         WebKitHitTestResult *mousepos;
105         GTlsCertificate *cert, *failedcert;
106         GTlsCertificateFlags tlserr;
107         Window xid;
108         unsigned long pageid;
109         int progress, fullscreen, https, insecure, errorpage;
110         const char *title, *overtitle, *targeturi;
111         const char *needle;
112         struct Client *next;
113 } Client;
114
115 typedef struct {
116         guint mod;
117         guint keyval;
118         void (*func)(Client *c, const Arg *a);
119         const Arg arg;
120 } Key;
121
122 typedef struct {
123         unsigned int target;
124         unsigned int mask;
125         guint button;
126         void (*func)(Client *c, const Arg *a, WebKitHitTestResult *h);
127         const Arg arg;
128         unsigned int stopevent;
129 } Button;
130
131
132 typedef struct {
133    char *token;
134    char *uri;
135 } SearchEngine;
136
137 typedef struct {
138         const char *uri;
139         Parameter config[ParameterLast];
140         regex_t re;
141 } UriParameters;
142
143 typedef struct {
144         char *regex;
145         char *file;
146         regex_t re;
147 } SiteSpecific;
148
149 /* Surf */
150 static void usage(void);
151 static void setup(void);
152 static void sigchld(int unused);
153 static void sighup(int unused);
154 static char *buildfile(const char *path);
155 static char *buildpath(const char *path);
156 static char *untildepath(const char *path);
157 static const char *getuserhomedir(const char *user);
158 static const char *getcurrentuserhomedir(void);
159 static Client *newclient(Client *c);
160 static void loaduri(Client *c, const Arg *a);
161 static const char *geturi(Client *c);
162 static void setatom(Client *c, int a, const char *v);
163 static const char *getatom(Client *c, int a);
164 static void updatetitle(Client *c);
165 static void gettogglestats(Client *c);
166 static void getpagestats(Client *c);
167 static WebKitCookieAcceptPolicy cookiepolicy_get(void);
168 static char cookiepolicy_set(const WebKitCookieAcceptPolicy p);
169 static void seturiparameters(Client *c, const char *uri, ParamName *params);
170 static void setparameter(Client *c, int refresh, ParamName p, const Arg *a);
171 static const char *getcert(const char *uri);
172 static void setcert(Client *c, const char *file);
173 static const char *getstyle(const char *uri);
174 static void setstyle(Client *c, const char *file);
175 static void runscript(Client *c);
176 static void evalscript(Client *c, const char *jsstr, ...);
177 static void updatewinid(Client *c);
178 static void handleplumb(Client *c, const char *uri);
179 static void newwindow(Client *c, const Arg *a, int noembed);
180 static void spawn(Client *c, const Arg *a);
181 static void msgext(Client *c, char type, const Arg *a);
182 static void destroyclient(Client *c);
183 static void cleanup(void);
184
185 /* GTK/WebKit */
186 static WebKitWebView *newview(Client *c, WebKitWebView *rv);
187 static void initwebextensions(WebKitWebContext *wc, Client *c);
188 static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a,
189                              Client *c);
190 static gboolean buttonreleased(GtkWidget *w, GdkEvent *e, Client *c);
191 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
192                                 gpointer d);
193 static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c);
194 static gboolean readpipe(GIOChannel *s, GIOCondition ioc, gpointer unused);
195 static void showview(WebKitWebView *v, Client *c);
196 static GtkWidget *createwindow(Client *c);
197 static gboolean loadfailedtls(WebKitWebView *v, gchar *uri,
198                               GTlsCertificate *cert,
199                               GTlsCertificateFlags err, Client *c);
200 static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c);
201 static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c);
202 static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c);
203 static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
204                                guint modifiers, Client *c);
205 static gboolean permissionrequested(WebKitWebView *v,
206                                     WebKitPermissionRequest *r, Client *c);
207 static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
208                              WebKitPolicyDecisionType dt, Client *c);
209 static void decidenavigation(WebKitPolicyDecision *d, Client *c);
210 static void decidenewwindow(WebKitPolicyDecision *d, Client *c);
211 static void decideresource(WebKitPolicyDecision *d, Client *c);
212 static void insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e,
213                             Client *c);
214 static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d,
215                             Client *c);
216 static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c);
217 static void download(Client *c, WebKitURIResponse *r);
218 static void webprocessterminated(WebKitWebView *v,
219                                  WebKitWebProcessTerminationReason r,
220                                  Client *c);
221 static void closeview(WebKitWebView *v, Client *c);
222 static void destroywin(GtkWidget* w, Client *c);
223 static gchar *parseuri(const gchar *uri);
224
225 /* Hotkeys */
226 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
227 static void reload(Client *c, const Arg *a);
228 static void print(Client *c, const Arg *a);
229 static void showcert(Client *c, const Arg *a);
230 static void clipboard(Client *c, const Arg *a);
231 static void zoom(Client *c, const Arg *a);
232 static void scrollv(Client *c, const Arg *a);
233 static void scrollh(Client *c, const Arg *a);
234 static void navigate(Client *c, const Arg *a);
235 static void stop(Client *c, const Arg *a);
236 static void toggle(Client *c, const Arg *a);
237 static void togglefullscreen(Client *c, const Arg *a);
238 static void togglecookiepolicy(Client *c, const Arg *a);
239 static void toggleinspector(Client *c, const Arg *a);
240 static void find(Client *c, const Arg *a);
241 static void playexternal(Client *c, const Arg *a);
242
243 /* Buttons */
244 static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h);
245 static void clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h);
246 static void clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h);
247
248 static char winid[64];
249 static char togglestats[12];
250 static char pagestats[2];
251 static Atom atoms[AtomLast];
252 static Window embed;
253 static int showxid;
254 static int cookiepolicy;
255 static Display *dpy;
256 static Client *clients;
257 static GdkDevice *gdkkb;
258 static char *stylefile;
259 static const char *useragent;
260 static Parameter *curconfig;
261 static int modparams[ParameterLast];
262 static int pipein[2], pipeout[2];
263 char *argv0;
264
265 static ParamName loadtransient[] = {
266         Certificate,
267         CookiePolicies,
268         DiskCache,
269         DNSPrefetch,
270         FileURLsCrossAccess,
271         JavaScript,
272         LoadImages,
273         PreferredLanguages,
274         ShowIndicators,
275         StrictTLS,
276         ParameterLast
277 };
278
279 static ParamName loadcommitted[] = {
280         AcceleratedCanvas,
281 //      AccessMicrophone,
282 //      AccessWebcam,
283         CaretBrowsing,
284         DefaultCharset,
285         FontSize,
286         FrameFlattening,
287         Geolocation,
288         HideBackground,
289         Inspector,
290         Java,
291 //      KioskMode,
292         MediaManualPlay,
293         Plugins,
294         RunInFullscreen,
295         ScrollBars,
296         SiteQuirks,
297         SmoothScrolling,
298         SpellChecking,
299         SpellLanguages,
300         Style,
301         ZoomLevel,
302         ParameterLast
303 };
304
305 static ParamName loadfinished[] = {
306         ParameterLast
307 };
308
309 /* configuration, allows nested code to access above variables */
310 #include "config.h"
311
312 void
313 usage(void)
314 {
315         die("usage: surf [-bBdDfFgGiIkKmMnNpPsStTvwxX]\n"
316             "[-a cookiepolicies ] [-c cookiefile] [-C stylefile] [-e xid]\n"
317             "[-r scriptfile] [-u useragent] [-z zoomlevel] [uri]\n");
318 }
319
320 void
321 setup(void)
322 {
323         GIOChannel *gchanin;
324         GdkDisplay *gdpy;
325         int i, j;
326
327         /* clean up any zombies immediately */
328         sigchld(0);
329         if (signal(SIGHUP, sighup) == SIG_ERR)
330                 die("Can't install SIGHUP handler");
331
332         if (!(dpy = XOpenDisplay(NULL)))
333                 die("Can't open default display");
334
335         /* atoms */
336         atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
337         atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
338         atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
339
340         gtk_init(NULL, NULL);
341
342         gdpy = gdk_display_get_default();
343
344         curconfig = defconfig;
345
346         /* dirs and files */
347         cookiefile = buildfile(cookiefile);
348         scriptfile = buildfile(scriptfile);
349         cachedir   = buildpath(cachedir);
350         certdir    = buildpath(certdir);
351
352         gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy));
353
354         if (pipe(pipeout) < 0 || pipe(pipein) < 0) {
355                 fputs("Unable to create pipes\n", stderr);
356         } else {
357                 gchanin = g_io_channel_unix_new(pipein[0]);
358                 g_io_channel_set_encoding(gchanin, NULL, NULL);
359                 g_io_channel_set_close_on_unref(gchanin, TRUE);
360                 g_io_add_watch(gchanin, G_IO_IN, readpipe, NULL);
361         }
362
363
364         for (i = 0; i < LENGTH(certs); ++i) {
365                 if (!regcomp(&(certs[i].re), certs[i].regex, REG_EXTENDED)) {
366                         certs[i].file = g_strconcat(certdir, "/", certs[i].file,
367                                                     NULL);
368                 } else {
369                         fprintf(stderr, "Could not compile regex: %s\n",
370                                 certs[i].regex);
371                         certs[i].regex = NULL;
372                 }
373         }
374
375         if (!stylefile) {
376                 styledir = buildpath(styledir);
377                 for (i = 0; i < LENGTH(styles); ++i) {
378                         if (!regcomp(&(styles[i].re), styles[i].regex,
379                             REG_EXTENDED)) {
380                                 styles[i].file = g_strconcat(styledir, "/",
381                                                     styles[i].file, NULL);
382                         } else {
383                                 fprintf(stderr, "Could not compile regex: %s\n",
384                                         styles[i].regex);
385                                 styles[i].regex = NULL;
386                         }
387                 }
388                 g_free(styledir);
389         } else {
390                 stylefile = buildfile(stylefile);
391         }
392
393         for (i = 0; i < LENGTH(uriparams); ++i) {
394                 if (regcomp(&(uriparams[i].re), uriparams[i].uri,
395                     REG_EXTENDED)) {
396                         fprintf(stderr, "Could not compile regex: %s\n",
397                                 uriparams[i].uri);
398                         uriparams[i].uri = NULL;
399                         continue;
400                 }
401
402                 /* copy default parameters with higher priority */
403                 for (j = 0; j < ParameterLast; ++j) {
404                         if (defconfig[j].prio >= uriparams[i].config[j].prio)
405                                 uriparams[i].config[j] = defconfig[j];
406                 }
407         }
408 }
409
410 void
411 sigchld(int unused)
412 {
413         if (signal(SIGCHLD, sigchld) == SIG_ERR)
414                 die("Can't install SIGCHLD handler");
415         while (waitpid(-1, NULL, WNOHANG) > 0)
416                 ;
417 }
418
419 void
420 sighup(int unused)
421 {
422         Arg a = { .i = 0 };
423         Client *c;
424
425         for (c = clients; c; c = c->next)
426                 reload(c, &a);
427 }
428
429 char *
430 buildfile(const char *path)
431 {
432         char *dname, *bname, *bpath, *fpath;
433         FILE *f;
434
435         dname = g_path_get_dirname(path);
436         bname = g_path_get_basename(path);
437
438         bpath = buildpath(dname);
439         g_free(dname);
440
441         fpath = g_build_filename(bpath, bname, NULL);
442         g_free(bpath);
443         g_free(bname);
444
445         if (!(f = fopen(fpath, "a")))
446                 die("Could not open file: %s\n", fpath);
447
448         g_chmod(fpath, 0600); /* always */
449         fclose(f);
450
451         return fpath;
452 }
453
454 static const char*
455 getuserhomedir(const char *user)
456 {
457         struct passwd *pw = getpwnam(user);
458
459         if (!pw)
460                 die("Can't get user %s login information.\n", user);
461
462         return pw->pw_dir;
463 }
464
465 static const char*
466 getcurrentuserhomedir(void)
467 {
468         const char *homedir;
469         const char *user;
470         struct passwd *pw;
471
472         homedir = getenv("HOME");
473         if (homedir)
474                 return homedir;
475
476         user = getenv("USER");
477         if (user)
478                 return getuserhomedir(user);
479
480         pw = getpwuid(getuid());
481         if (!pw)
482                 die("Can't get current user home directory\n");
483
484         return pw->pw_dir;
485 }
486
487 char *
488 buildpath(const char *path)
489 {
490         char *apath, *fpath;
491
492         if (path[0] == '~')
493                 apath = untildepath(path);
494         else
495                 apath = g_strdup(path);
496
497         /* creating directory */
498         if (g_mkdir_with_parents(apath, 0700) < 0)
499                 die("Could not access directory: %s\n", apath);
500
501         fpath = realpath(apath, NULL);
502         g_free(apath);
503
504         return fpath;
505 }
506
507 char *
508 untildepath(const char *path)
509 {
510        char *apath, *name, *p;
511        const char *homedir;
512
513        if (path[1] == '/' || path[1] == '\0') {
514                p = (char *)&path[1];
515                homedir = getcurrentuserhomedir();
516        } else {
517                if ((p = strchr(path, '/')))
518                        name = g_strndup(&path[1], p - (path + 1));
519                else
520                        name = g_strdup(&path[1]);
521
522                homedir = getuserhomedir(name);
523                g_free(name);
524        }
525        apath = g_build_filename(homedir, p, NULL);
526        return apath;
527 }
528
529 Client *
530 newclient(Client *rc)
531 {
532         Client *c;
533
534         if (!(c = calloc(1, sizeof(Client))))
535                 die("Cannot malloc!\n");
536
537         c->next = clients;
538         clients = c;
539
540         c->progress = 100;
541         c->view = newview(c, rc ? rc->view : NULL);
542
543         return c;
544 }
545
546 void
547 loaduri(Client *c, const Arg *a)
548 {
549         struct stat st;
550         char *url, *path, *apath;
551         const char *uri = a->v;
552
553         if (g_strcmp0(uri, "") == 0)
554                 return;
555
556         if (g_str_has_prefix(uri, "http://")  ||
557             g_str_has_prefix(uri, "https://") ||
558             g_str_has_prefix(uri, "file://")  ||
559             g_str_has_prefix(uri, "about:")) {
560                 url = g_strdup(uri);
561         } else {
562                 if (uri[0] == '~')
563                         apath = untildepath(uri);
564                 else
565                         apath = (char *)uri;
566                 if (!stat(apath, &st) && (path = realpath(apath, NULL))) {
567                         url = g_strdup_printf("file://%s", path);
568                         free(path);
569                 } else {
570                         url = parseuri(uri);
571                 }
572                 if (apath != uri)
573                         free(apath);
574         }
575
576         setatom(c, AtomUri, url);
577
578         if (strcmp(url, geturi(c)) == 0) {
579                 reload(c, a);
580         } else {
581                 webkit_web_view_load_uri(c->view, url);
582                 updatetitle(c);
583         }
584
585         g_free(url);
586 }
587
588 const char *
589 geturi(Client *c)
590 {
591         const char *uri;
592
593         if (!(uri = webkit_web_view_get_uri(c->view)))
594                 uri = "about:blank";
595         return uri;
596 }
597
598 void
599 setatom(Client *c, int a, const char *v)
600 {
601         XChangeProperty(dpy, c->xid,
602                         atoms[a], XA_STRING, 8, PropModeReplace,
603                         (unsigned char *)v, strlen(v) + 1);
604         XSync(dpy, False);
605 }
606
607 const char *
608 getatom(Client *c, int a)
609 {
610         static char buf[BUFSIZ];
611         Atom adummy;
612         int idummy;
613         unsigned long ldummy;
614         unsigned char *p = NULL;
615
616         XSync(dpy, False);
617         XGetWindowProperty(dpy, c->xid, atoms[a], 0L, BUFSIZ, False, XA_STRING,
618                            &adummy, &idummy, &ldummy, &ldummy, &p);
619         if (p)
620                 strncpy(buf, (char *)p, LENGTH(buf) - 1);
621         else
622                 buf[0] = '\0';
623         XFree(p);
624
625         return buf;
626 }
627
628 void
629 updatetitle(Client *c)
630 {
631         char *title;
632         const char *name = c->overtitle ? c->overtitle :
633                            c->title ? c->title : "";
634
635         if (curconfig[ShowIndicators].val.i) {
636                 gettogglestats(c);
637                 getpagestats(c);
638
639                 if (c->progress != 100)
640                         title = g_strdup_printf("[%i%%] %s:%s | %s",
641                                 c->progress, togglestats, pagestats, name);
642                 else
643                         title = g_strdup_printf("%s:%s | %s",
644                                 togglestats, pagestats, name);
645
646                 gtk_window_set_title(GTK_WINDOW(c->win), title);
647                 g_free(title);
648         } else {
649                 gtk_window_set_title(GTK_WINDOW(c->win), name);
650         }
651 }
652
653 void
654 gettogglestats(Client *c)
655 {
656         togglestats[0] = cookiepolicy_set(cookiepolicy_get());
657         togglestats[1] = curconfig[CaretBrowsing].val.i ?   'C' : 'c';
658         togglestats[2] = curconfig[Geolocation].val.i ?     'G' : 'g';
659         togglestats[3] = curconfig[DiskCache].val.i ?       'D' : 'd';
660         togglestats[4] = curconfig[LoadImages].val.i ?      'I' : 'i';
661         togglestats[5] = curconfig[JavaScript].val.i ?      'S' : 's';
662         togglestats[6] = curconfig[Plugins].val.i ?         'V' : 'v';
663         togglestats[7] = curconfig[Style].val.i ?           'M' : 'm';
664         togglestats[8] = curconfig[FrameFlattening].val.i ? 'F' : 'f';
665         togglestats[9] = curconfig[Certificate].val.i ?     'X' : 'x';
666         togglestats[10] = curconfig[StrictTLS].val.i ?      'T' : 't';
667         togglestats[11] = '\0';
668 }
669
670 void
671 getpagestats(Client *c)
672 {
673         if (c->https)
674                 pagestats[0] = (c->tlserr || c->insecure) ?  'U' : 'T';
675         else
676                 pagestats[0] = '-';
677         pagestats[1] = '\0';
678 }
679
680 WebKitCookieAcceptPolicy
681 cookiepolicy_get(void)
682 {
683         switch (((char *)curconfig[CookiePolicies].val.v)[cookiepolicy]) {
684         case 'a':
685                 return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
686         case '@':
687                 return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
688         default: /* fallthrough */
689         case 'A':
690                 return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
691         }
692 }
693
694 char
695 cookiepolicy_set(const WebKitCookieAcceptPolicy p)
696 {
697         switch (p) {
698         case WEBKIT_COOKIE_POLICY_ACCEPT_NEVER:
699                 return 'a';
700         case WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY:
701                 return '@';
702         default: /* fallthrough */
703         case WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS:
704                 return 'A';
705         }
706 }
707
708 void
709 seturiparameters(Client *c, const char *uri, ParamName *params)
710 {
711         Parameter *config, *uriconfig = NULL;
712         int i, p;
713
714         for (i = 0; i < LENGTH(uriparams); ++i) {
715                 if (uriparams[i].uri &&
716                     !regexec(&(uriparams[i].re), uri, 0, NULL, 0)) {
717                         uriconfig = uriparams[i].config;
718                         break;
719                 }
720         }
721
722         curconfig = uriconfig ? uriconfig : defconfig;
723
724         for (i = 0; (p = params[i]) != ParameterLast; ++i) {
725                 switch(p) {
726                 default: /* FALLTHROUGH */
727                         if (!(defconfig[p].prio < curconfig[p].prio ||
728                             defconfig[p].prio < modparams[p]))
729                                 continue;
730                 case Certificate:
731                 case CookiePolicies:
732                 case Style:
733                         setparameter(c, 0, p, &curconfig[p].val);
734                 }
735         }
736 }
737
738 void
739 setparameter(Client *c, int refresh, ParamName p, const Arg *a)
740 {
741         GdkRGBA bgcolor = { 0 };
742         WebKitSettings *s = webkit_web_view_get_settings(c->view);
743
744         modparams[p] = curconfig[p].prio;
745
746         switch (p) {
747         case AcceleratedCanvas:
748                 webkit_settings_set_enable_accelerated_2d_canvas(s, a->i);
749                 break;
750         case AccessMicrophone:
751                 return; /* do nothing */
752         case AccessWebcam:
753                 return; /* do nothing */
754         case CaretBrowsing:
755                 webkit_settings_set_enable_caret_browsing(s, a->i);
756                 refresh = 0;
757                 break;
758         case Certificate:
759                 if (a->i)
760                         setcert(c, geturi(c));
761                 return; /* do not update */
762         case CookiePolicies:
763                 webkit_cookie_manager_set_accept_policy(
764                     webkit_web_context_get_cookie_manager(
765                     webkit_web_view_get_context(c->view)),
766                     cookiepolicy_get());
767                 refresh = 0;
768                 break;
769         case DiskCache:
770                 webkit_web_context_set_cache_model(
771                     webkit_web_view_get_context(c->view), a->i ?
772                     WEBKIT_CACHE_MODEL_WEB_BROWSER :
773                     WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
774                 return; /* do not update */
775         case DefaultCharset:
776                 webkit_settings_set_default_charset(s, a->v);
777                 return; /* do not update */
778         case DNSPrefetch:
779                 webkit_settings_set_enable_dns_prefetching(s, a->i);
780                 return; /* do not update */
781         case FileURLsCrossAccess:
782                 webkit_settings_set_allow_file_access_from_file_urls(s, a->i);
783                 webkit_settings_set_allow_universal_access_from_file_urls(s, a->i);
784                 return; /* do not update */
785         case FontSize:
786                 webkit_settings_set_default_font_size(s, a->i);
787                 return; /* do not update */
788         case FrameFlattening:
789                 webkit_settings_set_enable_frame_flattening(s, a->i);
790                 break;
791         case Geolocation:
792                 refresh = 0;
793                 break;
794         case HideBackground:
795                 if (a->i)
796                         webkit_web_view_set_background_color(c->view, &bgcolor);
797                 return; /* do not update */
798         case Inspector:
799                 webkit_settings_set_enable_developer_extras(s, a->i);
800                 return; /* do not update */
801         case Java:
802                 webkit_settings_set_enable_java(s, a->i);
803                 return; /* do not update */
804         case JavaScript:
805                 webkit_settings_set_enable_javascript(s, a->i);
806                 break;
807         case KioskMode:
808                 return; /* do nothing */
809         case LoadImages:
810                 webkit_settings_set_auto_load_images(s, a->i);
811                 break;
812         case MediaManualPlay:
813                 webkit_settings_set_media_playback_requires_user_gesture(s, a->i);
814                 break;
815         case Plugins:
816                 webkit_settings_set_enable_plugins(s, a->i);
817                 break;
818         case PreferredLanguages:
819                 return; /* do nothing */
820         case RunInFullscreen:
821                 return; /* do nothing */
822         case ScrollBars:
823                 /* Disabled until we write some WebKitWebExtension for
824                  * manipulating the DOM directly.
825                 enablescrollbars = !enablescrollbars;
826                 evalscript(c, "document.documentElement.style.overflow = '%s'",
827                     enablescrollbars ? "auto" : "hidden");
828                 */
829                 return; /* do not update */
830         case ShowIndicators:
831                 break;
832         case SmoothScrolling:
833                 webkit_settings_set_enable_smooth_scrolling(s, a->i);
834                 return; /* do not update */
835         case SiteQuirks:
836                 webkit_settings_set_enable_site_specific_quirks(s, a->i);
837                 break;
838         case SpellChecking:
839                 webkit_web_context_set_spell_checking_enabled(
840                     webkit_web_view_get_context(c->view), a->i);
841                 return; /* do not update */
842         case SpellLanguages:
843                 return; /* do nothing */
844         case StrictTLS:
845                 webkit_web_context_set_tls_errors_policy(
846                     webkit_web_view_get_context(c->view), a->i ?
847                     WEBKIT_TLS_ERRORS_POLICY_FAIL :
848                     WEBKIT_TLS_ERRORS_POLICY_IGNORE);
849                 break;
850         case Style:
851                 webkit_user_content_manager_remove_all_style_sheets(
852                     webkit_web_view_get_user_content_manager(c->view));
853                 if (a->i)
854                         setstyle(c, getstyle(geturi(c)));
855                 refresh = 0;
856                 break;
857         case WebGL:
858                 webkit_settings_set_enable_webgl(s, a->i);
859                 break;
860         case ZoomLevel:
861                 webkit_web_view_set_zoom_level(c->view, a->f);
862                 return; /* do not update */
863         default:
864                 return; /* do nothing */
865         }
866
867         updatetitle(c);
868         if (refresh)
869                 reload(c, a);
870 }
871
872 const char *
873 getcert(const char *uri)
874 {
875         int i;
876
877         for (i = 0; i < LENGTH(certs); ++i) {
878                 if (certs[i].regex &&
879                     !regexec(&(certs[i].re), uri, 0, NULL, 0))
880                         return certs[i].file;
881         }
882
883         return NULL;
884 }
885
886 void
887 setcert(Client *c, const char *uri)
888 {
889         const char *file = getcert(uri);
890         char *host;
891         GTlsCertificate *cert;
892
893         if (!file)
894                 return;
895
896         if (!(cert = g_tls_certificate_new_from_file(file, NULL))) {
897                 fprintf(stderr, "Could not read certificate file: %s\n", file);
898                 return;
899         }
900
901         if ((uri = strstr(uri, "https://"))) {
902                 uri += sizeof("https://") - 1;
903                 host = g_strndup(uri, strchr(uri, '/') - uri);
904                 webkit_web_context_allow_tls_certificate_for_host(
905                     webkit_web_view_get_context(c->view), cert, host);
906                 g_free(host);
907         }
908
909         g_object_unref(cert);
910
911 }
912
913 const char *
914 getstyle(const char *uri)
915 {
916         int i;
917
918         if (stylefile)
919                 return stylefile;
920
921         for (i = 0; i < LENGTH(styles); ++i) {
922                 if (styles[i].regex &&
923                     !regexec(&(styles[i].re), uri, 0, NULL, 0))
924                         return styles[i].file;
925         }
926
927         return "";
928 }
929
930 void
931 setstyle(Client *c, const char *file)
932 {
933         gchar *style;
934
935         if (!g_file_get_contents(file, &style, NULL, NULL)) {
936                 fprintf(stderr, "Could not read style file: %s\n", file);
937                 return;
938         }
939
940         webkit_user_content_manager_add_style_sheet(
941             webkit_web_view_get_user_content_manager(c->view),
942             webkit_user_style_sheet_new(style,
943             WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
944             WEBKIT_USER_STYLE_LEVEL_USER,
945             NULL, NULL));
946
947         g_free(style);
948 }
949
950 void
951 runscript(Client *c)
952 {
953         gchar *script;
954         gsize l;
955
956         if (g_file_get_contents(scriptfile, &script, &l, NULL) && l)
957                 evalscript(c, "%s", script);
958         g_free(script);
959 }
960
961 void
962 evalscript(Client *c, const char *jsstr, ...)
963 {
964         va_list ap;
965         gchar *script;
966
967         va_start(ap, jsstr);
968         script = g_strdup_vprintf(jsstr, ap);
969         va_end(ap);
970
971         webkit_web_view_run_javascript(c->view, script, NULL, NULL, NULL);
972         g_free(script);
973 }
974
975 void
976 updatewinid(Client *c)
977 {
978         snprintf(winid, LENGTH(winid), "%lu", c->xid);
979 }
980
981 void
982 handleplumb(Client *c, const char *uri)
983 {
984         Arg a = (Arg)PLUMB(uri);
985         spawn(c, &a);
986 }
987
988 void
989 newwindow(Client *c, const Arg *a, int noembed)
990 {
991         int i = 0;
992         char tmp[64];
993         const char *cmd[29], *uri;
994         const Arg arg = { .v = cmd };
995
996         cmd[i++] = argv0;
997         cmd[i++] = "-a";
998         cmd[i++] = curconfig[CookiePolicies].val.v;
999         cmd[i++] = curconfig[ScrollBars].val.i ? "-B" : "-b";
1000         if (cookiefile && g_strcmp0(cookiefile, "")) {
1001                 cmd[i++] = "-c";
1002                 cmd[i++] = cookiefile;
1003         }
1004         if (stylefile && g_strcmp0(stylefile, "")) {
1005                 cmd[i++] = "-C";
1006                 cmd[i++] = stylefile;
1007         }
1008         cmd[i++] = curconfig[DiskCache].val.i ? "-D" : "-d";
1009         if (embed && !noembed) {
1010                 cmd[i++] = "-e";
1011                 snprintf(tmp, LENGTH(tmp), "%lu", embed);
1012                 cmd[i++] = tmp;
1013         }
1014         cmd[i++] = curconfig[RunInFullscreen].val.i ? "-F" : "-f" ;
1015         cmd[i++] = curconfig[Geolocation].val.i ?     "-G" : "-g" ;
1016         cmd[i++] = curconfig[LoadImages].val.i ?      "-I" : "-i" ;
1017         cmd[i++] = curconfig[KioskMode].val.i ?       "-K" : "-k" ;
1018         cmd[i++] = curconfig[Style].val.i ?           "-M" : "-m" ;
1019         cmd[i++] = curconfig[Inspector].val.i ?       "-N" : "-n" ;
1020         cmd[i++] = curconfig[Plugins].val.i ?         "-P" : "-p" ;
1021         if (scriptfile && g_strcmp0(scriptfile, "")) {
1022                 cmd[i++] = "-r";
1023                 cmd[i++] = scriptfile;
1024         }
1025         cmd[i++] = curconfig[JavaScript].val.i ? "-S" : "-s";
1026         cmd[i++] = curconfig[StrictTLS].val.i ? "-T" : "-t";
1027         if (fulluseragent && g_strcmp0(fulluseragent, "")) {
1028                 cmd[i++] = "-u";
1029                 cmd[i++] = fulluseragent;
1030         }
1031         if (showxid)
1032                 cmd[i++] = "-w";
1033         cmd[i++] = curconfig[Certificate].val.i ? "-X" : "-x" ;
1034         /* do not keep zoom level */
1035         cmd[i++] = "--";
1036         if ((uri = a->v))
1037                 cmd[i++] = uri;
1038         cmd[i] = NULL;
1039
1040         spawn(c, &arg);
1041 }
1042
1043 void
1044 spawn(Client *c, const Arg *a)
1045 {
1046         if (fork() == 0) {
1047                 if (dpy)
1048                         close(ConnectionNumber(dpy));
1049                 close(pipein[0]);
1050                 close(pipeout[1]);
1051                 setsid();
1052                 execvp(((char **)a->v)[0], (char **)a->v);
1053                 fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[0]);
1054                 perror(" failed");
1055                 exit(1);
1056         }
1057 }
1058
1059 void
1060 destroyclient(Client *c)
1061 {
1062         Client *p;
1063
1064         webkit_web_view_stop_loading(c->view);
1065         /* Not needed, has already been called
1066         gtk_widget_destroy(c->win);
1067          */
1068
1069         for (p = clients; p && p->next != c; p = p->next)
1070                 ;
1071         if (p)
1072                 p->next = c->next;
1073         else
1074                 clients = c->next;
1075         free(c);
1076 }
1077
1078 void
1079 cleanup(void)
1080 {
1081         while (clients)
1082                 destroyclient(clients);
1083
1084         close(pipein[0]);
1085         close(pipeout[1]);
1086         g_free(cookiefile);
1087         g_free(scriptfile);
1088         g_free(stylefile);
1089         g_free(cachedir);
1090         XCloseDisplay(dpy);
1091 }
1092
1093 WebKitWebView *
1094 newview(Client *c, WebKitWebView *rv)
1095 {
1096         WebKitWebView *v;
1097         WebKitSettings *settings;
1098         WebKitWebContext *context;
1099         WebKitCookieManager *cookiemanager;
1100         WebKitUserContentManager *contentmanager;
1101
1102         /* Webview */
1103         if (rv) {
1104                 v = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(rv));
1105         } else {
1106                 settings = webkit_settings_new_with_settings(
1107                    "allow-file-access-from-file-urls", curconfig[FileURLsCrossAccess].val.i,
1108                    "allow-universal-access-from-file-urls", curconfig[FileURLsCrossAccess].val.i,
1109                    "auto-load-images", curconfig[LoadImages].val.i,
1110                    "default-charset", curconfig[DefaultCharset].val.v,
1111                    "default-font-size", curconfig[FontSize].val.i,
1112                    "enable-caret-browsing", curconfig[CaretBrowsing].val.i,
1113                    "enable-developer-extras", curconfig[Inspector].val.i,
1114                    "enable-dns-prefetching", curconfig[DNSPrefetch].val.i,
1115                    "enable-frame-flattening", curconfig[FrameFlattening].val.i,
1116                    "enable-html5-database", curconfig[DiskCache].val.i,
1117                    "enable-html5-local-storage", curconfig[DiskCache].val.i,
1118                    "enable-java", curconfig[Java].val.i,
1119                    "enable-javascript", curconfig[JavaScript].val.i,
1120                    "enable-plugins", curconfig[Plugins].val.i,
1121                    "enable-accelerated-2d-canvas", curconfig[AcceleratedCanvas].val.i,
1122                    "enable-site-specific-quirks", curconfig[SiteQuirks].val.i,
1123                    "enable-smooth-scrolling", curconfig[SmoothScrolling].val.i,
1124                    "enable-webgl", curconfig[WebGL].val.i,
1125                    "media-playback-requires-user-gesture", curconfig[MediaManualPlay].val.i,
1126                    NULL);
1127 /* For more interesting settings, have a look at
1128  * http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html */
1129
1130                 if (strcmp(fulluseragent, "")) {
1131                         webkit_settings_set_user_agent(settings, fulluseragent);
1132                 } else if (surfuseragent) {
1133                         webkit_settings_set_user_agent_with_application_details(
1134                             settings, "Surf", VERSION);
1135                 }
1136                 useragent = webkit_settings_get_user_agent(settings);
1137
1138                 contentmanager = webkit_user_content_manager_new();
1139
1140                 context = webkit_web_context_new_with_website_data_manager(
1141                           webkit_website_data_manager_new(
1142                           "base-cache-directory", cachedir,
1143                           "base-data-directory", cachedir,
1144                           NULL));
1145
1146                 cookiemanager = webkit_web_context_get_cookie_manager(context);
1147
1148                 /* rendering process model, can be a shared unique one
1149                  * or one for each view */
1150                 webkit_web_context_set_process_model(context,
1151                     WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
1152                 /* TLS */
1153                 webkit_web_context_set_tls_errors_policy(context,
1154                     curconfig[StrictTLS].val.i ? WEBKIT_TLS_ERRORS_POLICY_FAIL :
1155                     WEBKIT_TLS_ERRORS_POLICY_IGNORE);
1156                 /* disk cache */
1157                 webkit_web_context_set_cache_model(context,
1158                     curconfig[DiskCache].val.i ? WEBKIT_CACHE_MODEL_WEB_BROWSER :
1159                     WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
1160
1161                 /* Currently only works with text file to be compatible with curl */
1162                 webkit_cookie_manager_set_persistent_storage(cookiemanager,
1163                     cookiefile, WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
1164                 /* cookie policy */
1165                 webkit_cookie_manager_set_accept_policy(cookiemanager,
1166                     cookiepolicy_get());
1167                 /* languages */
1168                 webkit_web_context_set_preferred_languages(context,
1169                     curconfig[PreferredLanguages].val.v);
1170                 webkit_web_context_set_spell_checking_languages(context,
1171                     curconfig[SpellLanguages].val.v);
1172                 webkit_web_context_set_spell_checking_enabled(context,
1173                     curconfig[SpellChecking].val.i);
1174
1175                 g_signal_connect(G_OBJECT(context), "download-started",
1176                                  G_CALLBACK(downloadstarted), c);
1177                 g_signal_connect(G_OBJECT(context), "initialize-web-extensions",
1178                                  G_CALLBACK(initwebextensions), c);
1179
1180                 v = g_object_new(WEBKIT_TYPE_WEB_VIEW,
1181                     "settings", settings,
1182                     "user-content-manager", contentmanager,
1183                     "web-context", context,
1184                     NULL);
1185         }
1186
1187         g_signal_connect(G_OBJECT(v), "notify::estimated-load-progress",
1188                          G_CALLBACK(progresschanged), c);
1189         g_signal_connect(G_OBJECT(v), "notify::title",
1190                          G_CALLBACK(titlechanged), c);
1191         g_signal_connect(G_OBJECT(v), "button-release-event",
1192                          G_CALLBACK(buttonreleased), c);
1193         g_signal_connect(G_OBJECT(v), "close",
1194                         G_CALLBACK(closeview), c);
1195         g_signal_connect(G_OBJECT(v), "create",
1196                          G_CALLBACK(createview), c);
1197         g_signal_connect(G_OBJECT(v), "decide-policy",
1198                          G_CALLBACK(decidepolicy), c);
1199         g_signal_connect(G_OBJECT(v), "insecure-content-detected",
1200                          G_CALLBACK(insecurecontent), c);
1201         g_signal_connect(G_OBJECT(v), "load-failed-with-tls-errors",
1202                          G_CALLBACK(loadfailedtls), c);
1203         g_signal_connect(G_OBJECT(v), "load-changed",
1204                          G_CALLBACK(loadchanged), c);
1205         g_signal_connect(G_OBJECT(v), "mouse-target-changed",
1206                          G_CALLBACK(mousetargetchanged), c);
1207         g_signal_connect(G_OBJECT(v), "permission-request",
1208                          G_CALLBACK(permissionrequested), c);
1209         g_signal_connect(G_OBJECT(v), "ready-to-show",
1210                          G_CALLBACK(showview), c);
1211         g_signal_connect(G_OBJECT(v), "web-process-terminated",
1212                          G_CALLBACK(webprocessterminated), c);
1213
1214         return v;
1215 }
1216
1217 static gboolean
1218 readpipe(GIOChannel *s, GIOCondition ioc, gpointer unused)
1219 {
1220         static char msg[MSGBUFSZ], msgsz;
1221         GError *gerr = NULL;
1222
1223         if (g_io_channel_read_chars(s, msg, sizeof(msg), NULL, &gerr) !=
1224             G_IO_STATUS_NORMAL) {
1225                 fprintf(stderr, "surf: error reading pipe: %s\n",
1226                         gerr->message);
1227                 g_error_free(gerr);
1228                 return TRUE;
1229         }
1230         if ((msgsz = msg[0]) < 3) {
1231                 fprintf(stderr, "surf: message too short: %d\n", msgsz);
1232                 return TRUE;
1233         }
1234
1235         switch (msg[2]) {
1236         case 'i':
1237                 close(pipein[1]);
1238                 close(pipeout[0]);
1239                 break;
1240         }
1241
1242         return TRUE;
1243 }
1244
1245 void
1246 initwebextensions(WebKitWebContext *wc, Client *c)
1247 {
1248         GVariant *gv;
1249
1250         if (!pipeout[0] || !pipein[1])
1251                 return;
1252
1253         gv = g_variant_new("(ii)", pipeout[0], pipein[1]);
1254
1255         webkit_web_context_set_web_extensions_initialization_user_data(wc, gv);
1256         webkit_web_context_set_web_extensions_directory(wc, WEBEXTDIR);
1257 }
1258
1259 GtkWidget *
1260 createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c)
1261 {
1262         Client *n;
1263
1264         switch (webkit_navigation_action_get_navigation_type(a)) {
1265         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1266                 /*
1267                  * popup windows of type “other” are almost always triggered
1268                  * by user gesture, so inverse the logic here
1269                  */
1270 /* instead of this, compare destination uri to mouse-over uri for validating window */
1271                 if (webkit_navigation_action_is_user_gesture(a))
1272                         return NULL;
1273         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1274         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1275         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1276         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1277         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1278                 n = newclient(c);
1279                 break;
1280         default:
1281                 return NULL;
1282         }
1283
1284         return GTK_WIDGET(n->view);
1285 }
1286
1287 gboolean
1288 buttonreleased(GtkWidget *w, GdkEvent *e, Client *c)
1289 {
1290         WebKitHitTestResultContext element;
1291         int i;
1292
1293         element = webkit_hit_test_result_get_context(c->mousepos);
1294
1295         for (i = 0; i < LENGTH(buttons); ++i) {
1296                 if (element & buttons[i].target &&
1297                     e->button.button == buttons[i].button &&
1298                     CLEANMASK(e->button.state) == CLEANMASK(buttons[i].mask) &&
1299                     buttons[i].func) {
1300                         buttons[i].func(c, &buttons[i].arg, c->mousepos);
1301                         return buttons[i].stopevent;
1302                 }
1303         }
1304
1305         return FALSE;
1306 }
1307
1308 GdkFilterReturn
1309 processx(GdkXEvent *e, GdkEvent *event, gpointer d)
1310 {
1311         Client *c = (Client *)d;
1312         XPropertyEvent *ev;
1313         Arg a;
1314
1315         if (((XEvent *)e)->type == PropertyNotify) {
1316                 ev = &((XEvent *)e)->xproperty;
1317                 if (ev->state == PropertyNewValue) {
1318                         if (ev->atom == atoms[AtomFind]) {
1319                                 find(c, NULL);
1320
1321                                 return GDK_FILTER_REMOVE;
1322                         } else if (ev->atom == atoms[AtomGo]) {
1323                                 a.v = getatom(c, AtomGo);
1324                                 loaduri(c, &a);
1325
1326                                 return GDK_FILTER_REMOVE;
1327                         }
1328                 }
1329         }
1330         return GDK_FILTER_CONTINUE;
1331 }
1332
1333 gboolean
1334 winevent(GtkWidget *w, GdkEvent *e, Client *c)
1335 {
1336         int i;
1337
1338         switch (e->type) {
1339         case GDK_ENTER_NOTIFY:
1340                 c->overtitle = c->targeturi;
1341                 updatetitle(c);
1342                 break;
1343         case GDK_KEY_PRESS:
1344                 if (!curconfig[KioskMode].val.i) {
1345                         for (i = 0; i < LENGTH(keys); ++i) {
1346                                 if (gdk_keyval_to_lower(e->key.keyval) ==
1347                                     keys[i].keyval &&
1348                                     CLEANMASK(e->key.state) == keys[i].mod &&
1349                                     keys[i].func) {
1350                                         updatewinid(c);
1351                                         keys[i].func(c, &(keys[i].arg));
1352                                         return TRUE;
1353                                 }
1354                         }
1355                 }
1356         case GDK_LEAVE_NOTIFY:
1357                 c->overtitle = NULL;
1358                 updatetitle(c);
1359                 break;
1360         case GDK_WINDOW_STATE:
1361                 if (e->window_state.changed_mask ==
1362                     GDK_WINDOW_STATE_FULLSCREEN)
1363                         c->fullscreen = e->window_state.new_window_state &
1364                                         GDK_WINDOW_STATE_FULLSCREEN;
1365                 break;
1366         default:
1367                 break;
1368         }
1369
1370         return FALSE;
1371 }
1372
1373 void
1374 showview(WebKitWebView *v, Client *c)
1375 {
1376         GdkRGBA bgcolor = { 0 };
1377         GdkWindow *gwin;
1378
1379         c->finder = webkit_web_view_get_find_controller(c->view);
1380         c->inspector = webkit_web_view_get_inspector(c->view);
1381
1382         c->pageid = webkit_web_view_get_page_id(c->view);
1383         c->win = createwindow(c);
1384
1385         gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
1386         gtk_widget_show_all(c->win);
1387         gtk_widget_grab_focus(GTK_WIDGET(c->view));
1388
1389         gwin = gtk_widget_get_window(GTK_WIDGET(c->win));
1390         c->xid = gdk_x11_window_get_xid(gwin);
1391         updatewinid(c);
1392         if (showxid) {
1393                 gdk_display_sync(gtk_widget_get_display(c->win));
1394                 puts(winid);
1395                 fflush(stdout);
1396         }
1397
1398         if (curconfig[HideBackground].val.i)
1399                 webkit_web_view_set_background_color(c->view, &bgcolor);
1400
1401         if (!curconfig[KioskMode].val.i) {
1402                 gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK);
1403                 gdk_window_add_filter(gwin, processx, c);
1404         }
1405
1406         if (curconfig[RunInFullscreen].val.i)
1407                 togglefullscreen(c, NULL);
1408
1409         if (curconfig[ZoomLevel].val.f != 1.0)
1410                 webkit_web_view_set_zoom_level(c->view,
1411                                                curconfig[ZoomLevel].val.f);
1412
1413         setatom(c, AtomFind, "");
1414         setatom(c, AtomUri, "about:blank");
1415 }
1416
1417 GtkWidget *
1418 createwindow(Client *c)
1419 {
1420         char *wmstr;
1421         GtkWidget *w;
1422
1423         if (embed) {
1424                 w = gtk_plug_new(embed);
1425         } else {
1426                 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1427
1428                 wmstr = g_path_get_basename(argv0);
1429                 gtk_window_set_wmclass(GTK_WINDOW(w), wmstr, "Surf");
1430                 g_free(wmstr);
1431
1432                 wmstr = g_strdup_printf("%s[%lu]", "Surf", c->pageid);
1433                 gtk_window_set_role(GTK_WINDOW(w), wmstr);
1434                 g_free(wmstr);
1435
1436                 gtk_window_set_default_size(GTK_WINDOW(w), winsize[0], winsize[1]);
1437         }
1438
1439         g_signal_connect(G_OBJECT(w), "destroy",
1440                          G_CALLBACK(destroywin), c);
1441         g_signal_connect(G_OBJECT(w), "enter-notify-event",
1442                          G_CALLBACK(winevent), c);
1443         g_signal_connect(G_OBJECT(w), "key-press-event",
1444                          G_CALLBACK(winevent), c);
1445         g_signal_connect(G_OBJECT(w), "leave-notify-event",
1446                          G_CALLBACK(winevent), c);
1447         g_signal_connect(G_OBJECT(w), "window-state-event",
1448                          G_CALLBACK(winevent), c);
1449
1450         return w;
1451 }
1452
1453 gboolean
1454 loadfailedtls(WebKitWebView *v, gchar *uri, GTlsCertificate *cert,
1455               GTlsCertificateFlags err, Client *c)
1456 {
1457         GString *errmsg = g_string_new(NULL);
1458         gchar *html, *pem;
1459
1460         c->failedcert = g_object_ref(cert);
1461         c->tlserr = err;
1462         c->errorpage = 1;
1463
1464         if (err & G_TLS_CERTIFICATE_UNKNOWN_CA)
1465                 g_string_append(errmsg,
1466                     "The signing certificate authority is not known.<br>");
1467         if (err & G_TLS_CERTIFICATE_BAD_IDENTITY)
1468                 g_string_append(errmsg,
1469                     "The certificate does not match the expected identity "
1470                     "of the site that it was retrieved from.<br>");
1471         if (err & G_TLS_CERTIFICATE_NOT_ACTIVATED)
1472                 g_string_append(errmsg,
1473                     "The certificate's activation time "
1474                     "is still in the future.<br>");
1475         if (err & G_TLS_CERTIFICATE_EXPIRED)
1476                 g_string_append(errmsg, "The certificate has expired.<br>");
1477         if (err & G_TLS_CERTIFICATE_REVOKED)
1478                 g_string_append(errmsg,
1479                     "The certificate has been revoked according to "
1480                     "the GTlsConnection's certificate revocation list.<br>");
1481         if (err & G_TLS_CERTIFICATE_INSECURE)
1482                 g_string_append(errmsg,
1483                     "The certificate's algorithm is considered insecure.<br>");
1484         if (err & G_TLS_CERTIFICATE_GENERIC_ERROR)
1485                 g_string_append(errmsg,
1486                     "Some error occurred validating the certificate.<br>");
1487
1488         g_object_get(cert, "certificate-pem", &pem, NULL);
1489         html = g_strdup_printf("<p>Could not validate TLS for “%s”<br>%s</p>"
1490                                "<p>You can inspect the following certificate "
1491                                "with Ctrl-t (default keybinding).</p>"
1492                                "<p><pre>%s</pre></p>", uri, errmsg->str, pem);
1493         g_free(pem);
1494         g_string_free(errmsg, TRUE);
1495
1496         webkit_web_view_load_alternate_html(c->view, html, uri, NULL);
1497         g_free(html);
1498
1499         return TRUE;
1500 }
1501
1502 void
1503 loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
1504 {
1505         const char *uri = geturi(c);
1506
1507         switch (e) {
1508         case WEBKIT_LOAD_STARTED:
1509                 setatom(c, AtomUri, uri);
1510                 c->title = uri;
1511                 c->https = c->insecure = 0;
1512                 seturiparameters(c, uri, loadtransient);
1513                 if (c->errorpage)
1514                         c->errorpage = 0;
1515                 else
1516                         g_clear_object(&c->failedcert);
1517                 break;
1518         case WEBKIT_LOAD_REDIRECTED:
1519                 setatom(c, AtomUri, uri);
1520                 c->title = uri;
1521                 seturiparameters(c, uri, loadtransient);
1522                 break;
1523         case WEBKIT_LOAD_COMMITTED:
1524                 seturiparameters(c, uri, loadcommitted);
1525                 c->https = webkit_web_view_get_tls_info(c->view, &c->cert,
1526                                                         &c->tlserr);
1527                 break;
1528         case WEBKIT_LOAD_FINISHED:
1529                 seturiparameters(c, uri, loadfinished);
1530                 /* Disabled until we write some WebKitWebExtension for
1531                  * manipulating the DOM directly.
1532                 evalscript(c, "document.documentElement.style.overflow = '%s'",
1533                     enablescrollbars ? "auto" : "hidden");
1534                 */
1535                 runscript(c);
1536                 break;
1537         }
1538         updatetitle(c);
1539 }
1540
1541 void
1542 progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c)
1543 {
1544         c->progress = webkit_web_view_get_estimated_load_progress(c->view) *
1545                       100;
1546         updatetitle(c);
1547 }
1548
1549 void
1550 titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c)
1551 {
1552         c->title = webkit_web_view_get_title(c->view);
1553         updatetitle(c);
1554 }
1555
1556 void
1557 mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modifiers,
1558     Client *c)
1559 {
1560         WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h);
1561
1562         /* Keep the hit test to know where is the pointer on the next click */
1563         c->mousepos = h;
1564
1565         if (hc & OnLink)
1566                 c->targeturi = webkit_hit_test_result_get_link_uri(h);
1567         else if (hc & OnImg)
1568                 c->targeturi = webkit_hit_test_result_get_image_uri(h);
1569         else if (hc & OnMedia)
1570                 c->targeturi = webkit_hit_test_result_get_media_uri(h);
1571         else
1572                 c->targeturi = NULL;
1573
1574         c->overtitle = c->targeturi;
1575         updatetitle(c);
1576 }
1577
1578 gboolean
1579 permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client *c)
1580 {
1581         ParamName param = ParameterLast;
1582
1583         if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
1584                 param = Geolocation;
1585         } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(r)) {
1586                 if (webkit_user_media_permission_is_for_audio_device(
1587                     WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r)))
1588                         param = AccessMicrophone;
1589                 else if (webkit_user_media_permission_is_for_video_device(
1590                          WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r)))
1591                         param = AccessWebcam;
1592         } else {
1593                 return FALSE;
1594         }
1595
1596         if (curconfig[param].val.i)
1597                 webkit_permission_request_allow(r);
1598         else
1599                 webkit_permission_request_deny(r);
1600
1601         return TRUE;
1602 }
1603
1604 gboolean
1605 decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
1606     WebKitPolicyDecisionType dt, Client *c)
1607 {
1608         switch (dt) {
1609         case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
1610                 decidenavigation(d, c);
1611                 break;
1612         case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
1613                 decidenewwindow(d, c);
1614                 break;
1615         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
1616                 decideresource(d, c);
1617                 break;
1618         default:
1619                 webkit_policy_decision_ignore(d);
1620                 break;
1621         }
1622         return TRUE;
1623 }
1624
1625 void
1626 decidenavigation(WebKitPolicyDecision *d, Client *c)
1627 {
1628         WebKitNavigationAction *a =
1629             webkit_navigation_policy_decision_get_navigation_action(
1630             WEBKIT_NAVIGATION_POLICY_DECISION(d));
1631
1632         switch (webkit_navigation_action_get_navigation_type(a)) {
1633         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1634         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1635         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1636         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1637         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: /* fallthrough */
1638         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1639         default:
1640                 /* Do not navigate to links with a "_blank" target (popup) */
1641                 if (webkit_navigation_policy_decision_get_frame_name(
1642                     WEBKIT_NAVIGATION_POLICY_DECISION(d))) {
1643                         webkit_policy_decision_ignore(d);
1644                 } else {
1645                         /* Filter out navigation to different domain ? */
1646                         /* get action→urirequest, copy and load in new window+view
1647                          * on Ctrl+Click ? */
1648                         webkit_policy_decision_use(d);
1649                 }
1650                 break;
1651         }
1652 }
1653
1654 void
1655 decidenewwindow(WebKitPolicyDecision *d, Client *c)
1656 {
1657         Arg arg;
1658         WebKitNavigationAction *a =
1659             webkit_navigation_policy_decision_get_navigation_action(
1660             WEBKIT_NAVIGATION_POLICY_DECISION(d));
1661
1662
1663         switch (webkit_navigation_action_get_navigation_type(a)) {
1664         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1665         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1666         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1667         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1668         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1669                 /* Filter domains here */
1670 /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
1671  * test for link clicked but no button ? */
1672                 arg.v = webkit_uri_request_get_uri(
1673                         webkit_navigation_action_get_request(a));
1674                 newwindow(c, &arg, 0);
1675                 break;
1676         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1677         default:
1678                 break;
1679         }
1680
1681         webkit_policy_decision_ignore(d);
1682 }
1683
1684 void
1685 decideresource(WebKitPolicyDecision *d, Client *c)
1686 {
1687         int i, isascii = 1;
1688         WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISION(d);
1689         WebKitURIResponse *res =
1690             webkit_response_policy_decision_get_response(r);
1691         const gchar *uri = webkit_uri_response_get_uri(res);
1692
1693         if (g_str_has_suffix(uri, "/favicon.ico")) {
1694                 webkit_policy_decision_ignore(d);
1695                 return;
1696         }
1697
1698         if (!g_str_has_prefix(uri, "http://")
1699             && !g_str_has_prefix(uri, "https://")
1700             && !g_str_has_prefix(uri, "about:")
1701             && !g_str_has_prefix(uri, "file://")
1702             && !g_str_has_prefix(uri, "data:")
1703             && !g_str_has_prefix(uri, "blob:")
1704             && strlen(uri) > 0) {
1705                 for (i = 0; i < strlen(uri); i++) {
1706                         if (!g_ascii_isprint(uri[i])) {
1707                                 isascii = 0;
1708                                 break;
1709                         }
1710                 }
1711                 if (isascii) {
1712                         handleplumb(c, uri);
1713                         webkit_policy_decision_ignore(d);
1714                         return;
1715                 }
1716         }
1717
1718         if (webkit_response_policy_decision_is_mime_type_supported(r)) {
1719                 webkit_policy_decision_use(d);
1720         } else {
1721                 webkit_policy_decision_ignore(d);
1722                 download(c, res);
1723         }
1724 }
1725
1726 void
1727 insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e, Client *c)
1728 {
1729         c->insecure = 1;
1730 }
1731
1732 void
1733 downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c)
1734 {
1735         g_signal_connect(G_OBJECT(d), "notify::response",
1736                          G_CALLBACK(responsereceived), c);
1737 }
1738
1739 void
1740 responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c)
1741 {
1742         download(c, webkit_download_get_response(d));
1743         webkit_download_cancel(d);
1744 }
1745
1746 void
1747 download(Client *c, WebKitURIResponse *r)
1748 {
1749         Arg a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
1750         spawn(c, &a);
1751 }
1752
1753 void
1754 webprocessterminated(WebKitWebView *v, WebKitWebProcessTerminationReason r,
1755                      Client *c)
1756 {
1757         fprintf(stderr, "web process terminated: %s\n",
1758                 r == WEBKIT_WEB_PROCESS_CRASHED ? "crashed" : "no memory");
1759         closeview(v, c);
1760 }
1761
1762 void
1763 closeview(WebKitWebView *v, Client *c)
1764 {
1765         gtk_widget_destroy(c->win);
1766 }
1767
1768 void
1769 destroywin(GtkWidget* w, Client *c)
1770 {
1771         destroyclient(c);
1772         if (!clients)
1773                 gtk_main_quit();
1774 }
1775
1776 gchar *
1777 parseuri(const gchar *uri) {
1778    guint i;
1779
1780    for (i = 0; i < LENGTH(searchengines); i++) {
1781        if (searchengines[i].token == NULL || searchengines[i].uri == NULL ||
1782            *(uri + strlen(searchengines[i].token)) != ' ')
1783            continue;
1784        if (g_str_has_prefix(uri, searchengines[i].token))
1785            return g_strdup_printf(searchengines[i].uri,
1786                           uri + strlen(searchengines[i].token) + 1);
1787    }
1788
1789    return g_strdup_printf("http://%s", uri);
1790 }
1791
1792 void
1793 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
1794 {
1795         Arg a = {.v = text };
1796         if (text)
1797                 loaduri((Client *) d, &a);
1798 }
1799
1800 void
1801 reload(Client *c, const Arg *a)
1802 {
1803         if (a->i)
1804                 webkit_web_view_reload_bypass_cache(c->view);
1805         else
1806                 webkit_web_view_reload(c->view);
1807 }
1808
1809 void
1810 print(Client *c, const Arg *a)
1811 {
1812         webkit_print_operation_run_dialog(webkit_print_operation_new(c->view),
1813                                           GTK_WINDOW(c->win));
1814 }
1815
1816 void
1817 showcert(Client *c, const Arg *a)
1818 {
1819         GTlsCertificate *cert = c->failedcert ? c->failedcert : c->cert;
1820         GcrCertificate *gcrt;
1821         GByteArray *crt;
1822         GtkWidget *win;
1823         GcrCertificateWidget *wcert;
1824
1825         if (!cert)
1826                 return;
1827
1828         g_object_get(cert, "certificate", &crt, NULL);
1829         gcrt = gcr_simple_certificate_new(crt->data, crt->len);
1830         g_byte_array_unref(crt);
1831
1832         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1833         wcert = gcr_certificate_widget_new(gcrt);
1834         g_object_unref(gcrt);
1835
1836         gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(wcert));
1837         gtk_widget_show_all(win);
1838 }
1839
1840 void
1841 clipboard(Client *c, const Arg *a)
1842 {
1843         if (a->i) { /* load clipboard uri */
1844                 gtk_clipboard_request_text(gtk_clipboard_get(
1845                                            GDK_SELECTION_PRIMARY),
1846                                            pasteuri, c);
1847         } else { /* copy uri */
1848                 gtk_clipboard_set_text(gtk_clipboard_get(
1849                                        GDK_SELECTION_PRIMARY), c->targeturi
1850                                        ? c->targeturi : geturi(c), -1);
1851         }
1852 }
1853
1854 void
1855 zoom(Client *c, const Arg *a)
1856 {
1857         if (a->i > 0)
1858                 webkit_web_view_set_zoom_level(c->view,
1859                                                curconfig[ZoomLevel].val.f + 0.1);
1860         else if (a->i < 0)
1861                 webkit_web_view_set_zoom_level(c->view,
1862                                                curconfig[ZoomLevel].val.f - 0.1);
1863         else
1864                 webkit_web_view_set_zoom_level(c->view, 1.0);
1865
1866         curconfig[ZoomLevel].val.f = webkit_web_view_get_zoom_level(c->view);
1867 }
1868
1869 static void
1870 msgext(Client *c, char type, const Arg *a)
1871 {
1872         static char msg[MSGBUFSZ];
1873         int ret;
1874
1875         if ((ret = snprintf(msg, sizeof(msg), "%c%c%c%c",
1876                             4, c->pageid, type, a->i))
1877             >= sizeof(msg)) {
1878                 fprintf(stderr, "surf: message too long: %d\n", ret);
1879                 return;
1880         }
1881
1882         if (pipeout[1] && write(pipeout[1], msg, sizeof(msg)) < 0)
1883                 fprintf(stderr, "surf: error sending: %.*s\n", ret-2, msg+2);
1884 }
1885
1886 void
1887 scrollv(Client *c, const Arg *a)
1888 {
1889         msgext(c, 'v', a);
1890 }
1891
1892 void
1893 scrollh(Client *c, const Arg *a)
1894 {
1895         msgext(c, 'h', a);
1896 }
1897
1898 void
1899 navigate(Client *c, const Arg *a)
1900 {
1901         if (a->i < 0)
1902                 webkit_web_view_go_back(c->view);
1903         else if (a->i > 0)
1904                 webkit_web_view_go_forward(c->view);
1905 }
1906
1907 void
1908 stop(Client *c, const Arg *a)
1909 {
1910         webkit_web_view_stop_loading(c->view);
1911 }
1912
1913 void
1914 toggle(Client *c, const Arg *a)
1915 {
1916         curconfig[a->i].val.i ^= 1;
1917         setparameter(c, 1, (ParamName)a->i, &curconfig[a->i].val);
1918 }
1919
1920 void
1921 togglefullscreen(Client *c, const Arg *a)
1922 {
1923         /* toggling value is handled in winevent() */
1924         if (c->fullscreen)
1925                 gtk_window_unfullscreen(GTK_WINDOW(c->win));
1926         else
1927                 gtk_window_fullscreen(GTK_WINDOW(c->win));
1928 }
1929
1930 void
1931 togglecookiepolicy(Client *c, const Arg *a)
1932 {
1933         ++cookiepolicy;
1934         cookiepolicy %= strlen(curconfig[CookiePolicies].val.v);
1935
1936         setparameter(c, 0, CookiePolicies, NULL);
1937 }
1938
1939 void
1940 toggleinspector(Client *c, const Arg *a)
1941 {
1942         if (webkit_web_inspector_is_attached(c->inspector))
1943                 webkit_web_inspector_close(c->inspector);
1944         else if (curconfig[Inspector].val.i)
1945                 webkit_web_inspector_show(c->inspector);
1946 }
1947
1948 void
1949 find(Client *c, const Arg *a)
1950 {
1951         const char *s, *f;
1952
1953         if (a && a->i) {
1954                 if (a->i > 0)
1955                         webkit_find_controller_search_next(c->finder);
1956                 else
1957                         webkit_find_controller_search_previous(c->finder);
1958         } else {
1959                 s = getatom(c, AtomFind);
1960                 f = webkit_find_controller_get_search_text(c->finder);
1961
1962                 if (g_strcmp0(f, s) == 0) /* reset search */
1963                         webkit_find_controller_search(c->finder, "", findopts,
1964                                                       G_MAXUINT);
1965
1966                 webkit_find_controller_search(c->finder, s, findopts,
1967                                               G_MAXUINT);
1968
1969                 if (strcmp(s, "") == 0)
1970                         webkit_find_controller_search_finish(c->finder);
1971         }
1972 }
1973
1974 void
1975 clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h)
1976 {
1977         navigate(c, a);
1978 }
1979
1980 void
1981 clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h)
1982 {
1983         Arg arg;
1984
1985         arg.v = webkit_hit_test_result_get_link_uri(h);
1986         newwindow(c, &arg, a->i);
1987 }
1988
1989 void
1990 clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h)
1991 {
1992         Arg arg;
1993
1994         arg = (Arg)VIDEOPLAY(webkit_hit_test_result_get_media_uri(h));
1995         spawn(c, &arg);
1996 }
1997
1998 void
1999 playexternal(Client *c, const Arg *a)
2000 {
2001         Arg arg;
2002
2003         arg = (Arg)VIDEOPLAY(geturi(c));
2004         spawn(c, &arg);
2005 }
2006
2007 int
2008 main(int argc, char *argv[])
2009 {
2010         Arg arg;
2011         Client *c;
2012
2013         memset(&arg, 0, sizeof(arg));
2014
2015         /* command line args */
2016         ARGBEGIN {
2017         case 'a':
2018                 defconfig[CookiePolicies].val.v = EARGF(usage());
2019                 defconfig[CookiePolicies].prio = 2;
2020                 break;
2021         case 'b':
2022                 defconfig[ScrollBars].val.i = 0;
2023                 defconfig[ScrollBars].prio = 2;
2024                 break;
2025         case 'B':
2026                 defconfig[ScrollBars].val.i = 1;
2027                 defconfig[ScrollBars].prio = 2;
2028                 break;
2029         case 'c':
2030                 cookiefile = EARGF(usage());
2031                 break;
2032         case 'C':
2033                 stylefile = EARGF(usage());
2034                 break;
2035         case 'd':
2036                 defconfig[DiskCache].val.i = 0;
2037                 defconfig[DiskCache].prio = 2;
2038                 break;
2039         case 'D':
2040                 defconfig[DiskCache].val.i = 1;
2041                 defconfig[DiskCache].prio = 2;
2042                 break;
2043         case 'e':
2044                 embed = strtol(EARGF(usage()), NULL, 0);
2045                 break;
2046         case 'f':
2047                 defconfig[RunInFullscreen].val.i = 0;
2048                 defconfig[RunInFullscreen].prio = 2;
2049                 break;
2050         case 'F':
2051                 defconfig[RunInFullscreen].val.i = 1;
2052                 defconfig[RunInFullscreen].prio = 2;
2053                 break;
2054         case 'g':
2055                 defconfig[Geolocation].val.i = 0;
2056                 defconfig[Geolocation].prio = 2;
2057                 break;
2058         case 'G':
2059                 defconfig[Geolocation].val.i = 1;
2060                 defconfig[Geolocation].prio = 2;
2061                 break;
2062         case 'i':
2063                 defconfig[LoadImages].val.i = 0;
2064                 defconfig[LoadImages].prio = 2;
2065                 break;
2066         case 'I':
2067                 defconfig[LoadImages].val.i = 1;
2068                 defconfig[LoadImages].prio = 2;
2069                 break;
2070         case 'k':
2071                 defconfig[KioskMode].val.i = 0;
2072                 defconfig[KioskMode].prio = 2;
2073                 break;
2074         case 'K':
2075                 defconfig[KioskMode].val.i = 1;
2076                 defconfig[KioskMode].prio = 2;
2077                 break;
2078         case 'm':
2079                 defconfig[Style].val.i = 0;
2080                 defconfig[Style].prio = 2;
2081                 break;
2082         case 'M':
2083                 defconfig[Style].val.i = 1;
2084                 defconfig[Style].prio = 2;
2085                 break;
2086         case 'n':
2087                 defconfig[Inspector].val.i = 0;
2088                 defconfig[Inspector].prio = 2;
2089                 break;
2090         case 'N':
2091                 defconfig[Inspector].val.i = 1;
2092                 defconfig[Inspector].prio = 2;
2093                 break;
2094         case 'p':
2095                 defconfig[Plugins].val.i = 0;
2096                 defconfig[Plugins].prio = 2;
2097                 break;
2098         case 'P':
2099                 defconfig[Plugins].val.i = 1;
2100                 defconfig[Plugins].prio = 2;
2101                 break;
2102         case 'r':
2103                 scriptfile = EARGF(usage());
2104                 break;
2105         case 's':
2106                 defconfig[JavaScript].val.i = 0;
2107                 defconfig[JavaScript].prio = 2;
2108                 break;
2109         case 'S':
2110                 defconfig[JavaScript].val.i = 1;
2111                 defconfig[JavaScript].prio = 2;
2112                 break;
2113         case 't':
2114                 defconfig[StrictTLS].val.i = 0;
2115                 defconfig[StrictTLS].prio = 2;
2116                 break;
2117         case 'T':
2118                 defconfig[StrictTLS].val.i = 1;
2119                 defconfig[StrictTLS].prio = 2;
2120                 break;
2121         case 'u':
2122                 fulluseragent = EARGF(usage());
2123                 break;
2124         case 'v':
2125                 die("surf-"VERSION", see LICENSE for © details\n");
2126         case 'w':
2127                 showxid = 1;
2128                 break;
2129         case 'x':
2130                 defconfig[Certificate].val.i = 0;
2131                 defconfig[Certificate].prio = 2;
2132                 break;
2133         case 'X':
2134                 defconfig[Certificate].val.i = 1;
2135                 defconfig[Certificate].prio = 2;
2136                 break;
2137         case 'z':
2138                 defconfig[ZoomLevel].val.f = strtof(EARGF(usage()), NULL);
2139                 defconfig[ZoomLevel].prio = 2;
2140                 break;
2141         default:
2142                 usage();
2143         } ARGEND;
2144         if (argc > 0)
2145                 arg.v = argv[0];
2146         else
2147 #ifdef HOMEPAGE
2148                 arg.v = HOMEPAGE;
2149 #else
2150                 arg.v = "about:blank";
2151 #endif
2152
2153         setup();
2154         c = newclient(NULL);
2155         showview(NULL, c);
2156
2157         loaduri(c, &arg);
2158         updatetitle(c);
2159
2160         gtk_main();
2161         cleanup();
2162
2163         return 0;
2164 }