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) |
---|
-
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' 60 60 61 61 _ICON_NAME = 'network-wireless' 62 62 63 class AccessPointView(CanvasPulsingIcon): 63 64 class AccessPoint(gobject.GObject): 65 __gsignals__ = { 66 'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 67 ([gobject.TYPE_PYOBJECT])) 68 } 69 64 70 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 166 class WirelessNetworkView(CanvasPulsingIcon): 167 def __init__(self, initial_ap): 65 168 CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, 66 169 cache=True) 67 170 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 70 174 self._palette_icon = None 71 175 self._disconnect_item = None 72 176 self._connect_item = None 73 177 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_UNKNOWN178 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 80 184 self._device_caps = 0 81 185 self._device_state = None 82 186 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])) 85 202 86 203 self.connect('button-release-event', self.__button_release_event_cb) 87 204 … … class AccessPointView(CanvasPulsingIcon): 91 208 92 209 self._palette = self._create_palette() 93 210 self.set_palette(self._palette) 211 self._palette_icon.props.xo_color = self._color 94 212 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 105 222 106 223 interface_props = dbus.Interface(self._device, 107 224 'org.freedesktop.DBus.Properties') … … class AccessPointView(CanvasPulsingIcon): 117 234 118 235 self._bus.add_signal_receiver(self.__device_state_changed_cb, 119 236 signal_name='StateChanged', 120 path= device.object_path,237 path=self._device.object_path, 121 238 dbus_interface=_NM_DEVICE_IFACE) 122 239 self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, 123 240 signal_name='PropertiesChanged', 124 path= device.object_path,241 path=self._device.object_path, 125 242 dbus_interface=_NM_WIRELESS_IFACE) 126 243 127 244 def _create_palette(self): … … class AccessPointView(CanvasPulsingIcon): 148 265 self._device_state = new_state 149 266 self._update_state() 150 267 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() 153 280 154 281 def __wireless_properties_changed_cb(self, properties): 155 282 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']) 175 284 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) 196 287 197 288 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) 199 290 200 291 def __get_device_caps_reply_cb(self, caps): 201 292 self._device_caps = caps 202 293 203 294 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) 205 296 206 297 def __get_device_state_reply_cb(self, state): 207 298 self._device_state = state 208 299 self._update() 209 300 210 301 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) 218 303 219 304 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 305 self._update_state() 233 306 self._update_color() 234 307 235 308 def _update_state(self): 236 if self._active :309 if self._active_ap is not None: 237 310 state = self._device_state 238 311 else: 239 312 state = network.DEVICE_STATE_UNKNOWN … … class AccessPointView(CanvasPulsingIcon): 283 356 else: 284 357 self.props.base_color = self._color 285 358 286 self._palette_icon.props.xo_color = self._color287 288 359 def _disconnect_activate_cb(self, item): 289 360 pass 290 361 291 292 362 def _add_ciphers_from_flags(self, flags, pairwise): 293 363 ciphers = [] 294 364 if pairwise: … … class AccessPointView(CanvasPulsingIcon): 386 456 obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) 387 457 netmgr = dbus.Interface(obj, _NM_IFACE) 388 458 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 389 468 netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, 390 469 self._device.object_path, 391 self._model.object_path,470 best.model.object_path, 392 471 reply_handler=self.__activate_reply_cb, 393 472 error_handler=self.__activate_error_cb) 394 473 … … class AccessPointView(CanvasPulsingIcon): 396 475 logging.debug('Connection activated: %s', connection) 397 476 398 477 def __activate_error_cb(self, err): 399 logging. debug('Failed to activate connection: %s', err)478 logging.error('Failed to activate connection: %s', err) 400 479 401 480 def set_filter(self, query): 402 481 self._greyed_out = self._name.lower().find(query) == -1 … … class AccessPointView(CanvasPulsingIcon): 407 486 keydialog.create(self._name, self._flags, self._wpa_flags, 408 487 self._rsn_flags, self._device_caps, response) 409 488 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) 415 518 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): 416 525 self._bus.remove_signal_receiver(self.__device_state_changed_cb, 417 526 signal_name='StateChanged', 418 527 path=self._device.object_path, … … class AccessPointView(CanvasPulsingIcon): 422 531 path=self._device.object_path, 423 532 dbus_interface=_NM_WIRELESS_IFACE) 424 533 534 425 535 class ActivityView(hippo.CanvasBox): 426 536 def __init__(self, model): 427 537 hippo.CanvasBox.__init__(self) … … class ActivityView(hippo.CanvasBox): 538 648 539 649 _AUTOSEARCH_TIMEOUT = 1000 540 650 651 541 652 class MeshToolbar(gtk.Toolbar): 542 653 __gtype_name__ = 'MeshToolbar' 543 654 … … class MeshToolbar(gtk.Toolbar): 606 717 self.search_entry.activate() 607 718 return False 608 719 720 609 721 class DeviceObserver(object): 610 722 def __init__(self, box, device): 611 723 self._box = box … … class DeviceObserver(object): 650 762 path=self._device.object_path, 651 763 dbus_interface=_NM_WIRELESS_IFACE) 652 764 765 653 766 class NetworkManagerObserver(object): 654 767 def __init__(self, box): 655 768 self._box = box … … class NetworkManagerObserver(object): 693 806 state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State') 694 807 if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING: 695 808 ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') 809 found = False 696 810 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: 700 816 logging.error('Could not determine AP for' 701 817 ' specific object %s' % conn_o) 702 818 … … class NetworkManagerObserver(object): 727 843 observer.disconnect() 728 844 del self._devices[device_o] 729 845 846 730 847 class MeshBox(gtk.VBox): 731 848 __gtype_name__ = 'SugarMeshBox' 732 849 … … class MeshBox(gtk.VBox): 735 852 736 853 gobject.GObject.__init__(self) 737 854 738 self. access_points = {}855 self.wireless_networks = {} 739 856 740 857 self._model = neighborhood.get_model() 741 858 self._buddies = {} … … class MeshBox(gtk.VBox): 863 980 del self._activities[activity_model.get_id()] 864 981 icon.destroy() 865 982 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) 869 996 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] 872 1003 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() 874 1025 875 1026 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) 883 1040 884 1041 def suspend(self): 885 1042 if not self._suspended: 886 1043 self._suspended = True 887 for ap in self.access_points.values():888 ap.props.paused = True1044 for net in self.wireless_networks.values(): 1045 net.props.paused = True 889 1046 890 1047 def resume(self): 891 1048 if self._suspended: 892 1049 self._suspended = False 893 for ap in self.access_points.values():894 ap.props.paused = False1050 for net in self.wireless_networks.values(): 1051 net.props.paused = False 895 1052 896 1053 def _toolbar_query_changed_cb(self, toolbar, query): 897 1054 self._query = query.lower()