Introduction
Introduction Statistics Contact Development Disclaimer Help
surf.c - surf - surf browser, a WebKit based browser
git clone git://git.suckless.org/surf
Log
Files
Refs
README
LICENSE
---
surf.c (53414B)
---
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 <gio/gunixfdlist.h>
25 #include <glib/gstdio.h>
26 #include <gtk/gtk.h>
27 #include <gtk/gtkx.h>
28 #include <gcr/gcr.h>
29 #include <JavaScriptCore/JavaScript.h>
30 #include <webkit2/webkit2.h>
31 #include <X11/X.h>
32 #include <X11/Xatom.h>
33 #include <glib.h>
34
35 #include "arg.h"
36 #include "common.h"
37
38 #define LENGTH(x) (sizeof(x) / sizeof(x[0]))
39 #define CLEANMASK(mask) (mask & (MODKEY|GDK_SHIFT_MASK))
40
41 enum { AtomFind, AtomGo, AtomUri, AtomUTF8, AtomLast };
42
43 enum {
44 OnDoc = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
45 OnLink = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
46 OnImg = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
47 OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
48 OnEdit = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
49 OnBar = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR,
50 OnSel = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
51 OnAny = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | On…
52 };
53
54 typedef enum {
55 AccessMicrophone,
56 AccessWebcam,
57 CaretBrowsing,
58 Certificate,
59 CookiePolicies,
60 DarkMode,
61 DiskCache,
62 DefaultCharset,
63 DNSPrefetch,
64 Ephemeral,
65 FileURLsCrossAccess,
66 FontSize,
67 Geolocation,
68 HideBackground,
69 Inspector,
70 JavaScript,
71 KioskMode,
72 LoadImages,
73 MediaManualPlay,
74 PDFJSviewer,
75 PreferredLanguages,
76 RunInFullscreen,
77 ScrollBars,
78 ShowIndicators,
79 SiteQuirks,
80 SmoothScrolling,
81 SpellChecking,
82 SpellLanguages,
83 StrictTLS,
84 Style,
85 WebGL,
86 ZoomLevel,
87 ParameterLast
88 } ParamName;
89
90 typedef union {
91 int i;
92 float f;
93 const void *v;
94 } Arg;
95
96 typedef struct {
97 Arg val;
98 int prio;
99 } Parameter;
100
101 typedef struct Client {
102 GtkWidget *win;
103 WebKitWebView *view;
104 WebKitSettings *settings;
105 WebKitWebContext *context;
106 WebKitWebInspector *inspector;
107 WebKitFindController *finder;
108 WebKitHitTestResult *mousepos;
109 GTlsCertificate *cert, *failedcert;
110 GTlsCertificateFlags tlserr;
111 Window xid;
112 guint64 pageid;
113 int progress, fullscreen, https, insecure, errorpage;
114 const char *title, *overtitle, *targeturi;
115 const char *needle;
116 struct Client *next;
117 } Client;
118
119 typedef struct {
120 guint mod;
121 guint keyval;
122 void (*func)(Client *c, const Arg *a);
123 const Arg arg;
124 } Key;
125
126 typedef struct {
127 unsigned int target;
128 unsigned int mask;
129 guint button;
130 void (*func)(Client *c, const Arg *a, WebKitHitTestResult *h);
131 const Arg arg;
132 unsigned int stopevent;
133 } Button;
134
135 typedef struct {
136 const char *uri;
137 Parameter config[ParameterLast];
138 regex_t re;
139 } UriParameters;
140
141 typedef struct {
142 char *regex;
143 char *file;
144 regex_t re;
145 } SiteSpecific;
146
147 /* Surf */
148 static void die(const char *errstr, ...);
149 static void usage(void);
150 static void setup(void);
151 static void sigchld(int unused);
152 static void sighup(int unused);
153 static char *buildfile(const char *path);
154 static char *buildpath(const char *path);
155 static char *untildepath(const char *path);
156 static const char *getuserhomedir(const char *user);
157 static const char *getcurrentuserhomedir(void);
158 static Client *newclient(Client *c);
159 static void loaduri(Client *c, const Arg *a);
160 static const char *geturi(Client *c);
161 static void setatom(Client *c, int a, const char *v);
162 static const char *getatom(Client *c, int a);
163 static void updatetitle(Client *c);
164 static void gettogglestats(Client *c);
165 static void getpagestats(Client *c);
166 static WebKitCookieAcceptPolicy cookiepolicy_get(void);
167 static char cookiepolicy_set(const WebKitCookieAcceptPolicy p);
168 static void seturiparameters(Client *c, const char *uri, ParamName *para…
169 static void setparameter(Client *c, int refresh, ParamName p, const Arg …
170 static const char *getcert(const char *uri);
171 static void setcert(Client *c, const char *file);
172 static const char *getstyle(const char *uri);
173 static void setstyle(Client *c, const char *file);
174 static void runscript(Client *c);
175 static void evalscript(Client *c, const char *jsstr, ...);
176 static void updatewinid(Client *c);
177 static void handleplumb(Client *c, const char *uri);
178 static void newwindow(Client *c, const Arg *a, int noembed);
179 static void spawn(Client *c, const Arg *a);
180 static void msgext(Client *c, char type, const Arg *a);
181 static void destroyclient(Client *c);
182 static void cleanup(void);
183
184 /* GTK/WebKit */
185 static WebKitWebView *newview(Client *c, WebKitWebView *rv);
186 static void initwebextensions(WebKitWebContext *wc, Client *c);
187 static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a,
188 Client *c);
189 static gboolean buttonreleased(GtkWidget *w, GdkEvent *e, Client *c);
190 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
191 gpointer d);
192 static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c);
193 static void showview(WebKitWebView *v, Client *c);
194 static GtkWidget *createwindow(Client *c);
195 static gboolean loadfailedtls(WebKitWebView *v, gchar *uri,
196 GTlsCertificate *cert,
197 GTlsCertificateFlags err, Client *c);
198 static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c);
199 static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c);
200 static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c);
201 static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
202 guint modifiers, Client *c);
203 static gboolean permissionrequested(WebKitWebView *v,
204 WebKitPermissionRequest *r, Client *…
205 static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
206 WebKitPolicyDecisionType dt, Client *c);
207 static void decidenavigation(WebKitPolicyDecision *d, Client *c);
208 static void decidenewwindow(WebKitPolicyDecision *d, Client *c);
209 static void decideresource(WebKitPolicyDecision *d, Client *c);
210 static void insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent…
211 Client *c);
212 static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d,
213 Client *c);
214 static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *…
215 static void download(Client *c, WebKitURIResponse *r);
216 static gboolean viewusrmsgrcv(WebKitWebView *v, WebKitUserMessage *m,
217 gpointer u);
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…
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 *…
243 static void clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult …
244 static void clickexternplayer(Client *c, const Arg *a, WebKitHitTestResu…
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 DarkMode,
282 DefaultCharset,
283 FontSize,
284 Geolocation,
285 HideBackground,
286 Inspector,
287 // KioskMode,
288 MediaManualPlay,
289 PDFJSviewer,
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 [-bBdDfFgGiIkKmMnNsStTvwxX]\n"
323 "[-a cookiepolicies ] [-c cookiefile] [-C stylefile] [-e xid…
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(g…
372 | G_IO_FLAG_NONBLOCK, NULL);
373 g_io_channel_set_close_on_unref(gchanin, TRUE);
374 }
375
376
377 for (i = 0; i < LENGTH(certs); ++i) {
378 if (!regcomp(&(certs[i].re), certs[i].regex, REG_EXTENDE…
379 certs[i].file = g_strconcat(certdir, "/", certs[…
380 NULL);
381 } else {
382 fprintf(stderr, "Could not compile regex: %s\n",
383 certs[i].regex);
384 certs[i].regex = NULL;
385 }
386 }
387
388 if (!stylefile) {
389 styledir = buildpath(styledir);
390 for (i = 0; i < LENGTH(styles); ++i) {
391 if (!regcomp(&(styles[i].re), styles[i].regex,
392 REG_EXTENDED)) {
393 styles[i].file = g_strconcat(styledir, "…
394 styles[i].file, NULL…
395 } else {
396 fprintf(stderr, "Could not compile regex…
397 styles[i].regex);
398 styles[i].regex = NULL;
399 }
400 }
401 g_free(styledir);
402 } else {
403 stylefile = buildfile(stylefile);
404 }
405
406 for (i = 0; i < LENGTH(uriparams); ++i) {
407 if (regcomp(&(uriparams[i].re), uriparams[i].uri,
408 REG_EXTENDED)) {
409 fprintf(stderr, "Could not compile regex: %s\n",
410 uriparams[i].uri);
411 uriparams[i].uri = NULL;
412 continue;
413 }
414
415 /* copy default parameters with higher priority */
416 for (j = 0; j < ParameterLast; ++j) {
417 if (defconfig[j].prio >= uriparams[i].config[j].…
418 uriparams[i].config[j] = defconfig[j];
419 }
420 }
421 }
422
423 void
424 sigchld(int unused)
425 {
426 if (signal(SIGCHLD, sigchld) == SIG_ERR)
427 die("Can't install SIGCHLD handler");
428 while (waitpid(-1, NULL, WNOHANG) > 0)
429 ;
430 }
431
432 void
433 sighup(int unused)
434 {
435 Arg a = { .i = 0 };
436 Client *c;
437
438 for (c = clients; c; c = c->next)
439 reload(c, &a);
440 }
441
442 char *
443 buildfile(const char *path)
444 {
445 char *dname, *bname, *bpath, *fpath;
446 FILE *f;
447
448 dname = g_path_get_dirname(path);
449 bname = g_path_get_basename(path);
450
451 bpath = buildpath(dname);
452 g_free(dname);
453
454 fpath = g_build_filename(bpath, bname, NULL);
455 g_free(bpath);
456 g_free(bname);
457
458 if (!(f = fopen(fpath, "a")))
459 die("Could not open file: %s\n", fpath);
460
461 g_chmod(fpath, 0600); /* always */
462 fclose(f);
463
464 return fpath;
465 }
466
467 static const char*
468 getuserhomedir(const char *user)
469 {
470 struct passwd *pw = getpwnam(user);
471
472 if (!pw)
473 die("Can't get user %s login information.\n", user);
474
475 return pw->pw_dir;
476 }
477
478 static const char*
479 getcurrentuserhomedir(void)
480 {
481 const char *homedir;
482 const char *user;
483 struct passwd *pw;
484
485 homedir = getenv("HOME");
486 if (homedir)
487 return homedir;
488
489 user = getenv("USER");
490 if (user)
491 return getuserhomedir(user);
492
493 pw = getpwuid(getuid());
494 if (!pw)
495 die("Can't get current user home directory\n");
496
497 return pw->pw_dir;
498 }
499
500 char *
501 buildpath(const char *path)
502 {
503 char *apath, *fpath;
504
505 if (path[0] == '~')
506 apath = untildepath(path);
507 else
508 apath = g_strdup(path);
509
510 /* creating directory */
511 if (g_mkdir_with_parents(apath, 0700) < 0)
512 die("Could not access directory: %s\n", apath);
513
514 fpath = realpath(apath, NULL);
515 g_free(apath);
516
517 return fpath;
518 }
519
520 char *
521 untildepath(const char *path)
522 {
523 char *apath, *name, *p;
524 const char *homedir;
525
526 if (path[1] == '/' || path[1] == '\0') {
527 p = (char *)&path[1];
528 homedir = getcurrentuserhomedir();
529 } else {
530 if ((p = strchr(path, '/')))
531 name = g_strndup(&path[1], p - (path + 1));
532 else
533 name = g_strdup(&path[1]);
534
535 homedir = getuserhomedir(name);
536 g_free(name);
537 }
538 apath = g_build_filename(homedir, p, NULL);
539 return apath;
540 }
541
542 Client *
543 newclient(Client *rc)
544 {
545 Client *c;
546
547 if (!(c = calloc(1, sizeof(Client))))
548 die("Cannot malloc!\n");
549
550 c->next = clients;
551 clients = c;
552
553 c->progress = 100;
554 c->view = newview(c, rc ? rc->view : NULL);
555
556 return c;
557 }
558
559 void
560 loaduri(Client *c, const Arg *a)
561 {
562 struct stat st;
563 char *url, *path, *apath;
564 const char *uri = a->v;
565
566 if (g_strcmp0(uri, "") == 0)
567 return;
568
569 if (g_str_has_prefix(uri, "http://") ||
570 g_str_has_prefix(uri, "https://") ||
571 g_str_has_prefix(uri, "file://") ||
572 g_str_has_prefix(uri, "webkit://") ||
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("https://%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, nam…
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[8] = curconfig[Certificate].val.i ? 'X' : 'x';
679 togglestats[9] = curconfig[StrictTLS].val.i ? 'T' : 't';
680 }
681
682 void
683 getpagestats(Client *c)
684 {
685 if (c->https)
686 pagestats[0] = (c->tlserr || c->insecure) ? 'U' : 'T';
687 else
688 pagestats[0] = '-';
689 pagestats[1] = '\0';
690 }
691
692 WebKitCookieAcceptPolicy
693 cookiepolicy_get(void)
694 {
695 switch (((char *)curconfig[CookiePolicies].val.v)[cookiepolicy])…
696 case 'a':
697 return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
698 case '@':
699 return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
700 default: /* fallthrough */
701 case 'A':
702 return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
703 }
704 }
705
706 char
707 cookiepolicy_set(const WebKitCookieAcceptPolicy p)
708 {
709 switch (p) {
710 case WEBKIT_COOKIE_POLICY_ACCEPT_NEVER:
711 return 'a';
712 case WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY:
713 return '@';
714 default: /* fallthrough */
715 case WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS:
716 return 'A';
717 }
718 }
719
720 void
721 seturiparameters(Client *c, const char *uri, ParamName *params)
722 {
723 Parameter *config, *uriconfig = NULL;
724 int i, p;
725
726 for (i = 0; i < LENGTH(uriparams); ++i) {
727 if (uriparams[i].uri &&
728 !regexec(&(uriparams[i].re), uri, 0, NULL, 0)) {
729 uriconfig = uriparams[i].config;
730 break;
731 }
732 }
733
734 curconfig = uriconfig ? uriconfig : defconfig;
735
736 for (i = 0; (p = params[i]) != ParameterLast; ++i) {
737 switch(p) {
738 default: /* FALLTHROUGH */
739 if (!(defconfig[p].prio < curconfig[p].prio ||
740 defconfig[p].prio < modparams[p]))
741 continue;
742 case Certificate:
743 case CookiePolicies:
744 case Style:
745 setparameter(c, 0, p, &curconfig[p].val);
746 }
747 }
748 }
749
750 void
751 setparameter(Client *c, int refresh, ParamName p, const Arg *a)
752 {
753 GdkRGBA bgcolor = { 0 };
754
755 modparams[p] = curconfig[p].prio;
756
757 switch (p) {
758 case AccessMicrophone:
759 return; /* do nothing */
760 case AccessWebcam:
761 return; /* do nothing */
762 case CaretBrowsing:
763 webkit_settings_set_enable_caret_browsing(c->settings, a…
764 refresh = 0;
765 break;
766 case Certificate:
767 if (a->i)
768 setcert(c, geturi(c));
769 return; /* do not update */
770 case CookiePolicies:
771 webkit_cookie_manager_set_accept_policy(
772 webkit_web_context_get_cookie_manager(c->context),
773 cookiepolicy_get());
774 refresh = 0;
775 break;
776 case DarkMode:
777 g_object_set(gtk_settings_get_default(),
778 "gtk-application-prefer-dark-theme", a->i, …
779 return;
780 case DiskCache:
781 webkit_web_context_set_cache_model(c->context, 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(c->settings, a->v);
787 return; /* do not update */
788 case DNSPrefetch:
789 webkit_settings_set_enable_dns_prefetching(c->settings, …
790 return; /* do not update */
791 case FileURLsCrossAccess:
792 webkit_settings_set_allow_file_access_from_file_urls(
793 c->settings, a->i);
794 webkit_settings_set_allow_universal_access_from_file_url…
795 c->settings, a->i);
796 return; /* do not update */
797 case FontSize:
798 webkit_settings_set_default_font_size(c->settings, a->i);
799 return; /* do not update */
800 case Geolocation:
801 refresh = 0;
802 break;
803 case HideBackground:
804 if (a->i)
805 webkit_web_view_set_background_color(c->view, &b…
806 return; /* do not update */
807 case Inspector:
808 webkit_settings_set_enable_developer_extras(c->settings,…
809 return; /* do not update */
810 case JavaScript:
811 webkit_settings_set_enable_javascript(c->settings, a->i);
812 break;
813 case KioskMode:
814 return; /* do nothing */
815 case LoadImages:
816 webkit_settings_set_auto_load_images(c->settings, a->i);
817 break;
818 case MediaManualPlay:
819 webkit_settings_set_media_playback_requires_user_gesture(
820 c->settings, a->i);
821 break;
822 case PDFJSviewer:
823 return; /* do nothing */
824 case PreferredLanguages:
825 return; /* do nothing */
826 case RunInFullscreen:
827 return; /* do nothing */
828 case ScrollBars:
829 /* Disabled until we write some WebKitWebExtension for
830 * manipulating the DOM directly.
831 enablescrollbars = !enablescrollbars;
832 evalscript(c, "document.documentElement.style.overflow =…
833 enablescrollbars ? "auto" : "hidden");
834 */
835 return; /* do not update */
836 case ShowIndicators:
837 break;
838 case SmoothScrolling:
839 webkit_settings_set_enable_smooth_scrolling(c->settings,…
840 return; /* do not update */
841 case SiteQuirks:
842 webkit_settings_set_enable_site_specific_quirks(
843 c->settings, a->i);
844 break;
845 case SpellChecking:
846 webkit_web_context_set_spell_checking_enabled(
847 c->context, a->i);
848 return; /* do not update */
849 case SpellLanguages:
850 return; /* do nothing */
851 case StrictTLS:
852 webkit_website_data_manager_set_tls_errors_policy(
853 webkit_web_view_get_website_data_manager(c->view), a…
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(c->settings, 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",…
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(c->con…
912 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_evaluate_javascript(c->view, script, -1,
979 NULL, NULL, NULL, NULL, NULL);
980 g_free(script);
981 }
982
983 void
984 updatewinid(Client *c)
985 {
986 snprintf(winid, LENGTH(winid), "%lu", c->xid);
987 }
988
989 void
990 handleplumb(Client *c, const char *uri)
991 {
992 Arg a = (Arg)PLUMB(uri);
993 spawn(c, &a);
994 }
995
996 void
997 newwindow(Client *c, const Arg *a, int noembed)
998 {
999 int i = 0;
1000 char tmp[64];
1001 const char *cmd[29], *uri;
1002 const Arg arg = { .v = cmd };
1003
1004 cmd[i++] = argv0;
1005 cmd[i++] = "-a";
1006 cmd[i++] = curconfig[CookiePolicies].val.v;
1007 cmd[i++] = curconfig[ScrollBars].val.i ? "-B" : "-b";
1008 if (cookiefile && g_strcmp0(cookiefile, "")) {
1009 cmd[i++] = "-c";
1010 cmd[i++] = cookiefile;
1011 }
1012 if (stylefile && g_strcmp0(stylefile, "")) {
1013 cmd[i++] = "-C";
1014 cmd[i++] = stylefile;
1015 }
1016 cmd[i++] = curconfig[DiskCache].val.i ? "-D" : "-d";
1017 if (embed && !noembed) {
1018 cmd[i++] = "-e";
1019 snprintf(tmp, LENGTH(tmp), "%lu", embed);
1020 cmd[i++] = tmp;
1021 }
1022 cmd[i++] = curconfig[RunInFullscreen].val.i ? "-F" : "-f" ;
1023 cmd[i++] = curconfig[Geolocation].val.i ? "-G" : "-g" ;
1024 cmd[i++] = curconfig[LoadImages].val.i ? "-I" : "-i" ;
1025 cmd[i++] = curconfig[KioskMode].val.i ? "-K" : "-k" ;
1026 cmd[i++] = curconfig[Style].val.i ? "-M" : "-m" ;
1027 cmd[i++] = curconfig[Inspector].val.i ? "-N" : "-n" ;
1028 if (scriptfile && g_strcmp0(scriptfile, "")) {
1029 cmd[i++] = "-r";
1030 cmd[i++] = scriptfile;
1031 }
1032 cmd[i++] = curconfig[JavaScript].val.i ? "-S" : "-s";
1033 cmd[i++] = curconfig[StrictTLS].val.i ? "-T" : "-t";
1034 if (fulluseragent && g_strcmp0(fulluseragent, "")) {
1035 cmd[i++] = "-u";
1036 cmd[i++] = fulluseragent;
1037 }
1038 if (showxid)
1039 cmd[i++] = "-w";
1040 cmd[i++] = curconfig[Certificate].val.i ? "-X" : "-x" ;
1041 /* do not keep zoom level */
1042 cmd[i++] = "--";
1043 if ((uri = a->v))
1044 cmd[i++] = uri;
1045 cmd[i] = NULL;
1046
1047 spawn(c, &arg);
1048 }
1049
1050 void
1051 spawn(Client *c, const Arg *a)
1052 {
1053 if (fork() == 0) {
1054 if (dpy)
1055 close(ConnectionNumber(dpy));
1056 close(spair[0]);
1057 close(spair[1]);
1058 setsid();
1059 execvp(((char **)a->v)[0], (char **)a->v);
1060 fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[…
1061 perror(" failed");
1062 exit(1);
1063 }
1064 }
1065
1066 void
1067 destroyclient(Client *c)
1068 {
1069 Client *p;
1070
1071 webkit_web_view_stop_loading(c->view);
1072 /* Not needed, has already been called
1073 gtk_widget_destroy(c->win);
1074 */
1075
1076 for (p = clients; p && p->next != c; p = p->next)
1077 ;
1078 if (p)
1079 p->next = c->next;
1080 else
1081 clients = c->next;
1082 free(c);
1083 }
1084
1085 void
1086 cleanup(void)
1087 {
1088 while (clients)
1089 destroyclient(clients);
1090
1091 close(spair[0]);
1092 close(spair[1]);
1093 g_free(cookiefile);
1094 g_free(scriptfile);
1095 g_free(stylefile);
1096 g_free(cachedir);
1097 XCloseDisplay(dpy);
1098 }
1099
1100 WebKitWebView *
1101 newview(Client *c, WebKitWebView *rv)
1102 {
1103 WebKitWebView *v;
1104 WebKitSettings *settings;
1105 WebKitWebContext *context;
1106 WebKitCookieManager *cookiemanager;
1107 WebKitUserContentManager *contentmanager;
1108
1109 /* Webview */
1110 if (rv) {
1111 v = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_vie…
1112 context = webkit_web_view_get_context(v);
1113 settings = webkit_web_view_get_settings(v);
1114 } else {
1115 settings = webkit_settings_new_with_settings(
1116 "allow-file-access-from-file-urls", curconfig[FileURL…
1117 "allow-universal-access-from-file-urls", curconfig[Fi…
1118 "auto-load-images", curconfig[LoadImages].val.i,
1119 "default-charset", curconfig[DefaultCharset].val.v,
1120 "default-font-size", curconfig[FontSize].val.i,
1121 "enable-caret-browsing", curconfig[CaretBrowsing].val…
1122 "enable-developer-extras", curconfig[Inspector].val.i,
1123 "enable-dns-prefetching", curconfig[DNSPrefetch].val.…
1124 "enable-html5-database", curconfig[DiskCache].val.i,
1125 "enable-html5-local-storage", curconfig[DiskCache].va…
1126 "enable-javascript", curconfig[JavaScript].val.i,
1127 "enable-site-specific-quirks", curconfig[SiteQuirks].…
1128 "enable-smooth-scrolling", curconfig[SmoothScrolling]…
1129 "enable-webgl", curconfig[WebGL].val.i,
1130 "media-playback-requires-user-gesture", curconfig[Med…
1131 NULL);
1132 /* For more interesting settings, have a look at
1133 * http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html …
1134
1135 if (strcmp(fulluseragent, "")) {
1136 webkit_settings_set_user_agent(settings, fulluse…
1137 } else if (surfuseragent) {
1138 webkit_settings_set_user_agent_with_application_…
1139 settings, "Surf", VERSION);
1140 }
1141 useragent = webkit_settings_get_user_agent(settings);
1142
1143 contentmanager = webkit_user_content_manager_new();
1144
1145 if (curconfig[Ephemeral].val.i) {
1146 context = webkit_web_context_new_ephemeral();
1147 } else {
1148 context = webkit_web_context_new_with_website_da…
1149 webkit_website_data_manager_new(
1150 "base-cache-directory", cachedir,
1151 "base-data-directory", cachedir,
1152 NULL));
1153 }
1154
1155 cookiemanager = webkit_web_context_get_cookie_manager(co…
1156
1157 /* TLS */
1158 webkit_website_data_manager_set_tls_errors_policy(
1159 webkit_web_context_get_website_data_manager(context),
1160 curconfig[StrictTLS].val.i ? WEBKIT_TLS_ERRORS_POLIC…
1161 WEBKIT_TLS_ERRORS_POLICY_IGNORE);
1162 /* disk cache */
1163 webkit_web_context_set_cache_model(context,
1164 curconfig[DiskCache].val.i ? WEBKIT_CACHE_MODEL_WEB_…
1165 WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
1166
1167 /* Currently only works with text file to be compatible …
1168 if (!curconfig[Ephemeral].val.i)
1169 webkit_cookie_manager_set_persistent_storage(coo…
1170 cookiefile, WEBKIT_COOKIE_PERSISTENT_STORAGE…
1171 /* cookie policy */
1172 webkit_cookie_manager_set_accept_policy(cookiemanager,
1173 cookiepolicy_get());
1174 /* languages */
1175 webkit_web_context_set_preferred_languages(context,
1176 curconfig[PreferredLanguages].val.v);
1177 webkit_web_context_set_spell_checking_languages(context,
1178 curconfig[SpellLanguages].val.v);
1179 webkit_web_context_set_spell_checking_enabled(context,
1180 curconfig[SpellChecking].val.i);
1181
1182 g_signal_connect(G_OBJECT(context), "download-started",
1183 G_CALLBACK(downloadstarted), c);
1184 g_signal_connect(G_OBJECT(context), "initialize-web-exte…
1185 G_CALLBACK(initwebextensions), c);
1186
1187 v = g_object_new(WEBKIT_TYPE_WEB_VIEW,
1188 "settings", settings,
1189 "user-content-manager", contentmanager,
1190 "web-context", context,
1191 NULL);
1192 }
1193
1194 g_signal_connect(G_OBJECT(v), "notify::estimated-load-progress",
1195 G_CALLBACK(progresschanged), c);
1196 g_signal_connect(G_OBJECT(v), "notify::title",
1197 G_CALLBACK(titlechanged), c);
1198 g_signal_connect(G_OBJECT(v), "button-release-event",
1199 G_CALLBACK(buttonreleased), c);
1200 g_signal_connect(G_OBJECT(v), "close",
1201 G_CALLBACK(closeview), c);
1202 g_signal_connect(G_OBJECT(v), "create",
1203 G_CALLBACK(createview), c);
1204 g_signal_connect(G_OBJECT(v), "decide-policy",
1205 G_CALLBACK(decidepolicy), c);
1206 g_signal_connect(G_OBJECT(v), "insecure-content-detected",
1207 G_CALLBACK(insecurecontent), c);
1208 g_signal_connect(G_OBJECT(v), "load-failed-with-tls-errors",
1209 G_CALLBACK(loadfailedtls), c);
1210 g_signal_connect(G_OBJECT(v), "load-changed",
1211 G_CALLBACK(loadchanged), c);
1212 g_signal_connect(G_OBJECT(v), "mouse-target-changed",
1213 G_CALLBACK(mousetargetchanged), c);
1214 g_signal_connect(G_OBJECT(v), "permission-request",
1215 G_CALLBACK(permissionrequested), c);
1216 g_signal_connect(G_OBJECT(v), "ready-to-show",
1217 G_CALLBACK(showview), c);
1218 g_signal_connect(G_OBJECT(v), "user-message-received",
1219 G_CALLBACK(viewusrmsgrcv), c);
1220 g_signal_connect(G_OBJECT(v), "web-process-terminated",
1221 G_CALLBACK(webprocessterminated), c);
1222
1223 c->context = context;
1224 c->settings = settings;
1225
1226 setparameter(c, 0, DarkMode, &curconfig[DarkMode].val);
1227
1228 return v;
1229 }
1230
1231 void
1232 initwebextensions(WebKitWebContext *wc, Client *c)
1233 {
1234 webkit_web_context_set_web_extensions_directory(wc, WEBEXTDIR);
1235 }
1236
1237 GtkWidget *
1238 createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c)
1239 {
1240 Client *n;
1241
1242 switch (webkit_navigation_action_get_navigation_type(a)) {
1243 case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1244 /*
1245 * popup windows of type “other” are almost always t…
1246 * by user gesture, so inverse the logic here
1247 */
1248 /* instead of this, compare destination uri to mouse-over uri for valida…
1249 if (webkit_navigation_action_is_user_gesture(a))
1250 return NULL;
1251 case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1252 case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1253 case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1254 case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1255 case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1256 n = newclient(c);
1257 break;
1258 default:
1259 return NULL;
1260 }
1261
1262 return GTK_WIDGET(n->view);
1263 }
1264
1265 gboolean
1266 buttonreleased(GtkWidget *w, GdkEvent *e, Client *c)
1267 {
1268 WebKitHitTestResultContext element;
1269 int i;
1270
1271 element = webkit_hit_test_result_get_context(c->mousepos);
1272
1273 for (i = 0; i < LENGTH(buttons); ++i) {
1274 if (element & buttons[i].target &&
1275 e->button.button == buttons[i].button &&
1276 CLEANMASK(e->button.state) == CLEANMASK(buttons[i].m…
1277 buttons[i].func) {
1278 buttons[i].func(c, &buttons[i].arg, c->mousepos);
1279 return buttons[i].stopevent;
1280 }
1281 }
1282
1283 return FALSE;
1284 }
1285
1286 GdkFilterReturn
1287 processx(GdkXEvent *e, GdkEvent *event, gpointer d)
1288 {
1289 Client *c = (Client *)d;
1290 XPropertyEvent *ev;
1291 Arg a;
1292
1293 if (((XEvent *)e)->type == PropertyNotify) {
1294 ev = &((XEvent *)e)->xproperty;
1295 if (ev->state == PropertyNewValue) {
1296 if (ev->atom == atoms[AtomFind]) {
1297 find(c, NULL);
1298
1299 return GDK_FILTER_REMOVE;
1300 } else if (ev->atom == atoms[AtomGo]) {
1301 a.v = getatom(c, AtomGo);
1302 loaduri(c, &a);
1303
1304 return GDK_FILTER_REMOVE;
1305 }
1306 }
1307 }
1308 return GDK_FILTER_CONTINUE;
1309 }
1310
1311 gboolean
1312 winevent(GtkWidget *w, GdkEvent *e, Client *c)
1313 {
1314 int i;
1315
1316 switch (e->type) {
1317 case GDK_ENTER_NOTIFY:
1318 c->overtitle = c->targeturi;
1319 updatetitle(c);
1320 break;
1321 case GDK_KEY_PRESS:
1322 if (!curconfig[KioskMode].val.i) {
1323 for (i = 0; i < LENGTH(keys); ++i) {
1324 if (gdk_keyval_to_lower(e->key.keyval) ==
1325 keys[i].keyval &&
1326 CLEANMASK(e->key.state) == keys[i].m…
1327 keys[i].func) {
1328 updatewinid(c);
1329 keys[i].func(c, &(keys[i].arg));
1330 return TRUE;
1331 }
1332 }
1333 }
1334 case GDK_LEAVE_NOTIFY:
1335 c->overtitle = NULL;
1336 updatetitle(c);
1337 break;
1338 case GDK_WINDOW_STATE:
1339 if (e->window_state.changed_mask ==
1340 GDK_WINDOW_STATE_FULLSCREEN)
1341 c->fullscreen = e->window_state.new_window_state…
1342 GDK_WINDOW_STATE_FULLSCREEN;
1343 break;
1344 default:
1345 break;
1346 }
1347
1348 return FALSE;
1349 }
1350
1351 void
1352 showview(WebKitWebView *v, Client *c)
1353 {
1354 GdkRGBA bgcolor = { 0 };
1355 GdkWindow *gwin;
1356
1357 c->finder = webkit_web_view_get_find_controller(c->view);
1358 c->inspector = webkit_web_view_get_inspector(c->view);
1359
1360 c->pageid = webkit_web_view_get_page_id(c->view);
1361 c->win = createwindow(c);
1362
1363 gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
1364 gtk_widget_show_all(c->win);
1365 gtk_widget_grab_focus(GTK_WIDGET(c->view));
1366
1367 gwin = gtk_widget_get_window(GTK_WIDGET(c->win));
1368 c->xid = gdk_x11_window_get_xid(gwin);
1369 updatewinid(c);
1370 if (showxid) {
1371 gdk_display_sync(gtk_widget_get_display(c->win));
1372 puts(winid);
1373 fflush(stdout);
1374 }
1375
1376 if (curconfig[HideBackground].val.i)
1377 webkit_web_view_set_background_color(c->view, &bgcolor);
1378
1379 if (!curconfig[KioskMode].val.i) {
1380 gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK);
1381 gdk_window_add_filter(gwin, processx, c);
1382 }
1383
1384 if (curconfig[RunInFullscreen].val.i)
1385 togglefullscreen(c, NULL);
1386
1387 if (curconfig[ZoomLevel].val.f != 1.0)
1388 webkit_web_view_set_zoom_level(c->view,
1389 curconfig[ZoomLevel].val.…
1390
1391 setatom(c, AtomFind, "");
1392 setatom(c, AtomUri, "about:blank");
1393 }
1394
1395 GtkWidget *
1396 createwindow(Client *c)
1397 {
1398 char *wmstr;
1399 GtkWidget *w;
1400
1401 if (embed) {
1402 w = gtk_plug_new(embed);
1403 } else {
1404 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1405
1406 wmstr = g_path_get_basename(argv0);
1407 gtk_window_set_wmclass(GTK_WINDOW(w), wmstr, "Surf");
1408 g_free(wmstr);
1409
1410 wmstr = g_strdup_printf("%s[%"PRIu64"]", "Surf", c->page…
1411 gtk_window_set_role(GTK_WINDOW(w), wmstr);
1412 g_free(wmstr);
1413
1414 gtk_window_set_default_size(GTK_WINDOW(w), winsize[0], w…
1415 }
1416
1417 g_signal_connect(G_OBJECT(w), "destroy",
1418 G_CALLBACK(destroywin), c);
1419 g_signal_connect(G_OBJECT(w), "enter-notify-event",
1420 G_CALLBACK(winevent), c);
1421 g_signal_connect(G_OBJECT(w), "key-press-event",
1422 G_CALLBACK(winevent), c);
1423 g_signal_connect(G_OBJECT(w), "leave-notify-event",
1424 G_CALLBACK(winevent), c);
1425 g_signal_connect(G_OBJECT(w), "window-state-event",
1426 G_CALLBACK(winevent), c);
1427
1428 return w;
1429 }
1430
1431 gboolean
1432 loadfailedtls(WebKitWebView *v, gchar *uri, GTlsCertificate *cert,
1433 GTlsCertificateFlags err, Client *c)
1434 {
1435 GString *errmsg = g_string_new(NULL);
1436 gchar *html, *pem;
1437
1438 c->failedcert = g_object_ref(cert);
1439 c->tlserr = err;
1440 c->errorpage = 1;
1441
1442 if (err & G_TLS_CERTIFICATE_UNKNOWN_CA)
1443 g_string_append(errmsg,
1444 "The signing certificate authority is not known.<br>…
1445 if (err & G_TLS_CERTIFICATE_BAD_IDENTITY)
1446 g_string_append(errmsg,
1447 "The certificate does not match the expected identit…
1448 "of the site that it was retrieved from.<br>");
1449 if (err & G_TLS_CERTIFICATE_NOT_ACTIVATED)
1450 g_string_append(errmsg,
1451 "The certificate's activation time "
1452 "is still in the future.<br>");
1453 if (err & G_TLS_CERTIFICATE_EXPIRED)
1454 g_string_append(errmsg, "The certificate has expired.<br…
1455 if (err & G_TLS_CERTIFICATE_REVOKED)
1456 g_string_append(errmsg,
1457 "The certificate has been revoked according to "
1458 "the GTlsConnection's certificate revocation list.<b…
1459 if (err & G_TLS_CERTIFICATE_INSECURE)
1460 g_string_append(errmsg,
1461 "The certificate's algorithm is considered insecure.…
1462 if (err & G_TLS_CERTIFICATE_GENERIC_ERROR)
1463 g_string_append(errmsg,
1464 "Some error occurred validating the certificate.<br>…
1465
1466 g_object_get(cert, "certificate-pem", &pem, NULL);
1467 html = g_strdup_printf("<p>Could not validate TLS for “%s”<b…
1468 "<p>You can inspect the following certifi…
1469 "with Ctrl-t (default keybinding).</p>"
1470 "<p><pre>%s</pre></p>", uri, errmsg->str,…
1471 g_free(pem);
1472 g_string_free(errmsg, TRUE);
1473
1474 webkit_web_view_load_alternate_html(c->view, html, uri, NULL);
1475 g_free(html);
1476
1477 return TRUE;
1478 }
1479
1480 void
1481 loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
1482 {
1483 const char *uri = geturi(c);
1484
1485 switch (e) {
1486 case WEBKIT_LOAD_STARTED:
1487 setatom(c, AtomUri, uri);
1488 c->title = uri;
1489 c->https = c->insecure = 0;
1490 seturiparameters(c, uri, loadtransient);
1491 if (c->errorpage)
1492 c->errorpage = 0;
1493 else
1494 g_clear_object(&c->failedcert);
1495 break;
1496 case WEBKIT_LOAD_REDIRECTED:
1497 setatom(c, AtomUri, uri);
1498 c->title = uri;
1499 seturiparameters(c, uri, loadtransient);
1500 break;
1501 case WEBKIT_LOAD_COMMITTED:
1502 setatom(c, AtomUri, uri);
1503 c->title = uri;
1504 seturiparameters(c, uri, loadcommitted);
1505 c->https = webkit_web_view_get_tls_info(c->view, &c->cer…
1506 &c->tlserr);
1507 break;
1508 case WEBKIT_LOAD_FINISHED:
1509 seturiparameters(c, uri, loadfinished);
1510 /* Disabled until we write some WebKitWebExtension for
1511 * manipulating the DOM directly.
1512 evalscript(c, "document.documentElement.style.overflow =…
1513 enablescrollbars ? "auto" : "hidden");
1514 */
1515 runscript(c);
1516 break;
1517 }
1518 updatetitle(c);
1519 }
1520
1521 void
1522 progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c)
1523 {
1524 c->progress = webkit_web_view_get_estimated_load_progress(c->vie…
1525 100;
1526 updatetitle(c);
1527 }
1528
1529 void
1530 titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c)
1531 {
1532 c->title = webkit_web_view_get_title(c->view);
1533 updatetitle(c);
1534 }
1535
1536 gboolean
1537 viewusrmsgrcv(WebKitWebView *v, WebKitUserMessage *m, gpointer unused)
1538 {
1539 WebKitUserMessage *r;
1540 GUnixFDList *gfd;
1541 const char *name;
1542
1543 name = webkit_user_message_get_name(m);
1544 if (strcmp(name, "page-created") != 0) {
1545 fprintf(stderr, "surf: Unknown UserMessage: %s\n", name);
1546 return TRUE;
1547 }
1548
1549 if (spair[1] < 0)
1550 return TRUE;
1551
1552 gfd = g_unix_fd_list_new_from_array(&spair[1], 1);
1553 r = webkit_user_message_new_with_fd_list("surf-pipe", NULL, gfd);
1554
1555 webkit_user_message_send_reply(m, r);
1556
1557 return TRUE;
1558 }
1559
1560 void
1561 mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modif…
1562 Client *c)
1563 {
1564 WebKitHitTestResultContext hc = webkit_hit_test_result_get_conte…
1565
1566 /* Keep the hit test to know where is the pointer on the next cl…
1567 c->mousepos = h;
1568
1569 if (hc & OnLink)
1570 c->targeturi = webkit_hit_test_result_get_link_uri(h);
1571 else if (hc & OnImg)
1572 c->targeturi = webkit_hit_test_result_get_image_uri(h);
1573 else if (hc & OnMedia)
1574 c->targeturi = webkit_hit_test_result_get_media_uri(h);
1575 else
1576 c->targeturi = NULL;
1577
1578 c->overtitle = c->targeturi;
1579 updatetitle(c);
1580 }
1581
1582 gboolean
1583 permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client…
1584 {
1585 ParamName param = ParameterLast;
1586
1587 if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
1588 param = Geolocation;
1589 } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(r)) {
1590 if (webkit_user_media_permission_is_for_audio_device(
1591 WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r)))
1592 param = AccessMicrophone;
1593 else if (webkit_user_media_permission_is_for_video_devic…
1594 WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r)))
1595 param = AccessWebcam;
1596 } else {
1597 return FALSE;
1598 }
1599
1600 if (curconfig[param].val.i)
1601 webkit_permission_request_allow(r);
1602 else
1603 webkit_permission_request_deny(r);
1604
1605 return TRUE;
1606 }
1607
1608 gboolean
1609 decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
1610 WebKitPolicyDecisionType dt, Client *c)
1611 {
1612 switch (dt) {
1613 case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
1614 decidenavigation(d, c);
1615 break;
1616 case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
1617 decidenewwindow(d, c);
1618 break;
1619 case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
1620 decideresource(d, c);
1621 break;
1622 default:
1623 webkit_policy_decision_ignore(d);
1624 break;
1625 }
1626 return TRUE;
1627 }
1628
1629 void
1630 decidenavigation(WebKitPolicyDecision *d, Client *c)
1631 {
1632 WebKitNavigationAction *a =
1633 webkit_navigation_policy_decision_get_navigation_action(
1634 WEBKIT_NAVIGATION_POLICY_DECISION(d));
1635
1636 switch (webkit_navigation_action_get_navigation_type(a)) {
1637 case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1638 case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1639 case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1640 case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1641 case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: /* fallthrough */
1642 case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1643 default:
1644 /* Do not navigate to links with a "_blank" target (popu…
1645 if (webkit_navigation_action_get_frame_name(a)) {
1646 webkit_policy_decision_ignore(d);
1647 } else {
1648 /* Filter out navigation to different domain ? */
1649 /* get action→urirequest, copy and load in new…
1650 * on Ctrl+Click ? */
1651 webkit_policy_decision_use(d);
1652 }
1653 break;
1654 }
1655 }
1656
1657 void
1658 decidenewwindow(WebKitPolicyDecision *d, Client *c)
1659 {
1660 Arg arg;
1661 WebKitNavigationAction *a =
1662 webkit_navigation_policy_decision_get_navigation_action(
1663 WEBKIT_NAVIGATION_POLICY_DECISION(d));
1664
1665
1666 switch (webkit_navigation_action_get_navigation_type(a)) {
1667 case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1668 case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1669 case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1670 case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1671 case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1672 /* Filter domains here */
1673 /* If the value of “mouse-button” is not 0, then the navigation was …
1674 * test for link clicked but no button ? */
1675 arg.v = webkit_uri_request_get_uri(
1676 webkit_navigation_action_get_request(a));
1677 newwindow(c, &arg, 0);
1678 break;
1679 case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1680 default:
1681 break;
1682 }
1683
1684 webkit_policy_decision_ignore(d);
1685 }
1686
1687 void
1688 decideresource(WebKitPolicyDecision *d, Client *c)
1689 {
1690 int i, isascii = 1;
1691 WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISIO…
1692 WebKitURIResponse *res =
1693 webkit_response_policy_decision_get_response(r);
1694 const gchar *uri = webkit_uri_response_get_uri(res);
1695
1696 if (g_str_has_suffix(uri, "/favicon.ico")) {
1697 webkit_policy_decision_ignore(d);
1698 return;
1699 }
1700
1701 if (!g_str_has_prefix(uri, "http://")
1702 && !g_str_has_prefix(uri, "https://")
1703 && !g_str_has_prefix(uri, "about:")
1704 && !g_str_has_prefix(uri, "file://")
1705 && !g_str_has_prefix(uri, "webkit://")
1706 && !g_str_has_prefix(uri, "data:")
1707 && !g_str_has_prefix(uri, "blob:")
1708 && !(g_str_has_prefix(uri, "webkit-pdfjs-viewer://") && curc…
1709 && strlen(uri) > 0) {
1710 for (i = 0; i < strlen(uri); i++) {
1711 if (!g_ascii_isprint(uri[i])) {
1712 isascii = 0;
1713 break;
1714 }
1715 }
1716 if (isascii) {
1717 handleplumb(c, uri);
1718 webkit_policy_decision_ignore(d);
1719 return;
1720 }
1721 }
1722
1723 if (webkit_response_policy_decision_is_mime_type_supported(r)) {
1724 webkit_policy_decision_use(d);
1725 } else {
1726 webkit_policy_decision_ignore(d);
1727 download(c, res);
1728 }
1729 }
1730
1731 void
1732 insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e, Client *…
1733 {
1734 c->insecure = 1;
1735 }
1736
1737 void
1738 downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c)
1739 {
1740 g_signal_connect(G_OBJECT(d), "notify::response",
1741 G_CALLBACK(responsereceived), c);
1742 }
1743
1744 void
1745 responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c)
1746 {
1747 download(c, webkit_download_get_response(d));
1748 webkit_download_cancel(d);
1749 }
1750
1751 void
1752 download(Client *c, WebKitURIResponse *r)
1753 {
1754 Arg a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
1755 spawn(c, &a);
1756 }
1757
1758 void
1759 webprocessterminated(WebKitWebView *v, WebKitWebProcessTerminationReason…
1760 Client *c)
1761 {
1762 fprintf(stderr, "web process terminated: %s\n",
1763 r == WEBKIT_WEB_PROCESS_CRASHED ? "crashed" : "no memory…
1764 closeview(v, c);
1765 }
1766
1767 void
1768 closeview(WebKitWebView *v, Client *c)
1769 {
1770 gtk_widget_destroy(c->win);
1771 }
1772
1773 void
1774 destroywin(GtkWidget* w, Client *c)
1775 {
1776 destroyclient(c);
1777 if (!clients)
1778 gtk_main_quit();
1779 }
1780
1781 void
1782 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
1783 {
1784 Arg a = {.v = text };
1785 if (text)
1786 loaduri((Client *) d, &a);
1787 }
1788
1789 void
1790 reload(Client *c, const Arg *a)
1791 {
1792 if (a->i)
1793 webkit_web_view_reload_bypass_cache(c->view);
1794 else
1795 webkit_web_view_reload(c->view);
1796 }
1797
1798 void
1799 print(Client *c, const Arg *a)
1800 {
1801 webkit_print_operation_run_dialog(webkit_print_operation_new(c->…
1802 GTK_WINDOW(c->win));
1803 }
1804
1805 void
1806 showcert(Client *c, const Arg *a)
1807 {
1808 GTlsCertificate *cert = c->failedcert ? c->failedcert : c->cert;
1809 GcrCertificate *gcrt;
1810 GByteArray *crt;
1811 GtkWidget *win;
1812 GcrCertificateWidget *wcert;
1813
1814 if (!cert)
1815 return;
1816
1817 g_object_get(cert, "certificate", &crt, NULL);
1818 gcrt = gcr_simple_certificate_new(crt->data, crt->len);
1819 g_byte_array_unref(crt);
1820
1821 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1822 wcert = gcr_certificate_widget_new(gcrt);
1823 g_object_unref(gcrt);
1824
1825 gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(wcert));
1826 gtk_widget_show_all(win);
1827 }
1828
1829 void
1830 clipboard(Client *c, const Arg *a)
1831 {
1832 if (a->i) { /* load clipboard uri */
1833 gtk_clipboard_request_text(gtk_clipboard_get(
1834 GDK_SELECTION_PRIMARY),
1835 pasteuri, c);
1836 } else { /* copy uri */
1837 gtk_clipboard_set_text(gtk_clipboard_get(
1838 GDK_SELECTION_PRIMARY), c->target…
1839 ? c->targeturi : geturi(c), -1);
1840 }
1841 }
1842
1843 void
1844 zoom(Client *c, const Arg *a)
1845 {
1846 if (a->i > 0)
1847 webkit_web_view_set_zoom_level(c->view,
1848 curconfig[ZoomLevel].val.…
1849 else if (a->i < 0)
1850 webkit_web_view_set_zoom_level(c->view,
1851 curconfig[ZoomLevel].val.…
1852 else
1853 webkit_web_view_set_zoom_level(c->view, 1.0);
1854
1855 curconfig[ZoomLevel].val.f = webkit_web_view_get_zoom_level(c->v…
1856 }
1857
1858 static void
1859 msgext(Client *c, char type, const Arg *a)
1860 {
1861 static unsigned char msg[MSGBUFSZ];
1862 int ret;
1863
1864 if (spair[0] < 0)
1865 return;
1866
1867 ret = snprintf(msg, sizeof(msg), "%c%c%c",
1868 (unsigned char)c->pageid, type, (signed char)a->i…
1869 if (ret >= sizeof(msg)) {
1870 fprintf(stderr, "surf: message too long: %d\n", ret);
1871 return;
1872 }
1873
1874 if (send(spair[0], msg, ret, 0) != ret)
1875 fprintf(stderr, "surf: error sending: %hhu/%c/%d (%d)\n",
1876 (unsigned char)c->pageid, type, a->i, ret);
1877 }
1878
1879 void
1880 scrollv(Client *c, const Arg *a)
1881 {
1882 msgext(c, 'v', a);
1883 }
1884
1885 void
1886 scrollh(Client *c, const Arg *a)
1887 {
1888 msgext(c, 'h', a);
1889 }
1890
1891 void
1892 navigate(Client *c, const Arg *a)
1893 {
1894 if (a->i < 0)
1895 webkit_web_view_go_back(c->view);
1896 else if (a->i > 0)
1897 webkit_web_view_go_forward(c->view);
1898 }
1899
1900 void
1901 stop(Client *c, const Arg *a)
1902 {
1903 webkit_web_view_stop_loading(c->view);
1904 }
1905
1906 void
1907 toggle(Client *c, const Arg *a)
1908 {
1909 curconfig[a->i].val.i ^= 1;
1910 setparameter(c, 1, (ParamName)a->i, &curconfig[a->i].val);
1911 }
1912
1913 void
1914 togglefullscreen(Client *c, const Arg *a)
1915 {
1916 /* toggling value is handled in winevent() */
1917 if (c->fullscreen)
1918 gtk_window_unfullscreen(GTK_WINDOW(c->win));
1919 else
1920 gtk_window_fullscreen(GTK_WINDOW(c->win));
1921 }
1922
1923 void
1924 togglecookiepolicy(Client *c, const Arg *a)
1925 {
1926 ++cookiepolicy;
1927 cookiepolicy %= strlen(curconfig[CookiePolicies].val.v);
1928
1929 setparameter(c, 0, CookiePolicies, NULL);
1930 }
1931
1932 void
1933 toggleinspector(Client *c, const Arg *a)
1934 {
1935 if (webkit_web_inspector_is_attached(c->inspector))
1936 webkit_web_inspector_close(c->inspector);
1937 else if (curconfig[Inspector].val.i)
1938 webkit_web_inspector_show(c->inspector);
1939 }
1940
1941 void
1942 find(Client *c, const Arg *a)
1943 {
1944 const char *s, *f;
1945
1946 if (a && a->i) {
1947 if (a->i > 0)
1948 webkit_find_controller_search_next(c->finder);
1949 else
1950 webkit_find_controller_search_previous(c->finder…
1951 } else {
1952 s = getatom(c, AtomFind);
1953 f = webkit_find_controller_get_search_text(c->finder);
1954
1955 if (g_strcmp0(f, s) == 0) /* reset search */
1956 webkit_find_controller_search(c->finder, "", fin…
1957 G_MAXUINT);
1958
1959 webkit_find_controller_search(c->finder, s, findopts,
1960 G_MAXUINT);
1961
1962 if (strcmp(s, "") == 0)
1963 webkit_find_controller_search_finish(c->finder);
1964 }
1965 }
1966
1967 void
1968 clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h)
1969 {
1970 navigate(c, a);
1971 }
1972
1973 void
1974 clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h)
1975 {
1976 Arg arg;
1977
1978 arg.v = webkit_hit_test_result_get_link_uri(h);
1979 newwindow(c, &arg, a->i);
1980 }
1981
1982 void
1983 clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h)
1984 {
1985 Arg arg;
1986
1987 arg = (Arg)VIDEOPLAY(webkit_hit_test_result_get_media_uri(h));
1988 spawn(c, &arg);
1989 }
1990
1991 int
1992 main(int argc, char *argv[])
1993 {
1994 Arg arg;
1995 Client *c;
1996
1997 memset(&arg, 0, sizeof(arg));
1998
1999 /* command line args */
2000 ARGBEGIN {
2001 case 'a':
2002 defconfig[CookiePolicies].val.v = EARGF(usage());
2003 defconfig[CookiePolicies].prio = 2;
2004 break;
2005 case 'b':
2006 defconfig[ScrollBars].val.i = 0;
2007 defconfig[ScrollBars].prio = 2;
2008 break;
2009 case 'B':
2010 defconfig[ScrollBars].val.i = 1;
2011 defconfig[ScrollBars].prio = 2;
2012 break;
2013 case 'c':
2014 cookiefile = EARGF(usage());
2015 break;
2016 case 'C':
2017 stylefile = EARGF(usage());
2018 break;
2019 case 'd':
2020 defconfig[DiskCache].val.i = 0;
2021 defconfig[DiskCache].prio = 2;
2022 break;
2023 case 'D':
2024 defconfig[DiskCache].val.i = 1;
2025 defconfig[DiskCache].prio = 2;
2026 break;
2027 case 'e':
2028 embed = strtol(EARGF(usage()), NULL, 0);
2029 break;
2030 case 'f':
2031 defconfig[RunInFullscreen].val.i = 0;
2032 defconfig[RunInFullscreen].prio = 2;
2033 break;
2034 case 'F':
2035 defconfig[RunInFullscreen].val.i = 1;
2036 defconfig[RunInFullscreen].prio = 2;
2037 break;
2038 case 'g':
2039 defconfig[Geolocation].val.i = 0;
2040 defconfig[Geolocation].prio = 2;
2041 break;
2042 case 'G':
2043 defconfig[Geolocation].val.i = 1;
2044 defconfig[Geolocation].prio = 2;
2045 break;
2046 case 'i':
2047 defconfig[LoadImages].val.i = 0;
2048 defconfig[LoadImages].prio = 2;
2049 break;
2050 case 'I':
2051 defconfig[LoadImages].val.i = 1;
2052 defconfig[LoadImages].prio = 2;
2053 break;
2054 case 'k':
2055 defconfig[KioskMode].val.i = 0;
2056 defconfig[KioskMode].prio = 2;
2057 break;
2058 case 'K':
2059 defconfig[KioskMode].val.i = 1;
2060 defconfig[KioskMode].prio = 2;
2061 break;
2062 case 'm':
2063 defconfig[Style].val.i = 0;
2064 defconfig[Style].prio = 2;
2065 break;
2066 case 'M':
2067 defconfig[Style].val.i = 1;
2068 defconfig[Style].prio = 2;
2069 break;
2070 case 'n':
2071 defconfig[Inspector].val.i = 0;
2072 defconfig[Inspector].prio = 2;
2073 break;
2074 case 'N':
2075 defconfig[Inspector].val.i = 1;
2076 defconfig[Inspector].prio = 2;
2077 break;
2078 case 'r':
2079 scriptfile = EARGF(usage());
2080 break;
2081 case 's':
2082 defconfig[JavaScript].val.i = 0;
2083 defconfig[JavaScript].prio = 2;
2084 break;
2085 case 'S':
2086 defconfig[JavaScript].val.i = 1;
2087 defconfig[JavaScript].prio = 2;
2088 break;
2089 case 't':
2090 defconfig[StrictTLS].val.i = 0;
2091 defconfig[StrictTLS].prio = 2;
2092 break;
2093 case 'T':
2094 defconfig[StrictTLS].val.i = 1;
2095 defconfig[StrictTLS].prio = 2;
2096 break;
2097 case 'u':
2098 fulluseragent = EARGF(usage());
2099 break;
2100 case 'v':
2101 die("surf-"VERSION", see LICENSE for © details\n");
2102 case 'w':
2103 showxid = 1;
2104 break;
2105 case 'x':
2106 defconfig[Certificate].val.i = 0;
2107 defconfig[Certificate].prio = 2;
2108 break;
2109 case 'X':
2110 defconfig[Certificate].val.i = 1;
2111 defconfig[Certificate].prio = 2;
2112 break;
2113 case 'z':
2114 defconfig[ZoomLevel].val.f = strtof(EARGF(usage()), NULL…
2115 defconfig[ZoomLevel].prio = 2;
2116 break;
2117 default:
2118 usage();
2119 } ARGEND;
2120 if (argc > 0)
2121 arg.v = argv[0];
2122 else
2123 arg.v = "about:blank";
2124
2125 setup();
2126 c = newclient(NULL);
2127 showview(NULL, c);
2128
2129 loaduri(c, &arg);
2130 updatetitle(c);
2131
2132 gtk_main();
2133 cleanup();
2134
2135 return 0;
2136 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.