surf.c (51025B)
1 /* See LICENSE file for copyright and license details. 2 * 3 * To understand surf, start reading main(). 4 */ 5 #include <signal.h> 6 #include <X11/X.h> 7 #include <X11/Xatom.h> 8 #include <gtk/gtk.h> 9 #include <gdk/gdkx.h> 10 #include <gdk/gdk.h> 11 #include <gdk/gdkkeysyms.h> 12 #include <string.h> 13 #include <sys/types.h> 14 #include <sys/wait.h> 15 #include <fcntl.h> 16 #include <unistd.h> 17 #include <limits.h> 18 #include <stdlib.h> 19 #include <stdio.h> 20 #include <webkit/webkit.h> 21 #include <JavaScriptCore/JavaScript.h> 22 #include <sys/file.h> 23 #include <libgen.h> 24 #include <stdarg.h> 25 #include <regex.h> 26 #include <pwd.h> 27 #include <glib.h> 28 #include <glib/gstdio.h> 29 30 #include "arg.h" 31 32 char *argv0; 33 34 #define LENGTH(x) (sizeof(x) / sizeof(x[0])) 35 #define CLEANMASK(mask) (mask & (MODKEY|GDK_SHIFT_MASK)) 36 #define COOKIEJAR_TYPE (cookiejar_get_type ()) 37 #define COOKIEJAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), COOKIEJAR_TYPE, CookieJar)) 38 39 enum { AtomFind, AtomGo, AtomUri, AtomUA, AtomLast }; 40 enum { 41 ClkDoc = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT, 42 ClkLink = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK, 43 ClkImg = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE, 44 ClkMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA, 45 ClkSel = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION, 46 ClkEdit = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE, 47 ClkAny = ClkDoc | ClkLink | ClkImg | ClkMedia | ClkSel | ClkEdit, 48 }; 49 50 typedef union Arg Arg; 51 union Arg { 52 gboolean b; 53 gint i; 54 const void *v; 55 }; 56 57 typedef struct Client { 58 GtkWidget *win, *scroll, *vbox, *pane; 59 WebKitWebView *view; 60 WebKitWebInspector *inspector; 61 char *title, *linkhover; 62 const char *needle; 63 gint progress; 64 struct Client *next; 65 gboolean zoomed, fullscreen, isinspecting, sslfailed; 66 } Client; 67 68 typedef struct { 69 char *key; 70 char *value; 71 } HttpHeader; 72 73 typedef struct { 74 guint mod; 75 guint keyval; 76 void (*func)(Client *c, const Arg *arg); 77 const Arg arg; 78 } Key; 79 80 typedef struct { 81 unsigned int click; 82 unsigned int mask; 83 guint button; 84 void (*func)(Client *c, const Arg *arg); 85 const Arg arg; 86 } Button; 87 88 typedef struct { 89 SoupCookieJarText parent_instance; 90 int lock; 91 } CookieJar; 92 93 typedef struct { 94 SoupCookieJarTextClass parent_class; 95 } CookieJarClass; 96 97 G_DEFINE_TYPE(CookieJar, cookiejar, SOUP_TYPE_COOKIE_JAR_TEXT) 98 99 typedef struct { 100 char *regex; 101 char *style; 102 regex_t re; 103 } SiteStyle; 104 105 static Display *dpy; 106 static Atom atoms[AtomLast]; 107 static Client *clients = NULL; 108 static GdkNativeWindow embed = 0; 109 static gboolean showxid = FALSE; 110 static char winid[64]; 111 static gboolean usingproxy = 0; 112 static char togglestat[12]; 113 static char pagestat[3]; 114 static GTlsDatabase *tlsdb; 115 static int policysel = 0; 116 static char *stylefile = NULL; 117 static SoupCache *diskcache = NULL; 118 119 static void addaccelgroup(Client *c); 120 static void beforerequest(WebKitWebView *w, WebKitWebFrame *f, 121 WebKitWebResource *r, WebKitNetworkRequest *req, 122 WebKitNetworkResponse *resp, Client *c); 123 static const char *getuserhomedir(const char *user); 124 static const char *getcurrentuserhomedir(void); 125 static char *buildfile(const char *path); 126 static char *buildpath(const char *path); 127 static gboolean buttonrelease(WebKitWebView *web, GdkEventButton *e, Client *c); 128 static void cleanup(void); 129 static void clipboard(Client *c, const Arg *arg); 130 131 /* Cookiejar implementation */ 132 static void cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie, 133 SoupCookie *new_cookie); 134 static void cookiejar_finalize(GObject *self); 135 static SoupCookieJarAcceptPolicy cookiepolicy_get(void); 136 static SoupCookieJar *cookiejar_new(const char *filename, gboolean read_only, 137 SoupCookieJarAcceptPolicy policy); 138 static void cookiejar_set_property(GObject *self, guint prop_id, 139 const GValue *value, GParamSpec *pspec); 140 static char cookiepolicy_set(const SoupCookieJarAcceptPolicy p); 141 142 static char *copystr(char **str, const char *src); 143 static WebKitWebView *createwindow(WebKitWebView *v, WebKitWebFrame *f, 144 Client *c); 145 static gboolean decidedownload(WebKitWebView *v, WebKitWebFrame *f, 146 WebKitNetworkRequest *r, gchar *m, 147 WebKitWebPolicyDecision *p, Client *c); 148 static gboolean decidewindow(WebKitWebView *v, WebKitWebFrame *f, 149 WebKitNetworkRequest *r, WebKitWebNavigationAction 150 *n, WebKitWebPolicyDecision *p, Client *c); 151 static gboolean deletion_interface(WebKitWebView *view, 152 WebKitDOMHTMLElement *arg1, Client *c); 153 static void destroyclient(Client *c); 154 static void destroywin(GtkWidget* w, Client *c); 155 static void die(const char *errstr, ...); 156 static void eval(Client *c, const Arg *arg); 157 static void find(Client *c, const Arg *arg); 158 static void fullscreen(Client *c, const Arg *arg); 159 static void geopolicyrequested(WebKitWebView *v, WebKitWebFrame *f, 160 WebKitGeolocationPolicyDecision *d, Client *c); 161 static const char *getatom(Client *c, int a); 162 static void gettogglestat(Client *c); 163 static void getpagestat(Client *c); 164 static char *geturi(Client *c); 165 static const gchar *getstyle(const char *uri); 166 static void setstyle(Client *c, const char *style); 167 168 static void handleplumb(Client *c, WebKitWebView *w, const gchar *uri); 169 170 static gboolean initdownload(WebKitWebView *v, WebKitDownload *o, Client *c); 171 172 static void inspector(Client *c, const Arg *arg); 173 static WebKitWebView *inspector_new(WebKitWebInspector *i, WebKitWebView *v, 174 Client *c); 175 static gboolean inspector_show(WebKitWebInspector *i, Client *c); 176 static gboolean inspector_close(WebKitWebInspector *i, Client *c); 177 static void inspector_finished(WebKitWebInspector *i, Client *c); 178 179 static gboolean keypress(GtkAccelGroup *group, GObject *obj, guint key, 180 GdkModifierType mods, Client *c); 181 static void linkhover(WebKitWebView *v, const char* t, const char* l, 182 Client *c); 183 static void loadstatuschange(WebKitWebView *view, GParamSpec *pspec, 184 Client *c); 185 static void loaduri(Client *c, const Arg *arg); 186 static void navigate(Client *c, const Arg *arg); 187 static Client *newclient(void); 188 static void newwindow(Client *c, const Arg *arg, gboolean noembed); 189 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d); 190 static gboolean contextmenu(WebKitWebView *view, GtkWidget *menu, 191 WebKitHitTestResult *target, gboolean keyboard, 192 Client *c); 193 static void menuactivate(GtkMenuItem *item, Client *c); 194 static void print(Client *c, const Arg *arg); 195 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event, 196 gpointer d); 197 static void progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c); 198 static void linkopen(Client *c, const Arg *arg); 199 static void linkopenembed(Client *c, const Arg *arg); 200 static void reload(Client *c, const Arg *arg); 201 static void scroll_h(Client *c, const Arg *arg); 202 static void scroll_v(Client *c, const Arg *arg); 203 static void scroll(GtkAdjustment *a, const Arg *arg); 204 static void setatom(Client *c, int a, const char *v); 205 static void setup(void); 206 static void setup_proxy(void); 207 static void sigchld(int unused); 208 static void sighup(int unused); 209 static void source(Client *c, const Arg *arg); 210 static void spawn(Client *c, const Arg *arg); 211 static void stop(Client *c, const Arg *arg); 212 static void titlechange(WebKitWebView *view, GParamSpec *pspec, Client *c); 213 static void titlechangeleave(void *a, void *b, Client *c); 214 static void toggle(Client *c, const Arg *arg); 215 static void togglehelper(Client *c, const Arg *arg, int reload); 216 static void togglecookiepolicy(Client *c, const Arg *arg); 217 static void toggleinsecurecontent(Client *c, const Arg *arg); 218 static void togglegeolocation(Client *c, const Arg *arg); 219 static void toggleproxy(Client *c, const Arg *arg); 220 static void togglescrollbars(Client *c, const Arg *arg); 221 static void togglesoup(Client *c, const Arg *arg); 222 static void togglestyle(Client *c, const Arg *arg); 223 static void updatetitle(Client *c); 224 static void updatewinid(Client *c); 225 static void usage(void); 226 static void windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame, 227 JSContextRef js, JSObjectRef win, Client *c); 228 static void zoom(Client *c, const Arg *arg); 229 230 /* configuration, allows nested code to access above variables */ 231 #include "config.h" 232 233 void 234 addaccelgroup(Client *c) 235 { 236 int i; 237 GtkAccelGroup *group = gtk_accel_group_new(); 238 GClosure *closure; 239 240 for (i = 0; i < LENGTH(keys); i++) { 241 closure = g_cclosure_new(G_CALLBACK(keypress), c, NULL); 242 gtk_accel_group_connect(group, keys[i].keyval, keys[i].mod, 0, 243 closure); 244 } 245 gtk_window_add_accel_group(GTK_WINDOW(c->win), group); 246 } 247 248 void 249 beforerequest(WebKitWebView *w, WebKitWebFrame *f, WebKitWebResource *r, 250 WebKitNetworkRequest *req, WebKitNetworkResponse *resp, 251 Client *c) 252 { 253 SoupMessage *msg; 254 SoupMessageHeaders *hdrs; 255 const gchar *uri = webkit_network_request_get_uri(req); 256 int i, isascii = 1; 257 258 if (g_str_has_suffix(uri, "/favicon.ico")) 259 webkit_network_request_set_uri(req, "about:blank"); 260 261 if (!g_str_has_prefix(uri, "http://") 262 && !g_str_has_prefix(uri, "https://") 263 && !g_str_has_prefix(uri, "about:") 264 && !g_str_has_prefix(uri, "file://") 265 && !g_str_has_prefix(uri, "data:") 266 && !g_str_has_prefix(uri, "blob:") 267 && strlen(uri) > 0) { 268 for (i = 0; i < strlen(uri); i++) { 269 if (!g_ascii_isprint(uri[i])) { 270 isascii = 0; 271 break; 272 } 273 } 274 if (isascii) 275 handleplumb(c, w, uri); 276 277 return; 278 } 279 if (g_str_has_prefix(uri, "http://") 280 || g_str_has_prefix(uri, "https://")) { 281 msg = webkit_network_request_get_message(req); 282 g_object_get(G_OBJECT(msg), "request-headers", &hdrs, 283 NULL); 284 if (hdrs != NULL) { 285 for (i = 0; i < LENGTH(customheaders); i++) { 286 soup_message_headers_replace(hdrs, 287 customheaders[i].key, 288 customheaders[i].value); 289 } 290 } 291 } 292 } 293 294 char * 295 buildfile(const char *path) 296 { 297 char *dname, *bname, *bpath, *fpath; 298 FILE *f; 299 300 dname = g_path_get_dirname(path); 301 bname = g_path_get_basename(path); 302 303 bpath = buildpath(dname); 304 g_free(dname); 305 306 fpath = g_build_filename(bpath, bname, NULL); 307 g_free(bpath); 308 g_free(bname); 309 310 if (!(f = fopen(fpath, "a"))) 311 die("Could not open file: %s\n", fpath); 312 313 g_chmod(fpath, 0600); /* always */ 314 fclose(f); 315 316 return fpath; 317 } 318 319 static const char* 320 getuserhomedir(const char *user) 321 { 322 struct passwd *pw = getpwnam(user); 323 324 if (!pw) 325 die("Can't get user %s login information.\n", user); 326 327 return pw->pw_dir; 328 } 329 330 static const char* 331 getcurrentuserhomedir(void) 332 { 333 const char *homedir; 334 const char *user; 335 struct passwd *pw; 336 337 homedir = getenv("HOME"); 338 if (homedir) 339 return homedir; 340 341 user = getenv("USER"); 342 if (user) 343 return getuserhomedir(user); 344 345 pw = getpwuid(getuid()); 346 if (!pw) 347 die("Can't get current user home directory\n"); 348 349 return pw->pw_dir; 350 } 351 352 char * 353 buildpath(const char *path) 354 { 355 char *apath, *name, *p, *fpath; 356 const char *homedir; 357 358 if (path[0] == '~') { 359 if (path[1] == '/' || path[1] == '\0') { 360 p = (char *)&path[1]; 361 homedir = getcurrentuserhomedir(); 362 } else { 363 if ((p = strchr(path, '/'))) { 364 name = g_strndup(&path[1], --p - path); 365 } else { 366 name = g_strdup(&path[1]); 367 } 368 369 homedir = getuserhomedir(name); 370 g_free(name); 371 } 372 apath = g_build_filename(homedir, p, NULL); 373 } else { 374 apath = g_strdup(path); 375 } 376 377 /* creating directory */ 378 if (g_mkdir_with_parents(apath, 0700) < 0) 379 die("Could not access directory: %s\n", apath); 380 381 fpath = realpath(apath, NULL); 382 g_free(apath); 383 384 return fpath; 385 } 386 387 gboolean 388 buttonrelease(WebKitWebView *web, GdkEventButton *e, Client *c) 389 { 390 WebKitHitTestResultContext context; 391 WebKitHitTestResult *result; 392 Arg arg; 393 unsigned int i; 394 395 result = webkit_web_view_get_hit_test_result(web, e); 396 g_object_get(result, "context", &context, NULL); 397 g_object_get(result, "link-uri", &arg.v, NULL); 398 for (i = 0; i < LENGTH(buttons); i++) { 399 if (context & buttons[i].click 400 && e->button == buttons[i].button 401 && CLEANMASK(e->state) == CLEANMASK(buttons[i].mask) 402 && buttons[i].func) { 403 buttons[i].func(c, buttons[i].click == ClkLink 404 && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); 405 return true; 406 } 407 } 408 return false; 409 } 410 411 void 412 cleanup(void) 413 { 414 if (diskcache) { 415 soup_cache_flush(diskcache); 416 soup_cache_dump(diskcache); 417 } 418 while (clients) 419 destroyclient(clients); 420 g_free(cookiefile); 421 g_free(scriptfile); 422 g_free(stylefile); 423 } 424 425 void 426 cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie, 427 SoupCookie *new_cookie) 428 { 429 flock(COOKIEJAR(self)->lock, LOCK_EX); 430 if (new_cookie && !new_cookie->expires && sessiontime) { 431 soup_cookie_set_expires(new_cookie, 432 soup_date_new_from_now(sessiontime)); 433 } 434 SOUP_COOKIE_JAR_CLASS(cookiejar_parent_class)->changed(self, 435 old_cookie, 436 new_cookie); 437 flock(COOKIEJAR(self)->lock, LOCK_UN); 438 } 439 440 void 441 cookiejar_class_init(CookieJarClass *klass) 442 { 443 SOUP_COOKIE_JAR_CLASS(klass)->changed = cookiejar_changed; 444 G_OBJECT_CLASS(klass)->get_property = 445 G_OBJECT_CLASS(cookiejar_parent_class)->get_property; 446 G_OBJECT_CLASS(klass)->set_property = cookiejar_set_property; 447 G_OBJECT_CLASS(klass)->finalize = cookiejar_finalize; 448 g_object_class_override_property(G_OBJECT_CLASS(klass), 1, "filename"); 449 } 450 451 void 452 cookiejar_finalize(GObject *self) 453 { 454 close(COOKIEJAR(self)->lock); 455 G_OBJECT_CLASS(cookiejar_parent_class)->finalize(self); 456 } 457 458 void 459 cookiejar_init(CookieJar *self) 460 { 461 self->lock = open(cookiefile, 0); 462 } 463 464 SoupCookieJar * 465 cookiejar_new(const char *filename, gboolean read_only, 466 SoupCookieJarAcceptPolicy policy) 467 { 468 return g_object_new(COOKIEJAR_TYPE, 469 SOUP_COOKIE_JAR_TEXT_FILENAME, filename, 470 SOUP_COOKIE_JAR_READ_ONLY, read_only, 471 SOUP_COOKIE_JAR_ACCEPT_POLICY, policy, NULL); 472 } 473 474 void 475 cookiejar_set_property(GObject *self, guint prop_id, const GValue *value, 476 GParamSpec *pspec) 477 { 478 flock(COOKIEJAR(self)->lock, LOCK_SH); 479 G_OBJECT_CLASS(cookiejar_parent_class)->set_property(self, prop_id, 480 value, pspec); 481 flock(COOKIEJAR(self)->lock, LOCK_UN); 482 } 483 484 SoupCookieJarAcceptPolicy 485 cookiepolicy_get(void) 486 { 487 switch (cookiepolicies[policysel]) { 488 case 'a': 489 return SOUP_COOKIE_JAR_ACCEPT_NEVER; 490 case '@': 491 return SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY; 492 case 'A': 493 default: 494 break; 495 } 496 497 return SOUP_COOKIE_JAR_ACCEPT_ALWAYS; 498 } 499 500 char 501 cookiepolicy_set(const SoupCookieJarAcceptPolicy ep) 502 { 503 switch (ep) { 504 case SOUP_COOKIE_JAR_ACCEPT_NEVER: 505 return 'a'; 506 case SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY: 507 return '@'; 508 case SOUP_COOKIE_JAR_ACCEPT_ALWAYS: 509 default: 510 break; 511 } 512 513 return 'A'; 514 } 515 516 void 517 evalscript(JSContextRef js, char *script, char *scriptname) 518 { 519 JSStringRef jsscript, jsscriptname; 520 JSValueRef exception = NULL; 521 522 jsscript = JSStringCreateWithUTF8CString(script); 523 jsscriptname = JSStringCreateWithUTF8CString(scriptname); 524 JSEvaluateScript(js, jsscript, JSContextGetGlobalObject(js), 525 jsscriptname, 0, &exception); 526 JSStringRelease(jsscript); 527 JSStringRelease(jsscriptname); 528 } 529 530 void 531 runscript(WebKitWebFrame *frame) 532 { 533 char *script; 534 GError *error; 535 536 if (g_file_get_contents(scriptfile, &script, NULL, &error)) { 537 evalscript(webkit_web_frame_get_global_context(frame), script, 538 scriptfile); 539 } 540 } 541 542 void 543 clipboard(Client *c, const Arg *arg) 544 { 545 gboolean paste = *(gboolean *)arg; 546 547 if (paste) { 548 gtk_clipboard_request_text(gtk_clipboard_get( 549 GDK_SELECTION_PRIMARY), 550 pasteuri, c); 551 } else { 552 gtk_clipboard_set_text(gtk_clipboard_get( 553 GDK_SELECTION_PRIMARY), c->linkhover 554 ? c->linkhover : geturi(c), -1); 555 } 556 } 557 558 char * 559 copystr(char **str, const char *src) 560 { 561 char *tmp; 562 tmp = g_strdup(src); 563 564 if (str && *str) { 565 g_free(*str); 566 *str = tmp; 567 } 568 return tmp; 569 } 570 571 WebKitWebView * 572 createwindow(WebKitWebView *v, WebKitWebFrame *f, Client *c) 573 { 574 Client *n = newclient(); 575 return n->view; 576 } 577 578 gboolean 579 decidedownload(WebKitWebView *v, WebKitWebFrame *f, WebKitNetworkRequest *r, 580 gchar *m, WebKitWebPolicyDecision *p, Client *c) 581 { 582 if (!webkit_web_view_can_show_mime_type(v, m)) { 583 webkit_web_policy_decision_download(p); 584 return TRUE; 585 } 586 return FALSE; 587 } 588 589 gboolean 590 decidewindow(WebKitWebView *view, WebKitWebFrame *f, WebKitNetworkRequest *r, 591 WebKitWebNavigationAction *n, WebKitWebPolicyDecision *p, 592 Client *c) 593 { 594 Arg arg; 595 596 if (webkit_web_navigation_action_get_reason(n) 597 == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) { 598 webkit_web_policy_decision_ignore(p); 599 arg.v = (void *)webkit_network_request_get_uri(r); 600 newwindow(NULL, &arg, 0); 601 return TRUE; 602 } 603 return FALSE; 604 } 605 606 gboolean 607 deletion_interface(WebKitWebView *view, WebKitDOMHTMLElement *arg1, Client *c) 608 { 609 return FALSE; 610 } 611 612 void 613 destroyclient(Client *c) 614 { 615 Client *p; 616 617 webkit_web_view_stop_loading(c->view); 618 gtk_widget_destroy(GTK_WIDGET(c->view)); 619 gtk_widget_destroy(c->scroll); 620 gtk_widget_destroy(c->vbox); 621 gtk_widget_destroy(c->win); 622 623 for (p = clients; p && p->next != c; p = p->next) 624 ; 625 if (p) 626 p->next = c->next; 627 else 628 clients = c->next; 629 free(c); 630 if (clients == NULL) 631 gtk_main_quit(); 632 } 633 634 void 635 destroywin(GtkWidget* w, Client *c) 636 { 637 destroyclient(c); 638 } 639 640 void 641 die(const char *errstr, ...) 642 { 643 va_list ap; 644 645 va_start(ap, errstr); 646 vfprintf(stderr, errstr, ap); 647 va_end(ap); 648 exit(EXIT_FAILURE); 649 } 650 651 void 652 find(Client *c, const Arg *arg) 653 { 654 const char *s; 655 656 s = getatom(c, AtomFind); 657 gboolean forward = *(gboolean *)arg; 658 webkit_web_view_search_text(c->view, s, FALSE, forward, TRUE); 659 } 660 661 void 662 fullscreen(Client *c, const Arg *arg) 663 { 664 if (c->fullscreen) 665 gtk_window_unfullscreen(GTK_WINDOW(c->win)); 666 else 667 gtk_window_fullscreen(GTK_WINDOW(c->win)); 668 c->fullscreen = !c->fullscreen; 669 } 670 671 void 672 geopolicyrequested(WebKitWebView *v, WebKitWebFrame *f, 673 WebKitGeolocationPolicyDecision *d, Client *c) 674 { 675 if (allowgeolocation) 676 webkit_geolocation_policy_allow(d); 677 else 678 webkit_geolocation_policy_deny(d); 679 } 680 681 const char * 682 getatom(Client *c, int a) 683 { 684 static char buf[BUFSIZ]; 685 Atom adummy; 686 int idummy; 687 unsigned long ldummy; 688 unsigned char *p = NULL; 689 690 XGetWindowProperty(dpy, GDK_WINDOW_XID(GTK_WIDGET(c->win)->window), 691 atoms[a], 0L, BUFSIZ, False, AnyPropertyType, 692 &adummy, &idummy, &ldummy, &ldummy, &p); 693 if (p) 694 strncpy(buf, (char *)p, LENGTH(buf)-1); 695 else 696 buf[0] = '\0'; 697 XFree(p); 698 699 return buf; 700 } 701 702 char * 703 geturi(Client *c) 704 { 705 char *uri; 706 707 if (!(uri = (char *)webkit_web_view_get_uri(c->view))) 708 uri = "about:blank"; 709 return uri; 710 } 711 712 const gchar * 713 getstyle(const char *uri) 714 { 715 int i; 716 717 if (stylefile != NULL) 718 return stylefile; 719 720 for (i = 0; i < LENGTH(styles); i++) { 721 if (styles[i].regex && !regexec(&(styles[i].re), uri, 0, 722 NULL, 0)) 723 return styles[i].style; 724 } 725 726 return ""; 727 } 728 729 void 730 setstyle(Client *c, const char *style) 731 { 732 WebKitWebSettings *settings = webkit_web_view_get_settings(c->view); 733 734 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", style, NULL); 735 } 736 737 void 738 handleplumb(Client *c, WebKitWebView *w, const gchar *uri) 739 { 740 Arg arg; 741 742 webkit_web_view_stop_loading(w); 743 arg = (Arg)PLUMB((char *)uri); 744 spawn(c, &arg); 745 } 746 747 gboolean 748 initdownload(WebKitWebView *view, WebKitDownload *o, Client *c) 749 { 750 Arg arg; 751 752 updatewinid(c); 753 arg = (Arg)DOWNLOAD((char *)webkit_download_get_uri(o), geturi(c)); 754 spawn(c, &arg); 755 return FALSE; 756 } 757 758 void 759 inspector(Client *c, const Arg *arg) 760 { 761 if (enableinspector) { 762 if (c->isinspecting) 763 webkit_web_inspector_close(c->inspector); 764 else 765 webkit_web_inspector_show(c->inspector); 766 } 767 } 768 769 WebKitWebView * 770 inspector_new(WebKitWebInspector *i, WebKitWebView *v, Client *c) 771 { 772 return WEBKIT_WEB_VIEW(webkit_web_view_new()); 773 } 774 775 gboolean 776 inspector_show(WebKitWebInspector *i, Client *c) 777 { 778 WebKitWebView *w; 779 780 if (c->isinspecting) 781 return false; 782 783 w = webkit_web_inspector_get_web_view(i); 784 gtk_paned_pack2(GTK_PANED(c->pane), GTK_WIDGET(w), TRUE, TRUE); 785 gtk_widget_show(GTK_WIDGET(w)); 786 c->isinspecting = true; 787 788 return true; 789 } 790 791 gboolean 792 inspector_close(WebKitWebInspector *i, Client *c) 793 { 794 GtkWidget *w; 795 796 if (!c->isinspecting) 797 return false; 798 799 w = GTK_WIDGET(webkit_web_inspector_get_web_view(i)); 800 gtk_widget_hide(w); 801 gtk_widget_destroy(w); 802 c->isinspecting = false; 803 804 return true; 805 } 806 807 void 808 inspector_finished(WebKitWebInspector *i, Client *c) 809 { 810 g_free(c->inspector); 811 } 812 813 gboolean 814 keypress(GtkAccelGroup *group, GObject *obj, guint key, GdkModifierType mods, 815 Client *c) 816 { 817 guint i; 818 gboolean processed = FALSE; 819 820 mods = CLEANMASK(mods); 821 key = gdk_keyval_to_lower(key); 822 updatewinid(c); 823 for (i = 0; i < LENGTH(keys); i++) { 824 if (key == keys[i].keyval 825 && mods == keys[i].mod 826 && keys[i].func) { 827 keys[i].func(c, &(keys[i].arg)); 828 processed = TRUE; 829 } 830 } 831 832 return processed; 833 } 834 835 void 836 linkhover(WebKitWebView *v, const char* t, const char* l, Client *c) 837 { 838 if (l) { 839 c->linkhover = copystr(&c->linkhover, l); 840 } else if (c->linkhover) { 841 free(c->linkhover); 842 c->linkhover = NULL; 843 } 844 updatetitle(c); 845 } 846 847 void 848 loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) 849 { 850 WebKitWebFrame *frame; 851 WebKitWebDataSource *src; 852 WebKitNetworkRequest *request; 853 SoupMessage *msg; 854 char *uri; 855 856 switch (webkit_web_view_get_load_status (c->view)) { 857 case WEBKIT_LOAD_COMMITTED: 858 uri = geturi(c); 859 if (strstr(uri, "https://") == uri) { 860 frame = webkit_web_view_get_main_frame(c->view); 861 src = webkit_web_frame_get_data_source(frame); 862 request = webkit_web_data_source_get_request(src); 863 msg = webkit_network_request_get_message(request); 864 c->sslfailed = !(soup_message_get_flags(msg) 865 & SOUP_MESSAGE_CERTIFICATE_TRUSTED); 866 } 867 setatom(c, AtomUri, uri); 868 c->title = copystr(&c->title, uri); 869 870 if (enablestyle) 871 setstyle(c, getstyle(uri)); 872 break; 873 case WEBKIT_LOAD_FINISHED: 874 c->progress = 100; 875 updatetitle(c); 876 if (diskcache) { 877 soup_cache_flush(diskcache); 878 soup_cache_dump(diskcache); 879 } 880 break; 881 default: 882 break; 883 } 884 } 885 886 void 887 loaduri(Client *c, const Arg *arg) 888 { 889 char *u = NULL, *rp; 890 const char *uri = (char *)arg->v; 891 Arg a = { .b = FALSE }; 892 struct stat st; 893 894 if (strcmp(uri, "") == 0) 895 return; 896 897 /* In case it's a file path. */ 898 if (stat(uri, &st) == 0) { 899 rp = realpath(uri, NULL); 900 u = g_strdup_printf("file://%s", rp); 901 free(rp); 902 } else { 903 u = g_strrstr(uri, "://") || g_str_has_prefix(uri, "about:") ? g_strdup(uri) 904 : g_strdup_printf("http://%s", uri); 905 } 906 907 setatom(c, AtomUri, uri); 908 909 910 /* prevents endless loop */ 911 if (strcmp(u, geturi(c)) == 0) { 912 reload(c, &a); 913 } else { 914 webkit_web_view_load_uri(c->view, u); 915 c->progress = 0; 916 c->title = copystr(&c->title, u); 917 updatetitle(c); 918 } 919 g_free(u); 920 } 921 922 void 923 navigate(Client *c, const Arg *arg) 924 { 925 int steps = *(int *)arg; 926 webkit_web_view_go_back_or_forward(c->view, steps); 927 } 928 929 Client * 930 newclient(void) 931 { 932 Client *c; 933 WebKitWebSettings *settings; 934 WebKitWebFrame *frame; 935 GdkGeometry hints = { 1, 1 }; 936 GdkScreen *screen; 937 gdouble dpi; 938 char *ua; 939 940 if (!(c = calloc(1, sizeof(Client)))) 941 die("Cannot malloc!\n"); 942 943 c->title = NULL; 944 c->progress = 100; 945 946 /* Window */ 947 if (embed) { 948 c->win = gtk_plug_new(embed); 949 } else { 950 c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); 951 952 /* TA: 20091214: Despite what the GNOME docs say, the ICCCM 953 * is always correct, so we should still call this function. 954 * But when doing so, we *must* differentiate between a 955 * WM_CLASS and a resource on the window. By convention, the 956 * window class (WM_CLASS) is capped, while the resource is in 957 * lowercase. Both these values come as a pair. 958 */ 959 gtk_window_set_wmclass(GTK_WINDOW(c->win), "surf", "Surf"); 960 961 /* TA: 20091214: And set the role here as well -- so that 962 * sessions can pick this up. 963 */ 964 gtk_window_set_role(GTK_WINDOW(c->win), "Surf"); 965 } 966 gtk_window_set_default_size(GTK_WINDOW(c->win), 800, 600); 967 g_signal_connect(G_OBJECT(c->win), 968 "destroy", 969 G_CALLBACK(destroywin), c); 970 g_signal_connect(G_OBJECT(c->win), 971 "leave_notify_event", 972 G_CALLBACK(titlechangeleave), c); 973 974 if (!kioskmode) 975 addaccelgroup(c); 976 977 /* Pane */ 978 c->pane = gtk_vpaned_new(); 979 980 /* VBox */ 981 c->vbox = gtk_vbox_new(FALSE, 0); 982 gtk_paned_pack1(GTK_PANED(c->pane), c->vbox, TRUE, TRUE); 983 984 /* Webview */ 985 c->view = WEBKIT_WEB_VIEW(webkit_web_view_new()); 986 987 g_signal_connect(G_OBJECT(c->view), 988 "notify::title", 989 G_CALLBACK(titlechange), c); 990 g_signal_connect(G_OBJECT(c->view), 991 "hovering-over-link", 992 G_CALLBACK(linkhover), c); 993 g_signal_connect(G_OBJECT(c->view), 994 "geolocation-policy-decision-requested", 995 G_CALLBACK(geopolicyrequested), c); 996 g_signal_connect(G_OBJECT(c->view), 997 "create-web-view", 998 G_CALLBACK(createwindow), c); 999 g_signal_connect(G_OBJECT(c->view), 1000 "new-window-policy-decision-requested", 1001 G_CALLBACK(decidewindow), c); 1002 g_signal_connect(G_OBJECT(c->view), 1003 "mime-type-policy-decision-requested", 1004 G_CALLBACK(decidedownload), c); 1005 g_signal_connect(G_OBJECT(c->view), 1006 "window-object-cleared", 1007 G_CALLBACK(windowobjectcleared), c); 1008 g_signal_connect(G_OBJECT(c->view), 1009 "notify::load-status", 1010 G_CALLBACK(loadstatuschange), c); 1011 g_signal_connect(G_OBJECT(c->view), 1012 "notify::progress", 1013 G_CALLBACK(progresschange), c); 1014 g_signal_connect(G_OBJECT(c->view), 1015 "download-requested", 1016 G_CALLBACK(initdownload), c); 1017 g_signal_connect(G_OBJECT(c->view), 1018 "button-release-event", 1019 G_CALLBACK(buttonrelease), c); 1020 g_signal_connect(G_OBJECT(c->view), 1021 "context-menu", 1022 G_CALLBACK(contextmenu), c); 1023 g_signal_connect(G_OBJECT(c->view), 1024 "resource-request-starting", 1025 G_CALLBACK(beforerequest), c); 1026 g_signal_connect(G_OBJECT(c->view), 1027 "should-show-delete-interface-for-element", 1028 G_CALLBACK(deletion_interface), c); 1029 1030 /* Scrolled Window */ 1031 c->scroll = gtk_scrolled_window_new(NULL, NULL); 1032 1033 frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(c->view)); 1034 g_signal_connect(G_OBJECT(frame), "scrollbars-policy-changed", 1035 G_CALLBACK(gtk_true), NULL); 1036 1037 if (!enablescrollbars) { 1038 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll), 1039 GTK_POLICY_NEVER, 1040 GTK_POLICY_NEVER); 1041 } else { 1042 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll), 1043 GTK_POLICY_AUTOMATIC, 1044 GTK_POLICY_AUTOMATIC); 1045 } 1046 1047 /* Arranging */ 1048 gtk_container_add(GTK_CONTAINER(c->scroll), GTK_WIDGET(c->view)); 1049 gtk_container_add(GTK_CONTAINER(c->win), c->pane); 1050 gtk_container_add(GTK_CONTAINER(c->vbox), c->scroll); 1051 1052 /* Setup */ 1053 gtk_box_set_child_packing(GTK_BOX(c->vbox), c->scroll, TRUE, TRUE, 0, 1054 GTK_PACK_START); 1055 gtk_widget_grab_focus(GTK_WIDGET(c->view)); 1056 gtk_widget_show(c->pane); 1057 gtk_widget_show(c->vbox); 1058 gtk_widget_show(c->scroll); 1059 gtk_widget_show(GTK_WIDGET(c->view)); 1060 gtk_widget_show(c->win); 1061 gtk_window_set_geometry_hints(GTK_WINDOW(c->win), NULL, &hints, 1062 GDK_HINT_MIN_SIZE); 1063 gdk_window_set_events(GTK_WIDGET(c->win)->window, GDK_ALL_EVENTS_MASK); 1064 gdk_window_add_filter(GTK_WIDGET(c->win)->window, processx, c); 1065 webkit_web_view_set_full_content_zoom(c->view, TRUE); 1066 1067 runscript(frame); 1068 1069 settings = webkit_web_view_get_settings(c->view); 1070 g_object_set(G_OBJECT(settings), "html5-local-storage-database-path", 1071 dbfolder, NULL); 1072 g_object_set(G_OBJECT(settings), 1073 "enable-offline-web-application-cache", 1074 offlineappcache, NULL); 1075 g_object_set(G_OBJECT(settings), 1076 "enable-page-cache", enablepagecache, NULL); 1077 g_object_set(G_OBJECT(settings), 1078 "enable-private-browsing", privatebrowsing, NULL); 1079 g_object_set(G_OBJECT(settings), "enable-dns-prefetching", 1080 dnsprefetching, NULL); 1081 1082 if (!(ua = getenv("SURF_USERAGENT"))) 1083 ua = useragent; 1084 g_object_set(G_OBJECT(settings), "user-agent", ua, NULL); 1085 setatom(c, AtomUA, ua); 1086 1087 g_object_set(G_OBJECT(settings), 1088 "auto-load-images", loadimages, NULL); 1089 g_object_set(G_OBJECT(settings), 1090 "enable-plugins", enableplugins, NULL); 1091 g_object_set(G_OBJECT(settings), 1092 "enable-scripts", enablescripts, NULL); 1093 g_object_set(G_OBJECT(settings), 1094 "enable-spatial-navigation", enablespatialbrowsing, NULL); 1095 g_object_set(G_OBJECT(settings), 1096 "enable-spell-checking", enablespellchecking, NULL); 1097 g_object_set(G_OBJECT(settings), 1098 "media-playback-allows-inline", inlineplayback, NULL); 1099 g_object_set(G_OBJECT(settings), 1100 "media-playback-requires-user-gesture", inlinegestures, NULL); 1101 g_object_set(G_OBJECT(settings), 1102 "enable-webaudio", enablewebaudio, NULL); 1103 g_object_set(G_OBJECT(settings), 1104 "enable-webgl", enablewebgl, NULL); 1105 g_object_set(G_OBJECT(settings), 1106 "enable-developer-extras", enableinspector, NULL); 1107 g_object_set(G_OBJECT(settings), 1108 "enable-default-context-menu", kioskmode ^ 1, NULL); 1109 g_object_set(G_OBJECT(settings), 1110 "default-font-size", defaultfontsize, NULL); 1111 g_object_set(G_OBJECT(settings), 1112 "default-monospace-font-size", defaultmonofontsize, NULL); 1113 g_object_set(G_OBJECT(settings), 1114 "resizable-text-areas", 1, NULL); 1115 g_object_set(G_OBJECT(settings), 1116 "default-encoding", defaultencoding, NULL); 1117 g_object_set(G_OBJECT(settings), 1118 "enable-accelerated-compositing", accelrendering, NULL); 1119 g_object_set(G_OBJECT(settings), 1120 "enable-display-of-insecure-content", insecureresources, 1121 NULL); 1122 g_object_set(G_OBJECT(settings), 1123 "enable-running-of-insecure-content", insecureresources, 1124 NULL); 1125 g_object_set(G_OBJECT(settings), 1126 "enable-html5-database", enablehtml5db, NULL); 1127 g_object_set(G_OBJECT(settings), 1128 "enable-html5-local-storage", enablehtml5local, NULL); 1129 g_object_set(G_OBJECT(settings), 1130 "enable-java-applet", enablejava, NULL); 1131 g_object_set(G_OBJECT(settings), 1132 "enable-media-stream", enablemediastream, NULL); 1133 g_object_set(G_OBJECT(settings), 1134 "enable-mediasource", enablemediasource, NULL); 1135 if (enablestyle) 1136 setstyle(c, getstyle("about:blank")); 1137 1138 /* 1139 * While stupid, CSS specifies that a pixel represents 1/96 of an inch. 1140 * This ensures websites are not unusably small with a high DPI screen. 1141 * It is equivalent to firefox's "layout.css.devPixelsPerPx" setting. 1142 */ 1143 if (zoomto96dpi) { 1144 screen = gdk_window_get_screen(GTK_WIDGET(c->win)->window); 1145 dpi = gdk_screen_get_resolution(screen); 1146 if (dpi != -1) { 1147 g_object_set(G_OBJECT(settings), 1148 "enforce-96-dpi", true, NULL); 1149 webkit_web_view_set_zoom_level(c->view, dpi/96); 1150 } 1151 } 1152 /* This might conflict with _zoomto96dpi_. */ 1153 if (zoomlevel != 1.0) 1154 webkit_web_view_set_zoom_level(c->view, zoomlevel); 1155 1156 if (enableinspector) { 1157 c->inspector = webkit_web_view_get_inspector(c->view); 1158 g_signal_connect(G_OBJECT(c->inspector), "inspect-web-view", 1159 G_CALLBACK(inspector_new), c); 1160 g_signal_connect(G_OBJECT(c->inspector), "show-window", 1161 G_CALLBACK(inspector_show), c); 1162 g_signal_connect(G_OBJECT(c->inspector), "close-window", 1163 G_CALLBACK(inspector_close), c); 1164 g_signal_connect(G_OBJECT(c->inspector), "finished", 1165 G_CALLBACK(inspector_finished), c); 1166 c->isinspecting = false; 1167 } 1168 1169 if (runinfullscreen) 1170 fullscreen(c, NULL); 1171 1172 setatom(c, AtomFind, ""); 1173 setatom(c, AtomUri, "about:blank"); 1174 if (hidebackground) 1175 webkit_web_view_set_transparent(c->view, TRUE); 1176 1177 c->next = clients; 1178 clients = c; 1179 1180 if (showxid) { 1181 gdk_display_sync(gtk_widget_get_display(c->win)); 1182 printf("%u\n", 1183 (guint)GDK_WINDOW_XID(GTK_WIDGET(c->win)->window)); 1184 fflush(NULL); 1185 if (fclose(stdout) != 0) 1186 die("Error closing stdout"); 1187 } 1188 1189 return c; 1190 } 1191 1192 void 1193 newwindow(Client *c, const Arg *arg, gboolean noembed) 1194 { 1195 guint i = 0; 1196 const char *cmd[30], *uri; 1197 const Arg a = { .v = (void *)cmd }; 1198 char tmp[64], ztmp[6]; 1199 1200 cmd[i++] = argv0; 1201 1202 if (cookiepolicies != NULL) { 1203 cmd[i++] = "-a"; 1204 cmd[i++] = cookiepolicies; 1205 } 1206 1207 if (enablescrollbars) 1208 cmd[i++] = "-B"; 1209 else 1210 cmd[i++] = "-b"; 1211 1212 if (cookiefile != NULL) { 1213 cmd[i++] = "-c"; 1214 cmd[i++] = cookiefile; 1215 } 1216 1217 if (enablediskcache) 1218 cmd[i++] = "-D"; 1219 else 1220 cmd[i++] = "-d"; 1221 1222 if (embed && !noembed) { 1223 cmd[i++] = "-e"; 1224 snprintf(tmp, LENGTH(tmp), "%u", (int)embed); 1225 cmd[i++] = tmp; 1226 } 1227 1228 if (runinfullscreen) 1229 cmd[i++] = "-F"; 1230 else 1231 cmd[i++] = "-f"; 1232 1233 if (allowgeolocation) 1234 cmd[i++] = "-G"; 1235 else 1236 cmd[i++] = "-g"; 1237 1238 if (loadimages) 1239 cmd[i++] = "-I"; 1240 else 1241 cmd[i++] = "-i"; 1242 1243 if (kioskmode) 1244 cmd[i++] = "-K"; 1245 else 1246 cmd[i++] = "-k"; 1247 1248 if (insecureresources) 1249 cmd[i++] = "-L"; 1250 else 1251 cmd[i++] = "-l"; 1252 1253 if (enablestyle) 1254 cmd[i++] = "-M"; 1255 else 1256 cmd[i++] = "-m"; 1257 1258 if (enableinspector) 1259 cmd[i++] = "-N"; 1260 else 1261 cmd[i++] = "-n"; 1262 1263 if (enableplugins) 1264 cmd[i++] = "-P"; 1265 else 1266 cmd[i++] = "-p"; 1267 1268 if (scriptfile != NULL) { 1269 cmd[i++] = "-r"; 1270 cmd[i++] = scriptfile; 1271 } 1272 1273 if (enablescripts) 1274 cmd[i++] = "-S"; 1275 else 1276 cmd[i++] = "-s"; 1277 1278 if (strictssl) 1279 cmd[i++] = "-T"; 1280 else 1281 cmd[i++] = "-t"; 1282 1283 if (useragent != NULL) { 1284 cmd[i++] = "-u"; 1285 cmd[i++] = useragent; 1286 } 1287 1288 if (privatebrowsing) 1289 cmd[i++] = "-W"; 1290 else 1291 cmd[i++] = "-w"; 1292 1293 1294 if (showxid) 1295 cmd[i++] = "-x"; 1296 1297 if (zoomlevel != 1.0) { 1298 cmd[i++] = "-z"; 1299 snprintf(ztmp, LENGTH(ztmp), "%.1f", zoomlevel); 1300 cmd[i++] = ztmp; 1301 } 1302 1303 cmd[i++] = "--"; 1304 uri = arg->v? (char *)arg->v : c->linkhover; 1305 if (uri) 1306 cmd[i++] = uri; 1307 cmd[i++] = NULL; 1308 spawn(NULL, &a); 1309 } 1310 1311 gboolean 1312 contextmenu(WebKitWebView *view, GtkWidget *menu, WebKitHitTestResult *target, 1313 gboolean keyboard, Client *c) 1314 { 1315 GList *items = gtk_container_get_children(GTK_CONTAINER(GTK_MENU(menu))); 1316 1317 for (GList *l = items; l; l = l->next) 1318 g_signal_connect(l->data, "activate", G_CALLBACK(menuactivate), c); 1319 1320 g_list_free(items); 1321 return FALSE; 1322 } 1323 1324 void 1325 menuactivate(GtkMenuItem *item, Client *c) 1326 { 1327 /* 1328 * context-menu-action-2000 open link 1329 * context-menu-action-1 open link in window 1330 * context-menu-action-2 download linked file 1331 * context-menu-action-3 copy link location 1332 * context-menu-action-7 copy image address 1333 * context-menu-action-13 reload 1334 * context-menu-action-10 back 1335 * context-menu-action-11 forward 1336 * context-menu-action-12 stop 1337 */ 1338 1339 GtkAction *a = NULL; 1340 const char *name, *uri; 1341 GtkClipboard *prisel, *clpbrd; 1342 1343 a = gtk_activatable_get_related_action(GTK_ACTIVATABLE(item)); 1344 if (a == NULL) 1345 return; 1346 1347 name = gtk_action_get_name(a); 1348 if (!g_strcmp0(name, "context-menu-action-3")) { 1349 prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY); 1350 gtk_clipboard_set_text(prisel, c->linkhover, -1); 1351 } else if (!g_strcmp0(name, "context-menu-action-7")) { 1352 prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY); 1353 clpbrd = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); 1354 uri = gtk_clipboard_wait_for_text(clpbrd); 1355 if (uri) 1356 gtk_clipboard_set_text(prisel, uri, -1); 1357 } 1358 } 1359 1360 void 1361 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d) 1362 { 1363 Arg arg = {.v = text }; 1364 if (text != NULL) 1365 loaduri((Client *) d, &arg); 1366 } 1367 1368 void 1369 print(Client *c, const Arg *arg) 1370 { 1371 webkit_web_frame_print(webkit_web_view_get_main_frame(c->view)); 1372 } 1373 1374 GdkFilterReturn 1375 processx(GdkXEvent *e, GdkEvent *event, gpointer d) 1376 { 1377 Client *c = (Client *)d; 1378 WebKitWebSettings *settings; 1379 XPropertyEvent *ev; 1380 Arg arg; 1381 1382 if (((XEvent *)e)->type == PropertyNotify) { 1383 ev = &((XEvent *)e)->xproperty; 1384 if (ev->state == PropertyNewValue) { 1385 if (ev->atom == atoms[AtomFind]) { 1386 arg.b = TRUE; 1387 find(c, &arg); 1388 return GDK_FILTER_REMOVE; 1389 } else if (ev->atom == atoms[AtomGo]) { 1390 arg.v = getatom(c, AtomGo); 1391 loaduri(c, &arg); 1392 return GDK_FILTER_REMOVE; 1393 } else if (ev->atom == atoms[AtomUA]) { 1394 settings = webkit_web_view_get_settings(c->view); 1395 g_object_set(G_OBJECT(settings), "user-agent", 1396 getatom(c, AtomUA), NULL); 1397 return GDK_FILTER_REMOVE; 1398 } 1399 } 1400 } 1401 return GDK_FILTER_CONTINUE; 1402 } 1403 1404 void 1405 progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c) 1406 { 1407 c->progress = webkit_web_view_get_progress(c->view) * 100; 1408 updatetitle(c); 1409 } 1410 1411 void 1412 linkopen(Client *c, const Arg *arg) 1413 { 1414 newwindow(NULL, arg, 1); 1415 } 1416 1417 void 1418 linkopenembed(Client *c, const Arg *arg) 1419 { 1420 newwindow(NULL, arg, 0); 1421 } 1422 1423 void 1424 reload(Client *c, const Arg *arg) 1425 { 1426 gboolean nocache = *(gboolean *)arg; 1427 if (nocache) 1428 webkit_web_view_reload_bypass_cache(c->view); 1429 else 1430 webkit_web_view_reload(c->view); 1431 } 1432 1433 void 1434 scroll_h(Client *c, const Arg *arg) 1435 { 1436 scroll(gtk_scrolled_window_get_hadjustment( 1437 GTK_SCROLLED_WINDOW(c->scroll)), arg); 1438 } 1439 1440 void 1441 scroll_v(Client *c, const Arg *arg) 1442 { 1443 scroll(gtk_scrolled_window_get_vadjustment( 1444 GTK_SCROLLED_WINDOW(c->scroll)), arg); 1445 } 1446 1447 void 1448 scroll(GtkAdjustment *a, const Arg *arg) 1449 { 1450 gdouble v; 1451 1452 v = gtk_adjustment_get_value(a); 1453 switch (arg->i) { 1454 case +10000: 1455 case -10000: 1456 v += gtk_adjustment_get_page_increment(a) * (arg->i / 10000); 1457 break; 1458 case +20000: 1459 case -20000: 1460 default: 1461 v += gtk_adjustment_get_step_increment(a) * arg->i; 1462 } 1463 1464 v = MAX(v, 0.0); 1465 v = MIN(v, gtk_adjustment_get_upper(a) - 1466 gtk_adjustment_get_page_size(a)); 1467 gtk_adjustment_set_value(a, v); 1468 } 1469 1470 void 1471 setatom(Client *c, int a, const char *v) 1472 { 1473 XSync(dpy, False); 1474 XChangeProperty(dpy, GDK_WINDOW_XID(GTK_WIDGET(c->win)->window), 1475 atoms[a], XA_STRING, 8, PropModeReplace, 1476 (unsigned char *)v, strlen(v) + 1); 1477 XSync(dpy, False); 1478 } 1479 1480 void 1481 setup(void) 1482 { 1483 int i; 1484 char *styledirfile, *stylepath; 1485 SoupSession *s; 1486 GError *error = NULL; 1487 1488 /* clean up any zombies immediately */ 1489 sigchld(0); 1490 if (signal(SIGHUP, sighup) == SIG_ERR) 1491 die("Can't install SIGHUP handler"); 1492 gtk_init(NULL, NULL); 1493 1494 dpy = GDK_DISPLAY(); 1495 1496 /* atoms */ 1497 atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False); 1498 atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False); 1499 atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False); 1500 atoms[AtomUA] = XInternAtom(dpy, "_SURF_UA", False); 1501 1502 /* dirs and files */ 1503 cookiefile = buildfile(cookiefile); 1504 scriptfile = buildfile(scriptfile); 1505 cachefolder = buildpath(cachefolder); 1506 dbfolder = buildpath(dbfolder); 1507 if (stylefile == NULL) { 1508 styledir = buildpath(styledir); 1509 for (i = 0; i < LENGTH(styles); i++) { 1510 if (regcomp(&(styles[i].re), styles[i].regex, 1511 REG_EXTENDED)) { 1512 fprintf(stderr, 1513 "Could not compile regex: %s\n", 1514 styles[i].regex); 1515 styles[i].regex = NULL; 1516 } 1517 styledirfile = g_strconcat(styledir, "/", 1518 styles[i].style, NULL); 1519 stylepath = buildfile(styledirfile); 1520 styles[i].style = g_strconcat("file://", stylepath, 1521 NULL); 1522 g_free(styledirfile); 1523 g_free(stylepath); 1524 } 1525 g_free(styledir); 1526 } else { 1527 stylepath = buildfile(stylefile); 1528 stylefile = g_strconcat("file://", stylepath, NULL); 1529 g_free(stylepath); 1530 } 1531 1532 /* request handler */ 1533 s = webkit_get_default_session(); 1534 1535 /* cookie jar */ 1536 soup_session_add_feature(s, 1537 SOUP_SESSION_FEATURE(cookiejar_new(cookiefile, 1538 FALSE, cookiepolicy_get()))); 1539 1540 /* disk cache */ 1541 if (enablediskcache) { 1542 diskcache = soup_cache_new(cachefolder, 1543 SOUP_CACHE_SINGLE_USER); 1544 soup_cache_set_max_size(diskcache, diskcachebytes); 1545 soup_cache_load(diskcache); 1546 soup_session_add_feature(s, SOUP_SESSION_FEATURE(diskcache)); 1547 } 1548 1549 /* ssl */ 1550 tlsdb = g_tls_file_database_new(cafile, &error); 1551 if (error) { 1552 g_warning("Error loading SSL database %s: %s", cafile, 1553 error->message); 1554 g_error_free(error); 1555 } 1556 g_object_set(G_OBJECT(s), "tls-database", tlsdb, NULL); 1557 g_object_set(G_OBJECT(s), "ssl-strict", strictssl, NULL); 1558 1559 setup_proxy(); 1560 } 1561 1562 void 1563 setup_proxy(void) 1564 { 1565 char *proxy, *new_proxy, *no_proxy, **new_no_proxy; 1566 GProxyResolver *pr; 1567 SoupSession *s; 1568 1569 /* request handler */ 1570 s = webkit_get_default_session(); 1571 1572 /* proxy */ 1573 if ((proxy = getenv("http_proxy")) && strcmp(proxy, "")) { 1574 new_proxy = g_strrstr(proxy, "http://") 1575 || g_strrstr(proxy, "https://") 1576 || g_strrstr(proxy, "socks://") 1577 || g_strrstr(proxy, "socks4://") 1578 || g_strrstr(proxy, "socks4a://") 1579 || g_strrstr(proxy, "socks5://") 1580 ? g_strdup(proxy) 1581 : g_strdup_printf("http://%s", proxy); 1582 new_no_proxy = ((no_proxy = getenv("no_proxy")) && strcmp(no_proxy, "")) 1583 ? g_strsplit(no_proxy, ",", -1) : NULL; 1584 pr = g_simple_proxy_resolver_new(new_proxy, new_no_proxy); 1585 g_object_set(G_OBJECT(s), "proxy-resolver", pr, NULL); 1586 g_free(new_proxy); 1587 g_strfreev(new_no_proxy); 1588 usingproxy = 1; 1589 } else { 1590 usingproxy = 0; 1591 } 1592 } 1593 1594 void 1595 sigchld(int unused) 1596 { 1597 if (signal(SIGCHLD, sigchld) == SIG_ERR) 1598 die("Can't install SIGCHLD handler"); 1599 while (0 < waitpid(-1, NULL, WNOHANG)); 1600 } 1601 1602 void 1603 sighup(int unused) 1604 { 1605 Arg a = { .b = FALSE }; 1606 Client *c; 1607 1608 for (c = clients; c; c = c->next) 1609 reload(c, &a); 1610 } 1611 1612 void 1613 source(Client *c, const Arg *arg) 1614 { 1615 Arg a = { .b = FALSE }; 1616 gboolean s; 1617 1618 s = webkit_web_view_get_view_source_mode(c->view); 1619 webkit_web_view_set_view_source_mode(c->view, !s); 1620 reload(c, &a); 1621 } 1622 1623 void 1624 spawn(Client *c, const Arg *arg) 1625 { 1626 if (fork() == 0) { 1627 if (dpy) 1628 close(ConnectionNumber(dpy)); 1629 setsid(); 1630 execvp(((char **)arg->v)[0], (char **)arg->v); 1631 fprintf(stderr, "surf: execvp %s", ((char **)arg->v)[0]); 1632 perror(" failed"); 1633 exit(0); 1634 } 1635 } 1636 1637 void 1638 eval(Client *c, const Arg *arg) 1639 { 1640 WebKitWebFrame *frame = webkit_web_view_get_main_frame(c->view); 1641 evalscript(webkit_web_frame_get_global_context(frame), 1642 ((char **)arg->v)[0], ""); 1643 } 1644 1645 void 1646 stop(Client *c, const Arg *arg) 1647 { 1648 webkit_web_view_stop_loading(c->view); 1649 } 1650 1651 void 1652 titlechange(WebKitWebView *view, GParamSpec *pspec, Client *c) 1653 { 1654 const gchar *t = webkit_web_view_get_title(view); 1655 1656 if (t) { 1657 c->title = copystr(&c->title, t); 1658 updatetitle(c); 1659 } 1660 } 1661 1662 void 1663 titlechangeleave(void *a, void *b, Client *c) 1664 { 1665 c->linkhover = NULL; 1666 updatetitle(c); 1667 } 1668 1669 void 1670 togglehelper(Client *c, const Arg *arg, int doreload) 1671 { 1672 WebKitWebSettings *settings; 1673 char *name = (char *)arg->v; 1674 gboolean value; 1675 Arg a = { .b = FALSE }; 1676 1677 settings = webkit_web_view_get_settings(c->view); 1678 g_object_get(G_OBJECT(settings), name, &value, NULL); 1679 g_object_set(G_OBJECT(settings), name, !value, NULL); 1680 1681 if (doreload) 1682 reload(c, &a); 1683 } 1684 1685 void 1686 toggle(Client *c, const Arg *arg) 1687 { 1688 togglehelper(c, arg, 1); 1689 } 1690 1691 void 1692 togglecookiepolicy(Client *c, const Arg *arg) 1693 { 1694 SoupCookieJar *jar; 1695 SoupCookieJarAcceptPolicy policy; 1696 1697 jar = SOUP_COOKIE_JAR(soup_session_get_feature( 1698 webkit_get_default_session(), 1699 SOUP_TYPE_COOKIE_JAR)); 1700 g_object_get(G_OBJECT(jar), "accept-policy", &policy, NULL); 1701 1702 policysel++; 1703 if (policysel >= strlen(cookiepolicies)) 1704 policysel = 0; 1705 1706 g_object_set(G_OBJECT(jar), "accept-policy", cookiepolicy_get(), NULL); 1707 1708 updatetitle(c); 1709 /* Do not reload. */ 1710 } 1711 1712 void 1713 toggleinsecurecontent(Client *c, const Arg *arg) 1714 { 1715 Arg a; 1716 1717 a.v = "enable-running-of-insecure-content"; 1718 togglehelper(c, &a, 0); 1719 a.v = "enable-display-of-insecure-content"; 1720 togglehelper(c, &a, 1); 1721 } 1722 1723 void 1724 togglegeolocation(Client *c, const Arg *arg) 1725 { 1726 Arg a = { .b = FALSE }; 1727 1728 allowgeolocation ^= 1; 1729 reload(c, &a); 1730 } 1731 1732 void 1733 togglesoup(Client *c, const Arg *arg) 1734 { 1735 SoupSession *s; 1736 char *name = (char *)arg->v; 1737 gboolean value; 1738 Arg a = { .b = FALSE }; 1739 1740 /* request handler */ 1741 s = webkit_get_default_session(); 1742 g_object_get(G_OBJECT(s), name, &value, NULL); 1743 g_object_set(G_OBJECT(s), name, !value, NULL); 1744 1745 reload(c, &a); 1746 } 1747 1748 void 1749 twitch(Client *c, const Arg *arg) 1750 { 1751 GtkAdjustment *a; 1752 gdouble v; 1753 1754 a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW( 1755 c->scroll)); 1756 1757 v = gtk_adjustment_get_value(a); 1758 1759 v += arg->i; 1760 1761 v = MAX(v, 0.0); 1762 v = MIN(v, gtk_adjustment_get_upper(a) - 1763 gtk_adjustment_get_page_size(a)); 1764 gtk_adjustment_set_value(a, v); 1765 } 1766 1767 void 1768 toggleproxy(Client *c, const Arg *arg) 1769 { 1770 SoupSession *s; 1771 GProxyResolver *pr; 1772 1773 /* request handler */ 1774 s = webkit_get_default_session(); 1775 1776 if (usingproxy) { 1777 pr = NULL; 1778 g_object_get(G_OBJECT(s), "proxy-resolver", &pr, NULL); 1779 if (pr != NULL) 1780 g_object_unref(pr); 1781 1782 g_object_set(G_OBJECT(s), "proxy-resolver", NULL, NULL); 1783 usingproxy = 0; 1784 } else { 1785 setup_proxy(); 1786 } 1787 1788 updatetitle(c); 1789 /* Do not reload. */ 1790 } 1791 1792 void 1793 togglescrollbars(Client *c, const Arg *arg) 1794 { 1795 GtkPolicyType vspolicy; 1796 Arg a; 1797 1798 gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(c->scroll), NULL, 1799 &vspolicy); 1800 1801 if (vspolicy == GTK_POLICY_AUTOMATIC) { 1802 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll), 1803 GTK_POLICY_NEVER, 1804 GTK_POLICY_NEVER); 1805 } else { 1806 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll), 1807 GTK_POLICY_AUTOMATIC, 1808 GTK_POLICY_AUTOMATIC); 1809 a.i = +1; 1810 twitch(c, &a); 1811 a.i = -1; 1812 twitch(c, &a); 1813 } 1814 } 1815 1816 void 1817 togglestyle(Client *c, const Arg *arg) 1818 { 1819 enablestyle = !enablestyle; 1820 setstyle(c, enablestyle ? getstyle(geturi(c)) : ""); 1821 updatetitle(c); 1822 } 1823 1824 void 1825 gettogglestat(Client *c) 1826 { 1827 gboolean value; 1828 int p = 0; 1829 WebKitWebSettings *settings = webkit_web_view_get_settings(c->view); 1830 SoupSession *s = webkit_get_default_session(); 1831 1832 togglestat[p++] = cookiepolicy_set(cookiepolicy_get()); 1833 1834 g_object_get(G_OBJECT(settings), "enable-caret-browsing", &value, 1835 NULL); 1836 togglestat[p++] = value? 'C': 'c'; 1837 1838 togglestat[p++] = enablediskcache? 'D': 'd'; 1839 1840 togglestat[p++] = allowgeolocation? 'G': 'g'; 1841 1842 g_object_get(G_OBJECT(settings), "auto-load-images", &value, NULL); 1843 togglestat[p++] = value? 'I': 'i'; 1844 1845 g_object_get(G_OBJECT(settings), 1846 "enable-display-of-insecure-content", &value, NULL); 1847 togglestat[p++] = value? 'L': 'l'; 1848 1849 togglestat[p++] = enablestyle ? 'M': 'm'; 1850 1851 g_object_get(G_OBJECT(settings), "enable-scripts", &value, NULL); 1852 togglestat[p++] = value? 'S': 's'; 1853 1854 g_object_get(G_OBJECT(s), "ssl-strict", &value, NULL); 1855 togglestat[p++] = value? 'T': 't'; 1856 1857 g_object_get(G_OBJECT(settings), "enable-plugins", &value, NULL); 1858 togglestat[p++] = value? 'V': 'v'; 1859 1860 g_object_get(G_OBJECT(settings), "enable-private-browsing", &value, 1861 NULL); 1862 togglestat[p++] = value? 'W': 'w'; 1863 1864 togglestat[p] = '\0'; 1865 } 1866 1867 void 1868 getpagestat(Client *c) 1869 { 1870 const char *uri = geturi(c); 1871 1872 if (strstr(uri, "https://") == uri) 1873 pagestat[0] = c->sslfailed ? 'U' : 'T'; 1874 else 1875 pagestat[0] = '-'; 1876 1877 pagestat[1] = usingproxy ? 'P' : '-'; 1878 pagestat[2] = '\0'; 1879 } 1880 1881 void 1882 updatetitle(Client *c) 1883 { 1884 char *t; 1885 1886 if (showindicators) { 1887 gettogglestat(c); 1888 getpagestat(c); 1889 1890 if (c->linkhover) { 1891 t = g_strdup_printf("%s:%s | %s", togglestat, pagestat, 1892 c->linkhover); 1893 } else if (c->progress != 100) { 1894 t = g_strdup_printf("[%i%%] %s:%s | %s", c->progress, 1895 togglestat, pagestat, 1896 c->title == NULL ? "" : c->title); 1897 } else { 1898 t = g_strdup_printf("%s:%s | %s", togglestat, pagestat, 1899 c->title == NULL ? "" : c->title); 1900 } 1901 1902 gtk_window_set_title(GTK_WINDOW(c->win), t); 1903 g_free(t); 1904 } else { 1905 gtk_window_set_title(GTK_WINDOW(c->win), (c->title == NULL) ? 1906 "" : c->title); 1907 } 1908 } 1909 1910 void 1911 updatewinid(Client *c) 1912 { 1913 snprintf(winid, LENGTH(winid), "%u", 1914 (int)GDK_WINDOW_XID(GTK_WIDGET(c->win)->window)); 1915 } 1916 1917 void 1918 usage(void) 1919 { 1920 die("usage: %s [-bBdDfFgGiIkKlLmMnNpPsStTvwWx] [-a cookiepolicies ] " 1921 "[-c cookiefile] [-e xid] [-r scriptfile] [-y stylefile] " 1922 "[-u useragent] [-z zoomlevel] [uri]\n", basename(argv0)); 1923 } 1924 1925 void 1926 windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame, JSContextRef js, 1927 JSObjectRef win, Client *c) 1928 { 1929 runscript(frame); 1930 } 1931 1932 void 1933 zoom(Client *c, const Arg *arg) 1934 { 1935 c->zoomed = TRUE; 1936 if (arg->i < 0) { 1937 /* zoom out */ 1938 webkit_web_view_zoom_out(c->view); 1939 } else if (arg->i > 0) { 1940 /* zoom in */ 1941 webkit_web_view_zoom_in(c->view); 1942 } else { 1943 /* reset */ 1944 c->zoomed = FALSE; 1945 webkit_web_view_set_zoom_level(c->view, 1.0); 1946 } 1947 } 1948 1949 int 1950 main(int argc, char *argv[]) 1951 { 1952 Arg arg; 1953 Client *c; 1954 1955 memset(&arg, 0, sizeof(arg)); 1956 1957 /* command line args */ 1958 ARGBEGIN { 1959 case 'a': 1960 cookiepolicies = EARGF(usage()); 1961 break; 1962 case 'b': 1963 enablescrollbars = 0; 1964 break; 1965 case 'B': 1966 enablescrollbars = 1; 1967 break; 1968 case 'c': 1969 cookiefile = EARGF(usage()); 1970 break; 1971 case 'd': 1972 enablediskcache = 0; 1973 break; 1974 case 'D': 1975 enablediskcache = 1; 1976 break; 1977 case 'e': 1978 embed = strtol(EARGF(usage()), NULL, 0); 1979 break; 1980 case 'f': 1981 runinfullscreen = 0; 1982 break; 1983 case 'F': 1984 runinfullscreen = 1; 1985 break; 1986 case 'g': 1987 allowgeolocation = 0; 1988 break; 1989 case 'G': 1990 allowgeolocation = 1; 1991 break; 1992 case 'i': 1993 loadimages = 0; 1994 break; 1995 case 'I': 1996 loadimages = 1; 1997 break; 1998 case 'k': 1999 kioskmode = 0; 2000 break; 2001 case 'K': 2002 kioskmode = 1; 2003 break; 2004 case 'L': 2005 insecureresources = 1; 2006 break; 2007 case 'l': 2008 insecureresources = 0; 2009 break; 2010 case 'm': 2011 enablestyle = 0; 2012 break; 2013 case 'M': 2014 enablestyle = 1; 2015 break; 2016 case 'n': 2017 enableinspector = 0; 2018 break; 2019 case 'N': 2020 enableinspector = 1; 2021 break; 2022 case 'p': 2023 enableplugins = 0; 2024 break; 2025 case 'P': 2026 enableplugins = 1; 2027 break; 2028 case 'r': 2029 scriptfile = EARGF(usage()); 2030 break; 2031 case 's': 2032 enablescripts = 0; 2033 break; 2034 case 'S': 2035 enablescripts = 1; 2036 break; 2037 case 't': 2038 strictssl = 0; 2039 break; 2040 case 'T': 2041 strictssl = 1; 2042 break; 2043 case 'y': 2044 stylefile = EARGF(usage()); 2045 break; 2046 case 'u': 2047 useragent = EARGF(usage()); 2048 break; 2049 case 'v': 2050 die("surf-"VERSION", ©2009-2016 surf engineers, " 2051 "see LICENSE for details\n"); 2052 case 'w': 2053 privatebrowsing = 0; 2054 break; 2055 case 'W': 2056 privatebrowsing = 1; 2057 break; 2058 case 'x': 2059 showxid = TRUE; 2060 break; 2061 case 'z': 2062 zoomlevel = strtof(EARGF(usage()), NULL); 2063 break; 2064 default: 2065 usage(); 2066 } ARGEND; 2067 if (argc > 0) 2068 arg.v = argv[0]; 2069 2070 setup(); 2071 c = newclient(); 2072 if (arg.v) 2073 loaduri(clients, &arg); 2074 else 2075 updatetitle(c); 2076 2077 gtk_main(); 2078 cleanup(); 2079 2080 return EXIT_SUCCESS; 2081 } 2082