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) |
---|
-
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 49 49 from jarabe.model.network import Settings 50 50 from jarabe.model.network import IP4Config 51 51 from jarabe.model.network import WirelessSecurity 52 from jarabe.model.network import AccessPoint 52 53 53 54 _NM_SERVICE = 'org.freedesktop.NetworkManager' 54 55 _NM_IFACE = 'org.freedesktop.NetworkManager' … … _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' 60 61 61 62 _ICON_NAME = 'network-wireless' 62 63 63 class AccessPointView(CanvasPulsingIcon): 64 def __init__(self, device, model): 64 65 class WirelessNetworkView(CanvasPulsingIcon): 66 def __init__(self, initial_ap): 65 67 CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, 66 68 cache=True) 67 69 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 70 73 self._palette_icon = None 71 74 self._disconnect_item = None 72 75 self._connect_item = None 73 76 self._greyed_out = False 74 self._name = ''75 self._ strength = 076 self._ flags = 077 self._ wpa_flags = 078 self._ rsn_flags = 079 self._ mode = network.NM_802_11_MODE_UNKNOWN77 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 80 83 self._device_caps = 0 81 84 self._device_state = None 82 85 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])) 85 101 86 102 self.connect('button-release-event', self.__button_release_event_cb) 87 103 … … class AccessPointView(CanvasPulsingIcon): 91 107 92 108 self._palette = self._create_palette() 93 109 self.set_palette(self._palette) 110 self._palette_icon.props.xo_color = self._color 94 111 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 105 121 106 122 interface_props = dbus.Interface(self._device, 107 123 'org.freedesktop.DBus.Properties') … … class AccessPointView(CanvasPulsingIcon): 117 133 118 134 self._bus.add_signal_receiver(self.__device_state_changed_cb, 119 135 signal_name='StateChanged', 120 path= device.object_path,136 path=self._device.object_path, 121 137 dbus_interface=_NM_DEVICE_IFACE) 122 138 self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, 123 139 signal_name='PropertiesChanged', 124 path= device.object_path,140 path=self._device.object_path, 125 141 dbus_interface=_NM_WIRELESS_IFACE) 126 142 127 143 def _create_palette(self): … … class AccessPointView(CanvasPulsingIcon): 148 164 self._device_state = new_state 149 165 self._update_state() 150 166 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() 153 179 154 180 def __wireless_properties_changed_cb(self, properties): 155 181 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']) 159 183 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) 196 186 197 187 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) 199 189 200 190 def __get_device_caps_reply_cb(self, caps): 201 191 self._device_caps = caps 202 192 203 193 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) 205 195 206 196 def __get_device_state_reply_cb(self, state): 207 197 self._device_state = state 208 198 self._update() 209 199 210 200 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) 218 202 219 203 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 = None228 self._palette_icon.props.badge_name = None229 230 self._palette.props.primary_text = self._name231 232 204 self._update_state() 233 205 self._update_color() 234 206 235 207 def _update_state(self): 236 if self._active :208 if self._active_ap is not None: 237 209 state = self._device_state 238 210 else: 239 211 state = network.DEVICE_STATE_UNKNOWN … … class AccessPointView(CanvasPulsingIcon): 283 255 else: 284 256 self.props.base_color = self._color 285 257 286 self._palette_icon.props.xo_color = self._color287 288 258 def _disconnect_activate_cb(self, item): 289 259 pass 290 260 291 292 261 def _add_ciphers_from_flags(self, flags, pairwise): 293 262 ciphers = [] 294 263 if pairwise: … … class AccessPointView(CanvasPulsingIcon): 386 355 obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) 387 356 netmgr = dbus.Interface(obj, _NM_IFACE) 388 357 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 389 367 netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, 390 368 self._device.object_path, 391 self._model.object_path,369 best.model.object_path, 392 370 reply_handler=self.__activate_reply_cb, 393 371 error_handler=self.__activate_error_cb) 394 372 … … class AccessPointView(CanvasPulsingIcon): 396 374 logging.debug('Connection activated: %s', connection) 397 375 398 376 def __activate_error_cb(self, err): 399 logging. debug('Failed to activate connection: %s', err)377 logging.error('Failed to activate connection: %s', err) 400 378 401 379 def set_filter(self, query): 402 380 self._greyed_out = self._name.lower().find(query) == -1 … … class AccessPointView(CanvasPulsingIcon): 407 385 keydialog.create(self._name, self._flags, self._wpa_flags, 408 386 self._rsn_flags, self._device_caps, response) 409 387 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() 415 401 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): 416 424 self._bus.remove_signal_receiver(self.__device_state_changed_cb, 417 425 signal_name='StateChanged', 418 426 path=self._device.object_path, … … class AccessPointView(CanvasPulsingIcon): 422 430 path=self._device.object_path, 423 431 dbus_interface=_NM_WIRELESS_IFACE) 424 432 433 425 434 class ActivityView(hippo.CanvasBox): 426 435 def __init__(self, model): 427 436 hippo.CanvasBox.__init__(self) … … class ActivityView(hippo.CanvasBox): 538 547 539 548 _AUTOSEARCH_TIMEOUT = 1000 540 549 550 541 551 class MeshToolbar(gtk.Toolbar): 542 552 __gtype_name__ = 'MeshToolbar' 543 553 … … class MeshToolbar(gtk.Toolbar): 606 616 self.search_entry.activate() 607 617 return False 608 618 619 609 620 class DeviceObserver(object): 610 621 def __init__(self, box, device): 611 622 self._box = box … … class DeviceObserver(object): 650 661 path=self._device.object_path, 651 662 dbus_interface=_NM_WIRELESS_IFACE) 652 663 664 653 665 class NetworkManagerObserver(object): 654 666 def __init__(self, box): 655 667 self._box = box … … class NetworkManagerObserver(object): 693 705 state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State') 694 706 if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING: 695 707 ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') 708 found = False 696 709 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: 700 715 logging.error('Could not determine AP for' 701 716 ' specific object %s' % conn_o) 702 717 … … class NetworkManagerObserver(object): 727 742 observer.disconnect() 728 743 del self._devices[device_o] 729 744 745 730 746 class MeshBox(gtk.VBox): 731 747 __gtype_name__ = 'SugarMeshBox' 732 748 … … class MeshBox(gtk.VBox): 735 751 736 752 gobject.GObject.__init__(self) 737 753 738 self. access_points = {}754 self.wireless_networks = {} 739 755 740 756 self._model = neighborhood.get_model() 741 757 self._buddies = {} … … class MeshBox(gtk.VBox): 863 879 del self._activities[activity_model.get_id()] 864 880 icon.destroy() 865 881 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) 869 895 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] 872 902 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() 874 924 875 925 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) 883 939 884 940 def suspend(self): 885 941 if not self._suspended: 886 942 self._suspended = True 887 for ap in self.access_points.values():888 ap.props.paused = True943 for net in self.wireless_networks.values(): 944 net.props.paused = True 889 945 890 946 def resume(self): 891 947 if self._suspended: 892 948 self._suspended = False 893 for ap in self.access_points.values():894 ap.props.paused = False949 for net in self.wireless_networks.values(): 950 net.props.paused = False 895 951 896 952 def _toolbar_query_changed_cb(self, toolbar, query): 897 953 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 20 20 import time 21 21 22 22 import dbus 23 import gobject 23 24 import ConfigParser 24 25 25 26 from sugar import dispatch … … NM_SETTINGS_PATH = '/org/freedesktop/NetworkManagerSettings' 76 77 NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManagerSettings' 77 78 NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection' 78 79 NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets' 80 NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' 79 81 80 82 _nm_settings = None 81 83 _conn_counter = 0 … … class NMSettingsConnection(dbus.service.Object): 323 325 else: 324 326 reply(self._secrets.get_dict()) 325 327 328 329 class 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 326 431 def get_settings(): 327 432 global _nm_settings 328 433 if _nm_settings is None: