[nautilus] mime-actions: launch default uri handlers when activating files



commit f5206a6daf0991d91e885a28bb66795a8ae12a41
Author: Ernestas Kulik <ernestask gnome org>
Date:   Sun Apr 9 10:01:22 2017 +0300

    mime-actions: launch default uri handlers when activating files
    
    Currently, Nautilus tries to find the default applications for files
    itself, which does not work well in a sandbox. This commit makes
    Nautilus blindly launch the default applications, which makes use of the
    documents portal indirectly.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=781132

 src/nautilus-mime-actions.c     |  298 +++++++++++++--------------------------
 src/nautilus-program-choosing.c |  284 ++++++++++++++++++++++++++++++++++---
 src/nautilus-program-choosing.h |    7 +
 3 files changed, 368 insertions(+), 221 deletions(-)
---
diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c
index 14fe44b..2fc5320 100644
--- a/src/nautilus-mime-actions.c
+++ b/src/nautilus-mime-actions.c
@@ -67,12 +67,6 @@ typedef struct
 
 typedef struct
 {
-    GAppInfo *application;
-    GList *uris;
-} ApplicationLaunchParameters;
-
-typedef struct
-{
     NautilusWindowSlot *slot;
     gpointer window;
     GtkWindow *parent_window;
@@ -90,6 +84,13 @@ typedef struct
     gboolean user_confirmation;
 } ActivateParameters;
 
+typedef struct
+{
+    ActivateParameters *activation_params;
+    GQueue *uris;
+    GQueue *unhandled_uris;
+} ApplicationLaunchParameters;
+
 struct
 {
     char *name;
@@ -343,27 +344,19 @@ launch_locations_from_file_list (GList *list)
 }
 
 static ApplicationLaunchParameters *
-application_launch_parameters_new (GAppInfo *application,
-                                   GList    *uris)
+application_launch_parameters_new (ActivateParameters *activation_params,
+                                   GQueue             *uris)
 {
     ApplicationLaunchParameters *result;
 
     result = g_new0 (ApplicationLaunchParameters, 1);
-    result->application = g_object_ref (application);
-    result->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
+    result->activation_params = activation_params;
+    result->uris = uris;
+    result->unhandled_uris = g_queue_new ();
 
     return result;
 }
 
-static void
-application_launch_parameters_free (ApplicationLaunchParameters *parameters)
-{
-    g_object_unref (parameters->application);
-    g_list_free_full (parameters->uris, g_free);
-
-    g_free (parameters);
-}
-
 static gboolean
 nautilus_mime_actions_check_if_required_attributes_ready (NautilusFile *file)
 {
@@ -797,114 +790,6 @@ nautilus_mime_file_opens_in_external_app (NautilusFile *file)
     return (activation_action == ACTIVATION_ACTION_OPEN_IN_APPLICATION);
 }
 
-
-static unsigned int
-mime_application_hash (GAppInfo *app)
-{
-    const char *id;
-
-    id = g_app_info_get_id (app);
-
-    if (id == NULL)
-    {
-        return GPOINTER_TO_UINT (app);
-    }
-
-    return g_str_hash (id);
-}
-
-static void
-list_to_parameters_foreach (GAppInfo  *application,
-                            GList     *uris,
-                            GList    **ret)
-{
-    ApplicationLaunchParameters *parameters;
-
-    uris = g_list_reverse (uris);
-
-    parameters = application_launch_parameters_new
-                     (application, uris);
-    *ret = g_list_prepend (*ret, parameters);
-}
-
-
-/**
- * make_activation_parameters
- *
- * Construct a list of ApplicationLaunchParameters from a list of NautilusFiles,
- * where files that have the same default application are put into the same
- * launch parameter, and others are put into the unhandled_files list.
- *
- * @files: Files to use for construction.
- * @unhandled_files: Files without any default application will be put here.
- *
- * Return value: Newly allocated list of ApplicationLaunchParameters.
- **/
-static GList *
-make_activation_parameters (GList  *uris,
-                            GList **unhandled_uris)
-{
-    GList *ret, *l, *app_uris;
-    NautilusFile *file;
-    GAppInfo *app, *old_app;
-    GHashTable *app_table;
-    char *uri;
-
-    ret = NULL;
-    *unhandled_uris = NULL;
-
-    app_table = g_hash_table_new_full
-                    ((GHashFunc) mime_application_hash,
-                    (GEqualFunc) g_app_info_equal,
-                    (GDestroyNotify) g_object_unref,
-                    (GDestroyNotify) g_list_free);
-
-    for (l = uris; l != NULL; l = l->next)
-    {
-        uri = l->data;
-        file = nautilus_file_get_by_uri (uri);
-
-        app = nautilus_mime_get_default_application_for_file (file);
-        if (app != NULL)
-        {
-            app_uris = NULL;
-
-            if (g_hash_table_lookup_extended (app_table, app,
-                                              (gpointer *) &old_app,
-                                              (gpointer *) &app_uris))
-            {
-                g_hash_table_steal (app_table, old_app);
-
-                app_uris = g_list_prepend (app_uris, uri);
-
-                g_object_unref (app);
-                app = old_app;
-            }
-            else
-            {
-                app_uris = g_list_prepend (NULL, uri);
-            }
-
-            g_hash_table_insert (app_table, app, app_uris);
-        }
-        else
-        {
-            *unhandled_uris = g_list_prepend (*unhandled_uris, uri);
-        }
-        nautilus_file_unref (file);
-    }
-
-    g_hash_table_foreach (app_table,
-                          (GHFunc) list_to_parameters_foreach,
-                          &ret);
-
-    g_hash_table_destroy (app_table);
-
-    *unhandled_uris = g_list_reverse (*unhandled_uris);
-
-    return g_list_reverse (ret);
-}
-
 static gboolean
 file_was_cancelled (NautilusFile *file)
 {
@@ -957,6 +842,16 @@ activation_parameters_free (ActivateParameters *parameters)
 }
 
 static void
