summary refs log tree commit diff
path: root/clients/cli
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
committerMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
commit6518e361171f64bcaaa4bf868139362ed95cc2e0 (patch)
treed2d5b53faf80646a40ec2c0c7f2a42b3959612f5 /clients/cli
parente126f3e804c35480c4f075777430419d6ece23da (diff)
New upstream version 1.14.2 upstream/1.14.2
Diffstat (limited to 'clients/cli')
-rw-r--r--clients/cli/common.c191
-rw-r--r--clients/cli/common.h17
-rw-r--r--clients/cli/connections.c390
-rw-r--r--clients/cli/connections.h2
-rw-r--r--clients/cli/devices.c1002
-rw-r--r--clients/cli/devices.h14
-rw-r--r--clients/cli/general.c5
-rw-r--r--clients/cli/meson.build2
-rw-r--r--clients/cli/nmcli.c341
-rw-r--r--clients/cli/nmcli.h4
-rw-r--r--clients/cli/settings.c33
-rw-r--r--clients/cli/utils.c191
-rw-r--r--clients/cli/utils.h109
13 files changed, 1416 insertions, 885 deletions
diff --git a/clients/cli/common.c b/clients/cli/common.c
index 4aea0d5b..88e98154 100644
--- a/clients/cli/common.c
+++ b/clients/cli/common.c
@@ -102,6 +102,8 @@ _ip_config_get_routes (NMIPConfig *cfg)
 	return arr;
 }
 
+/*****************************************************************************/
+
 static gconstpointer
 _metagen_ip4_config_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
 {
@@ -177,6 +179,19 @@ arr_out:
 	return arr;
 }
 
+const NmcMetaGenericInfo *const metagen_ip4_config[_NMC_GENERIC_INFO_TYPE_IP4_CONFIG_NUM + 1] = {
+#define _METAGEN_IP4_CONFIG(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_ip4_config_get_fcn)
+	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_ADDRESS, "ADDRESS"),
+	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_GATEWAY, "GATEWAY"),
+	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_ROUTE,   "ROUTE"),
+	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_DNS,     "DNS"),
+	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_DOMAIN,  "DOMAIN"),
+	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_WINS,    "WINS"),
+};
+
+/*****************************************************************************/
+
 static gconstpointer
 _metagen_ip6_config_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
 {
@@ -247,17 +262,6 @@ arr_out:
 	return arr;
 }
 
-const NmcMetaGenericInfo *const metagen_ip4_config[_NMC_GENERIC_INFO_TYPE_IP4_CONFIG_NUM + 1] = {
-#define _METAGEN_IP4_CONFIG(type, name) \
-	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_ip4_config_get_fcn)
-	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_ADDRESS, "ADDRESS"),
-	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_GATEWAY, "GATEWAY"),
-	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_ROUTE,   "ROUTE"),
-	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_DNS,     "DNS"),
-	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_DOMAIN,  "DOMAIN"),
-	_METAGEN_IP4_CONFIG (NMC_GENERIC_INFO_TYPE_IP4_CONFIG_WINS,    "WINS"),
-};
-
 const NmcMetaGenericInfo *const metagen_ip6_config[_NMC_GENERIC_INFO_TYPE_IP6_CONFIG_NUM + 1] = {
 #define _METAGEN_IP6_CONFIG(type, name) \
 	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_ip6_config_get_fcn)
@@ -270,40 +274,92 @@ const NmcMetaGenericInfo *const metagen_ip6_config[_NMC_GENERIC_INFO_TYPE_IP6_CO
 
 /*****************************************************************************/
 
-const NmcMetaGenericInfo *const nmc_fields_dhcp_config[] = {
-	NMC_META_GENERIC ("GROUP"),    /* 0 */
-	NMC_META_GENERIC ("OPTION"),   /* 1 */
-	NULL,
-};
+static gconstpointer
+_metagen_dhcp_config_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMDhcpConfig *dhcp = target;
+	guint i;
+	char **arr = NULL;
+
+	NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
 
-const NmcMetaGenericInfo *const nmc_fields_ip6_config[] = {
-	NMC_META_GENERIC ("GROUP"),     /* 0 */
-	NMC_META_GENERIC ("ADDRESS"),   /* 1 */
-	NMC_META_GENERIC ("GATEWAY"),   /* 2 */
-	NMC_META_GENERIC ("ROUTE"),     /* 3 */
-	NMC_META_GENERIC ("DNS"),       /* 4 */
-	NMC_META_GENERIC ("DOMAIN"),    /* 5 */
-	NULL,
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_DHCP_CONFIG_OPTION:
+		{
+			GHashTable *table;
+			gs_free char **arr2 = NULL;
+			guint n;
+
+			if (!NM_FLAGS_HAS (get_flags, NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV))
+				return NULL;
+
+			table = nm_dhcp_config_get_options (dhcp);
+			if (!table)
+				goto arr_out;
+
+			arr2 = (char **) nm_utils_strdict_get_keys (table, TRUE, &n);
+			if (!n)
+				goto arr_out;
+
+			nm_assert (arr2 && !arr2[n] && n == NM_PTRARRAY_LEN (arr2));
+			for (i = 0; i < n; i++) {
+				const char *k = arr2[i];
+				const char *v;
+
+				nm_assert (k);
+				v = g_hash_table_lookup (table, k);
+				arr2[i] = g_strdup_printf ("%s = %s", k, v);
+			}
+
+			arr = g_steal_pointer (&arr2);
+			goto arr_out;
+		}
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+
+arr_out:
+	NM_SET_OUT (out_is_default, !arr || !arr[0]);
+	*out_flags |= NM_META_ACCESSOR_GET_OUT_FLAGS_STRV;
+	*out_to_free = arr;
+	return arr;
+}
+
+const NmcMetaGenericInfo *const metagen_dhcp_config[_NMC_GENERIC_INFO_TYPE_DHCP_CONFIG_NUM + 1] = {
+#define _METAGEN_DHCP_CONFIG(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_dhcp_config_get_fcn)
+	_METAGEN_DHCP_CONFIG (NMC_GENERIC_INFO_TYPE_DHCP_CONFIG_OPTION, "OPTION"),
 };
 
+/*****************************************************************************/
+
 gboolean
-print_ip4_config (NMIPConfig *cfg4,
-                  const NmcConfig *nmc_config,
-                  const char *one_field)
+print_ip_config (NMIPConfig *cfg,
+                 int addr_family,
+                 const NmcConfig *nmc_config,
+                 const char *one_field)
 {
 	gs_free_error GError *error = NULL;
 	gs_free char *field_str = NULL;
 
-	if (cfg4 == NULL)
+	if (!cfg)
 		return FALSE;
 
-	if (one_field)
-		field_str = g_strdup_printf ("IP4.%s", one_field);
+	if (one_field) {
+		field_str = g_strdup_printf ("IP%c.%s",
+		                             nm_utils_addr_family_to_char (addr_family),
+		                             one_field);
+	}
 
 	if (!nmc_print (nmc_config,
-	                (gpointer[]) { cfg4, NULL },
+	                (gpointer[]) { cfg, NULL },
+	                NULL,
 	                NULL,
-	                NMC_META_GENERIC_GROUP ("IP4", metagen_ip4_config, N_("GROUP")),
+	                addr_family == AF_INET
+	                  ? NMC_META_GENERIC_GROUP ("IP4", metagen_ip4_config, N_("GROUP"))
+	                  : NMC_META_GENERIC_GROUP ("IP6", metagen_ip6_config, N_("GROUP")),
 	                field_str,
 	                &error)) {
 		return FALSE;
@@ -312,24 +368,30 @@ print_ip4_config (NMIPConfig *cfg4,
 }
 
 gboolean
-print_ip6_config (NMIPConfig *cfg6,
-                  const NmcConfig *nmc_config,
-                  const char *group_prefix,
-                  const char *one_field)
+print_dhcp_config (NMDhcpConfig *dhcp,
+                   int addr_family,
+                   const NmcConfig *nmc_config,
+                   const char *one_field)
 {
 	gs_free_error GError *error = NULL;
 	gs_free char *field_str = NULL;
 
-	if (cfg6 == NULL)
+	if (!dhcp)
 		return FALSE;
 
-	if (one_field)
-		field_str = g_strdup_printf ("IP6.%s", one_field);
+	if (one_field) {
+		field_str = g_strdup_printf ("DHCP%c.%s",
+		                             nm_utils_addr_family_to_char (addr_family),
+		                             one_field);
+	}
 
 	if (!nmc_print (nmc_config,
-	                (gpointer[]) { cfg6, NULL },
+	                (gpointer[]) { dhcp, NULL },
 	                NULL,
-	                NMC_META_GENERIC_GROUP ("IP6", metagen_ip6_config, N_("GROUP")),
+	                NULL,
+	                addr_family == AF_INET
+	                  ? NMC_META_GENERIC_GROUP ("DHCP4", metagen_dhcp_config, N_("GROUP"))
+	                  : NMC_META_GENERIC_GROUP ("DHCP6", metagen_dhcp_config, N_("GROUP")),
 	                field_str,
 	                &error)) {
 		return FALSE;
@@ -337,53 +399,6 @@ print_ip6_config (NMIPConfig *cfg6,
 	return TRUE;
 }
 
-gboolean
-print_dhcp_config (NMDhcpConfig *dhcp,
-                   const NmcConfig *nmc_config,
-                   const char *group_prefix,
-                   const char *one_field)
-{
-	GHashTable *table;
-	const NMMetaAbstractInfo *const*tmpl;
-	NmcOutputField *arr;
-
-	if (dhcp == NULL)
-		return FALSE;
-
-	table = nm_dhcp_config_get_options (dhcp);
-	if (table) {
-		char **options_arr = NULL;
-		NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
-		gs_free const char **keys = NULL;
-		guint i, nkeys;
-
-		tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_dhcp_config;
-		out_indices = parse_output_fields (one_field,
-		                                   tmpl, FALSE, NULL, NULL);
-		arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_FIELD_NAMES);
-		g_ptr_array_add (out.output_data, arr);
-
-		keys = (const char **) g_hash_table_get_keys_as_array (table, &nkeys);
-		nm_utils_strv_sort (keys, nkeys);
-
-		options_arr = g_new (char *, nkeys + 1);
-		for (i = 0; i < nkeys; i++)
-			options_arr[i] = g_strdup_printf ("%s = %s", keys[i], (const char *) g_hash_table_lookup (table, keys[i]));
-		options_arr[i] = NULL;
-
-		arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_SECTION_PREFIX);
-		set_val_strc (arr, 0, group_prefix);
-		set_val_arr  (arr, 1, options_arr);
-		g_ptr_array_add (out.output_data, arr);
-
-		print_data_prepare_width (out.output_data);
-		print_data (nmc_config, out_indices, NULL, 0, &out);
-
-		return TRUE;
-	}
-	return FALSE;
-}
-
 /*
  * nmc_find_connection:
  * @connections: array of NMConnections to search in
diff --git a/clients/cli/common.h b/clients/cli/common.h
index 8f4e47c2..c180dfb7 100644
--- a/clients/cli/common.h
+++ b/clients/cli/common.h
@@ -25,9 +25,16 @@
 #include "nmcli.h"
 #include "nm-secret-agent-simple.h"
 
-gboolean print_ip4_config (NMIPConfig *cfg4, const NmcConfig *nmc_config, const char *one_field);
-gboolean print_ip6_config (NMIPConfig *cfg6, const NmcConfig *nmc_config, const char *group_prefix, const char *one_field);
-gboolean print_dhcp_config (NMDhcpConfig *dhcp, const NmcConfig *nmc_config, const char *group_prefix, const char *one_field);
+
+gboolean print_ip_config (NMIPConfig *cfg,
+                          int addr_family,
+                          const NmcConfig *nmc_config,
+                          const char *one_field);
+
+gboolean print_dhcp_config (NMDhcpConfig *dhcp,
+                            int addr_family,
+                            const NmcConfig *nmc_config,
+                            const char *one_field);
 
 NMConnection *nmc_find_connection (const GPtrArray *connections,
                                    const char *filter_type,
@@ -83,7 +90,7 @@ void nmc_complete_bool (const char *prefix);
 const char *nmc_error_get_simple_message (GError *error);
 
 extern const NmcMetaGenericInfo *const metagen_ip4_config[];
-extern const NmcMetaGenericInfo *const nmc_fields_ip6_config[];
-extern const NmcMetaGenericInfo *const nmc_fields_dhcp_config[];
+extern const NmcMetaGenericInfo *const metagen_ip6_config[];
+extern const NmcMetaGenericInfo *const metagen_dhcp_config[];
 
 #endif /* NMC_COMMON_H */
diff --git a/clients/cli/connections.c b/clients/cli/connections.c
index b547e34c..bcd257ac 100644
--- a/clients/cli/connections.c
+++ b/clients/cli/connections.c
@@ -67,10 +67,10 @@ struct _OptionInfo {
 
 /* define some other prompts */
 
-#define PROMPT_CONNECTION  _("Connection (name, UUID, or path)")
-#define PROMPT_VPN_CONNECTION  _("VPN connection (name, UUID, or path)")
-#define PROMPT_CONNECTIONS _("Connection(s) (name, UUID, or path)")
-#define PROMPT_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath)")
+#define PROMPT_CONNECTION  _("Connection (name, UUID, or path): ")
+#define PROMPT_VPN_CONNECTION  _("VPN connection (name, UUID, or path): ")
+#define PROMPT_CONNECTIONS _("Connection(s) (name, UUID, or path): ")
+#define PROMPT_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath): ")
 
 #define BASE_PROMPT "nmcli> "
 
@@ -185,17 +185,24 @@ get_ac_device_string (NMActiveConnection *active)
 /* 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 *
+static const char *
 get_vpn_connection_type (NMConnection *connection)
 {
+	NMSettingVpn *s_vpn;
 	const char *type, *p;
 
+	s_vpn = nm_connection_get_setting_vpn (connection);
+	if (!s_vpn)
+		return NULL;
+
 	/* 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));
+	if (!type)
+		return NULL;
 	p = strrchr (type, '.');
-	return g_strdup (p ? p + 1 : type);
+	return p ? p + 1 : type;
 }
 
 /* VPN parameters can be found at:
@@ -206,31 +213,35 @@ get_vpn_connection_type (NMConnection *connection)
  * http://git.gnome.org/browse/network-manager-openswan/tree/src/nm-openswan-service.h
  * See also 'properties' directory in these plugins.
  */
-static const gchar *
+static const char *
 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 "";
+	if (vpn_type) {
+		if (nm_streq (vpn_type, "openvpn"))     return "remote";
+		if (nm_streq (vpn_type, "vpnc"))        return "IPSec gateway";
+		if (nm_streq (vpn_type, "pptp"))        return "gateway";
+		if (nm_streq (vpn_type, "openconnect")) return "gateway";
+		if (nm_streq (vpn_type, "openswan"))    return "right";
+		if (nm_streq (vpn_type, "libreswan"))   return "right";
+		if (nm_streq (vpn_type, "ssh"))         return "remote";
+		if (nm_streq (vpn_type, "l2tp"))        return "gateway";
+	}
+	return NULL;
 }
 
-static const gchar *
+static const char *
 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 "";
+	if (vpn_type) {
+		if (nm_streq (vpn_type, "openvpn"))     return "username";
+		if (nm_streq (vpn_type, "vpnc"))        return "Xauth username";
+		if (nm_streq (vpn_type, "pptp"))        return "user";
+		if (nm_streq (vpn_type, "openconnect")) return "username";
+		if (nm_streq (vpn_type, "openswan"))    return "leftxauthusername";
+		if (nm_streq (vpn_type, "libreswan"))   return "leftxauthusername";
+		if (nm_streq (vpn_type, "l2tp"))        return "user";
+	}
+	return NULL;
 }
 
 enum VpnDataItem {
@@ -238,11 +249,11 @@ enum VpnDataItem {
 	VPN_DATA_ITEM_USERNAME
 };
 
-static const gchar *
+static const char *
 get_vpn_data_item (NMConnection *connection, enum VpnDataItem vpn_data_item)
 {
-	const char *key;
-	gs_free char *type = NULL;
+	const char *type;
+	const char *key = NULL;
 
 	type = get_vpn_connection_type (connection);
 
@@ -254,10 +265,11 @@ get_vpn_data_item (NMConnection *connection, enum VpnDataItem vpn_data_item)
 		key = find_vpn_username_key (type);
 		break;
 	default:
-		key = "";
 		break;
 	}
 
+	if (!key)
+		return NULL;
 	return nm_setting_vpn_get_data_item (nm_connection_get_setting_vpn (connection), key);
 }
 
@@ -627,7 +639,102 @@ const NmcMetaGenericInfo *const metagen_con_active_general[_NMC_GENERIC_INFO_TYP
 
 /*****************************************************************************/
 
+static gconstpointer
+_metagen_con_active_vpn_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMActiveConnection *ac = target;
+	NMConnection *c;
+	NMSettingVpn *s_vpn = NULL;
+	NMVpnConnectionState vpn_state;
+	guint i;
+	const char *s;
+	char **arr = NULL;
+
+	nm_assert (NM_IS_VPN_CONNECTION (ac));
+
+	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_vpn = nm_connection_get_setting_vpn (c);
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE:
+		return c ? get_vpn_connection_type (c) : NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_VPN_USERNAME:
+		if (   s_vpn
+		    && (s = nm_setting_vpn_get_user_name (s_vpn)))
+			return s;
+		return c ? get_vpn_data_item (c, VPN_DATA_ITEM_USERNAME) : NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_VPN_GATEWAY:
+		return c ? get_vpn_data_item (c, VPN_DATA_ITEM_GATEWAY) : NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_VPN_BANNER:
+		s = nm_vpn_connection_get_banner (NM_VPN_CONNECTION (ac));
+		if (s)
+			return (*out_to_free = g_strescape (s, ""));
+		return NULL;
+	case NMC_GENERIC_INFO_TYPE_CON_VPN_VPN_STATE:
+		vpn_state = nm_vpn_connection_get_vpn_state (NM_VPN_CONNECTION (ac));
+		return (*out_to_free = nmc_meta_generic_get_enum_with_detail (NMC_META_GENERIC_GET_ENUM_TYPE_DASH,
+		                                                              vpn_state,
+		                                                              vpn_connection_state_to_string (vpn_state),
+		                                                              get_type));
+	case NMC_GENERIC_INFO_TYPE_CON_VPN_CFG:
+		if (!NM_FLAGS_HAS (get_flags, NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV))
+			return NULL;
+		if (s_vpn) {
+			gs_free char **arr2 = NULL;
+			guint n;
+
+			arr2 = (char **) nm_setting_vpn_get_data_keys (s_vpn, &n);
+			if (!n)
+				goto arr_out;
+
+			nm_assert (arr2 && !arr2[n]);
+			for (i = 0; i < n; i++) {
+				const char *k = arr2[i];
+				const char *v;
+
+				nm_assert (k);
+				v = nm_setting_vpn_get_data_item (s_vpn, k);
+				/* update the arr array in-place. Previously it contained
+				 * the constant keys, now it contains the strdup'ed output text. */
+				arr2[i] = g_strdup_printf ("%s = %s", k, v);
+			}
+
+			arr = g_steal_pointer (&arr2);
+		}
+		goto arr_out;
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+
+arr_out:
+	NM_SET_OUT (out_is_default, !arr || !arr[0]);
+	*out_flags |= NM_META_ACCESSOR_GET_OUT_FLAGS_STRV;
+	*out_to_free = arr;
+	return arr;
+}
+
+const NmcMetaGenericInfo *const metagen_con_active_vpn[_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_VPN_NUM + 1] = {
+#define _METAGEN_CON_ACTIVE_VPN(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_active_vpn_get_fcn)
+	_METAGEN_CON_ACTIVE_VPN (NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE,      "TYPE"),
+	_METAGEN_CON_ACTIVE_VPN (NMC_GENERIC_INFO_TYPE_CON_VPN_USERNAME,  "USERNAME"),
+	_METAGEN_CON_ACTIVE_VPN (NMC_GENERIC_INFO_TYPE_CON_VPN_GATEWAY,   "GATEWAY"),
+	_METAGEN_CON_ACTIVE_VPN (NMC_GENERIC_INFO_TYPE_CON_VPN_BANNER,    "BANNER"),
+	_METAGEN_CON_ACTIVE_VPN (NMC_GENERIC_INFO_TYPE_CON_VPN_VPN_STATE, "VPN-STATE"),
+	_METAGEN_CON_ACTIVE_VPN (NMC_GENERIC_INFO_TYPE_CON_VPN_CFG,       "CFG"),
+};
+
+/*****************************************************************************/
+
 #define NMC_FIELDS_SETTINGS_NAMES_ALL    NM_SETTING_CONNECTION_SETTING_NAME","\
+                                         NM_SETTING_MATCH_SETTING_NAME","\
                                          NM_SETTING_WIRED_SETTING_NAME","\
                                          NM_SETTING_802_1X_SETTING_NAME","\
                                          NM_SETTING_WIRELESS_SETTING_NAME","\
@@ -660,29 +767,22 @@ const NmcMetaGenericInfo *const metagen_con_active_general[_NMC_GENERIC_INFO_TYP
                                          NM_SETTING_MACSEC_SETTING_NAME"," \
                                          NM_SETTING_MACVLAN_SETTING_NAME"," \
                                          NM_SETTING_VXLAN_SETTING_NAME"," \
+                                         NM_SETTING_WPAN_SETTING_NAME","\
+                                         NM_SETTING_6LOWPAN_SETTING_NAME","\
                                          NM_SETTING_PROXY_SETTING_NAME"," \
-                                         NM_SETTING_TC_CONFIG_SETTING_NAME
+                                         NM_SETTING_TC_CONFIG_SETTING_NAME"," \
+                                         NM_SETTING_SRIOV_SETTING_NAME"," \
+                                         NM_SETTING_ETHTOOL_SETTING_NAME
                                          // NM_SETTING_DUMMY_SETTING_NAME
                                          // NM_SETTING_WIMAX_SETTING_NAME
 
-const NmcMetaGenericInfo *const nmc_fields_con_active_details_vpn[] = {
-	NMC_META_GENERIC ("GROUP"),      /* 0 */
-	NMC_META_GENERIC ("TYPE"),       /* 1 */
-	NMC_META_GENERIC ("USERNAME"),   /* 2 */
-	NMC_META_GENERIC ("GATEWAY"),    /* 3 */
-	NMC_META_GENERIC ("BANNER"),     /* 4 */
-	NMC_META_GENERIC ("VPN-STATE"),  /* 5 */
-	NMC_META_GENERIC ("CFG"),        /* 6 */
-	NULL,
-};
-
 const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = {
 	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 */
-	NMC_META_GENERIC_WITH_NESTED ("DHCP6",   nmc_fields_dhcp_config + 1),                /* 4 */
-	NMC_META_GENERIC_WITH_NESTED ("VPN",     nmc_fields_con_active_details_vpn + 1),     /* 5 */
+	NMC_META_GENERIC_WITH_NESTED ("DHCP4",   metagen_dhcp_config),                       /* 2 */
+	NMC_META_GENERIC_WITH_NESTED ("IP6",     metagen_ip6_config),                        /* 3 */
+	NMC_META_GENERIC_WITH_NESTED ("DHCP6",   metagen_dhcp_config),                       /* 4 */
+	NMC_META_GENERIC_WITH_NESTED ("VPN",     metagen_con_active_vpn),                    /* 5 */
 	NULL,
 };
 
@@ -701,6 +801,7 @@ typedef struct {
 	NMConnection *connection;
 	NMSetting *setting;
 	const char *property;
+	char **words;
 } TabCompletionInfo;
 
 static TabCompletionInfo nmc_tab_completion;
@@ -890,7 +991,11 @@ usage_connection_add (void)
 	              "                  [source-port-min <0-65535>]\n"
 	              "                  [source-port-max <0-65535>]\n"
 	              "                  [destination-port <0-65535>]\n\n"
-	              "    dummy:         \n\n"
+	              "    wpan:         [short-addr <0x0000-0xffff>]\n\n"
+	              "                  [pan-id <0x0000-0xffff>]\n\n"
+	              "                  [mac <MAC address>]\n\n"
+	              "    6lowpan:      dev <parent device (connection UUID, ifname, or MAC)>\n"
+	              "    dummy:\n\n"
 	              "  SLAVE_OPTIONS:\n"
 	              "    bridge:       [priority <0-63>]\n"
 	              "                  [path-cost <1-65535>]\n"
@@ -1181,7 +1286,9 @@ nmc_connection_profile_details (NMConnection *connection, NmCli *nmc)
 		int section_idx = g_array_index (print_settings_array, int, i);
 		const char *prop_name = (const char *) g_ptr_array_index (prop_array, i);
 
-		if (nmc->nmc_config.print_output != NMC_PRINT_TERSE && !nmc->nmc_config.multiline_output && was_output)
+		if (   NM_IN_SET (nmc->nmc_config.print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
+		    && !nmc->nmc_config.multiline_output
+		    && was_output)
 			g_print ("\n"); /* Empty line */
 
 		was_output = FALSE;
@@ -1213,19 +1320,6 @@ nmc_active_connection_state_to_color (NMActiveConnectionState state)
 		return NM_META_COLOR_CONNECTION_UNKNOWN;
 }
 
-typedef struct {
-	char **array;
-	guint32 idx;
-} FillVPNDataInfo;
-
-static void
-fill_vpn_data_item (const char *key, const char *value, gpointer user_data)
-{
-        FillVPNDataInfo *info = (FillVPNDataInfo *) user_data;
-
-	info->array[info->idx++] = g_strdup_printf ("%s = %s", key, value);
-}
-
 static gboolean
 nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 {
@@ -1234,8 +1328,6 @@ nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 	GPtrArray *group_fields = NULL;
 	int i;
 	const char *fields_str = NULL;
-	const NMMetaAbstractInfo *const*tmpl;
-	NmcOutputField *arr;
 	const char *base_hdr = _("Activate connection details");
 	gboolean was_output = FALSE;
 
@@ -1278,7 +1370,7 @@ 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
+		if (   NM_IN_SET (nmc->nmc_config.print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
 		    && !nmc->nmc_config.multiline_output
 		    && was_output)
 			g_print ("\n");
@@ -1294,6 +1386,7 @@ nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 			nmc_print (&nmc->nmc_config,
 			           (gpointer[]) { acon, NULL },
 			           NULL,
+			           NULL,
 			           NMC_META_GENERIC_GROUP ("GENERAL", metagen_con_active_general, N_("GROUP")),
 			           f,
 			           NULL);
@@ -1306,7 +1399,7 @@ nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 			gboolean b1 = FALSE;
 			NMIPConfig *cfg4 = nm_active_connection_get_ip4_config (acon);
 
-			b1 = print_ip4_config (cfg4, &nmc->nmc_config, group_fld);
+			b1 = print_ip_config (cfg4, AF_INET, &nmc->nmc_config, group_fld);
 			was_output = was_output || b1;
 		}
 
@@ -1315,7 +1408,7 @@ nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 			gboolean b1 = FALSE;
 			NMDhcpConfig *dhcp4 = nm_active_connection_get_dhcp4_config (acon);
 
-			b1 = print_dhcp_config (dhcp4, &nmc->nmc_config, "DHCP4", group_fld);
+			b1 = print_dhcp_config (dhcp4, AF_INET, &nmc->nmc_config, group_fld);
 			was_output = was_output || b1;
 		}
 
@@ -1324,7 +1417,7 @@ nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 			gboolean b1 = FALSE;
 			NMIPConfig *cfg6 = nm_active_connection_get_ip6_config (acon);
 
-			b1 = print_ip6_config (cfg6, &nmc->nmc_config, "IP6", group_fld);
+			b1 = print_ip_config (cfg6, AF_INET6, &nmc->nmc_config, group_fld);
 			was_output = was_output || b1;
 		}
 
@@ -1333,73 +1426,22 @@ nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc)
 			gboolean b1 = FALSE;
 			NMDhcpConfig *dhcp6 = nm_active_connection_get_dhcp6_config (acon);
 
-			b1 = print_dhcp_config (dhcp6, &nmc->nmc_config, "DHCP6", group_fld);
+			b1 = print_dhcp_config (dhcp6, AF_INET6, &nmc->nmc_config, group_fld);
 			was_output = was_output || b1;
 		}
 
