[gnome-software/mwleeds/pwa-plugin] fixup! epiphany: Rewrite plugin (WIP)



commit b42842b423dd0db0b06401a0a3e756127a99dc4b
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Tue Jan 25 11:31:29 2022 -0800

    fixup! epiphany: Rewrite plugin (WIP)

 plugins/epiphany/gs-plugin-epiphany.c              | 214 +++++++++++++++------
 .../epiphany/org.gnome.Epiphany.WebAppProvider.xml |  89 ++-------
 2 files changed, 172 insertions(+), 131 deletions(-)
---
diff --git a/plugins/epiphany/gs-plugin-epiphany.c b/plugins/epiphany/gs-plugin-epiphany.c
index 6a97bcbfc..63e62a68a 100644
--- a/plugins/epiphany/gs-plugin-epiphany.c
+++ b/plugins/epiphany/gs-plugin-epiphany.c
@@ -9,6 +9,9 @@
 #include <config.h>
 #include <glib/gi18n.h>
 #include <gnome-software.h>
+#include <fcntl.h>
+#include <gio/gunixfdlist.h>
+#include <glib/gstdio.h>
 
 #include "gs-epiphany-generated.h"
 #include "gs-plugin-epiphany.h"
@@ -17,9 +20,10 @@
  * SECTION:
  * This plugin uses Epiphany to install, launch, and uninstall web applications.
  *
- * If the org.gnome.Epiphany.WebAppProvider D-Bus interface is not present then
- * it self-disables. This should work with both Flatpak'd and not Flatpak'd
- * Epiphany, for new enough versions of Epiphany.
+ * If the org.gnome.Epiphany.WebAppProvider D-Bus interface is not present or
+ * the DynamicLauncher portal is not available then it self-disables. This
+ * should work with both Flatpak'd and not Flatpak'd Epiphany, for new enough
+ * versions of Epiphany.
  */
 
 struct _GsPluginEpiphany
@@ -27,6 +31,7 @@ struct _GsPluginEpiphany
        GsPlugin parent;
 
        GsEphyWebAppProvider *epiphany_proxy;  /* (owned) */
+       GDBusProxy *launcher_portal_proxy;  /* (owned) */
 };
 
 G_DEFINE_TYPE (GsPluginEpiphany, gs_plugin_epiphany, GS_TYPE_PLUGIN)
@@ -65,17 +70,31 @@ gs_epiphany_error_convert (GError **perror)
                return;
 }
 
+/* Run in a worker thread */
 static void
-proxy_new_cb (GObject      *source_object,
-              GAsyncResult *result,
-              gpointer      user_data)
+setup_thread_cb (GTask        *task,
+                gpointer      source_object,
+                gpointer      task_data,
+                GCancellable *cancellable)
 {
-       g_autoptr(GTask) task = g_steal_pointer (&user_data);
-       GsPluginEpiphany *self = g_task_get_source_object (task);
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (source_object);
        g_autofree gchar *name_owner = NULL;
        g_autoptr(GError) local_error = NULL;
+       g_autoptr(GDBusProxy) portal_proxy = NULL;
+       g_autoptr(GVariant) version = NULL;
+       g_autoptr(GVariant) version_child = NULL;
+       g_autoptr(GVariant) version_grandchild = NULL;
 
-       self->epiphany_proxy = gs_ephy_web_app_provider_proxy_new_for_bus_finish (result, &local_error);
+       /* Check that the proxy exists (and is owned; it should auto-start) so
+        * we can disable the plugin for systems which don’t have new enough
+        * Epiphany.
+        */
+       self->epiphany_proxy = gs_ephy_web_app_provider_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                                               G_DBUS_PROXY_FLAGS_NONE,
+                                                                               
"org.gnome.Epiphany.WebAppProvider",
+                                                                               
"/org/gnome/Epiphany/WebAppProvider",
+                                                                               g_task_get_cancellable (task),
+                                                                               &local_error);
        if (self->epiphany_proxy == NULL) {
                gs_epiphany_error_convert (&local_error);
                g_task_return_error (task, g_steal_pointer (&local_error));
@@ -90,6 +109,45 @@ proxy_new_cb (GObject      *source_object,
                return;
        }
 
+       /* Check if the dynamic launcher portal is available and disable otherwise */
+       portal_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL,
+                                                     "org.freedesktop.portal.Desktop",
+                                                     "/org/freedesktop/portal/desktop",
+                                                     "org.freedesktop.DBus.Properties",
+                                                     g_task_get_cancellable (task),
+                                                     &local_error);
+       if (portal_proxy == NULL) {
+               gs_epiphany_error_convert (&local_error);
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
+       }
+       version = g_dbus_proxy_call_sync (portal_proxy, "Get",
+                                         g_variant_new ("(ss)", "org.freedesktop.portal.DynamicLauncher", 
"version"),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1, NULL, NULL);
+       if (version == NULL) {
+               g_task_return_new_error (task, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                        "Dynamic launcher portal not available");
+               return;
+       } else {
+               version_child = g_variant_get_child_value (version, 0);
+               version_grandchild = g_variant_get_child_value (version_child, 0);
+               g_debug ("Found version %" G_GUINT32_FORMAT " of the dynamic launcher portal", 
g_variant_get_uint32 (version_grandchild));
+       }
+
+       /* And finally, make a proxy object for the dynamic launcher portal */
+       self->launcher_portal_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, 
G_DBUS_PROXY_FLAGS_NONE, NULL,
+                                                                    "org.freedesktop.portal.Desktop",
+                                                                    "/org/freedesktop/portal/desktop",
+                                                                    "org.freedesktop.portal.DynamicLauncher",
+                                                                    g_task_get_cancellable (task),
+                                                                    &local_error);
+       if (self->launcher_portal_proxy == NULL) {
+               gs_epiphany_error_convert (&local_error);
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
+       }
+
        g_task_return_boolean (task, TRUE);
 }
 
