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