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