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