about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--xxxterm.14
-rw-r--r--xxxterm.c335
2 files changed, 320 insertions, 19 deletions
diff --git a/xxxterm.1 b/xxxterm.1
index b78c082..be62aee 100644
--- a/xxxterm.1
+++ b/xxxterm.1
@@ -208,6 +208,8 @@ Page far right
 Page far left
 .It Cm S-f
 Favorites
+.It Cm M-d
+Download manager
 .El
 .Pp
 Tab Manipulation:
@@ -258,6 +260,8 @@ Quit
 Show favorites
 .It Cm favadd
 Add current page to favorites
+.It Cm dl
+Show download manager
 .It Cm help
 Show help file.
 .It Cm open, op, o <URI>
diff --git a/xxxterm.c b/xxxterm.c
index 06c5da9..ded022b 100644
--- a/xxxterm.c
+++ b/xxxterm.c
@@ -20,13 +20,13 @@
  *	inverse color browsing
  *	favs
  *		- add favicon
- *	download files status
  *	multi letter commands
  *	pre and post counts for commands
  *	fav icon
  *	autocompletion on various inputs
  *	create privacy browsing
  *		- encrypted local data
+ *	printing support
  */
 
 #include <stdio.h>
@@ -36,8 +36,10 @@
 #include <string.h>
 #include <unistd.h>
 #include <util.h>
+#include <pthread.h>
 
 #include <sys/queue.h>
+#include <sys/tree.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
@@ -77,7 +79,7 @@ THE SOFTWARE.
 
 static char		*version = "$xxxterm$";
 
-/* #define XT_DEBUG */
+#define XT_DEBUG 
 #ifdef XT_DEBUG
 #define DPRINTF(x...)		do { if (swm_debug) fprintf(stderr, x); } while (0)
 #define DNPRINTF(n,x...)	do { if (swm_debug & n) fprintf(stderr, x); } while (0)
@@ -161,6 +163,16 @@ struct tab {
 };
 TAILQ_HEAD(tab_list, tab);
 