+application_launch_parameters_free (ApplicationLaunchParameters *parameters)
+{
+    g_queue_free (parameters->unhandled_uris);
+    g_queue_free (parameters->uris);
+    activation_parameters_free (parameters->activation_params);
+
+    g_free (parameters);
+}
+
+static void
 cancel_activate_callback (gpointer callback_data)
 {
     ActivateParameters *parameters = callback_data;
@@ -1629,21 +1524,69 @@ activate_desktop_file (ActivateParameters *parameters,
 }
 
 static void
+on_launch_default_for_uri (GObject      *source_object,
+                           GAsyncResult *res,
+                           gpointer      user_data)
+{
+    ApplicationLaunchParameters *params;
+    ActivateParameters *activation_params;
+    char *uri;
+    gboolean sandboxed;
+    GError *error = NULL;
+
+    params = user_data;
+    activation_params = params->activation_params;
+    uri = g_queue_pop_head (params->uris);
+    sandboxed = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
+
+    nautilus_launch_default_for_uri_finish (res, &error);
+    if (!sandboxed && error->code != G_IO_ERROR_CANCELLED)
+    {
+        g_queue_push_tail (params->unhandled_uris, uri);
+    }
+
+    if (!g_queue_is_empty (params->uris))
+    {
+        nautilus_launch_default_for_uri_async (g_queue_peek_head (params->uris),
+                                               activation_params->parent_window,
+                                               activation_params->cancellable,
+                                               on_launch_default_for_uri,
+                                               params);
+    }
+    else
+    {
+        gboolean should_close;
+        NautilusWindow *window;
+
+        should_close = activation_params->flags &
+                       NAUTILUS_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
+        window = nautilus_window_slot_get_window (activation_params->slot);
+
+        if (should_close && window != NULL)
+        {
+            nautilus_window_close (window);
+        }
+        else
+        {
+            while ((uri = g_queue_pop_head (params->unhandled_uris)) != NULL)
+            {
+                application_unhandled_uri (activation_params, uri);
+            }
+        }
+
+        application_launch_parameters_free (params);
+    }
+}
+
+static void
 activate_files (ActivateParameters *parameters)
 {
     NautilusFile *file;
     NautilusWindow *window;
     NautilusWindowOpenFlags flags;
-    g_autoptr (GList) open_in_app_parameters = NULL;
-    g_autoptr (GList) unhandled_open_in_app_uris = NULL;
-    ApplicationLaunchParameters *one_parameters;
     int count;
     g_autofree char *old_working_dir = NULL;
     GdkScreen *screen;
-    gint num_apps;
-    gint num_unhandled;
-    gint num_files;
-    gboolean open_files;
     gboolean closed_window;
     g_autoptr (GQueue) launch_desktop_files = NULL;
     g_autoptr (GQueue) launch_files = NULL;
@@ -1890,84 +1833,37 @@ activate_files (ActivateParameters *parameters)
         }
     }
 
