Major styles update.
[surf.git] / surf.c
diff --git a/surf.c b/surf.c
index c9fa08d..6f71657 100644 (file)
--- a/surf.c
+++ b/surf.c
@@ -2,6 +2,7 @@
  *
  * To understand surf, start reading main().
  */
+
 #include <signal.h>
 #include <X11/X.h>
 #include <X11/Xatom.h>
 #include <sys/file.h>
 #include <libgen.h>
 #include <stdarg.h>
+#include <regex.h>
 
 #include "arg.h"
 
 char *argv0;
 
 #define LENGTH(x)               (sizeof x / sizeof x[0])
-#define CLEANMASK(mask)                (mask & (MODKEY|GDK_SHIFT_MASK))
+#define CLEANMASK(mask)         (mask & (MODKEY|GDK_SHIFT_MASK))
 #define COOKIEJAR_TYPE          (cookiejar_get_type ())
 #define COOKIEJAR(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), COOKIEJAR_TYPE, CookieJar))
 
@@ -42,22 +44,16 @@ union Arg {
 };
 
 typedef struct Client {
-       GtkWidget *win, *scroll, *vbox, *indicator, *pane;
+       GtkWidget *win, *scroll, *vbox, *pane;
        WebKitWebView *view;
        WebKitWebInspector *inspector;
        char *title, *linkhover;
-       const char *uri, *needle;
+       const char *needle;
        gint progress;
        struct Client *next;
        gboolean zoomed, fullscreen, isinspecting, sslfailed;
 } Client;
 
