From 9eb4f964f337edc42728827901dcca0704cc9687 Mon Sep 17 00:00:00 2001 From: Derek Taylor Date: Wed, 25 Mar 2020 17:35:35 -0500 Subject: [PATCH] Initial push of my surf build. --- FAQ.md | 10 + LICENSE | 48 + Makefile | 77 + README.md | 41 + TODO.md | 10 + arg.h | 48 + common.c | 15 + common.h | 3 + common.o | Bin 0 -> 1664 bytes config.def.h | 208 +++ config.def.h.orig | 195 +++ config.h | 213 +++ config.mk | 31 + libsurf-webext.c | 128 ++ libsurf-webext.o | Bin 0 -> 5480 bytes libsurf-webext.so | Bin 0 -> 17576 bytes surf | Bin 0 -> 72808 bytes surf-bookmarks-20170722-723ff26.diff | 42 + surf-open.sh | 32 + surf.1 | 330 ++++ surf.c | 2127 ++++++++++++++++++++++++++ surf.o | Bin 0 -> 71720 bytes surf.png | Bin 0 -> 240 bytes 23 files changed, 3558 insertions(+) create mode 100755 FAQ.md create mode 100755 LICENSE create mode 100755 Makefile create mode 100755 TODO.md create mode 100755 arg.h create mode 100755 common.c create mode 100755 common.h create mode 100644 common.o create mode 100755 config.def.h create mode 100755 config.def.h.orig create mode 100755 config.h create mode 100755 config.mk create mode 100755 libsurf-webext.c create mode 100644 libsurf-webext.o create mode 100755 libsurf-webext.so create mode 100755 surf create mode 100644 surf-bookmarks-20170722-723ff26.diff create mode 100755 surf-open.sh create mode 100755 surf.1 create mode 100755 surf.c create mode 100644 surf.o create mode 100755 surf.png diff --git a/FAQ.md b/FAQ.md new file mode 100755 index 0000000000000000000000000000000000000000..48e6097c2eb2dede10b7d6a5d84e50dcfef373aa --- /dev/null +++ b/FAQ.md @@ -0,0 +1,10 @@ +# Frequently Asked Questions + +## Surf is starting up slowly. What might be causing this? + +The first suspect for such behaviour is the plugin handling. Run surf on +the commandline and see if there are errors because of “nspluginwrapper” +or failed RPCs to them. If that is true, go to ~/.mozilla/plugins and +try removing stale links to plugins not on your system anymore. This +will stop surf from trying to load them. + diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000000000000000000000000000000000000..2cdab7cc87e49387275cd92e2562da573bc0d352 --- /dev/null +++ b/LICENSE @@ -0,0 +1,48 @@ +MIT/X Consortium License + +© 2009-2010 Enno Boland +© 2009 Thomas Menari +© 2009 Simon Rozet +© 2009 Andrew Antle +© 2010-2011 pancake +© 2011-2013 Anselm R Garbe +© 2011-2012 Troels Henriksen +© 2011 Connor Lane Smith +© 2012-2017 Christoph Lohmann <20h@r-36.net> +© 2013 Shayan Pooya +© 2013 Jens Nyberg +© 2013 Carlos J. Torres +© 2013 Alexander Sedov +© 2013 Nick White +© 2013 David Dufberg +© 2014-2017 Quentin Rameau +© 2014-2016 Markus Teich +© 2015 Jakukyo Friel +© 2015 Ben Woolley +© 2015 Greg Reagle +© 2015 GhostAV +© 2015 Ivan Tham +© 2015 Alexander Huemer +© 2015 Michael Stevens +© 2015 Felix Janda +© 2016 Charles Lehner +© 2016 Dmitry Bogatov + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + diff --git a/Makefile b/Makefile new file mode 100755 index 0000000000000000000000000000000000000000..4f5253ab94c9f64af4ceb2243428e76e74cefe61 --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +# surf - simple browser +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = surf.c +CSRC = common.c +WEBEXTSRC = libsurf-webext.c +OBJ = $(SRC:.c=.o) +COBJ = $(CSRC:.c=.o) +WEBEXTOBJ = $(WEBEXTSRC:.c=.o) + +all: options libsurf-webext.so surf + +options: + @echo surf build options: + @echo "CC = $(CC)" + @echo "CFLAGS = $(SURFCFLAGS) $(CFLAGS)" + @echo "WEBEXTCFLAGS = $(WEBEXTCFLAGS) $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + +.c.o: + $(CC) $(SURFCFLAGS) $(CFLAGS) -c $< + +config.h: + cp config.def.h $@ + +$(OBJ): config.h common.h config.mk +$(COBJ): config.h common.h config.mk +$(WEBEXTOBJ): config.h common.h config.mk + +$(WEBEXTOBJ): $(WEBEXTSRC) + $(CC) $(WEBEXTCFLAGS) $(CFLAGS) -c $(WEBEXTSRC) + +libsurf-webext.so: $(WEBEXTOBJ) $(COBJ) + $(CC) -shared -Wl,-soname,$@ $(LDFLAGS) -o $@ \ + $(WEBEXTOBJ) $(COBJ) $(WEBEXTLIBS) + +surf: $(OBJ) $(COBJ) + $(CC) $(SURFLDFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(COBJ) $(LIBS) + +clean: + rm -f surf $(OBJ) $(COBJ) + rm -f libsurf-webext.so $(WEBEXTOBJ) + +distclean: clean + rm -f config.h surf-$(VERSION).tar.gz + +dist: distclean + mkdir -p surf-$(VERSION) + cp -R LICENSE Makefile config.mk config.def.h README \ + surf-open.sh arg.h TODO.md surf.png \ + surf.1 $(SRC) $(WEBEXTSRC) surf-$(VERSION) + tar -cf surf-$(VERSION).tar surf-$(VERSION) + gzip surf-$(VERSION).tar + rm -rf surf-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f surf $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/surf + mkdir -p $(DESTDIR)$(LIBDIR) + cp -f libsurf-webext.so $(DESTDIR)$(LIBDIR) + chmod 644 $(DESTDIR)$(LIBDIR)/libsurf-webext.so + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < surf.1 > $(DESTDIR)$(MANPREFIX)/man1/surf.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/surf.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/surf + rm -f $(DESTDIR)$(MANPREFIX)/man1/surf.1 + rm -f $(DESTDIR)$(LIBDIR)/libsurf-webext.so + - rmdir $(DESTDIR)$(LIBDIR) + +.SUFFIXES: .so .o .c +.PHONY: all options clean-dist clean dist install uninstall diff --git a/README.md b/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..26e87537020dd99cf852150f8462f05fbb745ebf 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,41 @@ +surf - simple webkit-based browser +================================== +surf is a simple Web browser based on WebKit/GTK+. + +Requirements +------------ +In order to build surf you need GTK+ and Webkit/GTK+ header files. + +In order to use the functionality of the url-bar, also install dmenu[0]. + +Installation +------------ +Edit config.mk to match your local setup (surf is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install surf (if +necessary as root): + + make clean install + +Running surf +------------ +run + surf [URI] + +See the manpage for further options. + +Running surf in tabbed +---------------------- +For running surf in tabbed[1] there is a script included in the distribution, +which is run like this: + + surf-open.sh [URI] + +Further invocations of the script will run surf with the specified URI in this +instance of tabbed. + +[0] http://tools.suckless.org/dmenu +[1] http://tools.suckless.org/tabbed + + diff --git a/TODO.md b/TODO.md new file mode 100755 index 0000000000000000000000000000000000000000..da5f44d57e606601d412dfb3e8a29fb6c694dfd7 --- /dev/null +++ b/TODO.md @@ -0,0 +1,10 @@ +# TODO + +* suckless adblocking +* replace twitch() with proper gtk calls to make scrollbars reappear +* replace webkit with something sane +* add video player options + * play in plugin + * play in video player + * call command with URI (quvi + cclive) + diff --git a/arg.h b/arg.h new file mode 100755 index 0000000000000000000000000000000000000000..ba3fb3fdb9ac264cf36eb286a36dabd9907a7126 --- /dev/null +++ b/arg.h @@ -0,0 +1,48 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/common.c b/common.c new file mode 100755 index 0000000000000000000000000000000000000000..42662ed487605d88503b9e92006dfd2f15080100 --- /dev/null +++ b/common.c @@ -0,0 +1,15 @@ +#include +#include +#include + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + diff --git a/common.h b/common.h new file mode 100755 index 0000000000000000000000000000000000000000..2778029a74992a7b0468b7b4c24af1b3b550745d --- /dev/null +++ b/common.h @@ -0,0 +1,3 @@ +#define MSGBUFSZ 8 + +void die(char *, ...); diff --git a/common.o b/common.o new file mode 100644 index 0000000000000000000000000000000000000000..b93f45af95e84d53cefe31237e1f2b44547ec585 GIT binary patch literal 1664 zcmbu8&uJJ8<-ny>s*v`{?LrHbHIjapX!09gfwK*~w9aMR9giWRVQf{XKRp zR-=5s$P|i8aQhp#|M2k{TmnAMeJA}wpX7K@+1lDrQU$ZoRpKKY}$1w zn(U%05PJClLc>I8HMST%z$(Nqi8VSbRF;o}`Xxe~UPj>8iJW^i9N1}qF#dup43Qv= z(P!uI#W~z*&dl0*oL%so$N4s7%QzAflA!(sw`|Sf;CO>6yk;UA2s%1!_~bbWnE&15oLMj2^OH-!jz7y+>;U zOK&^@)d@G+^_Ho3jcNV=bQH{|`{%y_d1Pere{^SVtn^7&aZr!)IJ`CyP2d8aL#2J_ z1(GFQfvc#*{WtcfSoe$nt=zx>s`;<^SVH%;ME05bPp?_#OHSMP_eKO ~/.surf/bookmarks.tmp && " \ + "mv ~/.surf/bookmarks.tmp ~/.surf/bookmarks", \ + winid, r, NULL \ + } \ +} + +/* styles */ +/* + * The iteration will stop at the first match, beginning at the beginning of + * the list. + */ +static SiteSpecific styles[] = { + /* regexp file in $styledir */ + { ".*", "default.css" }, +}; + +/* certificates */ +/* + * Provide custom certificate for urls + */ +static SiteSpecific certs[] = { + /* regexp file in $certdir */ + { "://suckless\\.org/", "suckless.org.crt" }, +}; + +#define MODKEY GDK_CONTROL_MASK + +/* hotkeys */ +/* + * If you use anything else but MODKEY and GDK_SHIFT_MASK, don't forget to + * edit the CLEANMASK() macro. + */ +static Key keys[] = { + /* modifier keyval function arg */ + { MODKEY, GDK_KEY_g, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, + { MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { MODKEY, GDK_KEY_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { MODKEY, GDK_KEY_m, spawn, BM_ADD("_SURF_URI") }, + + { 0, GDK_KEY_Escape, stop, { 0 } }, + { MODKEY, GDK_KEY_c, stop, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_r, reload, { .i = 1 } }, + { MODKEY, GDK_KEY_r, reload, { .i = 0 } }, + + { MODKEY, GDK_KEY_l, navigate, { .i = +1 } }, + { MODKEY, GDK_KEY_h, navigate, { .i = -1 } }, + + /* vertical and horizontal scrolling, in viewport percentage */ + { MODKEY, GDK_KEY_j, scrollv, { .i = +10 } }, + { MODKEY, GDK_KEY_k, scrollv, { .i = -10 } }, + { MODKEY, GDK_KEY_space, scrollv, { .i = +50 } }, + { MODKEY, GDK_KEY_b, scrollv, { .i = -50 } }, + { MODKEY, GDK_KEY_i, scrollh, { .i = +10 } }, + { MODKEY, GDK_KEY_u, scrollh, { .i = -10 } }, + + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_j, zoom, { .i = -1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_k, zoom, { .i = +1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_q, zoom, { .i = 0 } }, + { MODKEY, GDK_KEY_minus, zoom, { .i = -1 } }, + { MODKEY, GDK_KEY_plus, zoom, { .i = +1 } }, + + { MODKEY, GDK_KEY_p, clipboard, { .i = 1 } }, + { MODKEY, GDK_KEY_y, clipboard, { .i = 0 } }, + + { MODKEY, GDK_KEY_n, find, { .i = +1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_n, find, { .i = -1 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_p, print, { 0 } }, + { MODKEY, GDK_KEY_t, showcert, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_a, togglecookiepolicy, { 0 } }, + { 0, GDK_KEY_F11, togglefullscreen, { 0 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_o, toggleinspector, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_c, toggle, { .i = CaretBrowsing } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_f, toggle, { .i = FrameFlattening } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_g, toggle, { .i = Geolocation } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_s, toggle, { .i = JavaScript } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_i, toggle, { .i = LoadImages } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_v, toggle, { .i = Plugins } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_b, toggle, { .i = ScrollBars } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_t, toggle, { .i = StrictTLS } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_m, toggle, { .i = Style } }, +}; + +/* button definitions */ +/* target can be OnDoc, OnLink, OnImg, OnMedia, OnEdit, OnBar, OnSel, OnAny */ +static Button buttons[] = { + /* target event mask button function argument stop event */ + { OnLink, 0, 2, clicknewwindow, { .i = 0 }, 1 }, + { OnLink, MODKEY, 2, clicknewwindow, { .i = 1 }, 1 }, + { OnLink, MODKEY, 1, clicknewwindow, { .i = 1 }, 1 }, + { OnAny, 0, 8, clicknavigate, { .i = -1 }, 1 }, + { OnAny, 0, 9, clicknavigate, { .i = +1 }, 1 }, + { OnMedia, MODKEY, 1, clickexternplayer, { 0 }, 1 }, +}; diff --git a/config.def.h.orig b/config.def.h.orig new file mode 100755 index 0000000000000000000000000000000000000000..34265f6e89320b984968ebe04899914f671dec95 --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,195 @@ +/* modifier 0 means no modifier */ +static int surfuseragent = 1; /* Append Surf version to default WebKit user agent */ +static char *fulluseragent = ""; /* Or override the whole user agent string */ +static char *scriptfile = "~/.surf/script.js"; +static char *styledir = "~/.surf/styles/"; +static char *certdir = "~/.surf/certificates/"; +static char *cachedir = "~/.surf/cache/"; +static char *cookiefile = "~/.surf/cookies.txt"; + +/* Webkit default features */ +/* Highest priority value will be used. + * Default parameters are priority 0 + * Per-uri parameters are priority 1 + * Command parameters are priority 2 + */ +static Parameter defconfig[ParameterLast] = { + /* parameter Arg value priority */ + [AcceleratedCanvas] = { { .i = 1 }, }, + [AccessMicrophone] = { { .i = 0 }, }, + [AccessWebcam] = { { .i = 0 }, }, + [Certificate] = { { .i = 0 }, }, + [CaretBrowsing] = { { .i = 0 }, }, + [CookiePolicies] = { { .v = "@Aa" }, }, + [DefaultCharset] = { { .v = "UTF-8" }, }, + [DiskCache] = { { .i = 1 }, }, + [DNSPrefetch] = { { .i = 0 }, }, + [FileURLsCrossAccess] = { { .i = 0 }, }, + [FontSize] = { { .i = 12 }, }, + [FrameFlattening] = { { .i = 0 }, }, + [Geolocation] = { { .i = 0 }, }, + [HideBackground] = { { .i = 0 }, }, + [Inspector] = { { .i = 0 }, }, + [Java] = { { .i = 1 }, }, + [JavaScript] = { { .i = 1 }, }, + [KioskMode] = { { .i = 0 }, }, + [LoadImages] = { { .i = 1 }, }, + [MediaManualPlay] = { { .i = 1 }, }, + [Plugins] = { { .i = 1 }, }, + [PreferredLanguages] = { { .v = (char *[]){ NULL } }, }, + [RunInFullscreen] = { { .i = 0 }, }, + [ScrollBars] = { { .i = 1 }, }, + [ShowIndicators] = { { .i = 1 }, }, + [SiteQuirks] = { { .i = 1 }, }, + [SmoothScrolling] = { { .i = 0 }, }, + [SpellChecking] = { { .i = 0 }, }, + [SpellLanguages] = { { .v = ((char *[]){ "en_US", NULL }) }, }, + [StrictTLS] = { { .i = 1 }, }, + [Style] = { { .i = 1 }, }, + [WebGL] = { { .i = 0 }, }, + [ZoomLevel] = { { .f = 1.0 }, }, +}; + +static UriParameters uriparams[] = { + { "(://|\\.)suckless\\.org(/|$)", { + [JavaScript] = { { .i = 0 }, 1 }, + [Plugins] = { { .i = 0 }, 1 }, + }, }, +}; + +/* default window size: width, height */ +static int winsize[] = { 800, 600 }; + +static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | + WEBKIT_FIND_OPTIONS_WRAP_AROUND; + +#define PROMPT_GO "Go:" +#define PROMPT_FIND "Find:" + +/* SETPROP(readprop, setprop, prompt)*/ +#define SETPROP(r, s, p) { \ + .v = (const char *[]){ "/bin/sh", "-c", \ + "prop=\"$(printf '%b' \"$(xprop -id $1 $2 " \ + "| sed \"s/^$2(STRING) = //;s/^\\\"\\(.*\\)\\\"$/\\1/\")\" " \ + "| dmenu -p \"$4\" -w $1)\" && xprop -id $1 -f $3 8s -set $3 \"$prop\"", \ + "surf-setprop", winid, r, s, p, NULL \ + } \ +} + +/* DOWNLOAD(URI, referer) */ +#define DOWNLOAD(u, r) { \ + .v = (const char *[]){ "st", "-e", "/bin/sh", "-c",\ + "curl -g -L -J -O -A \"$1\" -b \"$2\" -c \"$2\"" \ + " -e \"$3\" \"$4\"; read", \ + "surf-download", useragent, cookiefile, r, u, NULL \ + } \ +} + +/* PLUMB(URI) */ +/* This called when some URI which does not begin with "about:", + * "http://" or "https://" should be opened. + */ +#define PLUMB(u) {\ + .v = (const char *[]){ "/bin/sh", "-c", \ + "xdg-open \"$0\"", u, NULL \ + } \ +} + +/* VIDEOPLAY(URI) */ +#define VIDEOPLAY(u) {\ + .v = (const char *[]){ "/bin/sh", "-c", \ + "mpv --really-quiet \"$0\"", u, NULL \ + } \ +} + +/* styles */ +/* + * The iteration will stop at the first match, beginning at the beginning of + * the list. + */ +static SiteSpecific styles[] = { + /* regexp file in $styledir */ + { ".*", "default.css" }, +}; + +/* certificates */ +/* + * Provide custom certificate for urls + */ +static SiteSpecific certs[] = { + /* regexp file in $certdir */ + { "://suckless\\.org/", "suckless.org.crt" }, +}; + +#define MODKEY GDK_CONTROL_MASK + +/* hotkeys */ +/* + * If you use anything else but MODKEY and GDK_SHIFT_MASK, don't forget to + * edit the CLEANMASK() macro. + */ +static Key keys[] = { + /* modifier keyval function arg */ + { MODKEY, GDK_KEY_g, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, + { MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { MODKEY, GDK_KEY_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + + { 0, GDK_KEY_Escape, stop, { 0 } }, + { MODKEY, GDK_KEY_c, stop, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_r, reload, { .i = 1 } }, + { MODKEY, GDK_KEY_r, reload, { .i = 0 } }, + + { MODKEY, GDK_KEY_l, navigate, { .i = +1 } }, + { MODKEY, GDK_KEY_h, navigate, { .i = -1 } }, + + /* vertical and horizontal scrolling, in viewport percentage */ + { MODKEY, GDK_KEY_j, scrollv, { .i = +10 } }, + { MODKEY, GDK_KEY_k, scrollv, { .i = -10 } }, + { MODKEY, GDK_KEY_space, scrollv, { .i = +50 } }, + { MODKEY, GDK_KEY_b, scrollv, { .i = -50 } }, + { MODKEY, GDK_KEY_i, scrollh, { .i = +10 } }, + { MODKEY, GDK_KEY_u, scrollh, { .i = -10 } }, + + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_j, zoom, { .i = -1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_k, zoom, { .i = +1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_q, zoom, { .i = 0 } }, + { MODKEY, GDK_KEY_minus, zoom, { .i = -1 } }, + { MODKEY, GDK_KEY_plus, zoom, { .i = +1 } }, + + { MODKEY, GDK_KEY_p, clipboard, { .i = 1 } }, + { MODKEY, GDK_KEY_y, clipboard, { .i = 0 } }, + + { MODKEY, GDK_KEY_n, find, { .i = +1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_n, find, { .i = -1 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_p, print, { 0 } }, + { MODKEY, GDK_KEY_t, showcert, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_a, togglecookiepolicy, { 0 } }, + { 0, GDK_KEY_F11, togglefullscreen, { 0 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_o, toggleinspector, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_c, toggle, { .i = CaretBrowsing } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_f, toggle, { .i = FrameFlattening } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_g, toggle, { .i = Geolocation } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_s, toggle, { .i = JavaScript } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_i, toggle, { .i = LoadImages } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_v, toggle, { .i = Plugins } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_b, toggle, { .i = ScrollBars } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_t, toggle, { .i = StrictTLS } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_m, toggle, { .i = Style } }, +}; + +/* button definitions */ +/* target can be OnDoc, OnLink, OnImg, OnMedia, OnEdit, OnBar, OnSel, OnAny */ +static Button buttons[] = { + /* target event mask button function argument stop event */ + { OnLink, 0, 2, clicknewwindow, { .i = 0 }, 1 }, + { OnLink, MODKEY, 2, clicknewwindow, { .i = 1 }, 1 }, + { OnLink, MODKEY, 1, clicknewwindow, { .i = 1 }, 1 }, + { OnAny, 0, 8, clicknavigate, { .i = -1 }, 1 }, + { OnAny, 0, 9, clicknavigate, { .i = +1 }, 1 }, + { OnMedia, MODKEY, 1, clickexternplayer, { 0 }, 1 }, +}; diff --git a/config.h b/config.h new file mode 100755 index 0000000000000000000000000000000000000000..3197d96c3c21ef8d88cfb4bf305abc9cebd47eac --- /dev/null +++ b/config.h @@ -0,0 +1,213 @@ +/* modifier 0 means no modifier */ +static int surfuseragent = 1; /* Append Surf version to default WebKit user agent */ +static char *fulluseragent = ""; /* Or override the whole user agent string */ +static char *scriptfile = "~/.surf/script.js"; +static char *styledir = "~/.surf/styles/"; +static char *certdir = "~/.surf/certificates/"; +static char *cachedir = "~/.surf/cache/"; +static char *cookiefile = "~/.surf/cookies.txt"; + +/* Webkit default features */ +/* Highest priority value will be used. + * Default parameters are priority 0 + * Per-uri parameters are priority 1 + * Command parameters are priority 2 + */ +static Parameter defconfig[ParameterLast] = { + /* parameter Arg value priority */ + [AcceleratedCanvas] = { { .i = 1 }, }, + [AccessMicrophone] = { { .i = 0 }, }, + [AccessWebcam] = { { .i = 0 }, }, + [Certificate] = { { .i = 0 }, }, + [CaretBrowsing] = { { .i = 0 }, }, + [CookiePolicies] = { { .v = "@Aa" }, }, + [DefaultCharset] = { { .v = "UTF-8" }, }, + [DiskCache] = { { .i = 1 }, }, + [DNSPrefetch] = { { .i = 0 }, }, + [FileURLsCrossAccess] = { { .i = 0 }, }, + [FontSize] = { { .i = 12 }, }, + [FrameFlattening] = { { .i = 0 }, }, + [Geolocation] = { { .i = 0 }, }, + [HideBackground] = { { .i = 0 }, }, + [Inspector] = { { .i = 0 }, }, + [Java] = { { .i = 1 }, }, + [JavaScript] = { { .i = 1 }, }, + [KioskMode] = { { .i = 0 }, }, + [LoadImages] = { { .i = 1 }, }, + [MediaManualPlay] = { { .i = 1 }, }, + [Plugins] = { { .i = 1 }, }, + [PreferredLanguages] = { { .v = (char *[]){ NULL } }, }, + [RunInFullscreen] = { { .i = 0 }, }, + [ScrollBars] = { { .i = 1 }, }, + [ShowIndicators] = { { .i = 1 }, }, + [SiteQuirks] = { { .i = 1 }, }, + [SmoothScrolling] = { { .i = 0 }, }, + [SpellChecking] = { { .i = 0 }, }, + [SpellLanguages] = { { .v = ((char *[]){ "en_US", NULL }) }, }, + [StrictTLS] = { { .i = 1 }, }, + [Style] = { { .i = 1 }, }, + [WebGL] = { { .i = 0 }, }, + [ZoomLevel] = { { .f = 1.0 }, }, +}; + +static UriParameters uriparams[] = { + { "(://|\\.)suckless\\.org(/|$)", { + [JavaScript] = { { .i = 0 }, 1 }, + [Plugins] = { { .i = 0 }, 1 }, + }, }, +}; + +/* default window size: width, height */ +static int winsize[] = { 800, 600 }; + +static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | + WEBKIT_FIND_OPTIONS_WRAP_AROUND; + +#define PROMPT_GO "Go:" +#define PROMPT_FIND "Find:" + +/* SETPROP(readprop, setprop, prompt)*/ +#define SETPROP(r, s, p) { \ + .v = (const char *[]){ "/bin/sh", "-c", \ + "prop=\"$(printf '%b' \"$(xprop -id $1 $2 " \ + "| sed \"s/^$2(STRING) = //;s/^\\\"\\(.*\\)\\\"$/\\1/\" && cat ~/.surf/bookmarks)\" " \ + "| dmenu -l 10 -p \"$4\" -w $1)\" && " \ + "xprop -id $1 -f $3 8s -set $3 \"$prop\"", \ + "surf-setprop", winid, r, s, p, NULL \ + } \ +} + +/* DOWNLOAD(URI, referer) */ +#define DOWNLOAD(u, r) { \ + .v = (const char *[]){ "st", "-e", "/bin/sh", "-c",\ + "curl -g -L -J -O -A \"$1\" -b \"$2\" -c \"$2\"" \ + " -e \"$3\" \"$4\"; read", \ + "surf-download", useragent, cookiefile, r, u, NULL \ + } \ +} + +/* PLUMB(URI) */ +/* This called when some URI which does not begin with "about:", + * "http://" or "https://" should be opened. + */ +#define PLUMB(u) {\ + .v = (const char *[]){ "/bin/sh", "-c", \ + "xdg-open \"$0\"", u, NULL \ + } \ +} + +/* VIDEOPLAY(URI) */ +#define VIDEOPLAY(u) {\ + .v = (const char *[]){ "/bin/sh", "-c", \ + "mpv --really-quiet \"$0\"", u, NULL \ + } \ +} + +/* BM_ADD(readprop) */ +#define BM_ADD(r) {\ + .v = (const char *[]){ "/bin/sh", "-c", \ + "(echo $(xprop -id $0 $1) | cut -d '\"' -f2 " \ + "| sed 's/.*https*:\\/\\/\\(www\\.\\)\\?//' && cat ~/.surf/bookmarks) " \ + "| awk '!seen[$0]++' > ~/.surf/bookmarks.tmp && " \ + "mv ~/.surf/bookmarks.tmp ~/.surf/bookmarks", \ + winid, r, NULL \ + } \ +} + +/* styles */ +/* + * The iteration will stop at the first match, beginning at the beginning of + * the list. + */ +static SiteSpecific styles[] = { + /* regexp file in $styledir */ + /* { ".*", "default.css" }, */ + { ".*archlinux.org.*", "archlinux.css" }, + { ".*arcolinux.info.*", "arcolinux-info.css" }, + { ".*arcolinuxforum.com.*", "arcolinuxforum.css" }, + { ".*suckless.org.*", "suckless.css" }, + { ".*wikipedia.org.*", "wikipedia.css" }, +}; + +/* certificates */ +/* + * Provide custom certificate for urls + */ +static SiteSpecific certs[] = { + /* regexp file in $certdir */ + { "://suckless\\.org/", "suckless.org.crt" }, +}; + +#define MODKEY GDK_CONTROL_MASK + +/* hotkeys */ +/* + * If you use anything else but MODKEY and GDK_SHIFT_MASK, don't forget to + * edit the CLEANMASK() macro. + */ +static Key keys[] = { + /* modifier keyval function arg */ + { MODKEY, GDK_KEY_g, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, + { MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { MODKEY, GDK_KEY_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { MODKEY, GDK_KEY_m, spawn, BM_ADD("_SURF_URI") }, + + { 0, GDK_KEY_Escape, stop, { 0 } }, + { MODKEY, GDK_KEY_c, stop, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_r, reload, { .i = 1 } }, + { MODKEY, GDK_KEY_r, reload, { .i = 0 } }, + + { MODKEY, GDK_KEY_l, navigate, { .i = +1 } }, + { MODKEY, GDK_KEY_h, navigate, { .i = -1 } }, + + /* vertical and horizontal scrolling, in viewport percentage */ + { MODKEY, GDK_KEY_j, scrollv, { .i = +10 } }, + { MODKEY, GDK_KEY_k, scrollv, { .i = -10 } }, + { MODKEY, GDK_KEY_space, scrollv, { .i = +50 } }, + { MODKEY, GDK_KEY_b, scrollv, { .i = -50 } }, + { MODKEY, GDK_KEY_i, scrollh, { .i = +10 } }, + { MODKEY, GDK_KEY_u, scrollh, { .i = -10 } }, + + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_j, zoom, { .i = -1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_k, zoom, { .i = +1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_q, zoom, { .i = 0 } }, + { MODKEY, GDK_KEY_minus, zoom, { .i = -1 } }, + { MODKEY, GDK_KEY_plus, zoom, { .i = +1 } }, + + { MODKEY, GDK_KEY_p, clipboard, { .i = 1 } }, + { MODKEY, GDK_KEY_y, clipboard, { .i = 0 } }, + + { MODKEY, GDK_KEY_n, find, { .i = +1 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_n, find, { .i = -1 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_p, print, { 0 } }, + { MODKEY, GDK_KEY_t, showcert, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_a, togglecookiepolicy, { 0 } }, + { 0, GDK_KEY_F11, togglefullscreen, { 0 } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_o, toggleinspector, { 0 } }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_c, toggle, { .i = CaretBrowsing } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_f, toggle, { .i = FrameFlattening } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_g, toggle, { .i = Geolocation } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_s, toggle, { .i = JavaScript } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_i, toggle, { .i = LoadImages } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_v, toggle, { .i = Plugins } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_b, toggle, { .i = ScrollBars } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_t, toggle, { .i = StrictTLS } }, + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_m, toggle, { .i = Style } }, +}; + +/* button definitions */ +/* target can be OnDoc, OnLink, OnImg, OnMedia, OnEdit, OnBar, OnSel, OnAny */ +static Button buttons[] = { + /* target event mask button function argument stop event */ + { OnLink, 0, 2, clicknewwindow, { .i = 0 }, 1 }, + { OnLink, MODKEY, 2, clicknewwindow, { .i = 1 }, 1 }, + { OnLink, MODKEY, 1, clicknewwindow, { .i = 1 }, 1 }, + { OnAny, 0, 8, clicknavigate, { .i = -1 }, 1 }, + { OnAny, 0, 9, clicknavigate, { .i = +1 }, 1 }, + { OnMedia, MODKEY, 1, clickexternplayer, { 0 }, 1 }, +}; diff --git a/config.mk b/config.mk new file mode 100755 index 0000000000000000000000000000000000000000..5e68e38338639472b64b12f8668b6d2794559811 --- /dev/null +++ b/config.mk @@ -0,0 +1,31 @@ +# surf version +VERSION = 2.0 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man +LIBPREFIX = $(PREFIX)/lib +LIBDIR = $(LIBPREFIX)/surf + +X11INC = `pkg-config --cflags x11` +X11LIB = `pkg-config --libs x11` + +GTKINC = `pkg-config --cflags gtk+-3.0 gcr-3 webkit2gtk-4.0` +GTKLIB = `pkg-config --libs gtk+-3.0 gcr-3 webkit2gtk-4.0` +WEBEXTINC = `pkg-config --cflags webkit2gtk-4.0 webkit2gtk-web-extension-4.0` +WEBEXTLIBS = `pkg-config --libs webkit2gtk-4.0 webkit2gtk-web-extension-4.0` + +# includes and libs +INCS = $(X11INC) $(GTKINC) +LIBS = $(X11LIB) $(GTKLIB) -lgthread-2.0 + +# flags +CPPFLAGS = -DVERSION=\"$(VERSION)\" -DWEBEXTDIR=\"$(LIBDIR)\" \ + -D_DEFAULT_SOURCE -DGCR_API_SUBJECT_TO_CHANGE +SURFCFLAGS = $(INCS) $(CPPFLAGS) -fPIC +WEBEXTCFLAGS = -fPIC $(WEBEXTINC) + +# compiler +#CC = c99 diff --git a/libsurf-webext.c b/libsurf-webext.c new file mode 100755 index 0000000000000000000000000000000000000000..ec9a2356f6facb08396babfa5fc7577c30fdcbd4 --- /dev/null +++ b/libsurf-webext.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common.h" + +#define LENGTH(x) (sizeof(x) / sizeof(x[0])) + +typedef struct Page { + guint64 id; + WebKitWebPage *webpage; + struct Page *next; +} Page; + +static int pipein, pipeout; +static Page *pages; + +Page * +newpage(WebKitWebPage *page) +{ + Page *p; + + if (!(p = calloc(1, sizeof(Page)))) + die("Cannot malloc!\n"); + + p->next = pages; + pages = p; + + p->id = webkit_web_page_get_id(page); + p->webpage = page; + + return p; +} + +static void +msgsurf(Page *p, const char *s) +{ + static char msg[MSGBUFSZ]; + size_t sln = strlen(s); + int ret; + + if ((ret = snprintf(msg, sizeof(msg), "%c%c%s", + 2 + sln, p ? p->id : 0, s)) + >= sizeof(msg)) { + fprintf(stderr, "webext: message too long: %d\n", ret); + return; + } + + if (pipeout && write(pipeout, msg, sizeof(msg)) < 0) + fprintf(stderr, "webext: error sending: %.*s\n", ret-2, msg+2); +} + +static gboolean +readpipe(GIOChannel *s, GIOCondition c, gpointer unused) +{ + static char msg[MSGBUFSZ], msgsz; + WebKitDOMDOMWindow *view; + GError *gerr = NULL; + glong wh, ww; + Page *p; + + if (g_io_channel_read_chars(s, msg, LENGTH(msg), NULL, &gerr) != + G_IO_STATUS_NORMAL) { + fprintf(stderr, "webext: error reading pipe: %s\n", + gerr->message); + g_error_free(gerr); + return TRUE; + } + if ((msgsz = msg[0]) < 3) { + fprintf(stderr, "webext: message too short: %d\n", msgsz); + return TRUE; + } + + for (p = pages; p; p = p->next) { + if (p->id == msg[1]) + break; + } + if (!p || !(view = webkit_dom_document_get_default_view( + webkit_web_page_get_dom_document(p->webpage)))) + return TRUE; + + switch (msg[2]) { + case 'h': + if (msgsz != 4) + return TRUE; + ww = webkit_dom_dom_window_get_inner_width(view); + webkit_dom_dom_window_scroll_by(view, + (ww / 100) * msg[3], 0); + break; + case 'v': + if (msgsz != 4) + return TRUE; + wh = webkit_dom_dom_window_get_inner_height(view); + webkit_dom_dom_window_scroll_by(view, + 0, (wh / 100) * msg[3]); + break; + } + + return TRUE; +} + +static void +webpagecreated(WebKitWebExtension *e, WebKitWebPage *wp, gpointer unused) +{ + Page *p = newpage(wp); +} + +G_MODULE_EXPORT void +webkit_web_extension_initialize_with_user_data(WebKitWebExtension *e, GVariant *gv) +{ + GIOChannel *gchanpipe; + + g_signal_connect(e, "page-created", G_CALLBACK(webpagecreated), NULL); + + g_variant_get(gv, "(ii)", &pipein, &pipeout); + msgsurf(NULL, "i"); + + gchanpipe = g_io_channel_unix_new(pipein); + g_io_channel_set_encoding(gchanpipe, NULL, NULL); + g_io_channel_set_close_on_unref(gchanpipe, TRUE); + g_io_add_watch(gchanpipe, G_IO_IN, readpipe, NULL); +} diff --git a/libsurf-webext.o b/libsurf-webext.o new file mode 100644 index 0000000000000000000000000000000000000000..24ec0c3cc8dc7d37a9d9c222c17ec7a3a423e0d6 GIT binary patch literal 5480 zcmbuCUu+yl9mmH$(;QB6ccd+i2!%~+gOk)pT&InjrmZh_l0{faMH*Bo3d{O#?OXY7 z&)VI~IgtRjjv*(@5)$xOK$I7r$_ph4$i#IajRX?VhXOAYo)QABpbu$*Vt(J*`JLz1 zw>-e_wDadPzxn-sGdnvo_vND#$GW?^1WT8AQf$9jJ*-Ihi!qr3EAE*?SLdIA$)5UUv-yX}UUSvFy7i40XP@YO<3;n% zpUvf-UUT)`yUf-1o-+5m)(SiO(koNhx3B%(%iEQ7A^hs;& zl-YczAQwhK>dB(HeDCWRoVH5L&lufT zdNa8~CSBMn1_+NTozEjDov(BkGCLPiP(0F`*-0eQg`bMjm0QnvCO45uOCJ{%-`?`m zeWO=;Pz@b2sP>4z@pQLoq&n_+LZs-|Nm68~lca?FaNC-8P_0|DFXx8XE_%AiPEKiK zy+Zow0p~j6h78dIjt>;V?I@?jUC7$xAl2aa!R`q2_I-@1uwz7+N|BtP3|#KpRAzo! z@klG~SC4`<5YGoUDyn43eDnO26gQL}XRa)49+#&$&l~Fpip$-JA?0(5KcF!bzo;cXPl+vBEh>JMvUn_Lc_D%SFoFLhfxnu-f1SYZB=GxaA$5v#B!Qnz;1?74 zH;M0{6~yaXe%}GCXQNS+Hz}F zu}o)+Q?U>iDt;h>utcYan40xnFPtJZd6rmHzT@C{nQ=p_RIAFT;d-T7!p~1~qQ?^?Xp3{I=QIy9in`sNiEs2ub8ZDaW_;$R&t$8$1H+1cad%+=> zVcDt&H13ie+B8#vJMGyOt5~DSDAF`V^qlRxwijXqBJf(%s?C1gbLT9ItNon>G_8(T ztl`3&>Ynf5HIY5LRI(a&Sfp3ryP+cn=^KL3@|1i2HdzzmVU^c-c{<8k_WAyBHpb;mkJ!A%ab~e!?6Z))^x&Ss{2PEI@Q1yhIrd)& z_{ZpB{&PSQ#E-L?`7Z%U;`V3^KcZ2unGry#7KjjCc$al~oR z!}^bFe7DBOH2#3bKc_g`&rlnr9OE2kL$mWB>9OB$#q>GOA8GoAroXE3K8^oQX*Bq^fcJjbS@D4;H2n{2{OcOOPvhU#IBc<OE=Hsk7-6u@4g3;Sn=H3*x0E;8r-PccwA_vbi9iFfLMUHLa-{kb3K zCsluZJGx{ssU*9jtd(*<&Yz_gF1M7ZuwV7p8Om|ps$%*#0X^>j9aJ>o@iQt@q0{@% z;gBc!f=q?L`^UdW?$7TZ@x*()nC>5nDO}d5oV|U Nvnu`ujA;_@{~z?P#5Vu{ literal 0 HcmV?d00001 diff --git a/libsurf-webext.so b/libsurf-webext.so new file mode 100755 index 0000000000000000000000000000000000000000..df03b21cd554cf880a1752423bb617ac5b374422 GIT binary patch literal 17576 zcmeHPe{dVebzTsp1Cthk$fn9jNd&7TiuyrGW&~0sb;?k(B2ylxu6qK3BX|}F zu($(BY0{LjBu5OxsmAemG-;A@Y}ZXao~cIT(I{%FP+}{|jq734HkLbWLZ@~M#7Zp3 zjVvp*(D!!t0e457C(U%)f3O_j?fdqA{j%Y8&s?XulTLsE_? zb-U+HGmiCRK_TFz(rreR^gauEGGx;7hZ73>UfG{pTzoPvL}hsS`0j!(uzAPseQbR9 zmcM(cVbj)kZod1`Ph8wH+W+Lvuc8Ooz5+g|hyESg6ZBMjCVYr}5%G7yB(P^1~o6mjbAj%J*FQZFRA8!A1TOm@Ic( ze(55Q^EOy=qgd;Akze8B4~UnwgUds(Xth)sfr%Vo4eUy!gki0R_glxpcrrlwJwe!E zF29WVIwc@UTFQSz%Hur7GA8An_76z;=Np9FNkJG>)x>Z*r5a{5W2!1sclPg6<61^b z=!U6f`gd(lrc+vfbRemTw(>1%Y&@zC>Zxc_KcumQs;AZ1P&Ad&l4?ea#*xVw%rL=n z2AbhJolyrfn#RVofdjg!#?!<6Gp48F=`l5-nW_%z8FfsLn?o*AMl6#~Ce?w1)DFhf zk!V8W`od%^JFKNl60l-INE?i1lcqYVYh!kyVd&c7%aEoghD-<Qjz%+jG-culn2{RE=qYp19`$TWA6LOIJ8D2!wNxw}*HekbEwN5as1XL%mdBq!!2PW9d6r%I09|Rws0

