[gnome-control-center] network: add VPN support to the connection editor



commit 8cfe8d10ba712bf879cd2d2dfc9766a02895f79e
Author: Dan Winship <danw gnome org>
Date:   Fri Jan 11 11:07:46 2013 -0500

    network: add VPN support to the connection editor
    
    Unfortunately, the VPN plugins provide their own .ui files for their
    editor pages, so we can't make them look competely GNOME-3-ish. But
    the code does try to fix them up a little bit by realigning the
    labels.
    
    vpn-helpers.[ch] is nearly identical to network-manager-applet's,
    but eventually this code will move into libnm-gtk.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=691285

 configure.ac                                       |   10 +-
 panels/network/cc-network-panel.c                  |    1 +
 panels/network/connection-editor/Makefile.am       |   10 +-
 panels/network/connection-editor/ce-page-details.c |    4 +-
 panels/network/connection-editor/ce-page-vpn.c     |  188 +++++++++
 panels/network/connection-editor/ce-page-vpn.h     |   74 ++++
 .../connection-editor.gresource.xml                |    1 +
 .../connection-editor/net-connection-editor.c      |    6 +-
 panels/network/connection-editor/vpn-helpers.c     |  414 ++++++++++++++++++++
 panels/network/connection-editor/vpn-helpers.h     |   44 ++
 panels/network/connection-editor/vpn-page.ui       |   70 ++++
 panels/network/net-vpn.c                           |   38 ++-
 panels/network/network-vpn.ui                      |    8 +-
 13 files changed, 848 insertions(+), 20 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 9d9146a..9b51cb0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -99,7 +99,7 @@ CANBERRA_REQUIRED_VERSION=0.13
 GDKPIXBUF_REQUIRED_VERSION=2.23.0
 POLKIT_REQUIRED_VERSION=0.103
 GSD_REQUIRED_VERSION=3.7.3
-NETWORK_MANAGER_REQUIRED_VERSION=0.9.6.4
+NETWORK_MANAGER_REQUIRED_VERSION=0.9.7.995
 NETWORK_MANAGER_APPLET_REQUIRED_VERSION=0.9.7
 LIBNOTIFY_REQUIRED_VERSION=0.7.3
 GNOME_DESKTOP_REQUIRED_VERSION=3.7.5
@@ -179,20 +179,24 @@ AM_CONDITIONAL(HAVE_INTROSPECTION, false)
 GDESKTOP_PREFIX=`$PKG_CONFIG --variable prefix gsettings-desktop-schemas`
 AC_SUBST(GDESKTOP_PREFIX)
 
-# Check for NetworkManager ~0.9
 PKG_CHECK_MODULES(NETWORK_MANAGER, NetworkManager >= $NETWORK_MANAGER_REQUIRED_VERSION
                   libnm-glib >= $NETWORK_MANAGER_REQUIRED_VERSION
+                  libnm-glib-vpn >= $NETWORK_MANAGER_REQUIRED_VERSION
                   libnm-util >= $NETWORK_MANAGER_REQUIRED_VERSION
 		  libnm-gtk >= $NETWORK_MANAGER_APPLET_REQUIRED_VERSION,
                   [have_networkmanager=yes], have_networkmanager=no)
 if test "x$have_networkmanager" = xno ; then
-        AC_MSG_WARN(*** Network panel will not be built (NetworkManager ~0.9 or newer not found) ***)
+        AC_MSG_WARN(*** Network panel will not be built (NetworkManager $NETWORK_MANAGER_REQUIRED_VERSION or newer not found) ***)
 else
 	AC_DEFINE(BUILD_NETWORK, 1, [Define to 1 to build the Network panel])
 fi
 AM_CONDITIONAL(BUILD_NETWORK, [test x$have_networkmanager = xyes])
 if test x${have_networkmanager} = xyes; then
   AC_DEFINE(HAVE_NETWORK_MANAGER, 1, [Define to 1 if NetworkManager is available])
+  NM_VPN_CONFIG_DIR=`$PKG_CONFIG --variable configdir NetworkManager`/VPN
+  NM_VPN_MODULE_DIR=`$PKG_CONFIG --variable plugindir NetworkManager`
+  AC_SUBST(NM_VPN_CONFIG_DIR)
+  AC_SUBST(NM_VPN_MODULE_DIR)
 fi
 
 # Check for gnome-bluetooth
diff --git a/panels/network/cc-network-panel.c b/panels/network/cc-network-panel.c
index 0091e4d..6526404 100644
--- a/panels/network/cc-network-panel.c
+++ b/panels/network/cc-network-panel.c
@@ -1020,6 +1020,7 @@ panel_add_vpn_device (CcNetworkPanel *panel, NMConnection *connection)
                                 "id", id,
                                 "connection", connection,
                                 "client", panel->priv->client,
