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