diff options
Diffstat (limited to 'tui/nm-editor-bindings.c')
-rw-r--r-- | tui/nm-editor-bindings.c | 1676 |
1 files changed, 1676 insertions, 0 deletions
diff --git a/tui/nm-editor-bindings.c b/tui/nm-editor-bindings.c new file mode 100644 index 000000000..5be13588e --- /dev/null +++ b/tui/nm-editor-bindings.c @@ -0,0 +1,1676 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright 2013 Red Hat, Inc. + */ + +/** + * SECTION:nm-editor-bindings + * @short_description: #GBinding-based NM connection editor helpers + * + * nm-editor-bindings contains helper functions to bind NMSettings objects + * to connection editing widgets. The goal is that this should eventually be + * shared between nmtui, nm-connection-editor, and gnome-control-center. + */ + +#include "config.h" + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <string.h> + +#include <dbus/dbus-glib.h> + +#include "nm-editor-bindings.h" +#include "nm-gvaluearray-compat.h" + +static void +value_transform_string_int (const GValue *src_value, + GValue *dest_value) +{ + long val; + char *end; + + val = strtol (g_value_get_string (src_value), &end, 10); + if (val < G_MININT || val > G_MAXINT || *end) + return; + + g_value_set_int (dest_value, (int) val); +} + +static void +value_transform_string_uint (const GValue *src_value, + GValue *dest_value) +{ + long val; + char *end; + + val = strtol (g_value_get_string (src_value), &end, 10); + if (val < 0 || val > G_MAXUINT || *end) + return; + + g_value_set_uint (dest_value, (gint) val); +} + +void +nm_editor_bindings_init (void) +{ + /* glib registers number -> string, but not string -> number */ + g_value_register_transform_func (G_TYPE_STRING, G_TYPE_INT, value_transform_string_int); + g_value_register_transform_func (G_TYPE_STRING, G_TYPE_UINT, value_transform_string_uint); +} + +static gboolean +ip_string_parse (const char *text, + int family, + gpointer addr, + guint32 *prefix) +{ + const char *slash; + char *addrstr, *end; + gboolean valid; + + slash = strchr (text, '/'); + + if (slash) { + if (!prefix) + return FALSE; + addrstr = g_strndup (text, slash - text); + } else + addrstr = g_strdup (text); + valid = (inet_pton (family, addrstr, addr) == 1); + g_free (addrstr); + + if (!valid) + return FALSE; + + if (slash) { + *prefix = strtoul (slash + 1, &end, 10); + if ( *end + || *prefix == 0 + || (family == AF_INET && *prefix > 32) + || (family == AF_INET6 && *prefix > 128)) + valid = FALSE; + } else if (prefix) { + if (family == AF_INET) + *prefix = 32; + else + *prefix = 128; + } + + return valid; +} + +static gboolean +ip4_addresses_with_prefix_to_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GPtrArray *addrs; + GArray *addr; + guint32 addrbytes, prefix; + char buf[INET_ADDRSTRLEN], **strings; + int i; + + addrs = g_value_get_boxed (source_value); + strings = g_new0 (char *, addrs->len + 1); + + for (i = 0; i < addrs->len; i++) { + addr = addrs->pdata[i]; + addrbytes = g_array_index (addr, guint32, 0); + prefix = g_array_index (addr, guint32, 1); + + if (addrbytes) { + strings[i] = g_strdup_printf ("%s/%d", + inet_ntop (AF_INET, &addrbytes, buf, sizeof (buf)), + (int) prefix); + } else + strings[i] = g_strdup (""); + } + + g_value_take_boxed (target_value, strings); + return TRUE; +} + +static gboolean +ip4_addresses_with_prefix_from_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + char **strings; + GPtrArray *addrs; + GArray *addr; + guint32 *addrvals; + int i; + + strings = g_value_get_boxed (source_value); + /* Fetch the original property value, so as to preserve the gateway elements */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &addrs, + NULL); + + for (i = 0; strings[i]; i++) { + if (i >= addrs->len) { + guint32 val; + + addr = g_array_sized_new (FALSE, FALSE, sizeof (guint32), 3); + val = 0; + g_array_append_val (addr, val); + val = 32; + g_array_append_val (addr, val); + val = 0; + g_array_append_val (addr, val); + g_ptr_array_add (addrs, addr); + } else + addr = addrs->pdata[i]; + addrvals = (guint32 *)addr->data; + + if (!ip_string_parse (strings[i], AF_INET, &addrvals[0], &addrvals[1])) { + g_ptr_array_unref (addrs); + return FALSE; + } + } + + g_ptr_array_set_size (addrs, i); + g_value_take_boxed (target_value, addrs); + return TRUE; +} + +/** + * nm_editor_bind_ip4_addresses_with_prefix_to_strv: + * @source: the source object (eg, an #NMSettingIP4Config) + * @source_property: the property on @source to bind (eg, + * %NM_SETTING_IP4_CONFIG_ADDRESSES) + * @target: the target object (eg, an #NmtAddressList) + * @target_property: the property on @target to bind + * (eg, "strings") + * @flags: %GBindingFlags + * + * Binds the %DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT property + * @source_property on @source to the %G_TYPE_STRV property + * @target_property on @target. + * + * Each address/prefix/gateway triplet in @source_property will be + * converted to a string of the form "ip.ad.dr.ess/prefix" in + * @target_property (and vice versa if %G_BINDING_BIDIRECTIONAL) is + * specified. The "gateway" fields in @source_property are ignored + * when converting to strings, and unmodified when converting from + * strings. + */ +void +nm_editor_bind_ip4_addresses_with_prefix_to_strv (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + target, target_property, + flags, + ip4_addresses_with_prefix_to_strv, + ip4_addresses_with_prefix_from_strv, + NULL, NULL); +} + +static gboolean +ip4_addresses_to_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GArray *addrs; + guint32 addrbytes; + char buf[INET_ADDRSTRLEN], **strings; + int i; + + addrs = g_value_get_boxed (source_value); + strings = g_new0 (char *, addrs->len + 1); + + for (i = 0; i < addrs->len; i++) { + addrbytes = g_array_index (addrs, guint32, i); + if (addrbytes) + inet_ntop (AF_INET, &addrbytes, buf, sizeof (buf)); + else + buf[0] = '\0'; + strings[i] = g_strdup (buf); + } + + g_value_take_boxed (target_value, strings); + return TRUE; +} + +static gboolean +ip4_addresses_from_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + char **strings; + GArray *addrs; + guint32 addr; + int i; + + strings = g_value_get_boxed (source_value); + addrs = g_array_new (FALSE, FALSE, sizeof (guint32)); + + for (i = 0; strings[i]; i++) { + if (!ip_string_parse (strings[i], AF_INET, &addr, NULL)) { + g_array_unref (addrs); + return FALSE; + } + g_array_append_val (addrs, addr); + } + + g_value_take_boxed (target_value, addrs); + return TRUE; +} + +/** + * nm_editor_bind_ip4_addresses_to_strv: + * @source: the source object (eg, an #NMSettingIP4Config) + * @source_property: the property on @source to bind (eg, + * %NM_SETTING_IP4_CONFIG_DNS) + * @target: the target object (eg, an #NmtAddressList) + * @target_property: the property on @target to bind + * (eg, "strings") + * @flags: %GBindingFlags + * + * Binds the %DBUS_TYPE_G_UINT_ARRAY property @source_property on + * @source to the %G_TYPE_STRV property @target_property on @target. + * + * Each address in @source_property will be converted to a string of + * the form "ip.ad.dr.ess" in @target_property (and vice versa if + * %G_BINDING_BIDIRECTIONAL) is specified. + */ +void +nm_editor_bind_ip4_addresses_to_strv (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + target, target_property, + flags, + ip4_addresses_to_strv, + ip4_addresses_from_strv, + NULL, NULL); +} + +static gboolean +ip4_gateway_to_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GPtrArray *addrs; + GArray *addr; + guint32 gateway = 0; + const char *str; + char buf[INET_ADDRSTRLEN]; + int i; + + addrs = g_value_get_boxed (source_value); + for (i = 0; i < addrs->len; i++) { + addr = addrs->pdata[i]; + gateway = g_array_index (addr, guint32, 2); + if (gateway) + break; + } + + if (gateway) + str = inet_ntop (AF_INET, &gateway, buf, sizeof (buf)); + else + str = ""; + g_value_set_string (target_value, str); + return TRUE; +} + +static gboolean +ip4_gateway_from_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + const char *text; + GPtrArray *addrs; + GArray *addr; + guint32 addrbytes, *addrvals; + int i; + + text = g_value_get_string (source_value); + if (!ip_string_parse (text, AF_INET, &addrbytes, NULL)) + return FALSE; + + /* Fetch the original property value, so as to preserve the IP address elements */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &addrs, + NULL); + if (!addrs->len) { + g_ptr_array_unref (addrs); + return FALSE; + } + addr = addrs->pdata[0]; + addrvals = (guint32 *)addr->data; + if (addrbytes == addrvals[2]) { + g_ptr_array_unref (addrs); + return FALSE; + } + addrvals[2] = addrbytes; + + for (i = 1; i < addrs->len; i++) { + addr = addrs->pdata[i]; + addrvals = (guint32 *)addr->data; + addrvals[2] = 0; + } + + g_value_take_boxed (target_value, addrs); + return TRUE; +} + +/** + * nm_editor_bind_ip4_gateway_to_string: + * @source: the source object (eg, an #NMSettingIP4Config) + * @source_property: the property on @source to bind (eg, + * %NM_SETTING_IP4_CONFIG_ADDRESSES) + * @target: the target object (eg, an #NmtNewtEntry) + * @target_property: the property on @target to bind + * (eg, "text") + * @flags: %GBindingFlags + * + * Binds the %DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT property + * @source_property on @source to the %G_TYPE_STRING property + * @target_property on @target. + * + * Specifically, this binds the "gateway" field of the first address + * in @source_property; all other addresses in @source_property are + * ignored, and its "address" and "prefix" fields are unmodified. + */ +void +nm_editor_bind_ip4_gateway_to_string (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + target, target_property, + flags, + ip4_gateway_to_string, + ip4_gateway_from_string, + NULL, NULL); +} + +static gboolean +ip4_route_transform_to_dest_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP4Route *route; + char buf[INET_ADDRSTRLEN], *string; + guint32 addrbytes; + + route = g_value_get_boxed (source_value); + if (route) + addrbytes = nm_ip4_route_get_dest (route); + else + addrbytes = 0; + + if (addrbytes) { + string = g_strdup_printf ("%s/%d", + inet_ntop (AF_INET, &addrbytes, buf, sizeof (buf)), + (int) nm_ip4_route_get_prefix (route)); + g_value_take_string (target_value, string); + } else + g_value_set_string (target_value, ""); + return TRUE; +} + +static gboolean +ip4_route_transform_to_next_hop_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP4Route *route; + char buf[INET_ADDRSTRLEN]; + guint32 addrbytes; + + route = g_value_get_boxed (source_value); + if (route) + addrbytes = nm_ip4_route_get_next_hop (route); + else + addrbytes = 0; + + if (addrbytes) + inet_ntop (AF_INET, &addrbytes, buf, sizeof (buf)); + else + buf[0] = '\0'; + g_value_set_string (target_value, buf); + return TRUE; +} + +static gboolean +ip4_route_transform_to_metric_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP4Route *route; + char *string; + + route = g_value_get_boxed (source_value); + if (route && nm_ip4_route_get_dest (route)) { + string = g_strdup_printf ("%lu", (gulong) nm_ip4_route_get_metric (route)); + g_value_take_string (target_value, string); + } else + g_value_set_string (target_value, ""); + return TRUE; +} + +static gboolean +ip4_route_transform_from_dest_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP4Route *route; + const char *text; + guint32 addrbytes, prefix; + + text = g_value_get_string (source_value); + if (!ip_string_parse (text, AF_INET, &addrbytes, &prefix)) + return FALSE; + + /* Fetch the original property value */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &route, + NULL); + + nm_ip4_route_set_dest (route, addrbytes); + nm_ip4_route_set_prefix (route, prefix); + + g_value_take_boxed (target_value, route); + return TRUE; +} + +static gboolean +ip4_route_transform_from_next_hop_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP4Route *route; + const char *text; + guint32 addrbytes; + + text = g_value_get_string (source_value); + if (*text) { + if (!ip_string_parse (text, AF_INET, &addrbytes, NULL)) + return FALSE; + } else + addrbytes = 0; + + /* Fetch the original property value */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &route, + NULL); + + nm_ip4_route_set_next_hop (route, addrbytes); + + g_value_take_boxed (target_value, route); + return TRUE; +} + +static gboolean +ip4_route_transform_from_metric_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP4Route *route; + const char *text; + guint32 metric; + + text = g_value_get_string (source_value); + metric = strtoul (text, NULL, 10); + + /* Fetch the original property value */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &route, + NULL); + + nm_ip4_route_set_metric (route, metric); + + g_value_take_boxed (target_value, route); + return TRUE; +} + +/** + * nm_editor_bind_ip4_route_to_strings: + * @source: the source object + * @source_property: the source property + * @dest_target: the target object for the route's destionation + * @dest_target_property: the property on @dest_target + * @next_hop_target: the target object for the route's next hop + * @next_hop_target_property: the property on @next_hop_target + * @metric_target: the target object for the route's metric + * @metric_target_property: the property on @metric_target + * @flags: %GBindingFlags + * + * Binds the #NMIP4Route-valued property @source_property on @source + * to the three indicated string-valued target properties (and vice + * versa if %G_BINDING_BIDIRECTIONAL is specified). + * + * @dest_target_property should be an "address/prefix" string, as with + * nm_editor_bind_ip4_addresses_with_prefix_to_strv(). @next_hop_target + * is a plain IP address, and @metric_target is a number. + */ +void +nm_editor_bind_ip4_route_to_strings (gpointer source, + const gchar *source_property, + gpointer dest_target, + const gchar *dest_target_property, + gpointer next_hop_target, + const gchar *next_hop_target_property, + gpointer metric_target, + const gchar *metric_target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + dest_target, dest_target_property, + flags, + ip4_route_transform_to_dest_string, + ip4_route_transform_from_dest_string, + NULL, NULL); + g_object_bind_property_full (source, source_property, + next_hop_target, next_hop_target_property, + flags, + ip4_route_transform_to_next_hop_string, + ip4_route_transform_from_next_hop_string, + NULL, NULL); + g_object_bind_property_full (source, source_property, + metric_target, metric_target_property, + flags, + ip4_route_transform_to_metric_string, + ip4_route_transform_from_metric_string, + NULL, NULL); +} + +#define IP6_ADDRESS_SET(addr) ( addr \ + && addr->len == sizeof (struct in6_addr) \ + && memcmp (addr->data, &in6addr_any, addr->len) != 0) + +static gboolean +ip6_addresses_with_prefix_to_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GPtrArray *addrs; + GValueArray *addr; + GValue *val; + GByteArray *addrbytes; + guint prefix; + char **strings, buf[INET6_ADDRSTRLEN]; + int i; + + addrs = g_value_get_boxed (source_value); + strings = g_new0 (char *, addrs->len + 1); + + for (i = 0; i < addrs->len; i++) { + addr = addrs->pdata[i]; + val = g_value_array_get_nth (addr, 0); + addrbytes = g_value_get_boxed (val); + val = g_value_array_get_nth (addr, 1); + prefix = g_value_get_uint (val); + + if (IP6_ADDRESS_SET (addrbytes)) { + strings[i] = g_strdup_printf ("%s/%d", + inet_ntop (AF_INET6, addrbytes->data, buf, sizeof (buf)), + prefix); + } else + strings[i] = g_strdup (""); + } + + g_value_take_boxed (target_value, strings); + return TRUE; +} + +static gboolean +ip6_addresses_with_prefix_from_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + char **strings; + GPtrArray *addrs; + GValueArray *addr; + guint32 prefix; + GValue val = G_VALUE_INIT, *valp; + GByteArray *ba; + int i; + + strings = g_value_get_boxed (source_value); + + /* Fetch the original property value, so as to preserve the gateway elements */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &addrs, + NULL); + + for (i = 0; strings[i]; i++) { + if (i >= addrs->len) { + addr = g_value_array_new (3); + + g_value_init (&val, DBUS_TYPE_G_UCHAR_ARRAY); + ba = g_byte_array_sized_new (sizeof (struct in6_addr)); + g_byte_array_append (ba, (guint8 *) &in6addr_any, sizeof (struct in6_addr)); + g_value_take_boxed (&val, ba); + g_value_array_append (addr, &val); + g_value_unset (&val); + + g_value_init (&val, G_TYPE_UINT); + g_value_set_uint (&val, 128); + g_value_array_append (addr, &val); + g_value_unset (&val); + + g_value_init (&val, DBUS_TYPE_G_UCHAR_ARRAY); + ba = g_byte_array_sized_new (sizeof (struct in6_addr)); + g_byte_array_append (ba, (guint8 *) &in6addr_any, sizeof (struct in6_addr)); + g_value_take_boxed (&val, ba); + g_value_array_append (addr, &val); + g_value_unset (&val); + + g_ptr_array_add (addrs, addr); + } else + addr = addrs->pdata[i]; + + valp = g_value_array_get_nth (addr, 0); + ba = g_value_get_boxed (valp); + g_assert (ba->len == sizeof (struct in6_addr)); + + if (!ip_string_parse (strings[i], AF_INET6, ba->data, &prefix)) { + g_ptr_array_unref (addrs); + return FALSE; + } + + valp = g_value_array_get_nth (addr, 1); + g_value_set_uint (valp, prefix); + } + + g_ptr_array_set_size (addrs, i); + g_value_set_boxed (target_value, addrs); + return TRUE; +} + +/** + * nm_editor_bind_ip6_addresses_with_prefix_to_strv: + * @source: the source object (eg, an #NMSettingIP6Config) + * @source_property: the property on @source to bind (eg, + * %NM_SETTING_IP6_CONFIG_ADDRESSES) + * @target: the target object (eg, an #NmtAddressList) + * @target_property: the property on @target to bind + * (eg, "strings") + * @flags: %GBindingFlags + * + * Binds the %DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS property + * @source_property on @source to the %G_TYPE_STRV property + * @target_property on @target. + * + * Each address/prefix/gateway triplet in @source_property will be + * converted to a string of the form "ip::ad:dr:ess/prefix" in + * @target_property (and vice versa if %G_BINDING_BIDIRECTIONAL) is + * specified. The "gateway" fields in @source_property are ignored + * when converting to strings, and unmodified when converting from + * strings. + */ +void +nm_editor_bind_ip6_addresses_with_prefix_to_strv (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + target, target_property, + flags, + ip6_addresses_with_prefix_to_strv, + ip6_addresses_with_prefix_from_strv, + NULL, NULL); +} + +static gboolean +ip6_addresses_to_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GPtrArray *addrs; + GByteArray *addrbytes; + char buf[INET6_ADDRSTRLEN], **strings; + int i; + + addrs = g_value_get_boxed (source_value); + strings = g_new0 (char *, addrs->len + 1); + + for (i = 0; i < addrs->len; i++) { + addrbytes = addrs->pdata[i]; + if (IP6_ADDRESS_SET (addrbytes)) + inet_ntop (AF_INET6, addrbytes->data, buf, sizeof (buf)); + else + buf[0] = '\0'; + strings[i] = g_strdup (buf); + } + + g_value_take_boxed (target_value, strings); + return TRUE; +} + +static gboolean +ip6_addresses_from_strv (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + char **strings; + GPtrArray *addrs; + GByteArray *addr; + struct in6_addr addrbytes; + int i; + + strings = g_value_get_boxed (source_value); + addrs = g_ptr_array_new (); + + for (i = 0; strings[i]; i++) { + if (!ip_string_parse (strings[i], AF_INET6, &addrbytes, NULL)) { + while (i--) + g_byte_array_unref (addrs->pdata[i]); + g_ptr_array_unref (addrs); + return FALSE; + } + + addr = g_byte_array_sized_new (sizeof (addrbytes)); + g_byte_array_append (addr, (guint8 *)&addrbytes, sizeof (addrbytes)); + g_ptr_array_add (addrs, addr); + } + + g_value_take_boxed (target_value, addrs); + return TRUE; +} + +/** + * nm_editor_bind_ip6_addresses_to_strv: + * @source: the source object (eg, an #NMSettingIP6Config) + * @source_property: the property on @source to bind (eg, + * %NM_SETTING_IP6_CONFIG_DNS) + * @target: the target object (eg, an #NmtAddressList) + * @target_property: the property on @target to bind + * (eg, "strings") + * @flags: %GBindingFlags + * + * Binds the %DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UCHAR property + * @source_property on @source to the %G_TYPE_STRV property + * @target_property on @target. + * + * Each address in @source_property will be converted to a string of + * the form "ip::ad:dr:ess" in @target_property (and vice versa if + * %G_BINDING_BIDIRECTIONAL) is specified. + */ +void +nm_editor_bind_ip6_addresses_to_strv (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + target, target_property, + flags, + ip6_addresses_to_strv, + ip6_addresses_from_strv, + NULL, NULL); +} + +static gboolean +ip6_gateway_to_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GPtrArray *addrs; + GValueArray *addr; + GValue *val; + GByteArray *gateway; + char buf[INET6_ADDRSTRLEN]; + const char *str; + + addrs = g_value_get_boxed (source_value); + if (addrs->len == 0) + return FALSE; + + addr = addrs->pdata[0]; + val = g_value_array_get_nth (addr, 2); + gateway = g_value_get_boxed (val); + + if (IP6_ADDRESS_SET (gateway)) + str = inet_ntop (AF_INET6, gateway->data, buf, sizeof (buf)); + else + str = ""; + g_value_set_string (target_value, str); + return TRUE; +} + +static gboolean +ip6_gateway_from_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + GPtrArray *addrs; + const char *text; + GValueArray *addr; + struct in6_addr gateway; + GValue *val; + GByteArray *ba; + int i; + + text = g_value_get_string (source_value); + if (!ip_string_parse (text, AF_INET6, &gateway, NULL)) + return FALSE; + + /* Fetch the original property value, so as to preserve the IP address elements */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &addrs, + NULL); + if (!addrs->len) { + g_ptr_array_unref (addrs); + return FALSE; + } + + addr = addrs->pdata[0]; + + ba = g_byte_array_sized_new (sizeof (gateway)); + g_byte_array_append (ba, (guint8 *) &gateway, sizeof (gateway)); + + val = g_value_array_get_nth (addr, 2); + g_value_take_boxed (val, ba); + + for (i = 1; i < addrs->len; i++) { + addr = addrs->pdata[i]; + val = g_value_array_get_nth (addr, 2); + ba = g_value_get_boxed (val); + + if (ba) + memset (ba->data, 0, ba->len); + } + + g_value_take_boxed (target_value, addrs); + return TRUE; +} + +/** + * nm_editor_bind_ip6_gateway_to_string: + * @source: the source object (eg, an #NMSettingIP6Config) + * @source_property: the property on @source to bind (eg, + * %NM_SETTING_IP6_CONFIG_ADDRESSES) + * @target: the target object (eg, an #NmtNewtEntry) + * @target_property: the property on @target to bind + * (eg, "text") + * @flags: %GBindingFlags + * + * Binds the %DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS property + * @source_property on @source to the %G_TYPE_STRING property + * @target_property on @target. + * + * Specifically, this binds the "gateway" field of the first address + * in @source_property; all other addresses in @source_property are + * ignored, and its "address" and "prefix" fields are unmodified. + */ +void +nm_editor_bind_ip6_gateway_to_string (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + target, target_property, + flags, + ip6_gateway_to_string, + ip6_gateway_from_string, + NULL, NULL); +} + +#define IN6_ADDR_SET(bytes) (memcmp (bytes, &in6addr_any, sizeof (struct in6_addr)) != 0) + +static gboolean +ip6_route_transform_to_dest_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP6Route *route; + char buf[INET6_ADDRSTRLEN], *string; + const struct in6_addr *addrbytes; + + route = g_value_get_boxed (source_value); + if (route) + addrbytes = nm_ip6_route_get_dest (route); + else + addrbytes = &in6addr_any; + + if (IN6_ADDR_SET (addrbytes)) { + string = g_strdup_printf ("%s/%d", + inet_ntop (AF_INET6, addrbytes, buf, sizeof (buf)), + (int) nm_ip6_route_get_prefix (route)); + g_value_take_string (target_value, string); + } else + g_value_set_string (target_value, ""); + return TRUE; +} + +static gboolean +ip6_route_transform_to_next_hop_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP6Route *route; + char buf[INET6_ADDRSTRLEN]; + const struct in6_addr *addrbytes; + + route = g_value_get_boxed (source_value); + if (route) + addrbytes = nm_ip6_route_get_next_hop (route); + else + addrbytes = &in6addr_any; + + if (IN6_ADDR_SET (addrbytes)) + inet_ntop (AF_INET6, addrbytes, buf, sizeof (buf)); + else + buf[0] = '\0'; + g_value_set_string (target_value, buf); + return TRUE; +} + +static gboolean +ip6_route_transform_to_metric_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP6Route *route; + char *string; + + route = g_value_get_boxed (source_value); + if (route && IN6_ADDR_SET (nm_ip6_route_get_dest (route))) { + string = g_strdup_printf ("%lu", (gulong) nm_ip6_route_get_metric (route)); + g_value_take_string (target_value, string); + } else + g_value_set_string (target_value, ""); + return TRUE; +} + +static gboolean +ip6_route_transform_from_dest_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP6Route *route; + const char *text; + struct in6_addr addrbytes; + guint32 prefix; + + text = g_value_get_string (source_value); + if (!ip_string_parse (text, AF_INET6, &addrbytes, &prefix)) + return FALSE; + + /* Fetch the original property value */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &route, + NULL); + + nm_ip6_route_set_dest (route, &addrbytes); + nm_ip6_route_set_prefix (route, prefix); + + g_value_take_boxed (target_value, route); + return TRUE; +} + +static gboolean +ip6_route_transform_from_next_hop_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP6Route *route; + const char *text; + struct in6_addr addrbytes; + + text = g_value_get_string (source_value); + if (*text) { + if (!ip_string_parse (text, AF_INET6, &addrbytes, NULL)) + return FALSE; + } else + addrbytes = in6addr_any; + + /* Fetch the original property value */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &route, + NULL); + + nm_ip6_route_set_next_hop (route, &addrbytes); + + g_value_take_boxed (target_value, route); + return TRUE; +} + +static gboolean +ip6_route_transform_from_metric_string (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data) +{ + NMIP6Route *route; + const char *text; + guint32 metric; + + text = g_value_get_string (source_value); + metric = strtoul (text, NULL, 10); + + /* Fetch the original property value */ + g_object_get (g_binding_get_source (binding), + g_binding_get_source_property (binding), &route, + NULL); + + nm_ip6_route_set_metric (route, metric); + + g_value_take_boxed (target_value, route); + return TRUE; +} + +/** + * nm_editor_bind_ip6_route_to_strings: + * @source: the source object + * @source_property: the source property + * @dest_target: the target object for the route's destionation + * @dest_target_property: the property on @dest_target + * @next_hop_target: the target object for the route's next hop + * @next_hop_target_property: the property on @next_hop_target + * @metric_target: the target object for the route's metric + * @metric_target_property: the property on @metric_target + * @flags: %GBindingFlags + * + * Binds the #NMIP6Route-valued property @source_property on @source + * to the three indicated string-valued target properties (and vice + * versa if %G_BINDING_BIDIRECTIONAL is specified). + * + * @dest_target_property should be an "address/prefix" string, as with + * nm_editor_bind_ip6_addresses_with_prefix_to_strv(). @next_hop_target + * is a plain IP address, and @metric_target is a number. + */ +void +nm_editor_bind_ip6_route_to_strings (gpointer source, + const gchar *source_property, + gpointer dest_target, + const gchar *dest_target_property, + gpointer next_hop_target, + const gchar *next_hop_target_property, + gpointer metric_target, + const gchar *metric_target_property, + GBindingFlags flags) +{ + g_object_bind_property_full (source, source_property, + dest_target, dest_target_property, + flags, + ip6_route_transform_to_dest_string, + ip6_route_transform_from_dest_string, + NULL, NULL); + g_object_bind_property_full (source, source_property, + next_hop_target, next_hop_target_property, + flags, + ip6_route_transform_to_next_hop_string, + ip6_route_transform_from_next_hop_string, + NULL, NULL); + g_object_bind_property_full (source, source_property, + metric_target, metric_target_property, + flags, + ip6_route_transform_to_metric_string, + ip6_route_transform_from_metric_string, + NULL, NULL); +} + +/* Wireless security method binding */ +typedef struct { + NMConnection *connection; + NMSettingWirelessSecurity *s_wsec; + gboolean s_wsec_in_use; + + GObject *target; + char *target_property; + + gboolean updating; +} NMEditorWirelessSecurityMethodBinding; + +static const char * +get_security_type (NMEditorWirelessSecurityMethodBinding *binding) +{ + const char *key_mgmt, *auth_alg; + + if (!binding->s_wsec_in_use) + return "none"; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (binding->s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg (binding->s_wsec); + + /* No IEEE 802.1x */ + if (!strcmp (key_mgmt, "none")) { + NMWepKeyType wep_type = nm_setting_wireless_security_get_wep_key_type (binding->s_wsec); + + if (wep_type == NM_WEP_KEY_TYPE_KEY) + return "wep-key"; + else + return "wep-passphrase"; + } + + if (!strcmp (key_mgmt, "ieee8021x")) { + if (auth_alg && !strcmp (auth_alg, "leap")) + return "leap"; + return "dynamic-wep"; + } + + if ( !strcmp (key_mgmt, "wpa-none") + || !strcmp (key_mgmt, "wpa-psk")) + return "wpa-personal"; + + if (!strcmp (key_mgmt, "wpa-eap")) + return "wpa-enterprise"; + + return NULL; +} + +static void +wireless_security_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NMEditorWirelessSecurityMethodBinding *binding = user_data; + + if (binding->updating) + return; + + binding->updating = TRUE; + g_object_set (binding->target, + binding->target_property, get_security_type (binding), + NULL); + binding->updating = FALSE; +} + +static void +wireless_connection_changed (NMConnection *connection, + gpointer user_data) +{ + NMEditorWirelessSecurityMethodBinding *binding = user_data; + NMSettingWirelessSecurity *s_wsec; + + if (binding->updating) + return; + + s_wsec = nm_connection_get_setting_wireless_security (connection); + if ( (s_wsec && binding->s_wsec_in_use) + || (!s_wsec && !binding->s_wsec_in_use)) + return; + + binding->s_wsec_in_use = !binding->s_wsec_in_use; + wireless_security_changed (NULL, NULL, binding); +} + +static void +wireless_security_target_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NMEditorWirelessSecurityMethodBinding *binding = user_data; + char *method; + + if (binding->updating) + return; + + g_object_get (binding->target, + binding->target_property, &method, + NULL); + + binding->updating = TRUE; + + if (!strcmp (method, "none")) { + if (!binding->s_wsec_in_use) + return; + binding->s_wsec_in_use = FALSE; + nm_connection_remove_setting (binding->connection, NM_TYPE_SETTING_WIRELESS_SECURITY); + + binding->updating = FALSE; + return; + } + + if (!binding->s_wsec_in_use) { + binding->s_wsec_in_use = TRUE; + nm_connection_add_setting (binding->connection, NM_SETTING (binding->s_wsec)); + } + + if (!strcmp (method, "wep-key")) { + g_object_set (binding->s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_KEY, + NULL); + } else if (!strcmp (method, "wep-passphrase")) { + g_object_set (binding->s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_PASSPHRASE, + NULL); + } else if (!strcmp (method, "leap")) { + g_object_set (binding->s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_UNKNOWN, + NULL); + } else if (!strcmp (method, "dynamic-wep")) { + g_object_set (binding->s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_UNKNOWN, + NULL); + } else if (!strcmp (method, "wpa-personal")) { + g_object_set (binding->s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, NULL, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_UNKNOWN, + NULL); + } else if (!strcmp (method, "wpa-enterprise")) { + g_object_set (binding->s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, NULL, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_UNKNOWN, + NULL); + } else + g_warn_if_reached (); + + binding->updating = FALSE; +} + +static void +wireless_security_target_destroyed (gpointer user_data, + GObject *ex_target) +{ + NMEditorWirelessSecurityMethodBinding *binding = user_data; + + g_signal_handlers_disconnect_by_func (binding->s_wsec, G_CALLBACK (wireless_security_changed), binding); + g_object_unref (binding->s_wsec); + g_object_unref (binding->connection); + + g_free (binding->target_property); + + g_slice_free (NMEditorWirelessSecurityMethodBinding, binding); +} + +/** + * nm_editor_bind_wireless_security_method: + * @connection: an #NMConnection + * @s_wsec: an #NMSettingWirelessSecurity + * @target: the target widget + * @target_property: the string-valued property on @target to bind + * @flags: %GBindingFlags + * + * Binds the wireless security method on @connection to + * @target_property on @target (and vice versa if + * %G_BINDING_BIDIRECTIONAL). + * + * @target_property will be of the values "none", "wpa-personal", + * "wpa-enterprise", "wep-key", "wep-passphrase", "dynamic-wep", or + * "leap". + * + * If binding bidirectionally, @s_wsec will be automatically added to + * or removed from @connection as needed when @target_property + * changes. + */ +void +nm_editor_bind_wireless_security_method (NMConnection *connection, + NMSettingWirelessSecurity *s_wsec, + gpointer target, + const char *target_property, + GBindingFlags flags) +{ + NMEditorWirelessSecurityMethodBinding *binding; + char *notify; + + binding = g_slice_new0 (NMEditorWirelessSecurityMethodBinding); + + binding->target = target; + binding->target_property = g_strdup (target_property); + if (flags & G_BINDING_BIDIRECTIONAL) { + notify = g_strdup_printf ("notify::%s", target_property); + g_signal_connect (target, notify, G_CALLBACK (wireless_security_target_changed), binding); + g_free (notify); + } + g_object_weak_ref (target, wireless_security_target_destroyed, binding); + + binding->connection = g_object_ref (connection); + g_signal_connect (connection, NM_CONNECTION_CHANGED, + G_CALLBACK (wireless_connection_changed), binding); + binding->s_wsec_in_use = (nm_connection_get_setting_wireless_security (connection) != NULL); + + binding->s_wsec = g_object_ref (s_wsec); + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + G_CALLBACK (wireless_security_changed), binding); + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + G_CALLBACK (wireless_security_changed), binding); + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + G_CALLBACK (wireless_security_changed), binding); + + if (flags & G_BINDING_SYNC_CREATE) + wireless_security_changed (NULL, NULL, binding); +} + +/* WEP key binding */ + +typedef struct { + NMSettingWirelessSecurity *s_wsec; + GObject *entry, *key_selector; + char *entry_property, *key_selector_property; + + gboolean updating; +} NMEditorWepKeyBinding; + +static void +wep_key_setting_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NMEditorWepKeyBinding *binding = user_data; + const char *key; + int index; + + if (binding->updating) + return; + + index = nm_setting_wireless_security_get_wep_tx_keyidx (binding->s_wsec); + key = nm_setting_wireless_security_get_wep_key (binding->s_wsec, index); + + binding->updating = TRUE; + g_object_set (binding->key_selector, + binding->key_selector_property, index, + NULL); + g_object_set (binding->entry, + binding->entry_property, key, + NULL); + binding->updating = FALSE; +} + +static void +wep_key_ui_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NMEditorWepKeyBinding *binding = user_data; + char *key; + int index; + + if (binding->updating) + return; + + g_object_get (binding->key_selector, + binding->key_selector_property, &index, + NULL); + g_object_get (binding->entry, + binding->entry_property, &key, + NULL); + + binding->updating = TRUE; + g_object_set (binding->s_wsec, + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, index, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, index == 0 ? key : NULL, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, index == 1 ? key : NULL, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, index == 2 ? key : NULL, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY3, index == 3 ? key : NULL, + NULL); + binding->updating = FALSE; + + g_free (key); +} + +static void +wep_key_target_destroyed (gpointer user_data, + GObject *ex_target) +{ + NMEditorWepKeyBinding *binding = user_data; + + g_signal_handlers_disconnect_by_func (binding->s_wsec, G_CALLBACK (wep_key_setting_changed), binding); + + if (ex_target != binding->entry) { + g_signal_handlers_disconnect_by_func (binding->entry, G_CALLBACK (wep_key_ui_changed), binding); + g_object_weak_unref (binding->entry, wep_key_target_destroyed, binding); + } else { + g_signal_handlers_disconnect_by_func (binding->key_selector, G_CALLBACK (wep_key_ui_changed), binding); + g_object_weak_unref (binding->key_selector, wep_key_target_destroyed, binding); + } + + g_object_unref (binding->s_wsec); + g_free (binding->entry_property); + g_free (binding->key_selector_property); + + g_slice_free (NMEditorWepKeyBinding, binding); +} + +/** + * nm_editor_bind_wireless_security_wep_key: + * @s_wsec: an #NMSettingWirelessSecurity + * @entry: an entry widget + * @entry_property: the string-valued property on @entry to bind + * @key_selector: a pop-up widget of some sort + * @key_selector_property: the integer-valued property on + * @key_selector to bind + * @flags: %GBindingFlags + * + * Binds the "wep-tx-keyidx" property on @s_wsec to + * @key_selector_property on @key_selector, and the corresponding + * "wep-keyN" property to @entry_property on @entry (and vice versa if + * %G_BINDING_BIDIRECTIONAL). + */ +void +nm_editor_bind_wireless_security_wep_key (NMSettingWirelessSecurity *s_wsec, + gpointer entry, + const char *entry_property, + gpointer key_selector, + const char *key_selector_property, + GBindingFlags flags) +{ + NMEditorWepKeyBinding *binding; + char *notify; + + binding = g_slice_new0 (NMEditorWepKeyBinding); + binding->s_wsec = g_object_ref (s_wsec); + binding->entry = entry; + binding->entry_property = g_strdup (entry_property); + binding->key_selector = key_selector; + binding->key_selector_property = g_strdup (key_selector_property); + + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + G_CALLBACK (wep_key_setting_changed), binding); + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, + G_CALLBACK (wep_key_setting_changed), binding); + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, + G_CALLBACK (wep_key_setting_changed), binding); + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY3, + G_CALLBACK (wep_key_setting_changed), binding); + + g_signal_connect (s_wsec, "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, + G_CALLBACK (wep_key_setting_changed), binding); + + if (flags & G_BINDING_BIDIRECTIONAL) { + notify = g_strdup_printf ("notify::%s", entry_property); + g_signal_connect (entry, notify, G_CALLBACK (wep_key_ui_changed), binding); + g_free (notify); + + notify = g_strdup_printf ("notify::%s", key_selector_property); + g_signal_connect (key_selector, notify, G_CALLBACK (wep_key_ui_changed), binding); + g_free (notify); + } + + g_object_weak_ref (entry, wep_key_target_destroyed, binding); + g_object_weak_ref (key_selector, wep_key_target_destroyed, binding); + + if (flags & G_BINDING_SYNC_CREATE) + wep_key_setting_changed (NULL, NULL, binding); +} + +/* VLAN binding */ + +typedef struct { + NMSettingVlan *s_vlan; + + char *last_ifname_parent; + int last_ifname_id; + + gboolean updating; +} NMEditorVlanWidgetBinding; + +static gboolean +parse_interface_name (const char *ifname, + char **parent_ifname, + int *id) +{ + const char *ifname_end; + char *end; + + if (!ifname || !*ifname) + return FALSE; + + if (g_str_has_prefix (ifname, "vlan")) { + ifname_end = ifname + 4; + *id = strtoul (ifname_end, &end, 10); + if (*end || end == (char *)ifname_end || *id < 0) + return FALSE; + *parent_ifname = NULL; + return TRUE; + } + + ifname_end = strchr (ifname, '.'); + if (ifname_end) { + *id = strtoul (ifname_end + 1, &end, 10); + if (*end || end == (char *)ifname_end + 1 || *id < 0) + return FALSE; + *parent_ifname = g_strndup (ifname, ifname_end - ifname); + return TRUE; + } + + return FALSE; +} + +static void +vlan_settings_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NMEditorVlanWidgetBinding *binding = user_data; + const char *ifname, *parent; + char *ifname_parent; + int ifname_id, id; + + if (binding->updating) + return; + + ifname = nm_setting_vlan_get_interface_name (binding->s_vlan); + parent = nm_setting_vlan_get_parent (binding->s_vlan); + id = nm_setting_vlan_get_id (binding->s_vlan); + + if (!parse_interface_name (ifname, &ifname_parent, &ifname_id)) + return; + + /* If the id in INTERFACE_NAME changed, and ID is either unset, or was previously + * in sync with INTERFACE_NAME, then update ID. + */ + if ( id != ifname_id + && (id == binding->last_ifname_id || id == 0)) { + binding->updating = TRUE; + g_object_set (G_OBJECT (binding->s_vlan), + NM_SETTING_VLAN_ID, ifname_id, + NULL); + binding->updating = FALSE; + } + + /* If the PARENT in INTERFACE_NAME changed, and PARENT is either unset, or was + * previously in sync with INTERFACE_NAME, then update PARENT. + */ + if ( g_strcmp0 (parent, ifname_parent) != 0 + && ( g_strcmp0 (parent, binding->last_ifname_parent) == 0 + || !parent || !*parent)) { + binding->updating = TRUE; + g_object_set (G_OBJECT (binding->s_vlan), + NM_SETTING_VLAN_PARENT, ifname_parent, + NULL); + binding->updating = FALSE; + } + + g_free (binding->last_ifname_parent); + binding->last_ifname_parent = ifname_parent; + binding->last_ifname_id = ifname_id; +} + +static void +vlan_target_destroyed (gpointer user_data, + GObject *ex_target) +{ + NMEditorVlanWidgetBinding *binding = user_data; + + g_free (binding->last_ifname_parent); + g_slice_free (NMEditorVlanWidgetBinding, binding); +} + +/** + * nm_editor_bind_vlan_name: + * @s_vlan: an #NMSettingVlan + * + * Binds together several properties on @s_vlan, so that if the + * %NM_SETTING_VLAN_INTERFACE_NAME matches %NM_SETTING_VLAN_PARENT + * and %NM_SETTING_VLAN_ID in the obvious way, then changes to + * %NM_SETTING_VLAN_INTERFACE_NAME will propagate to the other + * two properties automatically. + */ +void +nm_editor_bind_vlan_name (NMSettingVlan *s_vlan) +{ + NMEditorVlanWidgetBinding *binding; + const char *ifname; + + binding = g_slice_new0 (NMEditorVlanWidgetBinding); + binding->s_vlan = s_vlan; + + g_signal_connect (s_vlan, "notify::" NM_SETTING_VLAN_INTERFACE_NAME, + G_CALLBACK (vlan_settings_changed), binding); + + g_object_weak_ref (G_OBJECT (s_vlan), vlan_target_destroyed, binding); + + ifname = nm_setting_vlan_get_interface_name (s_vlan); + if (!parse_interface_name (ifname, &binding->last_ifname_parent, &binding->last_ifname_id)) { + binding->last_ifname_parent = NULL; + binding->last_ifname_id = 0; + } +} |