Ticket #330: 0001-Group-access-points-by-network-330-v2.2.patch

File 0001-Group-access-points-by-network-330-v2.2.patch, 24.4 KB (added by dsd, 14 years ago)

updated after review

  • src/jarabe/desktop/meshbox.py

    From 323c951a89b334699b80320a079c53cd1674b6c5 Mon Sep 17 00:00:00 2001
    From: Daniel Drake <dsd@laptop.org>
    Date: Fri, 13 Nov 2009 10:31:12 +0000
    Subject: [PATCH] Group access points by network (#330)
    
    This patch implements the same AP-grouping logic as GNOME's nm-applet.
    APs that are from the same network are now shown as just a single
    network icon on the network view.
    
    If connected to the network, the circle displays the signal strength
    of the AP that you're connected to. If you aren't connected, it displays
    the signal strength of the strongest AP in that network.
    
    Showing all the APs is redundant anyway, since sugar doesn't really
    have any say in which AP is connected to after the user selects the
    network.
    
    Fixes a 0.84 regression where networks were split. Restores 0.82 behaviour
    of one circle per network.
    ---
     src/jarabe/desktop/meshbox.py |  292 ++++++++++++++++++++++++-----------------
     src/jarabe/model/network.py   |  105 +++++++++++++++
     2 files changed, 279 insertions(+), 118 deletions(-)
    
    diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
    index f8c446d..231ec20 100644
    a b from jarabe.model import shell 
    4949from jarabe.model.network import Settings
    5050from jarabe.model.network import IP4Config
    5151from jarabe.model.network import WirelessSecurity
     52from jarabe.model.network import AccessPoint
    5253
    5354_NM_SERVICE = 'org.freedesktop.NetworkManager'
    5455_NM_IFACE = 'org.freedesktop.NetworkManager'
    _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' 
    6061
    6162_ICON_NAME = 'network-wireless'
    6263
    63 class AccessPointView(CanvasPulsingIcon):
    64     def __init__(self, device, model):
     64
     65class WirelessNetworkView(CanvasPulsingIcon):
     66    def __init__(self, initial_ap):
    6567        CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE,
    6668                                   cache=True)
    6769        self._bus = dbus.SystemBus()
    68         self._device = device
    69         self._model = model
     70        self._access_points = {initial_ap.model.object_path: initial_ap}
     71        self._active_ap = None
     72        self._device = initial_ap.device
    7073        self._palette_icon = None
    7174        self._disconnect_item = None
    7275        self._connect_item = None
    7376        self._greyed_out = False
    74         self._name = ''
    75         self._strength = 0
    76         self._flags = 0
    77         self._wpa_flags = 0
    78         self._rsn_flags = 0
    79         self._mode = network.NM_802_11_MODE_UNKNOWN
     77        self._name = initial_ap.name
     78        self._mode = initial_ap.mode
     79        self._strength = initial_ap.strength
     80        self._flags = initial_ap.flags
     81        self._wpa_flags = initial_ap.wpa_flags
     82        self._rsn_flags = initial_ap.rsn_flags
    8083        self._device_caps = 0
    8184        self._device_state = None
    8285        self._connection = None
    83         self._active = True
    84         self._color = None
     86
     87        if self._mode == network.NM_802_11_MODE_ADHOC:
     88            encoded_color = self._name.split("#", 1)
     89            if len(encoded_color) == 2:
     90                self._color = xocolor.XoColor('#' + encoded_color[1])
     91        if self._mode == network.NM_802_11_MODE_INFRA:
     92            sha_hash = hashlib.sha1()
     93            data = self._name + hex(self._flags)
     94            sha_hash.update(data)
     95            digest = hash(sha_hash.digest())
     96            index = digest % len(xocolor.colors)
     97
     98            self._color = xocolor.XoColor('%s,%s' %
     99                                          (xocolor.colors[index][0],
     100                                           xocolor.colors[index][1]))
    85101
    86102        self.connect('button-release-event', self.__button_release_event_cb)
    87103
    class AccessPointView(CanvasPulsingIcon): 
    91107
    92108        self._palette = self._create_palette()
    93109        self.set_palette(self._palette)
     110        self._palette_icon.props.xo_color = self._color
    94111
    95         model_props = dbus.Interface(model, 'org.freedesktop.DBus.Properties')
    96         model_props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
    97                            reply_handler=self.__get_all_props_reply_cb,
    98                            error_handler=self.__get_all_props_error_cb)
    99 
    100         self._bus.add_signal_receiver(self.__ap_properties_changed_cb,
    101                                       signal_name='PropertiesChanged',
    102                                       path=model.object_path,
    103                                       dbus_interface=_NM_ACCESSPOINT_IFACE,
    104                                       byte_arrays=True)
     112        if network.find_connection(self._name) is not None:
     113            self.props.badge_name = "emblem-favorite"
     114            self._palette_icon.props.badge_name = "emblem-favorite"
     115        elif initial_ap.flags == network.NM_802_11_AP_FLAGS_PRIVACY:
     116            self.props.badge_name = "emblem-locked"
     117            self._palette_icon.props.badge_name = "emblem-locked"
     118        else:
     119            self.props.badge_name = None
     120            self._palette_icon.props.badge_name = None
    105121
    106122        interface_props = dbus.Interface(self._device,
    107123                                         'org.freedesktop.DBus.Properties')
    class AccessPointView(CanvasPulsingIcon): 
    117133
    118134        self._bus.add_signal_receiver(self.__device_state_changed_cb,
    119135                                      signal_name='StateChanged',
    120                                       path=device.object_path,
     136                                      path=self._device.object_path,
    121137                                      dbus_interface=_NM_DEVICE_IFACE)
    122138        self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
    123139                                      signal_name='PropertiesChanged',
    124                                       path=device.object_path,
     140                                      path=self._device.object_path,
    125141                                      dbus_interface=_NM_WIRELESS_IFACE)
    126142
    127143    def _create_palette(self):
    class AccessPointView(CanvasPulsingIcon): 
    148164        self._device_state = new_state
    149165        self._update_state()
    150166
    151     def __ap_properties_changed_cb(self, properties):
    152         self._update_properties(properties)
     167    def __update_active_ap(self, ap_path):
     168        if ap_path in self._access_points:
     169            # save reference to active AP, so that we always display the
     170            # strength of that one
     171            self._active_ap = self._access_points[ap_path]
     172            self.update_strength()
     173            self._update_state()
     174        elif self._active_ap is not None:
     175            # revert to showing state of strongest AP again
     176            self._active_ap = None
     177            self.update_strength()
     178            self._update_state()
    153179
    154180    def __wireless_properties_changed_cb(self, properties):
    155181        if 'ActiveAccessPoint' in properties:
    156             ap = properties['ActiveAccessPoint']
    157             self._active = (ap == self._model.object_path)
    158             self._update_state()
     182            self.__update_active_ap(properties['ActiveAccessPoint'])
    159183
    160     def _update_properties(self, properties):
    161         if 'Mode' in properties:
    162             self._mode = properties['Mode']
    163             self._color = None
    164         if 'Ssid' in properties:
    165             self._name = properties['Ssid']
    166             self._color = None
    167         if 'Strength' in properties:
    168             self._strength = properties['Strength']
    169         if 'Flags' in properties:
    170             self._flags = properties['Flags']
    171         if 'WpaFlags' in properties:
    172             self._wpa_flags = properties['WpaFlags']
    173         if 'RsnFlags' in properties:
    174             self._rsn_flags = properties['RsnFlags']
    175 
    176         if self._color == None:
    177             if self._mode == network.NM_802_11_MODE_ADHOC:
    178                 encoded_color = self._name.split("#", 1)
    179                 if len(encoded_color) == 2:
    180                     self._color = XoColor('#' + encoded_color[1])
    181             if self._mode == network.NM_802_11_MODE_INFRA:
    182                 sha_hash = hashlib.sha1()
    183                 data = self._name + hex(self._flags)
    184                 sha_hash.update(data)
    185                 digest = hash(sha_hash.digest())
    186                 index = digest % len(xocolor.colors)
    187 
    188                 self._color = XoColor('%s,%s' %
    189                                       (xocolor.colors[index][0],
    190                                        xocolor.colors[index][1]))
    191         self._update()
    192 
    193     def __get_active_ap_reply_cb(self, ap):
    194         self._active = (ap == self._model.object_path)
    195         self._update_state()
     184    def __get_active_ap_reply_cb(self, ap_path):
     185        self.__update_active_ap(ap_path)
    196186
    197187    def __get_active_ap_error_cb(self, err):
    198         logging.debug('Error getting the active access point: %s', err)
     188        logging.error('Error getting the active access point: %s', err)
    199189
    200190    def __get_device_caps_reply_cb(self, caps):
    201191        self._device_caps = caps
    202192
    203193    def __get_device_caps_error_cb(self, err):
    204         logging.debug('Error getting the wireless device properties: %s', err)
     194        logging.error('Error getting the wireless device properties: %s', err)
    205195
    206196    def __get_device_state_reply_cb(self, state):
    207197        self._device_state = state
    208198        self._update()
    209199
    210200    def __get_device_state_error_cb(self, err):
    211         logging.debug('Error getting the device state: %s', err)
    212 
    213     def __get_all_props_reply_cb(self, properties):
    214         self._update_properties(properties)
    215 
    216     def __get_all_props_error_cb(self, err):
    217         logging.debug('Error getting the access point properties: %s', err)
     201        logging.error('Error getting the device state: %s', err)
    218202
    219203    def _update(self):
    220         if network.find_connection(self._name) is not None:
    221             self.props.badge_name = "emblem-favorite"
    222             self._palette_icon.props.badge_name = "emblem-favorite"
    223         elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
    224             self.props.badge_name = "emblem-locked"
    225             self._palette_icon.props.badge_name = "emblem-locked"
    226         else:
    227             self.props.badge_name = None
    228             self._palette_icon.props.badge_name = None
    229 
    230         self._palette.props.primary_text = self._name
    231 
    232204        self._update_state()
    233205        self._update_color()
    234206
    235207    def _update_state(self):
    236         if self._active:
     208        if self._active_ap is not None:
    237209            state = self._device_state
    238210        else:
    239211            state = network.DEVICE_STATE_UNKNOWN
    class AccessPointView(CanvasPulsingIcon): 
    283255        else:
    284256            self.props.base_color = self._color
    285257
    286         self._palette_icon.props.xo_color = self._color
    287 
    288258    def _disconnect_activate_cb(self, item):
    289259        pass
    290260
    291 
    292261    def _add_ciphers_from_flags(self, flags, pairwise):
    293262        ciphers = []
    294263        if pairwise:
    class AccessPointView(CanvasPulsingIcon): 
    386355        obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
    387356        netmgr = dbus.Interface(obj, _NM_IFACE)
    388357
     358        # Choose the AP with the strongest signal
     359        # This is not the deciding factor for which AP ends up being connected
     360        # to (you can even omit this parameter), but just-in-case this has
     361        # relevance in future...
     362        best = None
     363        for ap in self._access_points.values():
     364            if best is None or ap.strength > best.strength:
     365                best = ap
     366
    389367        netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path,
    390368                                  self._device.object_path,
    391                                   self._model.object_path,
     369                                  best.model.object_path,
    392370                                  reply_handler=self.__activate_reply_cb,
    393371                                  error_handler=self.__activate_error_cb)
    394372
    class AccessPointView(CanvasPulsingIcon): 
    396374        logging.debug('Connection activated: %s', connection)
    397375
    398376    def __activate_error_cb(self, err):
    399         logging.debug('Failed to activate connection: %s', err)
     377        logging.error('Failed to activate connection: %s', err)
    400378
    401379    def set_filter(self, query):
    402380        self._greyed_out = self._name.lower().find(query) == -1
    class AccessPointView(CanvasPulsingIcon): 
    407385        keydialog.create(self._name, self._flags, self._wpa_flags,
    408386                         self._rsn_flags, self._device_caps, response)
    409387
    410     def disconnect(self):
    411         self._bus.remove_signal_receiver(self.__ap_properties_changed_cb,
    412                                          signal_name='PropertiesChanged',
    413                                          path=self._model.object_path,
    414                                          dbus_interface=_NM_ACCESSPOINT_IFACE)
     388    def update_strength(self):
     389        if self._active_ap is not None:
     390            # display strength of AP that we are connected to
     391            new_strength = self._active_ap.strength
     392        else:
     393            # display the strength of the strongest AP that makes up this
     394            # network, also considering that there may be no APs
     395            new_strength = max([0] + [ap.strength for ap in
     396                                      self._access_points.values()])
     397
     398        if new_strength != self._strength:
     399            self._strength = new_strength
     400            self._update_state()
    415401
     402    def add_ap(self, ap):
     403        self._access_points[ap.model.object_path] = ap
     404        self.update_strength()
     405
     406    def remove_ap(self, ap):
     407        path = ap.model.object_path
     408        if path not in self._access_points:
     409            return
     410        del self._access_points[path]
     411        if self._active_ap == ap:
     412            self._active_ap = None
     413        self.update_strength()
     414
     415    def num_aps(self):
     416        return len(self._access_points)
     417
     418    def find_ap(self, ap_path):
     419        if ap_path not in self._access_points:
     420            return None
     421        return self._access_points[ap_path]
     422
     423    def disconnect(self):
    416424        self._bus.remove_signal_receiver(self.__device_state_changed_cb,
    417425                                         signal_name='StateChanged',
    418426                                         path=self._device.object_path,
    class AccessPointView(CanvasPulsingIcon): 
    422430                                         path=self._device.object_path,
    423431                                         dbus_interface=_NM_WIRELESS_IFACE)
    424432
     433
    425434class ActivityView(hippo.CanvasBox):
    426435    def __init__(self, model):
    427436        hippo.CanvasBox.__init__(self)
    class ActivityView(hippo.CanvasBox): 
    538547
    539548_AUTOSEARCH_TIMEOUT = 1000
    540549
     550
    541551class MeshToolbar(gtk.Toolbar):
    542552    __gtype_name__ = 'MeshToolbar'
    543553
    class MeshToolbar(gtk.Toolbar): 
    606616        self.search_entry.activate()
    607617        return False
    608618
     619
    609620class DeviceObserver(object):
    610621    def __init__(self, box, device):
    611622        self._box = box
    class DeviceObserver(object): 
    650661                                         path=self._device.object_path,
    651662                                         dbus_interface=_NM_WIRELESS_IFACE)
    652663
     664
    653665class NetworkManagerObserver(object):
    654666    def __init__(self, box):
    655667        self._box = box
    class NetworkManagerObserver(object): 
    693705            state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State')
    694706            if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
    695707                ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
     708                found = False
    696709                if ap_o != '/':
    697                     ap_view = self._box.access_points[ap_o]
    698                     ap_view.create_keydialog(kwargs['response'])
    699                 else:
     710                    for net in self._box.wireless_networks.values():
     711                        if net.find_ap(ap_o) is not None:
     712                            found = True
     713                            net.create_keydialog(kwargs['response'])
     714                if not found:
    700715                    logging.error('Could not determine AP for'
    701716                                  ' specific object %s' % conn_o)
    702717
    class NetworkManagerObserver(object): 
    727742            observer.disconnect()
    728743            del self._devices[device_o]
    729744
     745
    730746class MeshBox(gtk.VBox):
    731747    __gtype_name__ = 'SugarMeshBox'
    732748
    class MeshBox(gtk.VBox): 
    735751
    736752        gobject.GObject.__init__(self)
    737753
    738         self.access_points = {}
     754        self.wireless_networks = {}
    739755
    740756        self._model = neighborhood.get_model()
    741757        self._buddies = {}
    class MeshBox(gtk.VBox): 
    863879        del self._activities[activity_model.get_id()]
    864880        icon.destroy()
    865881
    866     def add_access_point(self, device, ap):
    867         icon = AccessPointView(device, ap)
    868         self._layout.add(icon)
     882    # add AP to its corresponding network icon on the desktop,
     883    # creating one if it doesn't already exist
     884    def _add_ap_to_network(self, ap):
     885        hash = ap.network_hash()
     886        if hash in self.wireless_networks:
     887            self.wireless_networks[hash].add_ap(ap)
     888        else:
     889            # this is a new network
     890            icon = WirelessNetworkView(ap)
     891            self.wireless_networks[hash] = icon
     892            self._layout.add(icon)
     893            if hasattr(icon, 'set_filter'):
     894                icon.set_filter(self._query)
    869895
    870         if hasattr(icon, 'set_filter'):
    871             icon.set_filter(self._query)
     896    def _remove_net_if_empty(self, net, hash):
     897        # remove a network if it has no APs left
     898        if net.num_aps() != 0:
     899            net.disconnect()
     900            self._layout.remove(net)
     901            del self.wireless_networks[hash]
    872902
    873         self.access_points[ap.object_path] = icon
     903    def _ap_props_changed_cb(self, ap, old_hash):
     904        if old_hash is None: # new AP finished initializing
     905            self._add_ap_to_network(ap)
     906            return
     907
     908        hash = ap.network_hash()
     909        if old_hash == hash:
     910            # no change in network identity, so just update signal strengths
     911            self.wireless_networks[hash].update_strength()
     912            return
     913
     914        # properties change includes a change of the identity of the network
     915        # that it is on. so create this as a new network.
     916        self.wireless_networks[old_hash].remove_ap(ap)
     917        self._remove_net_if_empty(wireless_networks[old_hash], old_hash)
     918        self._add_ap_to_network(ap)
     919
     920    def add_access_point(self, device, ap_o):
     921        ap = AccessPoint(device, ap_o)
     922        ap.connect('props-changed', self._ap_props_changed_cb)
     923        ap.initialize()
    874924
    875925    def remove_access_point(self, ap_o):
    876         if ap_o in self.access_points:
    877             icon = self.access_points[ap_o]
    878             icon.disconnect()
    879             self._layout.remove(icon)
    880             del self.access_points[ap_o]
    881         else:
    882             logging.error('Can not remove access point %s', ap_o)
     926        # we don't keep an index of ap object path to network, but since
     927        # we'll only ever have a handful of networks, just try them all...
     928        for net in self.wireless_networks.values():
     929            ap = net.find_ap(ap_o)
     930            if not ap:
     931                continue
     932
     933            ap.disconnect()
     934            net.remove_ap(ap)
     935            self._remove_net_if_empty(net, ap.network_hash())
     936            return
     937
     938        logging.error('Can not remove access point %s', ap_o)
    883939
    884940    def suspend(self):
    885941        if not self._suspended:
    886942            self._suspended = True
    887             for ap in self.access_points.values():
    888                 ap.props.paused = True
     943            for net in self.wireless_networks.values():
     944                net.props.paused = True
    889945
    890946    def resume(self):
    891947        if self._suspended:
    892948            self._suspended = False
    893             for ap in self.access_points.values():
    894                 ap.props.paused = False
     949            for net in self.wireless_networks.values():
     950                net.props.paused = False
    895951
    896952    def _toolbar_query_changed_cb(self, toolbar, query):
    897953        self._query = query.lower()
  • src/jarabe/model/network.py

    diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py
    index 0a79de4..df1c26a 100644
    a b import os 
    2020import time
    2121
    2222import dbus
     23import gobject
    2324import ConfigParser
    2425
    2526from sugar import dispatch
    NM_SETTINGS_PATH = '/org/freedesktop/NetworkManagerSettings' 
    7677NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManagerSettings'
    7778NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection'
    7879NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets'
     80NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
    7981
    8082_nm_settings = None
    8183_conn_counter = 0
    class NMSettingsConnection(dbus.service.Object): 
    323325        else:
    324326            reply(self._secrets.get_dict())
    325327
     328
     329class AccessPoint(gobject.GObject):
     330    __gsignals__ = {
     331        'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
     332                          ([gobject.TYPE_PYOBJECT]))
     333    }
     334
     335    def __init__(self, device, model):
     336        self.__gobject_init__()
     337        self.device = device
     338        self.model = model
     339
     340        self._initialized = False
     341        self._bus = dbus.SystemBus()
     342
     343        self.name = ''
     344        self.strength = 0
     345        self.flags = 0
     346        self.wpa_flags = 0
     347        self.rsn_flags = 0
     348        self.mode = 0
     349
     350    def initialize(self):
     351        model_props = dbus.Interface(self.model,
     352            'org.freedesktop.DBus.Properties')
     353        model_props.GetAll(NM_ACCESSPOINT_IFACE, byte_arrays=True,
     354                           reply_handler=self._ap_properties_changed_cb,
     355                           error_handler=self._get_all_props_error_cb)
     356
     357        self._bus.add_signal_receiver(self._ap_properties_changed_cb,
     358                                      signal_name='PropertiesChanged',
     359                                      path=self.model.object_path,
     360                                      dbus_interface=NM_ACCESSPOINT_IFACE,
     361                                      byte_arrays=True)
     362
     363    # this is a hash which uniquely identifies the network that this AP
     364    # is a bridge to. i.e. its expected for 2 APs with identical SSID and
     365    # other settings to have the same network hash, because we assume that
     366    # they are a part of the same underlying network.
     367    def network_hash(self):
     368        # based on logic from nm-applet
     369        fl = 0
     370
     371        if self.mode == NM_802_11_MODE_INFRA:
     372            fl |= 1 << 0
     373        elif self.mode == NM_802_11_MODE_ADHOC:
     374            fl |= 1 << 1
     375        else:
     376            fl |= 1 << 2
     377
     378        # Separate out no encryption, WEP-only, and WPA-capable */
     379        if (not (self.flags & NM_802_11_AP_FLAGS_PRIVACY)) \
     380                and self.wpa_flags == NM_802_11_AP_SEC_NONE \
     381                and self.rsn_flags == NM_802_11_AP_SEC_NONE:
     382            fl |= 1 << 3
     383        elif (self.flags & NM_802_11_AP_FLAGS_PRIVACY) \
     384                and self.wpa_flags == NM_802_11_AP_SEC_NONE \
     385                and self.rsn_flags == NM_802_11_AP_SEC_NONE:
     386            fl |= 1 << 4
     387        elif (not (self.flags & NM_802_11_AP_FLAGS_PRIVACY)) \
     388                and self.wpa_flags != NM_802_11_AP_SEC_NONE \
     389                and self.rsn_flags != NM_802_11_AP_SEC_NONE:
     390            fl |= 1 << 5
     391        else:
     392            fl |= 1 << 6
     393
     394        hashstr = str(fl) + "@" + self.name
     395        return hash(hashstr)
     396
     397    def _update_properties(self, properties):
     398        if self._initialized:
     399            old_hash = self.network_hash()
     400        else:
     401            old_hash = None
     402
     403        if 'Ssid' in properties:
     404            self.name = properties['Ssid']
     405        if 'Strength' in properties:
     406            self.strength = properties['Strength']
     407        if 'Flags' in properties:
     408            self.flags = properties['Flags']
     409        if 'WpaFlags' in properties:
     410            self.wpa_flags = properties['WpaFlags']
     411        if 'RsnFlags' in properties:
     412            self.rsn_flags = properties['RsnFlags']
     413        if 'Mode' in properties:
     414            self.mode = properties['Mode']
     415        self._initialized = True
     416        self.emit('props-changed', old_hash)
     417
     418    def _get_all_props_error_cb(self, err):
     419        logging.error('Error getting the access point properties: %s', err)
     420
     421    def _ap_properties_changed_cb(self, properties):
     422        self._update_properties(properties)
     423
     424    def disconnect(self):
     425        self._bus.remove_signal_receiver(self._ap_properties_changed_cb,
     426                                         signal_name='PropertiesChanged',
     427                                         path=self.model.object_path,
     428                                         dbus_interface=NM_ACCESSPOINT_IFACE)
     429
     430
    326431def get_settings():
    327432    global _nm_settings
    328433    if _nm_settings is None: