text: // SPDX-License-Identifier: GPL-2.0+
  /* NetworkManager Applet -- allow user control over networking
   *
   * Dan Williams <dcbw@redhat.com>
   *
   * Copyright 2008 - 2014 Red Hat, Inc.
   */
  
  #include "nm-default.h"
  
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <netinet/ether.h>
  #include <ctype.h>
  
  #include "applet.h"
  #include "applet-device-wifi.h"
  #include "ap-menu-item.h"
  #include "utils.h"
  #include "nma-wifi-dialog.h"
  #include "mobile-helpers.h"
  
  #define ACTIVE_AP_TAG "active-ap"
  
  static void wifi_dialog_response_cb (GtkDialog *dialog, gint response, gpointer user_data);
  
  static NMAccessPoint *update_active_ap (NMDevice *device, NMDeviceState state, NMApplet *applet);
  
  /*****************************************************************************/
  
  typedef struct {
  	NMApplet *applet;
  	NMDevice *device;
  	NMAccessPoint *ap;
  	gulong signal_id;
  } ActiveAPData;
  
  static void _active_ap_set (NMApplet *applet, NMDevice *device, NMAccessPoint *ap);
  static void _active_ap_set_weakref (gpointer data, GObject *where_the_object_was);
  
  static void
  _active_ap_set_notify (NMAccessPoint *ap, GParamSpec *pspec, gpointer user_data)
  {
  	ActiveAPData *d = user_data;
  
  	g_return_if_fail (NM_IS_ACCESS_POINT (ap));
  	g_return_if_fail (d);
  	g_return_if_fail (NM_IS_APPLET (d->applet));
  	g_return_if_fail (NM_IS_DEVICE (d->device));
  	g_return_if_fail (d->ap == ap);
  	g_return_if_fail (d->signal_id);
  
  	applet_schedule_update_icon (d->applet);
  }
  
  static void
  _active_ap_data_free (ActiveAPData *d)
  {
  	if (d->device)
  		g_object_weak_unref ((GObject *) d->device, _active_ap_set_weakref, d);
  	if (d->ap) {
  		g_object_weak_unref ((GObject *) d->ap, _active_ap_set_weakref, d);
  		g_signal_handler_disconnect (d->ap, d->signal_id);
  	}
  	g_slice_free (ActiveAPData, d);
  }
  
  static NMAccessPoint *
  _active_ap_get (NMApplet *applet, NMDevice *device)
  {
  	GSList *list, *iter;
  
  	g_return_val_if_fail (NM_IS_APPLET (applet), NULL);
  	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
  
  	list = g_object_get_data ((GObject *) applet, ACTIVE_AP_TAG);
  	for (iter = list; iter; iter = iter->next) {
  		ActiveAPData *d = iter->data;
  
  		if (device == d->device && d->ap)
  			return d->ap;
  	}
  	return NULL;
  }
  
  static void
  _active_ap_set_destroy (gpointer data)
  {
  	g_slist_free_full (data, (GDestroyNotify) _active_ap_data_free);
  }
  
  static void
  _active_ap_set_weakref (gpointer data, GObject *where_the_object_was)
  {
  	ActiveAPData *d = data;
  	NMApplet *applet = d->applet;
  
  	if ((GObject *) d->ap == where_the_object_was)
  		d->ap = NULL;
  	else if ((GObject *) d->device == where_the_object_was)
  		d->device = NULL;
  	else
  		g_return_if_reached ();
  	_active_ap_set (applet, NULL, NULL);
  
  	applet_schedule_update_icon (applet);
  }
  
  static void
  _active_ap_set (NMApplet *applet, NMDevice *device, NMAccessPoint *ap)
  {
  	GSList *list, *iter, *list0, *pcurrent;
  	ActiveAPData *d;
  
  	g_return_if_fail (NM_IS_APPLET (applet));
  	g_return_if_fail (!device || NM_IS_DEVICE (device));
  	g_return_if_fail (!ap || NM_IS_ACCESS_POINT (ap));
  
  	list0 = g_object_get_data ((GObject *) applet, ACTIVE_AP_TAG);
  	list = list0;
  
  remove_empty:
  	pcurrent = NULL;
  	for (iter = list; iter; iter = iter->next) {
  		d = iter->data;
  		if (!d->device || !d->ap) {
  			_active_ap_data_free (d);
  			list = g_slist_delete_link (list, iter);
  			goto remove_empty;
  		}
  		if (device && d->device == device)
  			pcurrent = iter;
  	}
  
  	if (!device)
  		goto out;
  
  	if (!ap) {
  		if (pcurrent) {
  			_active_ap_data_free (pcurrent->data);
  			list = g_slist_delete_link (list, pcurrent);
  		}
  		goto out;
  	}
  
  	if (pcurrent) {
  		d = pcurrent->data;
  
  		if (d->ap == ap)
  			goto out;
  		g_object_weak_unref ((GObject *) d->ap, _active_ap_set_weakref, d);
  		g_signal_handler_disconnect (d->ap, d->signal_id);
  	} else {
  		d = g_slice_new (ActiveAPData);
  
  		d->applet = applet;
  		d->device = device;
  		g_object_weak_ref ((GObject *) device, _active_ap_set_weakref, d);
  		list = g_slist_append (list, d);
  	}
  	d->ap = ap;
  	g_object_weak_ref ((GObject *) ap, _active_ap_set_weakref, d);
  	d->signal_id = g_signal_connect (ap,
  	                                 "notify::" NM_ACCESS_POINT_STRENGTH,
  	                                 G_CALLBACK (_active_ap_set_notify),
  	                                 d);
  
  out:
  	if (list0 != list) {
  		g_object_replace_data ((GObject *) applet, ACTIVE_AP_TAG,
  		                       list0, list,
  		                       _active_ap_set_destroy, NULL);
  	}
  }
  
  /*****************************************************************************/
  
  static void
  show_ignore_focus_stealing_prevention (GtkWidget *widget)
  {
  	gtk_widget_realize (widget);
  	gtk_widget_show (widget);
  	gtk_window_present (GTK_WINDOW (widget));
  }
  
  gboolean
  applet_wifi_connect_to_hidden_network (NMApplet *applet)
  {
  	GtkWidget *dialog;
  
  	dialog = nma_wifi_dialog_new_for_hidden (applet->nm_client);
  	if (dialog) {
  		g_signal_connect (dialog, "response",
  		                  G_CALLBACK (wifi_dialog_response_cb),
  		                  applet);
  		show_ignore_focus_stealing_prevention (dialog);
  	}
  	return !!dialog;
  }
  
  void
  nma_menu_add_hidden_network_item (GtkWidget *menu, NMApplet *applet)
  {
  	GtkWidget *menu_item;
  	GtkWidget *label;
  
  	menu_item = gtk_menu_item_new ();
  	label = gtk_label_new_with_mnemonic (_("_Connect to Hidden Wi-Fi Network…"));
  	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  	gtk_container_add (GTK_CONTAINER (menu_item), label);
  	gtk_widget_show_all (menu_item);
  	gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
  	g_signal_connect_swapped (menu_item, "activate",
  	                          G_CALLBACK (applet_wifi_connect_to_hidden_network),
  	                          applet);
  }
  
  gboolean
  applet_wifi_can_create_wifi_network (NMApplet *applet)
  {
  	gboolean disabled, allowed = FALSE;
  	NMClientPermissionResult perm;
  
  	/* FIXME: check WIFI_SHARE_PROTECTED too, and make the wifi 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 (applet->nm_client, NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN);
  	if (perm == NM_CLIENT_PERMISSION_RESULT_YES || perm == NM_CLIENT_PERMISSION_RESULT_AUTH) {
  		disabled = g_settings_get_boolean (applet->gsettings, PREF_DISABLE_WIFI_CREATE);
  		if (!disabled)
  			allowed = TRUE;
  	}
  	return allowed;
  }
  
  gboolean
  applet_wifi_create_wifi_network (NMApplet *applet)
  {
  	GtkWidget *dialog;
  
  	dialog = nma_wifi_dialog_new_for_create (applet->nm_client);
  	if (dialog) {
  		g_signal_connect (dialog, "response",
  		                  G_CALLBACK (wifi_dialog_response_cb),
  		                  applet);
  		show_ignore_focus_stealing_prevention (dialog);
  	}
  	return !!dialog;
  }
  
  void
  nma_menu_add_create_network_item (GtkWidget *menu, NMApplet *applet)
  {
  	GtkWidget *menu_item;
  	GtkWidget *label;
  
  	menu_item = gtk_menu_item_new ();
  	label = gtk_label_new_with_mnemonic (_("Create _New Wi-Fi Network…"));
  	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  	gtk_container_add (GTK_CONTAINER (menu_item), label);
  	gtk_widget_show_all (menu_item);
  	gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
  	g_signal_connect_swapped (menu_item, "activate",
  	                          G_CALLBACK (applet_wifi_create_wifi_network),
  	                          applet);
  
  	if (!applet_wifi_can_create_wifi_network (applet))
  		gtk_widget_set_sensitive (GTK_WIDGET (menu_item), FALSE);
  }
  
  typedef struct {
  	NMApplet *applet;
  	NMDeviceWifi *device;
  	NMAccessPoint *ap;
  	NMConnection *connection;
  } WifiMenuItemInfo;
  
  static void
  wifi_menu_item_info_destroy (gpointer data, GClosure *closure)
  {
  	WifiMenuItemInfo *info = (WifiMenuItemInfo *) data;
  
  	g_object_unref (G_OBJECT (info->device));
  	g_object_unref (G_OBJECT (info->ap));
  
  	if (info->connection)
  		g_object_unref (G_OBJECT (info->connection));
  
  	g_slice_free (WifiMenuItemInfo, data);
  }
  
  /*
   * NOTE: this list should *not* contain networks that you would like to
   * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot".
   */
  static const char *manf_default_ssids[] = {
  	"linksys",
  	"linksys-a",
  	"linksys-g",
  	"default",
  	"belkin54g",
  	"NETGEAR",
  	"o2DSL",
  	"WLAN",
  	"ALICE-WLAN",
  	NULL
  };
  
  static gboolean
  is_ssid_in_list (GBytes *ssid, const char **list)
  {
  	while (*list) {
  		if (g_bytes_get_size (ssid) == strlen (*list)) {
  			if (!memcmp (*list, g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)))
  				return TRUE;
  		}
  		list++;
  	}
  	return FALSE;
  }
  
  static gboolean
  is_manufacturer_default_ssid (GBytes *ssid)
  {
  	return is_ssid_in_list (ssid, manf_default_ssids);
  }
  
  static char *
  get_ssid_utf8 (NMAccessPoint *ap)
  {
  	char *ssid_utf8 = NULL;
  	GBytes *ssid;
  
  	if (ap) {
  		ssid = nm_access_point_get_ssid (ap);
  		if (ssid)
  			ssid_utf8 = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
  	}
  	if (!ssid_utf8)
  		ssid_utf8 = g_strdup (_("(none)"));
  
  	return ssid_utf8;
  }
  
  /* List known trojan networks that should never be shown to the user */
  static const char *blacklisted_ssids[] = {
  	/* http://www.npr.org/templates/story/story.php?storyId=130451369 */
  	"Free Public Wi-Fi",
  	NULL
  };
  
  static gboolean
  is_blacklisted_ssid (GBytes *ssid)
  {
  	return is_ssid_in_list (ssid, blacklisted_ssids);
  }
  
  static void
  clamp_ap_to_bssid (NMAccessPoint *ap, NMSettingWireless *s_wifi)
  {
  	const char *str_bssid;
  	struct ether_addr *eth_addr;
  	GByteArray *bssid;
  
  	/* For a certain list of known ESSIDs which are commonly preset by ISPs
  	 * and manufacturers and often unchanged by users, lock the connection
  	 * to the BSSID so that we don't try to auto-connect to your grandma's
  	 * neighbor's Wi-Fi.
  	 */
  
  	str_bssid = nm_access_point_get_bssid (ap);
  	if (str_bssid) {
  		eth_addr = ether_aton (str_bssid);
  		if (eth_addr) {
  			bssid = g_byte_array_sized_new (ETH_ALEN);
  			g_byte_array_append (bssid, eth_addr->ether_addr_octet, ETH_ALEN);
  			g_object_set (G_OBJECT (s_wifi),
  			              NM_SETTING_WIRELESS_BSSID, bssid,
  			              NULL);
  			g_byte_array_free (bssid, TRUE);
  		}
  	}
  }
  
  typedef struct {
  	NMApplet *applet;
  	AppletNewAutoConnectionCallback callback;
  	gpointer callback_data;
  } MoreInfo;
  
  static void
  more_info_wifi_dialog_response_cb (GtkDialog *foo,
                                     gint response,
                                     gpointer user_data)
  {
  	NMAWifiDialog *dialog = NMA_WIFI_DIALOG (foo);
  	MoreInfo *info = user_data;
  	NMConnection *connection = NULL;
  	NMDevice *device = NULL;
  	NMAccessPoint *ap = NULL;
  
  	if (response != GTK_RESPONSE_OK) {
  		info->callback (NULL, FALSE, TRUE, info->callback_data);
  		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);
  
  	info->callback (connection, TRUE, FALSE, info->callback_data);
  
  	/* Balance nma_wifi_dialog_get_connection() */
  	g_object_unref (connection);
  
  done:
  	g_free (info);
  	gtk_widget_hide (GTK_WIDGET (dialog));
  	gtk_widget_destroy (GTK_WIDGET (dialog));
  }
  
  static gboolean
  can_get_permission (NMApplet *applet, NMClientPermission perm)
  {
  	if (   applet->permissions[perm] == NM_CLIENT_PERMISSION_RESULT_YES
  	    || applet->permissions[perm] == NM_CLIENT_PERMISSION_RESULT_AUTH)
  		return TRUE;
  	return FALSE;
  }
  
  static gboolean
  wifi_new_auto_connection (NMDevice *device,
                            gpointer dclass_data,
                            AppletNewAutoConnectionCallback callback,
                            gpointer callback_data)
  {
  	WifiMenuItemInfo *info = (WifiMenuItemInfo *) dclass_data;
  	NMApplet *applet;
  	NMAccessPoint *ap;
  	NMConnection *connection;
  	NMSettingConnection *s_con;
  	NMSettingWireless *s_wifi = NULL;
  	NMSettingWirelessSecurity *s_wsec;
  	NMSetting8021x *s_8021x = NULL;
  	GBytes *ssid;
  	NM80211ApSecurityFlags wpa_flags, rsn_flags;
  	GtkWidget *dialog;
  	MoreInfo *more_info;
  	char *uuid;
  
  	g_return_val_if_fail (dclass_data, FALSE);
  	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
  	g_return_val_if_fail (NM_IS_ACCESS_POINT (info->ap), FALSE);
  	g_return_val_if_fail (NM_IS_APPLET (info->applet), FALSE);
  
  	applet = info->applet;
  	ap = info->ap;
  
  	connection = nm_simple_connection_new ();
  
  	/* Make the new connection available only for the current user */
  	s_con = (NMSettingConnection *) nm_setting_connection_new ();
  	nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL);
  	nm_connection_add_setting (connection, NM_SETTING (s_con));
  
  	ssid = nm_access_point_get_ssid (ap);
  	if (   (nm_access_point_get_mode (ap) == NM_802_11_MODE_INFRA)
  	    && (is_manufacturer_default_ssid (ssid) == TRUE)) {
  
  		/* Lock connection to this AP if it's a manufacturer-default SSID
  		 * so that we don't randomly connect to some other 'linksys'
  		 */
  		s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
  		clamp_ap_to_bssid (ap, s_wifi);
  		nm_connection_add_setting (connection, NM_SETTING (s_wifi));
  	}
  
  	/* 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)) {
  
  		/* Need a UUID for the "always ask" stuff in the Dialog of Doom */
  		uuid = nm_utils_uuid_generate ();
  		g_object_set (s_con, NM_SETTING_CONNECTION_UUID, uuid, NULL);
  		g_free (uuid);
  
  		if (!s_wifi) {
  			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, ssid,
  		              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));
  	}
  
  	/* If it's an 802.1x connection, we need more information, so pop up the
  	 * Dialog Of Doom.
  	 */
  	if (s_8021x) {
  		if (!can_get_permission (applet, NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM) &&
  		    !can_get_permission (applet, NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN)) {
  			const char *text = _("Failed to add new connection");
  			const char *err_text = _("Insufficient privileges.");
  			g_warning ("%s: %s", text, err_text);
  			utils_show_error_dialog (_("Connection failure"), text, err_text, FALSE, NULL);
  			g_clear_object (&connection);
  			return FALSE;
  		}
  		more_info = g_malloc0 (sizeof (*more_info));
  		more_info->applet = applet;
  		more_info->callback = callback;
  		more_info->callback_data = callback_data;
  
  		dialog = nma_wifi_dialog_new (applet->nm_client, connection, device, ap, FALSE);
  		if (dialog) {
  			g_signal_connect (dialog, "response",
  				              G_CALLBACK (more_info_wifi_dialog_response_cb),
  				              more_info);
  			show_ignore_focus_stealing_prevention (dialog);
  		}
  	} else {
  		/* Everything else can just get activated right away */
  		callback (connection, TRUE, FALSE, callback_data);
  	}
  
  	return TRUE;
  }
  
  static void
  wifi_menu_item_activate (GtkMenuItem *item, gpointer user_data)
  {
  	WifiMenuItemInfo *info = (WifiMenuItemInfo *) user_data;
  	const char *specific_object = NULL;
  
  	if (info->ap)
  		specific_object = nm_object_get_path (NM_OBJECT (info->ap));
  	applet_menu_item_activate_helper (NM_DEVICE (info->device),
  	                                  info->connection,
  	                                  specific_object ? specific_object : "/",
  	                                  info->applet,
  	                                  user_data);
  }
  
  struct dup_data {
  	NMDevice *device;
  	NMNetworkMenuItem *found;
  	char *hash;
  };
  
  static void
  find_duplicate (gpointer d, gpointer user_data)
  {
  	struct dup_data * data = (struct dup_data *) user_data;
  	NMDevice *device;
  	const char *hash;
  	GtkWidget *widget = GTK_WIDGET (d);
  
  	g_assert (d && widget);
  	g_return_if_fail (data);
  	g_return_if_fail (data->hash);
  
  	if (data->found || !NM_IS_NETWORK_MENU_ITEM (widget))
  		return;
  
  	device = g_object_get_data (G_OBJECT (widget), "device");
  	if (NM_DEVICE (device) != data->device)
  		return;
  
  	hash = nm_network_menu_item_get_hash (NM_NETWORK_MENU_ITEM (widget));
  	if (hash && (strcmp (hash, data->hash) == 0))
  		data->found = NM_NETWORK_MENU_ITEM (widget);
  }
  
  static NMNetworkMenuItem *
  create_new_ap_item (NMDeviceWifi *device,
                      NMAccessPoint *ap,
                      struct dup_data *dup_data,
                      const GPtrArray *connections,
                      NMApplet *applet)
  {
  	WifiMenuItemInfo *info;
  	int i;
  	GtkWidget *item;
  	GPtrArray *dev_connections;
  	GPtrArray *ap_connections;
  
  	dev_connections = nm_device_filter_connections (NM_DEVICE (device), connections);
  	ap_connections = nm_access_point_filter_connections (ap, dev_connections);
  	g_ptr_array_unref (dev_connections);
  
  	item = nm_network_menu_item_new (ap,
  	                                 nm_device_wifi_get_capabilities (device),
  	                                 dup_data->hash,
  	                                 ap_connections->len != 0,
  	                                 applet);
  	g_object_set_data (G_OBJECT (item), "device", NM_DEVICE (device));
  
  	/* If there's only one connection, don't show the submenu */
  	if (ap_connections->len > 1) {
  		GtkWidget *submenu;
  
  		submenu = gtk_menu_new ();
  
  		for (i = 0; i < ap_connections->len; i++) {
  			NMConnection *connection = NM_CONNECTION (ap_connections->pdata[i]);
  			NMSettingConnection *s_con;
  			GtkWidget *subitem;
  
  			s_con = nm_connection_get_setting_connection (connection);
  			subitem = gtk_menu_item_new_with_label (nm_setting_connection_get_id (s_con));
  
  			info = g_slice_new0 (WifiMenuItemInfo);
  			info->applet = applet;
  			info->device = g_object_ref (device);
  			info->ap = g_object_ref (ap);
  			info->connection = g_object_ref (connection);
  
  			g_signal_connect_data (subitem, "activate",
  			                       G_CALLBACK (wifi_menu_item_activate),
  			                       info,
  			                       wifi_menu_item_info_destroy, 0);
  
  			gtk_menu_shell_append (GTK_MENU_SHELL (submenu), GTK_WIDGET (subitem));
  			gtk_widget_show (subitem);
  		}
  
  		gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
  	} else {
  		NMConnection *connection;
  
  		info = g_slice_new0 (WifiMenuItemInfo);
  		info->applet = applet;
  		info->device = g_object_ref (device);
  		info->ap = g_object_ref (ap);
  
  		if (ap_connections->len == 1) {
  			connection = NM_CONNECTION (ap_connections->pdata[0]);
  			info->connection = g_object_ref (connection);
  		}
  
  		g_signal_connect_data (GTK_WIDGET (item),
  		                       "activate",
  		                       G_CALLBACK (wifi_menu_item_activate),
  		                       info,
  		                       wifi_menu_item_info_destroy,
  		                       0);
  	}
  
  	g_ptr_array_unref (ap_connections);
  	return NM_NETWORK_MENU_ITEM (item);
  }
  
  static NMNetworkMenuItem *
  get_menu_item_for_ap (NMDeviceWifi *device,
                        NMAccessPoint *ap,
                        const GPtrArray *connections,
                        GSList *menu_list,
                        NMApplet *applet)
  {
  	GBytes *ssid;
  	struct dup_data dup_data = { NULL, NULL };
  
  	/* Don't add BSSs that hide their SSID or are blacklisted */
  	ssid = nm_access_point_get_ssid (ap);
  	if (   !ssid
  	    || nm_utils_is_empty_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid))
  	    || is_blacklisted_ssid (ssid))
  		return NULL;
  
  	/* Find out if this AP is a member of a larger network that all uses the
  	 * same SSID and security settings.  If so, we'll already have a menu item
  	 * for this SSID, so just update that item's strength and add this AP to
  	 * menu item's duplicate list.
  	 */
  	dup_data.found = NULL;
  	dup_data.hash = g_object_get_data (G_OBJECT (ap), "hash");
  	g_return_val_if_fail (dup_data.hash != NULL, NULL);
  
  	dup_data.device = NM_DEVICE (device);
  	g_slist_foreach (menu_list, find_duplicate, &dup_data);
  
  	if (dup_data.found) {
  		nm_network_menu_item_set_strength (dup_data.found, nm_access_point_get_strength (ap), applet);
  		nm_network_menu_item_add_dupe (dup_data.found, ap);
  		return NULL;
  	}
  
  	return create_new_ap_item (device, ap, &dup_data, connections, applet);
  }
  
  static gint
  sort_by_name (gconstpointer tmpa, gconstpointer tmpb)
  {
  	NMNetworkMenuItem *a = NM_NETWORK_MENU_ITEM (tmpa);
  	NMNetworkMenuItem *b = NM_NETWORK_MENU_ITEM (tmpb);
  	const char *a_ssid, *b_ssid;
  	gboolean a_adhoc, b_adhoc;
  	int i;
  
  	if (a && !b)
  		return 1;
  	else if (!a && b)
  		return -1;
  	else if (a == b)
  		return 0;
  
  	a_ssid = nm_network_menu_item_get_ssid (a);
  	b_ssid = nm_network_menu_item_get_ssid (b);
  
  	if (a_ssid && !b_ssid)
  		return 1;
  	if (b_ssid && !a_ssid)
  		return -1;
  
  	if (a_ssid && b_ssid) {
  		i = g_ascii_strcasecmp (a_ssid, b_ssid);
  		if (i != 0)
  			return i;
  	}
  
  	/* If the names are the same, sort infrastructure APs first */
  	a_adhoc = nm_network_menu_item_get_is_adhoc (a);
  	b_adhoc = nm_network_menu_item_get_is_adhoc (b);
  	if (a_adhoc && !b_adhoc)
  		return 1;
  	else if (!a_adhoc && b_adhoc)
  		return -1;
  
  	return 0;
  }
  
  /* Sort menu items for the top-level menu:
   * 1) whether there's a saved connection or not
   *    a) sort alphabetically within #1
   * 2) encrypted without a saved connection
   * 3) unencrypted without a saved connection
   */
  static gint
  sort_toplevel (gconstpointer tmpa, gconstpointer tmpb)
  {
  	NMNetworkMenuItem *a = NM_NETWORK_MENU_ITEM (tmpa);
  	NMNetworkMenuItem *b = NM_NETWORK_MENU_ITEM (tmpb);
  	gboolean a_fave, b_fave;
  
  	if (a && !b)
  		return 1;
  	else if (!a && b)
  		return -1;
  	else if (a == b)
  		return 0;
  
  	a_fave = nm_network_menu_item_get_has_connections (a);
  	b_fave = nm_network_menu_item_get_has_connections (b);
  
  	/* Items with a saved connection first */
  	if (a_fave && !b_fave)
  		return -1;
  	else if (!a_fave && b_fave)
  		return 1;
  	else if (!a_fave && !b_fave) {
  		gboolean a_enc = nm_network_menu_item_get_is_encrypted (a);
  		gboolean b_enc = nm_network_menu_item_get_is_encrypted (b);
  
  		/* If neither item has a saved connection, sort by encryption */
  		if (a_enc && !b_enc)
  			return -1;
  		else if (!a_enc && b_enc)
  			return 1;
  	}
  
  	/* For all other cases (both have saved connections, both are encrypted, or
  	 * both are unencrypted) just sort by name.
  	 */
  	return sort_by_name (a, b);
  }
  
  static void
  wifi_add_menu_item (NMDevice *device,
                      gboolean multiple_devices,
                      const GPtrArray *connections,
                      NMConnection *active,
                      GtkWidget *menu,
                      NMApplet *applet)
  {
  	NMDeviceWifi *wdev;
  	char *text;
  	const GPtrArray *aps;
  	int i;
  	NMAccessPoint *active_ap = NULL;
  	GSList *iter;
  	gboolean wifi_enabled = TRUE;
  	gboolean wifi_hw_enabled = TRUE;
  	GSList *menu_items = NULL;  /* All menu items we'll be adding */
  	NMNetworkMenuItem *item, *active_item = NULL;
  	GtkWidget *widget;
  
  	wdev = NM_DEVICE_WIFI (device);
  	aps = nm_device_wifi_get_access_points (wdev);
  
  	if (multiple_devices) {
  		const char *desc;
  
  		desc = nm_device_get_description (device);
  		if (aps && aps->len > 1)
  			text = g_strdup_printf (_("Wi-Fi Networks (%s)"), desc);
  		else
  			text = g_strdup_printf (_("Wi-Fi Network (%s)"), desc);
  	} else
  		text = g_strdup (ngettext ("Wi-Fi Network", "Wi-Fi Networks", aps ? aps->len : 0));
  
  	widget = applet_menu_item_create_device_item_helper (device, applet, text);
  	g_free (text);
  
  	gtk_widget_set_sensitive (widget, FALSE);
  	gtk_menu_shell_append (GTK_MENU_SHELL (menu), widget);
  	gtk_widget_show (widget);
  
  	/* Add the active AP if we're connected to something and the device is available */
  	if (!nma_menu_device_check_unusable (device)) {
  		active_ap = nm_device_wifi_get_active_access_point (wdev);
  		if (active_ap) {
  			active_item = item = get_menu_item_for_ap (wdev, active_ap, connections, NULL, applet);
  			if (item) {
  				nm_network_menu_item_set_active (item, TRUE);
  				menu_items = g_slist_append (menu_items, item);
  
  				gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item));
  				gtk_widget_show_all (GTK_WIDGET (item));
  			}
  		}
  	}
  
  	/* Notify user of unmanaged or unavailable device */
  	wifi_enabled = nm_client_wireless_get_enabled (applet->nm_client);
  	wifi_hw_enabled = nm_client_wireless_hardware_get_enabled (applet->nm_client);
  	widget = nma_menu_device_get_menu_item (device, applet,
  	                                        wifi_hw_enabled ?
  	                                            (wifi_enabled ? NULL : _("Wi-Fi is disabled")) :
  	                                            _("Wi-Fi is disabled by hardware switch"));
  	if (widget) {
  		gtk_menu_shell_append (GTK_MENU_SHELL (menu), widget);
  		gtk_widget_show (widget);
  	}
  
  	/* If disabled or rfkilled or whatever, nothing left to do */
  	if (nma_menu_device_check_unusable (device))
  		goto out;
  
  	/* Create menu items for the rest of the APs */
  	for (i = 0; aps && (i < aps->len); i++) {
  		NMAccessPoint *ap = g_ptr_array_index (aps, i);
  
  		item = get_menu_item_for_ap (wdev, ap, connections, menu_items, applet);
  		if (item)
  			menu_items = g_slist_append (menu_items, item);
  	}
  
  	/* Now remove the active AP item from the list, as we've already dealt with
  	 * it.  (Needed it when creating menu items for the rest of the APs though
  	 * to ensure duplicate APs are handled correctly)
  	 */
  	if (active_item)
  		menu_items = g_slist_remove (menu_items, active_item);
  
  	/* Sort all the rest of the menu items for the top-level menu */
  	menu_items = g_slist_sort (menu_items, sort_toplevel);
  
  	if (g_slist_length (menu_items)) {
  		GSList *submenu_items = NULL;
  		GSList *topmenu_items = NULL;
  		guint32 num_for_toplevel = 5;
  
  		applet_menu_item_add_complex_separator_helper (menu, applet, _("Available"));
  
  		if (g_slist_length (menu_items) == (num_for_toplevel + 1))
  			num_for_toplevel++;
  
  		/* Add the first 5 APs (or 6 if there are only 6 total) from the sorted
  		 * toplevel list.
  		 */
  		for (iter = menu_items; iter && num_for_toplevel; iter = g_slist_next (iter)) {
  			topmenu_items = g_slist_append (topmenu_items, iter->data);
  			num_for_toplevel--;
  			submenu_items = iter->next;
  		}
  		topmenu_items = g_slist_sort (topmenu_items, sort_by_name);
  
  		for (iter = topmenu_items; iter; iter = g_slist_next (iter)) {
  			gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (iter->data));
  			gtk_widget_show_all (GTK_WIDGET (iter->data));
  		}
  		g_slist_free (topmenu_items);
  		topmenu_items = NULL;
  
  		/* If there are any submenu items, make a submenu for those */
  		if (submenu_items) {
  			GtkWidget *subitem, *submenu;
  			GSList *sorted_subitems;
  
  			subitem = gtk_menu_item_new_with_mnemonic (_("More networks"));
  			submenu = gtk_menu_new ();
  			gtk_menu_item_set_submenu (GTK_MENU_ITEM (subitem), submenu);
  
  			/* Sort the subitems alphabetically */
  			sorted_subitems = g_slist_copy (submenu_items);
  			sorted_subitems = g_slist_sort (sorted_subitems, sort_by_name);
  
  			/* And add the rest to the submenu */
  			for (iter = sorted_subitems; iter; iter = g_slist_next (iter))
  				gtk_menu_shell_append (GTK_MENU_SHELL (submenu), GTK_WIDGET (iter->data));
  			g_slist_free (sorted_subitems);
  
  			gtk_menu_shell_append (GTK_MENU_SHELL (menu), subitem);
  			gtk_widget_show_all (subitem);
  		}
  	}
  
  out:
  	g_slist_free (menu_items);
  }
  
  static void
  notify_active_ap_changed_cb (NMDeviceWifi *device,
                               GParamSpec *pspec,
                               NMApplet *applet)
  {
  	NMRemoteConnection *connection;
  	NMSettingWireless *s_wireless;
  	NMAccessPoint *new;
  	GBytes *ssid_ap, *ssid;
  	NMDeviceState state;
  
  	state = nm_device_get_state (NM_DEVICE (device));
  
  	new = update_active_ap (NM_DEVICE (device), state, applet);
  	if (!new || (state != NM_DEVICE_STATE_ACTIVATED))
  		return;
  
  	connection = applet_get_exported_connection_for_device (NM_DEVICE (device), applet);
  	if (!connection)
  		return;
  
  	s_wireless = nm_connection_get_setting_wireless (NM_CONNECTION (connection));
  	if (!s_wireless)
  		return;
  
  	ssid_ap = nm_access_point_get_ssid (new);
  	ssid = nm_setting_wireless_get_ssid (s_wireless);
  	if (   !ssid_ap
  	    || !ssid
  	    || !nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid),
  	                            g_bytes_get_data (ssid_ap, NULL), g_bytes_get_size (ssid_ap),
  	                            TRUE))
  		return;
  
  	applet_schedule_update_icon (applet);
  }
  
  static void
  add_hash_to_ap (NMAccessPoint *ap)
  {
  	char *hash;
  
  	hash = utils_hash_ap (nm_access_point_get_ssid (ap),
  	                      nm_access_point_get_mode (ap),
  	                      nm_access_point_get_flags (ap),
  	                      nm_access_point_get_wpa_flags (ap),
  	                      nm_access_point_get_rsn_flags (ap));
  	g_object_set_data_full (G_OBJECT (ap), "hash", hash, (GDestroyNotify) g_free);
  }
  
  static void
  notify_ap_prop_changed_cb (NMAccessPoint *ap,
                             GParamSpec *pspec,
                             NMApplet *applet)
  {
  	const char *prop = g_param_spec_get_name (pspec);
  
  	if (   !strcmp (prop, NM_ACCESS_POINT_FLAGS)
  	    || !strcmp (prop, NM_ACCESS_POINT_WPA_FLAGS)
  	    || !strcmp (prop, NM_ACCESS_POINT_RSN_FLAGS)
  	    || !strcmp (prop, NM_ACCESS_POINT_SSID)
  	    || !strcmp (prop, NM_ACCESS_POINT_FREQUENCY)
  	    || !strcmp (prop, NM_ACCESS_POINT_MODE)) {
  		add_hash_to_ap (ap);
  	}
  }
  
  static void
  wifi_available_dont_show_cb (NotifyNotification *notify,
  			                 gchar *id,
  			                 gpointer user_data)
  {
  	NMApplet *applet = NM_APPLET (user_data);
  
  	if (!id || strcmp (id, "dont-show"))
  		return;
  
  	g_settings_set_boolean (applet->gsettings,
  	                        PREF_SUPPRESS_WIFI_NETWORKS_AVAILABLE,
  	                        TRUE);
  }
  
  
  struct ap_notification_data 
  {
  	NMApplet *applet;
  	NMDeviceWifi *device;
  	guint id;
  	gulong last_notification_time;
  	guint new_con_id;
  };
  
  /* Scan the list of access points, looking for the case where we have no
   * known (i.e. autoconnect) access points, but we do have unknown ones.
   * 
   * If we find one, notify the user.
   */
  static gboolean
  idle_check_avail_access_point_notification (gpointer datap)
  {	
  	struct ap_notification_data *data = datap;
  	NMApplet *applet = data->applet;
  	NMDeviceWifi *device = data->device;
  	int i;
  	const GPtrArray *aps;
  	GPtrArray *all_connections;
  	GPtrArray *connections;
  	GTimeVal timeval;
  	gboolean have_unused_access_point = FALSE;
  	gboolean have_no_autoconnect_points = TRUE;
  
  	data->id = 0;
  
  	if (nm_client_get_state (data->applet->nm_client) != NM_STATE_DISCONNECTED)
  		return FALSE;
  
  	if (nm_device_get_state (NM_DEVICE (device)) != NM_DEVICE_STATE_DISCONNECTED)
  		return FALSE;
  
  	g_get_current_time (&timeval);
  	if ((timeval.tv_sec - data->last_notification_time) < 60*60) /* Notify at most once an hour */
  		return FALSE;	
  
  	all_connections = applet_get_all_connections (applet);
  	connections = nm_device_filter_connections (NM_DEVICE (device), all_connections);
  	g_ptr_array_unref (all_connections);	
  
  	aps = nm_device_wifi_get_access_points (device);
  	for (i = 0; i < aps->len; i++) {
  		NMAccessPoint *ap = aps->pdata[i];
  		GPtrArray *ap_connections;
  		int a;
  		gboolean is_autoconnect = FALSE;
  
  		if (!nm_access_point_get_ssid (ap))
  			continue;
  
  		ap_connections = nm_access_point_filter_connections (ap, connections);
  
  		for (a = 0; a < ap_connections->len; a++) {
  			NMConnection *connection = NM_CONNECTION (ap_connections->pdata[a]);
  			NMSettingConnection *s_con;
  
  			s_con = nm_connection_get_setting_connection (connection);
  			if (nm_setting_connection_get_autoconnect (s_con))  {
  				is_autoconnect = TRUE;
  				break;
  			}
  		}
  		g_ptr_array_unref (ap_connections);
  
  		if (!is_autoconnect)
  			have_unused_access_point = TRUE;
  		else
  			have_no_autoconnect_points = FALSE;
  	}
  	g_ptr_array_unref (connections);
  
  	if (!(have_unused_access_point && have_no_autoconnect_points))
  		return FALSE;
  
  	/* Avoid notifying too often */
  	g_get_current_time (&timeval);
  	data->last_notification_time = timeval.tv_sec;
  
  	applet_do_notify (applet,
  	                  NOTIFY_URGENCY_LOW,
  	                  _("Wi-Fi Networks Available"),
  	                  _("Use the network menu to connect to a Wi-Fi network"),
  	                  "nm-device-wireless",
  	                  "dont-show",
  	                  _("Don’t show this message again"),
  	                  wifi_available_dont_show_cb,
  	                  applet);
  	return FALSE;
  }
  
  static void
  queue_avail_access_point_notification (NMDevice *device)
  {
  	struct ap_notification_data *data;
  
  	data = g_object_get_data (G_OBJECT (device), "notify-wifi-avail-data");	
  	if (data->id != 0)
  		return;
  
  	if (g_settings_get_boolean (data->applet->gsettings,
  	                            PREF_SUPPRESS_WIFI_NETWORKS_AVAILABLE))
  		return;
  
  	data->id = g_timeout_add_seconds (3, idle_check_avail_access_point_notification, data);
  }
  
  static void
  access_point_added_cb (NMDeviceWifi *device,
                         NMAccessPoint *ap,
                         gpointer user_data)
  {
  	NMApplet *applet = NM_APPLET  (user_data);
  
  	add_hash_to_ap (ap);
  	g_signal_connect (G_OBJECT (ap),
  	                  "notify",
  	                  G_CALLBACK (notify_ap_prop_changed_cb),
  	                  applet);
  
  	queue_avail_access_point_notification (NM_DEVICE (device));
  	applet_schedule_update_menu (applet);
  }
  
  static void
  access_point_removed_cb (NMDeviceWifi *device,
                           NMAccessPoint *ap,
                           gpointer user_data)
  {
  	NMApplet *applet = NM_APPLET  (user_data);
  	NMAccessPoint *old;
  
  	/* If this AP was the active AP, make sure ACTIVE_AP_TAG gets cleared from
  	 * its device.
  	 */
  	old = _active_ap_get (applet, (NMDevice *) device);
  	if (old == ap) {
  		_active_ap_set (applet, (NMDevice *) device, NULL);
  		applet_schedule_update_icon (applet);
  	}
  
  	applet_schedule_update_menu (applet);
  }
  
  static void
  on_new_connection (NMClient *client,
                     NMRemoteConnection *connection,
                     gpointer datap)
  {
  	struct ap_notification_data *data = datap;
  	queue_avail_access_point_notification (NM_DEVICE (data->device));
  }
  
  static void
  free_ap_notification_data (gpointer user_data)
  {
  	struct ap_notification_data *data = user_data;
  	NMClient *client = data->applet->nm_client;
  
  	nm_clear_g_source (&data->id);
  
  	if (client)
  		g_signal_handler_disconnect (client, data->new_con_id);
  	memset (data, 0, sizeof (*data));
  	g_free (data);
  }
  
  static void
  wifi_device_added (NMDevice *device, NMApplet *applet)
  {
  	NMDeviceWifi *wdev = NM_DEVICE_WIFI (device);
  	const GPtrArray *aps;
  	int i;
  	struct ap_notification_data *data;
  	guint id;
  
  	g_signal_connect (wdev,
  	                  "notify::" NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT,
  	                  G_CALLBACK (notify_active_ap_changed_cb),
  	                  applet);
  
  	g_signal_connect (wdev,
  	                  "access-point-added",
  	                  G_CALLBACK (access_point_added_cb),
  	                  applet);
  
  	g_signal_connect (wdev,
  	                  "access-point-removed",
  	                  G_CALLBACK (access_point_removed_cb),
  	                  applet);
  
  	/* Now create the per-device hooks for watching for available wifi
  	 * connections.
  	 */
  	data = g_new0 (struct ap_notification_data, 1);
  	data->applet = applet;
  	data->device = wdev;
  	/* We also need to hook up to the client to find out when we have new connections
  	 * that might be candididates.  Keep the ID around so we can disconnect
  	 * when the device is destroyed.
  	 */ 
  	id = g_signal_connect (applet->nm_client,
  	                       NM_CLIENT_CONNECTION_ADDED,
  	                       G_CALLBACK (on_new_connection),
  	                       data);
  	data->new_con_id = id;
  	g_object_set_data_full (G_OBJECT (wdev), "notify-wifi-avail-data",
  	                        data, free_ap_notification_data);
  
  	queue_avail_access_point_notification (device);
  
  	/* Hash all APs this device knows about */
  	aps = nm_device_wifi_get_access_points (wdev);
  	for (i = 0; aps && (i < aps->len); i++)
  		add_hash_to_ap (g_ptr_array_index (aps, i));
  }
  
  static NMAccessPoint *
  update_active_ap (NMDevice *device, NMDeviceState state, NMApplet *applet)
  {
  	NMAccessPoint *new = NULL;
  
  	if (state == NM_DEVICE_STATE_PREPARE ||
  	    state == NM_DEVICE_STATE_CONFIG ||
  	    state == NM_DEVICE_STATE_IP_CONFIG ||
  	    state == NM_DEVICE_STATE_NEED_AUTH ||
  	    state == NM_DEVICE_STATE_ACTIVATED) {
  		new = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (device));
  	}
  
  	_active_ap_set (applet, device, new);
  	return new;
  }
  
  static void
  wifi_device_state_changed (NMDevice *device,
                             NMDeviceState new_state,
                             NMDeviceState old_state,
                             NMDeviceStateReason reason,
                             NMApplet *applet)
  {
  	update_active_ap (device, new_state, applet);
  
  	if (new_state == NM_DEVICE_STATE_DISCONNECTED)
  		queue_avail_access_point_notification (device);
  }
  
  static void
  wifi_notify_connected (NMDevice *device,
                         const char *msg,
                         NMApplet *applet)
  {
  	NMAccessPoint *ap;
  	char *esc_ssid;
  	char *ssid_msg;
  	const char *signal_strength_icon;
  
  	ap = _active_ap_get (applet, device);
  
  	esc_ssid = get_ssid_utf8 (ap);
  
  	if (!ap)
  		signal_strength_icon = "nm-device-wireless";
  	else
  		signal_strength_icon = mobile_helper_get_quality_icon_name (nm_access_point_get_strength (ap));
  
  	ssid_msg = g_strdup_printf (_("You are now connected to the Wi-Fi network “%s”."), esc_ssid);
  	applet_do_notify_with_pref (applet, _("Connection Established"),
  	                            ssid_msg, signal_strength_icon,
  	                            PREF_DISABLE_CONNECTED_NOTIFICATIONS);
  	g_free (ssid_msg);
  	g_free (esc_ssid);
  }
  
  static void
  wifi_get_icon (NMDevice *device,
                 NMDeviceState state,
                 NMConnection *connection,
                 GdkPixbuf **out_pixbuf,
                 const char **out_icon_name,
                 char **tip,
                 NMApplet *applet)
  {
  	NMSettingConnection *s_con;
  	NMAccessPoint *ap;
  	const char *id;
  	guint8 strength;
  
  	g_return_if_fail (out_icon_name && !*out_icon_name);
  	g_return_if_fail (tip && !*tip);
  
  	ap = _active_ap_get (applet, device);
  
  	id = nm_device_get_iface (device);
  	if (connection) {
  		s_con = nm_connection_get_setting_connection (connection);
  		id = nm_setting_connection_get_id (s_con);
  	}
  
  	switch (state) {
  	case NM_DEVICE_STATE_PREPARE:
  		*tip = g_strdup_printf (_("Preparing Wi-Fi network connection “%s”…"), id);
  		break;
  	case NM_DEVICE_STATE_CONFIG:
  		*tip = g_strdup_printf (_("Configuring Wi-Fi network connection “%s”…"), id);
  		break;
  	case NM_DEVICE_STATE_NEED_AUTH:
  		*tip = g_strdup_printf (_("User authentication required for Wi-Fi network “%s”…"), id);
  		break;
  	case NM_DEVICE_STATE_IP_CONFIG:
  		*tip = g_strdup_printf (_("Requesting a Wi-Fi network address for “%s”…"), id);
  		break;
  	case NM_DEVICE_STATE_ACTIVATED:
  		strength = ap ? nm_access_point_get_strength (ap) : 0;
  		strength = MIN (strength, 100);
  
  		*out_icon_name = mobile_helper_get_quality_icon_name (strength);
  
  		if (ap) {
  			char *ssid = get_ssid_utf8 (ap);
  
  			*tip = g_strdup_printf (_("Wi-Fi network connection “%s” active: %s (%d%%)"),
  			                        id, ssid, strength);
  			g_free (ssid);
  		} else
  			*tip = g_strdup_printf (_("Wi-Fi network connection “%s” active"), id);
  		break;
  	default:
  		break;
  	}
  }
  
  
  static void
  activate_existing_cb (GObject *client,
                        GAsyncResult *result,
                        gpointer user_data)
  {
  	GError *error = NULL;
  	NMActiveConnection *active;
  
  	active = nm_client_activate_connection_finish (NM_CLIENT (client), result, &error);
  	g_clear_object (&active);
  	if (error) {
  		const char *text = _("Failed to activate connection");
  		char *err_text = g_strdup_printf ("(%d) %s", error->code,
  		                                  error->message ? error->message : _("Unknown error"));
  
  		g_warning ("%s: %s", text, err_text);
  		utils_show_error_dialog (_("Connection failure"), text, err_text, FALSE, NULL);
  		g_free (err_text);
  		g_error_free (error);
  	}
  	applet_schedule_update_icon (NM_APPLET (user_data));
  }
  
  static void
  activate_new_cb (GObject *client,
                   GAsyncResult *result,
                   gpointer user_data)
  {
  	GError *error = NULL;
  	NMActiveConnection *active;
  
  	active = nm_client_add_and_activate_connection_finish (NM_CLIENT (client), result, &error);
  	g_clear_object (&active);
  	if (error) {
  		const char *text = _("Failed to add new connection");
  		char *err_text = g_strdup_printf ("(%d) %s", error->code,
  		                                  error->message ? error->message : _("Unknown error"));
  
  		g_warning ("%s: %s", text, err_text);
  		utils_show_error_dialog (_("Connection failure"), text, err_text, FALSE, NULL);
  		g_free (err_text);
  		g_error_free (error);
  	}
  	applet_schedule_update_icon (NM_APPLET (user_data));
  }
  
  static void
  wifi_dialog_response_cb (GtkDialog *foo,
                           gint response,
                           gpointer user_data)
  {
  	NMAWifiDialog *dialog = NMA_WIFI_DIALOG (foo);
  	NMApplet *applet = NM_APPLET (user_data);
  	NMConnection *connection = NULL, *fuzzy_match = NULL;
  	NMDevice *device = NULL;
  	NMAccessPoint *ap = NULL;
  	GPtrArray *all;
  	int 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 = applet_get_all_connections (applet);
  	for (i = 0; i < all->len; i++) {
  		if (nm_connection_compare (connection,
  		                           NM_CONNECTION (all->pdata[i]),
  		                           (NM_SETTING_COMPARE_FLAG_FUZZY | NM_SETTING_COMPARE_FLAG_IGNORE_ID))) {
  			fuzzy_match = NM_CONNECTION (all->pdata[i]);
  			break;
  		}
  	}
  	g_ptr_array_unref (all);
  
  	if (fuzzy_match) {
  		nm_client_activate_connection_async (applet->nm_client,
  		                                     fuzzy_match,
  		                                     device,
  		                                     ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL,
  		                                     NULL,
  		                                     activate_existing_cb,
  		                                     applet);
  	} else {
  		NMSetting *s_con;
  		NMSettingWireless *s_wifi = NULL;
  		const char *mode = NULL;
  
  		/* Entirely new connection */
  
  		/* Don't autoconnect adhoc networks by default for now */
  		s_wifi = nm_connection_get_setting_wireless (connection);
  		if (s_wifi)
  			mode = nm_setting_wireless_get_mode (s_wifi);
  		if (g_strcmp0 (mode, "adhoc") == 0 || g_strcmp0 (mode, "ap") == 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 (applet->nm_client,
  		                                             connection,
  		                                             device,
  		                                             ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL,
  		                                             NULL,
  		                                             activate_new_cb,
  		                                             applet);
  	}
  
  	/* Balance nma_wifi_dialog_get_connection() */
  	g_object_unref (connection);
  
  done:
  	gtk_widget_hide (GTK_WIDGET (dialog));
  	gtk_widget_destroy (GTK_WIDGET (dialog));
  }
  
  static GVariant *
  remove_unwanted_secrets (GVariant *secrets, gboolean keep_8021X)
  {
  	GVariant *copy, *setting_dict;
  	const char *setting_name;
  	GVariantBuilder conn_builder;
  	GVariantIter conn_iter;
  
  	g_variant_builder_init (&conn_builder, NM_VARIANT_TYPE_CONNECTION);
  	g_variant_iter_init (&conn_iter, secrets);
  	while (g_variant_iter_next (&conn_iter, "{&s@a{sv}}", &setting_name, &setting_dict)) {
  		if (   !strcmp (setting_name, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME)
  		    || (!strcmp (setting_name, NM_SETTING_802_1X_SETTING_NAME) && keep_8021X))
  			g_variant_builder_add (&conn_builder, "{s@a{sv}}", setting_name, setting_dict);
  
  		g_variant_unref (setting_dict);
  	}
  	copy = g_variant_builder_end (&conn_builder);
  	g_variant_unref (secrets);
  
  	return copy;
  }
  	
  
  typedef struct {
  	SecretsRequest req;
  
  	GtkWidget *dialog;
  } NMWifiInfo;
  
  static void
  free_wifi_info (SecretsRequest *req)
  {
  	NMWifiInfo *info = (NMWifiInfo *) req;
  
  	if (info->dialog) {
  		gtk_widget_hide (info->dialog);
  		gtk_widget_destroy (info->dialog);
  		info->dialog = NULL;
  	}
  }
  
  static void
  get_secrets_dialog_response_cb (GtkDialog *foo,
                                  gint response,
                                  gpointer user_data)
  {
  	SecretsRequest *req = user_data;
  	NMWifiInfo *info = (NMWifiInfo *) req;
  	NMAWifiDialog *dialog = NMA_WIFI_DIALOG (info->dialog);
  	NMConnection *connection = NULL;
  	NMSettingWirelessSecurity *s_wireless_sec;
  	GVariant *secrets = NULL;
  	const char *key_mgmt, *auth_alg;
  	gboolean keep_8021X = FALSE;
  	GError *error = NULL;
  
  	if (response != GTK_RESPONSE_OK) {
  		g_set_error (&error,
  		             NM_SECRET_AGENT_ERROR,
  		             NM_SECRET_AGENT_ERROR_USER_CANCELED,
  		             "%s.%d (%s): canceled",
  		             __FILE__, __LINE__, __func__);
  		goto done;
  	}
  
  	connection = nma_wifi_dialog_get_connection (dialog, NULL, NULL);
  	if (!connection) {
  		g_set_error (&error,
  		             NM_SECRET_AGENT_ERROR,
  		             NM_SECRET_AGENT_ERROR_FAILED,
  		             "%s.%d (%s): couldn't get connection from Wi-Fi dialog.",
  		             __FILE__, __LINE__, __func__);
  		goto done;
  	}
  
  	/* Second-guess which setting NM wants secrets for. */
  	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
  	if (!s_wireless_sec) {
  		g_set_error (&error,
  		             NM_SECRET_AGENT_ERROR,
  		             NM_SECRET_AGENT_ERROR_INVALID_CONNECTION,
  		             "%s.%d (%s): requested setting '802-11-wireless-security'"
  		             " didn't exist in the connection.",
  		             __FILE__, __LINE__, __func__);
  		goto done;  /* Unencrypted */
  	}
  
  	secrets = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
  	if (!secrets) {
  		g_set_error (&error,
  		             NM_SECRET_AGENT_ERROR,
  		             NM_SECRET_AGENT_ERROR_FAILED,
  		             "%s.%d (%s): failed to hash connection '%s'.",
  		             __FILE__, __LINE__, __func__, nm_connection_get_id (connection));
  		goto done;
  	}
  	/* If the user chose an 802.1x-based auth method, return 802.1x secrets,
  	 * not wireless secrets.  Can happen with Dynamic WEP, because NM doesn't
  	 * know the capabilities of the AP (since Dynamic WEP APs don't broadcast
  	 * beacons), and therefore defaults to requesting WEP secrets from the
  	 * wireless-security setting, not the 802.1x setting.
  	 */
  	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wireless_sec);
  	if (!strcmp (key_mgmt, "ieee8021x") || !strcmp (key_mgmt, "wpa-eap")) {
  		/* LEAP secrets aren't in the 802.1x setting */
  		auth_alg = nm_setting_wireless_security_get_auth_alg (s_wireless_sec);
  		if (!auth_alg || strcmp (auth_alg, "leap")) {
  			NMSetting8021x *s_8021x;
  
  			s_8021x = nm_connection_get_setting_802_1x (connection);
  			if (!s_8021x) {
  				g_set_error (&error,
  				             NM_SECRET_AGENT_ERROR,
  				             NM_SECRET_AGENT_ERROR_INVALID_CONNECTION,
  				             "%s.%d (%s): requested setting '802-1x' didn't"
  				             " exist in the connection.",
  				             __FILE__, __LINE__, __func__);
  				goto done;
  			}
  			keep_8021X = TRUE;
  		}
  	}
  
  	/* Remove all not-relevant secrets (inner dicts) */
  	secrets = remove_unwanted_secrets (secrets, keep_8021X);
  	g_variant_take_ref (secrets);
  
  done:
  	applet_secrets_request_complete (req, secrets, error);
  	applet_secrets_request_free (req);
  
  	if (secrets)
  		g_variant_unref (secrets);
  	if (connection)
  		nm_connection_clear_secrets (connection);
  }
  
  static gboolean
  wifi_get_secrets (SecretsRequest *req, GError **error)
  {
  	NMWifiInfo *info = (NMWifiInfo *) req;
  
  	g_return_val_if_fail (!info->dialog, FALSE);
  
  	info->dialog = nma_wifi_dialog_new_for_secrets (req->applet->nm_client,
  	                                                req->connection,
  	                                                req->setting_name,
  	                                                (const char *const*) req->hints);
  	if (info->dialog) {
  		applet_secrets_request_set_free_func (req, free_wifi_info);
  		g_signal_connect (info->dialog, "response",
  		                  G_CALLBACK (get_secrets_dialog_response_cb),
  		                  info);
  		show_ignore_focus_stealing_prevention (info->dialog);
  	} else {
  		g_set_error (error,
  		             NM_SECRET_AGENT_ERROR,
  		             NM_SECRET_AGENT_ERROR_FAILED,
  		             "%s.%d (%s): couldn't display secrets UI",
  		             __FILE__, __LINE__, __func__);
  	}
  	return !!info->dialog;
  }
  
  NMADeviceClass *
  applet_device_wifi_get_class (NMApplet *applet)
  {
  	NMADeviceClass *dclass;
  
  	dclass = g_slice_new0 (NMADeviceClass);
  	if (!dclass)
  		return NULL;
  
  	dclass->new_auto_connection = wifi_new_auto_connection;
  	dclass->add_menu_item = wifi_add_menu_item;
  	dclass->device_added = wifi_device_added;
  	dclass->device_state_changed = wifi_device_state_changed;
  	dclass->notify_connected = wifi_notify_connected;
  	dclass->get_icon = wifi_get_icon;
  	dclass->get_secrets = wifi_get_secrets;
  	dclass->secrets_request_size = sizeof (NMWifiInfo);
  
  	return dclass;
  }
  
  