#include <pwd.h>
#include <regex.h>
#include <signal.h>
-#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <webkit2/webkit2.h>
#include <X11/X.h>
#include <X11/Xatom.h>
+#include <glib.h>
#include "arg.h"
+#include "common.h"
#define LENGTH(x) (sizeof(x) / sizeof(x[0]))
#define CLEANMASK(mask) (mask & (MODKEY|GDK_SHIFT_MASK))
GTlsCertificate *cert, *failedcert;
GTlsCertificateFlags tlserr;
Window xid;
+ unsigned long pageid;
int progress, fullscreen, https, insecure, errorpage;
const char *title, *overtitle, *targeturi;
const char *needle;
unsigned int stopevent;
} Button;
+
+typedef struct {
+ char *token;
+ char *uri;
+} SearchEngine;
+
typedef struct {
const char *uri;
Parameter config[ParameterLast];
/* Surf */
static void usage(void);
-static void die(const char *errstr, ...);
static void setup(void);
static void sigchld(int unused);
static void sighup(int unused);
static void handleplumb(Client *c, const char *uri);
static void newwindow(Client *c, const Arg *a, int noembed);
static void spawn(Client *c, const Arg *a);
+static void msgext(Client *c, char type, const Arg *a);
static void destroyclient(Client *c);
static void cleanup(void);
+static void updatehistory(const char *u, const char *t);
/* GTK/WebKit */
static WebKitWebView *newview(Client *c, WebKitWebView *rv);
static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
gpointer d);
static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c);
+static gboolean readpipe(GIOChannel *s, GIOCondition ioc, gpointer unused);
static void showview(WebKitWebView *v, Client *c);
static GtkWidget *createwindow(Client *c);
static gboolean loadfailedtls(WebKitWebView *v, gchar *uri,
Client *c);
static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c);
static void download(Client *c, WebKitURIResponse *r);
+static void webprocessterminated(WebKitWebView *v,
+ WebKitWebProcessTerminationReason r,
+ Client *c);
static void closeview(WebKitWebView *v, Client *c);
static void destroywin(GtkWidget* w, Client *c);
+static gchar *parseuri(const gchar *uri);
/* Hotkeys */
static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
static void showcert(Client *c, const Arg *a);
static void clipboard(Client *c, const Arg *a);
static void zoom(Client *c, const Arg *a);
-static void scroll(Client *c, const Arg *a);
+static void scrollv(Client *c, const Arg *a);
+static void scrollh(Client *c, const Arg *a);
static void navigate(Client *c, const Arg *a);
static void stop(Client *c, const Arg *a);
static void toggle(Client *c, const Arg *a);
static void togglecookiepolicy(Client *c, const Arg *a);
static void toggleinspector(Client *c, const Arg *a);
static void find(Client *c, const Arg *a);
+static void playexternal(Client *c, const Arg *a);
/* Buttons */
static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h);
static const char *useragent;
static Parameter *curconfig;
static int modparams[ParameterLast];
+static int pipein[2], pipeout[2];
char *argv0;
static ParamName loadtransient[] = {
"[-r scriptfile] [-u useragent] [-z zoomlevel] [uri]\n");
}
-void
-die(const char *errstr, ...)
-{
- va_list ap;
-
- va_start(ap, errstr);
- vfprintf(stderr, errstr, ap);
- va_end(ap);
- exit(1);
-}
-
void
setup(void)
{
+ GIOChannel *gchanin;
GdkDisplay *gdpy;
int i, j;
curconfig = defconfig;
/* dirs and files */
- cookiefile = buildfile(cookiefile);
- scriptfile = buildfile(scriptfile);
- cachedir = buildpath(cachedir);
- certdir = buildpath(certdir);
+ cookiefile = buildfile(cookiefile);
+ historyfile = buildfile(historyfile);
+ scriptfile = buildfile(scriptfile);
+ cachedir = buildpath(cachedir);
+ certdir = buildpath(certdir);
gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy));
+ if (pipe(pipeout) < 0 || pipe(pipein) < 0) {
+ fputs("Unable to create pipes\n", stderr);
+ } else {
+ gchanin = g_io_channel_unix_new(pipein[0]);
+ g_io_channel_set_encoding(gchanin, NULL, NULL);
+ g_io_channel_set_close_on_unref(gchanin, TRUE);
+ g_io_add_watch(gchanin, G_IO_IN, readpipe, NULL);
+ }
+
+
for (i = 0; i < LENGTH(certs); ++i) {
if (!regcomp(&(certs[i].re), certs[i].regex, REG_EXTENDED)) {
certs[i].file = g_strconcat(certdir, "/", certs[i].file,
url = g_strdup_printf("file://%s", path);
free(path);
} else {
- url = g_strdup_printf("http://%s", uri);
+ url = parseuri(uri);
}
if (apath != uri)
free(apath);
if (fork() == 0) {
if (dpy)
close(ConnectionNumber(dpy));
+ close(pipein[0]);
+ close(pipeout[1]);
setsid();
execvp(((char **)a->v)[0], (char **)a->v);
fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[0]);
{
while (clients)
destroyclient(clients);
+
+ close(pipein[0]);
+ close(pipeout[1]);
g_free(cookiefile);
+ g_free(historyfile);
g_free(scriptfile);
g_free(stylefile);
g_free(cachedir);
XCloseDisplay(dpy);
}
+void
+updatehistory(const char *u, const char *t)
+{
+ FILE *f;
+ f = fopen(historyfile, "a+");
+
+ char b[20];
+ time_t now = time (0);
+ strftime (b, 20, "%Y-%m-%d %H:%M:%S", localtime (&now));
+ fputs(b, f);
+
+ fprintf(f, " %s %s\n", u, t);
+ fclose(f);
+}
+
WebKitWebView *
newview(Client *c, WebKitWebView *rv)
{
WebKitWebView *v;
WebKitSettings *settings;
- WebKitUserContentManager *contentmanager;
WebKitWebContext *context;
+ WebKitCookieManager *cookiemanager;
+ WebKitUserContentManager *contentmanager;
/* Webview */
if (rv) {
- v = WEBKIT_WEB_VIEW(
- webkit_web_view_new_with_related_view(rv));
+ v = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(rv));
} else {
settings = webkit_settings_new_with_settings(
"allow-file-access-from-file-urls", curconfig[FileURLsCrossAccess].val.i,
"base-data-directory", cachedir,
NULL));
+ cookiemanager = webkit_web_context_get_cookie_manager(context);
+
/* rendering process model, can be a shared unique one
* or one for each view */
webkit_web_context_set_process_model(context,
WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
/* Currently only works with text file to be compatible with curl */
- webkit_cookie_manager_set_persistent_storage(
- webkit_web_context_get_cookie_manager(context), cookiefile,
- WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
+ webkit_cookie_manager_set_persistent_storage(cookiemanager,
+ cookiefile, WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
/* cookie policy */
- webkit_cookie_manager_set_accept_policy(
- webkit_web_context_get_cookie_manager(context),
+ webkit_cookie_manager_set_accept_policy(cookiemanager,
cookiepolicy_get());
/* languages */
webkit_web_context_set_preferred_languages(context,
G_CALLBACK(permissionrequested), c);
g_signal_connect(G_OBJECT(v), "ready-to-show",
G_CALLBACK(showview), c);
+ g_signal_connect(G_OBJECT(v), "web-process-terminated",
+ G_CALLBACK(webprocessterminated), c);
return v;
}
+static gboolean
+readpipe(GIOChannel *s, GIOCondition ioc, gpointer unused)
+{
+ static char msg[MSGBUFSZ], msgsz;
+ GError *gerr = NULL;
+
+ if (g_io_channel_read_chars(s, msg, sizeof(msg), NULL, &gerr) !=
+ G_IO_STATUS_NORMAL) {
+ fprintf(stderr, "surf: error reading pipe: %s\n",
+ gerr->message);
+ g_error_free(gerr);
+ return TRUE;
+ }
+ if ((msgsz = msg[0]) < 3) {
+ fprintf(stderr, "surf: message too short: %d\n", msgsz);
+ return TRUE;
+ }
+
+ switch (msg[2]) {
+ case 'i':
+ close(pipein[1]);
+ close(pipeout[0]);
+ break;
+ }
+
+ return TRUE;
+}
+
void
initwebextensions(WebKitWebContext *wc, Client *c)
{
+ GVariant *gv;
+
+ if (!pipeout[0] || !pipein[1])
+ return;
+
+ gv = g_variant_new("(ii)", pipeout[0], pipein[1]);
+
+ webkit_web_context_set_web_extensions_initialization_user_data(wc, gv);
webkit_web_context_set_web_extensions_directory(wc, WEBEXTDIR);
}
c->finder = webkit_web_view_get_find_controller(c->view);
c->inspector = webkit_web_view_get_inspector(c->view);
+ c->pageid = webkit_web_view_get_page_id(c->view);
c->win = createwindow(c);
gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
gtk_window_set_wmclass(GTK_WINDOW(w), wmstr, "Surf");
g_free(wmstr);
- wmstr = g_strdup_printf("%s[%lu]", "Surf",
- webkit_web_view_get_page_id(c->view));
+ wmstr = g_strdup_printf("%s[%lu]", "Surf", c->pageid);
gtk_window_set_role(GTK_WINDOW(w), wmstr);
g_free(wmstr);
return TRUE;
}
+
void
loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
{
break;
case WEBKIT_LOAD_FINISHED:
seturiparameters(c, uri, loadfinished);
+ updatehistory(uri, c->title);
/* Disabled until we write some WebKitWebExtension for
* manipulating the DOM directly.
evalscript(c, "document.documentElement.style.overflow = '%s'",
spawn(c, &a);
}
+void
+webprocessterminated(WebKitWebView *v, WebKitWebProcessTerminationReason r,
+ Client *c)
+{
+ fprintf(stderr, "web process terminated: %s\n",
+ r == WEBKIT_WEB_PROCESS_CRASHED ? "crashed" : "no memory");
+ closeview(v, c);
+}
+
void
closeview(WebKitWebView *v, Client *c)
{
gtk_main_quit();
}
+gchar *
+parseuri(const gchar *uri) {
+ guint i;
+
+ for (i = 0; i < LENGTH(searchengines); i++) {
+ if (searchengines[i].token == NULL || searchengines[i].uri == NULL ||
+ *(uri + strlen(searchengines[i].token)) != ' ')
+ continue;
+ if (g_str_has_prefix(uri, searchengines[i].token))
+ return g_strdup_printf(searchengines[i].uri,
+ uri + strlen(searchengines[i].token) + 1);
+ }
+
+ return g_strdup_printf("http://%s", uri);
+}
+
void
pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
{
curconfig[ZoomLevel].val.f = webkit_web_view_get_zoom_level(c->view);
}
-void
-scroll(Client *c, const Arg *a)
+static void
+msgext(Client *c, char type, const Arg *a)
{
- GdkEvent *ev = gdk_event_new(GDK_KEY_PRESS);
+ static char msg[MSGBUFSZ];
+ int ret;
- gdk_event_set_device(ev, gdkkb);
- ev->key.window = gtk_widget_get_window(GTK_WIDGET(c->win));
- ev->key.state = GDK_CONTROL_MASK;
- ev->key.time = GDK_CURRENT_TIME;
-
- switch (a->i) {
- case 'd':
- ev->key.keyval = GDK_KEY_Down;
- break;
- case 'D':
- ev->key.keyval = GDK_KEY_Page_Down;
- break;
- case 'l':
- ev->key.keyval = GDK_KEY_Left;
- break;
- case 'r':
- ev->key.keyval = GDK_KEY_Right;
- break;
- case 'U':
- ev->key.keyval = GDK_KEY_Page_Up;
- break;
- case 'u':
- ev->key.keyval = GDK_KEY_Up;
- break;
+ if ((ret = snprintf(msg, sizeof(msg), "%c%c%c%c",
+ 4, c->pageid, type, a->i))
+ >= sizeof(msg)) {
+ fprintf(stderr, "surf: message too long: %d\n", ret);
+ return;
}
- gdk_event_put(ev);
+ if (pipeout[1] && write(pipeout[1], msg, sizeof(msg)) < 0)
+ fprintf(stderr, "surf: error sending: %.*s\n", ret-2, msg+2);
+}
+
+void
+scrollv(Client *c, const Arg *a)
+{
+ msgext(c, 'v', a);
+}
+
+void
+scrollh(Client *c, const Arg *a)
+{
+ msgext(c, 'h', a);
}
void
spawn(c, &arg);
}
+void
+playexternal(Client *c, const Arg *a)
+{
+ Arg arg;
+
+ arg = (Arg)VIDEOPLAY(geturi(c));
+ spawn(c, &arg);
+}
+
int
main(int argc, char *argv[])
{
if (argc > 0)
arg.v = argv[0];
else
+#ifdef HOMEPAGE
+ arg.v = HOMEPAGE;
+#else
arg.v = "about:blank";
+#endif
setup();
c = newclient(NULL);