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