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