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