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