browse

browse.git
git clone git://git.lenczewski.org/browse.git
Log | Files | Refs | README | LICENSE

commit 16531f38ddd13c5d87254df26cbeb56ad6f76c4a
parent d98a300b4969bd5960c91499adefdf645b58b5ba
Author: MikoĊ‚aj Lenczewski <mblenczewski@gmail.com>
Date:   Mon, 13 Nov 2023 17:59:05 +0000

Refactored code into separate files, added download support

Diffstat:
Aapi.c | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowse.c | 269++++---------------------------------------------------------------------------
Mbrowse.h | 60++++++++++++++++++++++++++++++++++++++----------------------
Mconfig.def.h | 16++++++++++++----
Awebview.c | 261+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 456 insertions(+), 282 deletions(-)

diff --git a/api.c b/api.c @@ -0,0 +1,132 @@ +#include "browse.h" + +static char * +shellcmd(char const *cmd) +{ + FILE *pstdout = popen(cmd, "r"); + if (!pstdout) return NULL; + + char *line = NULL; + size_t len; + + if (getline(&line, &len, pstdout) == -1) { + pclose(pstdout); + return NULL; + } + + pclose(pstdout); + + (void) len; + + return line; +} + +void +stopload(struct browse_client *client, union browse_keybind_arg const *arg) +{ + assert(client); + + (void) arg; + + webkit_web_view_stop_loading(client->webview); +} + +void +reload(struct browse_client *client, union browse_keybind_arg const *arg) +{ + assert(client); + assert(arg); + + if (arg->i) { + webkit_web_view_reload_bypass_cache(client->webview); + } else { + webkit_web_view_reload(client->webview); + } +} + +void +navigate(struct browse_client *client, union browse_keybind_arg const *arg) +{ + assert(client); + assert(arg); + + if (arg->i < 0) { + webkit_web_view_go_back(client->webview); + } else if (arg->i > 0) { + webkit_web_view_go_forward(client->webview); + } +} + +static void +clipboard_cb(GObject *src, GAsyncResult *res, void *user_data) +{ + (void) src; + + struct browse_client *client = user_data; + + char *text; + if ((text = gdk_clipboard_read_text_finish(GDK_CLIPBOARD(src), res, NULL))) { + browse_load_uri(client, text); + } +} + +void +clipboard(struct browse_client *client, union browse_keybind_arg const *arg) +{ + assert(client); + assert(arg); + + if (arg->i) { + gdk_clipboard_read_text_async(client->clipboard, NULL, clipboard_cb, client); + } else { + gdk_clipboard_set_text(client->clipboard, webkit_web_view_get_uri(client->webview)); + } +} + +void +javascript(struct browse_client *client, union browse_keybind_arg const *arg) +{ + assert(client); + assert(arg); + + webkit_web_view_evaluate_javascript(client->webview, arg->s, -1, + NULL, /* world */ + NULL, /* source_uri */ + NULL, /* cancellable */ + NULL, /* callback */ + NULL /* user data */); +} + +void +search(struct browse_client *client, union browse_keybind_arg const *arg) +{ + assert(client); + assert(arg); + + char *uri = shellcmd(arg->s); + if (!uri) return; + + browse_load_uri(client, uri); + + free(uri); +} + +void +toggle(struct browse_client *client, union browse_keybind_arg const *arg) +{ + assert(client); + assert(arg); + + union browse_prop *prop = &client->props[arg->i]; + + switch (arg->i) { + case BROWSE_PROP_STRICT_TLS: + prop->strict_tls.enabled = !prop->strict_tls.enabled; + webkit_network_session_set_tls_errors_policy(client->webnetsession, + prop->strict_tls.enabled ? WEBKIT_TLS_ERRORS_POLICY_FAIL + : WEBKIT_TLS_ERRORS_POLICY_IGNORE); + break; + } + + webkit_web_view_reload(client->webview); +} diff --git a/browse.c b/browse.c @@ -2,29 +2,7 @@ #include "config.h" -static struct browse_window * -browse_new_window(struct browse_ctx *ctx, char const *uri, struct browse_window *root); - -static void -browse_del_window(struct browse_window *window); - -static void -browse_update_title(struct browse_window *ctx, char const *uri); - -static void -browse_load_uri(struct browse_window *ctx, char const *uri); - -static void -browse_on_window_destroy(GtkWindow *window, struct browse_window *ctx); - -static gboolean -browse_on_key_pressed(GtkEventController *controller, guint keyval, guint keycode, - GdkModifierType state, struct browse_window *ctx); - -static void -browse_on_load_changed(WebKitWebView *webview, WebKitLoadEvent ev, struct browse_window *ctx); - -static struct browse_ctx ctx; +struct browse_ctx browse; int main(int argc, char **argv) @@ -32,250 +10,29 @@ main(int argc, char **argv) (void) argc; (void) argv; - memset(&ctx, 0, sizeof ctx); + memset(&browse, 0, sizeof browse); gtk_init(); - ctx.gtk_settings = gtk_settings_get_default(); + browse.gtk_settings = gtk_settings_get_default(); for (size_t i = 0; i < ARRLEN(gtk_settings); i++) { - g_object_set(G_OBJECT(ctx.gtk_settings), gtk_settings[i].name, gtk_settings[i].v, NULL); + g_object_set(G_OBJECT(browse.gtk_settings), gtk_settings[i].name, gtk_settings[i].v, NULL); } - ctx.webkit_settings = webkit_settings_new(); + browse.webkit_settings = webkit_settings_new(); for (size_t i = 0; i < ARRLEN(webkit_settings); i++) { - g_object_set(G_OBJECT(ctx.webkit_settings), webkit_settings[i].name, webkit_settings[i].v, NULL); + g_object_set(G_OBJECT(browse.webkit_settings), webkit_settings[i].name, webkit_settings[i].v, NULL); } - ctx.root = browse_new_window(&ctx, start_page, NULL); - - while (!ctx.shutdown) - g_main_context_iteration(NULL, TRUE); - - exit(EXIT_SUCCESS); -} - -static struct browse_window * -browse_new_window(struct browse_ctx *ctx, char const *uri, struct browse_window *root) -{ - assert(ctx); - assert(uri); - - (void) root; - - struct browse_window *window = malloc(sizeof *window); - if (!window) return NULL; - - window->window = GTK_WINDOW(gtk_window_new()); - window->event_handler = gtk_event_controller_key_new(); - - gtk_widget_add_controller(GTK_WIDGET(window->window), window->event_handler); - - g_signal_connect(window->event_handler, "key-pressed", G_CALLBACK(browse_on_key_pressed), window); - g_signal_connect(window->window, "destroy", G_CALLBACK(browse_on_window_destroy), window); - - window->clipboard = gtk_widget_get_primary_clipboard(GTK_WIDGET(window->window)); - - window->webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); - - g_signal_connect(window->webview, "load-changed", G_CALLBACK(browse_on_load_changed), window); - - webkit_web_view_set_settings(window->webview, ctx->webkit_settings); - - gtk_window_set_child(window->window, GTK_WIDGET(window->webview)); - - browse_load_uri(window, uri); - browse_update_title(window, NULL); - - window->next = window->prev = NULL; - - gtk_window_present(window->window); - - return window; -} - -static void -browse_del_window(struct browse_window *window) -{ - if (window->prev) window->prev->next = window->next; - if (window->next) window->next->prev = window->prev; - - if (ctx.root == window) ctx.shutdown = true; - - free(window); -} - -static void -browse_update_title(struct browse_window *ctx, char const *uri) -{ - if (uri) snprintf(ctx->title, sizeof ctx->title, "%s", uri); - - gtk_window_set_title(ctx->window, ctx->title); -} - -static void -browse_load_uri(struct browse_window *ctx, char const *uri) -{ - assert(uri); - - if (g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://") || - g_str_has_prefix(uri, "file://") || g_str_has_prefix(uri, "about:")) { - webkit_web_view_load_uri(ctx->webview, uri); - } else { - snprintf(ctx->url, sizeof ctx->url, search_page, uri); - webkit_web_view_load_uri(ctx->webview, ctx->url); - } -} - -static gboolean -browse_on_key_pressed(GtkEventController *controller, guint keyval, guint keycode, - GdkModifierType state, struct browse_window *ctx) -{ - (void) controller; - (void) keycode; - - for (size_t i = 0; i < ARRLEN(keybinds); i++) { - if ((state & GDK_MODIFIER_MASK) == keybinds[i].mod && keyval == keybinds[i].key) { - keybinds[i].handler(ctx, &keybinds[i].arg); - return TRUE; - } + if (!browse_new(start_page, NULL)) { + fprintf(stderr, "Failed to allocate root window\n"); + exit(EXIT_FAILURE); } - return FALSE; -} - -static void -browse_on_window_destroy(GtkWindow *window, struct browse_window *ctx) -{ - (void) window; + while (!browse.shutdown) g_main_context_iteration(NULL, TRUE); - browse_del_window(ctx); -} - -static void -browse_on_load_changed(WebKitWebView *webview, WebKitLoadEvent ev, struct browse_window *ctx) -{ - (void) ev; - - char const *uri = webkit_web_view_get_uri(webview); - browse_update_title(ctx, uri); -} - -void -stopload(struct browse_window *ctx, union browse_keybind_arg const *arg) -{ - assert(ctx); - - (void) arg; - - webkit_web_view_stop_loading(ctx->webview); -} - -void -reload(struct browse_window *ctx, union browse_keybind_arg const *arg) -{ - assert(ctx); - assert(arg); - - if (arg->i) { - webkit_web_view_reload_bypass_cache(ctx->webview); - } else { - webkit_web_view_reload(ctx->webview); - } -} - -void -navigate(struct browse_window *ctx, union browse_keybind_arg const *arg) -{ - assert(ctx); - assert(arg); - - if (arg->i < 0) { - webkit_web_view_go_back(ctx->webview); - } else if (arg->i > 0) { - webkit_web_view_go_forward(ctx->webview); - } -} - -static void -clipboard_cb(GObject *src, GAsyncResult *res, void *user_data) -{ - (void) src; - - struct browse_window *ctx = user_data; - - char *text; - if ((text = gdk_clipboard_read_text_finish(GDK_CLIPBOARD(src), res, NULL))) { - browse_load_uri(ctx, text); - } -} - -void -clipboard(struct browse_window *ctx, union browse_keybind_arg const *arg) -{ - assert(ctx); - assert(arg); - - if (arg->i) { - gdk_clipboard_read_text_async(ctx->clipboard, NULL, clipboard_cb, ctx); - } else { - gdk_clipboard_set_text(ctx->clipboard, webkit_web_view_get_uri(ctx->webview)); - } -} - -static void -javascript_cb(GObject *src, GAsyncResult *res, void *user_data) -{ - (void) user_data; - - JSCValue *jsres; - if ((jsres = webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(src), res, NULL))) { - // TODO: how to correctly free jsres, which we apparently own now? - } -} - -void -javascript(struct browse_window *ctx, union browse_keybind_arg const *arg) -{ - assert(ctx); - assert(arg); - - webkit_web_view_evaluate_javascript(ctx->webview, arg->s, -1, - NULL, /* world */ - NULL, /* source_uri */ - NULL, javascript_cb, NULL); -} - -static char * -spawn(char const *cmd) -{ - FILE *pstdout = popen(cmd, "r"); - if (!pstdout) return NULL; - - char *line = NULL; - size_t len; - - if (getline(&line, &len, pstdout) == -1) { - pclose(pstdout); - return NULL; - } - - pclose(pstdout); - - (void) len; - - return line; + exit(EXIT_SUCCESS); } -void -search(struct browse_window *ctx, union browse_keybind_arg const *arg) -{ - assert(ctx); - assert(arg); - - char *uri = spawn(arg->s); - if (!uri) return; - - browse_load_uri(ctx, uri); - - free(uri); -} +#include "webview.c" +#include "api.c" diff --git a/browse.h b/browse.h @@ -8,7 +8,9 @@ #define _XOPEN_SOURCE 700 #include <assert.h> +#include <limits.h> #include <signal.h> +#include <stdatomic.h> #include <stdbool.h> #include <stdlib.h> #include <stdint.h> @@ -25,28 +27,39 @@ #define BROWSE_WINDOW_TITLE_MAX 256 #define BROWSE_WINDOW_URL_MAX 1024 -struct browse_window; -struct browse_window { - GtkWindow *window; - GtkEventController *event_handler; +struct browse_ctx { + GtkSettings *gtk_settings; + WebKitSettings *webkit_settings; - GdkClipboard *clipboard; + atomic_uint clients; + atomic_bool shutdown; +}; - WebKitWebView *webview; +extern struct browse_ctx browse; - char title[BROWSE_WINDOW_TITLE_MAX]; - char url[BROWSE_WINDOW_URL_MAX]; +struct browse_client; - struct browse_window *next, *prev; -}; +extern struct browse_client * +browse_new(char const *uri, struct browse_client *root); -struct browse_ctx { - GtkSettings *gtk_settings; - WebKitSettings *webkit_settings; +extern void +browse_update_title(struct browse_client *self, char const *uri); + +extern void +browse_load_uri(struct browse_client *self, char const *uri); + +extern void +browse_download_uri(struct browse_client *self, char const *uri); - struct browse_window *root; +enum browse_prop_type { + BROWSE_PROP_STRICT_TLS, + _BROWSE_PROP_TYPE_COUNT, +}; - bool shutdown; +union browse_prop { + struct { + bool enabled; + } strict_tls; }; struct browse_gtk_setting { @@ -64,7 +77,7 @@ union browse_keybind_arg { char const *s; }; -typedef void (*browse_keybind_fn)(struct browse_window *ctx, union browse_keybind_arg const *arg); +typedef void (*browse_keybind_fn)(struct browse_client *client, union browse_keybind_arg const *arg); struct browse_keybind { GdkModifierType mod; @@ -74,21 +87,24 @@ struct browse_keybind { }; extern void -stopload(struct browse_window *ctx, union browse_keybind_arg const *arg); +stopload(struct browse_client *client, union browse_keybind_arg const *arg); + +extern void +reload(struct browse_client *client, union browse_keybind_arg const *arg); extern void -reload(struct browse_window *ctx, union browse_keybind_arg const *arg); +navigate(struct browse_client *client, union browse_keybind_arg const *arg); extern void -navigate(struct browse_window *ctx, union browse_keybind_arg const *arg); +clipboard(struct browse_client *client, union browse_keybind_arg const *arg); extern void -clipboard(struct browse_window *ctx, union browse_keybind_arg const *arg); +javascript(struct browse_client *client, union browse_keybind_arg const *arg); extern void -javascript(struct browse_window *ctx, union browse_keybind_arg const *arg); +search(struct browse_client *client, union browse_keybind_arg const *arg); extern void -search(struct browse_window *ctx, union browse_keybind_arg const *arg); +toggle(struct browse_client *client, union browse_keybind_arg const *arg); #endif /* BROWSE_H */ diff --git a/config.def.h b/config.def.h @@ -1,7 +1,9 @@ #include "browse.h" -static const char start_page[] = "https://searx.mblenczewski.com"; -static const char search_page[] = "https://searx.mblenczewski.com/search?q=%s"; +static char const start_page[] = "https://searx.mblenczewski.com"; +static char const search_page[] = "https://searx.mblenczewski.com/search?q=%s"; + +static char const download_fpath_fmt[] = "/tmp/%s"; // https://docs.gtk.org/gtk4/class/.Settings.html#Properties static const struct browse_gtk_setting gtk_settings[] = { @@ -17,14 +19,18 @@ static const struct browse_webkit_setting webkit_settings[] = { { .name = "enable-smooth-scrolling", .v = { true }, }, }; -#define BOOKMARK_FILE "$XDG_CONFIG_HOME/bookmarks" +static const union browse_prop window_default_props[_BROWSE_PROP_TYPE_COUNT] = { + [BROWSE_PROP_STRICT_TLS] = { .strict_tls = { .enabled = true, }, }, +}; + +#define BOOKMARK_FILE "$HOME/.browse/bookmarks" #define SEARCH_PROC { .s = "cat " BOOKMARK_FILE " | bemenu -l 10 -p 'Search: '", } #define MODKEY GDK_CONTROL_MASK static const struct browse_keybind keybinds[] = { - /* modifier keyval handler argument */ + /* modifier keyval handler argument */ { 0, GDK_KEY_Escape, stopload, { 0 }, }, { MODKEY, GDK_KEY_c, stopload, { 0 }, }, @@ -43,4 +49,6 @@ static const struct browse_keybind keybinds[] = { { MODKEY, GDK_KEY_p, clipboard, { .i = 1, }, }, { MODKEY, GDK_KEY_g, search, SEARCH_PROC, }, + + { MODKEY|GDK_SHIFT_MASK, GDK_KEY_T, toggle, { .i = BROWSE_PROP_STRICT_TLS, }, }, }; diff --git a/webview.c b/webview.c @@ -0,0 +1,261 @@ +#include "browse.h" + +struct browse_client { + GtkWindow *window; + GtkEventController *event_handler; + + GdkClipboard *clipboard; + + WebKitWebView *webview; + WebKitNetworkSession *webnetsession; + + char title[BROWSE_WINDOW_TITLE_MAX]; + char url[BROWSE_WINDOW_URL_MAX]; + + union browse_prop props[_BROWSE_PROP_TYPE_COUNT]; +}; + +static gboolean +event_controller_on_key_pressed(GtkEventController *controller, guint keyval, guint keycode, + GdkModifierType state, struct browse_client *client); + +static void +window_on_destroy(GtkWindow *window, struct browse_client *client); + +static void +webview_on_load_changed(WebKitWebView *webview, WebKitLoadEvent ev, struct browse_client *client); + +static gboolean +webview_on_decide_policy(WebKitWebView *webview, WebKitPolicyDecision *decision, + WebKitPolicyDecisionType type, struct browse_client *client); + +struct browse_client * +browse_new(char const *uri, struct browse_client *root) +{ + assert(uri); + + struct browse_client *self = malloc(sizeof *self); + if (!self) return NULL; + + self->window = GTK_WINDOW(gtk_window_new()); + self->event_handler = gtk_event_controller_key_new(); + + gtk_widget_add_controller(GTK_WIDGET(self->window), self->event_handler); + + g_signal_connect(self->event_handler, "key-pressed", G_CALLBACK(event_controller_on_key_pressed), self); + g_signal_connect(self->window, "destroy", G_CALLBACK(window_on_destroy), self); + + self->clipboard = gtk_widget_get_primary_clipboard(GTK_WIDGET(self->window)); + + if (root) { + self->webview = g_object_new(WEBKIT_TYPE_WEB_VIEW, "related-view", root->webview, NULL); + } else { + self->webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); + } + + self->webnetsession = webkit_web_view_get_network_session(self->webview); + + g_signal_connect(self->webview, "load-changed", G_CALLBACK(webview_on_load_changed), self); + g_signal_connect(self->webview, "decide-policy", G_CALLBACK(webview_on_decide_policy), self); + + webkit_web_view_set_settings(self->webview, browse.webkit_settings); + + gtk_window_set_child(self->window, GTK_WIDGET(self->webview)); + + browse_load_uri(self, uri); + browse_update_title(self, NULL); + + gtk_window_present(self->window); + + browse.clients++; + + return self; +} + +void +browse_update_title(struct browse_client *self, char const *uri) +{ + if (uri) snprintf(self->title, sizeof self->title, "%s", uri); + + gtk_window_set_title(self->window, self->title); +} + +void +browse_load_uri(struct browse_client *self, char const *uri) +{ + assert(uri); + + if (!g_str_has_prefix(uri, "http://") && !g_str_has_prefix(uri, "https://") && + !g_str_has_prefix(uri, "file://") && !g_str_has_prefix(uri, "about:")) { + snprintf(self->url, sizeof self->url, search_page, uri); + uri = self->url; + } + + webkit_web_view_load_uri(self->webview, uri); +} + +static gboolean +download_on_decide_destination(WebKitDownload *download, gchar const *suggested_filename, struct browse_client *client); + +static void +download_on_received_data(WebKitDownload *download, guint64 length, struct browse_client *client); + +static void +download_on_failed(WebKitDownload *download, WebKitDownloadError error, struct browse_client *client); + +void +browse_download_uri(struct browse_client *self, char const *uri) +{ + WebKitDownload *download = webkit_web_view_download_uri(self->webview, uri); + + g_signal_connect(download, "decide-destination", G_CALLBACK(download_on_decide_destination), self); + g_signal_connect(download, "received-data", G_CALLBACK(download_on_received_data), self); + g_signal_connect(download, "failed", G_CALLBACK(download_on_failed), self); + + // TODO: open new window to display download state +} + +static void +window_on_destroy(GtkWindow *window, struct browse_client *client) +{ + g_object_unref(window); + + free(client); + + browse.clients--; + browse.shutdown = browse.clients == 0; +} + +static gboolean +event_controller_on_key_pressed(GtkEventController *controller, guint keyval, guint keycode, + GdkModifierType state, struct browse_client *client) +{ + (void) controller; + (void) keycode; + + for (size_t i = 0; i < ARRLEN(keybinds); i++) { + if ((state & GDK_MODIFIER_MASK) == keybinds[i].mod && keyval == keybinds[i].key) { + keybinds[i].handler(client, &keybinds[i].arg); + return TRUE; + } + } + + return FALSE; +} + +static void +webview_on_load_changed(WebKitWebView *webview, WebKitLoadEvent ev, struct browse_client *client) +{ + (void) ev; + + char const *uri = webkit_web_view_get_uri(webview); + browse_update_title(client, uri); +} + +static gboolean +webview_on_decide_policy(WebKitWebView *webview, WebKitPolicyDecision *decision, + WebKitPolicyDecisionType type, struct browse_client *client) +{ + (void) webview; + + switch (type) { + case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: { + WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); + + if (webkit_navigation_action_get_frame_name(action)) { + webkit_policy_decision_ignore(decision); + } else { + webkit_policy_decision_use(decision); + } + } break; + + case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: { + WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); + + switch (webkit_navigation_action_get_navigation_type(action)) { + case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: + case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: + case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: + case WEBKIT_NAVIGATION_TYPE_RELOAD: + case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: { + char const *uri = webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)); + browse_new(uri, client); + } break; + + default: + break; + } + + webkit_policy_decision_ignore(decision); + } break; + + case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: { + WebKitResponsePolicyDecision *response_decision = WEBKIT_RESPONSE_POLICY_DECISION(decision); + WebKitURIResponse *response = webkit_response_policy_decision_get_response(response_decision); + + char const *uri = webkit_uri_response_get_uri(response); + + if (g_str_has_suffix(uri, "/favicon.ico")) { + webkit_policy_decision_ignore(decision); + break; + } + + if (webkit_response_policy_decision_is_mime_type_supported(response_decision)) { + webkit_policy_decision_use(decision); + } else { + webkit_policy_decision_ignore(decision); + + browse_download_uri(client, uri); + } + } break; + + default: + fprintf(stderr, "Unknown policy decision type: %d, ignoring\n", type); + webkit_policy_decision_ignore(decision); + break; + } + + return TRUE; +} + +static gboolean +download_on_decide_destination(WebKitDownload *download, gchar const *suggested_filename, + struct browse_client *client) +{ + (void) client; + + char fpath[PATH_MAX]; + snprintf(fpath, sizeof fpath, download_fpath_fmt, suggested_filename); + webkit_download_set_destination(download, fpath); + + return TRUE; +} + +static void +download_on_received_data(WebKitDownload *download, guint64 length, struct browse_client *client) +{ + (void) length; + (void) client; + + fprintf(stderr, "download %.03lf%% complete\n", 100 * webkit_download_get_estimated_progress(download)); + + // TODO: graphical progress bar +} + +static void +download_on_failed(WebKitDownload *download, WebKitDownloadError error, struct browse_client *client) +{ + (void) download; + (void) client; + + fprintf(stderr, "download error: "); + switch (error) { + case WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER: fprintf(stderr, "CANCELLED_BY_USER"); break; + case WEBKIT_DOWNLOAD_ERROR_DESTINATION: fprintf(stderr, "DESTINATION"); break; + case WEBKIT_DOWNLOAD_ERROR_NETWORK: fprintf(stderr, "NETWORK"); break; + default: fprintf(stderr, "UNKNOWN"); break; + } + fprintf(stderr, "\n"); + + // TODO: graphical error +}