uB8()`1CK(S(Ed8K&(R{6 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 C27)R8 literal 0 HcmV?d00001 diff --git a/surf-bookmarks-20170722-723ff26.diff b/surf-bookmarks-20170722-723ff26.diff new file mode 100644 index 0000000000000000000000000000000000000000..323ba55f222a32392bd22244d8625330c1b7e7e2 --- /dev/null +++ b/surf-bookmarks-20170722-723ff26.diff @@ -0,0 +1,42 @@ +diff --git a/config.def.h b/config.def.h +index 2e735bf..43ad9ab 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -69,8 +69,9 @@ static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | + #define SETPROP(r, s, p) { \ + .v = (const char *[]){ "/bin/sh", "-c", \ + "prop=\"$(printf '%b' \"$(xprop -id $1 $2 " \ +- "| sed \"s/^$2(STRING) = //;s/^\\\"\\(.*\\)\\\"$/\\1/\")\" " \ +- "| dmenu -p \"$4\" -w $1)\" && xprop -id $1 -f $3 8s -set $3 \"$prop\"", \ ++ "| sed \"s/^$2(STRING) = //;s/^\\\"\\(.*\\)\\\"$/\\1/\" && cat ~/.surf/bookmarks)\" " \ ++ "| dmenu -l 10 -p \"$4\" -w $1)\" && " \ ++ "xprop -id $1 -f $3 8s -set $3 \"$prop\"", \ + "surf-setprop", winid, r, s, p, NULL \ + } \ + } +@@ -101,6 +102,17 @@ static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | + } \ + } + ++/* BM_ADD(readprop) */ ++#define BM_ADD(r) {\ ++ .v = (const char *[]){ "/bin/sh", "-c", \ ++ "(echo $(xprop -id $0 $1) | cut -d '\"' -f2 " \ ++ "| sed 's/.*https*:\\/\\/\\(www\\.\\)\\?//' && cat ~/.surf/bookmarks) " \ ++ "| awk '!seen[$0]++' > ~/.surf/bookmarks.tmp && " \ ++ "mv ~/.surf/bookmarks.tmp ~/.surf/bookmarks", \ ++ winid, r, NULL \ ++ } \ ++} ++ + /* styles */ + /* + * The iteration will stop at the first match, beginning at the beginning of +@@ -132,6 +144,7 @@ static Key keys[] = { + { MODKEY, GDK_KEY_g, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, + { MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { MODKEY, GDK_KEY_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, ++ { MODKEY, GDK_KEY_m, spawn, BM_ADD("_SURF_URI") }, + + { 0, GDK_KEY_Escape, stop, { 0 } }, + { MODKEY, GDK_KEY_c, stop, { 0 } }, diff --git a/surf-open.sh b/surf-open.sh new file mode 100755 index 0000000000000000000000000000000000000000..c22edc28643b69f19a279587bb1784534780ddc2 --- /dev/null +++ b/surf-open.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# See the LICENSE file for copyright and license details. +# + +xidfile="$HOME/tmp/tabbed-surf.xid" +uri="" + +if [ "$#" -gt 0 ]; +then + uri="$1" +fi + +runtabbed() { + tabbed -dn tabbed-surf -r 2 surf -e '' "$uri" >"$xidfile" \ + 2>/dev/null & +} + +if [ ! -r "$xidfile" ]; +then + runtabbed +else + xid=$(cat "$xidfile") + xprop -id "$xid" >/dev/null 2>&1 + if [ $? -gt 0 ]; + then + runtabbed + else + surf -e "$xid" "$uri" >/dev/null 2>&1 & + fi +fi + diff --git a/surf.1 b/surf.1 new file mode 100755 index 0000000000000000000000000000000000000000..45c31bb17db5ef7c2a29b80b4e8631ee2715c04a --- /dev/null +++ b/surf.1 @@ -0,0 +1,330 @@ +.TH SURF 1 surf\-VERSION +.SH NAME +surf \- simple webkit-based browser +.SH SYNOPSIS +.B surf +.RB [-bBdDfFgGiIkKmMnNpPsStTvwxX] +.RB [-a\ cookiepolicies] +.RB [-c\ cookiefile] +.RB [-C\ stylefile] +.RB [-e\ xid] +.RB [-r\ scriptfile] +.RB [-u\ useragent] +.RB [-z\ zoomlevel] +.RB [URI] +.SH DESCRIPTION +surf is a simple Web browser based on WebKit/GTK+. It is able +to display websites and follow links. It supports the XEmbed protocol +which makes it possible to embed it in another application. Furthermore, +one can point surf to another URI by setting its XProperties. +.SH OPTIONS +.TP +.B \-a cookiepolicies +Define the order of +.I cookie policies\fR. +The default is "@Aa" but could be +redefined in the +.IR config.h , +with "A" meaning to +accept all cookies, "a" to deny all cookies and "@", which tells surf to +accept no third party cookies. +.TP +.B \-b +Disable Scrollbars. +.TP +.B \-B +Enable Scrollbars. +.TP +.B \-c cookiefile +Specify the +.I cookiefile +to use. +.TP +.B \-C stylefile +Specify the user +.IR stylefile . +This does disable the site-specific styles. +.TP +.B \-d +Disable the disk cache. +.TP +.B \-D +Enable the disk cache. +.TP +.B \-e xid +Reparents to window specified by +.IR xid . +.TP +.B \-f +Start surf in windowed mode (not fullscreen). +.TP +.B \-F +Start surf in fullscreen mode. +.TP +.B \-g +Disable giving the geolocation to websites. +.TP +.B \-G +Enable giving the geolocation to websites. +.TP +.B \-i +Disable Images. +.TP +.B \-I +Enable Images. +.TP +.B \-k +Disable kiosk mode (disable key strokes and right click). +.TP +.B \-K +Enable kiosk mode (disable key strokes and right click). +.TP +.B \-m +Disable application of user style sheets. +.TP +.B \-M +Enable application of user style sheets. +.TP +.B \-n +Disable the Web Inspector (Developer Tools). +.TP +.B \-N +Enable the Web Inspector (Developer Tools). +.TP +.B \-p +Disable Plugins. +.TP +.B \-P +Enable Plugins. +.TP +.B \-r scriptfile +Specify the user +.IR scriptfile . +.TP +.B \-s +Disable Javascript. +.TP +.B \-S +Enable Javascript. +.TP +.B \-t +Disable strict TLS check. +.TP +.B \-T +Enable strict TLS check. +.TP +.B \-u useragent +Specify the +.I useragent +which surf should use. +.TP +.B \-v +Prints version information to standard output, then exits. +.TP +.B \-w +Prints xid to standard output. This can be used to script the browser in for +example +.BR xdotool(1) . +.TP +.B -x +Disable custom certificates. +.TP +.B -X +Enable custom certificates. +.TP +.B \-z zoomlevel +Specify the +.I zoomlevel +which surf should use. +.SH USAGE +.B Escape +Stops loading current page or stops download. +.TP +.B Ctrl\-h +Walks back the history. +.TP +.B Ctrl\-l +Walks forward the history. +.TP +.B Ctrl\-k +Scrolls page upwards. +.TP +.B Ctrl\-j +Scrolls page downwards. +.TP +.B Ctrl\-b +Scroll up one whole page view. +.TP +.B Ctrl\-Space +Scroll down one whole page view. +.TP +.B Ctrl\-i +Scroll horizontally to the right. +.TP +.B Ctrl\-u +Scroll horizontally to the left. +.TP +.B Ctrl\-Shift\-k or Ctrl\-+ +Zooms page in. +.TP +.B Ctrl\-Shift\-j or Ctrl\-- +Zooms page out. +.TP +.B Ctrl\-Shift\-q +Resets Zoom. +.TP +.B Ctrl\-f and Ctrl\-/ +Opens the search-bar. +.TP +.B Ctrl\-n +Go to next search result. +.TP +.B Ctrl\-Shift\-n +Go to previous search result. +.TP +.B Ctrl\-g +Opens the URL-bar (requires dmenu installed). +.TP +.B Ctrl\-p +Loads URI from primary selection. +.TP +.B Ctrl\-Shift\-p +Calls Printpage Dialog. +.TP +.B Ctrl\-r +Reloads the website. +.TP +.B Ctrl\-Shift\-r +Reloads the website without using the cache. +.TP +.B Ctrl\-y +Copies current URI to primary selection. +.TP +.B Ctrl\-t +Display the current TLS certificate in a popup window. +.TP +.B Ctrl\-Shift\-a +Toggle through the the +.I cookie policies\fR. +This will not reload the page. +.TP +.B Ctrl\-Shift\-b +Toggle scrollbars. This will reload the page. +.TP +.B Ctrl\-Shift\-c +Toggle caret browsing. This will reload the page. +.TP +.B Ctrl\-Shift\-i +Toggle auto-loading of images. This will reload the page. +.TP +.B Ctrl\-Shift\-m +Toggle if the +.I stylefile +file should be loaded. This will reload the page. +.TP +.B Ctrl\-Shift\-o +Open the Web Inspector (Developer Tools) window for the current page. +.TP +.B Ctrl\-Shift\-s +Toggle script execution. This will reload the page. +.TP +.B Ctrl\-Shift\-t +Toggle strict TLS check. This will reload the page. +.TP +.B Ctrl\-Shift\-v +Toggle the enabling of plugins on that surf instance. This will reload the +page. +.TP +.B F11 +Toggle fullscreen mode. +.SH INDICATORS OF OPERATION +Surf is showing indicators of operation in front of the site title. +For all indicators, unless otherwise specified, a lower case letter means disabled and an upper case letter means enabled. +.TP +.B A +all cookies accepted +.TP +.B a +no cookies accepted +.TP +.B @ +all except third-party cookies accepted +.TP +.B c C +caret browsing +.TP +.B g G +geolocation +.TP +.B d D +disk cache +.TP +.B i I +images +.TP +.B s S +scripts +.TP +.B v V +plugins +.TP +.B m M +styles +.TP +.B f F +frame flattening +.TP +.B x X +custom certificates +.TP +.B t T +strict TLS +.SH INDICATORS OF WEB PAGE +The second part of the indicators specifies modes of the web page itself. +.SS First character: encryption +.TP +.B - +unencrypted +.TP +.B T +encrypted (TLS) +.TP +.B U +attempted encryption but failed +.SS Second character: proxying +.TP +.B - +no proxy +.TP +.B P +using proxy +.SH ENVIRONMENT +.B SURF_USERAGENT +If this variable is set upon startup, surf will use it as the +.I useragent +string. +.TP +.B http_proxy +If this variable is set and not empty upon startup, surf will use it as the http proxy. +.SH SIGNALS +Surf will reload the current page on +.BR SIGHUP . +.SH PLUGINS +For using plugins in surf, first determine your running architecture. Then get +the appropriate plugin for that architecture and copy it to +.BR /usr/lib/browser-plugins +or +.BR /usr/lib64/browser-plugins. +Surf should load them automatically. +.BR +If you want to use a 32bit plugin on a 64bit system, +.BR nspluginwrapper(1) +will help you. +.SH SEE ALSO +.BR dmenu(1), +.BR xprop(1), +.BR tabbed(1), +.BR nspluginwrapper(1), +.BR xdotool(1) +.SH BUGS +Please report them! diff --git a/surf.c b/surf.c new file mode 100755 index 0000000000000000000000000000000000000000..2b54e3cca144a74f815c058a5755b6d01e9000d8 --- /dev/null +++ b/surf.c @@ -0,0 +1,2127 @@ +/* See LICENSE file for copyright and license details. + * + * To understand surf, start reading main(). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" +#include "common.h" + +#define LENGTH(x) (sizeof(x) / sizeof(x[0])) +#define CLEANMASK(mask) (mask & (MODKEY|GDK_SHIFT_MASK)) + +enum { AtomFind, AtomGo, AtomUri, AtomLast }; + +enum { + OnDoc = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT, + OnLink = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK, + OnImg = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE, + OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA, + OnEdit = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE, + OnBar = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR, + OnSel = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION, + OnAny = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | OnSel, +}; + +typedef enum { + AcceleratedCanvas, + AccessMicrophone, + AccessWebcam, + CaretBrowsing, + Certificate, + CookiePolicies, + DiskCache, + DefaultCharset, + DNSPrefetch, + FileURLsCrossAccess, + FontSize, + FrameFlattening, + Geolocation, + HideBackground, + Inspector, + Java, + JavaScript, + KioskMode, + LoadImages, + MediaManualPlay, + Plugins, + PreferredLanguages, + RunInFullscreen, + ScrollBars, + ShowIndicators, + SiteQuirks, + SmoothScrolling, + SpellChecking, + SpellLanguages, + StrictTLS, + Style, + WebGL, + ZoomLevel, + ParameterLast +} ParamName; + +typedef union { + int i; + float f; + const void *v; +} Arg; + +typedef struct { + Arg val; + int prio; +} Parameter; + +typedef struct Client { + GtkWidget *win; + WebKitWebView *view; + WebKitWebInspector *inspector; + WebKitFindController *finder; + WebKitHitTestResult *mousepos; + GTlsCertificate *cert, *failedcert; + GTlsCertificateFlags tlserr; + Window xid; + unsigned long pageid; + int progress, fullscreen, https, insecure, errorpage; + const char *title, *overtitle, *targeturi; + const char *needle; + struct Client *next; +} Client; + +typedef struct { + guint mod; + guint keyval; + void (*func)(Client *c, const Arg *a); + const Arg arg; +} Key; + +typedef struct { + unsigned int target; + unsigned int mask; + guint button; + void (*func)(Client *c, const Arg *a, WebKitHitTestResult *h); + const Arg arg; + unsigned int stopevent; +} Button; + +typedef struct { + const char *uri; + Parameter config[ParameterLast]; + regex_t re; +} UriParameters; + +typedef struct { + char *regex; + char *file; + regex_t re; +} SiteSpecific; + +/* Surf */ +static void usage(void); +static void setup(void); +static void sigchld(int unused); +static void sighup(int unused); +static char *buildfile(const char *path); +static char *buildpath(const char *path); +static char *untildepath(const char *path); +static const char *getuserhomedir(const char *user); +static const char *getcurrentuserhomedir(void); +static Client *newclient(Client *c); +static void loaduri(Client *c, const Arg *a); +static const char *geturi(Client *c); +static void setatom(Client *c, int a, const char *v); +static const char *getatom(Client *c, int a); +static void updatetitle(Client *c); +static void gettogglestats(Client *c); +static void getpagestats(Client *c); +static WebKitCookieAcceptPolicy cookiepolicy_get(void); +static char cookiepolicy_set(const WebKitCookieAcceptPolicy p); +static void seturiparameters(Client *c, const char *uri, ParamName *params); +static void setparameter(Client *c, int refresh, ParamName p, const Arg *a); +static const char *getcert(const char *uri); +static void setcert(Client *c, const char *file); +static const char *getstyle(const char *uri); +static void setstyle(Client *c, const char *file); +static void runscript(Client *c); +static void evalscript(Client *c, const char *jsstr, ...); +static void updatewinid(Client *c); +static void handleplumb(Client *c, const char *uri); +static void newwindow(Client *c, const Arg *a, int noembed); +static void spawn(Client *c, const Arg *a); +static void msgext(Client *c, char type, const Arg *a); +static void destroyclient(Client *c); +static void cleanup(void); + +/* GTK/WebKit */ +static WebKitWebView *newview(Client *c, WebKitWebView *rv); +static void initwebextensions(WebKitWebContext *wc, Client *c); +static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a, + Client *c); +static gboolean buttonreleased(GtkWidget *w, GdkEvent *e, Client *c); +static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event, + gpointer d); +static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c); +static gboolean readpipe(GIOChannel *s, GIOCondition ioc, gpointer unused); +static void showview(WebKitWebView *v, Client *c); +static GtkWidget *createwindow(Client *c); +static gboolean loadfailedtls(WebKitWebView *v, gchar *uri, + GTlsCertificate *cert, + GTlsCertificateFlags err, Client *c); +static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c); +static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c); +static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c); +static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, + guint modifiers, Client *c); +static gboolean permissionrequested(WebKitWebView *v, + WebKitPermissionRequest *r, Client *c); +static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d, + WebKitPolicyDecisionType dt, Client *c); +static void decidenavigation(WebKitPolicyDecision *d, Client *c); +static void decidenewwindow(WebKitPolicyDecision *d, Client *c); +static void decideresource(WebKitPolicyDecision *d, Client *c); +static void insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e, + Client *c); +static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d, + Client *c); +static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c); +static void download(Client *c, WebKitURIResponse *r); +static void webprocessterminated(WebKitWebView *v, + WebKitWebProcessTerminationReason r, + Client *c); +static void closeview(WebKitWebView *v, Client *c); +static void destroywin(GtkWidget* w, Client *c); + +/* Hotkeys */ +static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d); +static void reload(Client *c, const Arg *a); +static void print(Client *c, const Arg *a); +static void showcert(Client *c, const Arg *a); +static void clipboard(Client *c, const Arg *a); +static void zoom(Client *c, const Arg *a); +static void scrollv(Client *c, const Arg *a); +static void scrollh(Client *c, const Arg *a); +static void navigate(Client *c, const Arg *a); +static void stop(Client *c, const Arg *a); +static void toggle(Client *c, const Arg *a); +static void togglefullscreen(Client *c, const Arg *a); +static void togglecookiepolicy(Client *c, const Arg *a); +static void toggleinspector(Client *c, const Arg *a); +static void find(Client *c, const Arg *a); + +/* Buttons */ +static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h); +static void clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h); +static void clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h); + +static char winid[64]; +static char togglestats[12]; +static char pagestats[2]; +static Atom atoms[AtomLast]; +static Window embed; +static int showxid; +static int cookiepolicy; +static Display *dpy; +static Client *clients; +static GdkDevice *gdkkb; +static char *stylefile; +static const char *useragent; +static Parameter *curconfig; +static int modparams[ParameterLast]; +static int pipein[2], pipeout[2]; +char *argv0; + +static ParamName loadtransient[] = { + Certificate, + CookiePolicies, + DiskCache, + DNSPrefetch, + FileURLsCrossAccess, + JavaScript, + LoadImages, + PreferredLanguages, + ShowIndicators, + StrictTLS, + ParameterLast +}; + +static ParamName loadcommitted[] = { + AcceleratedCanvas, +// AccessMicrophone, +// AccessWebcam, + CaretBrowsing, + DefaultCharset, + FontSize, + FrameFlattening, + Geolocation, + HideBackground, + Inspector, + Java, +// KioskMode, + MediaManualPlay, + Plugins, + RunInFullscreen, + ScrollBars, + SiteQuirks, + SmoothScrolling, + SpellChecking, + SpellLanguages, + Style, + ZoomLevel, + ParameterLast +}; + +static ParamName loadfinished[] = { + ParameterLast +}; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +void +usage(void) +{ + die("usage: surf [-bBdDfFgGiIkKmMnNpPsStTvwxX]\n" + "[-a cookiepolicies ] [-c cookiefile] [-C stylefile] [-e xid]\n" + "[-r scriptfile] [-u useragent] [-z zoomlevel] [uri]\n"); +} + +void +setup(void) +{ + GIOChannel *gchanin; + GdkDisplay *gdpy; + int i, j; + + /* clean up any zombies immediately */ + sigchld(0); + if (signal(SIGHUP, sighup) == SIG_ERR) + die("Can't install SIGHUP handler"); + + if (!(dpy = XOpenDisplay(NULL))) + die("Can't open default display"); + + /* atoms */ + atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False); + atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False); + atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False); + + gtk_init(NULL, NULL); + + gdpy = gdk_display_get_default(); + + curconfig = defconfig; + + /* dirs and files */ + cookiefile = buildfile(cookiefile); + scriptfile = buildfile(scriptfile); + cachedir = buildpath(cachedir); + certdir = buildpath(certdir); + + gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy)); + + if (pipe(pipeout) < 0 || pipe(pipein) < 0) { + fputs("Unable to create pipes\n", stderr); + } else { + gchanin = g_io_channel_unix_new(pipein[0]); + g_io_channel_set_encoding(gchanin, NULL, NULL); + g_io_channel_set_close_on_unref(gchanin, TRUE); + g_io_add_watch(gchanin, G_IO_IN, readpipe, NULL); + } + + + for (i = 0; i < LENGTH(certs); ++i) { + if (!regcomp(&(certs[i].re), certs[i].regex, REG_EXTENDED)) { + certs[i].file = g_strconcat(certdir, "/", certs[i].file, + NULL); + } else { + fprintf(stderr, "Could not compile regex: %s\n", + certs[i].regex); + certs[i].regex = NULL; + } + } + + if (!stylefile) { + styledir = buildpath(styledir); + for (i = 0; i < LENGTH(styles); ++i) { + if (!regcomp(&(styles[i].re), styles[i].regex, + REG_EXTENDED)) { + styles[i].file = g_strconcat(styledir, "/", + styles[i].file, NULL); + } else { + fprintf(stderr, "Could not compile regex: %s\n", + styles[i].regex); + styles[i].regex = NULL; + } + } + g_free(styledir); + } else { + stylefile = buildfile(stylefile); + } + + for (i = 0; i < LENGTH(uriparams); ++i) { + if (regcomp(&(uriparams[i].re), uriparams[i].uri, + REG_EXTENDED)) { + fprintf(stderr, "Could not compile regex: %s\n", + uriparams[i].uri); + uriparams[i].uri = NULL; + continue; + } + + /* copy default parameters with higher priority */ + for (j = 0; j < ParameterLast; ++j) { + if (defconfig[j].prio >= uriparams[i].config[j].prio) + uriparams[i].config[j] = defconfig[j]; + } + } +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("Can't install SIGCHLD handler"); + while (waitpid(-1, NULL, WNOHANG) > 0) + ; +} + +void +sighup(int unused) +{ + Arg a = { .i = 0 }; + Client *c; + + for (c = clients; c; c = c->next) + reload(c, &a); +} + +char * +buildfile(const char *path) +{ + char *dname, *bname, *bpath, *fpath; + FILE *f; + + dname = g_path_get_dirname(path); + bname = g_path_get_basename(path); + + bpath = buildpath(dname); + g_free(dname); + + fpath = g_build_filename(bpath, bname, NULL); + g_free(bpath); + g_free(bname); + + if (!(f = fopen(fpath, "a"))) + die("Could not open file: %s\n", fpath); + + g_chmod(fpath, 0600); /* always */ + fclose(f); + + return fpath; +} + +static const char* +getuserhomedir(const char *user) +{ + struct passwd *pw = getpwnam(user); + + if (!pw) + die("Can't get user %s login information.\n", user); + + return pw->pw_dir; +} + +static const char* +getcurrentuserhomedir(void) +{ + const char *homedir; + const char *user; + struct passwd *pw; + + homedir = getenv("HOME"); + if (homedir) + return homedir; + + user = getenv("USER"); + if (user) + return getuserhomedir(user); + + pw = getpwuid(getuid()); + if (!pw) + die("Can't get current user home directory\n"); + + return pw->pw_dir; +} + +char * +buildpath(const char *path) +{ + char *apath, *fpath; + + if (path[0] == '~') + apath = untildepath(path); + else + apath = g_strdup(path); + + /* creating directory */ + if (g_mkdir_with_parents(apath, 0700) < 0) + die("Could not access directory: %s\n", apath); + + fpath = realpath(apath, NULL); + g_free(apath); + + return fpath; +} + +char * +untildepath(const char *path) +{ + char *apath, *name, *p; + const char *homedir; + + if (path[1] == '/' || path[1] == '\0') { + p = (char *)&path[1]; + homedir = getcurrentuserhomedir(); + } else { + if ((p = strchr(path, '/'))) + name = g_strndup(&path[1], p - (path + 1)); + else + name = g_strdup(&path[1]); + + homedir = getuserhomedir(name); + g_free(name); + } + apath = g_build_filename(homedir, p, NULL); + return apath; +} + +Client * +newclient(Client *rc) +{ + Client *c; + + if (!(c = calloc(1, sizeof(Client)))) + die("Cannot malloc!\n"); + + c->next = clients; + clients = c; + + c->progress = 100; + c->view = newview(c, rc ? rc->view : NULL); + + return c; +} + +void +loaduri(Client *c, const Arg *a) +{ + struct stat st; + char *url, *path, *apath; + const char *uri = a->v; + + if (g_strcmp0(uri, "") == 0) + return; + + if (g_str_has_prefix(uri, "http://") || + g_str_has_prefix(uri, "https://") || + g_str_has_prefix(uri, "file://") || + g_str_has_prefix(uri, "about:")) { + url = g_strdup(uri); + } else { + if (uri[0] == '~') + apath = untildepath(uri); + else + apath = (char *)uri; + if (!stat(apath, &st) && (path = realpath(apath, NULL))) { + url = g_strdup_printf("file://%s", path); + free(path); + } else { + url = g_strdup_printf("http://%s", uri); + } + if (apath != uri) + free(apath); + } + + setatom(c, AtomUri, url); + + if (strcmp(url, geturi(c)) == 0) { + reload(c, a); + } else { + webkit_web_view_load_uri(c->view, url); + updatetitle(c); + } + + g_free(url); +} + +const char * +geturi(Client *c) +{ + const char *uri; + + if (!(uri = webkit_web_view_get_uri(c->view))) + uri = "about:blank"; + return uri; +} + +void +setatom(Client *c, int a, const char *v) +{ + XChangeProperty(dpy, c->xid, + atoms[a], XA_STRING, 8, PropModeReplace, + (unsigned char *)v, strlen(v) + 1); + XSync(dpy, False); +} + +const char * +getatom(Client *c, int a) +{ + static char buf[BUFSIZ]; + Atom adummy; + int idummy; + unsigned long ldummy; + unsigned char *p = NULL; + + XSync(dpy, False); + XGetWindowProperty(dpy, c->xid, atoms[a], 0L, BUFSIZ, False, XA_STRING, + &adummy, &idummy, &ldummy, &ldummy, &p); + if (p) + strncpy(buf, (char *)p, LENGTH(buf) - 1); + else + buf[0] = '\0'; + XFree(p); + + return buf; +} + +void +updatetitle(Client *c) +{ + char *title; + const char *name = c->overtitle ? c->overtitle : + c->title ? c->title : ""; + + if (curconfig[ShowIndicators].val.i) { + gettogglestats(c); + getpagestats(c); + + if (c->progress != 100) + title = g_strdup_printf("[%i%%] %s:%s | %s", + c->progress, togglestats, pagestats, name); + else + title = g_strdup_printf("%s:%s | %s", + togglestats, pagestats, name); + + gtk_window_set_title(GTK_WINDOW(c->win), title); + g_free(title); + } else { + gtk_window_set_title(GTK_WINDOW(c->win), name); + } +} + +void +gettogglestats(Client *c) +{ + togglestats[0] = cookiepolicy_set(cookiepolicy_get()); + togglestats[1] = curconfig[CaretBrowsing].val.i ? 'C' : 'c'; + togglestats[2] = curconfig[Geolocation].val.i ? 'G' : 'g'; + togglestats[3] = curconfig[DiskCache].val.i ? 'D' : 'd'; + togglestats[4] = curconfig[LoadImages].val.i ? 'I' : 'i'; + togglestats[5] = curconfig[JavaScript].val.i ? 'S' : 's'; + togglestats[6] = curconfig[Plugins].val.i ? 'V' : 'v'; + togglestats[7] = curconfig[Style].val.i ? 'M' : 'm'; + togglestats[8] = curconfig[FrameFlattening].val.i ? 'F' : 'f'; + togglestats[9] = curconfig[Certificate].val.i ? 'X' : 'x'; + togglestats[10] = curconfig[StrictTLS].val.i ? 'T' : 't'; + togglestats[11] = '\0'; +} + +void +getpagestats(Client *c) +{ + if (c->https) + pagestats[0] = (c->tlserr || c->insecure) ? 'U' : 'T'; + else + pagestats[0] = '-'; + pagestats[1] = '\0'; +} + +WebKitCookieAcceptPolicy +cookiepolicy_get(void) +{ + switch (((char *)curconfig[CookiePolicies].val.v)[cookiepolicy]) { + case 'a': + return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER; + case '@': + return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY; + default: /* fallthrough */ + case 'A': + return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS; + } +} + +char +cookiepolicy_set(const WebKitCookieAcceptPolicy p) +{ + switch (p) { + case WEBKIT_COOKIE_POLICY_ACCEPT_NEVER: + return 'a'; + case WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY: + return '@'; + default: /* fallthrough */ + case WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS: + return 'A'; + } +} + +void +seturiparameters(Client *c, const char *uri, ParamName *params) +{ + Parameter *config, *uriconfig = NULL; + int i, p; + + for (i = 0; i < LENGTH(uriparams); ++i) { + if (uriparams[i].uri && + !regexec(&(uriparams[i].re), uri, 0, NULL, 0)) { + uriconfig = uriparams[i].config; + break; + } + } + + curconfig = uriconfig ? uriconfig : defconfig; + + for (i = 0; (p = params[i]) != ParameterLast; ++i) { + switch(p) { + default: /* FALLTHROUGH */ + if (!(defconfig[p].prio < curconfig[p].prio || + defconfig[p].prio < modparams[p])) + continue; + case Certificate: + case CookiePolicies: + case Style: + setparameter(c, 0, p, &curconfig[p].val); + } + } +} + +void +setparameter(Client *c, int refresh, ParamName p, const Arg *a) +{ + GdkRGBA bgcolor = { 0 }; + WebKitSettings *s = webkit_web_view_get_settings(c->view); + + modparams[p] = curconfig[p].prio; + + switch (p) { + case AcceleratedCanvas: + webkit_settings_set_enable_accelerated_2d_canvas(s, a->i); + break; + case AccessMicrophone: + return; /* do nothing */ + case AccessWebcam: + return; /* do nothing */ + case CaretBrowsing: + webkit_settings_set_enable_caret_browsing(s, a->i); + refresh = 0; + break; + case Certificate: + if (a->i) + setcert(c, geturi(c)); + return; /* do not update */ + case CookiePolicies: + webkit_cookie_manager_set_accept_policy( + webkit_web_context_get_cookie_manager( + webkit_web_view_get_context(c->view)), + cookiepolicy_get()); + refresh = 0; + break; + case DiskCache: + webkit_web_context_set_cache_model( + webkit_web_view_get_context(c->view), a->i ? + WEBKIT_CACHE_MODEL_WEB_BROWSER : + WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER); + return; /* do not update */ + case DefaultCharset: + webkit_settings_set_default_charset(s, a->v); + return; /* do not update */ + case DNSPrefetch: + webkit_settings_set_enable_dns_prefetching(s, a->i); + return; /* do not update */ + case FileURLsCrossAccess: + webkit_settings_set_allow_file_access_from_file_urls(s, a->i); + webkit_settings_set_allow_universal_access_from_file_urls(s, a->i); + return; /* do not update */ + case FontSize: + webkit_settings_set_default_font_size(s, a->i); + return; /* do not update */ + case FrameFlattening: + webkit_settings_set_enable_frame_flattening(s, a->i); + break; + case Geolocation: + refresh = 0; + break; + case HideBackground: + if (a->i) + webkit_web_view_set_background_color(c->view, &bgcolor); + return; /* do not update */ + case Inspector: + webkit_settings_set_enable_developer_extras(s, a->i); + return; /* do not update */ + case Java: + webkit_settings_set_enable_java(s, a->i); + return; /* do not update */ + case JavaScript: + webkit_settings_set_enable_javascript(s, a->i); + break; + case KioskMode: + return; /* do nothing */ + case LoadImages: + webkit_settings_set_auto_load_images(s, a->i); + break; + case MediaManualPlay: + webkit_settings_set_media_playback_requires_user_gesture(s, a->i); + break; + case Plugins: + webkit_settings_set_enable_plugins(s, a->i); + break; + case PreferredLanguages: + return; /* do nothing */ + case RunInFullscreen: + return; /* do nothing */ + case ScrollBars: + /* Disabled until we write some WebKitWebExtension for + * manipulating the DOM directly. + enablescrollbars = !enablescrollbars; + evalscript(c, "document.documentElement.style.overflow = '%s'", + enablescrollbars ? "auto" : "hidden"); + */ + return; /* do not update */ + case ShowIndicators: + break; + case SmoothScrolling: + webkit_settings_set_enable_smooth_scrolling(s, a->i); + return; /* do not update */ + case SiteQuirks: + webkit_settings_set_enable_site_specific_quirks(s, a->i); + break; + case SpellChecking: + webkit_web_context_set_spell_checking_enabled( + webkit_web_view_get_context(c->view), a->i); + return; /* do not update */ + case SpellLanguages: + return; /* do nothing */ + case StrictTLS: + webkit_web_context_set_tls_errors_policy( + webkit_web_view_get_context(c->view), a->i ? + WEBKIT_TLS_ERRORS_POLICY_FAIL : + WEBKIT_TLS_ERRORS_POLICY_IGNORE); + break; + case Style: + webkit_user_content_manager_remove_all_style_sheets( + webkit_web_view_get_user_content_manager(c->view)); + if (a->i) + setstyle(c, getstyle(geturi(c))); + refresh = 0; + break; + case WebGL: + webkit_settings_set_enable_webgl(s, a->i); + break; + case ZoomLevel: + webkit_web_view_set_zoom_level(c->view, a->f); + return; /* do not update */ + default: + return; /* do nothing */ + } + + updatetitle(c); + if (refresh) + reload(c, a); +} + +const char * +getcert(const char *uri) +{ + int i; + + for (i = 0; i < LENGTH(certs); ++i) { + if (certs[i].regex && + !regexec(&(certs[i].re), uri, 0, NULL, 0)) + return certs[i].file; + } + + return NULL; +} + +void +setcert(Client *c, const char *uri) +{ + const char *file = getcert(uri); + char *host; + GTlsCertificate *cert; + + if (!file) + return; + + if (!(cert = g_tls_certificate_new_from_file(file, NULL))) { + fprintf(stderr, "Could not read certificate file: %s\n", file); + return; + } + + if ((uri = strstr(uri, "https://"))) { + uri += sizeof("https://") - 1; + host = g_strndup(uri, strchr(uri, '/') - uri); + webkit_web_context_allow_tls_certificate_for_host( + webkit_web_view_get_context(c->view), cert, host); + g_free(host); + } + + g_object_unref(cert); + +} + +const char * +getstyle(const char *uri) +{ + int i; + + if (stylefile) + return stylefile; + + for (i = 0; i < LENGTH(styles); ++i) { + if (styles[i].regex && + !regexec(&(styles[i].re), uri, 0, NULL, 0)) + return styles[i].file; + } + + return ""; +} + +void +setstyle(Client *c, const char *file) +{ + gchar *style; + + if (!g_file_get_contents(file, &style, NULL, NULL)) { + fprintf(stderr, "Could not read style file: %s\n", file); + return; + } + + webkit_user_content_manager_add_style_sheet( + webkit_web_view_get_user_content_manager(c->view), + webkit_user_style_sheet_new(style, + WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + WEBKIT_USER_STYLE_LEVEL_USER, + NULL, NULL)); + + g_free(style); +} + +void +runscript(Client *c) +{ + gchar *script; + gsize l; + + if (g_file_get_contents(scriptfile, &script, &l, NULL) && l) + evalscript(c, "%s", script); + g_free(script); +} + +void +evalscript(Client *c, const char *jsstr, ...) +{ + va_list ap; + gchar *script; + + va_start(ap, jsstr); + script = g_strdup_vprintf(jsstr, ap); + va_end(ap); + + webkit_web_view_run_javascript(c->view, script, NULL, NULL, NULL); + g_free(script); +} + +void +updatewinid(Client *c) +{ + snprintf(winid, LENGTH(winid), "%lu", c->xid); +} + +void +handleplumb(Client *c, const char *uri) +{ + Arg a = (Arg)PLUMB(uri); + spawn(c, &a); +} + +void +newwindow(Client *c, const Arg *a, int noembed) +{ + int i = 0; + char tmp[64]; + const char *cmd[29], *uri; + const Arg arg = { .v = cmd }; + + cmd[i++] = argv0; + cmd[i++] = "-a"; + cmd[i++] = curconfig[CookiePolicies].val.v; + cmd[i++] = curconfig[ScrollBars].val.i ? "-B" : "-b"; + if (cookiefile && g_strcmp0(cookiefile, "")) { + cmd[i++] = "-c"; + cmd[i++] = cookiefile; + } + if (stylefile && g_strcmp0(stylefile, "")) { + cmd[i++] = "-C"; + cmd[i++] = stylefile; + } + cmd[i++] = curconfig[DiskCache].val.i ? "-D" : "-d"; + if (embed && !noembed) { + cmd[i++] = "-e"; + snprintf(tmp, LENGTH(tmp), "%lu", embed); + cmd[i++] = tmp; + } + cmd[i++] = curconfig[RunInFullscreen].val.i ? "-F" : "-f" ; + cmd[i++] = curconfig[Geolocation].val.i ? "-G" : "-g" ; + cmd[i++] = curconfig[LoadImages].val.i ? "-I" : "-i" ; + cmd[i++] = curconfig[KioskMode].val.i ? "-K" : "-k" ; + cmd[i++] = curconfig[Style].val.i ? "-M" : "-m" ; + cmd[i++] = curconfig[Inspector].val.i ? "-N" : "-n" ; + cmd[i++] = curconfig[Plugins].val.i ? "-P" : "-p" ; + if (scriptfile && g_strcmp0(scriptfile, "")) { + cmd[i++] = "-r"; + cmd[i++] = scriptfile; + } + cmd[i++] = curconfig[JavaScript].val.i ? "-S" : "-s"; + cmd[i++] = curconfig[StrictTLS].val.i ? "-T" : "-t"; + if (fulluseragent && g_strcmp0(fulluseragent, "")) { + cmd[i++] = "-u"; + cmd[i++] = fulluseragent; + } + if (showxid) + cmd[i++] = "-w"; + cmd[i++] = curconfig[Certificate].val.i ? "-X" : "-x" ; + /* do not keep zoom level */ + cmd[i++] = "--"; + if ((uri = a->v)) + cmd[i++] = uri; + cmd[i] = NULL; + + spawn(c, &arg); +} + +void +spawn(Client *c, const Arg *a) +{ + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + close(pipein[0]); + close(pipeout[1]); + setsid(); + execvp(((char **)a->v)[0], (char **)a->v); + fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[0]); + perror(" failed"); + exit(1); + } +} + +void +destroyclient(Client *c) +{ + Client *p; + + webkit_web_view_stop_loading(c->view); + /* Not needed, has already been called + gtk_widget_destroy(c->win); + */ + + for (p = clients; p && p->next != c; p = p->next) + ; + if (p) + p->next = c->next; + else + clients = c->next; + free(c); +} + +void +cleanup(void) +{ + while (clients) + destroyclient(clients); + + close(pipein[0]); + close(pipeout[1]); + g_free(cookiefile); + g_free(scriptfile); + g_free(stylefile); + g_free(cachedir); + XCloseDisplay(dpy); +} + +WebKitWebView * +newview(Client *c, WebKitWebView *rv) +{ + WebKitWebView *v; + WebKitSettings *settings; + WebKitWebContext *context; + WebKitCookieManager *cookiemanager; + WebKitUserContentManager *contentmanager; + + /* Webview */ + if (rv) { + v = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(rv)); + } else { + settings = webkit_settings_new_with_settings( + "allow-file-access-from-file-urls", curconfig[FileURLsCrossAccess].val.i, + "allow-universal-access-from-file-urls", curconfig[FileURLsCrossAccess].val.i, + "auto-load-images", curconfig[LoadImages].val.i, + "default-charset", curconfig[DefaultCharset].val.v, + "default-font-size", curconfig[FontSize].val.i, + "enable-caret-browsing", curconfig[CaretBrowsing].val.i, + "enable-developer-extras", curconfig[Inspector].val.i, + "enable-dns-prefetching", curconfig[DNSPrefetch].val.i, + "enable-frame-flattening", curconfig[FrameFlattening].val.i, + "enable-html5-database", curconfig[DiskCache].val.i, + "enable-html5-local-storage", curconfig[DiskCache].val.i, + "enable-java", curconfig[Java].val.i, + "enable-javascript", curconfig[JavaScript].val.i, + "enable-plugins", curconfig[Plugins].val.i, + "enable-accelerated-2d-canvas", curconfig[AcceleratedCanvas].val.i, + "enable-site-specific-quirks", curconfig[SiteQuirks].val.i, + "enable-smooth-scrolling", curconfig[SmoothScrolling].val.i, + "enable-webgl", curconfig[WebGL].val.i, + "media-playback-requires-user-gesture", curconfig[MediaManualPlay].val.i, + NULL); +/* For more interesting settings, have a look at + * http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html */ + + if (strcmp(fulluseragent, "")) { + webkit_settings_set_user_agent(settings, fulluseragent); + } else if (surfuseragent) { + webkit_settings_set_user_agent_with_application_details( + settings, "Surf", VERSION); + } + useragent = webkit_settings_get_user_agent(settings); + + contentmanager = webkit_user_content_manager_new(); + + context = webkit_web_context_new_with_website_data_manager( + webkit_website_data_manager_new( + "base-cache-directory", cachedir, + "base-data-directory", cachedir, + NULL)); + + cookiemanager = webkit_web_context_get_cookie_manager(context); + + /* rendering process model, can be a shared unique one + * or one for each view */ + webkit_web_context_set_process_model(context, + WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES); + /* TLS */ + webkit_web_context_set_tls_errors_policy(context, + curconfig[StrictTLS].val.i ? WEBKIT_TLS_ERRORS_POLICY_FAIL : + WEBKIT_TLS_ERRORS_POLICY_IGNORE); + /* disk cache */ + webkit_web_context_set_cache_model(context, + curconfig[DiskCache].val.i ? WEBKIT_CACHE_MODEL_WEB_BROWSER : + WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER); + + /* Currently only works with text file to be compatible with curl */ + webkit_cookie_manager_set_persistent_storage(cookiemanager, + cookiefile, WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT); + /* cookie policy */ + webkit_cookie_manager_set_accept_policy(cookiemanager, + cookiepolicy_get()); + /* languages */ + webkit_web_context_set_preferred_languages(context, + curconfig[PreferredLanguages].val.v); + webkit_web_context_set_spell_checking_languages(context, + curconfig[SpellLanguages].val.v); + webkit_web_context_set_spell_checking_enabled(context, + curconfig[SpellChecking].val.i); + + g_signal_connect(G_OBJECT(context), "download-started", + G_CALLBACK(downloadstarted), c); + g_signal_connect(G_OBJECT(context), "initialize-web-extensions", + G_CALLBACK(initwebextensions), c); + + v = g_object_new(WEBKIT_TYPE_WEB_VIEW, + "settings", settings, + "user-content-manager", contentmanager, + "web-context", context, + NULL); + } + + g_signal_connect(G_OBJECT(v), "notify::estimated-load-progress", + G_CALLBACK(progresschanged), c); + g_signal_connect(G_OBJECT(v), "notify::title", + G_CALLBACK(titlechanged), c); + g_signal_connect(G_OBJECT(v), "button-release-event", + G_CALLBACK(buttonreleased), c); + g_signal_connect(G_OBJECT(v), "close", + G_CALLBACK(closeview), c); + g_signal_connect(G_OBJECT(v), "create", + G_CALLBACK(createview), c); + g_signal_connect(G_OBJECT(v), "decide-policy", + G_CALLBACK(decidepolicy), c); + g_signal_connect(G_OBJECT(v), "insecure-content-detected", + G_CALLBACK(insecurecontent), c); + g_signal_connect(G_OBJECT(v), "load-failed-with-tls-errors", + G_CALLBACK(loadfailedtls), c); + g_signal_connect(G_OBJECT(v), "load-changed", + G_CALLBACK(loadchanged), c); + g_signal_connect(G_OBJECT(v), "mouse-target-changed", + G_CALLBACK(mousetargetchanged), c); + g_signal_connect(G_OBJECT(v), "permission-request", + G_CALLBACK(permissionrequested), c); + g_signal_connect(G_OBJECT(v), "ready-to-show", + G_CALLBACK(showview), c); + g_signal_connect(G_OBJECT(v), "web-process-terminated", + G_CALLBACK(webprocessterminated), c); + + return v; +} + +static gboolean +readpipe(GIOChannel *s, GIOCondition ioc, gpointer unused) +{ + static char msg[MSGBUFSZ], msgsz; + GError *gerr = NULL; + + if (g_io_channel_read_chars(s, msg, sizeof(msg), NULL, &gerr) != + G_IO_STATUS_NORMAL) { + fprintf(stderr, "surf: error reading pipe: %s\n", + gerr->message); + g_error_free(gerr); + return TRUE; + } + if ((msgsz = msg[0]) < 3) { + fprintf(stderr, "surf: message too short: %d\n", msgsz); + return TRUE; + } + + switch (msg[2]) { + case 'i': + close(pipein[1]); + close(pipeout[0]); + break; + } + + return TRUE; +} + +void +initwebextensions(WebKitWebContext *wc, Client *c) +{ + GVariant *gv; + + if (!pipeout[0] || !pipein[1]) + return; + + gv = g_variant_new("(ii)", pipeout[0], pipein[1]); + + webkit_web_context_set_web_extensions_initialization_user_data(wc, gv); + webkit_web_context_set_web_extensions_directory(wc, WEBEXTDIR); +} + +GtkWidget * +createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c) +{ + Client *n; + + switch (webkit_navigation_action_get_navigation_type(a)) { + case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */ + /* + * popup windows of type “other” are almost always triggered + * by user gesture, so inverse the logic here + */ +/* instead of this, compare destination uri to mouse-over uri for validating window */ + if (webkit_navigation_action_is_user_gesture(a)) + return NULL; + case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: + n = newclient(c); + break; + default: + return NULL; + } + + return GTK_WIDGET(n->view); +} + +gboolean +buttonreleased(GtkWidget *w, GdkEvent *e, Client *c) +{ + WebKitHitTestResultContext element; + int i; + + element = webkit_hit_test_result_get_context(c->mousepos); + + for (i = 0; i < LENGTH(buttons); ++i) { + if (element & buttons[i].target && + e->button.button == buttons[i].button && + CLEANMASK(e->button.state) == CLEANMASK(buttons[i].mask) && + buttons[i].func) { + buttons[i].func(c, &buttons[i].arg, c->mousepos); + return buttons[i].stopevent; + } + } + + return FALSE; +} + +GdkFilterReturn +processx(GdkXEvent *e, GdkEvent *event, gpointer d) +{ + Client *c = (Client *)d; + XPropertyEvent *ev; + Arg a; + + if (((XEvent *)e)->type == PropertyNotify) { + ev = &((XEvent *)e)->xproperty; + if (ev->state == PropertyNewValue) { + if (ev->atom == atoms[AtomFind]) { + find(c, NULL); + + return GDK_FILTER_REMOVE; + } else if (ev->atom == atoms[AtomGo]) { + a.v = getatom(c, AtomGo); + loaduri(c, &a); + + return GDK_FILTER_REMOVE; + } + } + } + return GDK_FILTER_CONTINUE; +} + +gboolean +winevent(GtkWidget *w, GdkEvent *e, Client *c) +{ + int i; + + switch (e->type) { + case GDK_ENTER_NOTIFY: + c->overtitle = c->targeturi; + updatetitle(c); + break; + case GDK_KEY_PRESS: + if (!curconfig[KioskMode].val.i) { + for (i = 0; i < LENGTH(keys); ++i) { + if (gdk_keyval_to_lower(e->key.keyval) == + keys[i].keyval && + CLEANMASK(e->key.state) == keys[i].mod && + keys[i].func) { + updatewinid(c); + keys[i].func(c, &(keys[i].arg)); + return TRUE; + } + } + } + case GDK_LEAVE_NOTIFY: + c->overtitle = NULL; + updatetitle(c); + break; + case GDK_WINDOW_STATE: + if (e->window_state.changed_mask == + GDK_WINDOW_STATE_FULLSCREEN) + c->fullscreen = e->window_state.new_window_state & + GDK_WINDOW_STATE_FULLSCREEN; + break; + default: + break; + } + + return FALSE; +} + +void +showview(WebKitWebView *v, Client *c) +{ + GdkRGBA bgcolor = { 0 }; + GdkWindow *gwin; + + c->finder = webkit_web_view_get_find_controller(c->view); + c->inspector = webkit_web_view_get_inspector(c->view); + + c->pageid = webkit_web_view_get_page_id(c->view); + c->win = createwindow(c); + + gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view)); + gtk_widget_show_all(c->win); + gtk_widget_grab_focus(GTK_WIDGET(c->view)); + + gwin = gtk_widget_get_window(GTK_WIDGET(c->win)); + c->xid = gdk_x11_window_get_xid(gwin); + updatewinid(c); + if (showxid) { + gdk_display_sync(gtk_widget_get_display(c->win)); + puts(winid); + fflush(stdout); + } + + if (curconfig[HideBackground].val.i) + webkit_web_view_set_background_color(c->view, &bgcolor); + + if (!curconfig[KioskMode].val.i) { + gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK); + gdk_window_add_filter(gwin, processx, c); + } + + if (curconfig[RunInFullscreen].val.i) + togglefullscreen(c, NULL); + + if (curconfig[ZoomLevel].val.f != 1.0) + webkit_web_view_set_zoom_level(c->view, + curconfig[ZoomLevel].val.f); + + setatom(c, AtomFind, ""); + setatom(c, AtomUri, "about:blank"); +} + +GtkWidget * +createwindow(Client *c) +{ + char *wmstr; + GtkWidget *w; + + if (embed) { + w = gtk_plug_new(embed); + } else { + w = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + wmstr = g_path_get_basename(argv0); + gtk_window_set_wmclass(GTK_WINDOW(w), wmstr, "Surf"); + g_free(wmstr); + + wmstr = g_strdup_printf("%s[%lu]", "Surf", c->pageid); + gtk_window_set_role(GTK_WINDOW(w), wmstr); + g_free(wmstr); + + gtk_window_set_default_size(GTK_WINDOW(w), winsize[0], winsize[1]); + } + + g_signal_connect(G_OBJECT(w), "destroy", + G_CALLBACK(destroywin), c); + g_signal_connect(G_OBJECT(w), "enter-notify-event", + G_CALLBACK(winevent), c); + g_signal_connect(G_OBJECT(w), "key-press-event", + G_CALLBACK(winevent), c); + g_signal_connect(G_OBJECT(w), "leave-notify-event", + G_CALLBACK(winevent), c); + g_signal_connect(G_OBJECT(w), "window-state-event", + G_CALLBACK(winevent), c); + + return w; +} + +gboolean +loadfailedtls(WebKitWebView *v, gchar *uri, GTlsCertificate *cert, + GTlsCertificateFlags err, Client *c) +{ + GString *errmsg = g_string_new(NULL); + gchar *html, *pem; + + c->failedcert = g_object_ref(cert); + c->tlserr = err; + c->errorpage = 1; + + if (err & G_TLS_CERTIFICATE_UNKNOWN_CA) + g_string_append(errmsg, + "The signing certificate authority is not known.
"); + if (err & G_TLS_CERTIFICATE_BAD_IDENTITY) + g_string_append(errmsg, + "The certificate does not match the expected identity " + "of the site that it was retrieved from.
"); + if (err & G_TLS_CERTIFICATE_NOT_ACTIVATED) + g_string_append(errmsg, + "The certificate's activation time " + "is still in the future.
"); + if (err & G_TLS_CERTIFICATE_EXPIRED) + g_string_append(errmsg, "The certificate has expired.
"); + if (err & G_TLS_CERTIFICATE_REVOKED) + g_string_append(errmsg, + "The certificate has been revoked according to " + "the GTlsConnection's certificate revocation list.
"); + if (err & G_TLS_CERTIFICATE_INSECURE) + g_string_append(errmsg, + "The certificate's algorithm is considered insecure.
"); + if (err & G_TLS_CERTIFICATE_GENERIC_ERROR) + g_string_append(errmsg, + "Some error occurred validating the certificate.
"); + + g_object_get(cert, "certificate-pem", &pem, NULL); + html = g_strdup_printf("

Could not validate TLS for “%s”
%s

" + "

You can inspect the following certificate " + "with Ctrl-t (default keybinding).

" + "

%s

", uri, errmsg->str, pem); + g_free(pem); + g_string_free(errmsg, TRUE); + + webkit_web_view_load_alternate_html(c->view, html, uri, NULL); + g_free(html); + + return TRUE; +} + +void +loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c) +{ + const char *uri = geturi(c); + + switch (e) { + case WEBKIT_LOAD_STARTED: + setatom(c, AtomUri, uri); + c->title = uri; + c->https = c->insecure = 0; + seturiparameters(c, uri, loadtransient); + if (c->errorpage) + c->errorpage = 0; + else + g_clear_object(&c->failedcert); + break; + case WEBKIT_LOAD_REDIRECTED: + setatom(c, AtomUri, uri); + c->title = uri; + seturiparameters(c, uri, loadtransient); + break; + case WEBKIT_LOAD_COMMITTED: + seturiparameters(c, uri, loadcommitted); + c->https = webkit_web_view_get_tls_info(c->view, &c->cert, + &c->tlserr); + break; + case WEBKIT_LOAD_FINISHED: + seturiparameters(c, uri, loadfinished); + /* Disabled until we write some WebKitWebExtension for + * manipulating the DOM directly. + evalscript(c, "document.documentElement.style.overflow = '%s'", + enablescrollbars ? "auto" : "hidden"); + */ + runscript(c); + break; + } + updatetitle(c); +} + +void +progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c) +{ + c->progress = webkit_web_view_get_estimated_load_progress(c->view) * + 100; + updatetitle(c); +} + +void +titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c) +{ + c->title = webkit_web_view_get_title(c->view); + updatetitle(c); +} + +void +mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modifiers, + Client *c) +{ + WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h); + + /* Keep the hit test to know where is the pointer on the next click */ + c->mousepos = h; + + if (hc & OnLink) + c->targeturi = webkit_hit_test_result_get_link_uri(h); + else if (hc & OnImg) + c->targeturi = webkit_hit_test_result_get_image_uri(h); + else if (hc & OnMedia) + c->targeturi = webkit_hit_test_result_get_media_uri(h); + else + c->targeturi = NULL; + + c->overtitle = c->targeturi; + updatetitle(c); +} + +gboolean +permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client *c) +{ + ParamName param = ParameterLast; + + if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) { + param = Geolocation; + } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(r)) { + if (webkit_user_media_permission_is_for_audio_device( + WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r))) + param = AccessMicrophone; + else if (webkit_user_media_permission_is_for_video_device( + WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r))) + param = AccessWebcam; + } else { + return FALSE; + } + + if (curconfig[param].val.i) + webkit_permission_request_allow(r); + else + webkit_permission_request_deny(r); + + return TRUE; +} + +gboolean +decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d, + WebKitPolicyDecisionType dt, Client *c) +{ + switch (dt) { + case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: + decidenavigation(d, c); + break; + case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: + decidenewwindow(d, c); + break; + case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: + decideresource(d, c); + break; + default: + webkit_policy_decision_ignore(d); + break; + } + return TRUE; +} + +void +decidenavigation(WebKitPolicyDecision *d, Client *c) +{ + WebKitNavigationAction *a = + webkit_navigation_policy_decision_get_navigation_action( + WEBKIT_NAVIGATION_POLICY_DECISION(d)); + + switch (webkit_navigation_action_get_navigation_type(a)) { + case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */ + default: + /* Do not navigate to links with a "_blank" target (popup) */ + if (webkit_navigation_policy_decision_get_frame_name( + WEBKIT_NAVIGATION_POLICY_DECISION(d))) { + webkit_policy_decision_ignore(d); + } else { + /* Filter out navigation to different domain ? */ + /* get action→urirequest, copy and load in new window+view + * on Ctrl+Click ? */ + webkit_policy_decision_use(d); + } + break; + } +} + +void +decidenewwindow(WebKitPolicyDecision *d, Client *c) +{ + Arg arg; + WebKitNavigationAction *a = + webkit_navigation_policy_decision_get_navigation_action( + WEBKIT_NAVIGATION_POLICY_DECISION(d)); + + + switch (webkit_navigation_action_get_navigation_type(a)) { + case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */ + case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: + /* Filter domains here */ +/* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event. + * test for link clicked but no button ? */ + arg.v = webkit_uri_request_get_uri( + webkit_navigation_action_get_request(a)); + newwindow(c, &arg, 0); + break; + case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */ + default: + break; + } + + webkit_policy_decision_ignore(d); +} + +void +decideresource(WebKitPolicyDecision *d, Client *c) +{ + int i, isascii = 1; + WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISION(d); + WebKitURIResponse *res = + webkit_response_policy_decision_get_response(r); + const gchar *uri = webkit_uri_response_get_uri(res); + + if (g_str_has_suffix(uri, "/favicon.ico")) { + webkit_policy_decision_ignore(d); + return; + } + + if (!g_str_has_prefix(uri, "http://") + && !g_str_has_prefix(uri, "https://") + && !g_str_has_prefix(uri, "about:") + && !g_str_has_prefix(uri, "file://") + && !g_str_has_prefix(uri, "data:") + && !g_str_has_prefix(uri, "blob:") + && strlen(uri) > 0) { + for (i = 0; i < strlen(uri); i++) { + if (!g_ascii_isprint(uri[i])) { + isascii = 0; + break; + } + } + if (isascii) { + handleplumb(c, uri); + webkit_policy_decision_ignore(d); + return; + } + } + + if (webkit_response_policy_decision_is_mime_type_supported(r)) { + webkit_policy_decision_use(d); + } else { + webkit_policy_decision_ignore(d); + download(c, res); + } +} + +void +insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e, Client *c) +{ + c->insecure = 1; +} + +void +downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c) +{ + g_signal_connect(G_OBJECT(d), "notify::response", + G_CALLBACK(responsereceived), c); +} + +void +responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c) +{ + download(c, webkit_download_get_response(d)); + webkit_download_cancel(d); +} + +void +download(Client *c, WebKitURIResponse *r) +{ + Arg a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c)); + spawn(c, &a); +} + +void +webprocessterminated(WebKitWebView *v, WebKitWebProcessTerminationReason r, + Client *c) +{ + fprintf(stderr, "web process terminated: %s\n", + r == WEBKIT_WEB_PROCESS_CRASHED ? "crashed" : "no memory"); + closeview(v, c); +} + +void +closeview(WebKitWebView *v, Client *c) +{ + gtk_widget_destroy(c->win); +} + +void +destroywin(GtkWidget* w, Client *c) +{ + destroyclient(c); + if (!clients) + gtk_main_quit(); +} + +void +pasteuri(GtkClipboard *clipboard, const char *text, gpointer d) +{ + Arg a = {.v = text }; + if (text) + loaduri((Client *) d, &a); +} + +void +reload(Client *c, const Arg *a) +{ + if (a->i) + webkit_web_view_reload_bypass_cache(c->view); + else + webkit_web_view_reload(c->view); +} + +void +print(Client *c, const Arg *a) +{ + webkit_print_operation_run_dialog(webkit_print_operation_new(c->view), + GTK_WINDOW(c->win)); +} + +void +showcert(Client *c, const Arg *a) +{ + GTlsCertificate *cert = c->failedcert ? c->failedcert : c->cert; + GcrCertificate *gcrt; + GByteArray *crt; + GtkWidget *win; + GcrCertificateWidget *wcert; + + if (!cert) + return; + + g_object_get(cert, "certificate", &crt, NULL); + gcrt = gcr_simple_certificate_new(crt->data, crt->len); + g_byte_array_unref(crt); + + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + wcert = gcr_certificate_widget_new(gcrt); + g_object_unref(gcrt); + + gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(wcert)); + gtk_widget_show_all(win); +} + +void +clipboard(Client *c, const Arg *a) +{ + if (a->i) { /* load clipboard uri */ + gtk_clipboard_request_text(gtk_clipboard_get( + GDK_SELECTION_PRIMARY), + pasteuri, c); + } else { /* copy uri */ + gtk_clipboard_set_text(gtk_clipboard_get( + GDK_SELECTION_PRIMARY), c->targeturi + ? c->targeturi : geturi(c), -1); + } +} + +void +zoom(Client *c, const Arg *a) +{ + if (a->i > 0) + webkit_web_view_set_zoom_level(c->view, + curconfig[ZoomLevel].val.f + 0.1); + else if (a->i < 0) + webkit_web_view_set_zoom_level(c->view, + curconfig[ZoomLevel].val.f - 0.1); + else + webkit_web_view_set_zoom_level(c->view, 1.0); + + curconfig[ZoomLevel].val.f = webkit_web_view_get_zoom_level(c->view); +} + +static void +msgext(Client *c, char type, const Arg *a) +{ + static char msg[MSGBUFSZ]; + int ret; + + if ((ret = snprintf(msg, sizeof(msg), "%c%c%c%c", + 4, c->pageid, type, a->i)) + >= sizeof(msg)) { + fprintf(stderr, "surf: message too long: %d\n", ret); + return; + } + + if (pipeout[1] && write(pipeout[1], msg, sizeof(msg)) < 0) + fprintf(stderr, "surf: error sending: %.*s\n", ret-2, msg+2); +} + +void +scrollv(Client *c, const Arg *a) +{ + msgext(c, 'v', a); +} + +void +scrollh(Client *c, const Arg *a) +{ + msgext(c, 'h', a); +} + +void +navigate(Client *c, const Arg *a) +{ + if (a->i < 0) + webkit_web_view_go_back(c->view); + else if (a->i > 0) + webkit_web_view_go_forward(c->view); +} + +void +stop(Client *c, const Arg *a) +{ + webkit_web_view_stop_loading(c->view); +} + +void +toggle(Client *c, const Arg *a) +{ + curconfig[a->i].val.i ^= 1; + setparameter(c, 1, (ParamName)a->i, &curconfig[a->i].val); +} + +void +togglefullscreen(Client *c, const Arg *a) +{ + /* toggling value is handled in winevent() */ + if (c->fullscreen) + gtk_window_unfullscreen(GTK_WINDOW(c->win)); + else + gtk_window_fullscreen(GTK_WINDOW(c->win)); +} + +void +togglecookiepolicy(Client *c, const Arg *a) +{ + ++cookiepolicy; + cookiepolicy %= strlen(curconfig[CookiePolicies].val.v); + + setparameter(c, 0, CookiePolicies, NULL); +} + +void +toggleinspector(Client *c, const Arg *a) +{ + if (webkit_web_inspector_is_attached(c->inspector)) + webkit_web_inspector_close(c->inspector); + else if (curconfig[Inspector].val.i) + webkit_web_inspector_show(c->inspector); +} + +void +find(Client *c, const Arg *a) +{ + const char *s, *f; + + if (a && a->i) { + if (a->i > 0) + webkit_find_controller_search_next(c->finder); + else + webkit_find_controller_search_previous(c->finder); + } else { + s = getatom(c, AtomFind); + f = webkit_find_controller_get_search_text(c->finder); + + if (g_strcmp0(f, s) == 0) /* reset search */ + webkit_find_controller_search(c->finder, "", findopts, + G_MAXUINT); + + webkit_find_controller_search(c->finder, s, findopts, + G_MAXUINT); + + if (strcmp(s, "") == 0) + webkit_find_controller_search_finish(c->finder); + } +} + +void +clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h) +{ + navigate(c, a); +} + +void +clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h) +{ + Arg arg; + + arg.v = webkit_hit_test_result_get_link_uri(h); + newwindow(c, &arg, a->i); +} + +void +clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h) +{ + Arg arg; + + arg = (Arg)VIDEOPLAY(webkit_hit_test_result_get_media_uri(h)); + spawn(c, &arg); +} + +int +main(int argc, char *argv[]) +{ + Arg arg; + Client *c; + + memset(&arg, 0, sizeof(arg)); + + /* command line args */ + ARGBEGIN { + case 'a': + defconfig[CookiePolicies].val.v = EARGF(usage()); + defconfig[CookiePolicies].prio = 2; + break; + case 'b': + defconfig[ScrollBars].val.i = 0; + defconfig[ScrollBars].prio = 2; + break; + case 'B': + defconfig[ScrollBars].val.i = 1; + defconfig[ScrollBars].prio = 2; + break; + case 'c': + cookiefile = EARGF(usage()); + break; + case 'C': + stylefile = EARGF(usage()); + break; + case 'd': + defconfig[DiskCache].val.i = 0; + defconfig[DiskCache].prio = 2; + break; + case 'D': + defconfig[DiskCache].val.i = 1; + defconfig[DiskCache].prio = 2; + break; + case 'e': + embed = strtol(EARGF(usage()), NULL, 0); + break; + case 'f': + defconfig[RunInFullscreen].val.i = 0; + defconfig[RunInFullscreen].prio = 2; + break; + case 'F': + defconfig[RunInFullscreen].val.i = 1; + defconfig[RunInFullscreen].prio = 2; + break; + case 'g': + defconfig[Geolocation].val.i = 0; + defconfig[Geolocation].prio = 2; + break; + case 'G': + defconfig[Geolocation].val.i = 1; + defconfig[Geolocation].prio = 2; + break; + case 'i': + defconfig[LoadImages].val.i = 0; + defconfig[LoadImages].prio = 2; + break; + case 'I': + defconfig[LoadImages].val.i = 1; + defconfig[LoadImages].prio = 2; + break; + case 'k': + defconfig[KioskMode].val.i = 0; + defconfig[KioskMode].prio = 2; + break; + case 'K': + defconfig[KioskMode].val.i = 1; + defconfig[KioskMode].prio = 2; + break; + case 'm': + defconfig[Style].val.i = 0; + defconfig[Style].prio = 2; + break; + case 'M': + defconfig[Style].val.i = 1; + defconfig[Style].prio = 2; + break; + case 'n': + defconfig[Inspector].val.i = 0; + defconfig[Inspector].prio = 2; + break; + case 'N': + defconfig[Inspector].val.i = 1; + defconfig[Inspector].prio = 2; + break; + case 'p': + defconfig[Plugins].val.i = 0; + defconfig[Plugins].prio = 2; + break; + case 'P': + defconfig[Plugins].val.i = 1; + defconfig[Plugins].prio = 2; + break; + case 'r': + scriptfile = EARGF(usage()); + break; + case 's': + defconfig[JavaScript].val.i = 0; + defconfig[JavaScript].prio = 2; + break; + case 'S': + defconfig[JavaScript].val.i = 1; + defconfig[JavaScript].prio = 2; + break; + case 't': + defconfig[StrictTLS].val.i = 0; + defconfig[StrictTLS].prio = 2; + break; + case 'T': + defconfig[StrictTLS].val.i = 1; + defconfig[StrictTLS].prio = 2; + break; + case 'u': + fulluseragent = EARGF(usage()); + break; + case 'v': + die("surf-"VERSION", see LICENSE for © details\n"); + case 'w': + showxid = 1; + break; + case 'x': + defconfig[Certificate].val.i = 0; + defconfig[Certificate].prio = 2; + break; + case 'X': + defconfig[Certificate].val.i = 1; + defconfig[Certificate].prio = 2; + break; + case 'z': + defconfig[ZoomLevel].val.f = strtof(EARGF(usage()), NULL); + defconfig[ZoomLevel].prio = 2; + break; + default: + usage(); + } ARGEND; + if (argc > 0) + arg.v = argv[0]; + else + arg.v = "about:blank"; + + setup(); + c = newclient(NULL); + showview(NULL, c); + + loaduri(c, &arg); + updatetitle(c); + + gtk_main(); + cleanup(); + + return 0; +} diff --git a/surf.o b/surf.o new file mode 100644 index 0000000000000000000000000000000000000000..8c5a0c49e1b18575a8e12e7161a6c54bd7f2475c GIT binary patch literal 71720 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%0A$ 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