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:
A | api.c | | | 132 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | browse.c | | | 269 | ++++--------------------------------------------------------------------------- |
M | browse.h | | | 60 | ++++++++++++++++++++++++++++++++++++++---------------------- |
M | config.def.h | | | 16 | ++++++++++++---- |
A | webview.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
+}