summary refs log tree commit diff
path: root/clients/cli
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-06-17 14:54:10 +0200
committerMichael Biebl <biebl@debian.org>2018-06-17 14:54:10 +0200
commit069cb5c3a525ebcc19cc2927964258acaca87b13 (patch)
tree66ac6b21a44d630c0fc281c0df327239d491226a /clients/cli
parent04bc9e1cd3544445d883ad29ea108c1645c8e7b7 (diff)
New upstream version 1.11.90 upstream/1.11.90
Diffstat (limited to 'clients/cli')
-rw-r--r--clients/cli/common.c21
-rw-r--r--clients/cli/connections.c1264
-rw-r--r--clients/cli/connections.h4
-rw-r--r--clients/cli/devices.c437
-rw-r--r--clients/cli/general.c3
-rw-r--r--clients/cli/nmcli.c4
-rw-r--r--clients/cli/nmcli.h8
-rw-r--r--clients/cli/utils.c40
-rw-r--r--clients/cli/utils.h31
9 files changed, 1124 insertions, 688 deletions
diff --git a/clients/cli/common.c b/clients/cli/common.c
index e566de47..09c86334 100644
--- a/clients/cli/common.c
+++ b/clients/cli/common.c
@@ -49,6 +49,9 @@ _ip_config_get_routes (NMIPConfig *cfg)
 	if (!ptr_array)
 		return NULL;
 
