summaryrefslogtreecommitdiffstats
path: root/panels/network
diff options
context:
space:
mode:
Diffstat (limited to 'panels/network')
-rw-r--r--panels/network/cc-network-panel.c806
-rw-r--r--panels/network/cc-network-panel.h30
-rw-r--r--panels/network/cc-network-panel.ui94
-rw-r--r--panels/network/cc-qr-code.c186
-rw-r--r--panels/network/cc-qr-code.h43
-rw-r--r--panels/network/cc-wifi-connection-list.c799
-rw-r--r--panels/network/cc-wifi-connection-list.h41
-rw-r--r--panels/network/cc-wifi-connection-row.c678
-rw-r--r--panels/network/cc-wifi-connection-row.h54
-rw-r--r--panels/network/cc-wifi-connection-row.ui73
-rw-r--r--panels/network/cc-wifi-hotspot-dialog.c553
-rw-r--r--panels/network/cc-wifi-hotspot-dialog.h44
-rw-r--r--panels/network/cc-wifi-hotspot-dialog.ui140
-rw-r--r--panels/network/cc-wifi-panel.c1079
-rw-r--r--panels/network/cc-wifi-panel.h32
-rw-r--r--panels/network/cc-wifi-panel.ui326
-rw-r--r--panels/network/connection-editor/8021x-security-page.ui45
-rw-r--r--panels/network/connection-editor/ce-ip-address-entry.c99
-rw-r--r--panels/network/connection-editor/ce-ip-address-entry.h36
-rw-r--r--panels/network/connection-editor/ce-netmask-entry.c137
-rw-r--r--panels/network/connection-editor/ce-netmask-entry.h38
-rw-r--r--panels/network/connection-editor/ce-page-8021x-security.c202
-rw-r--r--panels/network/connection-editor/ce-page-8021x-security.h34
-rw-r--r--panels/network/connection-editor/ce-page-details.c571
-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.c846
-rw-r--r--panels/network/connection-editor/ce-page-ip4.h34
-rw-r--r--panels/network/connection-editor/ce-page-ip6.c817
-rw-r--r--panels/network/connection-editor/ce-page-ip6.h34
-rw-r--r--panels/network/connection-editor/ce-page-security.c553
-rw-r--r--panels/network/connection-editor/ce-page-security.h33
-rw-r--r--panels/network/connection-editor/ce-page-vpn.c227
-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.ui75
-rw-r--r--panels/network/connection-editor/details-page.ui458
-rw-r--r--panels/network/connection-editor/ethernet-page.ui121
-rw-r--r--panels/network/connection-editor/ip4-page.ui339
-rw-r--r--panels/network/connection-editor/ip6-page.ui349
-rw-r--r--panels/network/connection-editor/meson.build48
-rw-r--r--panels/network/connection-editor/net-connection-editor.c830
-rw-r--r--panels/network/connection-editor/net-connection-editor.h40
-rw-r--r--panels/network/connection-editor/security-page.ui45
-rw-r--r--panels/network/connection-editor/vpn-helpers.c358
-rw-r--r--panels/network/connection-editor/vpn-helpers.h39
-rw-r--r--panels/network/connection-editor/vpn-page.ui41
-rw-r--r--panels/network/connection-editor/wifi-page.ui109
-rw-r--r--panels/network/gnome-network-panel.desktop.in.in18
-rw-r--r--panels/network/gnome-wifi-panel.desktop.in.in18
-rw-r--r--panels/network/icons/meson.build4
-rw-r--r--panels/network/icons/scalable/org.gnome.Settings-network-symbolic.svg7
-rw-r--r--panels/network/lock-small-symbolic.svg1
-rw-r--r--panels/network/meson.build85
-rw-r--r--panels/network/net-device-bluetooth.c201
-rw-r--r--panels/network/net-device-bluetooth.h38
-rw-r--r--panels/network/net-device-ethernet.c540
-rw-r--r--panels/network/net-device-ethernet.h37
-rw-r--r--panels/network/net-device-mobile.c912
-rw-r--r--panels/network/net-device-mobile.h40
-rw-r--r--panels/network/net-device-wifi.c1303
-rw-r--r--panels/network/net-device-wifi.h47
-rw-r--r--panels/network/net-proxy.c372
-rw-r--r--panels/network/net-proxy.h32
-rw-r--r--panels/network/net-vpn.c228
-rw-r--r--panels/network/net-vpn.h37
-rw-r--r--panels/network/network-bluetooth.ui27
-rw-r--r--panels/network/network-dialogs.c499
-rw-r--r--panels/network/network-dialogs.h38
-rw-r--r--panels/network/network-ethernet.ui70
-rw-r--r--panels/network/network-mobile.ui334
-rw-r--r--panels/network/network-proxy.ui404
-rw-r--r--panels/network/network-vpn.ui29
-rw-r--r--panels/network/network-wifi.ui153
-rw-r--r--panels/network/network.gresource.xml24
-rw-r--r--panels/network/panel-common.c449
-rw-r--r--panels/network/panel-common.h39
-rw-r--r--panels/network/qrcodegen.c1009
-rw-r--r--panels/network/qrcodegen.h311
-rw-r--r--panels/network/ui-helpers.c38
-rw-r--r--panels/network/ui-helpers.h27
-rw-r--r--panels/network/warning-small-symbolic.svg1
-rw-r--r--panels/network/wifi-panel.css5
-rw-r--r--panels/network/wireless-security/eap-method-fast.c399
-rw-r--r--panels/network/wireless-security/eap-method-fast.h34
-rw-r--r--panels/network/wireless-security/eap-method-fast.ui151
-rw-r--r--panels/network/wireless-security/eap-method-leap.c266
-rw-r--r--panels/network/wireless-security/eap-method-leap.h34
-rw-r--r--panels/network/wireless-security/eap-method-leap.ui63
-rw-r--r--panels/network/wireless-security/eap-method-peap.c400
-rw-r--r--panels/network/wireless-security/eap-method-peap.h34
-rw-r--r--panels/network/wireless-security/eap-method-peap.ui167
-rw-r--r--panels/network/wireless-security/eap-method-simple.c356
-rw-r--r--panels/network/wireless-security/eap-method-simple.h52
-rw-r--r--panels/network/wireless-security/eap-method-simple.ui68
-rw-r--r--panels/network/wireless-security/eap-method-tls.c557
-rw-r--r--panels/network/wireless-security/eap-method-tls.h34
-rw-r--r--panels/network/wireless-security/eap-method-tls.ui139
-rw-r--r--panels/network/wireless-security/eap-method-ttls.c415
-rw-r--r--panels/network/wireless-security/eap-method-ttls.h34
-rw-r--r--panels/network/wireless-security/eap-method-ttls.ui160
-rw-r--r--panels/network/wireless-security/eap-method.c588
-rw-r--r--panels/network/wireless-security/eap-method.h108
-rw-r--r--panels/network/wireless-security/helpers.c60
-rw-r--r--panels/network/wireless-security/helpers.h45
-rw-r--r--panels/network/wireless-security/meson.build73
-rw-r--r--panels/network/wireless-security/wireless-security.c101
-rw-r--r--panels/network/wireless-security/wireless-security.gresource.xml17
-rw-r--r--panels/network/wireless-security/wireless-security.h53
-rw-r--r--panels/network/wireless-security/ws-dynamic-wep.c262
-rw-r--r--panels/network/wireless-security/ws-dynamic-wep.h34
-rw-r--r--panels/network/wireless-security/ws-dynamic-wep.ui89
-rw-r--r--panels/network/wireless-security/ws-file-chooser-button.c268
-rw-r--r--panels/network/wireless-security/ws-file-chooser-button.h44
-rw-r--r--panels/network/wireless-security/ws-leap.c211
-rw-r--r--panels/network/wireless-security/ws-leap.h34
-rw-r--r--panels/network/wireless-security/ws-leap.ui63
-rw-r--r--panels/network/wireless-security/ws-sae.c225
-rw-r--r--panels/network/wireless-security/ws-sae.h33
-rw-r--r--panels/network/wireless-security/ws-sae.ui63
-rw-r--r--panels/network/wireless-security/ws-wep-key.c369
-rw-r--r--panels/network/wireless-security/ws-wep-key.h35
-rw-r--r--panels/network/wireless-security/ws-wep-key.ui141
-rw-r--r--panels/network/wireless-security/ws-wpa-eap.c313
-rw-r--r--panels/network/wireless-security/ws-wpa-eap.h37
-rw-r--r--panels/network/wireless-security/ws-wpa-eap.ui93
-rw-r--r--panels/network/wireless-security/ws-wpa-psk.c237
-rw-r--r--panels/network/wireless-security/ws-wpa-psk.h34
-rw-r--r--panels/network/wireless-security/ws-wpa-psk.ui72
134 files changed, 27061 insertions, 0 deletions
diff --git a/panels/network/cc-network-panel.c b/panels/network/cc-network-panel.c
new file mode 100644
index 0000000..aa271fd
--- /dev/null
+++ b/panels/network/cc-network-panel.c
@@ -0,0 +1,806 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2012 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2012 Thomas Bechtold <thomasbechtold@jpberlin.de>
+ * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "shell/cc-object-storage.h"
+
+#include "cc-network-panel.h"
+#include "cc-network-resources.h"
+
+#include <NetworkManager.h>
+
+#include "net-device-bluetooth.h"
+#include "net-device-ethernet.h"
+#include "net-device-mobile.h"
+#include "net-device-wifi.h"
+#include "net-proxy.h"
+#include "net-vpn.h"
+
+#include "panel-common.h"
+
+#include "network-dialogs.h"
+#include "connection-editor/net-connection-editor.h"
+
+#include <libmm-glib.h>
+
+typedef enum {
+ OPERATION_NULL,
+ OPERATION_SHOW_DEVICE,
+ OPERATION_CONNECT_MOBILE
+} CmdlineOperation;
+
+struct _CcNetworkPanel
+{
+ CcPanel parent;
+
+ GPtrArray *bluetooth_devices;
+ GPtrArray *ethernet_devices;
+ GPtrArray *mobile_devices;
+ GPtrArray *vpns;
+ GHashTable *nm_device_to_device;
+
+ NMClient *client;
+ MMManager *modem_manager;
+ gboolean updating_device;
+
+ /* widgets */
+ GtkWidget *box_bluetooth;
+ GtkWidget *box_proxy;
+ GtkWidget *box_vpn;
+ GtkWidget *box_wired;
+ GtkWidget *container_bluetooth;
+ GtkWidget *empty_listbox;
+ GtkWidget *vpn_stack;
+
+ /* wireless dialog stuff */
+ CmdlineOperation arg_operation;
+ gchar *arg_device;
+ gchar *arg_access_point;
+ gboolean operation_done;
+};
+
+enum {
+ PROP_0,
+ PROP_PARAMETERS
+};
+
+static void handle_argv (CcNetworkPanel *self);
+
+CC_PANEL_REGISTER (CcNetworkPanel, cc_network_panel)
+
+static void
+cc_network_panel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static CmdlineOperation
+cmdline_operation_from_string (const gchar *string)
+{
+ if (g_strcmp0 (string, "connect-3g") == 0)
+ return OPERATION_CONNECT_MOBILE;
+ if (g_strcmp0 (string, "show-device") == 0)
+ return OPERATION_SHOW_DEVICE;
+
+ g_warning ("Invalid additional argument %s", string);
+ return OPERATION_NULL;
+}
+
+static void
+reset_command_line_args (CcNetworkPanel *self)
+{
+ self->arg_operation = OPERATION_NULL;
+ g_clear_pointer (&self->arg_device, g_free);
+ g_clear_pointer (&self->arg_access_point, g_free);
+}
+
+static gboolean
+verify_argv (CcNetworkPanel *self,
+ const char **args)
+{
+ switch (self->arg_operation) {
+ case OPERATION_CONNECT_MOBILE:
+ case OPERATION_SHOW_DEVICE:
+ if (self->arg_device == NULL) {
+ g_warning ("Operation %s requires an object path", args[0]);
+ return FALSE;
+ }
+ default:
+ return TRUE;
+ }
+}
+
+static GPtrArray *
+variant_av_to_string_array (GVariant *array)
+{
+ GVariantIter iter;
+ GVariant *v;
+ GPtrArray *strv;
+ gsize count;
+ count = g_variant_iter_init (&iter, array);
+ strv = g_ptr_array_sized_new (count + 1);
+ while (g_variant_iter_next (&iter, "v", &v)) {
+ g_ptr_array_add (strv, (gpointer)g_variant_get_string (v, NULL));
+ g_variant_unref (v);
+ }
+ g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */
+ return strv;
+}
+
+static void
+cc_network_panel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcNetworkPanel *self = CC_NETWORK_PANEL (object);
+
+ switch (property_id) {
+ case PROP_PARAMETERS: {
+ GVariant *parameters;
+
+ reset_command_line_args (self);
+
+ parameters = g_value_get_variant (value);
+ if (parameters) {
+ g_autoptr(GPtrArray) array = NULL;
+ const gchar **args;
+ array = variant_av_to_string_array (parameters);
+ args = (const gchar **) array->pdata;
+
+ g_debug ("Invoked with operation %s", args[0]);
+
+ if (args[0])
+ self->arg_operation = cmdline_operation_from_string (args[0]);
+ if (args[0] && args[1])
+ self->arg_device = g_strdup (args[1]);
+ if (args[0] && args[1] && args[2])
+ self->arg_access_point = g_strdup (args[2]);
+
+ if (verify_argv (self, (const char **) args) == FALSE) {
+ reset_command_line_args (self);
+ return;
+ }
+ g_debug ("Calling handle_argv() after setting property");
+ handle_argv (self);
+ }
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+cc_network_panel_dispose (GObject *object)
+{
+ CcNetworkPanel *self = CC_NETWORK_PANEL (object);
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->modem_manager);
+
+ g_clear_pointer (&self->bluetooth_devices, g_ptr_array_unref);
+ g_clear_pointer (&self->ethernet_devices, g_ptr_array_unref);
+ g_clear_pointer (&self->mobile_devices, g_ptr_array_unref);
+ g_clear_pointer (&self->vpns, g_ptr_array_unref);
+ g_clear_pointer (&self->nm_device_to_device, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (cc_network_panel_parent_class)->dispose (object);
+}
+
+static void
+cc_network_panel_finalize (GObject *object)
+{
+ CcNetworkPanel *self = CC_NETWORK_PANEL (object);
+
+ reset_command_line_args (self);
+
+ G_OBJECT_CLASS (cc_network_panel_parent_class)->finalize (object);
+}
+
+static const char *
+cc_network_panel_get_help_uri (CcPanel *self)
+{
+ return "help:gnome-help/net";
+}
+
+static void
+panel_refresh_device_titles (CcNetworkPanel *self)
+{
+ g_autoptr(GPtrArray) ndarray = NULL;
+ g_autoptr(GPtrArray) nmdarray = NULL;
+ GtkWidget **devices;
+ NMDevice **nm_devices;
+ g_auto(GStrv) titles = NULL;
+ guint i, num_devices;
+
+ ndarray = g_ptr_array_new ();
+ nmdarray = g_ptr_array_new ();
+ for (i = 0; i < self->bluetooth_devices->len; i++) {
+ NetDeviceBluetooth *device = g_ptr_array_index (self->bluetooth_devices, i);
+ g_ptr_array_add (ndarray, device);
+ g_ptr_array_add (nmdarray, net_device_bluetooth_get_device (device));
+ }
+ for (i = 0; i < self->ethernet_devices->len; i++) {
+ NetDeviceEthernet *device = g_ptr_array_index (self->ethernet_devices, i);
+ g_ptr_array_add (ndarray, device);
+ g_ptr_array_add (nmdarray, net_device_ethernet_get_device (device));
+ }
+ for (i = 0; i < self->mobile_devices->len; i++) {
+ NetDeviceMobile *device = g_ptr_array_index (self->mobile_devices, i);
+ g_ptr_array_add (ndarray, device);
+ g_ptr_array_add (nmdarray, net_device_mobile_get_device (device));
+ }
+
+ if (ndarray->len == 0)
+ return;
+
+ devices = (GtkWidget **)ndarray->pdata;
+ nm_devices = (NMDevice **)nmdarray->pdata;
+ num_devices = ndarray->len;
+
+ titles = nm_device_disambiguate_names (nm_devices, num_devices);
+ for (i = 0; i < num_devices; i++) {
+ if (NM_IS_DEVICE_BT (nm_devices[i]))
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (devices[i]), nm_device_bt_get_name (NM_DEVICE_BT (nm_devices[i])));
+ else if (NET_IS_DEVICE_ETHERNET (devices[i]))
+ adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (devices[i]), titles[i]);
+ else if (NET_IS_DEVICE_MOBILE (devices[i]))
+ net_device_mobile_set_title (NET_DEVICE_MOBILE (devices[i]), titles[i]);
+ }
+}
+
+static gboolean
+handle_argv_for_device (CcNetworkPanel *self,
+ NMDevice *device)
+{
+ GtkWidget *toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)));
+
+ if (self->arg_operation == OPERATION_NULL)
+ return TRUE;
+
+ if (g_strcmp0 (nm_object_get_path (NM_OBJECT (device)), self->arg_device) == 0) {
+ if (self->arg_operation == OPERATION_CONNECT_MOBILE) {
+ cc_network_panel_connect_to_3g_network (toplevel, self->client, device);
+
+ reset_command_line_args (self); /* done */
+ return TRUE;
+ } else if (self->arg_operation == OPERATION_SHOW_DEVICE) {
+ reset_command_line_args (self); /* done */
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+handle_argv_for_connection (CcNetworkPanel *self,
+ NMConnection *connection)
+{
+ if (self->arg_operation == OPERATION_NULL)
+ return TRUE;
+ if (self->arg_operation != OPERATION_SHOW_DEVICE)
+ return FALSE;
+
+ if (g_strcmp0 (nm_connection_get_path (connection), self->arg_device) == 0) {
+ reset_command_line_args (self);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static void
+handle_argv (CcNetworkPanel *self)
+{
+ gint i;
+
+ if (self->arg_operation == OPERATION_NULL)
+ return;
+
+ for (i = 0; i < self->bluetooth_devices->len; i++) {
+ NetDeviceBluetooth *device = g_ptr_array_index (self->bluetooth_devices, i);
+ if (handle_argv_for_device (self, net_device_bluetooth_get_device (device)))
+ return;
+ }
+ for (i = 0; i < self->ethernet_devices->len; i++) {
+ NetDeviceEthernet *device = g_ptr_array_index (self->ethernet_devices, i);
+ if (handle_argv_for_device (self, net_device_ethernet_get_device (device)))
+ return;
+ }
+ for (i = 0; i < self->mobile_devices->len; i++) {
+ NetDeviceMobile *device = g_ptr_array_index (self->mobile_devices, i);
+ if (handle_argv_for_device (self, net_device_mobile_get_device (device)))
+ return;
+ }
+ for (i = 0; i < self->vpns->len; i++) {
+ NetVpn *vpn = g_ptr_array_index (self->vpns, i);
+ if (handle_argv_for_connection (self, net_vpn_get_connection (vpn)))
+ return;
+ }
+
+ g_debug ("Could not handle argv operation, no matching device yet?");
+}
+
+static void
+update_vpn_section (CcNetworkPanel *self)
+{
+ gtk_stack_set_visible_child (GTK_STACK (self->vpn_stack),
+ self->vpns->len == 0 ? self->empty_listbox : self->box_vpn);
+}
+
+static void
+update_bluetooth_section (CcNetworkPanel *self)
+{
+ gtk_widget_set_visible (self->container_bluetooth, self->bluetooth_devices->len > 0);
+}
+
+static gboolean
+wwan_panel_supports_modem (GDBusObject *object)
+{
+ MMObject *mm_object;
+ MMModem *modem;
+ MMModemCapability capability, supported_capabilities;
+
+ g_assert (G_IS_DBUS_OBJECT (object));
+
+ supported_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE;
+#if MM_CHECK_VERSION (1,14,0)
+ supported_capabilities |= MM_MODEM_CAPABILITY_5GNR;
+#endif
+
+ mm_object = MM_OBJECT (object);
+ modem = mm_object_get_modem (mm_object);
+ capability = mm_modem_get_current_capabilities (modem);
+
+ return capability & supported_capabilities;
+}
+
+static void
+panel_add_device (CcNetworkPanel *self, NMDevice *device)
+{
+ NMDeviceType type;
+ NetDeviceEthernet *device_ethernet;
+ NetDeviceMobile *device_mobile;
+ NetDeviceBluetooth *device_bluetooth;
+ g_autoptr(GDBusObject) modem_object = NULL;
+
+ /* does already exist */
+ if (g_hash_table_lookup (self->nm_device_to_device, device) != NULL)
+ return;
+
+ type = nm_device_get_device_type (device);
+
+ g_debug ("device %s type %i path %s",
+ nm_device_get_udi (device), type, nm_object_get_path (NM_OBJECT (device)));
+
+ /* map the NMDeviceType to the GType, or ignore */
+ switch (type) {
+ case NM_DEVICE_TYPE_ETHERNET:
+ case NM_DEVICE_TYPE_INFINIBAND:
+ device_ethernet = net_device_ethernet_new (self->client, device);
+ gtk_box_append (GTK_BOX (self->box_wired), GTK_WIDGET (device_ethernet));
+ g_ptr_array_add (self->ethernet_devices, device_ethernet);
+ g_hash_table_insert (self->nm_device_to_device, device, device_ethernet);
+ break;
+ case NM_DEVICE_TYPE_MODEM:
+ if (g_str_has_prefix (nm_device_get_udi (device), "/org/freedesktop/ModemManager1/Modem/")) {
+ if (self->modem_manager == NULL) {
+ g_warning ("Cannot grab information for modem at %s: No ModemManager support",
+ nm_device_get_udi (device));
+ return;
+ }
+
+ modem_object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->modem_manager),
+ nm_device_get_udi (device));
+ if (modem_object == NULL) {
+ g_warning ("Cannot grab information for modem at %s: Not found",
+ nm_device_get_udi (device));
+ return;
+ }
+
+ /* This will be handled by cellular panel */
+ if (wwan_panel_supports_modem (modem_object))
+ return;
+ }
+
+ device_mobile = net_device_mobile_new (self->client, device, modem_object);
+ gtk_box_append (GTK_BOX (self->box_wired), GTK_WIDGET (device_mobile));
+ g_ptr_array_add (self->mobile_devices, device_mobile);
+ g_hash_table_insert (self->nm_device_to_device, device, device_mobile);
+ break;
+ case NM_DEVICE_TYPE_BT:
+ device_bluetooth = net_device_bluetooth_new (self->client, device);
+ gtk_list_box_append (GTK_LIST_BOX (self->box_bluetooth), GTK_WIDGET (device_bluetooth));
+ g_ptr_array_add (self->bluetooth_devices, device_bluetooth);
+ g_hash_table_insert (self->nm_device_to_device, device, device_bluetooth);
+
+ /* Update the device_bluetooth section if we're adding a bluetooth
+ * device. This is a temporary solution though, for these will
+ * be handled by the future Mobile Broadband panel */
+ update_bluetooth_section (self);
+ break;
+
+ /* For Wi-Fi and VPN we handle connections separately; we correctly manage
+ * them, but not here.
+ */
+ case NM_DEVICE_TYPE_WIFI:
+ case NM_DEVICE_TYPE_TUN:
+ /* And the rest we simply cannot deal with currently. */
+ default:
+ return;
+ }
+}
+
+static void
+panel_remove_device (CcNetworkPanel *self, NMDevice *device)
+{
+ GtkWidget *net_device;
+
+ net_device = g_hash_table_lookup (self->nm_device_to_device, device);
+ if (net_device == NULL)
+ return;
+
+ g_ptr_array_remove (self->bluetooth_devices, net_device);
+ g_ptr_array_remove (self->ethernet_devices, net_device);
+ g_ptr_array_remove (self->mobile_devices, net_device);
+ g_hash_table_remove (self->nm_device_to_device, device);
+
+ gtk_box_remove (GTK_BOX (gtk_widget_get_parent (net_device)), net_device);
+
+ /* update vpn widgets */
+ update_vpn_section (self);
+
+ /* update device_bluetooth widgets */
+ update_bluetooth_section (self);
+}
+
+static void
+connection_state_changed (CcNetworkPanel *self)
+{
+}
+
+static void
+active_connections_changed (CcNetworkPanel *self)
+{
+ const GPtrArray *connections;
+ int i, j;
+
+ g_debug ("Active connections changed:");
+ connections = nm_client_get_active_connections (self->client);
+ for (i = 0; connections && (i < connections->len); i++) {
+ NMActiveConnection *connection;
+ const GPtrArray *devices;
+
+ connection = g_ptr_array_index (connections, i);
+ g_debug (" %s", nm_object_get_path (NM_OBJECT (connection)));
+ devices = nm_active_connection_get_devices (connection);
+ for (j = 0; devices && j < devices->len; j++)
+ g_debug (" %s", nm_device_get_udi (g_ptr_array_index (devices, j)));
+ if (NM_IS_VPN_CONNECTION (connection))
+ g_debug (" VPN base connection: %s", nm_active_connection_get_specific_object_path (connection));
+
+ if (g_object_get_data (G_OBJECT (connection), "has-state-changed-handler") == NULL) {
+ g_signal_connect_object (connection, "notify::state",
+ G_CALLBACK (connection_state_changed), self, G_CONNECT_SWAPPED);
+ g_object_set_data (G_OBJECT (connection), "has-state-changed-handler", GINT_TO_POINTER (TRUE));
+ }
+ }
+}
+
+static void
+device_managed_cb (CcNetworkPanel *self, GParamSpec *pspec, NMDevice *device)
+{
+ if (!nm_device_get_managed (device))
+ return;
+
+ panel_add_device (self, device);
+ panel_refresh_device_titles (self);
+}
+
+static void
+device_added_cb (CcNetworkPanel *self, NMDevice *device)
+{
+ g_debug ("New device added");
+
+ if (nm_device_get_managed (device))
+ device_managed_cb (self, NULL, device);
+ else
+ g_signal_connect_object (device, "notify::managed", G_CALLBACK (device_managed_cb), self, G_CONNECT_SWAPPED);
+}
+
+static void
+device_removed_cb (CcNetworkPanel *self, NMDevice *device)
+{
+ g_debug ("Device removed");
+ panel_remove_device (self, device);
+ panel_refresh_device_titles (self);
+
+ g_signal_handlers_disconnect_by_func (device,
+ G_CALLBACK (device_managed_cb),
+ self);
+}
+
+static void
+manager_running (CcNetworkPanel *self)
+{
+ const GPtrArray *devices;
+ int i;
+
+ /* clear all devices we added */
+ if (!nm_client_get_nm_running (self->client)) {
+ g_debug ("NM disappeared");
+ goto out;
+ }
+
+ g_debug ("coldplugging devices");
+ devices = nm_client_get_devices (self->client);
+ if (devices == NULL) {
+ g_debug ("No devices to add");
+ return;
+ }
+ for (i = 0; i < devices->len; i++) {
+ NMDevice *device = g_ptr_array_index (devices, i);
+ device_added_cb (self, device);
+ }
+out:
+ panel_refresh_device_titles (self);
+
+ g_debug ("Calling handle_argv() after cold-plugging devices");
+ handle_argv (self);
+}
+
+static void
+panel_add_vpn_device (CcNetworkPanel *self, NMConnection *connection)
+{
+ NetVpn *net_vpn;
+ guint i;
+
+ /* does already exist */
+ for (i = 0; i < self->vpns->len; i++) {
+ net_vpn = g_ptr_array_index (self->vpns, i);
+ if (net_vpn_get_connection (net_vpn) == connection)
+ return;
+ }
+
+ net_vpn = net_vpn_new (self->client, connection);
+ gtk_list_box_append (GTK_LIST_BOX (self->box_vpn), GTK_WIDGET (net_vpn));
+
+ /* store in the devices array */
+ g_ptr_array_add (self->vpns, net_vpn);
+
+ /* update vpn widgets */
+ update_vpn_section (self);
+}
+
+static void
+add_connection (CcNetworkPanel *self, NMConnection *connection)
+{
+ NMSettingConnection *s_con;
+ const gchar *type, *iface;
+
+ s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection,
+ NM_TYPE_SETTING_CONNECTION));
+ type = nm_setting_connection_get_connection_type (s_con);
+ iface = nm_connection_get_interface_name (connection);
+ if (g_strcmp0 (type, "vpn") != 0 && iface == NULL)
+ return;
+
+ /* Don't add the libvirtd bridge to the UI */
+ if (g_strcmp0 (nm_setting_connection_get_interface_name (s_con), "virbr0") == 0)
+ return;
+
+ g_debug ("add %s/%s remote connection: %s",
+ type, g_type_name_from_instance ((GTypeInstance*)connection),
+ nm_connection_get_path (connection));
+ if (!iface)
+ panel_add_vpn_device (self, connection);
+}
+
+static void
+client_connection_removed_cb (CcNetworkPanel *self, NMConnection *connection)
+{
+ guint i;
+
+ for (i = 0; i < self->vpns->len; i++) {
+ NetVpn *vpn = g_ptr_array_index (self->vpns, i);
+ if (net_vpn_get_connection (vpn) == connection) {
+ g_ptr_array_remove (self->vpns, vpn);
+ gtk_list_box_remove (GTK_LIST_BOX (self->box_vpn), GTK_WIDGET (vpn));
+ update_vpn_section (self);
+ return;
+ }
+ }
+}
+
+static void
+panel_check_network_manager_version (CcNetworkPanel *self)
+{
+ const gchar *version;
+
+ /* parse running version */
+ version = nm_client_get_version (self->client);
+ if (version == NULL) {
+ GtkWidget *box;
+ GtkWidget *label;
+ g_autofree gchar *markup = NULL;
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
+ gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
+ gtk_widget_set_vexpand (box, TRUE);
+ adw_bin_set_child (ADW_BIN (self), box);
+
+ label = gtk_label_new (_("Oops, something has gone wrong. Please contact your software vendor."));
+ gtk_widget_set_vexpand (label, TRUE);
+ gtk_label_set_wrap (GTK_LABEL (label), TRUE);
+ gtk_widget_set_valign (label, GTK_ALIGN_END);
+ gtk_box_append (GTK_BOX (box), label);
+
+ markup = g_strdup_printf ("<small><tt>%s</tt></small>",
+ _("NetworkManager needs to be running."));
+ label = gtk_label_new (NULL);
+ gtk_widget_set_vexpand (label, TRUE);
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ gtk_label_set_wrap (GTK_LABEL (label), TRUE);
+ gtk_widget_set_valign (label, GTK_ALIGN_START);
+ gtk_box_append (GTK_BOX (box), label);
+ } else {
+ manager_running (self);
+ }
+}
+
+static void
+create_connection_cb (GtkWidget *button,
+ CcNetworkPanel *self)
+{
+ NetConnectionEditor *editor;
+
+ editor = net_connection_editor_new (NULL, NULL, NULL, self->client);
+ gtk_window_set_transient_for (GTK_WINDOW (editor),
+ GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self))));
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+static void
+cc_network_panel_map (GtkWidget *widget)
+{
+ GTK_WIDGET_CLASS (cc_network_panel_parent_class)->map (widget);
+
+ /* is the user compiling against a new version, but not running
+ * the daemon? */
+ panel_check_network_manager_version (CC_NETWORK_PANEL (widget));
+}
+
+
+static void
+cc_network_panel_class_init (CcNetworkPanelClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+
+ panel_class->get_help_uri = cc_network_panel_get_help_uri;
+
+ widget_class->map = cc_network_panel_map;
+
+ object_class->get_property = cc_network_panel_get_property;
+ object_class->set_property = cc_network_panel_set_property;
+ object_class->dispose = cc_network_panel_dispose;
+ object_class->finalize = cc_network_panel_finalize;
+
+ g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/cc-network-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_bluetooth);
+ gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_proxy);
+ gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_vpn);
+ gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_wired);
+ gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, container_bluetooth);
+ gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, empty_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, vpn_stack);
+
+ gtk_widget_class_bind_template_callback (widget_class, create_connection_cb);
+}
+
+static void
+cc_network_panel_init (CcNetworkPanel *self)
+{
+ g_autoptr(GDBusConnection) system_bus = NULL;
+ g_autoptr(GError) error = NULL;
+ const GPtrArray *connections;
+ NetProxy *proxy;
+ guint i;
+
+ g_resources_register (cc_network_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->bluetooth_devices = g_ptr_array_new ();
+ self->ethernet_devices = g_ptr_array_new ();
+ self->mobile_devices = g_ptr_array_new ();
+ self->vpns = g_ptr_array_new ();
+ self->nm_device_to_device = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ /* add the virtual proxy device */
+ proxy = net_proxy_new ();
+ gtk_box_append (GTK_BOX (self->box_proxy), GTK_WIDGET (proxy));
+
+ /* Create and store a NMClient instance if it doesn't exist yet */
+ if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) {
+ g_autoptr(NMClient) client = nm_client_new (NULL, NULL);
+ cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client);
+ }
+
+ /* use NetworkManager client */
+ self->client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT);
+
+ g_signal_connect_object (self->client, "notify::nm-running" ,
+ G_CALLBACK (manager_running), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->client, "notify::active-connections",
+ G_CALLBACK (active_connections_changed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->client, "device-added",
+ G_CALLBACK (device_added_cb), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->client, "device-removed",
+ G_CALLBACK (device_removed_cb), self, G_CONNECT_SWAPPED);
+
+ /* Setup ModemManager client */
+ system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (system_bus == NULL) {
+ g_warning ("Error connecting to system D-Bus: %s",
+ error->message);
+ } else {
+ self->modem_manager = mm_manager_new_sync (system_bus,
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+ NULL,
+ &error);
+ if (self->modem_manager == NULL)
+ g_warning ("Error connecting to ModemManager: %s",
+ error->message);
+ }
+
+ /* add remote settings such as VPN settings as virtual devices */
+ g_signal_connect_object (self->client, NM_CLIENT_CONNECTION_ADDED,
+ G_CALLBACK (add_connection), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->client, NM_CLIENT_CONNECTION_REMOVED,
+ G_CALLBACK (client_connection_removed_cb), self, G_CONNECT_SWAPPED);
+
+ /* Cold-plug existing connections */
+ connections = nm_client_get_connections (self->client);
+ if (connections) {
+ for (i = 0; i < connections->len; i++)
+ add_connection (self, connections->pdata[i]);
+ }
+
+ g_debug ("Calling handle_argv() after cold-plugging connections");
+ handle_argv (self);
+}
diff --git a/panels/network/cc-network-panel.h b/panels/network/cc-network-panel.h
new file mode 100644
index 0000000..9141ad0
--- /dev/null
+++ b/panels/network/cc-network-panel.h
@@ -0,0 +1,30 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard@hughsie.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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_NETWORK_PANEL cc_network_panel_get_type()
+
+G_DECLARE_FINAL_TYPE (CcNetworkPanel, cc_network_panel, CC, NETWORK_PANEL, CcPanel)
+
+G_END_DECLS
diff --git a/panels/network/cc-network-panel.ui b/panels/network/cc-network-panel.ui
new file mode 100644
index 0000000..1ae1009
--- /dev/null
+++ b/panels/network/cc-network-panel.ui
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcNetworkPanel" parent="CcPanel">
+
+ <child type="content">
+ <object class="AdwPreferencesPage">
+ <!-- Each group below will contain GtkStacks from the NetDevices -->
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkBox" id="box_wired">
+ <property name="orientation">vertical</property>
+ <property name="spacing">24</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup" id="container_bluetooth">
+ <property name="visible">False</property>
+ <property name="title" translatable="yes">Other Devices</property>
+ <child>
+ <object class="GtkListBox" id="box_bluetooth">
+ <property name="selection_mode">none</property>
+ <accessibility>
+ <property name="label" translatable="yes">Other Devices</property>
+ </accessibility>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGRoup">
+ <property name="title" translatable="yes">VPN</property>
+ <property name="header-suffix">
+ <object class="GtkButton">
+ <property name="icon_name">list-add-symbolic</property>
+ <accessibility>
+ <property name="label" translatable="yes">Add connection</property>
+ </accessibility>
+ <style>
+ <class name="flat" />
+ </style>
+ <signal name="clicked" handler="create_connection_cb" object="CcNetworkPanel" swapped="no" />
+ </object>
+ </property>
+ <child>
+ <object class="GtkStack" id="vpn_stack">
+ <child>
+ <!-- "Not set up" row -->
+ <object class="GtkListBox" id="empty_listbox">
+ <property name="selection_mode">none</property>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ <child>
+ <object class="AdwActionRow">
+ <property name="activatable">False</property>
+ <property name="title" translatable="yes">Not set up</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="box_vpn">
+ <property name="selection_mode">none</property>
+ <accessibility>
+ <property name="label" translatable="yes">VPN</property>
+ </accessibility>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkBox" id="box_proxy">
+ <property name="orientation">vertical</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/cc-qr-code.c b/panels/network/cc-qr-code.c
new file mode 100644
index 0000000..8bb8a14
--- /dev/null
+++ b/panels/network/cc-qr-code.c
@@ -0,0 +1,186 @@
+/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* cc-qr-code.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-qr-code"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "cc-qr-code.h"
+#include "qrcodegen.c"
+
+/**
+ * SECTION: cc-qr_code
+ * @title: CcQrCode
+ * @short_description: A Simple QR Code wrapper around libqrencode
+ * @include: "cc-qr-code.h"
+ *
+ * Generate a QR image from a given text.
+ */
+
+#define BYTES_PER_R8G8B8 3
+
+struct _CcQrCode
+{
+ GObject parent_instance;
+
+ gchar *text;
+ GdkTexture *texture;
+ gint size;
+};
+
+G_DEFINE_TYPE (CcQrCode, cc_qr_code, G_TYPE_OBJECT)
+
+
+static void
+cc_qr_code_finalize (GObject *object)
+{
+ CcQrCode *self = (CcQrCode *)object;
+
+ g_clear_object (&self->texture);
+ g_clear_pointer (&self->text, g_free);
+
+ G_OBJECT_CLASS (cc_qr_code_parent_class)->finalize (object);
+}
+
+static void
+cc_qr_code_class_init (CcQrCodeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_qr_code_finalize;
+}
+
+static void
+cc_qr_code_init (CcQrCode *self)
+{
+}
+
+CcQrCode *
+cc_qr_code_new (void)
+{
+ return g_object_new (CC_TYPE_QR_CODE, NULL);
+}
+
+gboolean
+cc_qr_code_set_text (CcQrCode *self,
+ const gchar *text)
+{
+ g_return_val_if_fail (CC_IS_QR_CODE (self), FALSE);
+ g_return_val_if_fail (!text || *text, FALSE);
+
+ if (g_strcmp0 (text, self->text) == 0)
+ return FALSE;
+
+ g_clear_object (&self->texture);
+ g_free (self->text);
+ self->text = g_strdup (text);
+
+ return TRUE;
+}
+
+static void
+cc_fill_pixel (GByteArray *array,
+ guint8 value,
+ int pixel_size)
+{
+ guint i;
+
+ for (i = 0; i < pixel_size; i++) {
+ g_byte_array_append (array, &value, 1); /* R */
+ g_byte_array_append (array, &value, 1); /* G */
+ g_byte_array_append (array, &value, 1); /* B */
+ }
+}
+
+GdkPaintable *
+cc_qr_code_get_paintable (CcQrCode *self,
+ gint size)
+{
+ uint8_t qr_code[qrcodegen_BUFFER_LEN_FOR_VERSION (qrcodegen_VERSION_MAX)];
+ uint8_t temp_buf[qrcodegen_BUFFER_LEN_FOR_VERSION (qrcodegen_VERSION_MAX)];
+ g_autoptr(GBytes) bytes = NULL;
+ GByteArray *qr_matrix;
+ gint pixel_size, qr_size, total_size;
+ gint column, row, i;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (CC_IS_QR_CODE (self), NULL);
+ g_return_val_if_fail (size > 0, NULL);
+
+ if (!self->text || !*self->text)
+ {
+ g_warn_if_reached ();
+ cc_qr_code_set_text (self, "invalid text");
+ }
+
+ if (self->texture && self->size == size)
+ return GDK_PAINTABLE (self->texture);
+
+ self->size = size;
+
+ success = qrcodegen_encodeText (self->text,
+ temp_buf,
+ qr_code,
+ qrcodegen_Ecc_LOW,
+ qrcodegen_VERSION_MIN,
+ qrcodegen_VERSION_MAX,
+ qrcodegen_Mask_AUTO,
+ FALSE);
+
+ if (!success)
+ return NULL;
+
+ qr_size = qrcodegen_getSize(qr_code);
+ pixel_size = MAX (1, size / (qr_size));
+ total_size = qr_size * pixel_size;
+ qr_matrix = g_byte_array_sized_new (total_size * total_size * pixel_size * BYTES_PER_R8G8B8);
+
+ for (column = 0; column < total_size; column++)
+ {
+ for (i = 0; i < pixel_size; i++)
+ {
+ for (row = 0; row < total_size / pixel_size; row++)
+ {
+ if (qrcodegen_getModule (qr_code, column, row))
+ cc_fill_pixel (qr_matrix, 0x00, pixel_size);
+ else
+ cc_fill_pixel (qr_matrix, 0xff, pixel_size);
+ }
+ }
+ }
+
+ bytes = g_byte_array_free_to_bytes (qr_matrix);
+
+ g_clear_object (&self->texture);
+ self->texture = gdk_memory_texture_new (total_size,
+ total_size,
+ GDK_MEMORY_R8G8B8,
+ bytes,
+ total_size * BYTES_PER_R8G8B8);
+
+ return GDK_PAINTABLE (self->texture);
+}
diff --git a/panels/network/cc-qr-code.h b/panels/network/cc-qr-code.h
new file mode 100644
index 0000000..844dd2a
--- /dev/null
+++ b/panels/network/cc-qr-code.h
@@ -0,0 +1,43 @@
+/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* cc-qr-code.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_QR_CODE (cc_qr_code_get_type ())
+
+G_DECLARE_FINAL_TYPE (CcQrCode, cc_qr_code, CC, QR_CODE, GObject)
+
+CcQrCode *cc_qr_code_new (void);
+gboolean cc_qr_code_set_text (CcQrCode *self,
+ const gchar *text);
+GdkPaintable *cc_qr_code_get_paintable (CcQrCode *self,
+ gint size);
+
+G_END_DECLS
diff --git a/panels/network/cc-wifi-connection-list.c b/panels/network/cc-wifi-connection-list.c
new file mode 100644
index 0000000..6a11d20
--- /dev/null
+++ b/panels/network/cc-wifi-connection-list.c
@@ -0,0 +1,799 @@
+/*
+ * Copyright © 2018 Red Hat Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cc-wifi-connection-list.h"
+#include "cc-wifi-connection-row.h"
+
+struct _CcWifiConnectionList
+{
+ AdwBin parent_instance;
+
+ GtkListBox *listbox;
+
+ guint freeze_count;
+ gboolean updating;
+
+ gboolean checkable;
+ gboolean hide_unavailable;
+ gboolean show_aps;
+
+ NMClient *client;
+ NMDeviceWifi *device;
+
+ NMConnection *last_active;
+
+ GPtrArray *connections;
+ GPtrArray *connections_row;
+
+ /* AP SSID cache stores the APs SSID used for assigning it to a row.
+ * This is necessary to efficiently remove it when its SSID changes.
+ *
+ * Note that we only group APs that cannot be assigned to a connection
+ * by the SSID. In principle this is wrong, because other attributes may
+ * be different rendering them separate networks.
+ * In practice this will almost never happen, and if it does, we just
+ * show and select the strongest AP.
+ */
+ GHashTable *ap_ssid_cache;
+ GHashTable *ssid_to_row;
+};
+
+static void on_device_ap_added_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device);
+static void on_device_ap_removed_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device);
+static void on_row_configured_cb (CcWifiConnectionList *self,
+ CcWifiConnectionRow *row);
+
+G_DEFINE_TYPE (CcWifiConnectionList, cc_wifi_connection_list, ADW_TYPE_BIN)
+
+enum
+{
+ PROP_0,
+ PROP_CHECKABLE,
+ PROP_HIDE_UNAVAILABLE,
+ PROP_SHOW_APS,
+ PROP_CLIENT,
+ PROP_DEVICE,
+ PROP_LAST
+};
+
+static GParamSpec *props [PROP_LAST];
+
+static GBytes*
+new_hashable_ssid (GBytes *ssid)
+{
+ GBytes *res;
+ const guint8 *data;
+ gsize size;
+
+ /* This is what nm_utils_same_ssid does, but returning it so that we can
+ * use the result in other ways (i.e. hash table lookups). */
+ data = g_bytes_get_data ((GBytes*) ssid, &size);
+ if (data[size-1] == '\0')
+ size -= 1;
+ res = g_bytes_new (data, size);
+
+ return res;
+}
+
+static gboolean
+connection_ignored (NMConnection *connection)
+{
+ NMSettingWireless *sw;
+
+ /* Ignore AP and adhoc modes (i.e. accept infrastructure or empty) */
+ sw = nm_connection_get_setting_wireless (connection);
+ if (!sw)
+ return TRUE;
+ if (g_strcmp0 (nm_setting_wireless_get_mode (sw), "adhoc") == 0 ||
+ g_strcmp0 (nm_setting_wireless_get_mode (sw), "ap") == 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static CcWifiConnectionRow*
+cc_wifi_connection_list_row_add (CcWifiConnectionList *self,
+ NMConnection *connection,
+ NMAccessPoint *ap,
+ gboolean known_connection)
+{
+ CcWifiConnectionRow *res;
+ g_autoptr(GPtrArray) aps = NULL;
+
+ if (ap)
+ {
+ aps = g_ptr_array_new ();
+ g_ptr_array_add (aps, ap);
+ }
+
+ res = cc_wifi_connection_row_new (self->device,
+ connection,
+ aps,
+ self->checkable,
+ known_connection);
+ gtk_list_box_append (self->listbox, GTK_WIDGET (res));
+
+ g_signal_connect_object (res, "configure", G_CALLBACK (on_row_configured_cb), self, G_CONNECT_SWAPPED);
+
+ g_signal_emit_by_name (self, "add-row", res);
+
+ return res;
+}
+
+static void
+clear_widget (CcWifiConnectionList *self)
+{
+ const GPtrArray *aps;
+ GHashTableIter iter;
+ CcWifiConnectionRow *row;
+ gint i;
+
+ /* Clear everything; disconnect all AP signals first */
+ aps = nm_device_wifi_get_access_points (self->device);
+ for (i = 0; i < aps->len; i++)
+ g_signal_handlers_disconnect_by_data (g_ptr_array_index (aps, i), self);
+
+ /* Remove all AP only rows */
+ g_hash_table_iter_init (&iter, self->ssid_to_row);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &row))
+ {
+ g_hash_table_iter_remove (&iter);
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+
+ /* Remove all connection rows */
+ for (i = 0; i < self->connections_row->len; i++)
+ {
+ if (!g_ptr_array_index (self->connections_row, i))
+ continue;
+
+ row = g_ptr_array_index (self->connections_row, i);
+ g_ptr_array_index (self->connections_row, i) = NULL;
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+
+ /* Reset the internal state */
+ g_ptr_array_set_size (self->connections, 0);
+ g_ptr_array_set_size (self->connections_row, 0);
+ g_hash_table_remove_all (self->ssid_to_row);
+ g_hash_table_remove_all (self->ap_ssid_cache);
+}
+
+static void
+update_connections (CcWifiConnectionList *self)
+{
+ const GPtrArray *aps;
+ const GPtrArray *acs_client;
+ g_autoptr(GPtrArray) acs = NULL;
+ NMActiveConnection *ac;
+ NMConnection *ac_con = NULL;
+ gint i;
+
+ /* We don't want full UI rebuilds during some UI interactions, so allow freezing the list. */
+ if (self->freeze_count > 0)
+ return;
+
+ /* Prevent recursion (maybe move this into an idle handler instead?) */
+ if (self->updating)
+ return;
+ self->updating = TRUE;
+
+ clear_widget (self);
+
+ /* Copy the new connections; also create a row if we show unavailable
+ * connections */
+ acs_client = nm_client_get_connections (self->client);
+
+ acs = g_ptr_array_new_full (acs_client->len + 1, NULL);
+ for (i = 0; i < acs_client->len; i++)
+ g_ptr_array_add (acs, g_ptr_array_index (acs_client, i));
+
+ ac = nm_device_get_active_connection (NM_DEVICE (self->device));
+ if (ac)
+ ac_con = NM_CONNECTION (nm_active_connection_get_connection (ac));
+
+ if (ac_con && !g_ptr_array_find (acs, ac_con, NULL))
+ {
+ g_debug ("Adding remote connection for active connection");
+ g_ptr_array_add (acs, g_object_ref (ac_con));
+ }
+
+ for (i = 0; i < acs->len; i++)
+ {
+ NMConnection *con;
+
+ con = g_ptr_array_index (acs, i);
+ if (connection_ignored (con))
+ continue;
+
+ g_ptr_array_add (self->connections, g_object_ref (con));
+ if (self->hide_unavailable && con != ac_con)
+ g_ptr_array_add (self->connections_row, NULL);
+ else
+ g_ptr_array_add (self->connections_row,
+ cc_wifi_connection_list_row_add (self, con,
+ NULL, TRUE));
+ }
+
+ /* Coldplug all known APs again */
+ aps = nm_device_wifi_get_access_points (self->device);
+ for (i = 0; i < aps->len; i++)
+ on_device_ap_added_cb (self, g_ptr_array_index (aps, i), self->device);
+
+ self->updating = FALSE;
+}
+
+static void
+on_row_configured_cb (CcWifiConnectionList *self, CcWifiConnectionRow *row)
+{
+ g_signal_emit_by_name (self, "configure", row);
+}
+
+static void
+on_access_point_property_changed (CcWifiConnectionList *self,
+ GParamSpec *pspec,
+ NMAccessPoint *ap)
+{
+ CcWifiConnectionRow *row;
+ GBytes *ssid;
+ gboolean has_connection = FALSE;
+ gint i;
+
+ /* If the SSID changed then the AP needs to be added/removed from rows.
+ * Do this by simulating an AP addition/removal. */
+ if (g_str_equal (pspec->name, NM_ACCESS_POINT_SSID))
+ {
+ g_debug ("Simulating add/remove for SSID change");
+ on_device_ap_removed_cb (self, ap, self->device);
+ on_device_ap_added_cb (self, ap, self->device);
+ return;
+ }
+
+ /* Otherwise, find all rows that contain the AP and update it. Do this by
+ * first searching all rows with connections, and then looking it up in the
+ * SSID rows if not found. */
+ for (i = 0; i < self->connections_row->len; i++)
+ {
+ row = g_ptr_array_index (self->connections_row, i);
+ if (row && cc_wifi_connection_row_has_access_point (row, ap))
+ {
+ cc_wifi_connection_row_update (row);
+ has_connection = TRUE;
+ }
+ }
+
+ if (!self->show_aps || has_connection)
+ return;
+
+ ssid = g_hash_table_lookup (self->ap_ssid_cache, ap);
+ if (!ssid)
+ return;
+
+ row = g_hash_table_lookup (self->ssid_to_row, ssid);
+ if (!row)
+ g_assert_not_reached ();
+ else
+ cc_wifi_connection_row_update (row);
+}
+
+static void
+on_device_ap_added_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device)
+{
+ g_autoptr(GPtrArray) connections = NULL;
+ NM80211ApSecurityFlags rsn_flags;
+ CcWifiConnectionRow *row;
+ GBytes *ap_ssid;
+ g_autoptr(GBytes) ssid = NULL;
+ guint i, j;
+
+ g_signal_connect_object (ap, "notify",
+ G_CALLBACK (on_access_point_property_changed),
+ self, G_CONNECT_SWAPPED);
+
+ connections = nm_access_point_filter_connections (ap, self->connections);
+
+ /* If this is the active AP, then add the active connection to the list. This
+ * is a workaround because nm_access_pointer_filter_connections() will not
+ * include it otherwise.
+ * So it seems like the dummy AP entry that NM creates internally is not actually
+ * compatible with the connection that is being activated.
+ */
+ if (ap == nm_device_wifi_get_active_access_point (device))
+ {
+ NMActiveConnection *ac;
+ NMConnection *ac_con;
+
+ ac = nm_device_get_active_connection (NM_DEVICE (self->device));
+
+ if (ac)
+ {
+ guint idx;
+
+ ac_con = NM_CONNECTION (nm_active_connection_get_connection (ac));
+
+ if (!g_ptr_array_find (connections, ac_con, NULL) &&
+ g_ptr_array_find (self->connections, ac_con, &idx))
+ {
+ g_debug ("Adding active connection to list of valid connections for AP");
+ g_ptr_array_add (connections, g_object_ref (ac_con));
+ }
+ }
+ }
+
+ /* Add the AP to all connection related rows, creating the row if neccessary. */
+ for (i = 0; i < connections->len; i++)
+ {
+ gboolean found = g_ptr_array_find (self->connections, g_ptr_array_index (connections, i), &j);
+
+ g_assert (found);
+
+ row = g_ptr_array_index (self->connections_row, j);
+ if (!row)
+ row = cc_wifi_connection_list_row_add (self, g_ptr_array_index (connections, i), NULL, TRUE);
+ cc_wifi_connection_row_add_access_point (row, ap);
+ g_ptr_array_index (self->connections_row, j) = row;
+ }
+
+ if (connections->len > 0)
+ return;
+
+ if (!self->show_aps)
+ return;
+
+ /* The AP is not compatible to any known connection, generate an entry for the
+ * SSID or add to existing one. However, not for hidden APs that don't have an SSID
+ * or a hidden OWE transition network.
+ */
+ ap_ssid = nm_access_point_get_ssid (ap);
+ if (ap_ssid == NULL)
+ return;
+
+ /* Skip OWE-TM network with OWE RSN */
+ rsn_flags = nm_access_point_get_rsn_flags (ap);
+ if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE && rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)
+ return;
+
+ ssid = new_hashable_ssid (ap_ssid);
+
+ g_hash_table_insert (self->ap_ssid_cache, ap, g_bytes_ref (ssid));
+
+ row = g_hash_table_lookup (self->ssid_to_row, ssid);
+ if (!row)
+ {
+ row = cc_wifi_connection_list_row_add (self, NULL, ap, FALSE);
+
+ g_hash_table_insert (self->ssid_to_row, g_bytes_ref (ssid), row);
+ }
+ else
+ {
+ cc_wifi_connection_row_add_access_point (row, ap);
+ }
+}
+
+static void
+on_device_ap_removed_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device)
+{
+ CcWifiConnectionRow *row;
+ g_autoptr(GBytes) ssid = NULL;
+ gboolean found = FALSE;
+ gint i;
+
+ g_signal_handlers_disconnect_by_data (ap, self);
+
+ /* Find any connection related row with the AP and remove the AP from it. Remove the
+ * row if it was the last AP and we are hiding unavailable connections. */
+ for (i = 0; i < self->connections_row->len; i++)
+ {
+ row = g_ptr_array_index (self->connections_row, i);
+ if (row && cc_wifi_connection_row_remove_access_point (row, ap))
+ {
+ found = TRUE;
+
+ if (self->hide_unavailable)
+ {
+ g_ptr_array_index (self->connections_row, i) = NULL;
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+ }
+ }
+
+ if (found || !self->show_aps)
+ return;
+
+ /* If the AP was inserted into a row without a connection, then we will get an
+ * SSID for it here. */
+ g_hash_table_steal_extended (self->ap_ssid_cache, ap, NULL, (gpointer*) &ssid);
+ if (!ssid)
+ return;
+
+ /* And we can update the row (possibly removing it) */
+ row = g_hash_table_lookup (self->ssid_to_row, ssid);
+ g_assert (row != NULL);
+
+ if (cc_wifi_connection_row_remove_access_point (row, ap))
+ {
+ g_hash_table_remove (self->ssid_to_row, ssid);
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+}
+
+static void
+on_client_connection_added_cb (CcWifiConnectionList *self,
+ NMConnection *connection,
+ NMClient *client)
+{
+ if (!nm_device_connection_compatible (NM_DEVICE (self->device), connection, NULL))
+ return;
+
+ if (connection_ignored (connection))
+ return;
+
+ /* The approach we take to handle connection changes is to do a full rebuild.
+ * It happens seldom enough to make this feasible.
+ */
+ update_connections (self);
+}
+
+static void
+on_client_connection_removed_cb (CcWifiConnectionList *self,
+ NMConnection *connection,
+ NMClient *client)
+{
+ if (!g_ptr_array_find (self->connections, connection, NULL))
+ return;
+
+ /* The approach we take to handle connection changes is to do a full rebuild.
+ * It happens seldom enough to make this feasible.
+ */
+ update_connections (self);
+}
+
+static void
+on_device_state_changed_cb (CcWifiConnectionList *self,
+ GParamSpec *pspec,
+ NMDeviceWifi *device)
+{
+ NMActiveConnection *ac;
+ NMConnection *connection = NULL;
+ guint idx;
+
+ ac = nm_device_get_active_connection (NM_DEVICE (self->device));
+ if (ac)
+ connection = NM_CONNECTION (nm_active_connection_get_connection (ac));
+
+ /* Just update the corresponding row if the AC is still the same. */
+ if (self->last_active == connection &&
+ g_ptr_array_find (self->connections, connection, &idx) &&
+ g_ptr_array_index (self->connections_row, idx))
+ {
+ cc_wifi_connection_row_update (g_ptr_array_index (self->connections_row, idx));
+ return;
+ }
+
+ /* Give up and do a full update. */
+ update_connections (self);
+ self->last_active = connection;
+}
+
+static void
+on_device_active_ap_changed_cb (CcWifiConnectionList *self,
+ GParamSpec *pspec,
+ NMDeviceWifi *device)
+{
+ NMAccessPoint *ap;
+ /* We need to make sure the active AP is grouped with the active connection.
+ * Do so by simply removing and adding it.
+ *
+ * This is necessary because the AP is added before this property
+ * is updated. */
+ ap = nm_device_wifi_get_active_access_point (self->device);
+ if (ap)
+ {
+ g_debug ("Simulating add/remove for active AP change");
+ on_device_ap_removed_cb (self, ap, self->device);
+ on_device_ap_added_cb (self, ap, self->device);
+ }
+}
+
+static void
+cc_wifi_connection_list_dispose (GObject *object)
+{
+ CcWifiConnectionList *self = (CcWifiConnectionList *)object;
+
+ /* Prevent any further updates; clear_widget must not indirectly recurse
+ * through updates_connections */
+ self->updating = TRUE;
+
+ /* Drop all external references */
+ clear_widget (self);
+
+ G_OBJECT_CLASS (cc_wifi_connection_list_parent_class)->dispose (object);
+}
+
+static void
+cc_wifi_connection_list_finalize (GObject *object)
+{
+ CcWifiConnectionList *self = (CcWifiConnectionList *)object;
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->device);
+
+ g_clear_pointer (&self->connections, g_ptr_array_unref);
+ g_clear_pointer (&self->connections_row, g_ptr_array_unref);
+ g_clear_pointer (&self->ssid_to_row, g_hash_table_unref);
+ g_clear_pointer (&self->ap_ssid_cache, g_hash_table_unref);
+
+ G_OBJECT_CLASS (cc_wifi_connection_list_parent_class)->finalize (object);
+}
+
+static void
+cc_wifi_connection_list_constructed (GObject *object)
+{
+ CcWifiConnectionList *self = (CcWifiConnectionList *)object;
+
+ G_OBJECT_CLASS (cc_wifi_connection_list_parent_class)->constructed (object);
+
+ g_assert (self->client);
+ g_assert (self->device);
+
+ g_signal_connect_object (self->client, "connection-added",
+ G_CALLBACK (on_client_connection_added_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->client, "connection-removed",
+ G_CALLBACK (on_client_connection_removed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->device, "access-point-added",
+ G_CALLBACK (on_device_ap_added_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->device, "access-point-removed",
+ G_CALLBACK (on_device_ap_removed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->device, "notify::state",
+ G_CALLBACK (on_device_state_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->device, "notify::active-connection",
+ G_CALLBACK (on_device_state_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->device, "notify::active-access-point",
+ G_CALLBACK (on_device_active_ap_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ on_device_state_changed_cb (self, NULL, self->device);
+
+ /* Simulate a change notification on the available connections.
+ * This uses the implementation detail that the list is rebuild
+ * completely in this case. */
+ update_connections (self);
+}
+
+static void
+cc_wifi_connection_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcWifiConnectionList *self = CC_WIFI_CONNECTION_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHECKABLE:
+ g_value_set_boolean (value, self->checkable);
+ break;
+
+ case PROP_HIDE_UNAVAILABLE:
+ g_value_set_boolean (value, self->hide_unavailable);
+ break;
+
+ case PROP_SHOW_APS:
+ g_value_set_boolean (value, self->show_aps);
+ break;
+
+ case PROP_CLIENT:
+ g_value_set_object (value, self->client);
+ break;
+
+ case PROP_DEVICE:
+ g_value_set_object (value, self->device);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wifi_connection_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWifiConnectionList *self = CC_WIFI_CONNECTION_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHECKABLE:
+ self->checkable = g_value_get_boolean (value);
+ break;
+
+ case PROP_HIDE_UNAVAILABLE:
+ self->hide_unavailable = g_value_get_boolean (value);
+ break;
+
+ case PROP_SHOW_APS:
+ self->show_aps = g_value_get_boolean (value);
+ break;
+
+ case PROP_CLIENT:
+ self->client = g_value_dup_object (value);
+ break;
+
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wifi_connection_list_class_init (CcWifiConnectionListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = cc_wifi_connection_list_constructed;
+ object_class->dispose = cc_wifi_connection_list_dispose;
+ object_class->finalize = cc_wifi_connection_list_finalize;
+ object_class->get_property = cc_wifi_connection_list_get_property;
+ object_class->set_property = cc_wifi_connection_list_set_property;
+
+ props[PROP_CHECKABLE] =
+ g_param_spec_boolean ("checkable", "checkable",
+ "Passed to the created rows to show/hide the checkbox for deletion",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_HIDE_UNAVAILABLE] =
+ g_param_spec_boolean ("hide-unavailable", "HideUnavailable",
+ "Whether to show or hide unavailable connections",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_SHOW_APS] =
+ g_param_spec_boolean ("show-aps", "ShowAPs",
+ "Whether to show available SSIDs/APs without a connection",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_CLIENT] =
+ g_param_spec_object ("client", "NMClient",
+ "The NM Client",
+ NM_TYPE_CLIENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_DEVICE] =
+ g_param_spec_object ("device", "WiFi Device",
+ "The WiFi Device for this connection list",
+ NM_TYPE_DEVICE_WIFI,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class,
+ PROP_LAST,
+ props);
+
+ g_signal_new ("configure",
+ CC_TYPE_WIFI_CONNECTION_LIST,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, CC_TYPE_WIFI_CONNECTION_ROW);
+ g_signal_new ("add-row",
+ CC_TYPE_WIFI_CONNECTION_LIST,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, CC_TYPE_WIFI_CONNECTION_ROW);
+ g_signal_new ("remove-row",
+ CC_TYPE_WIFI_CONNECTION_LIST,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, CC_TYPE_WIFI_CONNECTION_ROW);
+}
+
+static void
+cc_wifi_connection_list_init (CcWifiConnectionList *self)
+{
+ self->listbox = GTK_LIST_BOX (gtk_list_box_new ());
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->listbox), GTK_SELECTION_NONE);
+ gtk_widget_set_valign (GTK_WIDGET (self->listbox), GTK_ALIGN_START);
+ gtk_widget_add_css_class (GTK_WIDGET (self->listbox), "boxed-list");
+ adw_bin_set_child (ADW_BIN (self), GTK_WIDGET (self->listbox));
+
+ self->hide_unavailable = TRUE;
+ self->show_aps = TRUE;
+
+ self->connections = g_ptr_array_new_with_free_func (g_object_unref);
+ self->connections_row = g_ptr_array_new ();
+ self->ssid_to_row = g_hash_table_new_full (g_bytes_hash, g_bytes_equal,
+ (GDestroyNotify) g_bytes_unref, NULL);
+ self->ap_ssid_cache = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_bytes_unref);
+}
+
+CcWifiConnectionList *
+cc_wifi_connection_list_new (NMClient *client,
+ NMDeviceWifi *device,
+ gboolean hide_unavailable,
+ gboolean show_aps,
+ gboolean checkable)
+{
+ return g_object_new (CC_TYPE_WIFI_CONNECTION_LIST,
+ "client", client,
+ "device", device,
+ "hide-unavailable", hide_unavailable,
+ "show-aps", show_aps,
+ "checkable", checkable,
+ NULL);
+}
+
+void
+cc_wifi_connection_list_freeze (CcWifiConnectionList *self)
+{
+ g_return_if_fail (CC_WIFI_CONNECTION_LIST (self));
+
+ if (self->freeze_count == 0)
+ g_debug ("wifi connection list has been frozen");
+
+ self->freeze_count += 1;
+}
+
+void
+cc_wifi_connection_list_thaw (CcWifiConnectionList *self)
+{
+ g_return_if_fail (CC_WIFI_CONNECTION_LIST (self));
+
+ g_return_if_fail (self->freeze_count > 0);
+
+ self->freeze_count -= 1;
+
+ if (self->freeze_count == 0)
+ {
+ g_debug ("wifi connection list has been thawed");
+ update_connections (self);
+ }
+}
+
+GtkListBox *
+cc_wifi_connection_list_get_list_box (CcWifiConnectionList *self)
+{
+ g_return_val_if_fail (CC_IS_WIFI_CONNECTION_LIST (self), NULL);
+
+ return self->listbox;
+}
diff --git a/panels/network/cc-wifi-connection-list.h b/panels/network/cc-wifi-connection-list.h
new file mode 100644
index 0000000..eeec65f
--- /dev/null
+++ b/panels/network/cc-wifi-connection-list.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2018 Red Hat Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <adwaita.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WIFI_CONNECTION_LIST (cc_wifi_connection_list_get_type())
+
+G_DECLARE_FINAL_TYPE (CcWifiConnectionList, cc_wifi_connection_list, CC, WIFI_CONNECTION_LIST, AdwBin)
+
+CcWifiConnectionList *cc_wifi_connection_list_new (NMClient *client,
+ NMDeviceWifi *device,
+ gboolean hide_unavailable,
+ gboolean show_aps,
+ gboolean checkable);
+
+
+void cc_wifi_connection_list_freeze (CcWifiConnectionList *list);
+void cc_wifi_connection_list_thaw (CcWifiConnectionList *list);
+
+GtkListBox *cc_wifi_connection_list_get_list_box (CcWifiConnectionList *self);
+
+G_END_DECLS
diff --git a/panels/network/cc-wifi-connection-row.c b/panels/network/cc-wifi-connection-row.c
new file mode 100644
index 0000000..608dda6
--- /dev/null
+++ b/panels/network/cc-wifi-connection-row.c
@@ -0,0 +1,678 @@
+/*
+ * Copyright © 2018 Red Hat Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <config.h>
+#include "cc-wifi-connection-row.h"
+
+struct _CcWifiConnectionRow
+{
+ AdwActionRow parent_instance;
+
+ gboolean constructed;
+
+ gboolean checkable;
+ gboolean checked;
+
+ NMDeviceWifi *device;
+ GPtrArray *aps;
+ NMConnection *connection;
+ gboolean known_connection;
+
+ GtkLabel *active_label;
+ GtkCheckButton *checkbutton;
+ GtkSpinner *connecting_spinner;
+ GtkImage *encrypted_icon;
+ GtkButton *options_button;
+ GtkImage *strength_icon;
+};
+
+enum
+{
+ PROP_0,
+ PROP_CHECKABLE,
+ PROP_CHECKED,
+ PROP_DEVICE,
+ PROP_APS,
+ PROP_CONNECTION,
+ PROP_KNOWN_CONNECTION,
+ PROP_LAST
+};
+
+typedef enum
+{
+ NM_AP_SEC_UNKNOWN,
+ NM_AP_SEC_NONE,
+ NM_AP_SEC_WEP,
+ NM_AP_SEC_WPA,
+ NM_AP_SEC_WPA2,
+ NM_AP_SEC_SAE,
+ NM_AP_SEC_OWE,
+ NM_AP_SEC_OWE_TM
+} NMAccessPointSecurity;
+
+G_DEFINE_TYPE (CcWifiConnectionRow, cc_wifi_connection_row, ADW_TYPE_ACTION_ROW)
+
+static GParamSpec *props[PROP_LAST];
+
+static void configure_clicked_cb (CcWifiConnectionRow *self);
+
+static NMAccessPointSecurity
+get_access_point_security (NMAccessPoint *ap)
+{
+ NM80211ApFlags flags;
+ NM80211ApSecurityFlags wpa_flags;
+ NM80211ApSecurityFlags rsn_flags;
+ NMAccessPointSecurity type;
+
+ 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);
+
+ if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) &&
+ wpa_flags == NM_802_11_AP_SEC_NONE &&
+ rsn_flags == NM_802_11_AP_SEC_NONE)
+ {
+ type = NM_AP_SEC_NONE;
+ }
+ else if ((flags & NM_802_11_AP_FLAGS_PRIVACY) &&
+ wpa_flags == NM_802_11_AP_SEC_NONE &&
+ rsn_flags == NM_802_11_AP_SEC_NONE)
+ {
+ type = NM_AP_SEC_WEP;
+ }
+ else if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) &&
+ wpa_flags != NM_802_11_AP_SEC_NONE &&
+ rsn_flags != NM_802_11_AP_SEC_NONE)
+ {
+ type = NM_AP_SEC_WPA;
+ }
+#if NM_CHECK_VERSION(1,20,6)
+ else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)
+ {
+ type = NM_AP_SEC_SAE;
+ }
+#endif
+#if NM_CHECK_VERSION(1,24,0)
+ else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE)
+ {
+ type = NM_AP_SEC_OWE;
+ }
+#endif
+#if NM_CHECK_VERSION(1,26,0)
+ else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)
+ {
+ type = NM_AP_SEC_OWE_TM;
+ }
+#endif
+ else
+ {
+ type = NM_AP_SEC_WPA2;
+ }
+
+ return type;
+}
+
+static NMAccessPointSecurity
+get_connection_security (NMConnection *con)
+{
+ NMSettingWirelessSecurity *sws;
+ const gchar *key_mgmt;
+
+ sws = nm_connection_get_setting_wireless_security (con);
+ g_debug ("getting security from %p", sws);
+ if (!sws)
+ return NM_AP_SEC_NONE;
+
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (sws);
+ g_debug ("key management is %s", key_mgmt);
+
+ if (!key_mgmt)
+ return NM_AP_SEC_NONE;
+ else if (g_str_equal (key_mgmt, "none"))
+ return NM_AP_SEC_WEP;
+ else if (g_str_equal (key_mgmt, "ieee8021x"))
+ return NM_AP_SEC_WEP;
+ else if (g_str_equal (key_mgmt, "wpa-eap"))
+ return NM_AP_SEC_WPA2;
+ else if (strncmp (key_mgmt, "wpa-", 4) == 0)
+ return NM_AP_SEC_WPA;
+ else if (g_str_equal (key_mgmt, "sae"))
+ return NM_AP_SEC_SAE;
+ else if (g_str_equal (key_mgmt, "owe"))
+ return NM_AP_SEC_OWE;
+ else
+ return NM_AP_SEC_UNKNOWN;
+}
+
+static void
+update_ui (CcWifiConnectionRow *self)
+{
+ GBytes *ssid;
+ g_autofree gchar *title = NULL;
+ NMActiveConnection *active_connection = NULL;
+ gboolean active;
+ gboolean connecting;
+ NMAccessPointSecurity security = NM_AP_SEC_UNKNOWN;
+ NMAccessPoint *best_ap;
+ guint8 strength = 0;
+ NMActiveConnectionState state;
+
+ g_assert (self->device);
+ g_assert (self->connection || self->aps->len > 0);
+
+ best_ap = cc_wifi_connection_row_best_access_point (self);
+
+ if (self->connection)
+ {
+ active_connection = nm_device_get_active_connection (NM_DEVICE (self->device));
+ if (active_connection &&
+ NM_CONNECTION (nm_active_connection_get_connection (active_connection)) != self->connection)
+ active_connection = NULL;
+ }
+
+ if (self->connection)
+ {
+ NMSettingWireless *sw;
+ const gchar *name = NULL;
+ g_autofree gchar *ssid_str = NULL;
+ gchar *ssid_pos;
+
+ sw = nm_connection_get_setting_wireless (self->connection);
+
+ ssid = nm_setting_wireless_get_ssid (sw);
+ ssid_str = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+ name = nm_connection_get_id (NM_CONNECTION (self->connection));
+
+ ssid_pos = strstr (name, ssid_str);
+ if (ssid_pos == name && strlen (name) == strlen (ssid_str))
+ {
+ title = g_markup_escape_text (name, -1);
+ }
+ else if (ssid_pos)
+ {
+ g_autofree gchar *before = g_strndup (name, ssid_pos - name);
+ g_autofree gchar *after = g_strndup (ssid_pos + strlen (ssid_str), strlen(ssid_pos) - strlen(ssid_str));
+ title = g_markup_printf_escaped ("<i>%s</i>%s<i>%s</i>",
+ before, ssid_str, after);
+ }
+ else
+ {
+ /* TRANSLATORS: This happens when the connection name does not contain the SSID. */
+ title = g_markup_printf_escaped (C_("Wi-Fi Connection", "%s (SSID: %s)"),
+ name, ssid_str);
+ }
+
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self), title);
+ }
+ else
+ {
+ g_autofree char *title_escaped = NULL;
+
+ ssid = nm_access_point_get_ssid (best_ap);
+ title = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+ title_escaped = g_markup_escape_text (title, -1);
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self), title_escaped);
+ }
+
+ if (active_connection)
+ {
+ state = nm_active_connection_get_state (active_connection);
+
+ active = state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED;
+ connecting = state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING;
+ }
+ else
+ {
+ active = FALSE;
+ connecting = FALSE;
+ }
+
+ if (self->connection)
+ security = get_connection_security (self->connection);
+
+ if (best_ap != NULL)
+ {
+ security = get_access_point_security (best_ap);
+ strength = nm_access_point_get_strength (best_ap);
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->connecting_spinner), connecting);
+ if (connecting)
+ {
+ gtk_spinner_start (self->connecting_spinner);
+ }
+ else
+ {
+ gtk_spinner_stop (self->connecting_spinner);
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->active_label), active);
+ gtk_widget_set_visible (GTK_WIDGET (self->options_button), active || connecting || self->known_connection);
+
+ if (security != NM_AP_SEC_UNKNOWN && security != NM_AP_SEC_NONE && security != NM_AP_SEC_OWE && security != NM_AP_SEC_OWE_TM)
+ {
+ const gchar *icon_name = "lock-small-symbolic";
+
+ gtk_widget_set_child_visible (GTK_WIDGET (self->encrypted_icon), TRUE);
+ if (security == NM_AP_SEC_WEP)
+ {
+ icon_name = "warning-small-symbolic";
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Insecure network (WEP)"));
+ }
+ else if (security == NM_AP_SEC_WPA)
+ {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network (WPA)"));
+ }
+ else if (security == NM_AP_SEC_WPA2)
+ {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network (WPA2)"));
+ }
+ else if (security == NM_AP_SEC_SAE)
+ {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network (WPA3)"));
+ }
+ else
+ {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network"));
+ }
+
+ gtk_image_set_from_icon_name (self->encrypted_icon, icon_name);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (GTK_WIDGET (self->encrypted_icon), FALSE);
+ }
+
+ if (best_ap)
+ {
+ gchar *icon_name;
+
+ if (strength < 20)
+ icon_name = "network-wireless-signal-none-symbolic";
+ else if (strength < 40)
+ icon_name = "network-wireless-signal-weak-symbolic";
+ else if (strength < 50)
+ icon_name = "network-wireless-signal-ok-symbolic";
+ else if (strength < 80)
+ icon_name = "network-wireless-signal-good-symbolic";
+ else
+ icon_name = "network-wireless-signal-excellent-symbolic";
+
+ g_object_set (self->strength_icon, "icon-name", icon_name, NULL);
+ gtk_widget_set_child_visible (GTK_WIDGET (self->strength_icon), TRUE);
+ }
+ else
+ {
+ gtk_widget_set_child_visible (GTK_WIDGET (self->strength_icon), FALSE);
+ }
+}
+
+static void
+cc_wifi_connection_row_constructed (GObject *object)
+{
+ CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object);
+
+ G_OBJECT_CLASS (cc_wifi_connection_row_parent_class)->constructed (object);
+
+ /* Reparent the label into the checkbox */
+ gtk_widget_set_visible (GTK_WIDGET (self->checkbutton), self->checkable);
+
+ update_ui (CC_WIFI_CONNECTION_ROW (object));
+}
+
+static void
+cc_wifi_connection_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object);
+ GPtrArray *ptr_array;
+ gint i;
+
+ switch (prop_id)
+ {
+ case PROP_CHECKABLE:
+ g_value_set_boolean (value, self->checkable);
+ break;
+
+ case PROP_CHECKED:
+ g_value_set_boolean (value, self->checked);
+ break;
+
+ case PROP_DEVICE:
+ g_value_set_object (value, self->device);
+ break;
+
+ case PROP_APS:
+ ptr_array = g_ptr_array_new_full (self->aps->len, NULL);
+ for (i = 0; i < self->aps->len; i++)
+ g_ptr_array_add (ptr_array, g_ptr_array_index (self->aps, i));
+
+ g_value_take_boxed (value, ptr_array);
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->connection);
+ break;
+
+ case PROP_KNOWN_CONNECTION:
+ g_value_set_boolean (value, self->known_connection);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wifi_connection_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object);
+ GPtrArray *ptr_array;
+ gint i;
+
+ switch (prop_id)
+ {
+ case PROP_CHECKABLE:
+ self->checkable = g_value_get_boolean (value);
+ break;
+
+ case PROP_CHECKED:
+ self->checked = g_value_get_boolean (value);
+ break;
+
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ case PROP_APS:
+ ptr_array = g_value_get_boxed (value);
+ g_ptr_array_set_size (self->aps, 0);
+
+ if (ptr_array)
+ {
+ for (i = 0; i < ptr_array->len; i++)
+ g_ptr_array_add (self->aps, g_object_ref (g_ptr_array_index (ptr_array, i)));
+ }
+ if (self->constructed)
+ update_ui (self);
+ break;
+
+ case PROP_CONNECTION:
+ self->connection = g_value_dup_object (value);
+ break;
+
+ case PROP_KNOWN_CONNECTION:
+ self->known_connection = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wifi_connection_row_finalize (GObject *object)
+{
+ CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object);
+
+ g_clear_object (&self->device);
+ g_clear_pointer (&self->aps, g_ptr_array_unref);
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (cc_wifi_connection_row_parent_class)->finalize (object);
+}
+
+
+void
+cc_wifi_connection_row_class_init (CcWifiConnectionRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = cc_wifi_connection_row_constructed;
+ object_class->get_property = cc_wifi_connection_row_get_property;
+ object_class->set_property = cc_wifi_connection_row_set_property;
+ object_class->finalize = cc_wifi_connection_row_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/cc-wifi-connection-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, active_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, connecting_spinner);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, encrypted_icon);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, options_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, strength_icon);
+
+ gtk_widget_class_bind_template_callback (widget_class, configure_clicked_cb);
+
+ props[PROP_CHECKABLE] = g_param_spec_boolean ("checkable", "checkable",
+ "Whether to show a checkbox to select the row",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_CHECKED] = g_param_spec_boolean ("checked", "Checked",
+ "Whether the row is selected by checking it",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_DEVICE] = g_param_spec_object ("device", "WiFi Device",
+ "The WiFi Device for this connection/ap",
+ NM_TYPE_DEVICE_WIFI,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_APS] = g_param_spec_boxed ("aps", "Access Points",
+ "The access points for this connection (may be empty if a connection is given)",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_CONNECTION] = g_param_spec_object ("connection", "Connection",
+ "The NMConnection (may be NULL if there is an AP)",
+ NM_TYPE_CONNECTION,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_KNOWN_CONNECTION] = g_param_spec_boolean ("known-connection", "Known Connection",
+ "Whether this row is a known connection or not",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class,
+ PROP_LAST,
+ props);
+
+ g_signal_new ("configure",
+ CC_TYPE_WIFI_CONNECTION_ROW,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+}
+
+static void
+configure_clicked_cb (CcWifiConnectionRow *self)
+{
+ g_signal_emit_by_name (self, "configure");
+}
+
+void
+cc_wifi_connection_row_init (CcWifiConnectionRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->aps = g_ptr_array_new_with_free_func (g_object_unref);
+
+ g_object_bind_property (self, "checked",
+ self->checkbutton, "active",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+}
+
+CcWifiConnectionRow *
+cc_wifi_connection_row_new (NMDeviceWifi *device,
+ NMConnection *connection,
+ GPtrArray *aps,
+ gboolean checkable,
+ gboolean known_connection)
+{
+ return g_object_new (CC_TYPE_WIFI_CONNECTION_ROW,
+ "device", device,
+ "connection", connection,
+ "aps", aps,
+ "checkable", checkable,
+ "known-connection", known_connection,
+ NULL);
+}
+
+gboolean
+cc_wifi_connection_row_get_checkable (CcWifiConnectionRow *self)
+{
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE);
+
+ return self->checkable;
+}
+
+gboolean
+cc_wifi_connection_row_get_checked (CcWifiConnectionRow *self)
+{
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE);
+
+ return self->checked;
+}
+
+NMDeviceWifi*
+cc_wifi_connection_row_get_device (CcWifiConnectionRow *self)
+{
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL);
+
+ return self->device;
+}
+
+const GPtrArray*
+cc_wifi_connection_row_get_access_points (CcWifiConnectionRow *self)
+{
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL);
+
+ return self->aps;
+}
+
+NMConnection*
+cc_wifi_connection_row_get_connection (CcWifiConnectionRow *self)
+{
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL);
+
+ return self->connection;
+}
+
+void
+cc_wifi_connection_row_set_checked (CcWifiConnectionRow *self,
+ gboolean value)
+{
+ g_return_if_fail (CC_WIFI_CONNECTION_ROW (self));
+
+ self->checked = value;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHECKED]);
+}
+
+NMAccessPoint*
+cc_wifi_connection_row_best_access_point (CcWifiConnectionRow *self)
+{
+ NMAccessPoint *best_ap = NULL;
+ NMAccessPoint *active_ap = NULL;
+ guint8 strength = 0;
+ gint i;
+
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL);
+
+ if (self->aps->len == 0)
+ return NULL;
+
+ active_ap = nm_device_wifi_get_active_access_point (self->device);
+
+ for (i = 0; i < self->aps->len; i++)
+ {
+ NMAccessPoint *cur;
+ guint8 cur_strength;
+
+ cur = g_ptr_array_index (self->aps, i);
+
+ /* Prefer the active AP in all cases */
+ if (cur == active_ap)
+ return cur;
+
+ cur_strength = nm_access_point_get_strength (cur);
+ /* Use if we don't have an AP, this is the current AP, or it is better */
+ if (!best_ap || cur_strength > strength)
+ {
+ best_ap = cur;
+ strength = cur_strength;
+ }
+ }
+
+ return best_ap;
+}
+
+void
+cc_wifi_connection_row_add_access_point (CcWifiConnectionRow *self,
+ NMAccessPoint *ap)
+{
+ g_return_if_fail (CC_WIFI_CONNECTION_ROW (self));
+
+ g_ptr_array_add (self->aps, g_object_ref (ap));
+ update_ui (self);
+}
+
+gboolean
+cc_wifi_connection_row_remove_access_point (CcWifiConnectionRow *self,
+ NMAccessPoint *ap)
+{
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE);
+
+ if (!g_ptr_array_remove (self->aps, g_object_ref (ap)))
+ return FALSE;
+
+ /* Object might be invalid; this is alright if it is deleted right away */
+ if (self->aps->len > 0 || self->connection)
+ {
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_APS]);
+ update_ui (self);
+ }
+
+ return self->aps->len == 0;
+}
+
+gboolean
+cc_wifi_connection_row_has_access_point (CcWifiConnectionRow *self,
+ NMAccessPoint *ap)
+{
+ g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE);
+
+ return g_ptr_array_find (self->aps, ap, NULL);
+}
+
+void
+cc_wifi_connection_row_update (CcWifiConnectionRow *self)
+{
+ update_ui (self);
+
+ gtk_list_box_row_changed (GTK_LIST_BOX_ROW (self));
+
+}
+
diff --git a/panels/network/cc-wifi-connection-row.h b/panels/network/cc-wifi-connection-row.h
new file mode 100644
index 0000000..4d6f7ba
--- /dev/null
+++ b/panels/network/cc-wifi-connection-row.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2018 Red Hat Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <adwaita.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _CcWifiConnectionRow CcWifiConnectionRow;
+
+#define CC_TYPE_WIFI_CONNECTION_ROW (cc_wifi_connection_row_get_type ())
+G_DECLARE_FINAL_TYPE (CcWifiConnectionRow, cc_wifi_connection_row, CC, WIFI_CONNECTION_ROW, AdwActionRow)
+
+CcWifiConnectionRow *cc_wifi_connection_row_new (NMDeviceWifi *device,
+ NMConnection *connection,
+ GPtrArray *aps,
+ gboolean checkable,
+ gboolean known_connection);
+
+gboolean cc_wifi_connection_row_get_checkable (CcWifiConnectionRow *row);
+gboolean cc_wifi_connection_row_get_checked (CcWifiConnectionRow *row);
+NMDeviceWifi *cc_wifi_connection_row_get_device (CcWifiConnectionRow *row);
+const GPtrArray *cc_wifi_connection_row_get_access_points (CcWifiConnectionRow *row);
+NMConnection *cc_wifi_connection_row_get_connection (CcWifiConnectionRow *row);
+
+void cc_wifi_connection_row_set_checked (CcWifiConnectionRow *row,
+ gboolean value);
+
+NMAccessPoint *cc_wifi_connection_row_best_access_point (CcWifiConnectionRow *row);
+void cc_wifi_connection_row_add_access_point (CcWifiConnectionRow *row,
+ NMAccessPoint *ap);
+gboolean cc_wifi_connection_row_remove_access_point (CcWifiConnectionRow *row,
+ NMAccessPoint *ap);
+gboolean cc_wifi_connection_row_has_access_point (CcWifiConnectionRow *row,
+ NMAccessPoint *ap);
+
+void cc_wifi_connection_row_update (CcWifiConnectionRow *row);
+G_END_DECLS
diff --git a/panels/network/cc-wifi-connection-row.ui b/panels/network/cc-wifi-connection-row.ui
new file mode 100644
index 0000000..d02dfe6
--- /dev/null
+++ b/panels/network/cc-wifi-connection-row.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="CcWifiConnectionRow" parent="AdwActionRow">
+ <property name="activatable">True</property>
+ <child type="prefix">
+ <object class="GtkBox">
+ <property name="valign">center</property>
+ <property name="margin_end">6</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton">
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="margin_end">6</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="strength_icon">
+ <property name="icon_name">network-wireless-signal-excellent-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="encrypted_icon">
+ <property name="valign">end</property>
+ <property name="pixel-size">8</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child type="suffix">
+ <object class="GtkBox">
+ <property name="valign">center</property>
+ <property name="margin_start">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="active_label">
+ <property name="margin_end">6</property>
+ <property name="label" translatable="yes">Connected</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="connecting_spinner">
+ <property name="name">connecting_spinner</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="options_button">
+ <property name="name">options_button</property>
+ <property name="icon_name">emblem-system-symbolic</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="configure_clicked_cb" object="CcWifiConnectionRow" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Options…</property>
+ </accessibility>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </template>
+</interface>
diff --git a/panels/network/cc-wifi-hotspot-dialog.c b/panels/network/cc-wifi-hotspot-dialog.c
new file mode 100644
index 0000000..0610e24
--- /dev/null
+++ b/panels/network/cc-wifi-hotspot-dialog.c
@@ -0,0 +1,553 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wifi-hotspot-dialog.c
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wifi-hotspot-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "cc-wifi-hotspot-dialog.h"
+#include "cc-network-resources.h"
+#include "ui-helpers.h"
+
+/**
+ * @short_description: WWAN network type selection dialog
+ */
+
+struct _CcWifiHotspotDialog
+{
+ GtkDialog parent_instance;
+
+ GtkLabel *connection_label;
+ GtkEntry *name_entry;
+ GtkEntry *password_entry;
+ GtkLabel *error_label;
+ GtkButton *ok_button;
+
+ GCancellable *cancellable;
+
+ NMDeviceWifi *device;
+ NMConnection *connection;
+ gchar *host_name;
+ gboolean wpa_supported; /* WPA/WPA2 supported */
+};
+
+G_DEFINE_TYPE (CcWifiHotspotDialog, cc_wifi_hotspot_dialog, GTK_TYPE_DIALOG)
+
+static gchar *
+get_random_wpa_key (void)
+{
+ gchar *key;
+ gint i;
+
+ key = g_malloc (10 * sizeof (key));
+ for (i = 0; i < 8; i++)
+ {
+ gint c = 0;
+ /* too many non alphanumeric characters are hard to remember for humans */
+ while (!g_ascii_isalnum (c))
+ c = g_random_int_range (33, 126);
+
+ key[i] = (gchar) c;
+ }
+ key[i] = '\0';
+
+ return key;
+}
+
+static gchar *
+get_random_wep_key (void)
+{
+ const gchar *hexdigits = "0123456789abcdef";
+ gchar *key;
+ gint i;
+
+ key = g_malloc (12 * sizeof (key));
+
+ /* generate a 10-digit hex WEP key */
+ for (i = 0; i < 10; i++)
+ {
+ gint digit;
+ digit = g_random_int_range (0, 16);
+ key[i] = hexdigits[digit];
+ }
+
+ key[i] = '\0';
+
+ return key;
+}
+
+static void
+wifi_hotspot_dialog_update_main_label (CcWifiHotspotDialog *self)
+{
+ NMAccessPoint *ap;
+ GBytes *ssid = NULL;
+ g_autofree gchar *active_ssid = NULL;
+ g_autofree gchar *escape = NULL;
+ g_autofree gchar *ssid_text = NULL;
+ g_autofree gchar *label = NULL;
+
+ g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ gtk_label_set_markup (self->connection_label, "");
+
+ if (!self->device)
+ return;
+
+ ap = nm_device_wifi_get_active_access_point (self->device);
+
+ if (ap)
+ ssid = nm_access_point_get_ssid (ap);
+ if (ssid)
+ active_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+
+ if (!active_ssid || !*active_ssid)
+ return;
+
+ escape = g_markup_escape_text (active_ssid, -1);
+ ssid_text = g_strdup_printf ("<b>%s</b>", escape);
+ /* TRANSLATORS: ‘%s’ is a Wi-Fi Network(SSID) name */
+ label = g_strdup_printf (_("Turning on the hotspot will disconnect from %s, "
+ "and it will not be possible to access the internet through Wi-Fi."), ssid_text);
+ gtk_label_set_markup (self->connection_label, label);
+}
+
+static void
+get_secrets_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ CcWifiHotspotDialog *self;
+ g_autoptr(GVariant) secrets = NULL;
+ NMSettingWirelessSecurity *security_setting;
+ const gchar *key;
+ g_autoptr(GError) error = NULL;
+
+ secrets = nm_remote_connection_get_secrets_finish (NM_REMOTE_CONNECTION (source_object), res, &error);
+ if (!secrets)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get secrets: %s", error->message);
+ return;
+ }
+
+ self = CC_WIFI_HOTSPOT_DIALOG (data);
+
+ nm_connection_update_secrets (self->connection,
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ secrets, &error);
+ if (error)
+ {
+ g_warning ("Error updating secrets: %s", error->message);
+ return;
+ }
+
+ security_setting = nm_connection_get_setting_wireless_security (self->connection);
+ if (self->wpa_supported)
+ key = nm_setting_wireless_security_get_psk (security_setting);
+ else
+ key = nm_setting_wireless_security_get_wep_key (security_setting, 0);
+
+ if (key)
+ gtk_editable_set_text (GTK_EDITABLE (self->password_entry), key);
+
+ nm_connection_clear_secrets (self->connection);
+}
+
+static void
+wifi_hotspot_dialog_update_entries (CcWifiHotspotDialog *self)
+{
+ NMSettingWireless *setting;
+ GBytes *ssid;
+ g_autofree gchar *ssid_text = NULL;
+
+ g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ gtk_editable_set_text (GTK_EDITABLE (self->name_entry), "");
+ gtk_editable_set_text (GTK_EDITABLE (self->password_entry), "");
+
+ if (!self->connection)
+ return;
+
+ setting = nm_connection_get_setting_wireless (self->connection);
+
+ ssid = nm_setting_wireless_get_ssid (setting);
+ ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+
+ if (!ssid_text && self->host_name)
+ ssid_text = g_strdup (self->host_name);
+
+ if (ssid_text)
+ gtk_editable_set_text (GTK_EDITABLE (self->name_entry), ssid_text);
+
+ if (!NM_IS_REMOTE_CONNECTION (self->connection))
+ return;
+
+ /* Secrets may not be already loaded, we have to manually load it. */
+ nm_remote_connection_get_secrets_async (NM_REMOTE_CONNECTION (self->connection),
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ self->cancellable,
+ get_secrets_cb,
+ self);
+}
+
+static gboolean
+hotspot_password_is_valid (CcWifiHotspotDialog *self,
+ const gchar *password)
+{
+ g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ if (!self->device)
+ return FALSE;
+
+ if (!password || !*password)
+ return TRUE;
+
+ if (self->wpa_supported)
+ return nm_utils_wpa_psk_valid (password);
+ else
+ return nm_utils_wep_key_valid (password, NM_WEP_KEY_TYPE_KEY);
+}
+
+static void
+hotspot_entry_changed_cb (CcWifiHotspotDialog *self)
+{
+ const gchar *ssid, *password, *error_label;
+ gboolean valid_ssid, valid_password;
+
+ g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ valid_ssid = valid_password = FALSE;
+ ssid = gtk_editable_get_text (GTK_EDITABLE (self->name_entry));
+ password = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+
+ if (ssid && *ssid)
+ {
+ valid_ssid = TRUE;
+ widget_unset_error (GTK_WIDGET (self->name_entry));
+ }
+ else
+ widget_set_error (GTK_WIDGET (self->name_entry));
+
+ valid_password = hotspot_password_is_valid (self, password);
+
+ if (valid_password)
+ {
+ error_label = "";
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+ }
+ else
+ {
+ if (strlen (password) < 8)
+ {
+ error_label = _("Must have a minimum of 8 characters");
+ }
+ else
+ {
+ guint max_chars = self->wpa_supported ? 63 : 16;
+ error_label = g_strdup_printf (ngettext ("Must have a maximum of %d character",
+ "Must have a maximum of %d characters", max_chars), max_chars);
+ }
+
+ widget_set_error (GTK_WIDGET(self->password_entry));
+ }
+
+ gtk_label_set_label (self->error_label, error_label);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button),
+ valid_ssid && valid_password);
+}
+
+static void
+generate_password_clicked_cb (CcWifiHotspotDialog *self)
+{
+ g_autofree gchar *key = NULL;
+
+ g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ if (self->wpa_supported)
+ key = get_random_wpa_key ();
+ else
+ key = get_random_wep_key ();
+
+ gtk_editable_set_text (GTK_EDITABLE (self->password_entry), key);
+}
+
+static void
+hotspot_update_wireless_settings (CcWifiHotspotDialog *self)
+{
+ NMSettingWireless *setting;
+ g_autoptr(GBytes) ssid = NULL;
+ const gchar *ssid_text;
+ NMDeviceWifiCapabilities capabilities;
+
+ g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ if (nm_connection_get_setting_wireless (self->connection) == NULL)
+ nm_connection_add_setting (self->connection, nm_setting_wireless_new ());
+
+ setting = nm_connection_get_setting_wireless (self->connection);
+
+ capabilities = nm_device_wifi_get_capabilities (self->device);
+ if (capabilities & NM_WIFI_DEVICE_CAP_AP)
+ g_object_set (setting, "mode", "ap", NULL);
+ else
+ g_object_set (setting, "mode", "adhoc", NULL);
+
+ ssid_text = gtk_editable_get_text (GTK_EDITABLE (self->name_entry));
+ ssid = g_bytes_new (ssid_text, strlen (ssid_text));
+ g_object_set (setting, "ssid", ssid, NULL);
+}
+
+static void
+hotspot_update_wireless_security_settings (CcWifiHotspotDialog *self)
+{
+ NMSettingWirelessSecurity *setting;
+ const gchar *value, *key_type;
+
+ g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ if (nm_connection_get_setting_wireless_security (self->connection) == NULL)
+ nm_connection_add_setting (self->connection, nm_setting_wireless_security_new ());
+
+ setting = nm_connection_get_setting_wireless_security (self->connection);
+ nm_setting_wireless_security_clear_protos (setting);
+ nm_setting_wireless_security_clear_pairwise (setting);
+ nm_setting_wireless_security_clear_groups (setting);
+ value = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+
+ if (self->wpa_supported)
+ key_type = "psk";
+ else
+ key_type = "wep-key0";
+
+ if (self->wpa_supported)
+ g_object_set (setting, "key-mgmt", "wpa-psk", NULL);
+ else
+ g_object_set (setting,
+ "key-mgmt", "none",
+ "wep-key-type", NM_WEP_KEY_TYPE_KEY,
+ NULL);
+
+ if (!value || !*value)
+ {
+ g_autofree gchar *key = NULL;
+
+ if (self->wpa_supported)
+ key = get_random_wpa_key ();
+ else
+ key = get_random_wep_key ();
+
+ g_object_set (setting, key_type, key, NULL);
+ }
+ else
+ g_object_set (setting, key_type, value, NULL);
+
+ if (self->wpa_supported)
+ {
+ NMDeviceWifiCapabilities caps;
+
+ caps = nm_device_wifi_get_capabilities (self->device);
+
+ if (caps & NM_WIFI_DEVICE_CAP_RSN)
+ {
+ nm_setting_wireless_security_add_proto (setting, "rsn");
+ nm_setting_wireless_security_add_pairwise (setting, "ccmp");
+ nm_setting_wireless_security_add_group (setting, "ccmp");
+ }
+ else if (caps & NM_WIFI_DEVICE_CAP_WPA)
+ {
+ nm_setting_wireless_security_add_proto (setting, "wpa");
+ nm_setting_wireless_security_add_pairwise (setting, "tkip");
+ nm_setting_wireless_security_add_group (setting, "tkip");
+ }
+ }
+}
+
+static void
+cc_wifi_hotspot_dialog_finalize (GObject *object)
+{
+ CcWifiHotspotDialog *self = (CcWifiHotspotDialog *)object;
+
+ g_cancellable_cancel(self->cancellable);
+ g_clear_object (&self->cancellable);
+ g_clear_pointer (&self->host_name, g_free);
+ g_clear_object (&self->device);
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (cc_wifi_hotspot_dialog_parent_class)->finalize (object);
+}
+
+static void
+cc_wifi_hotspot_dialog_show (GtkWidget *widget)
+{
+ CcWifiHotspotDialog *self = (CcWifiHotspotDialog *)widget;
+ g_warn_if_fail (self->device != NULL);
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->ok_button));
+ wifi_hotspot_dialog_update_entries (self);
+
+ if (!self->connection)
+ if (self->host_name)
+ gtk_editable_set_text (GTK_EDITABLE (self->name_entry), self->host_name);
+
+ GTK_WIDGET_CLASS (cc_wifi_hotspot_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wifi_hotspot_dialog_response (GtkDialog *dialog,
+ gint response_id)
+{
+ CcWifiHotspotDialog *self = CC_WIFI_HOTSPOT_DIALOG (dialog);
+ NMSetting *setting;
+
+ if (response_id != GTK_RESPONSE_APPLY)
+ return;
+
+ if (!self->connection)
+ self->connection = NM_CONNECTION (nm_simple_connection_new ());
+
+ if (nm_connection_get_setting_connection (self->connection) == NULL)
+ {
+ setting = nm_setting_connection_new ();
+ g_object_set (setting,
+ "type", "802-11-wireless",
+ "id", "Hotspot",
+ "autoconnect", FALSE,
+ NULL);
+ nm_connection_add_setting (self->connection, setting);
+ }
+
+ if (nm_connection_get_setting_ip4_config (self->connection) == NULL)
+ {
+ setting = nm_setting_ip4_config_new ();
+ g_object_set (setting, "method", "shared", NULL);
+ nm_connection_add_setting (self->connection, setting);
+ }
+
+ hotspot_update_wireless_settings (self);
+ hotspot_update_wireless_security_settings (self);
+}
+
+static void
+cc_wifi_hotspot_dialog_class_init (CcWifiHotspotDialogClass *klass)
+{
+ GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_wifi_hotspot_dialog_finalize;
+
+ widget_class->show = cc_wifi_hotspot_dialog_show;
+ dialog_class->response = cc_wifi_hotspot_dialog_response;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/network/cc-wifi-hotspot-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, connection_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, name_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, error_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, ok_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, hotspot_entry_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, generate_password_clicked_cb);
+}
+
+static void
+cc_wifi_hotspot_dialog_init (CcWifiHotspotDialog *self)
+{
+ self->cancellable = g_cancellable_new ();
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcWifiHotspotDialog *
+cc_wifi_hotspot_dialog_new (GtkWindow *parent_window)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+
+ return g_object_new (CC_TYPE_WIFI_HOTSPOT_DIALOG,
+ "use-header-bar", TRUE,
+ "transient-for", parent_window,
+ NULL);
+}
+
+void
+cc_wifi_hotspot_dialog_set_hostname (CcWifiHotspotDialog *self,
+ const gchar *host_name)
+{
+ g_return_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+
+ g_clear_pointer (&self->host_name, g_free);
+ self->host_name = g_strdup (host_name);
+}
+
+void
+cc_wifi_hotspot_dialog_set_device (CcWifiHotspotDialog *self,
+ NMDeviceWifi *device)
+{
+ g_return_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+ g_return_if_fail (NM_IS_DEVICE_WIFI (device));
+
+ g_set_object (&self->device, device);
+
+ if (device)
+ {
+ NMDeviceWifiCapabilities caps;
+
+ caps = nm_device_wifi_get_capabilities (device);
+ self->wpa_supported = FALSE;
+
+ if (caps & NM_WIFI_DEVICE_CAP_AP)
+ if (caps & (NM_WIFI_DEVICE_CAP_RSN | NM_WIFI_DEVICE_CAP_WPA))
+ self->wpa_supported = TRUE;
+ }
+
+ wifi_hotspot_dialog_update_main_label (self);
+}
+
+NMConnection *
+cc_wifi_hotspot_dialog_get_connection (CcWifiHotspotDialog *self)
+{
+ g_return_val_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self), NULL);
+
+ return self->connection;
+}
+
+void
+cc_wifi_hotspot_dialog_set_connection (CcWifiHotspotDialog *self,
+ NMConnection *connection)
+{
+ NMSettingWireless *setting;
+
+ g_return_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self));
+ g_return_if_fail (NM_IS_CONNECTION (connection));
+
+ setting = nm_connection_get_setting_wireless (connection);
+ g_return_if_fail (setting);
+
+ g_set_object (&self->connection, connection);
+}
diff --git a/panels/network/cc-wifi-hotspot-dialog.h b/panels/network/cc-wifi-hotspot-dialog.h
new file mode 100644
index 0000000..29a326d
--- /dev/null
+++ b/panels/network/cc-wifi-hotspot-dialog.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* wifi-hotspot-dialog.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WIFI_HOTSPOT_DIALOG (cc_wifi_hotspot_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWifiHotspotDialog, cc_wifi_hotspot_dialog, CC, WIFI_HOTSPOT_DIALOG, GtkDialog)
+
+CcWifiHotspotDialog *cc_wifi_hotspot_dialog_new (GtkWindow *parent_window);
+void cc_wifi_hotspot_dialog_set_hostname (CcWifiHotspotDialog *self,
+ const gchar *host_name);
+void cc_wifi_hotspot_dialog_set_device (CcWifiHotspotDialog *self,
+ NMDeviceWifi *device);
+NMConnection *cc_wifi_hotspot_dialog_get_connection (CcWifiHotspotDialog *self);
+void cc_wifi_hotspot_dialog_set_connection (CcWifiHotspotDialog *self,
+ NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/cc-wifi-hotspot-dialog.ui b/panels/network/cc-wifi-hotspot-dialog.ui
new file mode 100644
index 0000000..1a69c46
--- /dev/null
+++ b/panels/network/cc-wifi-hotspot-dialog.ui
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWifiHotspotDialog" parent="GtkDialog">
+ <property name="title" translatable="yes">Turn On Wi-Fi Hotspot?</property>
+ <property name="modal">1</property>
+ <property name="destroy-with-parent">1</property>
+ <property name="hide-on-close">True</property>
+
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar">
+ <property name="show-title-buttons">0</property>
+ </object>
+ </child>
+
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="margin-top">30</property>
+ <property name="margin-bottom">30</property>
+ <property name="margin-start">30</property>
+ <property name="margin-end">30</property>
+ <property name="spacing">20</property>
+
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label" translatable="yes">Wi-Fi hotspot allows others to share your internet connection, by creating a Wi-Fi network that they can connect to. To do this, you must have an internet connection through a source other than Wi-Fi.</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="connection_label">
+ <property name="margin-bottom">18</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">40</property>
+ <property name="use-markup">1</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkGrid">
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">12</property>
+
+ <!-- Hotspot SSID Name -->
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Network Name</property>
+ <property name="halign">end</property>
+ <property name="mnemonic_widget">name_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="hexpand">1</property>
+ <property name="max-length">64</property>
+ <signal name="changed" handler="hotspot_entry_changed_cb" swapped="yes" />
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+
+ <!-- Hotspot Password -->
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Password</property>
+ <property name="halign">end</property>
+ <property name="mnemonic_widget">password_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="max-length">64</property>
+ <property name="secondary-icon-name">view-refresh-symbolic</property>
+ <property name="secondary-icon-tooltip-text" translatable="yes">Generate Random Password</property>
+ <property name="placeholder-text" translatable="yes">Autogenerate Password</property>
+ <signal name="icon-press" handler="generate_password_clicked_cb" swapped="yes" />
+ <signal name="changed" handler="hotspot_entry_changed_cb" swapped="yes" />
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+
+ <!-- Error Label -->
+ <child>
+ <object class="GtkLabel" id="error_label">
+ <property name="halign">start</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.83"/>
+ </attributes>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="ok_button">
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Turn On</property>
+ </object>
+ </child>
+
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <action-widget response="apply" default="true">ok_button</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/panels/network/cc-wifi-panel.c b/panels/network/cc-wifi-panel.c
new file mode 100644
index 0000000..e53bae7
--- /dev/null
+++ b/panels/network/cc-wifi-panel.c
@@ -0,0 +1,1079 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2017 Georges Basile Stavracas Neto <georges.stavracas@gmail.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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "cc-network-resources.h"
+#include "cc-wifi-panel.h"
+#include "cc-qr-code.h"
+#include "net-device-wifi.h"
+#include "network-dialogs.h"
+#include "panel-common.h"
+#include "cc-list-row.h"
+
+#include "shell/cc-application.h"
+#include "shell/cc-debug.h"
+#include "shell/cc-object-storage.h"
+
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#define QR_IMAGE_SIZE 180
+
+typedef enum
+{
+ OPERATION_NULL,
+ OPERATION_SHOW_DEVICE,
+ OPERATION_CREATE_WIFI,
+ OPERATION_CONNECT_HIDDEN,
+ OPERATION_CONNECT_8021X
+} CmdlineOperation;
+
+struct _CcWifiPanel
+{
+ CcPanel parent;
+
+ /* RFKill (Airplane Mode) */
+ GDBusProxy *rfkill_proxy;
+ CcListRow *rfkill_row;
+ GtkWidget *rfkill_widget;
+
+ /* Main widgets */
+ GtkStack *center_stack;
+ GtkStack *header_stack;
+ GtkBox *hotspot_box;
+ GtkLabel *list_label;
+ GtkStack *main_stack;
+ GtkWidget *spinner;
+ GtkStack *stack;
+ GtkPicture *wifi_qr_image;
+ CcQrCode *qr_code;
+
+ NMClient *client;
+
+ GPtrArray *devices;
+
+ GBinding *spinner_binding;
+
+ /* Command-line arguments */
+ CmdlineOperation arg_operation;
+ gchar *arg_device;
+ gchar *arg_access_point;
+};
+
+static void rfkill_switch_notify_activate_cb (CcListRow *rfkill_row,
+ GParamSpec *pspec,
+ CcWifiPanel *self);
+
+static void update_devices_names (CcWifiPanel *self);
+
+G_DEFINE_TYPE (CcWifiPanel, cc_wifi_panel, CC_TYPE_PANEL)
+
+enum
+{
+ PROP_0,
+ PROP_PARAMETERS,
+ N_PROPS
+};
+
+/* Static init function */
+
+static void
+update_panel_visibility (NMClient *client)
+{
+ const GPtrArray *devices;
+ CcApplication *application;
+ gboolean visible;
+ guint i;
+
+ CC_TRACE_MSG ("Updating Wi-Fi panel visibility");
+
+ devices = nm_client_get_devices (client);
+ visible = FALSE;
+
+ for (i = 0; devices && i < devices->len; i++)
+ {
+ NMDevice *device = g_ptr_array_index (devices, i);
+
+ visible |= NM_IS_DEVICE_WIFI (device);
+
+ if (visible)
+ break;
+ }
+
+ /* Set the new visibility */
+ application = CC_APPLICATION (g_application_get_default ());
+ cc_shell_model_set_panel_visibility (cc_application_get_model (application),
+ "wifi",
+ visible ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH);
+
+ g_debug ("Wi-Fi panel visible: %s", visible ? "yes" : "no");
+}
+
+void
+cc_wifi_panel_static_init_func (void)
+{
+ g_autoptr(NMClient) client = NULL;
+
+ g_debug ("Monitoring NetworkManager for Wi-Fi devices");
+
+ /* Create and store a NMClient instance if it doesn't exist yet */
+ if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT))
+ {
+ g_autoptr(NMClient) new_client = nm_client_new (NULL, NULL);
+ cc_object_storage_add_object (CC_OBJECT_NMCLIENT, new_client);
+ }
+
+ client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT);
+
+ /* Update the panel visibility and monitor for changes */
+
+ g_signal_connect (client, "device-added", G_CALLBACK (update_panel_visibility), NULL);
+ g_signal_connect (client, "device-removed", G_CALLBACK (update_panel_visibility), NULL);
+
+ update_panel_visibility (client);
+}
+
+/* Auxiliary methods */
+
+static gchar *
+escape_string (const gchar *str,
+ gboolean quote)
+{
+ GString *string;
+ const char *next;
+
+ if (!str)
+ return NULL;
+
+ string = g_string_new ("");
+ if (quote)
+ g_string_append_c (string, '"');
+
+ while ((next = strpbrk (str, "\\;,:\"")))
+ {
+ g_string_append_len (string, str, next - str);
+ g_string_append_c (string, '\\');
+ g_string_append_c (string, *next);
+ str = next + 1;
+ }
+
+ g_string_append (string, str);
+ if (quote)
+ g_string_append_c (string, '"');
+
+ return g_string_free (string, FALSE);
+}
+
+static const gchar *
+get_connection_security_type (NMConnection *c)
+{
+ NMSettingWirelessSecurity *setting;
+ const char *key_mgmt;
+
+ g_return_val_if_fail (c, "nopass");
+
+ setting = nm_connection_get_setting_wireless_security (c);
+
+ if (!setting)
+ return "nopass";
+
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting);
+
+ /* No IEEE 802.1x */
+ if (g_strcmp0 (key_mgmt, "none") == 0)
+ return "WEP";
+
+ if (g_strcmp0 (key_mgmt, "wpa-none") == 0 ||
+ g_strcmp0 (key_mgmt, "wpa-psk") == 0)
+ return "WPA";
+
+ return "nopass";
+}
+
+static gchar *
+get_wifi_password (NMConnection *c)
+{
+ NMSettingWirelessSecurity *setting;
+ g_autoptr(GVariant) secrets = NULL;
+ g_autoptr(GError) error = NULL;
+ const gchar *sec_type, *password;
+ gint wep_index;
+
+ g_assert (NM_IS_REMOTE_CONNECTION (c));
+
+ sec_type = get_connection_security_type (c);
+ setting = nm_connection_get_setting_wireless_security (c);
+
+ if (g_str_equal (sec_type, "nopass"))
+ return NULL;
+
+ secrets = nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (c),
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ NULL, &error);
+ if (!error)
+ nm_connection_update_secrets (c,
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ secrets, &error);
+ if (error)
+ {
+ g_warning ("Error: %s", error->message);
+ return NULL;
+ }
+
+ if (g_str_equal (sec_type, "WEP"))
+ {
+ wep_index = nm_setting_wireless_security_get_wep_tx_keyidx (setting);
+ password = nm_setting_wireless_security_get_wep_key (setting, wep_index);
+ }
+ else
+ {
+ password = nm_setting_wireless_security_get_psk (setting);
+ }
+
+ return escape_string (password, FALSE);
+}
+
+/* Generate a string representing the hotspot @connection
+ * An example generated text:
+ * WIFI:S:hotspot;T:WPA;P:my-valid-pass;H:true;
+ * Where,
+ * S = ssid, T = security, P = password, H = hidden (Optional)
+ *
+ * See https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11
+ */
+static gchar *
+get_qr_string_for_hotspot (NMClient *nm_client,
+ NMConnection *c)
+{
+ NMSettingWireless *setting;
+ g_autofree char *ssid_text = NULL;
+ g_autofree char *escaped_ssid = NULL;
+ g_autofree char *password_str = NULL;
+ GString *string;
+ GBytes *ssid;
+ gboolean hidden;
+
+ g_assert (NM_IS_CLIENT (nm_client));
+ g_assert (NM_IS_REMOTE_CONNECTION (c));
+
+ setting = nm_connection_get_setting_wireless (c);
+ ssid = nm_setting_wireless_get_ssid (setting);
+
+ if (!ssid)
+ return NULL;
+
+ string = g_string_new ("WIFI:S:");
+
+ /* SSID */
+ ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
+ g_bytes_get_size (ssid));
+ escaped_ssid = escape_string (ssid_text, FALSE);
+ g_string_append (string, escaped_ssid);
+ g_string_append_c (string, ';');
+
+ /* Security type */
+ g_string_append (string, "T:");
+ g_string_append (string, get_connection_security_type (c));
+ g_string_append_c (string, ';');
+
+ /* Password */
+ g_string_append (string, "P:");
+ password_str = get_wifi_password (c);
+ if (password_str)
+ g_string_append (string, password_str);
+ g_string_append_c (string, ';');
+
+ /* WiFi Hidden */
+ hidden = nm_setting_wireless_get_hidden (setting);
+ if (hidden)
+ g_string_append (string, "H:true");
+ g_string_append_c (string, ';');
+
+ return g_string_free (string, FALSE);
+}
+
+static NMConnection *
+wifi_device_get_hotspot (CcWifiPanel *self,
+ NMDevice *device)
+{
+ NMSettingIPConfig *ip4_setting;
+ NMConnection *c;
+
+ g_assert (CC_IS_WIFI_PANEL (self));
+ g_assert (NM_IS_DEVICE (device));
+
+ if (nm_device_get_active_connection (device) == NULL)
+ return NULL;
+
+ c = net_device_get_find_connection (self->client, device);
+ if (c == NULL)
+ return NULL;
+
+ ip4_setting = nm_connection_get_setting_ip4_config (c);
+ if (g_strcmp0 (nm_setting_ip_config_get_method (ip4_setting),
+ NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0)
+ return NULL;
+
+ return c;
+}
+
+static void
+wifi_panel_update_qr_image_cb (CcWifiPanel *self)
+{
+ NetDeviceWifi *child;
+ NMConnection *hotspot;
+ NMDevice *device;
+
+ g_assert (CC_IS_WIFI_PANEL (self));
+
+ child = NET_DEVICE_WIFI (gtk_stack_get_visible_child (self->stack));
+ device = net_device_wifi_get_device (child);
+ hotspot = wifi_device_get_hotspot (self, device);
+
+ if (hotspot)
+ {
+ g_autofree gchar *str = NULL;
+
+ if (!self->qr_code)
+ self->qr_code = cc_qr_code_new ();
+
+ str = get_qr_string_for_hotspot (self->client, hotspot);
+ if (cc_qr_code_set_text (self->qr_code, str))
+ {
+ GdkPaintable *paintable;
+ gint scale;
+
+ scale = gtk_widget_get_scale_factor (GTK_WIDGET (self->wifi_qr_image));
+ paintable = cc_qr_code_get_paintable (self->qr_code, QR_IMAGE_SIZE * scale);
+ gtk_picture_set_paintable (self->wifi_qr_image, paintable);
+ }
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->hotspot_box), hotspot != NULL);
+ gtk_widget_set_opacity (GTK_WIDGET (self->list_label), hotspot == NULL);
+ gtk_widget_set_opacity (GTK_WIDGET (self->spinner), hotspot == NULL);
+}
+
+static void
+add_wifi_device (CcWifiPanel *self,
+ NMDevice *device)
+{
+ GtkWidget *header_widget;
+ NetDeviceWifi *net_device;
+
+ /* Create the NetDevice */
+ net_device = net_device_wifi_new (CC_PANEL (self),
+ self->client,
+ device);
+ gtk_widget_show (GTK_WIDGET (net_device));
+
+ /* And add to the header widgets */
+ header_widget = net_device_wifi_get_header_widget (net_device);
+
+ gtk_stack_add_named (self->header_stack, header_widget, nm_device_get_udi (device));
+
+ /* Setup custom title properties */
+ g_ptr_array_add (self->devices, net_device);
+
+ update_devices_names (self);
+
+ /* Needs to be added after the device is added to the self->devices array */
+ gtk_stack_add_titled (self->stack, GTK_WIDGET (net_device),
+ nm_device_get_udi (device),
+ nm_device_get_description (device));
+ g_signal_connect_object (device, "state-changed",
+ G_CALLBACK (wifi_panel_update_qr_image_cb),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+remove_wifi_device (CcWifiPanel *self,
+ NMDevice *device)
+{
+ GtkWidget *child;
+ const gchar *id;
+ guint i;
+
+ id = nm_device_get_udi (device);
+
+ /* Remove from the devices list */
+ for (i = 0; i < self->devices->len; i++)
+ {
+ NetDeviceWifi *net_device = g_ptr_array_index (self->devices, i);
+
+ if (net_device_wifi_get_device (net_device) == device)
+ {
+ g_ptr_array_remove (self->devices, net_device);
+ break;
+ }
+ }
+
+ /* Disconnect the signal to prevent assertion crash */
+ g_signal_handlers_disconnect_by_func (device,
+ G_CALLBACK (wifi_panel_update_qr_image_cb),
+ self);
+
+ /* Destroy all stack pages related to this device */
+ child = gtk_stack_get_child_by_name (self->stack, id);
+ gtk_stack_remove (self->stack, child);
+
+ child = gtk_stack_get_child_by_name (self->header_stack, id);
+ gtk_stack_remove (self->header_stack, child);
+
+ /* Update the title widget */
+ update_devices_names (self);
+}
+
+static void
+check_main_stack_page (CcWifiPanel *self)
+{
+ const gchar *nm_version;
+ gboolean airplane_mode_active;
+ gboolean wireless_enabled;
+
+ nm_version = nm_client_get_version (self->client);
+ wireless_enabled = nm_client_wireless_get_enabled (self->client);
+ airplane_mode_active = cc_list_row_get_active (self->rfkill_row);
+
+ if (!nm_version)
+ gtk_stack_set_visible_child_name (self->main_stack, "nm-not-running");
+ else if (!wireless_enabled && airplane_mode_active)
+ gtk_stack_set_visible_child_name (self->main_stack, "airplane-mode");
+ else if (!wireless_enabled || self->devices->len == 0)
+ gtk_stack_set_visible_child_name (self->main_stack, "no-wifi-devices");
+ else
+ gtk_stack_set_visible_child_name (self->main_stack, "wifi-connections");
+}
+
+static void
+load_wifi_devices (CcWifiPanel *self)
+{
+ const GPtrArray *devices;
+ guint i;
+
+ devices = nm_client_get_devices (self->client);
+
+ /* Cold-plug existing devices */
+ if (devices)
+ {
+ for (i = 0; i < devices->len; i++)
+ {
+ NMDevice *device;
+
+ device = g_ptr_array_index (devices, i);
+ if (!NM_IS_DEVICE_WIFI (device) || !nm_device_get_managed (device))
+ continue;
+ add_wifi_device (self, device);
+ }
+ }
+
+ check_main_stack_page (self);
+}
+
+static inline gboolean
+get_cached_rfkill_property (CcWifiPanel *self,
+ const gchar *property)
+{
+ g_autoptr(GVariant) result = NULL;
+
+ result = g_dbus_proxy_get_cached_property (self->rfkill_proxy, property);
+ return result ? g_variant_get_boolean (result) : FALSE;
+}
+
+static void
+sync_airplane_mode_switch (CcWifiPanel *self)
+{
+ gboolean enabled, should_show, hw_enabled;
+
+ enabled = get_cached_rfkill_property (self, "HasAirplaneMode");
+ should_show = get_cached_rfkill_property (self, "ShouldShowAirplaneMode");
+
+ gtk_widget_set_visible (GTK_WIDGET (self->rfkill_widget), enabled && should_show);
+ if (!enabled || !should_show)
+ return;
+
+ enabled = get_cached_rfkill_property (self, "AirplaneMode");
+ hw_enabled = get_cached_rfkill_property (self, "HardwareAirplaneMode");
+
+ enabled |= hw_enabled;
+
+ if (enabled != cc_list_row_get_active (self->rfkill_row))
+ {
+ g_signal_handlers_block_by_func (self->rfkill_row,
+ rfkill_switch_notify_activate_cb,
+ self);
+ g_object_set (self->rfkill_row, "active", enabled, NULL);
+ check_main_stack_page (self);
+ g_signal_handlers_unblock_by_func (self->rfkill_row,
+ rfkill_switch_notify_activate_cb,
+ self);
+ }
+
+ cc_list_row_set_switch_sensitive (self->rfkill_row, !hw_enabled);
+
+ check_main_stack_page (self);
+}
+
+static void
+update_devices_names (CcWifiPanel *self)
+{
+ guint number_of_devices = self->devices->len;
+
+ if (number_of_devices == 1)
+ {
+ GtkWidget *title_widget;
+ NetDeviceWifi *net_device;
+
+ net_device = g_ptr_array_index (self->devices, 0);
+ title_widget = net_device_wifi_get_title_widget (net_device);
+
+ gtk_stack_add_named (self->center_stack, title_widget, "single");
+ gtk_stack_set_visible_child_name (self->center_stack, "single");
+
+ net_device_wifi_set_title (net_device, _("Wi-Fi"));
+ }
+ else
+ {
+ GtkWidget *single_page_widget;
+ guint i;
+
+ for (i = 0; i < number_of_devices; i++)
+ {
+ NetDeviceWifi *net_device;
+ NMDevice *device;
+
+ net_device = g_ptr_array_index (self->devices, i);
+ device = net_device_wifi_get_device (net_device);
+
+ net_device_wifi_set_title (net_device, nm_device_get_description (device));
+ }
+
+ /* Remove the widget at the "single" page */
+ single_page_widget = gtk_stack_get_child_by_name (self->center_stack, "single");
+
+ if (single_page_widget)
+ {
+ g_object_ref (single_page_widget);
+ gtk_stack_remove (self->center_stack, single_page_widget);
+ g_object_unref (single_page_widget);
+ }
+
+ /* Show the stack-switcher page */
+ gtk_stack_set_visible_child_name (self->center_stack, "many");
+ }
+}
+
+/* Command-line arguments */
+
+static void
+reset_command_line_args (CcWifiPanel *self)
+{
+ self->arg_operation = OPERATION_NULL;
+ g_clear_pointer (&self->arg_device, g_free);
+ g_clear_pointer (&self->arg_access_point, g_free);
+}
+
+static gboolean
+handle_argv_for_device (CcWifiPanel *self, NetDeviceWifi *net_device)
+{
+ GtkWidget *toplevel;
+ NMDevice *device;
+ gboolean ret;
+
+ toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)));
+ device = net_device_wifi_get_device (net_device);
+ ret = FALSE;
+
+ if (self->arg_operation == OPERATION_CREATE_WIFI)
+ {
+ cc_network_panel_create_wifi_network (toplevel, self->client);
+ ret = TRUE;
+ }
+ else if (self->arg_operation == OPERATION_CONNECT_HIDDEN)
+ {
+ cc_network_panel_connect_to_hidden_network (toplevel, self->client);
+ ret = TRUE;
+ }
+ else if (g_str_equal (nm_object_get_path (NM_OBJECT (device)), self->arg_device))
+ {
+ if (self->arg_operation == OPERATION_CONNECT_8021X)
+ {
+ cc_network_panel_connect_to_8021x_network (toplevel,
+ self->client,
+ device,
+ self->arg_access_point);
+ ret = TRUE;
+ }
+ else if (self->arg_operation == OPERATION_SHOW_DEVICE)
+ {
+ gtk_stack_set_visible_child_name (self->stack, nm_device_get_udi (device));
+ ret = TRUE;
+ }
+ }
+
+ if (ret)
+ reset_command_line_args (self);
+
+ return ret;
+}
+
+static void
+handle_argv (CcWifiPanel *self)
+{
+ guint i;
+
+ if (self->arg_operation == OPERATION_NULL)
+ return;
+
+ for (i = 0; i < self->devices->len; i++)
+ {
+ if (handle_argv_for_device (self, g_ptr_array_index (self->devices, i)))
+ break;
+ }
+}
+
+static GPtrArray *
+variant_av_to_string_array (GVariant *array)
+{
+ GVariantIter iter;
+ GVariant *v;
+ GPtrArray *strv;
+ gsize count;
+
+ count = g_variant_iter_init (&iter, array);
+ strv = g_ptr_array_sized_new (count + 1);
+
+ while (g_variant_iter_next (&iter, "v", &v))
+ {
+ g_ptr_array_add (strv, (gpointer) g_variant_get_string (v, NULL));
+ g_variant_unref (v);
+ }
+
+ g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */
+ return strv;
+}
+
+static gboolean
+verify_argv (CcWifiPanel *self,
+ const char **args)
+{
+ switch (self->arg_operation)
+ {
+ case OPERATION_CONNECT_8021X:
+ case OPERATION_SHOW_DEVICE:
+ if (!self->arg_device)
+ {
+ g_warning ("Operation %s requires an object path", args[0]);
+ return FALSE;
+ }
+ default:
+ return TRUE;
+ }
+}
+
+/* Callbacks */
+
+static void
+device_state_changed_cb (CcWifiPanel *self, GParamSpec *pspec, NMDevice *device)
+{
+ const gchar *id;
+
+ id = nm_device_get_udi (device);
+ /* Don't add a device that has already been added */
+ if (!NM_IS_DEVICE_WIFI (device) || !id)
+ return;
+
+ if (nm_device_get_managed (device))
+ {
+ if (gtk_stack_get_child_by_name (self->stack, id))
+ return;
+ add_wifi_device (self, device);
+ check_main_stack_page (self);
+ }
+ else
+ {
+ if (!gtk_stack_get_child_by_name (self->stack, id))
+ return;
+ remove_wifi_device (self, device);
+ check_main_stack_page (self);
+ }
+}
+
+static void
+device_added_cb (CcWifiPanel *self, NMDevice *device)
+{
+ if (!NM_IS_DEVICE_WIFI (device))
+ return;
+
+ if (nm_device_get_managed (device))
+ {
+ add_wifi_device (self, device);
+ check_main_stack_page (self);
+ }
+
+ g_signal_connect_object (device,
+ "notify::state",
+ G_CALLBACK (device_state_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+device_removed_cb (CcWifiPanel *self, NMDevice *device)
+{
+ const gchar *id;
+
+ if (!NM_IS_DEVICE_WIFI (device))
+ return;
+
+ id = nm_device_get_udi (device);
+ /* Don't remove a device that has already been removed */
+ if (!gtk_stack_get_child_by_name (self->stack, id))
+ return;
+
+ remove_wifi_device (self, device);
+ check_main_stack_page (self);
+
+ g_signal_handlers_disconnect_by_func (device,
+ G_CALLBACK (device_state_changed_cb),
+ self);
+}
+
+static void
+wireless_enabled_cb (CcWifiPanel *self)
+{
+ check_main_stack_page (self);
+}
+
+static void
+on_rfkill_proxy_properties_changed_cb (CcWifiPanel *self)
+{
+ g_debug ("Rfkill properties changed");
+
+ sync_airplane_mode_switch (self);
+}
+
+static void
+rfkill_proxy_acquired_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CcWifiPanel *self;
+ GDBusProxy *proxy;
+ g_autoptr(GError) error = NULL;
+
+ proxy = cc_object_storage_create_dbus_proxy_finish (res, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_printerr ("Error creating rfkill proxy: %s\n", error->message);
+
+ return;
+ }
+
+ self = CC_WIFI_PANEL (user_data);
+
+ self->rfkill_proxy = proxy;
+
+ g_signal_connect_object (proxy,
+ "g-properties-changed",
+ G_CALLBACK (on_rfkill_proxy_properties_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ sync_airplane_mode_switch (self);
+}
+
+static void
+rfkill_switch_notify_activate_cb (CcListRow *row,
+ GParamSpec *pspec,
+ CcWifiPanel *self)
+{
+ gboolean enable;
+
+ enable = cc_list_row_get_active (row);
+
+ g_dbus_proxy_call (self->rfkill_proxy,
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill',"
+ "'AirplaneMode', %v)",
+ g_variant_new_boolean (enable)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ NULL,
+ NULL);
+}
+
+static void
+on_stack_visible_child_changed_cb (GtkStack *stack,
+ GParamSpec *pspec,
+ CcWifiPanel *self)
+{
+ const gchar *visible_device_id = NULL;
+ guint i;
+
+ wifi_panel_update_qr_image_cb (self);
+
+ /* Remove previous bindings */
+ g_clear_pointer (&self->spinner_binding, g_binding_unbind);
+
+ visible_device_id = gtk_stack_get_visible_child_name (stack);
+ for (i = 0; i < self->devices->len; i++)
+ {
+ NetDeviceWifi *net_device = g_ptr_array_index (self->devices, i);
+
+ if (g_strcmp0 (nm_device_get_udi (net_device_wifi_get_device (net_device)), visible_device_id) == 0)
+ {
+ self->spinner_binding = g_object_bind_property (net_device,
+ "scanning",
+ self->spinner,
+ "spinning",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ break;
+ }
+ }
+}
+
+static void
+on_stop_hotspot_dialog_response_cb (GtkDialog *dialog,
+ gint response,
+ CcWifiPanel *self)
+{
+ if (response == GTK_RESPONSE_OK)
+ {
+ NetDeviceWifi *child;
+
+ child = NET_DEVICE_WIFI (gtk_stack_get_visible_child (self->stack));
+ net_device_wifi_turn_off_hotspot (child);
+ }
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+hotspot_stop_clicked_cb (CcWifiPanel *self)
+{
+ GtkWidget *dialog;
+ GtkNative *native;
+
+ g_assert (CC_IS_WIFI_PANEL (self));
+
+ native = gtk_widget_get_native (GTK_WIDGET (self));
+ dialog = gtk_message_dialog_new (GTK_WINDOW (native),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_OTHER,
+ GTK_BUTTONS_NONE,
+ _("Stop hotspot and disconnect any users?"));
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Stop Hotspot"), GTK_RESPONSE_OK,
+ NULL);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_stop_hotspot_dialog_response_cb), self);
+ gtk_window_present (GTK_WINDOW (dialog));
+
+}
+
+/* Overrides */
+
+static const gchar *
+cc_wifi_panel_get_help_uri (CcPanel *panel)
+{
+ return "help:gnome-help/net-wireless";
+}
+
+static void
+cc_wifi_panel_finalize (GObject *object)
+{
+ CcWifiPanel *self = (CcWifiPanel *)object;
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->rfkill_proxy);
+
+ g_clear_pointer (&self->devices, g_ptr_array_unref);
+
+ reset_command_line_args (self);
+
+ G_OBJECT_CLASS (cc_wifi_panel_parent_class)->finalize (object);
+}
+
+static void
+cc_wifi_panel_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+cc_wifi_panel_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWifiPanel *self = CC_WIFI_PANEL (object);
+ GVariant *parameters;
+
+ switch (prop_id)
+ {
+ case PROP_PARAMETERS:
+ reset_command_line_args (self);
+
+ parameters = g_value_get_variant (value);
+
+ if (parameters)
+ {
+ g_autoptr(GPtrArray) array = NULL;
+ const gchar **args;
+
+ array = variant_av_to_string_array (parameters);
+ args = (const gchar **) array->pdata;
+
+ if (args[0])
+ {
+ if (g_str_equal (args[0], "create-wifi"))
+ self->arg_operation = OPERATION_CREATE_WIFI;
+ else if (g_str_equal (args[0], "connect-hidden-wifi"))
+ self->arg_operation = OPERATION_CONNECT_HIDDEN;
+ else if (g_str_equal (args[0], "connect-8021x-wifi"))
+ self->arg_operation = OPERATION_CONNECT_8021X;
+ else if (g_str_equal (args[0], "show-device"))
+ self->arg_operation = OPERATION_SHOW_DEVICE;
+ else
+ self->arg_operation = OPERATION_NULL;
+ }
+
+ if (args[0] && args[1])
+ self->arg_device = g_strdup (args[1]);
+ if (args[0] && args[1] && args[2])
+ self->arg_access_point = g_strdup (args[2]);
+
+ if (!verify_argv (self, (const char **) args))
+ {
+ reset_command_line_args (self);
+ return;
+ }
+
+ handle_argv (self);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wifi_panel_class_init (CcWifiPanelClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+
+ panel_class->get_help_uri = cc_wifi_panel_get_help_uri;
+
+ object_class->finalize = cc_wifi_panel_finalize;
+ object_class->get_property = cc_wifi_panel_get_property;
+ object_class->set_property = cc_wifi_panel_set_property;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/cc-wifi-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, center_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, header_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, hotspot_box);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, list_label);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, main_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, rfkill_row);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, rfkill_widget);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, spinner);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, wifi_qr_image);
+
+ gtk_widget_class_bind_template_callback (widget_class, rfkill_switch_notify_activate_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_stack_visible_child_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, hotspot_stop_clicked_cb);
+
+ g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
+}
+
+static void
+cc_wifi_panel_init (CcWifiPanel *self)
+{
+ g_autoptr(GtkCssProvider) provider = NULL;
+
+ g_resources_register (cc_network_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->devices = g_ptr_array_new ();
+
+ /* Create and store a NMClient instance if it doesn't exist yet */
+ if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT))
+ {
+ g_autoptr(NMClient) client = nm_client_new (NULL, NULL);
+ cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client);
+ }
+
+ /* Load NetworkManager */
+ self->client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT);
+
+ g_signal_connect_object (self->client,
+ "device-added",
+ G_CALLBACK (device_added_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->client,
+ "device-removed",
+ G_CALLBACK (device_removed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->client,
+ "notify::wireless-enabled",
+ G_CALLBACK (wireless_enabled_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ /* Load Wi-Fi devices */
+ load_wifi_devices (self);
+
+ /* Acquire Airplane Mode proxy */
+ cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.gnome.SettingsDaemon.Rfkill",
+ "/org/gnome/SettingsDaemon/Rfkill",
+ "org.gnome.SettingsDaemon.Rfkill",
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ rfkill_proxy_acquired_cb,
+ self);
+
+ /* Handle comment-line arguments after loading devices */
+ handle_argv (self);
+
+ /* use custom CSS */
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/network/wifi-panel.css");
+ gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
diff --git a/panels/network/cc-wifi-panel.h b/panels/network/cc-wifi-panel.h
new file mode 100644
index 0000000..20b6e34
--- /dev/null
+++ b/panels/network/cc-wifi-panel.h
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2017 Georges Basile Stavracas Neto <georges.stavracas@gmail.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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WIFI_PANEL (cc_wifi_panel_get_type())
+
+G_DECLARE_FINAL_TYPE (CcWifiPanel, cc_wifi_panel, CC, WIFI_PANEL, CcPanel)
+
+void cc_wifi_panel_static_init_func (void);
+
+G_END_DECLS
diff --git a/panels/network/cc-wifi-panel.ui b/panels/network/cc-wifi-panel.ui
new file mode 100644
index 0000000..572ef0e
--- /dev/null
+++ b/panels/network/cc-wifi-panel.ui
@@ -0,0 +1,326 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcWifiPanel" parent="CcPanel">
+
+ <child type="titlebar">
+ <object class="AdwHeaderBar" id="titlebar">
+ <property name="show-end-title-buttons">True</property>
+ <property name="show-start-title-buttons" bind-source="CcWifiPanel" bind-property="folded" bind-flags="default|sync-create" />
+ <child type="start">
+ <object class="GtkButton">
+ <property name="visible" bind-source="CcWifiPanel" bind-property="folded" bind-flags="default|sync-create" />
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="action-name">window.navigate</property>
+ <property name="action-target">0</property> <!-- 0: ADW_NAVIGATION_DIRECTION_BACK -->
+ <accessibility>
+ <property name="label" translatable="yes">Back</property>
+ </accessibility>
+ </object>
+ </child>
+
+ <!-- Center Widget -->
+ <property name="title-widget">
+ <object class="GtkStack" id="center_stack">
+ <property name="halign">center</property>
+ <property name="hhomogeneous">False</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">many</property>
+ <property name="child">
+ <object class="GtkStackSwitcher">
+ <property name="stack">stack</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </property>
+
+ <!-- End Stack -->
+ <child type="end">
+ <object class="GtkStack" id="header_stack">
+ <property name="halign">end</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <child type="content">
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <child>
+ <object class="AdwClamp">
+ <property name="margin_top">32</property>
+ <property name="margin_bottom">32</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">12</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Airplane Mode -->
+ <child>
+ <object class="GtkListBox" id="rfkill_widget">
+ <property name="margin_bottom">32</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ <child>
+ <object class="CcListRow" id="rfkill_row">
+ <property name="show-switch">True</property>
+ <property name="title" translatable="yes">Airplane Mode</property>
+ <property name="subtitle" translatable="yes">Disables Wi-Fi, Bluetooth and mobile broadband</property>
+ <signal name="notify::active" handler="rfkill_switch_notify_activate_cb" object="CcWifiPanel" swapped="no" />
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkStack" id="main_stack">
+ <property name="hhomogeneous">False</property>
+ <property name="vhomogeneous">False</property>
+ <property name="transition_type">crossfade</property>
+
+ <!-- "No Wi-Fi Adapter" page -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">no-wifi-devices</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkImage">
+ <property name="icon_name">network-wireless-no-route-symbolic</property>
+ <property name="pixel_size">256</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="wrap">True</property>
+ <property name="label" translatable="yes">No Wi-Fi Adapter Found</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ <attribute name="scale" value="1.2" />
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="wrap">True</property>
+ <property name="label" translatable="yes">Make sure you have a Wi-Fi adapter plugged and turned on</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- "Airplane Mode" page -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">airplane-mode</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkImage">
+ <property name="icon_name">airplane-mode-symbolic</property>
+ <property name="pixel_size">256</property>
+ <property name="margin-bottom">18</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="wrap">True</property>
+ <property name="label" translatable="yes">Airplane Mode On</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ <attribute name="scale" value="1.2" />
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="wrap">True</property>
+ <property name="label" translatable="yes">Turn off to use Wi-Fi</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- Wi-Fi connections and devices -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">wifi-connections</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkBox" id="hotspot_box">
+ <property name="orientation">vertical</property>
+
+ <!-- Hotspot QR code -->
+ <child>
+ <object class="GtkPicture" id="wifi_qr_image">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="width-request">180</property>
+ <property name="height-request">180</property>
+ <style>
+ <class name="frame"/>
+ <class name="qr-image"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="margin-top">12</property>
+ <property name="label" translatable="yes">Wi-Fi Hotspot Active</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ <attribute name="scale" value="1.8" />
+ </attributes>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Mobile devices can scan the QR code to connect.</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton">
+ <property name="halign">center</property>
+ <property name="margin-top">12</property>
+ <property name="label" translatable="yes">Turn Off Hotspot…</property>
+ <signal name="clicked" handler="hotspot_stop_clicked_cb" swapped="yes"/>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <!-- Visible Networks label & spinner -->
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="spacing">6</property>
+ <property name="margin_bottom">12</property>
+ <child>
+ <object class="GtkLabel" id="list_label">
+ <property name="label" translatable="yes">Visible Networks</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="spinner">
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Stack with a listbox for each Wi-Fi device -->
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="transition_type">crossfade</property>
+ <signal name="notify::visible-child-name" handler="on_stack_visible_child_changed_cb" object="CcWifiPanel" swapped="no" />
+ </object>
+ </child>
+
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- "NetworkManager Not Running" page -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">nm-not-running</property>
+ <property name="child">
+ <object class="GtkCenterBox">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <child type="center">
+ <object class="GtkImage">
+ <property name="icon_name">face-sad-symbolic</property>
+ <property name="pixel_size">128</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkLabel">
+ <property name="wrap">True</property>
+ <property name="label" translatable="yes">NetworkManager needs to be running</property>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkLabel">
+ <property name="wrap">True</property>
+ <property name="label" translatable="yes">Oops, something has gone wrong. Please contact your software vendor.</property>
+ <attributes>
+ <attribute name="scale" value="1.42" />
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
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..5be96b3
--- /dev/null
+++ b/panels/network/connection-editor/8021x-security-page.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPage8021xSecurity" parent="GtkGrid">
+ <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="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>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="enable_8021x_switch">
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/ce-ip-address-entry.c b/panels/network/connection-editor/ce-ip-address-entry.c
new file mode 100644
index 0000000..586ea5d
--- /dev/null
+++ b/panels/network/connection-editor/ce-ip-address-entry.c
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2020 Canonical Ltd.
+ *
+ * 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 <NetworkManager.h>
+
+#include "ce-ip-address-entry.h"
+
+struct _CEIPAddressEntry
+{
+ GtkEntry parent_instance;
+
+ int family;
+};
+
+static void ce_ip_address_entry_editable_init (GtkEditableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CEIPAddressEntry, ce_ip_address_entry, GTK_TYPE_ENTRY,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
+ ce_ip_address_entry_editable_init))
+
+static void
+ce_ip_address_entry_changed (GtkEditable *editable)
+{
+ CEIPAddressEntry *self = CE_IP_ADDRESS_ENTRY (editable);
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ if (ce_ip_address_entry_is_valid (self))
+ gtk_style_context_remove_class (context, "error");
+ else
+ gtk_style_context_add_class (context, "error");
+}
+
+static void
+ce_ip_address_entry_init (CEIPAddressEntry *self)
+{
+}
+
+static void
+ce_ip_address_entry_editable_init (GtkEditableInterface *iface)
+{
+ iface->changed = ce_ip_address_entry_changed;
+}
+
+static void
+ce_ip_address_entry_class_init (CEIPAddressEntryClass *klass)
+{
+}
+
+CEIPAddressEntry *
+ce_ip_address_entry_new (int family)
+{
+ CEIPAddressEntry *self;
+
+ self = CE_IP_ADDRESS_ENTRY (g_object_new (ce_ip_address_entry_get_type (), NULL));
+ self->family = family;
+
+ return self;
+}
+
+gboolean
+ce_ip_address_entry_is_empty (CEIPAddressEntry *self)
+{
+ const gchar *text;
+
+ g_return_val_if_fail (CE_IS_IP_ADDRESS_ENTRY (self), FALSE);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self));
+ return text[0] == '\0';
+}
+
+gboolean
+ce_ip_address_entry_is_valid (CEIPAddressEntry *self)
+{
+ const gchar *text;
+
+ g_return_val_if_fail (CE_IS_IP_ADDRESS_ENTRY (self), FALSE);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self));
+ return text[0] == '\0' || nm_utils_ipaddr_valid (self->family, text);
+}
diff --git a/panels/network/connection-editor/ce-ip-address-entry.h b/panels/network/connection-editor/ce-ip-address-entry.h
new file mode 100644
index 0000000..e89a7fb
--- /dev/null
+++ b/panels/network/connection-editor/ce-ip-address-entry.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2020 Canonical Ltd.
+ *
+ * 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>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEIPAddressEntry, ce_ip_address_entry, CE, IP_ADDRESS_ENTRY, GtkEntry)
+
+CEIPAddressEntry *ce_ip_address_entry_new (int family);
+
+gboolean ce_ip_address_entry_is_empty (CEIPAddressEntry *entry);
+
+gboolean ce_ip_address_entry_is_valid (CEIPAddressEntry *entry);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-netmask-entry.c b/panels/network/connection-editor/ce-netmask-entry.c
new file mode 100644
index 0000000..2fccf0f
--- /dev/null
+++ b/panels/network/connection-editor/ce-netmask-entry.c
@@ -0,0 +1,137 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2020 Canonical Ltd.
+ *
+ * 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 <arpa/inet.h>
+#include <NetworkManager.h>
+
+#include "ce-netmask-entry.h"
+
+struct _CENetmaskEntry
+{
+ GtkEntry parent_instance;
+};
+
+static void ce_netmask_entry_editable_init (GtkEditableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CENetmaskEntry, ce_netmask_entry, GTK_TYPE_ENTRY,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
+ ce_netmask_entry_editable_init))
+
+static gboolean
+parse_netmask (const char *str, guint32 *prefix)
+{
+ struct in_addr tmp_addr;
+ glong tmp_prefix;
+
+ /* Is it a prefix? */
+ errno = 0;
+ if (!strchr (str, '.'))
+ {
+ tmp_prefix = strtol (str, NULL, 10);
+ if (!errno && tmp_prefix >= 0 && tmp_prefix <= 32)
+ {
+ if (prefix != NULL)
+ *prefix = tmp_prefix;
+ return TRUE;
+ }
+ }
+
+ /* Is it a netmask? */
+ if (inet_pton (AF_INET, str, &tmp_addr) > 0)
+ {
+ if (prefix != NULL)
+ *prefix = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+ce_netmask_entry_changed (GtkEditable *editable)
+{
+ CENetmaskEntry *self = CE_NETMASK_ENTRY (editable);
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ if (ce_netmask_entry_is_valid (self))
+ gtk_style_context_remove_class (context, "error");
+ else
+ gtk_style_context_add_class (context, "error");
+}
+
+static void
+ce_netmask_entry_init (CENetmaskEntry *self)
+{
+}
+
+static void
+ce_netmask_entry_editable_init (GtkEditableInterface *iface)
+{
+ iface->changed = ce_netmask_entry_changed;
+}
+
+static void
+ce_netmask_entry_class_init (CENetmaskEntryClass *klass)
+{
+}
+
+CENetmaskEntry *
+ce_netmask_entry_new (void)
+{
+ return CE_NETMASK_ENTRY (g_object_new (ce_netmask_entry_get_type (), NULL));
+}
+
+gboolean
+ce_netmask_entry_is_empty (CENetmaskEntry *self)
+{
+ const gchar *text;
+
+ g_return_val_if_fail (CE_IS_NETMASK_ENTRY (self), FALSE);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self));
+ return text[0] == '\0';
+}
+
+gboolean
+ce_netmask_entry_is_valid (CENetmaskEntry *self)
+{
+ const gchar *text;
+
+ g_return_val_if_fail (CE_IS_NETMASK_ENTRY (self), FALSE);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self));
+ return text[0] == '\0' || parse_netmask (text, NULL);
+}
+
+guint32
+ce_netmask_entry_get_prefix (CENetmaskEntry *self)
+{
+ const gchar *text;
+ guint32 prefix = 0;
+
+ g_return_val_if_fail (CE_IS_NETMASK_ENTRY (self), 0);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self));
+ parse_netmask (text, &prefix);
+
+ return prefix;
+}
diff --git a/panels/network/connection-editor/ce-netmask-entry.h b/panels/network/connection-editor/ce-netmask-entry.h
new file mode 100644
index 0000000..ff6337e
--- /dev/null
+++ b/panels/network/connection-editor/ce-netmask-entry.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2020 Canonical Ltd.
+ *
+ * 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>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CENetmaskEntry, ce_netmask_entry, CE, NETMASK_ENTRY, GtkEntry)
+
+CENetmaskEntry *ce_netmask_entry_new (void);
+
+gboolean ce_netmask_entry_is_empty (CENetmaskEntry *entry);
+
+gboolean ce_netmask_entry_is_valid (CENetmaskEntry *entry);
+
+guint32 ce_netmask_entry_get_prefix (CENetmaskEntry *entry);
+
+G_END_DECLS
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..90732fa
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-8021x-security.c
@@ -0,0 +1,202 @@
+/* -*- 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)
+{
+ 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);
+ if (gtk_widget_get_parent (GTK_WIDGET (self->security)))
+ gtk_box_remove (self->box, 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_box_append (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;
+
+ /* 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_clone (connection);
+ nm_connection_add_setting (tmp_connection, nm_setting_wireless_new ());
+
+ ws_wpa_eap_fill_connection (self->security, tmp_connection);
+
+ /* NOTE: It is important we create a copy of the settings, as the
+ * secrets might be cleared otherwise.
+ */
+ s_8021x = nm_connection_get_setting (tmp_connection, NM_TYPE_SETTING_802_1X);
+ nm_connection_add_setting (connection, nm_setting_duplicate (NM_SETTING (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_pointer ((GtkWidget **) &self->security, gtk_widget_unparent);
+ 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..e0f43ed
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-details.c
@@ -0,0 +1,571 @@
+/* -*- 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"
+
+#include "../panel-common.h"
+
+struct _CEPageDetails
+{
+ GtkGrid parent;
+
+ GtkCheckButton *all_user_check;
+ GtkCheckButton *auto_connect_check;
+ GtkLabel *dns4_heading_label;
+ GtkLabel *dns4_label;
+ GtkLabel *dns6_heading_label;
+ GtkLabel *dns6_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;
+ GtkBox *restrict_data_check_container;
+ 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
+#if NM_CHECK_VERSION(1,26,0)
+ else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM) {
+ /* Connected to open OWE-TM network. */
+ }
+#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_check_button_get_active (GTK_CHECK_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_check_button_get_active (GTK_CHECK_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_check_button_set_active (GTK_CHECK_BUTTON (self->restrict_data_check),
+ metered == NM_METERED_YES || metered == NM_METERED_GUESS_YES);
+ gtk_widget_show (GTK_WIDGET (self->restrict_data_check_container));
+
+ 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;
+ gboolean have_dns4 = FALSE, have_dns6 = FALSE;
+ const gchar *route4_text = NULL, *route6_text = NULL;
+
+ 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));
+ else
+ active_ap = NULL;
+ frequency = active_ap ? nm_access_point_get_frequency (active_ap) : 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 (self->device)
+ hw_address = nm_device_get_hw_address (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);
+ ipv6_config = nm_device_get_ip6_config (self->device);
+ }
+
+ if (ipv4_config != NULL) {
+ GPtrArray *addresses;
+ const gchar *ipv4_text = NULL;
+ g_autofree gchar *ip4_dns = NULL;
+
+ 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;
+
+ ip4_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv4_config));
+ if (!*ip4_dns)
+ ip4_dns = NULL;
+ gtk_label_set_label (self->dns4_label, ip4_dns);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns4_heading_label), ip4_dns != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns4_label), ip4_dns != NULL);
+ have_dns4 = ip4_dns != NULL;
+
+ route4_text = nm_ip_config_get_gateway (ipv4_config);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->ipv4_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->ipv4_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns4_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns4_label));
+ }
+
+ if (ipv6_config != NULL) {
+ g_autofree gchar *ipv6_text = NULL;
+ g_autofree gchar *ip6_dns = NULL;
+
+ ipv6_text = net_device_get_ip6_addresses (ipv6_config);
+ 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_valign (GTK_WIDGET (self->ipv6_heading_label), GTK_ALIGN_START);
+ gtk_widget_set_visible (GTK_WIDGET (self->ipv6_label), ipv6_text != NULL);
+ have_ipv6_address = ipv6_text != NULL;
+
+ ip6_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv6_config));
+ if (!*ip6_dns)
+ ip6_dns = NULL;
+ gtk_label_set_label (self->dns6_label, ip6_dns);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns6_heading_label), ip6_dns != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns6_label), ip6_dns != NULL);
+ have_dns6 = ip6_dns != NULL;
+
+ route6_text = nm_ip_config_get_gateway (ipv6_config);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->ipv6_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->ipv6_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns6_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns6_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 (have_dns4 && have_dns6) {
+ gtk_label_set_label (self->dns4_heading_label, _("DNS4"));
+ gtk_label_set_label (self->dns6_heading_label, _("DNS6"));
+ } else {
+ gtk_label_set_label (self->dns4_heading_label, _("DNS"));
+ gtk_label_set_label (self->dns6_heading_label, _("DNS"));
+ }
+
+ if (route4_text != NULL || route6_text != NULL) {
+ g_autofree const gchar *routes_text = NULL;
+
+ if (route4_text == NULL) {
+ routes_text = g_strdup (route6_text);
+ } else if (route6_text == NULL) {
+ routes_text = g_strdup (route4_text);
+ } else {
+ routes_text = g_strjoin ("\n", route4_text, route6_text, NULL);
+ }
+ gtk_label_set_label (self->route_label, routes_text);
+ gtk_widget_set_visible (GTK_WIDGET (self->route_heading_label), routes_text != NULL);
+ gtk_widget_set_valign (GTK_WIDGET (self->route_heading_label), GTK_ALIGN_START);
+ gtk_widget_set_visible (GTK_WIDGET (self->route_label), routes_text != NULL);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->route_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->route_label));
+ }
+
+ 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_check_button_set_active (GTK_CHECK_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, dns4_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns4_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns6_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns6_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, restrict_data_check_container);
+ 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..79c1b7d
--- /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_editable_get_text (GTK_EDITABLE (self->mtu_spin))))
+ gtk_editable_set_text (GTK_EDITABLE (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_editable_set_text (GTK_EDITABLE (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_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo));
+ if (entry) {
+ text = gtk_editable_get_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (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_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo));
+ if (entry) {
+ if (!ce_page_address_is_valid (gtk_editable_get_text (GTK_EDITABLE (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_combo_box_get_child (GTK_COMBO_BOX (self->cloned_mac_combo)));
+ ret = FALSE;
+ } else {
+ widget_unset_error (gtk_combo_box_get_child (GTK_COMBO_BOX (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..c3f0864
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ip4.c
@@ -0,0 +1,846 @@
+/* -*- 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 "ce-ip-address-entry.h"
+#include "ce-netmask-entry.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
+{
+ AdwBin parent;
+
+ GtkLabel *address_address_label;
+ GtkBox *address_box;
+ GtkLabel *address_gateway_label;
+ GtkLabel *address_netmask_label;
+ GtkSizeGroup *address_sizegroup;
+ GtkSwitch *auto_dns_switch;
+ GtkSwitch *auto_routes_switch;
+ GtkCheckButton *automatic_radio;
+ GtkBox *content_box;
+ GtkCheckButton *disabled_radio;
+ GtkEntry *dns_entry;
+ GtkCheckButton *local_radio;
+ GtkGrid *main_box;
+ GtkCheckButton *manual_radio;
+ GtkCheckButton *never_default_check;
+ GtkLabel *routes_address_label;
+ GtkBox *routes_box;
+ GtkLabel *routes_gateway_label;
+ GtkLabel *routes_metric_label;
+ GtkSizeGroup *routes_metric_sizegroup;
+ GtkLabel *routes_netmask_label;
+ GtkSizeGroup *routes_sizegroup;
+ GtkCheckButton *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, ADW_TYPE_BIN,
+ 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_check_button_get_active (GTK_CHECK_BUTTON (self->disabled_radio)) ||
+ gtk_check_button_get_active (GTK_CHECK_BUTTON (self->shared_radio))) {
+ addr_enabled = FALSE;
+ dns_enabled = FALSE;
+ routes_enabled = FALSE;
+ } else {
+ addr_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->manual_radio));
+ dns_enabled = !gtk_check_button_get_active (GTK_CHECK_BUTTON (self->local_radio));
+ routes_enabled = !gtk_check_button_get_active (GTK_CHECK_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)
+{
+ GtkWidget *child;
+ gint rows = 0, i = 0;
+
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (list));
+ child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button"));
+ if (button != NULL)
+ rows++;
+ }
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (list));
+ child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button"));
+ if (button != NULL)
+ gtk_widget_set_sensitive (button, rows > 1 && ++i < rows);
+ }
+}
+
+static void
+update_row_gateway_sensitivity (CEPageIP4 *self)
+{
+ GtkWidget *child;
+ gint rows = 0;
+
+ for (child = gtk_widget_get_first_child (self->address_list);
+ child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *entry;
+
+ entry = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "gateway"));
+
+ gtk_widget_set_sensitive (entry, (rows == 0));
+
+ rows++;
+ }
+}
+
+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_list_box_remove (GTK_LIST_BOX (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 *child;
+ GtkWidget *box;
+ gboolean valid;
+
+ valid = FALSE;
+ box = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row));
+
+ for (child = gtk_widget_get_first_child (box);
+ child;
+ child = gtk_widget_get_next_sibling (child)) {
+ if (!GTK_IS_ENTRY (child))
+ continue;
+
+ valid = valid || gtk_entry_get_text_length (GTK_ENTRY (child)) > 0;
+ }
+
+ 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;
+
+ row = gtk_list_box_row_new ();
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_add_css_class (row_box, "linked");
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET));
+ 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_editable_set_text (GTK_EDITABLE (widget), address);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_address_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ widget = GTK_WIDGET (ce_netmask_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), "netmask", widget);
+ gtk_editable_set_text (GTK_EDITABLE (widget), network);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_netmask_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET));
+ 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_editable_set_text (GTK_EDITABLE (widget), gateway ? gateway : "");
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_gateway_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ gtk_widget_set_sensitive (delete_button, FALSE);
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Address"),
+ -1);
+ gtk_box_append (GTK_BOX (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_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
+ gtk_list_box_append (GTK_LIST_BOX (self->address_list), row);
+
+ update_row_gateway_sensitivity (self);
+ update_row_sensitivity (self, self->address_list);
+}
+
+static void
+ensure_empty_address_row (CEPageIP4 *self)
+{
+ GtkWidget *child = gtk_widget_get_last_child (self->address_list);
+
+ /* Add the last, stub row if needed*/
+ if (!child || validate_row (child))
+ add_address_row (self, "", "", "");
+}
+
+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_box_append (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);
+}
+
+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_editable_set_text (GTK_EDITABLE (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;
+
+ row = gtk_list_box_row_new ();
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_add_css_class (row_box, "linked");
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET));
+ 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_editable_set_text (GTK_EDITABLE (widget), address);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_address_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ widget = GTK_WIDGET (ce_netmask_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_editable_set_text (GTK_EDITABLE (widget), netmask);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_netmask_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET));
+ 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_editable_set_text (GTK_EDITABLE (widget), gateway ? gateway : "");
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_gateway_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (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_editable_set_text (GTK_EDITABLE (widget), s);
+ }
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 5);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_metric_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ gtk_size_group_add_widget (self->routes_metric_sizegroup, widget);
+
+ delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ gtk_widget_set_sensitive (delete_button, FALSE);
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Route"),
+ -1);
+ gtk_widget_set_halign (delete_button, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (delete_button, GTK_ALIGN_CENTER);
+ gtk_box_append (GTK_BOX (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_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
+ gtk_list_box_append (GTK_LIST_BOX (self->routes_list), row);
+
+ update_row_sensitivity (self, self->routes_list);
+}
+
+static void
+ensure_empty_routes_row (CEPageIP4 *self)
+{
+ GtkWidget *child = gtk_widget_get_last_child (self->routes_list);
+
+ /* Add the last, stub row if needed*/
+ if (!child || validate_row (child))
+ add_route_row (self, "", "", "", -1);
+}
+
+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_box_append (GTK_BOX (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);
+}
+
+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_check_button_set_active (GTK_CHECK_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_check_button_set_active (GTK_CHECK_BUTTON (self->automatic_radio), TRUE);
+ break;
+ case IP4_METHOD_LINK_LOCAL:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->local_radio), TRUE);
+ break;
+ case IP4_METHOD_MANUAL:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->manual_radio), TRUE);
+ break;
+ case IP4_METHOD_SHARED:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->shared_radio), TRUE);
+ break;
+ case IP4_METHOD_DISABLED:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->disabled_radio), TRUE);
+ break;
+ default:
+ break;
+ }
+
+ method_changed (self);
+}
+
+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;
+ GtkWidget *child;
+ GStrv dns_addresses = NULL;
+ gboolean ret = TRUE;
+ const char *default_gateway = NULL;
+ gboolean add_addresses = FALSE;
+ gboolean add_routes = FALSE;
+ gchar *dns_text = NULL;
+ guint i;
+
+ if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->disabled_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+ else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->automatic_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+ else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->local_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL;
+ else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->manual_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
+ else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->shared_radio)))
+ method = NM_SETTING_IP4_CONFIG_METHOD_SHARED;
+
+ addresses = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref);
+ add_addresses = g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
+
+ for (child = gtk_widget_get_first_child (self->address_list);
+ add_addresses && child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *row = child;
+ CEIPAddressEntry *address_entry;
+ CENetmaskEntry *netmask_entry;
+ CEIPAddressEntry *gateway_entry;
+ NMIPAddress *addr;
+
+ address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!address_entry)
+ continue;
+
+ netmask_entry = CE_NETMASK_ENTRY (g_object_get_data (G_OBJECT (row), "netmask"));
+ gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway"));
+
+ if (ce_ip_address_entry_is_empty (address_entry) && ce_netmask_entry_is_empty (netmask_entry) && ce_ip_address_entry_is_empty (gateway_entry)) {
+ /* ignore empty rows */
+ continue;
+ }
+
+ if (!ce_ip_address_entry_is_valid (address_entry))
+ ret = FALSE;
+
+ if (!ce_netmask_entry_is_valid (netmask_entry))
+ ret = FALSE;
+
+ if (!ce_ip_address_entry_is_valid (gateway_entry)) {
+ ret = FALSE;
+ } else {
+ if (!ce_ip_address_entry_is_empty (gateway_entry)) {
+ g_assert (default_gateway == NULL);
+ default_gateway = gtk_editable_get_text (GTK_EDITABLE (gateway_entry));
+ }
+ }
+
+ if (!ret)
+ continue;
+
+ addr = nm_ip_address_new (AF_INET, gtk_editable_get_text (GTK_EDITABLE (address_entry)), ce_netmask_entry_get_prefix (netmask_entry), NULL);
+ if (addr)
+ g_ptr_array_add (addresses, addr);
+
+ if (!gtk_widget_get_next_sibling (row))
+ ensure_empty_address_row (self);
+ }
+
+ 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_editable_get_text (GTK_EDITABLE (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);
+ add_routes = g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) ||
+ g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
+
+ for (child = gtk_widget_get_first_child (self->routes_list);
+ add_routes && child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *row = child;
+ CEIPAddressEntry *address_entry;
+ CENetmaskEntry *netmask_entry;
+ CEIPAddressEntry *gateway_entry;
+ const gchar *text_metric;
+ gint64 metric;
+ NMIPRoute *route;
+
+ address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!address_entry)
+ continue;
+
+ netmask_entry = CE_NETMASK_ENTRY (g_object_get_data (G_OBJECT (row), "netmask"));
+ gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway"));
+ text_metric = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "metric")));
+
+ if (ce_ip_address_entry_is_empty (address_entry) && ce_netmask_entry_is_empty (netmask_entry) && ce_ip_address_entry_is_empty (gateway_entry) && !*text_metric) {
+ /* ignore empty rows */
+ continue;
+ }
+
+ if (!ce_ip_address_entry_is_valid (address_entry))
+ ret = FALSE;
+
+ if (!ce_netmask_entry_is_valid (netmask_entry))
+ ret = FALSE;
+
+ if (!ce_ip_address_entry_is_valid (gateway_entry))
+ ret = FALSE;
+
+ 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,
+ gtk_editable_get_text (GTK_EDITABLE (address_entry)),
+ ce_netmask_entry_get_prefix (netmask_entry),
+ gtk_editable_get_text (GTK_EDITABLE (gateway_entry)),
+ metric, NULL);
+ if (route)
+ g_ptr_array_add (routes, route);
+
+ if (!gtk_widget_get_next_sibling (row))
+ ensure_empty_routes_row (self);
+ }
+
+ 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_check_button_get_active (GTK_CHECK_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, main_box);
+ 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, address_address_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_netmask_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_gateway_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_box);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_address_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_netmask_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_gateway_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_metric_label);
+ 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..3e6545f
--- /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 <adwaita.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageIP4, ce_page_ip4, CE, PAGE_IP4, AdwBin)
+
+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..09b9eb6
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-ip6.c
@@ -0,0 +1,817 @@
+/* -*- 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 "ce-ip-address-entry.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
+{
+ AdwBin parent;
+
+ GtkBox *address_box;
+ GtkLabel *address_address_label;
+ GtkLabel *address_prefix_label;
+ GtkLabel *address_gateway_label;
+ GtkSizeGroup *address_sizegroup;
+ GtkSwitch *auto_dns_switch;
+ GtkSwitch *auto_routes_switch;
+ GtkCheckButton *automatic_radio;
+ GtkBox *content_box;
+ GtkCheckButton *dhcp_radio;
+ GtkCheckButton *disabled_radio;
+ GtkEntry *dns_entry;
+ GtkCheckButton *local_radio;
+ GtkGrid *main_box;
+ GtkCheckButton *manual_radio;
+ GtkCheckButton *never_default_check;
+ GtkBox *routes_box;
+ GtkLabel *routes_address_label;
+ GtkLabel *routes_prefix_label;
+ GtkLabel *routes_gateway_label;
+ GtkLabel *routes_metric_label;
+ GtkSizeGroup *routes_metric_sizegroup;
+ GtkSizeGroup *routes_sizegroup;
+ GtkCheckButton *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, ADW_TYPE_BIN,
+ 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_check_button_get_active (self->disabled_radio) ||
+ gtk_check_button_get_active (self->shared_radio)) {
+ addr_enabled = FALSE;
+ dns_enabled = FALSE;
+ routes_enabled = FALSE;
+ } else {
+ addr_enabled = gtk_check_button_get_active (self->manual_radio);
+ dns_enabled = !gtk_check_button_get_active (self->local_radio);
+ routes_enabled = !gtk_check_button_get_active (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)
+{
+ GtkWidget *child;
+ gint rows = 0, i = 0;
+
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (list));
+ child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button"));
+ if (button != NULL)
+ rows++;
+ }
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (list));
+ child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *button;
+
+ button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button"));
+ if (button != NULL)
+ gtk_widget_set_sensitive (button, rows > 1 && ++i < rows);
+ }
+}
+
+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_list_box_remove (GTK_LIST_BOX (list), row);
+
+ ce_page_changed (CE_PAGE (self));
+
+ update_row_sensitivity (self, list);
+}
+
+static gboolean
+validate_row (GtkWidget *row)
+{
+ GtkWidget *child;
+ GtkWidget *box;
+ gboolean valid;
+
+ valid = FALSE;
+ box = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row));
+
+ for (child = gtk_widget_get_first_child (box);
+ child;
+ child = gtk_widget_get_next_sibling (child)) {
+ if (!GTK_IS_ENTRY (child))
+ continue;
+
+ valid = valid || gtk_entry_get_text_length (GTK_ENTRY (child)) > 0;
+ }
+
+ 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;
+
+ row = gtk_list_box_row_new ();
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_add_css_class (row_box, "linked");
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6));
+ 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_editable_set_text (GTK_EDITABLE (widget), address);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_address_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (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_editable_set_text (GTK_EDITABLE (widget), network);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_prefix_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6));
+ 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_editable_set_text (GTK_EDITABLE (widget), gateway ? gateway : "");
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_gateway_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ gtk_widget_set_sensitive (delete_button, FALSE);
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Address"),
+ -1);
+ gtk_box_append (GTK_BOX (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_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
+ gtk_list_box_append (GTK_LIST_BOX (self->address_list), row);
+
+ update_row_sensitivity (self, self->address_list);
+}
+
+static void
+ensure_empty_address_row (CEPageIP6 *self)
+{
+ GtkWidget *child = gtk_widget_get_last_child (self->address_list);
+
+ /* Add the last, stub row if needed*/
+ if (!child || validate_row (child))
+ add_address_row (self, "", "", "");
+}
+
+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_box_append (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);
+}
+
+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_editable_set_text (GTK_EDITABLE (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;
+
+ row = gtk_list_box_row_new ();
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
+
+ row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_add_css_class (row_box, "linked");
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6));
+ 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_editable_set_text (GTK_EDITABLE (widget), address);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_address_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (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_editable_set_text (GTK_EDITABLE (widget), prefix ? prefix : "");
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_prefix_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6));
+ 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_editable_set_text (GTK_EDITABLE (widget), gateway);
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_gateway_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (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_editable_set_text (GTK_EDITABLE (widget), metric ? metric : "");
+ gtk_editable_set_width_chars (GTK_EDITABLE (widget), 5);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_prefix_label, NULL,
+ -1);
+ gtk_box_append (GTK_BOX (row_box), widget);
+
+ gtk_size_group_add_widget (self->routes_metric_sizegroup, widget);
+
+ delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Route"),
+ -1);
+ gtk_widget_set_halign (delete_button, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (delete_button, GTK_ALIGN_CENTER);
+ gtk_box_append (GTK_BOX (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_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
+ gtk_list_box_append (GTK_LIST_BOX (self->routes_list), row);
+
+ update_row_sensitivity (self, self->routes_list);
+}
+
+static void
+ensure_empty_routes_row (CEPageIP6 *self)
+{
+ GtkWidget *child = gtk_widget_get_last_child (self->routes_list);
+
+ /* Add the last, stub row if needed*/
+ if (!child || validate_row (child))
+ add_route_row (self, "", NULL, "", NULL);
+}
+
+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_box_append (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);
+}
+
+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_check_button_set_active (GTK_CHECK_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_check_button_set_active (GTK_CHECK_BUTTON (self->automatic_radio), TRUE);
+ break;
+ case IP6_METHOD_DHCP:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->dhcp_radio), TRUE);
+ break;
+ case IP6_METHOD_LINK_LOCAL:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->local_radio), TRUE);
+ break;
+ case IP6_METHOD_MANUAL:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->manual_radio), TRUE);
+ break;
+ case IP6_METHOD_SHARED:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->shared_radio), TRUE);
+ break;
+ case IP6_METHOD_DISABLED:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->disabled_radio), TRUE);
+ break;
+ default:
+ break;
+ }
+
+ method_changed (self);
+}
+
+static gboolean
+ui_to_setting (CEPageIP6 *self)
+{
+ GtkWidget *child;
+ const gchar *method;
+ gboolean ignore_auto_dns;
+ gboolean ignore_auto_routes;
+ gboolean never_default;
+ gboolean add_addresses = FALSE;
+ gboolean add_routes = FALSE;
+ gboolean ret = TRUE;
+ GStrv dns_addresses = NULL;
+ gchar *dns_text = NULL;
+ guint i;
+
+ if (gtk_check_button_get_active (self->disabled_radio))
+ method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+ else if (gtk_check_button_get_active (self->manual_radio))
+ method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
+ else if (gtk_check_button_get_active (self->local_radio))
+ method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL;
+ else if (gtk_check_button_get_active (self->dhcp_radio))
+ method = NM_SETTING_IP6_CONFIG_METHOD_DHCP;
+ else if (gtk_check_button_get_active (self->automatic_radio))
+ method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+ else if (gtk_check_button_get_active (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)) {
+ add_addresses = TRUE;
+ } else {
+ g_object_set (G_OBJECT (self->setting),
+ NM_SETTING_IP_CONFIG_GATEWAY, NULL,
+ NULL);
+ }
+
+ for (child = gtk_widget_get_first_child (self->address_list);
+ add_addresses && child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *row = child;
+ CEIPAddressEntry *address_entry;
+ CEIPAddressEntry *gateway_entry;
+ const gchar *text_prefix;
+ guint32 prefix;
+ gchar *end;
+ NMIPAddress *addr;
+
+ address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!address_entry)
+ continue;
+
+ text_prefix = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "prefix")));
+ gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway"));
+
+ if (ce_ip_address_entry_is_empty (address_entry) && !*text_prefix && ce_ip_address_entry_is_empty (gateway_entry)) {
+ /* ignore empty rows */
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ continue;
+ }
+
+ if (!ce_ip_address_entry_is_valid (address_entry))
+ ret = FALSE;
+
+ 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 (!ce_ip_address_entry_is_valid (gateway_entry))
+ ret = FALSE;
+
+ if (!ret)
+ continue;
+
+ addr = nm_ip_address_new (AF_INET6, gtk_editable_get_text (GTK_EDITABLE (address_entry)), prefix, NULL);
+ if (!ce_ip_address_entry_is_empty (gateway_entry))
+ g_object_set (G_OBJECT (self->setting),
+ NM_SETTING_IP_CONFIG_GATEWAY, gtk_editable_get_text (GTK_EDITABLE (gateway_entry)),
+ NULL);
+ nm_setting_ip_config_add_address (self->setting, addr);
+
+ if (!gtk_widget_get_next_sibling (row))
+ ensure_empty_address_row (self);
+ }
+
+ nm_setting_ip_config_clear_dns (self->setting);
+ dns_text = g_strstrip (g_strdup (gtk_editable_get_text (GTK_EDITABLE (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);
+ add_routes = 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);
+
+ for (child = gtk_widget_get_first_child (self->routes_list);
+ add_routes && child;
+ child = gtk_widget_get_next_sibling (child)) {
+ GtkWidget *row = child;
+ CEIPAddressEntry *address_entry;
+ CEIPAddressEntry *gateway_entry;
+ const gchar *text_prefix;
+ const gchar *text_metric;
+ guint32 prefix;
+ gint64 metric;
+ gchar *end;
+ NMIPRoute *route;
+
+ address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address"));
+ if (!address_entry)
+ continue;
+
+ text_prefix = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "prefix")));
+ gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway"));
+ text_metric = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "metric")));
+
+ if (ce_ip_address_entry_is_empty (address_entry) && !*text_prefix && ce_ip_address_entry_is_empty (gateway_entry) && !*text_metric) {
+ /* ignore empty rows */
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix"));
+ widget_unset_error (g_object_get_data (G_OBJECT (row), "metric"));
+ continue;
+ }
+
+ if (!ce_ip_address_entry_is_valid (address_entry))
+ ret = FALSE;
+
+ 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 (!ce_ip_address_entry_is_valid (gateway_entry))
+ ret = FALSE;
+
+ 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,
+ gtk_editable_get_text (GTK_EDITABLE (address_entry)),
+ prefix,
+ gtk_editable_get_text (GTK_EDITABLE (gateway_entry)),
+ metric,
+ NULL);
+ nm_setting_ip_config_add_route (self->setting, route);
+ nm_ip_route_unref (route);
+
+ if (!gtk_widget_get_next_sibling (row))
+ ensure_empty_routes_row (self);
+ }
+
+ 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_check_button_get_active (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_address_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_prefix_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_gateway_label);
+ 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, main_box);
+ 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_address_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_prefix_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_gateway_label);
+ gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_metric_label);
+ 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..c2c7d1b
--- /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 <adwaita.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (CEPageIP6, ce_page_ip6, CE, PAGE_IP6, AdwBin)
+
+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..c7cd7d9
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-security.c
@@ -0,0 +1,553 @@
+/* -*- 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-sae.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)
+{
+ g_autoptr(WirelessSecurity) sec = NULL;
+ GtkWidget *child;
+
+ wsec_size_group_clear (self->group);
+
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->box))) != NULL)
+ gtk_box_remove (self->box, child);
+
+ sec = security_combo_get_active (self);
+ if (sec) {
+ if (gtk_widget_get_parent (GTK_WIDGET (sec)))
+ gtk_box_remove (self->box, 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_box_append (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)) {
+ WirelessSecuritySAE *ws_sae;
+
+ ws_sae = ws_sae_new (self->connection);
+ if (ws_sae) {
+ add_security_item (self, WIRELESS_SECURITY (ws_sae), 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 {
+
+ if (gtk_combo_box_get_active ((CE_PAGE_SECURITY (self))->security_combo) == 0) {
+ /* 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;
+ } else {
+ /* owe case:
+ * fill the connection manually until libnma implements OWE wireless security
+ */
+ NMSetting *sws;
+
+ sws = nm_setting_wireless_security_new ();
+ g_object_set (sws, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL);
+ nm_connection_add_setting (connection, sws);
+ 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..36afde5
--- /dev/null
+++ b/panels/network/connection-editor/ce-page-vpn.c
@@ -0,0 +1,227 @@
+/* -*- 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_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);
+ } else if (GTK_IS_WIDGET (widget)) {
+ GtkWidget *child;
+
+ for (child = gtk_widget_get_first_child (widget);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ vpn_gnome3ify_editor (child);
+ }
+}
+
+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_box_remove (GTK_BOX (self), GTK_WIDGET (self->failure_label));
+ gtk_box_append (GTK_BOX (self), 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_editable_set_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (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..5a66292
--- /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_editable_set_text (GTK_EDITABLE (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_editable_get_text (GTK_EDITABLE (self->ssid_entry));
+ if (!utf8_ssid || !*utf8_ssid)
+ ssid = NULL;
+ else {
+ ssid = g_bytes_new_static (utf8_ssid, strlen (utf8_ssid));
+ }
+ entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->bssid_combo));
+ bssid = gtk_editable_get_text (GTK_EDITABLE (entry));
+ if (*bssid == '\0')
+ bssid = NULL;
+ entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo));
+ device_mac = ce_page_trim_address (gtk_editable_get_text (GTK_EDITABLE (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_combo_box_get_child (GTK_COMBO_BOX (self->bssid_combo));
+ if (!ce_page_address_is_valid (gtk_editable_get_text (GTK_EDITABLE (entry)))) {
+ widget_set_error (entry);
+ ret = FALSE;
+ } else {
+ widget_unset_error (entry);
+ }
+
+ entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo));
+ if (!ce_page_address_is_valid (gtk_editable_get_text (GTK_EDITABLE (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_combo_box_get_child (GTK_COMBO_BOX (self->cloned_mac_combo)));
+ ret = FALSE;
+ } else {
+ widget_unset_error (gtk_combo_box_get_child (GTK_COMBO_BOX (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..b6f0779
--- /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_combo_box_get_child (GTK_COMBO_BOX (combo));
+ if (entry)
+ gtk_editable_set_text (GTK_EDITABLE (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_combo_box_get_child (GTK_COMBO_BOX (combo));
+ g_assert (entry);
+ gtk_editable_set_text (GTK_EDITABLE (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..18031e8
--- /dev/null
+++ b/panels/network/connection-editor/connection-editor.ui
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="NetConnectionEditor" parent="GtkDialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="default_width">500</property>
+ <property name="default_height">600</property>
+ <!-- This doesn't seem to work for a template, so it is also hardcoded. -->
+ <property name="use_header_bar">1</property>
+ <signal name="close-request" handler="close_request_cb" swapped="yes"/>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</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="use_underline">True</property>
+ <signal name="clicked" handler="apply_clicked_cb" object="NetConnectionEditor" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkStack" id="toplevel_stack">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkSpinner" id="spinner">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="spinning">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="show_border">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="add_connection_box">
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="AdwBin" id="add_connection_frame">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="width_request">300</property>
+ <property name="valign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </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..c8e4595
--- /dev/null
+++ b/panels/network/connection-editor/details-page.ui
@@ -0,0 +1,458 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageDetails" parent="GtkGrid">
+ <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="xalign">1</property>
+ <property name="label" translatable="yes">Signal Strength</property>
+ <property name="mnemonic_widget">strength_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="strength_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="speed_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Link speed</property>
+ <property name="mnemonic_widget">speed_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="speed_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="security_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Security</property>
+ <property name="mnemonic_widget">security_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv4_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">IPv4 Address</property>
+ <property name="mnemonic_widget">ipv4_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv6_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">IPv6 Address</property>
+ <property name="mnemonic_widget">ipv6_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="mac_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Hardware Address</property>
+ <property name="mnemonic_widget">mac_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">5</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="freq_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Supported Frequencies</property>
+ <property name="mnemonic_widget">freq_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">6</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="route_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Default Route</property>
+ <property name="mnemonic_widget">route_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">7</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns4_heading_label">
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">DNS</property>
+ <property name="mnemonic_widget">dns4_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">8</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns6_heading_label">
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">DNS</property>
+ <property name="mnemonic_widget">dns6_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">9</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="last_used_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Last Used</property>
+ <property name="mnemonic_widget">last_used_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">10</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="security_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv4_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv6_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="mac_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">5</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="freq_label">
+ <property name="xalign">0</property>
+ <property name="label">2.4 GHz / 5 GHz</property>
+ <property name="selectable">True</property>
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">6</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="route_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">7</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns4_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">8</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns6_label">
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label">::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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">9</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="last_used_label">
+ <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>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">10</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="auto_connect_check">
+ <property name="label" translatable="yes">Connect _automatically</property>
+ <property name="valign">end</property>
+ <property name="use_underline">True</property>
+ <property name="margin_top">12</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">11</property>
+ <property name="column-span">2</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="all_user_check">
+ <property name="label" translatable="yes">Make available to _other users</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">12</property>
+ <property name="column-span">2</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+
+ <!-- "Restrict Data Usage" section -->
+ <child>
+ <object class="GtkBox" id="restrict_data_check_container">
+ <layout>
+ <property name="column">0</property>
+ <property name="row">13</property>
+ <property name="column-span">2</property>
+ <property name="row-span">1</property>
+ </layout>
+ <child>
+ <object class="GtkCheckButton" id="restrict_data_check">
+ <property name="margin_bottom">12</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <property name="mnemonic_widget">restrict_data_check</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="forget_button">
+ <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>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">14</property>
+ <property name="column-span">2</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="destructive-action" />
+ </style>
+ </object>
+ </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..ad4f2cb
--- /dev/null
+++ b/panels/network/connection-editor/ethernet-page.ui
@@ -0,0 +1,121 @@
+<?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="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="xalign">1</property>
+ <property name="label" translatable="yes">_Name</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">name_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="invisible_char">●</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="mac_combo">
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="cloned_mac_combo">
+ <property name="has_entry">True</property>
+ <property name="hexpand">True</property>
+ <property name="active_id">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ <child internal-child="entry">
+ <object class="GtkEntry">
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="mtu_label">
+ <property name="label" translatable="yes">bytes</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="mtu_spin">
+ <property name="adjustment">mtu_adjustment</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </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..a2210ea
--- /dev/null
+++ b/panels/network/connection-editor/ip4-page.ui
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageIP4" parent="AdwBin">
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkGrid" id="main_box">
+ <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="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>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">0</property>
+ </layout>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="automatic_radio">
+ <property name="label" translatable="yes">Automatic (DHCP)</property>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="local_radio">
+ <property name="label" translatable="yes">Link-Local Only</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="manual_radio">
+ <property name="label" translatable="yes">Manual</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">1</property>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="disabled_radio">
+ <property name="label" translatable="yes">Disable</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">1</property>
+ <property name="column">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="shared_radio">
+ <property name="label" translatable="yes">Shared to other computers</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">2</property>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="content_box">
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <layout>
+ <property name="row">3</property>
+ <property name="column">0</property>
+ <property name="column-span">3</property>
+ </layout>
+ <child>
+ <object class="GtkBox" id="address_box">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <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="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="address_address_label">
+ <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="address_netmask_label">
+ <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" id="address_gateway_label">
+ <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">
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="dns4_label">
+ <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="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="halign">end</property>
+ <property name="valign">center</property>
+ <accessibility>
+ <property name="label" translatable="yes">Automatic DNS</property>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dns_entry">
+ <accessibility>
+ <property name="label" translatable="yes">DNS server address(es)</property>
+ <relation name="described-by">dns_multiple_help</relation>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns_multiple_help">
+ <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="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <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="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="halign">end</property>
+ <property name="valign">center</property>
+ <accessibility>
+ <property name="label" translatable="yes">Automatic Routes</property>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="routes_box">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="routes_address_label">
+ <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_netmask_label">
+ <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" id="routes_gateway_label">
+ <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="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">
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </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="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </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..c22fb0f
--- /dev/null
+++ b/panels/network/connection-editor/ip6-page.ui
@@ -0,0 +1,349 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageIP6" parent="AdwBin">column
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkGrid" id="main_box">
+ <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="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>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">0</property>
+ </layout>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="automatic_radio">
+ <property name="label" translatable="yes">Automatic</property>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="dhcp_radio">
+ <property name="label" translatable="yes">Automatic, DHCP only</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="local_radio">
+ <property name="label" translatable="yes">Link-Local Only</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">1</property>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="manual_radio">
+ <property name="label" translatable="yes">Manual</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">1</property>
+ <property name="column">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="disabled_radio">
+ <property name="label" translatable="yes">Disable</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">2</property>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="shared_radio">
+ <property name="label" translatable="yes">Shared to other computers</property>
+ <property name="group">automatic_radio</property>
+ <layout>
+ <property name="row">2</property>
+ <property name="column">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="content_box">
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <layout>
+ <property name="row">3</property>
+ <property name="column">0</property>
+ <property name="column-span">3</property>
+ </layout>
+ <child>
+ <object class="GtkBox" id="address_box">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <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="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="address_address_label">
+ <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="address_prefix_label">
+ <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" id="address_gateway_label">
+ <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">
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="dns6_label">
+ <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="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="halign">end</property>
+ <property name="valign">center</property>
+ <accessibility>
+ <property name="label" translatable="yes">Automatic DNS</property>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dns_entry">
+ <accessibility>
+ <property name="label" translatable="yes">DNS server address(es)</property>
+ <relation name="described-by">dns_multiple_help</relation>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns_multiple_help">
+ <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="margin_top">24</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <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="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="halign">end</property>
+ <property name="valign">center</property>
+ <accessibility>
+ <property name="label" translatable="yes">Automatic Routes</property>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="routes_box">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="routes_address_label">
+ <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="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" id="routes_gateway_label">
+ <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="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">
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </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="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </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..fd4ddf9
--- /dev/null
+++ b/panels/network/connection-editor/meson.build
@@ -0,0 +1,48 @@
+name = 'connection-editor'
+
+sources = files(
+ 'ce-ip-address-entry.c',
+ 'ce-netmask-entry.c',
+ '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..9de938c
--- /dev/null
+++ b/panels/network/connection-editor/net-connection-editor.c
@@ -0,0 +1,830 @@
+/* -*- 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 "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;
+ AdwBin *add_connection_frame;
+ GtkButton *apply_button;
+ GtkButton *cancel_button;
+ GtkNotebook *notebook;
+ GtkStack *toplevel_stack;
+
+ NMClient *client;
+ NMDevice *device;
+
+ NMConnection *connection;
+ NMConnection *orig_connection;
+ gboolean is_new_connection;
+ gboolean is_changed;
+ NMAccessPoint *ap;
+
+ GSList *initializing_pages;
+
+ NMClientPermissionResult can_modify;
+
+ gboolean title_set;
+};
+
+G_DEFINE_TYPE (NetConnectionEditor, net_connection_editor, GTK_TYPE_DIALOG)
+
+static void page_changed (NetConnectionEditor *self);
+
+static void
+cancel_editing (NetConnectionEditor *self)
+{
+ g_signal_emit (self, signals[DONE], 0, FALSE);
+ gtk_window_destroy (GTK_WINDOW (self));
+}
+
+static void
+close_request_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);
+
+ g_clear_object (&self->connection);
+ g_clear_object (&self->orig_connection);
+ 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, cancel_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, close_request_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_get_transient_for (GTK_WINDOW (self));
+
+ 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);
+ }
+
+ gtk_window_present (GTK_WINDOW (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;
+ gint i;
+
+ 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 (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
+ GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i);
+ gtk_widget_set_sensitive (page, sensitive);
+ }
+}
+
+static void
+validate (NetConnectionEditor *self)
+{
+ gboolean valid = FALSE;
+ g_autofree gchar *apply_tooltip = NULL;
+ gint i;
+
+ if (!editor_is_initialized (self))
+ goto done;
+
+ valid = TRUE;
+ for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
+ CEPage *page = CE_PAGE (gtk_notebook_get_nth_page (self->notebook, i));
+ g_autoptr(GError) error = NULL;
+
+ if (!ce_page_validate (page, self->connection, &error)) {
+ valid = FALSE;
+ if (error) {
+ apply_tooltip = g_strdup_printf (_("Invalid setting %s: %s"), ce_page_get_title (page), error->message);
+ g_debug ("%s", apply_tooltip);
+ } else {
+ apply_tooltip = g_strdup_printf (_("Invalid setting %s"), ce_page_get_title (page));
+ g_debug ("%s", apply_tooltip);
+ }
+ }
+ }
+
+ update_sensitivity (self);
+done:
+ if (apply_tooltip != NULL)
+ gtk_widget_set_tooltip_text(GTK_WIDGET (self->apply_button), apply_tooltip);
+
+ 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_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->notebook));
+ gtk_notebook_set_current_page (self->notebook, 0);
+
+ g_idle_add (idle_validate, self);
+}
+
+static void
+page_initialized (NetConnectionEditor *self, GError *error, CEPage *page)
+{
+ GtkWidget *label;
+ gint position;
+ 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));
+ for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
+ GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i);
+ gint pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position"));
+ if (pos > position)
+ break;
+ }
+
+ 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);
+
+ 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)
+{
+ adw_bin_set_child (self->add_connection_frame, NULL);
+ 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;
+ GtkWidget *row, *row_box;
+ GtkWidget *name_label, *desc_label;
+ GtkWidget *child;
+
+ /* Get the available VPN types */
+ vpn_plugins = vpn_get_plugins ();
+
+ /* Remove the previous menu contents */
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (list))) != NULL)
+ gtk_list_box_remove (list, child);
+
+ /* 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;
+
+ 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_append (GTK_BOX (row_box), name_label);
+
+ desc_label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (desc_label), desc_markup);
+ gtk_label_set_wrap (GTK_LABEL (desc_label), TRUE);
+ gtk_widget_set_halign (desc_label, GTK_ALIGN_START);
+ gtk_widget_add_css_class (desc_label, "dim-label");
+ gtk_box_append (GTK_BOX (row_box), desc_label);
+
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
+ g_object_set_data_full (G_OBJECT (row), "service_name", g_steal_pointer (&service_name), g_free);
+ gtk_list_box_append (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_append (GTK_BOX (row_box), name_label);
+
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
+ g_object_set_data (G_OBJECT (row), "service_name", "import");
+ gtk_list_box_append (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)
+{
+ GtkListBox *list;
+
+ list = GTK_LIST_BOX (gtk_list_box_new ());
+ gtk_list_box_set_selection_mode (list, GTK_SELECTION_NONE);
+
+ select_vpn_type (self, list);
+
+ adw_bin_set_child (self->add_connection_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 (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 (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;
+}
+
+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..cbd197f
--- /dev/null
+++ b/panels/network/connection-editor/net-connection-editor.h
@@ -0,0 +1,40 @@
+/* -*- 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 (NMConnection *connection,
+ NMDevice *device,
+ NMAccessPoint *ap,
+ NMClient *client);
+void net_connection_editor_set_title (NetConnectionEditor *editor,
+ const gchar *title);
+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..9a1c0de
--- /dev/null
+++ b/panels/network/connection-editor/security-page.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageSecurity" parent="GtkGrid">
+ <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="xalign">1</property>
+ <property name="label" translatable="yes">S_ecurity</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">security_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="security_combo">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </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..e0358a9
--- /dev/null
+++ b/panels/network/connection-editor/vpn-helpers.c
@@ -0,0 +1,358 @@
+/* -*- 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 GSList *plugins = NULL;
+ GSList *p;
+
+ 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 {
+ GMainLoop *mainloop;
+ gint response;
+} RunData;
+
+static void
+on_dialog_close_request_cb (GtkDialog *dialog,
+ gint response,
+ RunData *data)
+{
+ data->response = GTK_RESPONSE_CLOSE;
+ g_main_loop_quit (data->mainloop);
+}
+
+static void
+on_dialog_response_cb (GtkDialog *dialog,
+ gint response,
+ RunData *data)
+{
+ data->response = response;
+ g_main_loop_quit (data->mainloop);
+}
+
+static int
+run_dialog (GtkDialog *dialog)
+{
+ g_autoptr(GMainLoop) mainloop = NULL;
+ RunData run_data;
+ gulong response_id;
+ gulong close_id;
+
+ mainloop = g_main_loop_new (NULL, FALSE);
+ run_data = (RunData) {
+ .response = GTK_RESPONSE_CLOSE,
+ .mainloop = mainloop,
+ };
+
+ close_id = g_signal_connect (dialog, "close-request", G_CALLBACK (on_dialog_close_request_cb), &run_data);
+ response_id = g_signal_connect_swapped (dialog, "response", G_CALLBACK (on_dialog_response_cb), &run_data);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+ g_main_loop_run (mainloop);
+
+ g_clear_signal_handler (&close_id, dialog);
+ g_clear_signal_handler (&response_id, dialog);
+
+ return run_data.response;
+}
+
+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;
+ g_autoptr(GFile) file = NULL;
+ ActionInfo *info = (ActionInfo *) user_data;
+ NMConnection *connection = NULL;
+ g_autoptr(GError) error = NULL;
+ GSList *iter;
+
+ if (response != GTK_RESPONSE_ACCEPT)
+ goto out;
+
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ if (!file) {
+ g_warning ("%s: didn't get a filename back from the chooser!", __func__);
+ goto out;
+ }
+
+ filename = g_file_get_path (file);
+ 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");
+ run_dialog (GTK_DIALOG (err_dialog));
+ }
+
+out:
+ gtk_window_destroy (GTK_WINDOW (dialog));
+
+ info->callback (connection, info->user_data);
+ g_free (info);
+}
+
+static gboolean
+destroy_import_chooser (GtkWidget *dialog, gpointer user_data)
+{
+ ActionInfo *info = (ActionInfo *) user_data;
+
+ info->callback (NULL, info->user_data);
+ g_free (info);
+
+ return FALSE;
+}
+
+void
+vpn_import (GtkWindow *parent, VpnImportCallback callback, gpointer user_data)
+{
+ g_autoptr(GFile) home_folder = NULL;
+ GtkWidget *dialog;
+ ActionInfo *info;
+
+ 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_file_new_for_path (g_get_home_dir ());
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder, NULL);
+
+ info = g_malloc0 (sizeof (ActionInfo));
+ info->callback = callback;
+ info->user_data = user_data;
+
+ g_signal_connect (G_OBJECT (dialog), "close-request", G_CALLBACK (destroy_import_chooser), info);
+ g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (import_vpn_from_file_cb), info);
+ 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);
+ g_autoptr(GFile) file = NULL;
+ 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;
+
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ if (!file) {
+ g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "no filename");
+ goto done;
+ }
+
+ filename = g_file_get_path (file);
+ 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 = run_dialog (GTK_DIALOG (replace_dialog));
+ gtk_window_destroy (GTK_WINDOW (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");
+ run_dialog (GTK_DIALOG (err_dialog));
+ }
+
+out:
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+void
+vpn_export (NMConnection *connection)
+{
+ g_autoptr(GFile) home_folder = NULL;
+ GtkWidget *dialog;
+ NMVpnEditorPlugin *plugin;
+ NMSettingVpn *s_vpn = NULL;
+ const char *service_type;
+
+ 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_file_new_for_path (g_get_home_dir ());
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder, NULL);
+
+ 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), "response", G_CALLBACK (export_vpn_to_file_cb), g_object_ref (connection));
+ 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..98f801b
--- /dev/null
+++ b/panels/network/connection-editor/vpn-page.ui
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageVpn" parent="GtkBox">
+ <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="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="hexpand">True</property>
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="failure_label">
+ <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>
+ </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..6662b8f
--- /dev/null
+++ b/panels/network/connection-editor/wifi-page.ui
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CEPageWifi" parent="GtkGrid">
+ <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="xalign">1</property>
+ <property name="label" translatable="yes">_SSID</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">ssid_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="ssid_entry">
+ <property name="hexpand">True</property>
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <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>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="cloned_mac_combo">
+ <property name="has_entry">True</property>
+ <property name="hexpand">True</property>
+ <property name="active_id">0</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ <child internal-child="entry">
+ <object class="GtkEntry">
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">_Cloned Address</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">cloned_mac_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="bssid_combo">
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="mac_combo">
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/gnome-network-panel.desktop.in.in b/panels/network/gnome-network-panel.desktop.in.in
new file mode 100644
index 0000000..d9ab05c
--- /dev/null
+++ b/panels/network/gnome-network-panel.desktop.in.in
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Name=Network
+Comment=Control how you connect to the Internet
+Exec=gnome-control-center network
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=org.gnome.Settings-network-symbolic
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings;
+OnlyShowIn=GNOME;Unity;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=network
+X-GNOME-Bugzilla-Version=@VERSION@
+# Translators: Search terms to find the Network panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Network;IP;LAN;Proxy;WAN;Broadband;Modem;Bluetooth;vpn;DNS;
diff --git a/panels/network/gnome-wifi-panel.desktop.in.in b/panels/network/gnome-wifi-panel.desktop.in.in
new file mode 100644
index 0000000..8104ede
--- /dev/null
+++ b/panels/network/gnome-wifi-panel.desktop.in.in
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Name=Wi-Fi
+Comment=Control how you connect to Wi-Fi networks
+Exec=gnome-control-center wifi
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=network-wireless
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings;
+OnlyShowIn=GNOME;Unity;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=network
+X-GNOME-Bugzilla-Version=@VERSION@
+# Translators: Search terms to find the Wi-Fi panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Network;Wireless;Wi-Fi;Wifi;IP;LAN;Broadband;DNS;Hotspot;
diff --git a/panels/network/icons/meson.build b/panels/network/icons/meson.build
new file mode 100644
index 0000000..0501dfe
--- /dev/null
+++ b/panels/network/icons/meson.build
@@ -0,0 +1,4 @@
+install_data(
+ 'scalable/org.gnome.Settings-network-symbolic.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps')
+)
diff --git a/panels/network/icons/scalable/org.gnome.Settings-network-symbolic.svg b/panels/network/icons/scalable/org.gnome.Settings-network-symbolic.svg
new file mode 100644
index 0000000..a6ef9ce
--- /dev/null
+++ b/panels/network/icons/scalable/org.gnome.Settings-network-symbolic.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <g fill="#2e3436">
+ <path d="m 3.75 0.00390625 c -0.953125 0.00390625 -1.75 0.80078175 -1.75 1.74999975 v 5.496094 c 0 0.953125 0.796875 1.75 1.75 1.75 h 0.75 s 0.5 0.007812 0.5 0.507812 c 0 0.492188 -0.5 0.492188 -0.5 0.492188 h -0.5 v 1.992188 h 8 v -2 h -0.5 s -0.5 0.007812 -0.5 -0.492188 s 0.5 -0.507812 0.5 -0.507812 h 0.75 c 0.953125 0 1.75 -0.796876 1.75 -1.75 v -5.492188 c 0 -0.953125 -0.796875 -1.75 -1.75 -1.75 z m 0.25 1.99999975 l 8 -0.003906 v 4.992188 l -8 0.007812 z m 0 0"/>
+ <path d="m 6.96875 10.003906 l 0.03125 3.996094 h -5.5 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 1 c 0 0.277344 0.222656 0.5 0.5 0.5 h 13 c 0.277344 0 0.5 -0.222656 0.5 -0.5 v -1 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 h -5.5 v -4 z m 0 0" fill-rule="evenodd"/>
+ </g>
+</svg>
diff --git a/panels/network/lock-small-symbolic.svg b/panels/network/lock-small-symbolic.svg
new file mode 100644
index 0000000..5d2e2a2
--- /dev/null
+++ b/panels/network/lock-small-symbolic.svg
@@ -0,0 +1 @@
+<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><path style="fill:#36343e;" d="M5 0C3.355 0 2 1.355 2 3v2H1v5h8V5H8V3c0-1.645-1.355-3-3-3zm0 2c.571 0 1 .429 1 1v2H4V3c0-.571.429-1 1-1z"/></svg>
diff --git a/panels/network/meson.build b/panels/network/meson.build
new file mode 100644
index 0000000..e6340ce
--- /dev/null
+++ b/panels/network/meson.build
@@ -0,0 +1,85 @@
+deps = common_deps + network_manager_deps + [
+ polkit_gobject_dep,
+ dependency('gmodule-2.0')
+]
+
+network_inc = include_directories('.')
+
+subdir('wireless-security')
+subdir('connection-editor')
+subdir('icons')
+
+panel_names = [
+ cappletname,
+ 'wifi'
+]
+
+panels_list += panel_names
+
+foreach name: panel_names
+ desktop = 'gnome-' + name + '-panel.desktop'
+
+ desktop_in = configure_file(
+ input: desktop + '.in.in',
+ output: desktop + '.in',
+ configuration: desktop_conf
+ )
+
+ i18n.merge_file(
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+ )
+endforeach
+
+sources = files(
+ 'cc-qr-code.c',
+ 'cc-network-panel.c',
+ 'cc-wifi-connection-row.c',
+ 'cc-wifi-connection-list.c',
+ 'cc-wifi-panel.c',
+ 'cc-wifi-hotspot-dialog.c',
+ 'net-device-bluetooth.c',
+ 'net-device-ethernet.c',
+ 'net-device-mobile.c',
+ 'net-device-wifi.c',
+ 'net-proxy.c',
+ 'net-vpn.c',
+ 'network-dialogs.c',
+ 'panel-common.c',
+ 'ui-helpers.c'
+)
+
+resource_data = files(
+ 'cc-network-panel.ui',
+ 'cc-wifi-connection-row.ui',
+ 'cc-wifi-panel.ui',
+ 'cc-wifi-hotspot-dialog.ui',
+ 'network-bluetooth.ui',
+ 'network-ethernet.ui',
+ 'network-mobile.ui',
+ 'network-proxy.ui',
+ 'network-vpn.ui',
+ 'network-wifi.ui',
+)
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ c_name: 'cc_' + cappletname,
+ dependencies: resource_data,
+ export: true
+)
+
+network_panel_lib = static_library(
+ cappletname,
+ sources: sources,
+ include_directories: [top_inc, common_inc],
+ dependencies: deps,
+ c_args: cflags,
+ link_with: libconnection_editor
+)
+panels_libs += network_panel_lib
diff --git a/panels/network/net-device-bluetooth.c b/panels/network/net-device-bluetooth.c
new file mode 100644
index 0000000..303f4a8
--- /dev/null
+++ b/panels/network/net-device-bluetooth.c
@@ -0,0 +1,201 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ * 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 "panel-common.h"
+
+#include "net-device-bluetooth.h"
+
+struct _NetDeviceBluetooth
+{
+ AdwActionRow parent;
+
+ GtkSwitch *device_off_switch;
+ GtkButton *options_button;
+
+ NMClient *client;
+ NMDevice *device;
+ gboolean updating_device;
+};
+
+G_DEFINE_TYPE (NetDeviceBluetooth, net_device_bluetooth, ADW_TYPE_ACTION_ROW)
+
+static void
+update_off_switch_from_device_state (GtkSwitch *sw,
+ NMDeviceState state,
+ NetDeviceBluetooth *self)
+{
+ self->updating_device = TRUE;
+ switch (state) {
+ case NM_DEVICE_STATE_UNMANAGED:
+ case NM_DEVICE_STATE_UNAVAILABLE:
+ case NM_DEVICE_STATE_DISCONNECTED:
+ case NM_DEVICE_STATE_DEACTIVATING:
+ case NM_DEVICE_STATE_FAILED:
+ gtk_switch_set_active (sw, FALSE);
+ break;
+ default:
+ gtk_switch_set_active (sw, TRUE);
+ break;
+ }
+ self->updating_device = FALSE;
+}
+
+static void
+nm_device_bluetooth_refresh_ui (NetDeviceBluetooth *self)
+{
+ NMDeviceState state;
+ g_autofree gchar *path = NULL;
+
+ /* set up the device on/off switch */
+ state = nm_device_get_state (self->device);
+ gtk_widget_set_visible (GTK_WIDGET (self->device_off_switch),
+ state != NM_DEVICE_STATE_UNAVAILABLE
+ && state != NM_DEVICE_STATE_UNMANAGED);
+ update_off_switch_from_device_state (self->device_off_switch, state, self);
+
+ /* set up the Options button */
+ path = g_find_program_in_path ("nm-connection-editor");
+ gtk_widget_set_visible (GTK_WIDGET (self->options_button), state != NM_DEVICE_STATE_UNMANAGED && path != NULL);
+}
+
+static void
+device_off_switch_changed_cb (NetDeviceBluetooth *self)
+{
+ const GPtrArray *acs;
+ gboolean active;
+ gint i;
+ NMActiveConnection *a;
+ NMConnection *connection;
+
+ if (self->updating_device)
+ return;
+
+ connection = net_device_get_find_connection (self->client, self->device);
+ if (connection == NULL)
+ return;
+
+ active = gtk_switch_get_active (self->device_off_switch);
+ if (active) {
+ nm_client_activate_connection_async (self->client,
+ connection,
+ self->device,
+ NULL, NULL, NULL, NULL);
+ } else {
+ const gchar *uuid;
+
+ uuid = nm_connection_get_uuid (connection);
+ acs = nm_client_get_active_connections (self->client);
+ for (i = 0; acs && i < acs->len; i++) {
+ a = (NMActiveConnection*)acs->pdata[i];
+ if (strcmp (nm_active_connection_get_uuid (a), uuid) == 0) {
+ nm_client_deactivate_connection_async (self->client, a, NULL, NULL, NULL);
+ break;
+ }
+ }
+ }
+}
+
+static void
+options_button_clicked_cb (NetDeviceBluetooth *self)
+{
+ const gchar *uuid;
+ g_autofree gchar *cmdline = NULL;
+ g_autoptr(GError) error = NULL;
+ NMConnection *connection;
+
+ connection = net_device_get_find_connection (self->client, self->device);
+ uuid = nm_connection_get_uuid (connection);
+ cmdline = g_strdup_printf ("nm-connection-editor --edit %s", uuid);
+ g_debug ("Launching '%s'\n", cmdline);
+ if (!g_spawn_command_line_async (cmdline, &error))
+ g_warning ("Failed to launch nm-connection-editor: %s", error->message);
+}
+
+static void
+net_device_bluetooth_finalize (GObject *object)
+{
+ NetDeviceBluetooth *self = NET_DEVICE_BLUETOOTH (object);
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->device);
+
+ G_OBJECT_CLASS (net_device_bluetooth_parent_class)->finalize (object);
+}
+
+static void
+net_device_bluetooth_class_init (NetDeviceBluetoothClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = net_device_bluetooth_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-bluetooth.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceBluetooth, device_off_switch);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceBluetooth, options_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, options_button_clicked_cb);
+
+}
+
+static void
+net_device_bluetooth_init (NetDeviceBluetooth *self)
+{
+ g_autofree gchar *path = NULL;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ path = g_find_program_in_path ("nm-connection-editor");
+ gtk_widget_set_visible (GTK_WIDGET (self->options_button), path != NULL);
+}
+
+NetDeviceBluetooth *
+net_device_bluetooth_new (NMClient *client, NMDevice *device)
+{
+ NetDeviceBluetooth *self;
+
+ self = g_object_new (net_device_bluetooth_get_type (), NULL);
+ self->client = g_object_ref (client);
+ self->device = g_object_ref (device);
+
+ g_signal_connect_object (device, "state-changed", G_CALLBACK (nm_device_bluetooth_refresh_ui), self, G_CONNECT_SWAPPED);
+
+ nm_device_bluetooth_refresh_ui (self);
+
+ return self;
+}
+
+NMDevice *
+net_device_bluetooth_get_device (NetDeviceBluetooth *self)
+{
+ g_return_val_if_fail (NET_IS_DEVICE_BLUETOOTH (self), NULL);
+ return self->device;
+}
diff --git a/panels/network/net-device-bluetooth.h b/panels/network/net-device-bluetooth.h
new file mode 100644
index 0000000..b0ce5ae
--- /dev/null
+++ b/panels/network/net-device-bluetooth.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ * 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 <adwaita.h>
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (NetDeviceBluetooth, net_device_bluetooth, NET, DEVICE_BLUETOOTH, AdwActionRow)
+
+NetDeviceBluetooth *net_device_bluetooth_new (NMClient *client,
+ NMDevice *device);
+
+NMDevice *net_device_bluetooth_get_device (NetDeviceBluetooth *device);
+
+G_END_DECLS
diff --git a/panels/network/net-device-ethernet.c b/panels/network/net-device-ethernet.c
new file mode 100644
index 0000000..a230498
--- /dev/null
+++ b/panels/network/net-device-ethernet.c
@@ -0,0 +1,540 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 "panel-common.h"
+
+#include "connection-editor/net-connection-editor.h"
+#include "connection-editor/ce-page.h"
+
+#include "net-device-ethernet.h"
+
+struct _NetDeviceEthernet
+{
+ AdwPreferencesGroup parent;
+
+ GtkListBox *connection_list;
+ GtkStack *connection_stack;
+ GtkButton *details_button;
+ GtkListBox *details_listbox;
+ AdwActionRow *details_row;
+ GtkSwitch *device_off_switch;
+
+ NMClient *client;
+ NMDevice *device;
+ gboolean updating_device;
+ GHashTable *connections;
+};
+
+G_DEFINE_TYPE (NetDeviceEthernet, net_device_ethernet, ADW_TYPE_PREFERENCES_GROUP)
+
+static void
+add_details_row (GtkWidget *details, gint top, const gchar *heading, const gchar *value)
+{
+ GtkWidget *heading_label;
+ GtkWidget *value_label;
+
+ heading_label = gtk_label_new (heading);
+ gtk_style_context_add_class (gtk_widget_get_style_context (heading_label), "dim-label");
+ gtk_widget_set_halign (heading_label, GTK_ALIGN_END);
+ gtk_widget_set_valign (heading_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand (heading_label, TRUE);
+
+ gtk_grid_attach (GTK_GRID (details), heading_label, 0, top, 1, 1);
+
+ value_label = gtk_label_new (value);
+ gtk_widget_set_halign (value_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand (value_label, TRUE);
+ gtk_label_set_selectable (GTK_LABEL (value_label), TRUE);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (heading_label), value_label);
+
+ gtk_grid_attach (GTK_GRID (details), value_label, 1, top, 1, 1);
+}
+
+static gchar *
+get_last_used_string (NMConnection *connection)
+{
+ 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)
+ return NULL;
+ timestamp = nm_setting_connection_get_timestamp (s_con);
+ if (timestamp == 0)
+ return g_strdup (_("never"));
+
+ /* 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)
+ return g_strdup (_("today"));
+ else if (days == 1)
+ return g_strdup (_("yesterday"));
+ else
+ return g_strdup_printf (ngettext ("%i day ago", "%i days ago", days), days);
+}
+
+static void
+add_details (GtkWidget *details, NMDevice *device, NMConnection *connection)
+{
+ NMIPConfig *ip4_config = NULL;
+ NMIPConfig *ip6_config = NULL;
+ const gchar *ip4_address = NULL;
+ const gchar *ip4_route = NULL;
+ g_autofree gchar *ip4_dns = NULL;
+ g_autofree gchar *ip6_addresses = NULL;
+ const gchar *ip6_route = NULL;
+ g_autofree gchar *ip6_dns = NULL;
+ gint i = 0;
+
+ ip4_config = nm_device_get_ip4_config (device);
+ if (ip4_config) {
+ GPtrArray *addresses;
+
+ addresses = nm_ip_config_get_addresses (ip4_config);
+ if (addresses->len > 0)
+ ip4_address = nm_ip_address_get_address (g_ptr_array_index (addresses, 0));
+
+ ip4_route = nm_ip_config_get_gateway (ip4_config);
+ ip4_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ip4_config));
+ if (!*ip4_dns)
+ ip4_dns = NULL;
+ }
+ ip6_config = nm_device_get_ip6_config (device);
+ if (ip6_config) {
+ ip6_addresses = net_device_get_ip6_addresses (ip6_config);
+ ip6_route = nm_ip_config_get_gateway (ip6_config);
+ ip6_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ip6_config));
+ if (!*ip6_dns)
+ ip6_dns = NULL;
+ }
+
+ if (ip4_address && ip6_addresses) {
+ add_details_row (details, i++, _("IPv4 Address"), ip4_address);
+ gtk_widget_set_valign (details, GTK_ALIGN_START);
+ add_details_row (details, i++, _("IPv6 Address"), ip6_addresses);
+ gtk_widget_set_valign (details, GTK_ALIGN_START);
+ } else if (ip4_address) {
+ add_details_row (details, i++, _("IP Address"), ip4_address);
+ } else if (ip6_addresses) {
+ add_details_row (details, i++, _("IP Address"), ip6_addresses);
+ }
+
+ add_details_row (details, i++, _("Hardware Address"), nm_device_get_hw_address (device));
+
+ if (ip4_route && ip6_route) {
+ g_autofree gchar *ip_routes = g_strjoin ("\n", ip4_route, ip6_route, NULL);
+ add_details_row (details, i++, _("Default Route"), ip_routes);
+ } else if (ip4_route) {
+ add_details_row (details, i++, _("Default Route"), ip4_route);
+ } else if (ip6_route) {
+ add_details_row (details, i++, _("Default Route"), ip6_route);
+ }
+
+ if (ip4_dns && ip6_dns) {
+ add_details_row (details, i++, _("DNS4"), ip4_dns);
+ add_details_row (details, i++, _("DNS6"), ip6_dns);
+ } else if (ip4_dns) {
+ add_details_row (details, i++, _("DNS"), ip4_dns);
+ } else if (ip6_dns) {
+ add_details_row (details, i++, _("DNS"), ip6_dns);
+ }
+
+ if (nm_device_get_state (device) != NM_DEVICE_STATE_ACTIVATED) {
+ g_autofree gchar *last_used = NULL;
+ last_used = get_last_used_string (connection);
+ add_details_row (details, i++, _("Last used"), last_used);
+ }
+}
+
+static void populate_ui (NetDeviceEthernet *self);
+
+static gboolean
+device_state_to_off_switch (NMDeviceState state)
+{
+ switch (state) {
+ case NM_DEVICE_STATE_UNMANAGED:
+ case NM_DEVICE_STATE_UNAVAILABLE:
+ case NM_DEVICE_STATE_DISCONNECTED:
+ case NM_DEVICE_STATE_DEACTIVATING:
+ case NM_DEVICE_STATE_FAILED:
+ return FALSE;
+ default:
+ return TRUE;
+ }
+}
+
+static void
+device_ethernet_refresh_ui (NetDeviceEthernet *self)
+{
+ NMDeviceState state;
+ g_autofree gchar *speed_text = NULL;
+ g_autofree gchar *status = NULL;
+
+ state = nm_device_get_state (self->device);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->device_off_switch),
+ state != NM_DEVICE_STATE_UNAVAILABLE
+ && state != NM_DEVICE_STATE_UNMANAGED);
+ self->updating_device = TRUE;
+ gtk_switch_set_active (self->device_off_switch, device_state_to_off_switch (state));
+ self->updating_device = FALSE;
+
+ if (state != NM_DEVICE_STATE_UNAVAILABLE) {
+ guint speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (self->device));
+ if (speed > 0) {
+ /* Translators: network device speed */
+ speed_text = g_strdup_printf (_("%d Mb/s"), speed);
+ }
+ }
+ status = panel_device_status_to_localized_string (self->device, speed_text);
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->details_row), status);
+
+ populate_ui (self);
+}
+
+static void
+editor_done (NetDeviceEthernet *self)
+{
+ device_ethernet_refresh_ui (self);
+}
+
+static void
+show_details (NetDeviceEthernet *self, GtkButton *button, const gchar *title)
+{
+ GtkWidget *row;
+ NMConnection *connection;
+ NetConnectionEditor *editor;
+
+ row = g_object_get_data (G_OBJECT (button), "row");
+ connection = NM_CONNECTION (g_object_get_data (G_OBJECT (row), "connection"));
+
+ editor = net_connection_editor_new (connection, self->device, NULL, self->client);
+ gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self))));
+ if (title)
+ net_connection_editor_set_title (editor, title);
+ g_signal_connect_object (editor, "done", G_CALLBACK (editor_done), self, G_CONNECT_SWAPPED);
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+static void
+show_details_for_row (NetDeviceEthernet *self, GtkButton *button)
+{
+ show_details (self, button, NULL);
+}
+
+static void
+details_button_clicked_cb (NetDeviceEthernet *self)
+{
+ /* Translators: This is used as the title of the connection
+ * details window for ethernet, if there is only a single
+ * profile. It is also used to display ethernet in the
+ * device list.
+ */
+ show_details (self, self->details_button, _("Wired"));
+}
+
+static void
+add_row (NetDeviceEthernet *self, NMConnection *connection)
+{
+ GtkWidget *row;
+ GtkWidget *widget;
+ GtkWidget *box;
+ GtkWidget *details;
+ NMActiveConnection *aconn;
+ gboolean active;
+
+ active = FALSE;
+
+ aconn = nm_device_get_active_connection (self->device);
+ if (aconn) {
+ const gchar *uuid1, *uuid2;
+ uuid1 = nm_active_connection_get_uuid (aconn);
+ uuid2 = nm_connection_get_uuid (connection);
+ active = g_strcmp0 (uuid1, uuid2) == 0;
+ }
+
+ row = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_append (GTK_BOX (row), box);
+ widget = gtk_label_new (nm_connection_get_id (connection));
+ gtk_widget_set_margin_start (widget, 12);
+ gtk_widget_set_margin_end (widget, 12);
+ gtk_widget_set_margin_top (widget, 8);
+ gtk_widget_set_margin_bottom (widget, 8);
+ gtk_box_append (GTK_BOX (box), widget);
+
+ if (active) {
+ widget = gtk_image_new_from_icon_name ("object-select-symbolic");
+ gtk_widget_set_halign (widget, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (widget, GTK_ALIGN_CENTER);
+ gtk_box_append (GTK_BOX (box), widget);
+
+ details = gtk_grid_new ();
+ gtk_grid_set_row_spacing (GTK_GRID (details), 10);
+ gtk_grid_set_column_spacing (GTK_GRID (details), 10);
+
+ gtk_box_append (GTK_BOX (row), details);
+
+ add_details (details, self->device, connection);
+ }
+
+ /* filler */
+ widget = gtk_label_new ("");
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_box_append (GTK_BOX (box), widget);
+
+ widget = gtk_button_new_from_icon_name ("emblem-system-symbolic");
+ gtk_widget_set_margin_start (widget, 12);
+ gtk_widget_set_margin_end (widget, 12);
+ gtk_widget_set_margin_top (widget, 8);
+ gtk_widget_set_margin_bottom (widget, 8);
+ gtk_widget_set_halign (widget, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (widget, GTK_ALIGN_CENTER);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Options…"),
+ -1);
+ gtk_box_append (GTK_BOX (box), widget);
+ g_object_set_data (G_OBJECT (widget), "edit", widget);
+ g_object_set_data (G_OBJECT (widget), "row", row);
+ g_signal_connect_object (widget, "clicked", G_CALLBACK (show_details_for_row), self, G_CONNECT_SWAPPED);
+
+ g_object_set_data (G_OBJECT (row), "connection", connection);
+
+ gtk_list_box_append (self->connection_list, row);
+}
+
+static void
+connection_removed (NetDeviceEthernet *self, NMRemoteConnection *connection)
+{
+ if (g_hash_table_remove (self->connections, connection))
+ device_ethernet_refresh_ui (self);
+}
+
+static void
+populate_ui (NetDeviceEthernet *self)
+{
+ GSList *connections, *l;
+ NMConnection *connection;
+ GtkWidget *child;
+ gint n_connections;
+
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->connection_list))) != NULL)
+ gtk_list_box_remove (self->connection_list, child);
+
+ connections = net_device_get_valid_connections (self->client, self->device);
+ for (l = connections; l; l = l->next) {
+ NMConnection *connection = l->data;
+ if (!g_hash_table_contains (self->connections, connection)) {
+ g_hash_table_add (self->connections, connection);
+ }
+ }
+ n_connections = g_slist_length (connections);
+
+ if (n_connections > 1) {
+ for (l = connections; l; l = l->next) {
+ NMConnection *connection = l->data;
+ add_row (self, connection);
+ }
+ gtk_stack_set_visible_child (self->connection_stack,
+ GTK_WIDGET (self->connection_list));
+ } else if (n_connections == 1) {
+ connection = connections->data;
+ gtk_stack_set_visible_child (self->connection_stack,
+ GTK_WIDGET (self->details_listbox));
+ g_object_set_data (G_OBJECT (self->details_button), "row", self->details_button);
+ g_object_set_data (G_OBJECT (self->details_button), "connection", connection);
+
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->connection_stack), n_connections >= 1);
+
+ g_slist_free (connections);
+}
+
+static void
+client_connection_added_cb (NetDeviceEthernet *self)
+{
+ device_ethernet_refresh_ui (self);
+}
+
+static void
+add_profile_button_clicked_cb (NetDeviceEthernet *self)
+{
+ NMConnection *connection;
+ NMSettingConnection *sc;
+ g_autofree gchar *uuid = NULL;
+ g_autofree gchar *id = NULL;
+ NetConnectionEditor *editor;
+ const GPtrArray *connections;
+ const char *iface;
+
+ connection = nm_simple_connection_new ();
+ sc = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+ nm_connection_add_setting (connection, NM_SETTING (sc));
+
+ uuid = nm_utils_uuid_generate ();
+
+ connections = nm_client_get_connections (self->client);
+ id = ce_page_get_next_available_name (connections, NAME_FORMAT_PROFILE, NULL);
+
+ g_object_set (sc,
+ NM_SETTING_CONNECTION_UUID, uuid,
+ NM_SETTING_CONNECTION_ID, id,
+ NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME,
+ NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+ NULL);
+
+ iface = nm_device_get_iface (self->device);
+ g_object_set (sc,
+ NM_SETTING_CONNECTION_INTERFACE_NAME, iface,
+ NULL);
+
+ nm_connection_add_setting (connection, nm_setting_wired_new ());
+
+ editor = net_connection_editor_new (connection, self->device, NULL, self->client);
+ gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self))));
+ g_signal_connect_object (editor, "done", G_CALLBACK (editor_done), self, G_CONNECT_SWAPPED);
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+static void
+device_off_switch_changed_cb (NetDeviceEthernet *self)
+{
+ NMConnection *connection;
+
+ if (self->updating_device)
+ return;
+
+ if (gtk_switch_get_active (self->device_off_switch)) {
+ connection = net_device_get_find_connection (self->client, self->device);
+ if (connection != NULL) {
+ nm_client_activate_connection_async (self->client,
+ connection,
+ self->device,
+ NULL, NULL, NULL, NULL);
+ }
+ } else {
+ nm_device_disconnect_async (self->device, NULL, NULL, NULL);
+ }
+}
+
+static void
+connection_list_row_activated_cb (NetDeviceEthernet *self, GtkListBoxRow *row)
+{
+ NMConnection *connection;
+ GtkWidget *child;
+
+ if (!NM_IS_DEVICE_ETHERNET (self->device) ||
+ !nm_device_ethernet_get_carrier (NM_DEVICE_ETHERNET (self->device)))
+ return;
+
+ child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row));
+ connection = NM_CONNECTION (g_object_get_data (G_OBJECT (child), "connection"));
+
+ nm_client_activate_connection_async (self->client,
+ connection,
+ self->device,
+ NULL, NULL, NULL, NULL);
+}
+
+static void
+device_ethernet_finalize (GObject *object)
+{
+ NetDeviceEthernet *self = NET_DEVICE_ETHERNET (object);
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->device);
+ g_hash_table_destroy (self->connections);
+
+ G_OBJECT_CLASS (net_device_ethernet_parent_class)->finalize (object);
+}
+
+static void
+net_device_ethernet_class_init (NetDeviceEthernetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = device_ethernet_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-ethernet.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, connection_list);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, connection_stack);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, details_button);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, details_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, details_row);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, device_off_switch);
+
+ gtk_widget_class_bind_template_callback (widget_class, connection_list_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, details_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, add_profile_button_clicked_cb);
+}
+
+static void
+net_device_ethernet_init (NetDeviceEthernet *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->connections = g_hash_table_new (NULL, NULL);
+}
+
+NetDeviceEthernet *
+net_device_ethernet_new (NMClient *client, NMDevice *device)
+{
+ NetDeviceEthernet *self;
+
+ self = g_object_new (net_device_ethernet_get_type (), NULL);
+ self->client = g_object_ref (client);
+ self->device = g_object_ref (device);
+
+ g_signal_connect_object (client, NM_CLIENT_CONNECTION_ADDED,
+ G_CALLBACK (client_connection_added_cb), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (client, NM_CLIENT_CONNECTION_REMOVED,
+ G_CALLBACK (connection_removed), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (device, "state-changed", G_CALLBACK (device_ethernet_refresh_ui), self, G_CONNECT_SWAPPED);
+
+ device_ethernet_refresh_ui (self);
+
+ return self;
+}
+
+NMDevice *
+net_device_ethernet_get_device (NetDeviceEthernet *self)
+{
+ g_return_val_if_fail (NET_IS_DEVICE_ETHERNET (self), NULL);
+ return self->device;
+}
diff --git a/panels/network/net-device-ethernet.h b/panels/network/net-device-ethernet.h
new file mode 100644
index 0000000..8a6acb8
--- /dev/null
+++ b/panels/network/net-device-ethernet.h
@@ -0,0 +1,37 @@
+/* -*- 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 <adwaita.h>
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (NetDeviceEthernet, net_device_ethernet, NET, DEVICE_ETHERNET, AdwPreferencesGroup)
+
+NetDeviceEthernet *net_device_ethernet_new (NMClient *client,
+ NMDevice *device);
+
+NMDevice *net_device_ethernet_get_device (NetDeviceEthernet *device);
+
+G_END_DECLS
diff --git a/panels/network/net-device-mobile.c b/panels/network/net-device-mobile.c
new file mode 100644
index 0000000..2c4012c
--- /dev/null
+++ b/panels/network/net-device-mobile.c
@@ -0,0 +1,912 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org>
+ *
+ * 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 <libmm-glib.h>
+#include <nma-mobile-providers.h>
+
+#include "panel-common.h"
+#include "network-dialogs.h"
+#include "net-device-mobile.h"
+
+static void nm_device_mobile_refresh_ui (NetDeviceMobile *self);
+
+struct _NetDeviceMobile
+{
+ GtkBox parent;
+
+ GtkLabel *device_label;
+ GtkSwitch *device_off_switch;
+ GtkLabel *dns4_heading_label;
+ GtkLabel *dns4_label;
+ GtkLabel *dns6_heading_label;
+ GtkLabel *dns6_label;
+ GtkLabel *imei_heading_label;
+ GtkLabel *imei_label;
+ GtkLabel *ipv4_heading_label;
+ GtkLabel *ipv4_label;
+ GtkLabel *ipv6_heading_label;
+ GtkLabel *ipv6_label;
+ GtkListStore *mobile_connections_list_store;
+ GtkComboBox *network_combo;
+ GtkLabel *network_label;
+ GtkButton *options_button;
+ GtkLabel *provider_heading_label;
+ GtkLabel *provider_label;
+ GtkLabel *route_heading_label;
+ GtkLabel *route_label;
+ GtkLabel *status_label;
+
+ NMClient *client;
+ NMDevice *device;
+ GDBusObject *modem;
+ GCancellable *cancellable;
+
+ gboolean updating_device;
+
+ /* Old MM < 0.7 support */
+ GDBusProxy *gsm_proxy;
+ GDBusProxy *cdma_proxy;
+
+ /* New MM >= 0.7 support */
+ MMObject *mm_object;
+
+ NMAMobileProvidersDatabase *mpd;
+};
+
+enum {
+ COLUMN_ID,
+ COLUMN_TITLE,
+ COLUMN_LAST
+};
+
+G_DEFINE_TYPE (NetDeviceMobile, net_device_mobile, GTK_TYPE_BOX)
+
+static void
+connection_activate_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!nm_client_activate_connection_finish (NM_CLIENT (source_object), res, &error)) {
+ /* failed to activate */
+ nm_device_mobile_refresh_ui (user_data);
+ }
+}
+
+static void
+network_combo_changed_cb (NetDeviceMobile *self)
+{
+ gboolean ret;
+ g_autofree gchar *object_path = NULL;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ NMConnection *connection;
+
+ if (self->updating_device)
+ return;
+
+ ret = gtk_combo_box_get_active_iter (self->network_combo, &iter);
+ if (!ret)
+ return;
+
+ /* get entry */
+ model = gtk_combo_box_get_model (self->network_combo);
+ gtk_tree_model_get (model, &iter,
+ COLUMN_ID, &object_path,
+ -1);
+ if (g_strcmp0 (object_path, NULL) == 0) {
+ GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self));
+ cc_network_panel_connect_to_3g_network (GTK_WIDGET (native), self->client, self->device);
+ return;
+ }
+
+ /* activate the connection */
+ g_debug ("try to switch to connection %s", object_path);
+ connection = (NMConnection*) nm_client_get_connection_by_path (self->client, object_path);
+ if (connection != NULL) {
+ nm_device_disconnect_async (self->device, NULL, NULL, NULL);
+ nm_client_activate_connection_async (self->client,
+ connection,
+ self->device, NULL, NULL,
+ connection_activate_cb,
+ self);
+ return;
+ }
+}
+
+static void
+mobilebb_enabled_toggled (NetDeviceMobile *self)
+{
+ gboolean enabled = FALSE;
+
+ if (nm_client_wwan_get_enabled (self->client)) {
+ NMDeviceState state;
+
+ state = nm_device_get_state (self->device);
+ if (state == NM_DEVICE_STATE_UNKNOWN ||
+ state == NM_DEVICE_STATE_UNMANAGED ||
+ state == NM_DEVICE_STATE_UNAVAILABLE ||
+ state == NM_DEVICE_STATE_DISCONNECTED ||
+ state == NM_DEVICE_STATE_DEACTIVATING ||
+ state == NM_DEVICE_STATE_FAILED) {
+ enabled = FALSE;
+ } else {
+ enabled = TRUE;
+ }
+ }
+
+ self->updating_device = TRUE;
+ gtk_switch_set_active (self->device_off_switch, enabled);
+ self->updating_device = FALSE;
+}
+
+static void
+device_add_device_connections (NetDeviceMobile *self,
+ NMDevice *nm_device,
+ GtkListStore *liststore,
+ GtkComboBox *combobox)
+{
+ GSList *list, *l;
+ GtkTreeIter treeiter;
+ NMActiveConnection *active_connection;
+ NMConnection *connection;
+
+ /* get the list of available connections for this device */
+ list = net_device_get_valid_connections (self->client, nm_device);
+ gtk_list_store_clear (liststore);
+ active_connection = nm_device_get_active_connection (nm_device);
+ for (l = list; l; l = g_slist_next (l)) {
+ connection = NM_CONNECTION (l->data);
+ gtk_list_store_append (liststore, &treeiter);
+ gtk_list_store_set (liststore,
+ &treeiter,
+ COLUMN_ID, nm_connection_get_uuid (connection),
+ COLUMN_TITLE, nm_connection_get_id (connection),
+ -1);
+
+ /* is this already activated? */
+ if (active_connection != NULL &&
+ g_strcmp0 (nm_connection_get_uuid (connection),
+ nm_active_connection_get_uuid (active_connection)) == 0) {
+ self->updating_device = TRUE;
+ gtk_combo_box_set_active_iter (combobox, &treeiter);
+ self->updating_device = FALSE;
+ }
+ }
+
+ /* add new connection entry */
+ gtk_list_store_append (liststore, &treeiter);
+ gtk_list_store_set (liststore,
+ &treeiter,
+ COLUMN_ID, NULL,
+ COLUMN_TITLE, _("Add new connection"),
+ -1);
+
+ g_slist_free (list);
+}
+
+static void
+device_mobile_refresh_equipment_id (NetDeviceMobile *self)
+{
+ const gchar *equipment_id = NULL;
+
+ if (self->mm_object != NULL) {
+ MMModem *modem;
+
+ /* Modem interface should always be present */
+ modem = mm_object_peek_modem (self->mm_object);
+ equipment_id = mm_modem_get_equipment_identifier (modem);
+
+ /* Set equipment ID */
+ if (equipment_id != NULL) {
+ g_debug ("[%s] Equipment ID set to '%s'",
+ mm_object_get_path (self->mm_object),
+ equipment_id);
+ }
+ } else {
+ /* Assume old MM handling */
+ equipment_id = g_object_get_data (G_OBJECT (self),
+ "ControlCenter::EquipmentIdentifier");
+ }
+
+ gtk_label_set_label (self->imei_label, equipment_id);
+ gtk_widget_set_visible (GTK_WIDGET (self->imei_heading_label), equipment_id != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->imei_label), equipment_id != NULL);
+}
+
+static gchar *
+device_mobile_find_provider (NetDeviceMobile *self,
+ const gchar *mccmnc,
+ guint32 sid)
+{
+ NMAMobileProvider *provider;
+ GString *name = NULL;
+
+ if (self->mpd == NULL) {
+ g_autoptr(GError) error = NULL;
+
+ /* Use defaults */
+ self->mpd = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error);
+ if (self->mpd == NULL) {
+ g_debug ("Couldn't load mobile providers database: %s",
+ error ? error->message : "");
+ return NULL;
+ }
+ }
+
+ if (mccmnc != NULL) {
+ provider = nma_mobile_providers_database_lookup_3gpp_mcc_mnc (self->mpd, mccmnc);
+ if (provider != NULL)
+ name = g_string_new (nma_mobile_provider_get_name (provider));
+ }
+
+ if (sid != 0) {
+ provider = nma_mobile_providers_database_lookup_cdma_sid (self->mpd, sid);
+ if (provider != NULL) {
+ if (name == NULL)
+ name = g_string_new (nma_mobile_provider_get_name (provider));
+ else
+ g_string_append_printf (name, ", %s", nma_mobile_provider_get_name (provider));
+ }
+ }
+
+ return (name != NULL ? g_string_free (name, FALSE) : NULL);
+}
+
+static void
+device_mobile_refresh_operator_name (NetDeviceMobile *self)
+{
+ g_autofree gchar *operator_name = NULL;
+
+ if (self->mm_object != NULL) {
+ MMModem3gpp *modem_3gpp;
+ MMModemCdma *modem_cdma;
+
+ modem_3gpp = mm_object_peek_modem_3gpp (self->mm_object);
+ modem_cdma = mm_object_peek_modem_cdma (self->mm_object);
+
+ if (modem_3gpp != NULL) {
+ const gchar *operator_name_unsafe;
+
+ operator_name_unsafe = mm_modem_3gpp_get_operator_name (modem_3gpp);
+ if (operator_name_unsafe != NULL && operator_name_unsafe[0] != '\0')
+ operator_name = g_strescape (operator_name_unsafe, NULL);
+ }
+
+ /* If not directly given in the 3GPP interface, try to guess from
+ * MCCMNC/SID */
+ if (operator_name == NULL) {
+ const gchar *mccmnc = NULL;
+ guint32 sid = 0;
+
+ if (modem_3gpp != NULL)
+ mccmnc = mm_modem_3gpp_get_operator_code (modem_3gpp);
+ if (modem_cdma != NULL)
+ sid = mm_modem_cdma_get_sid (modem_cdma);
+ operator_name = device_mobile_find_provider (self, mccmnc, sid);
+ }
+
+ /* Set operator name */
+ if (operator_name != NULL) {
+ g_debug ("[%s] Operator name set to '%s'",
+ mm_object_get_path (self->mm_object),
+ operator_name);
+ }
+
+ } else {
+ const gchar *gsm;
+ const gchar *cdma;
+
+ /* Assume old MM handling */
+ gsm = g_object_get_data (G_OBJECT (self),
+ "ControlCenter::OperatorNameGsm");
+ cdma = g_object_get_data (G_OBJECT (self),
+ "ControlCenter::OperatorNameCdma");
+
+ if (gsm != NULL && cdma != NULL)
+ operator_name = g_strdup_printf ("%s, %s", gsm, cdma);
+ else if (gsm != NULL)
+ operator_name = g_strdup (gsm);
+ else if (cdma != NULL)
+ operator_name = g_strdup (cdma);
+ }
+
+ gtk_label_set_label (self->provider_label, operator_name);
+ gtk_widget_set_visible (GTK_WIDGET (self->provider_heading_label), operator_name != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->provider_label), operator_name != NULL);
+}
+
+static void
+nm_device_mobile_refresh_ui (NetDeviceMobile *self)
+{
+ gboolean is_connected;
+ NMDeviceModemCapabilities caps;
+ g_autofree gchar *status = NULL;
+ NMIPConfig *ipv4_config = NULL, *ipv6_config = NULL;
+ gboolean have_ipv4_address = FALSE, have_ipv6_address = FALSE;
+ gboolean have_dns4 = FALSE, have_dns6 = FALSE;
+ const gchar *route4_text = NULL, *route6_text = NULL;
+
+ /* set up the device on/off switch */
+ gtk_widget_show (GTK_WIDGET (self->device_off_switch));
+ mobilebb_enabled_toggled (self);
+
+ /* set device state, with status */
+ status = panel_device_status_to_localized_string (self->device, NULL);
+ gtk_label_set_label (self->status_label, status);
+
+ /* sensitive for other connection types if the device is currently connected */
+ is_connected = net_device_get_find_connection (self->client, self->device) != NULL;
+ gtk_widget_set_sensitive (GTK_WIDGET (self->options_button), is_connected);
+
+ caps = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (self->device));
+ if ((caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) ||
+ (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) ||
+ (caps & NM_DEVICE_MODEM_CAPABILITY_LTE)) {
+ device_mobile_refresh_operator_name (self);
+ device_mobile_refresh_equipment_id (self);
+ }
+
+ /* add possible connections to device */
+ device_add_device_connections (self,
+ self->device,
+ self->mobile_connections_list_store,
+ self->network_combo);
+
+ ipv4_config = nm_device_get_ip4_config (self->device);
+ if (ipv4_config != NULL) {
+ GPtrArray *addresses;
+ const gchar *ipv4_text = NULL;
+ g_autofree gchar *ip4_dns = NULL;
+
+ 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;
+
+ ip4_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv4_config));
+ if (!*ip4_dns)
+ ip4_dns = NULL;
+ gtk_label_set_label (self->dns4_label, ip4_dns);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns4_heading_label), ip4_dns != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns4_label), ip4_dns != NULL);
+ have_dns4 = ip4_dns != NULL;
+
+ route4_text = nm_ip_config_get_gateway (ipv4_config);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->ipv4_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->ipv4_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns4_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns4_label));
+ }
+
+ ipv6_config = nm_device_get_ip6_config (self->device);
+ if (ipv6_config != NULL) {
+ g_autofree gchar *ipv6_text = NULL;
+ g_autofree gchar *ip6_dns = NULL;
+
+ ipv6_text = net_device_get_ip6_addresses (ipv6_config);
+ 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_valign (GTK_WIDGET (self->ipv6_heading_label), GTK_ALIGN_START);
+ gtk_widget_set_visible (GTK_WIDGET (self->ipv6_label), ipv6_text != NULL);
+ have_ipv6_address = ipv6_text != NULL;
+
+ ip6_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv6_config));
+ if (!*ip6_dns)
+ ip6_dns = NULL;
+ gtk_label_set_label (self->dns6_label, ip6_dns);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns6_heading_label), ip6_dns != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->dns6_label), ip6_dns != NULL);
+ have_dns6 = ip6_dns != NULL;
+
+ route6_text = nm_ip_config_get_gateway (ipv6_config);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->ipv6_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->ipv6_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns6_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->dns6_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 (have_dns4 && have_dns6) {
+ gtk_label_set_label (self->dns4_heading_label, _("DNS4"));
+ gtk_label_set_label (self->dns6_heading_label, _("DNS6"));
+ } else {
+ gtk_label_set_label (self->dns4_heading_label, _("DNS"));
+ gtk_label_set_label (self->dns6_heading_label, _("DNS"));
+ }
+
+ if (route4_text != NULL || route6_text != NULL) {
+ g_autofree const gchar *routes_text = NULL;
+
+ if (route4_text == NULL) {
+ routes_text = g_strdup (route6_text);
+ } else if (route6_text == NULL) {
+ routes_text = g_strdup (route4_text);
+ } else {
+ routes_text = g_strjoin ("\n", route4_text, route6_text, NULL);
+ }
+ gtk_label_set_label (self->route_label, routes_text);
+ gtk_widget_set_visible (GTK_WIDGET (self->route_heading_label), routes_text != NULL);
+ gtk_widget_set_valign (GTK_WIDGET (self->route_heading_label), GTK_ALIGN_START);
+ gtk_widget_set_visible (GTK_WIDGET (self->route_label), routes_text != NULL);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (self->route_heading_label));
+ gtk_widget_hide (GTK_WIDGET (self->route_label));
+ }
+}
+
+static void
+device_off_switch_changed_cb (NetDeviceMobile *self)
+{
+ const GPtrArray *acs;
+ gboolean active;
+ gint i;
+ NMActiveConnection *a;
+ NMConnection *connection;
+
+ if (self->updating_device)
+ return;
+
+ connection = net_device_get_find_connection (self->client, self->device);
+ if (connection == NULL)
+ return;
+
+ active = gtk_switch_get_active (self->device_off_switch);
+ if (active) {
+ nm_client_activate_connection_async (self->client,
+ connection,
+ self->device,
+ NULL, NULL, NULL, NULL);
+ } else {
+ const gchar *uuid;
+
+ uuid = nm_connection_get_uuid (connection);
+ acs = nm_client_get_active_connections (self->client);
+ for (i = 0; acs && i < acs->len; i++) {
+ a = (NMActiveConnection*)acs->pdata[i];
+ if (strcmp (nm_active_connection_get_uuid (a), uuid) == 0) {
+ nm_client_deactivate_connection_async (self->client, a, NULL, NULL, NULL);
+ break;
+ }
+ }
+ }
+}
+
+static void
+options_button_clicked_cb (NetDeviceMobile *self)
+{
+ const gchar *uuid;
+ g_autofree gchar *cmdline = NULL;
+ g_autoptr(GError) error = NULL;
+ NMConnection *connection;
+
+ connection = net_device_get_find_connection (self->client, self->device);
+ uuid = nm_connection_get_uuid (connection);
+ cmdline = g_strdup_printf ("nm-connection-editor --edit %s", uuid);
+ g_debug ("Launching '%s'\n", cmdline);
+ if (!g_spawn_command_line_async (cmdline, &error))
+ g_warning ("Failed to launch nm-connection-editor: %s", error->message);
+}
+
+static void
+device_mobile_device_got_modem_manager_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) result = NULL;
+ g_autoptr(GDBusProxy) proxy = NULL;
+ NetDeviceMobile *self = (NetDeviceMobile *)user_data;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (!proxy) {
+ g_warning ("Error creating ModemManager proxy: %s",
+ error->message);
+ return;
+ }
+
+ /* get the IMEI */
+ result = g_dbus_proxy_get_cached_property (proxy,
+ "EquipmentIdentifier");
+
+ /* save */
+ if (result)
+ g_object_set_data_full (G_OBJECT (self),
+ "ControlCenter::EquipmentIdentifier",
+ g_variant_dup_string (result, NULL),
+ g_free);
+
+ device_mobile_refresh_equipment_id (self);
+}
+
+static void
+device_mobile_save_operator_name (NetDeviceMobile *self,
+ const gchar *field,
+ const gchar *operator_name)
+{
+ gchar *operator_name_safe = NULL;
+
+ if (operator_name != NULL && operator_name[0] != '\0')
+ operator_name_safe = g_strescape (operator_name, NULL);
+
+ /* save */
+ g_object_set_data_full (G_OBJECT (self),
+ field,
+ operator_name_safe,
+ g_free);
+ /* refresh */
+ device_mobile_refresh_operator_name (self);
+}
+
+static void
+device_mobile_get_registration_info_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autofree gchar *operator_code = NULL;
+ g_autoptr(GError) error = NULL;
+ guint registration_status;
+ g_autoptr(GVariant) result = NULL;
+ g_autofree gchar *operator_name = NULL;
+ NetDeviceMobile *self = (NetDeviceMobile *)user_data;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
+ if (result == NULL) {
+ g_warning ("Error getting registration info: %s\n",
+ error->message);
+ return;
+ }
+
+ /* get values */
+ g_variant_get (result, "((uss))",
+ &registration_status,
+ &operator_code,
+ &operator_name);
+
+ /* If none give, try to guess it */
+ if (operator_name == NULL || operator_name[0] == '\0') {
+ g_free (operator_name);
+ operator_name = device_mobile_find_provider (self, operator_code, 0);
+ }
+
+ /* save and refresh */
+ device_mobile_save_operator_name (self,
+ "ControlCenter::OperatorNameGsm",
+ operator_name);
+}
+
+static void
+device_mobile_gsm_signal_cb (NetDeviceMobile *self,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters)
+{
+ guint registration_status = 0;
+ g_autofree gchar *operator_code = NULL;
+ g_autofree gchar *operator_name = NULL;
+
+ if (!g_str_equal (signal_name, "RegistrationInfo"))
+ return;
+
+ g_variant_get (parameters,
+ "(uss)",
+ &registration_status,
+ &operator_code,
+ &operator_name);
+
+ /* If none given, try to guess it */
+ if (operator_name == NULL || operator_name[0] == '\0') {
+ g_free (operator_name);
+ operator_name = device_mobile_find_provider (self, operator_code, 0);
+ }
+
+ /* save and refresh */
+ device_mobile_save_operator_name (self,
+ "ControlCenter::OperatorNameGsm",
+ operator_name);
+}
+
+static void
+device_mobile_device_got_modem_manager_gsm_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ NetDeviceMobile *self = (NetDeviceMobile *)user_data;
+
+ self->gsm_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (self->gsm_proxy == NULL) {
+ g_warning ("Error creating ModemManager GSM proxy: %s\n",
+ error->message);
+ return;
+ }
+
+ /* Setup value updates */
+ g_signal_connect_object (self->gsm_proxy,
+ "g-signal",
+ G_CALLBACK (device_mobile_gsm_signal_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ /* Load initial value */
+ g_dbus_proxy_call (self->gsm_proxy,
+ "GetRegistrationInfo",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ device_mobile_get_registration_info_cb,
+ self);
+}
+
+static void
+device_mobile_get_serving_system_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NetDeviceMobile *self = (NetDeviceMobile *)user_data;
+ g_autoptr(GVariant) result = NULL;
+ g_autoptr(GError) error = NULL;
+
+ guint32 band_class;
+ g_autofree gchar *band = NULL;
+ guint32 sid;
+ gchar *operator_name;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
+ if (result == NULL) {
+ g_warning ("Error getting serving system: %s\n",
+ error->message);
+ return;
+ }
+
+ /* get values */
+ g_variant_get (result, "((usu))",
+ &band_class,
+ &band,
+ &sid);
+
+ operator_name = device_mobile_find_provider (self, NULL, sid);
+
+ /* save and refresh */
+ device_mobile_save_operator_name (self,
+ "ControlCenter::OperatorNameCdma",
+ operator_name);
+}
+
+static void
+device_mobile_device_got_modem_manager_cdma_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ NetDeviceMobile *self = (NetDeviceMobile *)user_data;
+
+ self->cdma_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (self->cdma_proxy == NULL) {
+ g_warning ("Error creating ModemManager CDMA proxy: %s\n",
+ error->message);
+ return;
+ }
+
+ /* Load initial value */
+ g_dbus_proxy_call (self->cdma_proxy,
+ "GetServingSystem",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ device_mobile_get_serving_system_cb,
+ self);
+}
+
+static void
+operator_name_updated (NetDeviceMobile *self)
+{
+ device_mobile_refresh_operator_name (self);
+}
+
+static void
+net_device_mobile_dispose (GObject *object)
+{
+ NetDeviceMobile *self = NET_DEVICE_MOBILE (object);
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->device);
+ g_clear_object (&self->modem);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->gsm_proxy);
+ g_clear_object (&self->cdma_proxy);
+ g_clear_object (&self->mm_object);
+ g_clear_object (&self->mpd);
+
+ G_OBJECT_CLASS (net_device_mobile_parent_class)->dispose (object);
+}
+
+static void
+net_device_mobile_class_init (NetDeviceMobileClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = net_device_mobile_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-mobile.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, device_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, device_off_switch);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns4_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns4_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns6_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns6_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, imei_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, imei_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv4_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv4_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv6_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv6_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, mobile_connections_list_store);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, network_combo);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, network_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, options_button);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, provider_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, provider_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, route_heading_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, route_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, status_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, network_combo_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, options_button_clicked_cb);
+}
+
+static void
+net_device_mobile_init (NetDeviceMobile *self)
+{
+ g_autofree gchar *path = NULL;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->cancellable = g_cancellable_new ();
+
+ path = g_find_program_in_path ("nm-connection-editor");
+ gtk_widget_set_visible (GTK_WIDGET (self->options_button), path != NULL);
+}
+
+NetDeviceMobile *
+net_device_mobile_new (NMClient *client, NMDevice *device, GDBusObject *modem)
+{
+ NetDeviceMobile *self;
+ NMDeviceModemCapabilities caps;
+
+ self = g_object_new (net_device_mobile_get_type (), NULL);
+ self->client = g_object_ref (client);
+ self->device = g_object_ref (device);
+
+ g_signal_connect_object (device, "state-changed", G_CALLBACK (nm_device_mobile_refresh_ui), self, G_CONNECT_SWAPPED);
+
+ if (modem != NULL) {
+ MMModem3gpp *modem_3gpp;
+
+ self->modem = g_object_ref (modem);
+
+ /* Load equipment ID initially */
+ device_mobile_refresh_equipment_id (self);
+
+ /* Follow changes in operator name and load initial values */
+ modem_3gpp = mm_object_peek_modem_3gpp (self->mm_object);
+ if (modem_3gpp != NULL) {
+ g_signal_connect_object (modem_3gpp,
+ "notify::operator-name",
+ G_CALLBACK (operator_name_updated),
+ self,
+ G_CONNECT_SWAPPED);
+ device_mobile_refresh_operator_name (self);
+ }
+ }
+
+ caps = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (device));
+
+ /* Only load proxies if we have broadband modems of the OLD ModemManager interface */
+ if (g_str_has_prefix (nm_device_get_udi (device), "/org/freedesktop/ModemManager/") &&
+ ((caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) ||
+ (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) ||
+ (caps & NM_DEVICE_MODEM_CAPABILITY_LTE))) {
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.ModemManager",
+ nm_device_get_udi (device),
+ "org.freedesktop.ModemManager.Modem",
+ self->cancellable,
+ device_mobile_device_got_modem_manager_cb,
+ self);
+
+ if ((caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) ||
+ (caps & NM_DEVICE_MODEM_CAPABILITY_LTE)) {
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.ModemManager",
+ nm_device_get_udi (device),
+ "org.freedesktop.ModemManager.Modem.Gsm.Network",
+ self->cancellable,
+ device_mobile_device_got_modem_manager_gsm_cb,
+ self);
+ }
+
+ if (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) {
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.ModemManager",
+ nm_device_get_udi (device),
+ "org.freedesktop.ModemManager.Modem.Cdma",
+ self->cancellable,
+ device_mobile_device_got_modem_manager_cdma_cb,
+ self);
+ }
+ }
+
+ g_signal_connect_object (client, "notify::wwan-enabled",
+ G_CALLBACK (mobilebb_enabled_toggled),
+ self, G_CONNECT_SWAPPED);
+ nm_device_mobile_refresh_ui (self);
+
+ return self;
+}
+
+NMDevice *
+net_device_mobile_get_device (NetDeviceMobile *self)
+{
+ g_return_val_if_fail (NET_IS_DEVICE_MOBILE (self), NULL);
+ return self->device;
+}
+
+void
+net_device_mobile_set_title (NetDeviceMobile *self, const gchar *title)
+{
+ g_return_if_fail (NET_IS_DEVICE_MOBILE (self));
+ gtk_label_set_label (self->device_label, title);
+}
diff --git a/panels/network/net-device-mobile.h b/panels/network/net-device-mobile.h
new file mode 100644
index 0000000..7574d3f
--- /dev/null
+++ b/panels/network/net-device-mobile.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 (NetDeviceMobile, net_device_mobile, NET, DEVICE_MOBILE, GtkBox)
+
+NetDeviceMobile *net_device_mobile_new (NMClient *client,
+ NMDevice *device,
+ GDBusObject *modem);
+
+NMDevice *net_device_mobile_get_device (NetDeviceMobile *device);
+
+void net_device_mobile_set_title (NetDeviceMobile *device,
+ const gchar *title);
+
+G_END_DECLS
diff --git a/panels/network/net-device-wifi.c b/panels/network/net-device-wifi.c
new file mode 100644
index 0000000..28160b4
--- /dev/null
+++ b/panels/network/net-device-wifi.c
@@ -0,0 +1,1303 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <netinet/ether.h>
+
+#include <NetworkManager.h>
+#include <polkit/polkit.h>
+
+#include "cc-wifi-hotspot-dialog.h"
+#include "hostname-helper.h"
+#include "network-dialogs.h"
+#include "panel-common.h"
+#include "cc-list-row.h"
+
+#include "connection-editor/net-connection-editor.h"
+#include "net-device-wifi.h"
+
+#include "cc-wifi-connection-list.h"
+#include "cc-wifi-connection-row.h"
+
+#define PERIODIC_WIFI_SCAN_TIMEOUT 15
+
+static void nm_device_wifi_refresh_ui (NetDeviceWifi *self);
+static void show_wifi_list (NetDeviceWifi *self);
+static void show_hotspot_ui (NetDeviceWifi *self);
+
+
+struct _NetDeviceWifi
+{
+ AdwBin parent;
+
+ GtkBox *center_box;
+ GtkListBoxRow *connect_hidden_row;
+ GtkSwitch *device_off_switch;
+ GtkBox *header_box;
+ GtkPopover *header_button_popover;
+ GtkBox *hotspot_box;
+ CcListRow *hotspot_name_row;
+ CcListRow *hotspot_security_row;
+ CcListRow *hotspot_password_row;
+ GtkBox *listbox_box;
+ GtkStack *stack;
+ GtkListBoxRow *start_hotspot_row;
+ GtkLabel *status_label;
+ GtkLabel *title_label;
+
+ CcPanel *panel;
+ NMClient *client;
+ NMDevice *device;
+ gboolean updating_device;
+ gchar *selected_ssid_title;
+ gchar *selected_connection_id;
+ gchar *selected_ap_id;
+ CcWifiHotspotDialog *hotspot_dialog;
+
+ gint64 last_scan;
+ gboolean scanning;
+
+ guint monitor_scanning_id;
+ guint scan_id;
+ GCancellable *cancellable;
+};
+
+enum {
+ PROP_0,
+ PROP_SCANNING,
+ PROP_LAST,
+};
+
+G_DEFINE_TYPE (NetDeviceWifi, net_device_wifi, ADW_TYPE_BIN)
+
+static void
+disable_scan_timeout (NetDeviceWifi *self)
+{
+ g_debug ("Disabling periodic Wi-Fi scan");
+ if (self->monitor_scanning_id > 0) {
+ g_source_remove (self->monitor_scanning_id);
+ self->monitor_scanning_id = 0;
+ }
+ if (self->scan_id > 0) {
+ g_source_remove (self->scan_id);
+ self->scan_id = 0;
+ }
+}
+
+static void
+wireless_enabled_toggled (NetDeviceWifi *self)
+{
+ gboolean enabled;
+
+ enabled = nm_client_wireless_get_enabled (self->client);
+
+ self->updating_device = TRUE;
+ gtk_switch_set_active (self->device_off_switch, enabled);
+ if (!enabled)
+ disable_scan_timeout (self);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->connect_hidden_row), enabled);
+ self->updating_device = FALSE;
+}
+
+static NMConnection *
+find_connection_for_device (NetDeviceWifi *self,
+ NMDevice *device)
+{
+ return net_device_get_find_connection (self->client, device);
+}
+
+static gboolean
+connection_is_shared (NMConnection *c)
+{
+ NMSettingIPConfig *s_ip4;
+
+ s_ip4 = nm_connection_get_setting_ip4_config (c);
+ if (g_strcmp0 (nm_setting_ip_config_get_method (s_ip4),
+ NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+device_is_hotspot (NetDeviceWifi *self)
+{
+ NMConnection *c;
+
+ if (nm_device_get_active_connection (self->device) == NULL)
+ return FALSE;
+
+ c = find_connection_for_device (self, self->device);
+ if (c == NULL)
+ return FALSE;
+
+ return connection_is_shared (c);
+}
+
+static GBytes *
+device_get_hotspot_ssid (NetDeviceWifi *self,
+ NMDevice *device)
+{
+ NMConnection *c;
+ NMSettingWireless *sw;
+
+ c = find_connection_for_device (self, device);
+ if (c == NULL)
+ return NULL;
+
+ sw = nm_connection_get_setting_wireless (c);
+ return nm_setting_wireless_get_ssid (sw);
+}
+
+static void
+get_secrets_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ NetDeviceWifi *self = data;
+ g_autoptr(GVariant) secrets = NULL;
+ g_autoptr(GError) error = NULL;
+
+ secrets = nm_remote_connection_get_secrets_finish (NM_REMOTE_CONNECTION (source_object), res, &error);
+ if (!secrets) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get secrets: %s", error->message);
+ return;
+ }
+
+ nm_connection_update_secrets (NM_CONNECTION (source_object),
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ secrets, NULL);
+
+ nm_device_wifi_refresh_ui (self);
+}
+
+static void
+device_get_hotspot_security_details (NetDeviceWifi *self,
+ NMDevice *device,
+ gchar **secret,
+ gchar **security)
+{
+ NMConnection *c;
+ NMSettingWirelessSecurity *sws;
+ const gchar *key_mgmt;
+ const gchar *tmp_secret;
+ const gchar *tmp_security;
+
+ c = find_connection_for_device (self, device);
+ if (c == NULL)
+ return;
+
+ sws = nm_connection_get_setting_wireless_security (c);
+ if (sws == NULL)
+ return;
+
+ tmp_secret = NULL;
+ tmp_security = C_("Wifi security", "None");
+
+ /* Key management values:
+ * "none" = WEP
+ * "wpa-none" = WPAv1 Ad-Hoc mode (not supported in NM >= 0.9.4)
+ * "wpa-psk" = WPAv2 Ad-Hoc mode (eg IBSS RSN) and AP-mode WPA v1 and v2
+ */
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (sws);
+ if (strcmp (key_mgmt, "none") == 0) {
+ tmp_secret = nm_setting_wireless_security_get_wep_key (sws, 0);
+ tmp_security = _("WEP");
+ }
+ else if (strcmp (key_mgmt, "wpa-none") == 0 ||
+ strcmp (key_mgmt, "wpa-psk") == 0) {
+ tmp_secret = nm_setting_wireless_security_get_psk (sws);
+ tmp_security = _("WPA");
+ } else {
+ g_warning ("unhandled security key-mgmt: %s", key_mgmt);
+ }
+
+ /* If we don't have secrets, request them from NM and bail.
+ * We'll refresh the UI when secrets arrive.
+ */
+ if (tmp_secret == NULL) {
+ nm_remote_connection_get_secrets_async ((NMRemoteConnection*)c,
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ self->cancellable,
+ get_secrets_cb,
+ self);
+ return;
+ }
+
+ if (secret)
+ *secret = g_strdup (tmp_secret);
+ if (security)
+ *security = g_strdup (tmp_security);
+}
+
+static void
+nm_device_wifi_refresh_hotspot (NetDeviceWifi *self)
+{
+ GBytes *ssid;
+ g_autofree gchar *hotspot_secret = NULL;
+ g_autofree gchar *hotspot_security = NULL;
+ g_autofree gchar *hotspot_ssid = NULL;
+
+ /* refresh hotspot ui */
+ ssid = device_get_hotspot_ssid (self, self->device);
+ if (ssid)
+ hotspot_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+ device_get_hotspot_security_details (self,
+ self->device,
+ &hotspot_secret,
+ &hotspot_security);
+
+ g_debug ("Refreshing hotspot labels to name: '%s', security key: '%s', security: '%s'",
+ hotspot_ssid, hotspot_secret, hotspot_security);
+
+ cc_list_row_set_secondary_label (self->hotspot_name_row, hotspot_ssid);
+ gtk_widget_set_visible (GTK_WIDGET (self->hotspot_name_row), hotspot_ssid != NULL);
+
+ cc_list_row_set_secondary_label (self->hotspot_password_row, hotspot_secret);
+ gtk_widget_set_visible (GTK_WIDGET (self->hotspot_password_row), hotspot_secret != NULL);
+
+ cc_list_row_set_secondary_label (self->hotspot_security_row, hotspot_security);
+ gtk_widget_set_visible (GTK_WIDGET (self->hotspot_security_row), hotspot_security != NULL);
+}
+
+static void
+set_scanning (NetDeviceWifi *self,
+ gboolean scanning,
+ gint64 last_scan)
+{
+ gboolean scanning_changed = self->scanning != scanning;
+
+ self->scanning = scanning;
+ self->last_scan = last_scan;
+
+ if (scanning_changed)
+ g_object_notify (G_OBJECT (self), "scanning");
+}
+
+static gboolean
+update_scanning (gpointer user_data)
+{
+ NetDeviceWifi *self = user_data;
+ gint64 last_scan;
+
+ last_scan = nm_device_wifi_get_last_scan (NM_DEVICE_WIFI (self->device));
+
+ /* The last_scan property is updated after the device finished scanning,
+ * so notify about it and stop monitoring for changes.
+ */
+ if (self->last_scan != last_scan) {
+ set_scanning (self, FALSE, last_scan);
+ self->monitor_scanning_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+request_scan (gpointer user_data)
+{
+ NetDeviceWifi *self = user_data;
+
+ g_debug ("Periodic Wi-Fi scan requested");
+
+ set_scanning (self, TRUE,
+ nm_device_wifi_get_last_scan (NM_DEVICE_WIFI (self->device)));
+
+ if (self->monitor_scanning_id == 0) {
+ self->monitor_scanning_id = g_timeout_add (1500, update_scanning,
+ self);
+ }
+
+ nm_device_wifi_request_scan_async (NM_DEVICE_WIFI (self->device),
+ self->cancellable, NULL, NULL);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+nm_device_wifi_refresh_ui (NetDeviceWifi *self)
+{
+ g_autofree gchar *status = NULL;
+
+ if (device_is_hotspot (self)) {
+ nm_device_wifi_refresh_hotspot (self);
+ show_hotspot_ui (self);
+ disable_scan_timeout (self);
+ return;
+ }
+
+ if (self->scan_id == 0 &&
+ nm_client_wireless_get_enabled (self->client)) {
+ self->scan_id = g_timeout_add_seconds (PERIODIC_WIFI_SCAN_TIMEOUT,
+ request_scan, self);
+ request_scan (self);
+ }
+
+ /* keep this in sync with the signal handler setup in cc_network_panel_init */
+ wireless_enabled_toggled (self);
+
+ status = panel_device_status_to_localized_string (self->device, NULL);
+ gtk_label_set_label (self->status_label, status);
+
+ /* update list of APs */
+ show_wifi_list (self);
+}
+
+static void
+device_off_switch_changed_cb (NetDeviceWifi *self)
+{
+ gboolean active;
+
+ if (self->updating_device)
+ return;
+
+ active = gtk_switch_get_active (self->device_off_switch);
+ nm_client_dbus_set_property (self->client,
+ NM_DBUS_PATH,
+ NM_DBUS_INTERFACE,
+ "WirelessEnabled",
+ g_variant_new_boolean (active),
+ -1,
+ NULL, NULL, NULL);
+ if (!active)
+ disable_scan_timeout (self);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->connect_hidden_row), active);
+}
+
+static void
+connect_hidden (NetDeviceWifi *self)
+{
+ GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self));
+ cc_network_panel_connect_to_hidden_network (GTK_WIDGET (native), self->client);
+ gtk_popover_popdown (self->header_button_popover);
+}
+
+static void
+connection_add_activate_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NMActiveConnection *conn;
+ g_autoptr(GError) error = NULL;
+
+ conn = nm_client_add_and_activate_connection_finish (NM_CLIENT (source_object), res, &error);
+ if (!conn) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ nm_device_wifi_refresh_ui (user_data);
+ /* failed to activate */
+ g_warning ("Failed to add and activate connection '%d': %s",
+ error->code,
+ error->message);
+ }
+ return;
+ }
+}
+
+static void
+connection_activate_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!nm_client_activate_connection_finish (NM_CLIENT (source_object), res, &error)) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ nm_device_wifi_refresh_ui (user_data);
+ /* failed to activate */
+ g_debug ("Failed to add and activate connection '%d': %s",
+ error->code,
+ error->message);
+ }
+ return;
+ }
+}
+
+static gboolean
+is_8021x (NMDevice *device,
+ const char *ap_object_path)
+{
+ NM80211ApSecurityFlags wpa_flags, rsn_flags;
+ NMAccessPoint *ap;
+
+ ap = nm_device_wifi_get_access_point_by_path (NM_DEVICE_WIFI (device),
+ ap_object_path);
+ if (!ap)
+ return FALSE;
+
+ rsn_flags = nm_access_point_get_rsn_flags (ap);
+ if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
+ return TRUE;
+
+ wpa_flags = nm_access_point_get_wpa_flags (ap);
+ if (wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
+ return TRUE;
+ return FALSE;
+}
+
+static void
+wireless_try_to_connect (NetDeviceWifi *self,
+ GBytes *ssid,
+ const gchar *ap_object_path)
+{
+ const gchar *ssid_target;
+
+ if (self->updating_device)
+ return;
+
+ if (ap_object_path == NULL || ap_object_path[0] == 0)
+ return;
+
+ ssid_target = nm_utils_escape_ssid ((gpointer) g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+ g_debug ("try to connect to WIFI network %s [%s]",
+ ssid_target, ap_object_path);
+
+ /* activate the connection */
+ if (!is_8021x (self->device, ap_object_path)) {
+ g_autoptr(GPermission) permission = NULL;
+ gboolean allowed_to_share = FALSE;
+ g_autoptr(NMConnection) partial = NULL;
+
+ permission = polkit_permission_new_sync ("org.freedesktop.NetworkManager.settings.modify.system",
+ NULL, NULL, NULL);
+ if (permission)
+ allowed_to_share = g_permission_get_allowed (permission);
+
+ if (!allowed_to_share) {
+ NMSettingConnection *s_con;
+
+ s_con = (NMSettingConnection *)nm_setting_connection_new ();
+ nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL);
+ partial = nm_simple_connection_new ();
+ nm_connection_add_setting (partial, NM_SETTING (s_con));
+ }
+
+ g_debug ("no existing connection found for %s, creating and activating one", ssid_target);
+ nm_client_add_and_activate_connection_async (self->client,
+ partial,
+ self->device,
+ ap_object_path,
+ self->cancellable,
+ connection_add_activate_cb,
+ self);
+ } else {
+ g_autoptr(GVariantBuilder) builder = NULL;
+ GVariant *parameters;
+
+ g_debug ("no existing connection found for %s, creating", ssid_target);
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("av"));
+ g_variant_builder_add (builder, "v", g_variant_new_string ("connect-8021x-wifi"));
+ g_variant_builder_add (builder, "v", g_variant_new_string (nm_object_get_path (NM_OBJECT (self->device))));
+ g_variant_builder_add (builder, "v", g_variant_new_string (ap_object_path));
+ parameters = g_variant_new ("av", builder);
+
+ g_object_set (self->panel, "parameters", parameters, NULL);
+ }
+}
+
+static gchar *
+get_hostname (void)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) res = NULL;
+ g_autoptr(GVariant) inner = NULL;
+ g_autoptr(GError) error = NULL;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (bus == NULL) {
+ g_warning ("Failed to get system bus connection: %s", error->message);
+ return NULL;
+ }
+ res = g_dbus_connection_call_sync (bus,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.freedesktop.hostname1",
+ "PrettyHostname"),
+ (GVariantType*)"(v)",
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (res == NULL) {
+ g_warning ("Getting pretty hostname failed: %s", error->message);
+ return NULL;
+ }
+
+ g_variant_get (res, "(v)", &inner);
+ return g_variant_dup_string (inner, NULL);
+}
+
+static gboolean
+is_hotspot_connection (NMConnection *connection)
+{
+ NMSettingConnection *sc;
+ NMSettingWireless *sw;
+ NMSettingIPConfig *sip;
+ NMSetting *setting;
+
+ sc = nm_connection_get_setting_connection (connection);
+ if (g_strcmp0 (nm_setting_connection_get_connection_type (sc), "802-11-wireless") != 0) {
+ return FALSE;
+ }
+ sw = nm_connection_get_setting_wireless (connection);
+ if (g_strcmp0 (nm_setting_wireless_get_mode (sw), "adhoc") != 0 &&
+ g_strcmp0 (nm_setting_wireless_get_mode (sw), "ap") != 0) {
+ return FALSE;
+ }
+ setting = nm_connection_get_setting_by_name (connection, NM_SETTING_WIRELESS_SETTING_NAME);
+ if (!setting)
+ return FALSE;
+
+ sip = nm_connection_get_setting_ip4_config (connection);
+ if (g_strcmp0 (nm_setting_ip_config_get_method (sip), "shared") != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+show_hotspot_ui (NetDeviceWifi *self)
+{
+ /* show hotspot tab */
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->hotspot_box));
+}
+
+static void
+activate_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (nm_client_activate_connection_finish (NM_CLIENT (source_object), res, &error) == NULL) {
+ g_warning ("Failed to add new connection: (%d) %s",
+ error->code,
+ error->message);
+ return;
+ }
+
+ /* show hotspot tab */
+ nm_device_wifi_refresh_ui (user_data);
+}
+
+static void
+activate_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NMActiveConnection *conn;
+ g_autoptr(GError) error = NULL;
+
+ conn = nm_client_add_and_activate_connection_finish (NM_CLIENT (source_object),
+ res, &error);
+ if (!conn) {
+ g_warning ("Failed to add new connection: (%d) %s",
+ error->code,
+ error->message);
+ return;
+ }
+
+ /* show hotspot tab */
+ nm_device_wifi_refresh_ui (user_data);
+}
+
+static NMConnection *
+net_device_wifi_get_hotspot_connection (NetDeviceWifi *self)
+{
+ GSList *connections, *l;
+ NMConnection *c = NULL;
+
+ connections = net_device_get_valid_connections (self->client, self->device);
+ for (l = connections; l; l = l->next) {
+ NMConnection *tmp = l->data;
+ if (is_hotspot_connection (tmp)) {
+ c = tmp;
+ break;
+ }
+ }
+ g_slist_free (connections);
+
+ return c;
+}
+
+static void
+overwrite_ssid_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ NMRemoteConnection *connection;
+ NMConnection *c;
+ NetDeviceWifi *self;
+
+ connection = NM_REMOTE_CONNECTION (source_object);
+
+ if (!nm_remote_connection_commit_changes_finish (connection, res, &error)) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to save hotspot's settings to disk: %s",
+ error->message);
+ return;
+ }
+
+ self = user_data;
+ c = net_device_wifi_get_hotspot_connection (self);
+
+ g_debug ("activate existing hotspot connection\n");
+ nm_client_activate_connection_async (self->client,
+ c,
+ self->device,
+ NULL,
+ self->cancellable,
+ activate_cb,
+ self);
+}
+
+static void
+on_wifi_hotspot_dialog_respnse_cb (GtkDialog *dialog,
+ gint response,
+ NetDeviceWifi *self)
+{
+ if (response == GTK_RESPONSE_APPLY) {
+ NMConnection *connection;
+
+ connection = cc_wifi_hotspot_dialog_get_connection (self->hotspot_dialog);
+ if (NM_IS_REMOTE_CONNECTION (connection))
+ nm_remote_connection_commit_changes_async (NM_REMOTE_CONNECTION (connection),
+ TRUE,
+ self->cancellable,
+ overwrite_ssid_cb,
+ self);
+ else
+ nm_client_add_and_activate_connection_async (self->client,
+ connection,
+ self->device,
+ NULL,
+ self->cancellable,
+ activate_new_cb,
+ self);
+ }
+
+ gtk_widget_hide (GTK_WIDGET (self->hotspot_dialog));
+}
+
+static void
+start_hotspot (NetDeviceWifi *self)
+{
+ GtkNative *native;
+ NMConnection *c;
+ g_autofree gchar *hostname = NULL;
+ g_autofree gchar *ssid = NULL;
+
+ native = gtk_widget_get_native (GTK_WIDGET (self));
+
+ if (!self->hotspot_dialog)
+ self->hotspot_dialog = cc_wifi_hotspot_dialog_new (GTK_WINDOW (native));
+ cc_wifi_hotspot_dialog_set_device (self->hotspot_dialog, NM_DEVICE_WIFI (self->device));
+ hostname = get_hostname ();
+ ssid = pretty_hostname_to_ssid (hostname);
+ cc_wifi_hotspot_dialog_set_hostname (self->hotspot_dialog, ssid);
+ c = net_device_wifi_get_hotspot_connection (self);
+ if (c)
+ cc_wifi_hotspot_dialog_set_connection (self->hotspot_dialog, c);
+
+ g_signal_connect_after (self->hotspot_dialog, "response", G_CALLBACK (on_wifi_hotspot_dialog_respnse_cb), self);
+ gtk_window_present (GTK_WINDOW (self->hotspot_dialog));
+ gtk_popover_popdown (self->header_button_popover);
+}
+
+static void
+stop_shared_connection (NetDeviceWifi *self)
+{
+ const GPtrArray *connections;
+ const GPtrArray *devices;
+ gint i;
+ NMActiveConnection *c;
+ gboolean found = FALSE;
+
+ connections = nm_client_get_active_connections (self->client);
+ for (i = 0; connections && i < connections->len; i++) {
+ c = (NMActiveConnection *)connections->pdata[i];
+
+ devices = nm_active_connection_get_devices (c);
+ if (devices && devices->pdata[0] == self->device) {
+ nm_client_deactivate_connection_async (self->client, c, NULL, NULL, NULL);
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ g_warning ("Could not stop hotspot connection as no connection attached to the device could be found.");
+ return;
+ }
+
+ nm_device_wifi_refresh_ui (self);
+}
+
+static void
+show_wifi_list (NetDeviceWifi *self)
+{
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->listbox_box));
+}
+
+static void
+net_device_wifi_finalize (GObject *object)
+{
+ NetDeviceWifi *self = NET_DEVICE_WIFI (object);
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ disable_scan_timeout (self);
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->device);
+ g_clear_pointer (&self->selected_ssid_title, g_free);
+ g_clear_pointer (&self->selected_connection_id, g_free);
+ g_clear_pointer (&self->selected_ap_id, g_free);
+
+ G_OBJECT_CLASS (net_device_wifi_parent_class)->finalize (object);
+}
+
+static void
+net_device_wifi_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NetDeviceWifi *self = NET_DEVICE_WIFI (object);
+
+ switch (prop_id) {
+ case PROP_SCANNING:
+ g_value_set_boolean (value, self->scanning);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+really_forgotten (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(CcWifiConnectionList) list = user_data;
+ g_autoptr(GError) error = NULL;
+
+ cc_wifi_connection_list_thaw (list);
+
+ if (!nm_remote_connection_delete_finish (NM_REMOTE_CONNECTION (source_object), res, &error))
+ g_warning ("failed to delete connection %s: %s",
+ nm_object_get_path (NM_OBJECT (source_object)),
+ error->message);
+}
+
+static void
+really_forget (GtkDialog *dialog, gint response, gpointer data)
+{
+ GtkWidget *forget = data;
+ CcWifiConnectionRow *row;
+ GList *rows;
+ GList *r;
+ NMRemoteConnection *connection;
+ NetDeviceWifi *self;
+
+ gtk_window_destroy (GTK_WINDOW (dialog));
+
+ if (response != GTK_RESPONSE_OK)
+ return;
+
+ self = NET_DEVICE_WIFI (g_object_get_data (G_OBJECT (forget), "net"));
+ rows = g_object_steal_data (G_OBJECT (forget), "rows");
+ for (r = rows; r; r = r->next) {
+ row = r->data;
+ connection = NM_REMOTE_CONNECTION (cc_wifi_connection_row_get_connection (row));
+ nm_remote_connection_delete_async (connection, self->cancellable, really_forgotten, g_object_ref (data));
+ gtk_widget_hide (GTK_WIDGET (row));
+ }
+ g_list_free (rows);
+}
+
+static void
+forget_selected (GtkButton *forget, CcWifiConnectionList *list)
+{
+ GtkNative *native;
+ GtkWidget *dialog;
+
+ native = gtk_widget_get_native (GTK_WIDGET (forget));
+ dialog = gtk_message_dialog_new (GTK_WINDOW (native),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_OTHER,
+ GTK_BUTTONS_NONE,
+ NULL);
+ gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog),
+ _("Network details for the selected networks, including passwords and any custom configuration will be lost."));
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Forget"), GTK_RESPONSE_OK,
+ NULL);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (really_forget), list);
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+check_toggled (CcWifiConnectionRow *row, GParamSpec *pspec, CcWifiConnectionList *list)
+{
+ GtkWidget *forget;
+ gboolean active;
+ GList *rows;
+
+ active = cc_wifi_connection_row_get_checked (row);
+ rows = g_object_steal_data (G_OBJECT (list), "rows");
+
+ if (active) {
+ cc_wifi_connection_list_freeze (list);
+ rows = g_list_prepend (rows, row);
+ } else {
+ cc_wifi_connection_list_thaw (list);
+ rows = g_list_remove (rows, row);
+ }
+
+ g_object_set_data_full (G_OBJECT (list), "rows", rows, (GDestroyNotify)g_list_free);
+ forget = g_object_get_data (G_OBJECT (list), "forget");
+ gtk_widget_set_sensitive (forget, rows != NULL);
+}
+
+static gint
+history_sort (gconstpointer a, gconstpointer b, gpointer data)
+{
+ guint64 ta, tb;
+ NMConnection *ca, *cb;
+ NMSettingConnection *sc;
+
+ ca = cc_wifi_connection_row_get_connection (CC_WIFI_CONNECTION_ROW ((gpointer) a));
+ cb = cc_wifi_connection_row_get_connection (CC_WIFI_CONNECTION_ROW ((gpointer) b));
+
+ if (ca) {
+ sc = nm_connection_get_setting_connection (ca);
+ ta = nm_setting_connection_get_timestamp (sc);
+ } else {
+ ta = 0;
+ }
+
+ if (cb) {
+ sc = nm_connection_get_setting_connection (cb);
+ tb = nm_setting_connection_get_timestamp (sc);
+ } else {
+ tb = 0;
+ }
+
+ if (ta > tb) return -1;
+ if (tb > ta) return 1;
+
+ return 0;
+}
+
+static gint
+ap_sort (gconstpointer a, gconstpointer b, gpointer data)
+{
+ NetDeviceWifi *self = data;
+ CcWifiConnectionRow *a_row = CC_WIFI_CONNECTION_ROW ((gpointer) a);
+ CcWifiConnectionRow *b_row = CC_WIFI_CONNECTION_ROW ((gpointer) b);
+ NMActiveConnection *active_connection;
+ gboolean a_configured, b_configured;
+ NMAccessPoint *apa, *apb;
+ guint sa, sb;
+
+ /* Show the connected AP first */
+ active_connection = nm_device_get_active_connection (NM_DEVICE (self->device));
+ if (active_connection != NULL) {
+ NMConnection *connection = NM_CONNECTION (nm_active_connection_get_connection (active_connection));
+ if (connection == cc_wifi_connection_row_get_connection (a_row))
+ return -1;
+ else if (connection == cc_wifi_connection_row_get_connection (b_row))
+ return 1;
+ }
+
+ /* Show configured networks before non-configured */
+ a_configured = cc_wifi_connection_row_get_connection (a_row) != NULL;
+ b_configured = cc_wifi_connection_row_get_connection (b_row) != NULL;
+ if (a_configured != b_configured) {
+ if (a_configured) return -1;
+ if (b_configured) return 1;
+ }
+
+ /* Show higher strength networks above lower strength ones */
+
+ apa = cc_wifi_connection_row_best_access_point (a_row);
+ apb = cc_wifi_connection_row_best_access_point (b_row);
+
+ if (apa)
+ sa = nm_access_point_get_strength (apa);
+ else
+ sa = 0;
+
+ if (apb)
+ sb = nm_access_point_get_strength (apb);
+ else
+ sb = 0;
+
+ if (sa > sb) return -1;
+ if (sb > sa) return 1;
+
+ return 0;
+}
+
+static void
+show_details_for_row (NetDeviceWifi *self, CcWifiConnectionRow *row, CcWifiConnectionList *list)
+{
+ NMConnection *connection;
+ NMAccessPoint *ap;
+ NetConnectionEditor *editor;
+
+ connection = cc_wifi_connection_row_get_connection (row);
+ ap = cc_wifi_connection_row_best_access_point (row);
+
+ editor = net_connection_editor_new (connection, self->device, ap, self->client);
+ gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (row))));
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+static void
+on_connection_list_row_added_cb (NetDeviceWifi *self,
+ CcWifiConnectionRow *row,
+ CcWifiConnectionList *list)
+{
+ g_signal_connect (row, "notify::checked",
+ G_CALLBACK (check_toggled), list);
+}
+
+static void
+on_connection_list_row_removed_cb (NetDeviceWifi *self,
+ CcWifiConnectionRow *row,
+ CcWifiConnectionList *list)
+{
+ GList *rows = g_object_steal_data (G_OBJECT (list), "rows");
+ GList *item;
+ GtkWidget *forget;
+
+ item = g_list_find (rows, row);
+ if (item)
+ {
+ rows = g_list_remove (rows, row);
+ cc_wifi_connection_list_thaw (list);
+ }
+ g_object_set_data_full (G_OBJECT (list), "rows", rows, (GDestroyNotify)g_list_free);
+
+ forget = g_object_get_data (G_OBJECT (list), "forget");
+ gtk_widget_set_sensitive (forget, rows != NULL);
+}
+
+static void
+on_connection_list_row_activated_cb (NetDeviceWifi *self,
+ CcWifiConnectionRow *row,
+ CcWifiConnectionList *list)
+{
+ cc_wifi_connection_row_set_checked (row, !cc_wifi_connection_row_get_checked (row));
+}
+
+static void
+show_history (NetDeviceWifi *self)
+{
+ GtkNative *native;
+ GtkWidget *dialog;
+ GtkWidget *page;
+ GtkWidget *list_group;
+ GtkWidget *forget_group;
+ GtkListBox *listbox;
+ GtkWidget *list;
+ GtkWidget *forget;
+ GtkWidget *child;
+
+ dialog = adw_preferences_window_new ();
+ adw_preferences_window_set_search_enabled (ADW_PREFERENCES_WINDOW (dialog), FALSE);
+ adw_preferences_window_set_can_navigate_back (ADW_PREFERENCES_WINDOW (dialog), FALSE);
+ native = gtk_widget_get_native (GTK_WIDGET (self));
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (native));
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Known Wi-Fi Networks"));
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 500, 400);
+
+ page = adw_preferences_page_new ();
+ adw_preferences_window_add (ADW_PREFERENCES_WINDOW (dialog), ADW_PREFERENCES_PAGE (page));
+
+ list_group = adw_preferences_group_new ();
+ adw_preferences_page_add (ADW_PREFERENCES_PAGE (page), ADW_PREFERENCES_GROUP (list_group));
+
+ list = GTK_WIDGET (cc_wifi_connection_list_new (self->client,
+ NM_DEVICE_WIFI (self->device),
+ FALSE, FALSE, TRUE));
+ listbox = cc_wifi_connection_list_get_list_box (CC_WIFI_CONNECTION_LIST (list));
+ gtk_list_box_set_selection_mode (listbox, GTK_SELECTION_NONE);
+ gtk_list_box_set_sort_func (listbox, (GtkListBoxSortFunc)history_sort, NULL, NULL);
+ adw_preferences_group_add (ADW_PREFERENCES_GROUP (list_group), list);
+
+ /* translators: This is the label for the "Forget wireless network" functionality */
+ forget = gtk_button_new_with_mnemonic (C_("Wi-Fi Network", "_Forget"));
+ gtk_widget_set_sensitive (forget, FALSE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (forget), "destructive-action");
+
+ g_signal_connect (forget, "clicked",
+ G_CALLBACK (forget_selected), list);
+ forget_group = adw_preferences_group_new ();
+ adw_preferences_group_add (ADW_PREFERENCES_GROUP (forget_group), forget);
+ adw_preferences_page_add (ADW_PREFERENCES_PAGE (page), ADW_PREFERENCES_GROUP (forget_group));
+
+ g_object_set_data (G_OBJECT (list), "forget", forget);
+ g_object_set_data (G_OBJECT (list), "net", self);
+
+ g_signal_connect_object (list, "add-row",
+ G_CALLBACK (on_connection_list_row_added_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (list, "remove-row",
+ G_CALLBACK (on_connection_list_row_removed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (listbox, "row-activated",
+ G_CALLBACK (on_connection_list_row_activated_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (list, "configure",
+ G_CALLBACK (show_details_for_row),
+ self,
+ G_CONNECT_SWAPPED);
+
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (listbox));
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ on_connection_list_row_added_cb (self,
+ CC_WIFI_CONNECTION_ROW (child),
+ CC_WIFI_CONNECTION_LIST (list));
+ }
+
+ gtk_window_present (GTK_WINDOW (dialog));
+ gtk_popover_popdown (self->header_button_popover);
+}
+
+static void
+on_popover_row_activated_cb (GtkListBox *list,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NetDeviceWifi *self = user_data;
+
+ if (row == self->connect_hidden_row)
+ connect_hidden (self);
+ else if (row == self->start_hotspot_row)
+ start_hotspot (self);
+ else
+ show_history (self);
+}
+
+static void
+ap_activated (NetDeviceWifi *self, GtkListBoxRow *row)
+{
+ CcWifiConnectionRow *c_row;
+ NMConnection *connection;
+ NMAccessPoint *ap;
+
+ /* The mockups want a row to connecto hidden networks; this could
+ * be handeled here. */
+ if (!CC_IS_WIFI_CONNECTION_ROW (row))
+ return;
+
+ c_row = CC_WIFI_CONNECTION_ROW (row);
+
+ connection = cc_wifi_connection_row_get_connection (c_row);
+ ap = cc_wifi_connection_row_best_access_point (c_row);
+
+ if (ap != NULL) {
+ if (connection != NULL) {
+ nm_client_activate_connection_async (self->client,
+ connection,
+ self->device, NULL, self->cancellable,
+ connection_activate_cb, self);
+ } else {
+ GBytes *ssid;
+ const gchar *object_path;
+
+ ssid = nm_access_point_get_ssid (ap);
+ object_path = nm_object_get_path (NM_OBJECT (ap));
+ wireless_try_to_connect (self, ssid, object_path);
+ }
+ }
+}
+
+static void
+net_device_wifi_class_init (NetDeviceWifiClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = net_device_wifi_finalize;
+ object_class->get_property = net_device_wifi_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_SCANNING,
+ g_param_spec_boolean ("scanning",
+ "Scanning",
+ "Whether the device is scanning for access points",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-wifi.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, center_box);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, connect_hidden_row);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, device_off_switch);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, header_box);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, header_button_popover);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_box);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_name_row);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_security_row);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_password_row);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, listbox_box);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, stack);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, start_hotspot_row);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, status_label);
+ gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, title_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_popover_row_activated_cb);
+}
+
+static void
+net_device_wifi_init (NetDeviceWifi *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->cancellable = g_cancellable_new ();
+}
+
+
+static void
+nm_client_on_permission_change (NetDeviceWifi *self) {
+ NMClientPermissionResult perm;
+ NMDeviceWifiCapabilities caps;
+
+ if (nm_client_get_permissions_state (self->client) != NM_TERNARY_TRUE) {
+ /* permissions aren't ready yet */
+ return;
+ }
+
+ /* only enable the button if the user can create a hotspot */
+ perm = nm_client_get_permission_result (self->client, NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN);
+ caps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (self->device));
+ if (perm != NM_CLIENT_PERMISSION_RESULT_YES &&
+ perm != NM_CLIENT_PERMISSION_RESULT_AUTH) {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->start_hotspot_row), _("System policy prohibits use as a Hotspot"));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->start_hotspot_row), FALSE);
+ } else if (!(caps & (NM_WIFI_DEVICE_CAP_AP | NM_WIFI_DEVICE_CAP_ADHOC))) {
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->start_hotspot_row), _("Wireless device does not support Hotspot mode"));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->start_hotspot_row), FALSE);
+ } else
+ gtk_widget_set_sensitive (GTK_WIDGET (self->start_hotspot_row), TRUE);
+
+}
+
+NetDeviceWifi *
+net_device_wifi_new (CcPanel *panel, NMClient *client, NMDevice *device)
+{
+ NetDeviceWifi *self;
+ GtkListBox *listbox;
+ GtkWidget *list;
+
+ self = g_object_new (net_device_wifi_get_type (), NULL);
+ self->panel = panel;
+ self->client = g_object_ref (client);
+ self->device = g_object_ref (device);
+
+ g_signal_connect_object (client, "notify::wireless-enabled",
+ G_CALLBACK (wireless_enabled_toggled), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (device, "state-changed", G_CALLBACK (nm_device_wifi_refresh_ui), self, G_CONNECT_SWAPPED);
+
+ list = GTK_WIDGET (cc_wifi_connection_list_new (client, NM_DEVICE_WIFI (device), TRUE, TRUE, FALSE));
+ gtk_box_append (self->listbox_box, list);
+
+ listbox = cc_wifi_connection_list_get_list_box (CC_WIFI_CONNECTION_LIST (list));
+ gtk_list_box_set_sort_func (listbox, (GtkListBoxSortFunc)ap_sort, self, NULL);
+
+ g_signal_connect_object (listbox, "row-activated",
+ G_CALLBACK (ap_activated), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (list, "configure",
+ G_CALLBACK (show_details_for_row), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (client, "notify",
+ G_CALLBACK(nm_client_on_permission_change), self, G_CONNECT_SWAPPED);
+
+ nm_client_on_permission_change(self);
+
+ nm_device_wifi_refresh_ui (self);
+
+ return self;
+}
+
+NMDevice *
+net_device_wifi_get_device (NetDeviceWifi *self)
+{
+ g_return_val_if_fail (NET_IS_DEVICE_WIFI (self), NULL);
+ return self->device;
+}
+
+void
+net_device_wifi_set_title (NetDeviceWifi *self, const gchar *title)
+{
+ g_return_if_fail (NET_IS_DEVICE_WIFI (self));
+ gtk_label_set_label (self->title_label, title);
+}
+
+GtkWidget *
+net_device_wifi_get_header_widget (NetDeviceWifi *self)
+{
+ g_return_val_if_fail (NET_IS_DEVICE_WIFI (self), NULL);
+ return GTK_WIDGET (self->header_box);
+}
+
+GtkWidget *
+net_device_wifi_get_title_widget (NetDeviceWifi *self)
+{
+ g_return_val_if_fail (NET_IS_DEVICE_WIFI (self), NULL);
+ return GTK_WIDGET (self->center_box);
+}
+
+void
+net_device_wifi_turn_off_hotspot (NetDeviceWifi *self)
+{
+ g_return_if_fail (NET_IS_DEVICE_WIFI (self));
+
+ stop_shared_connection (self);
+}
+
diff --git a/panels/network/net-device-wifi.h b/panels/network/net-device-wifi.h
new file mode 100644
index 0000000..1d4ae7d
--- /dev/null
+++ b/panels/network/net-device-wifi.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <shell/cc-panel.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (NetDeviceWifi, net_device_wifi, NET, DEVICE_WIFI, AdwBin)
+
+NetDeviceWifi *net_device_wifi_new (CcPanel *panel,
+ NMClient *client,
+ NMDevice *device);
+
+NMDevice *net_device_wifi_get_device (NetDeviceWifi *device);
+
+void net_device_wifi_set_title (NetDeviceWifi *device,
+ const gchar *title);
+
+GtkWidget *net_device_wifi_get_header_widget (NetDeviceWifi *device);
+
+GtkWidget *net_device_wifi_get_title_widget (NetDeviceWifi *device);
+
+void net_device_wifi_turn_off_hotspot (NetDeviceWifi *self);
+
+G_END_DECLS
+
diff --git a/panels/network/net-proxy.c b/panels/network/net-proxy.c
new file mode 100644
index 0000000..fc6c744
--- /dev/null
+++ b/panels/network/net-proxy.c
@@ -0,0 +1,372 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <gio/gio.h>
+
+#include "net-proxy.h"
+
+typedef enum
+{
+ MODE_DISABLED,
+ MODE_MANUAL,
+ MODE_AUTOMATIC
+} ProxyMode;
+
+struct _NetProxy
+{
+ AdwBin parent;
+
+ GtkCheckButton *automatic_radio;
+ GtkDialog *dialog;
+ GtkCheckButton *manual_radio;
+ GtkCheckButton *none_radio;
+ GtkEntry *proxy_ftp_entry;
+ GtkEntry *proxy_http_entry;
+ GtkEntry *proxy_https_entry;
+ GtkEntry *proxy_ignore_entry;
+ GtkAdjustment *proxy_port_ftp_adjustment;
+ GtkAdjustment *proxy_port_http_adjustment;
+ GtkAdjustment *proxy_port_https_adjustment;
+ GtkAdjustment *proxy_port_socks_adjustment;
+ GtkEntry *proxy_socks_entry;
+ GtkEntry *proxy_url_entry;
+ GtkLabel *proxy_warning_label;
+ GtkStack *stack;
+ GtkLabel *status_label;
+
+ GSettings *settings;
+};
+
+G_DEFINE_TYPE (NetProxy, net_proxy, ADW_TYPE_BIN)
+
+static const gchar *
+panel_get_string_for_value (ProxyMode mode)
+{
+ switch (mode) {
+ case MODE_DISABLED:
+ return _("Off");
+ case MODE_MANUAL:
+ return _("Manual");
+ case MODE_AUTOMATIC:
+ return _("Automatic");
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static inline void
+panel_update_status_label (NetProxy *self,
+ ProxyMode mode)
+{
+ gtk_label_set_label (self->status_label, panel_get_string_for_value (mode));
+}
+
+static void
+check_wpad_warning (NetProxy *self)
+{
+ g_autofree gchar *autoconfig_url = NULL;
+ GString *string = NULL;
+ gboolean ret = FALSE;
+ guint mode;
+
+ string = g_string_new ("");
+
+ /* check we're using 'Automatic' */
+ mode = g_settings_get_enum (self->settings, "mode");
+ if (mode != MODE_AUTOMATIC)
+ goto out;
+
+ /* see if the PAC is blank */
+ autoconfig_url = g_settings_get_string (self->settings,
+ "autoconfig-url");
+ ret = autoconfig_url == NULL ||
+ autoconfig_url[0] == '\0';
+ if (!ret)
+ goto out;
+
+ g_string_append (string, "<small>");
+
+ /* TRANSLATORS: this is when the use leaves the PAC textbox blank */
+ g_string_append (string, _("Web Proxy Autodiscovery is used when a Configuration URL is not provided."));
+
+ g_string_append (string, "\n");
+
+ /* TRANSLATORS: WPAD is bad: if you enable it on an untrusted
+ * network, then anyone else on that network can tell your
+ * machine that it should proxy all of your web traffic
+ * through them. */
+ g_string_append (string, _("This is not recommended for untrusted public networks."));
+ g_string_append (string, "</small>");
+out:
+ gtk_label_set_markup (self->proxy_warning_label, string->str);
+ gtk_widget_set_visible (GTK_WIDGET (self->proxy_warning_label), (string->len > 0));
+
+ g_string_free (string, TRUE);
+}
+
+static void
+settings_changed_cb (NetProxy *self)
+{
+ check_wpad_warning (self);
+}
+
+static void
+panel_proxy_mode_setup_widgets (NetProxy *self, ProxyMode value)
+{
+ /* hide or show the PAC text box */
+ switch (value) {
+ case MODE_DISABLED:
+ gtk_stack_set_visible_child_name (self->stack, "disabled");
+ break;
+ case MODE_MANUAL:
+ gtk_stack_set_visible_child_name (self->stack, "manual");
+ break;
+ case MODE_AUTOMATIC:
+ gtk_stack_set_visible_child_name (self->stack, "automatic");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* perhaps show the wpad warning */
+ check_wpad_warning (self);
+}
+
+static void
+panel_proxy_mode_radio_changed_cb (NetProxy *self, GtkCheckButton *radio)
+{
+ ProxyMode value;
+
+ if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (radio)))
+ return;
+
+ /* get selected radio */
+ if (radio == self->none_radio)
+ value = MODE_DISABLED;
+ else if (radio == self->manual_radio)
+ value = MODE_MANUAL;
+ else if (radio == self->automatic_radio)
+ value = MODE_AUTOMATIC;
+ else
+ g_assert_not_reached ();
+
+ /* set */
+ g_settings_set_enum (self->settings, "mode", value);
+
+ /* hide or show the correct widgets */
+ panel_proxy_mode_setup_widgets (self, value);
+
+ /* status label */
+ panel_update_status_label (self, value);
+}
+
+static void
+show_dialog_cb (NetProxy *self)
+{
+ gtk_window_set_transient_for (GTK_WINDOW (self->dialog), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self))));
+ gtk_window_present (GTK_WINDOW (self->dialog));
+}
+
+static void
+net_proxy_finalize (GObject *object)
+{
+ NetProxy *self = NET_PROXY (object);
+
+ g_clear_object (&self->settings);
+
+ G_OBJECT_CLASS (net_proxy_parent_class)->finalize (object);
+}
+
+static void
+net_proxy_class_init (NetProxyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = net_proxy_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-proxy.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, automatic_radio);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, dialog);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, manual_radio);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, none_radio);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_ftp_entry);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_http_entry);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_https_entry);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_ignore_entry);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_ftp_adjustment);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_http_adjustment);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_https_adjustment);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_socks_adjustment);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_socks_entry);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_url_entry);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_warning_label);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, stack);
+ gtk_widget_class_bind_template_child (widget_class, NetProxy, status_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, panel_proxy_mode_radio_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, show_dialog_cb);
+}
+
+static gboolean
+get_ignore_hosts (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GVariantIter iter;
+ const gchar *s;
+ g_autofree gchar **av = NULL;
+ gchar **p;
+ gsize n;
+
+ n = g_variant_iter_init (&iter, variant);
+ p = av = g_new0 (gchar *, n + 1);
+
+ while (g_variant_iter_next (&iter, "&s", &s))
+ if (s[0] != '\0') {
+ *p = (gchar *) s;
+ ++p;
+ }
+
+ g_value_take_string (value, g_strjoinv (", ", av));
+
+ return TRUE;
+}
+
+static GVariant *
+set_ignore_hosts (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ GVariantBuilder builder;
+ const gchar *sv;
+ gchar **av, **p;
+
+ sv = g_value_get_string (value);
+ av = g_strsplit_set (sv, ", ", 0);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
+ for (p = av; *p; ++p) {
+ if (*p[0] != '\0')
+ g_variant_builder_add (&builder, "s", *p);
+ }
+
+ g_strfreev (av);
+
+ return g_variant_builder_end (&builder);
+}
+
+static void
+net_proxy_init (NetProxy *self)
+{
+ g_autoptr(GSettings) http_settings = NULL;
+ g_autoptr(GSettings) https_settings = NULL;
+ g_autoptr(GSettings) ftp_settings = NULL;
+ g_autoptr(GSettings) socks_settings = NULL;
+ ProxyMode value;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->settings = g_settings_new ("org.gnome.system.proxy");
+ g_signal_connect_object (self->settings,
+ "changed",
+ G_CALLBACK (settings_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ /* actions */
+ value = g_settings_get_enum (self->settings, "mode");
+
+ /* bind the proxy values */
+ g_settings_bind (self->settings, "autoconfig-url",
+ self->proxy_url_entry, "text",
+ G_SETTINGS_BIND_DEFAULT);
+
+ /* bind the HTTP proxy values */
+ http_settings = g_settings_get_child (self->settings, "http");
+ g_settings_bind (http_settings, "host",
+ self->proxy_http_entry, "text",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (http_settings, "port",
+ self->proxy_port_http_adjustment, "value",
+ G_SETTINGS_BIND_DEFAULT);
+
+ /* bind the HTTPS proxy values */
+ https_settings = g_settings_get_child (self->settings, "https");
+ g_settings_bind (https_settings, "host",
+ self->proxy_https_entry, "text",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (https_settings, "port",
+ self->proxy_port_https_adjustment, "value",
+ G_SETTINGS_BIND_DEFAULT);
+
+ /* bind the FTP proxy values */
+ ftp_settings = g_settings_get_child (self->settings, "ftp");
+ g_settings_bind (ftp_settings, "host",
+ self->proxy_ftp_entry, "text",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (ftp_settings, "port",
+ self->proxy_port_ftp_adjustment, "value",
+ G_SETTINGS_BIND_DEFAULT);
+
+ /* bind the SOCKS proxy values */
+ socks_settings = g_settings_get_child (self->settings, "socks");
+ g_settings_bind (socks_settings, "host",
+ self->proxy_socks_entry, "text",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (socks_settings, "port",
+ self->proxy_port_socks_adjustment, "value",
+ G_SETTINGS_BIND_DEFAULT);
+
+ /* bind the proxy ignore hosts */
+ g_settings_bind_with_mapping (self->settings, "ignore-hosts",
+ self->proxy_ignore_entry, "text",
+ G_SETTINGS_BIND_DEFAULT, get_ignore_hosts, set_ignore_hosts,
+ NULL, NULL);
+
+ /* setup the radio before connecting to the :toggled signal */
+ switch (value) {
+ case MODE_DISABLED:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->none_radio), TRUE);
+ break;
+ case MODE_MANUAL:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->manual_radio), TRUE);
+ break;
+ case MODE_AUTOMATIC:
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->automatic_radio), TRUE);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ panel_proxy_mode_setup_widgets (self, value);
+ panel_update_status_label (self, value);
+}
+
+NetProxy *
+net_proxy_new (void)
+{
+ return g_object_new (net_proxy_get_type (), NULL);
+}
diff --git a/panels/network/net-proxy.h b/panels/network/net-proxy.h
new file mode 100644
index 0000000..5469c25
--- /dev/null
+++ b/panels/network/net-proxy.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <adwaita.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (NetProxy, net_proxy, NET, PROXY, AdwBin)
+
+NetProxy *net_proxy_new (void);
+
+G_END_DECLS
diff --git a/panels/network/net-vpn.c b/panels/network/net-vpn.c
new file mode 100644
index 0000000..74009d5
--- /dev/null
+++ b/panels/network/net-vpn.c
@@ -0,0 +1,228 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 "panel-common.h"
+
+#include "net-vpn.h"
+
+#include "connection-editor/net-connection-editor.h"
+
+struct _NetVpn
+{
+ AdwActionRow parent;
+
+ GtkBox *box;
+ GtkSwitch *device_off_switch;
+
+ NMClient *client;
+ NMConnection *connection;
+ NMActiveConnection *active_connection;
+ gboolean updating_device;
+};
+
+G_DEFINE_TYPE (NetVpn, net_vpn, ADW_TYPE_ACTION_ROW)
+
+static void
+nm_device_refresh_vpn_ui (NetVpn *self)
+{
+ g_autofree char *title_escaped = NULL;
+ const GPtrArray *acs;
+ NMActiveConnection *a;
+ gint i;
+ NMVpnConnectionState state;
+
+ /* update title */
+ title_escaped = g_markup_escape_text (nm_connection_get_id (self->connection),
+ -1);
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self),
+ title_escaped);
+
+ if (self->active_connection) {
+ g_signal_handlers_disconnect_by_func (self->active_connection,
+ nm_device_refresh_vpn_ui,
+ self);
+ g_clear_object (&self->active_connection);
+ }
+
+
+ /* Default to disconnected if there is no active connection */
+ state = NM_VPN_CONNECTION_STATE_DISCONNECTED;
+ acs = nm_client_get_active_connections (self->client);
+ if (acs != NULL) {
+ const gchar *uuid;
+ uuid = nm_connection_get_uuid (self->connection);
+
+ for (i = 0; i < acs->len; i++) {
+ const gchar *auuid;
+
+ a = (NMActiveConnection*)acs->pdata[i];
+
+ auuid = nm_active_connection_get_uuid (a);
+ if (NM_IS_VPN_CONNECTION (a) && strcmp (auuid, uuid) == 0) {
+ self->active_connection = g_object_ref (a);
+ g_signal_connect_object (a, "notify::vpn-state",
+ G_CALLBACK (nm_device_refresh_vpn_ui),
+ self, G_CONNECT_SWAPPED);
+ state = nm_vpn_connection_get_vpn_state (NM_VPN_CONNECTION (a));
+ break;
+ }
+ }
+ }
+
+ self->updating_device = TRUE;
+ gtk_switch_set_active (self->device_off_switch,
+ state != NM_VPN_CONNECTION_STATE_FAILED &&
+ state != NM_VPN_CONNECTION_STATE_DISCONNECTED);
+ self->updating_device = FALSE;
+}
+
+static void
+nm_active_connections_changed (NetVpn *self)
+{
+ nm_device_refresh_vpn_ui (self);
+}
+
+static void
+device_off_toggled (NetVpn *self)
+{
+ const GPtrArray *acs;
+ gboolean active;
+ gint i;
+ NMActiveConnection *a;
+
+ if (self->updating_device)
+ return;
+
+ active = gtk_switch_get_active (self->device_off_switch);
+ if (active) {
+ nm_client_activate_connection_async (self->client,
+ self->connection, NULL, NULL,
+ NULL, NULL, NULL);
+ } else {
+ const gchar *uuid;
+
+ uuid = nm_connection_get_uuid (self->connection);
+ acs = nm_client_get_active_connections (self->client);
+ for (i = 0; acs && i < acs->len; i++) {
+ a = (NMActiveConnection*)acs->pdata[i];
+ if (strcmp (nm_active_connection_get_uuid (a), uuid) == 0) {
+ nm_client_deactivate_connection_async (self->client, a, NULL, NULL, NULL);
+ break;
+ }
+ }
+ }
+}
+
+static void
+editor_done (NetVpn *self)
+{
+ nm_device_refresh_vpn_ui (self);
+}
+
+static void
+edit_connection (NetVpn *self)
+{
+ NetConnectionEditor *editor;
+
+ editor = net_connection_editor_new (self->connection, NULL, NULL, self->client);
+ gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self))));
+ net_connection_editor_set_title (editor, nm_connection_get_id (self->connection));
+
+ g_signal_connect_object (editor, "done", G_CALLBACK (editor_done), self, G_CONNECT_SWAPPED);
+ gtk_window_present (GTK_WINDOW (editor));
+}
+
+static void
+net_vpn_finalize (GObject *object)
+{
+ NetVpn *self = NET_VPN (object);
+
+ if (self->active_connection)
+ g_signal_handlers_disconnect_by_func (self->active_connection,
+ nm_device_refresh_vpn_ui,
+ self);
+
+ g_clear_object (&self->active_connection);
+ g_clear_object (&self->client);
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (net_vpn_parent_class)->finalize (object);
+}
+
+static void
+net_vpn_class_init (NetVpnClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = net_vpn_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-vpn.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NetVpn, device_off_switch);
+
+ gtk_widget_class_bind_template_callback (widget_class, device_off_toggled);
+ gtk_widget_class_bind_template_callback (widget_class, edit_connection);
+}
+
+static void
+net_vpn_init (NetVpn *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+NetVpn *
+net_vpn_new (NMClient *client,
+ NMConnection *connection)
+{
+ NetVpn *self;
+
+ self = g_object_new (net_vpn_get_type (), NULL);
+ self->client = g_object_ref (client);
+ self->connection = g_object_ref (connection);
+
+ g_signal_connect_object (connection,
+ NM_CONNECTION_CHANGED,
+ G_CALLBACK (nm_device_refresh_vpn_ui),
+ self, G_CONNECT_SWAPPED);
+
+ nm_device_refresh_vpn_ui (self);
+
+ g_signal_connect_object (client,
+ "notify::active-connections",
+ G_CALLBACK (nm_active_connections_changed),
+ self, G_CONNECT_SWAPPED);
+
+ return self;
+}
+
+NMConnection *
+net_vpn_get_connection (NetVpn *self)
+{
+ g_return_val_if_fail (NET_IS_VPN (self), NULL);
+ return self->connection;
+}
diff --git a/panels/network/net-vpn.h b/panels/network/net-vpn.h
new file mode 100644
index 0000000..1835458
--- /dev/null
+++ b/panels/network/net-vpn.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <adwaita.h>
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (NetVpn, net_vpn, NET, VPN, AdwActionRow)
+
+NetVpn *net_vpn_new (NMClient *client,
+ NMConnection *connection);
+
+NMConnection *net_vpn_get_connection (NetVpn *vpn);
+
+G_END_DECLS
diff --git a/panels/network/network-bluetooth.ui b/panels/network/network-bluetooth.ui
new file mode 100644
index 0000000..fe3f8c1
--- /dev/null
+++ b/panels/network/network-bluetooth.ui
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="NetDeviceBluetooth" parent="AdwActionRow">
+ <property name="activatable_widget">device_off_switch</property>
+ <property name="title" translatable="yes">Wired</property>
+ <child type="suffix">
+ <object class="GtkSwitch" id="device_off_switch">
+ <property name="valign">center</property>
+ <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceBluetooth" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Turn device off</property>
+ </accessibility>
+ </object>
+ </child>
+ <child type="suffix">
+ <object class="GtkButton" id="options_button">
+ <property name="valign">center</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">emblem-system-symbolic</property>
+ <signal name="clicked" handler="options_button_clicked_cb" object="NetDeviceBluetooth" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Options…</property>
+ </accessibility>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/network-dialogs.c b/panels/network/network-dialogs.c
new file mode 100644
index 0000000..83df99a
--- /dev/null
+++ b/panels/network/network-dialogs.c
@@ -0,0 +1,499 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.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.
+ *
+ * Portions of this code were taken from network-manager-applet.
+ * Copyright 2008 - 2011 Red Hat, Inc.
+ */
+
+#include <NetworkManager.h>
+#include <nma-wifi-dialog.h>
+#include <nma-mobile-wizard.h>
+
+#include "network-dialogs.h"
+
+typedef struct {
+ NMClient *client;
+} WirelessDialogClosure;
+
+typedef struct {
+ NMClient *client;
+ NMDevice *device;
+} MobileDialogClosure;
+
+static void
+wireless_dialog_closure_closure_notify (gpointer data,
+ GClosure *gclosure)
+{
+ WirelessDialogClosure *closure = data;
+
+ g_clear_object (&closure->client);
+ g_slice_free (WirelessDialogClosure, data);
+}
+
+static void
+mobile_dialog_closure_free (gpointer data)
+{
+ MobileDialogClosure *closure = data;
+
+ g_clear_object (&closure->client);
+ g_clear_object (&closure->device);
+ g_slice_free (MobileDialogClosure, data);
+}
+
+static gboolean
+wifi_can_create_wifi_network (NMClient *client)
+{
+ NMClientPermissionResult perm;
+
+ /* FIXME: check WIFI_SHARE_PROTECTED too, and make the wireless dialog
+ * handle the permissions as well so that admins can restrict open network
+ * creation separately from protected network creation.
+ */
+ perm = nm_client_get_permission_result (client, NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN);
+ if (perm == NM_CLIENT_PERMISSION_RESULT_YES || perm == NM_CLIENT_PERMISSION_RESULT_AUTH)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+activate_existing_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!nm_client_activate_connection_finish (NM_CLIENT (source_object), res, &error))
+ g_warning ("Failed to activate connection: (%d) %s", error->code, error->message);
+}
+
+static void
+activate_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!nm_client_add_and_activate_connection_finish (NM_CLIENT (source_object), res, &error))
+ g_warning ("Failed to add new connection: (%d) %s", error->code, error->message);
+}
+
+static void
+wireless_dialog_response_cb (GtkDialog *foo,
+ gint response,
+ gpointer user_data)
+{
+ NMAWifiDialog *dialog = NMA_WIFI_DIALOG (foo);
+ WirelessDialogClosure *closure = user_data;
+ g_autoptr(NMConnection) connection = NULL;
+ NMConnection *fuzzy_match = NULL;
+ NMDevice *device;
+ NMAccessPoint *ap;
+ const GPtrArray *all;
+ guint i;
+
+ if (response != GTK_RESPONSE_OK)
+ goto done;
+
+ /* nma_wifi_dialog_get_connection() returns a connection with the
+ * refcount incremented, so the caller must remember to unref it.
+ */
+ connection = nma_wifi_dialog_get_connection (dialog, &device, &ap);
+ g_assert (connection);
+ g_assert (device);
+
+ /* Find a similar connection and use that instead */
+ all = nm_client_get_connections (closure->client);
+ for (i = 0; i < all->len; i++) {
+ if (nm_connection_compare (connection,
+ NM_CONNECTION (g_ptr_array_index (all, i)),
+ (NM_SETTING_COMPARE_FLAG_FUZZY | NM_SETTING_COMPARE_FLAG_IGNORE_ID))) {
+ fuzzy_match = NM_CONNECTION (g_ptr_array_index (all, i));
+ break;
+ }
+ }
+
+ if (fuzzy_match) {
+ nm_client_activate_connection_async (closure->client,
+ fuzzy_match,
+ device,
+ ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL,
+ NULL,
+ activate_existing_cb,
+ NULL);
+ } else {
+ NMSetting *s_con;
+ NMSettingWireless *s_wifi;
+ const char *mode = NULL;
+
+ /* Entirely new connection */
+
+ /* Don't autoconnect adhoc networks by default for now */
+ s_wifi = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS);
+ if (s_wifi)
+ mode = nm_setting_wireless_get_mode (s_wifi);
+ if (g_strcmp0 (mode, "adhoc") == 0) {
+ s_con = nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+ if (!s_con) {
+ s_con = nm_setting_connection_new ();
+ nm_connection_add_setting (connection, s_con);
+ }
+ g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL);
+ }
+
+ nm_client_add_and_activate_connection_async (closure->client,
+ connection,
+ device,
+ ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL,
+ NULL,
+ activate_new_cb,
+ NULL);
+ }
+
+done:
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+static void
+show_wireless_dialog (GtkWidget *toplevel,
+ NMClient *client,
+ GtkWidget *dialog)
+{
+ WirelessDialogClosure *closure;
+
+ g_debug ("About to parent and show a network dialog");
+
+ g_assert (GTK_IS_NATIVE (toplevel));
+ g_object_set (G_OBJECT (dialog),
+ "modal", TRUE,
+ "transient-for", toplevel,
+ NULL);
+
+ closure = g_slice_new (WirelessDialogClosure);
+ closure->client = g_object_ref (client);
+ g_signal_connect_data (dialog, "response",
+ G_CALLBACK (wireless_dialog_response_cb),
+ closure, wireless_dialog_closure_closure_notify, 0);
+
+ g_object_bind_property (G_OBJECT (toplevel), "visible",
+ G_OBJECT (dialog), "visible",
+ G_BINDING_SYNC_CREATE);
+}
+
+void
+cc_network_panel_create_wifi_network (GtkWidget *toplevel,
+ NMClient *client)
+{
+ if (wifi_can_create_wifi_network (client)) {
+ show_wireless_dialog (toplevel, client,
+ nma_wifi_dialog_new_for_create (client));
+ }
+}
+
+void
+cc_network_panel_connect_to_hidden_network (GtkWidget *toplevel,
+ NMClient *client)
+{
+ g_debug ("connect to hidden wifi");
+ show_wireless_dialog (toplevel, client,
+ nma_wifi_dialog_new_for_hidden (client));
+}
+
+void
+cc_network_panel_connect_to_8021x_network (GtkWidget *toplevel,
+ NMClient *client,
+ NMDevice *device,
+ const gchar *arg_access_point)
+{
+ NMConnection *connection;
+ NMSettingConnection *s_con;
+ NMSettingWireless *s_wifi;
+ NMSettingWirelessSecurity *s_wsec;
+ NMSetting8021x *s_8021x;
+ NM80211ApSecurityFlags wpa_flags, rsn_flags;
+ GtkWidget *dialog;
+ g_autofree gchar *uuid = NULL;
+ NMAccessPoint *ap;
+
+ g_debug ("connect to 8021x wifi");
+ ap = nm_device_wifi_get_access_point_by_path (NM_DEVICE_WIFI (device), arg_access_point);
+ if (ap == NULL) {
+ g_warning ("didn't find access point with path %s", arg_access_point);
+ return;
+ }
+
+ /* If the AP is WPA[2]-Enterprise then we need to set up a minimal 802.1x
+ * setting and ask the user for more information.
+ */
+ rsn_flags = nm_access_point_get_rsn_flags (ap);
+ wpa_flags = nm_access_point_get_wpa_flags (ap);
+ if (!(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
+ && !(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
+ g_warning ("Network panel loaded with connect-8021x-wifi but the "
+ "access point does not support 802.1x");
+ return;
+ }
+
+ connection = nm_simple_connection_new ();
+
+ /* Need a UUID for the "always ask" stuff in the Dialog of Doom */
+ s_con = (NMSettingConnection *) nm_setting_connection_new ();
+ uuid = nm_utils_uuid_generate ();
+ g_object_set (s_con, NM_SETTING_CONNECTION_UUID, uuid, NULL);
+ nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+ s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+ nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+ g_object_set (s_wifi,
+ NM_SETTING_WIRELESS_SSID, nm_access_point_get_ssid (ap),
+ NULL);
+
+ s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+ g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL);
+ nm_connection_add_setting (connection, NM_SETTING (s_wsec));
+
+ s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+ nm_setting_802_1x_add_eap_method (s_8021x, "ttls");
+ g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", NULL);
+ nm_connection_add_setting (connection, NM_SETTING (s_8021x));
+
+ dialog = nma_wifi_dialog_new (client, connection, device, ap, FALSE);
+ show_wireless_dialog (toplevel, client, dialog);
+}
+
+static void
+connect_3g (NMConnection *connection,
+ gboolean canceled,
+ gpointer user_data)
+{
+ MobileDialogClosure *closure = user_data;
+
+ if (canceled == FALSE) {
+ g_return_if_fail (connection != NULL);
+
+ /* Ask NM to add the new connection and activate it; NM will fill in the
+ * missing details based on the specific object and the device.
+ */
+ nm_client_add_and_activate_connection_async (closure->client,
+ connection,
+ closure->device,
+ "/",
+ NULL,
+ activate_new_cb,
+ NULL);
+ }
+
+ mobile_dialog_closure_free (closure);
+}
+
+static void
+cdma_mobile_wizard_done (NMAMobileWizard *wizard,
+ gboolean canceled,
+ NMAMobileWizardAccessMethod *method,
+ gpointer user_data)
+{
+ NMConnection *connection = NULL;
+
+ if (!canceled && method) {
+ NMSetting *setting;
+ g_autofree gchar *uuid = NULL;
+ g_autofree gchar *id = NULL;
+
+ if (method->devtype != NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) {
+ g_warning ("Unexpected device type (not CDMA).");
+ canceled = TRUE;
+ goto done;
+ }
+
+ connection = nm_simple_connection_new ();
+
+ setting = nm_setting_cdma_new ();
+ g_object_set (setting,
+ NM_SETTING_CDMA_NUMBER, "#777",
+ NM_SETTING_CDMA_USERNAME, method->username,
+ NM_SETTING_CDMA_PASSWORD, method->password,
+ NULL);
+ nm_connection_add_setting (connection, setting);
+
+ /* Serial setting */
+ setting = nm_setting_serial_new ();
+ g_object_set (setting,
+ NM_SETTING_SERIAL_BAUD, 115200,
+ NM_SETTING_SERIAL_BITS, 8,
+ NM_SETTING_SERIAL_PARITY, 'n',
+ NM_SETTING_SERIAL_STOPBITS, 1,
+ NULL);
+ nm_connection_add_setting (connection, setting);
+
+ nm_connection_add_setting (connection, nm_setting_ppp_new ());
+
+ setting = nm_setting_connection_new ();
+ if (method->plan_name)
+ id = g_strdup_printf ("%s %s", method->provider_name, method->plan_name);
+ else
+ id = g_strdup_printf ("%s connection", method->provider_name);
+ uuid = nm_utils_uuid_generate ();
+ g_object_set (setting,
+ NM_SETTING_CONNECTION_ID, id,
+ NM_SETTING_CONNECTION_TYPE, NM_SETTING_CDMA_SETTING_NAME,
+ NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+ NM_SETTING_CONNECTION_UUID, uuid,
+ NULL);
+ nm_connection_add_setting (connection, setting);
+ }
+
+ done:
+ connect_3g (connection, canceled, user_data);
+ nma_mobile_wizard_destroy (wizard);
+}
+
+static void
+gsm_mobile_wizard_done (NMAMobileWizard *wizard,
+ gboolean canceled,
+ NMAMobileWizardAccessMethod *method,
+ gpointer user_data)
+{
+ NMConnection *connection = NULL;
+
+ if (!canceled && method) {
+ NMSetting *setting;
+ g_autofree gchar *uuid = NULL;
+ g_autofree gchar *id = NULL;
+
+ if (method->devtype != NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) {
+ g_warning ("Unexpected device type (not GSM).");
+ canceled = TRUE;
+ goto done;
+ }
+
+ connection = nm_simple_connection_new ();
+
+ setting = nm_setting_gsm_new ();
+ g_object_set (setting,
+ NM_SETTING_GSM_NUMBER, "*99#",
+ NM_SETTING_GSM_USERNAME, method->username,
+ NM_SETTING_GSM_PASSWORD, method->password,
+ NM_SETTING_GSM_APN, method->gsm_apn,
+ NULL);
+ nm_connection_add_setting (connection, setting);
+
+ /* Serial setting */
+ setting = nm_setting_serial_new ();
+ g_object_set (setting,
+ NM_SETTING_SERIAL_BAUD, 115200,
+ NM_SETTING_SERIAL_BITS, 8,
+ NM_SETTING_SERIAL_PARITY, 'n',
+ NM_SETTING_SERIAL_STOPBITS, 1,
+ NULL);
+ nm_connection_add_setting (connection, setting);
+
+ nm_connection_add_setting (connection, nm_setting_ppp_new ());
+
+ setting = nm_setting_connection_new ();
+ if (method->plan_name)
+ id = g_strdup_printf ("%s %s", method->provider_name, method->plan_name);
+ else
+ id = g_strdup_printf ("%s connection", method->provider_name);
+ uuid = nm_utils_uuid_generate ();
+ g_object_set (setting,
+ NM_SETTING_CONNECTION_ID, id,
+ NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME,
+ NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+ NM_SETTING_CONNECTION_UUID, uuid,
+ NULL);
+ nm_connection_add_setting (connection, setting);
+ }
+
+done:
+ connect_3g (connection, canceled, user_data);
+ nma_mobile_wizard_destroy (wizard);
+}
+
+static void
+toplevel_shown (GtkWindow *toplevel,
+ GParamSpec *pspec,
+ NMAMobileWizard *wizard)
+{
+ gboolean visible = FALSE;
+
+ g_object_get (G_OBJECT (toplevel), "visible", &visible, NULL);
+ if (visible)
+ nma_mobile_wizard_present (wizard);
+}
+
+static gboolean
+show_wizard_idle_cb (NMAMobileWizard *wizard)
+{
+ nma_mobile_wizard_present (wizard);
+ return FALSE;
+}
+
+void
+cc_network_panel_connect_to_3g_network (GtkWidget *toplevel,
+ NMClient *client,
+ NMDevice *device)
+{
+ MobileDialogClosure *closure;
+ NMAMobileWizard *wizard;
+ NMDeviceModemCapabilities caps;
+ gboolean visible = FALSE;
+
+ g_debug ("connect to 3g");
+ if (!NM_IS_DEVICE_MODEM (device)) {
+ g_warning ("Network panel loaded with connect-3g but the selected device"
+ " is not a modem");
+ return;
+ }
+
+ closure = g_slice_new (MobileDialogClosure);
+ closure->client = g_object_ref (client);
+ closure->device = g_object_ref (device);
+
+ caps = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (device));
+ if (caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) {
+ wizard = nma_mobile_wizard_new (GTK_WINDOW (toplevel), NULL, NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS, FALSE,
+ gsm_mobile_wizard_done, closure);
+ if (wizard == NULL) {
+ g_warning ("failed to construct GSM wizard");
+ return;
+ }
+ } else if (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) {
+ wizard = nma_mobile_wizard_new (GTK_WINDOW (toplevel), NULL, NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO, FALSE,
+ cdma_mobile_wizard_done, closure);
+ if (wizard == NULL) {
+ g_warning ("failed to construct CDMA wizard");
+ return;
+ }
+ } else {
+ g_warning ("Network panel loaded with connect-3g but the selected device"
+ " does not support GSM or CDMA");
+ mobile_dialog_closure_free (closure);
+ return;
+ }
+
+ g_object_get (G_OBJECT (toplevel), "visible", &visible, NULL);
+ if (visible) {
+ g_debug ("Scheduling showing the Mobile wizard");
+ g_idle_add ((GSourceFunc) show_wizard_idle_cb, wizard);
+ } else {
+ g_debug ("Will show wizard a bit later, toplevel is not visible");
+ g_signal_connect (G_OBJECT (toplevel), "notify::visible",
+ G_CALLBACK (toplevel_shown), wizard);
+ }
+}
diff --git a/panels/network/network-dialogs.h b/panels/network/network-dialogs.h
new file mode 100644
index 0000000..54251c3
--- /dev/null
+++ b/panels/network/network-dialogs.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.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.
+ */
+
+#pragma once
+
+#include <NetworkManager.h>
+#include <gtk/gtk.h>
+
+void cc_network_panel_create_wifi_network (GtkWidget *toplevel,
+ NMClient *client);
+
+void cc_network_panel_connect_to_hidden_network (GtkWidget *toplevel,
+ NMClient *client);
+
+void cc_network_panel_connect_to_8021x_network (GtkWidget *toplevel,
+ NMClient *client,
+ NMDevice *device,
+ const gchar *arg_access_point);
+
+void cc_network_panel_connect_to_3g_network (GtkWidget *toplevel,
+ NMClient *client,
+ NMDevice *device);
diff --git a/panels/network/network-ethernet.ui b/panels/network/network-ethernet.ui
new file mode 100644
index 0000000..3651fa8
--- /dev/null
+++ b/panels/network/network-ethernet.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="NetDeviceEthernet" parent="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Wired</property>
+ <property name="header-suffix">
+ <object class="GtkButton">
+ <property name="icon_name">list-add-symbolic</property>
+ <accessibility>
+ <property name="label" translatable="Yes">Add Ethernet connection</property>
+ </accessibility>
+ <signal name="clicked" handler="add_profile_button_clicked_cb" object="NetDeviceEthernet" swapped="yes"/>
+ <style>
+ <class name="flat" />
+ </style>
+ </object>
+ </property>
+
+ <child>
+ <object class="GtkStack" id="connection_stack">
+ <child>
+ <object class="GtkListBox" id="connection_list">
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="connection_list_row_activated_cb" object="NetDeviceEthernet" swapped="yes"/>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ </object>
+ </child>
+
+ <!-- Single profile row -->
+ <child>
+ <object class="GtkListBox" id="details_listbox">
+ <property name="selection_mode">none</property>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ <child>
+ <object class="AdwActionRow" id="details_row">
+ <property name="activatable-widget">device_off_switch</property>
+ <child>
+ <object class="GtkSwitch" id="device_off_switch">
+ <property name="valign">center</property>
+ <accessibility>
+ <property name="label" translatable="yes">Active</property>
+ </accessibility>
+ <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceEthernet" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="details_button">
+ <property name="valign">center</property>
+ <property name="icon-name">emblem-system-symbolic</property>
+ <signal name="clicked" handler="details_button_clicked_cb" object="NetDeviceEthernet" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Options…</property>
+ </accessibility>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/network-mobile.ui b/panels/network/network-mobile.ui
new file mode 100644
index 0000000..e5d7f55
--- /dev/null
+++ b/panels/network/network-mobile.ui
@@ -0,0 +1,334 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkListStore" id="mobile_connections_list_store">
+ <columns>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ <!-- column-name title -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <template class="NetDeviceMobile" parent="GtkBox">
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="valign">start</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="imei_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">IMEI</property>
+ <property name="mnemonic_widget">imei_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="provider_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Provider</property>
+ <property name="mnemonic_widget">provider_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="imei_label">
+ <property name="xalign">0</property>
+ <property name="label">1234567890</property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="provider_label">
+ <property name="xalign">0</property>
+ <property name="label">SuperTel Supremo </property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv4_label">
+ <property name="xalign">0</property>
+ <property name="label">127.0.0.1</property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv6_label">
+ <property name="xalign">0</property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">5</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="route_label">
+ <property name="xalign">0</property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">6</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns4_label">
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">7</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns6_label">
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="max-width-chars">50</property>
+ <property name="ellipsize">end</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">8</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAlignment">
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv4_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">IP Address</property>
+ <property name="mnemonic_widget">ipv4_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ipv6_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">IPv6 Address</property>
+ <property name="mnemonic_label">ipv6_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">5</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="route_heading_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Default Route</property>
+ <property name="mnemonic_label">route_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">6</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns4_heading_label">
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">DNS</property>
+ <property name="mnemonic_widget">dns4_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">7</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dns6_heading_label">
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">DNS</property>
+ <property name="mnemonic_widget">dns6_label</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">8</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="network_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Network</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="network_combo">
+ <property name="model">mobile_connections_list_store</property>
+ <property name="entry_text_column">1</property>
+ <signal name="changed" handler="network_combo_changed_cb" object="NetDeviceMobile" swapped="yes"/>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">6</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="column-span">3</property>
+ </layout>
+ <child>
+ <object class="GtkImage">
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="xalign">1</property>
+ <property name="pixel_size">48</property>
+ <property name="icon_name">network-cellular-connected</property>
+ <property name="icon-size">6</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="valign">start</property>
+ <property name="hexpand">True</property>
+ <property name="spacing">3</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="device_label">
+ <property name="xalign">0</property>
+ <property name="label">Mobile Broadband</property>
+ <property name="ellipsize">end</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_label">
+ <property name="xalign">0</property>
+ <property name="label">Not connected</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="device_off_switch">
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <accessibility>
+ <property name="label" translatable="yes">Active</property>
+ </accessibility>
+ <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceMobile" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="options_button">
+ <property name="halign">end</property>
+ <property name="valign">end</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="margin-top">12</property>
+ <property name="icon_name">emblem-system-symbolic</property>
+ <signal name="clicked" handler="options_button_clicked_cb" object="NetDeviceMobile" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Options…</property>
+ </accessibility>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/network-proxy.ui b/panels/network/network-proxy.ui
new file mode 100644
index 0000000..f89fa04
--- /dev/null
+++ b/panels/network/network-proxy.ui
@@ -0,0 +1,404 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkAdjustment" id="proxy_port_ftp_adjustment">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="proxy_port_http_adjustment">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="proxy_port_https_adjustment">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="proxy_port_socks_adjustment">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ </object>
+ <template class="NetProxy" parent="AdwBin">
+ <child>
+ <object class="GtkListBox">
+ <property name="selection_mode">none</property>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="activatable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="margin_top">8</property>
+ <property name="margin_bottom">8</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Network Proxy</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold" />
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_label">
+ <property name="margin_top">8</property>
+ <property name="margin_bottom">8</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <signal name="clicked" handler="show_dialog_cb" object="NetProxy" swapped="yes"/>
+ <property name="icon_name">emblem-system-symbolic</property>
+ <accessibility>
+ <property name="label" translatable="yes">Options…</property>
+ </accessibility>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkDialog" id="dialog">
+ <property name="use_header_bar">1</property>
+ <property name="default_height">350</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="hide-on-close">True</property>
+ <property name="title" translatable="yes">Network Proxy</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="automatic_radio">
+ <property name="label" translatable="yes">Automatic</property>
+ <property name="group">none_radio</property>
+ <signal name="toggled" handler="panel_proxy_mode_radio_changed_cb" object="NetProxy" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="manual_radio">
+ <property name="label" translatable="yes">Manual</property>
+ <property name="group">none_radio</property>
+ <signal name="toggled" handler="panel_proxy_mode_radio_changed_cb" object="NetProxy" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="none_radio">
+ <property name="label" translatable="yes">Disabled</property>
+ <signal name="toggled" handler="panel_proxy_mode_radio_changed_cb" object="NetProxy" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="transition_type">crossfade</property>
+
+ <!-- Disabled (empty box) -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">disabled</property>
+ <property name="child">
+ <object class="GtkBox" />
+ </property>
+ </object>
+ </child>
+
+ <!-- Manual -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">manual</property>
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="valign">start</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_HTTP Proxy</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">proxy_http_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">H_TTPS Proxy</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">proxy_https_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_FTP Proxy</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">proxy_ftp_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">5</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Socks Host</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">proxy_socks_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">6</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Ignore Hosts</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">proxy_ignore_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">7</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="proxy_http_entry">
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="xalign">1</property>
+ <property name="adjustment">proxy_port_http_adjustment</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">3</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <accessibility>
+ <property name="label" translatable="yes">HTTP proxy port</property>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="proxy_https_entry">
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="proxy_ftp_entry">
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">5</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="proxy_socks_entry">
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">6</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="proxy_ignore_entry">
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">7</property>
+ <property name="column-span">2</property>
+ <property name="row-span">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="xalign">1</property>
+ <property name="adjustment">proxy_port_https_adjustment</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">4</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <accessibility>
+ <property name="label" translatable="yes">HTTPS proxy port</property>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="xalign">1</property>
+ <property name="adjustment">proxy_port_ftp_adjustment</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">5</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <accessibility>
+ <property name="label" translatable="yes">FTP proxy port</property>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="xalign">1</property>
+ <property name="adjustment">proxy_port_socks_adjustment</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">6</property>
+ <property name="column-span">1</property>
+ <property name="row-span">1</property>
+ </layout>
+ <accessibility>
+ <property name="label" translatable="yes">Socks proxy port</property>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- Automatic -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">automatic</property>
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Configuration URL</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">proxy_url_entry</property>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">0</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="proxy_url_entry">
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ <layout>
+ <property name="row">0</property>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="proxy_warning_label">
+ <property name="visible">False</property>
+ <property name="xalign">0</property>
+ <property name="wrap">True</property>
+ <property name="width_chars">50</property>
+ <layout>
+ <property name="row">1</property>
+ <property name="column">0</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/panels/network/network-vpn.ui b/panels/network/network-vpn.ui
new file mode 100644
index 0000000..c0c2913
--- /dev/null
+++ b/panels/network/network-vpn.ui
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="NetVpn" parent="AdwActionRow">
+ <property name="activatable-widget">device_off_switch</property>
+ <child type="suffix">
+ <object class="GtkSwitch" id="device_off_switch">
+ <property name="valign">center</property>
+ <signal name="notify::active" handler="device_off_toggled" object="NetVpn" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Turn VPN connection off</property>
+ </accessibility>
+ </object>
+ </child>
+ <child type="suffix">
+ <object class="GtkButton" id="options_button">
+ <property name="valign">center</property>
+ <property name="icon_name">emblem-system-symbolic</property>
+ <signal name="clicked" handler="edit_connection" object="NetVpn" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Options…</property>
+ </accessibility>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/network-wifi.ui b/panels/network/network-wifi.ui
new file mode 100644
index 0000000..be6500d
--- /dev/null
+++ b/panels/network/network-wifi.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="NetDeviceWifi" parent="AdwBin">
+
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="valign">start</property>
+ <property name="vhomogeneous">False</property>
+
+ <child>
+ <object class="GtkStackPage">
+ <property name="child">
+ <object class="GtkBox" id="listbox_box">
+ <property name="orientation">vertical</property>
+ <property name="height-request">140</property>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ <!-- Wi-Fi Hotspot deails -->
+ <child>
+ <object class="GtkStackPage">
+ <property name="child">
+ <object class="GtkListBox" id="hotspot_box">
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ <child>
+ <object class="CcListRow" id="hotspot_name_row">
+ <property name="activatable">False</property>
+ <property name="title" context="Wi-Fi Hotspot" translatable="yes">Network Name</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcListRow" id="hotspot_security_row">
+ <property name="activatable">False</property>
+ <property name="title" context="Wi-Fi Hotspot" translatable="yes">Security type</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcListRow" id="hotspot_password_row">
+ <property name="activatable">False</property>
+ <property name="title" context="Wi-Fi Hotspot" translatable="yes">Password</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </template>
+
+ <!-- Center widget -->
+ <object class="GtkBox" id="center_box">
+ <property name="orientation">vertical</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel" id="title_label">
+ <property name="label" translatable="yes">Wi-Fi</property>
+ <style>
+ <class name="title" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_label">
+ <style>
+ <class name="subtitle" />
+ </style>
+ </object>
+ </child>
+ </object>
+
+ <!-- Box with the On/Off switch + menu button -->
+ <object class="GtkBox" id="header_box">
+ <property name="spacing">6</property>
+ <property name="halign">end</property>
+ <child>
+ <object class="GtkSwitch" id="device_off_switch">
+ <property name="valign">center</property>
+ <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceWifi" swapped="yes"/>
+ <accessibility>
+ <property name="label" translatable="yes">Turn Wi-Fi off</property>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="header_button">
+ <property name="popover">header_button_popover</property>
+ <property name="icon_name">view-more-symbolic</property>
+ <accessibility>
+ <property name="label" translatable="yes">More options…</property>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+
+ <!-- Menu Popover -->
+ <object class="GtkPopover" id="header_button_popover">
+ <style>
+ <class name="menu" />
+ </style>
+ <child>
+ <object class="GtkListBox">
+ <property name="selection_mode">none</property>
+ <signal name="row_activated" handler="on_popover_row_activated_cb" />
+ <child>
+ <object class="GtkListBoxRow" id="connect_hidden_row">
+ <property name="activatable">True</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">_Connect to Hidden Network…</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow" id="start_hotspot_row">
+ <property name="activatable">True</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">_Turn On Wi-Fi Hotspot…</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="activatable">True</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">_Known Wi-Fi Networks</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/panels/network/network.gresource.xml b/panels/network/network.gresource.xml
new file mode 100644
index 0000000..d014387
--- /dev/null
+++ b/panels/network/network.gresource.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/network">
+ <!-- Network panel -->
+ <file preprocess="xml-stripblanks">cc-network-panel.ui</file>
+ <file preprocess="xml-stripblanks">cc-wifi-connection-row.ui</file>
+ <file preprocess="xml-stripblanks">cc-wifi-hotspot-dialog.ui</file>
+ <file preprocess="xml-stripblanks">network-bluetooth.ui</file>
+ <file preprocess="xml-stripblanks">network-proxy.ui</file>
+ <file preprocess="xml-stripblanks">network-vpn.ui</file>
+ <file preprocess="xml-stripblanks">network-wifi.ui</file>
+ <file preprocess="xml-stripblanks">network-mobile.ui</file>
+ <file preprocess="xml-stripblanks">network-ethernet.ui</file>
+
+ <!-- Wi-Fi panel -->
+ <file preprocess="xml-stripblanks">cc-wifi-panel.ui</file>
+ <file>wifi-panel.css</file>
+ </gresource>
+ <gresource prefix="/org/gnome/Settings/icons/scalable/actions">
+ <!-- Wi-Fi panel icons -->
+ <file preprocess="xml-stripblanks">lock-small-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">warning-small-symbolic.svg</file>
+ </gresource>
+</gresources>
diff --git a/panels/network/panel-common.c b/panels/network/panel-common.c
new file mode 100644
index 0000000..7654a56
--- /dev/null
+++ b/panels/network/panel-common.c
@@ -0,0 +1,449 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2012 Thomas Bechtold <thomasbechtold@jpberlin.de>
+ *
+ * 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 "panel-common.h"
+
+static const gchar *
+device_state_to_localized_string (NMDeviceState state)
+{
+ const gchar *value = NULL;
+
+ switch (state) {
+ case NM_DEVICE_STATE_UNKNOWN:
+ /* TRANSLATORS: device status */
+ value = _("Status unknown");
+ break;
+ case NM_DEVICE_STATE_UNMANAGED:
+ /* TRANSLATORS: device status */
+ value = _("Unmanaged");
+ break;
+ case NM_DEVICE_STATE_UNAVAILABLE:
+ /* TRANSLATORS: device status */
+ value = _("Unavailable");
+ break;
+ case NM_DEVICE_STATE_DISCONNECTED:
+ value = NULL;
+ break;
+ case NM_DEVICE_STATE_PREPARE:
+ case NM_DEVICE_STATE_CONFIG:
+ case NM_DEVICE_STATE_IP_CONFIG:
+ case NM_DEVICE_STATE_IP_CHECK:
+ /* TRANSLATORS: device status */
+ value = _("Connecting");
+ break;
+ case NM_DEVICE_STATE_NEED_AUTH:
+ /* TRANSLATORS: device status */
+ value = _("Authentication required");
+ break;
+ case NM_DEVICE_STATE_ACTIVATED:
+ /* TRANSLATORS: device status */
+ value = _("Connected");
+ break;
+ case NM_DEVICE_STATE_DEACTIVATING:
+ /* TRANSLATORS: device status */
+ value = _("Disconnecting");
+ break;
+ case NM_DEVICE_STATE_FAILED:
+ /* TRANSLATORS: device status */
+ value = _("Connection failed");
+ break;
+ default:
+ /* TRANSLATORS: device status */
+ value = _("Status unknown (missing)");
+ break;
+ }
+ return value;
+}
+
+static const gchar *
+device_state_reason_to_localized_string (NMDevice *device)
+{
+ const gchar *value = NULL;
+ NMDeviceStateReason state_reason;
+
+ /* This only covers NMDeviceStateReasons that explain why a connection
+ * failed / can't be attempted, and aren't redundant with the state
+ * (eg, NM_DEVICE_STATE_REASON_CARRIER).
+ */
+
+ state_reason = nm_device_get_state_reason (device);
+ switch (state_reason) {
+ case NM_DEVICE_STATE_REASON_CONFIG_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Configuration failed");
+ break;
+ case NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE:
+ /* TRANSLATORS: device status reason */
+ value = _("IP configuration failed");
+ break;
+ case NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED:
+ /* TRANSLATORS: device status reason */
+ value = _("IP configuration expired");
+ break;
+ case NM_DEVICE_STATE_REASON_NO_SECRETS:
+ /* TRANSLATORS: device status reason */
+ value = _("Secrets were required, but not provided");
+ break;
+ case NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT:
+ /* TRANSLATORS: device status reason */
+ value = _("802.1x supplicant disconnected");
+ break;
+ case NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("802.1x supplicant configuration failed");
+ break;
+ case NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("802.1x supplicant failed");
+ break;
+ case NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT:
+ /* TRANSLATORS: device status reason */
+ value = _("802.1x supplicant took too long to authenticate");
+ break;
+ case NM_DEVICE_STATE_REASON_PPP_START_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("PPP service failed to start");
+ break;
+ case NM_DEVICE_STATE_REASON_PPP_DISCONNECT:
+ /* TRANSLATORS: device status reason */
+ value = _("PPP service disconnected");
+ break;
+ case NM_DEVICE_STATE_REASON_PPP_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("PPP failed");
+ break;
+ case NM_DEVICE_STATE_REASON_DHCP_START_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("DHCP client failed to start");
+ break;
+ case NM_DEVICE_STATE_REASON_DHCP_ERROR:
+ /* TRANSLATORS: device status reason */
+ value = _("DHCP client error");
+ break;
+ case NM_DEVICE_STATE_REASON_DHCP_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("DHCP client failed");
+ break;
+ case NM_DEVICE_STATE_REASON_SHARED_START_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Shared connection service failed to start");
+ break;
+ case NM_DEVICE_STATE_REASON_SHARED_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Shared connection service failed");
+ break;
+ case NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("AutoIP service failed to start");
+ break;
+ case NM_DEVICE_STATE_REASON_AUTOIP_ERROR:
+ /* TRANSLATORS: device status reason */
+ value = _("AutoIP service error");
+ break;
+ case NM_DEVICE_STATE_REASON_AUTOIP_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("AutoIP service failed");
+ break;
+ case NM_DEVICE_STATE_REASON_MODEM_BUSY:
+ /* TRANSLATORS: device status reason */
+ value = _("Line busy");
+ break;
+ case NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE:
+ /* TRANSLATORS: device status reason */
+ value = _("No dial tone");
+ break;
+ case NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER:
+ /* TRANSLATORS: device status reason */
+ value = _("No carrier could be established");
+ break;
+ case NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT:
+ /* TRANSLATORS: device status reason */
+ value = _("Dialing request timed out");
+ break;
+ case NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Dialing attempt failed");
+ break;
+ case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Modem initialization failed");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Failed to select the specified APN");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
+ /* TRANSLATORS: device status reason */
+ value = _("Not searching for networks");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
+ /* TRANSLATORS: device status reason */
+ value = _("Network registration denied");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT:
+ /* TRANSLATORS: device status reason */
+ value = _("Network registration timed out");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Failed to register with the requested network");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("PIN check failed");
+ break;
+ case NM_DEVICE_STATE_REASON_FIRMWARE_MISSING:
+ /* TRANSLATORS: device status reason */
+ value = _("Firmware for the device may be missing");
+ break;
+ case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED:
+ /* TRANSLATORS: device status reason */
+ value = _("Connection disappeared");
+ break;
+ case NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED:
+ /* TRANSLATORS: device status reason */
+ value = _("Existing connection was assumed");
+ break;
+ case NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND:
+ /* TRANSLATORS: device status reason */
+ value = _("Modem not found");
+ break;
+ case NM_DEVICE_STATE_REASON_BT_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Bluetooth connection failed");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
+ /* TRANSLATORS: device status reason */
+ value = _("SIM Card not inserted");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
+ /* TRANSLATORS: device status reason */
+ value = _("SIM Pin required");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
+ /* TRANSLATORS: device status reason */
+ value = _("SIM Puk required");
+ break;
+ case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
+ /* TRANSLATORS: device status reason */
+ value = _("SIM wrong");
+ break;
+ case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
+ /* TRANSLATORS: device status reason */
+ value = _("Connection dependency failed");
+ break;
+ default:
+ /* no StateReason to show */
+ value = "";
+ break;
+ }
+
+ return value;
+}
+
+static gchar *
+get_mac_address_of_connection (NMConnection *connection)
+{
+ if (!connection)
+ return NULL;
+
+ /* check the connection type */
+ if (nm_connection_is_type (connection,
+ NM_SETTING_WIRELESS_SETTING_NAME)) {
+ /* check wireless settings */
+ NMSettingWireless *s_wireless = nm_connection_get_setting_wireless (connection);
+ if (!s_wireless)
+ return NULL;
+ return g_strdup (nm_setting_wireless_get_mac_address (s_wireless));
+ } else if (nm_connection_is_type (connection,
+ NM_SETTING_WIRED_SETTING_NAME)) {
+ /* check wired settings */
+ NMSettingWired *s_wired = nm_connection_get_setting_wired (connection);
+ if (!s_wired)
+ return NULL;
+ return g_strdup (nm_setting_wired_get_mac_address (s_wired));
+ }
+ /* no MAC address found */
+ return NULL;
+}
+
+/* returns TRUE if both MACs are equal */
+static gboolean
+compare_mac_device_with_mac_connection (NMDevice *device,
+ NMConnection *connection)
+{
+ const gchar *mac_dev = NULL;
+ g_autofree gchar *mac_conn = NULL;
+
+ mac_dev = nm_device_get_hw_address (device);
+ if (mac_dev == NULL)
+ return FALSE;
+
+ mac_conn = get_mac_address_of_connection (connection);
+ if (mac_conn == NULL)
+ return FALSE;
+
+ /* compare both MACs */
+ return g_strcmp0 (mac_dev, mac_conn) == 0;
+}
+
+gchar *
+panel_device_status_to_localized_string (NMDevice *nm_device,
+ const gchar *speed)
+{
+ NMDeviceState state;
+ GString *string;
+ const gchar *state_str = NULL, *reason_str = NULL;
+
+ string = g_string_new (NULL);
+
+ state = nm_device_get_state (nm_device);
+ if (state == NM_DEVICE_STATE_UNAVAILABLE) {
+ if (nm_device_get_firmware_missing (nm_device)) {
+ /* TRANSLATORS: device status */
+ state_str = _("Firmware missing");
+ } else if (NM_IS_DEVICE_ETHERNET (nm_device) &&
+ !nm_device_ethernet_get_carrier (NM_DEVICE_ETHERNET (nm_device))) {
+ /* TRANSLATORS: device status */
+ state_str = _("Cable unplugged");
+ }
+ }
+ if (!state_str)
+ state_str = device_state_to_localized_string (state);
+ if (state_str)
+ g_string_append (string, state_str);
+
+ if (state > NM_DEVICE_STATE_UNAVAILABLE && speed) {
+ if (string->len)
+ g_string_append (string, " - ");
+ g_string_append (string, speed);
+ } else if (state == NM_DEVICE_STATE_UNAVAILABLE ||
+ state == NM_DEVICE_STATE_DISCONNECTED ||
+ state == NM_DEVICE_STATE_DEACTIVATING ||
+ state == NM_DEVICE_STATE_FAILED) {
+ reason_str = device_state_reason_to_localized_string (nm_device);
+ if (*reason_str) {
+ if (string->len)
+ g_string_append (string, " - ");
+ g_string_append (string, reason_str);
+ }
+ }
+
+ return g_string_free (string, FALSE);
+}
+
+NMConnection *
+net_device_get_find_connection (NMClient *client, NMDevice *device)
+{
+ GSList *list, *iterator;
+ NMConnection *connection = NULL;
+ NMActiveConnection *ac;
+
+ /* is the device available in a active connection? */
+ ac = nm_device_get_active_connection (device);
+ if (ac)
+ return (NMConnection*) nm_active_connection_get_connection (ac);
+
+ /* not found in active connections - check all available connections */
+ list = net_device_get_valid_connections (client, device);
+ if (list != NULL) {
+ /* if list has only one connection, use this connection */
+ if (g_slist_length (list) == 1) {
+ connection = list->data;
+ goto out;
+ }
+
+ /* is there connection with the MAC address of the device? */
+ for (iterator = list; iterator; iterator = iterator->next) {
+ connection = iterator->data;
+ if (compare_mac_device_with_mac_connection (device,
+ connection)) {
+ goto out;
+ }
+ }
+ }
+
+ /* no connection found for the given device */
+ connection = NULL;
+out:
+ g_slist_free (list);
+ return connection;
+}
+
+GSList *
+net_device_get_valid_connections (NMClient *client, NMDevice *device)
+{
+ GSList *valid;
+ NMConnection *connection;
+ NMSettingConnection *s_con;
+ NMActiveConnection *active_connection;
+ const char *active_uuid;
+ const GPtrArray *all;
+ GPtrArray *filtered;
+ guint i;
+
+ all = nm_client_get_connections (client);
+ filtered = nm_device_filter_connections (device, all);
+
+ active_connection = nm_device_get_active_connection (device);
+ active_uuid = active_connection ? nm_active_connection_get_uuid (active_connection) : NULL;
+
+ valid = NULL;
+ for (i = 0; i < filtered->len; i++) {
+ connection = g_ptr_array_index (filtered, i);
+ s_con = nm_connection_get_setting_connection (connection);
+ if (!s_con)
+ continue;
+
+ if (nm_setting_connection_get_master (s_con) &&
+ g_strcmp0 (nm_setting_connection_get_uuid (s_con), active_uuid) != 0)
+ continue;
+
+ valid = g_slist_prepend (valid, connection);
+ }
+ g_ptr_array_free (filtered, FALSE);
+
+ return g_slist_reverse (valid);
+}
+
+gchar *
+net_device_get_ip6_addresses (NMIPConfig *ipv6_config)
+{
+ g_autoptr(GPtrArray) ipv6 = NULL;
+ GPtrArray *addresses;
+
+ addresses = nm_ip_config_get_addresses (ipv6_config);
+ if (addresses->len == 0) {
+ return NULL;
+ }
+ ipv6 = g_ptr_array_sized_new (addresses->len + 1);
+
+ for (int i = 0; i < addresses->len; i++) {
+ g_ptr_array_add (ipv6, (char *) nm_ip_address_get_address (g_ptr_array_index (addresses, i)));
+ }
+ g_ptr_array_add (ipv6, NULL);
+ return g_strjoinv ("\n", (char **) ipv6->pdata);
+}
diff --git a/panels/network/panel-common.h b/panels/network/panel-common.h
new file mode 100644
index 0000000..32aed09
--- /dev/null
+++ b/panels/network/panel-common.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+gchar *panel_device_status_to_localized_string (NMDevice *nm_device,
+ const gchar *speed);
+
+NMConnection *net_device_get_find_connection (NMClient *client,
+ NMDevice *device);
+
+GSList *net_device_get_valid_connections (NMClient *client,
+ NMDevice *device);
+
+gchar *net_device_get_ip6_addresses (NMIPConfig *ipv6_config);
+
+G_END_DECLS
diff --git a/panels/network/qrcodegen.c b/panels/network/qrcodegen.c
new file mode 100644
index 0000000..7cda965
--- /dev/null
+++ b/panels/network/qrcodegen.c
@@ -0,0 +1,1009 @@
+/*
+ * QR Code generator library (C)
+ *
+ * Copyright (c) Project Nayuki. (MIT License)
+ * https://www.nayuki.io/page/qr-code-generator-library
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ * - The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * - The Software is provided "as is", without warranty of any kind, express or
+ * implied, including but not limited to the warranties of merchantability,
+ * fitness for a particular purpose and noninfringement. In no event shall the
+ * authors or copyright holders be liable for any claim, damages or other
+ * liability, whether in an action of contract, tort or otherwise, arising from,
+ * out of or in connection with the Software or the use or other dealings in the
+ * Software.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include "qrcodegen.h"
+
+#ifndef QRCODEGEN_TEST
+ #define testable static // Keep functions private
+#else
+ #define testable // Expose private functions
+#endif
+
+
+/*---- Forward declarations for private functions ----*/
+
+// Regarding all public and private functions defined in this source file:
+// - They require all pointer/array arguments to be not null unless the array length is zero.
+// - They only read input scalar/array arguments, write to output pointer/array
+// arguments, and return scalar values; they are "pure" functions.
+// - They don't read mutable global variables or write to any global variables.
+// - They don't perform I/O, read the clock, print to console, etc.
+// - They allocate a small and constant amount of stack memory.
+// - They don't allocate or free any memory on the heap.
+// - They don't recurse or mutually recurse. All the code
+// could be inlined into the top-level public functions.
+// - They run in at most quadratic time with respect to input arguments.
+// Most functions run in linear time, and some in constant time.
+// There are no unbounded loops or non-obvious termination conditions.
+// - They are completely thread-safe if the caller does not give the
+// same writable buffer to concurrent calls to these functions.
+
+testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen);
+
+testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]);
+testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl);
+testable int getNumRawDataModules(int ver);
+
+testable void calcReedSolomonGenerator(int degree, uint8_t result[]);
+testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen,
+ const uint8_t generator[], int degree, uint8_t result[]);
+testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y);
+
+testable void initializeFunctionModules(int version, uint8_t qrcode[]);
+static void drawWhiteFunctionModules(uint8_t qrcode[], int version);
+static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]);
+testable int getAlignmentPatternPositions(int version, uint8_t result[7]);
+static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]);
+
+static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]);
+static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask);
+static long getPenaltyScore(const uint8_t qrcode[]);
+static void addRunToHistory(unsigned char run, unsigned char history[7]);
+static bool hasFinderLikePattern(unsigned char runHistory[7]);
+
+testable bool getModule(const uint8_t qrcode[], int x, int y);
+testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack);
+testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack);
+static bool getBit(int x, int i);
+
+testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars);
+testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version);
+static int numCharCountBits(enum qrcodegen_Mode mode, int version);
+
+
+
+/*---- Private tables of constants ----*/
+
+// The set of all legal characters in alphanumeric mode, where each character
+// value maps to the index in the string. For checking text and encoding segments.
+static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
+
+// For generating error correction codes.
+testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = {
+ // Version: (note that index 0 is for padding, and is set to an illegal value)
+ //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
+ {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
+ {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
+ {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
+ {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
+};
+
+#define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above
+
+// For generating error correction codes.
+testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = {
+ // Version: (note that index 0 is for padding, and is set to an illegal value)
+ //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
+ {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
+ {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
+ {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
+ {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
+};
+
+// For automatic mask pattern selection.
+static const int PENALTY_N1 = 3;
+static const int PENALTY_N2 = 3;
+static const int PENALTY_N3 = 40;
+static const int PENALTY_N4 = 10;
+
+
+
+/*---- High-level QR Code encoding functions ----*/
+
+// Public function - see documentation comment in header file.
+bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) {
+
+ size_t textLen = strlen(text);
+ if (textLen == 0)
+ return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode);
+ size_t bufLen = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion);
+
+ struct qrcodegen_Segment seg;
+ if (qrcodegen_isNumeric(text)) {
+ if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen)
+ goto fail;
+ seg = qrcodegen_makeNumeric(text, tempBuffer);
+ } else if (qrcodegen_isAlphanumeric(text)) {
+ if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen)
+ goto fail;
+ seg = qrcodegen_makeAlphanumeric(text, tempBuffer);
+ } else {
+ if (textLen > bufLen)
+ goto fail;
+ for (size_t i = 0; i < textLen; i++)
+ tempBuffer[i] = (uint8_t)text[i];
+ seg.mode = qrcodegen_Mode_BYTE;
+ seg.bitLength = calcSegmentBitLength(seg.mode, textLen);
+ if (seg.bitLength == -1)
+ goto fail;
+ seg.numChars = (int)textLen;
+ seg.data = tempBuffer;
+ }
+ return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode);
+
+fail:
+ qrcode[0] = 0; // Set size to invalid value for safety
+ return false;
+}
+
+
+// Public function - see documentation comment in header file.
+bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) {
+
+ struct qrcodegen_Segment seg;
+ seg.mode = qrcodegen_Mode_BYTE;
+ seg.bitLength = calcSegmentBitLength(seg.mode, dataLen);
+ if (seg.bitLength == -1) {
+ qrcode[0] = 0; // Set size to invalid value for safety
+ return false;
+ }
+ seg.numChars = (int)dataLen;
+ seg.data = dataAndTemp;
+ return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, dataAndTemp, qrcode);
+}
+
+
+// Appends the given number of low-order bits of the given value to the given byte-based
+// bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits.
+testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) {
+ assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0);
+ for (int i = numBits - 1; i >= 0; i--, (*bitLen)++)
+ buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7));
+}
+
+
+
+/*---- Low-level QR Code encoding functions ----*/
+
+// Public function - see documentation comment in header file.
+bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len,
+ enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) {
+ return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl,
+ qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, -1, true, tempBuffer, qrcode);
+}
+
+
+// Public function - see documentation comment in header file.
+bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl,
+ int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) {
+ assert(segs != NULL || len == 0);
+ assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX);
+ assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7);
+
+ // Find the minimal version number to use
+ int version, dataUsedBits;
+ for (version = minVersion; ; version++) {
+ int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
+ dataUsedBits = getTotalBits(segs, len, version);
+ if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
+ break; // This version number is found to be suitable
+ if (version >= maxVersion) { // All versions in the range could not fit the given data
+ qrcode[0] = 0; // Set size to invalid value for safety
+ return false;
+ }
+ }
+ assert(dataUsedBits != -1);
+
+ // Increase the error correction level while the data still fits in the current version number
+ for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high
+ if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8)
+ ecl = (enum qrcodegen_Ecc)i;
+ }
+
+ // Concatenate all segments to create the data bit string
+ memset(qrcode, 0, qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0]));
+ int bitLen = 0;
+ for (size_t i = 0; i < len; i++) {
+ const struct qrcodegen_Segment *seg = &segs[i];
+ appendBitsToBuffer((int)seg->mode, 4, qrcode, &bitLen);
+ appendBitsToBuffer(seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen);
+ for (int j = 0; j < seg->bitLength; j++)
+ appendBitsToBuffer((seg->data[j >> 3] >> (7 - (j & 7))) & 1, 1, qrcode, &bitLen);
+ }
+ assert(bitLen == dataUsedBits);
+
+ // Add terminator and pad up to a byte if applicable
+ int dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
+ assert(bitLen <= dataCapacityBits);
+ int terminatorBits = dataCapacityBits - bitLen;
+ if (terminatorBits > 4)
+ terminatorBits = 4;
+ appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen);
+ appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen);
+ assert(bitLen % 8 == 0);
+
+ // Pad with alternating bytes until data capacity is reached
+ for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
+ appendBitsToBuffer(padByte, 8, qrcode, &bitLen);
+
+ // Draw function and data codeword modules
+ addEccAndInterleave(qrcode, version, ecl, tempBuffer);
+ initializeFunctionModules(version, qrcode);
+ drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode);
+ drawWhiteFunctionModules(qrcode, version);
+ initializeFunctionModules(version, tempBuffer);
+
+ // Handle masking
+ if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask
+ long minPenalty = LONG_MAX;
+ for (int i = 0; i < 8; i++) {
+ enum qrcodegen_Mask msk = (enum qrcodegen_Mask)i;
+ drawFormatBits(ecl, msk, qrcode);
+ applyMask(tempBuffer, qrcode, msk);
+ long penalty = getPenaltyScore(qrcode);
+ if (penalty < minPenalty) {
+ mask = msk;
+ minPenalty = penalty;
+ }
+ applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR
+ }
+ }
+ assert(0 <= (int)mask && (int)mask <= 7);
+ drawFormatBits(ecl, mask, qrcode);
+ applyMask(tempBuffer, qrcode, mask);
+ return true;
+}
+
+
+
+/*---- Error correction code generation functions ----*/
+
+// Appends error correction bytes to each block of the given data array, then interleaves
+// bytes from the blocks and stores them in the result array. data[0 : dataLen] contains
+// the input data. data[dataLen : rawCodewords] is used as a temporary work area and will
+// be clobbered by this function. The final answer is stored in result[0 : rawCodewords].
+testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) {
+ // Calculate parameter numbers
+ assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX);
+ int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version];
+ int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version];
+ int rawCodewords = getNumRawDataModules(version) / 8;
+ int dataLen = getNumDataCodewords(version, ecl);
+ int numShortBlocks = numBlocks - rawCodewords % numBlocks;
+ int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen;
+
+ // Split data into blocks, calculate ECC, and interleave
+ // (not concatenate) the bytes into a single sequence
+ uint8_t generator[qrcodegen_REED_SOLOMON_DEGREE_MAX];
+ calcReedSolomonGenerator(blockEccLen, generator);
+ const uint8_t *dat = data;
+ for (int i = 0; i < numBlocks; i++) {
+ int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1);
+ uint8_t *ecc = &data[dataLen]; // Temporary storage
+ calcReedSolomonRemainder(dat, datLen, generator, blockEccLen, ecc);
+ for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data
+ if (j == shortBlockDataLen)
+ k -= numShortBlocks;
+ result[k] = dat[j];
+ }
+ for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) // Copy ECC
+ result[k] = ecc[j];
+ dat += datLen;
+ }
+}
+
+
+// Returns the number of 8-bit codewords that can be used for storing data (not ECC),
+// for the given version number and error correction level. The result is in the range [9, 2956].
+testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) {
+ int v = version, e = (int)ecl;
+ assert(0 <= e && e < 4);
+ return getNumRawDataModules(v) / 8
+ - ECC_CODEWORDS_PER_BLOCK [e][v]
+ * NUM_ERROR_CORRECTION_BLOCKS[e][v];
+}
+
+
+// Returns the number of data bits that can be stored in a QR Code of the given version number, after
+// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
+// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
+testable int getNumRawDataModules(int ver) {
+ assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX);
+ int result = (16 * ver + 128) * ver + 64;
+ if (ver >= 2) {
+ int numAlign = ver / 7 + 2;
+ result -= (25 * numAlign - 10) * numAlign - 55;
+ if (ver >= 7)
+ result -= 36;
+ }
+ return result;
+}
+
+
+
+/*---- Reed-Solomon ECC generator functions ----*/
+
+// Calculates the Reed-Solomon generator polynomial of the given degree, storing in result[0 : degree].
+testable void calcReedSolomonGenerator(int degree, uint8_t result[]) {
+ // Start with the monomial x^0
+ assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX);
+ memset(result, 0, degree * sizeof(result[0]));
+ result[degree - 1] = 1;
+
+ // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
+ // drop the highest term, and store the rest of the coefficients in order of descending powers.
+ // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
+ uint8_t root = 1;
+ for (int i = 0; i < degree; i++) {
+ // Multiply the current product by (x - r^i)
+ for (int j = 0; j < degree; j++) {
+ result[j] = finiteFieldMultiply(result[j], root);
+ if (j + 1 < degree)
+ result[j] ^= result[j + 1];
+ }
+ root = finiteFieldMultiply(root, 0x02);
+ }
+}
+
+
+// Calculates the remainder of the polynomial data[0 : dataLen] when divided by the generator[0 : degree], where all
+// polynomials are in big endian and the generator has an implicit leading 1 term, storing the result in result[0 : degree].
+testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen,
+ const uint8_t generator[], int degree, uint8_t result[]) {
+
+ // Perform polynomial division
+ assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX);
+ memset(result, 0, degree * sizeof(result[0]));
+ for (int i = 0; i < dataLen; i++) {
+ uint8_t factor = data[i] ^ result[0];
+ memmove(&result[0], &result[1], (degree - 1) * sizeof(result[0]));
+ result[degree - 1] = 0;
+ for (int j = 0; j < degree; j++)
+ result[j] ^= finiteFieldMultiply(generator[j], factor);
+ }
+}
+
+#undef qrcodegen_REED_SOLOMON_DEGREE_MAX
+
+
+// Returns the product of the two given field elements modulo GF(2^8/0x11D).
+// All inputs are valid. This could be implemented as a 256*256 lookup table.
+testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y) {
+ // Russian peasant multiplication
+ uint8_t z = 0;
+ for (int i = 7; i >= 0; i--) {
+ z = (z << 1) ^ ((z >> 7) * 0x11D);
+ z ^= ((y >> i) & 1) * x;
+ }
+ return z;
+}
+
+
+
+/*---- Drawing function modules ----*/
+
+// Clears the given QR Code grid with white modules for the given
+// version's size, then marks every function module as black.
+testable void initializeFunctionModules(int version, uint8_t qrcode[]) {
+ // Initialize QR Code
+ int qrsize = version * 4 + 17;
+ memset(qrcode, 0, ((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0]));
+ qrcode[0] = (uint8_t)qrsize;
+
+ // Fill horizontal and vertical timing patterns
+ fillRectangle(6, 0, 1, qrsize, qrcode);
+ fillRectangle(0, 6, qrsize, 1, qrcode);
+
+ // Fill 3 finder patterns (all corners except bottom right) and format bits
+ fillRectangle(0, 0, 9, 9, qrcode);
+ fillRectangle(qrsize - 8, 0, 8, 9, qrcode);
+ fillRectangle(0, qrsize - 8, 9, 8, qrcode);
+
+ // Fill numerous alignment patterns
+ uint8_t alignPatPos[7];
+ int numAlign = getAlignmentPatternPositions(version, alignPatPos);
+ for (int i = 0; i < numAlign; i++) {
+ for (int j = 0; j < numAlign; j++) {
+ // Don't draw on the three finder corners
+ if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)))
+ fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode);
+ }
+ }
+
+ // Fill version blocks
+ if (version >= 7) {
+ fillRectangle(qrsize - 11, 0, 3, 6, qrcode);
+ fillRectangle(0, qrsize - 11, 6, 3, qrcode);
+ }
+}
+
+
+// Draws white function modules and possibly some black modules onto the given QR Code, without changing
+// non-function modules. This does not draw the format bits. This requires all function modules to be previously
+// marked black (namely by initializeFunctionModules()), because this may skip redrawing black function modules.
+static void drawWhiteFunctionModules(uint8_t qrcode[], int version) {
+ // Draw horizontal and vertical timing patterns
+ int qrsize = qrcodegen_getSize(qrcode);
+ for (int i = 7; i < qrsize - 7; i += 2) {
+ setModule(qrcode, 6, i, false);
+ setModule(qrcode, i, 6, false);
+ }
+
+ // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
+ for (int dy = -4; dy <= 4; dy++) {
+ for (int dx = -4; dx <= 4; dx++) {
+ int dist = abs(dx);
+ if (abs(dy) > dist)
+ dist = abs(dy);
+ if (dist == 2 || dist == 4) {
+ setModuleBounded(qrcode, 3 + dx, 3 + dy, false);
+ setModuleBounded(qrcode, qrsize - 4 + dx, 3 + dy, false);
+ setModuleBounded(qrcode, 3 + dx, qrsize - 4 + dy, false);
+ }
+ }
+ }
+
+ // Draw numerous alignment patterns
+ uint8_t alignPatPos[7];
+ int numAlign = getAlignmentPatternPositions(version, alignPatPos);
+ for (int i = 0; i < numAlign; i++) {
+ for (int j = 0; j < numAlign; j++) {
+ if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))
+ continue; // Don't draw on the three finder corners
+ for (int dy = -1; dy <= 1; dy++) {
+ for (int dx = -1; dx <= 1; dx++)
+ setModule(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0);
+ }
+ }
+ }
+
+ // Draw version blocks
+ if (version >= 7) {
+ // Calculate error correction code and pack bits
+ int rem = version; // version is uint6, in the range [7, 40]
+ for (int i = 0; i < 12; i++)
+ rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
+ long bits = (long)version << 12 | rem; // uint18
+ assert(bits >> 18 == 0);
+
+ // Draw two copies
+ for (int i = 0; i < 6; i++) {
+ for (int j = 0; j < 3; j++) {
+ int k = qrsize - 11 + j;
+ setModule(qrcode, k, i, (bits & 1) != 0);
+ setModule(qrcode, i, k, (bits & 1) != 0);
+ bits >>= 1;
+ }
+ }
+ }
+}
+
+
+// Draws two copies of the format bits (with its own error correction code) based
+// on the given mask and error correction level. This always draws all modules of
+// the format bits, unlike drawWhiteFunctionModules() which might skip black modules.
+static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) {
+ // Calculate error correction code and pack bits
+ assert(0 <= (int)mask && (int)mask <= 7);
+ static const int table[] = {1, 0, 3, 2};
+ int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3
+ int rem = data;
+ for (int i = 0; i < 10; i++)
+ rem = (rem << 1) ^ ((rem >> 9) * 0x537);
+ int bits = (data << 10 | rem) ^ 0x5412; // uint15
+ assert(bits >> 15 == 0);
+
+ // Draw first copy
+ for (int i = 0; i <= 5; i++)
+ setModule(qrcode, 8, i, getBit(bits, i));
+ setModule(qrcode, 8, 7, getBit(bits, 6));
+ setModule(qrcode, 8, 8, getBit(bits, 7));
+ setModule(qrcode, 7, 8, getBit(bits, 8));
+ for (int i = 9; i < 15; i++)
+ setModule(qrcode, 14 - i, 8, getBit(bits, i));
+
+ // Draw second copy
+ int qrsize = qrcodegen_getSize(qrcode);
+ for (int i = 0; i < 8; i++)
+ setModule(qrcode, qrsize - 1 - i, 8, getBit(bits, i));
+ for (int i = 8; i < 15; i++)
+ setModule(qrcode, 8, qrsize - 15 + i, getBit(bits, i));
+ setModule(qrcode, 8, qrsize - 8, true); // Always black
+}
+
+
+// Calculates and stores an ascending list of positions of alignment patterns
+// for this version number, returning the length of the list (in the range [0,7]).
+// Each position is in the range [0,177), and are used on both the x and y axes.
+// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.
+testable int getAlignmentPatternPositions(int version, uint8_t result[7]) {
+ if (version == 1)
+ return 0;
+ int numAlign = version / 7 + 2;
+ int step = (version == 32) ? 26 :
+ (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2;
+ for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step)
+ result[i] = pos;
+ result[0] = 6;
+ return numAlign;
+}
+
+
+// Sets every pixel in the range [left : left + width] * [top : top + height] to black.
+static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]) {
+ for (int dy = 0; dy < height; dy++) {
+ for (int dx = 0; dx < width; dx++)
+ setModule(qrcode, left + dx, top + dy, true);
+ }
+}
+
+
+
+/*---- Drawing data modules and masking ----*/
+
+// Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of
+// the QR Code to be black at function modules and white at codeword modules (including unused remainder bits).
+static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) {
+ int qrsize = qrcodegen_getSize(qrcode);
+ int i = 0; // Bit index into the data
+ // Do the funny zigzag scan
+ for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair
+ if (right == 6)
+ right = 5;
+ for (int vert = 0; vert < qrsize; vert++) { // Vertical counter
+ for (int j = 0; j < 2; j++) {
+ int x = right - j; // Actual x coordinate
+ bool upward = ((right + 1) & 2) == 0;
+ int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate
+ if (!getModule(qrcode, x, y) && i < dataLen * 8) {
+ bool black = getBit(data[i >> 3], 7 - (i & 7));
+ setModule(qrcode, x, y, black);
+ i++;
+ }
+ // If this QR Code has any remainder bits (0 to 7), they were assigned as
+ // 0/false/white by the constructor and are left unchanged by this method
+ }
+ }
+ }
+ assert(i == dataLen * 8);
+}
+
+
+// XORs the codeword modules in this QR Code with the given mask pattern.
+// The function modules must be marked and the codeword bits must be drawn
+// before masking. Due to the arithmetic of XOR, calling applyMask() with
+// the same mask value a second time will undo the mask. A final well-formed
+// QR Code needs exactly one (not zero, two, etc.) mask applied.
+static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) {
+ assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO
+ int qrsize = qrcodegen_getSize(qrcode);
+ for (int y = 0; y < qrsize; y++) {
+ for (int x = 0; x < qrsize; x++) {
+ if (getModule(functionModules, x, y))
+ continue;
+ bool invert;
+ switch ((int)mask) {
+ case 0: invert = (x + y) % 2 == 0; break;
+ case 1: invert = y % 2 == 0; break;
+ case 2: invert = x % 3 == 0; break;
+ case 3: invert = (x + y) % 3 == 0; break;
+ case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
+ case 5: invert = x * y % 2 + x * y % 3 == 0; break;
+ case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
+ case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
+ default: assert(false); return;
+ }
+ bool val = getModule(qrcode, x, y);
+ setModule(qrcode, x, y, val ^ invert);
+ }
+ }
+}
+
+
+// Calculates and returns the penalty score based on state of the given QR Code's current modules.
+// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
+static long getPenaltyScore(const uint8_t qrcode[]) {
+ int qrsize = qrcodegen_getSize(qrcode);
+ long result = 0;
+
+ // Adjacent modules in row having same color, and finder-like patterns
+ for (int y = 0; y < qrsize; y++) {
+ unsigned char runHistory[7] = {0};
+ bool color = false;
+ unsigned char runX = 0;
+ for (int x = 0; x < qrsize; x++) {
+ if (getModule(qrcode, x, y) == color) {
+ runX++;
+ if (runX == 5)
+ result += PENALTY_N1;
+ else if (runX > 5)
+ result++;
+ } else {
+ addRunToHistory(runX, runHistory);
+ if (!color && hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ color = getModule(qrcode, x, y);
+ runX = 1;
+ }
+ }
+ addRunToHistory(runX, runHistory);
+ if (color)
+ addRunToHistory(0, runHistory); // Dummy run of white
+ if (hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ }
+ // Adjacent modules in column having same color, and finder-like patterns
+ for (int x = 0; x < qrsize; x++) {
+ unsigned char runHistory[7] = {0};
+ bool color = false;
+ unsigned char runY = 0;
+ for (int y = 0; y < qrsize; y++) {
+ if (getModule(qrcode, x, y) == color) {
+ runY++;
+ if (runY == 5)
+ result += PENALTY_N1;
+ else if (runY > 5)
+ result++;
+ } else {
+ addRunToHistory(runY, runHistory);
+ if (!color && hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ color = getModule(qrcode, x, y);
+ runY = 1;
+ }
+ }
+ addRunToHistory(runY, runHistory);
+ if (color)
+ addRunToHistory(0, runHistory); // Dummy run of white
+ if (hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ }
+
+ // 2*2 blocks of modules having same color
+ for (int y = 0; y < qrsize - 1; y++) {
+ for (int x = 0; x < qrsize - 1; x++) {
+ bool color = getModule(qrcode, x, y);
+ if ( color == getModule(qrcode, x + 1, y) &&
+ color == getModule(qrcode, x, y + 1) &&
+ color == getModule(qrcode, x + 1, y + 1))
+ result += PENALTY_N2;
+ }
+ }
+
+ // Balance of black and white modules
+ int black = 0;
+ for (int y = 0; y < qrsize; y++) {
+ for (int x = 0; x < qrsize; x++) {
+ if (getModule(qrcode, x, y))
+ black++;
+ }
+ }
+ int total = qrsize * qrsize; // Note that size is odd, so black/total != 1/2
+ // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
+ int k = (int)((labs(black * 20L - total * 10L) + total - 1) / total) - 1;
+ result += k * PENALTY_N4;
+ return result;
+}
+
+
+// Inserts the given value to the front of the given array, which shifts over the
+// existing values and deletes the last value. A helper function for getPenaltyScore().
+static void addRunToHistory(unsigned char run, unsigned char history[7]) {
+ memmove(&history[1], &history[0], 6 * sizeof(history[0]));
+ history[0] = run;
+}
+
+
+// Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and
+// surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore().
+// Must only be called immediately after a run of white modules has ended.
+static bool hasFinderLikePattern(unsigned char runHistory[7]) {
+ unsigned char n = runHistory[1];
+ // The maximum QR Code size is 177, hence the run length n <= 177.
+ // Arithmetic is promoted to int, so n*4 will not overflow.
+ return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n
+ && runHistory[3] == n * 3 && (runHistory[0] >= n * 4 || runHistory[6] >= n * 4);
+}
+
+
+
+/*---- Basic QR Code information ----*/
+
+// Public function - see documentation comment in header file.
+int qrcodegen_getSize(const uint8_t qrcode[]) {
+ assert(qrcode != NULL);
+ int result = qrcode[0];
+ assert((qrcodegen_VERSION_MIN * 4 + 17) <= result
+ && result <= (qrcodegen_VERSION_MAX * 4 + 17));
+ return result;
+}
+
+
+// Public function - see documentation comment in header file.
+bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) {
+ assert(qrcode != NULL);
+ int qrsize = qrcode[0];
+ return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y);
+}
+
+
+// Gets the module at the given coordinates, which must be in bounds.
+testable bool getModule(const uint8_t qrcode[], int x, int y) {
+ int qrsize = qrcode[0];
+ assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize);
+ int index = y * qrsize + x;
+ return getBit(qrcode[(index >> 3) + 1], index & 7);
+}
+
+
+// Sets the module at the given coordinates, which must be in bounds.
+testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack) {
+ int qrsize = qrcode[0];
+ assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize);
+ int index = y * qrsize + x;
+ int bitIndex = index & 7;
+ int byteIndex = (index >> 3) + 1;
+ if (isBlack)
+ qrcode[byteIndex] |= 1 << bitIndex;
+ else
+ qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF;
+}
+
+
+// Sets the module at the given coordinates, doing nothing if out of bounds.
+testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack) {
+ int qrsize = qrcode[0];
+ if (0 <= x && x < qrsize && 0 <= y && y < qrsize)
+ setModule(qrcode, x, y, isBlack);
+}
+
+
+// Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14.
+static bool getBit(int x, int i) {
+ return ((x >> i) & 1) != 0;
+}
+
+
+
+/*---- Segment handling ----*/
+
+// Public function - see documentation comment in header file.
+bool qrcodegen_isAlphanumeric(const char *text) {
+ assert(text != NULL);
+ for (; *text != '\0'; text++) {
+ if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL)
+ return false;
+ }
+ return true;
+}
+
+
+// Public function - see documentation comment in header file.
+bool qrcodegen_isNumeric(const char *text) {
+ assert(text != NULL);
+ for (; *text != '\0'; text++) {
+ if (*text < '0' || *text > '9')
+ return false;
+ }
+ return true;
+}
+
+
+// Public function - see documentation comment in header file.
+size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) {
+ int temp = calcSegmentBitLength(mode, numChars);
+ if (temp == -1)
+ return SIZE_MAX;
+ assert(0 <= temp && temp <= INT16_MAX);
+ return ((size_t)temp + 7) / 8;
+}
+
+
+// Returns the number of data bits needed to represent a segment
+// containing the given number of characters using the given mode. Notes:
+// - Returns -1 on failure, i.e. numChars > INT16_MAX or
+// the number of needed bits exceeds INT16_MAX (i.e. 32767).
+// - Otherwise, all valid results are in the range [0, INT16_MAX].
+// - For byte mode, numChars measures the number of bytes, not Unicode code points.
+// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned.
+// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
+testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) {
+ // All calculations are designed to avoid overflow on all platforms
+ if (numChars > (unsigned int)INT16_MAX)
+ return -1;
+ long result = (long)numChars;
+ if (mode == qrcodegen_Mode_NUMERIC)
+ result = (result * 10 + 2) / 3; // ceil(10/3 * n)
+ else if (mode == qrcodegen_Mode_ALPHANUMERIC)
+ result = (result * 11 + 1) / 2; // ceil(11/2 * n)
+ else if (mode == qrcodegen_Mode_BYTE)
+ result *= 8;
+ else if (mode == qrcodegen_Mode_KANJI)
+ result *= 13;
+ else if (mode == qrcodegen_Mode_ECI && numChars == 0)
+ result = 3 * 8;
+ else { // Invalid argument
+ assert(false);
+ return -1;
+ }
+ assert(result >= 0);
+ if (result > (unsigned int)INT16_MAX)
+ return -1;
+ return (int)result;
+}
+
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) {
+ assert(data != NULL || len == 0);
+ struct qrcodegen_Segment result;
+ result.mode = qrcodegen_Mode_BYTE;
+ result.bitLength = calcSegmentBitLength(result.mode, len);
+ assert(result.bitLength != -1);
+ result.numChars = (int)len;
+ if (len > 0)
+ memcpy(buf, data, len * sizeof(buf[0]));
+ result.data = buf;
+ return result;
+}
+
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) {
+ assert(digits != NULL);
+ struct qrcodegen_Segment result;
+ size_t len = strlen(digits);
+ result.mode = qrcodegen_Mode_NUMERIC;
+ int bitLen = calcSegmentBitLength(result.mode, len);
+ assert(bitLen != -1);
+ result.numChars = (int)len;
+ if (bitLen > 0)
+ memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0]));
+ result.bitLength = 0;
+
+ unsigned int accumData = 0;
+ int accumCount = 0;
+ for (; *digits != '\0'; digits++) {
+ char c = *digits;
+ assert('0' <= c && c <= '9');
+ accumData = accumData * 10 + (unsigned int)(c - '0');
+ accumCount++;
+ if (accumCount == 3) {
+ appendBitsToBuffer(accumData, 10, buf, &result.bitLength);
+ accumData = 0;
+ accumCount = 0;
+ }
+ }
+ if (accumCount > 0) // 1 or 2 digits remaining
+ appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength);
+ assert(result.bitLength == bitLen);
+ result.data = buf;
+ return result;
+}
+
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) {
+ assert(text != NULL);
+ struct qrcodegen_Segment result;
+ size_t len = strlen(text);
+ result.mode = qrcodegen_Mode_ALPHANUMERIC;
+ int bitLen = calcSegmentBitLength(result.mode, len);
+ assert(bitLen != -1);
+ result.numChars = (int)len;
+ if (bitLen > 0)
+ memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0]));
+ result.bitLength = 0;
+
+ unsigned int accumData = 0;
+ int accumCount = 0;
+ for (; *text != '\0'; text++) {
+ const char *temp = strchr(ALPHANUMERIC_CHARSET, *text);
+ assert(temp != NULL);
+ accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET);
+ accumCount++;
+ if (accumCount == 2) {
+ appendBitsToBuffer(accumData, 11, buf, &result.bitLength);
+ accumData = 0;
+ accumCount = 0;
+ }
+ }
+ if (accumCount > 0) // 1 character remaining
+ appendBitsToBuffer(accumData, 6, buf, &result.bitLength);
+ assert(result.bitLength == bitLen);
+ result.data = buf;
+ return result;
+}
+
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) {
+ struct qrcodegen_Segment result;
+ result.mode = qrcodegen_Mode_ECI;
+ result.numChars = 0;
+ result.bitLength = 0;
+ if (assignVal < 0)
+ assert(false);
+ else if (assignVal < (1 << 7)) {
+ memset(buf, 0, 1 * sizeof(buf[0]));
+ appendBitsToBuffer(assignVal, 8, buf, &result.bitLength);
+ } else if (assignVal < (1 << 14)) {
+ memset(buf, 0, 2 * sizeof(buf[0]));
+ appendBitsToBuffer(2, 2, buf, &result.bitLength);
+ appendBitsToBuffer(assignVal, 14, buf, &result.bitLength);
+ } else if (assignVal < 1000000L) {
+ memset(buf, 0, 3 * sizeof(buf[0]));
+ appendBitsToBuffer(6, 3, buf, &result.bitLength);
+ appendBitsToBuffer(assignVal >> 10, 11, buf, &result.bitLength);
+ appendBitsToBuffer(assignVal & 0x3FF, 10, buf, &result.bitLength);
+ } else
+ assert(false);
+ result.data = buf;
+ return result;
+}
+
+
+// Calculates the number of bits needed to encode the given segments at the given version.
+// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
+// many characters to fit its length field, or the total bits exceeds INT16_MAX.
+testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) {
+ assert(segs != NULL || len == 0);
+ long result = 0;
+ for (size_t i = 0; i < len; i++) {
+ int numChars = segs[i].numChars;
+ int bitLength = segs[i].bitLength;
+ assert(0 <= numChars && numChars <= INT16_MAX);
+ assert(0 <= bitLength && bitLength <= INT16_MAX);
+ int ccbits = numCharCountBits(segs[i].mode, version);
+ assert(0 <= ccbits && ccbits <= 16);
+ if (numChars >= (1L << ccbits))
+ return -1; // The segment's length doesn't fit the field's bit width
+ result += 4L + ccbits + bitLength;
+ if (result > INT16_MAX)
+ return -1; // The sum might overflow an int type
+ }
+ assert(0 <= result && result <= INT16_MAX);
+ return (int)result;
+}
+
+
+// Returns the bit width of the character count field for a segment in the given mode
+// in a QR Code at the given version number. The result is in the range [0, 16].
+static int numCharCountBits(enum qrcodegen_Mode mode, int version) {
+ assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX);
+ int i = (version + 7) / 17;
+ switch (mode) {
+ case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; }
+ case qrcodegen_Mode_ALPHANUMERIC: { static const int temp[] = { 9, 11, 13}; return temp[i]; }
+ case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; }
+ case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; }
+ case qrcodegen_Mode_ECI : return 0;
+ default: assert(false); return -1; // Dummy value
+ }
+}
diff --git a/panels/network/qrcodegen.h b/panels/network/qrcodegen.h
new file mode 100644
index 0000000..55e2bfe
--- /dev/null
+++ b/panels/network/qrcodegen.h
@@ -0,0 +1,311 @@
+/*
+ * QR Code generator library (C)
+ *
+ * Copyright (c) Project Nayuki. (MIT License)
+ * https://www.nayuki.io/page/qr-code-generator-library
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ * - The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * - The Software is provided "as is", without warranty of any kind, express or
+ * implied, including but not limited to the warranties of merchantability,
+ * fitness for a particular purpose and noninfringement. In no event shall the
+ * authors or copyright holders be liable for any claim, damages or other
+ * liability, whether in an action of contract, tort or otherwise, arising from,
+ * out of or in connection with the Software or the use or other dealings in the
+ * Software.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*
+ * This library creates QR Code symbols, which is a type of two-dimension barcode.
+ * Invented by Denso Wave and described in the ISO/IEC 18004 standard.
+ * A QR Code structure is an immutable square grid of black and white cells.
+ * The library provides functions to create a QR Code from text or binary data.
+ * The library covers the QR Code Model 2 specification, supporting all versions (sizes)
+ * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
+ *
+ * Ways to create a QR Code object:
+ * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary().
+ * - Low level: Custom-make the list of segments and call
+ * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced().
+ * (Note that all ways require supplying the desired error correction level and various byte buffers.)
+ */
+
+
+/*---- Enum and struct types----*/
+
+/*
+ * The error correction level in a QR Code symbol.
+ */
+enum qrcodegen_Ecc {
+ // Must be declared in ascending order of error protection
+ // so that an internal qrcodegen function works properly
+ qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords
+ qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords
+ qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
+ qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords
+};
+
+
+/*
+ * The mask pattern used in a QR Code symbol.
+ */
+enum qrcodegen_Mask {
+ // A special value to tell the QR Code encoder to
+ // automatically select an appropriate mask pattern
+ qrcodegen_Mask_AUTO = -1,
+ // The eight actual mask patterns
+ qrcodegen_Mask_0 = 0,
+ qrcodegen_Mask_1,
+ qrcodegen_Mask_2,
+ qrcodegen_Mask_3,
+ qrcodegen_Mask_4,
+ qrcodegen_Mask_5,
+ qrcodegen_Mask_6,
+ qrcodegen_Mask_7,
+};
+
+
+/*
+ * Describes how a segment's data bits are interpreted.
+ */
+enum qrcodegen_Mode {
+ qrcodegen_Mode_NUMERIC = 0x1,
+ qrcodegen_Mode_ALPHANUMERIC = 0x2,
+ qrcodegen_Mode_BYTE = 0x4,
+ qrcodegen_Mode_KANJI = 0x8,
+ qrcodegen_Mode_ECI = 0x7,
+};
+
+
+/*
+ * A segment of character/binary/control data in a QR Code symbol.
+ * The mid-level way to create a segment is to take the payload data
+ * and call a factory function such as qrcodegen_makeNumeric().
+ * The low-level way to create a segment is to custom-make the bit buffer
+ * and initialize a qrcodegen_Segment struct with appropriate values.
+ * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
+ * Any segment longer than this is meaningless for the purpose of generating QR Codes.
+ * Moreover, the maximum allowed bit length is 32767 because
+ * the largest QR Code (version 40) has 31329 modules.
+ */
+struct qrcodegen_Segment {
+ // The mode indicator of this segment.
+ enum qrcodegen_Mode mode;
+
+ // The length of this segment's unencoded data. Measured in characters for
+ // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
+ // Always zero or positive. Not the same as the data's bit length.
+ int numChars;
+
+ // The data bits of this segment, packed in bitwise big endian.
+ // Can be null if the bit length is zero.
+ uint8_t *data;
+
+ // The number of valid data bits used in the buffer. Requires
+ // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
+ // The character count (numChars) must agree with the mode and the bit buffer length.
+ int bitLength;
+};
+
+
+
+/*---- Macro constants and functions ----*/
+
+#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard
+#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard
+
+// Calculates the number of bytes needed to store any QR Code up to and including the given version number,
+// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];'
+// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16).
+// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX.
+#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1)
+
+// The worst-case number of bytes needed to store one QR Code, up to and including
+// version 40. This value equals 3918, which is just under 4 kilobytes.
+// Use this more convenient value to avoid calculating tighter memory bounds for buffers.
+#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)
+
+
+
+/*---- Functions (high level) to generate QR Codes ----*/
+
+/*
+ * Encodes the given text string to a QR Code, returning true if encoding succeeded.
+ * If the data is too long to fit in any version in the given range
+ * at the given ECC level, then false is returned.
+ * - The input text must be encoded in UTF-8 and contain no NULs.
+ * - The variables ecl and mask must correspond to enum constant values.
+ * - Requires 1 <= minVersion <= maxVersion <= 40.
+ * - The arrays tempBuffer and qrcode must each have a length
+ * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
+ * - After the function returns, tempBuffer contains no useful data.
+ * - If successful, the resulting QR Code may use numeric,
+ * alphanumeric, or byte mode to encode the text.
+ * - In the most optimistic case, a QR Code at version 40 with low ECC
+ * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string
+ * up to 4296 characters, or any digit string up to 7089 characters.
+ * These numbers represent the hard upper limit of the QR Code standard.
+ * - Please consult the QR Code specification for information on
+ * data capacities per version, ECC level, and text encoding mode.
+ */
+bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
+
+
+/*
+ * Encodes the given binary data to a QR Code, returning true if encoding succeeded.
+ * If the data is too long to fit in any version in the given range
+ * at the given ECC level, then false is returned.
+ * - The input array range dataAndTemp[0 : dataLen] should normally be
+ * valid UTF-8 text, but is not required by the QR Code standard.
+ * - The variables ecl and mask must correspond to enum constant values.
+ * - Requires 1 <= minVersion <= maxVersion <= 40.
+ * - The arrays dataAndTemp and qrcode must each have a length
+ * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
+ * - After the function returns, the contents of dataAndTemp may have changed,
+ * and does not represent useful data anymore.
+ * - If successful, the resulting QR Code will use byte mode to encode the data.
+ * - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte
+ * sequence up to length 2953. This is the hard upper limit of the QR Code standard.
+ * - Please consult the QR Code specification for information on
+ * data capacities per version, ECC level, and text encoding mode.
+ */
+bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
+
+
+/*---- Functions (low level) to generate QR Codes ----*/
+
+/*
+ * Renders a QR Code representing the given segments at the given error correction level.
+ * The smallest possible QR Code version is automatically chosen for the output. Returns true if
+ * QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level
+ * of the result may be higher than the ecl argument if it can be done without increasing the version.
+ * This function allows the user to create a custom sequence of segments that switches
+ * between modes (such as alphanumeric and byte) to encode text in less space.
+ * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
+ * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
+ * result in them being clobbered, but the QR Code output will still be correct.
+ * But the qrcode array must not overlap tempBuffer or any segment's data buffer.
+ */
+bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len,
+ enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]);
+
+
+/*
+ * Renders a QR Code representing the given segments with the given encoding parameters.
+ * Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions.
+ * The smallest possible QR Code version within the given range is automatically
+ * chosen for the output. Iff boostEcl is true, then the ECC level of the result
+ * may be higher than the ecl argument if it can be done without increasing the
+ * version. The mask number is either between 0 to 7 (inclusive) to force that
+ * mask, or -1 to automatically choose an appropriate mask (which may be slow).
+ * This function allows the user to create a custom sequence of segments that switches
+ * between modes (such as alphanumeric and byte) to encode text in less space.
+ * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
+ * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
+ * result in them being clobbered, but the QR Code output will still be correct.
+ * But the qrcode array must not overlap tempBuffer or any segment's data buffer.
+ */
+bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl,
+ int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]);
+
+
+/*
+ * Tests whether the given string can be encoded as a segment in alphanumeric mode.
+ * A string is encodable iff each character is in the following set: 0 to 9, A to Z
+ * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
+ */
+bool qrcodegen_isAlphanumeric(const char *text);
+
+
+/*
+ * Tests whether the given string can be encoded as a segment in numeric mode.
+ * A string is encodable iff each character is in the range 0 to 9.
+ */
+bool qrcodegen_isNumeric(const char *text);
+
+
+/*
+ * Returns the number of bytes (uint8_t) needed for the data buffer of a segment
+ * containing the given number of characters using the given mode. Notes:
+ * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or
+ * the number of needed bits exceeds INT16_MAX (i.e. 32767).
+ * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096.
+ * - It is okay for the user to allocate more bytes for the buffer than needed.
+ * - For byte mode, numChars measures the number of bytes, not Unicode code points.
+ * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned.
+ * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
+ */
+size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
+
+
+/*
+ * Returns a segment representing the given binary data encoded in
+ * byte mode. All input byte arrays are acceptable. Any text string
+ * can be converted to UTF-8 bytes and encoded as a byte mode segment.
+ */
+struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]);
+
+
+/*
+ * Returns a segment representing the given string of decimal digits encoded in numeric mode.
+ */
+struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]);
+
+
+/*
+ * Returns a segment representing the given text string encoded in alphanumeric mode.
+ * The characters allowed are: 0 to 9, A to Z (uppercase only), space,
+ * dollar, percent, asterisk, plus, hyphen, period, slash, colon.
+ */
+struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]);
+
+
+/*
+ * Returns a segment representing an Extended Channel Interpretation
+ * (ECI) designator with the given assignment value.
+ */
+struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]);
+
+
+/*---- Functions to extract raw data from QR Codes ----*/
+
+/*
+ * Returns the side length of the given QR Code, assuming that encoding succeeded.
+ * The result is in the range [21, 177]. Note that the length of the array buffer
+ * is related to the side length - every 'uint8_t qrcode[]' must have length at least
+ * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1).
+ */
+int qrcodegen_getSize(const uint8_t qrcode[]);
+
+
+/*
+ * Returns the color of the module (pixel) at the given coordinates, which is false
+ * for white or true for black. The top left corner has the coordinates (x=0, y=0).
+ * If the given coordinates are out of bounds, then false (white) is returned.
+ */
+bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/panels/network/ui-helpers.c b/panels/network/ui-helpers.c
new file mode 100644
index 0000000..c90ed09
--- /dev/null
+++ b/panels/network/ui-helpers.c
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2014 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include "ui-helpers.h"
+
+void
+widget_set_error (GtkWidget *widget)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error");
+}
+
+void
+widget_unset_error (GtkWidget *widget)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error");
+}
diff --git a/panels/network/ui-helpers.h b/panels/network/ui-helpers.h
new file mode 100644
index 0000000..c9754a4
--- /dev/null
+++ b/panels/network/ui-helpers.h
@@ -0,0 +1,27 @@
+/*
+ * 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 2014 Red Hat, Inc.
+ */
+
+#ifndef _UI_HELPERS_H_
+#define _UI_HELPERS_H_
+
+#include <gtk/gtk.h>
+
+void widget_set_error (GtkWidget *widget);
+void widget_unset_error (GtkWidget *widget);
+
+#endif /* _UI_HELPERS_H_ */
diff --git a/panels/network/warning-small-symbolic.svg b/panels/network/warning-small-symbolic.svg
new file mode 100644
index 0000000..b4f7d35
--- /dev/null
+++ b/panels/network/warning-small-symbolic.svg
@@ -0,0 +1 @@
+<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><path d="M5.014 0a1 1 0 0 0-.909.553l-4 8A1 1 0 0 0 1 10h8a1 1 0 0 0 .895-1.447l-4-8A1 1 0 0 0 5.014 0ZM4 3h2v2.5s0 .5-.5.5h-1C4 6 4 5.5 4 5.5Zm.5 4h1c.277 0 .5.223.5.5v1c0 .277-.223.5-.5.5h-1a.499.499 0 0 1-.5-.5v-1c0-.277.223-.5.5-.5Z" style="fill:#ff7800"/></svg>
diff --git a/panels/network/wifi-panel.css b/panels/network/wifi-panel.css
new file mode 100644
index 0000000..de7d80c
--- /dev/null
+++ b/panels/network/wifi-panel.css
@@ -0,0 +1,5 @@
+.qr-image {
+ background: #fff;
+ padding: 12px;
+}
+
diff --git a/panels/network/wireless-security/eap-method-fast.c b/panels/network/wireless-security/eap-method-fast.c
new file mode 100644
index 0000000..c0f4ad0
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-fast.c
@@ -0,0 +1,399 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* vim: set ft=c ts=4 sts=4 sw=4 noexpandtab smartindent: */
+
+/* EAP-FAST authentication method (RFC4851)
+ *
+ * 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.
+ *
+ * Copyright 2012 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "eap-method-fast.h"
+#include "eap-method-simple.h"
+#include "helpers.h"
+#include "ui-helpers.h"
+#include "ws-file-chooser-button.h"
+
+#define I_NAME_COLUMN 0
+#define I_ID_COLUMN 1
+
+struct _EAPMethodFAST {
+ GtkGrid parent;
+
+ GtkEntry *anon_identity_entry;
+ GtkLabel *anon_identity_label;
+ GtkComboBox *inner_auth_combo;
+ GtkLabel *inner_auth_label;
+ GtkListStore *inner_auth_model;
+ GtkBox *inner_auth_box;
+ WsFileChooserButton *pac_file_button;
+ GtkLabel *pac_file_label;
+ GtkCheckButton *pac_provision_check;
+ GtkComboBox *pac_provision_combo;
+
+ EAPMethodSimple *em_gtc;
+ EAPMethodSimple *em_mschap_v2;
+};
+
+static void eap_method_iface_init (EAPMethodInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (EAPMethodFAST, eap_method_fast, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init))
+
+static EAPMethod *
+get_inner_method (EAPMethodFAST *self)
+{
+ GtkTreeIter iter;
+ g_autofree gchar *id = NULL;
+
+ if (!gtk_combo_box_get_active_iter (self->inner_auth_combo, &iter))
+ return NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1);
+
+ if (strcmp (id, "gtc") == 0)
+ return EAP_METHOD (self->em_gtc);
+ if (strcmp (id, "mschapv2") == 0)
+ return EAP_METHOD (self->em_mschap_v2);
+
+ return NULL;
+}
+
+static gboolean
+validate (EAPMethod *parent, GError **error)
+{
+ EAPMethodFAST *self = (EAPMethodFAST *) parent;
+ g_autoptr(GFile) file = NULL;
+ gboolean provisioning;
+ gboolean valid = TRUE;
+
+ provisioning = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->pac_provision_check));
+ file = ws_file_chooser_button_get_file (self->pac_file_button);
+ if (!provisioning && !file) {
+ widget_set_error (GTK_WIDGET (self->pac_file_button));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-FAST PAC file"));
+ valid = FALSE;
+ } else
+ widget_unset_error (GTK_WIDGET (self->pac_file_button));
+
+ return eap_method_validate (get_inner_method (self), valid ? error : NULL) && valid;
+}
+
+static void
+add_to_size_group (EAPMethod *parent, GtkSizeGroup *group)
+{
+ EAPMethodFAST *self = (EAPMethodFAST *) parent;
+
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->anon_identity_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->pac_file_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->pac_provision_check));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->inner_auth_label));
+
+ eap_method_add_to_size_group (EAP_METHOD (self->em_gtc), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_mschap_v2), group);
+}
+
+static void
+fill_connection (EAPMethod *parent, NMConnection *connection, NMSettingSecretFlags flags)
+{
+ EAPMethodFAST *self = (EAPMethodFAST *) parent;
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GFile) file = NULL;
+ NMSetting8021x *s_8021x;
+ const char *text;
+ gboolean enabled;
+ int pac_provisioning = 0;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ g_assert (s_8021x);
+
+ nm_setting_802_1x_add_eap_method (s_8021x, "fast");
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->anon_identity_entry));
+ if (text && strlen (text))
+ g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, text, NULL);
+
+ file = ws_file_chooser_button_get_file (self->pac_file_button);
+ filename = file ? g_file_get_path (file) : NULL;
+ g_object_set (s_8021x, NM_SETTING_802_1X_PAC_FILE, filename, NULL);
+
+ enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->pac_provision_check));
+
+ if (!enabled)
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "0", NULL);
+ else {
+ pac_provisioning = gtk_combo_box_get_active (self->pac_provision_combo);
+
+ switch (pac_provisioning) {
+ case 0: /* Anonymous */
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "1", NULL);
+ break;
+ case 1: /* Authenticated */
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "2", NULL);
+ break;
+ case 2: /* Both - anonymous and authenticated */
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "3", NULL);
+ break;
+ default: /* Should not happen */
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "1", NULL);
+ break;
+ }
+ }
+
+ eap_method_fill_connection (get_inner_method (self), connection, flags);
+}
+
+static void
+inner_auth_combo_changed_cb (EAPMethodFAST *self)
+{
+ EAPMethod *inner_method;
+ GtkWidget *child;
+
+ inner_method = get_inner_method (self);
+
+ /* Remove the previous method and migrate username/password across */
+ child = gtk_widget_get_first_child (GTK_WIDGET (self->inner_auth_box));
+ if (child != NULL) {
+ EAPMethod *old_eap = EAP_METHOD (child);
+ eap_method_set_username (inner_method, eap_method_get_username (old_eap));
+ eap_method_set_password (inner_method, eap_method_get_password (old_eap));
+ eap_method_set_show_password (inner_method, eap_method_get_show_password (old_eap));
+ gtk_box_remove (self->inner_auth_box, child);
+ }
+
+ gtk_box_append (self->inner_auth_box, g_object_ref (GTK_WIDGET (inner_method)));
+
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+update_secrets (EAPMethod *parent, NMConnection *connection)
+{
+ EAPMethodFAST *self = (EAPMethodFAST *) parent;
+
+ eap_method_update_secrets (EAP_METHOD (self->em_gtc), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_mschap_v2), connection);
+}
+
+static GtkWidget *
+get_default_field (EAPMethod *parent)
+{
+ EAPMethodFAST *self = (EAPMethodFAST *) parent;
+ return GTK_WIDGET (self->anon_identity_entry);
+}
+
+static const gchar *
+get_password_flags_name (EAPMethod *parent)
+{
+ return NM_SETTING_802_1X_PASSWORD;
+}
+
+static const gchar *
+get_username (EAPMethod *method)
+{
+ EAPMethodFAST *self = EAP_METHOD_FAST (method);
+ return eap_method_get_username (get_inner_method (self));
+}
+
+static void
+set_username (EAPMethod *method, const gchar *username)
+{
+ EAPMethodFAST *self = EAP_METHOD_FAST (method);
+ return eap_method_set_username (get_inner_method (self), username);
+}
+
+static const gchar *
+get_password (EAPMethod *method)
+{
+ EAPMethodFAST *self = EAP_METHOD_FAST (method);
+ return eap_method_get_password (get_inner_method (self));
+}
+
+static void
+set_password (EAPMethod *method, const gchar *password)
+{
+ EAPMethodFAST *self = EAP_METHOD_FAST (method);
+ return eap_method_set_password (get_inner_method (self), password);
+}
+
+static gboolean
+get_show_password (EAPMethod *method)
+{
+ EAPMethodFAST *self = EAP_METHOD_FAST (method);
+ return eap_method_get_show_password (get_inner_method (self));
+}
+
+static void
+set_show_password (EAPMethod *method, gboolean show_password)
+{
+ EAPMethodFAST *self = EAP_METHOD_FAST (method);
+ return eap_method_set_show_password (get_inner_method (self), show_password);
+}
+
+static void
+pac_toggled_cb (EAPMethodFAST *self)
+{
+ gboolean enabled = FALSE;
+
+ enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->pac_provision_check));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->pac_provision_combo), enabled);
+
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+changed_cb (EAPMethodFAST *self)
+{
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+eap_method_fast_init (EAPMethodFAST *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+eap_method_fast_class_init (EAPMethodFASTClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_ensure (WS_TYPE_FILE_CHOOSER_BUTTON);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-fast.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, anon_identity_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, anon_identity_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_combo);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_model);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_box);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_file_button);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_file_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_provision_check);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_provision_combo);
+}
+
+static void
+eap_method_iface_init (EAPMethodInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->update_secrets = update_secrets;
+ iface->get_default_field = get_default_field;
+ iface->get_password_flags_name = get_password_flags_name;
+ iface->get_username = get_username;
+ iface->set_username = set_username;
+ iface->get_password = get_password;
+ iface->set_password = set_password;
+ iface->get_show_password = get_show_password;
+ iface->set_show_password = set_show_password;
+}
+
+EAPMethodFAST *
+eap_method_fast_new (NMConnection *connection)
+{
+ EAPMethodFAST *self;
+ GtkFileFilter *filter;
+ NMSetting8021x *s_8021x = NULL;
+ gboolean provisioning_enabled = TRUE;
+ const gchar *phase2_auth = NULL;
+ GtkTreeIter iter;
+
+ self = g_object_new (eap_method_fast_get_type (), NULL);
+
+ if (connection)
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+
+ gtk_combo_box_set_active (self->pac_provision_combo, 0);
+ if (s_8021x) {
+ const char *fast_prov;
+
+ fast_prov = nm_setting_802_1x_get_phase1_fast_provisioning (s_8021x);
+ if (fast_prov) {
+ if (!strcmp (fast_prov, "0"))
+ provisioning_enabled = FALSE;
+ else if (!strcmp (fast_prov, "1"))
+ gtk_combo_box_set_active (self->pac_provision_combo, 0);
+ else if (!strcmp (fast_prov, "2"))
+ gtk_combo_box_set_active (self->pac_provision_combo, 1);
+ else if (!strcmp (fast_prov, "3"))
+ gtk_combo_box_set_active (self->pac_provision_combo, 2);
+ }
+ }
+ gtk_widget_set_sensitive (GTK_WIDGET (self->pac_provision_combo), provisioning_enabled);
+ g_signal_connect_swapped (self->pac_provision_combo, "changed", G_CALLBACK (changed_cb), self);
+
+ gtk_check_button_set_active (self->pac_provision_check, provisioning_enabled);
+ g_signal_connect_swapped (self->pac_provision_check, "toggled", G_CALLBACK (pac_toggled_cb), self);
+
+ if (s_8021x && nm_setting_802_1x_get_anonymous_identity (s_8021x))
+ gtk_editable_set_text (GTK_EDITABLE (self->anon_identity_entry), nm_setting_802_1x_get_anonymous_identity (s_8021x));
+ g_signal_connect_swapped (self->anon_identity_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ g_signal_connect_swapped (self->pac_file_button, "notify::file", G_CALLBACK (changed_cb), self);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pattern (filter, "*.pac");
+ gtk_file_filter_set_name (filter, _("PAC files (*.pac)"));
+ gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->pac_file_button), filter);
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pattern (filter, "*");
+ gtk_file_filter_set_name (filter, _("All files"));
+ gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->pac_file_button), filter);
+
+ if (connection && s_8021x) {
+ const char *filename = nm_setting_802_1x_get_pac_file (s_8021x);
+ if (filename) {
+ g_autoptr(GFile) file = g_file_new_for_path (filename);
+ ws_file_chooser_button_set_file (self->pac_file_button, file);
+ }
+ }
+
+ self->em_gtc = eap_method_simple_new (connection, "gtc", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_gtc));
+ g_signal_connect_object (self->em_gtc, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_mschap_v2));
+ g_signal_connect_object (self->em_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ if (s_8021x) {
+ if (nm_setting_802_1x_get_phase2_auth (s_8021x))
+ phase2_auth = nm_setting_802_1x_get_phase2_auth (s_8021x);
+ else if (nm_setting_802_1x_get_phase2_autheap (s_8021x))
+ phase2_auth = nm_setting_802_1x_get_phase2_autheap (s_8021x);
+ }
+ if (phase2_auth == NULL)
+ phase2_auth = "gtc";
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->inner_auth_model), &iter)) {
+ do {
+ g_autofree gchar *id = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1);
+ if (strcmp (id, phase2_auth) == 0)
+ gtk_combo_box_set_active_iter (self->inner_auth_combo, &iter);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->inner_auth_model), &iter));
+ }
+
+ g_signal_connect_swapped (self->inner_auth_combo, "changed", G_CALLBACK (inner_auth_combo_changed_cb), self);
+ inner_auth_combo_changed_cb (self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/eap-method-fast.h b/panels/network/wireless-security/eap-method-fast.h
new file mode 100644
index 0000000..5528ab1
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-fast.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* vim: set ft=c ts=4 sts=4 sw=4 noexpandtab smartindent: */
+
+/* EAP-FAST authentication method (RFC4851)
+ *
+ * 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 2012 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (EAPMethodFAST, eap_method_fast, EAP, METHOD_FAST, GtkGrid)
+
+EAPMethodFAST *eap_method_fast_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/eap-method-fast.ui b/panels/network/wireless-security/eap-method-fast.ui
new file mode 100644
index 0000000..ad63b18
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-fast.ui
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <object class="GtkListStore" id="inner_auth_model">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">GTC</col>
+ <col id="1">gtc</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">MSCHAPv2</col>
+ <col id="1">mschapv2</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="pac_provision_model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Anonymous</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Authenticated</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Both</col>
+ </row>
+ </data>
+ </object>
+ <template class="EAPMethodFAST" parent="GtkGrid">
+ <property name="valign">start</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="anon_identity_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Anony_mous identity</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">anon_identity_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="anon_identity_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="pac_file_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">PAC _file</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">pac_file_button</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="WsFileChooserButton" id="pac_file_button">
+ <property name="hexpand">True</property>
+ <property name="title" translatable="yes">Choose a PAC file</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="inner_auth_box">
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="column-span">2</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="inner_auth_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Inner authentication</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">inner_auth_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="inner_auth_combo">
+ <property name="hexpand">True</property>
+ <property name="model">inner_auth_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="pac_provision_check">
+ <property name="label" translatable="yes">Allow automatic PAC pro_visioning</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="pac_provision_combo">
+ <property name="hexpand">True</property>
+ <property name="model">pac_provision_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/eap-method-leap.c b/panels/network/wireless-security/eap-method-leap.c
new file mode 100644
index 0000000..95e7a8e
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-leap.c
@@ -0,0 +1,266 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "eap-method-leap.h"
+#include "helpers.h"
+#include "nma-ui-utils.h"
+#include "ui-helpers.h"
+
+struct _EAPMethodLEAP {
+ GtkGrid parent;
+
+ GtkEntry *password_entry;
+ GtkLabel *password_label;
+ GtkCheckButton *show_password_check;
+ GtkEntry *username_entry;
+ GtkLabel *username_label;
+};
+
+static void eap_method_iface_init (EAPMethodInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (EAPMethodLEAP, eap_method_leap, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init))
+
+static void
+show_toggled_cb (EAPMethodLEAP *self)
+{
+ gboolean visible;
+ visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+ gtk_entry_set_visibility (self->password_entry, visible);
+}
+
+static gboolean
+validate (EAPMethod *parent, GError **error)
+{
+ EAPMethodLEAP *self = (EAPMethodLEAP *)parent;
+ NMSettingSecretFlags secret_flags;
+ const char *text;
+ gboolean ret = TRUE;
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->username_entry));
+ if (!text || !strlen (text)) {
+ widget_set_error (GTK_WIDGET (self->username_entry));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-LEAP username"));
+ ret = FALSE;
+ } else
+ widget_unset_error (GTK_WIDGET (self->username_entry));
+
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) {
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+ return TRUE;
+ }
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+ if (!text || !strlen (text)) {
+ widget_set_error (GTK_WIDGET (self->password_entry));
+ if (ret) {
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-LEAP password"));
+ ret = FALSE;
+ }
+ } else
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+
+ return ret;
+}
+
+static void
+add_to_size_group (EAPMethod *parent, GtkSizeGroup *group)
+{
+ EAPMethodLEAP *self = (EAPMethodLEAP *) parent;
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->username_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label));
+}
+
+static void
+fill_connection (EAPMethod *parent, NMConnection *connection, NMSettingSecretFlags flags)
+{
+ EAPMethodLEAP *self = (EAPMethodLEAP *) parent;
+ NMSetting8021x *s_8021x;
+ NMSettingSecretFlags secret_flags;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ g_assert (s_8021x);
+
+ nm_setting_802_1x_add_eap_method (s_8021x, "leap");
+
+ g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, gtk_editable_get_text (GTK_EDITABLE (self->username_entry)), NULL);
+ g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD, gtk_editable_get_text (GTK_EDITABLE (self->password_entry)), NULL);
+
+ /* Save 802.1X password flags to the connection */
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ nm_setting_set_secret_flags (NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD,
+ secret_flags, NULL);
+
+ /* Update secret flags and popup when editing the connection */
+ nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags,
+ NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD);
+}
+
+static void
+update_secrets (EAPMethod *parent, NMConnection *connection)
+{
+ EAPMethodLEAP *self = (EAPMethodLEAP *) parent;
+ helper_fill_secret_entry (connection,
+ self->password_entry,
+ NM_TYPE_SETTING_802_1X,
+ (HelperSecretFunc) nm_setting_802_1x_get_password);
+}
+
+static GtkWidget *
+get_default_field (EAPMethod *parent)
+{
+ EAPMethodLEAP *self = (EAPMethodLEAP *) parent;
+ return GTK_WIDGET (self->username_entry);
+}
+
+static const gchar *
+get_password_flags_name (EAPMethod *parent)
+{
+ return NM_SETTING_802_1X_PASSWORD;
+}
+
+static const gchar *
+get_username (EAPMethod *method)
+{
+ EAPMethodLEAP *self = EAP_METHOD_LEAP (method);
+ return gtk_editable_get_text (GTK_EDITABLE (self->username_entry));
+}
+
+static void
+set_username (EAPMethod *method, const gchar *username)
+{
+ EAPMethodLEAP *self = EAP_METHOD_LEAP (method);
+ gtk_editable_set_text (GTK_EDITABLE (self->username_entry), username);
+}
+
+static const gchar *
+get_password (EAPMethod *method)
+{
+ EAPMethodLEAP *self = EAP_METHOD_LEAP (method);
+ return gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+}
+
+static void
+set_password (EAPMethod *method, const gchar *password)
+{
+ EAPMethodLEAP *self = EAP_METHOD_LEAP (method);
+ gtk_editable_set_text (GTK_EDITABLE (self->password_entry), password);
+}
+
+static gboolean
+get_show_password (EAPMethod *method)
+{
+ EAPMethodLEAP *self = EAP_METHOD_LEAP (method);
+ return gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+}
+
+static void
+set_show_password (EAPMethod *method, gboolean show_password)
+{
+ EAPMethodLEAP *self = EAP_METHOD_LEAP (method);
+ gtk_check_button_set_active (self->show_password_check, show_password);
+}
+
+static void
+eap_method_leap_dispose (GObject *object)
+{
+ EAPMethodLEAP *self = EAP_METHOD_LEAP (object);
+
+ g_signal_handlers_disconnect_by_data (self, self);
+ g_signal_handlers_disconnect_by_data (self->show_password_check, self);
+
+ G_OBJECT_CLASS (eap_method_leap_parent_class)->dispose (object);
+}
+
+static void
+changed_cb (EAPMethodLEAP *self)
+{
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+eap_method_leap_init (EAPMethodLEAP *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+eap_method_leap_class_init (EAPMethodLEAPClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = eap_method_leap_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-leap.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, password_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, show_password_check);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, username_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, username_label);
+}
+
+static void
+eap_method_iface_init (EAPMethodInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->update_secrets = update_secrets;
+ iface->get_default_field = get_default_field;
+ iface->get_password_flags_name = get_password_flags_name;
+ iface->get_username = get_username;
+ iface->set_username = set_username;
+ iface->get_password = get_password;
+ iface->set_password = set_password;
+ iface->get_show_password = get_show_password;
+ iface->set_show_password = set_show_password;
+}
+
+EAPMethodLEAP *
+eap_method_leap_new (NMConnection *connection)
+{
+ EAPMethodLEAP *self;
+ NMSetting8021x *s_8021x = NULL;
+
+ self = g_object_new (eap_method_leap_get_type (), NULL);
+
+ g_signal_connect_swapped (self->username_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ /* Create password-storage popup menu for password entry under entry's secondary icon */
+ if (connection)
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, (NMSetting *) s_8021x, NM_SETTING_802_1X_PASSWORD,
+ FALSE, FALSE);
+
+ g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/eap-method-leap.h b/panels/network/wireless-security/eap-method-leap.h
new file mode 100644
index 0000000..18248a6
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-leap.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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 2007 - 2010 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (EAPMethodLEAP, eap_method_leap, EAP, METHOD_LEAP, GtkGrid)
+
+EAPMethodLEAP *eap_method_leap_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/eap-method-leap.ui b/panels/network/wireless-security/eap-method-leap.ui
new file mode 100644
index 0000000..3f945bd
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-leap.ui
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <template class="EAPMethodLEAP" parent="GtkGrid">
+ <property name="valign">start</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="username_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Username</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">username_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">password_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="hexpand">True</property>
+ <property name="visibility">False</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_password_check">
+ <property name="label" translatable="yes">Sho_w password</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="username_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/eap-method-peap.c b/panels/network/wireless-security/eap-method-peap.c
new file mode 100644
index 0000000..17eee12
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-peap.c
@@ -0,0 +1,400 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "eap-method-peap.h"
+#include "eap-method-simple.h"
+#include "helpers.h"
+#include "ui-helpers.h"
+#include "ws-file-chooser-button.h"
+
+#define I_NAME_COLUMN 0
+#define I_ID_COLUMN 1
+
+struct _EAPMethodPEAP {
+ GtkGrid parent;
+
+ GtkEntry *anon_identity_entry;
+ GtkLabel *anon_identity_label;
+ WsFileChooserButton *ca_cert_button;
+ GtkLabel *ca_cert_label;
+ GtkCheckButton *ca_cert_not_required_check;
+ GtkBox *inner_auth_box;
+ GtkComboBox *inner_auth_combo;
+ GtkLabel *inner_auth_label;
+ GtkListStore *inner_auth_model;
+ GtkComboBox *version_combo;
+ GtkLabel *version_label;
+
+ EAPMethodSimple *em_gtc;
+ EAPMethodSimple *em_md5;
+ EAPMethodSimple *em_mschap_v2;
+};
+
+static void eap_method_iface_init (EAPMethodInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (EAPMethodPEAP, eap_method_peap, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init))
+
+static EAPMethod *
+get_inner_method (EAPMethodPEAP *self)
+{
+ GtkTreeIter iter;
+ g_autofree gchar *id = NULL;
+
+ if (!gtk_combo_box_get_active_iter (self->inner_auth_combo, &iter))
+ return NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1);
+
+ if (strcmp (id, "gtc") == 0)
+ return EAP_METHOD (self->em_gtc);
+ if (strcmp (id, "md5") == 0)
+ return EAP_METHOD (self->em_md5);
+ if (strcmp (id, "mschapv2") == 0)
+ return EAP_METHOD (self->em_mschap_v2);
+
+ return NULL;
+}
+
+static gboolean
+validate (EAPMethod *method, GError **error)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ g_autoptr(GError) local_error = NULL;
+
+ if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->ca_cert_button),
+ TYPE_CA_CERT, NULL, NULL, &local_error)) {
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-PEAP CA certificate: %s"), local_error->message);
+ return FALSE;
+ }
+
+ if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (self->ca_cert_not_required_check))) {
+ g_autoptr(GFile) file = NULL;
+
+ file = ws_file_chooser_button_get_file (self->ca_cert_button);
+ if (file == NULL) {
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-PEAP CA certificate: no certificate specified"));
+ return FALSE;
+ }
+ }
+
+ return eap_method_validate (get_inner_method (self), error);
+}
+
+static void
+ca_cert_not_required_toggled (EAPMethodPEAP *self)
+{
+ eap_method_ca_cert_not_required_toggled (self->ca_cert_not_required_check,
+ ws_file_chooser_button_get_filechooser (self->ca_cert_button));
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+add_to_size_group (EAPMethod *method, GtkSizeGroup *group)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_not_required_check));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->anon_identity_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->version_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->inner_auth_label));
+
+ eap_method_add_to_size_group (EAP_METHOD (self->em_gtc), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_md5), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_mschap_v2), group);
+}
+
+static void
+fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ NMSetting8021x *s_8021x;
+ NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+ const char *text;
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GFile) file = NULL;
+ int peapver_active = 0;
+ g_autoptr(GError) error = NULL;
+ gboolean ca_cert_error = FALSE;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ g_assert (s_8021x);
+
+ nm_setting_802_1x_add_eap_method (s_8021x, "peap");
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->anon_identity_entry));
+ if (text && strlen (text))
+ g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, text, NULL);
+
+ file = ws_file_chooser_button_get_file (self->ca_cert_button);
+ filename = file ? g_file_get_path (file) : NULL;
+ if (!nm_setting_802_1x_set_ca_cert (s_8021x, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error)) {
+ g_warning ("Couldn't read CA certificate '%s': %s", filename, error ? error->message : "(unknown)");
+ ca_cert_error = TRUE;
+ }
+ eap_method_ca_cert_ignore_set (method, connection, filename, ca_cert_error);
+
+ peapver_active = gtk_combo_box_get_active (self->version_combo);
+ switch (peapver_active) {
+ case 1: /* PEAP v0 */
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_PEAPVER, "0", NULL);
+ break;
+ case 2: /* PEAP v1 */
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_PEAPVER, "1", NULL);
+ break;
+ default: /* Automatic */
+ g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_PEAPVER, NULL, NULL);
+ break;
+ }
+
+ eap_method_fill_connection (get_inner_method (self), connection, flags);
+}
+
+static void
+inner_auth_combo_changed_cb (EAPMethodPEAP *self)
+{
+ EAPMethod *inner_method;
+ GtkWidget *child;
+
+ inner_method = get_inner_method (self);
+
+ /* Remove the previous method and migrate username/password across */
+ child = gtk_widget_get_first_child (GTK_WIDGET (self->inner_auth_box));
+ if (child != NULL) {
+ EAPMethod *old_eap = EAP_METHOD (child);
+ eap_method_set_username (inner_method, eap_method_get_username (old_eap));
+ eap_method_set_password (inner_method, eap_method_get_password (old_eap));
+ eap_method_set_show_password (inner_method, eap_method_get_show_password (old_eap));
+ gtk_box_remove (self->inner_auth_box, GTK_WIDGET (old_eap));
+ }
+
+ gtk_box_append (self->inner_auth_box, g_object_ref (GTK_WIDGET (inner_method)));
+
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+update_secrets (EAPMethod *method, NMConnection *connection)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+
+ eap_method_update_secrets (EAP_METHOD (self->em_gtc), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_md5), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_mschap_v2), connection);
+}
+
+static GtkWidget *
+get_default_field (EAPMethod *method)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ return GTK_WIDGET (self->anon_identity_entry);
+}
+
+static const gchar *
+get_password_flags_name (EAPMethod *method)
+{
+ return NM_SETTING_802_1X_PASSWORD;
+}
+
+static const gchar *
+get_username (EAPMethod *method)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ return eap_method_get_username (get_inner_method (self));
+}
+
+static void
+set_username (EAPMethod *method, const gchar *username)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ return eap_method_set_username (get_inner_method (self), username);
+}
+
+static const gchar *
+get_password (EAPMethod *method)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ return eap_method_get_password (get_inner_method (self));
+}
+
+static void
+set_password (EAPMethod *method, const gchar *password)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ return eap_method_set_password (get_inner_method (self), password);
+}
+
+static gboolean
+get_show_password (EAPMethod *method)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ return eap_method_get_show_password (get_inner_method (self));
+}
+
+static void
+set_show_password (EAPMethod *method, gboolean show_password)
+{
+ EAPMethodPEAP *self = EAP_METHOD_PEAP (method);
+ return eap_method_set_show_password (get_inner_method (self), show_password);
+}
+
+static void
+changed_cb (EAPMethodPEAP *self)
+{
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+eap_method_peap_init (EAPMethodPEAP *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+eap_method_peap_class_init (EAPMethodPEAPClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-peap.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, anon_identity_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, anon_identity_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, ca_cert_button);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, ca_cert_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, ca_cert_not_required_check);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_box);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_combo);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_model);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, version_combo);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, version_label);
+}
+
+static void
+eap_method_iface_init (EAPMethodInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->update_secrets = update_secrets;
+ iface->get_default_field = get_default_field;
+ iface->get_password_flags_name = get_password_flags_name;
+ iface->get_username = get_username;
+ iface->set_username = set_username;
+ iface->get_password = get_password;
+ iface->set_password = set_password;
+ iface->get_show_password = get_show_password;
+ iface->set_show_password = set_show_password;
+}
+
+EAPMethodPEAP *
+eap_method_peap_new (NMConnection *connection)
+{
+ EAPMethodPEAP *self;
+ GtkFileFilter *filter;
+ NMSetting8021x *s_8021x = NULL;
+ const char *filename;
+ const gchar *phase2_auth = NULL;
+ GtkTreeIter iter;
+
+ self = g_object_new (eap_method_peap_get_type (), NULL);
+
+ if (connection)
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+
+ g_signal_connect_swapped (self->ca_cert_not_required_check, "toggled", G_CALLBACK (ca_cert_not_required_toggled), self);
+
+ g_signal_connect_swapped (self->ca_cert_button, "notify::file", G_CALLBACK (changed_cb), self);
+ filter = eap_method_default_file_chooser_filter_new (FALSE);
+ gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->ca_cert_button),
+ filter);
+ if (connection && s_8021x) {
+ filename = NULL;
+ if (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+ filename = nm_setting_802_1x_get_ca_cert_path (s_8021x);
+ if (filename) {
+ g_autoptr(GFile) file = g_file_new_for_path (filename);
+ ws_file_chooser_button_set_file (self->ca_cert_button, file);
+ }
+ }
+ gtk_check_button_set_active (self->ca_cert_not_required_check,
+ !filename && eap_method_ca_cert_ignore_get (EAP_METHOD (self), connection));
+ }
+
+ self->em_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_mschap_v2));
+ g_signal_connect_object (self->em_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_md5 = eap_method_simple_new (connection, "md5", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_md5));
+ g_signal_connect_object (self->em_md5, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_gtc = eap_method_simple_new (connection, "gtc", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_gtc));
+ g_signal_connect_object (self->em_gtc, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ if (s_8021x) {
+ if (nm_setting_802_1x_get_phase2_auth (s_8021x))
+ phase2_auth = nm_setting_802_1x_get_phase2_auth (s_8021x);
+ else if (nm_setting_802_1x_get_phase2_autheap (s_8021x))
+ phase2_auth = nm_setting_802_1x_get_phase2_autheap (s_8021x);
+ }
+ if (phase2_auth == NULL)
+ phase2_auth = "mschapv2";
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->inner_auth_model), &iter)) {
+ do {
+ g_autofree gchar *id = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1);
+ if (strcmp (id, phase2_auth) == 0)
+ gtk_combo_box_set_active_iter (self->inner_auth_combo, &iter);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->inner_auth_model), &iter));
+ }
+
+ g_signal_connect_swapped (self->inner_auth_combo, "changed", G_CALLBACK (inner_auth_combo_changed_cb), self);
+ inner_auth_combo_changed_cb (self);
+
+ gtk_combo_box_set_active (self->version_combo, 0);
+ if (s_8021x) {
+ const char *peapver;
+
+ peapver = nm_setting_802_1x_get_phase1_peapver (s_8021x);
+ if (peapver) {
+ /* Index 0 is "Automatic" */
+ if (!strcmp (peapver, "0"))
+ gtk_combo_box_set_active (self->version_combo, 1);
+ else if (!strcmp (peapver, "1"))
+ gtk_combo_box_set_active (self->version_combo, 2);
+ }
+ }
+ g_signal_connect_swapped (self->version_combo, "changed", G_CALLBACK (changed_cb), self);
+
+ if (s_8021x && nm_setting_802_1x_get_anonymous_identity (s_8021x))
+ gtk_editable_set_text (GTK_EDITABLE (self->anon_identity_entry), nm_setting_802_1x_get_anonymous_identity (s_8021x));
+ g_signal_connect_swapped (self->anon_identity_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/eap-method-peap.h b/panels/network/wireless-security/eap-method-peap.h
new file mode 100644
index 0000000..53b2f30
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-peap.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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 2007 - 2010 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (EAPMethodPEAP, eap_method_peap, EAP, METHOD_PEAP, GtkGrid)
+
+EAPMethodPEAP *eap_method_peap_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/eap-method-peap.ui b/panels/network/wireless-security/eap-method-peap.ui
new file mode 100644
index 0000000..ac04783
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-peap.ui
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <object class="GtkListStore" id="inner_auth_model">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">MSCHAPv2</col>
+ <col id="1">mschapv2</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">MD5</col>
+ <col id="1">md5</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">GTC</col>
+ <col id="1">gtc</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="version_model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Automatic</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Version 0</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Version 1</col>
+ </row>
+ </data>
+ </object>
+ <template class="EAPMethodPEAP" parent="GtkGrid">
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="anon_identity_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Anony_mous identity</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">anon_identity_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="anon_identity_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ca_cert_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">C_A certificate</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">ca_cert_button</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="WsFileChooserButton" id="ca_cert_button">
+ <property name="hexpand">True</property>
+ <property name="title" translatable="yes">Choose a Certificate Authority certificate</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="ca_cert_not_required_check">
+ <property name="label" translatable="yes">No CA certificate is _required</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="version_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">PEAP _version</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">version_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="version_combo">
+ <property name="hexpand">True</property>
+ <property name="model">version_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="inner_auth_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Inner authentication</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">inner_auth_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="inner_auth_combo">
+ <property name="hexpand">True</property>
+ <property name="model">inner_auth_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="inner_auth_box">
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">5</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/eap-method-simple.c b/panels/network/wireless-security/eap-method-simple.c
new file mode 100644
index 0000000..7bb29ec
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-simple.c
@@ -0,0 +1,356 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "eap-method-simple.h"
+#include "helpers.h"
+#include "nma-ui-utils.h"
+#include "ui-helpers.h"
+
+struct _EAPMethodSimple {
+ GtkGrid parent;
+
+ GtkEntry *password_entry;
+ GtkLabel *password_label;
+ GtkToggleButton *show_password_check;
+ GtkEntry *username_entry;
+ GtkLabel *username_label;
+
+ gchar *name;
+ gboolean phase2;
+ gboolean autheap_allowed;
+
+ guint idle_func_id;
+};
+
+static void eap_method_iface_init (EAPMethodInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (EAPMethodSimple, eap_method_simple, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init))
+
+static void
+show_toggled_cb (EAPMethodSimple *self)
+{
+ gboolean visible;
+
+ visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+ gtk_entry_set_visibility (self->password_entry, visible);
+}
+
+static gboolean
+always_ask_selected (GtkEntry *passwd_entry)
+{
+ return !!( nma_utils_menu_to_secret_flags (GTK_WIDGET (passwd_entry))
+ & NM_SETTING_SECRET_FLAG_NOT_SAVED);
+}
+
+static gboolean
+validate (EAPMethod *method, GError **error)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ const char *text;
+ gboolean ret = TRUE;
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->username_entry));
+ if (!text || !strlen (text)) {
+ widget_set_error (GTK_WIDGET (self->username_entry));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP username"));
+ ret = FALSE;
+ } else
+ widget_unset_error (GTK_WIDGET (self->username_entry));
+
+ /* Check if the password should always be requested */
+ if (always_ask_selected (self->password_entry))
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+ else {
+ text = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+ if (!text || !strlen (text)) {
+ widget_set_error (GTK_WIDGET (self->password_entry));
+ if (ret) {
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP password"));
+ ret = FALSE;
+ }
+ } else
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+ }
+
+ return ret;
+}
+
+static void
+add_to_size_group (EAPMethod *method, GtkSizeGroup *group)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->username_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label));
+}
+
+static void
+fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags prev_flags)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ NMSetting8021x *s_8021x;
+ gboolean not_saved = FALSE;
+ NMSettingSecretFlags flags;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ g_assert (s_8021x);
+
+ /* If this is the main EAP method, clear any existing methods because the
+ * user-selected on will replace it.
+ */
+ if (eap_method_get_phase2 (method) == FALSE)
+ nm_setting_802_1x_clear_eap_methods (s_8021x);
+
+ if (eap_method_get_phase2 (method)) {
+ /* If the outer EAP method (TLS, TTLS, PEAP, etc) allows inner/phase2
+ * EAP methods (which only TTLS allows) *and* the inner/phase2 method
+ * supports being an inner EAP method, then set PHASE2_AUTHEAP.
+ * Otherwise the inner/phase2 method goes into PHASE2_AUTH.
+ */
+ if (self->autheap_allowed) {
+ g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, self->name, NULL);
+ g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, NULL, NULL);
+ } else {
+ g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, self->name, NULL);
+ g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, NULL, NULL);
+ }
+ } else
+ nm_setting_802_1x_add_eap_method (s_8021x, self->name);
+
+ g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, gtk_editable_get_text (GTK_EDITABLE (self->username_entry)), NULL);
+
+ /* Save the password always ask setting */
+ not_saved = always_ask_selected (self->password_entry);
+ flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ nm_setting_set_secret_flags (NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD, flags, NULL);
+
+ /* Fill the connection's password if we're in the applet so that it'll get
+ * back to NM. From the editor though, since the connection isn't going
+ * back to NM in response to a GetSecrets() call, we don't save it if the
+ * user checked "Always Ask".
+ */
+ if (not_saved == FALSE)
+ g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD, gtk_editable_get_text (GTK_EDITABLE (self->password_entry)), NULL);
+
+ /* Update secret flags and popup when editing the connection */
+ nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), flags,
+ NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD);
+}
+
+static void
+update_secrets (EAPMethod *method, NMConnection *connection)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ helper_fill_secret_entry (connection,
+ self->password_entry,
+ NM_TYPE_SETTING_802_1X,
+ (HelperSecretFunc) nm_setting_802_1x_get_password);
+}
+
+static GtkWidget *
+get_default_field (EAPMethod *method)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ return GTK_WIDGET (self->username_entry);
+}
+
+static const gchar *
+get_password_flags_name (EAPMethod *method)
+{
+ return NM_SETTING_802_1X_PASSWORD;
+}
+
+static const gboolean
+get_phase2 (EAPMethod *method)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ return self->phase2;
+}
+
+static const gchar *
+get_username (EAPMethod *method)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ return gtk_editable_get_text (GTK_EDITABLE (self->username_entry));
+}
+
+static void
+set_username (EAPMethod *method, const gchar *username)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ if (username)
+ gtk_editable_set_text (GTK_EDITABLE (self->username_entry), username);
+}
+
+static const gchar *
+get_password (EAPMethod *method)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ return gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+}
+
+static void
+set_password (EAPMethod *method, const gchar *password)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ if (password)
+ gtk_editable_set_text (GTK_EDITABLE (self->password_entry), password);
+}
+
+static gboolean
+get_show_password (EAPMethod *method)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ return gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+}
+
+static void
+set_show_password (EAPMethod *method, gboolean show_password)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (method);
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->show_password_check), show_password);
+}
+
+static gboolean
+stuff_changed (EAPMethodSimple *self)
+{
+ eap_method_emit_changed (EAP_METHOD (self));
+ self->idle_func_id = 0;
+ return FALSE;
+}
+
+static void
+password_storage_changed (EAPMethodSimple *self)
+{
+ gboolean always_ask;
+
+ always_ask = always_ask_selected (self->password_entry);
+
+ if (always_ask) {
+ /* we always clear this button and do not restore it
+ * (because we want to hide the password). */
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->show_password_check), FALSE);
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->show_password_check), !always_ask);
+
+ if (!self->idle_func_id)
+ self->idle_func_id = g_idle_add ((GSourceFunc) stuff_changed, self);
+}
+
+static void
+eap_method_simple_dispose (GObject *object)
+{
+ EAPMethodSimple *self = EAP_METHOD_SIMPLE (object);
+
+ g_clear_pointer (&self->name, g_free);
+
+ g_signal_handlers_disconnect_by_data (self, self);
+ g_signal_handlers_disconnect_by_data (self->password_entry, self);
+ g_signal_handlers_disconnect_by_data (self->show_password_check, self);
+
+ if (self->idle_func_id != 0) {
+ g_source_remove (self->idle_func_id);
+ self->idle_func_id = 0;
+ }
+
+ G_OBJECT_CLASS (eap_method_simple_parent_class)->dispose (object);
+}
+
+static void
+changed_cb (EAPMethodSimple *self)
+{
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+eap_method_simple_init (EAPMethodSimple *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+eap_method_simple_class_init (EAPMethodSimpleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = eap_method_simple_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-simple.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, password_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, username_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, show_password_check);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, username_entry);
+}
+
+static void
+eap_method_iface_init (EAPMethodInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->update_secrets = update_secrets;
+ iface->get_default_field = get_default_field;
+ iface->get_password_flags_name = get_password_flags_name;
+ iface->get_phase2 = get_phase2;
+ iface->get_username = get_username;
+ iface->set_username = set_username;
+ iface->get_password = get_password;
+ iface->set_password = set_password;
+ iface->get_show_password = get_show_password;
+ iface->set_show_password = set_show_password;
+}
+
+EAPMethodSimple *
+eap_method_simple_new (NMConnection *connection, const gchar *name, gboolean phase2, gboolean autheap_allowed)
+{
+ EAPMethodSimple *self;
+ NMSetting8021x *s_8021x = NULL;
+
+ self = g_object_new (eap_method_simple_get_type (), NULL);
+ self->name = g_strdup (name);
+ self->phase2 = phase2;
+ self->autheap_allowed = autheap_allowed;
+
+ g_signal_connect_swapped (self->username_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ /* Create password-storage popup menu for password entry under entry's secondary icon */
+ if (connection)
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, (NMSetting *) s_8021x, NM_SETTING_802_1X_PASSWORD,
+ FALSE, FALSE);
+
+ g_signal_connect_swapped (self->password_entry, "notify::secondary-icon-name", G_CALLBACK (password_storage_changed), self);
+
+ g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/eap-method-simple.h b/panels/network/wireless-security/eap-method-simple.h
new file mode 100644
index 0000000..8645fb1
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-simple.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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 2007 - 2010 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (EAPMethodSimple, eap_method_simple, EAP, METHOD_SIMPLE, GtkGrid)
+
+typedef enum {
+ /* NOTE: when updating this table, also update eap_methods[] */
+ EAP_METHOD_SIMPLE_TYPE_PAP = 0,
+ EAP_METHOD_SIMPLE_TYPE_MSCHAP,
+ EAP_METHOD_SIMPLE_TYPE_MSCHAP_V2,
+ EAP_METHOD_SIMPLE_TYPE_PLAIN_MSCHAP_V2,
+ EAP_METHOD_SIMPLE_TYPE_MD5,
+ EAP_METHOD_SIMPLE_TYPE_PWD,
+ EAP_METHOD_SIMPLE_TYPE_CHAP,
+ EAP_METHOD_SIMPLE_TYPE_GTC,
+
+ /* Boundary value, do not use */
+ EAP_METHOD_SIMPLE_TYPE_LAST
+} EAPMethodSimpleType;
+
+EAPMethodSimple *eap_method_simple_new (NMConnection *connection,
+ const gchar *name,
+ gboolean phase2,
+ gboolean autheap_allowed);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/eap-method-simple.ui b/panels/network/wireless-security/eap-method-simple.ui
new file mode 100644
index 0000000..e5c37c9
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-simple.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <template class="EAPMethodSimple" parent="GtkGrid">
+ <property name="valign">start</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="username_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Username</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">username_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">password_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="hexpand">True</property>
+ <property name="visibility">False</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="username_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ <child>
+ <object class="GtkCheckButton" id="show_password_check">
+ <property name="label" translatable="yes">Sho_w password</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/eap-method-tls.c b/panels/network/wireless-security/eap-method-tls.c
new file mode 100644
index 0000000..5038915
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-tls.c
@@ -0,0 +1,557 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "eap-method-tls.h"
+#include "helpers.h"
+#include "nma-ui-utils.h"
+#include "ui-helpers.h"
+#include "ws-file-chooser-button.h"
+
+struct _EAPMethodTLS {
+ GtkGrid parent;
+
+ WsFileChooserButton *ca_cert_button;
+ GtkLabel *ca_cert_label;
+ GtkCheckButton *ca_cert_not_required_check;
+ GtkEntry *identity_entry;
+ GtkLabel *identity_label;
+ WsFileChooserButton *private_key_button;
+ GtkLabel *private_key_label;
+ GtkEntry *private_key_password_entry;
+ GtkLabel *private_key_password_label;
+ GtkCheckButton *show_password_check;
+ WsFileChooserButton *user_cert_button;
+ GtkLabel *user_cert_label;
+
+ gchar *username;
+ gchar *password;
+ gboolean show_password;
+};
+
+static void eap_method_iface_init (EAPMethodInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (EAPMethodTLS, eap_method_tls, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init))
+
+static void
+eap_method_tls_dispose (GObject *object)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (object);
+
+ g_clear_pointer (&self->username, g_free);
+ g_clear_pointer (&self->password, g_free);
+
+ G_OBJECT_CLASS (eap_method_tls_parent_class)->dispose (object);
+}
+
+static void
+show_toggled_cb (EAPMethodTLS *self)
+{
+ gboolean visible;
+
+ visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+ gtk_entry_set_visibility (self->private_key_password_entry, visible);
+}
+
+static gboolean
+validate (EAPMethod *method, GError **error)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+ NMSettingSecretFlags secret_flags;
+ const char *password, *identity;
+ g_autoptr(GError) ca_cert_error = NULL;
+ g_autoptr(GError) private_key_error = NULL;
+ g_autoptr(GError) user_cert_error = NULL;
+ gboolean ret = TRUE;
+
+ identity = gtk_editable_get_text (GTK_EDITABLE (self->identity_entry));
+ if (!identity || !strlen (identity)) {
+ widget_set_error (GTK_WIDGET (self->identity_entry));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-TLS identity"));
+ ret = FALSE;
+ } else {
+ widget_unset_error (GTK_WIDGET (self->identity_entry));
+ }
+
+ if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->ca_cert_button),
+ TYPE_CA_CERT, NULL, NULL, &ca_cert_error)) {
+ widget_set_error (GTK_WIDGET (self->ca_cert_button));
+ if (ret) {
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS CA certificate: %s"), ca_cert_error->message);
+ ret = FALSE;
+ }
+ } else if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (self->ca_cert_not_required_check))) {
+ g_autoptr(GFile) file = NULL;
+
+ file = ws_file_chooser_button_get_file (self->ca_cert_button);
+ if (file == NULL) {
+ widget_set_error (GTK_WIDGET (self->ca_cert_button));
+ if (ret) {
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS CA certificate: no certificate specified"));
+ ret = FALSE;
+ }
+ }
+ }
+
+ password = gtk_editable_get_text (GTK_EDITABLE (self->private_key_password_entry));
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->private_key_password_entry));
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ password = NULL;
+
+ if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->private_key_button),
+ TYPE_PRIVATE_KEY,
+ password,
+ &format,
+ &private_key_error)) {
+ if (ret) {
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS private-key: %s"), private_key_error->message);
+ ret = FALSE;
+ }
+ widget_set_error (GTK_WIDGET (self->private_key_button));
+ }
+
+ if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+ if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->user_cert_button),
+ TYPE_CLIENT_CERT, NULL, NULL, &user_cert_error)) {
+ if (ret) {
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS user-certificate: %s"), user_cert_error->message);
+ ret = FALSE;
+ }
+ widget_set_error (GTK_WIDGET (self->user_cert_button));
+ }
+ }
+
+ return ret;
+}
+
+static void
+ca_cert_not_required_toggled (EAPMethodTLS *self)
+{
+ eap_method_ca_cert_not_required_toggled (self->ca_cert_not_required_check,
+ ws_file_chooser_button_get_filechooser (self->ca_cert_button));
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+add_to_size_group (EAPMethod *method, GtkSizeGroup *group)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_not_required_check));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->identity_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->user_cert_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->private_key_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->private_key_password_label));
+}
+
+static void
+fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+ NMSetting8021x *s_8021x;
+ NMSettingSecretFlags secret_flags;
+ g_autofree gchar *ca_filename = NULL;
+ g_autoptr(GFile) ca_file = NULL;
+ g_autofree gchar *pk_filename = NULL;
+ g_autoptr(GFile) pk_file = NULL;
+ const char *password = NULL;
+ gboolean ca_cert_error = FALSE;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GError) error2 = NULL;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ g_assert (s_8021x);
+
+ nm_setting_802_1x_add_eap_method (s_8021x, "tls");
+
+ g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, gtk_editable_get_text (GTK_EDITABLE (self->identity_entry)), NULL);
+
+ /* TLS private key */
+ password = gtk_editable_get_text (GTK_EDITABLE (self->private_key_password_entry));
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->private_key_password_entry));
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ password = NULL;
+
+ pk_file = ws_file_chooser_button_get_file (self->private_key_button);
+ g_assert (pk_file);
+ pk_filename = g_file_get_path (pk_file);
+
+ if (!nm_setting_802_1x_set_private_key (s_8021x, pk_filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error))
+ g_warning ("Couldn't read private key '%s': %s", pk_filename, error ? error->message : "(unknown)");
+
+ /* Save 802.1X password flags to the connection */
+ nm_setting_set_secret_flags (NM_SETTING (s_8021x), NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD,
+ secret_flags, NULL);
+
+ /* Update secret flags and popup when editing the connection */
+ nma_utils_update_password_storage (GTK_WIDGET (self->private_key_password_entry), secret_flags,
+ NM_SETTING (s_8021x), NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
+
+ /* TLS client certificate */
+ if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+ g_autofree gchar *cc_filename = NULL;
+ g_autoptr(GFile) cc_file = NULL;
+ g_autoptr(GError) error = NULL;
+
+ /* If the key is pkcs#12 nm_setting_802_1x_set_private_key() already
+ * set the client certificate for us.
+ */
+ cc_file = ws_file_chooser_button_get_file (self->private_key_button);
+ g_assert (cc_file);
+ cc_filename = g_file_get_path (cc_file);
+
+ format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+ if (!nm_setting_802_1x_set_client_cert (s_8021x, cc_filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error))
+ g_warning ("Couldn't read client certificate '%s': %s", cc_filename, error ? error->message : "(unknown)");
+ }
+
+ /* TLS CA certificate */
+ ca_file = ws_file_chooser_button_get_file (self->private_key_button);
+ ca_filename = ca_file ? g_file_get_path (ca_file) : NULL;
+
+ format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+ if (!nm_setting_802_1x_set_ca_cert (s_8021x, ca_filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error2)) {
+ g_warning ("Couldn't read CA certificate '%s': %s", ca_filename, error2 ? error2->message : "(unknown)");
+ ca_cert_error = TRUE;
+ }
+ eap_method_ca_cert_ignore_set (method, connection, ca_filename, ca_cert_error);
+}
+
+static void
+private_key_picker_helper (EAPMethodTLS *self, const char *filename, gboolean changed)
+{
+ g_autoptr(NMSetting8021x) setting = NULL;
+ NMSetting8021xCKFormat cert_format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+ const char *password;
+
+ password = gtk_editable_get_text (GTK_EDITABLE (self->private_key_password_entry));
+
+ setting = (NMSetting8021x *) nm_setting_802_1x_new ();
+ nm_setting_802_1x_set_private_key (setting, filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, &cert_format, NULL);
+
+ /* With PKCS#12, the client cert must be the same as the private key */
+ if (cert_format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+ ws_file_chooser_button_set_file (self->user_cert_button, NULL);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->user_cert_button), FALSE);
+ } else if (changed)
+ gtk_widget_set_sensitive (GTK_WIDGET (self->user_cert_button), TRUE);
+
+ /* Warn the user if the private key is unencrypted */
+ if (!eap_method_is_encrypted_private_key (filename)) {
+ GtkWidget *dialog;
+ GtkNative *native;
+ GtkWindow *parent_window = NULL;
+
+ native = gtk_widget_get_native (GTK_WIDGET (self));
+ if (GTK_IS_WINDOW (native))
+ parent_window = GTK_WINDOW (native);
+
+ dialog = gtk_message_dialog_new (parent_window,
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_OK,
+ "%s",
+ _("Unencrypted private keys are insecure"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s",
+ _("The selected private key does not appear to be protected by a password. This could allow your security credentials to be compromised. Please select a password-protected private key.\n\n(You can password-protect your private key with openssl)"));
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+}
+
+static void
+private_key_picker_file_set_cb (WsFileChooserButton *chooser, gpointer user_data)
+{
+ EAPMethodTLS *self = user_data;
+ g_autoptr(GFile) file = NULL;
+ g_autofree gchar *filename = NULL;
+
+ file = ws_file_chooser_button_get_file (chooser);
+ filename = file ? g_file_get_path (file) : NULL;
+ if (filename)
+ private_key_picker_helper (self, filename, TRUE);
+}
+
+static void reset_filter (GtkWidget *widget, GParamSpec *spec, gpointer user_data)
+{
+ if (!gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (widget))) {
+ g_signal_handlers_block_by_func (widget, reset_filter, user_data);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (widget), GTK_FILE_FILTER (user_data));
+ g_signal_handlers_unblock_by_func (widget, reset_filter, user_data);
+ }
+}
+
+typedef const char * (*PathFunc) (NMSetting8021x *setting);
+typedef NMSetting8021xCKScheme (*SchemeFunc) (NMSetting8021x *setting);
+
+static void
+changed_cb (EAPMethodTLS *self)
+{
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+setup_filepicker (EAPMethodTLS *self,
+ WsFileChooserButton *button,
+ const char *title,
+ NMSetting8021x *s_8021x,
+ SchemeFunc scheme_func,
+ PathFunc path_func,
+ gboolean privkey,
+ gboolean client_cert)
+{
+ GtkFileFilter *filter;
+ const char *filename = NULL;
+
+ if (s_8021x && path_func && scheme_func) {
+ if (scheme_func (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+ filename = path_func (s_8021x);
+ if (filename) {
+ g_autoptr(GFile) file = g_file_new_for_path (filename);
+ ws_file_chooser_button_set_file (button, file);
+ }
+ }
+ }
+
+ /* Connect a special handler for private keys to intercept PKCS#12 key types
+ * and desensitize the user cert button.
+ */
+ if (privkey) {
+ g_signal_connect (button, "notify::file",
+ G_CALLBACK (private_key_picker_file_set_cb),
+ self);
+ if (filename)
+ private_key_picker_helper (self, filename, FALSE);
+ }
+
+ g_signal_connect_swapped (button, "notify::file", G_CALLBACK (changed_cb), self);
+
+ filter = eap_method_default_file_chooser_filter_new (privkey);
+ gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (button), filter);
+
+ /* For some reason, GTK+ calls set_current_filter (..., NULL) from
+ * gtkfilechooserdefault.c::show_and_select_files_finished_loading() on our
+ * dialog; so force-reset the filter to what we want it to be whenever
+ * it gets cleared.
+ */
+ if (client_cert)
+ g_signal_connect (button, "notify::filter", (GCallback) reset_filter, filter);
+}
+
+static void
+update_secrets (EAPMethod *method, NMConnection *connection)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ NMSetting8021x *s_8021x;
+ const char *filename;
+
+ helper_fill_secret_entry (connection,
+ self->private_key_password_entry,
+ NM_TYPE_SETTING_802_1X,
+ (HelperSecretFunc) nm_setting_802_1x_get_private_key_password);
+
+ /* Set the private key filepicker button path if we have a private key */
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x && (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH)) {
+ filename = nm_setting_802_1x_get_private_key_path (s_8021x);
+ if (filename) {
+ g_autoptr(GFile) file = g_file_new_for_path (filename);
+ ws_file_chooser_button_set_file (self->private_key_button, file);
+ }
+ }
+}
+
+static GtkWidget *
+get_default_field (EAPMethod *method)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ return GTK_WIDGET (self->identity_entry);
+}
+
+static const gchar *
+get_password_flags_name (EAPMethod *method)
+{
+ return NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD;
+}
+
+static const gchar *
+get_username (EAPMethod *method)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ return self->username;
+}
+
+static void
+set_username (EAPMethod *method, const gchar *username)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ g_free (self->username);
+ self->username = g_strdup (username);
+}
+
+static const gchar *
+get_password (EAPMethod *method)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ return self->password;
+}
+
+static void
+set_password (EAPMethod *method, const gchar *password)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ g_free (self->password);
+ self->password = g_strdup (password);
+}
+
+static const gboolean
+get_show_password (EAPMethod *method)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ return self->show_password;
+}
+
+static void
+set_show_password (EAPMethod *method, gboolean show_password)
+{
+ EAPMethodTLS *self = EAP_METHOD_TLS (method);
+ self->show_password = show_password;
+}
+
+static void
+eap_method_tls_init (EAPMethodTLS *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+ self->username = g_strdup ("");
+ self->password = g_strdup ("");
+}
+
+static void
+eap_method_tls_class_init (EAPMethodTLSClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = eap_method_tls_dispose;
+
+ g_type_ensure (WS_TYPE_FILE_CHOOSER_BUTTON);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-tls.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, ca_cert_button);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, ca_cert_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, ca_cert_not_required_check);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, identity_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, identity_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_button);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_password_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_password_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, show_password_check);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, user_cert_button);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, user_cert_label);
+}
+
+static void
+eap_method_iface_init (EAPMethodInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->update_secrets = update_secrets;
+ iface->get_default_field = get_default_field;
+ iface->get_password_flags_name = get_password_flags_name;
+ iface->get_username = get_username;
+ iface->set_username = set_username;
+ iface->get_password = get_password;
+ iface->set_password = set_password;
+ iface->get_show_password = get_show_password;
+ iface->set_show_password = set_show_password;
+}
+
+EAPMethodTLS *
+eap_method_tls_new (NMConnection *connection)
+{
+ EAPMethodTLS *self;
+ NMSetting8021x *s_8021x = NULL;
+ gboolean ca_not_required = FALSE;
+
+ self = g_object_new (eap_method_tls_get_type (), NULL);
+
+ if (connection)
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+
+ g_signal_connect_swapped (self->ca_cert_not_required_check, "toggled", G_CALLBACK (ca_cert_not_required_toggled), self);
+
+ g_signal_connect_swapped (self->identity_entry, "changed", G_CALLBACK (changed_cb), self);
+ if (s_8021x && nm_setting_802_1x_get_identity (s_8021x))
+ gtk_editable_set_text (GTK_EDITABLE (self->identity_entry), nm_setting_802_1x_get_identity (s_8021x));
+
+ setup_filepicker (self,
+ self->user_cert_button,
+ _("Choose your personal certificate"),
+ s_8021x,
+ nm_setting_802_1x_get_client_cert_scheme,
+ nm_setting_802_1x_get_client_cert_path,
+ FALSE, TRUE);
+ setup_filepicker (self,
+ self->ca_cert_button,
+ _("Choose a Certificate Authority certificate"),
+ s_8021x,
+ nm_setting_802_1x_get_ca_cert_scheme,
+ nm_setting_802_1x_get_ca_cert_path,
+ FALSE, FALSE);
+ setup_filepicker (self,
+ self->private_key_button,
+ _("Choose your private key"),
+ s_8021x,
+ nm_setting_802_1x_get_private_key_scheme,
+ nm_setting_802_1x_get_private_key_path,
+ TRUE, FALSE);
+
+ if (connection && eap_method_ca_cert_ignore_get (EAP_METHOD (self), connection)) {
+ g_autoptr(GFile) file = ws_file_chooser_button_get_file (self->ca_cert_button);
+ ca_not_required = !file;
+ }
+ gtk_check_button_set_active (self->ca_cert_not_required_check, ca_not_required);
+
+ /* Fill secrets, if any */
+ if (connection)
+ update_secrets (EAP_METHOD (self), connection);
+
+ g_signal_connect_swapped (self->private_key_password_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ /* Create password-storage popup menu for password entry under entry's secondary icon */
+ nma_utils_setup_password_storage (GTK_WIDGET (self->private_key_password_entry), 0, (NMSetting *) s_8021x, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD,
+ FALSE, FALSE);
+
+ g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/eap-method-tls.h b/panels/network/wireless-security/eap-method-tls.h
new file mode 100644
index 0000000..27f3843
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-tls.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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 2007 - 2010 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (EAPMethodTLS, eap_method_tls, EAP, METHOD_TLS, GtkGrid)
+
+EAPMethodTLS *eap_method_tls_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/eap-method-tls.ui b/panels/network/wireless-security/eap-method-tls.ui
new file mode 100644
index 0000000..69c2610
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-tls.ui
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <template class="EAPMethodTLS" parent="GtkGrid">
+ <property name="valign">start</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="identity_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">I_dentity</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">identity_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="identity_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="user_cert_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_User certificate</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">user_cert_button</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="WsFileChooserButton" id="user_cert_button">
+ <property name="hexpand">True</property>
+ <property name="title" translatable="yes">Choose a Certificate Authority certificate</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ca_cert_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">C_A certificate</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">ca_cert_button</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="WsFileChooserButton" id="ca_cert_button">
+ <property name="hexpand">True</property>
+ <property name="title" translatable="yes">Choose a Certificate Authority certificate</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="ca_cert_not_required_check">
+ <property name="label" translatable="yes">No CA certificate is _required</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="private_key_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Private _key</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">private_key_button</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="WsFileChooserButton" id="private_key_button">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="private_key_password_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Private key password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">private_key_password_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">5</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="private_key_password_entry">
+ <property name="hexpand">True</property>
+ <property name="visibility">False</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">5</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_password_check">
+ <property name="label" translatable="yes">Sho_w password</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">6</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/eap-method-ttls.c b/panels/network/wireless-security/eap-method-ttls.c
new file mode 100644
index 0000000..b41a57f
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-ttls.c
@@ -0,0 +1,415 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "eap-method-simple.h"
+#include "eap-method-ttls.h"
+#include "helpers.h"
+#include "ws-file-chooser-button.h"
+
+#define I_NAME_COLUMN 0
+#define I_ID_COLUMN 1
+
+struct _EAPMethodTTLS {
+ GtkGrid parent;
+
+ GtkEntry *anon_identity_entry;
+ GtkLabel *anon_identity_label;
+ WsFileChooserButton *ca_cert_button;
+ GtkLabel *ca_cert_label;
+ GtkCheckButton *ca_cert_not_required_check;
+ GtkEntry *domain_match_entry;
+ GtkLabel *domain_match_label;
+ GtkComboBox *inner_auth_combo;
+ GtkLabel *inner_auth_label;
+ GtkListStore *inner_auth_model;
+ GtkBox *inner_auth_box;
+
+ EAPMethodSimple *em_chap;
+ EAPMethodSimple *em_gtc;
+ EAPMethodSimple *em_md5;
+ EAPMethodSimple *em_mschap;
+ EAPMethodSimple *em_mschap_v2;
+ EAPMethodSimple *em_pap;
+ EAPMethodSimple *em_plain_mschap_v2;
+};
+
+static void eap_method_iface_init (EAPMethodInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (EAPMethodTTLS, eap_method_ttls, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init))
+
+static EAPMethod *
+get_inner_method (EAPMethodTTLS *self)
+{
+ GtkTreeIter iter;
+ g_autofree gchar *id = NULL;
+
+ if (!gtk_combo_box_get_active_iter (self->inner_auth_combo, &iter))
+ return NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1);
+
+ if (strcmp (id, "chap") == 0)
+ return EAP_METHOD (self->em_chap);
+ if (strcmp (id, "gtc") == 0)
+ return EAP_METHOD (self->em_gtc);
+ if (strcmp (id, "md5") == 0)
+ return EAP_METHOD (self->em_md5);
+ if (strcmp (id, "mschap") == 0)
+ return EAP_METHOD (self->em_mschap);
+ if (strcmp (id, "mschapv2") == 0)
+ return EAP_METHOD (self->em_mschap_v2);
+ if (strcmp (id, "pap") == 0)
+ return EAP_METHOD (self->em_pap);
+ if (strcmp (id, "plain_mschapv2") == 0)
+ return EAP_METHOD (self->em_plain_mschap_v2);
+
+ return NULL;
+}
+
+static gboolean
+validate (EAPMethod *method, GError **error)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ g_autoptr(GError) local_error = NULL;
+
+ if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->ca_cert_button),
+ TYPE_CA_CERT, NULL, NULL, &local_error)) {
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TTLS CA certificate: %s"), local_error->message);
+ return FALSE;
+ }
+ if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (self->ca_cert_not_required_check))) {
+ g_autoptr(GFile) file = NULL;
+
+ file = ws_file_chooser_button_get_file (self->ca_cert_button);
+ if (file == NULL) {
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TTLS CA certificate: no certificate specified"));
+ return FALSE;
+ }
+ }
+
+ return eap_method_validate (get_inner_method (self), error);
+}
+
+static void
+ca_cert_not_required_toggled (EAPMethodTTLS *self)
+{
+ eap_method_ca_cert_not_required_toggled (self->ca_cert_not_required_check,
+ ws_file_chooser_button_get_filechooser (self->ca_cert_button));
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+add_to_size_group (EAPMethod *method, GtkSizeGroup *group)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_not_required_check));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->anon_identity_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->domain_match_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->inner_auth_label));
+
+ eap_method_add_to_size_group (EAP_METHOD (self->em_chap), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_gtc), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_md5), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_mschap), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_mschap_v2), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_pap), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_plain_mschap_v2), group);
+}
+
+static void
+fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ NMSetting8021x *s_8021x;
+ NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+ const char *text;
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) file = NULL;
+ gboolean ca_cert_error = FALSE;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ g_assert (s_8021x);
+
+ nm_setting_802_1x_add_eap_method (s_8021x, "ttls");
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->anon_identity_entry));
+ if (text && strlen (text))
+ g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, text, NULL);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->domain_match_entry));
+ if (text && strlen (text))
+ g_object_set (s_8021x, NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH, text, NULL);
+
+ file = ws_file_chooser_button_get_file (self->ca_cert_button);
+ filename = file ? g_file_get_path (file) : NULL;
+ if (!nm_setting_802_1x_set_ca_cert (s_8021x, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error)) {
+ g_warning ("Couldn't read CA certificate '%s': %s", filename, error ? error->message : "(unknown)");
+ ca_cert_error = TRUE;
+ }
+ eap_method_ca_cert_ignore_set (method, connection, filename, ca_cert_error);
+
+ eap_method_fill_connection (get_inner_method (self), connection, flags);
+}
+
+static void
+inner_auth_combo_changed_cb (EAPMethodTTLS *self)
+{
+ EAPMethod *inner_method;
+ GtkWidget *child;
+
+ inner_method = get_inner_method (self);
+
+ /* Remove the previous method and migrate username/password across */
+ child = gtk_widget_get_first_child (GTK_WIDGET (self->inner_auth_box));
+ if (child != NULL) {
+ EAPMethod *old_eap = EAP_METHOD (child);
+ eap_method_set_username (inner_method, eap_method_get_username (old_eap));
+ eap_method_set_password (inner_method, eap_method_get_password (old_eap));
+ eap_method_set_show_password (inner_method, eap_method_get_show_password (old_eap));
+ gtk_box_remove (self->inner_auth_box, child);
+ }
+
+ gtk_box_append (self->inner_auth_box, g_object_ref (GTK_WIDGET (inner_method)));
+
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+update_secrets (EAPMethod *method, NMConnection *connection)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+
+ eap_method_update_secrets (EAP_METHOD (self->em_chap), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_gtc), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_md5), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_mschap), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_mschap_v2), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_pap), connection);
+ eap_method_update_secrets (EAP_METHOD (self->em_plain_mschap_v2), connection);
+}
+
+static GtkWidget *
+get_default_field (EAPMethod *method)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ return GTK_WIDGET (self->anon_identity_entry);
+}
+
+static const gchar *
+get_password_flags_name (EAPMethod *method)
+{
+ return NM_SETTING_802_1X_PASSWORD;
+}
+
+static const gchar *
+get_username (EAPMethod *method)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ return eap_method_get_username (get_inner_method (self));
+}
+
+static void
+set_username (EAPMethod *method, const gchar *username)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ return eap_method_set_username (get_inner_method (self), username);
+}
+
+static const gchar *
+get_password (EAPMethod *method)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ return eap_method_get_password (get_inner_method (self));
+}
+
+static void
+set_password (EAPMethod *method, const gchar *password)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ return eap_method_set_password (get_inner_method (self), password);
+}
+
+static gboolean
+get_show_password (EAPMethod *method)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ return eap_method_get_show_password (get_inner_method (self));
+}
+
+static void
+set_show_password (EAPMethod *method, gboolean show_password)
+{
+ EAPMethodTTLS *self = EAP_METHOD_TTLS (method);
+ return eap_method_set_show_password (get_inner_method (self), show_password);
+}
+
+static void
+changed_cb (EAPMethodTTLS *self)
+{
+ eap_method_emit_changed (EAP_METHOD (self));
+}
+
+static void
+eap_method_ttls_init (EAPMethodTTLS *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+eap_method_ttls_class_init (EAPMethodTTLSClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-ttls.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, anon_identity_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, anon_identity_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, ca_cert_button);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, ca_cert_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, ca_cert_not_required_check);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, domain_match_entry);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, domain_match_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_combo);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_label);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_model);
+ gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_box);
+}
+
+static void
+eap_method_iface_init (EAPMethodInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->update_secrets = update_secrets;
+ iface->get_default_field = get_default_field;
+ iface->get_password_flags_name = get_password_flags_name;
+ iface->get_username = get_username;
+ iface->set_username = set_username;
+ iface->get_password = get_password;
+ iface->set_password = set_password;
+ iface->get_show_password = get_show_password;
+ iface->set_show_password = set_show_password;
+}
+
+EAPMethodTTLS *
+eap_method_ttls_new (NMConnection *connection)
+{
+ EAPMethodTTLS *self;
+ GtkFileFilter *filter;
+ NMSetting8021x *s_8021x = NULL;
+ const char *filename;
+ const char *phase2_auth = NULL;
+ GtkTreeIter iter;
+
+ self = g_object_new (eap_method_ttls_get_type (), NULL);
+
+ if (connection)
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+
+ g_signal_connect_swapped (self->ca_cert_not_required_check, "toggled", G_CALLBACK (ca_cert_not_required_toggled), self);
+
+ g_signal_connect_swapped (self->ca_cert_button, "notify::file", G_CALLBACK (changed_cb), self);
+ filter = eap_method_default_file_chooser_filter_new (FALSE);
+ gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->ca_cert_button),
+ filter);
+ if (connection && s_8021x) {
+ filename = NULL;
+ if (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+ filename = nm_setting_802_1x_get_ca_cert_path (s_8021x);
+ if (filename) {
+ g_autoptr(GFile) file = g_file_new_for_path (filename);
+ ws_file_chooser_button_set_file (self->ca_cert_button, file);
+ }
+ }
+ gtk_check_button_set_active (self->ca_cert_not_required_check,
+ !filename && eap_method_ca_cert_ignore_get (EAP_METHOD (self), connection));
+ }
+
+ if (s_8021x && nm_setting_802_1x_get_anonymous_identity (s_8021x))
+ gtk_editable_set_text (GTK_EDITABLE (self->anon_identity_entry), nm_setting_802_1x_get_anonymous_identity (s_8021x));
+ g_signal_connect_swapped (self->anon_identity_entry, "changed", G_CALLBACK (changed_cb), self);
+ if (s_8021x && nm_setting_802_1x_get_domain_suffix_match (s_8021x))
+ gtk_editable_set_text (GTK_EDITABLE (self->domain_match_entry), nm_setting_802_1x_get_domain_suffix_match (s_8021x));
+ g_signal_connect_swapped (self->domain_match_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ self->em_pap = eap_method_simple_new (connection, "pap", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_pap));
+ g_signal_connect_object (self->em_pap, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_mschap = eap_method_simple_new (connection, "mschap", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_mschap));
+ g_signal_connect_object (self->em_mschap, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, TRUE);
+ gtk_widget_show (GTK_WIDGET (self->em_mschap_v2));
+ g_signal_connect_object (self->em_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_plain_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_plain_mschap_v2));
+ g_signal_connect_object (self->em_plain_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_chap = eap_method_simple_new (connection, "chap", TRUE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_chap));
+ g_signal_connect_object (self->em_chap, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_md5 = eap_method_simple_new (connection, "md5", TRUE, TRUE);
+ gtk_widget_show (GTK_WIDGET (self->em_md5));
+ g_signal_connect_object (self->em_md5, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ self->em_gtc = eap_method_simple_new (connection, "gtc", TRUE, TRUE);
+ gtk_widget_show (GTK_WIDGET (self->em_gtc));
+ g_signal_connect_object (self->em_gtc, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED);
+
+ if (s_8021x) {
+ if (nm_setting_802_1x_get_phase2_auth (s_8021x))
+ phase2_auth = nm_setting_802_1x_get_phase2_auth (s_8021x);
+ else if (nm_setting_802_1x_get_phase2_autheap (s_8021x))
+ phase2_auth = nm_setting_802_1x_get_phase2_autheap (s_8021x);
+ }
+ if (phase2_auth == NULL)
+ phase2_auth = "pap";
+
+ if (strcmp (phase2_auth, "mschapv2") == 0 && nm_setting_802_1x_get_phase2_auth (s_8021x) != NULL)
+ phase2_auth = "plain_mschapv2";
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->inner_auth_model), &iter)) {
+ do {
+ g_autofree gchar *id = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1);
+ if (strcmp (id, phase2_auth) == 0)
+ gtk_combo_box_set_active_iter (self->inner_auth_combo, &iter);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->inner_auth_model), &iter));
+ }
+
+ g_signal_connect_swapped (self->inner_auth_combo, "changed", G_CALLBACK (inner_auth_combo_changed_cb), self);
+ inner_auth_combo_changed_cb (self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/eap-method-ttls.h b/panels/network/wireless-security/eap-method-ttls.h
new file mode 100644
index 0000000..56b7085
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-ttls.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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 2007 - 2010 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (EAPMethodTTLS, eap_method_ttls, EAP, METHOD_TTLS, GtkGrid)
+
+EAPMethodTTLS *eap_method_ttls_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/eap-method-ttls.ui b/panels/network/wireless-security/eap-method-ttls.ui
new file mode 100644
index 0000000..b10dac2
--- /dev/null
+++ b/panels/network/wireless-security/eap-method-ttls.ui
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <object class="GtkListStore" id="inner_auth_model">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">PAP</col>
+ <col id="1">pap</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">MSCHAP</col>
+ <col id="1">mschap</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">MSCHAPv2</col>
+ <col id="1">mschapv2</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">MSCHAPv2 (no EAP)</col>
+ <col id="1">plain_mschapv2</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">CHAP</col>
+ <col id="1">chap</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">MD5</col>
+ <col id="1">md5</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">GTC</col>
+ <col id="1">gtc</col>
+ </row>
+ </data>
+ </object>
+ <template class="EAPMethodTTLS" parent="GtkGrid">
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="anon_identity_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Anony_mous identity</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">anon_identity_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="anon_identity_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="domain_match_label">
+ <property name="label" translatable="yes">_Domain</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">domain_match_entry</property>
+ <property name="xalign">1</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="domain_match_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ca_cert_label">
+ <property name="label" translatable="yes">C_A certificate</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">ca_cert_button</property>
+ <property name="xalign">1</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="WsFileChooserButton" id="ca_cert_button">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="ca_cert_not_required_check">
+ <property name="label" translatable="yes">No CA certificate is _required</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="inner_auth_label">
+ <property name="label" translatable="yes">_Inner authentication</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">inner_auth_combo</property>
+ <property name="xalign">1</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="inner_auth_combo">
+ <property name="hexpand">True</property>
+ <property name="model">inner_auth_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="inner_auth_box">
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">5</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/eap-method.c b/panels/network/wireless-security/eap-method.c
new file mode 100644
index 0000000..a9d11c7
--- /dev/null
+++ b/panels/network/wireless-security/eap-method.c
@@ -0,0 +1,588 @@
+/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */
+
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <fcntl.h>
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "helpers.h"
+#include "ui-helpers.h"
+
+G_DEFINE_INTERFACE (EAPMethod, eap_method, G_TYPE_OBJECT)
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+eap_method_default_init (EAPMethodInterface *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);
+}
+
+GtkWidget *
+eap_method_get_default_field (EAPMethod *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ return EAP_METHOD_GET_IFACE (self)->get_default_field (self);
+}
+
+const gchar *
+eap_method_get_password_flags_name (EAPMethod *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ if (EAP_METHOD_GET_IFACE (self)->get_password_flags_name)
+ return EAP_METHOD_GET_IFACE (self)->get_password_flags_name (self);
+ else
+ return NULL;
+}
+
+gboolean
+eap_method_get_phase2 (EAPMethod *self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+
+ if (EAP_METHOD_GET_IFACE (self)->get_phase2)
+ return EAP_METHOD_GET_IFACE (self)->get_phase2 (self);
+ else
+ return FALSE;
+}
+
+gboolean
+eap_method_validate (EAPMethod *self, GError **error)
+{
+ gboolean result;
+
+ g_return_val_if_fail (self != NULL, FALSE);
+
+ result = (*(EAP_METHOD_GET_IFACE (self)->validate)) (self, error);
+ if (!result && error && !*error)
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("undefined error in 802.1X security (wpa-eap)"));
+ return result;
+}
+
+void
+eap_method_update_secrets (EAPMethod *self, NMConnection *connection)
+{
+ g_return_if_fail (self != NULL);
+
+ if (EAP_METHOD_GET_IFACE (self)->update_secrets)
+ EAP_METHOD_GET_IFACE (self)->update_secrets (self, connection);
+}
+
+void
+eap_method_add_to_size_group (EAPMethod *self, GtkSizeGroup *group)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (group != NULL);
+
+ return (*(EAP_METHOD_GET_IFACE (self)->add_to_size_group)) (self, group);
+}
+
+void
+eap_method_fill_connection (EAPMethod *self,
+ NMConnection *connection,
+ NMSettingSecretFlags flags)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (connection != NULL);
+
+ return (*(EAP_METHOD_GET_IFACE (self)->fill_connection)) (self, connection, flags);
+}
+
+void
+eap_method_emit_changed (EAPMethod *self)
+{
+ g_return_if_fail (EAP_IS_METHOD (self));
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+const gchar *
+eap_method_get_username (EAPMethod *self)
+{
+ g_return_val_if_fail (EAP_IS_METHOD (self), NULL);
+ return EAP_METHOD_GET_IFACE (self)->get_username (self);
+}
+
+void
+eap_method_set_username (EAPMethod *self, const gchar *username)
+{
+ g_return_if_fail (EAP_IS_METHOD (self));
+ EAP_METHOD_GET_IFACE (self)->set_username (self, username);
+}
+
+const gchar *
+eap_method_get_password (EAPMethod *self)
+{
+ g_return_val_if_fail (EAP_IS_METHOD (self), NULL);
+ return EAP_METHOD_GET_IFACE (self)->get_password (self);
+}
+
+void
+eap_method_set_password (EAPMethod *self, const gchar *password)
+{
+ g_return_if_fail (EAP_IS_METHOD (self));
+ EAP_METHOD_GET_IFACE (self)->set_password (self, password);
+}
+
+gboolean
+eap_method_get_show_password (EAPMethod *self)
+{
+ g_return_val_if_fail (EAP_IS_METHOD (self), FALSE);
+ return EAP_METHOD_GET_IFACE (self)->get_show_password (self);
+}
+
+void
+eap_method_set_show_password (EAPMethod *self, gboolean show_password)
+{
+ g_return_if_fail (EAP_IS_METHOD (self));
+ EAP_METHOD_GET_IFACE (self)->set_show_password (self, show_password);
+}
+
+gboolean
+eap_method_validate_filepicker (GtkFileChooser *chooser,
+ guint32 item_type,
+ const char *password,
+ NMSetting8021xCKFormat *out_format,
+ GError **error)
+{
+ g_autofree gchar *filename = NULL;
+ g_autoptr(NMSetting8021x) setting = NULL;
+ g_autoptr(GFile) file = NULL;
+ gboolean success = TRUE;
+
+ if (item_type == TYPE_PRIVATE_KEY) {
+ if (!password || *password == '\0')
+ success = FALSE;
+ }
+
+ file = gtk_file_chooser_get_file (chooser);
+ if (!file) {
+ if (item_type != TYPE_CA_CERT) {
+ success = FALSE;
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("no file selected"));
+ }
+ goto out;
+ }
+
+ filename = g_file_get_path (file);
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
+ success = FALSE;
+ goto out;
+ }
+
+ setting = (NMSetting8021x *) nm_setting_802_1x_new ();
+
+ success = FALSE;
+ if (item_type == TYPE_PRIVATE_KEY) {
+ if (nm_setting_802_1x_set_private_key (setting, filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error))
+ success = TRUE;
+ } else if (item_type == TYPE_CLIENT_CERT) {
+ if (nm_setting_802_1x_set_client_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error))
+ success = TRUE;
+ } else if (item_type == TYPE_CA_CERT) {
+ if (nm_setting_802_1x_set_ca_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error))
+ success = TRUE;
+ } else
+ g_warning ("%s: invalid item type %d.", __func__, item_type);
+
+out:
+ if (!success && error && !*error)
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("unspecified error validating eap-method file"));
+
+ if (success)
+ widget_unset_error (GTK_WIDGET (chooser));
+ else
+ widget_set_error (GTK_WIDGET (chooser));
+ return success;
+}
+
+static gboolean
+file_is_mime_type (const char *path, const char *mime_types[])
+{
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFileInfo) info = NULL;
+ const char *content_type;
+ int i = 0;
+ gboolean found = FALSE;
+
+ file = g_file_new_for_path (path);
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (info) {
+ content_type = g_file_info_get_content_type (info);
+ if (content_type) {
+ while (mime_types[i]) {
+ if (strcmp (content_type, mime_types[i++]) == 0) {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ return found;
+}
+
+#if !LIBNM_BUILD
+static const char *
+find_tag (const char *tag, const char *buf, gsize len)
+{
+ gsize i, taglen;
+
+ taglen = strlen (tag);
+ if (len < taglen)
+ return NULL;
+
+ for (i = 0; i < len - taglen + 1; i++) {
+ if (memcmp (buf + i, tag, taglen) == 0)
+ return buf + i;
+ }
+ return NULL;
+}
+
+static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----";
+static const char *pem_dsa_key_begin = "-----BEGIN DSA PRIVATE KEY-----";
+static const char *pem_pkcs8_enc_key_begin = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
+static const char *pem_pkcs8_dec_key_begin = "-----BEGIN PRIVATE KEY-----";
+static const char *pem_cert_begin = "-----BEGIN CERTIFICATE-----";
+static const char *proc_type_tag = "Proc-Type: 4,ENCRYPTED";
+static const char *dek_info_tag = "DEK-Info:";
+
+static gboolean
+pem_file_is_encrypted (const char *buffer, gsize bytes_read)
+{
+ /* Check if the private key is encrypted or not by looking for the
+ * old OpenSSL-style proc-type and dec-info tags.
+ */
+ if (find_tag (proc_type_tag, (const char *) buffer, bytes_read)) {
+ if (find_tag (dek_info_tag, (const char *) buffer, bytes_read))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+file_is_der_or_pem (const char *filename,
+ gboolean privkey,
+ gboolean *out_privkey_encrypted)
+{
+ int fd;
+ unsigned char buffer[8192];
+ ssize_t bytes_read;
+ gboolean success = FALSE;
+
+ fd = open (filename, O_RDONLY);
+ if (fd < 0)
+ return FALSE;
+
+ bytes_read = read (fd, buffer, sizeof (buffer) - 1);
+ if (bytes_read < 400) /* needs to be lower? */
+ goto out;
+ buffer[bytes_read] = '\0';
+
+ /* Check for DER signature */
+ if (bytes_read > 2 && buffer[0] == 0x30 && buffer[1] == 0x82) {
+ success = TRUE;
+ goto out;
+ }
+
+ /* Check for PEM signatures */
+ if (privkey) {
+ if (find_tag (pem_rsa_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read);
+ goto out;
+ }
+
+ if (find_tag (pem_dsa_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read);
+ goto out;
+ }
+
+ if (find_tag (pem_pkcs8_enc_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = TRUE;
+ goto out;
+ }
+
+ if (find_tag (pem_pkcs8_dec_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = FALSE;
+ goto out;
+ }
+ } else {
+ if (find_tag (pem_cert_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ goto out;
+ }
+ }
+
+out:
+ close (fd);
+ return success;
+}
+#endif
+
+static const char *privkey_mime_types[] = {
+ "application/x-x509-ca-cert", "application/pkcs12", "application/x-pkcs12", "application/pgp-keys", NULL
+};
+static const char *cert_mime_types[] = {
+ "application/x-x509-ca-cert", NULL
+};
+
+static void
+add_mime_types_to_filter (GtkFileFilter *filter,
+ const char **mime_types)
+{
+ int i;
+
+ for (i = 0; mime_types[i] != NULL; i++)
+ gtk_file_filter_add_mime_type (filter, mime_types[i]);
+}
+
+
+GtkFileFilter *
+eap_method_default_file_chooser_filter_new (gboolean privkey)
+{
+ GtkFileFilter *filter;
+
+ filter = gtk_file_filter_new ();
+ if (privkey) {
+ add_mime_types_to_filter (filter, privkey_mime_types);
+ gtk_file_filter_set_name (filter, _("DER, PEM, PKCS#12, or PGP private keys"));
+ } else {
+ add_mime_types_to_filter (filter, cert_mime_types);
+ gtk_file_filter_set_name (filter, _("DER or PEM certificates"));
+ }
+ return filter;
+}
+
+gboolean
+eap_method_is_encrypted_private_key (const char *path)
+{
+ gboolean is_encrypted;
+
+ if (!file_is_mime_type (path, privkey_mime_types))
+ return FALSE;
+
+#if LIBNM_BUILD
+ is_encrypted = FALSE;
+ if (!nm_utils_file_is_private_key (path, &is_encrypted))
+ return FALSE;
+#else
+ is_encrypted = TRUE;
+ if ( !file_is_der_or_pem (path, TRUE, &is_encrypted)
+ && !nm_utils_file_is_pkcs12 (path))
+ return FALSE;
+#endif
+ return is_encrypted;
+}
+
+void
+eap_method_ca_cert_not_required_toggled (GtkCheckButton *id_ca_cert_not_required_checkbutton, GtkFileChooser *id_ca_cert_chooser)
+{
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFile) file_old = NULL;
+ gboolean is_not_required;
+
+ g_assert (id_ca_cert_not_required_checkbutton && id_ca_cert_chooser);
+
+ is_not_required = gtk_check_button_get_active (id_ca_cert_not_required_checkbutton);
+
+ file = gtk_file_chooser_get_file (id_ca_cert_chooser);
+ file_old = g_object_steal_data (G_OBJECT (id_ca_cert_chooser), "filename-old");
+ if (is_not_required) {
+ g_clear_object (&file_old);
+ file_old = g_steal_pointer (&file);
+ } else {
+ g_clear_object (&file);
+ file = g_steal_pointer (&file_old);
+ }
+ gtk_widget_set_sensitive (GTK_WIDGET (id_ca_cert_chooser), !is_not_required);
+ if (file)
+ gtk_file_chooser_set_file (id_ca_cert_chooser, file, NULL);
+ g_object_set_data_full (G_OBJECT (id_ca_cert_chooser), "filename-old", g_steal_pointer (&file_old), g_free);
+}
+
+/* Used as both GSettings keys and GObject data tags */
+#define IGNORE_CA_CERT_TAG "ignore-ca-cert"
+#define IGNORE_PHASE2_CA_CERT_TAG "ignore-phase2-ca-cert"
+
+/**
+ * eap_method_ca_cert_ignore_set:
+ * @method: the #EAPMethod object
+ * @connection: the #NMConnection
+ * @filename: the certificate file, if any
+ * @ca_cert_error: %TRUE if an error was encountered loading the given CA
+ * certificate, %FALSE if not or if a CA certificate is not present
+ *
+ * Updates the connection's CA cert ignore value to %TRUE if the "CA certificate
+ * not required" checkbox is checked. If @ca_cert_error is %TRUE, then the
+ * connection's CA cert ignore value will always be set to %FALSE, because it
+ * means that the user selected an invalid certificate (thus he does not want to
+ * ignore the CA cert)..
+ */
+void
+eap_method_ca_cert_ignore_set (EAPMethod *self,
+ NMConnection *connection,
+ const char *filename,
+ gboolean ca_cert_error)
+{
+ NMSetting8021x *s_8021x;
+ gboolean ignore;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x) {
+ ignore = !ca_cert_error && filename == NULL;
+ g_object_set_data (G_OBJECT (s_8021x),
+ eap_method_get_phase2 (self) ? IGNORE_PHASE2_CA_CERT_TAG : IGNORE_CA_CERT_TAG,
+ GUINT_TO_POINTER (ignore));
+ }
+}
+
+/**
+ * eap_method_ca_cert_ignore_get:
+ * @method: the #EAPMethod object
+ * @connection: the #NMConnection
+ *
+ * Returns: %TRUE if a missing CA certificate can be ignored, %FALSE if a CA
+ * certificate should be required for the connection to be valid.
+ */
+gboolean
+eap_method_ca_cert_ignore_get (EAPMethod *self, NMConnection *connection)
+{
+ NMSetting8021x *s_8021x;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x) {
+ return !!g_object_get_data (G_OBJECT (s_8021x),
+ eap_method_get_phase2 (self) ? IGNORE_PHASE2_CA_CERT_TAG : IGNORE_CA_CERT_TAG);
+ }
+ return FALSE;
+}
+
+static GSettings *
+_get_ca_ignore_settings (NMConnection *connection)
+{
+ GSettings *settings;
+ g_autofree gchar *path = NULL;
+ const char *uuid;
+
+ g_return_val_if_fail (connection, NULL);
+
+ uuid = nm_connection_get_uuid (connection);
+ g_return_val_if_fail (uuid && *uuid, NULL);
+
+ path = g_strdup_printf ("/org/gnome/nm-applet/eap/%s/", uuid);
+ settings = g_settings_new_with_path ("org.gnome.nm-applet.eap", path);
+
+ return settings;
+}
+
+/**
+ * eap_method_ca_cert_ignore_save:
+ * @connection: the connection for which to save CA cert ignore values to GSettings
+ *
+ * Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
+ * then to GSettings if present, using the connection UUID as the index.
+ */
+void
+eap_method_ca_cert_ignore_save (NMConnection *connection)
+{
+ NMSetting8021x *s_8021x;
+ g_autoptr(GSettings) settings = NULL;
+ gboolean ignore = FALSE, phase2_ignore = FALSE;
+
+ g_return_if_fail (connection);
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x) {
+ ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_CA_CERT_TAG);
+ phase2_ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_PHASE2_CA_CERT_TAG);
+ }
+
+ settings = _get_ca_ignore_settings (connection);
+ if (!settings)
+ return;
+
+ g_settings_set_boolean (settings, IGNORE_CA_CERT_TAG, ignore);
+ g_settings_set_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG, phase2_ignore);
+}
+
+/**
+ * eap_method_ca_cert_ignore_load:
+ * @connection: the connection for which to load CA cert ignore values to GSettings
+ *
+ * Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
+ * then to GSettings if present, using the connection UUID as the index.
+ */
+void
+eap_method_ca_cert_ignore_load (NMConnection *connection)
+{
+ g_autoptr(GSettings) settings = NULL;
+ NMSetting8021x *s_8021x;
+ gboolean ignore, phase2_ignore;
+
+ g_return_if_fail (connection);
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (!s_8021x)
+ return;
+
+ settings = _get_ca_ignore_settings (connection);
+ if (!settings)
+ return;
+
+ ignore = g_settings_get_boolean (settings, IGNORE_CA_CERT_TAG);
+ phase2_ignore = g_settings_get_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG);
+
+ g_object_set_data (G_OBJECT (s_8021x),
+ IGNORE_CA_CERT_TAG,
+ GUINT_TO_POINTER (ignore));
+ g_object_set_data (G_OBJECT (s_8021x),
+ IGNORE_PHASE2_CA_CERT_TAG,
+ GUINT_TO_POINTER (phase2_ignore));
+}
+
diff --git a/panels/network/wireless-security/eap-method.h b/panels/network/wireless-security/eap-method.h
new file mode 100644
index 0000000..d5a8908
--- /dev/null
+++ b/panels/network/wireless-security/eap-method.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_INTERFACE (EAPMethod, eap_method, EAP, METHOD, GObject)
+
+struct _EAPMethodInterface {
+ GTypeInterface g_iface;
+
+ void (*add_to_size_group) (EAPMethod *method, GtkSizeGroup *group);
+ void (*fill_connection) (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags);
+ void (*update_secrets) (EAPMethod *method, NMConnection *connection);
+ gboolean (*validate) (EAPMethod *method, GError **error);
+ GtkWidget* (*get_default_field) (EAPMethod *method);
+ const gchar* (*get_password_flags_name) (EAPMethod *method);
+ gboolean (*get_phase2) (EAPMethod *method);
+ const gchar* (*get_username) (EAPMethod *method);
+ void (*set_username) (EAPMethod *method, const gchar *username);
+ const gchar* (*get_password) (EAPMethod *method);
+ void (*set_password) (EAPMethod *method, const gchar *password);
+ gboolean (*get_show_password) (EAPMethod *method);
+ void (*set_show_password) (EAPMethod *method, gboolean show_password);
+};
+
+GtkWidget *eap_method_get_default_field (EAPMethod *method);
+
+const gchar *eap_method_get_password_flags_name (EAPMethod *method);
+
+gboolean eap_method_get_phase2 (EAPMethod *method);
+
+void eap_method_update_secrets (EAPMethod *method, NMConnection *connection);
+
+gboolean eap_method_validate (EAPMethod *method, GError **error);
+
+void eap_method_add_to_size_group (EAPMethod *method, GtkSizeGroup *group);
+
+void eap_method_fill_connection (EAPMethod *method,
+ NMConnection *connection,
+ NMSettingSecretFlags flags);
+
+void eap_method_emit_changed (EAPMethod *method);
+
+const gchar *eap_method_get_username (EAPMethod *method);
+
+void eap_method_set_username (EAPMethod *method, const gchar *username);
+
+const gchar *eap_method_get_password (EAPMethod *method);
+
+void eap_method_set_password (EAPMethod *method, const gchar *password);
+
+gboolean eap_method_get_show_password (EAPMethod *method);
+
+void eap_method_set_show_password (EAPMethod *method, gboolean show_password);
+
+/* Below for internal use only */
+
+GtkFileFilter * eap_method_default_file_chooser_filter_new (gboolean privkey);
+
+gboolean eap_method_is_encrypted_private_key (const char *path);
+
+#define TYPE_CLIENT_CERT 0
+#define TYPE_CA_CERT 1
+#define TYPE_PRIVATE_KEY 2
+
+gboolean eap_method_validate_filepicker (GtkFileChooser *chooser,
+ guint32 item_type,
+ const char *password,
+ NMSetting8021xCKFormat *out_format,
+ GError **error);
+
+void eap_method_ca_cert_not_required_toggled (GtkCheckButton *id_ca_cert_is_not_required_checkbox,
+ GtkFileChooser *id_ca_cert_chooser);
+
+void eap_method_ca_cert_ignore_set (EAPMethod *method,
+ NMConnection *connection,
+ const char *filename,
+ gboolean ca_cert_error);
+gboolean eap_method_ca_cert_ignore_get (EAPMethod *method, NMConnection *connection);
+
+void eap_method_ca_cert_ignore_save (NMConnection *connection);
+void eap_method_ca_cert_ignore_load (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/helpers.c b/panels/network/wireless-security/helpers.c
new file mode 100644
index 0000000..fc8446b
--- /dev/null
+++ b/panels/network/wireless-security/helpers.c
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2009 - 2014 Red Hat, Inc.
+ */
+
+#include "helpers.h"
+
+void
+helper_fill_secret_entry (NMConnection *connection,
+ GtkEntry *entry,
+ GType setting_type,
+ HelperSecretFunc func)
+{
+ NMSetting *setting;
+ const char *tmp;
+
+ g_return_if_fail (connection != NULL);
+ g_return_if_fail (entry != NULL);
+ g_return_if_fail (func != NULL);
+
+ setting = nm_connection_get_setting (connection, setting_type);
+ if (setting) {
+ tmp = (*func) (setting);
+ if (tmp) {
+ gtk_editable_set_text (GTK_EDITABLE (entry), tmp);
+ }
+ }
+}
+
+void
+wireless_security_clear_ciphers (NMConnection *connection)
+{
+ NMSettingWirelessSecurity *s_wireless_sec;
+
+ g_return_if_fail (connection != NULL);
+
+ s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+ g_assert (s_wireless_sec);
+
+ nm_setting_wireless_security_clear_protos (s_wireless_sec);
+ nm_setting_wireless_security_clear_pairwise (s_wireless_sec);
+ nm_setting_wireless_security_clear_groups (s_wireless_sec);
+}
diff --git a/panels/network/wireless-security/helpers.h b/panels/network/wireless-security/helpers.h
new file mode 100644
index 0000000..6a402d8
--- /dev/null
+++ b/panels/network/wireless-security/helpers.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2009 - 2014 Red Hat, Inc.
+ */
+
+#ifndef _HELPERS_H_
+#define _HELPERS_H_
+
+#include <NetworkManager.h>
+#include <gtk/gtk.h>
+
+#define NMA_ERROR (g_quark_from_static_string ("nma-error-quark"))
+
+typedef enum {
+ NMA_ERROR_GENERIC
+} NMAError;
+
+typedef const char * (*HelperSecretFunc)(NMSetting *);
+
+void helper_fill_secret_entry (NMConnection *connection,
+ GtkEntry *entry,
+ GType setting_type,
+ HelperSecretFunc func);
+
+void wireless_security_clear_ciphers (NMConnection *connection);
+
+#endif /* _HELPERS_H_ */
+
diff --git a/panels/network/wireless-security/meson.build b/panels/network/wireless-security/meson.build
new file mode 100644
index 0000000..7f43efc
--- /dev/null
+++ b/panels/network/wireless-security/meson.build
@@ -0,0 +1,73 @@
+name = 'wireless-security'
+
+wireless_security_inc = include_directories('.')
+
+nm_applet_headers = [
+ 'eap-method.h',
+ 'eap-method-fast.h',
+ 'eap-method-leap.h',
+ 'eap-method-peap.h',
+ 'eap-method-simple.h',
+ 'eap-method-tls.h',
+ 'eap-method-ttls.h',
+ 'helpers.h',
+ 'wireless-security.h',
+ 'ws-leap.h',
+ 'ws-dynamic-wep.h',
+ 'ws-file-chooser-button.h',
+ 'ws-sae.h',
+ 'ws-wep-key.h',
+ 'ws-wpa-eap.h',
+ 'ws-wpa-psk.h'
+]
+
+nm_applet_sources = [
+ 'eap-method.c',
+ 'eap-method-fast.c',
+ 'eap-method-leap.c',
+ 'eap-method-peap.c',
+ 'eap-method-simple.c',
+ 'eap-method-tls.c',
+ 'eap-method-ttls.c',
+ 'helpers.c',
+ 'wireless-security.c',
+ 'ws-leap.c',
+ 'ws-dynamic-wep.c',
+ 'ws-file-chooser-button.c',
+ 'ws-sae.c',
+ 'ws-wep-key.c',
+ 'ws-wpa-eap.c',
+ 'ws-wpa-psk.c'
+]
+
+sources = files(nm_applet_sources)
+
+nm_resource_data = [
+ 'eap-method-fast.ui',
+ 'eap-method-leap.ui',
+ 'eap-method-peap.ui',
+ 'eap-method-simple.ui',
+ 'eap-method-tls.ui',
+ 'eap-method-ttls.ui',
+ 'ws-dynamic-wep.ui',
+ 'ws-leap.ui',
+ 'ws-sae.ui',
+ 'ws-wep-key.ui',
+ 'ws-wpa-eap.ui',
+ 'ws-wpa-psk.ui'
+]
+
+sources += gnome.compile_resources(
+ name + '-resources',
+ name + '.gresource.xml',
+ c_name: name.underscorify(),
+ dependencies: files(nm_resource_data),
+ export: true
+)
+
+libwireless_security = static_library(
+ name,
+ sources: sources,
+ include_directories: [top_inc, network_inc],
+ dependencies: deps
+)
diff --git a/panels/network/wireless-security/wireless-security.c b/panels/network/wireless-security/wireless-security.c
new file mode 100644
index 0000000..9f083f6
--- /dev/null
+++ b/panels/network/wireless-security/wireless-security.c
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "helpers.h"
+#include "wireless-security.h"
+#include "wireless-security-resources.h"
+
+G_DEFINE_INTERFACE (WirelessSecurity, wireless_security, G_TYPE_OBJECT)
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+wireless_security_default_init (WirelessSecurityInterface *iface)
+{
+ g_resources_register (wireless_security_get_resource ());
+
+ 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);
+}
+
+void
+wireless_security_notify_changed (WirelessSecurity *self)
+{
+ g_return_if_fail (WIRELESS_IS_SECURITY (self));
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+gboolean
+wireless_security_validate (WirelessSecurity *self, GError **error)
+{
+ gboolean result;
+
+ g_return_val_if_fail (WIRELESS_IS_SECURITY (self), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+
+ result = WIRELESS_SECURITY_GET_IFACE (self)->validate (self, error);
+ if (!result && error && !*error)
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("Unknown error validating 802.1X security"));
+ return result;
+}
+
+void
+wireless_security_add_to_size_group (WirelessSecurity *self, GtkSizeGroup *group)
+{
+ g_return_if_fail (WIRELESS_IS_SECURITY (self));
+ g_return_if_fail (GTK_IS_SIZE_GROUP (group));
+
+ return WIRELESS_SECURITY_GET_IFACE (self)->add_to_size_group (self, group);
+}
+
+void
+wireless_security_fill_connection (WirelessSecurity *self,
+ NMConnection *connection)
+{
+ g_return_if_fail (WIRELESS_IS_SECURITY (self));
+ g_return_if_fail (connection != NULL);
+
+ return WIRELESS_SECURITY_GET_IFACE (self)->fill_connection (self, connection);
+}
+
+gboolean
+wireless_security_adhoc_compatible (WirelessSecurity *self)
+{
+ if (WIRELESS_SECURITY_GET_IFACE (self)->adhoc_compatible)
+ return WIRELESS_SECURITY_GET_IFACE (self)->adhoc_compatible (self);
+ else
+ return TRUE;
+}
diff --git a/panels/network/wireless-security/wireless-security.gresource.xml b/panels/network/wireless-security/wireless-security.gresource.xml
new file mode 100644
index 0000000..c82860c
--- /dev/null
+++ b/panels/network/wireless-security/wireless-security.gresource.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/Settings/network/">
+ <file preprocess="xml-stripblanks">eap-method-leap.ui</file>
+ <file preprocess="xml-stripblanks">eap-method-fast.ui</file>
+ <file preprocess="xml-stripblanks">eap-method-peap.ui</file>
+ <file preprocess="xml-stripblanks">eap-method-simple.ui</file>
+ <file preprocess="xml-stripblanks">eap-method-tls.ui</file>
+ <file preprocess="xml-stripblanks">eap-method-ttls.ui</file>
+ <file preprocess="xml-stripblanks">ws-dynamic-wep.ui</file>
+ <file preprocess="xml-stripblanks">ws-leap.ui</file>
+ <file preprocess="xml-stripblanks">ws-sae.ui</file>
+ <file preprocess="xml-stripblanks">ws-wep-key.ui</file>
+ <file preprocess="xml-stripblanks">ws-wpa-eap.ui</file>
+ <file preprocess="xml-stripblanks">ws-wpa-psk.ui</file>
+ </gresource>
+</gresources>
diff --git a/panels/network/wireless-security/wireless-security.h b/panels/network/wireless-security/wireless-security.h
new file mode 100644
index 0000000..7f1cbed
--- /dev/null
+++ b/panels/network/wireless-security/wireless-security.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_INTERFACE (WirelessSecurity, wireless_security, WIRELESS, SECURITY, GObject)
+
+struct _WirelessSecurityInterface {
+ GTypeInterface g_iface;
+
+ void (*add_to_size_group) (WirelessSecurity *sec, GtkSizeGroup *group);
+ void (*fill_connection) (WirelessSecurity *sec, NMConnection *connection);
+ gboolean (*validate) (WirelessSecurity *sec, GError **error);
+ gboolean (*adhoc_compatible) (WirelessSecurity *sec);
+};
+
+gboolean wireless_security_validate (WirelessSecurity *sec, GError **error);
+
+void wireless_security_add_to_size_group (WirelessSecurity *sec,
+ GtkSizeGroup *group);
+
+void wireless_security_fill_connection (WirelessSecurity *sec,
+ NMConnection *connection);
+
+gboolean wireless_security_adhoc_compatible (WirelessSecurity *sec);
+
+void wireless_security_notify_changed (WirelessSecurity *sec);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-dynamic-wep.c b/panels/network/wireless-security/ws-dynamic-wep.c
new file mode 100644
index 0000000..87a5bb9
--- /dev/null
+++ b/panels/network/wireless-security/ws-dynamic-wep.c
@@ -0,0 +1,262 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "eap-method.h"
+#include "eap-method-fast.h"
+#include "eap-method-leap.h"
+#include "eap-method-peap.h"
+#include "eap-method-simple.h"
+#include "eap-method-tls.h"
+#include "eap-method-ttls.h"
+#include "wireless-security.h"
+#include "ws-dynamic-wep.h"
+
+struct _WirelessSecurityDynamicWEP {
+ GtkGrid parent;
+
+ GtkComboBox *auth_combo;
+ GtkLabel *auth_label;
+ GtkListStore *auth_model;
+ GtkBox *method_box;
+
+ EAPMethodTLS *em_tls;
+ EAPMethodLEAP *em_leap;
+ EAPMethodSimple *em_pwd;
+ EAPMethodFAST *em_fast;
+ EAPMethodTTLS *em_ttls;
+ EAPMethodPEAP *em_peap;
+};
+
+static void wireless_security_iface_init (WirelessSecurityInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (WirelessSecurityDynamicWEP, ws_dynamic_wep, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init));
+
+#define AUTH_NAME_COLUMN 0
+#define AUTH_ID_COLUMN 1
+
+static EAPMethod *
+get_eap (WirelessSecurityDynamicWEP *self)
+{
+ GtkTreeIter iter;
+ g_autofree gchar *id = NULL;
+
+ if (!gtk_combo_box_get_active_iter (self->auth_combo, &iter))
+ return NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1);
+
+ if (strcmp (id, "tls") == 0)
+ return EAP_METHOD (self->em_tls);
+ if (strcmp (id, "leap") == 0)
+ return EAP_METHOD (self->em_leap);
+ if (strcmp (id, "pwd") == 0)
+ return EAP_METHOD (self->em_pwd);
+ if (strcmp (id, "fast") == 0)
+ return EAP_METHOD (self->em_fast);
+ if (strcmp (id, "ttls") == 0)
+ return EAP_METHOD (self->em_ttls);
+ if (strcmp (id, "peap") == 0)
+ return EAP_METHOD (self->em_peap);
+
+ return NULL;
+}
+
+static gboolean
+validate (WirelessSecurity *security, GError **error)
+{
+ WirelessSecurityDynamicWEP *self = WS_DYNAMIC_WEP (security);
+ return eap_method_validate (get_eap (self), error);
+}
+
+static void
+add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group)
+{
+ WirelessSecurityDynamicWEP *self = WS_DYNAMIC_WEP (security);
+
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->auth_label));
+ eap_method_add_to_size_group (EAP_METHOD (self->em_tls), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_leap), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_pwd), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_fast), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_ttls), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_peap), group);
+}
+
+static void
+fill_connection (WirelessSecurity *security, NMConnection *connection)
+{
+ WirelessSecurityDynamicWEP *self = WS_DYNAMIC_WEP (security);
+ NMSettingWirelessSecurity *s_wireless_sec;
+ NMSetting8021x *s_8021x;
+ NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+ EAPMethod *eap;
+
+ /* Get the EAPMethod object */
+ eap = get_eap (self);
+
+ /* Get previous pasword flags, if any. Otherwise default to agent-owned secrets */
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x)
+ nm_setting_get_secret_flags (NM_SETTING (s_8021x), eap_method_get_password_flags_name (eap), &secret_flags, NULL);
+ else
+ secret_flags = NM_SETTING_SECRET_FLAG_AGENT_OWNED;
+
+ /* Blow away the old wireless security setting by adding a clear one */
+ s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec);
+
+ /* Blow away the old 802.1x setting by adding a clear one */
+ s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_8021x);
+
+ eap_method_fill_connection (eap, connection, secret_flags);
+
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", NULL);
+}
+
+static gboolean
+adhoc_compatible (WirelessSecurity *security)
+{
+ return FALSE;
+}
+
+static void
+auth_combo_changed_cb (WirelessSecurityDynamicWEP *self)
+{
+ EAPMethod *eap;
+ GtkWidget *eap_default_field;
+ GtkWidget *child;
+
+ eap = get_eap (self);
+
+ /* Remove the previous method and migrate username/password across */
+ child = gtk_widget_get_first_child (GTK_WIDGET (self->method_box));
+ if (child != NULL) {
+ EAPMethod *old_eap = EAP_METHOD (child);
+ eap_method_set_username (eap, eap_method_get_username (old_eap));
+ eap_method_set_password (eap, eap_method_get_password (old_eap));
+ eap_method_set_show_password (eap, eap_method_get_show_password (old_eap));
+ gtk_box_remove (self->method_box, child);
+ }
+
+ gtk_box_append (self->method_box, g_object_ref (GTK_WIDGET (eap)));
+ eap_default_field = eap_method_get_default_field (eap);
+ if (eap_default_field)
+ gtk_widget_grab_focus (eap_default_field);
+
+ wireless_security_notify_changed (WIRELESS_SECURITY (self));
+}
+
+void
+ws_dynamic_wep_init (WirelessSecurityDynamicWEP *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ws_dynamic_wep_class_init (WirelessSecurityDynamicWEPClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-dynamic-wep.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, auth_combo);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, auth_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, auth_model);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, method_box);
+}
+
+static void
+wireless_security_iface_init (WirelessSecurityInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->adhoc_compatible = adhoc_compatible;
+}
+
+WirelessSecurityDynamicWEP *
+ws_dynamic_wep_new (NMConnection *connection)
+{
+ WirelessSecurityDynamicWEP *self;
+ const gchar *default_method = NULL;
+ GtkTreeIter iter;
+
+ self = g_object_new (ws_dynamic_wep_get_type (), NULL);
+
+ /* Grab the default EAP method out of the security object */
+ if (connection) {
+ NMSetting8021x *s_8021x;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x && nm_setting_802_1x_get_num_eap_methods (s_8021x))
+ default_method = nm_setting_802_1x_get_eap_method (s_8021x, 0);
+ }
+ if (default_method == NULL)
+ default_method = "tls";
+
+ self->em_tls = eap_method_tls_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_tls));
+ g_signal_connect_object (self->em_tls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_leap = eap_method_leap_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_leap));
+ g_signal_connect_object (self->em_leap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_pwd = eap_method_simple_new (connection, "pwd", FALSE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_pwd));
+ g_signal_connect_object (self->em_pwd, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_fast = eap_method_fast_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_fast));
+ g_signal_connect_object (self->em_fast, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_ttls = eap_method_ttls_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_ttls));
+ g_signal_connect_object (self->em_ttls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_peap = eap_method_peap_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_peap));
+ g_signal_connect_object (self->em_peap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->auth_model), &iter)) {
+ do {
+ g_autofree gchar *id = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1);
+ if (strcmp (id, default_method) == 0)
+ gtk_combo_box_set_active_iter (self->auth_combo, &iter);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->auth_model), &iter));
+ }
+
+ if (connection) {
+ NMSetting8021x *setting;
+
+ setting = nm_connection_get_setting_802_1x (connection);
+ if (setting) {
+ eap_method_set_username (get_eap (self), nm_setting_802_1x_get_identity (setting));
+ eap_method_set_password (get_eap (self), nm_setting_802_1x_get_password (setting));
+ }
+ }
+
+ g_signal_connect_object (G_OBJECT (self->auth_combo), "changed", G_CALLBACK (auth_combo_changed_cb), self, G_CONNECT_SWAPPED);
+ auth_combo_changed_cb (self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/ws-dynamic-wep.h b/panels/network/wireless-security/ws-dynamic-wep.h
new file mode 100644
index 0000000..80c914b
--- /dev/null
+++ b/panels/network/wireless-security/ws-dynamic-wep.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (WirelessSecurityDynamicWEP, ws_dynamic_wep, WS, DYNAMIC_WEP, GtkGrid)
+
+WirelessSecurityDynamicWEP *ws_dynamic_wep_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-dynamic-wep.ui b/panels/network/wireless-security/ws-dynamic-wep.ui
new file mode 100644
index 0000000..85d6389
--- /dev/null
+++ b/panels/network/wireless-security/ws-dynamic-wep.ui
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <object class="GtkListStore" id="auth_model">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ <!-- column-name visible -->
+ <column type="gboolean"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">TLS</col>
+ <col id="1">tls</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">LEAP</col>
+ <col id="1">leap</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">PWD</col>
+ <col id="1">pwd</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">FAST</col>
+ <col id="1">fast</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Tunneled TLS</col>
+ <col id="1">ttls</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Protected EAP (PEAP)</col>
+ <col id="1">peap</col>
+ <col id="2">True</col>
+ </row>
+ </data>
+ </object>
+ <template class="WirelessSecurityDynamicWEP" parent="GtkGrid">
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="auth_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Au_thentication</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">auth_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="auth_combo">
+ <property name="hexpand">True</property>
+ <property name="model">auth_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="method_box">
+ <property name="spacing">6</property>
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="column-span">2</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/ws-file-chooser-button.c b/panels/network/wireless-security/ws-file-chooser-button.c
new file mode 100644
index 0000000..998df42
--- /dev/null
+++ b/panels/network/wireless-security/ws-file-chooser-button.c
@@ -0,0 +1,268 @@
+/* cc-file-chooser-button.c
+ *
+ * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "ws-file-chooser-button.h"
+
+#include <glib/gi18n.h>
+
+struct _WsFileChooserButton
+{
+ GtkButton parent_instance;
+
+ GtkFileChooser *filechooser;
+ GFile *file;
+ char *title;
+};
+
+G_DEFINE_FINAL_TYPE (WsFileChooserButton, ws_file_chooser_button, GTK_TYPE_BUTTON)
+
+enum
+{
+ PROP_0,
+ PROP_FILE,
+ PROP_TITLE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS] = { NULL, };
+
+static const char *
+get_title (WsFileChooserButton *self)
+{
+ return self->title ? self->title : _("Select a file");
+}
+
+static void
+update_label (WsFileChooserButton *self)
+{
+ g_autofree gchar *label = NULL;
+
+ if (self->file)
+ label = g_file_get_basename (self->file);
+ else
+ label = g_strdup (get_title (self));
+
+ gtk_button_set_label (GTK_BUTTON (self), label);
+}
+
+static void
+on_filechooser_dialog_response_cb (GtkFileChooser *filechooser,
+ gint response,
+ WsFileChooserButton *self)
+{
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ g_autoptr(GFile) file = NULL;
+
+ file = gtk_file_chooser_get_file (filechooser);
+ ws_file_chooser_button_set_file (self, file);
+ }
+
+ gtk_widget_hide (GTK_WIDGET (filechooser));
+}
+static void
+ensure_filechooser (WsFileChooserButton *self)
+{
+ GtkNative *native;
+ GtkWidget *dialog;
+
+ if (self->filechooser)
+ return;
+
+ native = gtk_widget_get_native (GTK_WIDGET (self));
+
+ dialog = gtk_file_chooser_dialog_new (get_title (self),
+ GTK_WINDOW (native),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("Cancel"),
+ GTK_RESPONSE_CANCEL,
+ _("Open"),
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_hide_on_close (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ if (self->file)
+ gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), self->file, NULL);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (on_filechooser_dialog_response_cb), self);
+
+ self->filechooser = GTK_FILE_CHOOSER (dialog);
+}
+
+
+static void
+ws_file_chooser_button_clicked (GtkButton *button)
+{
+ WsFileChooserButton *self = WS_FILE_CHOOSER_BUTTON (button);
+ GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self));
+
+ ensure_filechooser (self);
+
+ gtk_window_set_transient_for (GTK_WINDOW (self->filechooser), GTK_WINDOW (native));
+ gtk_window_present (GTK_WINDOW (self->filechooser));
+}
+
+static void
+ws_file_chooser_button_finalize (GObject *object)
+{
+ WsFileChooserButton *self = (WsFileChooserButton *)object;
+
+ g_clear_pointer (&self->title, g_free);
+ g_clear_object (&self->file);
+
+ G_OBJECT_CLASS (ws_file_chooser_button_parent_class)->finalize (object);
+}
+
+static void
+ws_file_chooser_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WsFileChooserButton *self = WS_FILE_CHOOSER_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, self->title);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ws_file_chooser_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WsFileChooserButton *self = WS_FILE_CHOOSER_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ ws_file_chooser_button_set_file (self, g_value_get_object (value));
+ break;
+
+ case PROP_TITLE:
+ ws_file_chooser_button_set_title (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ws_file_chooser_button_class_init (WsFileChooserButtonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
+
+ button_class->clicked = ws_file_chooser_button_clicked;
+
+ object_class->finalize = ws_file_chooser_button_finalize;
+ object_class->get_property = ws_file_chooser_button_get_property;
+ object_class->set_property = ws_file_chooser_button_set_property;
+
+ properties[PROP_FILE] = g_param_spec_object ("file", "", "",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+ properties[PROP_TITLE] = g_param_spec_string ("title", "", "", NULL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ws_file_chooser_button_init (WsFileChooserButton *self)
+{
+ update_label (self);
+}
+
+GtkWidget *
+ws_file_chooser_button_new (void)
+{
+ return g_object_new (WS_TYPE_FILE_CHOOSER_BUTTON, NULL);
+}
+
+void
+ws_file_chooser_button_set_file (WsFileChooserButton *self,
+ GFile *file)
+{
+ g_return_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self));
+
+ if (g_set_object (&self->file, file))
+ {
+ gtk_file_chooser_set_file (self->filechooser, file, NULL);
+ update_label (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
+ }
+}
+
+GFile *
+ws_file_chooser_button_get_file (WsFileChooserButton *self)
+{
+ g_return_val_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self), NULL);
+
+ return self->file ? g_object_ref (self->file) : NULL;
+}
+
+void
+ws_file_chooser_button_set_title (WsFileChooserButton *self,
+ const char *title)
+{
+ g_autofree char *old_title = NULL;
+
+ g_return_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self));
+
+ old_title = g_steal_pointer (&self->title);
+ self->title = g_strdup (title);
+
+ update_label (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]);
+}
+
+const char *
+ws_file_chooser_button_get_title (WsFileChooserButton *self)
+{
+ g_return_val_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self), NULL);
+
+ return self->title;
+}
+
+GtkFileChooser *
+ws_file_chooser_button_get_filechooser (WsFileChooserButton *self)
+{
+ g_return_val_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self), NULL);
+
+ ensure_filechooser (self);
+
+ return self->filechooser;
+}
diff --git a/panels/network/wireless-security/ws-file-chooser-button.h b/panels/network/wireless-security/ws-file-chooser-button.h
new file mode 100644
index 0000000..db02020
--- /dev/null
+++ b/panels/network/wireless-security/ws-file-chooser-button.h
@@ -0,0 +1,44 @@
+/* ws-file-chooser-button.h
+ *
+ * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define WS_TYPE_FILE_CHOOSER_BUTTON (ws_file_chooser_button_get_type())
+G_DECLARE_FINAL_TYPE (WsFileChooserButton, ws_file_chooser_button, WS, FILE_CHOOSER_BUTTON, GtkButton)
+
+GtkWidget *ws_file_chooser_button_new (void);
+
+void ws_file_chooser_button_set_file (WsFileChooserButton *self,
+ GFile *file);
+
+GFile *ws_file_chooser_button_get_file (WsFileChooserButton *self);
+
+void ws_file_chooser_button_set_title (WsFileChooserButton *self,
+ const char *title);
+
+const char *ws_file_chooser_button_get_title (WsFileChooserButton *self);
+
+GtkFileChooser *ws_file_chooser_button_get_filechooser (WsFileChooserButton *self);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-leap.c b/panels/network/wireless-security/ws-leap.c
new file mode 100644
index 0000000..a234015
--- /dev/null
+++ b/panels/network/wireless-security/ws-leap.c
@@ -0,0 +1,211 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "helpers.h"
+#include "nma-ui-utils.h"
+#include "ui-helpers.h"
+#include "wireless-security.h"
+#include "ws-leap.h"
+
+struct _WirelessSecurityLEAP {
+ GtkGrid parent;
+
+ GtkEntry *password_entry;
+ GtkLabel *password_label;
+ GtkCheckButton *show_password_check;
+ GtkEntry *username_entry;
+ GtkLabel *username_label;
+};
+
+static void wireless_security_iface_init (WirelessSecurityInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (WirelessSecurityLEAP, ws_leap, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init));
+
+static void
+show_toggled_cb (WirelessSecurityLEAP *self)
+{
+ gboolean visible;
+
+ visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+ gtk_entry_set_visibility (self->password_entry, visible);
+}
+
+static gboolean
+validate (WirelessSecurity *security, GError **error)
+{
+ WirelessSecurityLEAP *self = WS_LEAP (security);
+ NMSettingSecretFlags secret_flags;
+ const char *text;
+ gboolean ret = TRUE;
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->username_entry));
+ if (!text || !strlen (text)) {
+ widget_set_error (GTK_WIDGET (self->username_entry));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing leap-username"));
+ ret = FALSE;
+ } else
+ widget_unset_error (GTK_WIDGET (self->username_entry));
+
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) {
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+ return TRUE;
+ }
+
+ text = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+ if (!text || !strlen (text)) {
+ widget_set_error (GTK_WIDGET (self->password_entry));
+ if (ret) {
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing leap-password"));
+ ret = FALSE;
+ }
+ } else
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+
+ return ret;
+}
+
+static void
+add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group)
+{
+ WirelessSecurityLEAP *self = WS_LEAP (security);
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->username_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label));
+}
+
+static void
+fill_connection (WirelessSecurity *security, NMConnection *connection)
+{
+ WirelessSecurityLEAP *self = WS_LEAP (security);
+ NMSettingWirelessSecurity *s_wireless_sec;
+ NMSettingSecretFlags secret_flags;
+ const char *leap_password = NULL, *leap_username = NULL;
+
+ /* Blow away the old security setting by adding a clear one */
+ s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec);
+
+ leap_username = gtk_editable_get_text (GTK_EDITABLE (self->username_entry));
+ leap_password = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+
+ g_object_set (s_wireless_sec,
+ NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x",
+ NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap",
+ NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username,
+ NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, leap_password,
+ NULL);
+
+ /* Save LEAP_PASSWORD_FLAGS to the connection */
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ nm_setting_set_secret_flags (NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
+ secret_flags, NULL);
+
+ /* Update secret flags and popup when editing the connection */
+ nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags,
+ NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD);
+}
+
+static gboolean
+adhoc_compatible (WirelessSecurity *security)
+{
+ return FALSE;
+}
+
+static void
+changed_cb (WirelessSecurityLEAP *self)
+{
+ wireless_security_notify_changed ((WirelessSecurity *) self);
+}
+
+void
+ws_leap_init (WirelessSecurityLEAP *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ws_leap_class_init (WirelessSecurityLEAPClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-leap.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, password_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, show_password_check);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, username_entry);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, username_label);
+}
+
+static void
+wireless_security_iface_init (WirelessSecurityInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->adhoc_compatible = adhoc_compatible;
+}
+
+WirelessSecurityLEAP *
+ws_leap_new (NMConnection *connection)
+{
+ WirelessSecurityLEAP *self;
+ NMSettingWirelessSecurity *wsec = NULL;
+
+ self = g_object_new (ws_leap_get_type (), NULL);
+
+ if (connection) {
+ wsec = nm_connection_get_setting_wireless_security (connection);
+ if (wsec) {
+ const char *auth_alg;
+
+ /* Ignore if wireless security doesn't specify LEAP */
+ auth_alg = nm_setting_wireless_security_get_auth_alg (wsec);
+ if (!auth_alg || strcmp (auth_alg, "leap"))
+ wsec = NULL;
+ }
+ }
+
+ g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self);
+
+ /* Create password-storage popup menu for password entry under entry's secondary icon */
+ nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, (NMSetting *) wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
+ FALSE, FALSE);
+
+ if (wsec)
+ helper_fill_secret_entry (connection,
+ self->password_entry,
+ NM_TYPE_SETTING_WIRELESS_SECURITY,
+ (HelperSecretFunc) nm_setting_wireless_security_get_leap_password);
+
+ g_signal_connect_swapped (self->username_entry, "changed", G_CALLBACK (changed_cb), self);
+ if (wsec)
+ gtk_editable_set_text (GTK_EDITABLE (self->username_entry), nm_setting_wireless_security_get_leap_username (wsec));
+
+ g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self);
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/ws-leap.h b/panels/network/wireless-security/ws-leap.h
new file mode 100644
index 0000000..ebe90bb
--- /dev/null
+++ b/panels/network/wireless-security/ws-leap.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (WirelessSecurityLEAP, ws_leap, WS, LEAP, GtkGrid)
+
+WirelessSecurityLEAP *ws_leap_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-leap.ui b/panels/network/wireless-security/ws-leap.ui
new file mode 100644
index 0000000..41e21d6
--- /dev/null
+++ b/panels/network/wireless-security/ws-leap.ui
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <template class="WirelessSecurityLEAP" parent="GtkGrid">
+ <property name="valign">start</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="username_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Username</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">username_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">password_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="hexpand">True</property>
+ <property name="visibility">False</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_password_check">
+ <property name="label" translatable="yes">Sho_w password</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="username_entry">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/ws-sae.c b/panels/network/wireless-security/ws-sae.c
new file mode 100644
index 0000000..c9e4e50
--- /dev/null
+++ b/panels/network/wireless-security/ws-sae.c
@@ -0,0 +1,225 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Jonathan Kang <songchuan.kang@suse.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.
+ *
+ * Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany.
+ */
+
+#include <ctype.h>
+#include <glib/gi18n.h>
+
+#include "helpers.h"
+#include "nma-ui-utils.h"
+#include "ui-helpers.h"
+#include "ws-sae.h"
+#include "wireless-security.h"
+
+#define WPA_PMK_LEN 32
+
+struct _WirelessSecuritySAE
+{
+ GtkGrid parent;
+
+ GtkEntry *password_entry;
+ GtkLabel *password_label;
+ GtkCheckButton *show_password_check;
+ GtkComboBox *type_combo;
+ GtkLabel *type_label;
+};
+
+static void wireless_security_iface_init (WirelessSecurityInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (WirelessSecuritySAE, ws_sae, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init));
+
+static void
+show_toggled_cb (WirelessSecuritySAE *self)
+{
+ gboolean visible;
+
+ visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+ gtk_entry_set_visibility (self->password_entry, visible);
+}
+
+static gboolean
+validate (WirelessSecurity *security, GError **error)
+{
+ WirelessSecuritySAE *self = WS_SAE (security);
+ NMSettingSecretFlags secret_flags;
+ const char *key;
+
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ {
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+ return TRUE;
+ }
+
+ key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+
+ if (key == NULL || key[0] == '\0')
+ {
+ widget_set_error (GTK_WIDGET (self->password_entry));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("Wi-Fi password is missing."));
+ return FALSE;
+ }
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+
+ return TRUE;
+}
+
+static void
+add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group)
+{
+ WirelessSecuritySAE *self = WS_SAE (security);
+
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->type_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label));
+}
+
+static void
+fill_connection (WirelessSecurity *security, NMConnection *connection)
+{
+ WirelessSecuritySAE *self = WS_SAE (security);
+ const char *key;
+ NMSettingWireless *s_wireless;
+ NMSettingWirelessSecurity *s_wireless_sec;
+ NMSettingSecretFlags secret_flags;
+ const char *mode;
+ gboolean is_adhoc = FALSE;
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ g_assert (s_wireless);
+
+ mode = nm_setting_wireless_get_mode (s_wireless);
+ if (mode && !g_strcmp0 (mode, "adhoc"))
+ is_adhoc = TRUE;
+
+ /* Blow away the old security setting by adding a clear one */
+ s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec);
+
+ key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_PSK, key, NULL);
+
+ /* Save PSK_FLAGS to the connection */
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ nm_setting_set_secret_flags (NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK,
+ secret_flags, NULL);
+
+ /* Update secret flags and popup when editing the connection */
+ nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags,
+ NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK);
+
+ wireless_security_clear_ciphers (connection);
+ if (is_adhoc)
+ {
+ /* Ad-Hoc settings as specified by the supplicant */
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
+ nm_setting_wireless_security_add_proto (s_wireless_sec, "rsn");
+ nm_setting_wireless_security_add_pairwise (s_wireless_sec, "ccmp");
+ nm_setting_wireless_security_add_group (s_wireless_sec, "ccmp");
+ }
+ else
+ {
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
+
+ /* Just leave ciphers and protocol empty, the supplicant will
+ * figure that out magically based on the AP IEs and card capabilities.
+ */
+ }
+}
+
+static gboolean
+adhoc_compatible (WirelessSecurity *security)
+{
+ return FALSE;
+}
+
+static void
+changed_cb (WirelessSecuritySAE *self)
+{
+ wireless_security_notify_changed ((WirelessSecurity *) self);
+}
+
+void
+ws_sae_init (WirelessSecuritySAE *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ws_sae_class_init (WirelessSecuritySAEClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-sae.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, password_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, show_password_check);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, type_combo);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, type_label);
+}
+
+static void
+wireless_security_iface_init (WirelessSecurityInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->adhoc_compatible = adhoc_compatible;
+}
+
+WirelessSecuritySAE *
+ws_sae_new (NMConnection *connection)
+{
+ WirelessSecuritySAE *self;
+ NMSetting *setting = NULL;
+
+ self = g_object_new (ws_sae_get_type (), NULL);
+
+ g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self);
+ gtk_editable_set_width_chars (GTK_EDITABLE (self->password_entry), 28);
+
+ /* Create password-storage popup menu for password entry under entry's secondary icon */
+ if (connection)
+ setting = (NMSetting *) nm_connection_get_setting_wireless_security (connection);
+ nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry),
+ 0, setting, NM_SETTING_WIRELESS_SECURITY_PSK,
+ FALSE, FALSE);
+
+ /* Fill secrets, if any */
+ if (connection)
+ {
+ helper_fill_secret_entry (connection,
+ self->password_entry,
+ NM_TYPE_SETTING_WIRELESS_SECURITY,
+ (HelperSecretFunc) nm_setting_wireless_security_get_psk);
+ }
+
+ g_signal_connect_swapped (self->show_password_check, "toggled",
+ G_CALLBACK (show_toggled_cb), self);
+
+ /* Hide WPA/RSN for now since this can be autodetected by NM and the
+ * supplicant when connecting to the AP.
+ */
+ gtk_widget_hide (GTK_WIDGET (self->type_combo));
+ gtk_widget_hide (GTK_WIDGET (self->type_label));
+
+ return self;
+}
diff --git a/panels/network/wireless-security/ws-sae.h b/panels/network/wireless-security/ws-sae.h
new file mode 100644
index 0000000..770f658
--- /dev/null
+++ b/panels/network/wireless-security/ws-sae.h
@@ -0,0 +1,33 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Jonathan Kang <songchuan.kang@suse.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.
+ *
+ * Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (WirelessSecuritySAE, ws_sae, WS, SAE, GtkGrid)
+
+WirelessSecuritySAE* ws_sae_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-sae.ui b/panels/network/wireless-security/ws-sae.ui
new file mode 100644
index 0000000..028cd9f
--- /dev/null
+++ b/panels/network/wireless-security/ws-sae.ui
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface domain="nm-applet">
+ <requires lib="gtk+" version="3.10"/>
+ <template class="WirelessSecuritySAE" parent="GtkGrid">
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="label" translatable="yes">_Password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">password_entry</property>
+ <property name="xalign">1</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="hexpand">True</property>
+ <property name="max_length">64</property>
+ <property name="visibility">False</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="type_label">
+ <property name="label" translatable="yes">_Type</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">type_combo</property>
+ <property name="xalign">1</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_password_check">
+ <property name="label" translatable="yes">Sho_w password</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="type_combo">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/ws-wep-key.c b/panels/network/wireless-security/ws-wep-key.c
new file mode 100644
index 0000000..8b354d5
--- /dev/null
+++ b/panels/network/wireless-security/ws-wep-key.c
@@ -0,0 +1,369 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "helpers.h"
+#include "nma-ui-utils.h"
+#include "ui-helpers.h"
+#include "ws-wep-key.h"
+#include "wireless-security.h"
+
+struct _WirelessSecurityWEPKey {
+ GtkGrid parent;
+
+ GtkComboBox *auth_method_combo;
+ GtkLabel *auth_method_label;
+ GtkEntry *key_entry;
+ GtkComboBox *key_index_combo;
+ GtkLabel *key_index_label;
+ GtkLabel *key_label;
+ GtkCheckButton *show_key_check;
+
+ NMWepKeyType type;
+ char keys[4][65];
+ guint8 cur_index;
+};
+
+static void wireless_security_iface_init (WirelessSecurityInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (WirelessSecurityWEPKey, ws_wep_key, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init));
+
+static void
+show_toggled_cb (WirelessSecurityWEPKey *self)
+{
+ gboolean visible;
+
+ visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_key_check));
+ gtk_entry_set_visibility (self->key_entry, visible);
+}
+
+static void
+key_index_combo_changed_cb (WirelessSecurityWEPKey *self)
+{
+ const char *key;
+ int key_index;
+
+ /* Save WEP key for old key index */
+ key = gtk_editable_get_text (GTK_EDITABLE (self->key_entry));
+ if (key)
+ g_strlcpy (self->keys[self->cur_index], key, sizeof (self->keys[self->cur_index]));
+ else
+ memset (self->keys[self->cur_index], 0, sizeof (self->keys[self->cur_index]));
+
+ key_index = gtk_combo_box_get_active (self->key_index_combo);
+ g_return_if_fail (key_index <= 3);
+ g_return_if_fail (key_index >= 0);
+
+ /* Populate entry with key from new index */
+ gtk_editable_set_text (GTK_EDITABLE (self->key_entry), self->keys[key_index]);
+ self->cur_index = key_index;
+
+ wireless_security_notify_changed ((WirelessSecurity *) self);
+}
+
+static void
+ws_wep_key_dispose (GObject *object)
+{
+ WirelessSecurityWEPKey *self = WS_WEP_KEY (object);
+ int i;
+
+ for (i = 0; i < 4; i++)
+ memset (self->keys[i], 0, sizeof (self->keys[i]));
+
+ G_OBJECT_CLASS (ws_wep_key_parent_class)->dispose (object);
+}
+
+static gboolean
+validate (WirelessSecurity *security, GError **error)
+{
+ WirelessSecurityWEPKey *self = WS_WEP_KEY (security);
+ NMSettingSecretFlags secret_flags;
+ const char *key;
+ int i;
+
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->key_entry));
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) {
+ widget_unset_error (GTK_WIDGET (self->key_entry));
+ return TRUE;
+ }
+
+ key = gtk_editable_get_text (GTK_EDITABLE (self->key_entry));
+ if (!key) {
+ widget_set_error (GTK_WIDGET (self->key_entry));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing wep-key"));
+ return FALSE;
+ }
+
+ if (self->type == NM_WEP_KEY_TYPE_KEY) {
+ if ((strlen (key) == 10) || (strlen (key) == 26)) {
+ for (i = 0; i < strlen (key); i++) {
+ if (!g_ascii_isxdigit (key[i])) {
+ widget_set_error (GTK_WIDGET (self->key_entry));
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: key with a length of %zu must contain only hex-digits"), strlen (key));
+ return FALSE;
+ }
+ }
+ } else if ((strlen (key) == 5) || (strlen (key) == 13)) {
+ for (i = 0; i < strlen (key); i++) {
+ if (!g_ascii_isprint (key[i])) {
+ widget_set_error (GTK_WIDGET (self->key_entry));
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: key with a length of %zu must contain only ascii characters"), strlen (key));
+ return FALSE;
+ }
+ }
+ } else {
+ widget_set_error (GTK_WIDGET (self->key_entry));
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: wrong key length %zu. A key must be either of length 5/13 (ascii) or 10/26 (hex)"), strlen (key));
+ return FALSE;
+ }
+ } else if (self->type == NM_WEP_KEY_TYPE_PASSPHRASE) {
+ if (!*key || (strlen (key) > 64)) {
+ widget_set_error (GTK_WIDGET (self->key_entry));
+ if (!*key)
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: passphrase must be non-empty"));
+ else
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: passphrase must be shorter than 64 characters"));
+ return FALSE;
+ }
+ }
+ widget_unset_error (GTK_WIDGET (self->key_entry));
+
+ return TRUE;
+}
+
+static void
+add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group)
+{
+ WirelessSecurityWEPKey *self = WS_WEP_KEY (security);
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->auth_method_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->key_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->key_index_label));
+}
+
+static void
+fill_connection (WirelessSecurity *security, NMConnection *connection)
+{
+ WirelessSecurityWEPKey *self = WS_WEP_KEY (security);
+ NMSettingWirelessSecurity *s_wsec;
+ NMSettingSecretFlags secret_flags;
+ gint auth_alg;
+ const char *key;
+ int i;
+
+ auth_alg = gtk_combo_box_get_active (self->auth_method_combo);
+
+ key = gtk_editable_get_text (GTK_EDITABLE (self->key_entry));
+ g_strlcpy (self->keys[self->cur_index], key, sizeof (self->keys[self->cur_index]));
+
+ /* Blow away the old security setting by adding a clear one */
+ s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_wsec);
+
+ g_object_set (s_wsec,
+ NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none",
+ NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, self->cur_index,
+ NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, (auth_alg == 1) ? "shared" : "open",
+ NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, self->type,
+ NULL);
+
+ for (i = 0; i < 4; i++) {
+ if (strlen (self->keys[i]))
+ nm_setting_wireless_security_set_wep_key (s_wsec, i, self->keys[i]);
+ }
+
+ /* Save WEP_KEY_FLAGS to the connection */
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->key_entry));
+ g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, secret_flags, NULL);
+
+ /* Update secret flags and popup when editing the connection */
+ nma_utils_update_password_storage (GTK_WIDGET (self->key_entry), secret_flags,
+ NM_SETTING (s_wsec), NM_SETTING_WIRELESS_SECURITY_WEP_KEY0);
+}
+
+static void
+wep_entry_filter_cb (WirelessSecurityWEPKey *self,
+ gchar *text,
+ gint length,
+ gint *position)
+{
+ if (self->type == NM_WEP_KEY_TYPE_KEY) {
+ int i, count = 0;
+ g_autofree gchar *result = g_new (gchar, length+1);
+
+ for (i = 0; i < length; i++) {
+ if (g_ascii_isprint (text[i]))
+ result[count++] = text[i];
+ }
+ result[count] = 0;
+
+ if (count > 0) {
+ g_signal_handlers_block_by_func (self->key_entry, G_CALLBACK (wep_entry_filter_cb), self);
+ gtk_editable_insert_text (GTK_EDITABLE (self->key_entry), result, count, position);
+ g_signal_handlers_unblock_by_func (self->key_entry, G_CALLBACK (wep_entry_filter_cb), self);
+ }
+ g_signal_stop_emission_by_name (self->key_entry, "insert-text");
+ }
+}
+
+static void
+update_secrets (WirelessSecurityWEPKey *self, NMConnection *connection)
+{
+ NMSettingWirelessSecurity *s_wsec;
+ const char *tmp;
+ int i;
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ for (i = 0; s_wsec && i < 4; i++) {
+ tmp = nm_setting_wireless_security_get_wep_key (s_wsec, i);
+ if (tmp)
+ g_strlcpy (self->keys[i], tmp, sizeof (self->keys[i]));
+ }
+
+ if (strlen (self->keys[self->cur_index]))
+ gtk_editable_set_text (GTK_EDITABLE (self->key_entry), self->keys[self->cur_index]);
+}
+
+static void
+changed_cb (WirelessSecurityWEPKey *self)
+{
+ wireless_security_notify_changed ((WirelessSecurity *) self);
+}
+
+void
+ws_wep_key_init (WirelessSecurityWEPKey *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ws_wep_key_class_init (WirelessSecurityWEPKeyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = ws_wep_key_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-wep-key.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, auth_method_combo);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, auth_method_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_entry);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_index_combo);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_index_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, show_key_check);
+}
+
+static void
+wireless_security_iface_init (WirelessSecurityInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+}
+
+WirelessSecurityWEPKey *
+ws_wep_key_new (NMConnection *connection,
+ NMWepKeyType type)
+{
+ WirelessSecurityWEPKey *self;
+ NMSettingWirelessSecurity *s_wsec = NULL;
+ NMSetting *setting = NULL;
+ guint8 default_key_idx = 0;
+ gboolean is_adhoc = FALSE;
+ gboolean is_shared_key = FALSE;
+
+ self = g_object_new (ws_wep_key_get_type (), NULL);
+
+ self->type = type;
+
+ gtk_editable_set_width_chars (GTK_EDITABLE (self->key_entry), 28);
+
+ /* Create password-storage popup menu for password entry under entry's secondary icon */
+ if (connection)
+ setting = (NMSetting *) nm_connection_get_setting_wireless_security (connection);
+ nma_utils_setup_password_storage (GTK_WIDGET (self->key_entry), 0, setting, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0,
+ FALSE, FALSE);
+
+ if (connection) {
+ NMSettingWireless *s_wireless;
+ const char *mode, *auth_alg;
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ mode = s_wireless ? nm_setting_wireless_get_mode (s_wireless) : NULL;
+ if (mode && !strcmp (mode, "adhoc"))
+ is_adhoc = TRUE;
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ if (s_wsec) {
+ auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec);
+ if (auth_alg && !strcmp (auth_alg, "shared"))
+ is_shared_key = TRUE;
+ }
+ }
+
+ g_signal_connect_swapped (self->key_entry, "changed", G_CALLBACK (changed_cb), self);
+ g_signal_connect_swapped (self->key_entry, "insert-text", G_CALLBACK (wep_entry_filter_cb), self);
+ if (self->type == NM_WEP_KEY_TYPE_KEY)
+ gtk_entry_set_max_length (self->key_entry, 26);
+ else if (self->type == NM_WEP_KEY_TYPE_PASSPHRASE)
+ gtk_entry_set_max_length (self->key_entry, 64);
+
+ if (connection && s_wsec)
+ default_key_idx = nm_setting_wireless_security_get_wep_tx_keyidx (s_wsec);
+
+ gtk_combo_box_set_active (self->key_index_combo, default_key_idx);
+ self->cur_index = default_key_idx;
+ g_signal_connect_swapped (self->key_index_combo, "changed", G_CALLBACK (key_index_combo_changed_cb), self);
+
+ /* Key index is useless with adhoc networks */
+ if (is_adhoc) {
+ gtk_widget_hide (GTK_WIDGET (self->key_index_combo));
+ gtk_widget_hide (GTK_WIDGET (self->key_index_label));
+ }
+
+ /* Fill the key entry with the key for that index */
+ if (connection)
+ update_secrets (self, connection);
+
+ g_signal_connect_swapped (self->show_key_check, "toggled", G_CALLBACK (show_toggled_cb), self);
+
+ gtk_combo_box_set_active (self->auth_method_combo, is_shared_key ? 1 : 0);
+
+ g_signal_connect_swapped (self->auth_method_combo, "changed", G_CALLBACK (changed_cb), self);
+
+ /* Don't show auth method for adhoc (which always uses open-system) or
+ * when in "simple" mode.
+ */
+ if (is_adhoc) {
+ /* Ad-Hoc connections can't use Shared Key auth */
+ if (is_adhoc)
+ gtk_combo_box_set_active (self->auth_method_combo, 0);
+ gtk_widget_hide (GTK_WIDGET (self->auth_method_combo));
+ gtk_widget_hide (GTK_WIDGET (self->auth_method_label));
+ }
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/ws-wep-key.h b/panels/network/wireless-security/ws-wep-key.h
new file mode 100644
index 0000000..dc85e8b
--- /dev/null
+++ b/panels/network/wireless-security/ws-wep-key.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (WirelessSecurityWEPKey, ws_wep_key, WS, WEP_KEY, GtkGrid)
+
+WirelessSecurityWEPKey *ws_wep_key_new (NMConnection *connection,
+ NMWepKeyType type);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-wep-key.ui b/panels/network/wireless-security/ws-wep-key.ui
new file mode 100644
index 0000000..49f525e
--- /dev/null
+++ b/panels/network/wireless-security/ws-wep-key.ui
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <object class="GtkListStore" id="key_index_model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">1 (Default)</col>
+ </row>
+ <row>
+ <col id="0">2</col>
+ </row>
+ <row>
+ <col id="0">3</col>
+ </row>
+ <row>
+ <col id="0">4</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="auth_method_model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Open System</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Shared Key</col>
+ </row>
+ </data>
+ </object>
+ <template class="WirelessSecurityWEPKey" parent="GtkGrid">
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="key_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Key</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">key_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="key_entry">
+ <property name="hexpand">True</property>
+ <property name="max_length">64</property>
+ <property name="visibility">False</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_key_check">
+ <property name="label" translatable="yes">Sho_w key</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="auth_method_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Au_thentication</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">auth_method_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="auth_method_combo">
+ <property name="hexpand">True</property>
+ <property name="model">auth_method_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="key_index_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">WEP inde_x</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">key_index_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="key_index_combo">
+ <property name="hexpand">True</property>
+ <property name="model">key_index_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/ws-wpa-eap.c b/panels/network/wireless-security/ws-wpa-eap.c
new file mode 100644
index 0000000..d2b01b6
--- /dev/null
+++ b/panels/network/wireless-security/ws-wpa-eap.c
@@ -0,0 +1,313 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ws-wpa-eap.h"
+#include "wireless-security.h"
+#include "eap-method.h"
+#include "eap-method-fast.h"
+#include "eap-method-leap.h"
+#include "eap-method-peap.h"
+#include "eap-method-simple.h"
+#include "eap-method-tls.h"
+#include "eap-method-ttls.h"
+
+struct _WirelessSecurityWPAEAP {
+ GtkGrid parent;
+
+ GtkComboBox *auth_combo;
+ GtkLabel *auth_label;
+ GtkListStore *auth_model;
+ GtkBox *method_box;
+
+ EAPMethodSimple *em_md5;
+ EAPMethodTLS *em_tls;
+ EAPMethodLEAP *em_leap;
+ EAPMethodSimple *em_pwd;
+ EAPMethodFAST *em_fast;
+ EAPMethodTTLS *em_ttls;
+ EAPMethodPEAP *em_peap;
+};
+
+static void wireless_security_iface_init (WirelessSecurityInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (WirelessSecurityWPAEAP, ws_wpa_eap, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init));
+
+#define AUTH_NAME_COLUMN 0
+#define AUTH_ID_COLUMN 1
+
+static EAPMethod *
+get_eap (WirelessSecurityWPAEAP *self)
+{
+ GtkTreeIter iter;
+ g_autofree gchar *id = NULL;
+
+ if (!gtk_combo_box_get_active_iter (self->auth_combo, &iter))
+ return NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1);
+
+ if (strcmp (id, "md5") == 0)
+ return EAP_METHOD (self->em_md5);
+ if (strcmp (id, "tls") == 0)
+ return EAP_METHOD (self->em_tls);
+ if (strcmp (id, "leap") == 0)
+ return EAP_METHOD (self->em_leap);
+ if (strcmp (id, "pwd") == 0)
+ return EAP_METHOD (self->em_pwd);
+ if (strcmp (id, "fast") == 0)
+ return EAP_METHOD (self->em_fast);
+ if (strcmp (id, "ttls") == 0)
+ return EAP_METHOD (self->em_ttls);
+ if (strcmp (id, "peap") == 0)
+ return EAP_METHOD (self->em_peap);
+
+ return NULL;
+}
+
+static gboolean
+validate (WirelessSecurity *security, GError **error)
+{
+ WirelessSecurityWPAEAP *self = WS_WPA_EAP (security);
+ return eap_method_validate (get_eap (self), error);
+}
+
+static void
+add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group)
+{
+ WirelessSecurityWPAEAP *self = WS_WPA_EAP (security);
+
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->auth_label));
+ eap_method_add_to_size_group (EAP_METHOD (self->em_md5), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_tls), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_leap), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_pwd), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_fast), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_ttls), group);
+ eap_method_add_to_size_group (EAP_METHOD (self->em_peap), group);
+}
+
+static void
+ws_802_1x_fill_connection (WirelessSecurityWPAEAP *self, NMConnection *connection)
+{
+ NMSettingWirelessSecurity *s_wireless_sec;
+ NMSetting8021x *s_8021x;
+ NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+ EAPMethod *eap;
+
+ /* Get the EAPMethod object */
+ eap = get_eap (self);
+
+ /* Get previous pasword flags, if any. Otherwise default to agent-owned secrets */
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x)
+ nm_setting_get_secret_flags (NM_SETTING (s_8021x), eap_method_get_password_flags_name (eap), &secret_flags, NULL);
+ else
+ secret_flags = NM_SETTING_SECRET_FLAG_AGENT_OWNED;
+
+ /* Blow away the old wireless security setting by adding a clear one */
+ s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec);
+
+ /* Blow away the old 802.1x setting by adding a clear one */
+ s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_8021x);
+
+ eap_method_fill_connection (eap, connection, secret_flags);
+}
+
+static void
+fill_connection (WirelessSecurity *security, NMConnection *connection)
+{
+ WirelessSecurityWPAEAP *self = WS_WPA_EAP (security);
+ NMSettingWirelessSecurity *s_wireless_sec;
+
+ ws_802_1x_fill_connection (self, connection);
+
+ s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+ g_assert (s_wireless_sec);
+
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL);
+}
+
+static gboolean
+adhoc_compatible (WirelessSecurity *security)
+{
+ return FALSE;
+}
+
+static void
+auth_combo_changed_cb (WirelessSecurityWPAEAP *self)
+{
+ EAPMethod *eap;
+ GtkWidget *eap_default_field;
+ GtkWidget *child;
+
+ eap = get_eap (self);
+
+ /* Remove the previous method and migrate username/password across */
+ child = gtk_widget_get_first_child (GTK_WIDGET (self->method_box));
+ if (child != NULL) {
+ EAPMethod *old_eap = EAP_METHOD (child);
+ eap_method_set_username (eap, eap_method_get_username (old_eap));
+ eap_method_set_password (eap, eap_method_get_password (old_eap));
+ eap_method_set_show_password (eap, eap_method_get_show_password (old_eap));
+ gtk_box_remove (self->method_box, child);
+ }
+
+ gtk_box_append (self->method_box, g_object_ref (GTK_WIDGET (eap)));
+ eap_default_field = eap_method_get_default_field (eap);
+ if (eap_default_field)
+ gtk_widget_grab_focus (eap_default_field);
+
+ wireless_security_notify_changed (WIRELESS_SECURITY (self));
+}
+
+void
+ws_wpa_eap_init (WirelessSecurityWPAEAP *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ws_wpa_eap_class_init (WirelessSecurityWPAEAPClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-wpa-eap.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, auth_combo);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, auth_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, auth_model);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, method_box);
+}
+
+static void
+wireless_security_iface_init (WirelessSecurityInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->adhoc_compatible = adhoc_compatible;
+}
+
+WirelessSecurityWPAEAP *
+ws_wpa_eap_new (NMConnection *connection)
+{
+ WirelessSecurityWPAEAP *self;
+ const gchar *remove_method, *default_method = NULL;
+ gboolean wired = FALSE;
+ GtkTreeIter iter;
+
+ self = g_object_new (ws_wpa_eap_get_type (), NULL);
+
+ /* Grab the default EAP method out of the security object */
+ if (connection) {
+ NMSettingConnection *s_con;
+ NMSetting8021x *s_8021x;
+ const char *ctype = NULL;
+
+ s_con = nm_connection_get_setting_connection (connection);
+ if (s_con)
+ ctype = nm_setting_connection_get_connection_type (s_con);
+ if ((g_strcmp0 (ctype, NM_SETTING_WIRED_SETTING_NAME) == 0)
+ || nm_connection_get_setting_wired (connection))
+ wired = TRUE;
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x && nm_setting_802_1x_get_num_eap_methods (s_8021x))
+ default_method = nm_setting_802_1x_get_eap_method (s_8021x, 0);
+ }
+ if (wired)
+ remove_method = "leap";
+ else
+ remove_method = "md5";
+ if (default_method == NULL) {
+ if (wired)
+ default_method = "md5";
+ else
+ default_method = "tls";
+ }
+
+ self->em_md5 = eap_method_simple_new (connection, "md5", FALSE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_md5));
+ g_signal_connect_object (self->em_md5, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_tls = eap_method_tls_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_tls));
+ g_signal_connect_object (self->em_tls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_leap = eap_method_leap_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_leap));
+ g_signal_connect_object (self->em_leap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_pwd = eap_method_simple_new (connection, "pwd", FALSE, FALSE);
+ gtk_widget_show (GTK_WIDGET (self->em_pwd));
+ g_signal_connect_object (self->em_pwd, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_fast = eap_method_fast_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_fast));
+ g_signal_connect_object (self->em_fast, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_ttls = eap_method_ttls_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_ttls));
+ g_signal_connect_object (self->em_ttls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+ self->em_peap = eap_method_peap_new (connection);
+ gtk_widget_show (GTK_WIDGET (self->em_peap));
+ g_signal_connect_object (self->em_peap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED);
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->auth_model), &iter)) {
+ do {
+ g_autofree gchar *id = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1);
+ if (strcmp (id, remove_method) == 0) {
+ gtk_list_store_remove (self->auth_model, &iter);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->auth_model), &iter));
+ }
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->auth_model), &iter)) {
+ do {
+ g_autofree gchar *id = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1);
+ if (strcmp (id, default_method) == 0)
+ gtk_combo_box_set_active_iter (self->auth_combo, &iter);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->auth_model), &iter));
+ }
+
+ if (connection) {
+ NMSetting8021x *setting;
+
+ setting = nm_connection_get_setting_802_1x (connection);
+ if (setting) {
+ eap_method_set_username (get_eap (self), nm_setting_802_1x_get_identity (setting));
+ eap_method_set_password (get_eap (self), nm_setting_802_1x_get_password (setting));
+ }
+ }
+
+ g_signal_connect_object (G_OBJECT (self->auth_combo), "changed", G_CALLBACK (auth_combo_changed_cb), self, G_CONNECT_SWAPPED);
+ auth_combo_changed_cb (self);
+
+ return self;
+}
+
+void
+ws_wpa_eap_fill_connection (WirelessSecurityWPAEAP *self, NMConnection *connection)
+{
+ ws_802_1x_fill_connection (self, connection);
+}
diff --git a/panels/network/wireless-security/ws-wpa-eap.h b/panels/network/wireless-security/ws-wpa-eap.h
new file mode 100644
index 0000000..da5aa7f
--- /dev/null
+++ b/panels/network/wireless-security/ws-wpa-eap.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (WirelessSecurityWPAEAP, ws_wpa_eap, WS, WPA_EAP, GtkGrid)
+
+WirelessSecurityWPAEAP *ws_wpa_eap_new (NMConnection *connection);
+
+void ws_wpa_eap_fill_connection (WirelessSecurityWPAEAP *sec,
+ NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-wpa-eap.ui b/panels/network/wireless-security/ws-wpa-eap.ui
new file mode 100644
index 0000000..222eba7
--- /dev/null
+++ b/panels/network/wireless-security/ws-wpa-eap.ui
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <object class="GtkListStore" id="auth_model">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ <!-- column-name visible -->
+ <column type="gboolean"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">MD5</col>
+ <col id="1">md5</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">TLS</col>
+ <col id="1">tls</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">LEAP</col>
+ <col id="1">leap</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">PWD</col>
+ <col id="1">pwd</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">FAST</col>
+ <col id="1">fast</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Tunneled TLS</col>
+ <col id="1">ttls</col>
+ <col id="2">True</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Protected EAP (PEAP)</col>
+ <col id="1">peap</col>
+ <col id="2">True</col>
+ </row>
+ </data>
+ </object>
+ <template class="WirelessSecurityWPAEAP" parent="GtkGrid">
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="auth_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Au_thentication</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">auth_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="auth_combo">
+ <property name="hexpand">True</property>
+ <property name="model">auth_model</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="method_box">
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="column-span">2</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/wireless-security/ws-wpa-psk.c b/panels/network/wireless-security/ws-wpa-psk.c
new file mode 100644
index 0000000..b911b36
--- /dev/null
+++ b/panels/network/wireless-security/ws-wpa-psk.c
@@ -0,0 +1,237 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#include <ctype.h>
+#include <glib/gi18n.h>
+
+#include "helpers.h"
+#include "nma-ui-utils.h"
+#include "ui-helpers.h"
+#include "ws-wpa-psk.h"
+#include "wireless-security.h"
+
+#define WPA_PMK_LEN 32
+
+struct _WirelessSecurityWPAPSK {
+ GtkGrid parent;
+
+ GtkEntry *password_entry;
+ GtkLabel *password_label;
+ GtkCheckButton *show_password_check;
+ GtkComboBox *type_combo;
+ GtkLabel *type_label;
+};
+
+static void wireless_security_iface_init (WirelessSecurityInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (WirelessSecurityWPAPSK, ws_wpa_psk, GTK_TYPE_GRID,
+ G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init));
+
+static void
+show_toggled_cb (WirelessSecurityWPAPSK *self)
+{
+ gboolean visible;
+
+ visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check));
+ gtk_entry_set_visibility (self->password_entry, visible);
+}
+
+static gboolean
+validate (WirelessSecurity *security, GError **error)
+{
+ WirelessSecurityWPAPSK *self = WS_WPA_PSK (security);
+ NMSettingSecretFlags secret_flags;
+ const char *key;
+ gsize len;
+ int i;
+
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) {
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+ return TRUE;
+ }
+
+ key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+ len = key ? strlen (key) : 0;
+ if ((len < 8) || (len > 64)) {
+ widget_set_error (GTK_WIDGET (self->password_entry));
+ g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wpa-psk: invalid key-length %zu. Must be [8,63] bytes or 64 hex digits"), len);
+ return FALSE;
+ }
+
+ if (len == 64) {
+ /* Hex PSK */
+ for (i = 0; i < len; i++) {
+ if (!isxdigit (key[i])) {
+ widget_set_error (GTK_WIDGET (self->password_entry));
+ g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wpa-psk: cannot interpret key with 64 bytes as hex"));
+ return FALSE;
+ }
+ }
+ }
+ widget_unset_error (GTK_WIDGET (self->password_entry));
+
+ /* passphrase can be between 8 and 63 characters inclusive */
+
+ return TRUE;
+}
+
+static void
+add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group)
+{
+ WirelessSecurityWPAPSK *self = WS_WPA_PSK (security);
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->type_label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label));
+}
+
+static void
+fill_connection (WirelessSecurity *security, NMConnection *connection)
+{
+ WirelessSecurityWPAPSK *self = WS_WPA_PSK (security);
+ const char *key;
+ NMSettingWireless *s_wireless;
+ NMSettingWirelessSecurity *s_wireless_sec;
+ NMSettingSecretFlags secret_flags;
+ const char *mode;
+ gboolean is_adhoc = FALSE;
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ g_assert (s_wireless);
+
+ mode = nm_setting_wireless_get_mode (s_wireless);
+ if (mode && !strcmp (mode, "adhoc"))
+ is_adhoc = TRUE;
+
+ /* Blow away the old security setting by adding a clear one */
+ s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+ nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec);
+
+ key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_PSK, key, NULL);
+
+ /* Save PSK_FLAGS to the connection */
+ secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry));
+ nm_setting_set_secret_flags (NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK,
+ secret_flags, NULL);
+
+ /* Update secret flags and popup when editing the connection */
+ nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags,
+ NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK);
+
+ wireless_security_clear_ciphers (connection);
+ if (is_adhoc) {
+ /* Ad-Hoc settings as specified by the supplicant */
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-none", NULL);
+ nm_setting_wireless_security_add_proto (s_wireless_sec, "wpa");
+ nm_setting_wireless_security_add_pairwise (s_wireless_sec, "none");
+
+ /* Ad-hoc can only have _one_ group cipher... default to TKIP to be more
+ * compatible for now. Maybe we'll support selecting CCMP later.
+ */
+ nm_setting_wireless_security_add_group (s_wireless_sec, "tkip");
+ } else {
+ g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NULL);
+
+ /* Just leave ciphers and protocol empty, the supplicant will
+ * figure that out magically based on the AP IEs and card capabilities.
+ */
+ }
+}
+
+static gboolean
+adhoc_compatible (WirelessSecurity *security)
+{
+ return FALSE;
+}
+
+static void
+changed_cb (WirelessSecurityWPAPSK *self)
+{
+ wireless_security_notify_changed ((WirelessSecurity *) self);
+}
+
+void
+ws_wpa_psk_init (WirelessSecurityWPAPSK *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ws_wpa_psk_class_init (WirelessSecurityWPAPSKClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-wpa-psk.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, password_entry);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, password_label);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, show_password_check);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, type_combo);
+ gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, type_label);
+}
+
+static void
+wireless_security_iface_init (WirelessSecurityInterface *iface)
+{
+ iface->validate = validate;
+ iface->add_to_size_group = add_to_size_group;
+ iface->fill_connection = fill_connection;
+ iface->adhoc_compatible = adhoc_compatible;
+}
+
+WirelessSecurityWPAPSK *
+ws_wpa_psk_new (NMConnection *connection)
+{
+ WirelessSecurityWPAPSK *self;
+ NMSetting *setting = NULL;
+
+ self = g_object_new (ws_wpa_psk_get_type (), NULL);
+
+ g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self);
+ gtk_editable_set_width_chars (GTK_EDITABLE (self->password_entry), 28);
+
+ /* Create password-storage popup menu for password entry under entry's secondary icon */
+ if (connection)
+ setting = (NMSetting *) nm_connection_get_setting_wireless_security (connection);
+ nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, setting, NM_SETTING_WIRELESS_SECURITY_PSK,
+ FALSE, FALSE);
+
+ /* Fill secrets, if any */
+ if (connection) {
+ helper_fill_secret_entry (connection,
+ self->password_entry,
+ NM_TYPE_SETTING_WIRELESS_SECURITY,
+ (HelperSecretFunc) nm_setting_wireless_security_get_psk);
+ }
+
+ g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self);
+
+ /* Hide WPA/RSN for now since this can be autodetected by NM and the
+ * supplicant when connecting to the AP.
+ */
+
+ gtk_widget_hide (GTK_WIDGET (self->type_combo));
+ gtk_widget_hide (GTK_WIDGET (self->type_label));
+
+ return self;
+}
+
diff --git a/panels/network/wireless-security/ws-wpa-psk.h b/panels/network/wireless-security/ws-wpa-psk.h
new file mode 100644
index 0000000..7ca8676
--- /dev/null
+++ b/panels/network/wireless-security/ws-wpa-psk.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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.
+ *
+ * Copyright 2007 - 2014 Red Hat, Inc.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE (WirelessSecurityWPAPSK, ws_wpa_psk, WS, WPA_PSK, GtkGrid)
+
+WirelessSecurityWPAPSK *ws_wpa_psk_new (NMConnection *connection);
+
+G_END_DECLS
diff --git a/panels/network/wireless-security/ws-wpa-psk.ui b/panels/network/wireless-security/ws-wpa-psk.ui
new file mode 100644
index 0000000..26c9415
--- /dev/null
+++ b/panels/network/wireless-security/ws-wpa-psk.ui
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.4"/>
+ <template class="WirelessSecurityWPAPSK" parent="GtkGrid">
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Password</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">password_entry</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="hexpand">True</property>
+ <property name="max_length">64</property>
+ <property name="visibility">False</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="type_label">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Type</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">type_combo</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_password_check">
+ <property name="label" translatable="yes">Sho_w password</property>
+ <property name="hexpand">True</property>
+ <property name="use_underline">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="type_combo">
+ <property name="hexpand">True</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ </template>
+</interface>