+                                "remote-settings", panel->priv->remote_settings,
                                 NULL);
         g_signal_connect_object (net_vpn, "removed",
                                  G_CALLBACK (object_removed_cb), panel, 0);
diff --git a/panels/network/connection-editor/Makefile.am b/panels/network/connection-editor/Makefile.am
index db1c150..de8bce4 100644
--- a/panels/network/connection-editor/Makefile.am
+++ b/panels/network/connection-editor/Makefile.am
@@ -25,12 +25,18 @@ libconnection_editor_la_SOURCES = 		\
 	ce-page-ethernet.h			\
 	ce-page-ethernet.c			\
 	ce-page-8021x-security.h		\
-	ce-page-8021x-security.c
+	ce-page-8021x-security.c		\
+	ce-page-vpn.h				\
+	ce-page-vpn.c				\
+	vpn-helpers.h				\
+	vpn-helpers.c
 
 libconnection_editor_la_CPPFLAGS = 		\
 	-I$(srcdir)/../wireless-security	\
 	$(NETWORK_PANEL_CFLAGS) 		\
-        $(NETWORK_MANAGER_CFLAGS)
+        $(NETWORK_MANAGER_CFLAGS)		\
+	-DNM_VPN_CONFIG_DIR=\""$(NM_VPN_CONFIG_DIR)"\" \
+	-DNM_VPN_MODULE_DIR=\""$(NM_VPN_MODULE_DIR)"\"
 
 libconnection_editor_la_LIBADD = 		\
 	$(builddir)/../wireless-security/libwireless-security.la \
diff --git a/panels/network/connection-editor/ce-page-details.c b/panels/network/connection-editor/ce-page-details.c
index 5bb05d8..6f62416 100644
--- a/panels/network/connection-editor/ce-page-details.c
+++ b/panels/network/connection-editor/ce-page-details.c
@@ -128,7 +128,7 @@ connect_details_page (CEPageDetails *page)
         else
                 active_ap = NULL;
 
-        state = nm_device_get_state (page->device);
+        state = page->device ? nm_device_get_state (page->device) : NM_DEVICE_STATE_DISCONNECTED;
 
         device_is_active = FALSE;
         speed = 0;
@@ -136,7 +136,7 @@ connect_details_page (CEPageDetails *page)
                 device_is_active = TRUE;
                 if (NM_IS_DEVICE_WIFI (page->device))
                         speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (page->device)) / 1000;
