summaryrefslogtreecommitdiffstats
path: root/panels/network/connection-editor
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--panels/network/connection-editor/8021x-security-page.ui62
-rw-r--r--panels/network/connection-editor/ce-page-8021x-security.c209
-rw-r--r--panels/network/connection-editor/ce-page-8021x-security.h34
-rw-r--r--panels/network/connection-editor/ce-page-details.c525
-rw-r--r--panels/network/connection-editor/ce-page-details.h38
-rw-r--r--panels/network/connection-editor/ce-page-ethernet.c224
-rw-r--r--panels/network/connection-editor/ce-page-ethernet.h34
-rw-r--r--panels/network/connection-editor/ce-page-ip4.c895
-rw-r--r--panels/network/connection-editor/ce-page-ip4.h34
-rw-r--r--panels/network/connection-editor/ce-page-ip6.c829
-rw-r--r--panels/network/connection-editor/ce-page-ip6.h34
-rw-r--r--panels/network/connection-editor/ce-page-security.c542
-rw-r--r--panels/network/connection-editor/ce-page-security.h33
-rw-r--r--panels/network/connection-editor/ce-page-vpn.c229
-rw-r--r--panels/network/connection-editor/ce-page-vpn.h33
-rw-r--r--panels/network/connection-editor/ce-page-wifi.c212
-rw-r--r--panels/network/connection-editor/ce-page-wifi.h32
-rw-r--r--panels/network/connection-editor/ce-page.c417
-rw-r--r--panels/network/connection-editor/ce-page.h79
-rw-r--r--panels/network/connection-editor/connection-editor.gresource.xml14
-rw-r--r--panels/network/connection-editor/connection-editor.ui108
-rw-r--r--panels/network/connection-editor/details-page.ui483
-rw-r--r--panels/network/connection-editor/ethernet-page.ui177
-rw-r--r--panels/network/connection-editor/ip4-page.ui442
-rw-r--r--panels/network/connection-editor/ip6-page.ui456
-rw-r--r--panels/network/connection-editor/meson.build46
-rw-r--r--panels/network/connection-editor/net-connection-editor.c868
-rw-r--r--panels/network/connection-editor/net-connection-editor.h42
-rw-r--r--panels/network/connection-editor/security-page.ui61
-rw-r--r--panels/network/connection-editor/vpn-helpers.c318
-rw-r--r--panels/network/connection-editor/vpn-helpers.h39
-rw-r--r--panels/network/connection-editor/vpn-page.ui70
-rw-r--r--panels/network/connection-editor/wifi-page.ui144
33 files changed, 7763 insertions, 0 deletions
diff --git a/panels/network/connection-editor/8021x-security-page.ui b/panels/network/connection-editor/8021x-security-page.ui
new file mode 100644
index 0000000..cde5d85
--- /dev/null
+++ b/panels/network/connection-editor/8021x-security-page.ui
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPage8021xSecurity" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">50</property>
+ <property name="margin_end">50</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="security_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">802.1x _Security</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">enable_8021x_switch</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="enable_8021x_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/ce-page-8021x-security.c b/panels/network/connection-editor/ce-page-8021x-security.c
new file mode 100644
index 0000000..1e4f146
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-8021x-security.c
@@ -0,0 +1,209 @@
+/* -*- 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 - 2012 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+#include <string.h>
+
+#include "ws-wpa-eap.h"
+#include "wireless-security.h"
+#include "ce-page.h"
+#include "ce-page-ethernet.h"
+#include "ce-page-8021x-security.h"
+
+struct _CEPage8021xSecurity {
+ GtkGrid parent;
+
+ GtkBox *box;
+ GtkSwitch *enable_8021x_switch;
+ GtkLabel *security_label;
+
+ NMConnection *connection;
+ WirelessSecurityWPAEAP *security;
+ GtkSizeGroup *group;
+ gboolean initial_have_8021x;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPage8021xSecurity, ce_page_8021x_security, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+static void
+enable_toggled (CEPage8021xSecurity *self)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (self->security), gtk_switch_get_active (self->enable_8021x_switch));
+ ce_page_changed (CE_PAGE (self));
+}
+
+static void
+security_item_changed_cb (CEPage8021xSecurity *self)
+{
+ ce_page_changed (CE_PAGE (self));
+}
+
+static void
+finish_setup (CEPage8021xSecurity *self, gpointer unused, GError *error, gpointer user_data)
+{
+ GtkWidget *parent;
+
+ if (error)
+ return;
+
+ self->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ self->security = ws_wpa_eap_new (self->connection);
+ if (!self->security) {
+ g_warning ("Could not load 802.1x user interface.");
+ return;
+ }
+
+ g_signal_connect_object (WIRELESS_SECURITY (self->security), "changed", G_CALLBACK (security_item_changed_cb), self, G_CONNECT_SWAPPED);
+ parent = gtk_widget_get_parent (GTK_WIDGET (self->security));
+ if (parent)
+ gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (self->security));
+
+ gtk_switch_set_active (self->enable_8021x_switch, self->initial_have_8021x);
+ g_signal_connect_object (self->enable_8021x_switch, "notify::active", G_CALLBACK (enable_toggled), self, G_CONNECT_SWAPPED);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->security), self->initial_have_8021x);
+
+ gtk_size_group_add_widget (self->group, GTK_WIDGET (self->security_label));
+ wireless_security_add_to_size_group (WIRELESS_SECURITY (self->security), self->group);
+
+ gtk_container_add (GTK_CONTAINER (self->box), GTK_WIDGET (self->security));
+
+}
+
+static const gchar *
+ce_page_8021x_security_get_security_setting (CEPage *page)
+{
+ CEPage8021xSecurity *self = CE_PAGE_8021X_SECURITY (page);
+
+ if (self->initial_have_8021x)
+ return NM_SETTING_802_1X_SETTING_NAME;
+
+ return NULL;
+}
+
+static const gchar *
+ce_page_8021x_security_get_title (CEPage *page)
+{
+ return _("Security");
+}
+
+static gboolean
+ce_page_8021x_security_validate (CEPage *cepage, NMConnection *connection, GError **error)
+{
+ CEPage8021xSecurity *self = CE_PAGE_8021X_SECURITY (cepage);
+ gboolean valid = TRUE;
+
+ if (gtk_switch_get_active (self->enable_8021x_switch)) {
+ NMSetting *s_8021x;
+
+ /* FIXME: get failed property and error out of wireless security objects */
+ valid = wireless_security_validate (WIRELESS_SECURITY (self->security), error);
+ if (valid) {
+ g_autoptr(NMConnection) tmp_connection = NULL;
+ NMSetting *s_con;
+
+ /* Here's a nice hack to work around the fact that ws_802_1x_fill_connection needs wireless setting. */
+ tmp_connection = nm_simple_connection_new ();
+ nm_connection_add_setting (tmp_connection, nm_setting_wireless_new ());
+
+ /* temp connection needs a 'connection' setting too, since most of
+ * the EAP methods need the UUID for CA cert ignore stuff.
+ */
+ s_con = nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+ nm_connection_add_setting (tmp_connection, nm_setting_duplicate (s_con));
+
+ ws_wpa_eap_fill_connection (self->security, tmp_connection);
+
+ s_8021x = nm_connection_get_setting (tmp_connection, NM_TYPE_SETTING_802_1X);
+ nm_connection_add_setting (connection, NM_SETTING (g_object_ref (s_8021x)));
+ }
+ } else {
+ nm_connection_remove_setting (connection, NM_TYPE_SETTING_802_1X);
+ valid = TRUE;
+ }
+
+ return valid;
+}
+
+static void
+ce_page_8021x_security_init (CEPage8021xSecurity *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_8021x_security_dispose (GObject *object)
+{
+ CEPage8021xSecurity *self = CE_PAGE_8021X_SECURITY (object);
+
+ g_clear_object (&self->connection);
+ g_clear_object (&self->security);
+ g_clear_object (&self->group);
+
+ G_OBJECT_CLASS (ce_page_8021x_security_parent_class)->dispose (object);
+}
+
+static void
+ce_page_8021x_security_class_init (CEPage8021xSecurityClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = ce_page_8021x_security_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/8021x-security-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPage8021xSecurity, box);
+ gtk_widget_class_bind_template_child (widget_class, CEPage8021xSecurity, enable_8021x_switch);
+ gtk_widget_class_bind_template_child (widget_class, CEPage8021xSecurity, security_label);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_security_setting = ce_page_8021x_security_get_security_setting;
+ iface->get_title = ce_page_8021x_security_get_title;
+ iface->validate = ce_page_8021x_security_validate;
+}
+
+CEPage8021xSecurity *
+ce_page_8021x_security_new (NMConnection *connection)
+{
+ CEPage8021xSecurity *self;
+
+ self = CE_PAGE_8021X_SECURITY (g_object_new (ce_page_8021x_security_get_type (), NULL));
+
+ self->connection = g_object_ref (connection);
+
+ if (nm_connection_get_setting_802_1x (connection))
+ self->initial_have_8021x = TRUE;
+
+ g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL);
+
+ return self;
+}
diff --git a/panels/network/connection-editor/ce-page-8021x-security.h b/panels/network/connection-editor/ce-page-8021x-security.h
new file mode 100644
index 0000000..e783587
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-8021x-security.h
@@ -0,0 +1,34 @@
+/* -*- 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 - 2012 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPage8021xSecurity, ce_page_8021x_security, CE, PAGE_8021X_SECURITY, GtkGrid)
+
+CEPage8021xSecurity *ce_page_8021x_security_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-page-details.c b/panels/network/connection-editor/ce-page-details.c
new file mode 100644
index 0000000..cd5d36c
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-details.c
@@ -0,0 +1,525 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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/gi18n.h>
+
+#include <NetworkManager.h>
+
+#include "ce-page.h"
+#include "ce-page-details.h"
+
+struct _CEPageDetails
+{
+ GtkGrid parent;
+
+ GtkCheckButton *all_user_check;
+ GtkCheckButton *auto_connect_check;
+ GtkLabel *dns_heading_label;
+ GtkLabel *dns_label;
+ GtkButton *forget_button;
+ GtkLabel *freq_heading_label;
+ GtkLabel *freq_label;
+ GtkLabel *ipv4_heading_label;
+ GtkLabel *ipv4_label;
+ GtkLabel *ipv6_heading_label;
+ GtkLabel *ipv6_label;
+ GtkLabel *last_used_heading_label;
+ GtkLabel *last_used_label;
+ GtkLabel *mac_heading_label;
+ GtkLabel *mac_label;
+ GtkCheckButton *restrict_data_check;
+ GtkLabel *route_heading_label;
+ GtkLabel *route_label;
+ GtkLabel *security_heading_label;
+ GtkLabel *security_label;
+ GtkLabel *speed_heading_label;
+ GtkLabel *speed_label;
+ GtkLabel *strength_heading_label;
+ GtkLabel *strength_label;
+
+ NMConnection *connection;
+ NMDevice *device;
+ NMAccessPoint *ap;
+ NetConnectionEditor *editor;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPageDetails, ce_page_details, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+static void
+forget_cb (CEPageDetails *self)
+{
+ net_connection_editor_forget (self->editor);
+}
+
+static gchar *
+get_ap_security_string (NMAccessPoint *ap)
+{
+ NM80211ApSecurityFlags wpa_flags, rsn_flags;
+ NM80211ApFlags flags;
+ GString *str;
+
+ flags = nm_access_point_get_flags (ap);
+ wpa_flags = nm_access_point_get_wpa_flags (ap);
+ rsn_flags = nm_access_point_get_rsn_flags (ap);
+
+ str = g_string_new ("");
+ if ((flags & NM_802_11_AP_FLAGS_PRIVACY) &&
+ (wpa_flags == NM_802_11_AP_SEC_NONE) &&
+ (rsn_flags == NM_802_11_AP_SEC_NONE)) {
+ /* TRANSLATORS: this WEP WiFi security */
+ g_string_append_printf (str, "%s, ", _("WEP"));
+ }
+ if (wpa_flags != NM_802_11_AP_SEC_NONE) {
+ /* TRANSLATORS: this WPA WiFi security */
+ g_string_append_printf (str, "%s, ", _("WPA"));
+ }
+ if (rsn_flags != NM_802_11_AP_SEC_NONE) {
+#if NM_CHECK_VERSION(1,20,6)
+ if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE) {
+ /* TRANSLATORS: this WPA3 WiFi security */
+ g_string_append_printf (str, "%s, ", _("WPA3"));
+ }
+#if NM_CHECK_VERSION(1,24,0)
+ else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE) {
+ /* TRANSLATORS: this Enhanced Open WiFi security */
+ g_string_append_printf (str, "%s, ", _("Enhanced Open"));
+ }
+#endif
+ else
+#endif
+ {
+ /* TRANSLATORS: this WPA WiFi security */
+ g_string_append_printf (str, "%s, ", _("WPA2"));
+ }
+ }
+ if ((wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) ||
+ (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
+ /* TRANSLATORS: this Enterprise WiFi security */
+ g_string_append_printf (str, "%s, ", _("Enterprise"));
+ }
+ if (str->len > 0)
+ g_string_set_size (str, str->len - 2);
+ else {
+ g_string_append (str, C_("Wifi security", "None"));
+ }
+ return g_string_free (str, FALSE);
+}
+
+static void
+update_last_used (CEPageDetails *self, NMConnection *connection)
+{
+ g_autofree gchar *last_used = NULL;
+ g_autoptr(GDateTime) now = NULL;
+ g_autoptr(GDateTime) then = NULL;
+ gint days;
+ GTimeSpan diff;
+ guint64 timestamp;
+ NMSettingConnection *s_con;
+
+ s_con = nm_connection_get_setting_connection (connection);
+ if (s_con == NULL)
+ goto out;
+ timestamp = nm_setting_connection_get_timestamp (s_con);
+ if (timestamp == 0) {
+ last_used = g_strdup (_("Never"));
+ goto out;
+ }
+
+ /* calculate the amount of time that has elapsed */
+ now = g_date_time_new_now_utc ();
+ then = g_date_time_new_from_unix_utc (timestamp);
+
+ diff = g_date_time_difference (now, then);
+ days = diff / G_TIME_SPAN_DAY;
+ if (days == 0)
+ last_used = g_strdup (_("Today"));
+ else if (days == 1)
+ last_used = g_strdup (_("Yesterday"));
+ else
+ last_used = g_strdup_printf (ngettext ("%i day ago", "%i days ago", days), days);
+out:
+ gtk_label_set_label (self->last_used_label, last_used);
+ gtk_widget_set_visible (GTK_WIDGET (self->last_used_heading_label), last_used != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->last_used_label), last_used != NULL);
+}
+
+static void
+all_user_changed (CEPageDetails *self)
+{
+ gboolean all_users;
+ NMSettingConnection *sc;
+
+ sc = nm_connection_get_setting_connection (self->connection);
+ all_users = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->all_user_check));
+
+ g_object_set (sc, "permissions", NULL, NULL);
+ if (!all_users)
+ nm_setting_connection_add_permission (sc, "user", g_get_user_name (), NULL);
+}
+
+static void
+restrict_data_changed (CEPageDetails *self)
+{
+ NMSettingConnection *s_con;
+ NMMetered metered;
+
+ s_con = nm_connection_get_setting_connection (self->connection);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->restrict_data_check)))
+ metered = NM_METERED_YES;
+ else
+ metered = NM_METERED_NO;
+
+ g_object_set (s_con, "metered", metered, NULL);
+}
+
+static void
+update_restrict_data (CEPageDetails *self)
+{
+ NMSettingConnection *s_con;
+ NMMetered metered;
+ const gchar *type;
+
+ s_con = nm_connection_get_setting_connection (self->connection);
+
+ if (s_con == NULL)
+ return;
+
+ /* Disable for VPN; NetworkManager does not implement that yet (see
+ * bug https://bugzilla.gnome.org/show_bug.cgi?id=792618) */
+ type = nm_setting_connection_get_connection_type (s_con);
+ if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME))
+ return;
+
+ metered = nm_setting_connection_get_metered (s_con);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->restrict_data_check),
+ metered == NM_METERED_YES || metered == NM_METERED_GUESS_YES);
+ gtk_widget_show (GTK_WIDGET (self->restrict_data_check));
+
+ g_signal_connect_object (self->restrict_data_check, "notify::active", G_CALLBACK (restrict_data_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->restrict_data_check, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+}
+
+static void
+connect_details_page (CEPageDetails *self)
+{
+ NMSettingConnection *sc;
+ guint speed;
+ NMDeviceWifiCapabilities wifi_caps;
+ guint frequency;
+ guint strength;
+ NMDeviceState state;
+ NMAccessPoint *active_ap;
+ g_autofree gchar *speed_label = NULL;
+ const gchar *type;
+ const gchar *hw_address = NULL;
+ g_autofree gchar *security_string = NULL;
+ g_autofree gchar *freq_string = NULL;
+ const gchar *strength_label;
+ gboolean device_is_active;
+ NMIPConfig *ipv4_config = NULL, *ipv6_config = NULL;
+ gboolean have_ipv4_address = FALSE, have_ipv6_address = FALSE;
+
+ sc = nm_connection_get_setting_connection (self->connection);
+ type = nm_setting_connection_get_connection_type (sc);
+
+ if (NM_IS_DEVICE_WIFI (self->device)) {
+ active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (self->device));
+ frequency = nm_access_point_get_frequency (active_ap);
+ } else {
+ active_ap = NULL;
+ frequency = 0;
+ }
+
+ state = self->device ? nm_device_get_state (self->device) : NM_DEVICE_STATE_DISCONNECTED;
+
+ device_is_active = FALSE;
+ speed = 0;
+ wifi_caps = 0;
+ if (active_ap && self->ap == active_ap && state != NM_DEVICE_STATE_UNAVAILABLE) {
+ device_is_active = TRUE;
+ if (NM_IS_DEVICE_WIFI (self->device)) {
+ speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (self->device)) / 1000;
+ wifi_caps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (self->device));
+ }
+ } else if (self->device) {
+ NMActiveConnection *ac;
+ const gchar *p1, *p2;
+
+ ac = nm_device_get_active_connection (self->device);
+ p1 = ac ? nm_active_connection_get_uuid (ac) : NULL;
+ p2 = nm_connection_get_uuid (self->connection);
+ if (g_strcmp0 (p1, p2) == 0) {
+ device_is_active = TRUE;
+ if (NM_IS_DEVICE_WIFI (self->device)) {
+ speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (self->device)) / 1000;
+ wifi_caps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (self->device));
+ } else if (NM_IS_DEVICE_ETHERNET (self->device))
+ speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (self->device));
+ }
+ }
+
+ if (speed > 0 && frequency > 0)
+ speed_label = g_strdup_printf (_("%d Mb/s (%1.1f GHz)"), speed, (float) (frequency) / 1000.0);
+ else if (speed > 0)
+ speed_label = g_strdup_printf (_("%d Mb/s"), speed);
+ gtk_label_set_label (self->speed_label, speed_label);
+ gtk_widget_set_visible (GTK_WIDGET (self->speed_heading_label), speed_label != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->speed_label), speed_label != NULL);
+
+ if (NM_IS_DEVICE_WIFI (self->device))
+ hw_address = nm_device_wifi_get_hw_address (NM_DEVICE_WIFI (self->device));
+ else if (NM_IS_DEVICE_ETHERNET (self->device))
+ hw_address = nm_device_ethernet_get_hw_address (NM_DEVICE_ETHERNET (self->device));
+
+ gtk_label_set_label (self->mac_label, hw_address);
+ gtk_widget_set_visible (GTK_WIDGET (self->mac_heading_label), hw_address != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->mac_label), hw_address != NULL);
+
+ if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_VALID) {
+ if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_2GHZ &&
+ wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_5GHZ)
+ freq_string = g_strdup (_("2.4 GHz / 5 GHz"));
+ else if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_2GHZ)
+ freq_string = g_strdup (_("2.4 GHz"));
+ else if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_5GHZ)
+ freq_string = g_strdup (_("5 GHz"));
+ }
+
+ gtk_label_set_label (self->freq_label, freq_string);
+ gtk_widget_set_visible (GTK_WIDGET (self->freq_heading_label), freq_string != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->freq_label), freq_string != NULL);
+
+ if (device_is_active && active_ap)
+ security_string = get_ap_security_string (active_ap);
+ gtk_label_set_label (self->security_label, security_string);
+ gtk_widget_set_visible (GTK_WIDGET (self->security_heading_label), security_string != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->security_label), security_string != NULL);
+
+ strength = 0;
+ if (self->ap != NULL)
+ strength = nm_access_point_get_strength (self->ap);
+
+ if (strength <= 0)
+ strength_label = NULL;
+ else if (strength < 20)
+ strength_label = C_("Signal strength", "None");
+ else if (strength < 40)
+ strength_label = C_("Signal strength", "Weak");
+ else if (strength < 50)
+ strength_label = C_("Signal strength", "Ok");
+ else if (strength < 80)
+ strength_label = C_("Signal strength", "Good");
+ else
+ strength_label = C_("Signal strength", "Excellent");
+ gtk_label_set_label (self->strength_label, strength_label);
+ gtk_widget_set_visible (GTK_WIDGET (self->strength_heading_label), strength_label != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->strength_label), strength_label != NULL);
+
+ if (device_is_active && self->device != NULL)
+ ipv4_config = nm_device_get_ip4_config (self->device);
+ if (ipv4_config != NULL) {
+ GPtrArray *addresses;
+ const gchar *ipv4_text = NULL;
+ g_autofree gchar *dns_text = NULL;
+ const gchar *route_text;
+
+ addresses = nm_ip_config_get_addresses (ipv4_config);
+ if (addresses->len > 0)
+ ipv4_text = nm_ip_address_get_address (g_ptr_array_index (addresses, 0));
+ gtk_label_set_label (self->ipv4_label, ipv4_text);
+ gtk_widget_set_visible (GTK_WIDGET (self->ipv4_heading_label), ipv4_text != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->ipv4_label), ipv4_text != NULL);
+ have_ipv4_address = ipv4_text != NULL;
+
+ dns_text = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv4_config));
+ gtk_label_set_label (self->dns_label, dns_text);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns_heading_label), dns_text != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns_label), dns_text != NULL);
+
+ route_text = nm_ip_config_get_gateway (ipv4_config);
+ gtk_label_set_label (self->route_label, route_text);
+ gtk_widget_set_visible (GTK_WIDGET (self->route_heading_label), route_text != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->route_label), route_text != NULL);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->ipv4_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->ipv4_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns_label));
+ gtk_widget_hide (GTK_WIDGET (self->route_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->route_label));
+ }
+
+ if (device_is_active && self->device != NULL)
+ ipv6_config = nm_device_get_ip6_config (self->device);
+ if (ipv6_config != NULL) {
+ GPtrArray *addresses;
+ const gchar *ipv6_text = NULL;
+
+ addresses = nm_ip_config_get_addresses (ipv6_config);
+ if (addresses->len > 0)
+ ipv6_text = nm_ip_address_get_address (g_ptr_array_index (addresses, 0));
+ gtk_label_set_label (self->ipv6_label, ipv6_text);
+ gtk_widget_set_visible (GTK_WIDGET (self->ipv6_heading_label), ipv6_text != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->ipv6_label), ipv6_text != NULL);
+ have_ipv6_address = ipv6_text != NULL;
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->ipv6_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->ipv6_label));
+ }
+
+ if (have_ipv4_address && have_ipv6_address) {
+ gtk_label_set_label (self->ipv4_heading_label, _("IPv4 Address"));
+ gtk_label_set_label (self->ipv6_heading_label, _("IPv6 Address"));
+ }
+ else {
+ gtk_label_set_label (self->ipv4_heading_label, _("IP Address"));
+ gtk_label_set_label (self->ipv6_heading_label, _("IP Address"));
+ }
+
+ if (!device_is_active && self->connection)
+ update_last_used (self, self->connection);
+ else {
+ gtk_widget_set_visible (GTK_WIDGET (self->last_used_heading_label), FALSE);
+ gtk_widget_set_visible (GTK_WIDGET (self->last_used_label), FALSE);
+ }
+
+ /* Auto connect check */
+ if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME)) {
+ gtk_widget_hide (GTK_WIDGET (self->auto_connect_check));
+ } else {
+ g_object_bind_property (sc, "autoconnect",
+ self->auto_connect_check, "active",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ g_signal_connect_object (self->auto_connect_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ }
+
+ /* All users check */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->all_user_check),
+ nm_setting_connection_get_num_permissions (sc) == 0);
+ g_signal_connect_object (self->all_user_check, "toggled", G_CALLBACK (all_user_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->all_user_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ /* Restrict Data check */
+ update_restrict_data (self);
+
+ /* Forget button */
+ g_signal_connect_object (self->forget_button, "clicked", G_CALLBACK (forget_cb), self, G_CONNECT_SWAPPED);
+
+ if (g_str_equal (type, NM_SETTING_WIRELESS_SETTING_NAME))
+ gtk_button_set_label (self->forget_button, _("Forget Connection"));
+ else if (g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME))
+ gtk_button_set_label (self->forget_button, _("Remove Connection Profile"));
+ else if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME))
+ gtk_button_set_label (self->forget_button, _("Remove VPN"));
+ else
+ gtk_widget_hide (GTK_WIDGET (self->forget_button));
+}
+
+static void
+ce_page_details_dispose (GObject *object)
+{
+ CEPageDetails *self = CE_PAGE_DETAILS (object);
+
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (ce_page_details_parent_class)->dispose (object);
+}
+
+static const gchar *
+ce_page_details_get_title (CEPage *page)
+{
+ return _("Details");
+}
+
+static void
+ce_page_details_init (CEPageDetails *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_details_class_init (CEPageDetailsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = ce_page_details_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/details-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, all_user_check);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, auto_connect_check);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, forget_button);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, freq_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, freq_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv4_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv4_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv6_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv6_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, last_used_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, last_used_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, mac_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, mac_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, restrict_data_check);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, route_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, route_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, security_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, security_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, speed_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, speed_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, strength_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, strength_label);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_title = ce_page_details_get_title;
+}
+
+CEPageDetails *
+ce_page_details_new (NMConnection *connection,
+ NMDevice *device,
+ NMAccessPoint *ap,
+ NetConnectionEditor *editor)
+{
+ CEPageDetails *self;
+
+ self = CE_PAGE_DETAILS (g_object_new (ce_page_details_get_type (), NULL));
+
+ self->connection = g_object_ref (connection);
+ self->editor = editor;
+ self->device = device;
+ self->ap = ap;
+
+ connect_details_page (self);
+
+ return self;
+}
diff --git a/panels/network/connection-editor/ce-page-details.h b/panels/network/connection-editor/ce-page-details.h
new file mode 100644
index 0000000..4fd660c
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-details.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+#include "net-connection-editor.h"
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageDetails, ce_page_details, CE, PAGE_DETAILS, GtkGrid)
+
+CEPageDetails *ce_page_details_new (NMConnection *connection,
+ NMDevice *device,
+ NMAccessPoint *ap,
+ NetConnectionEditor *editor);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-page-ethernet.c b/panels/network/connection-editor/ce-page-ethernet.c
new file mode 100644
index 0000000..98fe460
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ethernet.c
@@ -0,0 +1,224 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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/gi18n.h>
+#include <net/if_arp.h>
+#include <NetworkManager.h>
+
+#include "ce-page.h"
+#include "ce-page-ethernet.h"
+#include "ui-helpers.h"
+
+struct _CEPageEthernet
+{
+ GtkGrid parent;
+
+ GtkComboBoxText *cloned_mac_combo;
+ GtkComboBoxText *mac_combo;
+ GtkSpinButton *mtu_spin;
+ GtkWidget *mtu_label;
+ GtkEntry *name_entry;
+
+ NMClient *client;
+ NMSettingConnection *setting_connection;
+ NMSettingWired *setting_wired;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPageEthernet, ce_page_ethernet, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+static void
+mtu_changed (CEPageEthernet *self)
+{
+ if (gtk_spin_button_get_value_as_int (self->mtu_spin) == 0)
+ gtk_widget_hide (self->mtu_label);
+ else
+ gtk_widget_show (self->mtu_label);
+}
+
+static void
+mtu_output_cb (CEPageEthernet *self)
+{
+ gint defvalue;
+ gint val;
+ g_autofree gchar *buf = NULL;
+
+ val = gtk_spin_button_get_value_as_int (self->mtu_spin);
+ defvalue = ce_get_property_default (NM_SETTING (self->setting_wired), NM_SETTING_WIRED_MTU);
+ if (val == defvalue)
+ buf = g_strdup (_("automatic"));
+ else
+ buf = g_strdup_printf ("%d", val);
+
+ if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (self->mtu_spin))))
+ gtk_entry_set_text (GTK_ENTRY (self->mtu_spin), buf);
+}
+
+static void
+connect_ethernet_page (CEPageEthernet *self)
+{
+ NMSettingWired *setting = self->setting_wired;
+ char **mac_list;
+ const char *s_mac_str;
+ const gchar *name;
+ const gchar *cloned_mac;
+
+ name = nm_setting_connection_get_id (self->setting_connection);
+ gtk_entry_set_text (self->name_entry, name);
+
+ /* Device MAC address */
+ mac_list = ce_page_get_mac_list (self->client, NM_TYPE_DEVICE_ETHERNET,
+ NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS);
+ s_mac_str = nm_setting_wired_get_mac_address (setting);
+ ce_page_setup_mac_combo (self->mac_combo, s_mac_str, mac_list);
+ g_strfreev (mac_list);
+ g_signal_connect_object (self->mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ /* Cloned MAC address */
+ cloned_mac = nm_setting_wired_get_cloned_mac_address (setting);
+ ce_page_setup_cloned_mac_combo (self->cloned_mac_combo, cloned_mac);
+ g_signal_connect_object (self->cloned_mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ /* MTU */
+ g_signal_connect_object (self->mtu_spin, "output", G_CALLBACK (mtu_output_cb), self, G_CONNECT_SWAPPED);
+ gtk_spin_button_set_value (self->mtu_spin, (gdouble) nm_setting_wired_get_mtu (setting));
+ g_signal_connect_object (self->mtu_spin, "value-changed", G_CALLBACK (mtu_changed), self, G_CONNECT_SWAPPED);
+ mtu_changed (self);
+
+ g_signal_connect_object (self->name_entry, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->mtu_spin, "value-changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+}
+
+static void
+ui_to_setting (CEPageEthernet *self)
+{
+ g_autofree gchar *device_mac = NULL;
+ g_autofree gchar *cloned_mac = NULL;
+ const gchar *text;
+ GtkWidget *entry;
+
+ entry = gtk_bin_get_child (GTK_BIN (self->mac_combo));
+ if (entry) {
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ device_mac = ce_page_trim_address (text);
+ }
+
+ cloned_mac = ce_page_cloned_mac_get (self->cloned_mac_combo);
+
+ g_object_set (self->setting_wired,
+ NM_SETTING_WIRED_MAC_ADDRESS, device_mac,
+ NM_SETTING_WIRED_CLONED_MAC_ADDRESS, cloned_mac,
+ NM_SETTING_WIRED_MTU, (guint32) gtk_spin_button_get_value_as_int (self->mtu_spin),
+ NULL);
+
+ g_object_set (self->setting_connection,
+ NM_SETTING_CONNECTION_ID, gtk_entry_get_text (self->name_entry),
+ NULL);
+}
+
+static const gchar *
+ce_page_ethernet_get_title (CEPage *page)
+{
+ return _("Identity");
+}
+
+static gboolean
+ce_page_ethernet_validate (CEPage *page,
+ NMConnection *connection,
+ GError **error)
+{
+ CEPageEthernet *self = CE_PAGE_ETHERNET (page);
+ GtkWidget *entry;
+ gboolean ret = TRUE;
+
+ entry = gtk_bin_get_child (GTK_BIN (self->mac_combo));
+ if (entry) {
+ if (!ce_page_address_is_valid (gtk_entry_get_text (GTK_ENTRY (entry)))) {
+ widget_set_error (entry);
+ ret = FALSE;
+ } else {
+ widget_unset_error (entry);
+ }
+ }
+
+ if (!ce_page_cloned_mac_combo_valid (self->cloned_mac_combo)) {
+ widget_set_error (gtk_bin_get_child (GTK_BIN (self->cloned_mac_combo)));
+ ret = FALSE;
+ } else {
+ widget_unset_error (gtk_bin_get_child (GTK_BIN (self->cloned_mac_combo)));
+ }
+
+ if (!ret)
+ return ret;
+
+ ui_to_setting (self);
+
+ return nm_setting_verify (NM_SETTING (self->setting_connection), NULL, error) &&
+ nm_setting_verify (NM_SETTING (self->setting_wired), NULL, error);
+}
+
+static void
+ce_page_ethernet_init (CEPageEthernet *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_ethernet_class_init (CEPageEthernetClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/ethernet-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, cloned_mac_combo);
+ gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, mac_combo);
+ gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, mtu_spin);
+ gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, mtu_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, name_entry);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_title = ce_page_ethernet_get_title;
+ iface->validate = ce_page_ethernet_validate;
+}
+
+CEPageEthernet *
+ce_page_ethernet_new (NMConnection *connection,
+ NMClient *client)
+{
+ CEPageEthernet *self;
+
+ self = CE_PAGE_ETHERNET (g_object_new (ce_page_ethernet_get_type (), NULL));
+
+ self->client = client;
+ self->setting_connection = nm_connection_get_setting_connection (connection);
+ self->setting_wired = nm_connection_get_setting_wired (connection);
+
+ connect_ethernet_page (self);
+
+ return self;
+}
diff --git a/panels/network/connection-editor/ce-page-ethernet.h b/panels/network/connection-editor/ce-page-ethernet.h
new file mode 100644
index 0000000..5fce19f
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ethernet.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageEthernet, ce_page_ethernet, CE, PAGE_ETHERNET, GtkGrid)
+
+CEPageEthernet *ce_page_ethernet_new (NMConnection *connection,
+ NMClient *client);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-page-ip4.c b/panels/network/connection-editor/ce-page-ip4.c
new file mode 100644
index 0000000..2caf8a8
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ip4.c
@@ -0,0 +1,895 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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 <errno.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#include "list-box-helper.h"
+#include "ce-page.h"
+#include "ce-page-ip4.h"
+#include "ui-helpers.h"
+
+static void ensure_empty_address_row (CEPageIP4 *self);
+static void ensure_empty_routes_row (CEPageIP4 *self);
+
+struct _CEPageIP4
+{
+ GtkScrolledWindow parent;
+
+ GtkBox *address_box;
+ GtkSizeGroup *address_sizegroup;
+ GtkSwitch *auto_dns_switch;
+ GtkSwitch *auto_routes_switch;
+ GtkRadioButton *automatic_radio;
+ GtkBox *content_box;
+ GtkRadioButton *disabled_radio;
+ GtkEntry *dns_entry;
+ GtkRadioButton *local_radio;
+ GtkRadioButton *manual_radio;
+ GtkCheckButton *never_default_check;
+ GtkBox *routes_box;
+ GtkSizeGroup *routes_metric_sizegroup;
+ GtkSizeGroup *routes_sizegroup;
+ GtkRadioButton *shared_radio;
+
+ NMSettingIPConfig *setting;
+
+ GtkWidget *address_list;
+ GtkWidget *routes_list;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPageIP4, ce_page_ip4, GTK_TYPE_SCROLLED_WINDOW,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+enum {
+ METHOD_COL_NAME,
+ METHOD_COL_METHOD
+};
+
+enum {
+ IP4_METHOD_AUTO,
+ IP4_METHOD_MANUAL,
+ IP4_METHOD_LINK_LOCAL,
+ IP4_METHOD_SHARED,
+ IP4_METHOD_DISABLED
+};
+
+static void
+method_changed (CEPageIP4 *self)
+{
+ gboolean addr_enabled;
+ gboolean dns_enabled;
+ gboolean routes_enabled;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->disabled_radio)) ||
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->shared_radio))) {
+ addr_enabled = FALSE;
+ dns_enabled = FALSE;
+ routes_enabled = FALSE;
+ } else {
+ addr_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->manual_radio));
+ dns_enabled = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_radio));
+ routes_enabled = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_radio));
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->address_box), addr_enabled);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->dns_entry), dns_enabled);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->routes_list), routes_enabled);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->never_default_check), routes_enabled);
+
+ ce_page_changed (CE_PAGE (self));
+}
+
+static void
+update_row_sensitivity (CEPageIP4 *self, GtkWidget *list)
+{
+ GList *children, *l;
+ gint rows = 0, i = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (list));
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "delete-button"));
+ if (button != NULL)
+ rows++;
+ }
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "delete-button"));
+ if (button != NULL)
+ gtk_widget_set_sensitive (button, rows > 1 && ++i < rows);
+ }
+ g_list_free (children);
+}
+
+static void
+update_row_gateway_sensitivity (CEPageIP4 *self)
+{
+ GList *children, *l;
+ gint rows = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->address_list));
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkWidget *entry;
+
+ entry = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "gateway"));
+
+ gtk_widget_set_sensitive (entry, (rows == 0));
+
+ rows++;
+ }
+ g_list_free (children);
+}
+
+static void
+remove_row (CEPageIP4 *self, GtkButton *button)
+{
+ GtkWidget *list;
+ GtkWidget *row;
+ GtkWidget *row_box;
+
+ row_box = gtk_widget_get_parent (GTK_WIDGET (button));
+ row = gtk_widget_get_parent (row_box);
+ list = gtk_widget_get_parent (row);
+
+ gtk_container_remove (GTK_CONTAINER (list), row);
+
+ ce_page_changed (CE_PAGE (self));
+
+ update_row_sensitivity (self, list);
+ if (list == self->address_list)
+ update_row_gateway_sensitivity (self);
+}
+
+static gboolean
+validate_row (GtkWidget *row)
+{
+ GtkWidget *box;
+ GList *children, *l;
+ gboolean valid;
+
+ valid = FALSE;
+ box = gtk_bin_get_child (GTK_BIN (row));
+ children = gtk_container_get_children (GTK_CONTAINER (box));
+
+ for (l = children; l != NULL; l = l->next) {
+ if (!GTK_IS_ENTRY (l->data))
+ continue;
+
+ valid = valid || gtk_entry_get_text_length (l->data) > 0;
+ }
+
+ g_list_free (children);
+
+ return valid;
+}
+
+static void
+add_address_row (CEPageIP4 *self,
+ const gchar *address,
+ const gchar *network,
+ const gchar *gateway)
+{
+ GtkWidget *row;
+ GtkWidget *row_box;
+ GtkWidget *widget;
+ GtkWidget *delete_button;
+ GtkWidget *image;
+
+ row = gtk_list_box_row_new ();
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class (gtk_widget_get_style_context (row_box), "linked");
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "address", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), address);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "network", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), network);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "gateway", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), gateway ? gateway : "");
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ delete_button = gtk_button_new ();
+ gtk_widget_set_sensitive (delete_button, FALSE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (delete_button), "image-button");
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ image = gtk_image_new_from_icon_name ("edit-delete-symbolic", GTK_ICON_SIZE_MENU);
+ atk_object_set_name (gtk_widget_get_accessible (delete_button), _("Delete Address"));
+ gtk_button_set_image (GTK_BUTTON (delete_button), image);
+ gtk_container_add (GTK_CONTAINER (row_box), delete_button);
+ g_object_set_data (G_OBJECT (row), "delete-button", delete_button);
+
+ gtk_size_group_add_widget (self->address_sizegroup, delete_button);
+
+ gtk_container_add (GTK_CONTAINER (row), row_box);
+ gtk_widget_show_all (row);
+ gtk_container_add (GTK_CONTAINER (self->address_list), row);
+
+ update_row_gateway_sensitivity (self);
+ update_row_sensitivity (self, self->address_list);
+}
+
+static void
+ensure_empty_address_row (CEPageIP4 *self)
+{
+ GList *children, *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->address_list));
+ l = children;
+
+ while (l && l->next)
+ l = l->next;
+
+ /* Add the last, stub row if needed*/
+ if (!l || validate_row (l->data))
+ add_address_row (self, "", "", "");
+
+ g_list_free (children);
+}
+
+static void
+add_address_box (CEPageIP4 *self)
+{
+ GtkWidget *list;
+ gint i;
+
+ self->address_list = list = gtk_list_box_new ();
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (list), cc_list_box_update_header_func, NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (self->address_box), list);
+
+ for (i = 0; i < nm_setting_ip_config_get_num_addresses (self->setting); i++) {
+ NMIPAddress *addr;
+ struct in_addr tmp_addr;
+ gchar network[INET_ADDRSTRLEN + 1];
+
+ addr = nm_setting_ip_config_get_address (self->setting, i);
+ if (!addr)
+ continue;
+
+ tmp_addr.s_addr = nm_utils_ip4_prefix_to_netmask (nm_ip_address_get_prefix (addr));
+ (void) inet_ntop (AF_INET, &tmp_addr, &network[0], sizeof (network));
+
+ add_address_row (self,
+ nm_ip_address_get_address (addr),
+ network,
+ i == 0 ? nm_setting_ip_config_get_gateway (self->setting) : "");
+ }
+ if (nm_setting_ip_config_get_num_addresses (self->setting) == 0)
+ ensure_empty_address_row (self);
+
+ gtk_widget_show_all (GTK_WIDGET (self->address_box));
+}
+
+static void
+add_dns_section (CEPageIP4 *self)
+{
+ GString *string;
+ gint i;
+
+ gtk_switch_set_active (self->auto_dns_switch, !nm_setting_ip_config_get_ignore_auto_dns (self->setting));
+ g_signal_connect_object (self->auto_dns_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ string = g_string_new ("");
+
+ for (i = 0; i < nm_setting_ip_config_get_num_dns (self->setting); i++) {
+ const char *address;
+
+ address = nm_setting_ip_config_get_dns (self->setting, i);
+
+ if (i > 0)
+ g_string_append (string, ", ");
+
+ g_string_append (string, address);
+ }
+
+ gtk_entry_set_text (self->dns_entry, string->str);
+
+ g_signal_connect_object (self->dns_entry, "notify::text", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ g_string_free (string, TRUE);
+}
+
+static void
+add_route_row (CEPageIP4 *self,
+ const gchar *address,
+ const gchar *netmask,
+ const gchar *gateway,
+ gint metric)
+{
+ GtkWidget *row;
+ GtkWidget *row_box;
+ GtkWidget *widget;
+ GtkWidget *delete_button;
+ GtkWidget *image;
+
+ row = gtk_list_box_row_new ();
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class (gtk_widget_get_style_context (row_box), "linked");
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "address", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), address);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "netmask", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), netmask);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "gateway", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), gateway ? gateway : "");
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "metric", widget);
+ if (metric >= 0) {
+ g_autofree gchar *s = g_strdup_printf ("%d", metric);
+ gtk_entry_set_text (GTK_ENTRY (widget), s);
+ }
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 5);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ gtk_size_group_add_widget (self->routes_metric_sizegroup, widget);
+
+ delete_button = gtk_button_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (delete_button), "image-button");
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ image = gtk_image_new_from_icon_name ("edit-delete-symbolic", GTK_ICON_SIZE_MENU);
+ atk_object_set_name (gtk_widget_get_accessible (delete_button), _("Delete Route"));
+ gtk_button_set_image (GTK_BUTTON (delete_button), image);
+ gtk_widget_set_halign (delete_button, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (delete_button, GTK_ALIGN_CENTER);
+ gtk_container_add (GTK_CONTAINER (row_box), delete_button);
+ g_object_set_data (G_OBJECT (row), "delete-button", delete_button);
+
+ gtk_size_group_add_widget (self->routes_sizegroup, delete_button);
+
+ gtk_container_add (GTK_CONTAINER (row), row_box);
+ gtk_widget_show_all (row);
+ gtk_container_add (GTK_CONTAINER (self->routes_list), row);
+
+ update_row_sensitivity (self, self->routes_list);
+}
+
+static void
+ensure_empty_routes_row (CEPageIP4 *self)
+{
+ GList *children, *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->routes_list));
+ l = children;
+
+ while (l && l->next)
+ l = l->next;
+
+ /* Add the last, stub row if needed*/
+ if (!l || validate_row (l->data))
+ add_route_row (self, "", "", "", -1);
+
+ g_list_free (children);
+}
+
+static void
+add_routes_box (CEPageIP4 *self)
+{
+ GtkWidget *list;
+ gint i;
+
+ self->routes_list = list = gtk_list_box_new ();
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (list), cc_list_box_update_header_func, NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (self->routes_box), list);
+ gtk_switch_set_active (self->auto_routes_switch, !nm_setting_ip_config_get_ignore_auto_routes (self->setting));
+ g_signal_connect_object (self->auto_routes_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ for (i = 0; i < nm_setting_ip_config_get_num_routes (self->setting); i++) {
+ NMIPRoute *route;
+ struct in_addr tmp_addr;
+ gchar netmask[INET_ADDRSTRLEN + 1];
+
+ route = nm_setting_ip_config_get_route (self->setting, i);
+ if (!route)
+ continue;
+
+ tmp_addr.s_addr = nm_utils_ip4_prefix_to_netmask (nm_ip_route_get_prefix (route));
+ (void) inet_ntop (AF_INET, &tmp_addr, &netmask[0], sizeof (netmask));
+
+ add_route_row (self,
+ nm_ip_route_get_dest (route),
+ netmask,
+ nm_ip_route_get_next_hop (route),
+ nm_ip_route_get_metric (route));
+ }
+ if (nm_setting_ip_config_get_num_routes (self->setting) == 0)
+ ensure_empty_routes_row (self);
+
+ gtk_widget_show_all (GTK_WIDGET (self->routes_box));
+}
+
+static void
+connect_ip4_page (CEPageIP4 *self)
+{
+ const gchar *str_method;
+ guint method;
+
+ add_address_box (self);
+ add_dns_section (self);
+ add_routes_box (self);
+
+ str_method = nm_setting_ip_config_get_method (self->setting);
+ g_signal_connect_object (self->disabled_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_object_bind_property (self->disabled_radio, "active",
+ self->content_box, "sensitive",
+ G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+ g_signal_connect_object (self->shared_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_object_bind_property (self->shared_radio, "active",
+ self->content_box, "sensitive",
+ G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+ method = IP4_METHOD_AUTO;
+ if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0) {
+ method = IP4_METHOD_LINK_LOCAL;
+ } else if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) {
+ method = IP4_METHOD_MANUAL;
+ } else if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
+ method = IP4_METHOD_SHARED;
+ } else if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0) {
+ method = IP4_METHOD_DISABLED;
+ }
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->never_default_check),
+ nm_setting_ip_config_get_never_default (self->setting));
+ g_signal_connect_object (self->never_default_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->automatic_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->local_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->manual_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->disabled_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+
+ switch (method) {
+ case IP4_METHOD_AUTO:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->automatic_radio), TRUE);
+ break;
+ case IP4_METHOD_LINK_LOCAL:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->local_radio), TRUE);
+ break;
+ case IP4_METHOD_MANUAL:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->manual_radio), TRUE);
+ break;
+ case IP4_METHOD_SHARED:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->shared_radio), TRUE);
+ break;
+ case IP4_METHOD_DISABLED:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->disabled_radio), TRUE);
+ break;
+ default:
+ break;
+ }
+
+ method_changed (self);
+}
+
+static gboolean
+parse_netmask (const char *str, guint32 *prefix)
+{
+ struct in_addr tmp_addr;
+ glong tmp_prefix;
+
+ errno = 0;
+
+ /* Is it a prefix? */
+ if (!strchr (str, '.')) {
+ tmp_prefix = strtol (str, NULL, 10);
+ if (!errno && tmp_prefix >= 0 && tmp_prefix <= 32) {
+ *prefix = tmp_prefix;
+ return TRUE;
+ }
+ }
+
+ /* Is it a netmask? */
+ if (inet_pton (AF_INET, str, &tmp_addr) > 0) {
+ *prefix = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ui_to_setting (CEPageIP4 *self)
+{
+ const gchar *method;
+ gboolean ignore_auto_dns;
+ gboolean ignore_auto_routes;
+ gboolean never_default;
+ GPtrArray *addresses = NULL;
+ GPtrArray *dns_servers = NULL;
+ GPtrArray *routes = NULL;
+ GStrv dns_addresses = NULL;
+ GList *children, *l;
+ gboolean ret = TRUE;
+ const char *default_gateway = NULL;
+ gchar *dns_text = NULL;
+ guint i;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->disabled_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->automatic_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->manual_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->shared_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_SHARED;
+
+ addresses = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref);
+ if (g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+ children = gtk_container_get_children (GTK_CONTAINER (self->address_list));
+ else
+ children = NULL;
+
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkEntry *entry;
+ GtkEntry *gateway_entry;
+ const gchar *text_address;
+ const gchar *text_netmask;
+ const gchar *text_gateway = "";
+ NMIPAddress *addr;
+ guint32 prefix;
+
+ entry = GTK_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!entry)
+ continue;
+
+ text_address = gtk_entry_get_text (entry);
+ text_netmask = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "network")));
+ gateway_entry = g_object_get_data (G_OBJECT (row), "gateway");
+ if (gtk_widget_is_visible (GTK_WIDGET (gateway_entry)))
+ text_gateway = gtk_entry_get_text (gateway_entry);
+
+ if (!*text_address && !*text_netmask && !*text_gateway) {
+ /* ignore empty rows */
+ widget_unset_error (GTK_WIDGET (entry));
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "network"));
+ widget_unset_error (GTK_WIDGET (gateway_entry));
+ continue;
+ }
+
+ if (!nm_utils_ipaddr_valid (AF_INET, text_address)) {
+ widget_set_error (GTK_WIDGET (entry));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (entry));
+ }
+
+ if (!parse_netmask (text_netmask, &prefix)) {
+ widget_set_error (g_object_get_data (G_OBJECT (row), "network"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "network"));
+ }
+
+ if (gtk_widget_is_visible (GTK_WIDGET (gateway_entry)) &&
+ *text_gateway &&
+ !nm_utils_ipaddr_valid (AF_INET, text_gateway)) {
+ widget_set_error (g_object_get_data (G_OBJECT (row), "gateway"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (gateway_entry));
+ if (gtk_widget_is_visible (GTK_WIDGET (gateway_entry)) && *text_gateway) {
+ g_assert (default_gateway == NULL);
+ default_gateway = text_gateway;
+ }
+ }
+
+ if (!ret)
+ continue;
+
+ addr = nm_ip_address_new (AF_INET, text_address, prefix, NULL);
+ if (addr)
+ g_ptr_array_add (addresses, addr);
+
+ if (!l || !l->next)
+ ensure_empty_address_row (self);
+ }
+ g_list_free (children);
+
+ if (addresses->len == 0) {
+ g_ptr_array_free (addresses, TRUE);
+ addresses = NULL;
+ }
+
+ dns_servers = g_ptr_array_new_with_free_func (g_free);
+ dns_text = g_strstrip (g_strdup (gtk_entry_get_text (GTK_ENTRY (self->dns_entry))));
+ if (g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) ||
+ g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+ dns_addresses = g_strsplit_set (dns_text, ", ", -1);
+ else
+ dns_addresses = NULL;
+
+ for (i = 0; dns_addresses && dns_addresses[i]; i++) {
+ const gchar *text;
+
+ text = dns_addresses[i];
+
+ if (!text || !*text)
+ continue;
+
+ if (!nm_utils_ipaddr_valid (AF_INET, text)) {
+ g_ptr_array_remove_range (dns_servers, 0, dns_servers->len);
+ widget_set_error (GTK_WIDGET (self->dns_entry));
+ ret = FALSE;
+ break;
+ } else {
+ widget_unset_error (GTK_WIDGET (self->dns_entry));
+ g_ptr_array_add (dns_servers, g_strdup (text));
+ }
+ }
+ g_clear_pointer (&dns_addresses, g_strfreev);
+
+ if (dns_servers->len == 0) {
+ g_ptr_array_free (dns_servers, TRUE);
+ dns_servers = NULL;
+ } else {
+ g_ptr_array_add (dns_servers, NULL);
+ }
+
+ routes = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_route_unref);
+ if (g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) ||
+ g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+ children = gtk_container_get_children (GTK_CONTAINER (self->routes_list));
+ else
+ children = NULL;
+
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkEntry *entry;
+ const gchar *text_address;
+ const gchar *text_netmask;
+ const gchar *text_gateway;
+ const gchar *text_metric;
+ gint64 metric;
+ guint32 netmask;
+ NMIPRoute *route;
+
+ entry = GTK_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!entry)
+ continue;
+
+ text_address = gtk_entry_get_text (entry);
+ text_netmask = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "netmask")));
+ text_gateway = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "gateway")));
+ text_metric = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "metric")));
+
+ if (!*text_address && !*text_netmask && !*text_gateway && !*text_metric) {
+ /* ignore empty rows */
+ continue;
+ }
+
+ if (text_address && !nm_utils_ipaddr_valid (AF_INET, text_address)) {
+ widget_set_error (GTK_WIDGET (entry));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (entry));
+ }
+
+ if (!parse_netmask (text_netmask, &netmask)) {
+ widget_set_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "netmask")));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "netmask")));
+ }
+
+ if (text_gateway && !nm_utils_ipaddr_valid (AF_INET, text_gateway)) {
+ widget_set_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "gateway")));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "gateway")));
+ }
+
+ metric = -1;
+ if (*text_metric) {
+ errno = 0;
+ metric = g_ascii_strtoull (text_metric, NULL, 10);
+ if (errno || metric < 0 || metric > G_MAXUINT32) {
+ widget_set_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "metric")));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "metric")));
+ }
+ } else {
+ widget_unset_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "metric")));
+ }
+
+ if (!ret)
+ continue;
+
+ route = nm_ip_route_new (AF_INET, text_address, netmask, text_gateway, metric, NULL);
+ if (route)
+ g_ptr_array_add (routes, route);
+
+ if (!l || !l->next)
+ ensure_empty_routes_row (self);
+ }
+ g_list_free (children);
+
+ if (routes->len == 0) {
+ g_ptr_array_free (routes, TRUE);
+ routes = NULL;
+ }
+
+ if (!ret)
+ goto out;
+
+ ignore_auto_dns = !gtk_switch_get_active (self->auto_dns_switch);
+ ignore_auto_routes = !gtk_switch_get_active (self->auto_routes_switch);
+ never_default = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->never_default_check));
+
+ g_object_set (self->setting,
+ NM_SETTING_IP_CONFIG_METHOD, method,
+ NM_SETTING_IP_CONFIG_ADDRESSES, addresses,
+ NM_SETTING_IP_CONFIG_GATEWAY, default_gateway,
+ NM_SETTING_IP_CONFIG_DNS, dns_servers ? dns_servers->pdata : NULL,
+ NM_SETTING_IP_CONFIG_ROUTES, routes,
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, ignore_auto_dns,
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, ignore_auto_routes,
+ NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default,
+ NULL);
+
+out:
+ if (addresses)
+ g_ptr_array_free (addresses, TRUE);
+
+ if (dns_servers)
+ g_ptr_array_free (dns_servers, TRUE);
+
+ if (routes)
+ g_ptr_array_free (routes, TRUE);
+
+ g_clear_pointer (&dns_text, g_free);
+
+ return ret;
+}
+
+static const gchar *
+ce_page_ip4_get_title (CEPage *page)
+{
+ return _("IPv4");
+}
+
+static gboolean
+ce_page_ip4_validate (CEPage *self,
+ NMConnection *connection,
+ GError **error)
+{
+ if (!ui_to_setting (CE_PAGE_IP4 (self)))
+ return FALSE;
+
+ return nm_setting_verify (NM_SETTING (CE_PAGE_IP4 (self)->setting), NULL, error);
+}
+
+static void
+ce_page_ip4_init (CEPageIP4 *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_ip4_class_init (CEPageIP4Class *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/ip4-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, auto_dns_switch);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, auto_routes_switch);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, automatic_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, content_box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, disabled_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, dns_entry);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, local_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, manual_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, never_default_check);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_metric_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, shared_radio);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_title = ce_page_ip4_get_title;
+ iface->validate = ce_page_ip4_validate;
+}
+
+CEPageIP4 *
+ce_page_ip4_new (NMConnection *connection,
+ NMClient *client)
+{
+ CEPageIP4 *self;
+
+ self = CE_PAGE_IP4 (g_object_new (ce_page_ip4_get_type (), NULL));
+
+ self->setting = nm_connection_get_setting_ip4_config (connection);
+ if (!self->setting) {
+ self->setting = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
+ nm_connection_add_setting (connection, NM_SETTING (self->setting));
+ }
+
+ connect_ip4_page (self);
+
+ return self;
+}
diff --git a/panels/network/connection-editor/ce-page-ip4.h b/panels/network/connection-editor/ce-page-ip4.h
new file mode 100644
index 0000000..b76905d
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ip4.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageIP4, ce_page_ip4, CE, PAGE_IP4, GtkScrolledWindow)
+
+CEPageIP4 *ce_page_ip4_new (NMConnection *connection,
+ NMClient *client);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-page-ip6.c b/panels/network/connection-editor/ce-page-ip6.c
new file mode 100644
index 0000000..6b71429
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ip6.c
@@ -0,0 +1,829 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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 <errno.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#include "list-box-helper.h"
+#include "ce-page.h"
+#include "ce-page-ip6.h"
+#include "ui-helpers.h"
+
+static void ensure_empty_address_row (CEPageIP6 *self);
+static void ensure_empty_routes_row (CEPageIP6 *self);
+
+
+struct _CEPageIP6
+{
+ GtkScrolledWindow parent;
+
+ GtkBox *address_box;
+ GtkSizeGroup *address_sizegroup;
+ GtkSwitch *auto_dns_switch;
+ GtkSwitch *auto_routes_switch;
+ GtkRadioButton *automatic_radio;
+ GtkBox *content_box;
+ GtkRadioButton *dhcp_radio;
+ GtkRadioButton *disabled_radio;
+ GtkEntry *dns_entry;
+ GtkRadioButton *local_radio;
+ GtkRadioButton *manual_radio;
+ GtkCheckButton *never_default_check;
+ GtkBox *routes_box;
+ GtkSizeGroup *routes_metric_sizegroup;
+ GtkSizeGroup *routes_sizegroup;
+ GtkRadioButton *shared_radio;
+
+ NMSettingIPConfig *setting;
+
+ GtkWidget *address_list;
+ GtkWidget *routes_list;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPageIP6, ce_page_ip6, GTK_TYPE_SCROLLED_WINDOW,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+enum {
+ METHOD_COL_NAME,
+ METHOD_COL_METHOD
+};
+
+enum {
+ IP6_METHOD_AUTO,
+ IP6_METHOD_DHCP,
+ IP6_METHOD_MANUAL,
+ IP6_METHOD_LINK_LOCAL,
+ IP6_METHOD_SHARED,
+ IP6_METHOD_DISABLED
+};
+
+static void
+method_changed (CEPageIP6 *self)
+{
+ gboolean addr_enabled;
+ gboolean dns_enabled;
+ gboolean routes_enabled;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->disabled_radio)) ||
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->shared_radio))) {
+ addr_enabled = FALSE;
+ dns_enabled = FALSE;
+ routes_enabled = FALSE;
+ } else {
+ addr_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->manual_radio));
+ dns_enabled = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_radio));
+ routes_enabled = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_radio));
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->address_box), addr_enabled);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->dns_entry), dns_enabled);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->routes_list), routes_enabled);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->never_default_check), routes_enabled);
+
+ ce_page_changed (CE_PAGE (self));
+}
+
+static void
+update_row_sensitivity (CEPageIP6 *self, GtkWidget *list)
+{
+ GList *children, *l;
+ gint rows = 0, i = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (list));
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "delete-button"));
+ if (button != NULL)
+ rows++;
+ }
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "delete-button"));
+ if (button != NULL)
+ gtk_widget_set_sensitive (button, rows > 1 && ++i < rows);
+ }
+ g_list_free (children);
+}
+
+static void
+remove_row (CEPageIP6 *self, GtkButton *button)
+{
+ GtkWidget *row;
+ GtkWidget *row_box;
+ GtkWidget *list;
+
+ row_box = gtk_widget_get_parent (GTK_WIDGET (button));
+ row = gtk_widget_get_parent (row_box);
+ list = gtk_widget_get_parent (row);
+
+ gtk_container_remove (GTK_CONTAINER (list), row);
+
+ ce_page_changed (CE_PAGE (self));
+
+ update_row_sensitivity (self, list);
+}
+
+static gboolean
+validate_row (GtkWidget *row)
+{
+ GtkWidget *box;
+ GList *children, *l;
+ gboolean valid;
+
+ valid = FALSE;
+ box = gtk_bin_get_child (GTK_BIN (row));
+ children = gtk_container_get_children (GTK_CONTAINER (box));
+
+ for (l = children; l != NULL; l = l->next) {
+ if (!GTK_IS_ENTRY (l->data))
+ continue;
+
+ valid = valid || gtk_entry_get_text_length (l->data) > 0;
+ }
+
+ g_list_free (children);
+
+ return valid;
+}
+
+static void
+add_address_row (CEPageIP6 *self,
+ const gchar *address,
+ const gchar *network,
+ const gchar *gateway)
+{
+ GtkWidget *row;
+ GtkWidget *row_box;
+ GtkWidget *widget;
+ GtkWidget *delete_button;
+ GtkWidget *image;
+
+ row = gtk_list_box_row_new ();
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class (gtk_widget_get_style_context (row_box), "linked");
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "address", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), address);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "prefix", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), network);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "gateway", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), gateway ? gateway : "");
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ delete_button = gtk_button_new ();
+ gtk_widget_set_sensitive (delete_button, FALSE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (delete_button), "image-button");
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ image = gtk_image_new_from_icon_name ("edit-delete-symbolic", GTK_ICON_SIZE_MENU);
+ atk_object_set_name (gtk_widget_get_accessible (delete_button), _("Delete Address"));
+ gtk_button_set_image (GTK_BUTTON (delete_button), image);
+ gtk_container_add (GTK_CONTAINER (row_box), delete_button);
+ g_object_set_data (G_OBJECT (row), "delete-button", delete_button);
+
+ gtk_size_group_add_widget (self->address_sizegroup, delete_button);
+
+ gtk_container_add (GTK_CONTAINER (row), row_box);
+ gtk_widget_show_all (row);
+ gtk_container_add (GTK_CONTAINER (self->address_list), row);
+
+ update_row_sensitivity (self, self->address_list);
+}
+
+static void
+ensure_empty_address_row (CEPageIP6 *self)
+{
+ GList *children, *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->address_list));
+ l = children;
+
+ while (l && l->next)
+ l = l->next;
+
+ /* Add the last, stub row if needed*/
+ if (!l || validate_row (l->data))
+ add_address_row (self, "", "", "");
+
+ g_list_free (children);
+}
+
+static void
+add_address_box (CEPageIP6 *self)
+{
+ GtkWidget *list;
+ gint i;
+
+ self->address_list = list = gtk_list_box_new ();
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (list), cc_list_box_update_header_func, NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (self->address_box), list);
+
+ for (i = 0; i < nm_setting_ip_config_get_num_addresses (self->setting); i++) {
+ NMIPAddress *addr;
+ g_autofree gchar *netmask = NULL;
+
+ addr = nm_setting_ip_config_get_address (self->setting, i);
+ netmask = g_strdup_printf ("%u", nm_ip_address_get_prefix (addr));
+ add_address_row (self, nm_ip_address_get_address (addr), netmask,
+ i == 0 ? nm_setting_ip_config_get_gateway (self->setting) : NULL);
+ }
+ if (nm_setting_ip_config_get_num_addresses (self->setting) == 0)
+ ensure_empty_address_row (self);
+
+ gtk_widget_show_all (GTK_WIDGET (self->address_box));
+}
+
+static void
+add_dns_section (CEPageIP6 *self)
+{
+ GString *string;
+ gint i;
+
+ gtk_switch_set_active (self->auto_dns_switch, !nm_setting_ip_config_get_ignore_auto_dns (self->setting));
+ g_signal_connect_object (self->auto_dns_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ string = g_string_new ("");
+
+ for (i = 0; i < nm_setting_ip_config_get_num_dns (self->setting); i++) {
+ const char *address;
+
+ address = nm_setting_ip_config_get_dns (self->setting, i);
+
+ if (i > 0)
+ g_string_append (string, ", ");
+
+ g_string_append (string, address);
+
+ }
+
+ gtk_entry_set_text (self->dns_entry, string->str);
+
+ g_signal_connect_object (self->dns_entry, "notify::text", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ g_string_free (string, TRUE);
+}
+
+static void
+add_route_row (CEPageIP6 *self,
+ const gchar *address,
+ const gchar *prefix,
+ const gchar *gateway,
+ const gchar *metric)
+{
+ GtkWidget *row;
+ GtkWidget *row_box;
+ GtkWidget *widget;
+ GtkWidget *delete_button;
+ GtkWidget *image;
+
+ row = gtk_list_box_row_new ();
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class (gtk_widget_get_style_context (row_box), "linked");
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "address", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), address);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "prefix", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), prefix ? prefix : "");
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "gateway", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), gateway);
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ widget = gtk_entry_new ();
+ g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (row), "metric", widget);
+ gtk_entry_set_text (GTK_ENTRY (widget), metric ? metric : "");
+ gtk_entry_set_width_chars (GTK_ENTRY (widget), 5);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_container_add (GTK_CONTAINER (row_box), widget);
+
+ gtk_size_group_add_widget (self->routes_metric_sizegroup, widget);
+
+ delete_button = gtk_button_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (delete_button), "image-button");
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ image = gtk_image_new_from_icon_name ("edit-delete-symbolic", GTK_ICON_SIZE_MENU);
+ atk_object_set_name (gtk_widget_get_accessible (delete_button), _("Delete Route"));
+ gtk_button_set_image (GTK_BUTTON (delete_button), image);
+ gtk_widget_set_halign (delete_button, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (delete_button, GTK_ALIGN_CENTER);
+ gtk_container_add (GTK_CONTAINER (row_box), delete_button);
+ g_object_set_data (G_OBJECT (row), "delete-button", delete_button);
+
+ gtk_size_group_add_widget (self->routes_sizegroup, delete_button);
+
+ gtk_container_add (GTK_CONTAINER (row), row_box);
+ gtk_widget_show_all (row);
+ gtk_container_add (GTK_CONTAINER (self->routes_list), row);
+
+ update_row_sensitivity (self, self->routes_list);
+}
+
+static void
+ensure_empty_routes_row (CEPageIP6 *self)
+{
+ GList *children, *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->routes_list));
+ l = children;
+
+ while (l && l->next)
+ l = l->next;
+
+ /* Add the last, stub row if needed*/
+ if (!l || validate_row (l->data))
+ add_route_row (self, "", NULL, "", NULL);
+
+ g_list_free (children);
+}
+
+static void
+add_empty_route_row (CEPageIP6 *self)
+{
+ add_route_row (self, "", NULL, "", NULL);
+}
+
+static void
+add_routes_box (CEPageIP6 *self)
+{
+ GtkWidget *list;
+ gint i;
+
+ self->routes_list = list = gtk_list_box_new ();
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (list), cc_list_box_update_header_func, NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (self->routes_box), list);
+ gtk_switch_set_active (self->auto_routes_switch, !nm_setting_ip_config_get_ignore_auto_routes (self->setting));
+ g_signal_connect_object (self->auto_routes_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ for (i = 0; i < nm_setting_ip_config_get_num_routes (self->setting); i++) {
+ NMIPRoute *route;
+ g_autofree gchar *prefix = NULL;
+ g_autofree gchar *metric = NULL;
+
+ route = nm_setting_ip_config_get_route (self->setting, i);
+ prefix = g_strdup_printf ("%u", nm_ip_route_get_prefix (route));
+ metric = g_strdup_printf ("%" G_GINT64_FORMAT, nm_ip_route_get_metric (route));
+ add_route_row (self, nm_ip_route_get_dest (route),
+ prefix,
+ nm_ip_route_get_next_hop (route),
+ metric);
+ }
+ if (nm_setting_ip_config_get_num_routes (self->setting) == 0)
+ add_empty_route_row (self);
+
+ gtk_widget_show_all (GTK_WIDGET (self->routes_box));
+}
+
+static void
+connect_ip6_page (CEPageIP6 *self)
+{
+ const gchar *str_method;
+ guint method;
+
+ add_address_box (self);
+ add_dns_section (self);
+ add_routes_box (self);
+
+ str_method = nm_setting_ip_config_get_method (self->setting);
+ g_signal_connect_object (self->disabled_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_object_bind_property (self->disabled_radio, "active",
+ self->content_box, "sensitive",
+ G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+ g_signal_connect_object (self->shared_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+ g_object_bind_property (self->shared_radio, "active",
+ self->content_box, "sensitive",
+ G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+ method = IP6_METHOD_AUTO;
+ if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
+ method = IP6_METHOD_DHCP;
+ } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
+ method = IP6_METHOD_LINK_LOCAL;
+ } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) == 0) {
+ method = IP6_METHOD_MANUAL;
+ } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) {
+ method = IP6_METHOD_SHARED;
+ } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED) == 0 ||
+ g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
+ method = IP6_METHOD_DISABLED;
+ }
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->never_default_check),
+ nm_setting_ip_config_get_never_default (self->setting));
+ g_signal_connect_object (self->never_default_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->automatic_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->dhcp_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->local_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->manual_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->disabled_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED);
+
+ switch (method) {
+ case IP6_METHOD_AUTO:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->automatic_radio), TRUE);
+ break;
+ case IP6_METHOD_DHCP:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->dhcp_radio), TRUE);
+ break;
+ case IP6_METHOD_LINK_LOCAL:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->local_radio), TRUE);
+ break;
+ case IP6_METHOD_MANUAL:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->manual_radio), TRUE);
+ break;
+ case IP6_METHOD_SHARED:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->shared_radio), TRUE);
+ break;
+ case IP6_METHOD_DISABLED:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->disabled_radio), TRUE);
+ break;
+ default:
+ break;
+ }
+
+ method_changed (self);
+}
+
+static gboolean
+ui_to_setting (CEPageIP6 *self)
+{
+ const gchar *method;
+ gboolean ignore_auto_dns;
+ gboolean ignore_auto_routes;
+ gboolean never_default;
+ GList *children, *l;
+ gboolean ret = TRUE;
+ GStrv dns_addresses = NULL;
+ gchar *dns_text = NULL;
+ guint i;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->disabled_radio)))
+ method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->manual_radio)))
+ method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_radio)))
+ method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->dhcp_radio)))
+ method = NM_SETTING_IP6_CONFIG_METHOD_DHCP;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->automatic_radio)))
+ method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->shared_radio)))
+ method = NM_SETTING_IP6_CONFIG_METHOD_SHARED;
+
+ nm_setting_ip_config_clear_addresses (self->setting);
+ if (g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+ children = gtk_container_get_children (GTK_CONTAINER (self->address_list));
+ } else {
+ g_object_set (G_OBJECT (self->setting),
+ NM_SETTING_IP_CONFIG_GATEWAY, NULL,
+ NULL);
+ children = NULL;
+ }
+
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkEntry *entry;
+ const gchar *text_address;
+ const gchar *text_prefix;
+ const gchar *text_gateway;
+ guint32 prefix;
+ gchar *end;
+ NMIPAddress *addr;
+ gboolean have_gateway = FALSE;
+
+ entry = GTK_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!entry)
+ continue;
+
+ text_address = gtk_entry_get_text (entry);
+ text_prefix = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "prefix")));
+ text_gateway = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "gateway")));
+
+ if (!*text_address && !*text_prefix && !*text_gateway) {
+ /* ignore empty rows */
+ widget_unset_error (GTK_WIDGET (entry));
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "gateway"));
+ continue;
+ }
+
+ if (!*text_address || !nm_utils_ipaddr_valid (AF_INET6, text_address)) {
+ widget_set_error (GTK_WIDGET (entry));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (entry));
+ }
+
+ prefix = strtoul (text_prefix, &end, 10);
+ if (!end || *end || prefix == 0 || prefix > 128) {
+ widget_set_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ }
+
+ if (*text_gateway && !nm_utils_ipaddr_valid (AF_INET6, text_gateway)) {
+ widget_set_error (g_object_get_data (G_OBJECT (row), "gateway"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "gateway"));
+ if (*text_gateway)
+ have_gateway = TRUE;
+ }
+
+ if (!ret)
+ continue;
+
+ addr = nm_ip_address_new (AF_INET6, text_address, prefix, NULL);
+ if (have_gateway)
+ g_object_set (G_OBJECT (self->setting),
+ NM_SETTING_IP_CONFIG_GATEWAY, text_gateway,
+ NULL);
+ nm_setting_ip_config_add_address (self->setting, addr);
+
+ if (!l || !l->next)
+ ensure_empty_address_row (self);
+ }
+ g_list_free (children);
+
+ nm_setting_ip_config_clear_dns (self->setting);
+ dns_text = g_strstrip (g_strdup (gtk_entry_get_text (GTK_ENTRY (self->dns_entry))));
+
+ if (g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) ||
+ g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) ||
+ g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+ dns_addresses = g_strsplit_set (dns_text, ", ", -1);
+ else
+ dns_addresses = NULL;
+
+ for (i = 0; dns_addresses && dns_addresses[i]; i++) {
+ const gchar *text;
+ struct in6_addr tmp_addr;
+
+ text = dns_addresses[i];
+
+ if (!text || !*text)
+ continue;
+
+ if (inet_pton (AF_INET6, text, &tmp_addr) <= 0) {
+ g_clear_pointer (&dns_addresses, g_strfreev);
+ widget_set_error (GTK_WIDGET (self->dns_entry));
+ ret = FALSE;
+ break;
+ } else {
+ widget_unset_error (GTK_WIDGET (self->dns_entry));
+ nm_setting_ip_config_add_dns (self->setting, text);
+ }
+ }
+
+ nm_setting_ip_config_clear_routes (self->setting);
+ if (g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) ||
+ g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) ||
+ g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+ children = gtk_container_get_children (GTK_CONTAINER (self->routes_list));
+ else
+ children = NULL;
+
+ for (l = children; l; l = l->next) {
+ GtkWidget *row = l->data;
+ GtkEntry *entry;
+ const gchar *text_address;
+ const gchar *text_prefix;
+ const gchar *text_gateway;
+ const gchar *text_metric;
+ guint32 prefix;
+ gint64 metric;
+ gchar *end;
+ NMIPRoute *route;
+
+ entry = GTK_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!entry)
+ continue;
+
+ text_address = gtk_entry_get_text (entry);
+ text_prefix = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "prefix")));
+ text_gateway = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "gateway")));
+ text_metric = gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (row), "metric")));
+
+ if (!*text_address && !*text_prefix && !*text_gateway && !*text_metric) {
+ /* ignore empty rows */
+ widget_unset_error (GTK_WIDGET (entry));
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "gateway"));
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "metric"));
+ continue;
+ }
+
+ if (!nm_utils_ipaddr_valid (AF_INET6, text_address)) {
+ widget_set_error (GTK_WIDGET (entry));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (entry));
+ }
+
+ prefix = strtoul (text_prefix, &end, 10);
+ if (!end || *end || prefix == 0 || prefix > 128) {
+ widget_set_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ }
+
+ if (!nm_utils_ipaddr_valid (AF_INET6, text_gateway)) {
+ widget_set_error (g_object_get_data (G_OBJECT (row), "gateway"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "gateway"));
+ }
+
+ metric = -1;
+ if (*text_metric) {
+ errno = 0;
+ metric = g_ascii_strtoull (text_metric, NULL, 10);
+ if (errno) {
+ widget_set_error (g_object_get_data (G_OBJECT (row), "metric"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "metric"));
+ }
+ } else {
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "metric"));
+ }
+
+ if (!ret)
+ continue;
+
+ route = nm_ip_route_new (AF_INET6, text_address, prefix, text_gateway, metric, NULL);
+ nm_setting_ip_config_add_route (self->setting, route);
+ nm_ip_route_unref (route);
+
+ if (!l || !l->next)
+ ensure_empty_routes_row (self);
+ }
+ g_list_free (children);
+
+ if (!ret)
+ goto out;
+
+ ignore_auto_dns = !gtk_switch_get_active (self->auto_dns_switch);
+ ignore_auto_routes = !gtk_switch_get_active (self->auto_routes_switch);
+ never_default = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->never_default_check));
+
+ g_object_set (self->setting,
+ NM_SETTING_IP_CONFIG_METHOD, method,
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, ignore_auto_dns,
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, ignore_auto_routes,
+ NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default,
+ NULL);
+
+out:
+ g_clear_pointer (&dns_addresses, g_strfreev);
+ g_clear_pointer (&dns_text, g_free);
+
+ return ret;
+}
+
+static const gchar *
+ce_page_ip6_get_title (CEPage *page)
+{
+ return _("IPv6");
+}
+
+static gboolean
+ce_page_ip6_validate (CEPage *self,
+ NMConnection *connection,
+ GError **error)
+{
+ if (!ui_to_setting (CE_PAGE_IP6 (self)))
+ return FALSE;
+
+ return nm_setting_verify (NM_SETTING (CE_PAGE_IP6 (self)->setting), NULL, error);
+}
+
+static void
+ce_page_ip6_init (CEPageIP6 *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_ip6_class_init (CEPageIP6Class *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/ip6-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, auto_dns_switch);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, auto_routes_switch);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, automatic_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, content_box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, dhcp_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, disabled_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, dns_entry);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, local_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, manual_radio);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, never_default_check);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_metric_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, shared_radio);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_title = ce_page_ip6_get_title;
+ iface->validate = ce_page_ip6_validate;
+}
+
+CEPageIP6 *
+ce_page_ip6_new (NMConnection *connection,
+ NMClient *client)
+{
+ CEPageIP6 *self;
+
+ self = CE_PAGE_IP6 (g_object_new (ce_page_ip6_get_type (), NULL));
+
+ self->setting = nm_connection_get_setting_ip6_config (connection);
+ if (!self->setting) {
+ self->setting = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new ());
+ nm_connection_add_setting (connection, NM_SETTING (self->setting));
+ }
+
+ connect_ip6_page (self);
+
+ return self;
+}
diff --git a/panels/network/connection-editor/ce-page-ip6.h b/panels/network/connection-editor/ce-page-ip6.h
new file mode 100644
index 0000000..718a62c
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ip6.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageIP6, ce_page_ip6, CE, PAGE_IP6, GtkScrolledWindow)
+
+CEPageIP6 *ce_page_ip6_new (NMConnection *connection,
+ NMClient *client);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-page-security.c b/panels/network/connection-editor/ce-page-security.c
new file mode 100644
index 0000000..7d7f305
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-security.c
@@ -0,0 +1,542 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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/gi18n.h>
+
+#include <NetworkManager.h>
+
+#include "ce-page.h"
+#include "ce-page-security.h"
+#include "wireless-security.h"
+#include "ws-dynamic-wep.h"
+#include "ws-leap.h"
+#include "ws-wep-key.h"
+#include "ws-wpa-eap.h"
+#include "ws-wpa-psk.h"
+
+struct _CEPageSecurity
+{
+ GtkGrid parent;
+
+ GtkBox *box;
+ GtkComboBox *security_combo;
+ GtkLabel *security_label;
+
+ NMConnection *connection;
+ const gchar *security_setting;
+ GtkSizeGroup *group;
+ gboolean adhoc;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPageSecurity, ce_page_security, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+enum {
+ S_NAME_COLUMN,
+ S_SEC_COLUMN,
+ S_ADHOC_VALID_COLUMN
+};
+
+static gboolean
+find_proto (NMSettingWirelessSecurity *sec, const char *item)
+{
+ guint32 i;
+
+ for (i = 0; i < nm_setting_wireless_security_get_num_protos (sec); i++) {
+ if (!strcmp (item, nm_setting_wireless_security_get_proto (sec, i)))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static NMUtilsSecurityType
+get_default_type_for_security (NMSettingWirelessSecurity *sec)
+{
+ const char *key_mgmt, *auth_alg;
+
+ g_return_val_if_fail (sec != NULL, NMU_SEC_NONE);
+
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (sec);
+ auth_alg = nm_setting_wireless_security_get_auth_alg (sec);
+
+ /* No IEEE 802.1x */
+ if (!strcmp (key_mgmt, "none"))
+ return NMU_SEC_STATIC_WEP;
+
+ if (!strcmp (key_mgmt, "ieee8021x")) {
+ if (auth_alg && !strcmp (auth_alg, "leap"))
+ return NMU_SEC_LEAP;
+ return NMU_SEC_DYNAMIC_WEP;
+ }
+
+#if NM_CHECK_VERSION(1,24,0)
+ if (!strcmp (key_mgmt, "owe")) {
+ return NMU_SEC_OWE;
+ }
+#endif
+
+#if NM_CHECK_VERSION(1,20,6)
+ if (!strcmp (key_mgmt, "sae")) {
+ return NMU_SEC_SAE;
+ }
+#endif
+
+ if ( !strcmp (key_mgmt, "wpa-none")
+ || !strcmp (key_mgmt, "wpa-psk")) {
+ if (find_proto (sec, "rsn"))
+ return NMU_SEC_WPA2_PSK;
+ else if (find_proto (sec, "wpa"))
+ return NMU_SEC_WPA_PSK;
+ else
+ return NMU_SEC_WPA_PSK;
+ }
+
+ if (!strcmp (key_mgmt, "wpa-eap")) {
+ if (find_proto (sec, "rsn"))
+ return NMU_SEC_WPA2_ENTERPRISE;
+ else if (find_proto (sec, "wpa"))
+ return NMU_SEC_WPA_ENTERPRISE;
+ else
+ return NMU_SEC_WPA_ENTERPRISE;
+ }
+
+ return NMU_SEC_INVALID;
+}
+
+static WirelessSecurity *
+security_combo_get_active (CEPageSecurity *self)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ WirelessSecurity *sec = NULL;
+
+ model = gtk_combo_box_get_model (self->security_combo);
+ if (!gtk_combo_box_get_active_iter (self->security_combo, &iter))
+ return NULL;
+ gtk_tree_model_get (model, &iter, S_SEC_COLUMN, &sec, -1);
+
+ return sec;
+}
+
+static void
+wsec_size_group_clear (GtkSizeGroup *group)
+{
+ GSList *children;
+ GSList *iter;
+
+ g_return_if_fail (group != NULL);
+
+ children = gtk_size_group_get_widgets (group);
+ for (iter = children; iter; iter = g_slist_next (iter))
+ gtk_size_group_remove_widget (group, GTK_WIDGET (iter->data));
+}
+
+static void
+security_combo_changed (CEPageSecurity *self)
+{
+ GList *l, *children;
+ g_autoptr(WirelessSecurity) sec = NULL;
+
+ wsec_size_group_clear (self->group);
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->box));
+ for (l = children; l; l = l->next) {
+ gtk_container_remove (GTK_CONTAINER (self->box), GTK_WIDGET (l->data));
+ }
+
+ sec = security_combo_get_active (self);
+ if (sec) {
+ GtkWidget *parent;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (sec));
+ if (parent)
+ gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (sec));
+
+ gtk_size_group_add_widget (self->group, GTK_WIDGET (self->security_label));
+ wireless_security_add_to_size_group (sec, self->group);
+
+ gtk_container_add (GTK_CONTAINER (self->box), g_object_ref (GTK_WIDGET (sec)));
+ }
+
+ ce_page_changed (CE_PAGE (self));
+}
+
+static void
+security_item_changed_cb (CEPageSecurity *self)
+{
+ ce_page_changed (CE_PAGE (self));
+}
+
+static void
+add_security_item (CEPageSecurity *self,
+ WirelessSecurity *sec,
+ GtkListStore *model,
+ GtkTreeIter *iter,
+ const char *text,
+ gboolean adhoc_valid)
+{
+ g_signal_connect_object (sec, "changed", G_CALLBACK (security_item_changed_cb), self, G_CONNECT_SWAPPED);
+ gtk_list_store_append (model, iter);
+ gtk_list_store_set (model, iter,
+ S_NAME_COLUMN, text,
+ S_SEC_COLUMN, sec,
+ S_ADHOC_VALID_COLUMN, adhoc_valid,
+ -1);
+ g_object_unref (sec);
+}
+
+static void
+set_sensitive (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gboolean *adhoc = data;
+ gboolean sensitive = TRUE, adhoc_valid = TRUE;
+
+ gtk_tree_model_get (tree_model, iter, S_ADHOC_VALID_COLUMN, &adhoc_valid, -1);
+ if (*adhoc && !adhoc_valid)
+ sensitive = FALSE;
+
+ g_object_set (cell, "sensitive", sensitive, NULL);
+}
+
+static void
+finish_setup (CEPageSecurity *self)
+{
+ NMSettingWireless *sw;
+ NMSettingWirelessSecurity *sws;
+ gboolean is_adhoc = FALSE;
+ g_autoptr(GtkListStore) sec_model = NULL;
+ GtkTreeIter iter;
+ const gchar *mode;
+ guint32 dev_caps = 0;
+ NMUtilsSecurityType default_type = NMU_SEC_NONE;
+ int active = -1;
+ int item = 0;
+ GtkCellRenderer *renderer;
+
+ sw = nm_connection_get_setting_wireless (self->connection);
+ g_assert (sw);
+
+ self->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ dev_caps = NM_WIFI_DEVICE_CAP_CIPHER_WEP40
+ | NM_WIFI_DEVICE_CAP_CIPHER_WEP104
+ | NM_WIFI_DEVICE_CAP_CIPHER_TKIP
+ | NM_WIFI_DEVICE_CAP_CIPHER_CCMP
+ | NM_WIFI_DEVICE_CAP_WPA
+ | NM_WIFI_DEVICE_CAP_RSN;
+
+ mode = nm_setting_wireless_get_mode (sw);
+ if (mode && !strcmp (mode, "adhoc"))
+ is_adhoc = TRUE;
+ self->adhoc = is_adhoc;
+
+ sws = nm_connection_get_setting_wireless_security (self->connection);
+ if (sws)
+ default_type = get_default_type_for_security (sws);
+
+ sec_model = gtk_list_store_new (3, G_TYPE_STRING, wireless_security_get_type (), G_TYPE_BOOLEAN);
+
+ if (nm_utils_security_valid (NMU_SEC_NONE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ gtk_list_store_insert_with_values (sec_model, &iter, -1,
+ S_NAME_COLUMN, C_("Wi-Fi/Ethernet security", "None"),
+ S_ADHOC_VALID_COLUMN, TRUE,
+ -1);
+ if (default_type == NMU_SEC_NONE)
+ active = item;
+ item++;
+ }
+
+#if NM_CHECK_VERSION(1,24,0)
+ if (nm_utils_security_valid (NMU_SEC_OWE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ gtk_list_store_insert_with_values (sec_model, &iter, -1,
+ S_NAME_COLUMN, _("Enhanced Open"),
+ S_ADHOC_VALID_COLUMN, FALSE,
+ -1);
+ if (active < 0 && default_type == NMU_SEC_OWE)
+ active = item;
+ item++;
+ }
+#endif
+
+ if (nm_utils_security_valid (NMU_SEC_STATIC_WEP, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ WirelessSecurityWEPKey *ws_wep;
+ NMWepKeyType wep_type = NM_WEP_KEY_TYPE_KEY;
+
+ if (default_type == NMU_SEC_STATIC_WEP) {
+ sws = nm_connection_get_setting_wireless_security (self->connection);
+ if (sws)
+ wep_type = nm_setting_wireless_security_get_wep_key_type (sws);
+ if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN)
+ wep_type = NM_WEP_KEY_TYPE_KEY;
+ }
+
+ ws_wep = ws_wep_key_new (self->connection, NM_WEP_KEY_TYPE_KEY);
+ if (ws_wep) {
+ add_security_item (self, WIRELESS_SECURITY (ws_wep), sec_model,
+ &iter, _("WEP 40/128-bit Key (Hex or ASCII)"),
+ TRUE);
+ if ((active < 0) && (default_type == NMU_SEC_STATIC_WEP) && (wep_type == NM_WEP_KEY_TYPE_KEY))
+ active = item;
+ item++;
+ }
+
+ ws_wep = ws_wep_key_new (self->connection, NM_WEP_KEY_TYPE_PASSPHRASE);
+ if (ws_wep) {
+ add_security_item (self, WIRELESS_SECURITY (ws_wep), sec_model,
+ &iter, _("WEP 128-bit Passphrase"), TRUE);
+ if ((active < 0) && (default_type == NMU_SEC_STATIC_WEP) && (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE))
+ active = item;
+ item++;
+ }
+ }
+
+ if (nm_utils_security_valid (NMU_SEC_LEAP, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ WirelessSecurityLEAP *ws_leap;
+
+ ws_leap = ws_leap_new (self->connection);
+ if (ws_leap) {
+ add_security_item (self, WIRELESS_SECURITY (ws_leap), sec_model,
+ &iter, _("LEAP"), FALSE);
+ if ((active < 0) && (default_type == NMU_SEC_LEAP))
+ active = item;
+ item++;
+ }
+ }
+
+ if (nm_utils_security_valid (NMU_SEC_DYNAMIC_WEP, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ WirelessSecurityDynamicWEP *ws_dynamic_wep;
+
+ ws_dynamic_wep = ws_dynamic_wep_new (self->connection);
+ if (ws_dynamic_wep) {
+ add_security_item (self, WIRELESS_SECURITY (ws_dynamic_wep), sec_model,
+ &iter, _("Dynamic WEP (802.1x)"), FALSE);
+ if ((active < 0) && (default_type == NMU_SEC_DYNAMIC_WEP))
+ active = item;
+ item++;
+ }
+ }
+
+ if (nm_utils_security_valid (NMU_SEC_WPA_PSK, dev_caps, FALSE, is_adhoc, 0, 0, 0) ||
+ nm_utils_security_valid (NMU_SEC_WPA2_PSK, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ WirelessSecurityWPAPSK *ws_wpa_psk;
+
+ ws_wpa_psk = ws_wpa_psk_new (self->connection);
+ if (ws_wpa_psk) {
+ add_security_item (self, WIRELESS_SECURITY (ws_wpa_psk), sec_model,
+ &iter, _("WPA & WPA2 Personal"), FALSE);
+ if ((active < 0) && ((default_type == NMU_SEC_WPA_PSK) || (default_type == NMU_SEC_WPA2_PSK)))
+ active = item;
+ item++;
+ }
+ }
+
+ if (nm_utils_security_valid (NMU_SEC_WPA_ENTERPRISE, dev_caps, FALSE, is_adhoc, 0, 0, 0) ||
+ nm_utils_security_valid (NMU_SEC_WPA2_ENTERPRISE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ WirelessSecurityWPAEAP *ws_wpa_eap;
+
+ ws_wpa_eap = ws_wpa_eap_new (self->connection);
+ if (ws_wpa_eap) {
+ add_security_item (self, WIRELESS_SECURITY (ws_wpa_eap), sec_model,
+ &iter, _("WPA & WPA2 Enterprise"), FALSE);
+ if ((active < 0) && ((default_type == NMU_SEC_WPA_ENTERPRISE) || (default_type == NMU_SEC_WPA2_ENTERPRISE)))
+ active = item;
+ item++;
+ }
+ }
+
+#if NM_CHECK_VERSION(1,20,6)
+ if (nm_utils_security_valid (NMU_SEC_SAE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) {
+ WirelessSecurityWPAPSK *ws_wpa_psk;
+
+ ws_wpa_psk = ws_wpa_psk_new (self->connection);
+ if (ws_wpa_psk) {
+ add_security_item (self, WIRELESS_SECURITY (ws_wpa_psk), sec_model,
+ &iter, _("WPA3 Personal"), FALSE);
+ if ((active < 0) && ((default_type == NMU_SEC_SAE)))
+ active = item;
+ item++;
+ }
+ }
+#endif
+
+ gtk_combo_box_set_model (self->security_combo, GTK_TREE_MODEL (sec_model));
+ gtk_cell_layout_clear (GTK_CELL_LAYOUT (self->security_combo));
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->security_combo), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self->security_combo), renderer, "text", S_NAME_COLUMN, NULL);
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->security_combo), renderer, set_sensitive, &self->adhoc, NULL);
+
+ gtk_combo_box_set_active (self->security_combo, active < 0 ? 0 : (guint32) active);
+
+ security_combo_changed (self);
+ g_signal_connect_object (self->security_combo, "changed",
+ G_CALLBACK (security_combo_changed), self, G_CONNECT_SWAPPED);
+}
+
+static void
+ce_page_security_dispose (GObject *object)
+{
+ CEPageSecurity *self = CE_PAGE_SECURITY (object);
+
+ g_clear_object (&self->connection);
+ g_clear_object (&self->group);
+
+ G_OBJECT_CLASS (ce_page_security_parent_class)->dispose (object);
+}
+
+static const gchar *
+ce_page_security_get_security_setting (CEPage *page)
+{
+ return CE_PAGE_SECURITY (page)->security_setting;
+}
+
+static const gchar *
+ce_page_security_get_title (CEPage *page)
+{
+ return _("Security");
+}
+
+static gboolean
+ce_page_security_validate (CEPage *page,
+ NMConnection *connection,
+ GError **error)
+{
+ CEPageSecurity *self = CE_PAGE_SECURITY (page);
+ NMSettingWireless *sw;
+ g_autoptr(WirelessSecurity) sec = NULL;
+ gboolean valid = FALSE;
+ const char *mode;
+
+ sw = nm_connection_get_setting_wireless (connection);
+
+ mode = nm_setting_wireless_get_mode (sw);
+ if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0)
+ CE_PAGE_SECURITY (self)->adhoc = TRUE;
+ else
+ CE_PAGE_SECURITY (self)->adhoc = FALSE;
+
+ sec = security_combo_get_active (CE_PAGE_SECURITY (self));
+ if (sec) {
+ GBytes *ssid = nm_setting_wireless_get_ssid (sw);
+
+ if (ssid) {
+ /* FIXME: get failed property and error out of wifi security objects */
+ valid = wireless_security_validate (sec, error);
+ if (valid)
+ wireless_security_fill_connection (sec, connection);
+ } else {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, "Missing SSID");
+ valid = FALSE;
+ }
+
+ if (self->adhoc) {
+ if (!wireless_security_adhoc_compatible (sec)) {
+ if (valid)
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, "Security not compatible with Ad-Hoc mode");
+ valid = FALSE;
+ }
+ }
+ } else {
+ /* No security, unencrypted */
+ nm_connection_remove_setting (connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
+ nm_connection_remove_setting (connection, NM_TYPE_SETTING_802_1X);
+ valid = TRUE;
+ }
+
+ return valid;
+}
+
+static void
+ce_page_security_init (CEPageSecurity *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_security_class_init (CEPageSecurityClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = ce_page_security_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/security-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPageSecurity, box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageSecurity, security_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageSecurity, security_combo);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_security_setting = ce_page_security_get_security_setting;
+ iface->get_title = ce_page_security_get_title;
+ iface->validate = ce_page_security_validate;
+}
+
+CEPageSecurity *
+ce_page_security_new (NMConnection *connection)
+{
+ CEPageSecurity *self;
+ NMUtilsSecurityType default_type = NMU_SEC_NONE;
+ NMSettingWirelessSecurity *sws;
+
+ self = CE_PAGE_SECURITY (g_object_new (ce_page_security_get_type (), NULL));
+
+ self->connection = g_object_ref (connection);
+
+ sws = nm_connection_get_setting_wireless_security (connection);
+ if (sws)
+ default_type = get_default_type_for_security (sws);
+
+ if (default_type == NMU_SEC_STATIC_WEP ||
+ default_type == NMU_SEC_LEAP ||
+ default_type == NMU_SEC_WPA_PSK ||
+#if NM_CHECK_VERSION(1,20,6)
+ default_type == NMU_SEC_SAE ||
+#endif
+#if NM_CHECK_VERSION(1,24,0)
+ default_type == NMU_SEC_OWE ||
+#endif
+ default_type == NMU_SEC_WPA2_PSK) {
+ self->security_setting = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+ }
+
+ if (default_type == NMU_SEC_DYNAMIC_WEP ||
+ default_type == NMU_SEC_WPA_ENTERPRISE ||
+ default_type == NMU_SEC_WPA2_ENTERPRISE) {
+ self->security_setting = NM_SETTING_802_1X_SETTING_NAME;
+ }
+
+ g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL);
+
+ return self;
+}
diff --git a/panels/network/connection-editor/ce-page-security.h b/panels/network/connection-editor/ce-page-security.h
new file mode 100644
index 0000000..e7c84e5
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-security.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageSecurity, ce_page_security, CE, PAGE_SECURITY, GtkGrid)
+
+CEPageSecurity *ce_page_security_new (NMConnection *connection);
+
+G_END_DECLS
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..9ad0a26
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-vpn.c
@@ -0,0 +1,229 @@
+/* -*- 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/gi18n.h>
+
+#include <NetworkManager.h>
+
+#include "ce-page.h"
+#include "ce-page-vpn.h"
+#include "vpn-helpers.h"
+
+struct _CEPageVpn
+{
+ GtkBox parent;
+
+ GtkLabel *failure_label;
+ GtkEntry *name_entry;
+
+ NMConnection *connection;
+ NMSettingConnection *setting_connection;
+ NMSettingVpn *setting_vpn;
+
+ NMVpnEditorPlugin *plugin;
+ NMVpnEditor *editor;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPageVpn, ce_page_vpn, GTK_TYPE_BOX,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+/* 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;
+ g_autofree gchar *newtext = NULL;
+ int len;
+
+ xalign = gtk_label_get_xalign (GTK_LABEL (widget));
+ 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);
+ gtk_label_set_xalign (GTK_LABEL (widget), 1.0);
+ }
+}
+
+static void
+load_vpn_plugin (CEPageVpn *self)
+{
+ GtkWidget *ui_widget;
+
+ self->editor = nm_vpn_editor_plugin_get_editor (self->plugin,
+ self->connection,
+ NULL);
+ ui_widget = NULL;
+ if (self->editor)
+ ui_widget = GTK_WIDGET (nm_vpn_editor_get_widget (self->editor));
+
+ if (!ui_widget) {
+ g_clear_object (&self->editor);
+ self->plugin = NULL;
+ return;
+ }
+ vpn_gnome3ify_editor (ui_widget);
+
+ gtk_widget_destroy (GTK_WIDGET (self->failure_label));
+
+ gtk_box_pack_start (GTK_BOX (self), ui_widget, TRUE, TRUE, 0);
+ gtk_widget_show_all (ui_widget);
+
+ g_signal_connect_object (self->editor, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+}
+
+static void
+connect_vpn_page (CEPageVpn *self)
+{
+ const gchar *name;
+
+ name = nm_setting_connection_get_id (self->setting_connection);
+ gtk_entry_set_text (self->name_entry, name);
+ g_signal_connect_object (self->name_entry, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+}
+
+static void
+ce_page_vpn_dispose (GObject *object)
+{
+ CEPageVpn *self = CE_PAGE_VPN (object);
+
+ g_clear_object (&self->connection);
+ g_clear_object (&self->editor);
+
+ G_OBJECT_CLASS (ce_page_vpn_parent_class)->dispose (object);
+}
+
+static const gchar *
+ce_page_vpn_get_security_setting (CEPage *page)
+{
+ return NM_SETTING_VPN_SETTING_NAME;
+}
+
+static const gchar *
+ce_page_vpn_get_title (CEPage *page)
+{
+ return _("Identity");
+}
+
+static gboolean
+ce_page_vpn_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_entry),
+ NULL);
+
+ if (!nm_setting_verify (NM_SETTING (self->setting_connection), NULL, error))
+ return FALSE;
+
+ if (!self->editor)
+ return TRUE;
+
+ return nm_vpn_editor_update_connection (self->editor, connection, error);
+}
+
+static void
+ce_page_vpn_init (CEPageVpn *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_vpn_class_init (CEPageVpnClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ object_class->dispose = ce_page_vpn_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/vpn-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPageVpn, failure_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageVpn, name_entry);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_security_setting = ce_page_vpn_get_security_setting;
+ iface->get_title = ce_page_vpn_get_title;
+ iface->validate = ce_page_vpn_validate;
+}
+
+static void
+finish_setup (CEPageVpn *self, gpointer unused, GError *error, gpointer user_data)
+{
+ const char *vpn_type;
+
+ self->setting_connection = nm_connection_get_setting_connection (self->connection);
+ self->setting_vpn = nm_connection_get_setting_vpn (self->connection);
+ vpn_type = nm_setting_vpn_get_service_type (self->setting_vpn);
+
+ self->plugin = vpn_get_plugin_by_service (vpn_type);
+ if (self->plugin)
+ load_vpn_plugin (self);
+
+ connect_vpn_page (self);
+}
+
+CEPageVpn *
+ce_page_vpn_new (NMConnection *connection)
+{
+ CEPageVpn *self;
+
+ self = CE_PAGE_VPN (g_object_new (ce_page_vpn_get_type (), NULL));
+
+ self->connection = g_object_ref (connection);
+
+ g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL);
+
+ return self;
+}
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..f194080
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-vpn.h
@@ -0,0 +1,33 @@
+/* -*- 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageVpn, ce_page_vpn, CE, PAGE_VPN, GtkBox)
+
+CEPageVpn *ce_page_vpn_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-page-wifi.c b/panels/network/connection-editor/ce-page-wifi.c
new file mode 100644
index 0000000..7246170
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-wifi.c
@@ -0,0 +1,212 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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/gi18n.h>
+#include <NetworkManager.h>
+#include <net/if_arp.h>
+
+#include "ce-page.h"
+#include "ce-page-wifi.h"
+#include "ui-helpers.h"
+
+struct _CEPageWifi
+{
+ GtkGrid parent;
+
+ GtkComboBoxText *bssid_combo;
+ GtkComboBoxText *cloned_mac_combo;
+ GtkComboBoxText *mac_combo;
+ GtkEntry *ssid_entry;
+
+ NMClient *client;
+ NMSettingWireless *setting;
+};
+
+static void ce_page_iface_init (CEPageInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (CEPageWifi, ce_page_wifi, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init))
+
+static void
+connect_wifi_page (CEPageWifi *self)
+{
+ GBytes *ssid;
+ g_autofree gchar *utf8_ssid = NULL;
+ GPtrArray *bssid_array;
+ gchar **bssid_list;
+ const char *s_bssid_str;
+ gchar **mac_list;
+ const gchar *s_mac_str;
+ const gchar *cloned_mac;
+ gint i;
+
+ ssid = nm_setting_wireless_get_ssid (self->setting);
+ if (ssid)
+ utf8_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+ else
+ utf8_ssid = g_strdup ("");
+ gtk_entry_set_text (self->ssid_entry, utf8_ssid);
+
+ g_signal_connect_object (self->ssid_entry, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ bssid_array = g_ptr_array_new ();
+ for (i = 0; i < nm_setting_wireless_get_num_seen_bssids (self->setting); i++) {
+ g_ptr_array_add (bssid_array, g_strdup (nm_setting_wireless_get_seen_bssid (self->setting, i)));
+ }
+ g_ptr_array_add (bssid_array, NULL);
+ bssid_list = (gchar **) g_ptr_array_free (bssid_array, FALSE);
+ s_bssid_str = nm_setting_wireless_get_bssid (self->setting);
+ ce_page_setup_mac_combo (self->bssid_combo, s_bssid_str, bssid_list);
+ g_strfreev (bssid_list);
+ g_signal_connect_object (self->bssid_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ mac_list = ce_page_get_mac_list (self->client, NM_TYPE_DEVICE_WIFI,
+ NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS);
+ s_mac_str = nm_setting_wireless_get_mac_address (self->setting);
+ ce_page_setup_mac_combo (self->mac_combo, s_mac_str, mac_list);
+ g_strfreev (mac_list);
+ g_signal_connect_object (self->mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+
+ cloned_mac = nm_setting_wireless_get_cloned_mac_address (self->setting);
+ ce_page_setup_cloned_mac_combo (self->cloned_mac_combo, cloned_mac);
+ g_signal_connect_object (self->cloned_mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED);
+}
+
+static void
+ui_to_setting (CEPageWifi *self)
+{
+ g_autoptr(GBytes) ssid = NULL;
+ const gchar *utf8_ssid, *bssid;
+ GtkWidget *entry;
+ g_autofree gchar *device_mac = NULL;
+ g_autofree gchar *cloned_mac = NULL;
+
+ utf8_ssid = gtk_entry_get_text (self->ssid_entry);
+ if (!utf8_ssid || !*utf8_ssid)
+ ssid = NULL;
+ else {
+ ssid = g_bytes_new_static (utf8_ssid, strlen (utf8_ssid));
+ }
+ entry = gtk_bin_get_child (GTK_BIN (self->bssid_combo));
+ bssid = gtk_entry_get_text (GTK_ENTRY (entry));
+ if (*bssid == '\0')
+ bssid = NULL;
+ entry = gtk_bin_get_child (GTK_BIN (self->mac_combo));
+ device_mac = ce_page_trim_address (gtk_entry_get_text (GTK_ENTRY (entry)));
+ cloned_mac = ce_page_cloned_mac_get (self->cloned_mac_combo);
+
+ g_object_set (self->setting,
+ NM_SETTING_WIRELESS_SSID, ssid,
+ NM_SETTING_WIRELESS_BSSID, bssid,
+ NM_SETTING_WIRELESS_MAC_ADDRESS, device_mac,
+ NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, cloned_mac,
+ NULL);
+}
+
+static const gchar *
+ce_page_wifi_get_title (CEPage *page)
+{
+ return _("Identity");
+}
+
+static gboolean
+ce_page_wifi_class_validate (CEPage *parent,
+ NMConnection *connection,
+ GError **error)
+{
+ CEPageWifi *self = (CEPageWifi *) parent;
+ GtkWidget *entry;
+ gboolean ret = TRUE;
+
+ entry = gtk_bin_get_child (GTK_BIN (self->bssid_combo));
+ if (!ce_page_address_is_valid (gtk_entry_get_text (GTK_ENTRY (entry)))) {
+ widget_set_error (entry);
+ ret = FALSE;
+ } else {
+ widget_unset_error (entry);
+ }
+
+ entry = gtk_bin_get_child (GTK_BIN (self->mac_combo));
+ if (!ce_page_address_is_valid (gtk_entry_get_text (GTK_ENTRY (entry)))) {
+ widget_set_error (entry);
+ ret = FALSE;
+ } else {
+ widget_unset_error (entry);
+ }
+
+ if (!ce_page_cloned_mac_combo_valid (self->cloned_mac_combo)) {
+ widget_set_error (gtk_bin_get_child (GTK_BIN (self->cloned_mac_combo)));
+ ret = FALSE;
+ } else {
+ widget_unset_error (gtk_bin_get_child (GTK_BIN (self->cloned_mac_combo)));
+ }
+
+ if (!ret)
+ return ret;
+
+ ui_to_setting (CE_PAGE_WIFI (self));
+
+ return ret;
+}
+
+static void
+ce_page_wifi_init (CEPageWifi *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ce_page_wifi_class_init (CEPageWifiClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/wifi-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CEPageWifi, bssid_combo);
+ gtk_widget_class_bind_template_child (widget_class, CEPageWifi, cloned_mac_combo);
+ gtk_widget_class_bind_template_child (widget_class, CEPageWifi, mac_combo);
+ gtk_widget_class_bind_template_child (widget_class, CEPageWifi, ssid_entry);
+}
+
+static void
+ce_page_iface_init (CEPageInterface *iface)
+{
+ iface->get_title = ce_page_wifi_get_title;
+ iface->validate = ce_page_wifi_class_validate;
+}
+
+CEPageWifi *
+ce_page_wifi_new (NMConnection *connection,
+ NMClient *client)
+{
+ CEPageWifi *self;
+
+ self = CE_PAGE_WIFI (g_object_new (ce_page_wifi_get_type (), NULL));
+
+ self->client = client;
+ self->setting = nm_connection_get_setting_wireless (connection);
+
+ connect_wifi_page (self);
+
+ return self;
+}
diff --git a/panels/network/connection-editor/ce-page-wifi.h b/panels/network/connection-editor/ce-page-wifi.h
new file mode 100644
index 0000000..8beabad
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-wifi.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_DECLARE_FINAL_TYPE (CEPageWifi, ce_page_wifi, CE, PAGE_WIFI, GtkGrid)
+
+CEPageWifi *ce_page_wifi_new (NMConnection *connection,
+ NMClient *client);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-page.c b/panels/network/connection-editor/ce-page.c
new file mode 100644
index 0000000..fb5d399
--- /dev/null
+++ b/panels/network/connection-editor/ce-page.c
@@ -0,0 +1,417 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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 <string.h>
+
+#include <net/if_arp.h>
+#include <netinet/ether.h>
+
+#include <NetworkManager.h>
+
+#include <glib/gi18n.h>
+
+#include "ce-page.h"
+
+
+G_DEFINE_INTERFACE (CEPage, ce_page, G_TYPE_OBJECT)
+
+enum {
+ CHANGED,
+ INITIALIZED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+gboolean
+ce_page_validate (CEPage *self, NMConnection *connection, GError **error)
+{
+ g_return_val_if_fail (CE_IS_PAGE (self), FALSE);
+ g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+ if (CE_PAGE_GET_IFACE (self)->validate)
+ return CE_PAGE_GET_IFACE (self)->validate (self, connection, error);
+
+ return TRUE;
+}
+
+const char *
+ce_page_get_title (CEPage *self)
+{
+ g_return_val_if_fail (CE_IS_PAGE (self), NULL);
+
+ return CE_PAGE_GET_IFACE (self)->get_title (self);
+}
+
+void
+ce_page_changed (CEPage *self)
+{
+ g_return_if_fail (CE_IS_PAGE (self));
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+ce_page_default_init (CEPageInterface *iface)
+{
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[INITIALIZED] =
+ g_signal_new ("initialized",
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+static void
+emit_initialized (CEPage *self,
+ GError *error)
+{
+ g_signal_emit (self, signals[INITIALIZED], 0, error);
+ g_clear_error (&error);
+}
+
+void
+ce_page_complete_init (CEPage *self,
+ NMConnection *connection,
+ const gchar *setting_name,
+ GVariant *secrets,
+ GError *error)
+{
+ g_autoptr(GError) update_error = NULL;
+ g_autoptr(GVariant) setting_dict = NULL;
+ gboolean ignore_error = FALSE;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (CE_IS_PAGE (self));
+
+ if (error) {
+ ignore_error = g_error_matches (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND) ||
+ g_error_matches (error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_NO_SECRETS);
+ }
+
+ /* Ignore missing settings errors */
+ if (error && !ignore_error) {
+ emit_initialized (self, error);
+ return;
+ } else if (!setting_name || !secrets || g_variant_n_children (secrets) == 0) {
+ /* Success, no secrets */
+ emit_initialized (self, NULL);
+ return;
+ }
+
+ g_assert (setting_name);
+ g_assert (secrets);
+
+ setting_dict = g_variant_lookup_value (secrets, setting_name, NM_VARIANT_TYPE_SETTING);
+ if (!setting_dict) {
+ /* Success, no secrets */
+ emit_initialized (self, NULL);
+ return;
+ }
+
+ /* Update the connection with the new secrets */
+ if (!nm_connection_update_secrets (connection,
+ setting_name,
+ secrets,
+ &update_error))
+ g_warning ("Couldn't update secrets: %s", update_error->message);
+
+ emit_initialized (self, NULL);
+}
+
+gchar **
+ce_page_get_mac_list (NMClient *client,
+ GType device_type,
+ const gchar *mac_property)
+{
+ const GPtrArray *devices;
+ GPtrArray *macs;
+ int i;
+
+ macs = g_ptr_array_new ();
+ devices = nm_client_get_devices (client);
+ for (i = 0; devices && (i < devices->len); i++) {
+ NMDevice *dev = g_ptr_array_index (devices, i);
+ const char *iface;
+ g_autofree gchar *mac = NULL;
+ g_autofree gchar *item = NULL;
+
+ if (!G_TYPE_CHECK_INSTANCE_TYPE (dev, device_type))
+ continue;
+
+ g_object_get (G_OBJECT (dev), mac_property, &mac, NULL);
+ iface = nm_device_get_iface (NM_DEVICE (dev));
+ item = g_strdup_printf ("%s (%s)", mac, iface);
+ g_ptr_array_add (macs, g_steal_pointer (&item));
+ }
+
+ g_ptr_array_add (macs, NULL);
+ return (char **)g_ptr_array_free (macs, FALSE);
+}
+
+void
+ce_page_setup_mac_combo (GtkComboBoxText *combo,
+ const gchar *current_mac,
+ gchar **mac_list)
+{
+ gchar **m, *active_mac = NULL;
+ gint current_mac_len;
+ GtkWidget *entry;
+
+ if (current_mac)
+ current_mac_len = strlen (current_mac);
+ else
+ current_mac_len = -1;
+
+ for (m= mac_list; m && *m; m++) {
+ gtk_combo_box_text_append_text (combo, *m);
+ if (current_mac &&
+ g_ascii_strncasecmp (*m, current_mac, current_mac_len) == 0
+ && ((*m)[current_mac_len] == '\0' || (*m)[current_mac_len] == ' '))
+ active_mac = *m;
+ }
+
+ if (current_mac) {
+ if (!active_mac) {
+ gtk_combo_box_text_prepend_text (combo, current_mac);
+ }
+
+ entry = gtk_bin_get_child (GTK_BIN (combo));
+ if (entry)
+ gtk_entry_set_text (GTK_ENTRY (entry), active_mac ? active_mac : current_mac);
+ }
+}
+
+gchar *
+ce_page_trim_address (const gchar *addr)
+{
+ char *space;
+
+ if (!addr || *addr == '\0')
+ return NULL;
+
+ space = strchr (addr, ' ');
+ if (space != NULL)
+ return g_strndup (addr, space - addr);
+ return g_strdup (addr);
+}
+
+void
+ce_page_setup_cloned_mac_combo (GtkComboBoxText *combo, const char *current)
+{
+ GtkWidget *entry;
+ static const char *entries[][2] = { { "preserve", N_("Preserve") },
+ { "permanent", N_("Permanent") },
+ { "random", N_("Random") },
+ { "stable", N_("Stable") } };
+ int i, active = -1;
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (combo),
+ _("The MAC address entered here will be used as hardware address for "
+ "the network device this connection is activated on. This feature is "
+ "known as MAC cloning or spoofing. Example: 00:11:22:33:44:55"));
+
+ gtk_combo_box_text_remove_all (combo);
+
+ for (i = 0; i < G_N_ELEMENTS (entries); i++) {
+ gtk_combo_box_text_append (combo, entries[i][0], _(entries[i][1]));
+ if (g_strcmp0 (current, entries[i][0]) == 0)
+ active = i;
+ }
+
+ if (active != -1) {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), active);
+ } else if (current && current[0]) {
+ entry = gtk_bin_get_child (GTK_BIN (combo));
+ g_assert (entry);
+ gtk_entry_set_text (GTK_ENTRY (entry), current);
+ }
+}
+
+char *
+ce_page_cloned_mac_get (GtkComboBoxText *combo)
+{
+ g_autofree gchar *active_text = NULL;
+ const char *id;
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo));
+ if (id)
+ return g_strdup (id);
+
+ active_text = gtk_combo_box_text_get_active_text (combo);
+
+ if (active_text[0] == '\0')
+ return NULL;
+
+ return g_steal_pointer (&active_text);
+}
+
+gboolean
+ce_page_address_is_valid (const gchar *addr)
+{
+ guint8 invalid_addr[4][ETH_ALEN] = {
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x44, 0x44, 0x44, 0x44, 0x44, 0x44},
+ {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}, /* prism54 dummy MAC */
+ };
+ guint8 addr_bin[ETH_ALEN];
+ g_autofree gchar *trimmed_addr = NULL;
+ guint i;
+
+ if (!addr || *addr == '\0')
+ return TRUE;
+
+ trimmed_addr = ce_page_trim_address (addr);
+
+ if (!nm_utils_hwaddr_valid (trimmed_addr, -1))
+ return FALSE;
+
+ if (!nm_utils_hwaddr_aton (trimmed_addr, addr_bin, ETH_ALEN))
+ return FALSE;
+
+ /* Check for multicast address */
+ if ((((guint8 *) addr_bin)[0]) & 0x01)
+ return FALSE;
+
+ for (i = 0; i < G_N_ELEMENTS (invalid_addr); i++) {
+ if (nm_utils_hwaddr_matches (addr_bin, ETH_ALEN, invalid_addr[i], ETH_ALEN))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+ce_page_cloned_mac_combo_valid (GtkComboBoxText *combo)
+{
+ g_autofree gchar *active_text = NULL;
+
+ if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) != -1)
+ return TRUE;
+
+ active_text = gtk_combo_box_text_get_active_text (combo);
+
+ return active_text[0] == '\0' || ce_page_address_is_valid (active_text);
+}
+
+const gchar *
+ce_page_get_security_setting (CEPage *self)
+{
+ if (CE_PAGE_GET_IFACE (self)->get_security_setting)
+ return CE_PAGE_GET_IFACE (self)->get_security_setting (self);
+
+ return NULL;
+}
+
+gint
+ce_get_property_default (NMSetting *setting, const gchar *property_name)
+{
+ GParamSpec *spec;
+ GValue value = { 0, };
+
+ spec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), property_name);
+ g_return_val_if_fail (spec != NULL, -1);
+
+ g_value_init (&value, spec->value_type);
+ g_param_value_set_default (spec, &value);
+
+ if (G_VALUE_HOLDS_CHAR (&value))
+ return (int) g_value_get_schar (&value);
+ else if (G_VALUE_HOLDS_INT (&value))
+ return g_value_get_int (&value);
+ else if (G_VALUE_HOLDS_INT64 (&value))
+ return (int) g_value_get_int64 (&value);
+ else if (G_VALUE_HOLDS_LONG (&value))
+ return (int) g_value_get_long (&value);
+ else if (G_VALUE_HOLDS_UINT (&value))
+ return (int) g_value_get_uint (&value);
+ else if (G_VALUE_HOLDS_UINT64 (&value))
+ return (int) g_value_get_uint64 (&value);
+ else if (G_VALUE_HOLDS_ULONG (&value))
+ return (int) g_value_get_ulong (&value);
+ else if (G_VALUE_HOLDS_UCHAR (&value))
+ return (int) g_value_get_uchar (&value);
+ g_return_val_if_fail (FALSE, 0);
+ return 0;
+}
+
+gchar *
+ce_page_get_next_available_name (const GPtrArray *connections,
+ NameFormat format,
+ const gchar *type_name)
+{
+ GSList *names = NULL, *l;
+ gchar *cname = NULL;
+ gint i = 0;
+ guint con_idx;
+
+ for (con_idx = 0; con_idx < connections->len; con_idx++) {
+ NMConnection *connection = g_ptr_array_index (connections, con_idx);
+ const gchar *id;
+
+ id = nm_connection_get_id (connection);
+ g_assert (id);
+ names = g_slist_append (names, (gpointer) id);
+ }
+
+ /* Find the next available unique connection name */
+ while (!cname && (i++ < 10000)) {
+ g_autofree gchar *temp = NULL;
+ gboolean found = FALSE;
+
+ switch (format) {
+ case NAME_FORMAT_TYPE:
+ temp = g_strdup_printf ("%s %d", type_name, i);
+ break;
+ case NAME_FORMAT_PROFILE:
+ temp = g_strdup_printf (_("Profile %d"), i);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ for (l = names; l; l = l->next) {
+ if (!strcmp (l->data, temp)) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found)
+ cname = g_steal_pointer (&temp);
+ }
+ g_slist_free (names);
+
+ return cname;
+}
diff --git a/panels/network/connection-editor/ce-page.h b/panels/network/connection-editor/ce-page.h
new file mode 100644
index 0000000..a180afb
--- /dev/null
+++ b/panels/network/connection-editor/ce-page.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include <NetworkManager.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_INTERFACE (CEPage, ce_page, CE, PAGE, GObject)
+
+struct _CEPageInterface
+{
+ GTypeInterface g_iface;
+
+ gboolean (*validate) (CEPage *page, NMConnection *connection, GError **error);
+ const gchar *(*get_title) (CEPage *page);
+ const gchar *(*get_security_setting) (CEPage *page);
+};
+
+const gchar *ce_page_get_title (CEPage *page);
+const gchar *ce_page_get_security_setting (CEPage *page);
+gboolean ce_page_validate (CEPage *page,
+ NMConnection *connection,
+ GError **error);
+void ce_page_changed (CEPage *page);
+void ce_page_complete_init (CEPage *page,
+ NMConnection *connection,
+ const gchar *setting_name,
+ GVariant *variant,
+ GError *error);
+
+gchar **ce_page_get_mac_list (NMClient *client,
+ GType device_type,
+ const gchar *mac_property);
+void ce_page_setup_mac_combo (GtkComboBoxText *combo,
+ const gchar *current_mac,
+ gchar **mac_list);
+void ce_page_setup_cloned_mac_combo (GtkComboBoxText *combo,
+ const char *current);
+gint ce_get_property_default (NMSetting *setting,
+ const gchar *property_name);
+gboolean ce_page_address_is_valid (const gchar *addr);
+gchar *ce_page_trim_address (const gchar *addr);
+char *ce_page_cloned_mac_get (GtkComboBoxText *combo);
+gboolean ce_page_cloned_mac_combo_valid (GtkComboBoxText *combo);
+
+typedef enum {
+ NAME_FORMAT_TYPE,
+ NAME_FORMAT_PROFILE
+} NameFormat;
+
+gchar * ce_page_get_next_available_name (const GPtrArray *connections,
+ NameFormat format,
+ const gchar *type_name);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/connection-editor.gresource.xml b/panels/network/connection-editor/connection-editor.gresource.xml
new file mode 100644
index 0000000..3d06f5a
--- /dev/null
+++ b/panels/network/connection-editor/connection-editor.gresource.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/network">
+ <file preprocess="xml-stripblanks">8021x-security-page.ui</file>
+ <file preprocess="xml-stripblanks">connection-editor.ui</file>
+ <file preprocess="xml-stripblanks">details-page.ui</file>
+ <file preprocess="xml-stripblanks">ethernet-page.ui</file>
+ <file preprocess="xml-stripblanks">ip4-page.ui</file>
+ <file preprocess="xml-stripblanks">ip6-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/connection-editor.ui b/panels/network/connection-editor/connection-editor.ui
new file mode 100644
index 0000000..17a096f
--- /dev/null
+++ b/panels/network/connection-editor/connection-editor.ui
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="NetConnectionEditor" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">0</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="default_width">500</property>
+ <property name="default_height">300</property>
+ <property name="type_hint">dialog</property>
+ <!-- This doesn't seem to work for a template, so it is also hardcoded. -->
+ <property name="use_header_bar">1</property>
+ <signal name="delete-event" handler="delete_event_cb" swapped="yes"/>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="cancel_clicked_cb" object="NetConnectionEditor" swapped="yes"/>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="apply_button">
+ <property name="label" translatable="yes">_Apply</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="apply_clicked_cb" object="NetConnectionEditor" swapped="yes"/>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">0</property>
+ <property name="border_width">0</property>
+ <child>
+ <object class="GtkStack" id="toplevel_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_border">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="add_connection_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkFrame" id="add_connection_frame">
+ <property name="width_request">300</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <property name="vexpand">True</property>
+ <property name="valign">start</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <action-widget response="apply" default="true">apply_button</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/details-page.ui b/panels/network/connection-editor/details-page.ui
new file mode 100644
index 0000000..fba3aa7
--- /dev/null
+++ b/panels/network/connection-editor/details-page.ui
@@ -0,0 +1,483 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageDetails" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">24</property>
+ <property name="margin_end">24</property>
+ <property name="margin_top">24</property>
+ <property name="margin_bottom">24</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="strength_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Signal Strength</property>
+ <property name="mnemonic_widget">strength_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="strength_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="label">Weak</property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="speed_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Link speed</property>
+ <property name="mnemonic_widget">speed_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="speed_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">1Mb/sec</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="security_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Security</property>
+ <property name="mnemonic_widget">security_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv4_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">IPv4 Address</property>
+ <property name="mnemonic_widget">ipv4_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv6_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">IPv6 Address</property>
+ <property name="mnemonic_widget">ipv6_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="mac_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Hardware Address</property>
+ <property name="mnemonic_widget">mac_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="freq_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Supported Frequencies</property>
+ <property name="mnemonic_widget">freq_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="route_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Default Route</property>
+ <property name="mnemonic_widget">route_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">7</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">DNS</property>
+ <property name="mnemonic_widget">dns_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">8</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="last_used_heading_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Last Used</property>
+ <property name="mnemonic_widget">last_used_label</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">9</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="security_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">WPA</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv4_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">127.0.0.1</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv6_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">::1</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="mac_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">AA:BB:CC:DD:55:66:77:88</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">5</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="freq_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">2.4 GHz / 5 GHz</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">6</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="route_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">127.0.0.1</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">7</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label">127.0.0.1</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">8</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="last_used_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label">today</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">9</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="auto_connect_check">
+ <property name="label" translatable="yes">Connect _automatically</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="valign">end</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ <property name="margin_top">12</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">10</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="all_user_check">
+ <property name="label" translatable="yes">Make available to _other users</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">11</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+
+ <!-- "Restrict Data Usage" section -->
+ <child>
+ <object class="GtkCheckButton" id="restrict_data_check">
+ <property name="can_focus">True</property>
+ <property name="margin_bottom">12</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Metered connection: has data limits or can incur charges</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Software updates and other large downloads will not be started automatically.</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">60</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8" />
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">13</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="forget_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="halign">end</property>
+ <property name="valign">end</property>
+ <style>
+ <class name="destructive-action" />
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">14</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/ethernet-page.ui b/panels/network/connection-editor/ethernet-page.ui
new file mode 100644
index 0000000..4f7331b
--- /dev/null
+++ b/panels/network/connection-editor/ethernet-page.ui
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkAdjustment" id="mtu_adjustment">
+ <property name="upper">10000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <template class="CEPageEthernet" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">50</property>
+ <property name="margin_end">50</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <property name="mnemonic_widget">name_entry</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_MAC Address</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">mac_combo</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="mac_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="cloned_mac_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_entry">True</property>
+ <property name="hexpand">True</property>
+ <property name="active_id">0</property>
+ <child internal-child="entry">
+ <object class="GtkEntry">
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">M_TU</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">mtu_spin</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Cloned Address</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">cloned_mac_combo</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="mtu_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">bytes</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="mtu_spin">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <property name="adjustment">mtu_adjustment</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/ip4-page.ui b/panels/network/connection-editor/ip4-page.ui
new file mode 100644
index 0000000..60f9b30
--- /dev/null
+++ b/panels/network/connection-editor/ip4-page.ui
@@ -0,0 +1,442 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageIP4" parent="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">24</property>
+ <property name="margin_end">24</property>
+ <property name="margin_top">24</property>
+ <property name="margin_bottom">24</property>
+ <property name="orientation">vertical</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">IPv_4 Method</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">disabled_radio</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top-attach">0</property>
+ <property name="left-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="automatic_radio">
+ <property name="label" translatable="yes">Automatic (DHCP)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top-attach">0</property>
+ <property name="left-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="local_radio">
+ <property name="label" translatable="yes">Link-Local Only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">0</property>
+ <property name="left-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="manual_radio">
+ <property name="label" translatable="yes">Manual</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">1</property>
+ <property name="left-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="disabled_radio">
+ <property name="label" translatable="yes">Disable</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">1</property>
+ <property name="left-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="shared_radio">
+ <property name="label" translatable="yes">Shared to other computers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">2</property>
+ <property name="left-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="content_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox" id="address_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Addresses</property>
+ <property name="margin_top">24</property>
+ <property name="margin_bottom">8</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Address</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Netmask</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Gateway</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- This invisible box is used to add some width in the
+ end of the header row, assuming the space used by the
+ delete button in the rows -->
+ <child>
+ <object class="GtkBox" id="address_stub_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="dns_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">DNS</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Automatic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="auto_dns_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Automatic DNS</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dns_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Separate IP addresses with commas</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Routes</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Automatic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="auto_routes_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Automatic Routes</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="routes_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="routes_address_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Address</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Netmask</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Gateway</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="routes_metric_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Translators: Please see https://en.wikipedia.org/wiki/Metrics_(networking)">Metric</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- This invisible box is used to add some width in the
+ end of the header row, assuming the space used by the
+ delete button in the rows -->
+ <child>
+ <object class="GtkBox" id="routes_stub_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="never_default_check">
+ <property name="label" translatable="yes">Use this connection _only for resources on its network</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top-attach">3</property>
+ <property name="left-attach">0</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup" id="routes_metric_sizegroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="routes_metric_label" />
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="routes_sizegroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="routes_stub_box" />
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="address_sizegroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="address_stub_box" />
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/network/connection-editor/ip6-page.ui b/panels/network/connection-editor/ip6-page.ui
new file mode 100644
index 0000000..ed3f211
--- /dev/null
+++ b/panels/network/connection-editor/ip6-page.ui
@@ -0,0 +1,456 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageIP6" parent="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">24</property>
+ <property name="margin_end">24</property>
+ <property name="margin_top">24</property>
+ <property name="margin_bottom">24</property>
+ <property name="orientation">vertical</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">IPv_6 Method</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">disabled_radio</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top-attach">0</property>
+ <property name="left-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="automatic_radio">
+ <property name="label" translatable="yes">Automatic</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top-attach">0</property>
+ <property name="left-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="dhcp_radio">
+ <property name="label" translatable="yes">Automatic, DHCP only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">0</property>
+ <property name="left-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="local_radio">
+ <property name="label" translatable="yes">Link-Local Only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">1</property>
+ <property name="left-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="manual_radio">
+ <property name="label" translatable="yes">Manual</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">1</property>
+ <property name="left-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="disabled_radio">
+ <property name="label" translatable="yes">Disable</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">2</property>
+ <property name="left-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="shared_radio">
+ <property name="label" translatable="yes">Shared to other computers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">automatic_radio</property>
+ </object>
+ <packing>
+ <property name="top-attach">2</property>
+ <property name="left-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="content_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox" id="address_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Addresses</property>
+ <property name="margin_top">24</property>
+ <property name="margin_bottom">8</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="address_address_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Address</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Prefix</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Gateway</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- This invisible box is used to add some width in the
+ end of the header row, assuming the space used by the
+ delete button in the rows -->
+ <child>
+ <object class="GtkBox" id="address_stub_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="dns_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">DNS</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Automatic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="auto_dns_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Automatic DNS</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dns_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Separate IP addresses with commas</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Routes</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Automatic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="auto_routes_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Automatic Routes</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="routes_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="routes_address_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Address</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="routes_prefix_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Prefix</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Gateway</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="routes_metric_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Translators: Please see https://en.wikipedia.org/wiki/Metrics_(networking)">Metric</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- This invisible box is used to add some width in the
+ end of the header row, assuming the space used by the
+ delete button in the rows -->
+ <child>
+ <object class="GtkBox" id="routes_stub_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="never_default_check">
+ <property name="label" translatable="yes">Use this connection _only for resources on its network</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top-attach">3</property>
+ <property name="left-attach">0</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup" id="routes_metric_sizegroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="routes_metric_label" />
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="routes_sizegroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="routes_stub_box" />
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="address_sizegroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="address_stub_box" />
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/network/connection-editor/meson.build b/panels/network/connection-editor/meson.build
new file mode 100644
index 0000000..856833f
--- /dev/null
+++ b/panels/network/connection-editor/meson.build
@@ -0,0 +1,46 @@
+name = 'connection-editor'
+
+sources = files(
+ 'ce-page-8021x-security.c',
+ 'ce-page-details.c',
+ 'ce-page-ethernet.c',
+ 'ce-page-ip4.c',
+ 'ce-page-ip6.c',
+ 'ce-page-security.c',
+ 'ce-page-vpn.c',
+ 'ce-page-wifi.c',
+ 'ce-page.c',
+ 'net-connection-editor.c',
+ 'vpn-helpers.c'
+)
+
+resource_data = files(
+ '8021x-security-page.ui',
+ 'connection-editor.ui',
+ 'details-page.ui',
+ 'ethernet-page.ui',
+ 'ip4-page.ui',
+ 'ip6-page.ui',
+ 'security-page.ui',
+ 'vpn-page.ui',
+ 'wifi-page.ui'
+)
+
+c_name = 'net-' + name
+
+sources += gnome.compile_resources(
+ c_name + '-resources',
+ name + '.gresource.xml',
+ c_name: c_name.underscorify(),
+ dependencies: resource_data,
+ export: true
+)
+
+libconnection_editor = static_library(
+ name,
+ sources: sources,
+ include_directories: [top_inc, common_inc, network_inc, wireless_security_inc],
+ dependencies: deps,
+ c_args: cflags,
+ link_with: libwireless_security
+)
diff --git a/panels/network/connection-editor/net-connection-editor.c b/panels/network/connection-editor/net-connection-editor.c
new file mode 100644
index 0000000..b231caf
--- /dev/null
+++ b/panels/network/connection-editor/net-connection-editor.c
@@ -0,0 +1,868 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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 <NetworkManager.h>
+
+#include "list-box-helper.h"
+#include "net-connection-editor.h"
+#include "net-connection-editor-resources.h"
+#include "ce-page.h"
+#include "ce-page-details.h"
+#include "ce-page-wifi.h"
+#include "ce-page-ip4.h"
+#include "ce-page-ip6.h"
+#include "ce-page-security.h"
+#include "ce-page-ethernet.h"
+#include "ce-page-8021x-security.h"
+#include "ce-page-vpn.h"
+#include "vpn-helpers.h"
+#include "eap-method.h"
+
+enum {
+ DONE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _NetConnectionEditor
+{
+ GtkDialog parent;
+
+ GtkBox *add_connection_box;
+ GtkFrame *add_connection_frame;
+ GtkButton *apply_button;
+ GtkButton *cancel_button;
+ GtkNotebook *notebook;
+ GtkStack *toplevel_stack;
+
+ GtkWidget *parent_window;
+ NMClient *client;
+ NMDevice *device;
+
+ NMConnection *connection;
+ NMConnection *orig_connection;
+ gboolean is_new_connection;
+ gboolean is_changed;
+ NMAccessPoint *ap;
+
+ GSList *initializing_pages;
+ GSList *pages;
+
+ NMClientPermissionResult can_modify;
+
+ gboolean title_set;
+ gboolean show_when_initialized;
+};
+
+G_DEFINE_TYPE (NetConnectionEditor, net_connection_editor, GTK_TYPE_DIALOG)
+
+static void page_changed (NetConnectionEditor *self);
+
+static void
+cancel_editing (NetConnectionEditor *self)
+{
+ gtk_widget_hide (GTK_WIDGET (self));
+ g_signal_emit (self, signals[DONE], 0, FALSE);
+}
+
+static void
+delete_event_cb (NetConnectionEditor *self)
+{
+ cancel_editing (self);
+}
+
+static void
+cancel_clicked_cb (NetConnectionEditor *self)
+{
+ cancel_editing (self);
+}
+
+static void
+update_connection (NetConnectionEditor *self)
+{
+ g_autoptr(GVariant) settings = NULL;
+
+ settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL);
+ nm_connection_replace_settings (self->orig_connection, settings, NULL);
+}
+
+static void
+update_complete (NetConnectionEditor *self,
+ gboolean success)
+{
+ gtk_widget_hide (GTK_WIDGET (self));
+ g_signal_emit (self, signals[DONE], 0, success);
+}
+
+static void
+updated_connection_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NetConnectionEditor *self;
+ g_autoptr(GError) error = NULL;
+ gboolean success = TRUE;
+
+ if (!nm_remote_connection_commit_changes_finish (NM_REMOTE_CONNECTION (source_object),
+ res, &error)) {
+ g_warning ("Failed to commit changes: %s", error->message);
+ success = FALSE;
+ //return; FIXME return if cancelled
+ }
+
+ nm_connection_clear_secrets (NM_CONNECTION (source_object));
+
+ self = user_data;
+ update_complete (self, success);
+}
+
+static void
+added_connection_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NetConnectionEditor *self;
+ g_autoptr(GError) error = NULL;
+ gboolean success = TRUE;
+
+ if (!nm_client_add_connection_finish (NM_CLIENT (source_object), res, &error)) {
+ g_warning ("Failed to add connection: %s", error->message);
+ success = FALSE;
+ /* Leave the editor open */
+ // return; FIXME return if cancelled
+ }
+
+ self = user_data;
+ update_complete (self, success);
+}
+
+static void
+apply_clicked_cb (NetConnectionEditor *self)
+{
+ update_connection (self);
+
+ eap_method_ca_cert_ignore_save (self->connection);
+
+ if (self->is_new_connection) {
+ nm_client_add_connection_async (self->client,
+ self->orig_connection,
+ TRUE,
+ NULL,
+ added_connection_cb,
+ self);
+ } else {
+ nm_remote_connection_commit_changes_async (NM_REMOTE_CONNECTION (self->orig_connection),
+ TRUE,
+ NULL,
+ updated_connection_cb, self);
+ }
+}
+
+static void
+net_connection_editor_init (NetConnectionEditor *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+net_connection_editor_finalize (GObject *object)
+{
+ NetConnectionEditor *self = NET_CONNECTION_EDITOR (object);
+ GSList *l;
+
+ for (l = self->pages; l != NULL; l = l->next)
+ g_signal_handlers_disconnect_by_func (l->data, page_changed, self);
+
+ g_clear_object (&self->connection);
+ g_clear_object (&self->orig_connection);
+ g_clear_object (&self->parent_window);
+ g_clear_object (&self->device);
+ g_clear_object (&self->client);
+ g_clear_object (&self->ap);
+
+ G_OBJECT_CLASS (net_connection_editor_parent_class)->finalize (object);
+}
+
+static void
+net_connection_editor_class_init (NetConnectionEditorClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ g_resources_register (net_connection_editor_get_resource ());
+
+ object_class->finalize = net_connection_editor_finalize;
+
+ signals[DONE] = g_signal_new ("done",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/connection-editor.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_box);
+ gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_frame);
+ gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, apply_button);
+ gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, notebook);
+ gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, toplevel_stack);
+
+ gtk_widget_class_bind_template_callback (widget_class, delete_event_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cancel_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, apply_clicked_cb);
+}
+
+static void
+net_connection_editor_error_dialog (NetConnectionEditor *self,
+ const char *primary_text,
+ const char *secondary_text)
+{
+ GtkWidget *dialog;
+ GtkWindow *parent;
+
+ if (gtk_widget_is_visible (GTK_WIDGET (self)))
+ parent = GTK_WINDOW (self);
+ else
+ parent = GTK_WINDOW (self->parent_window);
+
+ dialog = gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", primary_text);
+
+ if (secondary_text) {
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", secondary_text);
+ }
+
+ g_signal_connect (dialog, "delete-event", G_CALLBACK (gtk_widget_destroy), NULL);
+ g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+}
+
+static void
+net_connection_editor_do_fallback (NetConnectionEditor *self, const gchar *type)
+{
+ g_autofree gchar *cmdline = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (self->is_new_connection) {
+ cmdline = g_strdup_printf ("nm-connection-editor --type='%s' --create", type);
+ } else {
+ cmdline = g_strdup_printf ("nm-connection-editor --edit='%s'",
+ nm_connection_get_uuid (self->connection));
+ }
+
+ g_spawn_command_line_async (cmdline, &error);
+
+ if (error)
+ net_connection_editor_error_dialog (self,
+ _("Unable to open connection editor"),
+ error->message);
+
+ g_signal_emit (self, signals[DONE], 0, FALSE);
+}
+
+static void
+net_connection_editor_update_title (NetConnectionEditor *self)
+{
+ g_autofree gchar *id = NULL;
+
+ if (self->title_set)
+ return;
+
+ if (self->is_new_connection) {
+ if (self->device) {
+ id = g_strdup (_("New Profile"));
+ } else {
+ /* Leave it set to "Add New Connection" */
+ return;
+ }
+ } else {
+ NMSettingWireless *sw;
+ sw = nm_connection_get_setting_wireless (self->connection);
+ if (sw) {
+ GBytes *ssid;
+ ssid = nm_setting_wireless_get_ssid (sw);
+ id = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+ } else {
+ id = g_strdup (nm_connection_get_id (self->connection));
+ }
+ }
+ gtk_window_set_title (GTK_WINDOW (self), id);
+}
+
+static gboolean
+editor_is_initialized (NetConnectionEditor *self)
+{
+ return self->initializing_pages == NULL;
+}
+
+static void
+update_sensitivity (NetConnectionEditor *self)
+{
+ NMSettingConnection *sc;
+ gboolean sensitive;
+ GSList *l;
+
+ if (!editor_is_initialized (self))
+ return;
+
+ sc = nm_connection_get_setting_connection (self->connection);
+
+ if (nm_setting_connection_get_read_only (sc)) {
+ sensitive = FALSE;
+ } else {
+ sensitive = self->can_modify;
+ }
+
+ for (l = self->pages; l; l = l->next)
+ gtk_widget_set_sensitive (GTK_WIDGET (l->data), sensitive);
+}
+
+static void
+validate (NetConnectionEditor *self)
+{
+ gboolean valid = FALSE;
+ GSList *l;
+
+ if (!editor_is_initialized (self))
+ goto done;
+
+ valid = TRUE;
+ for (l = self->pages; l; l = l->next) {
+ g_autoptr(GError) error = NULL;
+
+ if (!ce_page_validate (CE_PAGE (l->data), self->connection, &error)) {
+ valid = FALSE;
+ if (error) {
+ g_debug ("Invalid setting %s: %s", ce_page_get_title (CE_PAGE (l->data)), error->message);
+ } else {
+ g_debug ("Invalid setting %s", ce_page_get_title (CE_PAGE (l->data)));
+ }
+ }
+ }
+
+ update_sensitivity (self);
+done:
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), valid && self->is_changed);
+}
+
+static void
+page_changed (NetConnectionEditor *self)
+{
+ if (editor_is_initialized (self))
+ self->is_changed = TRUE;
+ validate (self);
+}
+
+static gboolean
+idle_validate (gpointer user_data)
+{
+ validate (NET_CONNECTION_EDITOR (user_data));
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+recheck_initialization (NetConnectionEditor *self)
+{
+ if (!editor_is_initialized (self))
+ return;
+
+ gtk_notebook_set_current_page (self->notebook, 0);
+
+ if (self->show_when_initialized)
+ gtk_window_present (GTK_WINDOW (self));
+
+ g_idle_add (idle_validate, self);
+}
+
+static void
+page_initialized (NetConnectionEditor *self, GError *error, CEPage *page)
+{
+ GtkWidget *label;
+ gint position;
+ GList *children, *l;
+ gint i;
+
+ position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position"));
+ g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position));
+ children = gtk_container_get_children (GTK_CONTAINER (self->notebook));
+ for (l = children, i = 0; l; l = l->next, i++) {
+ gint pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (l->data), "position"));
+ if (pos > position)
+ break;
+ }
+ g_list_free (children);
+
+ label = gtk_label_new (ce_page_get_title (page));
+
+ gtk_notebook_insert_page (self->notebook, GTK_WIDGET (page), label, i);
+
+ self->initializing_pages = g_slist_remove (self->initializing_pages, page);
+ self->pages = g_slist_append (self->pages, page);
+
+ recheck_initialization (self);
+}
+
+typedef struct {
+ NetConnectionEditor *editor;
+ CEPage *page;
+ const gchar *setting_name;
+} GetSecretsInfo;
+
+static void
+get_secrets_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NMRemoteConnection *connection;
+ g_autofree GetSecretsInfo *info = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) variant = NULL;
+
+ connection = NM_REMOTE_CONNECTION (source_object);
+ variant = nm_remote_connection_get_secrets_finish (connection, res, &error);
+
+ if (!variant && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ ce_page_complete_init (info->page, info->editor->connection, info->setting_name, variant, g_steal_pointer (&error));
+}
+
+static void
+get_secrets_for_page (NetConnectionEditor *self,
+ CEPage *page,
+ const gchar *setting_name)
+{
+ GetSecretsInfo *info;
+
+ info = g_new0 (GetSecretsInfo, 1);
+ info->editor = self;
+ info->page = page;
+ info->setting_name = setting_name;
+
+ nm_remote_connection_get_secrets_async (NM_REMOTE_CONNECTION (self->orig_connection),
+ setting_name,
+ NULL, //FIXME
+ get_secrets_cb,
+ info);
+}
+
+static void
+add_page (NetConnectionEditor *self, CEPage *page)
+{
+ gint position;
+
+ position = g_slist_length (self->initializing_pages);
+ g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position));
+
+ self->initializing_pages = g_slist_append (self->initializing_pages, page);
+
+ g_signal_connect_object (page, "changed", G_CALLBACK (page_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (page, "initialized", G_CALLBACK (page_initialized), self, G_CONNECT_SWAPPED);
+}
+
+static void
+net_connection_editor_set_connection (NetConnectionEditor *self,
+ NMConnection *connection)
+{
+ GSList *pages, *l;
+ NMSettingConnection *sc;
+ const gchar *type;
+ gboolean is_wired;
+ gboolean is_wifi;
+ gboolean is_vpn;
+
+ self->is_new_connection = !nm_client_get_connection_by_uuid (self->client,
+ nm_connection_get_uuid (connection));
+
+ if (self->is_new_connection) {
+ gtk_button_set_label (self->apply_button, _("_Add"));
+ self->is_changed = TRUE;
+ }
+
+ self->connection = nm_simple_connection_new_clone (connection);
+ self->orig_connection = g_object_ref (connection);
+
+ net_connection_editor_update_title (self);
+
+ eap_method_ca_cert_ignore_load (self->connection);
+
+ sc = nm_connection_get_setting_connection (connection);
+ type = nm_setting_connection_get_connection_type (sc);
+
+ is_wired = g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME);
+ is_wifi = g_str_equal (type, NM_SETTING_WIRELESS_SETTING_NAME);
+ is_vpn = g_str_equal (type, NM_SETTING_VPN_SETTING_NAME);
+
+ if (!self->is_new_connection)
+ add_page (self, CE_PAGE (ce_page_details_new (self->connection, self->device, self->ap, self)));
+
+ if (is_wifi)
+ add_page (self, CE_PAGE (ce_page_wifi_new (self->connection, self->client)));
+ else if (is_wired)
+ add_page (self, CE_PAGE (ce_page_ethernet_new (self->connection, self->client)));
+ else if (is_vpn)
+ add_page (self, CE_PAGE (ce_page_vpn_new (self->connection)));
+ else {
+ /* Unsupported type */
+ net_connection_editor_do_fallback (self, type);
+ return;
+ }
+
+ add_page (self, CE_PAGE (ce_page_ip4_new (self->connection, self->client)));
+ add_page (self, CE_PAGE (ce_page_ip6_new (self->connection, self->client)));
+
+ if (is_wifi)
+ add_page (self, CE_PAGE (ce_page_security_new (self->connection)));
+ else if (is_wired)
+ add_page (self, CE_PAGE (ce_page_8021x_security_new (self->connection)));
+
+ pages = g_slist_copy (self->initializing_pages);
+ for (l = pages; l; l = l->next) {
+ CEPage *page = l->data;
+ const gchar *security_setting;
+
+ security_setting = ce_page_get_security_setting (page);
+ if (!security_setting || self->is_new_connection) {
+ ce_page_complete_init (page, NULL, NULL, NULL, NULL);
+ } else {
+ get_secrets_for_page (self, page, security_setting);
+ }
+ }
+ g_slist_free (pages);
+}
+
+static NMConnection *
+complete_vpn_connection (NetConnectionEditor *self, NMConnection *connection)
+{
+ NMSettingConnection *s_con;
+ NMSetting *s_type;
+
+ if (!connection)
+ connection = nm_simple_connection_new ();
+
+ s_con = nm_connection_get_setting_connection (connection);
+ if (!s_con) {
+ s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+ nm_connection_add_setting (connection, NM_SETTING (s_con));
+ }
+
+ if (!nm_setting_connection_get_uuid (s_con)) {
+ g_autofree gchar *uuid = nm_utils_uuid_generate ();
+ g_object_set (s_con,
+ NM_SETTING_CONNECTION_UUID, uuid,
+ NULL);
+ }
+
+ if (!nm_setting_connection_get_id (s_con)) {
+ const GPtrArray *connections;
+ g_autofree gchar *id = NULL;
+
+ connections = nm_client_get_connections (self->client);
+ id = ce_page_get_next_available_name (connections, NAME_FORMAT_TYPE, _("VPN"));
+ g_object_set (s_con,
+ NM_SETTING_CONNECTION_ID, id,
+ NULL);
+ }
+
+ s_type = nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN);
+ if (!s_type) {
+ s_type = g_object_new (NM_TYPE_SETTING_VPN, NULL);
+ nm_connection_add_setting (connection, s_type);
+ }
+
+ if (!nm_setting_connection_get_connection_type (s_con)) {
+ g_object_set (s_con,
+ NM_SETTING_CONNECTION_TYPE, nm_setting_get_name (s_type),
+ NULL);
+ }
+
+ return connection;
+}
+
+static void
+finish_add_connection (NetConnectionEditor *self, NMConnection *connection)
+{
+ GtkBin *frame;
+
+ frame = GTK_BIN (self->add_connection_frame);
+ gtk_widget_destroy (gtk_bin_get_child (frame));
+
+ gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->notebook));
+ gtk_widget_show (GTK_WIDGET (self->apply_button));
+
+ if (connection)
+ net_connection_editor_set_connection (self, connection);
+}
+
+static void
+vpn_import_complete (NMConnection *connection, gpointer user_data)
+{
+ NetConnectionEditor *self = user_data;
+
+ if (!connection) {
+ /* The import code shows its own error dialogs. */
+ g_signal_emit (self, signals[DONE], 0, FALSE);
+ return;
+ }
+
+ complete_vpn_connection (self, connection);
+ finish_add_connection (self, connection);
+}
+
+static void
+vpn_type_activated (NetConnectionEditor *self, GtkWidget *row)
+{
+ const char *service_name = g_object_get_data (G_OBJECT (row), "service_name");
+ NMConnection *connection;
+ NMSettingVpn *s_vpn;
+ NMSettingConnection *s_con;
+
+ if (!strcmp (service_name, "import")) {
+ vpn_import (GTK_WINDOW (self), vpn_import_complete, self);
+ return;
+ }
+
+ connection = complete_vpn_connection (self, NULL);
+ s_vpn = nm_connection_get_setting_vpn (connection);
+ g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, service_name, NULL);
+
+ /* Mark the connection as private to this user, and non-autoconnect */
+ s_con = nm_connection_get_setting_connection (connection);
+ g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL);
+ nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL);
+
+ finish_add_connection (self, connection);
+}
+
+static void
+select_vpn_type (NetConnectionEditor *self, GtkListBox *list)
+{
+ GSList *vpn_plugins, *iter;
+ GList *l;
+ GList *children;
+ GtkWidget *row, *row_box;
+ GtkWidget *name_label, *desc_label;
+
+ /* Get the available VPN types */
+ vpn_plugins = vpn_get_plugins ();
+
+ /* Remove the previous menu contents */
+ children = gtk_container_get_children (GTK_CONTAINER (list));
+ for (l = children; l != NULL; l = l->next)
+ gtk_widget_destroy (l->data);
+
+ /* Add the VPN types */
+ for (iter = vpn_plugins; iter; iter = iter->next) {
+ NMVpnEditorPlugin *plugin = nm_vpn_plugin_info_get_editor_plugin (iter->data);
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *desc = NULL;
+ g_autofree gchar *desc_markup = NULL;
+ g_autofree gchar *service_name = NULL;
+ GtkStyleContext *context;
+
+ g_object_get (plugin,
+ NM_VPN_EDITOR_PLUGIN_NAME, &name,
+ NM_VPN_EDITOR_PLUGIN_DESCRIPTION, &desc,
+ NM_VPN_EDITOR_PLUGIN_SERVICE, &service_name,
+ NULL);
+ desc_markup = g_markup_printf_escaped ("<span size='smaller'>%s</span>", desc);
+
+ row = gtk_list_box_row_new ();
+
+ row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_set_margin_start (row_box, 12);
+ gtk_widget_set_margin_end (row_box, 12);
+ gtk_widget_set_margin_top (row_box, 12);
+ gtk_widget_set_margin_bottom (row_box, 12);
+
+ name_label = gtk_label_new (name);
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_box_pack_start (GTK_BOX (row_box), name_label, FALSE, TRUE, 0);
+
+ desc_label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (desc_label), desc_markup);
+ gtk_label_set_line_wrap (GTK_LABEL (desc_label), TRUE);
+ gtk_widget_set_halign (desc_label, GTK_ALIGN_START);
+ context = gtk_widget_get_style_context (desc_label);
+ gtk_style_context_add_class (context, "dim-label");
+ gtk_box_pack_start (GTK_BOX (row_box), desc_label, FALSE, TRUE, 0);
+
+ gtk_container_add (GTK_CONTAINER (row), row_box);
+ gtk_widget_show_all (row);
+ g_object_set_data_full (G_OBJECT (row), "service_name", g_steal_pointer (&service_name), g_free);
+ gtk_container_add (GTK_CONTAINER (list), row);
+ }
+
+ /* Import */
+ row = gtk_list_box_row_new ();
+
+ row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_set_margin_start (row_box, 12);
+ gtk_widget_set_margin_end (row_box, 12);
+ gtk_widget_set_margin_top (row_box, 12);
+ gtk_widget_set_margin_bottom (row_box, 12);
+
+ name_label = gtk_label_new (_("Import from file…"));
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_box_pack_start (GTK_BOX (row_box), name_label, FALSE, TRUE, 0);
+
+ gtk_container_add (GTK_CONTAINER (row), row_box);
+ gtk_widget_show_all (row);
+ g_object_set_data (G_OBJECT (row), "service_name", "import");
+ gtk_container_add (GTK_CONTAINER (list), row);
+
+ g_signal_connect_object (list, "row-activated",
+ G_CALLBACK (vpn_type_activated), self, G_CONNECT_SWAPPED);
+}
+
+static void
+net_connection_editor_add_connection (NetConnectionEditor *self)
+{
+ GtkContainer *frame;
+ GtkListBox *list;
+
+ frame = GTK_CONTAINER (self->add_connection_frame);
+
+ list = GTK_LIST_BOX (gtk_list_box_new ());
+ gtk_list_box_set_selection_mode (list, GTK_SELECTION_NONE);
+ gtk_list_box_set_header_func (list, cc_list_box_update_header_func, NULL, NULL);
+
+ select_vpn_type (self, list);
+
+ gtk_widget_show_all (GTK_WIDGET (list));
+ gtk_container_add (frame, GTK_WIDGET (list));
+
+ gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->add_connection_box));
+ gtk_widget_hide (GTK_WIDGET (self->apply_button));
+ gtk_window_set_title (GTK_WINDOW (self), _("Add VPN"));
+}
+
+static void
+permission_changed (NetConnectionEditor *self,
+ NMClientPermission permission,
+ NMClientPermissionResult result)
+{
+ if (permission != NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM)
+ return;
+
+ if (result == NM_CLIENT_PERMISSION_RESULT_YES || result == NM_CLIENT_PERMISSION_RESULT_AUTH)
+ self->can_modify = TRUE;
+ else
+ self->can_modify = FALSE;
+
+ validate (self);
+}
+
+NetConnectionEditor *
+net_connection_editor_new (GtkWindow *parent_window,
+ NMConnection *connection,
+ NMDevice *device,
+ NMAccessPoint *ap,
+ NMClient *client)
+{
+ NetConnectionEditor *self;
+
+ self = g_object_new (net_connection_editor_get_type (),
+ /* This doesn't seem to work for a template, so it is also hardcoded. */
+ "use-header-bar", 1,
+ NULL);
+
+ if (parent_window) {
+ self->parent_window = GTK_WIDGET (g_object_ref (parent_window));
+ gtk_window_set_transient_for (GTK_WINDOW (self),
+ parent_window);
+ }
+ if (ap)
+ self->ap = g_object_ref (ap);
+ if (device)
+ self->device = g_object_ref (device);
+ self->client = g_object_ref (client);
+
+ self->can_modify = nm_client_get_permission_result (client, NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM);
+ g_signal_connect_object (self->client, "permission-changed",
+ G_CALLBACK (permission_changed), self, G_CONNECT_SWAPPED);
+
+ if (connection)
+ net_connection_editor_set_connection (self, connection);
+ else
+ net_connection_editor_add_connection (self);
+
+ return self;
+}
+
+void
+net_connection_editor_run (NetConnectionEditor *self)
+{
+ if (!editor_is_initialized (self)) {
+ self->show_when_initialized = TRUE;
+ return;
+ }
+ gtk_window_present (GTK_WINDOW (self));
+}
+
+static void
+forgotten_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NMRemoteConnection *connection = NM_REMOTE_CONNECTION (source_object);
+ NetConnectionEditor *self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!nm_remote_connection_delete_finish (connection, res, &error)) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to delete connection %s: %s",
+ nm_connection_get_id (NM_CONNECTION (connection)),
+ error->message);
+ return;
+ }
+
+ cancel_editing (self);
+}
+
+void
+net_connection_editor_forget (NetConnectionEditor *self)
+{
+ nm_remote_connection_delete_async (NM_REMOTE_CONNECTION (self->orig_connection),
+ NULL, forgotten_cb, self);
+}
+
+void
+net_connection_editor_set_title (NetConnectionEditor *self,
+ const gchar *title)
+{
+ gtk_window_set_title (GTK_WINDOW (self), title);
+ self->title_set = TRUE;
+}
diff --git a/panels/network/connection-editor/net-connection-editor.h b/panels/network/connection-editor/net-connection-editor.h
new file mode 100644
index 0000000..ba4bf34
--- /dev/null
+++ b/panels/network/connection-editor/net-connection-editor.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (NetConnectionEditor, net_connection_editor, NET, CONNECTION_EDITOR, GtkDialog)
+
+NetConnectionEditor *net_connection_editor_new (GtkWindow *parent_window,
+ NMConnection *connection,
+ NMDevice *device,
+ NMAccessPoint *ap,
+ NMClient *client);
+void net_connection_editor_set_title (NetConnectionEditor *editor,
+ const gchar *title);
+void net_connection_editor_run (NetConnectionEditor *editor);
+void net_connection_editor_forget (NetConnectionEditor *editor);
+
+G_END_DECLS
+
diff --git a/panels/network/connection-editor/security-page.ui b/panels/network/connection-editor/security-page.ui
new file mode 100644
index 0000000..f35c250
--- /dev/null
+++ b/panels/network/connection-editor/security-page.ui
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageSecurity" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">50</property>
+ <property name="margin_end">50</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="security_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">S_ecurity</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">security_combo</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="security_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/vpn-helpers.c b/panels/network/connection-editor/vpn-helpers.c
new file mode 100644
index 0000000..8ef486b
--- /dev/null
+++ b/panels/network/connection-editor/vpn-helpers.c
@@ -0,0 +1,318 @@
+/* -*- 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 <NetworkManager.h>
+
+#include "vpn-helpers.h"
+
+NMVpnEditorPlugin *
+vpn_get_plugin_by_service (const char *service)
+{
+ NMVpnPluginInfo *plugin_info;
+
+ g_return_val_if_fail (service != NULL, NULL);
+
+ plugin_info = nm_vpn_plugin_info_list_find_by_service (vpn_get_plugins (), service);
+ if (plugin_info)
+ return nm_vpn_plugin_info_get_editor_plugin (plugin_info);
+ return NULL;
+}
+
+static gint
+_sort_vpn_plugins (NMVpnPluginInfo *aa, NMVpnPluginInfo *bb)
+{
+ return strcmp (nm_vpn_plugin_info_get_name (aa), nm_vpn_plugin_info_get_name (bb));
+}
+
+GSList *
+vpn_get_plugins (void)
+{
+ static gboolean plugins_loaded = FALSE;
+ static GSList *plugins = NULL;
+ GSList *p;
+
+ if (G_LIKELY (plugins_loaded))
+ return plugins;
+ plugins_loaded = TRUE;
+
+ p = nm_vpn_plugin_info_list_load ();
+ plugins = NULL;
+ while (p) {
+ g_autoptr(NMVpnPluginInfo) plugin_info = NM_VPN_PLUGIN_INFO (p->data);
+ g_autoptr(GError) error = NULL;
+
+ /* load the editor plugin, and preserve only those NMVpnPluginInfo that can
+ * successfully load the plugin. */
+ if (nm_vpn_plugin_info_load_editor_plugin (plugin_info, &error))
+ plugins = g_slist_prepend (plugins, g_steal_pointer (&plugin_info));
+ else {
+ if ( !nm_vpn_plugin_info_get_plugin (plugin_info)
+ && nm_vpn_plugin_info_lookup_property (plugin_info, NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "properties")) {
+ g_message ("vpn: (%s,%s) cannot load legacy-only plugin",
+ nm_vpn_plugin_info_get_name (plugin_info),
+ nm_vpn_plugin_info_get_filename (plugin_info));
+ } else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
+ g_message ("vpn: (%s,%s) file \"%s\" not found. Did you install the client package?",
+ nm_vpn_plugin_info_get_name (plugin_info),
+ nm_vpn_plugin_info_get_filename (plugin_info),
+ nm_vpn_plugin_info_get_plugin (plugin_info));
+ } else {
+ g_warning ("vpn: (%s,%s) could not load plugin: %s",
+ nm_vpn_plugin_info_get_name (plugin_info),
+ nm_vpn_plugin_info_get_filename (plugin_info),
+ error->message);
+ }
+ }
+ p = g_slist_delete_link (p, p);
+ }
+
+ /* sort the list of plugins alphabetically. */
+ plugins = g_slist_sort (plugins, (GCompareFunc) _sort_vpn_plugins);
+ return plugins;
+}
+
+typedef struct {
+ VpnImportCallback callback;
+ gpointer user_data;
+} ActionInfo;
+
+static void
+import_vpn_from_file_cb (GtkWidget *dialog, gint response, gpointer user_data)
+{
+ g_autofree gchar *filename = NULL;
+ ActionInfo *info = (ActionInfo *) user_data;
+ NMConnection *connection = NULL;
+ g_autoptr(GError) error = NULL;
+ GSList *iter;
+
+ 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;
+ }
+
+ for (iter = vpn_get_plugins (); !connection && iter; iter = iter->next) {
+ NMVpnEditorPlugin *plugin;
+
+ plugin = nm_vpn_plugin_info_get_editor_plugin (iter->data);
+ g_clear_error (&error);
+ connection = nm_vpn_editor_plugin_import (plugin, filename, &error);
+ }
+
+ if (!connection) {
+ GtkWidget *err_dialog;
+ g_autofree gchar *bname = g_path_get_basename (filename);
+
+ err_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
+ GTK_DIALOG_MODAL | 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_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_dialog_run (GTK_DIALOG (err_dialog));
+ }
+
+out:
+ gtk_widget_hide (dialog);
+ gtk_widget_destroy (dialog);
+
+ info->callback (connection, info->user_data);
+ g_free (info);
+}
+
+static void
+destroy_import_chooser (GtkWidget *dialog, gpointer user_data)
+{
+ ActionInfo *info = (ActionInfo *) user_data;
+
+ gtk_widget_destroy (dialog);
+ info->callback (NULL, info->user_data);
+ g_free (info);
+}
+
+void
+vpn_import (GtkWindow *parent, VpnImportCallback callback, gpointer user_data)
+{
+ GtkWidget *dialog;
+ ActionInfo *info;
+ const char *home_folder;
+
+ dialog = gtk_file_chooser_dialog_new (_("Select file to import"),
+ parent,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ 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)
+{
+ g_autoptr(NMConnection) connection = NM_CONNECTION (user_data);
+ char *filename = NULL;
+ g_autoptr(GError) error = NULL;
+ NMVpnEditorPlugin *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;
+ g_autofree gchar *bname = NULL;
+
+ 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);
+ 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_editor_plugin_export (plugin, filename, connection, &error);
+
+done:
+ if (!success) {
+ GtkWidget *err_dialog;
+ g_autofree gchar *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_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:
+ gtk_widget_hide (dialog);
+ gtk_widget_destroy (dialog);
+}
+
+void
+vpn_export (NMConnection *connection)
+{
+ GtkWidget *dialog;
+ NMVpnEditorPlugin *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,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_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) {
+ g_autofree gchar *suggested = NULL;
+
+ suggested = nm_vpn_editor_plugin_get_suggested_filename (plugin, connection);
+ if (suggested)
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), 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));
+}
diff --git a/panels/network/connection-editor/vpn-helpers.h b/panels/network/connection-editor/vpn-helpers.h
new file mode 100644
index 0000000..578f68c
--- /dev/null
+++ b/panels/network/connection-editor/vpn-helpers.h
@@ -0,0 +1,39 @@
+/* -*- 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 <NetworkManager.h>
+
+GSList *vpn_get_plugins (void);
+
+NMVpnEditorPlugin *vpn_get_plugin_by_service (const char *service);
+
+typedef void (*VpnImportCallback) (NMConnection *connection, gpointer user_data);
+void vpn_import (GtkWindow *parent, VpnImportCallback callback, gpointer user_data);
+
+void vpn_export (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..682e7a7
--- /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 -->
+ <template class="CEPageVpn" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">50</property>
+ <property name="margin_end">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">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <property name="mnemonic_widget">name_entry</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="name_entry">
+ <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">3</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/wifi-page.ui b/panels/network/connection-editor/wifi-page.ui
new file mode 100644
index 0000000..e156780
--- /dev/null
+++ b/panels/network/connection-editor/wifi-page.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageWifi" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">50</property>
+ <property name="margin_end">50</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_SSID</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">ssid_entry</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_BSSID</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">bssid_combo</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="ssid_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_MAC Address</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">mac_combo</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="cloned_mac_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_entry">True</property>
+ <property name="hexpand">True</property>
+ <property name="active_id">0</property>
+ <child internal-child="entry">
+ <object class="GtkEntry">
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Cloned Address</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">cloned_mac_combo</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="bssid_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="mac_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>