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