-    if (open_in_app_uris != NULL)
+    if (g_queue_is_empty (open_in_app_uris))
     {
-        open_in_app_parameters = make_activation_parameters (g_queue_peek_head_link (open_in_app_uris),
-                                                             &unhandled_open_in_app_uris);
-    }
-
-    num_apps = g_list_length (open_in_app_parameters);
-    num_unhandled = g_list_length (unhandled_open_in_app_uris);
-    num_files = g_queue_get_length (open_in_app_uris);
-    open_files = TRUE;
-
-    if (g_queue_is_empty (open_in_app_uris) &&
-        (!parameters->user_confirmation ||
-         num_files + num_unhandled > SILENT_OPEN_LIMIT) &&
-        num_apps > 1)
-    {
-        GtkDialog *dialog;
-        char *prompt;
-        g_autofree char *detail = NULL;
-        int response;
-
-        pause_activation_timed_cancel (parameters);
-
-        prompt = _("Are you sure you want to open all files?");
-        detail = g_strdup_printf (ngettext ("This will open %d separate application.",
-                                            "This will open %d separate applications.", num_apps), num_apps);
-        dialog = eel_show_yes_no_dialog (prompt, detail,
-                                         _("_OK"), _("_Cancel"),
-                                         parameters->parent_window);
-        response = gtk_dialog_run (dialog);
-        gtk_widget_destroy (GTK_WIDGET (dialog));
-
-        unpause_activation_timed_cancel (parameters);
-
-        if (response != GTK_RESPONSE_YES)
+        window = NULL;
+        if (parameters->slot != NULL)
         {
-            open_files = FALSE;
+            window = nautilus_window_slot_get_window (parameters->slot);
         }
-    }
 
-    if (open_files)
-    {
-        for (l = open_in_app_parameters; l != NULL; l = l->next)
-        {
-            one_parameters = l->data;
-
-            nautilus_launch_application_by_uri (one_parameters->application,
-                                                one_parameters->uris,
-                                                parameters->parent_window);
-            application_launch_parameters_free (one_parameters);
-        }
-
-        for (l = unhandled_open_in_app_uris; l != NULL; l = l->next)
-        {
-            char *uri = l->data;
-
-            /* this does not block */
-            application_unhandled_uri (parameters, uri);
-        }
-    }
-
-    window = NULL;
-    if (parameters->slot != NULL)
-    {
-        window = nautilus_window_slot_get_window (parameters->slot);
-    }
-
-    if (open_in_app_parameters != NULL ||
-        unhandled_open_in_app_uris != NULL)
-    {
         if ((parameters->flags & NAUTILUS_WINDOW_OPEN_FLAG_CLOSE_BEHIND) != 0 &&
             window != NULL)
         {
             nautilus_window_close (window);
         }
+
+        activation_parameters_free (parameters);
     }
+    else
+    {
+        const char *uri;
+        ApplicationLaunchParameters *params;
 
-    activation_parameters_free (parameters);
+        uri = g_queue_peek_head (open_in_app_uris);
+        params = application_launch_parameters_new (parameters,
+                                                    g_queue_copy (open_in_app_uris));
+
+        nautilus_launch_default_for_uri_async (uri,
+                                               parameters->parent_window,
+                                               parameters->cancellable,
+                                               on_launch_default_for_uri,
+                                               params);
+    }
 }
 
 static void
diff --git a/src/nautilus-program-choosing.c b/src/nautilus-program-choosing.c
index 92c6ddc..51d2e45 100644
--- a/src/nautilus-program-choosing.c
+++ b/src/nautilus-program-choosing.c
@@ -86,6 +86,32 @@ nautilus_launch_application (GAppInfo  *application,
     g_list_free_full (uris, g_free);
 }
 
