summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Garcia Campos <cgarcia@igalia.com>2018-06-13 14:01:44 +0200
committerCarlos Garcia Campos <carlosgc@gnome.org>2018-06-28 14:58:24 +0200
commit94c6fc6661141e5b195160764315f7e52e9c43e1 (patch)
treefd98cf489d16c948df479a910f4df91fee773176
parentf49fe289c91cfd8d79ca85e3b9e00f00a8e2ba27 (diff)
downloadepiphany-94c6fc6661141e5b195160764315f7e52e9c43e1.tar.gz
web-apps: add a web application section to preferences dialog
It allows to update the application name, URL and icon. It also adds the additional URLs setting, to make it possible to add URLs for pages that should also be opened inside the web application.
-rw-r--r--data/org.gnome.epiphany.gschema.xml7
-rw-r--r--lib/ephy-prefs.h9
-rw-r--r--lib/ephy-settings.c3
-rw-r--r--lib/ephy-settings.h1
-rw-r--r--lib/ephy-web-app-utils.c113
-rw-r--r--lib/ephy-web-app-utils.h30
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/ephy-window.c10
-rw-r--r--src/meson.build1
-rw-r--r--src/prefs-dialog.c198
-rw-r--r--src/resources/epiphany.gresource.xml1
-rw-r--r--src/resources/gtk/prefs-dialog.ui100
-rw-r--r--src/resources/gtk/webapp-additional-urls-dialog.ui154
-rw-r--r--src/webapp-additional-urls-dialog.c269
-rw-r--r--src/webapp-additional-urls-dialog.h33
15 files changed, 907 insertions, 23 deletions
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index 3502c1b64..83ddde0d6 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -237,6 +237,13 @@
<description>Whether to automatically search the web when something that does not look like a URL is entered in the address bar. If this setting is disabled, everything will be loaded as a URL unless a search engine is explicitly selected from the dropdown menu.</description>
</key>
</schema>
+ <schema id="org.gnome.Epiphany.webapp">
+ <key type="as" name="additional-urls">
+ <default>[]</default>
+ <summary>Web application additional URLs</summary>
+ <description>The list of URLs that should be opened by the web application</description>
+ </key>
+ </schema>
<schema id="org.gnome.Epiphany.state">
<key type="s" name="download-dir">
<default>'Downloads'</default>
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index 20c1de96d..ebf3a7447 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -177,12 +177,17 @@ static const char * const ephy_prefs_web_schema[] = {
#define EPHY_PREFS_SYNC_OPEN_TABS_ENABLED "sync-open-tabs-enabled"
#define EPHY_PREFS_SYNC_OPEN_TABS_TIME "sync-open-tabs-time"
+#define EPHY_PREFS_WEB_APP_SCHEMA "org.gnome.Epiphany.webapp"
+#define EPHY_PREFS_WEB_APP_ADDITIONAL_URLS "additional-urls"
+
static struct {
const char *schema;
const char *path;
+ gboolean is_webapp_only;
} const ephy_prefs_relocatable_schemas[] = {
- { EPHY_PREFS_STATE_SCHEMA, "state/" },
- { EPHY_PREFS_WEB_SCHEMA, "web/" },
+ { EPHY_PREFS_STATE_SCHEMA, "state/", FALSE },
+ { EPHY_PREFS_WEB_SCHEMA, "web/", FALSE },
+ { EPHY_PREFS_WEB_APP_SCHEMA, "webapp/", TRUE }
};
G_END_DECLS
diff --git a/lib/ephy-settings.c b/lib/ephy-settings.c
index 67a98d34a..8c415a607 100644
--- a/lib/ephy-settings.c
+++ b/lib/ephy-settings.c
@@ -57,6 +57,9 @@ ephy_settings_init (void)
for (guint i = 0; i < G_N_ELEMENTS (ephy_prefs_relocatable_schemas); i++) {
char *path;
+ if (!web_app_name && ephy_prefs_relocatable_schemas[i].is_webapp_only)
+ continue;
+
path = g_build_path ("/", base_path, ephy_prefs_relocatable_schemas[i].path, NULL);
g_hash_table_insert (settings, g_strdup (ephy_prefs_relocatable_schemas[i].schema),
g_settings_new_with_path (ephy_prefs_relocatable_schemas[i].schema, path));
diff --git a/lib/ephy-settings.h b/lib/ephy-settings.h
index ee186c427..2988fbabb 100644
--- a/lib/ephy-settings.h
+++ b/lib/ephy-settings.h
@@ -33,6 +33,7 @@ G_BEGIN_DECLS
#define EPHY_SETTINGS_LOCKDOWN ephy_settings_get (EPHY_PREFS_LOCKDOWN_SCHEMA)
#define EPHY_SETTINGS_STATE ephy_settings_get (EPHY_PREFS_STATE_SCHEMA)
#define EPHY_SETTINGS_SYNC ephy_settings_get (EPHY_PREFS_SYNC_SCHEMA)
+#define EPHY_SETTINGS_WEB_APP ephy_settings_get (EPHY_PREFS_WEB_APP_SCHEMA)
GSettings *ephy_settings_get (const char *schema);
diff --git a/lib/ephy-web-app-utils.c b/lib/ephy-web-app-utils.c
index dd08152cd..422519424 100644
--- a/lib/ephy-web-app-utils.c
+++ b/lib/ephy-web-app-utils.c
@@ -23,11 +23,12 @@
#include "ephy-debug.h"
#include "ephy-file-helpers.h"
-#include "ephy-prefs.h"
+#include "ephy-settings.h"
#include <errno.h>
#include <gio/gio.h>
#include <glib/gstdio.h>
+#include <libsoup/soup.h>
#include <stdlib.h>
#include <string.h>
@@ -462,7 +463,7 @@ ephy_web_application_setup_from_desktop_file (GDesktopAppInfo *desktop_info)
gdk_set_program_class (wm_class);
}
-static void
+void
ephy_web_application_free (EphyWebApplication *app)
{
g_free (app->id);
@@ -473,7 +474,8 @@ ephy_web_application_free (EphyWebApplication *app)
g_slice_free (EphyWebApplication, app);
}
-static EphyWebApplication *
+
+EphyWebApplication *
ephy_web_application_for_profile_directory (const char *profile_dir)
{
EphyWebApplication *app;
@@ -659,3 +661,108 @@ ephy_web_application_initialize_settings (const char *profile_directory)
g_object_unref (web_app_settings);
g_free (name);
}
+
+gboolean
+ephy_web_application_is_uri_allowed (const char* uri)
+{
+ SoupURI *request_uri;
+ char **urls;
+ guint i;
+ gboolean matched = FALSE;
+
+ if (g_strcmp0 (uri, "about:blank") == 0)
+ return TRUE;
+
+ request_uri = soup_uri_new (uri);
+ if (!request_uri)
+ return FALSE;
+
+ urls = g_settings_get_strv (EPHY_SETTINGS_WEB_APP, EPHY_PREFS_WEB_APP_ADDITIONAL_URLS);
+ for (i = 0; urls[i] && !matched; i++) {
+ if (!strstr (urls[i], "://")) {
+ char *url = g_strdup_printf ("%s://%s", request_uri->scheme, urls[i]);
+
+ matched = g_str_has_prefix (uri, url);
+ g_free (url);
+ } else {
+ matched = g_str_has_prefix (uri, urls[i]);
+ }
+ }
+ g_strfreev (urls);
+
+ soup_uri_free (request_uri);
+
+ return matched;
+}
+
+gboolean
+ephy_web_application_save (EphyWebApplication *app)
+{
+ char *profile_dir;
+ char *desktop_file_path;
+ char *contents;
+ gboolean saved = FALSE;
+ GError *error = NULL;
+
+ profile_dir = ephy_web_application_get_profile_directory (app->id);
+ desktop_file_path = g_build_filename (profile_dir, app->desktop_file, NULL);
+ if (g_file_get_contents (desktop_file_path, &contents, NULL, &error)) {
+ GKeyFile *key;
+ char *name;
+ char *icon;
+ char *exec;
+ char **strings;
+ guint exec_length;
+ gboolean changed = FALSE;
+
+ key = g_key_file_new ();
+ g_key_file_load_from_data (key, contents, -1, 0, NULL);
+
+ name = g_key_file_get_string (key, "Desktop Entry", "Name", NULL);
+ if (g_strcmp0 (name, app->name) != 0) {
+ changed = TRUE;
+ g_key_file_set_string (key, "Desktop Entry", "Name", app->name);
+ }
+ g_free (name);
+
+ icon = g_key_file_get_string (key, "Desktop Entry", "Icon", NULL);
+ if (g_strcmp0 (name, app->icon_url) != 0) {
+ changed = TRUE;
+ g_key_file_set_string (key, "Desktop Entry", "Icon", app->icon_url);
+ }
+ g_free (icon);
+
+ exec = g_key_file_get_string (key, "Desktop Entry", "Exec", NULL);
+ strings = g_strsplit (exec, " ", -1);
+ g_free (exec);
+
+ exec_length = g_strv_length (strings);
+ if (g_strcmp0 (strings[exec_length - 1], app->url) != 0) {
+ changed = TRUE;
+ g_free (strings[exec_length - 1]);
+ strings[exec_length - 1] = g_strdup (app->url);
+ exec = g_strjoinv (" ", strings);
+ g_key_file_set_string (key, "Desktop Entry", "Exec", exec);
+ g_free (exec);
+ }
+ g_strfreev (strings);
+
+ if (changed) {
+ saved = g_key_file_save_to_file (key, desktop_file_path, &error);
+ if (!saved) {
+ g_warning ("Failed to save desktop file of web application: %s\n", error->message);
+ g_error_free (error);
+ }
+ }
+ g_free (contents);
+ g_key_file_free (key);
+ } else {
+ g_warning ("Failed to load desktop file of web application: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ g_free (desktop_file_path);
+ g_free (profile_dir);
+
+ return saved;
+}
diff --git a/lib/ephy-web-app-utils.h b/lib/ephy-web-app-utils.h
index 5e77a99ca..c13d553b7 100644
--- a/lib/ephy-web-app-utils.h
+++ b/lib/ephy-web-app-utils.h
@@ -38,26 +38,34 @@ typedef struct {
#define EPHY_WEB_APP_PREFIX "app-"
#define EPHY_WEB_APP_ICON_NAME "app-icon.png"
-char *ephy_web_application_get_app_id_from_name (const char *name);
+char *ephy_web_application_get_app_id_from_name (const char *name);
-char *ephy_web_application_create (const char *id, const char *address, const char *name, GdkPixbuf *icon);
+char *ephy_web_application_create (const char *id, const char *address, const char *name, GdkPixbuf *icon);
-char *ephy_web_application_ensure_for_app_info (GAppInfo *app_info);
+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);
-void ephy_web_application_setup_from_profile_directory (const char *profile_directory);
+void ephy_web_application_setup_from_profile_directory (const char *profile_directory);
-void ephy_web_application_setup_from_desktop_file (GDesktopAppInfo *desktop_info);
+void ephy_web_application_setup_from_desktop_file (GDesktopAppInfo *desktop_info);
-char *ephy_web_application_get_profile_directory (const char *id);
+char *ephy_web_application_get_profile_directory (const char *id);
-GList *ephy_web_application_get_application_list (void);
+EphyWebApplication *ephy_web_application_for_profile_directory (const char *profile_dir);
-void ephy_web_application_free_application_list (GList *list);
+void ephy_web_application_free (EphyWebApplication *app);
-gboolean ephy_web_application_exists (const char *id);
+gboolean ephy_web_application_exists (const char *id);
-void ephy_web_application_initialize_settings (const char *profile_directory);
+GList *ephy_web_application_get_application_list (void);
+
+void ephy_web_application_free_application_list (GList *list);
+
+void ephy_web_application_initialize_settings (const char *profile_directory);
+
+gboolean ephy_web_application_is_uri_allowed (const char* uri);
+
+gboolean ephy_web_application_save (EphyWebApplication *app);
G_END_DECLS
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 22f5c0cd4..d985a4a76 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -71,6 +71,7 @@ src/resources/gtk/prefs-lang-dialog.ui
src/resources/gtk/search-engine-dialog.ui
src/resources/gtk/shortcuts-dialog.ui
src/resources/gtk/synced-tabs-dialog.ui
+src/resources/gtk/webapp-additional-urls-dialog.ui
src/search-provider/ephy-search-provider.c
src/synced-tabs-dialog.c
src/window-commands.c
diff --git a/src/ephy-window.c b/src/ephy-window.c
index 24ba49222..3697315c4 100644
--- a/src/ephy-window.c
+++ b/src/ephy-window.c
@@ -49,6 +49,7 @@
#include "ephy-title-box.h"
#include "ephy-title-widget.h"
#include "ephy-type-builtins.h"
+#include "ephy-web-app-utils.h"
#include "ephy-web-view.h"
#include "ephy-zoom.h"
#include "popup-commands.h"
@@ -1944,7 +1945,8 @@ decide_navigation_policy (WebKitWebView *web_view,
referrer = (char *)g_object_get_data (G_OBJECT (window), "referrer");
- if (ephy_embed_utils_urls_have_same_origin (uri, referrer)) {
+ if (ephy_embed_utils_urls_have_same_origin (uri, referrer) ||
+ ephy_web_application_is_uri_allowed (uri)) {
gtk_widget_show (GTK_WIDGET (window));
} else {
ephy_file_open_uri_in_default_browser (uri, GDK_CURRENT_TIME,
@@ -1957,8 +1959,10 @@ decide_navigation_policy (WebKitWebView *web_view,
}
}
- if (navigation_type == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED) {
- if (ephy_embed_utils_urls_have_same_origin (uri, webkit_web_view_get_uri (web_view))) {
+ if (navigation_type == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED ||
+ (navigation_type == WEBKIT_NAVIGATION_TYPE_OTHER && webkit_navigation_action_is_user_gesture (navigation_action))) {
+ if (ephy_embed_utils_urls_have_same_origin (uri, webkit_web_view_get_uri (web_view)) ||
+ ephy_web_application_is_uri_allowed (uri)) {
return FALSE;
}
diff --git a/src/meson.build b/src/meson.build
index cf8a17b60..c45a50c24 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -42,6 +42,7 @@ libephymain_sources = [
'popup-commands.c',
'prefs-dialog.c',
'synced-tabs-dialog.c',
+ 'webapp-additional-urls-dialog.c',
'window-commands.c',
compile_schemas,
enums
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index 33fec4415..edcd1b474 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -43,11 +43,13 @@
#include "ephy-sync-utils.h"
#include "ephy-time-helpers.h"
#include "ephy-uri-tester-shared.h"
+#include "ephy-web-app-utils.h"
#include "clear-data-dialog.h"
#include "cookies-dialog.h"
#include "languages.h"
#include "passwords-dialog.h"
#include "synced-tabs-dialog.h"
+#include "webapp-additional-urls-dialog.h"
#include <glib/gi18n.h>
#include <gtk/gtk.h>
@@ -74,6 +76,12 @@ struct _PrefsDialog {
GtkWidget *blank_homepage_radiobutton;
GtkWidget *custom_homepage_radiobutton;
GtkWidget *custom_homepage_entry;
+ EphyWebApplication *webapp;
+ guint webapp_save_id;
+ GtkWidget *webapp_box;
+ GtkWidget *webapp_icon;
+ GtkWidget *webapp_url;
+ GtkWidget *webapp_title;
GtkWidget *download_button_hbox;
GtkWidget *download_button_label;
GtkWidget *download_box;
@@ -180,6 +188,8 @@ prefs_dialog_finalize (GObject *object)
g_object_unref (dialog->fxa_manager);
}
+ g_clear_pointer (&dialog->webapp, ephy_web_application_free);
+
G_OBJECT_CLASS (prefs_dialog_parent_class)->finalize (object);
}
@@ -715,6 +725,165 @@ on_sync_device_name_cancel_button_clicked (GtkWidget *button,
g_free (name);
}
+static gboolean
+save_web_application (PrefsDialog *dialog)
+{
+ gboolean changed = FALSE;
+ const char *text;
+
+ dialog->webapp_save_id = 0;
+
+ if (!dialog->webapp)
+ return G_SOURCE_REMOVE;
+
+ text = gtk_entry_get_text (GTK_ENTRY (dialog->webapp_url));
+ if (g_strcmp0 (dialog->webapp->url, text) != 0) {
+ g_free (dialog->webapp->url);
+ dialog->webapp->url = g_strdup (text);
+ changed = TRUE;
+ }
+
+ text = gtk_entry_get_text (GTK_ENTRY (dialog->webapp_title));
+ if (g_strcmp0 (dialog->webapp->name, text) != 0) {
+ g_free (dialog->webapp->name);
+ dialog->webapp->name = g_strdup (text);
+ changed = TRUE;
+ }
+
+ text = (const char *)g_object_get_data (G_OBJECT (dialog->webapp_icon), "ephy-webapp-icon-url");
+ if (g_strcmp0 (dialog->webapp->icon_url, text) != 0) {
+ g_free (dialog->webapp->icon_url);
+ dialog->webapp->icon_url = g_strdup (text);
+ changed = TRUE;
+ }
+
+ if (changed)
+ ephy_web_application_save (dialog->webapp);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+prefs_dialog_save_web_application (PrefsDialog *dialog)
+{
+ if (!dialog->webapp)
+ return;
+
+ if (dialog->webapp_save_id)
+ g_source_remove (dialog->webapp_save_id);
+
+ dialog->webapp_save_id = g_timeout_add_seconds (1, (GSourceFunc)save_web_application, dialog);
+}
+
+static void
+prefs_dialog_update_webapp_icon (PrefsDialog *dialog,
+ const char *icon_url)
+{
+ GdkPixbuf *icon, *scaled_icon;
+ int icon_width, icon_height;
+ double scale;
+
+ icon = gdk_pixbuf_new_from_file (icon_url, NULL);
+ if (!icon)
+ return;
+
+ icon_width = gdk_pixbuf_get_width (icon);
+ icon_height = gdk_pixbuf_get_height (icon);
+ scale = MIN ((double)64 / icon_width, (double)64 / icon_height);
+ scaled_icon = gdk_pixbuf_scale_simple (icon, icon_width * scale, icon_height * scale, GDK_INTERP_NEAREST);
+ g_object_unref (icon);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (dialog->webapp_icon), scaled_icon);
+ g_object_set_data_full (G_OBJECT (dialog->webapp_icon), "ephy-webapp-icon-url",
+ g_strdup (icon_url), g_free);
+ g_object_unref (scaled_icon);
+}
+
+static void
+webapp_icon_chooser_response_cb (GtkNativeDialog *file_chooser,
+ int response,
+ PrefsDialog *dialog)
+{
+ if (response == GTK_RESPONSE_ACCEPT) {
+ char *icon_url;
+
+ icon_url = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_chooser));
+ prefs_dialog_update_webapp_icon (dialog, icon_url);
+ g_free (icon_url);
+ prefs_dialog_save_web_application (dialog);
+ }
+
+ g_object_unref (file_chooser);
+}
+
+static void
+on_webapp_icon_button_clicked (GtkWidget *button,
+ PrefsDialog *dialog)
+{
+ GtkFileChooser *file_chooser;
+ GSList *pixbuf_formats, *l;
+ GtkFileFilter *images_filter;
+
+ file_chooser = ephy_create_file_chooser (_("Web Application Icon"),
+ GTK_WIDGET (dialog),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ EPHY_FILE_FILTER_NONE);
+ images_filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (images_filter, _("Supported Image Files"));
+ gtk_file_chooser_add_filter (file_chooser, images_filter);
+
+ pixbuf_formats = gdk_pixbuf_get_formats ();
+ for (l = pixbuf_formats; l; l = g_slist_next (l)) {
+ GdkPixbufFormat *format = (GdkPixbufFormat *)l->data;
+ GtkFileFilter *filter;
+ gchar *name;
+ gchar **mime_types;
+ guint i;
+
+ if (gdk_pixbuf_format_is_disabled (format) || !gdk_pixbuf_format_is_writable (format))
+ continue;
+
+ filter = gtk_file_filter_new ();
+ name = gdk_pixbuf_format_get_description (format);
+ gtk_file_filter_set_name (filter, name);
+ g_free (name);
+
+ mime_types = gdk_pixbuf_format_get_mime_types (format);
+ for (i = 0; mime_types[i] != 0; i++) {
+ gtk_file_filter_add_mime_type (images_filter, mime_types[i]);
+ gtk_file_filter_add_mime_type (filter, mime_types[i]);
+ }
+ g_strfreev (mime_types);
+
+ gtk_file_chooser_add_filter (file_chooser, filter);
+ }
+ g_slist_free (pixbuf_formats);
+
+ g_signal_connect (file_chooser, "response",
+ G_CALLBACK (webapp_icon_chooser_response_cb),
+ dialog);
+
+ gtk_native_dialog_show (GTK_NATIVE_DIALOG (file_chooser));
+}
+
+static void
+on_webapp_entry_changed (GtkEditable *editable,
+ PrefsDialog *dialog)
+{
+ prefs_dialog_save_web_application (dialog);
+}
+
+static void
+on_manage_webapp_additional_urls_button_clicked (GtkWidget *button,
+ PrefsDialog *dialog)
+{
+ EphyWebappAdditionalURLsDialog *urls_dialog;
+
+ urls_dialog = ephy_webapp_additional_urls_dialog_new ();
+ gtk_window_set_transient_for (GTK_WINDOW (urls_dialog), GTK_WINDOW (dialog));
+ gtk_window_set_modal (GTK_WINDOW (urls_dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (urls_dialog));
+}
+
static void
on_manage_cookies_button_clicked (GtkWidget *button,
PrefsDialog *dialog)
@@ -775,6 +944,10 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, blank_homepage_radiobutton);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, custom_homepage_radiobutton);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, custom_homepage_entry);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, webapp_box);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, webapp_icon);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, webapp_url);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, webapp_title);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, search_box);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, session_box);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, restore_session_checkbutton);
@@ -835,6 +1008,9 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_last_sync_time_box);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_last_sync_time_label);
+ gtk_widget_class_bind_template_callback (widget_class, on_webapp_icon_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_webapp_entry_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_manage_webapp_additional_urls_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_manage_cookies_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_manage_passwords_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_search_engine_dialog_button_clicked);
@@ -1451,11 +1627,16 @@ create_download_path_button (PrefsDialog *dialog)
}
static void
-prefs_dialog_response_cb (GtkDialog *widget,
- int response,
- GtkDialog *dialog)
+prefs_dialog_response_cb (GtkWidget *widget,
+ int response,
+ PrefsDialog *dialog)
{
- gtk_widget_destroy (GTK_WIDGET (dialog));
+ if (dialog->webapp_save_id) {
+ g_source_remove (dialog->webapp_save_id);
+ save_web_application (dialog);
+ }
+
+ gtk_widget_destroy (widget);
}
static void
@@ -1703,6 +1884,13 @@ setup_general_page (PrefsDialog *dialog)
GSettings *settings;
GSettings *web_settings;
+ if (ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) == EPHY_EMBED_SHELL_MODE_APPLICATION) {
+ dialog->webapp = ephy_web_application_for_profile_directory (ephy_dot_dir ());
+ prefs_dialog_update_webapp_icon (dialog, dialog->webapp->icon_url);
+ gtk_entry_set_text (GTK_ENTRY (dialog->webapp_url), dialog->webapp->url);
+ gtk_entry_set_text (GTK_ENTRY (dialog->webapp_title), dialog->webapp->name);
+ }
+
settings = ephy_settings_get (EPHY_PREFS_SCHEMA);
web_settings = ephy_settings_get (EPHY_PREFS_WEB_SCHEMA);
@@ -2032,6 +2220,8 @@ prefs_dialog_init (PrefsDialog *dialog)
gtk_widget_init_template (GTK_WIDGET (dialog));
mode = ephy_embed_shell_get_mode (ephy_embed_shell_get_default ());
+ gtk_widget_set_visible (dialog->webapp_box,
+ mode == EPHY_EMBED_SHELL_MODE_APPLICATION);
gtk_widget_set_visible (dialog->homepage_box,
mode != EPHY_EMBED_SHELL_MODE_APPLICATION);
gtk_widget_set_visible (dialog->search_box,
diff --git a/src/resources/epiphany.gresource.xml b/src/resources/epiphany.gresource.xml
index 380de489c..ca9b9d241 100644
--- a/src/resources/epiphany.gresource.xml
+++ b/src/resources/epiphany.gresource.xml
@@ -32,6 +32,7 @@
<file preprocess="xml-stripblanks" compressed="true">gtk/search-engine-dialog.ui</file>
<file preprocess="xml-stripblanks" compressed="true">gtk/synced-tabs-dialog.ui</file>
<file preprocess="xml-stripblanks" compressed="true">gtk/shortcuts-dialog.ui</file>
+ <file preprocess="xml-stripblanks" compressed="true">gtk/webapp-additional-urls-dialog.ui</file>
<file compressed="true">readability.js</file>
<file compressed="true">reader.css</file>
</gresource>
diff --git a/src/resources/gtk/prefs-dialog.ui b/src/resources/gtk/prefs-dialog.ui
index 14798fa6b..eea805285 100644
--- a/src/resources/gtk/prefs-dialog.ui
+++ b/src/resources/gtk/prefs-dialog.ui
@@ -99,6 +99,106 @@
</object>
</child>
<child>
+ <object class="GtkBox" id="webapp_box">
+ <property name="visible">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Web Application</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="webapp_additional_urls_dialog_button">
+ <property name="label" translatable="yes">_Manage Additional URLs</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ <signal name="clicked" handler="on_manage_webapp_additional_urls_button_clicked"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="column-spacing">12</property>
+ <property name="row-spacing">6</property>
+ <property name="margin-start">12</property>
+ <child>
+ <object class="GtkButton" id="webapp_icon_button">
+ <property name="visible">true</property>
+ <property name="halign">center</property>
+ <signal name="clicked" handler="on_webapp_icon_button_clicked"/>
+ <child>
+ <object class="GtkImage" id="webapp_icon">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Homepage:</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="webapp_url">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <signal name="changed" handler="on_webapp_entry_changed"/>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Title:</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="webapp_title">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <signal name="changed" handler="on_webapp_entry_changed"/>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
<object class="GtkBox" id="download_box">
<property name="visible">True</property>
<property name="orientation">vertical</property>
diff --git a/src/resources/gtk/webapp-additional-urls-dialog.ui b/src/resources/gtk/webapp-additional-urls-dialog.ui
new file mode 100644
index 000000000..bec9c9447
--- /dev/null
+++ b/src/resources/gtk/webapp-additional-urls-dialog.ui
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.10 -->
+ <object class="GtkListStore" id="liststore">
+ <columns>
+ <!-- column-name URL -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <template class="EphyWebappAdditionalURLsDialog" parent="GtkDialog">
+ <property name="height_request">500</property>
+ <property name="modal">True</property>
+ <property name="window_position">center</property>
+ <property name="default_width">300</property>
+ <property name="default_height">400</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar">
+ <property name="title" translatable="yes">Additional URLs</property>
+ <property name="show-close-button">True</property>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">C_lear All</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="valign">center</property>
+ <property name="action-name">webapp-additional-urls.forget-all</property>
+ <accelerator key="Delete" modifiers="GDK_SHIFT_MASK" signal="clicked"/>
+ <style>
+ <class name="destructive-action"/>
+ <class name="text-button"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">dialog-information-symbolic</property>
+ <property name="icon_size">6</property>
+ <property name="margin_left">6</property>
+ <property name="margin_right">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">A URL that starts with any of the additional URLs will be opened by the web application. If you omit the URL scheme, the one from the currently loaded URL will be used.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="width_request">400</property>
+ <property name="height_request">300</property>
+ <property name="visible">True</property>
+ <property name="expand">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="min_content_width">300</property>
+ <property name="min_content_height">300</property>
+ <child>
+ <object class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="model">liststore</property>
+ <property name="enable_search">False</property>
+ <property name="search_column">0</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="tree_selection">
+ <property name="mode">multiple</property>
+ <signal name="changed" handler="on_treeview_selection_changed"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="url_column">
+ <property name="sizing">autosize</property>
+ <property name="title" translatable="yes">URL</property>
+ <property name="clickable">True</property>
+ <property name="reorderable">True</property>
+ <property name="sort_column_id">0</property>
+ <child>
+ <object class="GtkCellRendererText">
+ <property name="editable">TRUE</property>
+ <signal name="edited" handler="on_cell_edited"/>
+ </object>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="tooltip_text" translatable="yes">Add new URL</property>
+ <property name="action-name">webapp-additional-urls.new</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">list-add-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="tooltip_text" translatable="yes">Remove the selected URLs</property>
+ <property name="action-name">webapp-additional-urls.forget</property>
+ <accelerator key="Delete" signal="clicked"/>
+ <accelerator key="KP_Delete" signal="clicked"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">list-remove-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/webapp-additional-urls-dialog.c b/src/webapp-additional-urls-dialog.c
new file mode 100644
index 000000000..b57db25f2
--- /dev/null
+++ b/src/webapp-additional-urls-dialog.c
@@ -0,0 +1,269 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2018 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 "webapp-additional-urls-dialog.h"
+
+#include "ephy-settings.h"
+
+struct _EphyWebappAdditionalURLsDialog {
+ GtkDialog parent_instance;
+
+ GtkWidget *treeview;
+ GtkTreeViewColumn *url_column;
+ GtkTreeSelection *tree_selection;
+ GtkTreeModel *liststore;
+
+ GActionGroup *action_group;
+};
+
+G_DEFINE_TYPE (EphyWebappAdditionalURLsDialog, ephy_webapp_additional_urls_dialog, GTK_TYPE_DIALOG)
+
+static gboolean
+add_to_builder (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GVariantBuilder *builder)
+{
+ char *url;
+
+ gtk_tree_model_get (model, iter, 0, &url, -1);
+ if (url && url[0] != '\0')
+ g_variant_builder_add (builder, "s", url);
+ g_free (url);
+
+ return FALSE;
+}
+
+static void
+ephy_webapp_additional_urls_update_settings (EphyWebappAdditionalURLsDialog *dialog)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
+ gtk_tree_model_foreach (dialog->liststore,
+ (GtkTreeModelForeachFunc)add_to_builder,
+ &builder);
+ g_settings_set (EPHY_SETTINGS_WEB_APP,
+ EPHY_PREFS_WEB_APP_ADDITIONAL_URLS,
+ "as", &builder);
+}
+
+static void
+on_cell_edited (GtkCellRendererText *cell,
+ const gchar *path_string,
+ const gchar *new_text,
+ EphyWebappAdditionalURLsDialog *dialog)
+{
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter (dialog->liststore, &iter, path);
+ gtk_tree_path_free (path);
+
+ if (!new_text || new_text[0] == '\0')
+ gtk_list_store_remove (GTK_LIST_STORE (dialog->liststore), &iter);
+ else
+ gtk_list_store_set (GTK_LIST_STORE (dialog->liststore), &iter, 0, new_text, -1);
+
+ ephy_webapp_additional_urls_update_settings (dialog);
+}
+
+static void
+update_selection_actions (GActionMap *action_map,
+ gboolean has_selection)
+{
+ GAction *action;
+
+ action = g_action_map_lookup_action (action_map, "forget");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), has_selection);
+}
+
+static void
+on_treeview_selection_changed (GtkTreeSelection *selection,
+ EphyWebappAdditionalURLsDialog *dialog)
+{
+ update_selection_actions (G_ACTION_MAP (dialog->action_group),
+ gtk_tree_selection_count_selected_rows (selection) > 0);
+}
+
+static void
+ephy_webapp_additional_urls_dialog_class_init (EphyWebappAdditionalURLsDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/epiphany/gtk/webapp-additional-urls-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, EphyWebappAdditionalURLsDialog, liststore);
+ gtk_widget_class_bind_template_child (widget_class, EphyWebappAdditionalURLsDialog, treeview);
+ gtk_widget_class_bind_template_child (widget_class, EphyWebappAdditionalURLsDialog, url_column);
+ gtk_widget_class_bind_template_child (widget_class, EphyWebappAdditionalURLsDialog, tree_selection);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_treeview_selection_changed);
+ gtk_widget_class_bind_template_callback (widget_class, on_cell_edited);
+}
+
+static void
+add_new (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ EphyWebappAdditionalURLsDialog *dialog = EPHY_WEBAPP_ADDITIONAL_URLS_DIALOG (user_data);
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ gtk_list_store_insert_with_values (GTK_LIST_STORE (dialog->liststore), &iter, 0,
+ 0, "",
+ -1);
+ path = gtk_tree_model_get_path (dialog->liststore, &iter);
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->treeview), path, dialog->url_column, TRUE);
+ gtk_tree_path_free (path);
+}
+
+static void
+forget (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ EphyWebappAdditionalURLsDialog *dialog = EPHY_WEBAPP_ADDITIONAL_URLS_DIALOG (user_data);
+ GList *llist, *rlist = NULL, *l, *r;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter, iter2;
+ GtkTreeRowReference *row_ref = NULL;
+
+ llist = gtk_tree_selection_get_selected_rows (dialog->tree_selection, &model);
+ if (llist == NULL)
+ return;
+
+ for (l = llist; l != NULL; l = l->next) {
+ rlist = g_list_prepend (rlist, gtk_tree_row_reference_new (model, (GtkTreePath *)l->data));
+ }
+
+ /* Intelligent selection logic, no actual selection yet */
+
+ path = gtk_tree_row_reference_get_path ((GtkTreeRowReference *)g_list_first (rlist)->data);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+ iter2 = iter;
+
+ if (gtk_tree_model_iter_next (model, &iter)) {
+ path = gtk_tree_model_get_path (model, &iter);
+ row_ref = gtk_tree_row_reference_new (model, path);
+ } else {
+ path = gtk_tree_model_get_path (model, &iter2);
+ if (gtk_tree_path_prev (path)) {
+ row_ref = gtk_tree_row_reference_new (model, path);
+ }
+ }
+ gtk_tree_path_free (path);
+
+ /* Removal */
+ for (r = rlist; r != NULL; r = r->next) {
+ path = gtk_tree_row_reference_get_path ((GtkTreeRowReference *)r->data);
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_list_store_remove (GTK_LIST_STORE (dialog->liststore), &iter);
+ gtk_tree_row_reference_free ((GtkTreeRowReference *)r->data);
+ gtk_tree_path_free (path);
+ }
+ ephy_webapp_additional_urls_update_settings (dialog);
+
+ g_list_free_full (llist, (GDestroyNotify)gtk_tree_path_free);
+ g_list_free (rlist);
+
+ /* Selection */
+ if (row_ref != NULL) {
+ path = gtk_tree_row_reference_get_path (row_ref);
+
+ if (path != NULL) {
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->treeview), path, NULL, FALSE);
+ gtk_tree_path_free (path);
+ }
+
+ gtk_tree_row_reference_free (row_ref);
+ }
+}
+
+static void
+forget_all (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ EphyWebappAdditionalURLsDialog *dialog = EPHY_WEBAPP_ADDITIONAL_URLS_DIALOG (user_data);
+
+ gtk_list_store_clear (GTK_LIST_STORE (dialog->liststore));
+ g_settings_set_strv (EPHY_SETTINGS_WEB_APP, EPHY_PREFS_WEB_APP_ADDITIONAL_URLS, NULL);
+}
+
+static GActionGroup *
+create_action_group (EphyWebappAdditionalURLsDialog *dialog)
+{
+ const GActionEntry entries[] = {
+ { "new", add_new },
+ { "forget", forget },
+ { "forget-all", forget_all },
+ };
+ GSimpleActionGroup *group;
+
+ group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), dialog);
+
+ return G_ACTION_GROUP (group);
+}
+
+static void
+show_dialog_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ EphyWebappAdditionalURLsDialog *dialog = EPHY_WEBAPP_ADDITIONAL_URLS_DIALOG (widget);
+ char **urls;
+ guint i;
+
+ urls = g_settings_get_strv (EPHY_SETTINGS_WEB_APP, EPHY_PREFS_WEB_APP_ADDITIONAL_URLS);
+ for (i = 0; urls[i]; i++) {
+ gtk_list_store_insert_with_values (GTK_LIST_STORE (dialog->liststore), NULL, -1,
+ 0, urls[i],
+ -1);
+ }
+ g_strfreev (urls);
+}
+
+static void
+ephy_webapp_additional_urls_dialog_init (EphyWebappAdditionalURLsDialog *dialog)
+{
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+
+ dialog->action_group = create_action_group (dialog);
+ gtk_widget_insert_action_group (GTK_WIDGET (dialog), "webapp-additional-urls", dialog->action_group);
+
+ update_selection_actions (G_ACTION_MAP (dialog->action_group), FALSE);
+
+ g_signal_connect (GTK_WIDGET (dialog), "show", G_CALLBACK (show_dialog_cb), NULL);
+}
+
+EphyWebappAdditionalURLsDialog *
+ephy_webapp_additional_urls_dialog_new (void)
+{
+ return EPHY_WEBAPP_ADDITIONAL_URLS_DIALOG (g_object_new (EPHY_TYPE_WEBAPP_ADDITIONAL_URLS_DIALOG,
+ "use-header-bar", TRUE,
+ NULL));
+}
diff --git a/src/webapp-additional-urls-dialog.h b/src/webapp-additional-urls-dialog.h
new file mode 100644
index 000000000..6737ed6a6
--- /dev/null
+++ b/src/webapp-additional-urls-dialog.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2018 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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_WEBAPP_ADDITIONAL_URLS_DIALOG (ephy_webapp_additional_urls_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyWebappAdditionalURLsDialog, ephy_webapp_additional_urls_dialog, EPHY, WEBAPP_ADDITIONAL_URLS_DIALOG, GtkDialog);
+
+EphyWebappAdditionalURLsDialog *ephy_webapp_additional_urls_dialog_new (void);
+
+G_END_DECLS