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