-        } else {
+        } else if (page->device) {
                 NMActiveConnection *ac;
                 const gchar *p1, *p2;
 
diff --git a/panels/network/connection-editor/ce-page-vpn.c b/panels/network/connection-editor/ce-page-vpn.c
new file mode 100644
index 0000000..1eed050
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-vpn.c
@@ -0,0 +1,188 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Red Hat, Inc
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <glib/gi18n.h>
+
+#include <nm-utils.h>
+
+#include "ce-page-vpn.h"
+#include "vpn-helpers.h"
+
+G_DEFINE_TYPE (CEPageVpn, ce_page_vpn, CE_TYPE_PAGE)
+
+/* Hack to make the plugin-provided editor widget fit in better with
+ * the control center by changing
+ *
+ *     Foo:     [__________]
+ *     Bar baz: [__________]
+ *
+ * to
+ *
+ *          Foo [__________]
+ *      Bar baz [__________]
+ */
+static void
+vpn_gnome3ify_editor (GtkWidget *widget)
+{
+        if (GTK_IS_CONTAINER (widget)) {
+                GList *children, *iter;
+
+                children = gtk_container_get_children (GTK_CONTAINER (widget));
+                for (iter = children; iter; iter = iter->next)
+                        vpn_gnome3ify_editor (iter->data);
+                g_list_free (children);
+        } else if (GTK_IS_LABEL (widget)) {
+                const char *text;
+                gfloat xalign, yalign;
+                char *newtext;
+                int len;
+
+                gtk_misc_get_alignment (GTK_MISC (widget), &xalign, &yalign);
+                if (xalign != 0.0)
+                        return;
+                text = gtk_label_get_text (GTK_LABEL (widget));
+                len = strlen (text);
+                if (len < 2 || text[len - 1] != ':')
+                        return;
+
+                newtext = g_strndup (text, len - 1);
+                gtk_label_set_text (GTK_LABEL (widget), newtext);
+                g_free (newtext);
+                gtk_misc_set_alignment (GTK_MISC (widget), 1.0, yalign);
+        }
+}
+
+static void
+load_vpn_plugin (CEPageVpn *page, NMConnection *connection)
+{
+	CEPage *parent = CE_PAGE (page);
+        GtkWidget *ui_widget, *failure;
+
+	page->ui = nm_vpn_plugin_ui_interface_ui_factory (page->plugin, connection, NULL);
+	if (!page->ui) {
+                page->plugin = NULL;
+		return;
+        }
+	ui_widget = GTK_WIDGET (nm_vpn_plugin_ui_widget_interface_get_widget (page->ui));
+	if (!ui_widget) {
+		g_clear_object (&page->ui);
+                page->plugin = NULL;
+		return;
+	}
+        vpn_gnome3ify_editor (ui_widget);
+
+        failure = GTK_WIDGET (gtk_builder_get_object (parent->builder, "failure_label"));
+        gtk_widget_destroy (failure);
+
+        gtk_box_pack_start (page->box, ui_widget, TRUE, TRUE, 0);
+	gtk_widget_show_all (ui_widget);
+
+        g_signal_connect_swapped (page->ui, "changed", G_CALLBACK (ce_page_changed), page);
+}
+
+static void
+connect_vpn_page (CEPageVpn *page)
+{
+        const gchar *name;
+
+        name = nm_setting_connection_get_id (page->setting_connection);
+        gtk_entry_set_text (page->name, name);
+        g_signal_connect_swapped (page->name, "changed", G_CALLBACK (ce_page_changed), page);
+}
+
+static gboolean
+validate (CEPage        *page,
+          NMConnection  *connection,
+          GError       **error)
+{
+        CEPageVpn *self = CE_PAGE_VPN (page);
+
+        g_object_set (self->setting_connection,
+                      NM_SETTING_CONNECTION_ID, gtk_entry_get_text (self->name),
+                      NULL);
+        if (!nm_setting_verify (NM_SETTING (self->setting_connection), NULL, error))
+                return FALSE;
+
+        if (!self->ui)
+                return TRUE;
+
+	return nm_vpn_plugin_ui_widget_interface_update_connection (self->ui, connection, error);
+}
+
+static void
+ce_page_vpn_init (CEPageVpn *page)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+        CEPageVpn *page = CE_PAGE_VPN (object);
+
+        g_clear_object (&page->ui);
+
+        G_OBJECT_CLASS (ce_page_vpn_parent_class)->dispose (object);
+}
+
+static void
+ce_page_vpn_class_init (CEPageVpnClass *class)
+{
+        CEPageClass *page_class = CE_PAGE_CLASS (class);
+        GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+        object_class->dispose = dispose;
+
+        page_class->validate = validate;
+}
+
+CEPage *
+ce_page_vpn_new (NMConnection     *connection,
+		 NMClient         *client,
+		 NMRemoteSettings *settings)
+{
+        CEPageVpn *page;
+	const char *vpn_type;
+
+        page = CE_PAGE_VPN (ce_page_new (CE_TYPE_PAGE_VPN,
+					 connection,
+					 client,
+					 settings,
+					 "/org/gnome/control-center/network/vpn-page.ui",
+					 _("Identity")));
+
+        page->name = GTK_ENTRY (gtk_builder_get_object (CE_PAGE (page)->builder, "entry_name"));
+        page->box = GTK_BOX (gtk_builder_get_object (CE_PAGE (page)->builder, "page"));
+
+	page->setting_connection = nm_connection_get_setting_connection (connection);
+	page->setting_vpn = nm_connection_get_setting_vpn (connection);
+	vpn_type = nm_setting_vpn_get_service_type (page->setting_vpn);
+
+	page->plugin = vpn_get_plugin_by_service (vpn_type);
+	if (page->plugin)
+                load_vpn_plugin (page, connection);
+
+        connect_vpn_page (page);
+
+        return CE_PAGE (page);
+}
diff --git a/panels/network/connection-editor/ce-page-vpn.h b/panels/network/connection-editor/ce-page-vpn.h
new file mode 100644
index 0000000..dfed60a
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-vpn.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 vpn.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __CE_PAGE_VPN_H
+#define __CE_PAGE_VPN_H
+
+#include <glib-object.h>
+
+#include <nm-setting-wired.h>
+#define NM_VPN_API_SUBJECT_TO_CHANGE
+#include <nm-vpn-plugin-ui-interface.h>
+
+#include <gtk/gtk.h>
+#include "ce-page.h"
+
+G_BEGIN_DECLS
+
+#define CE_TYPE_PAGE_VPN          (ce_page_vpn_get_type ())
+#define CE_PAGE_VPN(o)            (G_TYPE_CHECK_INSTANCE_CAST ((o), CE_TYPE_PAGE_VPN, CEPageVpn))
+#define CE_PAGE_VPN_CLASS(k)      (G_TYPE_CHECK_CLASS_CAST((k), CE_TYPE_PAGE_VPN, CEPageVpnClass))
+#define CE_IS_PAGE_VPN(o)         (G_TYPE_CHECK_INSTANCE_TYPE ((o), CE_TYPE_PAGE_VPN))
+#define CE_IS_PAGE_VPN_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE ((k), CE_TYPE_PAGE_VPN))
+#define CE_PAGE_VPN_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS ((o), CE_TYPE_PAGE_VPN, CEPageVpnClass))
+
+typedef struct _CEPageVpn          CEPageVpn;
+typedef struct _CEPageVpnClass     CEPageVpnClass;
+
+struct _CEPageVpn
+{
+        CEPage parent;
+
+        NMSettingConnection *setting_connection;
+        NMSettingVPN *setting_vpn;
+
+        GtkEntry *name;
+        GtkBox   *box;
+
+	NMVpnPluginUiInterface *plugin;
+	NMVpnPluginUiWidgetInterface *ui;
+};
+
+struct _CEPageVpnClass
+{
+        CEPageClass parent_class;
+};
+
+GType   ce_page_vpn_get_type (void);
+
+CEPage *ce_page_vpn_new      (NMConnection     *connection,
+			      NMClient         *client,
+			      NMRemoteSettings *settings);
+
+G_END_DECLS
+
+#endif /* __CE_PAGE_VPN_H */
+
diff --git a/panels/network/connection-editor/connection-editor.gresource.xml b/panels/network/connection-editor/connection-editor.gresource.xml
index 74bf933..e9ad7d9 100644
--- a/panels/network/connection-editor/connection-editor.gresource.xml
+++ b/panels/network/connection-editor/connection-editor.gresource.xml
@@ -9,6 +9,7 @@
     <file preprocess="xml-stripblanks">ip6-page.ui</file>
     <file preprocess="xml-stripblanks">reset-page.ui</file>
     <file preprocess="xml-stripblanks">security-page.ui</file>