-typedef struct {
-       char *label;
-       void (*func)(Client *c, const Arg *arg);
-       const Arg arg;
-} Item;
-
 typedef struct {
        guint mod;
        guint keyval;
@@ -76,6 +72,12 @@ typedef struct {
 
 G_DEFINE_TYPE(CookieJar, cookiejar, SOUP_TYPE_COOKIE_JAR_TEXT)
 
+typedef struct {
+       char *regex;
+       char *style;
+       regex_t re;
+} SiteStyle;
+
 static Display *dpy;
 static Atom atoms[AtomLast];
 static Client *clients = NULL;
@@ -83,8 +85,13 @@ static GdkNativeWindow embed = 0;
 static gboolean showxid = FALSE;
 static char winid[64];
 static gboolean usingproxy = 0;
-static char togglestat[5];
+static char togglestat[8];
+static char pagestat[3];
+static GTlsDatabase *tlsdb;
+static int policysel = 0;
+static char *stylefile = NULL;
 
+static void addaccelgroup(Client *c);
 static void beforerequest(WebKitWebView *w, WebKitWebFrame *f,
                WebKitWebResource *r, WebKitNetworkRequest *req,
                WebKitNetworkResponse *resp, gpointer d);
@@ -94,12 +101,16 @@ static gboolean buttonrelease(WebKitWebView *web, GdkEventButton *e,
 static void cleanup(void);
 static void clipboard(Client *c, const Arg *arg);
 
+/* Cookiejar implementation */
 static void cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie,
                SoupCookie *new_cookie);
 static void cookiejar_finalize(GObject *self);
-static SoupCookieJar *cookiejar_new(const char *filename, gboolean read_only);
+static SoupCookieJarAcceptPolicy cookiepolicy_get(void);
+static SoupCookieJar *cookiejar_new(const char *filename, gboolean read_only,
+               SoupCookieJarAcceptPolicy policy);
 static void cookiejar_set_property(GObject *self, guint prop_id,
                const GValue *value, GParamSpec *pspec);
+static char cookiepolicy_set(const SoupCookieJarAcceptPolicy p);
 
 static char *copystr(char **str, const char *src);
 static WebKitWebView *createwindow(WebKitWebView *v, WebKitWebFrame *f,
@@ -110,17 +121,22 @@ static gboolean decidedownload(WebKitWebView *v, WebKitWebFrame *f,
 static gboolean decidewindow(WebKitWebView *v, WebKitWebFrame *f,
                WebKitNetworkRequest *r, WebKitWebNavigationAction *n,
                WebKitWebPolicyDecision *p, Client *c);
+static gboolean deletion_interface(WebKitWebView *view,
+               WebKitDOMHTMLElement *arg1, Client *c);
 static void destroyclient(Client *c);
 static void destroywin(GtkWidget* w, Client *c);
 static void die(const char *errstr, ...);
-static void drawindicator(Client *c);
 static void eval(Client *c, const Arg *arg);
-static gboolean exposeindicator(GtkWidget *w, GdkEventExpose *e, Client *c);
 static void find(Client *c, const Arg *arg);
 static void fullscreen(Client *c, const Arg *arg);
+static void geopolicyrequested(WebKitWebView *v, WebKitWebFrame *f,
+               WebKitGeolocationPolicyDecision *d, Client *c);
 static const char *getatom(Client *c, int a);
 static void gettogglestat(Client *c);
+static void getpagestat(Client *c);
 static char *geturi(Client *c);
+static gchar *getstyle(const char *uri);
+
 static gboolean initdownload(WebKitWebView *v, WebKitDownload *o, Client *c);
 
 static void inspector(Client *c, const Arg *arg);
@@ -130,7 +146,9 @@ static gboolean inspector_show(WebKitWebInspector *i, Client *c);
 static gboolean inspector_close(WebKitWebInspector *i, Client *c);
 static void inspector_finished(WebKitWebInspector *i, Client *c);
 
-static gboolean keypress(GtkWidget *w, GdkEventKey *ev, Client *c);
+static gboolean keypress(GtkAccelGroup *group,
+               GObject *obj, guint key, GdkModifierType mods,
+               Client *c);
 static void linkhover(WebKitWebView *v, const char* t, const char* l,
                Client *c);
 static void loadstatuschange(WebKitWebView *view, GParamSpec *pspec,
@@ -140,8 +158,9 @@ static void navigate(Client *c, const Arg *arg);
 static Client *newclient(void);
 static void newwindow(Client *c, const Arg *arg, gboolean noembed);
 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
-static void populatepopup(WebKitWebView *web, GtkMenu *menu, Client *c);
-static void popupactivate(GtkMenuItem *menu, Client *);
+static gboolean contextmenu(WebKitWebView *view, GtkWidget *menu,
+               WebKitHitTestResult *target, gboolean keyboard, Client *c);
+static void menuactivate(GtkMenuItem *item, Client *c);
 static void print(Client *c, const Arg *arg);
 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
                gpointer d);
@@ -156,10 +175,13 @@ static void sigchld(int unused);
 static void source(Client *c, const Arg *arg);
 static void spawn(Client *c, const Arg *arg);
 static void stop(Client *c, const Arg *arg);
-static void titlechange(WebKitWebView *v, WebKitWebFrame *frame,
-               const char *title, Client *c);
+static void titlechange(WebKitWebView *view, GParamSpec *pspec, Client *c);
 static void toggle(Client *c, const Arg *arg);
-static void update(Client *c);
+static void togglecookiepolicy(Client *c, const Arg *arg);
+static void togglegeolocation(Client *c, const Arg *arg);
+static void togglescrollbars(Client *c, const Arg *arg);
+static void togglestyle(Client *c, const Arg *arg);
+static void updatetitle(Client *c);
 static void updatewinid(Client *c);
 static void usage(void);
 static void windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame,
@@ -169,11 +191,26 @@ static void zoom(Client *c, const Arg *arg);
 /* configuration, allows nested code to access above variables */
 #include "config.h"
 
+static void
+addaccelgroup(Client *c) {
+       int i;
+       GtkAccelGroup *group = gtk_accel_group_new();
+       GClosure *closure;
+
+       for(i = 0; i < LENGTH(keys); i++) {
+               closure = g_cclosure_new(G_CALLBACK(keypress), c, NULL);
+               gtk_accel_group_connect(group, keys[i].keyval, keys[i].mod,
+                               0, closure);
+       }
+       gtk_window_add_accel_group(GTK_WINDOW(c->win), group);
+}
+
 static void
 beforerequest(WebKitWebView *w, WebKitWebFrame *f, WebKitWebResource *r,
                WebKitNetworkRequest *req, WebKitNetworkResponse *resp,
                gpointer d) {
        const gchar *uri = webkit_network_request_get_uri(req);
+
        if(g_str_has_suffix(uri, "/favicon.ico"))
                webkit_network_request_set_uri(req, "about:blank");
 }
@@ -221,7 +258,8 @@ buttonrelease(WebKitWebView *web, GdkEventButton *e, GList *gl) {
 
        g_object_get(result, "context", &context, NULL);
        if(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
-               if(e->button == 2) {
+               if(e->button == 2 ||
+                               (e->button == 1 && CLEANMASK(e->state) == CLEANMASK(MODKEY))) {
                        g_object_get(result, "link-uri", &arg.v, NULL);
                        newwindow(NULL, &arg, e->state & GDK_CONTROL_MASK);
                        return true;
@@ -274,10 +312,12 @@ cookiejar_init(CookieJar *self) {
 }
 
 static SoupCookieJar *
-cookiejar_new(const char *filename, gboolean read_only) {
+cookiejar_new(const char *filename, gboolean read_only,
+               SoupCookieJarAcceptPolicy policy) {
        return g_object_new(COOKIEJAR_TYPE,
                            SOUP_COOKIE_JAR_TEXT_FILENAME, filename,
-                           SOUP_COOKIE_JAR_READ_ONLY, read_only, NULL);
+                           SOUP_COOKIE_JAR_READ_ONLY, read_only,
+                           SOUP_COOKIE_JAR_ACCEPT_POLICY, policy, NULL);
 }
 
 static void
@@ -289,6 +329,36 @@ cookiejar_set_property(GObject *self, guint prop_id, const GValue *value,
        flock(COOKIEJAR(self)->lock, LOCK_UN);
 }
 
+static SoupCookieJarAcceptPolicy
+cookiepolicy_get(void) {
+       switch(cookiepolicies[policysel]) {
+       case 'a':
+               return SOUP_COOKIE_JAR_ACCEPT_NEVER;
+       case '@':
+               return SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
+       case 'A':
+       default:
+               break;
+       }
+
+       return SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
+}
+
+static char
+cookiepolicy_set(const SoupCookieJarAcceptPolicy ep) {
+       switch(ep) {
+       case SOUP_COOKIE_JAR_ACCEPT_NEVER:
+               return 'a';
+       case SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY:
+               return '@';
+       case SOUP_COOKIE_JAR_ACCEPT_ALWAYS:
+       default:
+               break;
+       }
+
+       return 'A';
+}
+
 static void
 evalscript(JSContextRef js, char *script, char* scriptname) {
        JSStringRef jsscript, jsscriptname;
@@ -296,7 +366,8 @@ evalscript(JSContextRef js, char *script, char* scriptname) {
 
        jsscript = JSStringCreateWithUTF8CString(script);
        jsscriptname = JSStringCreateWithUTF8CString(scriptname);
-       JSEvaluateScript(js, jsscript, JSContextGetGlobalObject(js), jsscriptname, 0, &exception);
+       JSEvaluateScript(js, jsscript, JSContextGetGlobalObject(js),
+                       jsscriptname, 0, &exception);
        JSStringRelease(jsscript);
        JSStringRelease(jsscriptname);
 }
@@ -307,7 +378,8 @@ runscript(WebKitWebFrame *frame) {
        GError *error;
 
        if(g_file_get_contents(scriptfile, &script, NULL, &error)) {
-               evalscript(webkit_web_frame_get_global_context(frame), script, scriptfile);
+               evalscript(webkit_web_frame_get_global_context(frame),
+                               script, scriptfile);
        }
 }
 
@@ -315,10 +387,15 @@ static void
 clipboard(Client *c, const Arg *arg) {
        gboolean paste = *(gboolean *)arg;
 
-       if(paste)
-               gtk_clipboard_request_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), pasteuri, c);
-       else
-               gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), c->linkhover ? c->linkhover : geturi(c), -1);
+       if(paste) {
+               gtk_clipboard_request_text(
+                               gtk_clipboard_get(GDK_SELECTION_PRIMARY),
+                               pasteuri, c);
+       } else {
+               gtk_clipboard_set_text(
+                               gtk_clipboard_get(GDK_SELECTION_PRIMARY),
+                               c->linkhover ? c->linkhover : geturi(c), -1);
+       }
 }
 
 static char *
@@ -365,12 +442,17 @@ decidewindow(WebKitWebView *view, WebKitWebFrame *f, WebKitNetworkRequest *r,
        return FALSE;
 }
 
+static gboolean
+deletion_interface(WebKitWebView *view,
+               WebKitDOMHTMLElement *arg1, Client *c) {
+       return FALSE;
+}
+
 static void
 destroyclient(Client *c) {
        Client *p;
 
        webkit_web_view_stop_loading(c->view);
-       gtk_widget_destroy(c->indicator);
        gtk_widget_destroy(GTK_WIDGET(c->view));
        gtk_widget_destroy(c->scroll);
        gtk_widget_destroy(c->vbox);
@@ -402,51 +484,6 @@ die(const char *errstr, ...) {
        exit(EXIT_FAILURE);
 }
 
-static void
-drawindicator(Client *c) {
-       gint width;
-       const char *uri;
-       char *colorname;
-       GtkWidget *w;
-       GdkGC *gc;
-       GdkColor fg;
-
-       uri = geturi(c);
-       w = c->indicator;
-       width = c->progress * w->allocation.width / 100;
-       gc = gdk_gc_new(w->window);
-       if(strstr(uri, "https://") == uri) {
-               if(usingproxy) {
-                       colorname = c->sslfailed? progress_proxy_untrust :
-                               progress_proxy_trust;
-               } else {
-                       colorname = c->sslfailed? progress_untrust :
-                               progress_trust;
-               }
-       } else {
-               if(usingproxy) {
-                       colorname = progress_proxy;
-               } else {
-                       colorname = progress;
-               }
-       }
-
-       gdk_color_parse(colorname, &fg);
-       gdk_gc_set_rgb_fg_color(gc, &fg);
-       gdk_draw_rectangle(w->window,
-                       w->style->bg_gc[GTK_WIDGET_STATE(w)],
-                       TRUE, 0, 0, w->allocation.width, w->allocation.height);
-       gdk_draw_rectangle(w->window, gc, TRUE, 0, 0, width,
-                       w->allocation.height);
-       g_object_unref(gc);
-}
-
-static gboolean
-exposeindicator(GtkWidget *w, GdkEventExpose *e, Client *c) {
-       drawindicator(c);
-       return TRUE;
-}
-
 static void
 find(Client *c, const Arg *arg) {
        const char *s;
@@ -466,6 +503,16 @@ fullscreen(Client *c, const Arg *arg) {
        c->fullscreen = !c->fullscreen;
 }
 
+static void
+geopolicyrequested(WebKitWebView *v, WebKitWebFrame *f,
+               WebKitGeolocationPolicyDecision *d, Client *c) {
+       if(allowgeolocation) {
+               webkit_geolocation_policy_allow(d);
+       } else {
+               webkit_geolocation_policy_deny(d);
+       }
+}
+
 static const char *
 getatom(Client *c, int a) {
        static char buf[BUFSIZ];
@@ -477,11 +524,13 @@ getatom(Client *c, int a) {
        XGetWindowProperty(dpy, GDK_WINDOW_XID(GTK_WIDGET(c->win)->window),
                        atoms[a], 0L, BUFSIZ, False, XA_STRING,
                        &adummy, &idummy, &ldummy, &ldummy, &p);
-       if(p)
+       if(p) {
                strncpy(buf, (char *)p, LENGTH(buf)-1);
-       else
+       } else {
                buf[0] = '\0';
+       }
        XFree(p);
+
        return buf;
 }
 
@@ -494,6 +543,19 @@ geturi(Client *c) {
        return uri;
 }
 
+static gchar *
+getstyle(const char *uri) {
+       int i;
+
+       for(i = 0; i < LENGTH(styles); i++) {
+               if(styles[i].regex && !regexec(&(styles[i].re), uri, 0,
+                                       NULL, 0)) {
+                       return g_strconcat("file://", styles[i].style, NULL);
+               }
+       }
+       return g_strdup("");
+}
+
 static gboolean
 initdownload(WebKitWebView *view, WebKitDownload *o, Client *c) {
        Arg arg;
@@ -554,14 +616,17 @@ inspector_finished(WebKitWebInspector *i, Client *c) {
 }
 
 static gboolean
-keypress(GtkWidget* w, GdkEventKey *ev, Client *c) {
+keypress(GtkAccelGroup *group, GObject *obj,
+               guint key, GdkModifierType mods, Client *c) {
        guint i;
        gboolean processed = FALSE;
 
+       mods = CLEANMASK(mods);
+       key = gdk_keyval_to_lower(key);
        updatewinid(c);
        for(i = 0; i < LENGTH(keys); i++) {
-               if(gdk_keyval_to_lower(ev->keyval) == keys[i].keyval
-                               && CLEANMASK(ev->state) == keys[i].mod
+               if(key == keys[i].keyval
+                               && mods == keys[i].mod
                                && keys[i].func) {
                        keys[i].func(c, &(keys[i].arg));
                        processed = TRUE;
@@ -579,7 +644,7 @@ linkhover(WebKitWebView *v, const char* t, const char* l, Client *c) {
                free(c->linkhover);
                c->linkhover = NULL;
        }
-       update(c);
+       updatetitle(c);
 }
 
 static void
@@ -587,6 +652,7 @@ loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
        WebKitWebFrame *frame;
        WebKitWebDataSource *src;
        WebKitNetworkRequest *request;
+       WebKitWebSettings *set = webkit_web_view_get_settings(c->view);
        SoupMessage *msg;
        char *uri;
 
@@ -598,14 +664,19 @@ loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
                        src = webkit_web_frame_get_data_source(frame);
                        request = webkit_web_data_source_get_request(src);
                        msg = webkit_network_request_get_message(request);
-                       c->sslfailed = soup_message_get_flags(msg)
-                                      ^ SOUP_MESSAGE_CERTIFICATE_TRUSTED;
+                       c->sslfailed = !(soup_message_get_flags(msg)
+                                       & SOUP_MESSAGE_CERTIFICATE_TRUSTED);
                }
                setatom(c, AtomUri, uri);
+
+               if(stylefile == NULL && enablestyles) {
+                       g_object_set(G_OBJECT(set), "user-stylesheet-uri",
+                                       getstyle(uri), NULL);
+               }
                break;
        case WEBKIT_LOAD_FINISHED:
                c->progress = 100;
-               update(c);
+               updatetitle(c);
                break;
        default:
                break;
@@ -614,15 +685,16 @@ loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
 
 static void
 loaduri(Client *c, const Arg *arg) {
-       char *u, *rp;
+       char *u = NULL, *rp;
        const char *uri = (char *)arg->v;
        Arg a = { .b = FALSE };
+       struct stat st;
 
        if(strcmp(uri, "") == 0)
                return;
 
        /* In case it's a file path. */
-       if(uri[0] == '/') {
+       if(stat(uri, &st) == 0) {
                rp = realpath(uri, NULL);
                u = g_strdup_printf("file://%s", rp);
                free(rp);
@@ -631,16 +703,18 @@ loaduri(Client *c, const Arg *arg) {
                        : g_strdup_printf("http://%s", uri);
        }
 
+       setatom(c, AtomUri, uri);
+
        /* prevents endless loop */
-       if(c->uri && strcmp(u, c->uri) == 0) {
+       if(strcmp(u, geturi(c)) == 0) {
                reload(c, &a);
        } else {
                webkit_web_view_load_uri(c->view, u);
                c->progress = 0;
                c->title = copystr(&c->title, u);
-               g_free(u);
-               update(c);
+               updatetitle(c);
        }
+       g_free(u);
 }
 
 static void
@@ -655,11 +729,16 @@ newclient(void) {
        WebKitWebSettings *settings;
        WebKitWebFrame *frame;
        GdkGeometry hints = { 1, 1 };
-       char *uri, *ua;
+       GdkScreen *screen;
+       gdouble dpi;
+       char *uri = NULL, *ua;
 
        if(!(c = calloc(1, sizeof(Client))))
                die("Cannot malloc!\n");
 
+       c->title = NULL;
+       c->progress = 100;
+
        /* Window */
        if(embed) {
                c->win = gtk_plug_new(embed);
@@ -684,9 +763,9 @@ newclient(void) {
        g_signal_connect(G_OBJECT(c->win),
                        "destroy",
                        G_CALLBACK(destroywin), c);
-       g_signal_connect(G_OBJECT(c->win),
-                       "key-press-event",
-                       G_CALLBACK(keypress), c);
+
+       if(!kioskmode)
+               addaccelgroup(c);
 
        /* Pane */
        c->pane = gtk_vpaned_new();
@@ -695,19 +774,18 @@ newclient(void) {
        c->vbox = gtk_vbox_new(FALSE, 0);
        gtk_paned_pack1(GTK_PANED(c->pane), c->vbox, TRUE, TRUE);
 
-       /* Scrolled Window */
-       c->scroll = gtk_scrolled_window_new(NULL, NULL);
-       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
-                       GTK_POLICY_NEVER, GTK_POLICY_NEVER);
-
        /* Webview */
        c->view = WEBKIT_WEB_VIEW(webkit_web_view_new());
+
        g_signal_connect(G_OBJECT(c->view),
-                       "title-changed",
+                       "notify::title",
                        G_CALLBACK(titlechange), c);
        g_signal_connect(G_OBJECT(c->view),
                        "hovering-over-link",
                        G_CALLBACK(linkhover), c);
+       g_signal_connect(G_OBJECT(c->view),
+                       "geolocation-policy-decision-requested",
+                       G_CALLBACK(geopolicyrequested), c);
        g_signal_connect(G_OBJECT(c->view),
                        "create-web-view",
                        G_CALLBACK(createwindow), c);
@@ -733,27 +811,36 @@ newclient(void) {
                        "button-release-event",
                        G_CALLBACK(buttonrelease), c);
        g_signal_connect(G_OBJECT(c->view),
-                       "populate-popup",
-                       G_CALLBACK(populatepopup), c);
+                       "context-menu",
+                       G_CALLBACK(contextmenu), c);
        g_signal_connect(G_OBJECT(c->view),
                        "resource-request-starting",
                        G_CALLBACK(beforerequest), c);
+       g_signal_connect(G_OBJECT(c->view),
+                       "should-show-delete-interface-for-element",
+                       G_CALLBACK(deletion_interface), c);
+
+       /* Scrolled Window */
+       c->scroll = gtk_scrolled_window_new(NULL, NULL);
+
+       frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(c->view));
+       g_signal_connect(G_OBJECT(frame), "scrollbars-policy-changed",
+                       G_CALLBACK(gtk_true), NULL);
 
-       /* Indicator */
-       c->indicator = gtk_drawing_area_new();
-       gtk_widget_set_size_request(c->indicator, 0, indicator_thickness);
-       g_signal_connect (G_OBJECT (c->indicator), "expose_event",
-                       G_CALLBACK (exposeindicator), c);
+       if(!enablescrollbars) {
+               gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
+                               GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+       } else {
+               gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
+                               GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+       }
 
        /* Arranging */
        gtk_container_add(GTK_CONTAINER(c->scroll), GTK_WIDGET(c->view));
        gtk_container_add(GTK_CONTAINER(c->win), c->pane);
        gtk_container_add(GTK_CONTAINER(c->vbox), c->scroll);
-       gtk_container_add(GTK_CONTAINER(c->vbox), c->indicator);
 
        /* Setup */
-       gtk_box_set_child_packing(GTK_BOX(c->vbox), c->indicator, FALSE,
-                       FALSE, 0, GTK_PACK_START);
        gtk_box_set_child_packing(GTK_BOX(c->vbox), c->scroll, TRUE,
                        TRUE, 0, GTK_PACK_START);
        gtk_widget_grab_focus(GTK_WIDGET(c->view));
@@ -768,14 +855,16 @@ newclient(void) {
        gdk_window_add_filter(GTK_WIDGET(c->win)->window, processx, c);
        webkit_web_view_set_full_content_zoom(c->view, TRUE);
 
-       frame = webkit_web_view_get_main_frame(c->view);
        runscript(frame);
+
        settings = webkit_web_view_get_settings(c->view);
        if(!(ua = getenv("SURF_USERAGENT")))
                ua = useragent;
        g_object_set(G_OBJECT(settings), "user-agent", ua, NULL);
-       uri = g_strconcat("file://", stylefile, NULL);
-       g_object_set(G_OBJECT(settings), "user-stylesheet-uri", uri, NULL);
+       if (stylefile != NULL) {
+               uri = g_strconcat("file://", stylefile, NULL);
+               g_object_set(G_OBJECT(settings), "user-stylesheet-uri", uri, NULL);
+       }
        g_object_set(G_OBJECT(settings), "auto-load-images", loadimages,
                        NULL);
        g_object_set(G_OBJECT(settings), "enable-plugins", enableplugins,
@@ -786,6 +875,30 @@ newclient(void) {
                        enablespatialbrowsing, NULL);
        g_object_set(G_OBJECT(settings), "enable-developer-extras",
                        enableinspector, NULL);
+       g_object_set(G_OBJECT(settings), "enable-default-context-menu",
+                       kioskmode ^ 1, NULL);
+       g_object_set(G_OBJECT(settings), "default-font-size",
+                       defaultfontsize, NULL);
+       g_object_set(G_OBJECT(settings), "resizable-text-areas",
+                       1, NULL);
+
+       /*
+        * While stupid, CSS specifies that a pixel represents 1/96 of an inch.
+        * This ensures websites are not unusably small with a high DPI screen.
+        * It is equivalent to firefox's "layout.css.devPixelsPerPx" setting.
+        */
+       if(zoomto96dpi) {
+               screen = gdk_window_get_screen(GTK_WIDGET(c->win)->window);
+               dpi = gdk_screen_get_resolution(screen);
+               if(dpi != -1) {
+                       g_object_set(G_OBJECT(settings), "enforce-96-dpi", true,
+                                       NULL);
+                       webkit_web_view_set_zoom_level(c->view, dpi/96);
+               }
+       }
+       /* This might conflict with _zoomto96dpi_. */
+       if(zoomlevel != 1.0)
+               webkit_web_view_set_zoom_level(c->view, zoomlevel);
 
        if(enableinspector) {
                c->inspector = WEBKIT_WEB_INSPECTOR(
@@ -801,14 +914,19 @@ newclient(void) {
                c->isinspecting = false;
        }
 
-       g_free(uri);
+       if(runinfullscreen) {
+               c->fullscreen = 0;
+               fullscreen(c, NULL);
+       }
+
+       if(stylefile != NULL)
+               g_free(uri);
 
        setatom(c, AtomFind, "");
        setatom(c, AtomUri, "about:blank");
        if(hidebackground)
                webkit_web_view_set_transparent(c->view, TRUE);
 
-       c->title = NULL;
        c->next = clients;
        clients = c;
 
@@ -828,24 +946,32 @@ newclient(void) {
 static void
 newwindow(Client *c, const Arg *arg, gboolean noembed) {
        guint i = 0;
-       const char *cmd[10], *uri;
+       const char *cmd[16], *uri;
        const Arg a = { .v = (void *)cmd };
        char tmp[64];
 
        cmd[i++] = argv0;
+       cmd[i++] = "-a";
+       cmd[i++] = cookiepolicies;
+       if(!enablescrollbars)
+               cmd[i++] = "-b";
        if(embed && !noembed) {
                cmd[i++] = "-e";
                snprintf(tmp, LENGTH(tmp), "%u\n", (int)embed);
                cmd[i++] = tmp;
        }
-       if(!enablescripts)
-               cmd[i++] = "-s";
-       if(!enableplugins)
-               cmd[i++] = "-p";
        if(!loadimages)
                cmd[i++] = "-i";
+       if(kioskmode)
+               cmd[i++] = "-k";
+       if(!enableplugins)
+               cmd[i++] = "-p";
+       if(!enablescripts)
+               cmd[i++] = "-s";
        if(showxid)
                cmd[i++] = "-x";
+       cmd[i++] = "-c";
+       cmd[i++] = cookiefile;
        cmd[i++] = "--";
        uri = arg->v ? (char *)arg->v : c->linkhover;
        if(uri)
@@ -854,24 +980,27 @@ newwindow(Client *c, const Arg *arg, gboolean noembed) {
        spawn(NULL, &a);
 }
 
-static void
-populatepopup(WebKitWebView *web, GtkMenu *menu, Client *c) {
-       GList *items = gtk_container_get_children(GTK_CONTAINER(menu));
+static gboolean
+contextmenu(WebKitWebView *view, GtkWidget *menu, WebKitHitTestResult *target,
+               gboolean keyboard, Client *c) {
+       GList *items = gtk_container_get_children(GTK_CONTAINER(GTK_MENU(menu)));
 
        for(GList *l = items; l; l = l->next) {
-               g_signal_connect(l->data, "activate", G_CALLBACK(popupactivate), c);
+               g_signal_connect(l->data, "activate", G_CALLBACK(menuactivate), c);
        }
 
        g_list_free(items);
+       return FALSE;
 }
 
 static void
-popupactivate(GtkMenuItem *menu, Client *c) {
+menuactivate(GtkMenuItem *item, Client *c) {
        /*
         * context-menu-action-2000     open link
         * context-menu-action-1        open link in window
         * context-menu-action-2        download linked file
         * context-menu-action-3        copy link location
+        * context-menu-action-7        copy image address
         * context-menu-action-13       reload
         * context-menu-action-10       back
         * context-menu-action-11       forward
@@ -879,10 +1008,10 @@ popupactivate(GtkMenuItem *menu, Client *c) {
         */
 
        GtkAction *a = NULL;
-       const char *name;
-       GtkClipboard *prisel;
+       const char *name, *uri;
+       GtkClipboard *prisel, *clpbrd;
 
-       a = gtk_activatable_get_related_action(GTK_ACTIVATABLE(menu));
+       a = gtk_activatable_get_related_action(GTK_ACTIVATABLE(item));
        if(a == NULL)
                return;
 
@@ -890,6 +1019,12 @@ popupactivate(GtkMenuItem *menu, Client *c) {
        if(!g_strcmp0(name, "context-menu-action-3")) {
                prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
                gtk_clipboard_set_text(prisel, c->linkhover, -1);
+       } else if(!g_strcmp0(name, "context-menu-action-7")) {
+               prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+               clpbrd = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+               uri = gtk_clipboard_wait_for_text(clpbrd);
+               if(uri)
+                       gtk_clipboard_set_text(prisel, uri, -1);
        }
 }
 
@@ -917,11 +1052,12 @@ processx(GdkXEvent *e, GdkEvent *event, gpointer d) {
                        if(ev->atom == atoms[AtomFind]) {
                                arg.b = TRUE;
                                find(c, &arg);
+
                                return GDK_FILTER_REMOVE;
-                       }
-                       else if(ev->atom == atoms[AtomGo]) {
+                       } else if(ev->atom == atoms[AtomGo]) {
                                arg.v = getatom(c, AtomGo);
                                loaduri(c, &arg);
+
                                return GDK_FILTER_REMOVE;
                        }
                }
@@ -932,16 +1068,17 @@ processx(GdkXEvent *e, GdkEvent *event, gpointer d) {
 static void
 progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
        c->progress = webkit_web_view_get_progress(c->view) * 100;
-       update(c);
+       updatetitle(c);
 }
 
 static void
 reload(Client *c, const Arg *arg) {
        gboolean nocache = *(gboolean *)arg;
-       if(nocache)
+       if(nocache) {
                 webkit_web_view_reload_bypass_cache(c->view);
-       else
+       } else {
                 webkit_web_view_reload(c->view);
+       }
 }
 
 static void
@@ -961,7 +1098,7 @@ scroll(GtkAdjustment *a, const Arg *arg) {
        gdouble v;
 
        v = gtk_adjustment_get_value(a);
-       switch (arg->i){
+       switch(arg->i) {
        case +10000:
        case -10000:
                v += gtk_adjustment_get_page_increment(a) *
@@ -989,16 +1126,16 @@ setatom(Client *c, int a, const char *v) {
 
 static void
 setup(void) {
+       int i;
        char *proxy;
        char *new_proxy;
        SoupURI *puri;
        SoupSession *s;
+       GError *error = NULL;
 
        /* clean up any zombies immediately */
        sigchld(0);
        gtk_init(NULL, NULL);
-       if (!g_thread_supported())
-               g_thread_init(NULL);
 
        dpy = GDK_DISPLAY();
 
@@ -1010,18 +1147,40 @@ setup(void) {
        /* dirs and files */
        cookiefile = buildpath(cookiefile);
        scriptfile = buildpath(scriptfile);
-       stylefile = buildpath(stylefile);
+       styledir = buildpath(styledir);
+       if(stylefile == NULL && enablestyles) {
+               for(i = 0; i < LENGTH(styles); i++) {
+                       if(regcomp(&(styles[i].re), styles[i].regex,
+                                               REG_EXTENDED)) {
+                               fprintf(stderr,
+                                       "Could not compile regex: %s\n",
+                                       styles[i].regex);
+                               styles[i].regex = NULL;
+                       }
+                       styles[i].style = buildpath(
+                                       g_strconcat(styledir,
+                                               styles[i].style, NULL));
+               }
+       } else {
+               stylefile = buildpath(stylefile);
+       }
 
        /* request handler */
        s = webkit_get_default_session();
 
        /* cookie jar */
        soup_session_add_feature(s,
-                       SOUP_SESSION_FEATURE(cookiejar_new(cookiefile,
-                                       FALSE)));
+                       SOUP_SESSION_FEATURE(cookiejar_new(cookiefile, FALSE,
+                                       cookiepolicy_get())));
 
        /* ssl */
-       g_object_set(G_OBJECT(s), "ssl-ca-file", cafile, NULL);
+       tlsdb = g_tls_file_database_new(cafile, &error);
+
+       if(error) {
+               g_warning("Error loading SSL database %s: %s", cafile, error->message);
+               g_error_free(error);
+       }
+       g_object_set(G_OBJECT(s), "tls-database", tlsdb, NULL);
        g_object_set(G_OBJECT(s), "ssl-strict", strictssl, NULL);
 
        /* proxy */
@@ -1079,9 +1238,12 @@ stop(Client *c, const Arg *arg) {
 }
 
 static void
-titlechange(WebKitWebView *v, WebKitWebFrame *f, const char *t, Client *c) {
-       c->title = copystr(&c->title, t);
-       update(c);
+titlechange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
+       const gchar *t = webkit_web_view_get_title(view);
+       if (t) {
+               c->title = copystr(&c->title, t);
+               updatetitle(c);
+       }
 }
 
 static void
@@ -1095,51 +1257,166 @@ toggle(Client *c, const Arg *arg) {
        g_object_get(G_OBJECT(settings), name, &value, NULL);
        g_object_set(G_OBJECT(settings), name, !value, NULL);
 
-       reload(c,&a);
+       reload(c, &a);
+}
+
+static void
+togglecookiepolicy(Client *c, const Arg *arg) {
+       SoupCookieJar *jar;
+       SoupCookieJarAcceptPolicy policy;
+
+       jar = SOUP_COOKIE_JAR(
+                       soup_session_get_feature(
+                               webkit_get_default_session(),
+                               SOUP_TYPE_COOKIE_JAR));
+       g_object_get(G_OBJECT(jar), "accept-policy", &policy, NULL);
+
+       policysel++;
+       if(policysel >= strlen(cookiepolicies))
+               policysel = 0;
+
+       g_object_set(G_OBJECT(jar), "accept-policy",
+                       cookiepolicy_get(), NULL);
+
+       updatetitle(c);
+       /* Do not reload. */
+}
+
+static void
+togglegeolocation(Client *c, const Arg *arg) {
+       Arg a = { .b = FALSE };
+
+       allowgeolocation ^= 1;
+
+       reload(c, &a);
+}
+
+static void
+twitch(Client *c, const Arg *arg) {
+       GtkAdjustment *a;
+       gdouble v;
+
+       a = gtk_scrolled_window_get_vadjustment(
+                       GTK_SCROLLED_WINDOW(c->scroll));
+
+       v = gtk_adjustment_get_value(a);
+
+       v += arg->i;
+
+       v = MAX(v, 0.0);
+       v = MIN(v, gtk_adjustment_get_upper(a) -
+                       gtk_adjustment_get_page_size(a));
+       gtk_adjustment_set_value(a, v);
+}
+
+static void
+togglescrollbars(Client *c, const Arg *arg) {
+       GtkPolicyType vspolicy;
+       Arg a;
+
+       gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(c->scroll), NULL, &vspolicy);
+
+       if(vspolicy == GTK_POLICY_AUTOMATIC) {
+               gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
+                               GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+       } else {
+               gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c->scroll),
+                               GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+               a.i = +1;
+               twitch(c, &a);
+               a.i = -1;
+               twitch(c, &a);
+       }
+}
+
+static void
+togglestyle(Client *c, const Arg *arg) {
+       WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
+       char *uri;
+
+       g_object_get(G_OBJECT(settings), "user-stylesheet-uri", &uri, NULL);
+       if(stylefile == NULL && enablestyles) {
+               uri = (uri && uri[0])? g_strdup("") : getstyle(geturi(c));
+       } else {
+               uri = uri[0]? g_strdup("") : g_strconcat("file://",
+                               stylefile, NULL);
+       }
+       g_object_set(G_OBJECT(settings), "user-stylesheet-uri", uri, NULL);
+
+       updatetitle(c);
 }
 
 static void
 gettogglestat(Client *c){
        gboolean value;
+       char *uri;
+       int p = 0;
        WebKitWebSettings *settings = webkit_web_view_get_settings(c->view);
 
+       togglestat[p++] = cookiepolicy_set(cookiepolicy_get());
+
        g_object_get(G_OBJECT(settings), "enable-caret-browsing",
                        &value, NULL);
-       togglestat[0] = value? 'C': 'c';
+       togglestat[p++] = value? 'C': 'c';
+
+       togglestat[p++] = allowgeolocation? 'G': 'g';
 
        g_object_get(G_OBJECT(settings), "auto-load-images", &value, NULL);
-       togglestat[1] = value? 'I': 'i';
+       togglestat[p++] = value? 'I': 'i';
 
        g_object_get(G_OBJECT(settings), "enable-scripts", &value, NULL);
-       togglestat[2] = value? 'S': 's';
+       togglestat[p++] = value? 'S': 's';
 
        g_object_get(G_OBJECT(settings), "enable-plugins", &value, NULL);
-       togglestat[3] = value? 'V': 'v';
+       togglestat[p++] = value? 'V': 'v';
+
+       g_object_get(G_OBJECT(settings), "user-stylesheet-uri", &uri, NULL);
+       togglestat[p++] = (uri && uri[0]) ? 'M': 'm';
 
-       togglestat[4] = '\0';
+       togglestat[p] = '\0';
 }
 
+static void
+getpagestat(Client *c) {
+       const char *uri = geturi(c);
+
+       if(strstr(uri, "https://") == uri) {
+               pagestat[0] = c->sslfailed ? 'U' : 'T';
+       } else {
+               pagestat[0] = '-';
+       }
+
+       pagestat[1] = usingproxy ? 'P' : '-';
+       pagestat[2] = '\0';
+
+}
 
 static void
-update(Client *c) {
+updatetitle(Client *c) {
        char *t;
 
-       gettogglestat(c);
+       if(showindicators) {
+               gettogglestat(c);
+               getpagestat(c);
+
+               if(c->linkhover) {
+                       t = g_strdup_printf("%s:%s | %s", togglestat,
+                                       pagestat, c->linkhover);
+               } else if(c->progress != 100) {
+                       t = g_strdup_printf("[%i%%] %s:%s | %s", c->progress,
+                                       togglestat, pagestat,
+                                       (c->title == NULL)? "" : c->title);
+               } else {
+                       t = g_strdup_printf("%s:%s | %s", togglestat, pagestat,
+                                       (c->title == NULL)? "" : c->title);
+               }
 
-       if(c->linkhover) {
-               t = g_strdup_printf("%s| %s", togglestat, c->linkhover);
-       } else if(c->progress != 100) {
-               drawindicator(c);
-               gtk_widget_show(c->indicator);
-               t = g_strdup_printf("[%i%%] %s| %s", c->progress, togglestat,
-                               c->title);
+               gtk_window_set_title(GTK_WINDOW(c->win), t);
+               g_free(t);
        } else {
-               gtk_widget_hide_all(c->indicator);
-               t = g_strdup_printf("%s| %s", togglestat, c->title);
+               gtk_window_set_title(GTK_WINDOW(c->win),
+                               (c->title == NULL)? "" : c->title);
        }
-
-       gtk_window_set_title(GTK_WINDOW(c->win), t);
-       g_free(t);
 }
 
 static void
@@ -1150,8 +1427,11 @@ updatewinid(Client *c) {
 
 static void
 usage(void) {
-       die("usage: %s [-inpsvx] [-c cookiefile] [-e xid] [-r scriptfile]"
-               " [-t stylefile] [-u useragent] [uri]\n", basename(argv0));
+       die("usage: %s [-bBfFgGiIkKnNpPsSvx]"
+               " [-a cookiepolicies ] "
+               " [-c cookiefile] [-e xid] [-r scriptfile]"
+               " [-t stylefile] [-u useragent] [-z zoomlevel]"
+               " [uri]\n", basename(argv0));
 }
 
 static void
@@ -1179,32 +1459,78 @@ zoom(Client *c, const Arg *arg) {
 int
 main(int argc, char *argv[]) {
        Arg arg;
+       Client *c;
 
        memset(&arg, 0, sizeof(arg));
 
        /* command line args */
        ARGBEGIN {
+       case 'a':
+               cookiepolicies = EARGF(usage());
+               break;
+       case 'b':
+               enablescrollbars = 0;
+               break;
+       case 'B':
+               enablescrollbars = 1;
+               break;
        case 'c':
                cookiefile = EARGF(usage());
                break;
        case 'e':
                embed = strtol(EARGF(usage()), NULL, 0);
                break;
+       case 'f':
+               runinfullscreen = 1;
+               break;
+       case 'F':
+               runinfullscreen = 0;
+               break;
+       case 'g':
+               allowgeolocation = 0;
+               break;
+       case 'G':
+               allowgeolocation = 1;
+               break;
        case 'i':
                loadimages = 0;
                break;
+       case 'I':
+               loadimages = 1;
+               break;
+       case 'k':
+               kioskmode = 0;
+               break;
+       case 'K':
+               kioskmode = 1;
+               break;
+       case 'm':
+               enablestyles = 0;
+               break;
+       case 'M':
+               enablestyles = 1;
+               break;
        case 'n':
                enableinspector = 0;
                break;
+       case 'N':
+               enableinspector = 1;
+               break;
        case 'p':
                enableplugins = 0;
                break;
+       case 'P':
+               enableplugins = 1;
+               break;
        case 'r':
                scriptfile = EARGF(usage());
                break;
        case 's':
                enablescripts = 0;
                break;
+       case 'S':
+               enablescripts = 1;
+               break;
        case 't':
                stylefile = EARGF(usage());
                break;
@@ -1212,11 +1538,14 @@ main(int argc, char *argv[]) {
                useragent = EARGF(usage());
                break;
        case 'v':
-               die("surf-"VERSION", ©2009-2012 surf engineers, "
+               die("surf-"VERSION", ©2009-2014 surf engineers, "
                                "see LICENSE for details\n");
        case 'x':
                showxid = TRUE;
                break;
+       case 'z':
+               zoomlevel = strtof(EARGF(usage()), NULL);
+               break;
        default:
                usage();
        } ARGEND;
@@ -1224,9 +1553,12 @@ main(int argc, char *argv[]) {
                arg.v = argv[0];
 
        setup();
-       newclient();
-       if(arg.v)
+       c = newclient();
+       if(arg.v) {
                loaduri(clients, &arg);
+       } else {
+               updatetitle(c);
+       }
 
        gtk_main();
        cleanup();