From 59adc0787f7b77922c671964e471d0fd2dd7fc90 Mon Sep 17 00:00:00 2001 From: Brendan Taylor Date: Fri, 3 Dec 2010 13:47:09 -0700 Subject: let webkit handle downloads (breaks backward compatibility) --- README | 9 ++- examples/config/config | 1 + examples/data/scripts/download.sh | 34 +++++------ src/callbacks.c | 125 ++++++++++++++++++++++++++++++++++++-- src/callbacks.h | 5 +- src/events.c | 7 ++- src/events.h | 3 +- src/uzbl-core.c | 1 + src/uzbl-core.h | 1 + 9 files changed, 154 insertions(+), 32 deletions(-) diff --git a/README b/README index 34556f2..6dddb35 100644 --- a/README +++ b/README @@ -668,8 +668,13 @@ Events have this format: * `EVENT [uzbl_instance_name] TITLE_CHANGED title_name`: When the title of the page (and hence maybe, the window title) changed. `title_name` is the new title. -* `EVENT [uzbl_instance_name] DOWNLOAD_REQUEST download_uri`: When content needs - to be downloaded, `download_uri` is the URI to get. +* `EVENT [uzbl_instance_name] DOWNLOAD_STARTED destination_path`: A download + has been started, the file will be saved to `destination_path`. +* `EVENT [uzbl_instance_name] DOWNLOAD_PROGRESS destination_path progress`: + While a download is active this event notifies you of the progress. + `progress` is a decimal between 0 and 1. +* `EVENT [uzbl_instance_name] DOWNLOAD_COMPLETE destination_path`: The + download being saved to `destination_path` is now complete. * `EVENT [uzbl_instance_name] LINK_HOVER uri`: The mouse hovers over the link `uri`. * `EVENT [uzbl_instance_name] LINK_UNHOVER uri`: The mouse leaves the link diff --git a/examples/config/config b/examples/config/config index 855f7c2..64e1c94 100644 --- a/examples/config/config +++ b/examples/config/config @@ -43,6 +43,7 @@ set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data:scri set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket set scheme_handler = sync_spawn @scripts_dir/scheme.py set authentication_handler = sync_spawn @scripts_dir/auth.py +set download_handler = sync_spawn @scripts_dir/download.sh # === Dynamic event handlers ================================================= diff --git a/examples/data/scripts/download.sh b/examples/data/scripts/download.sh index 606aa62..c6d95f7 100755 --- a/examples/data/scripts/download.sh +++ b/examples/data/scripts/download.sh @@ -1,26 +1,26 @@ #!/bin/sh -# just an example of how you could handle your downloads -# try some pattern matching on the uri to determine what we should do +# +# uzbl's example configuration sets this script up as its download_handler. +# when uzbl starts a download it runs this script. +# if the script prints a file path to stdout, uzbl will save the download to +# that path. +# if nothing is printed to stdout, the download will be cancelled. . $UZBL_UTIL_DIR/uzbl-args.sh . $UZBL_UTIL_DIR/uzbl-dir.sh -# Some sites block the default wget --user-agent.. -GET="wget --user-agent=Firefox --content-disposition --load-cookies=$UZBL_COOKIE_JAR" +# the URL that is being downloaded +uri=$1 -url="$1" +# a filename suggested by the server or based on the URL +suggested_filename=${2:-$(echo "$uri" | sed 's/\W/-/g')} -http_proxy="$2" -export http_proxy +# the mimetype of the file being downloaded +content_type=$3 -if [ -z "$url" ]; then - echo "you must supply a url! ($url)" - exit 1 -fi +# the size of the downloaded file in bytes. this is not always accurate, since +# the server might not have sent a size with its response headers. +total_size=$4 -# only changes the dir for the $get sub process -if echo "$url" | grep -E '.*\.torrent' >/dev/null; then - ( cd "$UZBL_DOWNLOAD_DIR"; $GET "$url") -else - ( cd "$UZBL_DOWNLOAD_DIR"; $GET "$url") -fi +# just save the file to the default directory with the suggested name +echo $UZBL_DOWNLOAD_DIR/$suggested_filename diff --git a/src/callbacks.c b/src/callbacks.c index f596472..803428d 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -771,13 +771,128 @@ create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer us return NULL; } +void +download_progress_cb(WebKitDownload *download, GParamSpec *pspec, gpointer user_data) { + (void) pspec; (void) user_data; + + gdouble progress; + g_object_get(download, "progress", &progress, NULL); + + const gchar *dest_uri = webkit_download_get_destination_uri(download); + const gchar *dest_path = dest_uri + strlen("file://"); + + gchar *details = g_strdup_printf("%s %.2lf", dest_path, progress); + send_event(DOWNLOAD_PROGRESS, details, NULL); + g_free(details); +} + +void +download_status_cb(WebKitDownload *download, GParamSpec *pspec, gpointer user_data) { + (void) pspec; (void) user_data; + + WebKitDownloadStatus status; + g_object_get(download, "status", &status, NULL); + + switch(status) { + case WEBKIT_DOWNLOAD_STATUS_CREATED: + case WEBKIT_DOWNLOAD_STATUS_STARTED: + case WEBKIT_DOWNLOAD_STATUS_ERROR: + case WEBKIT_DOWNLOAD_STATUS_CANCELLED: + return; /* these are irrelevant */ + case WEBKIT_DOWNLOAD_STATUS_FINISHED: + { + const gchar *dest_uri = webkit_download_get_destination_uri(download); + const gchar *dest_path = dest_uri + strlen("file://"); + send_event(DOWNLOAD_COMPLETE, dest_path, NULL); + } + } +} + gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) { - (void) web_view; - (void) user_data; +download_cb(WebKitWebView *web_view, WebKitDownload *download, gpointer user_data) { + (void) web_view; (void) user_data; - send_event(DOWNLOAD_REQ, webkit_download_get_uri ((WebKitDownload*)download), NULL); - return (FALSE); + /* get the URI being downloaded */ + const gchar *uri = webkit_download_get_uri(download); + + if (uzbl.state.verbose) + printf("Download requested -> %s\n", uri); + + if (!uzbl.behave.download_handler) { + webkit_download_cancel(download); + return FALSE; /* reject downloads when there's no download handler */ + } + + /* get a reasonable suggestion for a filename */ + const gchar *suggested_filename; + g_object_get(download, "suggested-filename", &suggested_filename, NULL); + + /* get the mimetype of the download */ + const gchar *content_type; + WebKitNetworkResponse *r = webkit_download_get_network_response(download); + /* downloads can be initiated from the context menu, in that case there is + no network response yet and trying to get one would crash. */ + if(WEBKIT_IS_NETWORK_RESPONSE(r)) { + SoupMessage *m = webkit_network_response_get_message(r); + SoupMessageHeaders *h; + g_object_get(m, "response-headers", &h, NULL); + content_type = soup_message_headers_get_one(h, "Content-Type"); + } else + content_type = "application/octet-stream"; + + /* get the filesize of the download, as given by the server. + (this may be inaccurate, there's nothing we can do about that.) */ + unsigned int total_size = webkit_download_get_total_size(download); + + gchar *ev = g_strdup_printf("'%s' '%s' '%s' %d", uri, suggested_filename, + content_type, total_size); + run_handler(uzbl.behave.download_handler, ev); + g_free(ev); + + /* no response, cancel the download */ + if(!uzbl.comm.sync_stdout) { + webkit_download_cancel(download); + return FALSE; + } + + /* no response, cancel the download */ + if(uzbl.comm.sync_stdout[0] == 0) { + webkit_download_cancel(download); + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + return FALSE; + } + + /* we got a response, it's the path we should download the file to */ + gchar *destination_path = uzbl.comm.sync_stdout; + uzbl.comm.sync_stdout = NULL; + + /* presumably people don't need newlines in their filenames. */ + char *p = strchr(destination_path, '\n'); + if ( p != NULL ) *p = '\0'; + + /* set up progress callbacks */ + g_signal_connect(download, "notify::status", G_CALLBACK(download_status_cb), NULL); + g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress_cb), NULL); + + /* convert relative path to absolute path */ + if(destination_path[0] != '/') { + gchar *rel_path = destination_path; + gchar *cwd = g_get_current_dir(); + destination_path = g_strconcat(cwd, "/", destination_path, NULL); + g_free(cwd); + g_free(rel_path); + } + + send_event(DOWNLOAD_STARTED, destination_path, NULL); + + /* convert absolute path to file:// URI */ + gchar *destination_uri = g_strconcat("file://", destination_path, NULL); + g_free(destination_path); + + webkit_download_set_destination_uri(download, destination_uri); + g_free(destination_uri); + + return TRUE; } gboolean diff --git a/src/callbacks.h b/src/callbacks.h index a4258f2..40fa80d 100644 --- a/src/callbacks.h +++ b/src/callbacks.h @@ -133,9 +133,6 @@ cmd_load_start(); WebKitWebSettings* view_settings(); -gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); - void toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result); @@ -197,7 +194,7 @@ request_starting_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebRes create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data); gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); +download_cb (WebKitWebView *web_view, WebKitDownload *download, gpointer user_data); void populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c); diff --git a/src/events.c b/src/events.c index 20e3675..55775a8 100644 --- a/src/events.c +++ b/src/events.c @@ -22,7 +22,6 @@ const char *event_table[LAST_EVENT] = { "REQUEST_STARTING" , "KEY_PRESS" , "KEY_RELEASE" , - "DOWNLOAD_REQUEST" , "COMMAND_EXECUTED" , "LINK_HOVER" , "TITLE_CHANGED" , @@ -45,10 +44,12 @@ const char *event_table[LAST_EVENT] = { "PLUG_CREATED" , "COMMAND_ERROR" , "BUILTINS" , - "PTR_MOVE" "PTR_MOVE" , "SCROLL_VERT" , - "SCROLL_HORIZ" + "SCROLL_HORIZ" , + "DOWNLOAD_STARTED" , + "DOWNLOAD_PROGRESS", + "DOWNLOAD_COMPLETE" }; void diff --git a/src/events.h b/src/events.h index bc7960d..4b04dd2 100644 --- a/src/events.h +++ b/src/events.h @@ -7,7 +7,7 @@ enum event_type { LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR, REQUEST_STARTING, - KEY_PRESS, KEY_RELEASE, DOWNLOAD_REQ, COMMAND_EXECUTED, + KEY_PRESS, KEY_RELEASE, COMMAND_EXECUTED, LINK_HOVER, TITLE_CHANGED, GEOMETRY_CHANGED, WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED, VARIABLE_SET, FIFO_SET, SOCKET_SET, @@ -16,6 +16,7 @@ enum event_type { FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED, PLUG_CREATED, COMMAND_ERROR, BUILTINS, PTR_MOVE, SCROLL_VERT, SCROLL_HORIZ, + DOWNLOAD_STARTED, DOWNLOAD_PROGRESS, DOWNLOAD_COMPLETE, /* must be last entry */ LAST_EVENT diff --git a/src/uzbl-core.c b/src/uzbl-core.c index cb20fd7..c960936 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -93,6 +93,7 @@ const struct var_name_to_ptr_t { { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, cmd_set_cookie_handler)}, { "authentication_handler", PTR_V_STR(uzbl.behave.authentication_handler, 1, set_authentication_handler)}, { "scheme_handler", PTR_V_STR(uzbl.behave.scheme_handler, 1, NULL)}, + { "download_handler", PTR_V_STR(uzbl.behave.download_handler, 1, NULL)}, { "fifo_dir", PTR_V_STR(uzbl.behave.fifo_dir, 1, cmd_fifo_dir)}, { "socket_dir", PTR_V_STR(uzbl.behave.socket_dir, 1, cmd_socket_dir)}, { "http_debug", PTR_V_INT(uzbl.behave.http_debug, 1, cmd_http_debug)}, diff --git a/src/uzbl-core.h b/src/uzbl-core.h index b5a502e..a62c3a6 100644 --- a/src/uzbl-core.h +++ b/src/uzbl-core.h @@ -133,6 +133,7 @@ typedef struct { gchar* fantasy_font_family; gchar* cursive_font_family; gchar* scheme_handler; + gchar* download_handler; gboolean show_status; gboolean forward_keys; gboolean status_top; -- cgit v1.2.3 From 5ab073d90e3c66a53e116c78299c657a13847d5c Mon Sep 17 00:00:00 2001 From: Brendan Taylor Date: Fri, 3 Dec 2010 14:55:47 -0700 Subject: display active downloads in the status bar --- examples/config/config | 4 ++- examples/data/plugins/downloads.py | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 examples/data/plugins/downloads.py diff --git a/examples/config/config b/examples/config/config index 64e1c94..a8547d2 100644 --- a/examples/config/config +++ b/examples/config/config @@ -105,7 +105,9 @@ set name_section = \@[\@NAME]\@ set status_section = \@status_message set selected_section = \@[\@SELECTED_URI]\@ -set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section +set download_section = \@downloads + +set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section @download_section set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI diff --git a/examples/data/plugins/downloads.py b/examples/data/plugins/downloads.py new file mode 100644 index 0000000..7bf32d7 --- /dev/null +++ b/examples/data/plugins/downloads.py @@ -0,0 +1,69 @@ +# this plugin does a very simple display of download progress. to use it, add +# @downloads to your status_format. + +import os +ACTIVE_DOWNLOADS = {} + +# after a download's status has changed this is called to update the status bar +def update_download_section(uzbl): + global ACTIVE_DOWNLOADS + + if len(ACTIVE_DOWNLOADS): + # add a newline before we list downloads + result = ' downloads:' + for path in ACTIVE_DOWNLOADS: + # add each download + fn = os.path.basename(path) + progress, = ACTIVE_DOWNLOADS[path] + + dl = " %s (%d%%)" % (fn, progress * 100) + + # replace entities to make sure we don't break our markup + # (this could be done with an @[]@ expansion in uzbl, but then we + # can't use the above to make a new line) + dl = dl.replace("&", "&").replace("<", "<") + result += dl + else: + result = '' + + # and the result gets saved to an uzbl variable that can be used in + # status_format + if uzbl.config.get('downloads', '') != result: + uzbl.config['downloads'] = result + +def download_started(uzbl, destination_path): + # add to the list of active downloads + global ACTIVE_DOWNLOADS + ACTIVE_DOWNLOADS[destination_path] = (0.0,) + + # update the progress + update_download_section(uzbl) + +def download_progress(uzbl, args): + # parse the arguments + s = args.rindex(' ') + destination_path = args[:s] + progress = float(args[s+1:]) + + # update the progress + global ACTIVE_DOWNLOADS + ACTIVE_DOWNLOADS[destination_path] = (progress,) + + # update the status bar variable + update_download_section(uzbl) + +def download_complete(uzbl, destination_path): + # remove from the list of active downloads + global ACTIVE_DOWNLOADS + del ACTIVE_DOWNLOADS[destination_path] + + # update the status bar variable + update_download_section(uzbl) + +# plugin init hook +def init(uzbl): + connect_dict(uzbl, { + 'DOWNLOAD_STARTED': download_started, + 'DOWNLOAD_PROGRESS': download_progress, + 'DOWNLOAD_COMPLETE': download_complete, + }) -- cgit v1.2.3