From 5757ab6a8a0af67413f19602a388cb1c2b115676 Mon Sep 17 00:00:00 2001 From: Felipe Borges Date: Fri, 20 Dec 2019 13:53:14 +0100 Subject: [PATCH] EogWindow: Use Portals for "Open With" when possible If we detect that Eog is running inside a Flatpak container, we can use the OpenURI Portal[0] to open an AppChooser dialog for the user to pick an appliation. [0] https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.OpenURI Fixes #98 --- data/eog-gear-menu.ui | 9 +-- data/popup-menus.ui | 7 +- src/eog-util.c | 180 ++++++++++++++++++++++++++++++++++++++++++ src/eog-util.h | 6 ++ src/eog-window.c | 140 +++++++++++--------------------- 5 files changed, 240 insertions(+), 102 deletions(-) diff --git a/data/eog-gear-menu.ui b/data/eog-gear-menu.ui index 1e9cb4a3..724156b7 100644 --- a/data/eog-gear-menu.ui +++ b/data/eog-gear-menu.ui @@ -6,11 +6,10 @@ _Open… win.open - - Op_en With - - - + + Op_en With… + win.open-with +
diff --git a/data/popup-menus.ui b/data/popup-menus.ui index aae780c0..ee629d74 100644 --- a/data/popup-menus.ui +++ b/data/popup-menus.ui @@ -2,11 +2,10 @@
- + Open _with - - - + win.open-with +
diff --git a/src/eog-util.c b/src/eog-util.c index a6828a58..3e253dc9 100644 --- a/src/eog-util.c +++ b/src/eog-util.c @@ -22,6 +22,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* For O_PATH */ +#define _GNU_SOURCE + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -37,12 +40,16 @@ #include "eog-debug.h" #include +#include #include #include #include #include #include +#include #include +#include +#include void eog_util_show_help (const gchar *section, GtkWindow *parent) @@ -503,3 +510,176 @@ eog_util_show_file_in_filemanager (GFile *file, GtkWindow *toplevel) if (!done) _eog_util_show_file_in_filemanager_fallback (file, toplevel); } + +/* Portal */ + +gboolean +eog_util_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) +{ + GFile *file = G_FILE (user_data); + guint32 response; + guint signal_id; + + signal_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (file), "signal-id")); + g_dbus_connection_signal_unsubscribe (connection, signal_id); + + g_variant_get (parameters, "(u@a{sv})", &response, NULL); + if (response == 0) { + g_debug ("Opening file"); + } else if (response == 1) { + g_debug ("User cancelled opening file"); + } else { + g_warning ("Failed to open file via portal"); + } +} + +static void +open_file_complete_cb (GObject *source, GAsyncResult *result, gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GFile *file = G_FILE (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) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to open file via portal: %s", error->message); + + goto out; + } + + g_variant_get (return_value, "(o)", &object_path); + handle = (const char *)g_object_get_data (G_OBJECT (file), "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 (file), "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, + file, + NULL); + g_object_set_data (G_OBJECT (file), "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 +open_with_flatpak_portal_cb (GObject *source, GAsyncResult *result, gpointer user_data) +{ + GVariantBuilder builder; + GUnixFDList *fd_list; + GFile *file; + GDBusProxy *proxy; + GDBusConnection *connection; + GError *error = NULL; + guint signal_id; + char *sender, *token, *handle; + int fd; + + file = G_FILE (user_data); + fd = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (file), "fd")); + + proxy = g_dbus_proxy_new_for_bus_finish (result, &error); + if (!proxy) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to create D-Bus proxy for OpenURI portal: %s", error->message); + + close (fd); + return; + } + + 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 ("eog%u", g_random_int ()); + handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token); + + g_object_set_data_full (G_OBJECT (file), "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, + file, + NULL); + g_object_set_data (G_OBJECT (file), "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_variant_builder_add (&builder, "{sv}", "ask", g_variant_new ("b", TRUE)); + 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, + file); + g_object_unref (fd_list); +} + +void +eog_util_open_file_with_flatpak_portal (GFile *file) +{ + const gchar *path; + int fd; + + path = g_file_get_path (file); + fd = open (path, O_PATH | O_CLOEXEC); + if (fd == -1) { + g_warning ("Failed to open %s: %s", path, g_strerror (errno)); + return; + } + + g_object_set_data (G_OBJECT (file), "fd", GINT_TO_POINTER (fd)); + 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, + open_with_flatpak_portal_cb, + file); +} diff --git a/src/eog-util.h b/src/eog-util.h index 9f2ad4c1..d328e1f9 100644 --- a/src/eog-util.h +++ b/src/eog-util.h @@ -69,6 +69,12 @@ G_GNUC_INTERNAL void eog_util_show_file_in_filemanager (GFile *file, GtkWindow *toplevel); +G_GNUC_INTERNAL +gboolean eog_util_is_running_inside_flatpak (void); + +G_GNUC_INTERNAL +void eog_util_open_file_with_flatpak_portal (GFile *file); + G_END_DECLS #endif /* __EOG_UTIL_H__ */ diff --git a/src/eog-window.c b/src/eog-window.c index d6fc3834..75d7c138 100644 --- a/src/eog-window.c +++ b/src/eog-window.c @@ -137,9 +137,6 @@ struct _EogWindowPrivate { GtkWidget *message_area; GtkWidget *properties_dlg; - GMenu *open_with_menu; - GPtrArray *appinfo; - GtkBuilder *gear_menu_builder; GtkWidget *fullscreen_popup; @@ -196,7 +193,6 @@ static void fullscreen_set_timeout (EogWindow *window); static void fullscreen_clear_timeout (EogWindow *window); static void slideshow_set_timeout (EogWindow *window); static void update_action_groups_state (EogWindow *window); -static void eog_window_update_open_with_menu (EogWindow *window, EogImage *image); static void eog_window_list_store_image_added (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, @@ -997,8 +993,6 @@ eog_window_display_image (EogWindow *window, EogImage *image) update_status_bar (window); - eog_window_update_open_with_menu (window, image); - file = eog_image_get_file (image); g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) add_file_to_recent_files, @@ -1046,28 +1040,24 @@ _eog_window_launch_appinfo_with_files (EogWindow *window, } static void -eog_window_action_open_with (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) +app_chooser_dialog_response_cb (GtkDialog *dialog, + gint response_id, + gpointer data) { EogWindow *window; GAppInfo *app; GFile *file; GList *files = NULL; - guint32 index = G_MAXUINT32; - - g_return_if_fail (EOG_IS_WINDOW (user_data)); - window = EOG_WINDOW (user_data); + g_return_if_fail (EOG_IS_WINDOW (data)); - index = g_variant_get_uint32 (parameter); - if (G_UNLIKELY (index >= window->priv->appinfo->len)) - return; + window = EOG_WINDOW (data); - app = g_ptr_array_index (window->priv->appinfo, index); - if (!app) - return; + if (response_id != GTK_RESPONSE_OK) { + goto out; + } + app = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog)); file = eog_image_get_file (window->priv->image); files = g_list_append (files, file); @@ -1075,73 +1065,56 @@ eog_window_action_open_with (GSimpleAction *action, g_list_free (files); g_object_unref (file); + +out: + gtk_widget_destroy (GTK_WIDGET (dialog)); } static void -eog_window_update_open_with_menu (EogWindow *window, EogImage *image) +eog_window_open_file_chooser_dialog (EogWindow *window) { - EogWindowPrivate *priv; - GFile *file; + GtkWidget *dialog; GFileInfo *file_info; - GList *apps = NULL, *li = NULL; - const gchar *mime_type; - guint32 count = 0; + GFile *file; + const gchar *mime_type = NULL; - priv = window->priv; + file = eog_image_get_file (window->priv->image); + file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, NULL, NULL); + mime_type = g_content_type_get_mime_type ( + g_file_info_get_content_type (file_info)); + g_object_unref (file_info); - g_menu_remove_all (priv->open_with_menu); - g_ptr_array_free (priv->appinfo, TRUE); - priv->appinfo = g_ptr_array_new_with_free_func (g_object_unref); + dialog = gtk_app_chooser_dialog_new_for_content_type (GTK_WINDOW (window), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT | + GTK_DIALOG_USE_HEADER_BAR, + mime_type); + gtk_widget_show (dialog); - file = eog_image_get_file (image); - file_info = g_file_query_info (file, - G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, - 0, NULL, NULL); + g_signal_connect_object (dialog, "response", + G_CALLBACK (app_chooser_dialog_response_cb), + window, 0); - if (file_info == NULL) { - g_object_unref (file); - return; - } - - mime_type = g_file_info_get_content_type (file_info); - apps = g_app_info_get_all_for_type (mime_type); - g_object_unref (file_info); - if (!apps) { - g_object_unref (file); - return; - } + g_object_unref (file); +} - for (li = apps; li != NULL; li = li->next) { - GAppInfo *app = li->data; - gchar *label; - GIcon *icon; - GVariant *value; - GMenuItem *item; +static void +eog_window_action_open_with (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + EogWindow *window; - /* Do not include eog itself */ - if (g_ascii_strcasecmp (g_app_info_get_executable (app), - g_get_prgname ()) == 0) { - g_object_unref (app); - continue; - } + g_return_if_fail (EOG_IS_WINDOW (user_data)); + window = EOG_WINDOW (user_data); - label = g_strdup (g_app_info_get_display_name (app)); - item = g_menu_item_new (label, NULL); - g_free (label); - icon = g_app_info_get_icon (app); - g_menu_item_set_icon (item, icon); + if (eog_util_is_running_inside_flatpak ()) { + GFile *file = eog_image_get_file (window->priv->image); - value = g_variant_new_uint32 (count++); - g_menu_item_set_action_and_target_value (item, - "win.open-with", - value); - g_ptr_array_add (priv->appinfo, app); - g_menu_append_item (priv->open_with_menu, item); - g_object_unref (item); + eog_util_open_file_with_flatpak_portal (file); + } else { + eog_window_open_file_chooser_dialog (window); } - - g_object_unref (file); - g_list_free (apps); } static void @@ -3973,7 +3946,7 @@ readonly_state_handler (GSimpleAction *action, static const GActionEntry window_actions[] = { /* Stateless actions on the window. */ { "open", eog_window_action_file_open }, - { "open-with", eog_window_action_open_with, "u" }, + { "open-with", eog_window_action_open_with }, { "open-folder", eog_window_action_open_containing_folder }, { "save", eog_window_action_save }, { "save-as", eog_window_action_save_as }, @@ -4290,12 +4263,6 @@ eog_window_construct_ui (EogWindow *window) gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), fullscreen_button); gtk_widget_show (fullscreen_button); - priv->open_with_menu = g_menu_new (); - priv->appinfo = g_ptr_array_new_with_free_func (g_object_unref); - builder_object = gtk_builder_get_object (builder, "open-with-menu"); - g_menu_append_section (G_MENU (builder_object), - NULL, - G_MENU_MODEL (priv->open_with_menu)); priv->gear_menu_builder = builder; builder = NULL; @@ -4388,10 +4355,7 @@ eog_window_construct_ui (EogWindow *window) builder = gtk_builder_new_from_resource ("/org/gnome/eog/ui/popup-menus.ui"); builder_object = gtk_builder_get_object (builder, "view-popup-menu"); - GObject *open_with_menu = gtk_builder_get_object (builder, "open-with-menu"); - g_menu_append_section (G_MENU (open_with_menu), - NULL, - G_MENU_MODEL (priv->open_with_menu)); + popup_menu = gtk_menu_new_from_model (G_MENU_MODEL (builder_object)); eog_scroll_view_set_popup (EOG_SCROLL_VIEW (priv->view), @@ -4603,16 +4567,6 @@ eog_window_dispose (GObject *object) priv->image = NULL; } - if (priv->open_with_menu != NULL) { - g_object_unref (priv->open_with_menu); - priv->open_with_menu = NULL; - } - - if (priv->appinfo != NULL) { - g_ptr_array_free (priv->appinfo, TRUE); - priv->appinfo = NULL; - } - fullscreen_clear_timeout (window); if (window->priv->fullscreen_popup != NULL) { -- 2.26.0