v0.0.61 - Release: fix Save Image context menu — add download-started handler with file chooser dialog
This commit is contained in:
+2
-2
@@ -11,9 +11,9 @@
|
||||
#ifndef SOVEREIGN_BROWSER_VERSION_H
|
||||
#define SOVEREIGN_BROWSER_VERSION_H
|
||||
|
||||
#define SB_VERSION "v0.0.60"
|
||||
#define SB_VERSION "v0.0.61"
|
||||
#define SB_VERSION_MAJOR 0
|
||||
#define SB_VERSION_MINOR 0
|
||||
#define SB_VERSION_PATCH 60
|
||||
#define SB_VERSION_PATCH 61
|
||||
|
||||
#endif /* SOVEREIGN_BROWSER_VERSION_H */
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "profile.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
|
||||
#include <string.h>
|
||||
@@ -18,6 +19,148 @@
|
||||
* wants the per-user context. */
|
||||
static WebKitWebContext *g_ctx = NULL;
|
||||
|
||||
/* ── Download handling ─────────────────────────────────────────────── *
|
||||
* WebKitGTK fires the "download-started" signal on the WebKitWebContext
|
||||
* whenever the user triggers a download — most notably the "Save Image"
|
||||
* context-menu item on an <img>. In the webkit2gtk-4.1 API the download
|
||||
* does NOT auto-prompt for a destination: the application must handle the
|
||||
* download's "decide-destination" signal (which supplies a suggested
|
||||
* filename), call webkit_download_set_destination(), and return TRUE.
|
||||
* If nobody handles "decide-destination", WebKit writes the file to
|
||||
* G_USER_DIRECTORY_DOWNLOAD using the suggested name — but on many
|
||||
* minimal/containerized setups that directory resolves to a path the
|
||||
* user never sees, so "Save Image" appears to do nothing.
|
||||
*
|
||||
* Our flow:
|
||||
* download-started (context) → take a ref, wire finished/failed
|
||||
* decide-destination (download) → show a GtkFileChooserDialog pre-filled
|
||||
* with the suggested filename, set the
|
||||
* chosen destination, return TRUE
|
||||
* finished / failed → unref the download
|
||||
*
|
||||
* Since 2.40, returning TRUE from "decide-destination" without
|
||||
* immediately calling webkit_download_set_destination() is allowed: the
|
||||
* download pauses until the destination is set or the download is
|
||||
* cancelled. We use that to run a modal file chooser inside the handler.
|
||||
*/
|
||||
|
||||
/* Find a reasonable parent GtkWindow for the file chooser. We prefer the
|
||||
* currently-active (focused) top-level window; if none is focused we fall
|
||||
* back to the first visible top-level. web_context.c deliberately does
|
||||
* not depend on tab_manager.c's g_active_window, so this stays self-
|
||||
* contained. Returns NULL if no usable window is found. */
|
||||
static GtkWindow *pick_dialog_parent(void) {
|
||||
GList *toplevels = gtk_window_list_toplevels();
|
||||
GtkWindow *active = NULL;
|
||||
GtkWindow *first_visible = NULL;
|
||||
|
||||
for (GList *l = toplevels; l != NULL; l = l->next) {
|
||||
GtkWindow *w = GTK_WINDOW(l->data);
|
||||
if (!gtk_widget_get_visible(GTK_WIDGET(w))) continue;
|
||||
if (first_visible == NULL) first_visible = w;
|
||||
if (gtk_window_is_active(w)) { active = w; break; }
|
||||
}
|
||||
|
||||
GtkWindow *result = active ? active : first_visible;
|
||||
if (result != NULL) g_object_ref_sink(G_OBJECT(result));
|
||||
g_list_free(toplevels);
|
||||
return result; /* caller unrefs */
|
||||
}
|
||||
|
||||
/* Reduce a possibly-full suggested path to its final path segment so the
|
||||
* save dialog never starts in an unexpected directory. Returns a newly
|
||||
* allocated string. */
|
||||
static char *basename_of(const char *name) {
|
||||
if (name == NULL || name[0] == '\0') return NULL;
|
||||
const char *slash = strrchr(name, '/');
|
||||
return g_strdup(slash ? slash + 1 : name);
|
||||
}
|
||||
|
||||
static void on_download_finished(WebKitDownload *dl, gpointer user) {
|
||||
(void)user;
|
||||
const char *dest = webkit_download_get_destination(dl);
|
||||
g_print("[download] Finished: %s\n", dest ? dest : "(no destination)");
|
||||
g_object_unref(dl);
|
||||
}
|
||||
|
||||
static void on_download_failed(WebKitDownload *dl, GError *err, gpointer user) {
|
||||
(void)user;
|
||||
g_printerr("[download] Failed: %s\n", err ? err->message : "(unknown)");
|
||||
g_object_unref(dl);
|
||||
}
|
||||
|
||||
/* "decide-destination" handler. WebKit passes the suggested filename
|
||||
* (derived from the response Content-Disposition / URL). We show a save
|
||||
* dialog, set the destination, and return TRUE. Returning TRUE without
|
||||
* setting a destination is explicitly supported since 2.40 and pauses
|
||||
* the download until we set one (or cancel). */
|
||||
static gboolean on_download_decide_destination(WebKitDownload *dl,
|
||||
const char *suggested,
|
||||
gpointer user) {
|
||||
(void)user;
|
||||
|
||||
char *default_name = basename_of(suggested);
|
||||
if (default_name == NULL || default_name[0] == '\0') {
|
||||
g_free(default_name);
|
||||
default_name = g_strdup("download");
|
||||
}
|
||||
|
||||
GtkWindow *parent = pick_dialog_parent();
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new(
|
||||
"Save File", parent, GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Save", GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
if (parent) g_object_unref(parent);
|
||||
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
|
||||
gtk_file_chooser_set_current_name(chooser, default_name);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE);
|
||||
|
||||
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
char *path = (resp == GTK_RESPONSE_ACCEPT)
|
||||
? gtk_file_chooser_get_filename(chooser)
|
||||
: NULL;
|
||||
gtk_widget_destroy(dialog);
|
||||
g_free(default_name);
|
||||
|
||||
if (path == NULL) {
|
||||
/* User cancelled — abort the download. */
|
||||
webkit_download_cancel(dl);
|
||||
return TRUE; /* we handled it (by cancelling) */
|
||||
}
|
||||
|
||||
char *uri = g_filename_to_uri(path, NULL, NULL);
|
||||
g_free(path);
|
||||
if (uri == NULL) {
|
||||
webkit_download_cancel(dl);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
webkit_download_set_destination(dl, uri);
|
||||
g_free(uri);
|
||||
return TRUE; /* stop other handlers; destination is now set */
|
||||
}
|
||||
|
||||
static void on_download_started(WebKitWebContext *ctx,
|
||||
WebKitDownload *dl,
|
||||
gpointer user_data) {
|
||||
(void)ctx;
|
||||
(void)user_data;
|
||||
|
||||
/* Hold a ref for the lifetime of the download so our finished/failed
|
||||
* handlers can safely access it; we unref there. */
|
||||
g_object_ref(dl);
|
||||
|
||||
g_signal_connect(dl, "decide-destination",
|
||||
G_CALLBACK(on_download_decide_destination), NULL);
|
||||
g_signal_connect(dl, "finished",
|
||||
G_CALLBACK(on_download_finished), NULL);
|
||||
g_signal_connect(dl, "failed",
|
||||
G_CALLBACK(on_download_failed), NULL);
|
||||
}
|
||||
|
||||
/* ── Security / TLS / favicon configuration ────────────────────────── *
|
||||
* Applied to every context we build (per-user and ephemeral). Mirrors
|
||||
* the configuration that main.c used to apply to the default context.
|
||||
@@ -67,6 +210,12 @@ static void configure_context(WebKitWebContext *ctx) {
|
||||
g_get_tmp_dir());
|
||||
}
|
||||
webkit_web_context_set_favicon_database_directory(ctx, fav_dir);
|
||||
|
||||
/* Handle downloads — most importantly the "Save Image" context-menu
|
||||
* action. Without this handler WebKitGTK cancels the download (and
|
||||
* the user sees nothing happen). See on_download_started() above. */
|
||||
g_signal_connect(ctx, "download-started",
|
||||
G_CALLBACK(on_download_started), NULL);
|
||||
}
|
||||
|
||||
WebKitWebContext *web_context_build_for_pubkey(const char *pubkey_hex) {
|
||||
|
||||
Reference in New Issue
Block a user