-		/* VPN */
-		if (NM_IS_VPN_CONNECTION (acon) &&
-		    strcasecmp (nmc_fields_con_active_details_groups[group_idx]->name,  nmc_fields_con_active_details_groups[5]->name) == 0) {
-			NMConnection *con;
-			NMSettingConnection *s_con;
-			NMSettingVpn *s_vpn;
-			NMVpnConnectionState vpn_state;
-			char *type_str, *banner_str = NULL, *vpn_state_str;
-			const char *banner;
-			const char *username = NULL;
-			char **vpn_data_array = NULL;
-			guint32 items_num;
-			NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
-
-			con = NM_CONNECTION (nm_active_connection_get_connection (acon));
-
-			s_con = nm_connection_get_setting_connection (con);
-			g_assert (s_con);
-
-			tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_con_active_details_vpn;
-			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);
-
-			s_vpn = nm_connection_get_setting_vpn (con);
-			if (s_vpn) {
-				items_num = nm_setting_vpn_get_num_data_items (s_vpn);
-				if (items_num > 0) {
-					FillVPNDataInfo info;
-
-					vpn_data_array = g_new (char *, items_num + 1);
-					info.array = vpn_data_array;
-					info.idx = 0;
-					nm_setting_vpn_foreach_data_item (s_vpn, &fill_vpn_data_item, &info);
-					vpn_data_array[items_num] = NULL;
-				}
-				username = nm_setting_vpn_get_user_name (s_vpn);
+		if (nmc_fields_con_active_details_groups[group_idx]->nested == metagen_con_active_vpn) {
+			if (NM_IS_VPN_CONNECTION (acon)) {
+				nmc_print (&nmc->nmc_config,
+				           (gpointer[]) { acon, NULL },
+				           NULL,
+				           NULL,
+				           NMC_META_GENERIC_GROUP ("VPN", metagen_con_active_vpn, N_("NAME")),
+				           group_fld,
+				           NULL);
+				was_output = TRUE;
 			}
-
-			type_str = get_vpn_connection_type (con);
-			banner = nm_vpn_connection_get_banner (NM_VPN_CONNECTION (acon));
-			if (banner)
-				banner_str = g_strescape (banner, "");
-			vpn_state = nm_vpn_connection_get_vpn_state (NM_VPN_CONNECTION (acon));
-			vpn_state_str = g_strdup_printf ("%d - %s",
-			                                 vpn_state,
-			                                 gettext (vpn_connection_state_to_string (vpn_state)));
-
-			/* Add values */
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_SECTION_PREFIX);
-			set_val_strc (arr, 0, nmc_fields_con_active_details_groups[5]->name);
-			set_val_str  (arr, 1, type_str);
-			set_val_strc (arr, 2, username ?: get_vpn_data_item (con, VPN_DATA_ITEM_USERNAME));
-			set_val_strc (arr, 3, get_vpn_data_item (con, VPN_DATA_ITEM_GATEWAY));
-			set_val_str  (arr, 4, banner_str);
-			set_val_str  (arr, 5, vpn_state_str);
-			set_val_arr  (arr, 6, vpn_data_array);
-			g_ptr_array_add (out.output_data, arr);
-
-			print_data_prepare_width (out.output_data);
-			print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
-			was_output = TRUE;
+			continue;
 		}
 	}
 
@@ -1767,7 +1809,7 @@ parse_preferred_connection_order (const char *order, GError **error)
 	gboolean inverse, unique;
 	int i;
 
-	strv = nm_utils_strsplit_set (order, ":");
+	strv = nm_utils_strsplit_set (order, ":", FALSE);
 	if (!strv) {
 		g_set_error (error, NMCLI_ERROR, 0,
 		             _("incorrect string '%s' of '--order' option"), order);
@@ -1926,7 +1968,6 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 		 * 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);
@@ -1951,10 +1992,11 @@ do_connections_show (NmCli *nmc, int argc, char **argv)
 		g_ptr_array_add (items, NULL);
 		if (!nmc_print (&nmc->nmc_config,
 		                items->pdata,
+		                NULL,
 		                active_only
 		                  ? _("NetworkManager active profiles")
 		                  : _("NetworkManager connection profiles"),
-		               (const NMMetaAbstractInfo *const*) metagen_con_show,
+		                (const NMMetaAbstractInfo *const*) metagen_con_show,
 		                fields_str,
 		                &err))
 			goto finish;
@@ -2406,7 +2448,7 @@ progress_active_connection_cb (gpointer user_data)
 	}
 
 	str =   device
-	      ? nmc_device_state_to_string (nm_device_get_state (device))
+	      ? gettext (nmc_device_state_to_string (nm_device_get_state (device)))
 	      : active_connection_state_to_string (ac_state);
 
 	nmc_terminal_show_progress (str);
@@ -2533,7 +2575,7 @@ parse_passwords (const char *passwd_file, GError **error)
 		return NULL;
 	}
 
