tsurf.c - surf - surf browser, a WebKit2GTK based browser
Log
Files
Refs
README
LICENSE
---
tsurf.c (54230B)
---
     1 /* See LICENSE file for copyright and license details.
     2  *
     3  * To understand surf, start reading main().
     4  */
     5 #include 
     6 #include 
     7 #include 
     8 #include 
     9 #include 
    10 #include 
    11 #include 
    12 #include 
    13 #include 
    14 #include 
    15 #include 
    16 #include 
    17 #include 
    18 #include 
    19 #include 
    20 
    21 #include 
    22 #include 
    23 #include 
    24 #include 
    25 #include 
    26 #include 
    27 #include 
    28 #include 
    29 #include 
    30 #include 
    31 #include 
    32 #include 
    33 #include 
    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.
"); 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.
"); 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.
"); 1489 if (err & G_TLS_CERTIFICATE_EXPIRED) 1490 g_string_append(errmsg, "The certificate has expired.
"); 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.
"); 1495 if (err & G_TLS_CERTIFICATE_INSECURE) 1496 g_string_append(errmsg, 1497 "The certificate's algorithm is considered insecure.
"); 1498 if (err & G_TLS_CERTIFICATE_GENERIC_ERROR) 1499 g_string_append(errmsg, 1500 "Some error occurred validating the certificate.
"); 1501 1502 g_object_get(cert, "certificate-pem", &pem, NULL); 1503 html = g_strdup_printf("

Could not validate TLS for “%s”
%s

" 1504 "

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

" 1506 "

%s

", 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 }