@@ -106,17 +164,7 @@ gs_plugin_epiphany_setup_async (GsPlugin            *plugin,
 
        g_debug ("%s", G_STRFUNC);
 
-       /* Check that the proxy exists (and is owned; it should auto-start) so
-        * we can disable the plugin for systems which don’t have new enough
-        * Epiphany.
-        */
-       gs_ephy_web_app_provider_proxy_new_for_bus (G_BUS_TYPE_SESSION,
-                                                   G_DBUS_PROXY_FLAGS_NONE,
-                                                   "org.gnome.Epiphany.WebAppProvider",
-                                                   "/org/gnome/Epiphany/WebAppProvider",
-                                                   cancellable,
-                                                   proxy_new_cb,
-                                                   g_steal_pointer (&task));
+       g_task_run_in_thread (task, setup_thread_cb);
 }
 
 static gboolean
@@ -143,6 +191,7 @@ gs_plugin_epiphany_dispose (GObject *object)
        GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (object);
 
        g_clear_object (&self->epiphany_proxy);
+       g_clear_object (&self->launcher_portal_proxy);
 
        G_OBJECT_CLASS (gs_plugin_epiphany_parent_class)->dispose (object);
 }
@@ -168,55 +217,71 @@ gs_plugin_add_installed (GsPlugin      *plugin,
 {
        GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
        g_autoptr(GVariant) webapps_v = NULL;
-       gsize n_webapps;
+       g_auto(GStrv) webapps = NULL;
+       guint n_webapps;
 
-       if (!gs_ephy_web_app_provider_call_get_installed_web_apps_sync (self->epiphany_proxy,
-                                                                       &webapps_v,
-                                                                       cancellable,
-                                                                       error)) {
+       if (!gs_ephy_web_app_provider_call_get_installed_apps_sync (self->epiphany_proxy,
+                                                                   &webapps,
+                                                                   cancellable,
+                                                                   error)) {
                gs_epiphany_error_convert (error);
                return FALSE;
        }
 
-       g_assert (g_variant_is_of_type (webapps_v, G_VARIANT_TYPE ("aa{sv}")));
-       n_webapps = g_variant_n_children (webapps_v);
-       g_debug ("%s: epiphany-webapp-provider returned %" G_GSIZE_FORMAT " installed web apps", G_STRFUNC, 
n_webapps);
-       for (gsize i = 0; i < n_webapps; i++) {
-               g_autoptr(GVariant) webapp_v = g_variant_get_child_value (webapps_v, i);
-               GVariantDict dict;
+       n_webapps = g_strv_length (webapps);
+       g_debug ("%s: epiphany-webapp-provider returned %u installed web apps", G_STRFUNC, n_webapps);
+       for (guint i = 0; i < n_webapps; i++) {
+               const gchar *desktop_file_id = webapps[i];
                const gchar *desktop_path;
                const gchar *name;
                const gchar *url;
-               const gchar *icon_path = NULL;
+               g_autofree char *icon_path = NULL;
+               const gchar *exec;
+               int argc;
+               g_auto (GStrv) argv = NULL;
                guint64 install_date = 0;
-               g_autofree char *app_id = NULL;
                g_autoptr(GsApp) app = NULL;
+               g_autoptr(GDesktopAppInfo) desktop_info = NULL;
+               g_autoptr(GFileInfo) file_info = NULL;
+               g_autoptr(GFile) desktop_file = NULL;
 
-               g_variant_dict_init (&dict, webapp_v);
-               if (!g_variant_dict_lookup (&dict, "desktop-path", "&s", &desktop_path)) {
-                       g_warning ("%s: webapp missing desktop-path", G_STRFUNC);
-                       continue;
-               }
-               if (!g_variant_dict_lookup (&dict, "name", "&s", &name)) {
-                       g_warning ("%s: webapp missing name", G_STRFUNC);
+               g_debug ("%s: Working on installed web app %s", G_STRFUNC, desktop_file_id);
+
+               desktop_info = g_desktop_app_info_new (desktop_file_id);
+               if (desktop_info == NULL) {
+                       g_warning ("Epiphany returned a non-existent desktop ID %s", desktop_file_id);
                        continue;
                }
-               if (!g_variant_dict_lookup (&dict, "url", "&s", &url)) {
-                       g_warning ("%s: webapp missing url", G_STRFUNC);
+
+               name = g_app_info_get_name (G_APP_INFO (desktop_info));
+
+               /* This way of getting the URL is a bit hacky but it's what Epiphany does */
+               exec = g_app_info_get_commandline (G_APP_INFO (desktop_info));
+               if (g_shell_parse_argv (exec, &argc, &argv, NULL)) {
+                       url = argv[argc - 1];
+               } else {
+                       g_warning ("Failed to parse URL for web app %s", desktop_file_id);
                        continue;
                }
-               g_variant_dict_lookup (&dict, "icon-path", "&s", &icon_path);
-               g_variant_dict_lookup (&dict, "install-date", "t", &install_date);
 
-               app_id = g_path_get_basename (desktop_path);
+               icon_path = g_desktop_app_info_get_string (desktop_info, "Icon");
+
+               desktop_path = g_desktop_app_info_get_filename (desktop_info);
+               g_assert (desktop_path);
+               desktop_file = g_file_new_for_path (desktop_path);
+
+               /* FIXME: this should use TIME_CREATED but it does not seem to
+                * be working (copied from Epiphany) */
+               file_info = g_file_query_info (desktop_file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
+               install_date = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
                //TODO do we need to check a cache here, see if a GsApp already exists?
-               app = gs_app_new (app_id);
+               app = gs_app_new (desktop_file_id);
                gs_app_set_management_plugin (app, plugin);
                gs_app_set_state (app, GS_APP_STATE_INSTALLED);
                gs_app_set_kind (app, AS_COMPONENT_KIND_WEB_APP);
                gs_app_set_name (app, GS_APP_QUALITY_NORMAL, name);
                gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, url);
-               gs_app_set_metadata (app, "epiphany::desktop-path", desktop_path);
 
                if (icon_path) {
                        g_autoptr(GFile) icon_file = g_file_new_for_path (icon_path);
@@ -244,6 +309,9 @@ gs_plugin_app_install (GsPlugin      *plugin,
        const char *name;
        GPtrArray *icons;
        g_autofree char *icon_path = NULL;
+       g_autoptr(GUnixFDList) fd_list = NULL;
+       int icon_fd;
+       g_autoptr(GVariant) token_v = NULL;
 
        if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
@@ -281,7 +349,40 @@ gs_plugin_app_install (GsPlugin      *plugin,
                return FALSE;
        }
 
-       //TODO get install_token, do install
+       /* First get a token from xdg-desktop-portal so Epiphany can do the
+        * installation without user confirmation
+        */
+       icon_fd = g_open (icon_path, O_RDONLY | O_CLOEXEC);
+       if (icon_fd == -1) {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't install web app %s because the icon failed to open",
+                            gs_app_get_id (app));
+               return FALSE;
+       }
+       fd_list = g_unix_fd_list_new_from_array (&icon_fd, 1);
+       icon_fd = -1;
+       token_v = g_dbus_proxy_call_with_unix_fd_list_sync (self->launcher_portal_proxy,
+                                                         "RequestInstallToken",
+                                                         g_variant_new ("(sh@a{sv})", name, 0, g_variant_new 
("a{sv}", 0)),
+                                                         G_DBUS_CALL_FLAGS_NONE,
+                                                         -1, fd_list,
+                                                         NULL, cancellable, error);
+       if (token_v == NULL) {
+               gs_epiphany_error_convert (error);
+               return FALSE;
+       }
+
+       /* Then pass the token to Epiphany which will use xdg-desktop-portal to
+        * complete the installation
+        */
+       if (!gs_ephy_web_app_provider_call_install_sync (self->epiphany_proxy,
+                                                        url, name,
+                                                        g_variant_get_string (token_v, NULL),
+                                                        cancellable,
+                                                        error)) {
+               gs_epiphany_error_convert (error);
+               return FALSE;
+       }
 
        return TRUE;
 }
@@ -293,23 +394,14 @@ gs_plugin_app_remove (GsPlugin      *plugin,
                      GError       **error)
 {
        GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
-       const char *desktop_path;
 
        if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
 
-       desktop_path = gs_app_get_metadata_item (app, "epiphany::desktop-path");
-       if (desktop_path == NULL) {
-               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
-                            "Can't remove app %s without desktop file path",
-                            gs_app_get_id (app));
-               return FALSE;
-       }
-
-       if (!gs_ephy_web_app_provider_call_uninstall_web_app_sync (self->epiphany_proxy,
-                                                                  desktop_path,
-                                                                  cancellable,
-                                                                  error)) {
+       if (!gs_ephy_web_app_provider_call_uninstall_sync (self->epiphany_proxy,
+                                                          gs_app_get_id (app),
+                                                          cancellable,
+                                                          error)) {
                gs_epiphany_error_convert (error);
                return FALSE;
        }
diff --git a/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml 
b/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml
index 67acd2ca5..9ff86cd55 100644
--- a/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml
+++ b/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml
@@ -20,68 +20,21 @@
     <property name="version" type="u" access="read"/>
 
     <!--
-        sandboxed:
+        GetInstalledApps:
+        @desktop_file_ids: An array of .desktop file names, one for each
+          installed web app
 
-        This indicates whether Epiphany is running under Flatpak or a
-        Flatpak-compatible sandbox which means that portals will be necessary
-        for installing or removing web apps from the host system.
+        Returns the set of installed Epiphany web applications. The caller can
+        use them with g_desktop_app_info_new() if outside the sandbox.
     -->
-    <property name="sandboxed" type="b" access="read"/>
-
-    <!--
-        GetInstalledWebApps:
-        @webapps: An array of dictionaries, one for each installed Epiphany web
-          app.
-
-        Returns the set of installed Epiphany web applications.
-
-        The following information may be included in each @webapps dictionary:
-        <variablelist>
-          <varlistentry>
-            <term>desktop-path s</term>
-            <listitem><para>
-              The path to the .desktop file on the local filesystem.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term>name s</term>
-            <listitem><para>
-              The human readable name of the application.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term>url s</term>
-            <listitem><para>
-              The URL of the application.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term>icon-path s</term>
-            <listitem><para>
-              The path to the icon on the local filesystem.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term>install-date t</term>
-            <listitem><para>
-              The install time, in seconds since the UNIX epoch.
-            </para></listitem>
-          </varlistentry>
-        </variablelist>
-
-        Clients should gracefully handle unrecognized keys in the @webapps
-        dictionary, to allow extending it in the future without using new
-        interface and method names.
-    -->
-    <method name="GetInstalledWebApps">
-      <arg type="aa{sv}" name="webapps" direction="out" />
+    <method name="GetInstalledApps">
+      <arg type="as" name="webapps" direction="out" />
     </method>
 
     <!--
-        InstallWebApp:
+        Install:
         @url: the URL of the web app
         @name: the human readable name of the web app
-        @icon_path: the path to the icon on the local filesystem
         @install_token: the token acquired via org.freedesktop.portal.InstallDynamicLauncher
 
         Installs a web app. This interface is expected to be used by trusted
@@ -92,32 +45,28 @@
         Software; they should not have to confirm the operation again in a different
         app (Epiphany).
 
-        The @install_token can be the empty string if and only if Epiphany is
-        not running as a Flatpak (or similar sandbox, see the "sandboxed" property).
-
-        The @icon_path only needs to be valid for the duration of the method call,
-        since the icon will be copied elsewhere.
+        The @install_token must be provided so that Epiphany can complete the
+        installation without a user facing dialog. The icon given to
+        org.freedesktop.portal.InstallDynamicLauncher.RequestInstallToken() will
+        be used, and the name given to that method should match the @name given here.
     -->
-    <method name="InstallWebApp">
+    <method name="Install">
       <arg type="s" name="url" direction="in" />
       <arg type="s" name="name" direction="in" />
-      <arg type="s" name="icon_path" direction="in" />
       <arg type="s" name="install_token" direction="in" />
     </method>
 
     <!--
-        UninstallWebApp:
-        @desktop_path: the path to the .desktop file of an installed web app,
-          as returned by GetInstalledWebApps()
+        Uninstall:
+        @desktop_file_id: the filename of the .desktop file for an installed web app
 
-        Uninstalls a web app. Note that the @desktop_path is the target of a
-        symbolic link created in $XDG_DATA_DIRS, not the path of the symbolic
-        link itself. If you use the path returned by GetInstalledWebApps() you
-        don't have to worry about that distinction.
+        Uninstalls a web app. Note that the @desktop_file_id is just a filename
+        not a full path, and it's the same one returned by the
+        GetInstalledWebApps() method.
 
         An error will be returned if the specified web app is not installed.
     -->
-    <method name="UninstallWebApp">
+    <method name="Uninstall">
       <arg type="s" name="desktop_path" direction="in" />
     </method>
   </interface>


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