X-Git-Url: https://git.danieliu.xyz/?a=blobdiff_plain;f=surf.c;h=6f71657570e9cefeb02c0ef4fbc7e94f40906ccb;hb=c2da2bae5dbca73f26fe77bb8080e2a90d3cbb1d;hp=939a06fff155bf1eec8f2c94b853345c53abdbeb;hpb=2e978627ddd4d18470b0d49008d9c285ed774c3d;p=surf.git diff --git a/surf.c b/surf.c index 939a06f..6f71657 100644 --- a/surf.c +++ b/surf.c @@ -23,13 +23,14 @@ #include #include #include +#include #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)) @@ -47,7 +48,7 @@ typedef struct Client { WebKitWebView *view; WebKitWebInspector *inspector; char *title, *linkhover; - const char *uri, *needle; + const char *needle; gint progress; struct Client *next; gboolean zoomed, fullscreen, isinspecting, sslfailed; @@ -71,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; @@ -78,8 +85,11 @@ static GdkNativeWindow embed = 0; static gboolean showxid = FALSE; static char winid[64]; static gboolean usingproxy = 0; -static char togglestat[7]; +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, @@ -91,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, @@ -121,6 +135,8 @@ 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); @@ -142,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); @@ -158,9 +175,9 @@ 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 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); @@ -241,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; @@ -294,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 @@ -309,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; @@ -493,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; @@ -589,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; @@ -600,10 +664,15 @@ 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; @@ -616,7 +685,7 @@ 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; @@ -634,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); updatetitle(c); } + g_free(u); } static void @@ -658,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); @@ -702,7 +778,7 @@ newclient(void) { 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", @@ -735,8 +811,8 @@ 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); @@ -785,8 +861,10 @@ newclient(void) { 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, @@ -801,6 +879,26 @@ newclient(void) { 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( @@ -816,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; @@ -843,11 +946,13 @@ newclient(void) { static void newwindow(Client *c, const Arg *arg, gboolean noembed) { guint i = 0; - const char *cmd[14], *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) { @@ -875,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 @@ -900,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; @@ -911,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); } } @@ -984,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) * @@ -1012,10 +1126,12 @@ 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); @@ -1031,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 */ @@ -1100,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); - updatetitle(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 @@ -1119,6 +1260,37 @@ toggle(Client *c, const Arg *arg) { 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; @@ -1137,15 +1309,6 @@ twitch(Client *c, const Arg *arg) { gtk_adjustment_set_value(a, v); } -static void -togglegeolocation(Client *c, const Arg *arg) { - Arg a = { .b = FALSE }; - - allowgeolocation ^= 1; - - reload(c, &a); -} - static void togglescrollbars(Client *c, const Arg *arg) { GtkPolicyType vspolicy; @@ -1168,12 +1331,16 @@ togglescrollbars(Client *c, const Arg *arg) { static void togglestyle(Client *c, const Arg *arg) { - WebKitWebSettings *settings; + WebKitWebSettings *settings = webkit_web_view_get_settings(c->view); char *uri; - settings = webkit_web_view_get_settings(c->view); g_object_get(G_OBJECT(settings), "user-stylesheet-uri", &uri, NULL); - uri = uri[0] ? g_strdup("") : g_strconcat("file://", stylefile, 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); @@ -1183,27 +1350,30 @@ 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[1] = allowgeolocation? 'G': 'g'; + togglestat[p++] = allowgeolocation? 'G': 'g'; g_object_get(G_OBJECT(settings), "auto-load-images", &value, NULL); - togglestat[2] = value? 'I': 'i'; + togglestat[p++] = value? 'I': 'i'; g_object_get(G_OBJECT(settings), "enable-scripts", &value, NULL); - togglestat[3] = value? 'S': 's'; + togglestat[p++] = value? 'S': 's'; g_object_get(G_OBJECT(settings), "enable-plugins", &value, NULL); - togglestat[4] = value? 'V': 'v'; + togglestat[p++] = value? 'V': 'v'; g_object_get(G_OBJECT(settings), "user-stylesheet-uri", &uri, NULL); - togglestat[5] = uri[0] ? 'M': 'm'; + togglestat[p++] = (uri && uri[0]) ? 'M': 'm'; - togglestat[6] = '\0'; + togglestat[p] = '\0'; } static void @@ -1234,16 +1404,18 @@ updatetitle(Client *c) { pagestat, c->linkhover); } else if(c->progress != 100) { t = g_strdup_printf("[%i%%] %s:%s | %s", c->progress, - togglestat, pagestat, c->title); + togglestat, pagestat, + (c->title == NULL)? "" : c->title); } else { t = g_strdup_printf("%s:%s | %s", togglestat, pagestat, - c->title); + (c->title == NULL)? "" : c->title); } gtk_window_set_title(GTK_WINDOW(c->win), t); g_free(t); } else { - gtk_window_set_title(GTK_WINDOW(c->win), c->title); + gtk_window_set_title(GTK_WINDOW(c->win), + (c->title == NULL)? "" : c->title); } } @@ -1255,8 +1427,11 @@ updatewinid(Client *c) { static void usage(void) { - die("usage: %s [-biknpsvx] [-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 @@ -1284,11 +1459,15 @@ 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; @@ -1301,6 +1480,12 @@ main(int argc, char *argv[]) { case 'e': embed = strtol(EARGF(usage()), NULL, 0); break; + case 'f': + runinfullscreen = 1; + break; + case 'F': + runinfullscreen = 0; + break; case 'g': allowgeolocation = 0; break; @@ -1319,6 +1504,12 @@ main(int argc, char *argv[]) { case 'K': kioskmode = 1; break; + case 'm': + enablestyles = 0; + break; + case 'M': + enablestyles = 1; + break; case 'n': enableinspector = 0; break; @@ -1347,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; @@ -1359,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();