Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/network.py
diff options
context:
space:
mode:
authorDaniel Drake <dsd@laptop.org>2011-02-02 16:27:46 (GMT)
committer Daniel Drake <dsd@laptop.org>2011-02-02 16:37:48 (GMT)
commit3bc80c776f7ef2578a4b9fd26028574a12982ea3 (patch)
tree890aa0ee4a2b07d9c9704883d11f0385e03a2e70 /network.py
parentca2bbd6d74342247de97cffa150b577246c63107 (diff)
UI rework
This is a rework of the UI that uses a more standard GTK+ principles than previously. There is still a small amount of black magic, kept away inside mediaview.py. The UI/model separation was also refined in places, and a lot of code was simplified. Overall the functionality remains identical, and I tried to keep the UI the same as before (but it is not pixel perfect). As this was quite big there may be some bugs to shake out.
Diffstat (limited to 'network.py')
-rw-r--r--network.py1076
1 files changed, 1076 insertions, 0 deletions
diff --git a/network.py b/network.py
new file mode 100644
index 0000000..bd4848a
--- /dev/null
+++ b/network.py
@@ -0,0 +1,1076 @@
+#
+# Copyright (C) 2008-2010 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# 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 logging
+import sha
+import socket
+import struct
+import re
+import datetime
+import time
+import gtk
+import gobject
+import gconf
+import dbus
+
+from sugar.graphics.icon import get_icon_state
+from sugar.graphics import style
+from sugar.graphics.palette import Palette
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics import xocolor
+from sugar.util import unique_id
+from sugar import profile
+
+from jarabe.model import network
+from jarabe.model.network import Settings
+from jarabe.model.network import IP4Config
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.view.pulsingicon import PulsingIcon
+
+IP_ADDRESS_TEXT_TEMPLATE = _("IP address: %s")
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
+_NM_WIRED_IFACE = 'org.freedesktop.NetworkManager.Device.Wired'
+_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
+_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh'
+_NM_SERIAL_IFACE = 'org.freedesktop.NetworkManager.Device.Serial'
+_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
+
+_GSM_STATE_NOT_READY = 0
+_GSM_STATE_DISCONNECTED = 1
+_GSM_STATE_CONNECTING = 2
+_GSM_STATE_CONNECTED = 3
+_GSM_STATE_NEED_AUTH = 4
+
+def frequency_to_channel(frequency):
+ ftoc = { 2412: 1, 2417: 2, 2422: 3, 2427: 4,
+ 2432: 5, 2437: 6, 2442: 7, 2447: 8,
+ 2452: 9, 2457: 10, 2462: 11, 2467: 12,
+ 2472: 13}
+ return ftoc[frequency]
+
+class WirelessPalette(Palette):
+ __gtype_name__ = 'SugarWirelessPalette'
+
+ __gsignals__ = {
+ 'deactivate-connection' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'create-connection' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, primary_text, can_create=True):
+ Palette.__init__(self, label=primary_text)
+
+ self._disconnect_item = None
+
+ self._channel_label = gtk.Label()
+ self._channel_label.props.xalign = 0.0
+ self._channel_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._channel_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self._disconnect_item = gtk.MenuItem(_('Disconnect...'))
+ self._disconnect_item.connect('activate', self.__disconnect_activate_cb)
+ self.menu.append(self._disconnect_item)
+
+ if can_create:
+ self._adhoc_item = gtk.MenuItem(_('Create new wireless network'))
+ self._adhoc_item.connect('activate', self.__adhoc_activate_cb)
+ self.menu.append(self._adhoc_item)
+ self._adhoc_item.show()
+
+ def set_connecting(self):
+ self.props.secondary_text = _('Connecting...')
+
+ def _set_connected(self, iaddress):
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+ self._set_ip_address(iaddress)
+ self._disconnect_item.show()
+
+ def set_connected_with_frequency(self, frequency, iaddress):
+ self._set_connected(iaddress)
+ self._set_frequency(frequency)
+
+ def set_connected_with_channel(self, channel, iaddress):
+ self._set_connected(iaddress)
+ self._set_channel(channel)
+
+ def set_disconnected(self):
+ self.props.primary_text = ''
+ self.props.secondary_text = ''
+ self._disconnect_item.hide()
+ self.set_content(None)
+
+ def __disconnect_activate_cb(self, menuitem):
+ self.emit('deactivate-connection')
+
+ def __adhoc_activate_cb(self, menuitem):
+ self.emit('create-connection')
+
+ def _set_frequency(self, frequency):
+ try:
+ channel = frequency_to_channel(frequency)
+ except KeyError:
+ channel = 0
+ self._set_channel(channel)
+
+ def _set_channel(self, channel):
+ self._channel_label.set_text("%s: %d" % (_("Channel"), channel))
+
+ def _set_ip_address(self, ip_address):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ ip_address_text = ""
+ self._ip_address_label.set_text(ip_address_text)
+
+
+class WiredPalette(Palette):
+ __gtype_name__ = 'SugarWiredPalette'
+
+ def __init__(self):
+ Palette.__init__(self, label=_('Wired Network'))
+
+ self._speed_label = gtk.Label()
+ self._speed_label.props.xalign = 0.0
+ self._speed_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._speed_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+
+ def set_connected(self, speed, iaddress):
+ self._speed_label.set_text('%s: %d Mb/s' % (_('Speed'), speed))
+ self._set_ip_address(iaddress)
+
+ def _inet_ntoa(self, iaddress):
+ address = ['%s' % ((iaddress >> i) % 256) for i in [0, 8, 16, 24]]
+ return ".".join(address)
+
+ def _set_ip_address(self, ip_address):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ ip_address_text = ""
+ self._ip_address_label.set_text(ip_address_text)
+
+class GsmPalette(Palette):
+ __gtype_name__ = 'SugarGsmPalette'
+
+ __gsignals__ = {
+ 'gsm-connect' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'gsm-disconnect' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self):
+ Palette.__init__(self, label=_('Wireless modem'))
+
+ self._current_state = None
+
+ self._toggle_state_item = gtk.MenuItem('')
+ self._toggle_state_item.connect('activate', self.__toggle_state_cb)
+ self.menu.append(self._toggle_state_item)
+ self._toggle_state_item.show()
+
+ self.set_gsm_state(_GSM_STATE_NOT_READY)
+
+ self.info_box = gtk.VBox()
+
+ self.data_label = gtk.Label()
+ self.data_label.props.xalign = 0.0
+ label_alignment = self._add_widget_with_padding(self.data_label)
+ self.info_box.pack_start(label_alignment)
+ self.data_label.show()
+ label_alignment.show()
+
+ self.connection_time_label = gtk.Label()
+ self.connection_time_label.props.xalign = 0.0
+ label_alignment = self._add_widget_with_padding( \
+ self.connection_time_label)
+ self.info_box.pack_start(label_alignment)
+ self.connection_time_label.show()
+ label_alignment.show()
+
+ self.info_box.show()
+ self.set_content(self.info_box)
+
+ def _add_widget_with_padding(self, child, xalign=0, yalign=0.5):
+ alignment = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ alignment.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ alignment.add(child)
+ return alignment
+
+ def set_gsm_state(self, state):
+ self._current_state = state
+ self._update_label_and_text()
+
+ def _update_label_and_text(self):
+ if self._current_state == _GSM_STATE_NOT_READY:
+ self._toggle_state_item.get_child().set_label('...')
+ self.props.secondary_text = _('Please wait...')
+
+ elif self._current_state == _GSM_STATE_DISCONNECTED:
+ self._toggle_state_item.get_child().set_label(_('Connect'))
+ self.props.secondary_text = _('Disconnected')
+
+ elif self._current_state == _GSM_STATE_CONNECTING:
+ self._toggle_state_item.get_child().set_label(_('Cancel'))
+ self.props.secondary_text = _('Connecting...')
+
+ elif self._current_state == _GSM_STATE_CONNECTED:
+ self._toggle_state_item.get_child().set_label(_('Disconnect'))
+ self.props.secondary_text = _('Connected')
+
+ elif self._current_state == _GSM_STATE_NEED_AUTH:
+ self._toggle_state_item.get_child().set_label(_('Sim requires Pin/Puk'))
+ self.props.secondary_text = _('Authentication Error')
+
+ else:
+ raise ValueError('Invalid GSM state while updating label and ' \
+ 'text, %s' % str(self._current_state))
+
+ def __toggle_state_cb(self, menuitem):
+ if self._current_state == _GSM_STATE_NOT_READY:
+ pass
+ elif self._current_state == _GSM_STATE_DISCONNECTED:
+ self.emit('gsm-connect')
+ elif self._current_state == _GSM_STATE_CONNECTING:
+ self.emit('gsm-disconnect')
+ elif self._current_state == _GSM_STATE_CONNECTED:
+ self.emit('gsm-disconnect')
+ elif self._current_state == _GSM_STATE_NEED_AUTH:
+ self.emit('gsm-disconnect')
+ else:
+ raise ValueError('Invalid GSM state while emitting signal, %s' % \
+ str(self._current_state))
+
+
+class WirelessDeviceView(ToolButton):
+
+ _ICON_NAME = 'network-wireless'
+ FRAME_POSITION_RELATIVE = 302
+
+ def __init__(self, device):
+ ToolButton.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_props = None
+ self._flags = 0
+ self._name = ''
+ self._mode = network.NM_802_11_MODE_UNKNOWN
+ self._strength = 0
+ self._frequency = 0
+ self._device_state = None
+ self._color = None
+ self._active_ap_op = None
+
+ self._icon = PulsingIcon()
+ self._icon.props.icon_name = get_icon_state(self._ICON_NAME, 0)
+ self._inactive_color = xocolor.XoColor( \
+ "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WirelessPalette(self._name)
+ self._palette.connect('deactivate-connection',
+ self.__deactivate_connection_cb)
+ self._palette.connect('create-connection',
+ self.__create_connection_cb)
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+
+ self._device_props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._device_state = properties['State']
+ self._update_state()
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __get_active_ap_reply_cb(self, active_ap_op):
+ if self._active_ap_op != active_ap_op:
+ if self._active_ap_op is not None:
+ self._bus.remove_signal_receiver(
+ self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+ if active_ap_op == '/':
+ self._active_ap_op = None
+ return
+ self._active_ap_op = active_ap_op
+ active_ap = self._bus.get_object(_NM_SERVICE, active_ap_op)
+ props = dbus.Interface(active_ap, dbus.PROPERTIES_IFACE)
+
+ props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
+ reply_handler=self.__get_all_ap_props_reply_cb,
+ error_handler=self.__get_all_ap_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+
+ def __get_active_ap_error_cb(self, err):
+ logging.error('Error getting the active access point: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update_state()
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ def __ap_properties_changed_cb(self, properties):
+ self._update_properties(properties)
+
+ def _name_encodes_colors(self):
+ """Match #XXXXXX,#YYYYYY at the end of the network name"""
+ return self._name[-7] == '#' and self._name[-8] == ',' \
+ and self._name[-15] == '#'
+
+ def _update_properties(self, properties):
+ if 'Mode' in properties:
+ self._mode = properties['Mode']
+ self._color = None
+ if 'Ssid' in properties:
+ self._name = properties['Ssid']
+ self._color = None
+ if 'Strength' in properties:
+ self._strength = properties['Strength']
+ if 'Flags' in properties:
+ self._flags = properties['Flags']
+ if 'Frequency' in properties:
+ self._frequency = properties['Frequency']
+
+ if self._color == None:
+ if self._mode == network.NM_802_11_MODE_ADHOC \
+ and self._name_encodes_colors():
+ encoded_color = self._name.split("#", 1)
+ if len(encoded_color) == 2:
+ self._color = xocolor.XoColor('#' + encoded_color[1])
+ else:
+ sh = sha.new()
+ data = self._name + hex(self._flags)
+ sh.update(data)
+ h = hash(sh.digest())
+ idx = h % len(xocolor.colors)
+
+ self._color = xocolor.XoColor('%s,%s' %
+ (xocolor.colors[idx][0],
+ xocolor.colors[idx][1]))
+ self._update()
+
+ def __get_all_ap_props_reply_cb(self, properties):
+ self._update_properties(properties)
+
+ def __get_all_ap_props_error_cb(self, err):
+ logging.error('Error getting the access point properties: %s', err)
+
+ def _update(self):
+ if self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
+ self._icon.props.badge_name = "emblem-locked"
+ else:
+ self._icon.props.badge_name = None
+
+ self._palette.props.primary_text = self._name
+
+ self._update_state()
+ self._update_color()
+
+ def _update_state(self):
+ if self._active_ap_op is not None:
+ state = self._device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ if state == network.DEVICE_STATE_ACTIVATED:
+ icon_name = '%s-connected' % self._ICON_NAME
+ else:
+ icon_name = self._ICON_NAME
+
+ icon_name = get_icon_state(icon_name, self._strength)
+ if icon_name:
+ self._icon.props.icon_name = icon_name
+
+ if state == network.DEVICE_STATE_PREPARE or \
+ state == network.DEVICE_STATE_CONFIG or \
+ state == network.DEVICE_STATE_NEED_AUTH or \
+ state == network.DEVICE_STATE_IP_CONFIG:
+ self._palette.set_connecting()
+ self._icon.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ self._palette.set_connected_with_frequency(self._frequency,
+ address)
+ self._icon.props.pulsing = False
+ else:
+ self._icon.props.badge_name = None
+ self._icon.props.pulsing = False
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+ self._palette.set_disconnected()
+
+ def _update_color(self):
+ self._icon.props.base_color = self._color
+
+ def __deactivate_connection_cb(self, palette, data=None):
+ connection = network.find_connection_by_ssid(self._name)
+ if connection:
+ if self._mode == network.NM_802_11_MODE_INFRA:
+ connection.set_disconnected()
+
+ if self._active_ap_op is not None:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE)
+ active_connections_o = netmgr_props.Get(_NM_IFACE,
+ 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
+ ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+ if ap_op == self._active_ap_op:
+ netmgr.DeactivateConnection(conn_o)
+ break
+
+ def __create_connection_cb(self, palette, data=None):
+ """Create an 802.11 IBSS network.
+
+ The user's color is encoded at the end of the network name. The network
+ name is truncated so that it does not exceed the 32 byte SSID limit.
+ """
+ client = gconf.client_get_default()
+ nick = client.get_string('/desktop/sugar/user/nick').decode('utf-8')
+ color = client.get_string('/desktop/sugar/user/color')
+ color_suffix = ' %s' % color
+
+ format = _('%s\'s network').encode('utf-8')
+ extra_length = (len(format) - len('%s')) + len(color_suffix)
+ name_limit = 32 - extra_length
+
+ # truncate the nick and use a regex to drop any partial characters
+ # at the end
+ nick = nick.encode('utf-8')[:name_limit]
+ pattern = "([\xf6-\xf7][\x80-\xbf]{0,2}|[\xe0-\xef][\x80-\xbf]{0,1}|[\xc0-\xdf])$"
+ nick = re.sub(pattern, '', nick)
+
+ connection_name = format % nick
+ connection_name += color_suffix
+
+ connection = network.find_connection_by_ssid(connection_name)
+ if connection is None:
+ settings = Settings()
+ settings.connection.id = 'Auto ' + connection_name
+ uuid = settings.connection.uuid = unique_id()
+ settings.connection.type = '802-11-wireless'
+ settings.wireless.ssid = dbus.ByteArray(connection_name)
+ settings.wireless.band = 'bg'
+ settings.wireless.mode = 'adhoc'
+ settings.ip4_config = IP4Config()
+ settings.ip4_config.method = 'link-local'
+
+ connection = network.add_connection(uuid, settings)
+
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+
+ netmgr.ActivateConnection(network.SETTINGS_SERVICE,
+ connection.path,
+ self._device.object_path,
+ '/',
+ reply_handler=self.__activate_reply_cb,
+ error_handler=self.__activate_error_cb)
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Network created: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.debug('Failed to create network: %s', err)
+
+class OlpcMeshDeviceView(ToolButton):
+ _ICON_NAME = 'network-mesh'
+ FRAME_POSITION_RELATIVE = 302
+
+ def __init__(self, device, state):
+ ToolButton.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_props = None
+ self._device_state = None
+ self._channel = 0
+
+ self._icon = PulsingIcon(icon_name=self._ICON_NAME)
+ self._inactive_color = xocolor.XoColor( \
+ "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulse_color = profile.get_color()
+ self._icon.props.base_color = self._inactive_color
+
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WirelessPalette(_("Mesh Network"), can_create=False)
+ self._palette.connect('deactivate-connection',
+ self.__deactivate_connection)
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+
+ self.update_state(state)
+
+ self._device_props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ self._device_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel',
+ reply_handler=self.__get_active_channel_reply_cb,
+ error_handler=self.__get_active_channel_error_cb)
+
+ self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=device.object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
+ def __get_active_channel_reply_cb(self, channel):
+ self._channel = channel
+ self._update_text()
+
+ def __get_active_channel_error_cb(self, err):
+ logging.error('Error getting the active channel: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update()
+
+ def __wireless_properties_changed_cb(self, properties):
+ if 'ActiveChannel' in properties:
+ self._channel = properties['ActiveChannel']
+ self._update_text()
+
+ def _update_text(self):
+ state = self._device_state
+ if state in (network.DEVICE_STATE_PREPARE, network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG,
+ network.DEVICE_STATE_ACTIVATED):
+ text = _("Mesh Network") + " " + str(self._channel)
+ else:
+ text = _("Mesh Network")
+ self._palette.props.primary_text = text
+
+ def _update(self):
+ state = self._device_state
+
+ if state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG]:
+ self._icon.props.base_color = self._inactive_color
+ self._icon.props.pulse_color = profile.get_color()
+ self._palette.set_connecting()
+ self._icon.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ self._palette.set_connected_with_channel(self._channel, address)
+ self._icon.props.base_color = profile.get_color()
+ self._icon.props.pulsing = False
+ self._update_text()
+
+ def update_state(self, state):
+ self._device_state = state
+ self._update()
+
+ def __deactivate_connection(self, palette, data=None):
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE,
+ 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ # The connection path for a mesh connection is the device itself.
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+
+ try:
+ obj = self._bus.get_object(_NM_IFACE, ap_op)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ netmgr.DeactivateConnection(conn_o)
+ break
+ except dbus.exceptions.DBusException:
+ pass
+
+class WiredDeviceView(TrayIcon):
+
+ _ICON_NAME = 'network-wired'
+ FRAME_POSITION_RELATIVE = 301
+
+ def __init__(self, speed, address):
+ client = gconf.client_get_default()
+ color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=self._ICON_NAME, xo_color=color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WiredPalette()
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+ self._palette.set_connected(speed, address)
+
+
+class GsmDeviceView(TrayIcon):
+
+ _ICON_NAME = 'network-gsm'
+ FRAME_POSITION_RELATIVE = 303
+
+ def __init__(self, device):
+ self._connection_time_handler = None
+ self._connection_timestamp = 0
+
+ client = gconf.client_get_default()
+ color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=self._ICON_NAME, xo_color=color)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = self._create_gsm_palette()
+ self.set_palette(self._palette)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.add_signal_receiver(self.__ppp_stats_changed_cb,
+ signal_name='PppStats',
+ path=self._device.object_path,
+ dbus_interface=_NM_SERIAL_IFACE)
+
+ def _create_gsm_palette(self):
+ palette = GsmPalette()
+
+ palette.set_group_id('frame')
+ palette.connect('gsm-connect', self.__gsm_connect_cb)
+ palette.connect('gsm-disconnect', self.__gsm_disconnect_cb)
+
+ props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties')
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__current_state_check_cb,
+ error_handler=self.__current_state_check_error_cb)
+
+ return palette
+
+ def __gsm_connect_cb(self, palette, data=None):
+ connection = network.find_gsm_connection()
+ if connection is not None:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr.ActivateConnection(network.SETTINGS_SERVICE,
+ connection.path,
+ self._device.object_path,
+ '/',
+ reply_handler=self.__connect_cb,
+ error_handler=self.__connect_error_cb)
+
+ def __connect_cb(self, active_connection):
+ logging.debug('Connected successfully to gsm device, %s',
+ active_connection)
+
+ def __connect_error_cb(self, error):
+ raise RuntimeError('Error when connecting to gsm device, %s' % error)
+
+ def __gsm_disconnect_cb(self, palette, data=None):
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ devices = props.Get(_NM_ACTIVE_CONN_IFACE, 'Devices')
+ if self._device.object_path in devices:
+ netmgr.DeactivateConnection(
+ conn_o,
+ reply_handler=self.__disconnect_cb,
+ error_handler=self.__disconnect_error_cb)
+ break
+
+ def __disconnect_cb(self):
+ logging.debug('Disconnected successfully gsm device')
+
+ def __disconnect_error_cb(self, error):
+ raise RuntimeError('Error when disconnecting gsm device, %s' % error)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ logging.debug('GSM State: %s to %s, reason %s', old_state, new_state, reason)
+ self._update_state(int(new_state))
+
+ def __current_state_check_cb(self, properties):
+ self._update_state(int(properties['State']))
+
+ def __current_state_check_error_cb(self, error):
+ raise RuntimeError('Error when checking gsm device state, %s' % error)
+
+ def _update_state(self, state):
+ gsm_state = None
+
+ if state == network.DEVICE_STATE_ACTIVATED:
+ gsm_state = _GSM_STATE_CONNECTED
+ connection = network.find_gsm_connection()
+ if connection is not None:
+ connection.set_connected()
+ self._connection_timestamp = time.time() - \
+ connection.get_settings().connection.timestamp
+ self._connection_time_handler = gobject.timeout_add( \
+ 1000, self.__connection_timecount_cb)
+ self._update_stats(0, 0)
+ self._update_connection_time()
+ self._palette.info_box.show()
+
+ if state == network.DEVICE_STATE_DISCONNECTED:
+ gsm_state = _GSM_STATE_DISCONNECTED
+ self._connection_timestamp = 0
+ if self._connection_time_handler is not None:
+ gobject.source_remove(self._connection_time_handler)
+ self._palette.info_box.hide()
+
+ elif state in [network.DEVICE_STATE_UNMANAGED,
+ network.DEVICE_STATE_UNAVAILABLE,
+ network.DEVICE_STATE_UNKNOWN]:
+ gsm_state = _GSM_STATE_NOT_READY
+
+ elif state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_IP_CONFIG]:
+ gsm_state = _GSM_STATE_CONNECTING
+
+ elif state in [network.DEVICE_STATE_NEED_AUTH]:
+ gsm_state = _GSM_STATE_NEED_AUTH
+
+ if self._palette is not None:
+ self._palette.set_gsm_state(gsm_state)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __ppp_stats_changed_cb(self, in_bytes, out_bytes):
+ self._update_stats(in_bytes, out_bytes)
+
+ def _update_stats(self, in_bytes, out_bytes):
+ in_kbytes = in_bytes / 1024
+ out_kbytes = out_bytes / 1024
+ text = _("Data sent %d kb / received %d kb") % (out_kbytes, in_kbytes)
+ self._palette.data_label.set_text(text)
+
+ def __connection_timecount_cb(self):
+ self._connection_timestamp = self._connection_timestamp + 1
+ self._update_connection_time()
+ return True
+
+ def _update_connection_time(self):
+ connection_time = datetime.datetime.fromtimestamp( \
+ self._connection_timestamp)
+ text = _("Connection time ") + connection_time.strftime('%H : %M : %S')
+ self._palette.connection_time_label.set_text(text)
+
+class WirelessDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+ self._device_view = WirelessDeviceView(self._device)
+ self._tray.add_device(self._device_view)
+
+ def disconnect(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+
+class MeshDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+
+ props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def _remove_device_view(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+
+
+ def disconnect(self):
+ if self._device_view is not None:
+ self._remove_device_view()
+
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._update_state(properties['State'])
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._update_state(new_state)
+
+ def _update_state(self, state):
+ if state >= network.DEVICE_STATE_PREPARE \
+ and state <= network.DEVICE_STATE_ACTIVATED:
+ if self._device_view is not None:
+ self._device_view.update_state(state)
+ return
+
+ self._device_view = OlpcMeshDeviceView(self._device, state)
+ self._tray.add_device(self._device_view)
+ else:
+ if self._device_view is not None:
+ self._remove_device_view()
+
+
+class WiredDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_state = None
+ self._device_view = None
+ self._tray = tray
+
+ props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._update_state(properties['State'])
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._update_state(new_state)
+
+ def _update_state(self, state):
+ if state == network.DEVICE_STATE_ACTIVATED:
+ props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ address = props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ speed = props.Get(_NM_WIRED_IFACE, 'Speed')
+ self._device_view = WiredDeviceView(speed, address)
+ self._tray.add_device(self._device_view)
+ else:
+ if self._device_view is not None:
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+class GsmDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+
+ self._device_view = GsmDeviceView(device)
+ self._tray.add_device(self._device_view)
+
+ def disconnect(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ self._device_view = None
+
+class NetworkManagerObserver(object):
+ def __init__(self, tray):
+ self._bus = dbus.SystemBus()
+ self._devices = {}
+ self._netmgr = None
+ self._tray = tray
+
+ try:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ self._netmgr = dbus.Interface(obj, _NM_IFACE)
+ except dbus.DBusException:
+ logging.error('%s service not available', _NM_SERVICE)
+ return
+
+ self._netmgr.GetDevices(reply_handler=self.__get_devices_reply_cb,
+ error_handler=self.__get_devices_error_cb)
+
+ self._bus.add_signal_receiver(self.__device_added_cb,
+ signal_name='DeviceAdded',
+ dbus_interface=_NM_IFACE)
+ self._bus.add_signal_receiver(self.__device_removed_cb,
+ signal_name='DeviceRemoved',
+ dbus_interface=_NM_IFACE)
+
+ def __get_devices_reply_cb(self, devices):
+ for device_op in devices:
+ self._check_device(device_op)
+
+ def __get_devices_error_cb(self, err):
+ logging.error('Failed to get devices: %s', err)
+
+ def _check_device(self, device_op):
+ nm_device = self._bus.get_object(_NM_SERVICE, device_op)
+ props = dbus.Interface(nm_device, dbus.PROPERTIES_IFACE)
+
+ device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if device_type == network.DEVICE_TYPE_802_3_ETHERNET:
+ device = WiredDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_802_11_WIRELESS:
+ device = WirelessDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ device = MeshDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_GSM_MODEM:
+ device = GsmDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+
+ def __device_added_cb(self, device_op):
+ self._check_device(device_op)
+
+ def __device_removed_cb(self, device_op):
+ if device_op in self._devices:
+ device = self._devices[device_op]
+ device.disconnect()
+ del self._devices[device_op]
+
+def setup(tray):
+ device_observer = NetworkManagerObserver(tray)