Hiding the loading indicator when it is not used. Based on a patch from
[surf.git] / surf.c
diff --git a/surf.c b/surf.c
index 154957d..ae1a935 100644 (file)
--- a/surf.c
+++ b/surf.c
@@ -21,6 +21,8 @@
 #include <sys/file.h>
 
 #define LENGTH(x)               (sizeof x / sizeof x[0])
+#define COOKIEJAR_TYPE          (cookiejar_get_type ())
+#define COOKIEJAR(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), COOKIEJAR_TYPE, CookieJar))
 
 enum { AtomFind, AtomGo, AtomUri, AtomLast };
 
@@ -55,6 +57,17 @@ typedef struct {
        const Arg arg;
 } Key;
 
+typedef struct {
+       SoupCookieJarText parent_instance;
+       int lock;
+} CookieJar;
+
+typedef struct {
+       SoupCookieJarTextClass parent_class;
+} CookieJarClass;
+
+G_DEFINE_TYPE(CookieJar, cookiejar, SOUP_TYPE_COOKIE_JAR_TEXT)
+
 static Display *dpy;
 static Atom atoms[AtomLast];
 static Client *clients = NULL;
@@ -65,8 +78,13 @@ static char *progname;
 static gboolean loadimage = 1, plugin = 1, script = 1;
 
 static char *buildpath(const char *path);
+static gboolean buttonrelease(WebKitWebView *web, GdkEventButton *e, GList *gl);
 static void cleanup(void);
 static void clipboard(Client *c, const Arg *arg);
+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 void cookiejar_set_property(GObject *self, guint prop_id, const GValue *value, GParamSpec *pspec);
 static char *copystr(char **str, const char *src);
 static WebKitWebView *createwindow(WebKitWebView *v, WebKitWebFrame *f, Client *c);
 static gboolean decidedownload(WebKitWebView *v, WebKitWebFrame *f, WebKitNetworkRequest *r, gchar *m,  WebKitWebPolicyDecision *p, Client *c);
@@ -78,9 +96,7 @@ static void drawindicator(Client *c);
 static gboolean exposeindicator(GtkWidget *w, GdkEventExpose *e, Client *c);
 static void find(Client *c, const Arg *arg);
 static const char *getatom(Client *c, int a);
-static const char *getcookies(SoupURI *uri);
 static char *geturi(Client *c);
-void gotheaders(SoupMessage *msg, gpointer user_data);
 static gboolean initdownload(WebKitWebView *v, WebKitDownload *o, Client *c);
 static gboolean keypress(GtkWidget *w, GdkEventKey *ev, Client *c);
 static void linkhover(WebKitWebView *v, const char* t, const char* l, Client *c);
@@ -88,17 +104,18 @@ static void loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c);
 static void loaduri(Client *c, const Arg *arg);
 static void navigate(Client *c, const Arg *arg);
 static Client *newclient(void);
-static void newwindow(Client *c, const Arg *arg);
-static void newrequest(SoupSession *s, SoupMessage *msg, gpointer v);
+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 void print(Client *c, const Arg *arg);
 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event, gpointer d);
 static void progresschange(WebKitWebView *view, GParamSpec *pspec, Client *c);
 static void reload(Client *c, const Arg *arg);
-static void resize(GtkWidget *w, GtkAllocation *a, Client *c);
-static void scroll(Client *c, const Arg *arg);
+static void scroll_h(Client *c, const Arg *arg);
+static void scroll_v(Client *c, const Arg *arg);
+static void scroll(GtkAdjustment *a, const Arg *arg);
 static void setatom(Client *c, int a, const char *v);
-static void setcookie(SoupCookie *c);
 static void setup(void);
 static void sigchld(int unused);
 static void source(Client *c, const Arg *arg);
@@ -139,6 +156,23 @@ buildpath(const char *path) {
        return apath;
 }
 