-	strv = nm_utils_strsplit_set (contents, "\r\n");
+	strv = nm_utils_strsplit_set (contents, "\r\n", FALSE);
 	for (iter = strv; *iter; iter++) {
 		gs_free char *iter_s = g_strdup (*iter);
 
@@ -2689,7 +2731,7 @@ do_connection_up (NmCli *nmc, int argc, char **argv)
 		/* nmc_do_cmd() should not call this with argc=0. */
 		g_assert (!nmc->complete);
 
-		line = nmc_readline ("%s: ", PROMPT_CONNECTION);
+		line = nmc_readline (PROMPT_CONNECTION);
 		nmc_string_to_arg_array (line, NULL, TRUE, &arg_arr, &arg_num);
 		g_free (line);
 		argv_ptr = &arg_arr;
@@ -3189,7 +3231,7 @@ get_valid_properties_string (const NMMetaSettingValidPartItem *const*array,
 
 			/* Search the array with the arguments of the current property */
 			for (j = 0; j < setting_info->properties_num; j++) {
-				gchar *new;
+				char *new;
 				const char *arg_name;
 
 				arg_name = setting_info->properties[j]->property_name;
@@ -3644,6 +3686,18 @@ _meta_abstract_complete (const NMMetaAbstractInfo *abstract_info, const char *te
 	return NULL;
 }
 
+static char *
+_meta_abstract_generator (const char *text, int state)
+{
+	if (nmc_tab_completion.words) {
+		return nmc_rl_gen_func_basic (text,
+		                              state,
+		                              (const char *const *) nmc_tab_completion.words);
+	}
+
+	return NULL;
+}
+
 static void
 _meta_abstract_get (const NMMetaAbstractInfo *abstract_info,
                     const NMMetaSettingInfoEditor **out_setting_info,
@@ -3673,7 +3727,7 @@ static const OptionInfo *_meta_abstract_get_option_info (const NMMetaAbstractInf
  * The questionnaire (for --ask) will ask for them.
  */
 static void
-enable_options (const gchar *setting_name, const gchar *property, const gchar * const *opts)
+enable_options (const char *setting_name, const char *property, const char * const *opts)
 {
 	const NMMetaPropertyInfo *property_info;
 
@@ -3711,7 +3765,7 @@ enable_options (const gchar *setting_name, const gchar *property, const gchar *
  * The questionnaire (for --ask) will not ask for them.
  */
 static void
-disable_options (const gchar *setting_name, const gchar *property)
+disable_options (const char *setting_name, const char *property)
 {
 	const NMMetaPropertyInfo *property_infos_local[2];
 	const NMMetaPropertyInfo *const*property_infos;
@@ -3869,7 +3923,7 @@ set_property (NMConnection *connection,
 }
 
 static gboolean
-set_option (NmCli *nmc, NMConnection *connection, const NMMetaAbstractInfo *abstract_info, const gchar *value, GError **error)
+set_option (NmCli *nmc, NMConnection *connection, const NMMetaAbstractInfo *abstract_info, const char *value, GError **error)
 {
 	const char *setting_name, *property_name, *option_name;
 	NMMetaPropertyInfFlags inf_flags;
@@ -3993,7 +4047,7 @@ set_connection_type (NmCli *nmc, NMConnection *con, const OptionInfo *option, co
 	const NMMetaSettingValidPartItem *const*type_settings;
 	const NMMetaSettingValidPartItem *const*slv_settings;
 	GError *local = NULL;
-	const gchar *master[] = { "master", NULL };
+	const char *master[] = { "master", NULL };
 	const char *slave_type = NULL;
 
 	value = check_valid_name_toplevel (value, &slave_type, &local);
@@ -4101,7 +4155,7 @@ set_bond_option (NmCli *nmc, NMConnection *con, const OptionInfo *option, const
 			return FALSE;
 
 		if (g_strcmp0 (value, "active-backup") == 0) {
-			const gchar *primary[] = { "primary", NULL };
+			const char *primary[] = { "primary", NULL };
 			enable_options (NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS, primary);
 		}
 
@@ -4136,9 +4190,9 @@ static gboolean
 set_bond_monitoring_mode (NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, GError **error)
 {
 	NMSettingBond *s_bond;
-	gs_free gchar *monitor_mode = NULL;
-	const gchar *miimon_opts[] = { "miimon", "downdelay", "updelay", NULL };
-	const gchar *arp_opts[] = { "arp-interval", "arp-ip-target", NULL };
+	gs_free char *monitor_mode = NULL;
+	const char *miimon_opts[] = { "miimon", "downdelay", "updelay", NULL };
+	const char *arp_opts[] = { "arp-interval", "arp-ip-target", NULL };
 
 	s_bond = nm_connection_get_setting_bond (con);
 	g_return_val_if_fail (s_bond, FALSE);
@@ -4307,8 +4361,8 @@ option_relevant (NMConnection *connection, const NMMetaAbstractInfo *abstract_in
 static void
 complete_property_name (NmCli *nmc, NMConnection *connection,
                         char modifier,
-                        const gchar *prefix,
-                        const gchar *postfix)
+                        const char *prefix,
+                        const char *postfix)
 {
 	NMSettingConnection *s_con;
 	const NMMetaSettingValidPartItem *const*valid_settings_main;
@@ -4383,7 +4437,7 @@ run_rl_generator (rl_compentry_func_t *generator_func, const char *prefix)
 }
 
 static gboolean
-complete_option (const NMMetaAbstractInfo *abstract_info, const gchar *prefix, NMConnection *context_connection)
+complete_option (const NMMetaAbstractInfo *abstract_info, const char *prefix, NMConnection *context_connection)
 {
 	const OptionInfo *candidate;
 	const char *const*values;
@@ -4414,7 +4468,7 @@ complete_option (const NMMetaAbstractInfo *abstract_info, const gchar *prefix, N
 }
 
 static void
-complete_property (const gchar *setting_name, const gchar *property, const gchar *prefix, NMConnection *connection)
+complete_property (const char *setting_name, const char *property, const char *prefix, NMConnection *connection)
 {
 	const NMMetaPropertyInfo *property_info;
 
@@ -4468,7 +4522,7 @@ nmc_read_connection_properties (NmCli *nmc,
 	/* Go through arguments and set properties */
 	do {
 		const NMMetaAbstractInfo *chosen = NULL;
-		gs_strfreev gchar **strv = NULL;
+		gs_strfreev char **strv = NULL;
 		const NMMetaSettingValidPartItem *const*type_settings;
 		const NMMetaSettingValidPartItem *const*slv_settings;
 		char modifier = '\0';
@@ -4711,6 +4765,7 @@ nmcli_con_add_tab_completion (const char *text, int start, int end)
 	rl_compentry_func_t *generator_func = NULL;
 	gs_free char *no = g_strdup_printf ("[%s]: ", _("no"));
 	gs_free char *yes = g_strdup_printf ("[%s]: ", _("yes"));
+	const NMMetaAbstractInfo *info;
 
 	/* Disable readline's default filename completion */
 	rl_attempted_completion_over = 1;
@@ -4745,12 +4800,13 @@ nmcli_con_add_tab_completion (const char *text, int start, int end)
 			} else {
 				if (   property_info->prompt
 				    && g_str_has_prefix (rl_prompt, property_info->prompt)) {
-					char **values;
-
-					values = _meta_abstract_complete ((const NMMetaAbstractInfo *) property_info, text);
-					if (values)
-						return values;
-					goto next;
+					info = (const NMMetaAbstractInfo *) property_info;
+					nmc_tab_completion.words = _meta_abstract_complete (info, text);
+					if (nmc_tab_completion.words) {
+						match_array = rl_completion_matches (text, _meta_abstract_generator);
+						nm_clear_pointer (&nmc_tab_completion.words, g_strfreev);
+					}
+					return match_array;
 				}
 			}
 		}
@@ -4776,9 +4832,9 @@ next:
 static void
 ask_option (NmCli *nmc, NMConnection *connection, const NMMetaAbstractInfo *abstract_info)
 {
-	gchar *value;
+	char *value;
 	GError *error = NULL;
-	gs_free gchar *prompt = NULL;
+	gs_free char *prompt = NULL;
 	gboolean multi;
 	const char *opt_prompt, *opt_def_hint;
 	NMMetaPropertyInfFlags inf_flags;
@@ -6534,7 +6590,7 @@ progress_activation_editor_cb (gpointer user_data)
 	ac_state = nm_active_connection_get_state (ac);
 	dev_state = nm_device_get_state (device);
 
-	nmc_terminal_show_progress (nmc_device_state_to_string (dev_state));
+	nmc_terminal_show_progress (gettext (nmc_device_state_to_string (dev_state)));
 
 	if (   ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
 	    || dev_state == NM_DEVICE_STATE_ACTIVATED) {
@@ -8114,6 +8170,8 @@ do_connection_edit (NmCli *nmc, int argc, char **argv)
 	g_print ("\n\n");
 	g_print (_("Type 'help' or '?' for available commands."));
 	g_print ("\n");
+	g_print (_("Type 'print' to show all the connection properties."));
+	g_print ("\n");
 	g_print (_("Type 'describe [<setting>.<prop>]' for detailed property description."));
 	g_print ("\n\n");
 
@@ -8270,7 +8328,7 @@ do_connection_clone (NmCli *nmc, int argc, char **argv)
 		/* nmc_do_cmd() should not call this with argc=0. */
 		g_assert (!nmc->complete);
 
-		line = nmc_readline ("%s: ", PROMPT_CONNECTION);
+		line = nmc_readline (PROMPT_CONNECTION);
 		nmc_string_to_arg_array (line, NULL, TRUE, &arg_arr, &arg_num);
 		g_free (line);
 		argv_ptr = &arg_arr;
@@ -8380,7 +8438,7 @@ do_connection_delete (NmCli *nmc, int argc, char **argv)
 			/* nmc_do_cmd() should not call this with argc=0. */
 			g_assert (!nmc->complete);
 
-			line = nmc_readline ("%s: ", PROMPT_CONNECTIONS);
+			line = nmc_readline (PROMPT_CONNECTIONS);
 			nmc_string_to_arg_array (line, NULL, TRUE, &arg_arr, &arg_num);
 			g_free (line);
 			arg_ptr = arg_arr;
@@ -8631,7 +8689,7 @@ do_connection_import (NmCli *nmc, int argc, char **argv)
 		g_assert (!nmc->complete);
 
 		if (nmc->ask) {
-			type_ask = nmc_readline (gettext (NM_META_TEXT_PROMPT_VPN_TYPE));
+			type_ask = nmc_readline ("%s: ", gettext (NM_META_TEXT_PROMPT_VPN_TYPE));
 			filename_ask = nmc_readline (gettext (PROMPT_IMPORT_FILE));
 			type = type_ask = type_ask ? g_strstrip (type_ask) : NULL;
 			filename = filename_ask = filename_ask ? g_strstrip (filename_ask) : NULL;
@@ -8756,7 +8814,7 @@ do_connection_export (NmCli *nmc, int argc, char **argv)
 		/* nmc_do_cmd() should not call this with argc=0. */
 		g_assert (!nmc->complete);
 
-		line = nmc_readline ("%s: ", PROMPT_VPN_CONNECTION);
+		line = nmc_readline (PROMPT_VPN_CONNECTION);
 		nmc_string_to_arg_array (line, NULL, TRUE, &arg_arr, &arg_num);
 		g_free (line);
 		argv_ptr = &arg_arr;
@@ -8896,6 +8954,7 @@ nmcli_con_tab_completion (const char *text, int start, int end)
 {
 	char **match_array = NULL;
 	rl_compentry_func_t *generator_func = NULL;
+	const NMMetaAbstractInfo *info;
 
 	/* Disable readline's default filename completion */
 	rl_attempted_completion_over = 1;
@@ -8912,8 +8971,10 @@ nmcli_con_tab_completion (const char *text, int start, int end)
 		generator_func = gen_func_connection_names;
 	} else if (g_strcmp0 (rl_prompt, PROMPT_ACTIVE_CONNECTIONS) == 0) {
 		generator_func = gen_func_active_connection_names;
-	} else if (g_strcmp0 (rl_prompt, NM_META_TEXT_PROMPT_VPN_TYPE) == 0) {
-		return _meta_abstract_complete ((const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type, text);
+	} else if (rl_prompt && g_str_has_prefix (rl_prompt, NM_META_TEXT_PROMPT_VPN_TYPE)) {
+		info = (const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type;
+		nmc_tab_completion.words = _meta_abstract_complete (info, text);
+		generator_func = _meta_abstract_generator;
 	} else if (g_strcmp0 (rl_prompt, PROMPT_IMPORT_FILE) == 0) {
 		rl_attempted_completion_over = 0;
 		rl_complete_with_tilde_expansion = 1;
@@ -8924,6 +8985,7 @@ nmcli_con_tab_completion (const char *text, int start, int end)
 	if (generator_func)
 		match_array = rl_completion_matches (text, generator_func);
 
+	g_clear_pointer (&nmc_tab_completion.words, g_strfreev);
 	return match_array;
 }
 
diff --git a/clients/cli/connections.h b/clients/cli/connections.h
index 43cd97f6..22bfa8ec 100644
--- a/clients/cli/connections.h
+++ b/clients/cli/connections.h
@@ -37,7 +37,7 @@ NMMetaColor nmc_active_connection_state_to_color (NMActiveConnectionState state)
 
 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 metagen_con_active_vpn[];
 extern const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[];
 
 #endif /* NMC_CONNECTIONS_H */
diff --git a/clients/cli/devices.c b/clients/cli/devices.c
index be759732..ca575c33 100644
--- a/clients/cli/devices.c
+++ b/clients/cli/devices.c
@@ -39,99 +39,518 @@
 #define PROMPT_INTERFACE  _("Interface: ")
 #define PROMPT_INTERFACES _("Interface(s): ")
 
-const NmcMetaGenericInfo *const nmc_fields_dev_status[] = {
-	NMC_META_GENERIC ("DEVICE"),       /* 0 */
-	NMC_META_GENERIC ("TYPE"),         /* 1 */
-	NMC_META_GENERIC ("STATE"),        /* 2 */
-	NMC_META_GENERIC ("DBUS-PATH"),    /* 3 */
-	NMC_META_GENERIC ("CONNECTION"),   /* 4 */
-	NMC_META_GENERIC ("CON-UUID"),     /* 5 */
-	NMC_META_GENERIC ("CON-PATH"),     /* 6 */
-	NULL,
+/*****************************************************************************/
+
+static char *
+ap_wpa_rsn_flags_to_string (NM80211ApSecurityFlags flags)
+{
+	char *flags_str[16]; /* Enough space for flags and terminating NULL */
+	char *ret_str;
+	int i = 0;
+
+	if (flags & NM_802_11_AP_SEC_PAIR_WEP40)
+		flags_str[i++] = g_strdup ("pair_wpe40");
+	if (flags & NM_802_11_AP_SEC_PAIR_WEP104)
+		flags_str[i++] = g_strdup ("pair_wpe104");
+	if (flags & NM_802_11_AP_SEC_PAIR_TKIP)
+		flags_str[i++] = g_strdup ("pair_tkip");
+	if (flags & NM_802_11_AP_SEC_PAIR_CCMP)
+		flags_str[i++] = g_strdup ("pair_ccmp");
+	if (flags & NM_802_11_AP_SEC_GROUP_WEP40)
+		flags_str[i++] = g_strdup ("group_wpe40");
+	if (flags & NM_802_11_AP_SEC_GROUP_WEP104)
+		flags_str[i++] = g_strdup ("group_wpe104");
+	if (flags & NM_802_11_AP_SEC_GROUP_TKIP)
+		flags_str[i++] = g_strdup ("group_tkip");
+	if (flags & NM_802_11_AP_SEC_GROUP_CCMP)
+		flags_str[i++] = g_strdup ("group_ccmp");
+	if (flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
+		flags_str[i++] = g_strdup ("psk");
+	if (flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
+		flags_str[i++] = g_strdup ("802.1X");
+
+	if (i == 0)
+		flags_str[i++] = g_strdup (_("(none)"));
+
+	flags_str[i] = NULL;
+
+	ret_str = g_strjoinv (" ", flags_str);
+
+	i = 0;
+	while (flags_str[i])
+		g_free (flags_str[i++]);
+
+	return ret_str;
+}
+
+static NMMetaColor
+wifi_signal_to_color (guint8 strength)
+{
+	if (strength > 80)
+		return NM_META_COLOR_WIFI_SIGNAL_EXCELLENT;
+	else if (strength > 55)
+		return NM_META_COLOR_WIFI_SIGNAL_GOOD;
+	else if (strength > 30)
+		return NM_META_COLOR_WIFI_SIGNAL_FAIR;
+	else if (strength > 5)
+		return NM_META_COLOR_WIFI_SIGNAL_POOR;
+	else
+		return NM_META_COLOR_WIFI_SIGNAL_UNKNOWN;
+}
+
+/*****************************************************************************/
+
+static gconstpointer
+_metagen_device_status_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMDevice *d = target;
+	NMActiveConnection *ac;
+
+	NMC_HANDLE_COLOR (nmc_device_state_to_color (nm_device_get_state (d)));
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_DEVICE:
+		return nm_device_get_iface (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_TYPE:
+		return nm_device_get_type_description (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_STATE:
+		return nmc_meta_generic_get_str_i18n (nmc_device_state_to_string (nm_device_get_state (d)),
+		                                      get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_DBUS_PATH:
+		return nm_object_get_path (NM_OBJECT (d));
+	case NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CONNECTION:
+		ac = nm_device_get_active_connection (d);
+		return ac ? nm_active_connection_get_id (ac) : NULL;
+	case NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CON_UUID:
+		ac = nm_device_get_active_connection (d);
+		return ac ? nm_active_connection_get_uuid (ac) : NULL;
+	case NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CON_PATH:
+		ac = nm_device_get_active_connection (d);
+		return ac ? nm_object_get_path (NM_OBJECT (ac)) : NULL;
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+const NmcMetaGenericInfo *const metagen_device_status[_NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_NUM + 1] = {
+#define _METAGEN_DEVICE_STATUS(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_device_status_get_fcn)
+	_METAGEN_DEVICE_STATUS (NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_DEVICE,     "DEVICE"),
+	_METAGEN_DEVICE_STATUS (NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_TYPE,       "TYPE"),
+	_METAGEN_DEVICE_STATUS (NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_STATE,      "STATE"),
+	_METAGEN_DEVICE_STATUS (NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_DBUS_PATH,  "DBUS-PATH"),
+	_METAGEN_DEVICE_STATUS (NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CONNECTION, "CONNECTION"),
+	_METAGEN_DEVICE_STATUS (NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CON_UUID,   "CON-UUID"),
+	_METAGEN_DEVICE_STATUS (NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CON_PATH,   "CON-PATH"),
 };
-#define NMC_FIELDS_DEV_STATUS_COMMON  "DEVICE,TYPE,STATE,CONNECTION"
-
-const NmcMetaGenericInfo *const nmc_fields_dev_show_general[] = {
-	NMC_META_GENERIC ("NAME"),                /* 0 */
-	NMC_META_GENERIC ("DEVICE"),              /* 1 */
-	NMC_META_GENERIC ("TYPE"),                /* 2 */
-	NMC_META_GENERIC ("NM-TYPE"),             /* 3 */
-	NMC_META_GENERIC ("VENDOR"),              /* 4 */
-	NMC_META_GENERIC ("PRODUCT"),             /* 5 */
-	NMC_META_GENERIC ("DRIVER"),              /* 6 */
-	NMC_META_GENERIC ("DRIVER-VERSION"),      /* 7 */
-	NMC_META_GENERIC ("FIRMWARE-VERSION"),    /* 8 */
-	NMC_META_GENERIC ("HWADDR"),              /* 9 */
-	NMC_META_GENERIC ("MTU"),                 /* 10 */
-	NMC_META_GENERIC ("STATE"),               /* 11 */
-	NMC_META_GENERIC ("REASON"),              /* 12 */
-	NMC_META_GENERIC ("UDI"),                 /* 13 */
-	NMC_META_GENERIC ("IP-IFACE"),            /* 14 */
-	NMC_META_GENERIC ("IS-SOFTWARE"),         /* 15 */
-	NMC_META_GENERIC ("NM-MANAGED"),          /* 16 */
-	NMC_META_GENERIC ("AUTOCONNECT"),         /* 17 */
-	NMC_META_GENERIC ("FIRMWARE-MISSING"),    /* 18 */
-	NMC_META_GENERIC ("NM-PLUGIN-MISSING"),   /* 19 */
-	NMC_META_GENERIC ("PHYS-PORT-ID"),        /* 20 */
-	NMC_META_GENERIC ("CONNECTION"),          /* 21 */
-	NMC_META_GENERIC ("CON-UUID"),            /* 22 */
-	NMC_META_GENERIC ("CON-PATH"),            /* 23 */
-	NMC_META_GENERIC ("METERED"),             /* 24 */
-	NULL,
+
+/*****************************************************************************/
+
+static gconstpointer
+_metagen_device_detail_general_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMDevice *d = target;
+	NMActiveConnection *ac;
+	NMDeviceState state;
+	NMDeviceStateReason state_reason;
+	const char *s;
+
+	NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DEVICE:
+		return nm_device_get_iface (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_TYPE:
+		return nm_device_get_type_description (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_TYPE:
+		return G_OBJECT_TYPE_NAME (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_VENDOR:
+		return nm_device_get_vendor (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_PRODUCT:
+		return nm_device_get_product (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DRIVER:
+		s = nm_device_get_driver (d);
+		return s ?: nmc_meta_generic_get_str_i18n (N_("(unknown)"), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DRIVER_VERSION:
+		return nm_device_get_driver_version (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_FIRMWARE_VERSION:
+		return nm_device_get_firmware_version (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_HWADDR:
+		s = nm_device_get_hw_address (d);
+		return s ?: nmc_meta_generic_get_str_i18n (N_("(unknown)"), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_MTU:
+		return (*out_to_free = g_strdup_printf ("%u", (guint) nm_device_get_mtu (d)));
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_STATE:
+		state = nm_device_get_state (d);
+		return (*out_to_free = nmc_meta_generic_get_enum_with_detail (NMC_META_GENERIC_GET_ENUM_TYPE_PARENTHESES,
+		                                                              state,
+		                                                              nmc_device_state_to_string (state),
+		                                                              get_type));
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_REASON:
+		state_reason = nm_device_get_state_reason (d);
+		return (*out_to_free = nmc_meta_generic_get_enum_with_detail (NMC_META_GENERIC_GET_ENUM_TYPE_PARENTHESES,
+		                                                              state_reason,
+		                                                              nmc_device_reason_to_string (state_reason),
+		                                                              get_type));
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_UDI:
+		return nm_device_get_udi (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_IP_IFACE:
+		return nm_device_get_ip_iface (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_IS_SOFTWARE:
+		return nmc_meta_generic_get_bool (nm_device_is_software (d), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_MANAGED:
+		return nmc_meta_generic_get_bool (nm_device_get_managed (d), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_AUTOCONNECT:
+		return nmc_meta_generic_get_bool (nm_device_get_autoconnect (d), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_FIRMWARE_MISSING:
+		return nmc_meta_generic_get_bool (nm_device_get_firmware_missing (d), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_PLUGIN_MISSING:
+		return nmc_meta_generic_get_bool (nm_device_get_nm_plugin_missing (d), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_PHYS_PORT_ID:
+		return nm_device_get_physical_port_id (d);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CONNECTION:
+		ac = nm_device_get_active_connection (d);
+		return ac ? nm_active_connection_get_id (ac) : NULL;
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CON_UUID:
+		ac = nm_device_get_active_connection (d);
+		return ac ? nm_active_connection_get_uuid (ac) : NULL;
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CON_PATH:
+		ac = nm_device_get_active_connection (d);
+		return ac ? nm_object_get_path (NM_OBJECT (ac)) : NULL;
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_METERED:
+		return nmc_meta_generic_get_str_i18n (nmc_device_metered_to_string (nm_device_get_metered (d)),
+		                                      get_type);
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+const NmcMetaGenericInfo *const metagen_device_detail_general[_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NUM + 1] = {
+#define _METAGEN_DEVICE_DETAIL_GENERAL(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_device_detail_general_get_fcn)
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DEVICE,            "DEVICE"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_TYPE,              "TYPE"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_TYPE,           "NM-TYPE"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_VENDOR,            "VENDOR"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_PRODUCT,           "PRODUCT"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DRIVER,            "DRIVER"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DRIVER_VERSION,    "DRIVER-VERSION"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_FIRMWARE_VERSION,  "FIRMWARE-VERSION"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_HWADDR,            "HWADDR"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_MTU,               "MTU"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_STATE,             "STATE"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_REASON,            "REASON"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_UDI,               "UDI"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_IP_IFACE,          "IP-IFACE"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_IS_SOFTWARE,       "IS-SOFTWARE"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_MANAGED,        "NM-MANAGED"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_AUTOCONNECT,       "AUTOCONNECT"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_FIRMWARE_MISSING,  "FIRMWARE-MISSING"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_PLUGIN_MISSING, "NM-PLUGIN-MISSING"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_PHYS_PORT_ID,      "PHYS-PORT-ID"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CONNECTION,        "CONNECTION"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CON_UUID,          "CON-UUID"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CON_PATH,          "CON-PATH"),
+	_METAGEN_DEVICE_DETAIL_GENERAL (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_METERED,           "METERED"),
 };
-#define NMC_FIELDS_DEV_SHOW_GENERAL_COMMON  "NAME,DEVICE,TYPE,VENDOR,PRODUCT,DRIVER,HWADDR,STATE"
 
-const NmcMetaGenericInfo *const nmc_fields_dev_show_connections[] = {
-	NMC_META_GENERIC ("NAME"),                         /* 0 */
-	NMC_META_GENERIC ("AVAILABLE-CONNECTION-PATHS"),   /* 1 */
-	NMC_META_GENERIC ("AVAILABLE-CONNECTIONS"),        /* 2 */
-	NULL,
+/*****************************************************************************/
+
+static NMRemoteConnection **
+_device_get_available_connections (NMDevice *d, guint *out_len)
+{
+	NMRemoteConnection **avail_cons;
+	const GPtrArray *avail_cons_arr;
+
+	avail_cons_arr = nm_device_get_available_connections (d);
+	if (!avail_cons_arr || avail_cons_arr->len == 0) {
+		*out_len = 0;
+		return NULL;
+	}
+
+	avail_cons = (NMRemoteConnection **) nmc_objects_sort_by_path ((const NMObject *const*) avail_cons_arr->pdata,
+	                                                               avail_cons_arr->len);
+	nm_assert (avail_cons_arr->len == NM_PTRARRAY_LEN (avail_cons));
+	*out_len = avail_cons_arr->len;
+	return avail_cons;
+}
+
+static gconstpointer
+_metagen_device_detail_connections_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMDevice *d = target;
+	gs_free NMRemoteConnection **avail_cons = NULL;
+	guint avail_cons_len;
+	guint i, j;
+	char **arr = NULL;
+	GString *str;
+	gboolean had_prefix, has_prefix;
+
+	NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_AVAILABLE_CONNECTIONS:
+		if (!NM_FLAGS_HAS (get_flags, NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV))
+			return NULL;
+
+		avail_cons = _device_get_available_connections (d, &avail_cons_len);
+		if (avail_cons_len == 0)
+			goto arr_out;
+
+		arr = g_new (char *, avail_cons_len + 1);
+		j = 0;
+		for (i = 0; i < avail_cons_len; i++) {
+			NMRemoteConnection *ac = avail_cons[i];
+			const char *ac_id = nm_connection_get_id (NM_CONNECTION (ac));
+			const char *ac_uuid = nm_connection_get_uuid (NM_CONNECTION (ac));
+
+			if (!ac_id || !ac_uuid) {
+				const char *ac_path = nm_connection_get_path (NM_CONNECTION (ac));
+
+				if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) {
+					arr[j++] =   ac_path
+					           ? g_strdup_printf (_("<invisible> | %s"), ac_path)
+					           : g_strdup (_("<invisible>"));
+				} else {
+					arr[j++] =   ac_path
+					           ? g_strdup_printf ("<invisible> | %s", ac_path)
+					           : g_strdup ("<invisible>");
+				}
+			} else
+				arr[j++] = g_strdup_printf ("%s | %s", ac_uuid, ac_id);
+		}
+		arr[j] = NULL;
+		goto arr_out;
+
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_AVAILABLE_CONNECTION_PATHS:
+
+		avail_cons = _device_get_available_connections (d, &avail_cons_len);
+		if (avail_cons_len == 0)
+			return NULL;
+
+		str = g_string_new (NULL);
+
+		had_prefix = FALSE;
+		for (i = 0; i < avail_cons_len; i++) {
+			NMRemoteConnection *ac = avail_cons[i];
+			const char *p = nm_connection_get_path (NM_CONNECTION (ac));
+
+			if (!p)
+				continue;
+
+			has_prefix =    g_str_has_prefix (p, NM_DBUS_PATH_SETTINGS_CONNECTION"/")
+			             && p[NM_STRLEN (NM_DBUS_PATH_SETTINGS_CONNECTION"/")];
+
+			if (str->len > 0) {
+				if (   had_prefix
+				    && !has_prefix)
+					g_string_append_c (str, '}');
+				g_string_append_c (str, ',');
+			}
+
+			if (!has_prefix)
+				g_string_append (str, p);
+			else {
+				if (!had_prefix)
+					g_string_printf (str, "%s/{", NM_DBUS_PATH_SETTINGS_CONNECTION);
+				g_string_append (str, &p[NM_STRLEN (NM_DBUS_PATH_SETTINGS_CONNECTION"/")]);
+			}
+			had_prefix = has_prefix;
+		}
+		if (had_prefix)
+			g_string_append_c (str, '}');
+
+		return (*out_to_free = g_string_free (str, FALSE));
+
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+
+arr_out:
+	NM_SET_OUT (out_is_default, !arr || !arr[0]);
+	*out_flags |= NM_META_ACCESSOR_GET_OUT_FLAGS_STRV;
+	*out_to_free = arr;
+	return arr;
+}
+
+const NmcMetaGenericInfo *const metagen_device_detail_connections[_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_NUM + 1] = {
+#define _METAGEN_DEVICE_DETAIL_CONNECTIONS(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_device_detail_connections_get_fcn)
+	_METAGEN_DEVICE_DETAIL_CONNECTIONS (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_AVAILABLE_CONNECTION_PATHS, "AVAILABLE-CONNECTION-PATHS"),
+	_METAGEN_DEVICE_DETAIL_CONNECTIONS (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_AVAILABLE_CONNECTIONS,      "AVAILABLE-CONNECTIONS"),
 };
-#define NMC_FIELDS_DEV_SHOW_CONNECTIONS_COMMON  "NAME,AVAILABLE-CONNECTION-PATHS,AVAILABLE-CONNECTIONS"
-
-const NmcMetaGenericInfo *const nmc_fields_dev_show_cap[] = {
-	NMC_META_GENERIC ("NAME"),             /* 0 */
-	NMC_META_GENERIC ("CARRIER-DETECT"),   /* 1 */
-	NMC_META_GENERIC ("SPEED"),            /* 2 */
-	NMC_META_GENERIC ("IS-SOFTWARE"),      /* 3 */
-	NMC_META_GENERIC ("SRIOV"),            /* 4 */
-	NULL,
+
+/*****************************************************************************/
+
+static gconstpointer
+_metagen_device_detail_capabilities_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMDevice *d = target;
+	NMDeviceCapabilities caps;
+	guint32 speed;
+
+	NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
+
+	caps = nm_device_get_capabilities (d);
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_CARRIER_DETECT:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (caps, NM_DEVICE_CAP_CARRIER_DETECT), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_SPEED:
+		speed = 0;
+		if (NM_IS_DEVICE_ETHERNET (d)) {
+			/* Speed in Mb/s */
+			speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (d));
+		} else if (NM_IS_DEVICE_WIFI (d)) {
+			/* Speed in b/s */
+			speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (d));
+			speed /= 1000;
+		}
+
+		if (speed) {
+			if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY)
+				return (*out_to_free = g_strdup_printf (_("%u Mb/s"), (guint) speed));
+			return (*out_to_free = g_strdup_printf ("%u Mb/s", (guint) speed));
+		}
+		return nmc_meta_generic_get_str_i18n (N_("unknown"), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_IS_SOFTWARE:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (caps, NM_DEVICE_CAP_IS_SOFTWARE), get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_SRIOV:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (caps, NM_DEVICE_CAP_SRIOV), get_type);
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+const NmcMetaGenericInfo *const metagen_device_detail_capabilities[_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_NUM + 1] = {
+#define _METAGEN_DEVICE_DETAIL_CAPABILITIES(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_device_detail_capabilities_get_fcn)
+	_METAGEN_DEVICE_DETAIL_CAPABILITIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_CARRIER_DETECT, "CARRIER-DETECT"),
+	_METAGEN_DEVICE_DETAIL_CAPABILITIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_SPEED,          "SPEED"),
+	_METAGEN_DEVICE_DETAIL_CAPABILITIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_IS_SOFTWARE,    "IS-SOFTWARE"),
+	_METAGEN_DEVICE_DETAIL_CAPABILITIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_SRIOV,          "SRIOV"),
 };
-#define NMC_FIELDS_DEV_SHOW_CAP_COMMON  "NAME,CARRIER-DETECT,SPEED,IS-SOFTWARE"
 
-const NmcMetaGenericInfo *const nmc_fields_dev_show_wired_prop[] = {
-	NMC_META_GENERIC ("NAME"),               /* 0 */
-	NMC_META_GENERIC ("CARRIER"),            /* 1 */
-	NMC_META_GENERIC ("S390-SUBCHANNELS"),   /* 2 */
-	NULL,
+/*****************************************************************************/
+
+static gconstpointer
+_metagen_device_detail_wired_properties_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMDevice *d = target;
+
+	NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_CARRIER:
+		return nmc_meta_generic_get_bool_onoff (nm_device_ethernet_get_carrier (NM_DEVICE_ETHERNET (d)),
+		                                        get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_S390_SUBCHANNELS:
+		if (!NM_FLAGS_HAS (get_flags, NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV))
+			return NULL;
+		*out_flags |= NM_META_ACCESSOR_GET_OUT_FLAGS_STRV;
+		return nm_device_ethernet_get_s390_subchannels (NM_DEVICE_ETHERNET (d));
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+const NmcMetaGenericInfo *const metagen_device_detail_wired_properties[_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_NUM + 1] = {
+#define _METAGEN_DEVICE_DETAIL_WIRED_PROPERTIES(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_device_detail_wired_properties_get_fcn)
+	_METAGEN_DEVICE_DETAIL_WIRED_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_CARRIER,          "CARRIER"),
+	_METAGEN_DEVICE_DETAIL_WIRED_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_S390_SUBCHANNELS, "S390-SUBCHANNELS"),
 };
-#define NMC_FIELDS_DEV_SHOW_WIRED_PROP_COMMON  "NAME,CARRIER,S390-SUBCHANNELS"
-
-const NmcMetaGenericInfo *const nmc_fields_dev_show_wifi_prop[] = {
-	NMC_META_GENERIC ("NAME"),    /* 0 */
-	NMC_META_GENERIC ("WEP"),     /* 1 */
-	NMC_META_GENERIC ("WPA"),     /* 2 */
-	NMC_META_GENERIC ("WPA2"),    /* 3 */
-	NMC_META_GENERIC ("TKIP"),    /* 4 */
-	NMC_META_GENERIC ("CCMP"),    /* 5 */
-	NMC_META_GENERIC ("AP"),      /* 6 */
-	NMC_META_GENERIC ("ADHOC"),   /* 7 */
-	NMC_META_GENERIC ("2GHZ"),    /* 8 */
-	NMC_META_GENERIC ("5GHZ"),    /* 9 */
-	NULL,
+
+/*****************************************************************************/
+
+static gconstpointer
+_metagen_device_detail_wifi_properties_get_fcn (NMC_META_GENERIC_INFO_GET_FCN_ARGS)
+{
+	NMDevice *d = target;
+	NMDeviceWifiCapabilities wcaps;
+
+	NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
+
+	wcaps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (d));
+
+	switch (info->info_type) {
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WEP:
+		return nmc_meta_generic_get_bool (NM_FLAGS_ANY (wcaps, NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104),
+		                                  get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WPA:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_WPA),
+		                                  get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WPA2:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_RSN),
+		                                  get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_TKIP:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_CIPHER_TKIP),
+		                                  get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_CCMP:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_CIPHER_CCMP),
+		                                  get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_AP:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_AP),
+		                                  get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_ADHOC:
+		return nmc_meta_generic_get_bool (NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_ADHOC),
+		                                  get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_2GHZ:
+		return nmc_meta_generic_get_str_i18n (  NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_FREQ_VALID)
+		                                      ? (  NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_FREQ_2GHZ)
+		                                         ? N_("yes")
+		                                         : N_("no"))
+		                                      : N_("unknown"),
+		                                      get_type);
+	case NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_5GHZ:
+		return nmc_meta_generic_get_str_i18n (  NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_FREQ_VALID)
+		                                      ? (  NM_FLAGS_HAS (wcaps, NM_WIFI_DEVICE_CAP_FREQ_5GHZ)
+		                                         ? N_("yes")
+		                                         : N_("no"))
+		                                      : N_("unknown"),
+		                                      get_type);
+	default:
+		break;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+const NmcMetaGenericInfo *const metagen_device_detail_wifi_properties[_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_NUM + 1] = {
+#define _METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES(type, name) \
+	[type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_device_detail_wifi_properties_get_fcn)
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WEP,   "WEP"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WPA,   "WPA"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WPA2,  "WPA2"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_TKIP,  "TKIP"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_CCMP,  "CCMP"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_AP,    "AP"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_ADHOC, "ADHOC"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_2GHZ,  "2GHZ"),
+	_METAGEN_DEVICE_DETAIL_WIFI_PROPERTIES (NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_5GHZ,  "5GHZ"),
 };
-#define NMC_FIELDS_DEV_SHOW_WIFI_PROP_COMMON  "NAME,WEP,WPA,WPA2,TKIP,CCMP,AP,ADHOC"
-
-const NmcMetaGenericInfo *const nmc_fields_dev_show_wimax_prop[] = {
-	NMC_META_GENERIC ("NAME"),       /* 0 */
-	NMC_META_GENERIC ("CTR-FREQ"),   /* 1 */
-	NMC_META_GENERIC ("RSSI"),       /* 2 */
-	NMC_META_GENERIC ("CINR"),       /* 3 */
-	NMC_META_GENERIC ("TX-POW"),     /* 4 */
-	NMC_META_GENERIC ("BSID"),       /* 5 */
-	NULL,
+
+/*****************************************************************************/
+
+const NmcMetaGenericInfo *const metagen_device_detail_wimax_properties[] = {
+	NMC_META_GENERIC ("CTR-FREQ"),
+	NMC_META_GENERIC ("RSSI"),
+	NMC_META_GENERIC ("CINR"),
+	NMC_META_GENERIC ("TX-POW"),
+	NMC_META_GENERIC ("BSID"),
 };
-#define NMC_FIELDS_DEV_SHOW_WIMAX_PROP_COMMON  "NAME,CTR-FREQ,RSSI,CINR,TX-POW,BSID"
+
+/*****************************************************************************/
 
 const NmcMetaGenericInfo *const nmc_fields_dev_wifi_list[] = {
 	NMC_META_GENERIC ("NAME"),        /* 0 */
@@ -201,23 +620,23 @@ const NmcMetaGenericInfo *const nmc_fields_dev_show_bluetooth[] = {
 
 /* Available sections for 'device show' */
 const NmcMetaGenericInfo *const nmc_fields_dev_show_sections[] = {
-	NMC_META_GENERIC_WITH_NESTED ("GENERAL",           nmc_fields_dev_show_general + 1),      /* 0 */
-	NMC_META_GENERIC_WITH_NESTED ("CAPABILITIES",      nmc_fields_dev_show_cap + 1),          /* 1 */
-	NMC_META_GENERIC_WITH_NESTED ("WIFI-PROPERTIES",   nmc_fields_dev_show_wifi_prop + 1),    /* 2 */
+	NMC_META_GENERIC_WITH_NESTED ("GENERAL",           metagen_device_detail_general),        /* 0 */
+	NMC_META_GENERIC_WITH_NESTED ("CAPABILITIES",      metagen_device_detail_capabilities),   /* 1 */
+	NMC_META_GENERIC_WITH_NESTED ("WIFI-PROPERTIES",   metagen_device_detail_wifi_properties), /* 2 */
 	NMC_META_GENERIC_WITH_NESTED ("AP",                nmc_fields_dev_wifi_list + 1),         /* 3 */
-	NMC_META_GENERIC_WITH_NESTED ("WIRED-PROPERTIES",  nmc_fields_dev_show_wired_prop + 1),   /* 4 */
-	NMC_META_GENERIC_WITH_NESTED ("WIMAX-PROPERTIES",  nmc_fields_dev_show_wimax_prop + 1),   /* 5 */
+	NMC_META_GENERIC_WITH_NESTED ("WIRED-PROPERTIES",  metagen_device_detail_wired_properties), /* 4 */
+	NMC_META_GENERIC_WITH_NESTED ("WIMAX-PROPERTIES",  metagen_device_detail_wimax_properties), /* 5 */
 	NMC_META_GENERIC_WITH_NESTED ("NSP",               nmc_fields_dev_wimax_list + 1),        /* 6 */
 	NMC_META_GENERIC_WITH_NESTED ("IP4",               metagen_ip4_config),                   /* 7 */
-	NMC_META_GENERIC_WITH_NESTED ("DHCP4",             nmc_fields_dhcp_config + 1),           /* 8 */
-	NMC_META_GENERIC_WITH_NESTED ("IP6",               nmc_fields_ip6_config + 1),            /* 9 */
-	NMC_META_GENERIC_WITH_NESTED ("DHCP6",             nmc_fields_dhcp_config + 1),           /* 10 */
+	NMC_META_GENERIC_WITH_NESTED ("DHCP4",             metagen_dhcp_config),                  /* 8 */
+	NMC_META_GENERIC_WITH_NESTED ("IP6",               metagen_ip6_config),                   /* 9 */
+	NMC_META_GENERIC_WITH_NESTED ("DHCP6",             metagen_dhcp_config),                  /* 10 */
 	NMC_META_GENERIC_WITH_NESTED ("BOND",              nmc_fields_dev_show_master_prop + 1),  /* 11 */
 	NMC_META_GENERIC_WITH_NESTED ("TEAM",              nmc_fields_dev_show_team_prop + 1),    /* 12 */
 	NMC_META_GENERIC_WITH_NESTED ("BRIDGE",            nmc_fields_dev_show_master_prop + 1),  /* 13 */
 	NMC_META_GENERIC_WITH_NESTED ("VLAN",              nmc_fields_dev_show_vlan_prop + 1),    /* 14 */
 	NMC_META_GENERIC_WITH_NESTED ("BLUETOOTH",         nmc_fields_dev_show_bluetooth + 1),    /* 15 */
-	NMC_META_GENERIC_WITH_NESTED ("CONNECTIONS",       nmc_fields_dev_show_connections + 1),  /* 16 */
+	NMC_META_GENERIC_WITH_NESTED ("CONNECTIONS",       metagen_device_detail_connections),    /* 16 */
 	NULL,
 };
 #define NMC_FIELDS_DEV_SHOW_SECTIONS_COMMON  "GENERAL.DEVICE,GENERAL.TYPE,GENERAL.HWADDR,GENERAL.MTU,GENERAL.STATE,"\
@@ -707,63 +1126,6 @@ sort_access_points (const GPtrArray *aps)
 	return sorted;
 }
 
-static NMMetaColor
-wifi_signal_to_color (guint8 strength)
-{
-	if (strength > 80)
-		return NM_META_COLOR_WIFI_SIGNAL_EXCELLENT;
-	else if (strength > 55)
-		return NM_META_COLOR_WIFI_SIGNAL_GOOD;
-	else if (strength > 30)
-		return NM_META_COLOR_WIFI_SIGNAL_FAIR;
-	else if (strength > 5)
-		return NM_META_COLOR_WIFI_SIGNAL_POOR;
-	else
-		return NM_META_COLOR_WIFI_SIGNAL_UNKNOWN;
-}
-
-static char *
-ap_wpa_rsn_flags_to_string (NM80211ApSecurityFlags flags)
-{
-	char *flags_str[16]; /* Enough space for flags and terminating NULL */
-	char *ret_str;
-	int i = 0;
-
-	if (flags & NM_802_11_AP_SEC_PAIR_WEP40)
-		flags_str[i++] = g_strdup ("pair_wpe40");
-	if (flags & NM_802_11_AP_SEC_PAIR_WEP104)
-		flags_str[i++] = g_strdup ("pair_wpe104");
-	if (flags & NM_802_11_AP_SEC_PAIR_TKIP)
-		flags_str[i++] = g_strdup ("pair_tkip");
-	if (flags & NM_802_11_AP_SEC_PAIR_CCMP)
-		flags_str[i++] = g_strdup ("pair_ccmp");
-	if (flags & NM_802_11_AP_SEC_GROUP_WEP40)
-		flags_str[i++] = g_strdup ("group_wpe40");
-	if (flags & NM_802_11_AP_SEC_GROUP_WEP104)
-		flags_str[i++] = g_strdup ("group_wpe104");
-	if (flags & NM_802_11_AP_SEC_GROUP_TKIP)
-		flags_str[i++] = g_strdup ("group_tkip");
-	if (flags & NM_802_11_AP_SEC_GROUP_CCMP)
-		flags_str[i++] = g_strdup ("group_ccmp");
-	if (flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
-		flags_str[i++] = g_strdup ("psk");
-	if (flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
-		flags_str[i++] = g_strdup ("802.1X");
-
-	if (i == 0)
-		flags_str[i++] = g_strdup (_("(none)"));
-
-	flags_str[i] = NULL;
-
-	ret_str = g_strjoinv (" ", flags_str);
-
-	i = 0;
-	while (flags_str[i])
-		g_free (flags_str[i++]);
-
-	return ret_str;
-}
-
 typedef struct {
 	NmCli *nmc;
 	int index;
@@ -923,18 +1285,6 @@ construct_header_name (const char *base, const char *spec)
 	return g_strdup_printf ("%s (%s)", base, spec);
 }
 
-static const char *
-get_active_connection_id (NMDevice *device)
-{
-	NMActiveConnection *ac;
-
-	ac = nm_device_get_active_connection (device);
-	if (!ac)
-		return NULL;
-
-	return nm_active_connection_get_id (ac);
-}
-
 static gboolean
 print_bond_bridge_info (NMDevice *device,
                         NmCli *nmc,
@@ -1061,13 +1411,7 @@ static gboolean
 show_device_info (NMDevice *device, NmCli *nmc)
 {
 	GError *error = NULL;
-	const char *hwaddr = NULL;
 	NMDeviceState state = NM_DEVICE_STATE_UNKNOWN;
-	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
-	NMDeviceCapabilities caps;
-	NMActiveConnection *acon;
-	guint32 speed;
-	char *speed_str, *state_str, *reason_str, *mtu_str;
 	GArray *sections_array;
 	int k;
 	const char *fields_str = NULL;
@@ -1104,12 +1448,12 @@ show_device_info (NMDevice *device, NmCli *nmc)
 
 		/* Lazy way to retrieve sorted array from 0 to the number of dev fields */
 		out_indices = parse_output_fields (NULL,
-		                                   (const NMMetaAbstractInfo *const*) nmc_fields_dev_show_general,
+		                                   (const NMMetaAbstractInfo *const*) metagen_device_detail_general,
 		                                   FALSE, NULL, NULL);
 
-		row = g_new0 (NmcOutputField, G_N_ELEMENTS (nmc_fields_dev_show_general));
-		for (i = 0; i < G_N_ELEMENTS (nmc_fields_dev_show_general); i++)
-			row[i].info = (const NMMetaAbstractInfo *) &nmc_fields_dev_show_general[i];
+		row = g_new0 (NmcOutputField, G_N_ELEMENTS (metagen_device_detail_general));
+		for (i = 0; i < G_N_ELEMENTS (metagen_device_detail_general); i++)
+			row[i].info = (const NMMetaAbstractInfo *) &metagen_device_detail_general[i];
 
 		print_required_fields (&nmc->nmc_config, NMC_OF_FLAG_MAIN_HEADER_ONLY,
 		                       out_indices, header_name,
@@ -1121,138 +1465,65 @@ show_device_info (NMDevice *device, NmCli *nmc)
 		int section_idx = g_array_index (sections_array, int, k);
 		char *section_fld = (char *) g_ptr_array_index (fields_in_section, k);
 
-		if (nmc->nmc_config.print_output != NMC_PRINT_TERSE && !nmc->nmc_config.multiline_output && was_output)
+		if (   NM_IN_SET (nmc->nmc_config.print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
+		    && !nmc->nmc_config.multiline_output
+		    && was_output)
 			g_print ("\n"); /* Print empty line between groups in tabular mode */
 
 		was_output = FALSE;
 
 		state = nm_device_get_state (device);
-		reason = nm_device_get_state_reason (device);
-
-		/* section GENERAL */
-		if (!strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[0]->name)) {
-			NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
 
-			tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_dev_show_general;
-			out_indices = parse_output_fields (section_fld,
-			                                   tmpl, FALSE, NULL, NULL);
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_FIELD_NAMES);
-			g_ptr_array_add (out.output_data, arr);
-
-			state_str = g_strdup_printf ("%d (%s)", state, nmc_device_state_to_string (state));
-			reason_str = g_strdup_printf ("%d (%s)", reason, nmc_device_reason_to_string (reason));
-			hwaddr = nm_device_get_hw_address (device);
-			mtu_str = g_strdup_printf ("%u", nm_device_get_mtu (device));
-			acon = nm_device_get_active_connection (device);
-
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_SECTION_PREFIX);
-			set_val_strc (arr, 0, nmc_fields_dev_show_sections[0]->name);  /* "GENERAL"*/
-			set_val_strc (arr, 1, nm_device_get_iface (device));
-			set_val_strc (arr, 2, nm_device_get_type_description (device));
-			set_val_strc (arr, 3, G_OBJECT_TYPE_NAME (device));
-			set_val_strc (arr, 4, nm_device_get_vendor (device));
-			set_val_strc (arr, 5, nm_device_get_product (device));
-			set_val_strc (arr, 6, nm_device_get_driver (device) ? nm_device_get_driver (device) : _("(unknown)"));
-			set_val_strc (arr, 7, nm_device_get_driver_version (device));
-			set_val_strc (arr, 8, nm_device_get_firmware_version (device));
-			set_val_strc (arr, 9, hwaddr ?: _("(unknown)"));
-			set_val_str  (arr, 10, mtu_str);
-			set_val_str  (arr, 11, state_str);
-			set_val_str  (arr, 12, reason_str);
-			set_val_strc (arr, 13, nm_device_get_udi (device));
-			set_val_strc (arr, 14, nm_device_get_ip_iface (device));
-			set_val_strc (arr, 15, nm_device_is_software (device) ? _("yes") : _("no"));
-			set_val_strc (arr, 16, nm_device_get_managed (device) ? _("yes") : _("no"));
-			set_val_strc (arr, 17, nm_device_get_autoconnect (device) ? _("yes") : _("no"));
-			set_val_strc (arr, 18, nm_device_get_firmware_missing (device) ? _("yes") : _("no"));
-			set_val_strc (arr, 19, nm_device_get_nm_plugin_missing (device) ? _("yes") : _("no"));
-			set_val_strc (arr, 20, nm_device_get_physical_port_id (device));
-			set_val_strc (arr, 21, get_active_connection_id (device));
-			set_val_strc (arr, 22, acon ? nm_active_connection_get_uuid (acon) : NULL);
-			set_val_strc (arr, 23, acon ? nm_object_get_path (NM_OBJECT (acon)) : NULL);
-			set_val_strc (arr, 24, nmc_device_metered_to_string (nm_device_get_metered (device)));
-			g_ptr_array_add (out.output_data, arr);
+		if (nmc_fields_dev_show_sections[section_idx]->nested == metagen_device_detail_general) {
+			gs_free char *f = section_fld ? g_strdup_printf ("GENERAL.%s", section_fld) : NULL;
 
-			print_data_prepare_width (out.output_data);
-			print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
+			nmc_print (&nmc->nmc_config,
+			           (gpointer[]) { device, NULL },
+			           NULL,
+			           NULL,
+			           NMC_META_GENERIC_GROUP ("GENERAL", metagen_device_detail_general, N_("NAME")),
+			           f,
+			           NULL);
 			was_output = TRUE;
+			continue;
 		}
 
-		/* section CAPABILITIES */
-		if (!strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[1]->name)) {
-			NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
+		if (nmc_fields_dev_show_sections[section_idx]->nested == metagen_device_detail_capabilities) {
+			gs_free char *f = section_fld ? g_strdup_printf ("CAPABILITIES.%s", section_fld) : NULL;
 
-			tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_dev_show_cap;
-			out_indices = parse_output_fields (section_fld,
-			                                   tmpl, FALSE, NULL, NULL);
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_FIELD_NAMES);
-			g_ptr_array_add (out.output_data, arr);
+			nmc_print (&nmc->nmc_config,
+			           (gpointer[]) { device, NULL },
+			           NULL,
+			           NULL,
+			           NMC_META_GENERIC_GROUP ("CAPABILITIES", metagen_device_detail_capabilities, N_("NAME")),
+			           f,
+			           NULL);
+			was_output = TRUE;
+			continue;
+		}
 
-			caps = nm_device_get_capabilities (device);
-			speed = 0;
-			if (NM_IS_DEVICE_ETHERNET (device)) {
-				/* Speed in Mb/s */
-				speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (device));
-			} else if (NM_IS_DEVICE_WIFI (device)) {
-				/* Speed in b/s */
-				speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (device));
-				speed /= 1000;
+		if (nmc_fields_dev_show_sections[section_idx]->nested == metagen_device_detail_wifi_properties) {
+			if (NM_IS_DEVICE_WIFI (device)) {
+				gs_free char *f = section_fld ? g_strdup_printf ("WIFI-PROPERTIES.%s", section_fld) : NULL;
+
+				nmc_print (&nmc->nmc_config,
+				           (gpointer[]) { device, NULL },
+				           NULL,
+				           NULL,
+				           NMC_META_GENERIC_GROUP ("WIFI-PROPERTIES", metagen_device_detail_wifi_properties, N_("NAME")),
+				           f,
+				           NULL);
+				was_output = TRUE;
 			}
-			speed_str = speed ? g_strdup_printf (_("%u Mb/s"), speed) : g_strdup (_("unknown"));
-
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_SECTION_PREFIX);
-			set_val_strc (arr, 0, nmc_fields_dev_show_sections[1]->name);  /* "CAPABILITIES" */
-			set_val_strc (arr, 1, (caps & NM_DEVICE_CAP_CARRIER_DETECT) ? _("yes") : _("no"));
-			set_val_str  (arr, 2, speed_str);
-			set_val_strc (arr, 3, (caps & NM_DEVICE_CAP_IS_SOFTWARE) ? _("yes") : _("no"));
-			set_val_strc (arr, 4, (caps & NM_DEVICE_CAP_SRIOV) ? _("yes") : _("no"));
-			g_ptr_array_add (out.output_data, arr);
-
-			print_data_prepare_width (out.output_data);
-			print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
-			was_output = TRUE;
+			continue;
 		}
 
 		/* Wireless specific information */
 		if ((NM_IS_DEVICE_WIFI (device))) {
-			NMDeviceWifiCapabilities wcaps;
 			NMAccessPoint *active_ap = NULL;
 			const char *active_bssid = NULL;
 			GPtrArray *aps;
 
-			/* section WIFI-PROPERTIES */
-			if (!strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[2]->name)) {
-				NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
-
-				wcaps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (device));
-
-				tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_dev_show_wifi_prop;
-				out_indices = parse_output_fields (section_fld,
-				                                   tmpl, FALSE, NULL, NULL);
-				arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_FIELD_NAMES);
-				g_ptr_array_add (out.output_data, arr);
-
-				arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_SECTION_PREFIX);
-				set_val_strc (arr, 0, nmc_fields_dev_show_sections[2]->name);  /* "WIFI-PROPERTIES" */
-				set_val_strc (arr, 1, (wcaps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104)) ?
-				                        _("yes") : _("no"));
-				set_val_strc (arr, 2, (wcaps & NM_WIFI_DEVICE_CAP_WPA) ? _("yes") : _("no"));
-				set_val_strc (arr, 3, (wcaps & NM_WIFI_DEVICE_CAP_RSN) ? _("yes") : _("no"));
-				set_val_strc (arr, 4, (wcaps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP) ? _("yes") : _("no"));
-				set_val_strc (arr, 5, (wcaps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP) ? _("yes") : _("no"));
-				set_val_strc (arr, 6, (wcaps & NM_WIFI_DEVICE_CAP_AP) ? _("yes") : _("no"));
-				set_val_strc (arr, 7, (wcaps & NM_WIFI_DEVICE_CAP_ADHOC) ? _("yes") : _("no"));
-				set_val_strc (arr, 8, !(wcaps & NM_WIFI_DEVICE_CAP_FREQ_VALID) ? _("unknown") :
-				                      ((wcaps & NM_WIFI_DEVICE_CAP_FREQ_2GHZ) ? _("yes") : _("no")));
-				set_val_strc (arr, 9, !(wcaps & NM_WIFI_DEVICE_CAP_FREQ_VALID) ? _("unknown") :
-				                      ((wcaps & NM_WIFI_DEVICE_CAP_FREQ_5GHZ) ? _("yes") : _("no")));
-				g_ptr_array_add (out.output_data, arr);
-
-				print_data_prepare_width (out.output_data);
-				print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
-				was_output = TRUE;
-			}
-
 			/* section AP */
 			if (!strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[3]->name)) {
 				NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
@@ -1287,28 +1558,22 @@ show_device_info (NMDevice *device, NmCli *nmc)
 				print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
 				was_output = TRUE;
 			}
-		} else if (NM_IS_DEVICE_ETHERNET (device)) {
-			/* WIRED-PROPERTIES */
-			if (!strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[4]->name)) {
-				NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
-
-				tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_dev_show_wired_prop;
-				out_indices = parse_output_fields (section_fld,
-				                                   tmpl, FALSE, NULL, NULL);
-				arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_FIELD_NAMES);
-				g_ptr_array_add (out.output_data, arr);
-
-				arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_SECTION_PREFIX);
-				set_val_strc (arr, 0, nmc_fields_dev_show_sections[4]->name);  /* "WIRED-PROPERTIES" */
-				set_val_strc (arr, 1, (nm_device_ethernet_get_carrier (NM_DEVICE_ETHERNET (device))) ?
-				                        _("on") : _("off"));
-				set_val_arrc (arr, 2, ((const char **) nm_device_ethernet_get_s390_subchannels (NM_DEVICE_ETHERNET (device))));
-				g_ptr_array_add (out.output_data, arr);
+		}
 
-				print_data_prepare_width (out.output_data);
-				print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
+		if (nmc_fields_dev_show_sections[section_idx]->nested == metagen_device_detail_wired_properties) {
+			if ((NM_IS_DEVICE_ETHERNET (device))) {
+				gs_free char *f = section_fld ? g_strdup_printf ("WIRED-PROPERTIES.%s", section_fld) : NULL;
+
+				nmc_print (&nmc->nmc_config,
+				           (gpointer[]) { device, NULL },
+				           NULL,
+				           NULL,
+				           NMC_META_GENERIC_GROUP ("WIRED-PROPERTIES", metagen_device_detail_wired_properties, N_("NAME")),
+				           f,
+				           NULL);
 				was_output = TRUE;
 			}
+			continue;
 		}
 
 		/* IP configuration info */
@@ -1319,19 +1584,19 @@ show_device_info (NMDevice *device, NmCli *nmc)
 
 		/* IP4 */
 		if (cfg4 && !strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[7]->name))
-			was_output = print_ip4_config (cfg4, &nmc->nmc_config, section_fld);
+			was_output = print_ip_config (cfg4, AF_INET, &nmc->nmc_config, section_fld);
 
 		/* DHCP4 */
 		if (dhcp4 && !strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[8]->name))
-			was_output = print_dhcp_config (dhcp4, &nmc->nmc_config, nmc_fields_dev_show_sections[8]->name, section_fld);
+			was_output = print_dhcp_config (dhcp4, AF_INET, &nmc->nmc_config, section_fld);
 
 		/* IP6 */
 		if (cfg6 && !strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[9]->name))
-			was_output = print_ip6_config (cfg6, &nmc->nmc_config, nmc_fields_dev_show_sections[9]->name, section_fld);
+			was_output = print_ip_config (cfg6, AF_INET6, &nmc->nmc_config, section_fld);
 
 		/* DHCP6 */
 		if (dhcp6 && !strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[10]->name))
-			was_output = print_dhcp_config (dhcp6, &nmc->nmc_config, nmc_fields_dev_show_sections[10]->name, section_fld);
+			was_output = print_dhcp_config (dhcp6, AF_INET6, &nmc->nmc_config, section_fld);
 
 		/* Bond specific information */
 		if (NM_IS_DEVICE_BOND (device)) {
@@ -1398,55 +1663,18 @@ show_device_info (NMDevice *device, NmCli *nmc)
 			}
 		}
 
-		/* section CONNECTIONS */
-		if (!strcasecmp (nmc_fields_dev_show_sections[section_idx]->name, nmc_fields_dev_show_sections[16]->name)) {
-			const GPtrArray *avail_cons;
-			GString *ac_paths_str;
-			char **ac_arr = NULL;
-			int i;
-			NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
-
-			tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_dev_show_connections;
-			out_indices = parse_output_fields (section_fld,
-			                                   tmpl, FALSE, NULL, NULL);
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_FIELD_NAMES);
-			g_ptr_array_add (out.output_data, arr);
-
-			/* available-connections */
-			avail_cons = nm_device_get_available_connections (device);
-			ac_paths_str = g_string_new (NULL);
-			if (avail_cons->len) {
-				ac_arr = g_new (char *, avail_cons->len + 1);
-				ac_arr[avail_cons->len] = NULL;
-			}
-			for (i = 0; i < avail_cons->len; i++) {
-				NMRemoteConnection *avail_con = g_ptr_array_index (avail_cons, i);
-				const char *ac_path = nm_connection_get_path (NM_CONNECTION (avail_con));
-				const char *ac_id = nm_connection_get_id (NM_CONNECTION (avail_con));
-				const char *ac_uuid = nm_connection_get_uuid (NM_CONNECTION (avail_con));
-
-				ac_arr[i] = g_strdup_printf ("%s | %s", ac_uuid, ac_id);
-
-				if (i == 0)
-					g_string_printf (ac_paths_str, "%s/{", NM_DBUS_PATH_SETTINGS);
-				else
-					g_string_append_c (ac_paths_str, ',');
-				g_string_append (ac_paths_str, strrchr (ac_path, '/') + 1);
-			}
-			if (ac_paths_str->len > 0)
-				g_string_append_c (ac_paths_str, '}');
-
-			arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_SECTION_PREFIX);
-			set_val_strc (arr, 0, nmc_fields_dev_show_sections[16]->name);  /* "CONNECTIONS" */
-			set_val_str  (arr, 1, ac_paths_str->str);
-			set_val_arr  (arr, 2, (ac_arr));
-			g_ptr_array_add (out.output_data, arr);
-
-			print_data_prepare_width (out.output_data);
-			print_data (&nmc->nmc_config, out_indices, NULL, 0, &out);
+		if (nmc_fields_dev_show_sections[section_idx]->nested == metagen_device_detail_connections) {
+			gs_free char *f = section_fld ? g_strdup_printf ("CONNECTIONS.%s", section_fld) : NULL;
 
-			g_string_free (ac_paths_str, FALSE);
+			nmc_print (&nmc->nmc_config,
+			           (gpointer[]) { device, NULL },
+			           NULL,
+			           NULL,
+			           NMC_META_GENERIC_GROUP ("CONNECTIONS", metagen_device_detail_connections, N_("NAME")),
+			           f,
+			           NULL);
 			was_output = TRUE;
+			continue;
 		}
 	}
 
@@ -1473,47 +1701,15 @@ nmc_device_state_to_color (NMDeviceState state)
 	return NM_META_COLOR_DEVICE_UNKNOWN;
 }
 
-static void
-fill_output_device_status (NMDevice *device, GPtrArray *output_data)
-{
-	NMActiveConnection *ac;
-	NMDeviceState state;
-	NMMetaColor color;
-	NmcOutputField *arr = nmc_dup_fields_array ((const NMMetaAbstractInfo *const*) nmc_fields_dev_status,
-	                                            0);
-
-	state = nm_device_get_state (device);
-	ac = nm_device_get_active_connection (device);
-
-	/* Show devices in color */
-	color = nmc_device_state_to_color (state);
-	set_val_color_all (arr, color);
-
-	set_val_strc (arr, 0, nm_device_get_iface (device));
-	set_val_strc (arr, 1, nm_device_get_type_description (device));
-	set_val_strc (arr, 2, nmc_device_state_to_string (state));
-	set_val_strc (arr, 3, nm_object_get_path (NM_OBJECT (device)));
-	set_val_strc (arr, 4, get_active_connection_id (device));
-	set_val_strc (arr, 5, ac ? nm_active_connection_get_uuid (ac) : NULL);
-	set_val_strc (arr, 6, ac ? nm_object_get_path (NM_OBJECT (ac)) : NULL);
-
-	g_ptr_array_add (output_data, arr);
-}
-
 static NMCResultCode
 do_devices_status (NmCli *nmc, int argc, char **argv)
 {
 	GError *error = NULL;
-	NMDevice **devices;
-	int i;
+	gs_free NMDevice **devices = NULL;
 	const char *fields_str = NULL;
-	const NMMetaAbstractInfo *const*tmpl;
-	NmcOutputField *arr;
-	NMC_OUTPUT_DATA_DEFINE_SCOPED (out);
 
 	next_arg (nmc, &argc, &argv, NULL);
 
-	/* Nothing to complete */
 	if (nmc->complete)
 		return nmc->return_value;
 
@@ -1523,33 +1719,25 @@ do_devices_status (NmCli *nmc, int argc, char **argv)
 	}
 
 	if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0)
-		fields_str = NMC_FIELDS_DEV_STATUS_COMMON;
+		fields_str = "DEVICE,TYPE,STATE,CONNECTION";
 	else if (!nmc->required_fields || strcasecmp (nmc->required_fields, "all") == 0) {
 	} else
 		fields_str = nmc->required_fields;
 
-	tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_dev_status;
-	out_indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error);
+	devices = nmc_get_devices_sorted (nmc->client);
 
-	if (error) {
+	if (!nmc_print (&nmc->nmc_config,
+	                (gpointer *) devices,
+	                NULL,
+	                N_("Status of devices"),
+	                (const NMMetaAbstractInfo *const*) metagen_device_status,
+	                fields_str,
+	                &error)) {
 		g_string_printf (nmc->return_text, _("Error: 'device status': %s"), error->message);
 		g_error_free (error);
 		return NMC_RESULT_ERROR_USER_INPUT;
 	}
 
-	/* 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);
-
-	devices = nmc_get_devices_sorted (nmc->client);
-	for (i = 0; devices[i]; i++)
-		fill_output_device_status (devices[i], out.output_data);
-
-	print_data_prepare_width (out.output_data);
-	print_data (&nmc->nmc_config, out_indices, _("Status of devices"), 0, &out);
-
-	g_free (devices);
-
 	return NMC_RESULT_SUCCESS;
 }
 
@@ -1619,7 +1807,7 @@ progress_cb (gpointer user_data)
 {
 	NMDevice *device = (NMDevice *) user_data;
 
-	nmc_terminal_show_progress (device ? nmc_device_state_to_string (nm_device_get_state (device)) : "");
+	nmc_terminal_show_progress (device ? gettext (nmc_device_state_to_string (nm_device_get_state (device))) : "");
 
 	return TRUE;
 }
@@ -1664,7 +1852,7 @@ connected_state_cb (NMDevice *device, NMActiveConnection *active)
 	           || state >= NM_DEVICE_STATE_DEACTIVATING) {
 		reason = nm_device_get_state_reason (device);
 		g_print (_("Error: Connection activation failed: (%d) %s.\n"),
-		         reason, nmc_device_reason_to_string (reason));
+		         reason, gettext (nmc_device_reason_to_string (reason)));
 	} else
 		return;
 
@@ -2404,7 +2592,7 @@ device_state (NMDevice *device, GParamSpec *pspec, NmCli *nmc)
 	color = nmc_device_state_to_color (state);
 	str = nmc_colorize (&nmc->nmc_config, color, "%s: %s\n",
 	                    nm_device_get_iface (device),
-	                    nmc_device_state_to_string (state));
+	                    gettext (nmc_device_state_to_string (state)));
 
 	g_print ("%s", str);
 	g_free (str);
@@ -3354,7 +3542,7 @@ generate_wpa_key (char *key, size_t len)
 		while (!g_ascii_isalnum (c))
 			c = g_random_int_range (33, 126);
 
-		key[i] = (gchar) c;
+		key[i] = (char) c;
 	}
 	key[WPA_PASSKEY_SIZE] = '\0';
 }
diff --git a/clients/cli/devices.h b/clients/cli/devices.h
index c78680da..7a05b50b 100644
--- a/clients/cli/devices.h
+++ b/clients/cli/devices.h
@@ -34,13 +34,13 @@ NMDevice ** nmc_get_devices_sorted (NMClient *client);
 
 NMMetaColor nmc_device_state_to_color (NMDeviceState state);
 
-extern const NmcMetaGenericInfo *const nmc_fields_dev_status[];
-extern const NmcMetaGenericInfo *const nmc_fields_dev_show_general[];
-extern const NmcMetaGenericInfo *const nmc_fields_dev_show_connections[];
-extern const NmcMetaGenericInfo *const nmc_fields_dev_show_cap[];
-extern const NmcMetaGenericInfo *const nmc_fields_dev_show_wired_prop[];
-extern const NmcMetaGenericInfo *const nmc_fields_dev_show_wifi_prop[];
-extern const NmcMetaGenericInfo *const nmc_fields_dev_show_wimax_prop[];
+extern const NmcMetaGenericInfo *const metagen_device_status[];
+extern const NmcMetaGenericInfo *const metagen_device_detail_general[];
+extern const NmcMetaGenericInfo *const metagen_device_detail_connections[];
+extern const NmcMetaGenericInfo *const metagen_device_detail_capabilities[];
+extern const NmcMetaGenericInfo *const metagen_device_detail_wired_properties[];
+extern const NmcMetaGenericInfo *const metagen_device_detail_wifi_properties[];
+extern const NmcMetaGenericInfo *const metagen_device_detail_wimax_properties[];
 extern const NmcMetaGenericInfo *const nmc_fields_dev_wifi_list[];
 extern const NmcMetaGenericInfo *const nmc_fields_dev_wimax_list[];
 extern const NmcMetaGenericInfo *const nmc_fields_dev_show_master_prop[];
diff --git a/clients/cli/general.c b/clients/cli/general.c
index de743970..cb87c110 100644
--- a/clients/cli/general.c
+++ b/clients/cli/general.c
@@ -508,6 +508,7 @@ show_nm_status (NmCli *nmc, const char *pretty_header_name, const char *print_fl
 
 	if (!nmc_print (&nmc->nmc_config,
 	                (gpointer[]) { nmc, NULL },
+	                NULL,
 	                pretty_header_name ?: N_("NetworkManager status"),
 	                (const NMMetaAbstractInfo *const*) metagen_general_status,
 	                fields_str,
@@ -565,6 +566,7 @@ print_permissions (void *user_data)
 
 	if (!nmc_print (&nmc->nmc_config,
 	                permissions,
+	                NULL,
 	                _("NetworkManager permissions"),
 	                (const NMMetaAbstractInfo *const*) metagen_general_permissions,
 	                fields_str,
@@ -657,6 +659,7 @@ show_general_logging (NmCli *nmc)
 
 	if (!nmc_print (&nmc->nmc_config,
 	                (gpointer const []) { &d, NULL },
+	                NULL,
 	                _("NetworkManager logging"),
 	                (const NMMetaAbstractInfo *const*) metagen_general_logging,
 	                fields_str,
@@ -1316,7 +1319,7 @@ do_overview (NmCli *nmc, int argc, char **argv)
 		color = nmc_device_state_to_color (state);
 		tmp = nmc_colorize (&nmc->nmc_config, color, "%s: %s%s%s",
 		                    nm_device_get_iface (devices[i]),
-		                    nmc_device_state_to_string (state),
+		                    gettext (nmc_device_state_to_string (state)),
 		                    ac ? " to " : "",
 		                    ac ? nm_active_connection_get_id (ac) : "");
 		g_print ("%s\n", tmp);
diff --git a/clients/cli/meson.build b/clients/cli/meson.build
index 9449ce63..c5669c96 100644
--- a/clients/cli/meson.build
+++ b/clients/cli/meson.build
@@ -27,9 +27,7 @@ deps = [
 ]
 
 cflags = clients_cflags + [
-  '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir),
   '-DG_LOG_DOMAIN="@0@"'.format(name),
-  '-DNMLOCALEDIR="@0@"'.format(nm_localedir)
 ]
 
 if enable_polkit_agent
diff --git a/clients/cli/nmcli.c b/clients/cli/nmcli.c
index 6407f50b..bbfc310d 100644
--- a/clients/cli/nmcli.c
+++ b/clients/cli/nmcli.c
@@ -51,6 +51,42 @@
 # define NMCLI_VERSION VERSION
 #endif
 
+#define DEFAULT_PALETTE_INIT \
+	[NM_META_COLOR_CONNECTION_ACTIVATED]     = "32", \
+	[NM_META_COLOR_CONNECTION_ACTIVATING]    = "33", \
+	[NM_META_COLOR_CONNECTION_DISCONNECTING] = "31", \
+	[NM_META_COLOR_CONNECTION_INVISIBLE]     =  "2", \
+	[NM_META_COLOR_CONNECTIVITY_FULL]        = "32", \
+	[NM_META_COLOR_CONNECTIVITY_LIMITED]     = "33", \
+	[NM_META_COLOR_CONNECTIVITY_NONE]        = "31", \
+	[NM_META_COLOR_CONNECTIVITY_PORTAL]      = "33", \
+	[NM_META_COLOR_DEVICE_ACTIVATED]         = "32", \
+	[NM_META_COLOR_DEVICE_ACTIVATING]        = "33", \
+	[NM_META_COLOR_DEVICE_DISCONNECTED]      = "31", \
+	[NM_META_COLOR_DEVICE_FIRMWARE_MISSING]  = "31", \
+	[NM_META_COLOR_DEVICE_PLUGIN_MISSING]    = "31", \
+	[NM_META_COLOR_DEVICE_UNAVAILABLE]       =  "2", \
+	[NM_META_COLOR_MANAGER_RUNNING]          = "32", \
+	[NM_META_COLOR_MANAGER_STARTING]         = "33", \
+	[NM_META_COLOR_MANAGER_STOPPED]          = "31", \
+	[NM_META_COLOR_PERMISSION_AUTH]          = "33", \
+	[NM_META_COLOR_PERMISSION_NO]            = "31", \
+	[NM_META_COLOR_PERMISSION_YES]           = "32", \
+	[NM_META_COLOR_STATE_ASLEEP]             = "31", \
+	[NM_META_COLOR_STATE_CONNECTED_GLOBAL]   = "32", \
+	[NM_META_COLOR_STATE_CONNECTED_LOCAL]    = "32", \
+	[NM_META_COLOR_STATE_CONNECTED_SITE]     = "32", \
+	[NM_META_COLOR_STATE_CONNECTING]         = "33", \
+	[NM_META_COLOR_STATE_DISCONNECTED]       = "31", \
+	[NM_META_COLOR_STATE_DISCONNECTING]      = "33", \
+	[NM_META_COLOR_WIFI_SIGNAL_EXCELLENT]    = "32", \
+	[NM_META_COLOR_WIFI_SIGNAL_FAIR]         = "35", \
+	[NM_META_COLOR_WIFI_SIGNAL_GOOD]         = "33", \
+	[NM_META_COLOR_WIFI_SIGNAL_POOR]         = "36", \
+	[NM_META_COLOR_WIFI_SIGNAL_UNKNOWN]      =  "2", \
+	[NM_META_COLOR_ENABLED]                  = "32", \
+	[NM_META_COLOR_DISABLED]                 = "31", \
+
 NmCli nm_cli = {
 	.client = NULL,
 
@@ -74,40 +110,7 @@ NmCli nm_cli = {
 	.nmc_config.show_secrets = FALSE,
 	.nmc_config.in_editor = FALSE,
 	.nmc_config.palette = {
-		[NM_META_COLOR_CONNECTION_ACTIVATED]	 = "32",
-		[NM_META_COLOR_CONNECTION_ACTIVATING]	 = "33",
-		[NM_META_COLOR_CONNECTION_DISCONNECTING] = "31",
-		[NM_META_COLOR_CONNECTION_INVISIBLE]	 = "2",
-		[NM_META_COLOR_CONNECTIVITY_FULL]	 = "32",
-		[NM_META_COLOR_CONNECTIVITY_LIMITED]	 = "33",
-		[NM_META_COLOR_CONNECTIVITY_NONE]	 = "31",
-		[NM_META_COLOR_CONNECTIVITY_PORTAL]	 = "33",
-		[NM_META_COLOR_DEVICE_ACTIVATED]	 = "32",
-		[NM_META_COLOR_DEVICE_ACTIVATING]	 = "33",
-		[NM_META_COLOR_DEVICE_DISCONNECTED]	 = "31",
-		[NM_META_COLOR_DEVICE_FIRMWARE_MISSING]	 = "31",
-		[NM_META_COLOR_DEVICE_PLUGIN_MISSING]	 = "31",
-		[NM_META_COLOR_DEVICE_UNAVAILABLE]	 = "2",
-		[NM_META_COLOR_MANAGER_RUNNING]		 = "32",
-		[NM_META_COLOR_MANAGER_STARTING]	 = "33",
-		[NM_META_COLOR_MANAGER_STOPPED]		 = "31",
-		[NM_META_COLOR_PERMISSION_AUTH]		 = "33",
-		[NM_META_COLOR_PERMISSION_NO]		 = "31",
-		[NM_META_COLOR_PERMISSION_YES]		 = "32",
-		[NM_META_COLOR_STATE_ASLEEP]		 = "31",
-		[NM_META_COLOR_STATE_CONNECTED_GLOBAL]	 = "32",
-		[NM_META_COLOR_STATE_CONNECTED_LOCAL]	 = "32",
-		[NM_META_COLOR_STATE_CONNECTED_SITE]	 = "32",
-		[NM_META_COLOR_STATE_CONNECTING]	 = "33",
-		[NM_META_COLOR_STATE_DISCONNECTED]	 = "31",
-		[NM_META_COLOR_STATE_DISCONNECTING]	 = "33",
-		[NM_META_COLOR_WIFI_SIGNAL_EXCELLENT]	 = "32",
-		[NM_META_COLOR_WIFI_SIGNAL_FAIR]	 = "35",
-		[NM_META_COLOR_WIFI_SIGNAL_GOOD]	 = "33",
-		[NM_META_COLOR_WIFI_SIGNAL_POOR]	 = "36",
-		[NM_META_COLOR_WIFI_SIGNAL_UNKNOWN]	 = "2",
-		[NM_META_COLOR_ENABLED]			 = "32",
-		[NM_META_COLOR_DISABLED]		 = "31",
+		DEFAULT_PALETTE_INIT
 	},
 	.editor_status_line = FALSE,
 	.editor_save_confirmation = TRUE,
@@ -186,19 +189,19 @@ complete_fields (const char *option, const char *prefix)
 	h = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
 	complete_field (h, metagen_ip4_config);
-	complete_field (h, nmc_fields_dhcp_config);
-	complete_field (h, nmc_fields_ip6_config);
+	complete_field (h, metagen_dhcp_config);
+	complete_field (h, metagen_ip6_config);
 	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, metagen_con_active_vpn);
 	complete_field (h, nmc_fields_con_active_details_groups);
-	complete_field (h, nmc_fields_dev_status);
-	complete_field (h, nmc_fields_dev_show_general);
-	complete_field (h, nmc_fields_dev_show_connections);
-	complete_field (h, nmc_fields_dev_show_cap);
-	complete_field (h, nmc_fields_dev_show_wired_prop);
-	complete_field (h, nmc_fields_dev_show_wifi_prop);
-	complete_field (h, nmc_fields_dev_show_wimax_prop);
+	complete_field (h, metagen_device_status);
+	complete_field (h, metagen_device_detail_general);
+	complete_field (h, metagen_device_detail_connections);
+	complete_field (h, metagen_device_detail_capabilities);
+	complete_field (h, metagen_device_detail_wired_properties);
+	complete_field (h, metagen_device_detail_wifi_properties);
+	complete_field (h, metagen_device_detail_wimax_properties);
 	complete_field (h, nmc_fields_dev_wifi_list);
 	complete_field (h, nmc_fields_dev_wimax_list);
 	complete_field (h, nmc_fields_dev_show_master_prop);
@@ -337,76 +340,162 @@ typedef enum {
         NMC_USE_COLOR_NO,
 } NmcColorOption;
 
-/* Checks whether a particular terminal-colors.d(5) file (.enabled, .disabled or .schem)
- * exists. If contents is non-NULL, it returns the content. */
-static gboolean
-check_colors_file (NmCli *nmc, NmcColorOption *color_option,
-                   const char *base_dir, const char *name, const char *term, const char *type,
-                   char **contents)
+static char *
+check_colors_construct_filename (const char *base_dir,
+                                 const char *name,
+                                 const char *term,
+                                 const char *type)
 {
-	char *filename;
-	gboolean exists;
-
-	filename = g_strdup_printf ("%s/terminal-colors.d/%s%s%s%s%s",
-	                            base_dir,
-	                            name ? name : "",
-	                            term ? "@" : "", term ? term : "",
-	                            (name || term) ? "." : "",
-	                            type);
-	if (contents)
-		exists = g_file_get_contents (filename, contents, NULL, NULL);
-	else
-		exists = g_file_test (filename, G_FILE_TEST_EXISTS);
-	g_free (filename);
+	return g_strdup_printf ("%s/terminal-colors.d/%s%s%s%s%s",
+	                        base_dir,
+	                        name ? name : "",
+	                        term ? "@" : "", term ? term : "",
+	                        (name || term) ? "." : "",
+	                        type);
+}
+
+static NmcColorOption
+check_colors_check_enabled_one_file (const char *base_dir,
+                                     const char *name,
+                                     const char *term)
+{
+	gs_free char *filename_e = NULL;
+	gs_free char *filename_d = NULL;
+
+	filename_e = check_colors_construct_filename (base_dir, name, term, "enable");
+	if (g_file_test (filename_e, G_FILE_TEST_EXISTS))
+		return NMC_USE_COLOR_YES;
+
+	filename_d = check_colors_construct_filename (base_dir, name, term, "disable");
+	if (g_file_test (filename_d, G_FILE_TEST_EXISTS))
+		return NMC_USE_COLOR_NO;
 
-	return exists;
+	return NMC_USE_COLOR_AUTO;
 }
 
-static void
-check_colors_files_for_term (NmCli *nmc, NmcColorOption *color_option,
-                             const char *base_dir, const char *name, const char *term)
+static char *
+check_colors_check_palette_one_file (const char *base_dir,
+                                     const char *name,
+                                     const char *term)
 {
-	if (   *color_option == NMC_USE_COLOR_AUTO
-	    && check_colors_file (nmc, color_option, base_dir, name, term, "enable", NULL)) {
-		*color_option = NMC_USE_COLOR_YES;
-	}
+	gs_free char *filename = check_colors_construct_filename (base_dir, name, term, "schem");
+	char *contents;
 
-	if (   *color_option == NMC_USE_COLOR_AUTO
-	    && check_colors_file (nmc, color_option, base_dir, name, term, "disable", NULL)) {
-		*color_option = NMC_USE_COLOR_NO;
-	}
+	if (g_file_get_contents (filename, &contents, NULL, NULL))
+		return contents;
+	return NULL;
+}
 
-	if (*color_option == NMC_USE_COLOR_NO) {
-		/* No need to bother any further. */
-		return;
-	}
+static gboolean
+check_colors_check_enabled (const char *base_dir_1, const char *base_dir_2, const char *name, const char *term)
+{
+	int i;
+
+	if (term && strchr (term, '/'))
+		term = NULL;
+
+#define CHECK_AND_RETURN(cmd) \
+	G_STMT_START { \
+		NmcColorOption _color_option; \
+		\
+		_color_option = (cmd); \
+		if (_color_option != NMC_USE_COLOR_AUTO) \
+			return _color_option == NMC_USE_COLOR_YES; \
+	} G_STMT_END
+
+	for (i = 0; i < 2; i++) {
+		const char *base_dir = (i == 0 ? base_dir_1 : base_dir_2);
 
-	if (nmc->palette_buffer == NULL)
-		check_colors_file (nmc, color_option, base_dir, name, term, "schem", &nmc->palette_buffer);
+		if (!base_dir)
+			continue;
+		if (name && term)
+			CHECK_AND_RETURN (check_colors_check_enabled_one_file (base_dir, name, term));
+		if (name)
+			CHECK_AND_RETURN (check_colors_check_enabled_one_file (base_dir, name, NULL));
+		if (term)
+			CHECK_AND_RETURN (check_colors_check_enabled_one_file (base_dir, NULL, term));
+		if (TRUE)
+			CHECK_AND_RETURN (check_colors_check_enabled_one_file (base_dir, NULL, NULL));
+	}
+#undef CHECK_AND_RETURN
+	return TRUE;
 }
 
-static void
-check_colors_files_for_name (NmCli *nmc, NmcColorOption *color_option,
-                             const char *base_dir, const char *name)
+static char *
+check_colors_check_palette (const char *base_dir_1, const char *base_dir_2, const char *name, const char *term)
 {
-	const gchar *term;
+	int i;
 
-	/* Take a shortcut if the directory is not there. */
-	if (!g_file_test (base_dir, G_FILE_TEST_EXISTS))
-		return;
+	if (term && strchr (term, '/'))
+		term = NULL;
 
-	term = g_getenv ("TERM");
-	if (term)
-		check_colors_files_for_term (nmc, color_option, base_dir, name, term);
-	check_colors_files_for_term (nmc, color_option, base_dir, name, NULL);
+#define CHECK_AND_RETURN(cmd) \
+	G_STMT_START { \
+		char *_palette; \
+		\
+		_palette = (cmd); \
+		if (_palette) \
+			return _palette; \
+	} G_STMT_END
+
+	for (i = 0; i < 2; i++) {
+		const char *base_dir = (i == 0 ? base_dir_1 : base_dir_2);
+
+		if (!base_dir)
+			continue;
+		if (name && term)
+			CHECK_AND_RETURN (check_colors_check_palette_one_file (base_dir, name, term));
+		if (name)
+			CHECK_AND_RETURN (check_colors_check_palette_one_file (base_dir, name, NULL));
+		if (term)
+			CHECK_AND_RETURN (check_colors_check_palette_one_file (base_dir, NULL, term));
+		if (TRUE)
+			CHECK_AND_RETURN (check_colors_check_palette_one_file (base_dir, NULL, NULL));
+	}
+#undef CHECK_AND_RETURN
+	return NULL;
 }
 
-static void
-check_colors_files_for_base_dir (NmCli *nmc, NmcColorOption *color_option,
-                                 const char *base_dir)
+static gboolean
+check_colors (NmcColorOption color_option,
+              char **out_palette_str)
 {
-	check_colors_files_for_name (nmc, color_option, base_dir, "nmcli");
-	check_colors_files_for_name (nmc, color_option, base_dir, NULL);
+	const char *base_dir_1, *base_dir_2;
+	const char *const NAME = "nmcli";
+	const char *term;
+
+	*out_palette_str = NULL;
+
+	if (!NM_IN_SET (color_option, NMC_USE_COLOR_AUTO, NMC_USE_COLOR_YES)) {
+		/* nothing to do. Colors are disabled. */
+		return FALSE;
+	}
+
+	term = g_getenv ("TERM");
+
+	if (color_option == NMC_USE_COLOR_AUTO) {
+		if (   nm_streq0 (term, "dumb")
+		    || !isatty (STDOUT_FILENO))
+			return FALSE;
+	}
+
+	base_dir_1 = g_get_user_config_dir ();
+	base_dir_2 = ""SYSCONFDIR;
+
+	if (base_dir_1) {
+		if (   nm_streq (base_dir_1, base_dir_2)
+		    || !g_file_test (base_dir_1, G_FILE_TEST_EXISTS))
+			base_dir_1 = NULL;
+	}
+	if (!g_file_test (base_dir_2, G_FILE_TEST_EXISTS))
+		base_dir_2 = NULL;
+
+	if (   color_option == NMC_USE_COLOR_AUTO
+	    && !check_colors_check_enabled (base_dir_1, base_dir_2, NAME, term))
+		return FALSE;
+
+	*out_palette_str = check_colors_check_palette (base_dir_1, base_dir_2, NAME, term);
+	return TRUE;
 }
 
 static const char *
@@ -415,7 +504,7 @@ resolve_color_alias (const char *color)
 	static const struct {
 		const char *name;
 		const char *alias;
-	} aliases[] = {
+	} const aliases[] = {
 		{ "reset",        "0" },
 		{ "bold",         "1" },
 		{ "white",        "1;37" },
@@ -455,12 +544,17 @@ resolve_color_alias (const char *color)
 }
 
 static gboolean
-parse_color_scheme (NmCli *nmc, GError **error)
+parse_color_scheme (char *palette_buffer,
+                    const char **palette /* _NM_META_COLOR_NUM elements */,
+                    GError **error)
 {
-	char *p = nmc->palette_buffer;
+	char *p = palette_buffer;
 	const char *name;
 	const char *color;
-	const char *map[_NM_META_COLOR_NUM] = {
+	const char *tmp_palette[_NM_META_COLOR_NUM] = {
+		DEFAULT_PALETTE_INIT
+	};
+	static const char *const map[_NM_META_COLOR_NUM] = {
 		[NM_META_COLOR_NONE]                     = NULL,
 		[NM_META_COLOR_CONNECTION_ACTIVATED]     = "connection-activated",
 		[NM_META_COLOR_CONNECTION_ACTIVATING]    = "connection-activating",
@@ -573,7 +667,7 @@ parse_color_scheme (NmCli *nmc, GError **error)
 		/* All good, set the palette entry. */
 		for (i = NM_META_COLOR_NONE + 1; i < _NM_META_COLOR_NUM; i++) {
 			if (strcmp (map[i], name) == 0) {
-				nmc->nmc_config_mutable.palette[i] = resolve_color_alias (color);
+				tmp_palette[i] = resolve_color_alias (color);
 				break;
 			}
 		}
@@ -581,38 +675,30 @@ parse_color_scheme (NmCli *nmc, GError **error)
 			g_debug ("Ignoring an unrecognized color: '%s'\n", name);
 	}
 
+	memcpy (palette, tmp_palette, sizeof (tmp_palette));
 	return TRUE;
 }
 
 static void
-set_colors (NmCli *nmc, NmcColorOption color_option)
+set_colors (NmcColorOption color_option,
+            bool *out_use_colors,
+            char **out_palette_buffer,
+            const char **palette /* _NM_META_COLOR_NUM elements */)
 {
-	GError *error = NULL;
-
-	if (color_option == NMC_USE_COLOR_AUTO) {
-		if (   g_strcmp0 (g_getenv ("TERM"), "dumb") == 0
-		    || !isatty (STDOUT_FILENO))
-			color_option = NMC_USE_COLOR_NO;
-	}
+	gs_free char *palette_str = NULL;
+	gboolean use_colors;
 
-	check_colors_files_for_base_dir (nmc, &color_option, g_get_user_config_dir ());
-	check_colors_files_for_base_dir (nmc, &color_option, SYSCONFDIR);
+	use_colors = check_colors (color_option, &palette_str);
 
-	switch (color_option) {
-	case NMC_USE_COLOR_YES:
-	case NMC_USE_COLOR_AUTO:
-		nmc->nmc_config_mutable.use_colors = TRUE;
-		break;
-	case NMC_USE_COLOR_NO:
-		nmc->nmc_config_mutable.use_colors = FALSE;
-		break;
-	}
+	*out_use_colors = use_colors;
+	if (use_colors && palette_str) {
+		GError *error = NULL;
 
-	if (nmc->nmc_config_mutable.use_colors && nmc->palette_buffer) {
-		if (!parse_color_scheme (nmc, &error)) {
+		if (!parse_color_scheme (palette_str, palette, &error)) {
 			g_debug ("Error parsing color scheme: %s", error->message);
 			g_error_free (error);
-		}
+		} else
+			*out_palette_buffer = g_steal_pointer (&palette_str);
 	}
 }
 
@@ -771,7 +857,10 @@ process_command_line (NmCli *nmc, int argc, char **argv)
 	if (nmc->required_fields)
 		nmc->nmc_config_mutable.overview = FALSE;
 
-	set_colors (nmc, colors);
+	set_colors (colors,
+	            &nmc->nmc_config_mutable.use_colors,
+	            &nmc->palette_buffer,
+	            nmc->nmc_config_mutable.palette);
 
 	/* Now run the requested command */
 	nmc_do_cmd (nmc, nmcli_cmds, *argv, argc, argv);
diff --git a/clients/cli/nmcli.h b/clients/cli/nmcli.h
index bcf1c01b..28616855 100644
--- a/clients/cli/nmcli.h
+++ b/clients/cli/nmcli.h
@@ -75,7 +75,7 @@ typedef enum {
 static inline NMMetaAccessorGetType
 nmc_print_output_to_accessor_get_type (NMCPrintOutput print_output)
 {
-	return   (print_output != NMC_PRINT_TERSE)
+	return   NM_IN_SET (print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
 	       ? NM_META_ACCESSOR_GET_TYPE_PRETTY
 	       : NM_META_ACCESSOR_GET_TYPE_PARSABLE;
 }
@@ -106,7 +106,7 @@ struct _NmcOutputField {
 
 typedef struct _NmcConfig {
 	NMCPrintOutput print_output;                      /* Output mode */
-	gboolean use_colors;                              /* Whether to use colors for output: option '--color' */
+	bool use_colors;                                  /* Whether to use colors for output: option '--color' */
 	bool multiline_output;                            /* Multiline output instead of default tabular */
 	bool escape_values;                               /* Whether to escape ':' and '\' in terse tabular mode */
 	bool in_editor;                                   /* Whether running the editor - nmcli con edit' */
diff --git a/clients/cli/settings.c b/clients/cli/settings.c
index 01142ba2..2d231e1c 100644
--- a/clients/cli/settings.c
+++ b/clients/cli/settings.c
@@ -318,7 +318,7 @@ _set_fcn_precheck_connection_secondaries (const char *value,
 	char **iter;
 	gboolean modified = FALSE;
 
-	strv0 = nm_utils_strsplit_set (value, " \t,");
+	strv0 = nm_utils_strsplit_set (value, " \t,", FALSE);
 	if (!strv0)
 		return TRUE;
 
@@ -662,25 +662,19 @@ nmc_setting_remove_property_option (NMSetting *setting,
 char **
 nmc_setting_get_valid_properties (NMSetting *setting)
 {
-	char **valid_props = NULL;
-	GParamSpec **props, **iter;
-	guint num;
-	int i;
-
-	/* Iterate through properties */
-	i = 0;
-	props = g_object_class_list_properties (G_OBJECT_GET_CLASS (G_OBJECT (setting)), &num);
-	valid_props = g_malloc0 (sizeof (char*) * (num + 1));
-	for (iter = props; iter && *iter; iter++) {
-		const char *key_name = g_param_spec_get_name (*iter);
-
-		/* Add all properties except for "name" that is non-editable */
-		if (g_strcmp0 (key_name, "name") != 0)
-			valid_props[i++] = g_strdup (key_name);
-	}
-	valid_props[i] = NULL;
-	g_free (props);
+	const NMMetaSettingInfoEditor *setting_info;
+	char **valid_props;
+	guint i, num;
+
+	setting_info = nm_meta_setting_info_editor_find_by_setting (setting);
 
+	num = setting_info ? setting_info->properties_num : 0;
+
+	valid_props = g_new (char *, num + 1);
+	for (i = 0; i < num; i++)
+		valid_props[i] = g_strdup (setting_info->properties[i]->property_name);
+
+	valid_props[num] = NULL;
 	return valid_props;
 }
 
@@ -814,6 +808,7 @@ setting_details (const NmcConfig *nmc_config, NMSetting *setting, const char *on
 	if (!nmc_print (nmc_config,
 	                (gpointer[]) { setting, NULL },
 	                NULL,
+	                NULL,
 	                (const NMMetaAbstractInfo *const[]) { (const NMMetaAbstractInfo *) setting_info, NULL },
 	                fields_str,
 	                &error))
diff --git a/clients/cli/utils.c b/clients/cli/utils.c
index 365a0303..e21c108d 100644
--- a/clients/cli/utils.c
+++ b/clients/cli/utils.c
@@ -62,7 +62,6 @@ _meta_type_nmc_generic_info_get_nested (const NMMetaAbstractInfo *abstract_info,
 
 	info = (const NmcMetaGenericInfo *) abstract_info;
 
-	*out_to_free = NULL;
 	NM_SET_OUT (out_len, NM_PTRARRAY_LEN (info->nested));
 	return (const NMMetaAbstractInfo *const*) info->nested;
 }
@@ -72,6 +71,7 @@ _meta_type_nmc_generic_info_get_fcn (const NMMetaAbstractInfo *abstract_info,
                                      const NMMetaEnvironment *environment,
                                      gpointer environment_user_data,
                                      gpointer target,
+                                     gpointer target_data,
                                      NMMetaAccessorGetType get_type,
                                      NMMetaAccessorGetFlags get_flags,
                                      NMMetaAccessorGetOutFlags *out_flags,
@@ -95,7 +95,9 @@ _meta_type_nmc_generic_info_get_fcn (const NMMetaAbstractInfo *abstract_info,
 	if (info->get_fcn) {
 		return info->get_fcn (environment,
 		                      environment_user_data,
-		                      info, target,
+		                      info,
+		                      target,
+		                      target_data,
 		                      get_type,
 		                      get_flags,
 		                      out_flags,
@@ -507,7 +509,7 @@ nmc_string_to_arg_array (const char *line, const char *delim, gboolean unquote,
 	gs_free const char **arr0 = NULL;
 	char **arr;
 
-	arr0 = nm_utils_strsplit_set (line ?: "", delim ?: " \t");
+	arr0 = nm_utils_strsplit_set (line ?: "", delim ?: " \t", FALSE);
 	if (!arr0)
 		arr = g_new0 (char *, 1);
 	else
@@ -651,16 +653,24 @@ nmc_free_output_field_values (NmcOutputField fields_array[])
 
 #define PRINT_DATA_COL_PARENT_NIL (G_MAXUINT)
 
-typedef struct {
+typedef struct _PrintDataCol {
+	union {
+		const struct _PrintDataCol *parent_col;
+
+		/* while constructing the list of columns in _output_selection_append(), we keep track
+		 * of the parent by index. The reason is, that at that point our columns are still
+		 * tracked in a GArray which is growing (hence, the pointers are changing).
+		 * Later, _output_selection_complete() converts the index into the actual pointer.
+		 */
+		guint _parent_idx;
+	};
 	const NMMetaSelectionItem *selection_item;
-	guint parent_idx;
 	guint self_idx;
 	bool is_leaf;
 } PrintDataCol;
 
 static gboolean
 _output_selection_append (GArray *cols,
-                          const char *fields_prefix,
                           guint parent_idx,
                           const NMMetaSelectionItem *selection_item,
                           GPtrArray *gfree_keeper,
@@ -678,7 +688,7 @@ _output_selection_append (GArray *cols,
 	{
 		PrintDataCol col = {
 			.selection_item = selection_item,
-			.parent_idx = parent_idx,
+			._parent_idx = parent_idx,
 			.self_idx = col_idx,
 			.is_leaf = TRUE,
 		};
@@ -724,8 +734,11 @@ _output_selection_append (GArray *cols,
 
 		for (i = 0; i < selection->num; i++) {
 			si = &selection->items[i];
-			if (!_output_selection_append (cols, si->self_selection, col_idx,
-			                               si, gfree_keeper, error))
+			if (!_output_selection_append (cols,
+			                               col_idx,
+			                               si,
+			                               gfree_keeper,
+			                               error))
 				return FALSE;
 		}
 
@@ -738,6 +751,26 @@ _output_selection_append (GArray *cols,
 	return TRUE;
 }
 
+static void
+_output_selection_complete (GArray *cols)
+{
+	guint i;
+
+	nm_assert (cols);
+	nm_assert (g_array_get_element_size (cols) == sizeof (PrintDataCol));
+
+	for (i = 0; i < cols->len; i++) {
+		PrintDataCol *col = &g_array_index (cols, PrintDataCol, i);
+
+		if (col->_parent_idx == PRINT_DATA_COL_PARENT_NIL)
+			col->parent_col = NULL;
+		else {
+			nm_assert (col->_parent_idx < i);
+			col->parent_col = &g_array_index (cols, PrintDataCol, col->_parent_idx);
+		}
+	}
+}
+
 /*****************************************************************************/
 
 /**
@@ -768,7 +801,7 @@ _output_selection_parse (const NMMetaAbstractInfo *const*fields,
 	gs_unref_array GArray *cols = NULL;
 	guint i;
 
-	selection = nm_meta_selection_create_parse_list (fields, NULL, fields_str, FALSE, error);
+	selection = nm_meta_selection_create_parse_list (fields, fields_str, FALSE, error);
 	if (!selection)
 		return FALSE;
 
@@ -786,11 +819,13 @@ _output_selection_parse (const NMMetaAbstractInfo *const*fields,
 	for (i = 0; i < selection->num; i++) {
 		const NMMetaSelectionItem *si = &selection->items[i];
 
-		if (!_output_selection_append (cols, NULL, PRINT_DATA_COL_PARENT_NIL,
+		if (!_output_selection_append (cols, PRINT_DATA_COL_PARENT_NIL,
 		                               si, gfree_keeper, error))
 			return FALSE;
 	}
 
+	_output_selection_complete (cols);
+
 	*out_cols = g_steal_pointer (&cols);
 	*out_gfree_keeper = g_steal_pointer (&gfree_keeper);
 	return TRUE;
@@ -833,7 +868,7 @@ parse_output_fields (const char *fields_str,
 	g_return_val_if_fail (!error || !*error, NULL);
 	g_return_val_if_fail (!out_group_fields || !*out_group_fields, NULL);
 
-	selection = nm_meta_selection_create_parse_list (fields_array, NULL, fields_str, TRUE, error);
+	selection = nm_meta_selection_create_parse_list (fields_array, fields_str, TRUE, error);
 	if (!selection)
 		return NULL;
 
@@ -893,7 +928,11 @@ typedef struct {
 	const PrintDataCol *col;
 	const char *title;
 	bool title_to_free:1;
-	bool skip:1;
+
+	/* whether the column should be printed. If not %TRUE,
+	 * the column will be skipped. */
+	bool to_print:1;
+
 	int width;
 } PrintDataHeaderCell;
 
@@ -929,18 +968,20 @@ _print_data_header_cell_clear (gpointer cell_p)
 static void
 _print_data_cell_clear_text (PrintDataCell *cell)
 {
-	if (cell->text_to_free) {
-		switch (cell->text_format) {
-		case PRINT_DATA_CELL_FORMAT_TYPE_PLAIN:
+	switch (cell->text_format) {
+	case PRINT_DATA_CELL_FORMAT_TYPE_PLAIN:
+		if (cell->text_to_free)
 			g_free ((char *) cell->text.plain);
-			break;
-		case PRINT_DATA_CELL_FORMAT_TYPE_STRV:
+		cell->text.plain = NULL;
+		break;
+	case PRINT_DATA_CELL_FORMAT_TYPE_STRV:
+		if (cell->text_to_free)
 			g_strfreev ((char **) cell->text.strv);
-			break;
-		};
-		cell->text_to_free = FALSE;
-	}
-	memset (&cell->text, 0, sizeof (cell->text));
+		cell->text.strv = NULL;
+		break;
+	};
+	cell->text_format = PRINT_DATA_CELL_FORMAT_TYPE_PLAIN;
+	cell->text_to_free = FALSE;
 }
 
 static void
@@ -954,6 +995,7 @@ _print_data_cell_clear (gpointer cell_p)
 static void
 _print_fill (const NmcConfig *nmc_config,
              gpointer const *targets,
+             gpointer targets_data,
              const PrintDataCol *cols,
              guint cols_len,
              GArray **out_header_row,
@@ -963,11 +1005,9 @@ _print_fill (const NmcConfig *nmc_config,
 	GArray *header_row;
 	guint i_row, i_col;
 	guint targets_len;
-	gboolean pretty;
 	NMMetaAccessorGetType text_get_type;
 	NMMetaAccessorGetFlags text_get_flags;
 
-	pretty = (nmc_config->print_output != NMC_PRINT_TERSE);
 
 	header_row = g_array_sized_new (FALSE, TRUE, sizeof (PrintDataHeaderCell), cols_len);
 	g_array_set_clear_func (header_row, _print_data_header_cell_clear);
@@ -991,16 +1031,19 @@ _print_fill (const NmcConfig *nmc_config,
 
 		header_cell->col_idx = col_idx;
 		header_cell->col = col;
-		header_cell->skip = FALSE;
+
+		/* by default, the entire column is skipped. That is the case,
+		 * unless we have a cell (below) which opts-in to be printed. */
+		header_cell->to_print = FALSE;
 
 		header_cell->title = nm_meta_abstract_info_get_name (info, TRUE);
 		if (   nmc_config->multiline_output
-		    && col->parent_idx != PRINT_DATA_COL_PARENT_NIL
+		    && col->parent_col
 		    && NM_IN_SET (info->meta_type,
 		                  &nm_meta_type_property_info,
 		                  &nmc_meta_type_generic_info)) {
 			header_cell->title = g_strdup_printf ("%s.%s",
-			                                      nm_meta_abstract_info_get_name (cols[col->parent_idx].selection_item->info, FALSE),
+			                                      nm_meta_abstract_info_get_name (col->parent_col->selection_item->info, FALSE),
 			                                      header_cell->title);
 			header_cell->title_to_free = TRUE;
 		}
@@ -1040,6 +1083,7 @@ _print_fill (const NmcConfig *nmc_config,
 			                                   nmc_meta_environment,
 			                                   nmc_meta_environment_arg,
 			                                   target,
+			                                   targets_data,
 			                                   text_get_type,
 			                                   text_get_flags,
 			                                   &text_out_flags,
@@ -1048,7 +1092,19 @@ _print_fill (const NmcConfig *nmc_config,
 
 			nm_assert (!to_free || value == to_free);
 
-			header_cell->skip = nmc_config->overview && is_default;
+			if (   is_default
+			    && (   nmc_config->overview
+			        || NM_FLAGS_HAS (text_out_flags, NM_META_ACCESSOR_GET_OUT_FLAGS_HIDE))) {
+				/* don't mark the entry for display. This is to shorten the output in case
+				 * the property is the default value. But we only do that, if the user
+				 * opts in to this behavior (-overview), or of the property marks itself
+				 * elegible to be hidden.
+				 *
+				 * In general, only new API shall mark itself eligible to be hidden.
+				 * Long established properties cannot, because it would be a change
+				 * in behavior. */
+			} else
+				header_cell->to_print = TRUE;
 
 			if (NM_FLAGS_HAS (text_out_flags, NM_META_ACCESSOR_GET_OUT_FLAGS_STRV)) {
 				if (nmc_config->multiline_output) {
@@ -1072,6 +1128,7 @@ _print_fill (const NmcConfig *nmc_config,
 			                                                          nmc_meta_environment,
 			                                                          nmc_meta_environment_arg,
 			                                                          target,
+			                                                          targets_data,
 			                                                          NM_META_ACCESSOR_GET_TYPE_COLOR,
 			                                                          NM_META_ACCESSOR_GET_FLAGS_NONE,
 			                                                          &color_out_flags,
@@ -1079,11 +1136,14 @@ _print_fill (const NmcConfig *nmc_config,
 			                                                          NULL));
 
 			if (cell->text_format == PRINT_DATA_CELL_FORMAT_TYPE_PLAIN) {
-				if (pretty && (!cell->text.plain|| !cell->text.plain[0])) {
+				if (   NM_IN_SET (nmc_config->print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
+				    && (   !cell->text.plain
+				        || !cell->text.plain[0])) {
 					_print_data_cell_clear_text (cell);
 					cell->text.plain = "--";
 				} else if (!cell->text.plain)
 					cell->text.plain = "";
+				nm_assert (cell->text_format == PRINT_DATA_CELL_FORMAT_TYPE_PLAIN);
 			}
 		}
 	}
@@ -1131,7 +1191,7 @@ _print_skip_column (const NmcConfig *nmc_config,
 	selection_item = header_cell->col->selection_item;
 	info = selection_item->info;
 
-	if (header_cell->skip)
+	if (!header_cell->to_print)
 		return TRUE;
 
 	if (nmc_config->multiline_output) {
@@ -1167,23 +1227,21 @@ _print_do (const NmcConfig *nmc_config,
 {
 	int width1, width2;
 	int table_width = 0;
-	gboolean pretty = (nmc_config->print_output == NMC_PRINT_PRETTY);
-	gboolean terse = (nmc_config->print_output == NMC_PRINT_TERSE);
-	gboolean multiline = nmc_config->multiline_output;
 	guint i_row, i_col;
 	nm_auto_free_gstring GString *str = NULL;
 
 	g_assert (col_len);
 
 	/* Main header */
-	if (pretty && header_name_no_l10n) {
+	if (   nmc_config->print_output == NMC_PRINT_PRETTY
+	    && header_name_no_l10n) {
 		gs_free char *line = NULL;
 		int header_width;
 		const char *header_name = _(header_name_no_l10n);
 
 		header_width = nmc_string_screen_width (header_name, NULL) + 4;
 
-		if (multiline) {
+		if (nmc_config->multiline_output) {
 			table_width = NM_MAX (header_width, ML_HEADER_WIDTH);
 			line = g_strnfill (ML_HEADER_WIDTH, '=');
 		} else { /* tabular */
@@ -1198,12 +1256,13 @@ _print_do (const NmcConfig *nmc_config,
 		g_print ("%s\n", line);
 	}
 
-	str = !multiline
+	str = !nmc_config->multiline_output
 	      ? g_string_sized_new (100)
 	      : NULL;
 
 	/* print the header for the tabular form */
-	if (!multiline && !terse) {
+	if (   NM_IN_SET (nmc_config->print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
+	    && !nmc_config->multiline_output) {
 		for (i_col = 0; i_col < col_len; i_col++) {
 			const PrintDataHeaderCell *header_cell = &header_row[i_col];
 			const char *title;
@@ -1226,7 +1285,7 @@ _print_do (const NmcConfig *nmc_config,
 		g_string_truncate (str, 0);
 
 		/* Print horizontal separator */
-		if (pretty) {
+		if (nmc_config->print_output == NMC_PRINT_PRETTY) {
 			gs_free char *line = NULL;
 
 			g_print ("%s\n", (line = g_strnfill (table_width, '-')));
@@ -1251,7 +1310,7 @@ _print_do (const NmcConfig *nmc_config,
 				lines_len = 1;
 				break;
 			case PRINT_DATA_CELL_FORMAT_TYPE_STRV:
-				nm_assert (multiline);
+				nm_assert (nmc_config->multiline_output);
 				lines = cell->text.strv;
 				lines_len = NM_PTRARRAY_LEN (lines);
 				break;
@@ -1262,7 +1321,7 @@ _print_do (const NmcConfig *nmc_config,
 				const char *text;
 
 				text = colorize_string (nmc_config, cell->color, lines[i_lines], &text_to_free);
-				if (multiline) {
+				if (nmc_config->multiline_output) {
 					gs_free char *prefix = NULL;
 
 					if (cell->text_format == PRINT_DATA_CELL_FORMAT_TYPE_STRV)
@@ -1271,10 +1330,15 @@ _print_do (const NmcConfig *nmc_config,
 						prefix = g_strdup_printf ("%s:", cell->header_cell->title);
 					width1 = strlen (prefix);
 					width2 = nmc_string_screen_width (prefix, NULL);
-					g_print ("%-*s%s\n", (int) (terse ? 0 : ML_VALUE_INDENT+width1-width2), prefix, text);
+					g_print ("%-*s%s\n",
+					         (int) (  nmc_config->print_output == NMC_PRINT_TERSE
+					               ? 0
+					               : ML_VALUE_INDENT+width1-width2),
+					         prefix,
+					         text);
 				} else {
 					nm_assert (str);
-					if (terse) {
+					if (nmc_config->print_output == NMC_PRINT_TERSE) {
 						if (nmc_config->escape_values) {
 							const char *p = text;
 							while (*p) {
@@ -1300,7 +1364,7 @@ _print_do (const NmcConfig *nmc_config,
 			}
 		}
 
-		if (!multiline) {
+		if (!nmc_config->multiline_output) {
 			if (str->len)
 				g_string_truncate (str, str->len-1);  /* Chop off last column separator */
 			g_print ("%s\n", str->str);
@@ -1308,8 +1372,8 @@ _print_do (const NmcConfig *nmc_config,
 			g_string_truncate (str, 0);
 		}
 
-		if (   pretty
-		    && multiline) {
+		if (   nmc_config->print_output == NMC_PRINT_PRETTY
+		    && nmc_config->multiline_output) {
 			gs_free char *line = NULL;
 
 			g_print ("%s\n", (line = g_strnfill (ML_HEADER_WIDTH, '-')));
@@ -1320,6 +1384,7 @@ _print_do (const NmcConfig *nmc_config,
 gboolean
 nmc_print (const NmcConfig *nmc_config,
            gpointer const *targets,
+           gpointer targets_data,
            const char *header_name_no_l10n,
            const NMMetaAbstractInfo *const*fields,
            const char *fields_str,
@@ -1337,6 +1402,7 @@ nmc_print (const NmcConfig *nmc_config,
 
 	_print_fill (nmc_config,
 	             targets,
+	             targets_data,
 	             &g_array_index (cols, PrintDataCol, 0),
 	             cols->len,
 	             &header_row,
@@ -1516,8 +1582,6 @@ print_required_fields (const NmcConfig *nmc_config,
 	int table_width = 0;
 	const char *not_set_str;
 	int i;
-	gboolean terse = (nmc_config->print_output == NMC_PRINT_TERSE);
-	gboolean pretty = (nmc_config->print_output == NMC_PRINT_PRETTY);
 	gboolean main_header_add = of_flags & NMC_OF_FLAG_MAIN_HEADER_ADD;
 	gboolean main_header_only = of_flags & NMC_OF_FLAG_MAIN_HEADER_ONLY;
 	gboolean field_names = of_flags & NMC_OF_FLAG_FIELD_NAMES;
@@ -1527,7 +1591,9 @@ print_required_fields (const NmcConfig *nmc_config,
 	nmc_terminal_spawn_pager (nmc_config);
 
 	/* --- Main header --- */
-	if ((main_header_add || main_header_only) && pretty) {
+	if (   nmc_config->print_output == NMC_PRINT_PRETTY
+	    && (   main_header_add
+	        || main_header_only)) {
 		gs_free char *line = NULL;
 		int header_width;
 
@@ -1552,11 +1618,13 @@ print_required_fields (const NmcConfig *nmc_config,
 		return;
 
 	/* No field headers are printed in terse mode nor for multiline output */
-	if ((terse || nmc_config->multiline_output) && field_names)
+	if (   (   nmc_config->print_output == NMC_PRINT_TERSE
+	        || nmc_config->multiline_output)
+	    && field_names)
 		return;
 
 	/* Don't replace empty strings in terse mode */
-	not_set_str = terse ? "" : "--";
+	not_set_str = nmc_config->print_output == NMC_PRINT_TERSE ? "" : "--";
 
 	if (nmc_config->multiline_output) {
 		for (i = 0; i < indices->len; i++) {
@@ -1589,7 +1657,12 @@ print_required_fields (const NmcConfig *nmc_config,
 					                       j);
 					width1 = strlen (tmp);
 					width2 = nmc_string_screen_width (tmp, NULL);
-					g_print ("%-*s%s\n", (int) (terse ? 0 : ML_VALUE_INDENT+width1-width2), tmp, print_val);
+					g_print ("%-*s%s\n",
+					         (int) (nmc_config->print_output == NMC_PRINT_TERSE
+					                ? 0
+					                : ML_VALUE_INDENT + width1 - width2),
+					         tmp,
+					         print_val);
 				}
 			} else {
 				gs_free char *val_to_free = NULL;
@@ -1609,10 +1682,15 @@ print_required_fields (const NmcConfig *nmc_config,
 				                       nm_meta_abstract_info_get_name (field_values[idx].info, FALSE));
 				width1 = strlen (tmp);
 				width2 = nmc_string_screen_width (tmp, NULL);
-				g_print ("%-*s%s\n", (int) (terse ? 0 : ML_VALUE_INDENT+width1-width2), tmp, print_val);
+				g_print ("%-*s%s\n",
+				         (int) (  nmc_config->print_output == NMC_PRINT_TERSE
+				                ? 0
+				                : ML_VALUE_INDENT + width1 - width2),
+				         tmp,
+				         print_val);
 			}
 		}
-		if (pretty) {
+		if (nmc_config->print_output == NMC_PRINT_PRETTY) {
 			gs_free char *line = NULL;
 
 			g_print ("%s\n", (line = g_strnfill (ML_HEADER_WIDTH, '-')));
@@ -1635,7 +1713,7 @@ print_required_fields (const NmcConfig *nmc_config,
 		value = get_value_to_print (nmc_config, (NmcOutputField *) field_values+idx, field_names,
 		                            not_set_str, &val_to_free);
 
-		if (terse) {
+		if (nmc_config->print_output == NMC_PRINT_TERSE) {
 			if (nmc_config->escape_values) {
 				const char *p = value;
 				while (*p) {
@@ -1669,7 +1747,8 @@ print_required_fields (const NmcConfig *nmc_config,
 		g_print ("%s\n", str->str);
 
 		/* Print horizontal separator */
-		if (field_names && pretty) {
+		if (   nmc_config->print_output == NMC_PRINT_PRETTY
+		    && field_names) {
 			gs_free char *line = NULL;
 
 			g_print ("%s\n", (line = g_strnfill (table_width, '-')));
diff --git a/clients/cli/utils.h b/clients/cli/utils.h
index 19694d11..b2a3e5eb 100644
--- a/clients/cli/utils.h
+++ b/clients/cli/utils.h
@@ -121,6 +121,9 @@ typedef enum {
 	NMC_GENERIC_INFO_TYPE_IP6_CONFIG_DOMAIN,
 	_NMC_GENERIC_INFO_TYPE_IP6_CONFIG_NUM,
 
+	NMC_GENERIC_INFO_TYPE_DHCP_CONFIG_OPTION = 0,
+	_NMC_GENERIC_INFO_TYPE_DHCP_CONFIG_NUM,
+
 	NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME = 0,
 	NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID,
 	NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE,
@@ -152,6 +155,74 @@ typedef enum {
 	NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH,
 	_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NUM,
 
+	NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE = 0,
+	NMC_GENERIC_INFO_TYPE_CON_VPN_USERNAME,
+	NMC_GENERIC_INFO_TYPE_CON_VPN_GATEWAY,
+	NMC_GENERIC_INFO_TYPE_CON_VPN_BANNER,
+	NMC_GENERIC_INFO_TYPE_CON_VPN_VPN_STATE,
+	NMC_GENERIC_INFO_TYPE_CON_VPN_CFG,
+	_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_VPN_NUM,
+
+	NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_DEVICE = 0,
+	NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_TYPE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_STATE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_DBUS_PATH,
+	NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CONNECTION,
+	NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CON_UUID,
+	NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_CON_PATH,
+	_NMC_GENERIC_INFO_TYPE_DEVICE_STATUS_NUM,
+
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DEVICE = 0,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_TYPE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_TYPE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_VENDOR,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_PRODUCT,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DRIVER,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_DRIVER_VERSION,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_FIRMWARE_VERSION,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_HWADDR,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_MTU,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_STATE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_REASON,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_UDI,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_IP_IFACE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_IS_SOFTWARE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_MANAGED,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_AUTOCONNECT,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_FIRMWARE_MISSING,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NM_PLUGIN_MISSING,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_PHYS_PORT_ID,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CONNECTION,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CON_UUID,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_CON_PATH,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_METERED,
+	_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_GENERAL_NUM,
+
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_AVAILABLE_CONNECTION_PATHS = 0,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_AVAILABLE_CONNECTIONS,
+	_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CONNECTIONS_NUM,
+
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_CARRIER_DETECT = 0,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_SPEED,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_IS_SOFTWARE,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_SRIOV,
+	_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_CAPABILITIES_NUM,
+
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_CARRIER = 0,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_S390_SUBCHANNELS,
+	_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIRED_PROPERTIES_NUM,
+
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WEP = 0,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WPA,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_WPA2,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_TKIP,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_CCMP,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_AP,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_ADHOC,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_2GHZ,
+	NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_5GHZ,
+	_NMC_GENERIC_INFO_TYPE_DEVICE_DETAIL_WIFI_PROPERTIES_NUM,
+
 } NmcGenericInfoType;
 
 #define NMC_HANDLE_COLOR(color) \
@@ -175,6 +246,7 @@ struct _NmcMetaGenericInfo {
 	gpointer environment_user_data, \
 	const NmcMetaGenericInfo *info, \
 	gpointer target, \
+	gpointer target_data, \
 	NMMetaAccessorGetType get_type, \
 	NMMetaAccessorGetFlags get_flags, \
 	NMMetaAccessorGetOutFlags *out_flags, \
@@ -220,8 +292,19 @@ nmc_meta_generic_get_bool (gboolean val, NMMetaAccessorGetType get_type)
 	return nmc_meta_generic_get_str_i18n (val ? N_("yes") : N_("no"), get_type);
 }
 
+static inline const char *
+nmc_meta_generic_get_bool_onoff (gboolean val, NMMetaAccessorGetType get_type)
+{
+	return nmc_meta_generic_get_str_i18n (val ? N_("on") : N_("off"), get_type);
+}
+
+typedef enum {
+	NMC_META_GENERIC_GET_ENUM_TYPE_PARENTHESES,
+	NMC_META_GENERIC_GET_ENUM_TYPE_DASH,
+} NmcMetaGenericGetEnumType;
+
 static inline char *
-nmc_meta_generic_get_enum_with_detail (gint64 enum_val, const char *str_val, NMMetaAccessorGetType get_type)
+nmc_meta_generic_get_enum_with_detail (NmcMetaGenericGetEnumType get_enum_type, gint64 enum_val, const char *str_val, NMMetaAccessorGetType get_type)
 {
 	if (!NM_IN_SET (get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY,
 	                          NM_META_ACCESSOR_GET_TYPE_PARSABLE))
@@ -232,18 +315,30 @@ nmc_meta_generic_get_enum_with_detail (gint64 enum_val, const char *str_val, NMM
 		return g_strdup_printf ("%lld", (long long) enum_val);
 	}
 
-	/* note that this function will always print "$NUM ($NICK)", also in PARSABLE
-	 * mode. That might not be desired, but it's done for certain properties to preserve
-	 * previous behavior. */
-	if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY)
-		return g_strdup_printf (_("%lld (%s)"), (long long) enum_val, gettext (str_val));
-	return g_strdup_printf ("%lld (%s)", (long long) enum_val, str_val);
+	switch (get_enum_type) {
+	case NMC_META_GENERIC_GET_ENUM_TYPE_PARENTHESES:
+		/* note that this function will always print "$NUM ($NICK)", also in PARSABLE
+		 * mode. That might not be desired, but it's done for certain properties to preserve
+		 * previous behavior. */
+		if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY)
+			return g_strdup_printf (_("%lld (%s)"), (long long) enum_val, gettext (str_val));
+		return g_strdup_printf ("%lld (%s)", (long long) enum_val, str_val);
+	case NMC_META_GENERIC_GET_ENUM_TYPE_DASH:
+		/* note that this function will always print "$NUM ($NICK)", also in PARSABLE
+		 * mode. That might not be desired, but it's done for certain properties to preserve
+		 * previous behavior. */
+		if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY)
+			return g_strdup_printf (_("%lld - %s"), (long long) enum_val, gettext (str_val));
+		return g_strdup_printf ("%lld - %s", (long long) enum_val, str_val);
+	}
+	g_return_val_if_reached (NULL);
 }
 
 /*****************************************************************************/
 
 gboolean nmc_print (const NmcConfig *nmc_config,
                     gpointer const *targets,
+                    gpointer targets_data,
                     const char *header_name_no_l10n,
                     const NMMetaAbstractInfo *const*fields,
                     const char *fields_str,