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-21 14:08:15 +0200
commitacbf3e730784ed57a84b1e90db9db55d8a4c3f58 (patch)
tree22a86f3dfcb6a567956f19e21b11f76068ec16f5
parent25463107e67d26393972d16eac86491c02d6a110 (diff)
downloadepiphany-carlosgc/webapp-config.tar.gz
web-apps: add a web application section to preferences dialogcarlosgc/webapp-config
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.c199
-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.h32
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..e35fe2499 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 25cc5e22f..88bef7cec 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 1ee501cff..526ca7a3c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -67,6 +67,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 1522126eb..70829cef3 100644
--- a/src/ephy-window.c
+++ b/src/ephy-window.c
@@ -48,6 +48,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"
@@ -1925,7 +1926,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,
@@ -1938,8 +1940,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 b0781a170..a6061a3f6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -39,6 +39,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 091dea877..02d45f2d5 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 *search_box;
@@ -179,6 +187,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);
}
@@ -714,6 +724,166 @@ 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)
@@ -774,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);
@@ -833,6 +1007,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);
@@ -1449,11 +1626,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
@@ -1701,6 +1883,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);
@@ -2030,6 +2219,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 09f5d634d..a1d6fe88e 100644
--- a/src/resources/epiphany.gresource.xml
+++ b/src/resources/epiphany.gresource.xml
@@ -30,6 +30,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 599cf5f12..73077c102 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">
<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..31749b0ef
--- /dev/null
+++ b/src/webapp-additional-urls-dialog.h
@@ -0,0 +1,32 @@
+/* -*- 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