+    <file preprocess="xml-stripblanks">vpn-page.ui</file>
     <file preprocess="xml-stripblanks">wifi-page.ui</file>
   </gresource>
 </gresources>
diff --git a/panels/network/connection-editor/net-connection-editor.c b/panels/network/connection-editor/net-connection-editor.c
index e889e3a..812b8a1 100644
--- a/panels/network/connection-editor/net-connection-editor.c
+++ b/panels/network/connection-editor/net-connection-editor.c
@@ -37,6 +37,7 @@
 #include "ce-page-reset.h"
 #include "ce-page-ethernet.h"
 #include "ce-page-8021x-security.h"
+#include "ce-page-vpn.h"
 
 #include "egg-list-box/egg-list-box.h"
 
@@ -457,6 +458,8 @@ net_connection_editor_set_connection (NetConnectionEditor *editor,
                 add_page (editor, ce_page_wifi_new (editor->connection, editor->client, editor->settings));
         else if (strcmp (type, NM_SETTING_WIRED_SETTING_NAME) == 0)
                 add_page (editor, ce_page_ethernet_new (editor->connection, editor->client, editor->settings));
+        else if (strcmp (type, NM_SETTING_VPN_SETTING_NAME) == 0)
+                add_page (editor, ce_page_vpn_new (editor->connection, editor->client, editor->settings));
 
         add_page (editor, ce_page_ip4_new (editor->connection, editor->client, editor->settings));
         add_page (editor, ce_page_ip6_new (editor->connection, editor->client, editor->settings));
@@ -514,7 +517,8 @@ net_connection_editor_new (GtkWindow        *parent_window,
         }
         if (ap)
                 editor->ap = g_object_ref (ap);
-        editor->device = g_object_ref (device);
+        if (device)
+                editor->device = g_object_ref (device);
         editor->client = g_object_ref (client);
         editor->settings = g_object_ref (settings);
 
diff --git a/panels/network/connection-editor/vpn-helpers.c b/panels/network/connection-editor/vpn-helpers.c
new file mode 100644
index 0000000..5be39ad
--- /dev/null
+++ b/panels/network/connection-editor/vpn-helpers.c
@@ -0,0 +1,414 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * Dan Williams <dcbw redhat com>
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2008 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib.h>
+#include <gmodule.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <nm-connection.h>
+#include <nm-setting-connection.h>
+#include <nm-setting-vpn.h>
+
+#include "vpn-helpers.h"
+
+#define NM_VPN_API_SUBJECT_TO_CHANGE
+#include "nm-vpn-plugin-ui-interface.h"
+
+static GHashTable *plugins = NULL;
+
+NMVpnPluginUiInterface *
+vpn_get_plugin_by_service (const char *service)
+{
+	g_return_val_if_fail (service != NULL, NULL);
+
+	if (!plugins) {
+		vpn_get_plugins (NULL);
+		if (!plugins)
+			return NULL;
+	}
+	return g_hash_table_lookup (plugins, service);
+}
+
+GHashTable *
+vpn_get_plugins (GError **error)
+{
+	GDir *dir;
+	const char *f;
+
+	if (error)
+		g_return_val_if_fail (*error == NULL, NULL);
+
+	if (plugins)
+		return plugins;
+
+	dir = g_dir_open (NM_VPN_CONFIG_DIR, 0, error);
+	if (!dir)
+		return NULL;
+
+	plugins = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                 (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref);
+
+	while ((f = g_dir_read_name (dir))) {
+		char *path = NULL, *service = NULL;
+		char *so_path = NULL, *so_name = NULL;
+		GKeyFile *keyfile = NULL;
+		GModule *module;
+		NMVpnPluginUiFactory factory = NULL;
+
+		if (!g_str_has_suffix (f, ".name"))
+			continue;
+
+		path = g_strdup_printf ("%s/%s", NM_VPN_CONFIG_DIR, f);
+
+		keyfile = g_key_file_new ();
+		if (!g_key_file_load_from_file (keyfile, path, 0, NULL))
+			goto next;
+
+		service = g_key_file_get_string (keyfile, "VPN Connection", "service", NULL);
+		if (!service)
+			goto next;
+
+		so_path = g_key_file_get_string (keyfile,  "GNOME", "properties", NULL);
+		if (!so_path)
+			goto next;
+
+		/* Remove any path and extension components, then reconstruct path
+		 * to the SO in LIBDIR
+		 */
+		so_name = g_path_get_basename (so_path);
+		g_free (so_path);
+		so_path = g_build_filename (NM_VPN_MODULE_DIR, so_name, NULL);
+		g_free (so_name);
+
+		module = g_module_open (so_path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+		if (!module) {
+			g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot load the VPN plugin which provides the "
+			             "service '%s'.", service);
+			goto next;
+		}
+
+		if (g_module_symbol (module, "nm_vpn_plugin_ui_factory", (gpointer) &factory)) {
+			NMVpnPluginUiInterface *plugin;
+			GError *factory_error = NULL;
+			gboolean success = FALSE;
+
+			plugin = factory (&factory_error);
+			if (plugin) {
+				char *plug_name = NULL, *plug_service = NULL;
+
+				/* Validate plugin properties */
+				g_object_get (G_OBJECT (plugin),
+				              NM_VPN_PLUGIN_UI_INTERFACE_NAME, &plug_name,
+				              NM_VPN_PLUGIN_UI_INTERFACE_SERVICE, &plug_service,
+				              NULL);
+				if (!plug_name || !strlen (plug_name)) {
+					g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot load VPN plugin in '%s': missing plugin name", 
+					             g_module_name (module));
+				} else if (!plug_service || strcmp (plug_service, service)) {
+					g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot load VPN plugin in '%s': invalid service name", 
+					             g_module_name (module));
+				} else {
+					/* Success! */
+					g_object_set_data_full (G_OBJECT (plugin), "gmodule", module,
+					                        (GDestroyNotify) g_module_close);
+					g_hash_table_insert (plugins, g_strdup (service), plugin);
+					success = TRUE;
+				}
+				g_free (plug_name);
+				g_free (plug_service);
+			} else {
+				g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot load VPN plugin in '%s': %s", 
+				             g_module_name (module), g_module_error ());
+			}
+
+			if (!success)
+				g_module_close (module);
+		} else {
+			g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot locate nm_vpn_plugin_ui_factory() in '%s': %s", 
+			             g_module_name (module), g_module_error ());
+			g_module_close (module);
+		}
+
+	next:
+		g_free (so_path);
+		g_free (service);
+		g_key_file_free (keyfile);
+		g_free (path);
+	}
+	g_dir_close (dir);
+
+	return plugins;
+}
+
+typedef struct {
+	VpnImportSuccessCallback callback;
+	gpointer user_data;
+} ActionInfo;
+
+static void
+import_vpn_from_file_cb (GtkWidget *dialog, gint response, gpointer user_data)
+{
+	char *filename = NULL;
+	ActionInfo *info = (ActionInfo *) user_data;
+	GHashTableIter iter;
+	gpointer key;
+	NMVpnPluginUiInterface *plugin;
+	NMConnection *connection = NULL;
+	GError *error = NULL;
+
+	if (response != GTK_RESPONSE_ACCEPT)
+		goto out;
+
+	filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+	if (!filename) {
+		g_warning ("%s: didn't get a filename back from the chooser!", __func__);
+		goto out;
+	}
+
+	g_hash_table_iter_init (&iter, plugins);
+	while (!connection && g_hash_table_iter_next (&iter, &key, (gpointer *)&plugin)) {
+		g_clear_error (&error);
+		connection = nm_vpn_plugin_ui_interface_import (plugin, filename, &error);
+	}
+
+	if (connection)
+		info->callback (connection, info->user_data);
+	else {
+		GtkWidget *err_dialog;
+		char *bname = g_path_get_basename (filename);
+
+		err_dialog = gtk_message_dialog_new (NULL,
+		                                     GTK_DIALOG_DESTROY_WITH_PARENT,
+		                                     GTK_MESSAGE_ERROR,
+		                                     GTK_BUTTONS_OK,
+		                                     _("Cannot import VPN connection"));
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (err_dialog),
+		                                 _("The file '%s' could not be read or does not contain recognized VPN connection information\n\nError: %s."),
+		                                 bname, error ? error->message : "unknown error");
+		g_free (bname);
+		g_signal_connect (err_dialog, "delete-event", G_CALLBACK (gtk_widget_destroy), NULL);
+		g_signal_connect (err_dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+		gtk_widget_show_all (err_dialog);
+		gtk_window_present (GTK_WINDOW (err_dialog));
+	}
+
+	g_clear_error (&error);
+	g_free (filename);
+
+out:
+	gtk_widget_hide (dialog);
+	gtk_widget_destroy (dialog);
+	g_free (info);
+}
+
+static void
+destroy_import_chooser (GtkWidget *dialog, gpointer user_data)
+{
+	g_free (user_data);
+	gtk_widget_destroy (dialog);
+}
+
+void
+vpn_import (VpnImportSuccessCallback callback, gpointer user_data)
+{
+	GtkWidget *dialog;
+	ActionInfo *info;
+	const char *home_folder;
+
+	dialog = gtk_file_chooser_dialog_new (_("Select file to import"),
+	                                      NULL,
+	                                      GTK_FILE_CHOOSER_ACTION_OPEN,
+	                                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                                      GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+	                                      NULL);
+	home_folder = g_get_home_dir ();
+	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder);
+
+	info = g_malloc0 (sizeof (ActionInfo));
+	info->callback = callback;
+	info->user_data = user_data;
+
+	g_signal_connect (G_OBJECT (dialog), "close", G_CALLBACK (destroy_import_chooser), info);
+	g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (import_vpn_from_file_cb), info);
+	gtk_widget_show_all (dialog);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+export_vpn_to_file_cb (GtkWidget *dialog, gint response, gpointer user_data)
+{
+	NMConnection *connection = NM_CONNECTION (user_data);
+	char *filename = NULL;
+	GError *error = NULL;
+	NMVpnPluginUiInterface *plugin;
+	NMSettingConnection *s_con = NULL;
+	NMSettingVPN *s_vpn = NULL;
+	const char *service_type;
+	const char *id = NULL;
+	gboolean success = FALSE;
+
+	if (response != GTK_RESPONSE_ACCEPT)
+		goto out;
+
+	filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+	if (!filename) {
+		g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "no filename");
+		goto done;
+	}
+
+	if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+		int replace_response;
+		GtkWidget *replace_dialog;
+		char *bname;
+
+		bname = g_path_get_basename (filename);
+		replace_dialog = gtk_message_dialog_new (NULL,
+		                                         GTK_DIALOG_DESTROY_WITH_PARENT,
+		                                         GTK_MESSAGE_QUESTION,
+		                                         GTK_BUTTONS_CANCEL,
+		                                         _("A file named \"%s\" already exists."),
+		                                         bname);
+		gtk_dialog_add_buttons (GTK_DIALOG (replace_dialog), _("_Replace"), GTK_RESPONSE_OK, NULL);
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (replace_dialog),
+							  _("Do you want to replace %s with the VPN connection you are saving?"), bname);
+		g_free (bname);
+		replace_response = gtk_dialog_run (GTK_DIALOG (replace_dialog));
+		gtk_widget_destroy (replace_dialog);
+		if (replace_response != GTK_RESPONSE_OK)
+			goto out;
+	}
+
+	s_con = nm_connection_get_setting_connection (connection);
+	id = s_con ? nm_setting_connection_get_id (s_con) : NULL;
+	if (!id) {
+		g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "connection setting invalid");
+		goto done;
+	}
+
+	s_vpn = nm_connection_get_setting_vpn (connection);
+	service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL;
+
+	if (!service_type) {
+		g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "VPN setting invalid");
+		goto done;
+	}
+
+	plugin = vpn_get_plugin_by_service (service_type);
+	if (plugin)
+		success = nm_vpn_plugin_ui_interface_export (plugin, filename, connection, &error);
+
+done:
+	if (!success) {
+		GtkWidget *err_dialog;
+		char *bname = filename ? g_path_get_basename (filename) : g_strdup ("(none)");
+
+		err_dialog = gtk_message_dialog_new (NULL,
+		                                     GTK_DIALOG_DESTROY_WITH_PARENT,
+		                                     GTK_MESSAGE_ERROR,
+		                                     GTK_BUTTONS_OK,
+		                                     _("Cannot export VPN connection"));
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (err_dialog),
+		                                 _("The VPN connection '%s' could not be exported to %s.\n\nError: %s."),
+		                                 id ? id : "(unknown)", bname, error ? error->message : "unknown error");
+		g_free (bname);
+		g_signal_connect (err_dialog, "delete-event", G_CALLBACK (gtk_widget_destroy), NULL);
+		g_signal_connect (err_dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+		gtk_widget_show_all (err_dialog);
+		gtk_window_present (GTK_WINDOW (err_dialog));
+	}
+
+out:
+	if (error)
+		g_error_free (error);
+	g_object_unref (connection);
+
+	gtk_widget_hide (dialog);
+	gtk_widget_destroy (dialog);
+}
+
+void
+vpn_export (NMConnection *connection)
+{
+	GtkWidget *dialog;
+	NMVpnPluginUiInterface *plugin;
+	NMSettingVPN *s_vpn = NULL;
+	const char *service_type;
+	const char *home_folder;
+
+	s_vpn = nm_connection_get_setting_vpn (connection);
+	service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL;
+
+	if (!service_type) {
+		g_warning ("%s: invalid VPN connection!", __func__);
+		return;
+	}
+
+	dialog = gtk_file_chooser_dialog_new (_("Export VPN connection..."),
+	                                      NULL,
+	                                      GTK_FILE_CHOOSER_ACTION_SAVE,
+	                                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                                      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+	                                      NULL);
+	home_folder = g_get_home_dir ();
+	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder);
+
+	plugin = vpn_get_plugin_by_service (service_type);
+	if (plugin) {
+		char *suggested = NULL;
+
+		suggested = nm_vpn_plugin_ui_interface_get_suggested_name (plugin, connection);
+		if (suggested) {
+			gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), suggested);
+			g_free (suggested);
+		}
+	}
+
+	g_signal_connect (G_OBJECT (dialog), "close", G_CALLBACK (gtk_widget_destroy), NULL);
+	g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (export_vpn_to_file_cb), g_object_ref (connection));
+	gtk_widget_show_all (dialog);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+gboolean
+vpn_supports_ipv6 (NMConnection *connection)
+{
+	NMSettingVPN *s_vpn;
+	const char *service_type;
+	NMVpnPluginUiInterface *plugin;
+	guint32 capabilities;
+
+	s_vpn = nm_connection_get_setting_vpn (connection);
+	g_return_val_if_fail (s_vpn != NULL, FALSE);
+
+	service_type = nm_setting_vpn_get_service_type (s_vpn);
+	g_return_val_if_fail (service_type != NULL, FALSE);
+
+	plugin = vpn_get_plugin_by_service (service_type);
+	g_return_val_if_fail (plugin != NULL, FALSE);
+
+	capabilities = nm_vpn_plugin_ui_interface_get_capabilities (plugin);
+	return (capabilities & NM_VPN_PLUGIN_UI_CAPABILITY_IPV6) != 0;
+}
diff --git a/panels/network/connection-editor/vpn-helpers.h b/panels/network/connection-editor/vpn-helpers.h
new file mode 100644
index 0000000..d14fc8f
--- /dev/null
+++ b/panels/network/connection-editor/vpn-helpers.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * Dan Williams <dcbw redhat com>
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2008 Red Hat, Inc.
+ */
+
+#ifndef _VPN_HELPERS_H_
+#define _VPN_HELPERS_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <nm-connection.h>
+
+#define NM_VPN_API_SUBJECT_TO_CHANGE
+#include <nm-vpn-plugin-ui-interface.h>
+
+GHashTable *vpn_get_plugins (GError **error);
+
+NMVpnPluginUiInterface *vpn_get_plugin_by_service (const char *service);
+
+typedef void (*VpnImportSuccessCallback) (NMConnection *connection, gpointer user_data);
+void vpn_import (VpnImportSuccessCallback callback, gpointer user_data);
+
+void vpn_export (NMConnection *connection);
+
+gboolean vpn_supports_ipv6 (NMConnection *connection);
+
+#endif  /* _VPN_HELPERS_H_ */
diff --git a/panels/network/connection-editor/vpn-page.ui b/panels/network/connection-editor/vpn-page.ui
new file mode 100644
index 0000000..050bc0b
--- /dev/null
+++ b/panels/network/connection-editor/vpn-page.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkBox" id="page">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_left">50</property>
+    <property name="margin_right">50</property>
+    <property name="margin_top">12</property>
+    <property name="margin_bottom">12</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">10</property>
+    <child>
+      <object class="GtkBox" id="box2">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="heading_name">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">1</property>
+            <property name="label" translatable="yes">_Name</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="entry_name">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">â</property>
+            <property name="invisible_char_set">True</property>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="failure_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">(Error: unable to load VPN connection editor)</property>
+        <attributes>
+          <attribute name="style" value="italic"/>
+        </attributes>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkSizeGroup" id="sizegroup1"/>
+</interface>
diff --git a/panels/network/net-vpn.c b/panels/network/net-vpn.c
index 7cfaa7a..61ed8cc 100644
--- a/panels/network/net-vpn.c
+++ b/panels/network/net-vpn.c
@@ -31,6 +31,8 @@
 #include "nm-remote-connection.h"
 #include "nm-setting-vpn.h"
 
