From 6b6470ebcb73b60bbcb844ff202bb75a6b4e37c3 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 09 Sep 2007 04:02:26 +0000 Subject: * #1260, #2664, #1542, #2985: Rework network UI bits to be more informative and increase granularity of mesh device control --- diff --git a/NEWS b/NEWS index e8ba447..f07afed 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +* #1260, #2664, #1542, #2985: Rework network UI bits to be more informative and + increase granularity of mesh device control * #2909: Make python activities more tolerant to missing metadata properties. (tomeu) * #2653: Add audio/wav and audio/x-wav as Audio objects. (tomeu) * Support moving of data files written to the datastore using standard Activity diff --git a/shell/hardware/nmclient.py b/shell/hardware/nmclient.py index 01f6cd9..042ee6f 100644 --- a/shell/hardware/nmclient.py +++ b/shell/hardware/nmclient.py @@ -207,6 +207,8 @@ class Device(gobject.GObject): gobject.TYPE_NONE, ([])), 'state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'activation-stage-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([])), 'network-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), @@ -332,6 +334,15 @@ class Device(gobject.GObject): ret.append(net.get_op()) return ret + def get_mesh_step(self): + if self._type != DEVICE_TYPE_802_11_MESH_OLPC: + raise RuntimeError("Only valid for mesh devices") + try: + step = self.dev.getMeshStep(timeout=3000) + except dbus.DBusException, e: + step = 0 + return step + def get_frequency(self): freq = 0.0 try: @@ -426,6 +437,9 @@ class Device(gobject.GObject): if state == self._state: return + if state == DEVICE_STATE_INACTIVE: + self._act_stage = 0 + self._state = state if self._valid: self.emit('state-changed') @@ -437,6 +451,16 @@ class Device(gobject.GObject): self.dev.getActiveNetwork(reply_handler=lambda *args: self._get_active_net_cb(state, *args), error_handler=self._get_active_net_error_cb) + def set_activation_stage(self, stage): + if stage == self._act_stage: + return + self._act_stage = stage + if self._valid: + self.emit('activation-stage-changed') + + def get_activation_stage(self): + return self._act_stage + def get_ssid(self): if self._active_network and self._active_network.is_valid(): return self._active_network.get_ssid() @@ -586,26 +610,32 @@ class NMClient(gobject.GObject): except dbus.DBusException: pass - def set_active_device(self, device, network=None): + def set_active_device(self, device, network=None, mesh_freq=None, mesh_start=None): ssid = "" if network: ssid = network.get_ssid() - try: - # NM 0.6.4 and earlier have a bug which returns an - # InvalidArguments error if no security information is passed - # for wireless networks - self._nm_obj.setActiveDevice(device.get_op(), ssid) - except dbus.DBusException, e: - if str(e).find("invalid arguments"): - pass + if device.get_type() == DEVICE_TYPE_802_11_MESH_OLPC: + if mesh_freq or mesh_start: + if mesh_freq and not mesh_start: + self._nm_obj.setActiveDevice(device.get_op(), dbus.Double(mesh_freq)) + elif mesh_start and not mesh_freq: + self._nm_obj.setActiveDevice(device.get_op(), dbus.Double(0.0), dbus.UInt32(mesh_start)) + else: + self._nm_obj.setActiveDevice(device.get_op(), dbus.Double(mesh_freq), dbus.UInt32(mesh_start)) else: - raise dbus.DBusException(e) + self._nm_obj.setActiveDevice(device.get_op()) + else: + self._nm_obj.setActiveDevice(device.get_op(), ssid) def state_changed_sig_handler(self, new_state): logging.debug('NM State Changed to %d' % new_state) def device_activation_stage_sig_handler(self, device, stage): logging.debug('Device Activation Stage "%s" for device %s' % (NM_DEVICE_STAGE_STRINGS[stage], device)) + if not self._devices.has_key(device): + logging.debug('DeviceActivationStage, device %s does not exist' % (device)) + return + self._devices[device].set_activation_stage(stage) def device_activating_sig_handler(self, device): logging.debug('DeviceActivating for %s' % (device)) diff --git a/shell/model/devices/network/mesh.py b/shell/model/devices/network/mesh.py index 33d44bd..0152d8a 100644 --- a/shell/model/devices/network/mesh.py +++ b/shell/model/devices/network/mesh.py @@ -25,7 +25,10 @@ class Device(device.Device): 'strength' : (int, None, None, 0, 100, 0, gobject.PARAM_READABLE), 'state' : (int, None, None, device.STATE_ACTIVATING, - device.STATE_INACTIVE, 0, gobject.PARAM_READABLE) + device.STATE_INACTIVE, 0, gobject.PARAM_READABLE), + 'activation-stage': (int, None, None, 0, 7, 0, gobject.PARAM_READABLE), + 'frequency': (float, None, None, 0, 2.72, 0, gobject.PARAM_READABLE), + 'mesh-step': (int, None, None, 0, 4, 0, gobject.PARAM_READABLE), } def __init__(self, nm_device): @@ -36,6 +39,8 @@ class Device(device.Device): self._strength_changed_cb) self._nm_device.connect('state-changed', self._state_changed_cb) + self._nm_device.connect('activation-stage-changed', + self._activation_stage_changed_cb) def _strength_changed_cb(self, nm_device): self.notify('strength') @@ -43,15 +48,28 @@ class Device(device.Device): def _state_changed_cb(self, nm_device): self.notify('state') + def _activation_stage_changed_cb(self, nm_device): + self.notify('activation-stage') + def do_get_property(self, pspec): if pspec.name == 'strength': return self._nm_device.get_strength() elif pspec.name == 'state': nm_state = self._nm_device.get_state() return device._nm_state_to_state[nm_state] + elif pspec.name == 'activation-stage': + return self._nm_device.get_activation_stage() + elif pspec.name == 'frequency': + return self._nm_device.get_frequency() + elif pspec.name == 'mesh-step': + return self._nm_device.get_mesh_step() def get_type(self): return 'network.mesh' def get_id(self): return str(self._nm_device.get_op()) + + def get_nm_device(self): + return self._nm_device + diff --git a/shell/view/devices/network/mesh.py b/shell/view/devices/network/mesh.py index 0ce4e71..da6a068 100644 --- a/shell/view/devices/network/mesh.py +++ b/shell/view/devices/network/mesh.py @@ -15,18 +15,28 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +from gettext import gettext as _ + +import gtk + from sugar import profile from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from model.devices import device +from sugar.graphics.palette import Palette +from model.devices.network import wireless + class DeviceView(CanvasIcon): def __init__(self, model): CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, icon_name='network-mesh') self._model = model + self._palette = MeshPalette(_("Mesh Network"), model) + self.set_palette(self._palette) model.connect('notify::state', self._state_changed_cb) + model.connect('notify::activation-stage', self._state_changed_cb) self._update_state() def _state_changed_cb(self, model, pspec): @@ -35,6 +45,8 @@ class DeviceView(CanvasIcon): def _update_state(self): # FIXME Change icon colors once we have real icons state = self._model.props.state + self._palette.update_state(state) + if state == device.STATE_ACTIVATING: self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() @@ -43,3 +55,69 @@ class DeviceView(CanvasIcon): elif state == device.STATE_INACTIVE: self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() + + if state == device.STATE_INACTIVE: + self._palette.set_primary_text(_("Mesh Network")) + else: + chan = wireless.freq_to_channel(self._model.props.frequency) + if chan > 0: + self._palette.set_primary_text(_("Mesh Network") + " %d" % chan) + self._palette.set_mesh_step(self._model.props.mesh_step, state) + +class MeshPalette(Palette): + def __init__(self, primary_text, model): + Palette.__init__(self, primary_text, menu_after_content=True) + self._model = model + + self._step_label = gtk.Label() + self._step_label.show() + + vbox = gtk.VBox() + vbox.pack_start(self._step_label) + vbox.show() + + self.set_content(vbox) + + self._disconnect_item = gtk.MenuItem(_('Disconnect...')) + self._disconnect_item.connect('activate', self._disconnect_activate_cb) + self.menu.append(self._disconnect_item) + + def update_state(self, state): + if state == device.STATE_ACTIVATED: + self._disconnect_item.show() + else: + self._disconnect_item.hide() + + def _disconnect_activate_cb(self, menuitem): + # Disconnection for an mesh means activating the default mesh device + # again without a channel + network_manager = hardwaremanager.get_network_manager() + nm_device = self._model.get_nm_device() + if network_manager and nm_device: + network_manager.set_active_device(nm_device) + + def set_mesh_step(self, step, state): + label = "" + if step == 1: + if state == device.STATE_ACTIVATED: + label = _("Connected to a School Mesh Portal") + elif state == device.STATE_ACTIVATING: + label = _("Looking for a School Mesh Portal...") + elif step == 3: + if state == device.STATE_ACTIVATED: + label = _("Connected to an XO Mesh Portal") + elif state == device.STATE_ACTIVATING: + label = _("Looking for an XO Mesh Portal...") + elif step == 4: + if state == device.STATE_ACTIVATED: + label = _("Connected to a Simple Mesh") + elif state == device.STATE_ACTIVATING: + label = _("Starting a Simple Mesh") + + if len(label): + self._step_label.set_text(label) + else: + import logging + logging.debug("Unhandled mesh step %d" % step) + self._step_label.set_text(_("Unknown Mesh")) + diff --git a/shell/view/devices/network/wireless.py b/shell/view/devices/network/wireless.py index 23aa149..f4f8869 100644 --- a/shell/view/devices/network/wireless.py +++ b/shell/view/devices/network/wireless.py @@ -27,13 +27,24 @@ from sugar.graphics.palette import Palette from model.devices.network import wireless from model.devices import device +from hardware import hardwaremanager +from hardware import nmclient + _ICON_NAME = 'network-wireless' class DeviceView(CanvasIcon): def __init__(self, model): CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE) self._model = model - self._palette = WirelessPalette(self._get_palette_primary_text()) + + meshdev = None + network_manager = hardwaremanager.get_network_manager() + for device in network_manager.get_devices(): + if device.get_type() == nmclient.DEVICE_TYPE_802_11_MESH_OLPC: + meshdev = device + break + + self._palette = WirelessPalette(self._get_palette_primary_text(), meshdev) self.set_palette(self._palette) self._counter = 0 self._palette.set_frequency(self._model.props.frequency) @@ -87,8 +98,9 @@ class DeviceView(CanvasIcon): self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() class WirelessPalette(Palette): - def __init__(self, primary_text): - Palette.__init__(self, primary_text) + def __init__(self, primary_text, meshdev): + Palette.__init__(self, primary_text, menu_after_content=True) + self._meshdev = meshdev self._chan_label = gtk.Label() self._chan_label.show() @@ -97,16 +109,23 @@ class WirelessPalette(Palette): vbox.pack_start(self._chan_label) vbox.show() + if meshdev: + disconnect_item = gtk.MenuItem(_('Disconnect...')) + disconnect_item.connect('activate', self._disconnect_activate_cb) + self.menu.append(disconnect_item) + disconnect_item.show() + self.set_content(vbox) + def _disconnect_activate_cb(self, menuitem): + # Disconnection for an AP means activating the default mesh device + network_manager = hardwaremanager.get_network_manager() + if network_manager and self._meshdev: + network_manager.set_active_device(self._meshdev) + def set_frequency(self, freq): - chans = { 2.412: 1, 2.417: 2, 2.422: 3, 2.427: 4, - 2.432: 5, 2.437: 6, 2.442: 7, 2.447: 8, - 2.452: 9, 2.457: 10, 2.462: 11, 2.467: 12, - 2.472: 13 - } try: - chan = chans[freq] + chan = wireless.freq_to_channel(freq) except KeyError: chan = 0 self._chan_label.set_text("%s: %d" % (_("Channel"), chan)) diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py index 2209bde..117fcac 100644 --- a/shell/view/home/MeshBox.py +++ b/shell/view/home/MeshBox.py @@ -18,6 +18,7 @@ import random import hippo import gobject +import gtk from gettext import gettext as _ from sugar.graphics.spreadlayout import SpreadLayout @@ -26,10 +27,12 @@ from sugar.graphics import style from sugar.graphics import xocolor from sugar.graphics.icon import get_icon_state from sugar.graphics import style +from sugar.graphics import palette from sugar import profile from model import accesspointmodel from model.devices.network import mesh +from model.devices.network import wireless from hardware import hardwaremanager from hardware import nmclient from view.BuddyIcon import BuddyIcon @@ -42,9 +45,11 @@ from hardware.nmclient import NM_802_11_CAP_PROTO_WEP, NM_802_11_CAP_PROTO_WPA, _ICON_NAME = 'network-wireless' class AccessPointView(PulsingIcon): - def __init__(self, model): + def __init__(self, model, mesh_device=None): PulsingIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, cache=True) self._model = model + self._meshdev = mesh_device + self._disconnect_item = None self.connect('activated', self._activate_cb) @@ -56,6 +61,9 @@ class AccessPointView(PulsingIcon): self._device_stroke = stroke self._device_fill = fill + self._palette = self._create_palette() + self.set_palette(self._palette) + self._update_icon() self._update_name() self._update_state() @@ -67,6 +75,28 @@ class AccessPointView(PulsingIcon): elif (caps & NM_802_11_CAP_PROTO_WEP) or (caps & NM_802_11_CAP_PROTO_WPA) or (caps & NM_802_11_CAP_PROTO_WPA2): self.props.badge_name = "badge-locked" + def _create_palette(self): + p = palette.Palette(self._model.props.name, menu_after_content=True) + if not self._meshdev: + return p + + # Only show disconnect when there's a mesh device, because mesh takes + # priority over the normal wireless device. NM doesn't have a "disconnect" + # method for a device either (for various reasons) so this doesn't + # have a good mapping + self._disconnect_item = gtk.MenuItem(_('Disconnect...')) + self._disconnect_item.connect('activate', self._disconnect_activate_cb) + p.menu.append(self._disconnect_item) + if self._model.props.state == accesspointmodel.STATE_CONNECTED: + self._disconnect_item.show() + return p + + def _disconnect_activate_cb(self, menuitem): + # Disconnection for an AP means activating the default mesh device + network_manager = hardwaremanager.get_network_manager() + if network_manager and self._meshdev: + network_manager.set_active_device(self._meshdev) + def _strength_changed_cb(self, model, pspec): self._update_icon() @@ -84,10 +114,7 @@ class AccessPointView(PulsingIcon): network_manager.set_active_device(device, network) def _update_name(self): - if self.palette: - self.palette.set_primary_text(self._model.props.name) - else: - self.set_tooltip(self._model.props.name) + self._palette.set_primary_text(self._model.props.name) def _update_icon(self): icon_name = get_icon_state(_ICON_NAME, self._model.props.strength) @@ -96,6 +123,8 @@ class AccessPointView(PulsingIcon): def _update_state(self): if self._model.props.state == accesspointmodel.STATE_CONNECTING: + if self._disconnect_item: + self._disconnect_item.hide() self.props.pulse_time = 1.0 self.props.colors = [ [ style.Color(self._device_stroke).get_svg(), @@ -104,6 +133,8 @@ class AccessPointView(PulsingIcon): '#e2e2e2' ] ] elif self._model.props.state == accesspointmodel.STATE_CONNECTED: + if self._disconnect_item: + self._disconnect_item.show() self.props.pulse_time = 2.0 self.props.colors = [ [ style.Color(self._device_stroke).get_svg(), @@ -112,6 +143,8 @@ class AccessPointView(PulsingIcon): style.Color(self._device_fill).get_svg() ] ] elif self._model.props.state == accesspointmodel.STATE_NOTCONNECTED: + if self._disconnect_item: + self._disconnect_item.hide() self.props.pulse_time = 0.0 self.props.colors = [ [ style.Color(self._device_stroke).get_svg(), @@ -122,11 +155,20 @@ class AccessPointView(PulsingIcon): _MESH_ICON_NAME = 'network-mesh' class MeshDeviceView(PulsingIcon): - def __init__(self, nm_device): + def __init__(self, nm_device, channel): + if not channel in [1, 6, 11]: + raise ValueError("Invalid channel %d" % channel) + PulsingIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, icon_name=_MESH_ICON_NAME, cache=True) + self._nm_device = nm_device - self.set_tooltip(_("Mesh Network")) + self.channel = channel + self.props.badge_name = "badge-channel-%d" % self.channel + + self._disconnect_item = None + self._palette = self._create_palette() + self.set_palette(self._palette) mycolor = profile.get_color() self._device_fill = mycolor.get_fill_color() @@ -135,39 +177,66 @@ class MeshDeviceView(PulsingIcon): self.connect('activated', self._activate_cb) self._nm_device.connect('state-changed', self._state_changed_cb) + self._nm_device.connect('activation-stage-changed', self._state_changed_cb) self._update_state() + def _create_palette(self): + p = palette.Palette(_("Mesh Network") + " " + str(self.channel), menu_after_content=True) + + self._disconnect_item = gtk.MenuItem(_('Disconnect...')) + self._disconnect_item.connect('activate', self._disconnect_activate_cb) + p.menu.append(self._disconnect_item) + + state = self._nm_device.get_state() + chan = wireless.freq_to_channel(self._nm_device.get_frequency()) + if state == nmclient.DEVICE_STATE_ACTIVATED and chan == self.channel: + self._disconnect_item.show() + return p + + def _disconnect_activate_cb(self, menuitem): + network_manager = hardwaremanager.get_network_manager() + if network_manager: + network_manager.set_active_device(self._nm_device) + def _activate_cb(self, icon): network_manager = hardwaremanager.get_network_manager() - network_manager.set_active_device(self._nm_device) + if network_manager: + freq = wireless.channel_to_freq(self.channel) + network_manager.set_active_device(self._nm_device, mesh_freq=freq) def _state_changed_cb(self, model): self._update_state() def _update_state(self): state = self._nm_device.get_state() - if state == nmclient.DEVICE_STATE_ACTIVATING: + chan = wireless.freq_to_channel(self._nm_device.get_frequency()) + if state == nmclient.DEVICE_STATE_ACTIVATING and chan == self.channel: + self._disconnect_item.hide() self.props.pulse_time = 0.75 self.props.colors = [ [ style.Color(self._device_stroke).get_svg(), - style.Color(self._device_fill).get_svg() ], + style.Color(self._device_fill).get_svg() ], [ style.Color(self._device_stroke).get_svg(), - '#e2e2e2' ] + '#e2e2e2' ] ] - elif state == nmclient.DEVICE_STATE_ACTIVATED: + elif state == nmclient.DEVICE_STATE_ACTIVATED and chan == self.channel: + self._disconnect_item.show() self.props.pulse_time = 1.5 self.props.colors = [ [ style.Color(self._device_stroke).get_svg(), - style.Color(self._device_fill).get_svg() ], + style.Color(self._device_fill).get_svg() ], [ '#ffffff', - style.Color(self._device_fill).get_svg() ] + style.Color(self._device_fill).get_svg() ] ] - elif state == nmclient.DEVICE_STATE_INACTIVE: + elif state == nmclient.DEVICE_STATE_INACTIVE or chan != self.channel: + self._disconnect_item.hide() self.props.pulse_time = 0.0 self.props.colors = [ [ style.Color(self._device_stroke).get_svg(), style.Color(self._device_fill).get_svg() ] ] + else: + raise RuntimeError("Shouldn't get here") class ActivityView(hippo.CanvasBox): def __init__(self, shell, model): @@ -216,7 +285,7 @@ class MeshBox(hippo.CanvasBox): self._buddies = {} self._activities = {} self._access_points = {} - self._mesh = None + self._mesh = {} self._buddy_to_activity = {} self._suspended = True @@ -245,18 +314,22 @@ class MeshBox(hippo.CanvasBox): self._access_point_removed_cb) if self._model.get_mesh(): - self._add_mesh_icon(self._model.get_mesh()) + self._mesh_added_cb(self._model, self._model.get_mesh()) self._model.connect('mesh-added', self._mesh_added_cb) self._model.connect('mesh-removed', self._mesh_removed_cb) - def _mesh_added_cb(self, model, mesh): - self._add_mesh_icon(mesh) + def _mesh_added_cb(self, model, meshdev): + self._add_mesh_icon(meshdev, 1) + self._add_mesh_icon(meshdev, 6) + self._add_mesh_icon(meshdev, 11) def _mesh_removed_cb(self, model): - self._remove_mesh() + self._remove_mesh_icon(1) + self._remove_mesh_icon(6) + self._remove_mesh_icon(11) def _buddy_added_cb(self, model, buddy_model): self._add_alone_buddy(buddy_model) @@ -282,19 +355,19 @@ class MeshBox(hippo.CanvasBox): def _access_point_removed_cb(self, model, ap_model): self._remove_access_point(ap_model) - def _add_mesh_icon(self, mesh): - if self._mesh: - self._remove_mesh() - if not mesh: + def _add_mesh_icon(self, meshdev, channel): + if self._mesh.has_key(channel): + self._remove_mesh_icon(channel) + if not meshdev: return - self._mesh = MeshDeviceView(mesh) - self._layout.add(self._mesh) + self._mesh[channel] = MeshDeviceView(meshdev, channel) + self._layout.add(self._mesh[channel]) - def _remove_mesh(self): - if not self._mesh: + def _remove_mesh_icon(self, channel): + if not self._mesh.has_key(channel): return - self._layout.remove(self._mesh) - self._mesh = None + self._layout.remove(self._mesh[channel]) + del self._mesh[channel] def _add_alone_buddy(self, buddy_model): icon = BuddyIcon(self._shell, buddy_model) @@ -344,7 +417,8 @@ class MeshBox(hippo.CanvasBox): del self._activities[activity_model.get_id()] def _add_access_point(self, ap_model): - icon = AccessPointView(ap_model) + meshdev = self._model.get_mesh() + icon = AccessPointView(ap_model, meshdev) self._layout.add(icon) self._access_points[ap_model.get_id()] = icon -- cgit v0.9.1