[gnome-software/mwleeds/pwa-plugin] fixup! epiphany: Rewrite plugin (WIP)
- From: Phaedrus Leeds <mwleeds src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/mwleeds/pwa-plugin] fixup! epiphany: Rewrite plugin (WIP)
- Date: Tue, 25 Jan 2022 19:31:44 +0000 (UTC)
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]