+static gboolean
+buttonrelease(WebKitWebView *web, GdkEventButton *e, GList *gl) {
+       WebKitHitTestResultContext context;
+       WebKitHitTestResult *result = webkit_web_view_get_hit_test_result(web, e);
+       Arg arg;
+
+       g_object_get(result, "context", &context, NULL);
+       if(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
+               if(e->button == 2) {
+                       g_object_get(result, "link-uri", &arg.v, NULL);
+                       newwindow(NULL, &arg, e->state & GDK_CONTROL_MASK);
+                       return true;
+               }
+       }
+       return false;
+}
+
 void
 cleanup(void) {
        while(clients)
@@ -148,8 +182,51 @@ cleanup(void) {
        g_free(stylefile);
 }
 
+static void
+cookiejar_changed(SoupCookieJar *self, SoupCookie *old_cookie, SoupCookie *new_cookie) {
+       flock(COOKIEJAR(self)->lock, LOCK_EX);
+       if(new_cookie && !new_cookie->expires && sessiontime)
+               soup_cookie_set_expires(new_cookie, soup_date_new_from_now(sessiontime));
+       SOUP_COOKIE_JAR_CLASS(cookiejar_parent_class)->changed(self, old_cookie, new_cookie);
+       flock(COOKIEJAR(self)->lock, LOCK_UN);
+}
+
+static void
+cookiejar_class_init(CookieJarClass *klass) {
+       SOUP_COOKIE_JAR_CLASS(klass)->changed = cookiejar_changed;
+       G_OBJECT_CLASS(klass)->get_property = G_OBJECT_CLASS(cookiejar_parent_class)->get_property;
+       G_OBJECT_CLASS(klass)->set_property = cookiejar_set_property;
+       G_OBJECT_CLASS(klass)->finalize = cookiejar_finalize;
+       g_object_class_override_property(G_OBJECT_CLASS(klass), 1, "filename");
+}
+
+static void
+cookiejar_finalize(GObject *self) {
+       close(COOKIEJAR(self)->lock);
+       G_OBJECT_CLASS(cookiejar_parent_class)->finalize(self);
+}
+
+static void
+cookiejar_init(CookieJar *self) {
+       self->lock = open(cookiefile, 0);
+}
+
+static SoupCookieJar *
+cookiejar_new(const char *filename, gboolean read_only) {
+       return g_object_new(COOKIEJAR_TYPE,
+                           SOUP_COOKIE_JAR_TEXT_FILENAME, filename,
+                           SOUP_COOKIE_JAR_READ_ONLY, read_only, NULL);
+} 
+
+static void
+cookiejar_set_property(GObject *self, guint prop_id, const GValue *value, GParamSpec *pspec) {
+       flock(COOKIEJAR(self)->lock, LOCK_SH);
+       G_OBJECT_CLASS(cookiejar_parent_class)->set_property(self, prop_id, value, pspec);
+       flock(COOKIEJAR(self)->lock, LOCK_UN);
+}
+
 void
-evalscript(WebKitWebFrame *frame, JSContextRef js, char *script, char* scriptname) {
+evalscript(JSContextRef js, char *script, char* scriptname) {
        JSStringRef jsscript, jsscriptname;
        JSValueRef exception = NULL;
 
@@ -161,12 +238,12 @@ evalscript(WebKitWebFrame *frame, JSContextRef js, char *script, char* scriptnam
 }
 
 void
-runscript(WebKitWebFrame *frame, JSContextRef js) {
+runscript(WebKitWebFrame *frame) {
        char *script;
        GError *error;
 
        if(g_file_get_contents(scriptfile, &script, NULL, &error)) {
-               evalscript(frame, webkit_web_frame_get_global_context(frame), script, scriptfile);
+               evalscript(webkit_web_frame_get_global_context(frame), script, scriptfile);
        }
 }
 
@@ -214,7 +291,7 @@ decidewindow(WebKitWebView *view, WebKitWebFrame *f, WebKitNetworkRequest *r, We
        if(webkit_web_navigation_action_get_reason(n) == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
                webkit_web_policy_decision_ignore(p);
                arg.v = (void *)webkit_network_request_get_uri(r);
-               newwindow(NULL, &arg);
+               newwindow(NULL, &arg, 0);
                return TRUE;
        }
        return FALSE;
@@ -264,11 +341,12 @@ drawindicator(Client *c) {
        w = c->indicator;
        width = c->progress * w->allocation.width / 100;
        gc = gdk_gc_new(w->window);
-       if(strstr(uri, "https://") == uri)
+       if(strstr(uri, "https://") == uri) {
                gdk_color_parse(c->sslfailed ?
                                progress_untrust : progress_trust, &fg);
-       else
+       } else {
                gdk_color_parse(progress, &fg);
+       }
        gdk_gc_set_rgb_fg_color(gc, &fg);
        gdk_draw_rectangle(w->window,
                        w->style->bg_gc[GTK_WIDGET_STATE(w)],
@@ -293,15 +371,6 @@ find(Client *c, const Arg *arg) {
        webkit_web_view_search_text(c->view, s, FALSE, forward, TRUE);
 }
 
-const char *
-getcookies(SoupURI *uri) {
-       const char *c;
-       SoupCookieJar *j = soup_cookie_jar_text_new(cookiefile, TRUE);
-       c = soup_cookie_jar_get_cookies(j, uri, TRUE);
-       g_object_unref(j);
-       return c;
-}
-
 const char *
 getatom(Client *c, int a) {
        static char buf[BUFSIZ];
@@ -330,19 +399,6 @@ geturi(Client *c) {
        return uri;
 }
 
-void
-gotheaders(SoupMessage *msg, gpointer v) {
-       SoupURI *uri;
-       GSList *l, *p;
-
-       uri = soup_message_get_uri(msg);
-       for(p = l = soup_cookies_from_response(msg); p;
-               p = g_slist_next(p))  {
-               setcookie((SoupCookie *)p->data);
-       }
-       soup_cookies_free(l);
-}
-
 gboolean
 initdownload(WebKitWebView *view, WebKitDownload *o, Client *c) {
        Arg arg;
@@ -404,7 +460,7 @@ loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) {
                setatom(c, AtomUri, uri);
                break;
        case WEBKIT_LOAD_FINISHED:
-               c->progress = 0;
+               c->progress = 100;
                update(c);
                break;
        default:
@@ -474,7 +530,6 @@ newclient(void) {
        gtk_window_set_default_size(GTK_WINDOW(c->win), 800, 600);
        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);
-       g_signal_connect(G_OBJECT(c->win), "size-allocate", G_CALLBACK(resize), c);
 
        /* VBox */
        c->vbox = gtk_vbox_new(FALSE, 0);
@@ -495,10 +550,12 @@ newclient(void) {
        g_signal_connect(G_OBJECT(c->view), "notify::load-status", G_CALLBACK(loadstatuschange), c);
        g_signal_connect(G_OBJECT(c->view), "notify::progress", G_CALLBACK(progresschange), c);
        g_signal_connect(G_OBJECT(c->view), "download-requested", G_CALLBACK(initdownload), c);
+       g_signal_connect(G_OBJECT(c->view), "button-release-event", G_CALLBACK(buttonrelease), c);
+       g_signal_connect(G_OBJECT(c->view), "populate-popup", G_CALLBACK(populatepopup), c);
 
        /* Indicator */
        c->indicator = gtk_drawing_area_new();
-       gtk_widget_set_size_request(c->indicator, 0, 2);
+       gtk_widget_set_size_request(c->indicator, 0, indicator_thickness);
        g_signal_connect (G_OBJECT (c->indicator), "expose_event",
                        G_CALLBACK (exposeindicator), c);
 
@@ -513,7 +570,6 @@ newclient(void) {
        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));
        gtk_widget_show(c->vbox);
-       gtk_widget_show(c->indicator);
        gtk_widget_show(c->scroll);
        gtk_widget_show(GTK_WIDGET(c->view));
        gtk_widget_show(c->win);
@@ -522,7 +578,7 @@ 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, webkit_web_frame_get_global_context(frame));
+       runscript(frame);
        settings = webkit_web_view_get_settings(c->view);
        if(!(ua = getenv("SURF_USERAGENT")))
                ua = useragent;
@@ -548,32 +604,22 @@ newclient(void) {
                gdk_display_sync(gtk_widget_get_display(c->win));
                printf("%u\n", (guint)GDK_WINDOW_XID(GTK_WIDGET(c->win)->window));
                fflush(NULL);
+                if (fclose(stdout) != 0) {
+                       die("Error closing stdout");
+                }
        }
        return c;
 }
 
 void
-newrequest(SoupSession *s, SoupMessage *msg, gpointer v) {
-       SoupMessageHeaders *h = msg->request_headers;
-       SoupURI *uri;
-       const char *c;
-
-       soup_message_headers_remove(h, "Cookie");
-       uri = soup_message_get_uri(msg);
-       if((c = getcookies(uri)))
-               soup_message_headers_append(h, "Cookie", c);
-       g_signal_connect_after(G_OBJECT(msg), "got-headers", G_CALLBACK(gotheaders), NULL);
-}
-
-void
-newwindow(Client *c, const Arg *arg) {
+newwindow(Client *c, const Arg *arg, gboolean noembed) {
        guint i = 0;
        const char *cmd[10], *uri;
        const Arg a = { .v = (void *)cmd };
        char tmp[64];
 
        cmd[i++] = progname;
-       if(embed) {
+       if(embed && !noembed) {
                cmd[i++] = "-e";
                snprintf(tmp, LENGTH(tmp), "%u\n", (int)embed);
                cmd[i++] = tmp;
@@ -594,6 +640,45 @@ newwindow(Client *c, const Arg *arg) {
        spawn(NULL, &a);
 }
 
+static void
+populatepopup(WebKitWebView *web, GtkMenu *menu, Client *c) {
+       GList *items = gtk_container_get_children(GTK_CONTAINER(menu));
+
+       for(GList *l = items; l; l = l->next) {
+               g_signal_connect(l->data, "activate", G_CALLBACK(popupactivate), c);
+       }
+
+       g_list_free(items);
+}
+
+static void
+popupactivate(GtkMenuItem *menu, 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-13       reload
+        * context-menu-action-10       back
+        * context-menu-action-11       forward
+        * context-menu-action-12       stop
+        */
+
+       GtkAction *a = NULL;
+       const char *name;
+       GtkClipboard *prisel;
+
+       a = gtk_activatable_get_related_action(GTK_ACTIVATABLE(menu));
+       if(a == NULL)
+               return;
+
+       name = gtk_action_get_name(a);
+       if(!g_strcmp0(name, "context-menu-action-3")) {
+               prisel = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+               gtk_clipboard_set_text(prisel, c->linkhover, -1);
+       }
+}
+
 void
 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d) {
        Arg arg = {.v = text };
@@ -646,50 +731,40 @@ reload(Client *c, const Arg *arg) {
 }
 
 void
-resize(GtkWidget *w, GtkAllocation *a, Client *c) {
-       double zoom;
+scroll_h(Client *c, const Arg *arg) {
+       scroll(gtk_scrolled_window_get_hadjustment(
+                               GTK_SCROLLED_WINDOW(c->scroll)), arg);
+}
 
-       if(c->zoomed)
-               return;
-       zoom = webkit_web_view_get_zoom_level(c->view);
-       if(a->width * a->height < 300 * 400 && zoom != 0.2)
-               webkit_web_view_set_zoom_level(c->view, 0.2);
-       else if(zoom != 1.0)
-               webkit_web_view_set_zoom_level(c->view, 1.0);
+void
+scroll_v(Client *c, const Arg *arg) {
+       scroll(gtk_scrolled_window_get_vadjustment(
+                               GTK_SCROLLED_WINDOW(c->scroll)), arg);
 }
 
 void
-scroll(Client *c, const Arg *arg) {
+scroll(GtkAdjustment *a, const Arg *arg) {
        gdouble v;
-       GtkAdjustment *a;
 
-       a = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(c->scroll));
        v = gtk_adjustment_get_value(a);
-       v += gtk_adjustment_get_step_increment(a) * arg->i;
+       switch (arg->i){
+       case +10000:
+       case -10000:
+               v += gtk_adjustment_get_page_increment(a) *
+                       (arg->i / 10000);
+               break;
+       case +20000:
+       case -20000:
+       default:
+               v += gtk_adjustment_get_step_increment(a) * arg->i;
+       }
+
        v = MAX(v, 0.0);
-       v = MIN(v, gtk_adjustment_get_upper(a) - gtk_adjustment_get_page_size(a));
+       v = MIN(v, gtk_adjustment_get_upper(a) -
+                       gtk_adjustment_get_page_size(a));
        gtk_adjustment_set_value(a, v);
 }
 
-void
-setcookie(SoupCookie *c) {
-       int lock;
-
-       lock = open(cookiefile, 0);
-       flock(lock, LOCK_EX);
-       SoupDate *e;
-       SoupCookieJar *j = soup_cookie_jar_text_new(cookiefile, FALSE);
-       c = soup_cookie_copy(c);
-       if(c->expires == NULL && sessiontime) {
-               e = soup_date_new_from_time_t(time(NULL) + sessiontime);
-               soup_cookie_set_expires(c, e);
-       }
-       soup_cookie_jar_add_cookie(j, c);
-       g_object_unref(j);
-       flock(lock, LOCK_UN);
-       close(lock);
-}
-
 void
 setatom(Client *c, int a, const char *v) {
        XSync(dpy, False);
@@ -712,7 +787,6 @@ setup(void) {
                g_thread_init(NULL);
 
        dpy = GDK_DISPLAY();
-       s = webkit_get_default_session();
 
        /* atoms */
        atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
@@ -726,9 +800,9 @@ setup(void) {
 
        /* request handler */
        s = webkit_get_default_session();
-       soup_session_remove_feature_by_type(s, soup_cookie_get_type());
-       soup_session_remove_feature_by_type(s, soup_cookie_jar_get_type());
-       g_signal_connect_after(G_OBJECT(s), "request-started", G_CALLBACK(newrequest), NULL);
+
+       /* cookie jar */
+       soup_session_add_feature(s, SOUP_SESSION_FEATURE(cookiejar_new(cookiefile, FALSE)));
 
        /* ssl */
        g_object_set(G_OBJECT(s), "ssl-ca-file", cafile, NULL);
@@ -778,7 +852,8 @@ spawn(Client *c, const Arg *arg) {
 void
 eval(Client *c, const Arg *arg) {
        WebKitWebFrame *frame = webkit_web_view_get_main_frame(c->view);
-       evalscript(frame, webkit_web_frame_get_global_context(frame), ((char **)arg->v)[0], "");
+       evalscript(webkit_web_frame_get_global_context(frame),
+                       ((char **)arg->v)[0], "");
 }
 
 void
@@ -796,13 +871,16 @@ void
 update(Client *c) {
        char *t;
 
-       if(c->linkhover)
+       if(c->linkhover) {
                t = g_strdup(c->linkhover);
-        else if(c->progress != 100)
+       } else if(c->progress != 100) {
+               drawindicator(c);
+               gtk_widget_show(c->indicator);
                t = g_strdup_printf("[%i%%] %s", c->progress, c->title);
-       else
+       } else {
+               gtk_widget_hide_all(c->indicator);
                t = g_strdup(c->title);
-       drawindicator(c);
+       }
        gtk_window_set_title(GTK_WINDOW(c->win), t);
        g_free(t);
 }
@@ -821,7 +899,7 @@ usage(void) {
 
 void
 windowobjectcleared(GtkWidget *w, WebKitWebFrame *frame, JSContextRef js, JSObjectRef win, Client *c) {
-       runscript(frame, js);
+       runscript(frame);
 }
 
 void
@@ -853,7 +931,7 @@ main(int argc, char *argv[]) {
                switch(argv[i][1]) {
                case 'e':
                        if(++i < argc)
-                               embed = atoi(argv[i]);
+                               embed = strtol(argv[i], NULL, 0);
                        else
                                usage();
                        break;