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

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

updated patch

  • src/jarabe/desktop/meshbox.py

    From f28cc203a2e8a7e20accf513b5d1790ed9b3917f 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 |  391 +++++++++++++++++++++++++++++------------
     1 files changed, 274 insertions(+), 117 deletions(-)
    
    diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
    index f8c446d..20a4eae 100644
    a b _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' 
    6060
    6161_ICON_NAME = 'network-wireless'
    6262
    63 class AccessPointView(CanvasPulsingIcon):
     63
     64class AccessPoint(gobject.GObject):
     65    __gsignals__ = {
     66        'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
     67                          ([gobject.TYPE_PYOBJECT]))
     68    }
     69
    6470    def __init__(self, device, model):
     71        self.__gobject_init__()
     72        self.device = device
     73        self.model = model
     74
     75        self._initialized = False
     76        self._bus = dbus.SystemBus()
     77
     78        self.name = ''
     79        self.strength = 0
     80        self.flags = 0
     81        self.wpa_flags = 0
     82        self.rsn_flags = 0
     83        self.mode = 0
     84
     85    def initialize(self):
     86        model_props = dbus.Interface(self.model,
     87            'org.freedesktop.DBus.Properties')
     88        model_props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
     89                           reply_handler=self._ap_properties_changed_cb,
     90                           error_handler=self._get_all_props_error_cb)
     91
     92        self._bus.add_signal_receiver(self._ap_properties_changed_cb,
     93                                      signal_name='PropertiesChanged',
     94                                      path=self.model.object_path,
     95                                      dbus_interface=_NM_ACCESSPOINT_IFACE,
     96                                      byte_arrays=True)
     97
     98    # this is a hash which uniquely identifies the network that this AP
     99    # is a bridge to. i.e. its expected for 2 APs with identical SSID and
     100    # other settings to have the same network hash, because we assume that
     101    # they are a part of the same underlying network.
     102    def network_hash(self):
     103        # based on logic from nm-applet
     104        fl = 0
     105
     106        if self.mode == network.NM_802_11_MODE_INFRA:
     107            fl |= 1 << 0
     108        elif self.mode == network.NM_802_11_MODE_ADHOC:
     109            fl |= 1 << 1
     110        else:
     111            fl |= 1 << 2
     112
     113        # Separate out no encryption, WEP-only, and WPA-capable */
     114        if (not (self.flags & network.NM_802_11_AP_FLAGS_PRIVACY)) \
     115                and self.wpa_flags == network.NM_802_11_AP_SEC_NONE \
     116                and self.rsn_flags == network.NM_802_11_AP_SEC_NONE:
     117            fl |= 1 << 3
     118        elif (self.flags & network.NM_802_11_AP_FLAGS_PRIVACY) \
     119                and self.wpa_flags == network.NM_802_11_AP_SEC_NONE \
     120                and self.rsn_flags == network.NM_802_11_AP_SEC_NONE:
     121            fl |= 1 << 4
     122        elif (not (self.flags & network.NM_802_11_AP_FLAGS_PRIVACY)) \
     123                and self.wpa_flags != network.NM_802_11_AP_SEC_NONE \
     124                and self.rsn_flags != networkNM_802_11_AP_SEC_NONE:
     125            fl |= 1 << 5
     126        else:
     127            fl |= 1 << 6
     128
     129        hashstr = str(fl) + "@" + self.name
     130        return hash(hashstr)
     131
     132    def _update_properties(self, properties):
     133        if self._initialized:
     134            old_hash = self.network_hash()
     135        else:
     136            old_hash = None
     137
     138        if 'Ssid' in properties:
     139            self.name = properties['Ssid']
     140        if 'Strength' in properties:
     141            self.strength = properties['Strength']
     142        if 'Flags' in properties:
     143            self.flags = properties['Flags']
     144        if 'WpaFlags' in properties:
     145            self.wpa_flags = properties['WpaFlags']
     146        if 'RsnFlags' in properties:
     147            self.rsn_flags = properties['RsnFlags']
     148        if 'Mode' in properties:
     149            self.mode = properties['Mode']
     150        self._initialized = True
     151        self.emit('props-changed', old_hash)
     152
     153    def _get_all_props_error_cb(self, err):
     154        logging.error('Error getting the access point properties: %s', err)
     155
     156    def _ap_properties_changed_cb(self, properties):
     157        self._update_properties(properties)
     158
     159    def disconnect(self):
     160        self._bus.remove_signal_receiver(self._ap_properties_changed_cb,
     161                                         signal_name='PropertiesChanged',
     162                                         path=self.model.object_path,
     163                                         dbus_interface=_NM_ACCESSPOINT_IFACE)
     164
     165
     166class WirelessNetworkView(CanvasPulsingIcon):
     167    def __init__(self, initial_ap):
    65168        CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE,
    66169                                   cache=True)
    67170        self._bus = dbus.SystemBus()
    68         self._device = device
    69         self._model = model
     171        self._access_points = {initial_ap.model.object_path: initial_ap}
     172        self._active_ap = None
     173        self._device = initial_ap.device
    70174        self._palette_icon = None
    71175        self._disconnect_item = None
    72176        self._connect_item = None
    73177        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
     178        self._name = initial_ap.name
     179        self._mode = initial_ap.mode
     180        self._strength = initial_ap.strength
     181        self._flags = initial_ap.flags
     182        self._wpa_flags = initial_ap.wpa_flags
     183        self._rsn_flags = initial_ap.rsn_flags
    80184        self._device_caps = 0
    81185        self._device_state = None
    82186        self._connection = None
    83         self._active = True
    84         self._color = None
     187
     188        if self._mode == network.NM_802_11_MODE_ADHOC:
     189            encoded_color = self._name.split("#", 1)
     190            if len(encoded_color) == 2:
     191                self._color = xocolor.XoColor('#' + encoded_color[1])
     192        if self._mode == network.NM_802_11_MODE_INFRA:
     193            sha_hash = hashlib.sha1()
     194            data = self._name + hex(self._flags)
     195            sha_hash.update(data)
     196            digest = hash(sha_hash.digest())
     197            index = digest % len(xocolor.colors)
     198
     199            self._color = xocolor.XoColor('%s,%s' %
     200                                          (xocolor.colors[index][0],
     201                                           xocolor.colors[index][1]))
    85202
    86203        self.connect('button-release-event', self.__button_release_event_cb)
    87204
    class AccessPointView(CanvasPulsingIcon): 
    91208
    92209        self._palette = self._create_palette()
    93210        self.set_palette(self._palette)
     211        self._palette_icon.props.xo_color = self._color
    94212
    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)
     213        if network.find_connection(self._name) is not None:
     214            self.props.badge_name = "emblem-favorite"
     215            self._palette_icon.props.badge_name = "emblem-favorite"
     216        elif initial_ap.flags == network.NM_802_11_AP_FLAGS_PRIVACY:
     217            self.props.badge_name = "emblem-locked"
     218            self._palette_icon.props.badge_name = "emblem-locked"
     219        else:
     220            self.props.badge_name = None
     221            self._palette_icon.props.badge_name = None
    105222
    106223        interface_props = dbus.Interface(self._device,
    107224                                         'org.freedesktop.DBus.Properties')
    class AccessPointView(CanvasPulsingIcon): 
    117234
    118235        self._bus.add_signal_receiver(self.__device_state_changed_cb,
    119236                                      signal_name='StateChanged',
    120                                       path=device.object_path,
     237                                      path=self._device.object_path,
    121238                                      dbus_interface=_NM_DEVICE_IFACE)
    122239        self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
    123240                                      signal_name='PropertiesChanged',
    124                                       path=device.object_path,
     241                                      path=self._device.object_path,
    125242                                      dbus_interface=_NM_WIRELESS_IFACE)
    126243
    127244    def _create_palette(self):
    class AccessPointView(CanvasPulsingIcon): 
    148265        self._device_state = new_state
    149266        self._update_state()
    150267
    151     def __ap_properties_changed_cb(self, properties):
    152         self._update_properties(properties)
     268    def __update_active_ap(self, ap_path):
     269        if ap_path in self._access_points:
     270            # save reference to active AP, so that we always display the
     271            # strength of that one
     272            self._active_ap = self._access_points[ap_path]
     273            self.update_strength()
     274            self._update_state()
     275        elif self._active_ap is not None:
     276            # revert to showing state of strongest AP again
     277            self._active_ap = None
     278            self.update_strength()
     279            self._update_state()
    153280
    154281    def __wireless_properties_changed_cb(self, properties):
    155282        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']
     283            self.__update_active_ap(properties['ActiveAccessPoint'])
    175284
    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()
     285    def __get_active_ap_reply_cb(self, ap_path):
     286        self.__update_active_ap(ap_path)
    196287
    197288    def __get_active_ap_error_cb(self, err):
    198         logging.debug('Error getting the active access point: %s', err)
     289        logging.error('Error getting the active access point: %s', err)
    199290
    200291    def __get_device_caps_reply_cb(self, caps):
    201292        self._device_caps = caps
    202293
    203294    def __get_device_caps_error_cb(self, err):
    204         logging.debug('Error getting the wireless device properties: %s', err)
     295        logging.error('Error getting the wireless device properties: %s', err)
    205296
    206297    def __get_device_state_reply_cb(self, state):
    207298        self._device_state = state
    208299        self._update()
    209300
    210301    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)
     302        logging.error('Error getting the device state: %s', err)
    218303
    219304    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 
    232305        self._update_state()
    233306        self._update_color()
    234307
    235308    def _update_state(self):
    236         if self._active:
     309        if self._active_ap is not None:
    237310            state = self._device_state
    238311        else:
    239312            state = network.DEVICE_STATE_UNKNOWN
    class AccessPointView(CanvasPulsingIcon): 
    283356        else:
    284357            self.props.base_color = self._color
    285358
    286         self._palette_icon.props.xo_color = self._color
    287 
    288359    def _disconnect_activate_cb(self, item):
    289360        pass
    290361
    291 
    292362    def _add_ciphers_from_flags(self, flags, pairwise):
    293363        ciphers = []
    294364        if pairwise:
    class AccessPointView(CanvasPulsingIcon): 
    386456        obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
    387457        netmgr = dbus.Interface(obj, _NM_IFACE)
    388458
     459        # Choose the AP with the strongest signal
     460        # This is not the deciding factor for which AP ends up being connected
     461        # to (you can even omit this parameter), but just-in-case this has
     462        # relevance in future...
     463        best = None
     464        for ap in self._access_points.values():
     465            if best is None or ap.strength > best.strength:
     466                best = ap
     467
    389468        netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path,
    390469                                  self._device.object_path,
    391                                   self._model.object_path,
     470                                  best.model.object_path,
    392471                                  reply_handler=self.__activate_reply_cb,
    393472                                  error_handler=self.__activate_error_cb)
    394473
    class AccessPointView(CanvasPulsingIcon): 
    396475        logging.debug('Connection activated: %s', connection)
    397476
    398477    def __activate_error_cb(self, err):
    399         logging.debug('Failed to activate connection: %s', err)
     478        logging.error('Failed to activate connection: %s', err)
    400479
    401480    def set_filter(self, query):
    402481        self._greyed_out = self._name.lower().find(query) == -1
    class AccessPointView(CanvasPulsingIcon): 
    407486        keydialog.create(self._name, self._flags, self._wpa_flags,
    408487                         self._rsn_flags, self._device_caps, response)
    409488
    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)
     489    def update_strength(self):
     490        if self._active_ap is not None:
     491            # display strength of AP that we are connected to
     492            new_strength = self._active_ap.strength
     493        else:
     494            # display the strength of the strongest AP that makes up this
     495            # network, also considering that there may be no APs
     496            new_strength = max([0] + [ap.strength for ap in
     497                                      self._access_points.values()])
     498
     499        if new_strength != self._strength:
     500            self._strength = new_strength
     501            self._update_state()
     502
     503    def add_ap(self, ap):
     504        self._access_points[ap.model.object_path] = ap
     505        self.update_strength()
     506
     507    def remove_ap(self, ap):
     508        path = ap.model.object_path
     509        if path not in self._access_points:
     510            return
     511        del self._access_points[path]
     512        if self._active_ap == ap:
     513            self._active_ap = None
     514        self.update_strength()
     515
     516    def num_aps(self):
     517        return len(self._access_points)
    415518
     519    def find_ap(self, ap_path):
     520        if ap_path not in self._access_points:
     521            return None
     522        return self._access_points[ap_path]
     523
     524    def disconnect(self):
    416525        self._bus.remove_signal_receiver(self.__device_state_changed_cb,
    417526                                         signal_name='StateChanged',
    418527                                         path=self._device.object_path,
    class AccessPointView(CanvasPulsingIcon): 
    422531                                         path=self._device.object_path,
    423532                                         dbus_interface=_NM_WIRELESS_IFACE)
    424533
     534
    425535class ActivityView(hippo.CanvasBox):
    426536    def __init__(self, model):
    427537        hippo.CanvasBox.__init__(self)
    class ActivityView(hippo.CanvasBox): 
    538648
    539649_AUTOSEARCH_TIMEOUT = 1000
    540650
     651
    541652class MeshToolbar(gtk.Toolbar):
    542653    __gtype_name__ = 'MeshToolbar'
    543654
    class MeshToolbar(gtk.Toolbar): 
    606717        self.search_entry.activate()
    607718        return False
    608719
     720
    609721class DeviceObserver(object):
    610722    def __init__(self, box, device):
    611723        self._box = box
    class DeviceObserver(object): 
    650762                                         path=self._device.object_path,
    651763                                         dbus_interface=_NM_WIRELESS_IFACE)
    652764
     765
    653766class NetworkManagerObserver(object):
    654767    def __init__(self, box):
    655768        self._box = box
    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 NetworkManagerObserver(object): 
    727843            observer.disconnect()
    728844            del self._devices[device_o]
    729845
     846
    730847class MeshBox(gtk.VBox):
    731848    __gtype_name__ = 'SugarMeshBox'
    732849
    class MeshBox(gtk.VBox): 
    735852
    736853        gobject.GObject.__init__(self)
    737854
    738         self.access_points = {}
     855        self.wireless_networks = {}
    739856
    740857        self._model = neighborhood.get_model()
    741858        self._buddies = {}
    class MeshBox(gtk.VBox): 
    863980        del self._activities[activity_model.get_id()]
    864981        icon.destroy()
    865982
    866     def add_access_point(self, device, ap):
    867         icon = AccessPointView(device, ap)
    868         self._layout.add(icon)
     983    # add AP to its corresponding network icon on the desktop,
     984    # creating one if it doesn't already exist
     985    def _add_ap_to_network(self, ap):
     986        hash = ap.network_hash()
     987        if hash in self.wireless_networks:
     988            self.wireless_networks[hash].add_ap(ap)
     989        else:
     990            # this is a new network
     991            icon = WirelessNetworkView(ap)
     992            self.wireless_networks[hash] = icon
     993            self._layout.add(icon)
     994            if hasattr(icon, 'set_filter'):
     995                icon.set_filter(self._query)
    869996
    870         if hasattr(icon, 'set_filter'):
    871             icon.set_filter(self._query)
     997    def _remove_net_if_empty(self, net, hash):
     998        # remove a network if it has no APs left
     999        if net.num_aps() != 0:
     1000            net.disconnect()
     1001            self._layout.remove(net)
     1002            del self.wireless_networks[hash]
    8721003
    873         self.access_points[ap.object_path] = icon
     1004    def _ap_props_changed_cb(self, ap, old_hash):
     1005        if old_hash is None: # new AP finished initializing
     1006            self._add_ap_to_network(ap)
     1007            return
     1008
     1009        hash = ap.network_hash()
     1010        if old_hash == hash:
     1011            # no change in network identity, so just update signal strengths
     1012            self.wireless_networks[hash].update_strength()
     1013            return
     1014
     1015        # properties change includes a change of the identity of the network
     1016        # that it is on. so create this as a new network.
     1017        self.wireless_networks[old_hash].remove_ap(ap)
     1018        self._remove_net_if_empty(wireless_networks[old_hash], old_hash)
     1019        self._add_ap_to_network(ap)
     1020
     1021    def add_access_point(self, device, ap_o):
     1022        ap = AccessPoint(device, ap_o)
     1023        ap.connect('props-changed', self._ap_props_changed_cb)
     1024        ap.initialize()
    8741025
    8751026    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)
     1027        # we don't keep an index of ap object path to network, but since
     1028        # we'll only ever have a handful of networks, just try them all...
     1029        for net in self.wireless_networks.values():
     1030            ap = net.find_ap(ap_o)
     1031            if not ap:
     1032                continue
     1033
     1034            ap.disconnect()
     1035            net.remove_ap(ap)
     1036            self._remove_net_if_empty(net, ap.network_hash())
     1037            return
     1038
     1039        logging.error('Can not remove access point %s', ap_o)
    8831040
    8841041    def suspend(self):
    8851042        if not self._suspended:
    8861043            self._suspended = True
    887             for ap in self.access_points.values():
    888                 ap.props.paused = True
     1044            for net in self.wireless_networks.values():
     1045                net.props.paused = True
    8891046
    8901047    def resume(self):
    8911048        if self._suspended:
    8921049            self._suspended = False
    893             for ap in self.access_points.values():
    894                 ap.props.paused = False
     1050            for net in self.wireless_networks.values():
     1051                net.props.paused = False
    8951052
    8961053    def _toolbar_query_changed_cb(self, toolbar, query):
    8971054        self._query = query.lower()