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

File 0001-Group-access-points-by-network-330.patch, 20.7 KB (added by dsd, 14 years ago)
  • src/jarabe/desktop/meshbox.py

    From 3f260708394122c5a358743aec54a5f879d6237d 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 |  367 +++++++++++++++++++++++++++++------------
     1 files changed, 260 insertions(+), 107 deletions(-)
    
    diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
    index f8c446d..87ce7c6 100644
    a b _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' 
    6060
    6161_ICON_NAME = 'network-wireless'
    6262
    63 class AccessPointView(CanvasPulsingIcon):
     63class AccessPoint(gobject.GObject):
     64    __gsignals__ = {
     65        'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
     66                          ([gobject.TYPE_PYOBJECT]))
     67    }
     68
    6469    def __init__(self, device, model):
     70        self.__gobject_init__()
     71        self.device = device
     72        self.model = model
     73
     74        self._initialized = False
     75        self._bus = dbus.SystemBus()
     76
     77        self.name = ''
     78        self.strength = 0
     79        self.flags = 0
     80        self.wpa_flags = 0
     81        self.rsn_flags = 0
     82        self.mode = 0
     83
     84    def initialize(self):
     85        model_props = dbus.Interface(self.model,
     86            'org.freedesktop.DBus.Properties')
     87        model_props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
     88                           reply_handler=self._ap_properties_changed_cb,
     89                           error_handler=self._get_all_props_error_cb)
     90
     91        self._bus.add_signal_receiver(self._ap_properties_changed_cb,
     92                                      signal_name='PropertiesChanged',
     93                                      path=self.model.object_path,
     94                                      dbus_interface=_NM_ACCESSPOINT_IFACE,
     95                                      byte_arrays=True)
     96
     97    # this is a hash which uniquely identifies the network that this AP
     98    # is a bridge to. i.e. its expected for 2 APs with identical SSID and
     99    # other settings to have the same network hash, because we assume that
     100    # they are a part of the same underlying network.
     101    def network_hash(self):
     102        # based on logic from nm-applet
     103        fl = 0
     104
     105        if self.mode == network.NM_802_11_MODE_INFRA:
     106            fl |= 1 << 0
     107        elif self.mode == network.NM_802_11_MODE_ADHOC:
     108            fl |= 1 << 1
     109        else:
     110            fl |= 1 << 2
     111
     112        # Separate out no encryption, WEP-only, and WPA-capable */
     113        if (not (self.flags & network.NM_802_11_AP_FLAGS_PRIVACY)) \
     114                and self.wpa_flags == network.NM_802_11_AP_SEC_NONE \
     115                and self.rsn_flags == network.NM_802_11_AP_SEC_NONE:
     116            fl |= 1 << 3
     117        elif (self.flags & network.NM_802_11_AP_FLAGS_PRIVACY) \
     118                and self.wpa_flags == network.NM_802_11_AP_SEC_NONE \
     119                and self.rsn_flags == network.NM_802_11_AP_SEC_NONE:
     120            fl |= 1 << 4
     121        elif (not (self.flags & network.NM_802_11_AP_FLAGS_PRIVACY)) \
     122                and self.wpa_flags != network.NM_802_11_AP_SEC_NONE \
     123                and self.rsn_flags != networkNM_802_11_AP_SEC_NONE:
     124            fl |= 1 << 5
     125        else:
     126            fl |= 1 << 6
     127
     128        hashstr = str(fl) + "@" + self.name
     129        return hash(hashstr)
     130
     131    def _update_properties(self, properties):
     132        if self._initialized:
     133            old_hash = self.network_hash()
     134        else:
     135            old_hash = None
     136
     137        if 'Ssid' in properties:
     138            self.name = properties['Ssid']
     139        if 'Strength' in properties:
     140            self.strength = properties['Strength']
     141        if 'Flags' in properties:
     142            self.flags = properties['Flags']
     143        if 'WpaFlags' in properties:
     144            self.wpa_flags = properties['WpaFlags']
     145        if 'RsnFlags' in properties:
     146            self.rsn_flags = properties['RsnFlags']
     147        if 'Mode' in properties:
     148            self.mode = properties['Mode']
     149        self._initialized = True
     150        self.emit('props-changed', old_hash)
     151
     152    def _get_all_props_error_cb(self, err):
     153        logging.debug('Error getting the access point properties: %s', err)
     154
     155    def _ap_properties_changed_cb(self, properties):
     156        self._update_properties(properties)
     157
     158    def disconnect(self):
     159        self._bus.remove_signal_receiver(self._ap_properties_changed_cb,
     160                                         signal_name='PropertiesChanged',
     161                                         path=self.model.object_path,
     162                                         dbus_interface=_NM_ACCESSPOINT_IFACE)
     163
     164class WirelessNetworkView(CanvasPulsingIcon):
     165    def __init__(self, initial_ap):
    65166        CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE,
    66167                                   cache=True)
    67168        self._bus = dbus.SystemBus()
    68         self._device = device
    69         self._model = model
     169        self._access_points = {initial_ap.model.object_path: initial_ap}
     170        self._active_ap = None
     171        self._device = initial_ap.device
    70172        self._palette_icon = None
    71173        self._disconnect_item = None
    72174        self._connect_item = None
    73175        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
     176        self._name = initial_ap.name
     177        self._mode = initial_ap.mode
     178        self._strength = initial_ap.strength
     179        self._flags = initial_ap.flags
     180        self._wpa_flags = initial_ap.wpa_flags
     181        self._rsn_flags = initial_ap.rsn_flags
    80182        self._device_caps = 0
    81183        self._device_state = None
    82184        self._connection = None
    83         self._active = True
    84         self._color = None
     185
     186        if self._mode == network.NM_802_11_MODE_ADHOC:
     187            encoded_color = self._name.split("#", 1)
     188            if len(encoded_color) == 2:
     189                self._color = xocolor.XoColor('#' + encoded_color[1])
     190        if self._mode == network.NM_802_11_MODE_INFRA:
     191            sha_hash = hashlib.sha1()
     192            data = self._name + hex(self._flags)
     193            sha_hash.update(data)
     194            digest = hash(sha_hash.digest())
     195            index = digest % len(xocolor.colors)
     196
     197            self._color = xocolor.XoColor('%s,%s' %
     198                                          (xocolor.colors[index][0],
     199                                           xocolor.colors[index][1]))
    85200
    86201        self.connect('button-release-event', self.__button_release_event_cb)
    87202
    class AccessPointView(CanvasPulsingIcon): 
    91206
    92207        self._palette = self._create_palette()
    93208        self.set_palette(self._palette)
     209        self._palette_icon.props.xo_color = self._color
    94210
    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)
     211        if network.find_connection(self._name) is not None:
     212            self.props.badge_name = "emblem-favorite"
     213            self._palette_icon.props.badge_name = "emblem-favorite"
     214        elif initial_ap.flags == network.NM_802_11_AP_FLAGS_PRIVACY:
     215            self.props.badge_name = "emblem-locked"
     216            self._palette_icon.props.badge_name = "emblem-locked"
     217        else:
     218            self.props.badge_name = None
     219            self._palette_icon.props.badge_name = None
    105220
    106221        interface_props = dbus.Interface(self._device,
    107222                                         'org.freedesktop.DBus.Properties')
    class AccessPointView(CanvasPulsingIcon): 
    117232
    118233        self._bus.add_signal_receiver(self.__device_state_changed_cb,
    119234                                      signal_name='StateChanged',
    120                                       path=device.object_path,
     235                                      path=self._device.object_path,
    121236                                      dbus_interface=_NM_DEVICE_IFACE)
    122237        self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
    123238                                      signal_name='PropertiesChanged',
    124                                       path=device.object_path,
     239                                      path=self._device.object_path,
    125240                                      dbus_interface=_NM_WIRELESS_IFACE)
    126241
    127242    def _create_palette(self):
    class AccessPointView(CanvasPulsingIcon): 
    148263        self._device_state = new_state
    149264        self._update_state()
    150265
    151     def __ap_properties_changed_cb(self, properties):
    152         self._update_properties(properties)
     266    def __update_active_ap(self, ap_path):
     267        if ap_path in self._access_points:
     268            # save reference to active AP, so that we always display the
     269            # strength of that one
     270            self._active_ap = self._access_points[ap_path]
     271            self.update_strength()
     272            self._update_state()
     273        elif self._active_ap is not None:
     274            # revert to showing state of strongest AP again
     275            self._active_ap = None
     276            self.update_strength()
     277            self._update_state()
    153278
    154279    def __wireless_properties_changed_cb(self, properties):
    155280        if 'ActiveAccessPoint' in properties:
    156             ap = properties['ActiveAccessPoint']
    157             self._active = (ap == self._model.object_path)
    158             self._update_state()
    159 
    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']
     281            self.__update_active_ap(properties['ActiveAccessPoint'])
    175282
    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()
     283    def __get_active_ap_reply_cb(self, ap_path):
     284        self.__update_active_ap(ap_path)
    196285
    197286    def __get_active_ap_error_cb(self, err):
    198287        logging.debug('Error getting the active access point: %s', err)
    class AccessPointView(CanvasPulsingIcon): 
    217306        logging.debug('Error getting the access point properties: %s', err)
    218307
    219308    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 
    232309        self._update_state()
    233310        self._update_color()
    234311
    235312    def _update_state(self):
    236         if self._active:
     313        if self._active_ap is not None:
    237314            state = self._device_state
    238315        else:
    239316            state = network.DEVICE_STATE_UNKNOWN
    class AccessPointView(CanvasPulsingIcon): 
    283360        else:
    284361            self.props.base_color = self._color
    285362
    286         self._palette_icon.props.xo_color = self._color
    287 
    288363    def _disconnect_activate_cb(self, item):
    289364        pass
    290365
    291 
    292366    def _add_ciphers_from_flags(self, flags, pairwise):
    293367        ciphers = []
    294368        if pairwise:
    class AccessPointView(CanvasPulsingIcon): 
    386460        obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
    387461        netmgr = dbus.Interface(obj, _NM_IFACE)
    388462
     463        # Choose the AP with the strongest signal
     464        # This is not the deciding factor for which AP ends up being connected
     465        # to (you can even omit this parameter), but just-in-case this has
     466        # relevance in future...
     467        best = None
     468        for ap in self._access_points.values():
     469            if best is None or ap.strength > best.strength:
     470                best = ap
     471
    389472        netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path,
    390473                                  self._device.object_path,
    391                                   self._model.object_path,
     474                                  best.model.object_path,
    392475                                  reply_handler=self.__activate_reply_cb,
    393476                                  error_handler=self.__activate_error_cb)
    394477
    class AccessPointView(CanvasPulsingIcon): 
    407490        keydialog.create(self._name, self._flags, self._wpa_flags,
    408491                         self._rsn_flags, self._device_caps, response)
    409492
    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)
     493    def update_strength(self):
     494        if self._active_ap is not None:
     495            # display strength of AP that we are connected to
     496            new_strength = self._active_ap.strength
     497        else:
     498            # display the strength of the strongest AP that makes up this
     499            # network
     500            new_strength = 0
     501            for ap in self._access_points.values():
     502                if ap.strength > new_strength:
     503                    new_strength = ap.strength
     504
     505        if new_strength != self._strength:
     506            self._strength = new_strength
     507            self._update_state()
    415508
     509    def add_ap(self, ap):
     510        self._access_points[ap.model.object_path] = ap
     511        self.update_strength()
     512
     513    def remove_ap(self, ap):
     514        path = ap.model.object_path
     515        if path not in self._access_points:
     516            return
     517        del self._access_points[path]
     518        self.update_strength()
     519
     520    def num_aps(self):
     521        return len(self._access_points)
     522
     523    def find_ap(self, ap_path):
     524        if ap_path not in self._access_points:
     525            return None
     526        return self._access_points[ap_path]
     527
     528    def disconnect(self):
    416529        self._bus.remove_signal_receiver(self.__device_state_changed_cb,
    417530                                         signal_name='StateChanged',
    418531                                         path=self._device.object_path,
    class NetworkManagerObserver(object): 
    693806            state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State')
    694807            if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
    695808                ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
     809                found = False
    696810                if ap_o != '/':
    697                     ap_view = self._box.access_points[ap_o]
    698                     ap_view.create_keydialog(kwargs['response'])
    699                 else:
     811                    for net in self._box.wireless_networks.values():
     812                        if net.find_ap(ap_o) is not None:
     813                            found = True
     814                            net.create_keydialog(kwargs['response'])
     815                if not found:
    700816                    logging.error('Could not determine AP for'
    701817                                  ' specific object %s' % conn_o)
    702818
    class MeshBox(gtk.VBox): 
    735851
    736852        gobject.GObject.__init__(self)
    737853
    738         self.access_points = {}
     854        self.wireless_networks = {}
    739855
    740856        self._model = neighborhood.get_model()
    741857        self._buddies = {}
    class MeshBox(gtk.VBox): 
    863979        del self._activities[activity_model.get_id()]
    864980        icon.destroy()
    865981
    866     def add_access_point(self, device, ap):
    867         icon = AccessPointView(device, ap)
    868         self._layout.add(icon)
     982    # add AP to its corresponding network icon on the desktop,
     983    # creating one if it doesn't already exist
     984    def _add_ap_to_network(self, ap):
     985        hash = ap.network_hash()
     986        if hash in self.wireless_networks:
     987            self.wireless_networks[hash].add_ap(ap)
     988        else:
     989            # this is a new network
     990            icon = WirelessNetworkView(ap)
     991            self.wireless_networks[hash] = icon
     992            self._layout.add(icon)
     993            if hasattr(icon, 'set_filter'):
     994                icon.set_filter(self._query)
    869995
    870         if hasattr(icon, 'set_filter'):
    871             icon.set_filter(self._query)
     996    def _ap_props_changed_cb(self, ap, old_hash):
     997        if old_hash is None: # new AP finished initializing
     998            self._add_ap_to_network(ap)
     999            return
    8721000
    873         self.access_points[ap.object_path] = icon
     1001        hash = ap.network_hash()
     1002        if old_hash == hash:
     1003            # no change in network identity, so just update signal strengths
     1004            self.wireless_networks[hash].update_strength()
     1005            return
     1006
     1007        # properties change includes a change of the identity of the network
     1008        # that it is on. so create this as a new network.
     1009        self.wireless_networks[old_hash].remove_ap(ap)
     1010        self._add_ap_to_network(ap)
     1011
     1012    def add_access_point(self, device, ap_o):
     1013        ap = AccessPoint(device, ap_o)
     1014        ap.connect('props-changed', self._ap_props_changed_cb)
     1015        ap.initialize()
    8741016
    8751017    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)
     1018        # we don't keep an index of ap object path to network, but since
     1019        # we'll only ever have a handful of networks, just try them all...
     1020        for net in self.wireless_networks.values():
     1021            ap = net.find_ap(ap_o)
     1022            if not ap:
     1023                continue
     1024
     1025            ap.disconnect()
     1026            net.remove_ap(ap)
     1027
     1028            # if that was the last AP in the network, then remove the network
     1029            if net.num_aps() == 0:
     1030                net.disconnect()
     1031                self._layout.remove(net)
     1032                del self.wireless_networks[ap.network_hash()]
     1033            return
     1034
     1035        logging.error('Can not remove access point %s', ap_o)
    8831036
    8841037    def suspend(self):
    8851038        if not self._suspended:
    8861039            self._suspended = True
    887             for ap in self.access_points.values():
    888                 ap.props.paused = True
     1040            for net in self.wireless_networks.values():
     1041                net.props.paused = True
    8891042
    8901043    def resume(self):
    8911044        if self._suspended:
    8921045            self._suspended = False
    893             for ap in self.access_points.values():
    894                 ap.props.paused = False
     1046            for net in self.wireless_networks.values():
     1047                net.props.paused = False
    8951048
    8961049    def _toolbar_query_changed_cb(self, toolbar, query):
    8971050        self._query = query.lower()