+struct download {
+	int			id;
+	RB_ENTRY(download)	entry;
+	WebKitDownload		*download;
+	struct tab		*tab;
+};
+
+RB_HEAD(download_list, download);
+int 				next_download_id = 0;
+
 struct karg {
 	int		i;
 	char		*s;
@@ -171,8 +183,36 @@ struct karg {
 #define XT_DIR			(".xxxterm")
 #define XT_CONF_FILE		("xxxterm.conf")
 #define XT_FAVS_FILE		("favorites")
+#define XT_DOWNLOADS_FILE	("downloads")
 #define XT_CB_HANDLED		(TRUE)
 #define XT_CB_PASSTHROUGH	(FALSE)
+#define XT_DOCTYPE		"<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
+#define XT_HTML_TAG		"<html xmlns='http://www.w3.org/1999/xhtml'>"
+#define XT_DLMAN_REFRESH	"10"
+
+/* file sizes */
+#define SZ_KB		((uint64_t) 1024)
+#define SZ_MB		(SZ_KB * SZ_KB)
+#define SZ_GB		(SZ_KB * SZ_KB * SZ_KB)
+#define SZ_TB		(SZ_KB * SZ_KB * SZ_KB * SZ_KB)
+
+/*
+ * xxxterm "protocol"
+ */
+
+#define XT_XTP_STR		"xxxt://"
+
+/* XTP classes (xxxt://<class>) */
+#define XT_XTP_DL_STR		"dl"
+
+/* XTP download commands */
+#define XT_XTP_DL_CANCEL	0
+#define XT_XTP_DL_REMOVE	1
+#define XT_XTP_DL_LIST		2
+
+#define XT_XTP_DL_CANCEL_STR	"cancel"
+#define XT_XTP_DL_REMOVE_STR	"remove"
+#define XT_XTP_DL_LIST_STR	"list"
 
 /* actions */
 #define XT_MOVE_INVALID		(0)
@@ -220,6 +260,8 @@ struct passwd		*pwd;
 GtkWidget		*main_window;
 GtkNotebook		*notebook;
 struct tab_list		tabs;
+struct download_list	downloads;
+pthread_mutex_t		dlman_mtx = PTHREAD_MUTEX_INITIALIZER;
 
 /* mime types */
 struct mime_type {
@@ -268,6 +310,14 @@ void			create_new_tab(char *, int);
 void			delete_tab(struct tab *);
 void			adjustfont_webkit(struct tab *, int);
 int			run_script(struct tab *, char *);
+int			download_rb_cmp(struct download *e1, struct download *e2);
+
+int
+download_rb_cmp(struct download *e1, struct download *e2)
+{
+	return (e1->id < e2->id ? -1 : e1->id > e2->id);
+}
+RB_GENERATE(download_list, download, entry, download_rb_cmp);
 
 struct valid_url_types {
 	char		*type;
@@ -276,6 +326,7 @@ struct valid_url_types {
 	{ "https://" },
 	{ "ftp://" },
 	{ "file://" },
+	{ XT_XTP_STR },
 };
 
 int
@@ -1078,6 +1129,141 @@ command(struct tab *t, struct karg *args)
 	return (XT_CB_HANDLED);
 }
 
+/*
+ * write single row for download manager table.
+ * XXX try to do this in memory instead of to a file.
+ */
+void
+dlman_table_row(FILE *f, struct download *dl)
+{
+	WebKitDownloadStatus	stat;
+	char			*status_html = NULL, *cmd_html = NULL;
+	gdouble			progress;
+	char			cur_sz[FMT_SCALED_STRSIZE];
+	char			tot_sz[FMT_SCALED_STRSIZE];
+
+	DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
+
+	stat = webkit_download_get_status(dl->download);
+
+	switch (stat) {
+	case WEBKIT_DOWNLOAD_STATUS_FINISHED:
+		status_html = g_strdup_printf("Finished");
+		cmd_html = g_strdup_printf("<a href='" XT_XTP_STR XT_XTP_DL_STR
+		    "/" XT_XTP_DL_REMOVE_STR "/%d'>Remove</a>", dl->id);
+		break;
+	case WEBKIT_DOWNLOAD_STATUS_STARTED:
+		/* gather size info */
+		progress = 100 * webkit_download_get_progress(dl->download);
+
+		fmt_scaled(webkit_download_get_current_size(dl->download), cur_sz);
+		fmt_scaled(webkit_download_get_total_size(dl->download), tot_sz);
+
+		status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz,
+		    tot_sz, progress);
+		cmd_html = g_strdup_printf("<a href='" XT_XTP_STR XT_XTP_DL_STR
+		    "/" XT_XTP_DL_CANCEL_STR "/%d'>Cancel</a>", dl->id);
+
+		break;
+	case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
+		status_html = g_strdup_printf("Cancelled");
+		cmd_html = g_strdup_printf("<a href='" XT_XTP_STR XT_XTP_DL_STR
+		    "/" XT_XTP_DL_REMOVE_STR "/%d'>Remove</a>", dl->id);
+		break;
+	case WEBKIT_DOWNLOAD_STATUS_ERROR:
+		status_html = g_strdup_printf("Error!");
+		cmd_html = g_strdup_printf("<a href='" XT_XTP_STR XT_XTP_DL_STR "/" 
+		    XT_XTP_DL_REMOVE_STR "/%d'>Remove</a>", dl->id);
+		break;
+	case WEBKIT_DOWNLOAD_STATUS_CREATED:
+		cmd_html = g_strdup_printf("<a href='" XT_XTP_STR XT_XTP_DL_STR
+		    "/" XT_XTP_DL_CANCEL_STR "/%d'>Cancel</a>", dl->id);
+		status_html = g_strdup_printf("Starting");
+		break;
+	default:
+		warn("%s: unknown download status", __func__);
+	};
+
+	fprintf(f, "<tr><td>%s</td><td>%s</td><td>%s</td></tr>\n",
+	    webkit_download_get_uri(dl->download), status_html, cmd_html);
+
+	if (status_html)
+		g_free(status_html);
+
+	if (cmd_html)
+		g_free(cmd_html);
+}
+
+/*
+ * write away a page which describes download status' and go there
+ */
+int
+dlman(struct tab *t, struct karg *args)
+{
+	struct download		*dl;
+	char			file[PATH_MAX];
+	FILE			*f;
+	int			n_dl = 0;
+
+	DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
+
+	if (t == NULL)
+		errx(1, "%s: null tab", __func__);
+
+	/* no two tabs should come here at the same time */
+	pthread_mutex_lock(&dlman_mtx);
+
+	/* open downloads html */
+	snprintf(file, sizeof file, "%s/%s/%s.html", pwd->pw_dir,
+	    XT_DIR, XT_DOWNLOADS_FILE);
+	if ((f = fopen(file, "w+")) == NULL)
+		errx(1, "dlman: writing download file");
+
+	/* header */
+	fprintf(f, XT_DOCTYPE XT_HTML_TAG "\n");
+	fprintf(f, "<head><title>Downloads</title>\n");
+
+	/* ensure the page refreshes every so often */
+	fprintf(f, "<meta http-equiv='refresh' content='" XT_DLMAN_REFRESH
+	    ";url=" XT_XTP_STR XT_XTP_DL_STR "/" XT_XTP_DL_LIST_STR "' />\n");
+
+	/* style (XXX move to separate func so favs can use this too) */
+	fprintf(f, "<style type='text/css'>\n");
+	fprintf(f, "td {text-align: center} th {background-color: #cccccc}\n");
+	fprintf(f, "table {width: 90%%; border: 1px black solid}\n");
+	fprintf(f, "</style>\n");
+
+	/* actual content of page */
+	fprintf(f, "</head><body><h1>Downloads</h1><div align='center'><p>\n");
+	fprintf(f, "<a href='" XT_XTP_STR XT_XTP_DL_STR "/"
+	    XT_XTP_DL_LIST_STR "'>\n");
+	fprintf(f, "[ Refresh Downloads ]</a>\n");
+	fprintf(f, "</p><table><tr><th style='width: 60%%'>File</th>\n");
+	fprintf(f, "<th>Progress</th><th>Command</th></tr>\n");
+
+	RB_FOREACH_REVERSE(dl, download_list, &downloads) {
+		dlman_table_row(f, dl);
+		n_dl ++;
+	}
+
+	/* message if no downloads in list */
+	if (n_dl == 0)
+		fprintf(f, "<tr><td colspan='3'>No downloads</td></tr>\n");
+
+	/* footer */
+	fprintf(f, "</table></div></body></html>");
+	fclose(f);
+
+	snprintf(file, sizeof file, "file://%s/%s/%s.html",
+	    pwd->pw_dir, XT_DIR, XT_DOWNLOADS_FILE);
+	webkit_web_view_load_uri(t->wv, file);
+
+	/* let other tabs in here */
+	pthread_mutex_unlock(&dlman_mtx);
+
+	return (0);
+}
+
 int
 search(struct tab *t, struct karg *args)
 {
@@ -1212,6 +1398,7 @@ struct key {
 	int		(*func)(struct tab *, struct karg *);
 	struct karg	arg;
 } keys[] = {
+	{ GDK_MOD1_MASK, 	0,	GDK_d,		dlman,	{0} },
 	{ 0,			0,	GDK_slash,	command,	{.i = '/'} },
 	{ GDK_SHIFT_MASK,	0,	GDK_question,	command,	{.i = '?'} },
 	{ GDK_SHIFT_MASK,	0,	GDK_colon,	command,	{.i = ':'} },
@@ -1296,6 +1483,7 @@ struct cmd {
 	/* favorites */
 	{ "fav",		0,	favorites,		{0} },
 	{ "favadd",		0,	favadd,			{0} },
+	{ XT_XTP_DL_STR,	0,	dlman,			{0} },
 
 	{ "1",			0,	move,			{.i = XT_MOVE_TOP} },
 
@@ -1350,6 +1538,94 @@ focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
 		gtk_widget_grab_focus(GTK_WIDGET(t->wv));
 }
 
+/*
+ * cancel, remove, etc. downloads
+ */
+void
+dlman_ctrl(struct tab *t, uint8_t cmd, int id)
+{
+	struct download		find, *d;
+
+	DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
+	
+	/* lookup download in question */
+	find.id = id;
+	d = RB_FIND(download_list, &downloads, &find);
+
+	if (d == NULL) {
+		warn("%s: no such download", __func__);
+		return;
+	}
+
+	/* decide what to do */
+	switch (cmd) {
+	case XT_XTP_DL_CANCEL:
+		webkit_download_cancel(d->download);
+		break;
+	case XT_XTP_DL_REMOVE:
+		webkit_download_cancel(d->download); /* just incase */
+		g_object_unref(d->download);
+		RB_REMOVE(download_list, &downloads, d);
+		break;
+	default:
+		warn("%s: unknown command", __func__);
+		break;
+	};
+	dlman(t, NULL);
+	return;
+}
+
+/* 
+ * is the url xxxt:// protocol? 
+ * if so, parse and despatch correct bahvior
+ */
+int
+parse_xtp_url(struct tab *t, const char *url)
+{
+	char		*dup = NULL, *p, *tokens[3], *last;
+	uint8_t		n_tokens = 0;
+
+	DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
+	
+	if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
+		return 0;
+
+	dup = g_strdup(url + strlen(XT_XTP_STR));
+
+	/* split out the url */
+	for ((p = strtok_r(dup, "/", &last)); p;
+	    (p = strtok_r(NULL, "/", &last))) {
+		if (n_tokens < 3)
+			tokens[n_tokens++] = p;
+	}
+
+	/* should be exactly three fields 'class/command/arg' */
+	if (n_tokens < 2)
+		return 0;
+
+	/* if a download XTP url */
+	if (!strcmp(XT_XTP_DL_STR, tokens[0])) {
+		if (!strcmp(tokens[1], XT_XTP_DL_CANCEL_STR))
+			dlman_ctrl(t, XT_XTP_DL_CANCEL, atoi(tokens[2]));
+		else if (!strcmp(tokens[1], XT_XTP_DL_REMOVE_STR))
+			dlman_ctrl(t, XT_XTP_DL_REMOVE, atoi(tokens[2]));
+		else if (!strcmp(tokens[1], XT_XTP_DL_LIST_STR))
+			dlman(t, NULL);
+		else /* unsupported download command */
+			warn("%s: unsupported dl command: %s",
+			    __func__, tokens[1]);
+	/* XXX add favorites handling here eventually */
+	} else /* unsupported class */
+		warn("%s: unsupported class: %s", __func__, tokens[0]);
+
+	if (dup)
+		g_free(dup);
+
+	return 1;
+}
+
+
+
 void
 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
 {
@@ -1366,13 +1642,16 @@ activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
 
 	uri += strspn(uri, "\t ");
 
-	if (valid_url_type((char *)uri)) {
-		newuri = guess_url_type((char *)uri);
-		uri = newuri;
-	}
+	/* if xxxt:// treat specially */
+	if (!parse_xtp_url(t, uri)) { 
+		if (valid_url_type((char *)uri)) {
+			newuri = guess_url_type((char *)uri);
+			uri = newuri;
+		}
 
-	webkit_web_view_load_uri(t->wv, uri);
-	gtk_widget_grab_focus(GTK_WIDGET(t->wv));
+		webkit_web_view_load_uri(t->wv, uri);
+		gtk_widget_grab_focus(GTK_WIDGET(t->wv));
+	}
 
 	if (newuri)
 		g_free(newuri);
@@ -1520,7 +1799,8 @@ webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
 	    webkit_network_request_get_uri(request));
 
 	uri = (char *)webkit_network_request_get_uri(request);
-	if (t->ctrl_click) {
+
+	if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
 		t->ctrl_click = 0;
 		create_new_tab(uri, ctrl_click_focus);
 		webkit_web_policy_decision_ignore(pd);
@@ -1619,31 +1899,47 @@ webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
 }
 
 int
-webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
+webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t)
 {
 	const gchar		*filename;
 	char			*uri = NULL;
+	struct download		*download_entry;
+	int			ret = TRUE;
 
-	if (download == NULL || t == NULL)
-		errx(1, "webview_download_cb: invalid pointers");
+	if (wk_download == NULL || t == NULL)
+		errx(1, "%s: invalid pointers", __func__);
 
-	filename = webkit_download_get_suggested_filename(download);
+	filename = webkit_download_get_suggested_filename(wk_download);
 	if (filename == NULL)
 		return (FALSE); /* abort download */
 
 	uri = g_strdup_printf("file://%s/%s", download_dir, filename);
 
-	DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
-	    "local %s\n",
-	    t->tab_id, filename, uri);
+	DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
+	    "local %s\n", __func__, t->tab_id, filename, uri);
+
+	webkit_download_set_destination_uri(wk_download, uri);
 
-	webkit_download_set_destination_uri(download, uri);
-	webkit_download_start(download);
+	if (webkit_download_get_status(wk_download) ==
+	    WEBKIT_DOWNLOAD_STATUS_ERROR) {
+		warn("%s: download failed to start", __func__);
+		ret = FALSE;
+		gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
+	} else {
+		download_entry = g_malloc(sizeof(struct download));
+		download_entry->download = wk_download;
+		download_entry->tab = t;
+		download_entry->id = next_download_id ++;
+		RB_INSERT(download_list, &downloads, download_entry);
+		/* get from history */
+		g_object_ref(wk_download);
+		gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
+	}
 
 	if (uri)
 		g_free(uri);
 
-	return (TRUE); /* start download */
+	return (ret); /* start download */
 }
 
 /* XXX currently unused */
@@ -2448,6 +2744,7 @@ main(int argc, char *argv[])
 	argv += optind;
 
 	TAILQ_INIT(&tabs);
+	RB_INIT(&downloads);
 
 	/* prepare gtk */
 	gtk_init(&argc, &argv);