+static GdkAppLaunchContext *
+get_launch_context (GtkWindow *parent_window)
+{
+    GdkDisplay *display;
+    GdkAppLaunchContext *launch_context;
+
+    if (parent_window != NULL)
+    {
+        display = gtk_widget_get_display (GTK_WIDGET (parent_window));
+    }
+    else
+    {
+        display = gdk_display_get_default ();
+    }
+
+    launch_context = gdk_display_get_app_launch_context (display);
+
+    if (parent_window != NULL)
+    {
+        gdk_app_launch_context_set_screen (launch_context,
+                                           gtk_window_get_screen (parent_window));
+    }
+
+    return launch_context;
+}
+
 void
 nautilus_launch_application_by_uri (GAppInfo  *application,
                                     GList     *uris,
@@ -97,8 +123,7 @@ nautilus_launch_application_by_uri (GAppInfo  *application,
     NautilusFile *file;
     gboolean result;
     GError *error;
-    GdkDisplay *display;
-    GdkAppLaunchContext *launch_context;
+    g_autoptr (GdkAppLaunchContext) launch_context = NULL;
     NautilusIconInfo *icon;
     int count, total;
 
@@ -121,22 +146,7 @@ nautilus_launch_application_by_uri (GAppInfo  *application,
     }
     locations = g_list_reverse (locations);
 
-    if (parent_window != NULL)
-    {
-        display = gtk_widget_get_display (GTK_WIDGET (parent_window));
-    }
-    else
-    {
-        display = gdk_display_get_default ();
-    }
-
-    launch_context = gdk_display_get_app_launch_context (display);
-
-    if (parent_window != NULL)
-    {
-        gdk_app_launch_context_set_screen (launch_context,
-                                           gtk_window_get_screen (parent_window));
-    }
+    launch_context = get_launch_context (parent_window);
 
     file = nautilus_file_get_by_uri (uris->data);
     icon = nautilus_file_get_icon (file,
@@ -172,8 +182,6 @@ nautilus_launch_application_by_uri (GAppInfo  *application,
                                          &error);
     }
 
-    g_object_unref (launch_context);
-
     if (result)
     {
         for (l = uris; l != NULL; l = l->next)
@@ -430,3 +438,239 @@ nautilus_launch_desktop_file (GdkScreen   *screen,
     g_object_unref (context);
     g_object_unref (app_info);
 }
+
+/* HAX
+ *
+ * TODO: remove everything below once it’s doable from GTK+.
+ *
+ * Context: https://bugzilla.gnome.org/show_bug.cgi?id=781132 and
+ *          https://bugzilla.gnome.org/show_bug.cgi?id=779312
+ *
+ * In a sandboxed environment, this is needed to able to get the actual
+ * result of the operation, since gtk_show_uri_on_window () neither blocks
+ * nor returns a useful value.
+ */
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif
+
+typedef void (*GtkWindowHandleExported) (GtkWindow               *window,
+                                         const char              *handle,
+                                         gpointer                 user_data);
+
+#ifdef GDK_WINDOWING_WAYLAND
+typedef struct
+{
+    GtkWindow *window;
+    GtkWindowHandleExported callback;
+    gpointer user_data;
+} WaylandWindowHandleExportedData;
+
+static void
+wayland_window_handle_exported (GdkWindow  *window,
+                                const char *wayland_handle_str,
+                                gpointer    user_data)
+{
+    WaylandWindowHandleExportedData *data = user_data;
+    char *handle_str;
+
+    handle_str = g_strdup_printf ("wayland:%s", wayland_handle_str);
+    data->callback (data->window, handle_str, data->user_data);
+    g_free (handle_str);
+
+    g_free (data);
+}
+#endif
+
+static gboolean
+window_export_handle (GtkWindow               *window,
+                      GtkWindowHandleExported  callback,
+                      gpointer                 user_data)
+{
+
+#ifdef GDK_WINDOWING_X11
+    if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+    {
+        GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+        char *handle_str;
+        guint32 xid = (guint32) gdk_x11_window_get_xid (gdk_window);
+
+        handle_str = g_strdup_printf ("x11:%x", xid);
+        callback (window, handle_str, user_data);
+
+        return TRUE;
+    }
+#endif
+#ifdef GDK_WINDOWING_WAYLAND
+    if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+    {
+        GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+        WaylandWindowHandleExportedData *data;
+
+        data = g_new0 (WaylandWindowHandleExportedData, 1);
+        data->window = window;
+        data->callback = callback;
+        data->user_data = user_data;
+
+        if (!gdk_wayland_window_export_handle (gdk_window,
+                                               wayland_window_handle_exported,
+                                               data,
+                                               g_free))
+        {
+            g_free (data);
+            return FALSE;
+        }
+        else
+        {
+            return TRUE;
+        }
+    }
+#endif
+
+    g_warning ("Couldn't export handle, unsupported windowing system");
+
+    return FALSE;
+}
+
+void
+gtk_window_unexport_handle (GtkWindow *window)
+{
+#ifdef GDK_WINDOWING_WAYLAND
+    if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
+    {
+        GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+
+        gdk_wayland_window_unexport_handle (gdk_window);
+    }
+#endif
+}
+
+static void
+on_launch_default_for_uri (GObject      *source,
+                           GAsyncResult *result,
+                           gpointer      data)
+{
+    GTask *task;
+    GtkWindow *window;
+    gboolean success;
+    GError *error = NULL;
+
+    task = data;
+    window = g_task_get_source_object (task);
+
+    success = g_app_info_launch_default_for_uri_finish (result, &error);
+
+    if (window)
+    {
+        gtk_window_unexport_handle (window);
+    }
+
+    if (success)
+    {
+        g_task_return_boolean (task, success);
+    }
+    else
+    {
+        g_task_return_error (task, error);
+    }
+
+    /* Reffed in the call to window_export_handle */
+    g_object_unref (task);
+}
+
+static void
+on_window_handle_export (GtkWindow  *window,
+                         const char *handle_str,
+                         gpointer    user_data)
+{
+    GTask *task = user_data;
+    GAppLaunchContext *context = g_task_get_task_data (task);
+    const char *uri;
+
+    uri = g_object_get_data (G_OBJECT (context), "uri");
+
+    g_app_launch_context_setenv (context, "PARENT_WINDOW_ID", handle_str);
+
+    g_app_info_launch_default_for_uri_async (uri,
+                                             context,
+                                             g_task_get_cancellable (task),
+                                             on_launch_default_for_uri,
+                                             task);
+}
+
+static void
+launch_default_for_uri_thread_func (GTask        *task,
+                                    gpointer      source_object,
+                                    gpointer      task_data,
+                                    GCancellable *cancellable)
+{
+    GAppLaunchContext *launch_context;
+    const char *uri;
+    gboolean success;
+    GError *error = NULL;
+
+    launch_context = task_data;
+    uri = g_object_get_data (G_OBJECT (launch_context), "uri");
+    success = g_app_info_launch_default_for_uri (uri, launch_context, &error);
+
+    if (success)
+    {
+        g_task_return_boolean (task, success);
+    }
+    else
+    {
+        g_task_return_error (task, error);
+    }
+}
+
+void
+nautilus_launch_default_for_uri_async  (const char          *uri,
+                                        GtkWindow           *parent_window,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             callback_data)
+{
+    g_autoptr (GdkAppLaunchContext) launch_context = NULL;
+    g_autoptr (GTask) task = NULL;
+
+    g_return_if_fail (uri != NULL);
+
+    launch_context = get_launch_context (parent_window);
+    task = g_task_new (parent_window, cancellable, callback, callback_data);
+
+    gdk_app_launch_context_set_timestamp (launch_context, GDK_CURRENT_TIME);
+
+    g_object_set_data_full (G_OBJECT (launch_context),
+                            "uri", g_strdup (uri), g_free);
+    g_task_set_task_data (task,
+                          g_object_ref (launch_context), g_object_unref);
+
+    if (parent_window != NULL)
+    {
+        gboolean handle_exported;
+
+        handle_exported = window_export_handle (parent_window,
+                                                on_window_handle_export,
+                                                g_object_ref (task));
+
+        if (handle_exported)
+        {
+            /* Launching will now be handled from the callback */
+            return;
+        }
+    }
+
+    g_task_run_in_thread (task, launch_default_for_uri_thread_func);
+}
+
+gboolean
+nautilus_launch_default_for_uri_finish (GAsyncResult  *result,
+                                        GError       **error)
+{
+    g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+    return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/* END OF HAX */
diff --git a/src/nautilus-program-choosing.h b/src/nautilus-program-choosing.h
index b92fc98..ded108a 100644
--- a/src/nautilus-program-choosing.h
+++ b/src/nautilus-program-choosing.h
@@ -52,5 +52,12 @@ void nautilus_launch_desktop_file                   (GdkScreen
                                                      const char                        *desktop_file_uri,
                                                      const GList                       *parameter_uris,
                                                      GtkWindow                         *parent_window);
+void nautilus_launch_default_for_uri_async          (const char                        *uri,
+                                                     GtkWindow                         *parent_window,
+                                                     GCancellable                      *cancellable,
+                                                     GAsyncReadyCallback                callback,
+                                                     gpointer                           callback_data);
+gboolean nautilus_launch_default_for_uri_finish     (GAsyncResult                      *result,
+                                                     GError                           **error);
 
 #endif /* NAUTILUS_PROGRAM_CHOOSING_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]