[epiphany] flatpak: Implement view page source
- From: Michael Catanzaro <mcatanzaro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany] flatpak: Implement view page source
- Date: Mon, 4 Dec 2017 17:33:35 +0000 (UTC)
commit 5d7e8272f00fc817b29fda86aaefd928997af770
Author: Michael Catanzaro <mcatanzaro igalia com>
Date: Mon Dec 4 11:20:43 2017 -0600
flatpak: Implement view page source
This was way too hard!
We really need to reimplement in-browser view source mode. But we have
always supported external view source mode, using the default text/plain
handler, via a hidden GSetting. So even once we restore in-browser view
source, we will still want to keep this code around.
We have to use the portal D-Bus API manually because the only way to get
it using GLib is to use g_app_info_launch_default_for_uri(), and that
will always pick Epiphany itself as the default handler, because
Epiphany supports opening HTML files. The portal is only accessible via
a fallback path.
https://bugzilla.gnome.org/show_bug.cgi?id=787849
lib/ephy-flatpak-utils.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++
lib/ephy-flatpak-utils.h | 10 ++-
src/window-commands.c | 84 +++++++++++++++--
3 files changed, 320 insertions(+), 10 deletions(-)
---
diff --git a/lib/ephy-flatpak-utils.c b/lib/ephy-flatpak-utils.c
index 84c06f9..86fff89 100644
--- a/lib/ephy-flatpak-utils.c
+++ b/lib/ephy-flatpak-utils.c
@@ -18,11 +18,247 @@
* along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
*/
+/* For O_PATH */
+#define _GNU_SOURCE
+
#include <config.h>
#include "ephy-flatpak-utils.h"
+#ifdef __linux__
+
+#include <errno.h>
+#include <fcntl.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
gboolean
ephy_is_running_inside_flatpak (void)
{
return g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
}
+
+static void
+response_cb (GDBusConnection *connection,
+ const char *sender_name,
+ const char *object_path,
+ const char *interface_name,
+ const char *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ guint32 response;
+ guint signal_id;
+
+ signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
+ g_dbus_connection_signal_unsubscribe (connection, signal_id);
+
+ g_task_return_error_if_cancelled (task);
+
+ g_variant_get (parameters, "(u@a{sv})", &response, NULL);
+ if (response == 0)
+ g_task_return_boolean (task, TRUE);
+ else if (response == 1)
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Operation cancelled");
+ else /* yes, this is abuse of G_IO_ERROR, but I don't want to make a new error quark */
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Portal failed to open file");
+}
+
+static void
+open_file_complete_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GTask *task = G_TASK (user_data);
+ GVariant *return_value = NULL;
+ const char *handle;
+ char *object_path = NULL;
+ GError *error = NULL;
+
+ return_value = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, NULL, result, &error);
+ if (!return_value) {
+ g_warning ("Failed to open file via portal: %s", error->message);
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ /* Copied from Gio source code. To understand the signal resubscription
+ * dance, refer to the org.freedesktop.portal.Request documentation. */
+ g_variant_get (return_value, "(o)", &object_path);
+ handle = (const char *)g_object_get_data (G_OBJECT (task), "handle");
+ if (strcmp (handle, object_path) != 0) {
+ GDBusConnection *connection;
+ guint signal_id;
+
+ connection = g_dbus_proxy_get_connection (proxy);
+ signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
+ g_dbus_connection_signal_unsubscribe (connection, signal_id);
+
+ signal_id = g_dbus_connection_signal_subscribe (connection,
+ "org.freedesktop.portal.Desktop",
+ "org.freedesktop.portal.Request",
+ "Response",
+ handle,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ response_cb,
+ task,
+ NULL);
+ g_object_set_data (G_OBJECT (task), "signal-id", GUINT_TO_POINTER (signal_id));
+ }
+
+out:
+ if (return_value)
+ g_variant_unref (return_value);
+ if (object_path)
+ g_free (object_path);
+}
+
+static void
+portal_proxy_created_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task;
+ GDBusProxy *proxy;
+ GVariantBuilder builder;
+ GDBusConnection *connection;
+ GUnixFDList *fd_list;
+ int fd;
+ guint signal_id;
+ char *sender;
+ char *token;
+ char *handle;
+ GError *error = NULL;
+
+ task = G_TASK (user_data);
+ fd = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "fd"));
+
+ proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+ if (!proxy) {
+ g_warning ("Failed to create D-Bus proxy for OpenURI portal: %s", error->message);
+ g_task_return_error (task, error);
+ close (fd);
+ return;
+ }
+
+ g_object_set_data_full (G_OBJECT (task), "proxy", proxy, g_object_unref);
+
+ /* Refer to org.freedesktop.portal.Request documentation. */
+ connection = g_dbus_proxy_get_connection (proxy);
+ sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1);
+ for (guint i = 0; sender[i] != '\0'; i++)
+ if (sender[i] == '.')
+ sender[i] = '_';
+ token = g_strdup_printf ("epiphany%u", g_random_int ());
+ handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
+ g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free);
+ g_free (sender);
+
+ signal_id = g_dbus_connection_signal_subscribe (connection,
+ "org.freedesktop.portal.Desktop",
+ "org.freedesktop.portal.Request",
+ "Response",
+ handle,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ response_cb,
+ task,
+ NULL);
+ g_object_set_data (G_OBJECT (task), "signal-id", GUINT_TO_POINTER (signal_id));
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add (&builder, "{sv}", "handle_token", g_variant_new_string (token));
+ g_free (token);
+
+ fd_list = g_unix_fd_list_new_from_array (&fd, 1);
+ g_dbus_proxy_call_with_unix_fd_list (proxy,
+ "OpenFile",
+ g_variant_new ("(s@h@a{sv})",
+ "",
+ g_variant_new("h", 0),
+ g_variant_builder_end (&builder)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ fd_list,
+ NULL,
+ open_file_complete_cb,
+ task);
+ g_object_unref (fd_list);
+}
+
+void
+ephy_open_file_via_flatpak_portal (const char *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ int fd;
+
+ fd = open (path, O_PATH | O_CLOEXEC);
+ if (fd == -1) {
+ g_warning ("Failed to open %s: %s", path, g_strerror (errno));
+ return;
+ }
+
+ task = g_task_new (NULL, cancellable, callback, user_data);
+ g_object_set_data (G_OBJECT (task), "fd", GINT_TO_POINTER (fd));
+
+ /* We have to do this manually. The recommended solution is to use
+ * g_app_info_launch_default_for_uri(), but that will fail if trying
+ * to open anything that Epiphany itself can open... like text files.
+ * The file will be opened in Epiphany, but we want to get an app
+ * chooser. Otherwise, trying to view page source is just going to open
+ * another browser tab displaying the page in question. Ugh. */
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ "org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.OpenURI",
+ NULL,
+ portal_proxy_created_cb,
+ task);
+}
+
+gboolean
+ephy_open_file_via_flatpak_portal_finish (GAsyncResult *result,
+ GError **error)
+{
+ gboolean ret;
+
+ ret = g_task_propagate_boolean (G_TASK (result), error);
+ g_object_unref (result);
+ return ret;
+}
+
+#else /* __linux__ */
+
+gboolean
+ephy_is_running_inside_flatpak (void)
+{
+ return FALSE;
+}
+
+void
+ephy_open_file_via_flatpak_portal (const char *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_assert_not_reached ();
+}
+
+gboolean
+ephy_open_file_via_flatpak_portal_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_assert_not_reached ();
+}
+
+#endif /* __linux__ */
diff --git a/lib/ephy-flatpak-utils.h b/lib/ephy-flatpak-utils.h
index f70a4f1..1af19f2 100644
--- a/lib/ephy-flatpak-utils.h
+++ b/lib/ephy-flatpak-utils.h
@@ -20,6 +20,12 @@
#pragma once
-#include <glib.h>
+#include <gio/gio.h>
-gboolean ephy_is_running_inside_flatpak (void);
+gboolean ephy_is_running_inside_flatpak (void);
+void ephy_open_file_via_flatpak_portal (const char *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ephy_open_file_via_flatpak_portal_finish (GAsyncResult *result,
+ GError **error);
diff --git a/src/window-commands.c b/src/window-commands.c
index 93d9616..c7e7f56 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -40,6 +40,7 @@
#include "ephy-file-chooser.h"
#include "ephy-file-helpers.h"
#include "ephy-find-toolbar.h"
+#include "ephy-flatpak-utils.h"
#include "ephy-gui.h"
#include "ephy-header-bar.h"
#include "ephy-history-dialog.h"
@@ -1832,6 +1833,55 @@ view_source_embedded (const char *uri, EphyEmbed *embed)
}
static void
+tmp_source_delete_cb (GObject *source, GAsyncResult *result, gpointer data)
+{
+ GFile *file = G_FILE (source);
+ GError *error = NULL;
+
+ if (!g_file_delete_finish (G_FILE (source), result, &error)) {
+ g_warning ("Failed to delete %s: %s", g_file_get_path (file), error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+}
+
+static void
+delete_tmp_source_file (GOutputStream *ostream)
+{
+ char *uri;
+ GFile *file;
+
+ g_object_get (G_OBJECT (ostream), "ephy-save-temp-source-uri", &uri, NULL);
+ file = g_file_new_for_uri (uri);
+ g_file_delete_async (file, G_PRIORITY_LOW, NULL, tmp_source_delete_cb, NULL);
+
+ g_free (uri);
+}
+
+static void
+portal_opened_cb (GObject *source, GAsyncResult *result, gpointer data)
+{
+ char *path = data;
+ GFile *file;
+ gboolean ret;
+ GError *error = NULL;
+
+ ret = ephy_open_file_via_flatpak_portal_finish (result, &error);
+ if (!ret) {
+ /* It can be canceled by the user, even without a cancellable. */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to open file via flatpak portal: %s", error->message);
+ g_error_free (error);
+ }
+
+ file = g_file_new_for_path (path);
+ g_free (path);
+
+ g_file_delete_async (file, G_PRIORITY_LOW, NULL, tmp_source_delete_cb, NULL);
+}
+
+static void
save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointer data)
{
const char *uri;
@@ -1842,6 +1892,7 @@ save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointe
if (error) {
g_warning ("Unable to close file: %s", error->message);
g_error_free (error);
+ delete_tmp_source_file (ostream);
return;
}
@@ -1849,6 +1900,14 @@ save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointe
file = g_file_new_for_uri (uri);
+ if (ephy_is_running_inside_flatpak ()) {
+ const char *path;
+
+ path = g_file_get_path (file);
+ ephy_open_file_via_flatpak_portal (path, NULL, portal_opened_cb, g_strdup (path));
+ goto out;
+ }
+
if (!ephy_file_launch_handler ("text/plain", file, gtk_get_current_event_time ())) {
/* Fallback to view the source inside the browser */
EphyEmbed *embed;
@@ -1859,8 +1918,9 @@ save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointe
"ephy-save-temp-source-embed");
view_source_embedded (uri, embed);
}
- g_object_unref (ostream);
+out:
+ g_object_unref (ostream);
g_object_unref (file);
}
@@ -1915,6 +1975,7 @@ get_main_resource_data_cb (WebKitWebResource *resource, GAsyncResult *result, GO
if (error) {
g_warning ("Unable to get main resource data: %s", error->message);
g_error_free (error);
+ delete_tmp_source_file (ostream);
return;
}
@@ -1974,19 +2035,26 @@ save_temp_source (EphyEmbed *embed,
{
GFile *file;
char *tmp, *base;
- const char *static_temp_dir;
- static_temp_dir = ephy_file_tmp_dir ();
- if (static_temp_dir == NULL) {
- return;
+ if (ephy_is_running_inside_flatpak ()) {
+ /* It has to go here because the portal has no access to our tmpfs.
+ * This means we have to delete it manually! */
+ base = g_build_filename (g_get_user_cache_dir (), "tmp", "viewsourceXXXXXX", NULL);
+ } else {
+ const char *static_temp_dir;
+
+ static_temp_dir = ephy_file_tmp_dir ();
+ if (static_temp_dir == NULL)
+ return;
+
+ base = g_build_filename (static_temp_dir, "viewsourceXXXXXX", NULL);
}
- base = g_build_filename (static_temp_dir, "viewsourceXXXXXX", NULL);
tmp = ephy_file_tmp_filename (base, "html");
g_free (base);
- if (tmp == NULL) {
+
+ if (tmp == NULL)
return;
- }
file = g_file_new_for_path (tmp);
g_file_replace_async (file, NULL, FALSE,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]