+	if (ptr_array->len == 0)
+		return NULL;
+
 	arr = g_new (char *, ptr_array->len + 1);
 	for (i = 0; i < ptr_array->len; i++) {
 		NMIPRoute *route = g_ptr_array_index (ptr_array, i);
@@ -384,7 +387,7 @@ print_dhcp_config (NMDhcpConfig *dhcp,
 /*
  * nmc_find_connection:
  * @connections: array of NMConnections to search in
- * @filter_type: "id", "uuid", "path" or %NULL
+ * @filter_type: "id", "uuid", "path", "filename", or %NULL
  * @filter_val: connection to find (connection name, UUID or path)
  * @out_result: if not NULL, attach all matching connection to this
  *   list. If necessary, a new array will be allocated. If the array
@@ -452,6 +455,14 @@ nmc_find_connection (const GPtrArray *connections,
 				goto found;
 		}
 
+		if (NM_IN_STRSET (filter_type, NULL, "filename")) {
+			v = nm_remote_connection_get_filename (NM_REMOTE_CONNECTION (connections->pdata[i]));
+			if (complete && (filter_type || *filter_val))
+				nmc_complete_strings (filter_val, v, NULL);
+			if (nm_streq0 (filter_val, v))
+				goto found;
+		}
+
 		continue;
 found:
 		if (!out_result)
@@ -523,6 +534,14 @@ nmc_find_active_connection (const GPtrArray *active_cons,
 				goto found;
 		}
 
+		if (NM_IN_STRSET (filter_type, NULL, "filename")) {
+			v = nm_remote_connection_get_filename (con);
+			if (complete && (filter_type || *filter_val))
+				nmc_complete_strings (filter_val, v, NULL);
+			if (nm_streq0 (filter_val, v))
+				goto found;
+		}
+
 		if (NM_IN_STRSET (filter_type, NULL, "apath")) {
 			v = nm_object_get_path (NM_OBJECT (candidate));
 			v_num = nm_utils_dbus_path_get_last_component (v);
diff --git a/clients/cli/connections.c b/clients/cli/connections.c
index a4f129b6..1563178d 100644
--- a/clients/cli/connections.c
+++ b/clients/cli/connections.c
@@ -101,27 +101,57 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (vpn_connection_state_to_string, NMVpnConnecti
  * prefers an alias instead of the settings name when in pretty print mode.
  * That is so that we print "wifi" instead of "802-11-wireless" in "nmcli c". */
 static const char *
-connection_type_pretty (const char *type, NMCPrintOutput print_output)
+connection_type_to_display (const char *type, NMMetaAccessorGetType get_type)
 {
 	const NMMetaSettingInfoEditor *editor;
 	int i;
 
-	if (print_output == NMC_PRINT_TERSE)
+	nm_assert (NM_IN_SET (get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE));
+
+	if (!type)
+		return NULL;
+
+	if (get_type != NM_META_ACCESSOR_GET_TYPE_PRETTY)
 		return type;
 
 	for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
 		editor = &nm_meta_setting_infos_editor[i];
-		if (strcmp (type, editor->general->setting_name) == 0) {
-			if (editor->alias)
-				return editor->alias;
-			break;
-		}
+		if (nm_streq (type, editor->general->setting_name))
+			return editor->alias ?: type;
 	}
-
 	return type;
 }
 
-/* Caller has to free the returned string */
+static int
+active_connection_get_state_ord (NMActiveConnection *active)
+{
+	/* returns an integer related to @active's state, that can be used for sorting
+	 * active connections based on their activation state. */
+	if (!active)
+		return -2;
+
+	switch (nm_active_connection_get_state (active)) {
+	case NM_ACTIVE_CONNECTION_STATE_UNKNOWN:      return 0;
+	case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED:  return 1;
+	case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: return 2;
+	case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:   return 3;
+	case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:    return 4;
+	}
+	return -1;
+}
+
+static int
+active_connection_cmp (NMActiveConnection *ac_a, NMActiveConnection *ac_b)
+{
+	NM_CMP_SELF (ac_a, ac_b);
+	NM_CMP_DIRECT (active_connection_get_state_ord (ac_b),
+	               active_connection_get_state_ord (ac_a));
+	NM_CMP_DIRECT_STRCMP0 (nm_active_connection_get_id (ac_a), nm_active_connection_get_id (ac_b));
+	NM_CMP_DIRECT_STRCMP0 (nm_active_connection_get_connection_type (ac_a), nm_active_connection_get_connection_type (ac_b));
+	NM_CMP_DIRECT_STRCMP0 (nm_object_get_path (NM_OBJECT (ac_a)), nm_object_get_path (NM_OBJECT (ac_b)));
+	return 0;
+}
+
 static char *
 get_ac_device_string (NMActiveConnection *active)
 {
@@ -152,41 +182,451 @@ get_ac_device_string (NMActiveConnection *active)
 
 /*****************************************************************************/
 
-const NmcMetaGenericInfo *const nmc_fields_con_show[] = {
-	NMC_META_GENERIC ("NAME"),                  /* 0 */
-	NMC_META_GENERIC ("UUID"),                  /* 1 */
-	NMC_META_GENERIC ("TYPE"),                  /* 2 */
-	NMC_META_GENERIC ("TIMESTAMP"),             /* 3 */
-	NMC_META_GENERIC ("TIMESTAMP-REAL"),        /* 4 */
-	NMC_META_GENERIC ("AUTOCONNECT"),           /* 5 */
-	NMC_META_GENERIC ("AUTOCONNECT-PRIORITY"),  /* 6 */
-	NMC_META_GENERIC ("READONLY"),              /* 7 */
-	NMC_META_GENERIC ("DBUS-PATH"),             /* 8 */
-	NMC_META_GENERIC ("ACTIVE"),                /* 9 */
-	NMC_META_GENERIC ("DEVICE"),                /* 10 */
-	NMC_META_GENERIC ("STATE"),                 /* 11 */
-	NMC_META_GENERIC ("ACTIVE-PATH"),           /* 12 */
-	NMC_META_GENERIC ("SLAVE"),                 /* 13 */
-	NULL,
+/* FIXME: The same or similar code for VPN info appears also in nm-applet (applet-dialogs.c),
+ * and in gnome-control-center as well. It could probably be shared somehow. */
+
+static char *
+get_vpn_connection_type (NMConnection *connection)
+{
+	const char *type, *p;
+
+	/* The service type is in form of "org.freedesktop.NetworkManager.vpnc".
+	 * Extract end part after last dot, e.g. "vpnc"
+	 */
+	type = nm_setting_vpn_get_service_type (nm_connection_get_setting_vpn (connection));
+	p = strrchr (type, '.');
+	return g_strdup (p ? p + 1 : type);
+}
+
+/* VPN parameters can be found at:
+ * http://git.gnome.org/browse/network-manager-openvpn/tree/src/nm-openvpn-service.h
+ * http://git.gnome.org/browse/network-manager-vpnc/tree/src/nm-vpnc-service.h
+ * http://git.gnome.org/browse/network-manager-pptp/tree/src/nm-pptp-service.h
+ * http://git.gnome.org/browse/network-manager-openconnect/tree/src/nm-openconnect-service.h
+ * http://git.gnome.org/browse/network-manager-openswan/tree/src/nm-openswan-service.h
+ * See also 'properties' directory in these plugins.
+ */
+static const gchar *
+find_vpn_gateway_key (const char *vpn_type)
+{
+	if (g_strcmp0 (vpn_type, "openvpn") == 0)     return "remote";
+	if (g_strcmp0 (vpn_type, "vpnc") == 0)        return "IPSec gateway";
+	if (g_strcmp0 (vpn_type, "pptp") == 0)        return "gateway";
+	if (g_strcmp0 (vpn_type, "openconnect") == 0) return "gateway";
+	if (g_strcmp0 (vpn_type, "openswan") == 0)    return "right";
+	if (g_strcmp0 (vpn_type, "libreswan") == 0)   return "right";
+	if (g_strcmp0 (vpn_type, "ssh") == 0)         return "remote";
+	if (g_strcmp0 (vpn_type, "l2tp") == 0)        return "gateway";
+	return "";
+}
+
+static const gchar *
+find_vpn_username_key (const char *vpn_type)
+{
+	if (g_strcmp0 (vpn_type, "openvpn") == 0)     return "username";
+	if (g_strcmp0 (vpn_type, "vpnc") == 0)        return "Xauth username";
+	if (g_strcmp0 (vpn_type, "pptp") == 0)        return "user";
+	if (g_strcmp0 (vpn_type, "openconnect") == 0) return "username";
+	if (g_strcmp0 (vpn_type, "openswan") == 0)    return "leftxauthusername";
+	if (g_strcmp0 (vpn_type, "libreswan") == 0)   return "leftxauthusername";
+	if (g_strcmp0 (vpn_type, "l2tp") == 0)        return "user";
+	return "";
+}
+
+enum VpnDataItem {
+	VPN_DATA_ITEM_GATEWAY,
+	VPN_DATA_ITEM_USERNAME
+};
+
+static const gchar *
+get_vpn_data_item (NMConnection *connection, enum VpnDataItem vpn_data_item)
+{
+	const char *key;
+	gs_free char *type = NULL;
+
+	type = get_vpn_connection_type (connection);
+
+	switch (vpn_data_item) {
+	case VPN_DATA_ITEM_GATEWAY:
+		key = find_vpn_gateway_key (type);
+		break;
+	case VPN_DATA_ITEM_USERNAME:
+		key = find_vpn_username_key (type);
+		break;
+	default:
+		key = "";
+		break;
+	}
+
+	return nm_setting_vpn_get_data_item (nm_connection_get_setting_vpn (connection), key);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	NMConnection *connection;
+	NMActiveConnection *primary_active;
+	GPtrArray *all_active;
+	bool show_active_fields;
+} MetagenConShowRowData;
+
+static MetagenConShowRowData *
+_metagen_con_show_row_data_new_for_connection (NMRemoteConnection *connection, gboolean show_active_fields)
+{
+	MetagenConShowRowData *row_data;
+
+	row_data = g_slice_new0 (MetagenConShowRowData);
+	row_data->connection = g_object_ref (NM_CONNECTION (connection));
+	row_data->show_active_fields = show_active_fields;
+	return row_data;
+}
+
+static MetagenConShowRowData *
+_metagen_con_show_row_data_new_for_active_connection (NMRemoteConnection *connection, NMActiveConnection *active, gboolean show_active_fields)
+{
+	MetagenConShowRowData *row_data;
+
+	row_data = g_slice_new0 (MetagenConShowRowData);
+	if (connection)
+		row_data->connection = g_object_ref (NM_CONNECTION (connection));
+	row_data->primary_active = g_object_ref (active);
+	row_data->show_active_fields = show_active_fields;
+	return row_data;
+}
+
+static void
+_metagen_con_show_row_data_add_active_connection (MetagenConShowRowData *row_data, NMActiveConnection *active)
+{
+	if (!row_data->primary_active) {
+		row_data->primary_active = g_object_ref (active);
+		return;
+	}
+	if (!row_data->all_active) {
+		row_data->all_active = g_ptr_array_new_with_free_func (g_object_unref);
+		g_ptr_array_add (row_data->all_active, g_object_ref (row_data->primary_active));
+	}
+	g_ptr_array_add (row_data->all_active, g_object_ref (active));
+}
+
+static void
+_metagen_con_show_row_data_init_primary_active (MetagenConShowRowData *row_data)
+{
+	NMActiveConnection *ac, *best_ac;
+	guint i;
+
+	if (!row_data->all_active)
+		return;
+
+	best_ac = row_data->all_active->pdata[0];
+	for (i = 1; i < row_data->all_active->len; i++) {
+		ac = row_data->all_active->pdata[i];
+
+		if (active_connection_get_state_ord (ac) > active_connection_get_state_ord (best_ac))
+			best_ac = ac;
+	}
+
+	if (row_data->primary_active != best_ac) {
+		g_object_unref (row_data->primary_active);
+		row_data->primary_active = g_object_ref (best_ac);
+	}
+	g_clear_pointer (&row_data->all_active, g_ptr_array_unref);
+}
+
+static void
+_metagen_con_show_row_data_destroy (gpointer data)
+{
+	MetagenConShowRowData *row_data = data;
+
+	if (!row_data)
+		return;
+
+	g_clear_object (&row_data->connection);
+	g_clear_object (&row_data->primary_active);
+	g_clear_pointer (&row_data->all_active, g_ptr_array_unref);
+	g_slice_free (MetagenConShowRowData, row_data);
+}
+
+static const char *
+_con_show_fcn_get_id (NMConnection *c, NMActiveConnection *ac)
+{
+	NMSettingConnection *s_con = NULL;
+	const char *s;
+
+	if (c)
+		s_con = nm_connection_get_setting_connection (c);
+
+	s = s_con ? nm_setting_connection_get_id (s_con) : NULL;
+	if (!s && ac) {
+		/* note that if we have no s_con, that usually means that the user has no permissions
+		 * to see the connection. We still fall to get the ID from the active-connection,
+		 * which exposes it despite the user having no permissions.
+		 *
+		 * That might be unexpected, because the user is shown an ID, which he later
+		 * is unable to resolve in other operations. */
+		s = nm_active_connection_get_id (ac);
+	}
+	return s;
+}
+
+static const char *
+_con_show_fcn_get_type (NMConnection *c, NMActiveConnection *ac, NMMetaAccessorGetType get_type)
+{
+	NMSettingConnection *s_con = NULL;
+	const char *s;
+
+	if (c)
+		s_con = nm_connection_get_setting_connection (c);
+
+	s = s_con ? nm_setting_connection_get_connection_type (s_con) : NULL;
+	if (!s && ac) {
+		/* see _con_show_fcn_get_id() for why we fallback to get the value
+		 * from @ac. */
+		s = nm_active_connection_get_connection_type (ac);
+	}
+	return connection_type_to_display (s, get_type);
+}
+
+static gconstpointer
+_metagen_con_show_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	const MetagenConShowRowData *row_data = target;
+	NMConnection *c = row_data->connection;
+	NMActiveConnection *ac = row_data->primary_active;
+	NMSettingConnection *s_con = NULL;
+	const char *s;
+	char *s_mut;
+
+	NMC_HANDLE_COLOR (  ac
+	                  ? nmc_active_connection_state_to_color (nm_active_connection_get_state (ac))
+	                  : NM_META_COLOR_CONNECTION_UNKNOWN);
+
+	if (c)
+		s_con = nm_connection_get_setting_connection (c);
+
+	if (!row_data->show_active_fields) {
+		/* we are not supposed to show any fields of the active connection.
+		 * We only tracked the primary_active to get the coloring right.
+		 * From now on, there is no active connection. */
+		ac = NULL;
+
+		/* in this mode, we expect that we are called only with connections that
+		 * have a [connection] setting and a UUID. Otherwise, the connection is
+		 * effectively invisible to the user, and should be hidden.
+		 *
+		 * But in that case, we expect that the caller pre-filtered this row out.
+		 * So assert(). */
+		nm_assert (s_con);
+		nm_assert (nm_setting_connection_get_uuid (s_con));
+	}
+
+	nm_assert (NM_IN_SET (get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE));
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME:
+		return _con_show_fcn_get_id (c, ac);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID:
+		s = s_con ? nm_setting_connection_get_uuid (s_con) : NULL;
+		if (!s && ac) {
+			/* see _con_show_fcn_get_id() for why we fallback to get the value
+			 * from @ac. */
+			s = nm_active_connection_get_uuid (ac);
+		}
+		return s;
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE:
+		return _con_show_fcn_get_type (c, ac, get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP:
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP_REAL:
+		if (!s_con)
+			return NULL;
+		{
+			guint64 timestamp;
+			time_t timestamp_real;
+
+			timestamp = nm_setting_connection_get_timestamp (s_con);
+
+			if (info->info_type == NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP)
+				return (*out_to_free = g_strdup_printf ("%" G_GUINT64_FORMAT, timestamp));
+			else {
+				if (!timestamp) {
+					if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY)
+						return _("never");
+					return "never";
+				}
+				timestamp_real = timestamp;
+				s_mut = g_malloc0 (128);
+				strftime (s_mut, 64, "%c", localtime (&timestamp_real));
+				return (*out_to_free = s_mut);
+			}
+		}
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT:
+		if (!s_con)
+			return NULL;
+		return nmc_meta_generic_get_bool (nm_setting_connection_get_autoconnect (s_con), get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT_PRIORITY:
+		if (!s_con)
+			return NULL;
+		return (*out_to_free = g_strdup_printf ("%d", nm_setting_connection_get_autoconnect_priority (s_con)));
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_READONLY:
+		if (!s_con)
+			return NULL;
+		return nmc_meta_generic_get_bool (nm_setting_connection_get_read_only (s_con), get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_DBUS_PATH:
+		if (!c)
+			return NULL;
+		return nm_connection_get_path (c);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE:
+		return nmc_meta_generic_get_bool (!!ac, get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE:
+		if (ac)
+			return (*out_to_free = get_ac_device_string (ac));
+		return NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE:
+		return nmc_meta_generic_get_str_i18n (ac
+		                                        ? active_connection_state_to_string (nm_active_connection_get_state (ac))
+		                                        : NULL,
+		                                      get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH:
+		if (ac)
+			return nm_object_get_path (NM_OBJECT (ac));
+		return NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE:
+		if (!s_con)
+			return NULL;
+		return nm_setting_connection_get_slave_type (s_con);
+	case NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME:
+		if (!NM_IS_REMOTE_CONNECTION (c))
+			return NULL;
+		return nm_remote_connection_get_filename (NM_REMOTE_CONNECTION (c));
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+const NmcMetaGenericInfo *const metagen_con_show[_NMC_GENERIC_INFO_TYPE_CON_SHOW_NUM + 1] = {
+#define _METAGEN_CON_SHOW(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_show_get_fcn)
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME,                 "NAME"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID,                 "UUID"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE,                 "TYPE"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP,            "TIMESTAMP"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP_REAL,       "TIMESTAMP-REAL"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT,          "AUTOCONNECT"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT_PRIORITY, "AUTOCONNECT-PRIORITY"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_READONLY,             "READONLY"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_DBUS_PATH,            "DBUS-PATH"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE,               "ACTIVE"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE,               "DEVICE"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE,                "STATE"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH,          "ACTIVE-PATH"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE,                "SLAVE"),
+	_METAGEN_CON_SHOW (NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME,             "FILENAME"),
 };
 #define NMC_FIELDS_CON_SHOW_COMMON  "NAME,UUID,TYPE,DEVICE"
 
-const NmcMetaGenericInfo *const nmc_fields_con_active_details_general[] = {
-	NMC_META_GENERIC ("GROUP"),        /* 0 */
-	NMC_META_GENERIC ("NAME"),         /* 1 */
-	NMC_META_GENERIC ("UUID"),         /* 2 */
-	NMC_META_GENERIC ("DEVICES"),      /* 3 */
-	NMC_META_GENERIC ("STATE"),        /* 4 */
-	NMC_META_GENERIC ("DEFAULT"),      /* 5 */
-	NMC_META_GENERIC ("DEFAULT6"),     /* 6 */
-	NMC_META_GENERIC ("SPEC-OBJECT"),  /* 7 */
-	NMC_META_GENERIC ("VPN"),          /* 8 */
-	NMC_META_GENERIC ("DBUS-PATH"),    /* 9 */
-	NMC_META_GENERIC ("CON-PATH"),     /* 10 */
-	NMC_META_GENERIC ("ZONE"),         /* 11 */
-	NMC_META_GENERIC ("MASTER-PATH"),  /* 12 */
-	NULL,
+/*****************************************************************************/
+
+static gconstpointer
+_metagen_con_active_general_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMActiveConnection *ac = target;
+	NMConnection *c;
+	NMSettingConnection *s_con = NULL;
+	NMDevice *dev;
+	guint i;
+	const char *s;
+
+	NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
+
+	nm_assert (NM_IN_SET (get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE));
+
+	c = NM_CONNECTION (nm_active_connection_get_connection (ac));
+	if (c)
+		s_con = nm_connection_get_setting_connection (c);
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NAME:
+		return nm_active_connection_get_id (ac);
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_UUID:
+		return nm_active_connection_get_uuid (ac);
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES:
+		{
+			GString *str = NULL;
+			const GPtrArray *devices;
+
+			s = NULL;
+			devices = nm_active_connection_get_devices (ac);
+			if (devices) {
+				for (i = 0; i < devices->len; i++) {
+					NMDevice *device = devices->pdata[i];
+					const char *iface;
+
+					iface = nm_device_get_iface (device);
+					if (!iface)
+						continue;
+					if (!s) {
+						s = iface;
+						continue;
+					}
+					if (!str)
+						str = g_string_new (s);
+					g_string_append_c (str, ',');
+					g_string_append (str, iface);
+				}
+			}
+			if (str)
+				return (*out_to_free = g_string_free (str, FALSE));
+			return s;
+		}
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_STATE:
+		return nmc_meta_generic_get_str_i18n (active_connection_state_to_string (nm_active_connection_get_state (ac)),
+		                                      get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT:
+		return nmc_meta_generic_get_bool (nm_active_connection_get_default (ac), get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT6:
+		return nmc_meta_generic_get_bool (nm_active_connection_get_default6 (ac), get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_SPEC_OBJECT:
+		return nm_active_connection_get_specific_object_path (ac);
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_VPN:
+		return nmc_meta_generic_get_bool (NM_IS_VPN_CONNECTION (ac), get_type);
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH:
+		return nm_object_get_path (NM_OBJECT (ac));
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH:
+		return c ? nm_connection_get_path (c) : NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE:
+		/* this is really ugly, because the zone is not a property of the active-connection,
+		 * but the settings-connection profile. There is no guarantee, that they agree. */
+		return s_con ? nm_setting_connection_get_zone (s_con) : NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH:
+		dev = nm_active_connection_get_master (ac);
+		return dev ? nm_object_get_path (NM_OBJECT (dev)) : NULL;
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+const NmcMetaGenericInfo *const metagen_con_active_general[_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NUM + 1] = {
+#define _METAGEN_CON_ACTIVE_GENERAL(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_active_general_get_fcn)
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NAME,        "NAME"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_UUID,        "UUID"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES,     "DEVICES"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_STATE,       "STATE"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT,     "DEFAULT"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT6,    "DEFAULT6"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_SPEC_OBJECT, "SPEC-OBJECT"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_VPN,         "VPN"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH,   "DBUS-PATH"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH,    "CON-PATH"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE,        "ZONE"),
+	_METAGEN_CON_ACTIVE_GENERAL (NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH, "MASTER-PATH"),
 };
+
+/*****************************************************************************/
+
 #define NMC_FIELDS_SETTINGS_NAMES_ALL    NM_SETTING_CONNECTION_SETTING_NAME","\
                                          NM_SETTING_WIRED_SETTING_NAME","\
                                          NM_SETTING_802_1X_SETTING_NAME","\
@@ -237,7 +677,7 @@ const NmcMetaGenericInfo *const nmc_fields_con_active_details_vpn[] = {
 };
 
 const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = {
-	NMC_META_GENERIC_WITH_NESTED ("GENERAL", nmc_fields_con_active_details_general + 1), /* 0 */
+	NMC_META_GENERIC_WITH_NESTED ("GENERAL", metagen_con_active_general),                /* 0 */
 	NMC_META_GENERIC_WITH_NESTED ("IP4",     metagen_ip4_config),                        /* 1 */
 	NMC_META_GENERIC_WITH_NESTED ("DHCP4",   nmc_fields_dhcp_config + 1),                /* 2 */
 	NMC_META_GENERIC_WITH_NESTED ("IP6",     nmc_fields_ip6_config + 1),                 /* 3 */
@@ -595,36 +1035,42 @@ construct_header_name (const char *base, const char *spec)
 	return g_strdup_printf ("%s (%s)", base, spec);
 }
 
+static int
+get_ac_for_connection_cmp (gconstpointer pa, gconstpointer pb, gpointer user_data)
+{
+	NMActiveConnection *ac_a = *((NMActiveConnection *const*) pa);
+	NMActiveConnection *ac_b = *((NMActiveConnection *const*) pb);
+
+	return active_connection_cmp (ac_a, ac_b);
+}
+
 static NMActiveConnection *
 get_ac_for_connection (const GPtrArray *active_cons, NMConnection *connection, GPtrArray **out_result)
 {
-	const char *con_path, *ac_con_path;
 	guint i;
 	NMActiveConnection *best_candidate = NULL;
 	GPtrArray *result = out_result ? *out_result : NULL;
 
-	con_path = nm_connection_get_path (connection);
 	for (i = 0; i < active_cons->len; i++) {
 		NMActiveConnection *candidate = g_ptr_array_index (active_cons, i);
 		NMRemoteConnection *con;
 
 		con = nm_active_connection_get_connection (candidate);
-		if (NM_CONNECTION (con) != connection) {
-			/* also compare the D-Bus paths. Why? I don't know. */
-			ac_con_path = con ? nm_connection_get_path (NM_CONNECTION (con)) : NULL;
-			if (!nm_streq0 (ac_con_path, con_path))
-				continue;
-		}
+		if (NM_CONNECTION (con) != connection)
+			continue;
 
 		if (!out_result)
 			return candidate;
-		if (!best_candidate)
-			best_candidate = candidate;
 		if (!result)
 			result = g_ptr_array_new_with_free_func (g_object_unref);
 		g_ptr_array_add (result, g_object_ref (candidate));
 	}
 
+	if (result) {
+		g_ptr_array_sort_with_data (result, get_ac_for_connection_cmp, NULL);
+		best_candidate = result->pdata[0];
+	}
+
 	NM_SET_OUT (out_result, result);
 	return best_candidate;
 }
@@ -767,178 +1213,6 @@ nmc_active_connection_state_to_color (NMActiveConnectionState state)
 		return NM_META_COLOR_CONNECTION_UNKNOWN;
 }
 
-static void
-fill_output_connection (NMConnection *connection, NMClient *client, NMCPrintOutput print_output,
-                        GPtrArray *output_data, gboolean active_only)
-{
-	NMSettingConnection *s_con;
-	guint64 timestamp;
-	time_t timestamp_real;
-	char *timestamp_str;
-	char *timestamp_real_str = "";
-	char *prio_str;
-	NmcOutputField *arr;
-	NMActiveConnection *ac = NULL;
-	const char *ac_path = NULL;
-	const char *ac_state = NULL;
-	NMActiveConnectionState ac_state_int = NM_ACTIVE_CONNECTION_STATE_UNKNOWN;
-	char *ac_dev = NULL;
-	NMMetaColor color;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	ac = get_ac_for_connection (nm_client_get_active_connections (client), connection, NULL);
-	if (active_only && !ac)
-		return;
-
-	if (ac) {
-		ac_path = nm_object_get_path (NM_OBJECT (ac));
-		ac_state_int = nm_active_connection_get_state (ac);
-		ac_state = gettext (active_connection_state_to_string (ac_state_int));
-		ac_dev = get_ac_device_string (ac);
-	}
-
-	/* Obtain field values */
-	timestamp = nm_setting_connection_get_timestamp (s_con);
-	timestamp_str = g_strdup_printf ("%" G_GUINT64_FORMAT, timestamp);
-	if (timestamp) {
-		timestamp_real = timestamp;
-		timestamp_real_str = g_malloc0 (64);
-		strftime (timestamp_real_str, 64, "%c", localtime (&timestamp_real));
-	}
-	prio_str = g_strdup_printf ("%u", nm_setting_connection_get_autoconnect_priority (s_con));
-
-	arr = nmc_dup_fields_array ((const NMMetaAbstractInfo *const*) nmc_fields_con_show, 0);
-
-	/* Show active connections in color */
-	color = nmc_active_connection_state_to_color (ac_state_int);
-	set_val_color_all (arr, color);
-
-	set_val_strc (arr, 0, nm_setting_connection_get_id (s_con));
-	set_val_strc (arr, 1, nm_setting_connection_get_uuid (s_con));
-	set_val_strc (arr, 2, connection_type_pretty (nm_setting_connection_get_connection_type (s_con), print_output));
-	set_val_str  (arr, 3, timestamp_str);
-	set_val_str  (arr, 4, timestamp ? timestamp_real_str : g_strdup (_("never")));
-	set_val_strc (arr, 5, nm_setting_connection_get_autoconnect (s_con) ? _("yes") : _("no"));
-	set_val_str  (arr, 6, prio_str);
-	set_val_strc (arr, 7, nm_setting_connection_get_read_only (s_con) ? _("yes") : _("no"));
-	set_val_strc (arr, 8, nm_connection_get_path (connection));
-	set_val_strc (arr, 9, ac ? _("yes") : _("no"));
-	set_val_str  (arr, 10, ac_dev);
-	set_val_strc (arr, 11, ac_state);
-	set_val_strc (arr, 12, ac_path);
-	set_val_strc (arr, 13, nm_setting_connection_get_slave_type (s_con));
-
-	g_ptr_array_add (output_data, arr);
-}
-
-static void
-fill_output_connection_for_invisible (NMActiveConnection *ac, NMCPrintOutput print_output, GPtrArray *output_data)
-{
-	NmcOutputField *arr;
-	const char *ac_path = NULL;
-	const char *ac_state = NULL;
-	char *name, *ac_dev = NULL;
-
-	name = g_strdup_printf ("<invisible> %s", nm_active_connection_get_id (ac));
-	ac_path = nm_object_get_path (NM_OBJECT (ac));
-	ac_state = active_connection_state_to_string (nm_active_connection_get_state (ac));
-	ac_dev = get_ac_device_string (ac);
-
-	arr = nmc_dup_fields_array ((const NMMetaAbstractInfo *const*) nmc_fields_con_show, 0);
-
-	set_val_str  (arr, 0, name);
-	set_val_strc (arr, 1, nm_active_connection_get_uuid (ac));
-	set_val_strc (arr, 2, connection_type_pretty (nm_active_connection_get_connection_type (ac), print_output));
-	set_val_strc (arr, 3, NULL);
-	set_val_strc (arr, 4, NULL);
-	set_val_strc (arr, 5, NULL);
-	set_val_strc (arr, 6, NULL);
-	set_val_strc (arr, 7, NULL);
-	set_val_strc (arr, 8, NULL);
-	set_val_strc (arr, 9, _("yes"));
-	set_val_str  (arr, 10, ac_dev);
-	set_val_strc (arr, 11, ac_state);
-	set_val_strc (arr, 12, ac_path);
-	set_val_strc (arr, 13, NULL);
-
-	set_val_color_all (arr, NM_META_COLOR_CONNECTION_INVISIBLE);
-
-	g_ptr_array_add (output_data, arr);
-}
-
-static void
-fill_output_active_connection (NMActiveConnection *active,
-                               GPtrArray *output_data,
-                               gboolean with_group,
-                               guint32 o_flags)
-{
-	NMRemoteConnection *con;
-	NMSettingConnection *s_con = NULL;
-	const GPtrArray *devices;
-	GString *dev_str;
-	NMActiveConnectionState state;
-	NMDevice *master;
-	const char *con_path = NULL, *con_zone = NULL;
-	int i;
-	const NMMetaAbstractInfo *const*tmpl;
-	NmcOutputField *arr;
-	int idx_start = with_group ? 0 : 1;
-
-	con = nm_active_connection_get_connection (active);
-	if (con) {
-		con_path = nm_connection_get_path (NM_CONNECTION (con));
-		s_con = nm_connection_get_setting_connection (NM_CONNECTION (con));
-		g_assert (s_con);
-		con_zone = nm_setting_connection_get_zone (s_con);
-	}
-
-	state = nm_active_connection_get_state (active);
-	master = nm_active_connection_get_master (active);
-
-	/* Get devices of the active connection */
-	dev_str = g_string_new (NULL);
-	devices = nm_active_connection_get_devices (active);
-	for (i = 0; i < devices->len; i++) {
-		NMDevice *device = g_ptr_array_index (devices, i);
-		const char *dev_iface = nm_device_get_iface (device);
-
-		if (dev_iface) {
-			g_string_append (dev_str, dev_iface);
-			g_string_append_c (dev_str, ',');
-		}
-	}
-	if (dev_str->len > 0)
-		g_string_truncate (dev_str, dev_str->len - 1);  /* Cut off last ',' */
-
-	tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_con_active_details_general;
-	if (!with_group)
-		tmpl++;
-
-	/* Fill field values */
-	arr = nmc_dup_fields_array (tmpl, o_flags);
-	if (with_group)
-		set_val_strc (arr, 0, nmc_fields_con_active_details_groups[0]->name);
-	set_val_strc (arr, 1-idx_start, nm_active_connection_get_id (active));
-	set_val_strc (arr, 2-idx_start, nm_active_connection_get_uuid (active));
-	set_val_str  (arr, 3-idx_start, dev_str->str);
-	set_val_strc (arr, 4-idx_start, active_connection_state_to_string (state));
-	set_val_strc (arr, 5-idx_start, nm_active_connection_get_default (active) ? _("yes") : _("no"));
-	set_val_strc (arr, 6-idx_start, nm_active_connection_get_default6 (active) ? _("yes") : _("no"));
-	set_val_strc (arr, 7-idx_start, nm_active_connection_get_specific_object_path (active));
-	set_val_strc (arr, 8-idx_start, NM_IS_VPN_CONNECTION (active) ? _("yes") : _("no"));
-	set_val_strc (arr, 9-idx_start, nm_object_get_path (NM_OBJECT (active)));
-	set_val_strc (arr, 10-idx_start, con_path);
-	set_val_strc (arr, 11-idx_start, con_zone);
-	set_val_strc (arr, 12-idx_start, master ? nm_object_get_path (NM_OBJECT (master)) : NULL);
-	set_val_strc (arr, 13-idx_start, s_con ? nm_setting_connection_get_slave_type (s_con) : NULL);
-
-	g_ptr_array_add (output_data, arr);
-
-	g_string_free (dev_str, FALSE);
-}
-
 typedef struct {
 	char **array;
 	guint32 idx;
@@ -952,85 +1226,6 @@ fill_vpn_data_item (const char *key, const char *value, gpointer user_data)
 	info->array[info->idx++] = g_strdup_printf ("%s = %s", key, value);
 }
 
-// FIXME: The same or similar code for VPN info appears also in nm-applet (applet-dialogs.c),
-// and in gnome-control-center as well. It could probably be shared somehow.
-static char *
-get_vpn_connection_type (NMConnection *connection)
-{
-	const char *type, *p;
-
-	/* The service type is in form of "org.freedesktop.NetworkManager.vpnc".
-	 * Extract end part after last dot, e.g. "vpnc"
-	 */
-	type = nm_setting_vpn_get_service_type (nm_connection_get_setting_vpn (connection));
-	p = strrchr (type, '.');
-	return g_strdup (p ? p + 1 : type);
-}
-
-/* VPN parameters can be found at:
- * http://git.gnome.org/browse/network-manager-openvpn/tree/src/nm-openvpn-service.h
- * http://git.gnome.org/browse/network-manager-vpnc/tree/src/nm-vpnc-service.h
- * http://git.gnome.org/browse/network-manager-pptp/tree/src/nm-pptp-service.h
- * http://git.gnome.org/browse/network-manager-openconnect/tree/src/nm-openconnect-service.h
- * http://git.gnome.org/browse/network-manager-openswan/tree/src/nm-openswan-service.h
- * See also 'properties' directory in these plugins.
- */
-static const gchar *
-find_vpn_gateway_key (const char *vpn_type)
-{
-	if (g_strcmp0 (vpn_type, "openvpn") == 0)     return "remote";
-	if (g_strcmp0 (vpn_type, "vpnc") == 0)        return "IPSec gateway";
-	if (g_strcmp0 (vpn_type, "pptp") == 0)        return "gateway";
-	if (g_strcmp0 (vpn_type, "openconnect") == 0) return "gateway";
-	if (g_strcmp0 (vpn_type, "openswan") == 0)    return "right";
-	if (g_strcmp0 (vpn_type, "libreswan") == 0)   return "right";
-	if (g_strcmp0 (vpn_type, "ssh") == 0)         return "remote";
-	if (g_strcmp0 (vpn_type, "l2tp") == 0)        return "gateway";
-	return "";
-}
-
-static const gchar *
-find_vpn_username_key (const char *vpn_type)
-{
-	if (g_strcmp0 (vpn_type, "openvpn") == 0)     return "username";
-	if (g_strcmp0 (vpn_type, "vpnc") == 0)        return "Xauth username";
-	if (g_strcmp0 (vpn_type, "pptp") == 0)        return "user";
-	if (g_strcmp0 (vpn_type, "openconnect") == 0) return "username";
-	if (g_strcmp0 (vpn_type, "openswan") == 0)    return "leftxauthusername";
-	if (g_strcmp0 (vpn_type, "libreswan") == 0)   return "leftxauthusername";
-	if (g_strcmp0 (vpn_type, "l2tp") == 0)        return "user";
-	return "";
-}
-
-enum VpnDataItem {
-	VPN_DATA_ITEM_GATEWAY,
-	VPN_DATA_ITEM_USERNAME
-};
-
-static const gchar *
-get_vpn_data_item (NMConnection *connection, enum VpnDataItem vpn_data_item)
-{
-	const char *key;
-	gs_free char *type = NULL;
-
-	type = get_vpn_connection_type (connection);
-
-	switch (vpn_data_item) {
-	case VPN_DATA_ITEM_GATEWAY:
-		key = find_vpn_gateway_key (type);
-		break;
-	case VPN_DATA_ITEM_USERNAME:
-		key = find_vpn_username_key (type);
-		break;
-	default:
-		key = "";
-		break;
-	}
-
-	return nm_setting_vpn_get_data_item (nm_connection_get_setting_vpn (connection), key);
-}
-/* FIXME end */
-
 static gboolean
 nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 {
@@ -1083,29 +1278,27 @@ nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 		int group_idx = g_array_index (print_groups, int, i);
 		char *group_fld = (char *) g_ptr_array_index (group_fields, i);
 
-		if (nmc->nmc_config.print_output != NMC_PRINT_TERSE && !nmc->nmc_config.multiline_output && was_output)
-			g_print ("\n"); /* Empty line */
+		if (   nmc->nmc_config.print_output != NMC_PRINT_TERSE
+		    && !nmc->nmc_config.multiline_output
+		    && was_output)
+			g_print ("\n");
 
 		was_output = FALSE;
 
-		/* GENERAL */
-		if (strcasecmp (nmc_fields_con_active_details_groups[group_idx]->name, nmc_fields_con_active_details_groups[0]->name) == 0) {
-			NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
-
-			/* Add field names */
-			tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_con_active_details_general;
-			out_indices = parse_output_fields (group_fld,
-			                                   tmpl, FALSE, NULL, NULL);
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_FIELD_NAMES);
-			g_ptr_array_add (out.output_data, arr);
-
-			/* Fill in values */
-			fill_output_active_connection (acon, out.output_data, TRUE, NMC_OF_FLAG_SECTION_PREFIX);
+		if (nmc_fields_con_active_details_groups[group_idx]->nested == metagen_con_active_general) {
+			gs_free char *f = NULL;
 
-			print_data_prepare_width (out.output_data);
-			print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
+			if (group_fld)
+				f = g_strdup_printf ("GENERAL.%s", group_fld);
 
+			nmc_print (&nmc->nmc_config,
+			           (gpointer[]) { acon, NULL },
+			           NULL,
+			           NMC_META_GENERIC_GROUP ("GENERAL", metagen_con_active_general, N_("GROUP")),
+			           f,
+			           NULL);
 			was_output = TRUE;
+			continue;
 		}
 
 		/* IP4 */
@@ -1347,154 +1540,220 @@ typedef enum {
 typedef struct {
 	NmCli *nmc;
 	const GArray *order;
-} NmcSortInfo;
+	gboolean show_active_fields;
+} ConShowSortInfo;
 
 static int
-compare_connections (gconstpointer a, gconstpointer b, gpointer user_data)
-{
-	NMConnection *ca = *(NMConnection **) a;
-	NMConnection *cb = *(NMConnection **) b;
-	const NmcSortInfo *info = user_data;
-	NMActiveConnection *aca, *acb;
-	const NmcSortOrder *order_arr;
-	guint i, order_len;
-	const char *tmp1, *tmp2;
-	unsigned long tmp1_int, tmp2_int;
-
-	if (info->order) {
-		order_arr = &g_array_index (info->order, NmcSortOrder, 0);
-		order_len = info->order->len;
-	} else {
-		static const NmcSortOrder def[] = { NMC_SORT_ACTIVE, NMC_SORT_NAME, NMC_SORT_PATH };
+con_show_get_items_cmp (gconstpointer pa, gconstpointer pb, gpointer user_data)
+{
+	const ConShowSortInfo *sort_info = user_data;
+	const MetagenConShowRowData *row_data_a = *((const MetagenConShowRowData *const*) pa);
+	const MetagenConShowRowData *row_data_b = *((const MetagenConShowRowData *const*) pb);
+	NMConnection *c_a = row_data_a->connection;
+	NMConnection *c_b = row_data_b->connection;
+	NMActiveConnection *ac_a = row_data_a->primary_active;
+	NMActiveConnection *ac_b = row_data_b->primary_active;
+	NMActiveConnection *ac_a_effective = sort_info->show_active_fields ? ac_a : NULL;
+	NMActiveConnection *ac_b_effective = sort_info->show_active_fields ? ac_b : NULL;
+
+	/* first sort active-connections which are invisible, i.e. that have no connection */
+	if (!c_a && c_b)
+		return -1;
+	if (!c_b && c_a)
+		return 1;
+
+	/* we have two connections... */
+	if (c_a && c_b && c_a != c_b) {
+		const NmcSortOrder *order_arr;
+		guint i, order_len;
+		NMMetaAccessorGetType get_type = nmc_print_output_to_accessor_get_type (sort_info->nmc->nmc_config.print_output);
+
+		if (sort_info->order) {
+			order_arr = &g_array_index (sort_info->order, NmcSortOrder, 0);
+			order_len = sort_info->order->len;
+		} else {
+			static const NmcSortOrder def[] = { NMC_SORT_ACTIVE, NMC_SORT_NAME, NMC_SORT_PATH };
 
-		order_arr = def;
-		order_len = G_N_ELEMENTS (def);
-	}
+			/* Note: the default order does not consider whether a column is shown.
+			 *       That means, the selection of the output fields, does not affect the
+			 *       order (although there could be an argument that it should). */
+			order_arr = def;
+			order_len = G_N_ELEMENTS (def);
+		}
 
-	for (i = 0; i < order_len; i++) {
-		NmcSortOrder item = order_arr[i];
-		int cmp = 0;
+		for (i = 0; i < order_len; i++) {
+			NmcSortOrder item = order_arr[i];
 
-		switch (item) {
-		case NMC_SORT_ACTIVE:
-		case NMC_SORT_ACTIVE_INV:
-			aca = get_ac_for_connection (nm_client_get_active_connections (info->nmc->client), ca, NULL);
-			acb = get_ac_for_connection (nm_client_get_active_connections (info->nmc->client), cb, NULL);
-			cmp = (aca && !acb) ? -1 : (!aca && acb) ? 1 : 0;
-			if (item == NMC_SORT_ACTIVE_INV)
-				cmp = -(cmp);
-			break;
-		case NMC_SORT_TYPE:
-		case NMC_SORT_TYPE_INV:
-			cmp = g_strcmp0 (nm_connection_get_connection_type (ca),
-			                 nm_connection_get_connection_type (cb));
-			if (item == NMC_SORT_TYPE_INV)
-				cmp = -(cmp);
-			break;
-		case NMC_SORT_NAME:
-		case NMC_SORT_NAME_INV:
-			cmp = g_strcmp0 (nm_connection_get_id (ca),
-			                 nm_connection_get_id (cb));
-			if (item == NMC_SORT_NAME_INV)
-				cmp = -(cmp);
-			break;
-		case NMC_SORT_PATH:
-		case NMC_SORT_PATH_INV:
-			tmp1 = nm_connection_get_path (ca);
-			tmp2 = nm_connection_get_path (cb);
-			tmp1 = tmp1 ? strrchr (tmp1, '/') : "0";
-			tmp2 = tmp2 ? strrchr (tmp2, '/') : "0";
-			nmc_string_to_uint (tmp1 ? tmp1+1 : "0", FALSE, 0, 0, &tmp1_int);
-			nmc_string_to_uint (tmp2 ? tmp2+1 : "0", FALSE, 0, 0, &tmp2_int);
-			cmp = (int) tmp1_int - tmp2_int;
-			if (item == NMC_SORT_PATH_INV)
-				cmp = -(cmp);
-			break;
-		default:
-			cmp = 0;
-			break;
-		}
-		if (cmp != 0)
-			return cmp;
-	}
+			switch (item) {
 
-	return 0;
-}
+			case NMC_SORT_ACTIVE:
+				NM_CMP_DIRECT (active_connection_get_state_ord (ac_b),
+				               active_connection_get_state_ord (ac_a));
+				break;
+			case NMC_SORT_ACTIVE_INV:
+				NM_CMP_DIRECT (active_connection_get_state_ord (ac_a),
+				               active_connection_get_state_ord (ac_b));
+				break;
 
-static GPtrArray *
-sort_connections (const GPtrArray *cons, NmCli *nmc, const GArray *order)
-{
-	GPtrArray *sorted;
-	int i;
-	NmcSortInfo compare_info;
+			case NMC_SORT_TYPE:
+				NM_CMP_DIRECT_STRCMP0 (_con_show_fcn_get_type (c_a, ac_a_effective, get_type),
+				                       _con_show_fcn_get_type (c_b, ac_b_effective, get_type));
+				break;
+			case NMC_SORT_TYPE_INV:
+				NM_CMP_DIRECT_STRCMP0 (_con_show_fcn_get_type (c_b, ac_b_effective, get_type),
+				                       _con_show_fcn_get_type (c_a, ac_a_effective, get_type));
+				break;
 
-	if (!cons)
-		return NULL;
+			case NMC_SORT_NAME:
+				NM_CMP_RETURN (nm_utf8_collate0 (_con_show_fcn_get_id (c_a, ac_a_effective),
+				                                 _con_show_fcn_get_id (c_b, ac_b_effective)));
+				break;
+			case NMC_SORT_NAME_INV:
+				NM_CMP_RETURN (nm_utf8_collate0 (_con_show_fcn_get_id (c_b, ac_b_effective),
+				                                 _con_show_fcn_get_id (c_a, ac_a_effective)));
+				break;
 
-	compare_info.nmc = nmc;
-	compare_info.order = order;
+			case NMC_SORT_PATH:
+				NM_CMP_RETURN (nm_utils_dbus_path_cmp (nm_connection_get_path (c_a), nm_connection_get_path (c_b)));
+				break;
 
-	sorted = g_ptr_array_sized_new (cons->len);
-	for (i = 0; i < cons->len; i++)
-		g_ptr_array_add (sorted, cons->pdata[i]);
-	g_ptr_array_sort_with_data (sorted, compare_connections, &compare_info);
-	return sorted;
-}
+			case NMC_SORT_PATH_INV:
+				NM_CMP_RETURN (nm_utils_dbus_path_cmp (nm_connection_get_path (c_b), nm_connection_get_path (c_a)));
+				break;
 
-static int
-compare_ac_connections (gconstpointer a, gconstpointer b, gpointer user_data)
-{
-	NMActiveConnection *ca = *(NMActiveConnection **)a;
-	NMActiveConnection *cb = *(NMActiveConnection **)b;
-	int cmp;
+			default:
+				nm_assert_not_reached ();
+				break;
+			}
+		}
 
-	/* Sort states first */
-	cmp = nm_active_connection_get_state (cb) - nm_active_connection_get_state (ca);
-	if (cmp != 0)
-		return cmp;
+		NM_CMP_DIRECT_STRCMP0 (nm_connection_get_uuid (c_a),
+		                       nm_connection_get_uuid (c_b));
+		NM_CMP_DIRECT_STRCMP0 (nm_connection_get_path (c_a),
+		                       nm_connection_get_path (c_b));
 
-	cmp = g_strcmp0 (nm_active_connection_get_id (ca),
-	                 nm_active_connection_get_id (cb));
-	if (cmp != 0)
-		return cmp;
+		/* This line is not expected to be reached, because there shouldn't be two
+		 * different connections with the same path. Anyway, fall-through and compare by
+		 * active connections... */
+	}
 
-	return g_strcmp0 (nm_active_connection_get_connection_type (ca),
-	                  nm_active_connection_get_connection_type (cb));
+	return active_connection_cmp (ac_a, ac_b);
 }
 
 static GPtrArray *
-get_invisible_active_connections (NmCli *nmc)
-{
-	const GPtrArray *acons;
-	const GPtrArray *connections;
-	GPtrArray *invisibles;
-	int a, c;
+con_show_get_items (NmCli *nmc, gboolean active_only, gboolean show_active_fields, GArray *order)
+{
+	gs_unref_hashtable GHashTable *row_hash = NULL;
+	GHashTableIter hiter;
+	GPtrArray *result;
+	const GPtrArray *arr;
+	NMRemoteConnection *c;
+	MetagenConShowRowData *row_data;
+	guint i;
+	const ConShowSortInfo sort_info = {
+		.nmc = nmc,
+		.order = order,
+		.show_active_fields = show_active_fields,
+	};
 
-	g_return_val_if_fail (nmc, NULL);
+	row_hash = g_hash_table_new (nm_direct_hash, NULL);
+
+	arr = nm_client_get_connections (nmc->client);
+	for (i = 0; i < arr->len; i++) {
+		/* Note: libnm will not expose connection that are invisible
+		 * to the user but currently inactive.
+		 *
+		 * That differs from get-active-connection(). If an invisible connection
+		 * is active, we can get its NMActiveConnection. We can even obtain
+		 * the corresponding NMRemoteConnection (although, of course it has
+		 * no visible settings).
+		 *
+		 * I think this inconsistency is a bug in libnm. Anyway, the result is,
+		 * that we print invisible connections if they are active, but otherwise
+		 * we exclude them. */
+		c = arr->pdata[i];
+		g_hash_table_insert (row_hash,
+		                     c,
+		                     _metagen_con_show_row_data_new_for_connection (c,
+		                                                                    show_active_fields));
+	}
+
+	arr = nm_client_get_active_connections (nmc->client);
+	for (i = 0; i < arr->len; i++) {
+		NMActiveConnection *ac = arr->pdata[i];
+
+		c = nm_active_connection_get_connection (ac);
+		if (!show_active_fields && !c) {
+			/* the active connection has no connection, and we don't show
+			 * any active fields. Skip this row. */
+			continue;
+		}
 
-	invisibles = g_ptr_array_new ();
-	acons = nm_client_get_active_connections (nmc->client);
-	connections = nm_client_get_connections (nmc->client);
-	for (a = 0; a < acons->len; a++) {
-		gboolean found = FALSE;
-		NMActiveConnection *acon = g_ptr_array_index (acons, a);
-		const char *a_uuid = nm_active_connection_get_uuid (acon);
+		row_data =   c
+		           ? g_hash_table_lookup (row_hash, c)
+		           : NULL;
+
+		if (show_active_fields || !c) {
+			/* the active connection either has no connection (in which we create a
+			 * connection-less row), or we are interested in showing each active
+			 * connection in its own row. Add a row. */
+			if (row_data) {
+				/* we create a rowdata for this connection earlier. We drop it, because this
+				 * connection is tracked via the rowdata of the active connection. */
+				g_hash_table_remove (row_hash, c);
+				_metagen_con_show_row_data_destroy (row_data);
+			}
+			row_data = _metagen_con_show_row_data_new_for_active_connection (c, ac, show_active_fields);
+			g_hash_table_insert (row_hash, ac, row_data);
+			continue;
+		}
 
-		for (c = 0; c < connections->len; c++) {
-			NMConnection *con = g_ptr_array_index (connections, c);
-			const char *c_uuid = nm_connection_get_uuid (con);
+		/* we add the active connection to the row for the referenced
+		 * connection. We need to group them this way, to print the proper
+		 * color (activated or not) based on primary_active. */
+		if (!row_data) {
+			/* this is unexpected. The active connection references a connection that
+			 * seemingly no longer exists. It's a bug in libnm. Add a row nontheless. */
+			row_data = _metagen_con_show_row_data_new_for_connection (c, show_active_fields);
+			g_hash_table_insert (row_hash, c, row_data);
+		}
+		_metagen_con_show_row_data_add_active_connection (row_data, ac);
+	}
 
-			if (strcmp (a_uuid, c_uuid) == 0) {
-				found = TRUE;
-				break;
-			}
+	result = g_ptr_array_new_with_free_func (_metagen_con_show_row_data_destroy);
+
+	g_hash_table_iter_init (&hiter, row_hash);
+	while (g_hash_table_iter_next (&hiter, NULL, (gpointer *) &row_data)) {
+		if (   active_only
+		    && !row_data->primary_active) {
+			/* We only print connections that are active. Skip this row. */
+			_metagen_con_show_row_data_destroy (row_data);
+			continue;
 		}
-		/* Active connection is not in connections array, add it to  */
-		if (!found)
-			g_ptr_array_add (invisibles, acon);
+		if (!show_active_fields) {
+			NMSettingConnection *s_con;
+
+			nm_assert (NM_IS_REMOTE_CONNECTION (row_data->connection));
+			s_con = nm_connection_get_setting_connection (row_data->connection);
+			if (   !s_con
+			    || !nm_setting_connection_get_uuid (s_con)) {
+				/* we are in a mode, where we only print rows for connection.
+				 * For that we require that all rows are visible to the user,
+				 * meaning: the have a [connection] setting and a UUID.
+				 *
+				 * Otherwise, this connection is likely invisible to the user.
+				 * Skip it. */
+				_metagen_con_show_row_data_destroy (row_data);
+				continue;
+			}
+			_metagen_con_show_row_data_init_primary_active (row_data);
+		} else
+			nm_assert (!row_data->all_active);
+		g_ptr_array_add (result, row_data);
 	}
-	g_ptr_array_sort_with_data (invisibles, compare_ac_connections, NULL);
-	return invisibles;
+
+	g_ptr_array_sort_with_data (result, con_show_get_items_cmp, (gpointer) &sort_info);
+	return result;
 }
 
 static GArray *
@@ -1579,9 +1838,9 @@ get_connection (NmCli *nmc,
 	}
 
 	if (*argc == 1 && nmc->complete)
-		nmc_complete_strings (**argv, "id", "uuid", "path", NULL);
+		nmc_complete_strings (**argv, "id", "uuid", "path", "filename", NULL);
 
-	if (NM_IN_STRSET (**argv, "id", "uuid", "path")) {
+	if (NM_IN_STRSET (**argv, "id", "uuid", "path", "filename")) {
 		if (*argc == 1) {
 			if (!nmc->complete) {
 				g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
@@ -1616,10 +1875,9 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 	gs_free_error GError *err = NULL;
 	gs_free char *profile_flds = NULL;
 	gs_free char *active_flds = NULL;
-	GPtrArray *invisibles, *sorted_cons;
 	gboolean active_only = FALSE;
 	gs_unref_array GArray *order = NULL;
-	guint i, j;
+	guint i;
 	int option;
 
 	/* check connection show options [--active] [--order <order spec>] */
@@ -1647,50 +1905,59 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 	}
 
 	if (argc == 0) {
-		const GPtrArray *connections;
 		const char *fields_str = NULL;
-		char *fields_common = NMC_FIELDS_CON_SHOW_COMMON;
-		const NMMetaAbstractInfo *const*tmpl;
-		NmcOutputField *arr;
-		NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
+		gs_unref_ptrarray GPtrArray *items = NULL;
+		gs_free NMMetaSelectionResultList *selection = NULL;
+		gboolean show_active_fields = TRUE;
 
 		if (nmc->complete)
 			goto finish;
 
 		if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0)
-			fields_str = fields_common;
+			fields_str = NMC_FIELDS_CON_SHOW_COMMON;
 		else if (!nmc->required_fields || strcasecmp (nmc->required_fields, "all") == 0) {
 		} else
 			fields_str = nmc->required_fields;
 
-		tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_con_show;
-		out_indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &err);
-		if (err)
-			goto finish;
-
-		/* Add headers */
-		arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
-		g_ptr_array_add (out.output_data, arr);
-
-		/* There might be active connections not present in connection list
-		 * (e.g. private connections of a different user). Show them as well. */
-		invisibles = get_invisible_active_connections (nmc);
-		for (i = 0; i < invisibles->len; i++)
-			fill_output_connection_for_invisible (invisibles->pdata[i], nmc->nmc_config.print_output, out.output_data);
-		g_ptr_array_free (invisibles, TRUE);
+		/* determine whether the user wants to see any fields that are related to active-connections
+		 * (e.g. the apath, the current state, or the device where the profile is active).
+		 *
+		 * If that's the case, then we will show one line for each active connection. In case
+		 * a profile has multiple active connections, it will be listed multiple times.
+		 * If that's not the case, we filter out these duplicate lines. */
+		selection = nm_meta_selection_create_parse_list ((const NMMetaAbstractInfo *const*) metagen_con_show,
+		                                                 NULL,
+		                                                 fields_str,
+		                                                 FALSE,
+		                                                 NULL);
+		if (selection && selection->num > 0) {
+			show_active_fields = FALSE;
+			for (i = 0; i < selection->num; i++) {
+				const NmcMetaGenericInfo *info = (const NmcMetaGenericInfo *) selection->items[i].info;
+
+				if (NM_IN_SET (info->info_type, NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE,
+				                                NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE,
+				                                NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH)) {
+					show_active_fields = TRUE;
+					break;
+				}
+			}
+		}
 
-		/* Sort the connections and fill the output data */
-		connections = nm_client_get_connections (nmc->client);
-		sorted_cons = sort_connections (connections, nmc, order);
-		for (i = 0; i < sorted_cons->len; i++)
-			fill_output_connection (sorted_cons->pdata[i], nmc->client, nmc->nmc_config.print_output, out.output_data, active_only);
-		g_ptr_array_free (sorted_cons, TRUE);
-
-		print_data_prepare_width (out.output_data);
-		print_data (&nmc->nmc_config, out_indices,
-		            active_only ? _("NetworkManager active profiles")
-		                        : _("NetworkManager connection profiles"),
-		            0, &out);
+		/* Optionally start paging the output. */
+		nmc_terminal_spawn_pager (&nmc->nmc_config);
+
+		items = con_show_get_items (nmc, active_only, show_active_fields, order);
+		g_ptr_array_add (items, NULL);
+		if (!nmc_print (&nmc->nmc_config,
+		                items->pdata,
+		                active_only
+		                  ? _("NetworkManager active profiles")
+		                  : _("NetworkManager connection profiles"),
+		               (const NMMetaAbstractInfo *const*) metagen_con_show,
+		                fields_str,
+		                &err))
+			goto finish;
 	} else {
 		gboolean new_line = FALSE;
 		gboolean without_fields = (nmc->required_fields == NULL);
@@ -1713,7 +1980,7 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 			char **argv_cp = argv;
 
 			do {
-				if (NM_IN_STRSET (*argv_cp, "id", "uuid", "path", "apath")) {
+				if (NM_IN_STRSET (*argv_cp, "id", "uuid", "path", "filename", "apath")) {
 					argc_cp--;
 					argv_cp++;
 				}
@@ -1731,9 +1998,9 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 			guint i_found_cons;
 
 			if (argc == 1 && nmc->complete)
-				nmc_complete_strings (*argv, "id", "uuid", "path", "apath", NULL);
+				nmc_complete_strings (*argv, "id", "uuid", "path", "filename", "apath", NULL);
 
-			if (NM_IN_STRSET (*argv, "id", "uuid", "path", "apath")) {
+			if (NM_IN_STRSET (*argv, "id", "uuid", "path", "filename", "apath")) {
 				selector = *argv;
 				argc--;
 				argv++;
@@ -1827,10 +2094,10 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 				if (without_fields || active_flds) {
 					guint l = explicit_acon ? 1 : (found_acons ? found_acons->len : 0);
 
-					for (j = 0; j < l; j++) {
+					for (i = 0; i < l; i++) {
 						NMActiveConnection *acon;
 
-						if (j > 0) {
+						if (i > 0) {
 							/* if there are multiple active connections, separate them with newline.
 							 * that is a bit odd, because we already separate connections with newlines,
 							 * and commonly don't separate the connection from the first active connection. */
@@ -1840,7 +2107,7 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 						if (explicit_acon)
 							acon = explicit_acon;
 						else
-							acon = found_acons->pdata[j];
+							acon = found_acons->pdata[i];
 
 						nmc->required_fields = active_flds;
 						res = nmc_active_connection_details (acon, nmc);
@@ -2579,7 +2846,7 @@ connection_cb_info_finish (ConnectionCbInfo *info, gpointer obj)
 	} else {
 		while (info->obj_list->len > 0) {
 			obj = info->obj_list->pdata[info->obj_list->len - 1];
-			g_ptr_array_remove_index (info->obj_list, info->obj_list->len);
+			g_ptr_array_remove_index (info->obj_list, info->obj_list->len - 1);
 			connection_cb_info_obj_list_destroy (info, obj);
 		}
 	}
@@ -2680,9 +2947,9 @@ do_connection_down (NmCli *nmc, int argc, char **argv)
 		const char *selector = NULL;
 
 		if (arg_num == 1 && nmc->complete)
-			nmc_complete_strings (*arg_ptr, "id", "uuid", "path", "apath", NULL);
+			nmc_complete_strings (*arg_ptr, "id", "uuid", "path",  "filename", "apath", NULL);
 
-		if (NM_IN_STRSET (*arg_ptr, "id", "uuid", "path", "apath")) {
+		if (NM_IN_STRSET (*arg_ptr, "id", "uuid", "path",  "filename", "apath")) {
 			selector = *arg_ptr;
 			arg_num--;
 			arg_ptr++;
@@ -7681,6 +7948,7 @@ do_connection_edit (NmCli *nmc, int argc, char **argv)
 	const char *con_id = NULL;
 	const char *con_uuid = NULL;
 	const char *con_path = NULL;
+	const char *con_filename = NULL;
 	const char *selector = NULL;
 	gs_free_error GError *error = NULL;
 	GError *err1 = NULL;
@@ -7689,11 +7957,12 @@ do_connection_edit (NmCli *nmc, int argc, char **argv)
 	                         { "id",       TRUE, &con_id,   FALSE },
 	                         { "uuid",     TRUE, &con_uuid, FALSE },
 	                         { "path",     TRUE, &con_path, FALSE },
+	                         { "filename", TRUE, &con_filename, FALSE },
 	                         { NULL } };
 
 	next_arg (nmc, &argc, &argv, NULL);
 	if (argc == 1 && nmc->complete)
-		nmc_complete_strings (*argv, "type", "con-name", "id", "uuid", "path", NULL);
+		nmc_complete_strings (*argv, "type", "con-name", "id", "uuid", "path",  "filename", NULL);
 
 	nmc->return_value = NMC_RESULT_SUCCESS;
 
@@ -7715,20 +7984,23 @@ do_connection_edit (NmCli *nmc, int argc, char **argv)
 	connections = nm_client_get_connections (nmc->client);
 
 	if (!con) {
-		if (con_id && !con_uuid && !con_path) {
+		if (con_id && !con_uuid && !con_path && !con_filename) {
 			con = con_id;
 			selector = "id";
-		} else if (con_uuid && !con_id && !con_path) {
+		} else if (con_uuid && !con_id && !con_path && !con_filename) {
 			con = con_uuid;
 			selector = "uuid";
-		} else if (con_path && !con_id && !con_uuid) {
+		} else if (con_path && !con_id && !con_uuid && !con_filename) {
 			con = con_path;
 			selector = "path";
-		} else if (!con_path && !con_id && !con_uuid) {
+		} else if (con_filename && !con_path && !con_id && !con_uuid) {
+			con = con_filename;
+			selector = "filename";
+		} else if (!con_path && !con_id && !con_uuid && !con_filename) {
 			/* no-op */
 		} else {
 			g_string_printf (nmc->return_text,
-			                 _("Error: only one of 'id', uuid, or 'path' can be provided."));
+			                 _("Error: only one of 'id', 'filename', uuid, or 'path' can be provided."));
 			NMC_RETURN (nmc, NMC_RESULT_ERROR_USER_INPUT);
 		}
 	}
diff --git a/clients/cli/connections.h b/clients/cli/connections.h
index 591e9cda..43cd97f6 100644
--- a/clients/cli/connections.h
+++ b/clients/cli/connections.h
@@ -35,8 +35,8 @@ nmc_read_connection_properties (NmCli *nmc,
 
 NMMetaColor nmc_active_connection_state_to_color (NMActiveConnectionState state);
 
-extern const NmcMetaGenericInfo *const nmc_fields_con_show[];
-extern const NmcMetaGenericInfo *const nmc_fields_con_active_details_general[];
+extern const NmcMetaGenericInfo *const metagen_con_show[];
+extern const NmcMetaGenericInfo *const metagen_con_active_general[];
 extern const NmcMetaGenericInfo *const nmc_fields_con_active_details_vpn[];
 extern const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[];
 
diff --git a/clients/cli/devices.c b/clients/cli/devices.c
index b99e606a..58102ed3 100644
--- a/clients/cli/devices.c
+++ b/clients/cli/devices.c
@@ -1725,7 +1725,7 @@ add_and_activate_cb (GObject *client,
 			if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
 				if (nmc->nmc_config.print_output == NMC_PRINT_PRETTY)
 					nmc_terminal_erase_line ();
-				if (info->hotspot)
+				if (!info->hotspot)
 					g_print (_("Connection with UUID '%s' created and activated on device '%s'\n"),
 					         nm_active_connection_get_uuid (active), nm_device_get_iface (device));
 				else
@@ -2488,41 +2488,6 @@ do_devices_monitor (NmCli *nmc, int argc, char **argv)
 	return nmc->return_value;
 }
 
-static void
-show_access_point_info (NMDevice *device, NmCli *nmc, NmcOutputData *out)
-{
-	NMAccessPoint *active_ap = NULL;
-	const char *active_bssid = NULL;
-	GPtrArray *aps;
-	NmcOutputField *arr;
-
-	if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
-		active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (device));
-		active_bssid = active_ap ? nm_access_point_get_bssid (active_ap) : NULL;
-	}
-
-	arr = nmc_dup_fields_array ((const NMMetaAbstractInfo *const*) nmc_fields_dev_wifi_list,
-	                            NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
-	g_ptr_array_add (out->output_data, arr);
-
-	{
-		APInfo info = {
-			.nmc = nmc,
-			.index = 1,
-			.output_flags = 0,
-			.active_bssid = active_bssid,
-			.device = nm_device_get_iface (device),
-			.output_data = out->output_data,
-		};
-
-		aps = sort_access_points (nm_device_wifi_get_access_points (NM_DEVICE_WIFI (device)));
-		g_ptr_array_foreach (aps, fill_output_access_point, &info);
-		g_ptr_array_free (aps, FALSE);
-	}
-
-	print_data_prepare_width (out->output_data);
-}
-
 /*
  * Find a Wi-Fi device with 'iface' in 'devices' array. If 'iface' is NULL,
  * the first Wi-Fi device is returned. 'idx' parameter is updated to the point
@@ -2616,6 +2581,217 @@ find_ap_on_device (NMDevice *device, const char *bssid, const char *ssid, gboole
 }
 
 static void
+show_access_point_info (NMDeviceWifi *wifi, NmCli *nmc, NmcOutputData *out)
+{
+	NMAccessPoint *active_ap = NULL;
+	const char *active_bssid = NULL;
+	GPtrArray *aps;
+	NmcOutputField *arr;
+
+	if (nm_device_get_state (NM_DEVICE (wifi)) == NM_DEVICE_STATE_ACTIVATED) {
+		active_ap = nm_device_wifi_get_active_access_point (wifi);
+		active_bssid = active_ap ? nm_access_point_get_bssid (active_ap) : NULL;
+	}
+
+	arr = nmc_dup_fields_array ((const NMMetaAbstractInfo *const*) nmc_fields_dev_wifi_list,
+	                            NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
+	g_ptr_array_add (out->output_data, arr);
+
+	{
+		APInfo info = {
+			.nmc = nmc,
+			.index = 1,
+			.output_flags = 0,
+			.active_bssid = active_bssid,
+			.device = nm_device_get_iface (NM_DEVICE (wifi)),
+			.output_data = out->output_data,
+		};
+
+		aps = sort_access_points (nm_device_wifi_get_access_points (wifi));
+		g_ptr_array_foreach (aps, fill_output_access_point, &info);
+		g_ptr_array_free (aps, FALSE);
+	}
+
+	print_data_prepare_width (out->output_data);
+}
+
+static void
+wifi_print_aps (NMDeviceWifi *wifi,
+                NmCli *nmc,
+                GArray *_out_indices,
+                const NMMetaAbstractInfo *const*tmpl,
+                const char *bssid_user)
+{
+	NMAccessPoint *ap = NULL;
+	const GPtrArray *aps;
+	APInfo *info;
+	guint i;
+	NmcOutputField *arr;
+	const char *base_hdr = _("Wi-Fi scan list");
+	NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
+	gs_free char *header_name = NULL;
+	static gboolean empty_line = FALSE;
+
+	if (empty_line)
+		g_print ("\n"); /* Empty line between devices' APs */
+
+	/* Main header name */
+	header_name = construct_header_name (base_hdr, nm_device_get_iface (NM_DEVICE (wifi)));
+
+	out_indices = g_array_ref (_out_indices);
+
+	if (bssid_user) {
+		/* Specific AP requested - list only that */
+		aps = nm_device_wifi_get_access_points (wifi);
+		for (i = 0; i < aps->len; i++) {
+			char *bssid_up;
+			NMAccessPoint *candidate_ap = g_ptr_array_index (aps, i);
+			const char *candidate_bssid = nm_access_point_get_bssid (candidate_ap);
+
+			bssid_up = g_ascii_strup (bssid_user, -1);
+			if (!strcmp (bssid_up, candidate_bssid))
+				ap = candidate_ap;
+			g_free (bssid_up);
+		}
+		if (ap) {
+			/* Add headers (field names) */
+			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
+			g_ptr_array_add (out.output_data, arr);
+
+			info = g_malloc0 (sizeof (APInfo));
+			info->nmc = nmc;
+			info->index = 1;
+			info->output_flags = 0;
+			info->active_bssid = NULL;
+			info->device = nm_device_get_iface (NM_DEVICE (wifi));
+			info->output_data = out.output_data;
+
+			fill_output_access_point (ap, info);
+
+			print_data_prepare_width (out.output_data);
+			print_data (&nmc->nmc_config, out_indices, header_name, 0, &out);
+			g_free (info);
+
+			nmc->return_value = NMC_RESULT_SUCCESS;
+			empty_line = TRUE;
+		}
+	} else {
+		show_access_point_info (wifi, nmc, &out);
+		print_data (&nmc->nmc_config, out_indices, header_name, 0, &out);
+		empty_line = TRUE;
+	}
+}
+
+typedef struct {
+	NmCli *nmc;
+	NMDeviceWifi *wifi;
+	const NMMetaAbstractInfo *const*tmpl;
+
+	const char *bssid_user;
+	gulong last_scan_id;
+	guint  timeout_id;
+	GCancellable *scan_cancellable;
+	GArray *out_indices;
+} WifiListData;
+
+static void
+wifi_list_finish (WifiListData *data)
+{
+	NmCli *nmc = data->nmc;
+
+	wifi_print_aps (data->wifi, data->nmc, data->out_indices,
+	                data->tmpl, data->bssid_user);
+
+	if (--nmc->should_wait == 0) {
+		if (nmc->return_value == NMC_RESULT_ERROR_NOT_FOUND) {
+			g_string_printf (nmc->return_text, _("Error: Access point with bssid '%s' not found."),
+			                 data->bssid_user);
+		}
+		g_main_loop_quit (loop);
+	}
+
+	g_signal_handler_disconnect (data->wifi, data->last_scan_id);
+	nm_clear_g_source (&data->timeout_id);
+	nm_clear_g_cancellable (&data->scan_cancellable);
+	g_array_unref (data->out_indices);
+	g_object_unref (data->wifi);
+	g_slice_free (WifiListData, data);
+}
+
+static void
+wifi_last_scan_updated (GObject *gobject, GParamSpec *pspec, gpointer user_data)
+{
+	WifiListData *data = user_data;
+
+	wifi_list_finish (data);
+}
+
+static void
+wifi_list_rescan_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceWifi *wifi = NM_DEVICE_WIFI (source_object);
+	WifiListData *data = user_data;
+	gs_free_error GError *error = NULL;
+
+	if (!nm_device_wifi_request_scan_finish (wifi, res, &error)) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			return;
+
+		if (g_error_matches (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ALLOWED)) {
+			/* This likely means that scanning is already in progress. There's
+			 * a good chance we'll get updated results soon; wait for them. */
+			return;
+		}
+
+		/* Scanning could not be initiated for unknown reason,
+		 * no point in waiting for results. */
+		wifi_list_finish (data);
+	}
+}
+
+static gboolean
+wifi_list_scan_timeout (gpointer user_data)
+{
+	WifiListData *data = user_data;
+
+	wifi_list_finish (data);
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+wifi_list_aps (NMDeviceWifi *wifi,
+               NmCli *nmc,
+               GArray *out_indices,
+               const NMMetaAbstractInfo *const*tmpl,
+               const char *bssid_user,
+               gint64 rescan_cutoff)
+{
+	gboolean needs_rescan;
+	WifiListData *data;
+
+	needs_rescan = rescan_cutoff < 0 || (rescan_cutoff > 0 && nm_device_wifi_get_last_scan (wifi) < rescan_cutoff);
+
+	if (needs_rescan) {
+		data = g_slice_new0 (WifiListData);
+		data->nmc = nmc;
+		data->wifi = g_object_ref (wifi);
+		data->tmpl = tmpl;
+		data->out_indices = g_array_ref (out_indices);;
+		data->bssid_user = bssid_user;
+		data->last_scan_id = g_signal_connect (wifi, "notify::" NM_DEVICE_WIFI_LAST_SCAN,
+		                                       G_CALLBACK (wifi_last_scan_updated), data);
+		data->scan_cancellable = g_cancellable_new ();
+		data->timeout_id = g_timeout_add_seconds (15, wifi_list_scan_timeout, data);
+		nm_device_wifi_request_scan_async (wifi, data->scan_cancellable, wifi_list_rescan_cb, data);
+
+		nmc->should_wait++;
+	} else {
+		wifi_print_aps (wifi, nmc, out_indices, tmpl, bssid_user);
+	}
+}
+
+static void
 complete_aps (NMDevice **devices, const char *ifname,
               const char *bssid_prefix, const char *ssid_prefix)
 {
@@ -2640,28 +2816,22 @@ do_device_wifi_list (NmCli *nmc, int argc, char **argv)
 {
 	GError *error = NULL;
 	NMDevice *device = NULL;
-	NMAccessPoint *ap = NULL;
 	const char *ifname = NULL;
 	const char *bssid_user = NULL;
+	const char *rescan = NULL;
 	gs_free NMDevice **devices = NULL;
-	const GPtrArray *aps;
-	APInfo *info;
-	int i, j;
+	guint i;
 	const char *fields_str = NULL;
 	const NMMetaAbstractInfo *const*tmpl;
-	NmcOutputField *arr;
-	const char *base_hdr = _("Wi-Fi scan list");
-	NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
-	gs_free char *header_name = NULL;
+	gs_unref_array GArray *out_indices = NULL;
+	int option;
+	guint64 rescan_cutoff;
 
 	devices = nmc_get_devices_sorted (nmc->client);
 
-	next_arg (nmc, &argc, &argv, NULL);
-	while (argc > 0) {
-		if (argc == 1 && nmc->complete)
-			nmc_complete_strings (*argv, "ifname", "bssid", NULL);
-
-		if (strcmp (*argv, "ifname") == 0) {
+	while ((option = next_arg (nmc, &argc, &argv, "ifname", "hwaddr", "bssid", "--rescan", NULL)) > 0) {
+		switch (option) {
+		case 1: /* ifname */
 			argc--;
 			argv++;
 			if (!argc) {
@@ -2671,8 +2841,9 @@ do_device_wifi_list (NmCli *nmc, int argc, char **argv)
 			ifname = *argv;
 			if (argc == 1 && nmc->complete)
 				complete_device (devices, ifname, TRUE);
-		} else if (strcmp (*argv, "bssid") == 0 || strcmp (*argv, "hwaddr") == 0) {
-			/* hwaddr is deprecated and will be removed later */
+			break;
+		case 2: /* hwaddr is deprecated and will be removed later */
+		case 3: /* bssid */
 			argc--;
 			argv++;
 			if (!argc) {
@@ -2682,11 +2853,24 @@ do_device_wifi_list (NmCli *nmc, int argc, char **argv)
 			bssid_user = *argv;
 			if (argc == 1 && nmc->complete)
 				complete_aps (devices, NULL, bssid_user, NULL);
-		} else if (!nmc->complete) {
-			g_printerr (_("Unknown parameter: %s\n"), *argv);
+			/* We'll switch this to NMC_RESULT_SUCCESS if we find an access point. */
+			nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
+			break;
+		case 4: /* --rescan */
+			argc--;
+			argv++;
+			if (!argc) {
+				g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
+				return NMC_RESULT_ERROR_USER_INPUT;
+			}
+			rescan = *argv;
+			if (argc == 1 && nmc->complete)
+				nmc_complete_strings (rescan, "auto", "no", "yes", NULL);
+			break;
+		default:
+			g_assert_not_reached();
+			break;
 		}
-
-		next_arg (nmc, &argc, &argv, NULL);
 	}
 
 	if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0)
@@ -2707,55 +2891,29 @@ do_device_wifi_list (NmCli *nmc, int argc, char **argv)
 	if (nmc->complete)
 		return nmc->return_value;
 
-	if (ifname) {
+	if (argc)
+		g_printerr (_("Unknown parameter: %s\n"), *argv);
+
+	if (rescan == NULL || strcmp (rescan, "auto") == 0) {
+		rescan_cutoff = NM_MAX (nm_utils_get_timestamp_msec () - 30 * NM_UTILS_MSEC_PER_SECOND, 0);
+	} else if (strcmp (rescan, "no") == 0) {
+		rescan_cutoff = 0;
+	} else if (strcmp (rescan, "yes") == 0) {
+		rescan_cutoff = -1;
+	} else {
+		g_string_printf (nmc->return_text, _("Error: invalid rescan argument: '%s' not among [auto, no, yes]"), rescan);
+		return NMC_RESULT_ERROR_USER_INPUT;
+	}
 
+	if (ifname) {
 		device = find_wifi_device_by_iface (devices, ifname, NULL);
 		if (!device) {
 			g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname);
 			return NMC_RESULT_ERROR_NOT_FOUND;
 		}
-		/* Main header name */
-		header_name = construct_header_name (base_hdr, ifname);
 
 		if (NM_IS_DEVICE_WIFI (device)) {
-			if (bssid_user) {
-				/* Specific AP requested - list only that */
-				aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (device));
-				for (j = 0; j < aps->len; j++) {
-					char *bssid_up;
-					NMAccessPoint *candidate_ap = g_ptr_array_index (aps, j);
-					const char *candidate_bssid = nm_access_point_get_bssid (candidate_ap);
-
-					bssid_up = g_ascii_strup (bssid_user, -1);
-					if (!strcmp (bssid_up, candidate_bssid))
-						ap = candidate_ap;
-					g_free (bssid_up);
-				}
-				if (!ap) {
-					g_string_printf (nmc->return_text, _("Error: Access point with bssid '%s' not found."),
-					                 bssid_user);
-					return NMC_RESULT_ERROR_NOT_FOUND;
-				}
-				/* Add headers (field names) */
-				arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
-				g_ptr_array_add (out.output_data, arr);
-
-				info = g_malloc0 (sizeof (APInfo));
-				info->nmc = nmc;
-				info->index = 1;
-				info->output_flags = 0;
-				info->active_bssid = NULL;
-				info->device = nm_device_get_iface (device);
-
-				fill_output_access_point (ap, info);
-
-				print_data_prepare_width (out.output_data);
-				print_data (&nmc->nmc_config, out_indices, header_name, 0, &out);
-				g_free (info);
-			} else {
-				show_access_point_info (device, nmc, &out);
-				print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
-			}
+			wifi_list_aps (NM_DEVICE_WIFI (device), nmc, out_indices, tmpl, bssid_user, rescan_cutoff);
 		} else {
 			if (   nm_device_get_device_type (device) == NM_DEVICE_TYPE_GENERIC
 			    && g_strcmp0 (nm_device_get_type_description (device), "wifi") == 0) {
@@ -2770,76 +2928,11 @@ do_device_wifi_list (NmCli *nmc, int argc, char **argv)
 			return NMC_RESULT_ERROR_UNKNOWN;
 		}
 	} else {
-		gboolean empty_line = FALSE;
-
-		/* List APs for all devices */
-		if (bssid_user) {
-			/* Specific AP requested - list only that */
-			for (i = 0; devices[i]; i++) {
-				NMDevice *dev = devices[i];
-				NMC_OUTPUT_DATA_DEFINE_SCOPED (out2);
-				gs_free char *header_name2 = NULL;
-
-				if (!NM_IS_DEVICE_WIFI (dev))
-					continue;
-
-				/* Main header name */
-				header_name2 = construct_header_name (base_hdr, nm_device_get_iface (dev));
-				out2_indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, NULL);
-
-				arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
-				g_ptr_array_add (out2.output_data, arr);
-
-				aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (dev));
-				for (j = 0; j < aps->len; j++) {
-					char *bssid_up;
-					NMAccessPoint *candidate_ap = g_ptr_array_index (aps, j);
-					const char *candidate_bssid = nm_access_point_get_bssid (candidate_ap);
-
-					bssid_up = g_ascii_strup (bssid_user, -1);
-					if (!strcmp (bssid_up, candidate_bssid)) {
-						ap = candidate_ap;
-
-						info = g_malloc0 (sizeof (APInfo));
-						info->nmc = nmc;
-						info->index = 1;
-						info->output_flags = 0;
-						info->active_bssid = NULL;
-						info->device = nm_device_get_iface (dev);
-						fill_output_access_point (ap, info);
-						g_free (info);
-					}
-					g_free (bssid_up);
-				}
-				if (empty_line)
-					g_print ("\n"); /* Empty line between devices' APs */
-				print_data_prepare_width (out2.output_data);
-				print_data (&nmc->nmc_config, out2_indices, header_name2, 0, &out2);
-				empty_line = TRUE;
-			}
-			if (!ap) {
-				g_string_printf (nmc->return_text, _("Error: Access point with bssid '%s' not found."),
-				                 bssid_user);
-				return NMC_RESULT_ERROR_NOT_FOUND;
-			}
-		} else {
-			for (i = 0; devices[i]; i++) {
-				NMDevice *dev = devices[i];
-				NMC_OUTPUT_DATA_DEFINE_SCOPED (out2);
-				gs_free char *header_name2 = NULL;
-
-				/* Main header name */
-				header_name2 = construct_header_name (base_hdr,
-				                                      nm_device_get_iface (dev));
-				out2_indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, NULL);
-
-				if (NM_IS_DEVICE_WIFI (dev)) {
-					if (empty_line)
-						g_print ("\n"); /* Empty line between devices' APs */
-					show_access_point_info (dev, nmc, &out2);
-					print_data (&nmc->nmc_config, out2_indices, header_name2, 0, &out2);
-					empty_line = TRUE;
-				}
+		for (i = 0; devices[i]; i++) {
+			NMDevice *dev = devices[i];
+
+			if (NM_IS_DEVICE_WIFI (dev)) {
+				wifi_list_aps (NM_DEVICE_WIFI (dev), nmc, out_indices, tmpl, bssid_user, rescan_cutoff);
 			}
 		}
 	}
@@ -2852,9 +2945,9 @@ do_device_wifi_connect_network (NmCli *nmc, int argc, char **argv)
 {
 	NMDevice *device = NULL;
 	NMAccessPoint *ap = NULL;
-	NM80211ApFlags ap_flags = NM_802_11_AP_FLAGS_NONE;
-	NM80211ApSecurityFlags ap_wpa_flags = NM_802_11_AP_SEC_NONE;
-	NM80211ApSecurityFlags ap_rsn_flags = NM_802_11_AP_SEC_NONE;
+	NM80211ApFlags ap_flags;
+	NM80211ApSecurityFlags ap_wpa_flags;
+	NM80211ApSecurityFlags ap_rsn_flags;
 	NMConnection *connection = NULL;
 	NMSettingConnection *s_con;
 	NMSettingWireless *s_wifi;
@@ -3151,7 +3244,9 @@ do_device_wifi_connect_network (NmCli *nmc, int argc, char **argv)
 	ap_rsn_flags = nm_access_point_get_rsn_flags (ap);
 
 	/* Set password for WEP or WPA-PSK. */
-	if (ap_flags & NM_802_11_AP_FLAGS_PRIVACY) {
+	if (   (ap_flags & NM_802_11_AP_FLAGS_PRIVACY)
+	    || ap_wpa_flags != NM_802_11_AP_SEC_NONE
+	    || ap_rsn_flags != NM_802_11_AP_SEC_NONE) {
 		/* Ask for missing password when one is expected and '--ask' is used */
 		if (!password && nmc->ask)
 			password = passwd_ask = nmc_readline_echo (nmc->nmc_config.show_secrets, _("Password: "));
diff --git a/clients/cli/general.c b/clients/cli/general.c
index 841df8bd..de743970 100644
--- a/clients/cli/general.c
+++ b/clients/cli/general.c
@@ -560,6 +560,9 @@ print_permissions (void *user_data)
 		permissions[i++] = GINT_TO_POINTER (perm);
 	permissions[i++] = NULL;
 
+	/* Optionally start paging the output. */
+	nmc_terminal_spawn_pager (&nmc->nmc_config);
+
 	if (!nmc_print (&nmc->nmc_config,
 	                permissions,
 	                _("NetworkManager permissions"),
diff --git a/clients/cli/nmcli.c b/clients/cli/nmcli.c
index e9752952..6407f50b 100644
--- a/clients/cli/nmcli.c
+++ b/clients/cli/nmcli.c
@@ -188,8 +188,8 @@ complete_fields (const char *option, const char *prefix)
 	complete_field (h, metagen_ip4_config);
 	complete_field (h, nmc_fields_dhcp_config);
 	complete_field (h, nmc_fields_ip6_config);
-	complete_field (h, nmc_fields_con_show);
-	complete_field (h, nmc_fields_con_active_details_general);
+	complete_field (h, metagen_con_show);
+	complete_field (h, metagen_con_active_general);
 	complete_field (h, nmc_fields_con_active_details_vpn);
 	complete_field (h, nmc_fields_con_active_details_groups);
 	complete_field (h, nmc_fields_dev_status);
diff --git a/clients/cli/nmcli.h b/clients/cli/nmcli.h
index 61bf86de..bcf1c01b 100644
--- a/clients/cli/nmcli.h
+++ b/clients/cli/nmcli.h
@@ -72,6 +72,14 @@ typedef enum {
 	NMC_PRINT_PRETTY = 2
 } NMCPrintOutput;
 
+static inline NMMetaAccessorGetType
+nmc_print_output_to_accessor_get_type (NMCPrintOutput print_output)
+{
+	return   (print_output != NMC_PRINT_TERSE)
+	       ? NM_META_ACCESSOR_GET_TYPE_PRETTY
+	       : NM_META_ACCESSOR_GET_TYPE_PARSABLE;
+}
+
 /* === Output fields === */
 
 typedef enum {
diff --git a/clients/cli/utils.c b/clients/cli/utils.c
index 32c44e39..365a0303 100644
--- a/clients/cli/utils.c
+++ b/clients/cli/utils.c
@@ -199,10 +199,18 @@ next_arg (NmCli *nmc, int *argc, char ***argv, ...)
 
 		/* Check command dependent options first */
 		while ((cmd_option = va_arg (args, const char *))) {
-			/* strip heading "--" form cmd_option */
-			if (nmc_arg_is_option (**argv, cmd_option + 2)) {
-				va_end (args);
-				return cmd_option_pos;
+			if (cmd_option[0] == '-' && cmd_option[1] == '-') {
+				/* Match as an option (leading "--" stripped) */
+				if (nmc_arg_is_option (**argv, cmd_option + 2)) {
+					va_end (args);
+					return cmd_option_pos;
+				}
+			} else {
+				/* Match literally. */
+				if (strcmp (**argv, cmd_option) == 0) {
+					va_end (args);
+					return cmd_option_pos;
+				}
 			}
 			cmd_option_pos++;
 		}
@@ -1004,9 +1012,7 @@ _print_fill (const NmcConfig *nmc_config,
 	g_array_set_clear_func (cells, _print_data_cell_clear);
 	g_array_set_size (cells, targets_len * header_row->len);
 
-	text_get_type = pretty
-	                ? NM_META_ACCESSOR_GET_TYPE_PRETTY
-	                : NM_META_ACCESSOR_GET_TYPE_PARSABLE;
+	text_get_type = nmc_print_output_to_accessor_get_type (nmc_config->print_output);
 	text_get_flags = NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV;
 	if (nmc_config->show_secrets)
 		text_get_flags |= NM_META_ACCESSOR_GET_FLAGS_SHOW_SECRETS;
@@ -1040,20 +1046,22 @@ _print_fill (const NmcConfig *nmc_config,
 			                                   &is_default,
 			                                   (gpointer *) &to_free);
 
+			nm_assert (!to_free || value == to_free);
+
 			header_cell->skip = nmc_config->overview && is_default;
 
 			if (NM_FLAGS_HAS (text_out_flags, NM_META_ACCESSOR_GET_OUT_FLAGS_STRV)) {
-				if (value) {
-					if (nmc_config->multiline_output) {
-						cell->text_format = PRINT_DATA_CELL_FORMAT_TYPE_STRV;
-						cell->text.strv = value;
-						cell->text_to_free = !!to_free;
-					} else {
+				if (nmc_config->multiline_output) {
+					cell->text_format = PRINT_DATA_CELL_FORMAT_TYPE_STRV;
+					cell->text.strv = value;
+					cell->text_to_free = !!to_free;
+				} else {
+					if (value && ((const char *const*) value)[0]) {
 						cell->text.plain = g_strjoinv (" | ", (char **) value);
 						cell->text_to_free = TRUE;
-						if (to_free)
-							g_strfreev ((char **) to_free);
 					}
+					if (to_free)
+						g_strfreev ((char **) to_free);
 				}
 			} else {
 				cell->text.plain = value;
@@ -1165,7 +1173,7 @@ _print_do (const NmcConfig *nmc_config,
 	guint i_row, i_col;
 	nm_auto_free_gstring GString *str = NULL;
 
-	g_assert (col_len && row_len);
+	g_assert (col_len);
 
 	/* Main header */
 	if (pretty && header_name_no_l10n) {
diff --git a/clients/cli/utils.h b/clients/cli/utils.h
index dc0ce083..19694d11 100644
--- a/clients/cli/utils.h
+++ b/clients/cli/utils.h
@@ -121,6 +121,37 @@ typedef enum {
 	NMC_GENERIC_INFO_TYPE_IP6_CONFIG_DOMAIN,
 	_NMC_GENERIC_INFO_TYPE_IP6_CONFIG_NUM,
 
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME = 0,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP_REAL,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT_PRIORITY,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_READONLY,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_DBUS_PATH,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE,
+	NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME,
+	_NMC_GENERIC_INFO_TYPE_CON_SHOW_NUM,
+
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NAME = 0,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_UUID,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_STATE,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT6,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_SPEC_OBJECT,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_VPN,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE,
+	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH,
+	_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NUM,
+
 } NmcGenericInfoType;
 
 #define NMC_HANDLE_COLOR(color) \