Ticket #9: frame_wireless_device.patch

File frame_wireless_device.patch, 14.4 KB (added by erikos, 15 years ago)

implements the frame device for wireless connections

  • extensions/deviceicon/Makefile.am

    diff --git a/extensions/deviceicon/Makefile.am b/extensions/deviceicon/Makefile.am
    index e400339..8a2e765 100644
    a b sugardir = $(pkgdatadir)/extensions/deviceicon 
    33sugar_PYTHON =          \
    44        __init__.py     \
    55        battery.py      \
     6        network.py      \
    67        speaker.py      \
    78        volume.py
  • new file extensions/deviceicon/network.py

    diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py
    new file mode 100644
    index 0000000..4fc79cb
    - +  
     1#
     2# Copyright (C) 2008 OLPC
     3#
     4# This program is free software; you can redistribute it and/or modify
     5# it under the terms of the GNU General Public License as published by
     6# the Free Software Foundation; either version 2 of the License, or
     7# (at your option) any later version.
     8#
     9# This program is distributed in the hope that it will be useful,
     10# but WITHOUT ANY WARRANTY; without even the implied warranty of
     11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12# GNU General Public License for more details.
     13#
     14# You should have received a copy of the GNU General Public License
     15# along with this program; if not, write to the Free Software
     16# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     17
     18from gettext import gettext as _
     19import logging
     20import sha
     21
     22import gtk
     23import dbus
     24
     25from sugar.graphics.icon import get_icon_state
     26from sugar.graphics import style
     27from sugar.graphics.palette import Palette
     28from sugar.graphics.toolbutton import ToolButton
     29from sugar.graphics import xocolor
     30
     31from jarabe.model import network
     32from jarabe.frame.frameinvoker import FrameWidgetInvoker
     33from jarabe.view.pulsingicon import PulsingIcon
     34
     35_ICON_NAME = 'network-wireless'
     36
     37IP_ADDRESS_TEXT_TEMPLATE = _("IP address: %s")
     38
     39_NM_SERVICE = 'org.freedesktop.NetworkManager'
     40_NM_IFACE = 'org.freedesktop.NetworkManager'
     41_NM_PATH = '/org/freedesktop/NetworkManager'
     42_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
     43_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
     44_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
     45_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
     46
     47_NM_DEVICE_STATE_UNKNOWN = 0
     48_NM_DEVICE_STATE_UNMANAGED = 1
     49_NM_DEVICE_STATE_UNAVAILABLE = 2
     50_NM_DEVICE_STATE_DISCONNECTED = 3
     51_NM_DEVICE_STATE_PREPARE = 4
     52_NM_DEVICE_STATE_CONFIG = 5
     53_NM_DEVICE_STATE_NEED_AUTH = 6
     54_NM_DEVICE_STATE_IP_CONFIG = 7
     55_NM_DEVICE_STATE_ACTIVATED = 8
     56_NM_DEVICE_STATE_FAILED = 9
     57
     58def freq_to_channel(freq):
     59    ftoc = { 2412: 1, 2417: 2, 2422: 3, 2427: 4,
     60             2432: 5, 2437: 6, 2442: 7, 2447: 8,
     61             2452: 9, 2457: 10, 2462: 11, 2467: 12,
     62             2472: 13}
     63    return ftoc[freq]
     64
     65class WirelessPalette(Palette):
     66    def __init__(self, primary_text, device_view):
     67        Palette.__init__(self, label=primary_text)
     68
     69        self._device_view = device_view
     70        self._disconnect_item = None
     71
     72        self._chan_label = gtk.Label()
     73        self._chan_label.props.xalign = 0.0
     74        self._chan_label.show()
     75
     76        self._ip_address_label = gtk.Label()
     77
     78        self._info = gtk.VBox()
     79
     80        def _padded(child, xalign=0, yalign=0.5):
     81            padder = gtk.Alignment(xalign=xalign, yalign=yalign,
     82                                   xscale=1, yscale=0.33)
     83            padder.set_padding(style.DEFAULT_SPACING,
     84                               style.DEFAULT_SPACING,
     85                               style.DEFAULT_SPACING,
     86                               style.DEFAULT_SPACING)
     87            padder.add(child)
     88            return padder
     89
     90        self._info.pack_start(_padded(self._chan_label))
     91        self._info.pack_start(_padded(self._ip_address_label))
     92        self._info.show_all()
     93
     94        self._disconnect_item = gtk.MenuItem(_('Disconnect...'))
     95        self._disconnect_item.connect('activate', self._disconnect_activate_cb)
     96        self.menu.append(self._disconnect_item)
     97
     98        self.disconnected()
     99
     100    def connecting(self):
     101        self.props.secondary_text = _('Connecting...')
     102
     103    def connected(self, frequency, iaddress):
     104        self.set_content(self._info)
     105        self.props.secondary_text = _('Connected')
     106        self._set_channel(frequency)
     107        self._set_ip_address(iaddress)
     108        self._disconnect_item.show()
     109
     110    def disconnected(self):
     111        self.props.primary_text = ''
     112        self.props.secondary_text = _('Not connected')
     113        self.set_content(None)
     114        self._disconnect_item.hide()
     115
     116    def _disconnect_activate_cb(self, menuitem):
     117        self._device_view.deactivate_connection()
     118
     119    def _inet_ntoa(self, iaddress):
     120        address = ['%s' % ((iaddress >> i) % 256) for i in [0, 8, 16, 24]]
     121        return ".".join(address)
     122
     123    def _set_channel(self, freq):
     124        try:
     125            chan = freq_to_channel(freq)
     126        except KeyError:
     127            chan = 0
     128        self._chan_label.set_text("%s: %d" % (_("Channel"), chan))
     129
     130    def _set_ip_address(self, ip_address):
     131        if ip_address is not None:
     132            ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
     133                self._inet_ntoa(ip_address)
     134        else:
     135            ip_address_text = ""
     136        self._ip_address_label.set_text(ip_address_text)
     137
     138
     139class WirelessDeviceView(ToolButton):
     140
     141    FRAME_POSITION_RELATIVE = 300
     142
     143    def __init__(self, device):
     144        ToolButton.__init__(self)
     145
     146        self._bus = dbus.SystemBus()
     147        self._device = device
     148        self._flags = 0
     149        self._name = ''
     150        self._strength = 0
     151        self._frequency = 0
     152        self._device_state = None
     153        self._color = None
     154        self._active_ap_op = None
     155
     156        self._icon = PulsingIcon()
     157        self._icon.props.icon_name = get_icon_state(_ICON_NAME, 0)
     158        self._inactive_color = xocolor.XoColor( \
     159            "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
     160                       style.COLOR_TRANSPARENT.get_svg()))
     161        self._icon.props.pulse_color = self._inactive_color
     162        self._icon.props.base_color = self._inactive_color
     163
     164        self.set_icon_widget(self._icon)
     165        self._icon.show()
     166
     167        self._palette = WirelessPalette(self._name, self)
     168        self.set_palette(self._palette)
     169        self._palette.props.invoker = FrameWidgetInvoker(self)
     170        self._palette.set_group_id('frame')
     171
     172        self._device.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
     173                         reply_handler=self.__get_active_ap_reply_cb,
     174                         error_handler=self.__get_active_ap_error_cb)
     175
     176        self._bus.add_signal_receiver(self.__state_changed_cb,
     177                                      signal_name='StateChanged',
     178                                      path=self._device.object_path,
     179                                      dbus_interface=_NM_DEVICE_IFACE)
     180
     181    def disconnect(self):
     182        self._bus.remove_signal_receiver(self.__state_changed_cb,
     183                                         signal_name='StateChanged',
     184                                         path=self._device.object_path,
     185                                         dbus_interface=_NM_DEVICE_IFACE)
     186
     187    def __get_active_ap_reply_cb(self, active_ap_op):
     188        if self._active_ap_op != active_ap_op:
     189            if self._active_ap_op is not None:
     190                self._bus.remove_signal_receiver(
     191                    self.__ap_properties_changed_cb,
     192                    signal_name='PropertiesChanged',
     193                    path=self._active_ap_op,
     194                    dbus_interface=_NM_ACCESSPOINT_IFACE)
     195            if active_ap_op == '/':
     196                self._active_ap_op = None
     197                return
     198            self._active_ap_op = active_ap_op
     199            active_ap = self._bus.get_object(_NM_SERVICE, active_ap_op)
     200            props = dbus.Interface(active_ap, 'org.freedesktop.DBus.Properties')
     201
     202            props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
     203                         reply_handler=self.__get_all_props_reply_cb,
     204                         error_handler=self.__get_all_props_error_cb)
     205
     206            self._bus.add_signal_receiver(self.__ap_properties_changed_cb,
     207                                          signal_name='PropertiesChanged',
     208                                          path=self._active_ap_op,
     209                                          dbus_interface=_NM_ACCESSPOINT_IFACE)
     210
     211    def __get_active_ap_error_cb(self, err):
     212        logging.debug('Error getting the active access point: %s', err)
     213
     214    def __state_changed_cb(self, new_state, old_state, reason):
     215        self._device_state = new_state
     216        self._update_state()
     217
     218        self._device.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
     219                         reply_handler=self.__get_active_ap_reply_cb,
     220                         error_handler=self.__get_active_ap_error_cb)
     221
     222    def __ap_properties_changed_cb(self, properties):
     223        self._update_properties(properties)
     224
     225    def _update_properties(self, properties):
     226        if 'Ssid' in properties:
     227            self._name = properties['Ssid']
     228        if 'Strength' in properties:
     229            self._strength = properties['Strength']
     230        if 'Flags' in properties:
     231            self._flags = properties['Flags']
     232        if 'Frequency' in properties:
     233            self._frequency = properties['Frequency']
     234
     235        sh = sha.new()
     236        data = self._name + hex(self._flags)
     237        sh.update(data)
     238        h = hash(sh.digest())
     239        idx = h % len(xocolor.colors)
     240
     241        self._color = xocolor.XoColor('%s,%s' % (xocolor.colors[idx][0],
     242                                                 xocolor.colors[idx][1]))
     243        self._update()
     244
     245    def __get_all_props_reply_cb(self, properties):
     246        self._update_properties(properties)
     247
     248    def __get_all_props_error_cb(self, err):
     249        logging.debug('Error getting the access point properties: %s', err)
     250
     251    def _update(self):
     252        if self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
     253            self._icon.props.badge_name = "emblem-locked"
     254        else:
     255            self._icon.props.badge_name = None
     256
     257        self._palette.props.primary_text = self._name
     258
     259        self._update_state()
     260        self._update_color()
     261
     262    def _update_state(self):
     263        if self._active_ap_op is not None:
     264            state = self._device_state
     265        else:
     266            state = network.DEVICE_STATE_UNKNOWN
     267
     268        if state == network.DEVICE_STATE_ACTIVATED:
     269            icon_name = '%s-connected' % _ICON_NAME
     270        else:
     271            icon_name = _ICON_NAME
     272
     273        icon_name = get_icon_state(icon_name, self._strength)
     274        if icon_name:
     275            self._icon.props.icon_name = icon_name
     276
     277        if state == network.DEVICE_STATE_PREPARE or \
     278           state == network.DEVICE_STATE_CONFIG or \
     279           state == network.DEVICE_STATE_NEED_AUTH or \
     280           state == network.DEVICE_STATE_IP_CONFIG:
     281            self._palette.connecting()
     282            self._icon.props.pulsing = True
     283        elif state == network.DEVICE_STATE_ACTIVATED:
     284            props = dbus.Interface(self._device,
     285                                   'org.freedesktop.DBus.Properties')
     286            address = props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
     287            self._palette.connected(self._frequency, address)
     288            self._icon.props.pulsing = False
     289        else:
     290            self._palette.disconnected()
     291            self._icon.props.pulsing = False
     292            self._icon.props.base_color = self._inactive_color
     293            self._icon.props.badge_name = None
     294            self._name = ''
     295
     296    def _update_color(self):
     297        self._icon.props.base_color = self._color
     298
     299    def deactivate_connection(self):
     300        if self._active_ap_op is not None:
     301            obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
     302            netmgr = dbus.Interface(obj, _NM_IFACE)
     303            netmgr_props = dbus.Interface(
     304                netmgr, 'org.freedesktop.DBus.Properties')
     305            active_connections_o = netmgr_props.Get(_NM_IFACE,
     306                                                    'ActiveConnections')
     307
     308            for conn_o in active_connections_o:
     309                obj = self._bus.get_object(_NM_IFACE, conn_o)
     310                props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
     311                ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
     312                if ap_op == self._active_ap_op:
     313                    netmgr.DeactivateConnection(conn_o)
     314                    break
     315
     316class NetworkManagerObserver(object):
     317    def __init__(self, tray):
     318        self._bus = dbus.SystemBus()
     319        self._devices = {}
     320        self._netmgr = None
     321        self._tray = tray
     322
     323        try:
     324            obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
     325            self._netmgr = dbus.Interface(obj, _NM_IFACE)
     326        except dbus.DBusException:
     327            logging.debug('%s service not available', _NM_SERVICE)
     328            return
     329
     330        self._netmgr.GetDevices(reply_handler=self.__get_devices_reply_cb,
     331                                error_handler=self.__get_devices_error_cb)
     332
     333        self._bus.add_signal_receiver(self.__device_added_cb,
     334                                      signal_name='DeviceAdded',
     335                                      dbus_interface=_NM_IFACE)
     336        self._bus.add_signal_receiver(self.__device_removed_cb,
     337                                      signal_name='DeviceRemoved',
     338                                      dbus_interface=_NM_IFACE)
     339
     340    def __get_devices_reply_cb(self, devices_o):
     341        for dev_o in devices_o:
     342            self._check_device(dev_o)
     343
     344    def __get_devices_error_cb(self, err):
     345        logging.error('Failed to get devices: %s', err)
     346
     347    def _check_device(self, device_o):
     348        nm_device = self._bus.get_object(_NM_SERVICE, device_o)
     349        props = dbus.Interface(nm_device, 'org.freedesktop.DBus.Properties')
     350
     351        device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
     352        if device_type == network.DEVICE_TYPE_802_11_WIRELESS:
     353            device = WirelessDeviceView(nm_device)
     354            self._devices[device_o] = device
     355            self._tray.add_device(device)
     356
     357    def __device_added_cb(self, device_o):
     358        self._check_device(device_o)
     359
     360    def __device_removed_cb(self, device_o):
     361        if device_o in self._devices:
     362            device = self._devices[device_o]
     363            device.disconnect()
     364            self._tray.remove_device(device)
     365            del self._devices[device_o]
     366
     367def setup(tray):
     368    device_observer = NetworkManagerObserver(tray)