diff options
-rw-r--r-- | data/meson.build | 7 | ||||
-rw-r--r-- | data/org.gnome.Epiphany.WebAppProvider.service.in | 3 | ||||
-rw-r--r-- | embed/ephy-embed-shell.c | 2 | ||||
-rw-r--r-- | lib/ephy-web-app-utils.c | 154 | ||||
-rw-r--r-- | lib/ephy-web-app-utils.h | 24 | ||||
-rw-r--r-- | org.gnome.Epiphany.Canary.json.in | 3 | ||||
-rw-r--r-- | org.gnome.Epiphany.json | 3 | ||||
-rw-r--r-- | src/ephy-main.c | 2 | ||||
-rw-r--r-- | src/meson.build | 23 | ||||
-rw-r--r-- | src/webapp-provider/ephy-webapp-provider-main.c | 61 | ||||
-rw-r--r-- | src/webapp-provider/ephy-webapp-provider.c | 282 | ||||
-rw-r--r-- | src/webapp-provider/ephy-webapp-provider.h | 36 | ||||
-rw-r--r-- | src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml | 85 | ||||
-rw-r--r-- | src/window-commands.c | 3 | ||||
-rw-r--r-- | tests/ephy-web-app-utils-test.c | 4 |
15 files changed, 659 insertions, 33 deletions
diff --git a/data/meson.build b/data/meson.build index eac6b8224..3108a0060 100644 --- a/data/meson.build +++ b/data/meson.build @@ -96,6 +96,13 @@ configure_file( install_dir: servicedir ) +configure_file( + input: 'org.gnome.Epiphany.WebAppProvider.service.in', + output: 'org.gnome.Epiphany.WebAppProvider.service', + configuration: service_conf, + install_dir: servicedir +) + search_provider_conf = configuration_data() search_provider_conf.set('appid', application_id) search_provider_conf.set('profile', profile != '' ? '/' + profile : '') diff --git a/data/org.gnome.Epiphany.WebAppProvider.service.in b/data/org.gnome.Epiphany.WebAppProvider.service.in new file mode 100644 index 000000000..1ab1699a6 --- /dev/null +++ b/data/org.gnome.Epiphany.WebAppProvider.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.gnome.Epiphany.WebAppProvider +Exec=@libexecdir@/epiphany-webapp-provider diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c index 99d1fb0ef..cc5f9832c 100644 --- a/embed/ephy-embed-shell.c +++ b/embed/ephy-embed-shell.c @@ -340,7 +340,7 @@ web_process_extension_about_apps_message_received_cb (WebKitUserContentManager * g_autofree char *app_id = NULL; app_id = jsc_value_to_string (webkit_javascript_result_get_js_value (message)); - ephy_web_application_delete (app_id); + ephy_web_application_delete (app_id, NULL); } static char * diff --git a/lib/ephy-web-app-utils.c b/lib/ephy-web-app-utils.c index dd2291833..233999942 100644 --- a/lib/ephy-web-app-utils.c +++ b/lib/ephy-web-app-utils.c @@ -242,6 +242,21 @@ ephy_web_application_get_profile_directory (const char *id) return ephy_web_application_get_directory_under (id, g_get_user_data_dir ()); } +/** + * ephy_web_application_get_desktop_path: + * @app: the #EphyWebApplication + * + * Gets the path to the .desktop file for @app + * + * Returns: (transfer full): A newly allocated string. + **/ +char * +ephy_web_application_get_desktop_path (EphyWebApplication *app) +{ + g_autofree char *profile_dir = ephy_web_application_get_profile_directory (app->id); + return g_build_filename (profile_dir, app->desktop_file, NULL); +} + static char * ephy_web_application_get_cache_directory (const char *id) { @@ -256,7 +271,10 @@ ephy_web_application_get_config_directory (const char *id) /** * ephy_web_application_delete: - * @id: the identifier of the web application do delete + * @id: the identifier of the web application to delete + * @out_app_found: return location for a #EphyWebAppFound. This will be set to + * %EPHY_WEB_APP_NOT_FOUND if deleting the app failed due to it not being + * installed, and %EPHY_WEB_APP_FOUND otherwise. * * Deletes all the data associated with a Web Application created by * Epiphany. @@ -264,7 +282,8 @@ ephy_web_application_get_config_directory (const char *id) * Returns: %TRUE if the web app was succesfully deleted, %FALSE otherwise **/ gboolean -ephy_web_application_delete (const char *id) +ephy_web_application_delete (const char *id, + EphyWebAppFound *out_app_found) { g_autofree char *profile_dir = NULL; g_autofree char *cache_dir = NULL; @@ -276,6 +295,9 @@ ephy_web_application_delete (const char *id) g_assert (id); + if (out_app_found) + *out_app_found = EPHY_WEB_APP_FOUND; + profile_dir = ephy_web_application_get_profile_directory (id); if (!profile_dir) return FALSE; @@ -284,6 +306,8 @@ ephy_web_application_delete (const char *id) * exist. */ if (!g_file_test (profile_dir, G_FILE_TEST_IS_DIR)) { g_warning ("No application with id '%s' is installed.\n", id); + if (out_app_found) + *out_app_found = EPHY_WEB_APP_NOT_FOUND; return FALSE; } @@ -330,12 +354,43 @@ ephy_web_application_delete (const char *id) return TRUE; } +/** + * ephy_web_application_delete_by_desktop_file_id: + * @desktop_file_id: the .desktop file name for the web app to be deleted, with + * the extension + * @out_app_found: return location for a #EphyWebAppFound. This will be set to + * %EPHY_WEB_APP_NOT_FOUND if deleting the app failed due to it not being + * installed, and %EPHY_WEB_APP_FOUND otherwise. + * + * Deletes all the data associated with a Web Application created by + * Epiphany. + * + * Returns: %TRUE if the web app was succesfully deleted, %FALSE otherwise + **/ +gboolean +ephy_web_application_delete_by_desktop_file_id (const char *desktop_file_id, + EphyWebAppFound *out_app_found) +{ + const char *id; + g_autofree char *gapp_id = NULL; + + g_assert (desktop_file_id); + + gapp_id = g_strdup (desktop_file_id); + if (g_str_has_suffix (desktop_file_id, ".desktop")) + gapp_id[strlen (desktop_file_id) - strlen (".desktop")] = '\0'; + + id = get_app_id_from_gapplication_id (gapp_id); + + return ephy_web_application_delete (id, out_app_found); +} + static char * create_desktop_file (const char *id, const char *name, const char *address, const char *profile_dir, - GdkPixbuf *icon) + const char *icon_path) { g_autofree char *filename = NULL; g_autoptr (GKeyFile) file = NULL; @@ -365,18 +420,8 @@ create_desktop_file (const char *id, g_key_file_set_value (file, "Desktop Entry", "Type", "Application"); g_key_file_set_value (file, "Desktop Entry", "Categories", "GNOME;GTK;"); - if (icon) { - g_autoptr (GOutputStream) stream = NULL; - g_autofree char *path = NULL; - g_autoptr (GFile) image = NULL; - - path = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL); - image = g_file_new_for_path (path); - - stream = (GOutputStream *)g_file_create (image, 0, NULL, NULL); - gdk_pixbuf_save_to_stream (icon, stream, "png", NULL, NULL, NULL); - g_key_file_set_value (file, "Desktop Entry", "Icon", path); - } + if (icon_path) + g_key_file_set_value (file, "Desktop Entry", "Icon", icon_path); wm_class = g_strconcat (EPHY_WEB_APP_GAPPLICATION_ID_PREFIX, id, NULL); g_key_file_set_value (file, "Desktop Entry", "StartupWMClass", wm_class); @@ -409,7 +454,10 @@ create_desktop_file (const char *id, * @id: the identifier for the new web application * @address: the address of the new web application * @name: the name for the new web application - * @icon: the icon for the new web application + * @icon_pixbuf: the icon for the new web application as a #GdkPixbuf + * @icon_path: the path to the icon, used instead of @icon_pixbuf + * @install_token: the install token acquired via portal, used for + * non-interactive sandboxed installation * @options: the options for the new web application * * Creates a new Web Application for @address. @@ -420,14 +468,19 @@ char * ephy_web_application_create (const char *id, const char *address, const char *name, - GdkPixbuf *icon, + GdkPixbuf *icon_pixbuf, + const char *icon_path, + const char *install_token, EphyWebApplicationOptions options) { g_autofree char *app_file = NULL; g_autofree char *profile_dir = NULL; g_autofree char *desktop_file_path = NULL; + g_autofree char *icon_path_owned = NULL; int fd; + g_return_val_if_fail (!icon_pixbuf || !icon_path, NULL); + /* If there's already a WebApp profile for the contents of this * view, do nothing. */ profile_dir = ephy_web_application_get_profile_directory (id); @@ -454,8 +507,22 @@ ephy_web_application_create (const char *id, } close (fd); + /* Write the icon to a file */ + if (icon_pixbuf) { + g_autoptr (GOutputStream) stream = NULL; + g_autoptr (GFile) image = NULL; + + icon_path_owned = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL); + image = g_file_new_for_path (icon_path_owned); + + stream = (GOutputStream *)g_file_create (image, 0, NULL, NULL); + gdk_pixbuf_save_to_stream (icon_pixbuf, stream, "png", NULL, NULL, NULL); + } else { + icon_path_owned = g_strdup (icon_path); + } + /* Create the deskop file. */ - desktop_file_path = create_desktop_file (id, name, address, profile_dir, icon); + desktop_file_path = create_desktop_file (id, name, address, profile_dir, icon_path_owned); if (desktop_file_path) ephy_web_application_initialize_settings (profile_dir, options); @@ -586,7 +653,6 @@ ephy_web_application_for_profile_directory (const char *profile_dir) g_auto (GStrv) argv = NULL; g_autoptr (GFile) file = NULL; g_autoptr (GFileInfo) file_info = NULL; - guint64 created; g_autoptr (GDate) date = NULL; id = get_app_id_from_profile_directory (profile_dir); @@ -614,10 +680,10 @@ ephy_web_application_for_profile_directory (const char *profile_dir) /* FIXME: this should use TIME_CREATED but it does not seem to be working. */ file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL); - created = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + app->install_date_uint64 = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); date = g_date_new (); - g_date_set_time_t (date, (time_t)created); + g_date_set_time_t (date, (time_t)app->install_date_uint64); g_date_strftime (app->install_date, 127, "%x", date); return g_steal_pointer (&app); @@ -705,7 +771,6 @@ ephy_web_application_get_legacy_application_list (void) return ephy_web_application_get_application_list_internal (TRUE); } - /** * ephy_web_application_free_application_list: * @list: an #EphyWebApplication GList @@ -719,6 +784,51 @@ ephy_web_application_free_application_list (GList *list) } /** + * ephy_web_application_get_desktop_id_list: + * + * Gets a list of the currently installed web applications' .desktop filenames. + * This is useful even though we don't have access to the actual .desktop files + * when running under Flatpak, because we return it over D-Bus in the + * WebAppProvider service. + * + * Returns: (transfer-full): a %NULL-terminated array of strings + **/ +char ** +ephy_web_application_get_desktop_id_list (void) +{ + g_autoptr (GFileEnumerator) children = NULL; + g_autoptr (GFile) parent_directory = NULL; + GPtrArray *desktop_file_ids; + + parent_directory = g_file_new_for_path (g_get_user_data_dir ()); + children = g_file_enumerate_children (parent_directory, + "standard::name", + 0, NULL, NULL); + if (!children) + return NULL; + + desktop_file_ids = g_ptr_array_new_with_free_func (g_free); + for (;;) { + g_autoptr (GFileInfo) info = g_file_enumerator_next_file (children, NULL, NULL); + const char *name; + + if (!info) + break; + + name = g_file_info_get_name (info); + if (g_str_has_prefix (name, get_gapplication_id_prefix ())) { + g_autofree char *desktop_file_id = NULL; + desktop_file_id = g_strconcat (name, ".desktop", NULL); + g_ptr_array_add (desktop_file_ids, g_steal_pointer (&desktop_file_id)); + } + } + + g_ptr_array_add (desktop_file_ids, NULL); + + return (char **)g_ptr_array_free (desktop_file_ids, FALSE); +} + +/** * ephy_web_application_exists: * @id: the potential identifier of the web application * diff --git a/lib/ephy-web-app-utils.h b/lib/ephy-web-app-utils.h index 4f98fc678..900b101f5 100644 --- a/lib/ephy-web-app-utils.h +++ b/lib/ephy-web-app-utils.h @@ -33,6 +33,7 @@ typedef struct { char *url; char *desktop_file; char install_date[128]; + guint64 install_date_uint64; } EphyWebApplication; /** @@ -51,17 +52,32 @@ typedef enum { EPHY_WEB_APPLICATION_SYSTEM, } EphyWebApplicationOptions; +typedef enum { + EPHY_WEB_APP_FOUND, + EPHY_WEB_APP_NOT_FOUND, +} EphyWebAppFound; + #define EPHY_WEB_APP_ICON_NAME "app-icon.png" char *ephy_web_application_get_app_id_from_name (const char *name); const char *ephy_web_application_get_gapplication_id_from_profile_directory (const char *profile_dir); -char *ephy_web_application_create (const char *id, const char *address, const char *name, GdkPixbuf *icon, EphyWebApplicationOptions options); +char *ephy_web_application_create (const char *id, + const char *address, + const char *name, + GdkPixbuf *icon_pixbuf, + const char *icon_path, + const char *install_token, + EphyWebApplicationOptions options); char *ephy_web_application_ensure_for_app_info (GAppInfo *app_info); -gboolean ephy_web_application_delete (const char *id); +gboolean ephy_web_application_delete (const char *id, + EphyWebAppFound *out_app_found); + +gboolean ephy_web_application_delete_by_desktop_file_id (const char *desktop_file_id, + EphyWebAppFound *out_app_found); void ephy_web_application_setup_from_profile_directory (const char *profile_directory); @@ -69,6 +85,8 @@ void ephy_web_application_setup_from_desktop_file (GDesktopAppInf char *ephy_web_application_get_profile_directory (const char *id); +char *ephy_web_application_get_desktop_path (EphyWebApplication *app); + EphyWebApplication *ephy_web_application_for_profile_directory (const char *profile_dir); void ephy_web_application_free (EphyWebApplication *app); @@ -79,6 +97,8 @@ GList *ephy_web_application_get_application_list (void); GList *ephy_web_application_get_legacy_application_list (void); +char **ephy_web_application_get_desktop_id_list (void); + void ephy_web_application_free_application_list (GList *list); void ephy_web_application_initialize_settings (const char *profile_directory, EphyWebApplicationOptions options); diff --git a/org.gnome.Epiphany.Canary.json.in b/org.gnome.Epiphany.Canary.json.in index 17fc14086..bade6e118 100644 --- a/org.gnome.Epiphany.Canary.json.in +++ b/org.gnome.Epiphany.Canary.json.in @@ -17,7 +17,8 @@ "--socket=fallback-x11", "--socket=pulseaudio", "--socket=wayland", - "--system-talk-name=org.freedesktop.GeoClue2" + "--system-talk-name=org.freedesktop.GeoClue2", + "--own-name=org.gnome.Epiphany.WebAppProvider" ], "modules" : [ { diff --git a/org.gnome.Epiphany.json b/org.gnome.Epiphany.json index 78ef33483..e0dddbf79 100644 --- a/org.gnome.Epiphany.json +++ b/org.gnome.Epiphany.json @@ -17,7 +17,8 @@ "--socket=fallback-x11", "--socket=pulseaudio", "--socket=wayland", - "--system-talk-name=org.freedesktop.GeoClue2" + "--system-talk-name=org.freedesktop.GeoClue2", + "--own-name=org.gnome.Epiphany.WebAppProvider" ], "modules" : [ { diff --git a/src/ephy-main.c b/src/ephy-main.c index 48b6c684b..bcf5ce703 100644 --- a/src/ephy-main.c +++ b/src/ephy-main.c @@ -370,7 +370,7 @@ main (int argc, /* Delete the requested web application, if any. Must happen after * ephy_file_helpers_init (). */ if (application_to_delete) { - ephy_web_application_delete (application_to_delete); + ephy_web_application_delete (application_to_delete, NULL); exit (0); } diff --git a/src/meson.build b/src/meson.build index eaee92180..50f6710b3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -111,7 +111,7 @@ ephy_profile_migrator = executable('ephy-profile-migrator', ) -codegen = gnome.gdbus_codegen('ephy-shell-search-provider-generated', +search_provider_codegen = gnome.gdbus_codegen('ephy-shell-search-provider-generated', 'search-provider/org.gnome.ShellSearchProvider2.xml', interface_prefix: 'org.gnome', namespace: 'Ephy' @@ -120,7 +120,7 @@ codegen = gnome.gdbus_codegen('ephy-shell-search-provider-generated', search_provider_sources = [ 'search-provider/ephy-search-provider.c', 'search-provider/ephy-search-provider-main.c', - codegen + search_provider_codegen ] executable('epiphany-search-provider', @@ -131,6 +131,25 @@ executable('epiphany-search-provider', install_rpath: pkglibdir ) +webapp_codegen = gnome.gdbus_codegen('ephy-webapp-provider-generated', + 'webapp-provider/org.gnome.Epiphany.WebAppProvider.xml', + interface_prefix: 'org.gnome.Epiphany', + namespace: 'Ephy' +) + +webapp_provider_sources = [ + 'webapp-provider/ephy-webapp-provider.c', + 'webapp-provider/ephy-webapp-provider-main.c', + webapp_codegen +] + +executable('epiphany-webapp-provider', + webapp_provider_sources, + dependencies: ephymain_dep, + install: true, + install_dir: libexecdir, + install_rpath: pkglibdir +) resource_files = files('resources/epiphany.gresource.xml') resources = gnome.compile_resources('epiphany-resources', diff --git a/src/webapp-provider/ephy-webapp-provider-main.c b/src/webapp-provider/ephy-webapp-provider-main.c new file mode 100644 index 000000000..e4fdcfb55 --- /dev/null +++ b/src/webapp-provider/ephy-webapp-provider-main.c @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright (c) 2013 Igalia S.L. + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "ephy-webapp-provider.h" +#include "ephy-file-helpers.h" + +#include <glib/gi18n.h> +#include <locale.h> + +int +main (int argc, + char **argv) +{ + g_autoptr (EphyWebAppProviderService) webapp_provider = NULL; + int status; + GError *error = NULL; + + g_setenv ("GIO_USE_VFS", "local", TRUE); + + g_debug ("started %s", argv[0]); + + /* Initialize the i18n stuff */ + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + if (!ephy_file_helpers_init (NULL, 0, &error)) { + g_printerr ("%s\n", error->message); + g_error_free (error); + return 1; + } + + webapp_provider = ephy_web_app_provider_service_new (); + status = g_application_run (G_APPLICATION (webapp_provider), argc, argv); + + ephy_file_helpers_shutdown (); + + g_debug ("stopping %s with status %d", argv[0], status); + + return status; +} diff --git a/src/webapp-provider/ephy-webapp-provider.c b/src/webapp-provider/ephy-webapp-provider.c new file mode 100644 index 000000000..af5fd7e05 --- /dev/null +++ b/src/webapp-provider/ephy-webapp-provider.c @@ -0,0 +1,282 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright (c) 2021 Matthew Leeds <mwleeds@protonmail.com> + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "ephy-webapp-provider.h" + +#include "ephy-web-app-utils.h" +#include "ephy-flatpak-utils.h" + +#include <gio/gio.h> +#include <glib/gi18n.h> + +struct _EphyWebAppProviderService { + GApplication parent_instance; + + EphyWebAppProvider *skeleton; +}; + +struct _EphyWebAppProviderServiceClass { + GApplicationClass parent_class; +}; + +G_DEFINE_TYPE (EphyWebAppProviderService, ephy_web_app_provider_service, G_TYPE_APPLICATION) + +#define INACTIVITY_TIMEOUT 60 * 1000 /* One minute, in milliseconds */ + +typedef enum { + EPHY_WEBAPP_PROVIDER_ERROR_FAILED, + EPHY_WEBAPP_PROVIDER_ERROR_INVALID_ARGS, + EPHY_WEBAPP_PROVIDER_ERROR_NOT_INSTALLED, + EPHY_WEBAPP_PROVIDER_ERROR_LAST = EPHY_WEBAPP_PROVIDER_ERROR_NOT_INSTALLED, /*< skip >*/ +} EphyWebAppProviderError; + +static const GDBusErrorEntry ephy_webapp_provider_error_entries[] = { + { EPHY_WEBAPP_PROVIDER_ERROR_FAILED, "org.gnome.Epiphany.WebAppProvider.Error.Failed" }, + { EPHY_WEBAPP_PROVIDER_ERROR_INVALID_ARGS, "org.gnome.Epiphany.WebAppProvider.Error.InvalidArgs" }, + { EPHY_WEBAPP_PROVIDER_ERROR_NOT_INSTALLED, "org.gnome.Epiphany.WebAppProvider.Error.NotInstalled" }, +}; + +/* Ensure that every error code has an associated D-Bus error name */ +G_STATIC_ASSERT (G_N_ELEMENTS (ephy_webapp_provider_error_entries) == EPHY_WEBAPP_PROVIDER_ERROR_LAST + 1); + +#define EPHY_WEBAPP_PROVIDER_ERROR (ephy_webapp_provider_error_quark ()) +GQuark +ephy_webapp_provider_error_quark (void) +{ + static gsize quark = 0; + g_dbus_error_register_error_domain ("ephy-webapp-provider-error-quark", + &quark, + ephy_webapp_provider_error_entries, + G_N_ELEMENTS (ephy_webapp_provider_error_entries)); + return (GQuark)quark; +} + +static gboolean +handle_get_installed_apps (EphyWebAppProvider *skeleton, + GDBusMethodInvocation *invocation, + EphyWebAppProviderService *self) +{ + g_auto (GStrv) desktop_ids = NULL; + + g_debug ("%s", G_STRFUNC); + + g_application_hold (G_APPLICATION (self)); + + desktop_ids = ephy_web_application_get_desktop_id_list (); + + ephy_web_app_provider_complete_get_installed_apps (skeleton, invocation, + (const gchar * const *)desktop_ids); + + g_application_release (G_APPLICATION (self)); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_install (EphyWebAppProvider *skeleton, + GDBusMethodInvocation *invocation, + char *url, + char *name, + char *install_token, + EphyWebAppProviderService *self) +{ + g_autofree char *id = NULL; + g_autofree char *desktop_path = NULL; + g_autofree char *desktop_file_id = NULL; + + g_debug ("%s", G_STRFUNC); + + g_application_hold (G_APPLICATION (self)); + + /* We need an install token acquired by a trusted system component such as + * gnome-software because otherwise the Flatpak/Snap sandbox prevents us from + * installing the app without using a portal (which would not be appropriate + * since Epiphany is not the focused application). We use the same code path + * when not running under a sandbox too. + */ + if (!install_token || *install_token == '\0') { + g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR, + EPHY_WEBAPP_PROVIDER_ERROR_INVALID_ARGS, + _("The install_token is required for the Install() method")); + goto out; + } + if (!g_uri_is_valid (url, G_URI_FLAGS_NONE, NULL)) { + g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR, + EPHY_WEBAPP_PROVIDER_ERROR_INVALID_ARGS, + _("The url passed was not valid: ‘%s’"), url); + goto out; + } + if (!name || *name == '\0') { + g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR, + EPHY_WEBAPP_PROVIDER_ERROR_INVALID_ARGS, + _("The name passed was not valid")); + goto out; + } + + id = ephy_web_application_get_app_id_from_name (name); + + desktop_path = ephy_web_application_create (id, url, name, + NULL, NULL, /* icon_pixbuf, icon_path */ + install_token, + EPHY_WEB_APPLICATION_NONE); + if (!desktop_path) { + g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR, + EPHY_WEBAPP_PROVIDER_ERROR_FAILED, + _("Installing the web application ‘%s’ (%s) failed"), + name, url); + goto out; + } + + desktop_file_id = g_path_get_basename (desktop_path); + ephy_web_app_provider_complete_install (skeleton, invocation, desktop_file_id); + +out: + g_application_release (G_APPLICATION (self)); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_uninstall (EphyWebAppProvider *skeleton, + GDBusMethodInvocation *invocation, + char *desktop_file_id, + EphyWebAppProviderService *self) +{ + EphyWebAppFound app_found; + + g_debug ("%s", G_STRFUNC); + + g_application_hold (G_APPLICATION (self)); + + if (!desktop_file_id || !g_str_has_suffix (desktop_file_id, ".desktop")) { + g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR, + EPHY_WEBAPP_PROVIDER_ERROR_INVALID_ARGS, + _("The desktop file ID passed ‘%s’ was not valid"), + desktop_file_id ? desktop_file_id : "(null)"); + goto out; + } + + if (!ephy_web_application_delete_by_desktop_file_id (desktop_file_id, &app_found)) { + if (app_found == EPHY_WEB_APP_NOT_FOUND) { + g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR, + EPHY_WEBAPP_PROVIDER_ERROR_NOT_INSTALLED, + _("The web application ‘%s’ does not exist"), + desktop_file_id); + } else { + g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR, + EPHY_WEBAPP_PROVIDER_ERROR_FAILED, + _("The web application ‘%s’ could not be deleted"), + desktop_file_id); + } + goto out; + } + + ephy_web_app_provider_complete_uninstall (skeleton, invocation); + +out: + g_application_release (G_APPLICATION (self)); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +ephy_web_app_provider_service_init (EphyWebAppProviderService *self) +{ + g_application_set_flags (G_APPLICATION (self), G_APPLICATION_IS_SERVICE); + + g_application_set_inactivity_timeout (G_APPLICATION (self), INACTIVITY_TIMEOUT); +} + +static gboolean +ephy_web_app_provider_service_dbus_register (GApplication *application, + GDBusConnection *connection, + const gchar *object_path, + GError **error) +{ + EphyWebAppProviderService *self; + + g_debug ("registering at object path %s", object_path); + + if (!G_APPLICATION_CLASS (ephy_web_app_provider_service_parent_class)->dbus_register (application, + connection, + object_path, + error)) + return FALSE; + + self = EPHY_WEB_APP_PROVIDER_SERVICE (application); + self->skeleton = ephy_web_app_provider_skeleton_new (); + ephy_web_app_provider_set_version (EPHY_WEB_APP_PROVIDER (self->skeleton), 1); + + g_signal_connect (self->skeleton, "handle-get-installed-apps", + G_CALLBACK (handle_get_installed_apps), self); + g_signal_connect (self->skeleton, "handle-install", + G_CALLBACK (handle_install), self); + g_signal_connect (self->skeleton, "handle-uninstall", + G_CALLBACK (handle_uninstall), self); + + return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton), + connection, object_path, error); +} + +static void +ephy_web_app_provider_service_dbus_unregister (GApplication *application, + GDBusConnection *connection, + const gchar *object_path) +{ + EphyWebAppProviderService *self; + GDBusInterfaceSkeleton *skeleton; + + g_debug ("unregistering at object path %s", object_path); + + self = EPHY_WEB_APP_PROVIDER_SERVICE (application); + skeleton = G_DBUS_INTERFACE_SKELETON (self->skeleton); + if (g_dbus_interface_skeleton_has_connection (skeleton, connection)) + g_dbus_interface_skeleton_unexport_from_connection (skeleton, connection); + + g_clear_object (&self->skeleton); + + G_APPLICATION_CLASS (ephy_web_app_provider_service_parent_class)->dbus_unregister (application, + connection, + object_path); +} + +static void +ephy_web_app_provider_service_class_init (EphyWebAppProviderServiceClass *klass) +{ + GApplicationClass *application_class = G_APPLICATION_CLASS (klass); + + application_class->dbus_register = ephy_web_app_provider_service_dbus_register; + application_class->dbus_unregister = ephy_web_app_provider_service_dbus_unregister; +} + +EphyWebAppProviderService * +ephy_web_app_provider_service_new (void) +{ + /* Note the application ID is constant for release/devel/canary builds + * because we want to always use the same well-known D-Bus name. + */ + g_autofree gchar *app_id = g_strconcat ("org.gnome.Epiphany.WebAppProvider", NULL); + + return g_object_new (EPHY_TYPE_WEB_APP_PROVIDER_SERVICE, + "application-id", app_id, + NULL); +} diff --git a/src/webapp-provider/ephy-webapp-provider.h b/src/webapp-provider/ephy-webapp-provider.h new file mode 100644 index 000000000..1286cf0e0 --- /dev/null +++ b/src/webapp-provider/ephy-webapp-provider.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright (c) 2021 Matthew Leeds <mwleeds@protonmail.com> + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "ephy-webapp-provider-generated.h" + +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define EPHY_TYPE_WEB_APP_PROVIDER_SERVICE (ephy_web_app_provider_service_get_type ()) + +G_DECLARE_FINAL_TYPE (EphyWebAppProviderService, ephy_web_app_provider_service, EPHY, WEB_APP_PROVIDER_SERVICE, GApplication) + +EphyWebAppProviderService *ephy_web_app_provider_service_new (void); + +G_END_DECLS diff --git a/src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml b/src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml new file mode 100644 index 000000000..6c2954dc3 --- /dev/null +++ b/src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml @@ -0,0 +1,85 @@ +<!DOCTYPE node PUBLIC +'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' +'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'> +<node> + + <!-- + org.gnome.Epiphany.WebAppProvider: + @short_description: Webapp provider interface + + The interface used for handling Epiphany Webapps in GNOME Software, or other + clients (version 1). + --> + <interface name="org.gnome.Epiphany.WebAppProvider"> + <!-- + GetInstalledApps: + @desktop_file_ids: An array of .desktop file names, one for each + installed web app, with the .desktop suffix included + + Returns the set of installed Epiphany web applications. The caller can + use them with g_desktop_app_info_new() if outside the sandbox. + --> + <method name="GetInstalledApps"> + <arg type="as" name="webapps" direction="out" /> + </method> + + <!-- + Install: + @url: the URL of the web app + @name: the human readable name of the web app + @install_token: the token acquired via org.freedesktop.portal.InstallDynamicLauncher + @desktop_file_id: the desktop file id of the installed app, with a + ".desktop" suffix + + Installs a web app. This interface is expected to be used by trusted + system components such as GNOME Software, which can acquire an + @install_token using the portal method + org.freedesktop.portal.DynamicLauncher.RequestInstallToken(). This allows Epiphany + to install the web app without user interaction and despite being sandboxed. + This is desirable because the user would've already clicked "Install" in + Software; they should not have to confirm the operation again in a different + app (Epiphany). + + 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. + + If the arguments passed are invalid this method returns the error + `org.gnome.Epiphany.WebAppProvider.Error.InvalidArgs`, and otherwise + `org.gnome.Epiphany.WebAppProvider.Error.Failed`. + --> + <method name="Install"> + <arg type="s" name="url" direction="in" /> + <arg type="s" name="name" direction="in" /> + <arg type="s" name="install_token" direction="in" /> + <arg type="s" name="desktop_file_id" direction="out" /> + </method> + + <!-- + Uninstall: + @desktop_file_id: the filename of the .desktop file for an installed web + app, with the .desktop suffix + + 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. + + The error `org.gnome.Epiphany.WebAppProvider.Error.NotInstalled` will be + returned if the specified web app is not installed. The other possible + error values are `org.gnome.Epiphany.WebAppProvider.Error.InvalidArgs` + and `org.gnome.Epiphany.WebAppProvider.Error.Failed`. + --> + <method name="Uninstall"> + <arg type="s" name="desktop_file_id" direction="in" /> + </method> + <!-- + Version: + + The API version number, to be incremented for backwards compatible + changes so clients can determine which features are available. For + backwards incompatible changes, the interface name will change. + --> + <property name="Version" type="u" access="read"/> + </interface> +</node> diff --git a/src/window-commands.c b/src/window-commands.c index 91d62b14c..32f10091d 100644 --- a/src/window-commands.c +++ b/src/window-commands.c @@ -1793,6 +1793,7 @@ save_as_application_proceed (EphyApplicationDialogData *data) webkit_web_view_get_uri (WEBKIT_WEB_VIEW (data->view)), app_name, gtk_image_get_pixbuf (GTK_IMAGE (data->image)), + NULL, NULL, /* icon_path, install_token */ data->webapp_options); if (desktop_file) @@ -1842,7 +1843,7 @@ dialog_save_as_application_confirmation_cb (GtkDialog *dialog, gtk_widget_destroy (GTK_WIDGET (dialog)); if (response == GTK_RESPONSE_OK) { - ephy_web_application_delete (app_id); + ephy_web_application_delete (app_id, NULL); save_as_application_proceed (data); } } diff --git a/tests/ephy-web-app-utils-test.c b/tests/ephy-web-app-utils-test.c index 5026388db..8f32570fe 100644 --- a/tests/ephy-web-app-utils-test.c +++ b/tests/ephy-web-app-utils-test.c @@ -68,7 +68,7 @@ test_web_app_lifetime (void) /* Test creation */ id = ephy_web_application_get_app_id_from_name (test.name); - desktop_file = ephy_web_application_create (id, test.url, test.name, NULL, EPHY_WEB_APPLICATION_NONE); + desktop_file = ephy_web_application_create (id, test.url, test.name, NULL, NULL, NULL, EPHY_WEB_APPLICATION_NONE); g_assert_true (g_str_has_prefix (desktop_file, ephy_profile_dir ())); g_assert_true (g_file_test (desktop_file, G_FILE_TEST_EXISTS)); @@ -119,7 +119,7 @@ test_web_app_lifetime (void) /* Test delete API */ g_test_message ("DELETE: %s", test.name); - g_assert_true (ephy_web_application_delete (id)); + g_assert_true (ephy_web_application_delete (id, NULL)); g_assert_false (g_file_test (desktop_link, G_FILE_TEST_EXISTS)); g_assert_false (g_file_test (desktop_link, G_FILE_TEST_IS_SYMLINK)); |