+#include "connection-editor/net-connection-editor.h"
+
 #define NET_VPN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NET_TYPE_VPN, NetVpnPrivate))
 
 struct _NetVpnPrivate
@@ -382,21 +384,35 @@ edit_connection (GtkButton *button, NetVpn *vpn)
 }
 
 static void
+editor_done (NetConnectionEditor *editor,
+             gboolean             success,
+             NetVpn              *vpn)
+{
+        g_object_unref (editor);
+        net_object_refresh (NET_OBJECT (vpn));
+}
+
+static void
 vpn_proxy_edit (NetObject *object)
 {
-        const gchar *uuid;
-        gchar *cmdline;
-        GError *error = NULL;
         NetVpn *vpn = NET_VPN (object);
+        GtkWidget *button, *window;
+        NetConnectionEditor *editor;
+        NMClient *client;
+        NMRemoteSettings *settings;
 
-        uuid = nm_connection_get_uuid (vpn->priv->connection);
-        cmdline = g_strdup_printf ("nm-connection-editor --edit %s", uuid);
-        g_debug ("Launching '%s'\n", cmdline);
-        if (!g_spawn_command_line_async (cmdline, &error)) {
-                g_warning ("Failed to launch nm-connection-editor: %s", error->message);
-                g_error_free (error);
-        }
-        g_free (cmdline);
+        button = GTK_WIDGET (gtk_builder_get_object (vpn->priv->builder,
+                                                     "button_options"));
+        window = gtk_widget_get_toplevel (button);
+
+        client = net_object_get_client (object);
+        settings = net_object_get_remote_settings (object);
+
+        editor = net_connection_editor_new (GTK_WINDOW (window),
+                                            vpn->priv->connection,
+                                            NULL, NULL, client, settings);
+        g_signal_connect (editor, "done", G_CALLBACK (editor_done), vpn);
+        net_connection_editor_run (editor);
 }
 
 /**
diff --git a/panels/network/network-vpn.ui b/panels/network/network-vpn.ui
index 8995a5c..565461c 100644
--- a/panels/network/network-vpn.ui
+++ b/panels/network/network-vpn.ui
@@ -295,7 +295,6 @@
             <property name="can_focus">False</property>
             <child>
               <object class="GtkButton" id="button_options">
-                <property name="label" translatable="yes">_Configureâ</property>
                 <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
@@ -306,6 +305,13 @@
                 <property name="vexpand">True</property>
                 <property name="use_underline">True</property>
                 <property name="xalign">1</property>
+                <child>
+                  <object class="GtkImage" id="image1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">emblem-system-symbolic</property>
+                  </object>
+                </child>
               </object>
               <packing>
                 <property name="expand">False</property>



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