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