From 755250947a5b49c7fc8353055f65db6c4b277cbd Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 25 Feb 2007 02:00:40 +0100 Subject: [PATCH] Some work on the network devices --- shell/Makefile.am | 2 +- shell/hardware/Makefile.am | 4 +- shell/hardware/hardwaremanager.py | 5 + shell/hardware/nmclient.py | 467 ++++++++++++++++++ shell/hardware/nminfo.py | 467 ++++++++++++++++++ shell/model/devices/Makefile.am | 3 +- shell/model/devices/battery.py | 2 +- shell/model/devices/devicesmodel.py | 22 +- .../devices/{network.py => wirednetwork.py} | 8 +- shell/model/devices/wirelessnetwork.py | 12 + shell/view/devices/Makefile.am | 3 +- shell/view/devices/battery.py | 2 +- .../devices/{network.py => wirednetwork.py} | 2 +- shell/view/devices/wirelessnetwork.py | 6 + 14 files changed, 990 insertions(+), 15 deletions(-) create mode 100644 shell/hardware/nmclient.py create mode 100644 shell/hardware/nminfo.py rename shell/model/devices/{network.py => wirednetwork.py} (56%) create mode 100644 shell/model/devices/wirelessnetwork.py rename shell/view/devices/{network.py => wirednetwork.py} (74%) create mode 100644 shell/view/devices/wirelessnetwork.py diff --git a/shell/Makefile.am b/shell/Makefile.am index 6d6f6336..e5a566f5 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = data model view +SUBDIRS = data hardware model view bin_SCRIPTS = \ sugar-activity \ diff --git a/shell/hardware/Makefile.am b/shell/hardware/Makefile.am index 86cc0c75..da992764 100644 --- a/shell/hardware/Makefile.am +++ b/shell/hardware/Makefile.am @@ -1,4 +1,6 @@ sugardir = $(pkgdatadir)/shell/hardware sugar_PYTHON = \ __init__.py \ - hardwaremanager.py + hardwaremanager.py \ + nmclient.py \ + nminfo.py diff --git a/shell/hardware/hardwaremanager.py b/shell/hardware/hardwaremanager.py index 39ae767f..007241bb 100644 --- a/shell/hardware/hardwaremanager.py +++ b/shell/hardware/hardwaremanager.py @@ -18,6 +18,7 @@ import logging import dbus +from hardware.nmclient import NMClient from _sugar import AudioManager _HARDWARE_MANAGER_INTERFACE = 'org.laptop.HardwareManager' @@ -65,5 +66,9 @@ def get_hardware_manager(): def get_audio_manager(): return _audio_manager +def get_network_manager(): + return _network_manager + _hardware_manager = HardwareManager() _audio_manager = AudioManager() +_network_manager = NMClient() diff --git a/shell/hardware/nmclient.py b/shell/hardware/nmclient.py new file mode 100644 index 00000000..9a2c5a80 --- /dev/null +++ b/shell/hardware/nmclient.py @@ -0,0 +1,467 @@ +# +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +import logging +import os + +import dbus +import dbus.glib +import dbus.decorators +import gobject + +from hardware import nminfo + +IW_AUTH_ALG_OPEN_SYSTEM = 0x00000001 +IW_AUTH_ALG_SHARED_KEY = 0x00000002 + +NM_DEVICE_STAGE_STRINGS=("Unknown", + "Prepare", + "Config", + "Need Users Key", + "IP Config", + "IP Config Get", + "IP Config Commit", + "Activated", + "Failed", + "Cancled" +) + +NM_SERVICE = 'org.freedesktop.NetworkManager' +NM_IFACE = 'org.freedesktop.NetworkManager' +NM_IFACE_DEVICES = 'org.freedesktop.NetworkManager.Devices' +NM_PATH = '/org/freedesktop/NetworkManager' + +DEVICE_TYPE_UNKNOWN = 0 +DEVICE_TYPE_802_3_ETHERNET = 1 +DEVICE_TYPE_802_11_WIRELESS = 2 + +NM_DEVICE_CAP_NONE = 0x00000000 +NM_DEVICE_CAP_NM_SUPPORTED = 0x00000001 +NM_DEVICE_CAP_CARRIER_DETECT = 0x00000002 +NM_DEVICE_CAP_WIRELESS_SCAN = 0x00000004 + +sys_bus = dbus.SystemBus() + +NM_802_11_CAP_NONE = 0x00000000 +NM_802_11_CAP_PROTO_NONE = 0x00000001 +NM_802_11_CAP_PROTO_WEP = 0x00000002 +NM_802_11_CAP_PROTO_WPA = 0x00000004 +NM_802_11_CAP_PROTO_WPA2 = 0x00000008 +NM_802_11_CAP_KEY_MGMT_PSK = 0x00000040 +NM_802_11_CAP_KEY_MGMT_802_1X = 0x00000080 +NM_802_11_CAP_CIPHER_WEP40 = 0x00001000 +NM_802_11_CAP_CIPHER_WEP104 = 0x00002000 +NM_802_11_CAP_CIPHER_TKIP = 0x00004000 +NM_802_11_CAP_CIPHER_CCMP = 0x00008000 + + +class Network(gobject.GObject): + __gsignals__ = { + 'init-failed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) + } + + def __init__(self, op): + gobject.GObject.__init__(self) + self._op = op + self._ssid = None + self._mode = None + self._strength = 0 + self._valid = False + + obj = sys_bus.get_object(NM_SERVICE, self._op) + net = dbus.Interface(obj, NM_IFACE_DEVICES) + net.getProperties(reply_handler=self._update_reply_cb, + error_handler=self._update_error_cb) + + def _update_reply_cb(self, *props): + self._ssid = props[1] + self._strength = props[3] + self._mode = props[6] + caps = props[7] + if caps & NM_802_11_CAP_PROTO_WPA or caps & NM_802_11_CAP_PROTO_WPA2: + # We do not support WPA at this time, so don't show + # WPA-enabled access points in the menu + logging.debug("Net(%s): ssid '%s' dropping because WPA[2] unsupported" % (self._op, + self._ssid)) + self._valid = False + self.emit('init-failed') + else: + self._valid = True + logging.debug("Net(%s): ssid '%s', mode %d, strength %d" % (self._op, + self._ssid, self._mode, self._strength)) + + def _update_error_cb(self, err): + logging.debug("Net(%s): failed to update. (%s)" % (self._op, err)) + self._valid = False + self.emit('init-failed') + + def get_ssid(self): + return self._ssid + + def get_op(self): + return self._op + + def get_strength(self): + return self._strength + + def set_strength(self, strength): + self._strength = strength + + def is_valid(self): + return self._valid + +class Device(gobject.GObject): + __gsignals__ = { + 'init-failed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'strength-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self, op): + gobject.GObject.__init__(self) + self._op = op + self._iface = None + self._type = DEVICE_TYPE_UNKNOWN + self._udi = None + self._active = False + self._strength = 0 + self._link = False + self._valid = False + self._networks = {} + self._active_net = None + self._caps = 0 + + obj = sys_bus.get_object(NM_SERVICE, self._op) + dev = dbus.Interface(obj, NM_IFACE_DEVICES) + dev.getProperties(reply_handler=self._update_reply_cb, + error_handler=self._update_error_cb) + + def _update_reply_cb(self, *props): + self._iface = props[1] + self._type = props[2] + self._udi = props[3] + self._active = props[4] + if self._active: + self.emit('activated') + self._link = props[15] + self._caps = props[17] + + if self._type == DEVICE_TYPE_802_11_WIRELESS: + old_strength = self._strength + self._strength = props[14] + if self._strength != old_strength: + self.emit('strength-changed', self._strength) + self._update_networks(props[20], props[19]) + + self._valid = True + + def _update_networks(self, net_ops, active_op): + for op in net_ops: + net = Network(op) + self._networks[op] = net + net.connect('init-failed', self._net_init_failed) + if op == active_op: + self._active_net = op + + def _update_error_cb(self, err): + logging.debug("Device(%s): failed to update. (%s)" % (self._op, err)) + self._valid = False + self.emit('init-failed') + + def _net_init_failed(self, net): + net_op = net.get_op() + if not self._networks.has_key(net_op): + return + if net_op == self._active_net: + self._active_net = None + del self._networks[net_op] + + def get_op(self): + return self._op + + def get_network(self, op): + if self._networks.has_key(op): + return self._networks[op] + return None + + def get_network_ops(self): + return self._networks.keys() + + def get_strength(self): + return self._strength + + def set_strength(self, strength): + if strength == self._strength: + return False + + if strength >= 0 and strength <= 100: + self._strength = strength + else: + self._strength = 0 + + self.emit('strength-changed', self._strength) + + def network_appeared(self, network): + if self._networks.has_key(network): + return + net = Network(network) + self._networks[network] = net + net.connect('init-failed', self._net_init_failed) + + def network_disappeared(self, network): + if not self._networks.has_key(network): + return + if network == self._active_net: + self._active_net = None + del self._networks[network] + + def get_active(self): + return self._active + + def set_active(self, active, ssid=None): + self._active = active + if self._type == DEVICE_TYPE_802_11_WIRELESS: + if not ssid: + self._active_net = None + else: + for (op, net) in self._networks.items(): + if net.get_ssid() == ssid: + self._active_net = op + + def get_type(self): + return self._type + + def is_valid(self): + return self._valid + + def set_carrier(self, on): + self._link = on + + def get_capabilities(self): + return self._caps + +NM_STATE_UNKNOWN = 0 +NM_STATE_ASLEEP = 1 +NM_STATE_CONNECTING = 2 +NM_STATE_CONNECTED = 3 +NM_STATE_DISCONNECTED = 4 + +class NMClient(object): + def __init__(self): + self.nminfo = None + self._nm_present = False + self._nm_state = NM_STATE_UNKNOWN + self._update_timer = 0 + self._active_device = None + self._devices = {} + + try: + self.nminfo = nminfo.NMInfo(self) + except RuntimeError: + pass + self._setup_dbus() + if self._nm_present: + self._get_nm_state() + self._get_initial_devices() + + def get_devices(self): + return self._devices + + def _get_nm_state(self): + # Grab NM's state + self._nm_obj.state(reply_handler=self._get_state_reply_cb, \ + error_handler=self._get_state_error_cb) + + def _get_state_reply_cb(self, state): + self._nm_state = state + + def _get_state_error_cb(self, err): + logging.debug("Failed to get NetworkManager state! %s" % err) + + def _get_initial_devices_reply_cb(self, ops): + for op in ops: + self._add_device(op) + + def _dev_init_failed_cb(self, dev): + # Device failed to initialize, likely due to dbus errors or something + op = dev.get_op() + self._remove_device(op) + + def _get_initial_devices_error_cb(self, err): + logging.debug("Error updating devices (%s)" % err) + + def _get_initial_devices(self): + self._nm_obj.getDevices(reply_handler=self._get_initial_devices_reply_cb, \ + error_handler=self._get_initial_devices_error_cb) + + def _add_device(self, dev_op): + if self._devices.has_key(dev_op): + return + dev = Device(dev_op) + self._devices[dev_op] = dev + dev.connect('init-failed', self._dev_init_failed_cb) + dev.connect('activated', self._dev_activated_cb) + dev.connect('strength-changed', self._dev_strength_changed_cb) + + def _remove_device(self, dev_op): + if not self._devices.has_key(dev_op): + return + if self._active_device == dev_op: + self._active_device = None + dev = self._devices[dev_op] + dev.disconnect('activated') + dev.disconnect('init-failed') + dev.disconnect('strength-changed') + del self._devices[dev_op] + + def _dev_activated_cb(self, dev): + op = dev.get_op() + if not self._devices.has_key(op): + return + if not dev.get_active(): + return + self._active_device = op + + def _dev_strength_changed_cb(self, dev, strength): + op = dev.get_op() + if not self._devices.has_key(op): + return + if not dev.get_active(): + return + + def get_device(self, dev_op): + if not self._devices.has_key(dev_op): + return None + return self._devices[dev_op] + + def _setup_dbus(self): + self._sig_handlers = { + 'StateChange': self.state_change_sig_handler, + 'DeviceAdded': self.device_added_sig_handler, + 'DeviceRemoved': self.device_removed_sig_handler, + 'DeviceActivationStage': self.device_activation_stage_sig_handler, + 'DeviceActivating': self.device_activating_sig_handler, + 'DeviceNowActive': self.device_now_active_sig_handler, + 'DeviceNoLongerActive': self.device_no_longer_active_sig_handler, + 'DeviceCarrierOn': self.device_carrier_on_sig_handler, + 'DeviceCarrierOff': self.device_carrier_off_sig_handler, + 'DeviceStrengthChanged': self.wireless_device_strength_changed_sig_handler, + 'WirelessNetworkAppeared': self.wireless_network_appeared_sig_handler, + 'WirelessNetworkDisappeared': self.wireless_network_disappeared_sig_handler, + 'WirelessNetworkStrengthChanged': self.wireless_network_strength_changed_sig_handler + } + + self._nm_proxy = sys_bus.get_object(NM_SERVICE, NM_PATH) + self._nm_obj = dbus.Interface(self._nm_proxy, NM_IFACE) + + sys_bus.add_signal_receiver(self.name_owner_changed_sig_handler, + signal_name="NameOwnerChanged", + dbus_interface="org.freedesktop.DBus") + + for (signal, handler) in self._sig_handlers.items(): + sys_bus.add_signal_receiver(handler, signal_name=signal, dbus_interface=NM_IFACE) + + # Find out whether or not NM is running + try: + bus_object = sys_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') + name = bus_object.GetNameOwner("org.freedesktop.NetworkManagerInfo", \ + dbus_interface='org.freedesktop.DBus') + if name: + self._nm_present = True + except dbus.DBusException: + pass + + def get_key_for_network(self, net, async_cb, async_err_cb): + pass + + def device_activation_stage_sig_handler(self, device, stage): + logging.debug('Device Activation Stage "%s" for device %s' % (NM_DEVICE_STAGE_STRINGS[stage], device)) + + def state_change_sig_handler(self, state): + self._nm_state = state + + def device_activating_sig_handler(self, device): + self._active_device = device + + def device_now_active_sig_handler(self, device, ssid=None): + if not self._devices.has_key(device): + return + self._active_device = device + self._devices[device].set_active(True, ssid) + + def device_no_longer_active_sig_handler(self, device): + if not self._devices.has_key(device): + return + if self._active_device == device: + self._active_device = None + self._devices[device].set_active(False) + + def name_owner_changed_sig_handler(self, name, old, new): + if name != NM_SERVICE: + return + if (old and len(old)) and (not new and not len(new)): + # NM went away + self._nm_present = False + for op in self._devices.keys(): + del self._devices[op] + self._devices = {} + self._active_device = None + self._nm_state = NM_STATE_UNKNOWN + elif (not old and not len(old)) and (new and len(new)): + # NM started up + self._nm_present = True + self._get_nm_state() + self._get_initial_devices() + + def device_added_sig_handler(self, device): + self._add_device(device) + + def device_removed_sig_handler(self, device): + self._remove_device(device) + + def wireless_network_appeared_sig_handler(self, device, network): + if not self._devices.has_key(device): + return + self._devices[device].network_appeared(network) + + def wireless_network_disappeared_sig_handler(self, device, network): + if not self._devices.has_key(device): + return + self._devices[device].network_disappeared(network) + + def wireless_device_strength_changed_sig_handler(self, device, strength): + if not self._devices.has_key(device): + return + self._devices[device].set_strength(strength) + + def wireless_network_strength_changed_sig_handler(self, device, network, strength): + if not self._devices.has_key(device): + return + net = self._devices[device].get_network(network) + if net: + net.set_strength(strength) + + def device_carrier_on_sig_handler(self, device): + if not self._devices.has_key(device): + return + self._devices[device].set_carrier(True) + + def device_carrier_off_sig_handler(self, device): + if not self._devices.has_key(device): + return + self._devices[device].set_carrier(False) diff --git a/shell/hardware/nminfo.py b/shell/hardware/nminfo.py new file mode 100644 index 00000000..f512a8b8 --- /dev/null +++ b/shell/hardware/nminfo.py @@ -0,0 +1,467 @@ +# vi: ts=4 ai noet +# +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +import dbus +import dbus.service +import time +import os +import binascii +import ConfigParser +import logging + +import nmclient +try: + from sugar import env +except ImportError: + pass + +NM_INFO_IFACE='org.freedesktop.NetworkManagerInfo' +NM_INFO_PATH='/org/freedesktop/NetworkManagerInfo' + + +class NoNetworks(dbus.DBusException): + def __init__(self): + dbus.DBusException.__init__(self) + self._dbus_error_name = NM_INFO_IFACE + '.NoNetworks' + +class CanceledKeyRequestError(dbus.DBusException): + def __init__(self): + dbus.DBusException.__init__(self) + self._dbus_error_name = NM_INFO_IFACE + '.CanceledError' + + +class NetworkInvalidError(Exception): + pass + + +class NMConfig(ConfigParser.ConfigParser): + def get_bool(self, section, name): + opt = self.get(section, name) + if type(opt) == type(""): + if opt.lower() == 'yes' or opt.lower() == 'true': + return True + elif opt.lower() == 'no' or opt.lower() == 'false': + return False + raise ValueError("Invalid format for %s/%s. Should be one of [yes, no, true, false]." % (section, name)) + + def get_list(self, section, name): + opt = self.get(section, name) + if type(opt) == type(""): + if not len(opt): + return [] + try: + return opt.split() + except Exception: + pass + raise ValueError("Invalid format for %s/%s. Should be a space-separate list." % (section, name)) + + def get_int(self, section, name): + opt = self.get(section, name) + try: + return int(opt) + except Exception: + pass + raise ValueError("Invalid format for %s/%s. Should be a valid integer." % (section, name)) + + def get_float(self, section, name): + opt = self.get(section, name) + try: + return float(opt) + except Exception: + pass + raise ValueError("Invalid format for %s/%s. Should be a valid float." % (section, name)) + + +IW_AUTH_CIPHER_NONE = 0x00000001 +IW_AUTH_CIPHER_WEP40 = 0x00000002 +IW_AUTH_CIPHER_TKIP = 0x00000004 +IW_AUTH_CIPHER_CCMP = 0x00000008 +IW_AUTH_CIPHER_WEP104 = 0x00000010 + +IW_AUTH_ALG_OPEN_SYSTEM = 0x00000001 +IW_AUTH_ALG_SHARED_KEY = 0x00000002 + +NETWORK_TYPE_UNKNOWN = 0 +NETWORK_TYPE_ALLOWED = 1 +NETWORK_TYPE_INVALID = 2 + + +class Security(object): + def __init__(self, we_cipher): + self._we_cipher = we_cipher + + def read_from_config(self, cfg, name): + pass + + def read_from_args(self, args): + pass + + def new_from_config(cfg, name): + security = None + try: + we_cipher = cfg.get_int(name, "we_cipher") + if we_cipher == IW_AUTH_CIPHER_NONE: + security = Security(we_cipher) + elif we_cipher == IW_AUTH_CIPHER_WEP40 or we_cipher == IW_AUTH_CIPHER_WEP104: + security = WEPSecurity(we_cipher) + else: + # FIXME: find a way to make WPA config option matrix not + # make you want to throw up + raise ValueError("Unsupported security combo") + security.read_from_config(cfg, name) + except (ConfigParser.NoOptionError, ValueError), e: + return None + return security + new_from_config = staticmethod(new_from_config) + + def new_from_args(we_cipher, args): + security = None + try: + if we_cipher == IW_AUTH_CIPHER_NONE: + security = Security(we_cipher) + elif we_cipher == IW_AUTH_CIPHER_WEP40 or we_cipher == IW_AUTH_CIPHER_WEP104: + security = WEPSecurity(we_cipher) + else: + # FIXME: find a way to make WPA config option matrix not + # make you want to throw up + raise ValueError("Unsupported security combo") + security.read_from_args(args) + except ValueError, e: + logging.debug("Error reading security information: %s" % e) + del security + return None + return security + new_from_args = staticmethod(new_from_args) + + def get_properties(self): + return [dbus.Int32(self._we_cipher)] + + def write_to_config(self, section, config): + config.set(section, "we_cipher", self._we_cipher) + + +class WEPSecurity(Security): + def read_from_args(self, args): + if len(args) != 2: + raise ValueError("not enough arguments") + key = args[0] + auth_alg = args[1] + if isinstance(key, unicode): + key = key.encode() + if not isinstance(key, str): + raise ValueError("wrong argument type for key") + if not isinstance(auth_alg, int): + raise ValueError("wrong argument type for auth_alg") + self._key = key + self._auth_alg = auth_alg + + def read_from_config(self, cfg, name): + # Key should be a hex encoded string + self._key = cfg.get(name, "key") + if self._we_cipher == IW_AUTH_CIPHER_WEP40 and len(self._key) != 10: + raise ValueError("Key length not right for 40-bit WEP") + if self._we_cipher == IW_AUTH_CIPHER_WEP104 and len(self._key) != 26: + raise ValueError("Key length not right for 104-bit WEP") + + try: + a = binascii.a2b_hex(self._key) + except TypeError: + raise ValueError("Key was not a hexadecimal string.") + + self._auth_alg = cfg.get_int(name, "auth_alg") + if self._auth_alg != IW_AUTH_ALG_OPEN_SYSTEM and self._auth_alg != IW_AUTH_ALG_SHARED_KEY: + raise ValueError("Invalid authentication algorithm %d" % self._auth_alg) + + def get_properties(self): + args = Security.get_properties(self) + args.append(dbus.String(self._key)) + args.append(dbus.Int32(self._auth_alg)) + return args + + def write_to_config(self, section, config): + Security.write_to_config(self, section, config) + config.set(section, "key", self._key) + config.set(section, "auth_alg", self._auth_alg) + + +class Network: + def __init__(self, ssid): + self.ssid = ssid + self.timestamp = int(time.time()) + self.bssids = [] + self.we_cipher = 0 + self._security = None + + def get_properties(self): + bssid_list = dbus.Array([], signature="s") + for item in self.bssids: + bssid_list.append(dbus.String(item)) + args = [dbus.String(self.ssid), dbus.Int32(self.timestamp), dbus.Boolean(True), bssid_list] + args += self._security.get_properties() + return tuple(args) + + def get_security(self): + return self._security.get_properties() + + def set_security(self, security): + self._security = security + + def read_from_args(self, auto, bssid, we_cipher, args): + if auto == False: + self.timestamp = int(time.time()) + if not bssid in self.bssids: + self.bssids.append(bssid) + + self._security = Security.new_from_args(we_cipher, args) + if not self._security: + raise NetworkInvalidError("Invalid security information") + + def read_from_config(self, config): + try: + self.timestamp = config.get_int(self.ssid, "timestamp") + except (ConfigParser.NoOptionError, ValueError), e: + raise NetworkInvalidError(e) + + self._security = Security.new_from_config(config, self.ssid) + if not self._security: + raise NetworkInvalidError(e) + + # The following don't need to be present + try: + self.bssids = config.get_list(self.ssid, "bssids") + except (ConfigParser.NoOptionError, ValueError), e: + pass + + def write_to_config(self, config): + try: + config.add_section(self.ssid) + config.set(self.ssid, "timestamp", self.timestamp) + if len(self.bssids) > 0: + opt = " " + opt.join(self.bssids) + config.set(self.ssid, "bssids", opt) + self._security.write_to_config(self.ssid, config) + except Exception, e: + logging.debug("Error writing '%s': %s" % (self.ssid, e)) + + +class NotFoundError(dbus.DBusException): + pass +class UnsupportedError(dbus.DBusException): + pass + +class NMInfoDBusServiceHelper(dbus.service.Object): + def __init__(self, parent): + self._parent = parent + bus = dbus.SystemBus() + + # If NMI is already around, don't grab the NMI service + bus_object = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') + name = None + try: + name = bus_object.GetNameOwner("org.freedesktop.NetworkManagerInfo", \ + dbus_interface='org.freedesktop.DBus') + except dbus.DBusException: + pass + if name: + logging.debug("NMI service already owned by %s, won't claim it." % name) + raise RuntimeError + + bus_name = dbus.service.BusName(NM_INFO_IFACE, bus=bus) + dbus.service.Object.__init__(self, bus_name, NM_INFO_PATH) + + @dbus.service.method(NM_INFO_IFACE, in_signature='i', out_signature='as') + def getNetworks(self, net_type): + ssids = self._parent.get_networks(net_type) + if len(ssids) > 0: + return dbus.Array(ssids) + + raise NoNetworks() + + @dbus.service.method(NM_INFO_IFACE, in_signature='si', async_callbacks=('async_cb', 'async_err_cb')) + def getNetworkProperties(self, ssid, net_type, async_cb, async_err_cb): + self._parent.get_network_properties(ssid, net_type, async_cb, async_err_cb) + + @dbus.service.method(NM_INFO_IFACE) + def updateNetworkInfo(self, ssid, bauto, bssid, cipher, *args): + self._parent.update_network_info(ssid, bauto, bssid, cipher, args) + + @dbus.service.method(NM_INFO_IFACE, async_callbacks=('async_cb', 'async_err_cb')) + def getKeyForNetwork(self, dev_path, net_path, ssid, attempt, new_key, async_cb, async_err_cb): + self._parent.get_key_for_network(dev_path, net_path, ssid, + attempt, new_key, async_cb, async_err_cb) + + @dbus.service.method(NM_INFO_IFACE) + def cancelGetKeyForNetwork(self): + self._parent.cancel_get_key_for_network() + +class NMInfo(object): + def __init__(self, client): + try: + profile_path = env.get_profile_path() + except NameError: + home = os.path.expanduser("~") + profile_path = os.path.join(home, ".sugar", "default") + self._cfg_file = os.path.join(profile_path, "nm", "networks.cfg") + self._nmclient = client + self._allowed_networks = self._read_config() + self._dbus_helper = NMInfoDBusServiceHelper(self) + + def save_config(self): + self._write_config(self._allowed_networks) + + def _read_config(self): + if not os.path.exists(os.path.dirname(self._cfg_file)): + os.makedirs(os.path.dirname(self._cfg_file), 0755) + if not os.path.exists(self._cfg_file): + self._write_config({}) + return {} + + config = NMConfig() + config.read(self._cfg_file) + networks = {} + for name in config.sections(): + if not isinstance(name, unicode): + name = unicode(name) + net = Network(name) + try: + net.read_from_config(config) + networks[name] = net + except NetworkInvalidError, e: + logging.debug("Error: invalid stored network config: %s" % e) + del net + del config + return networks + + def _write_config(self, networks): + fp = open(self._cfg_file, 'w') + config = NMConfig() + for net in networks.values(): + net.write_to_config(config) + config.write(fp) + fp.close() + del config + + def get_networks(self, net_type): + if net_type != NETWORK_TYPE_ALLOWED: + raise ValueError("Bad network type") + nets = [] + for net in self._allowed_networks.values(): + nets.append(net.ssid) + logging.debug("Returning networks: %s" % nets) + return nets + + def get_network_properties(self, ssid, net_type, async_cb, async_err_cb): + if not isinstance(ssid, unicode): + async_err_cb(ValueError("Invalid arguments; ssid must be unicode.")) + if net_type != NETWORK_TYPE_ALLOWED: + async_err_cb(ValueError("Bad network type")) + if not self._allowed_networks.has_key(ssid): + async_err_cb(NotFoundError("Network '%s' not found." % ssid)) + network = self._allowed_networks[ssid] + props = network.get_properties() + + # DBus workaround: the normal method return handler wraps + # the returned arguments in a tuple and then converts that to a + # struct, but NetworkManager expects a plain list of arguments. + # It turns out that the async callback method return code _doesn't_ + # wrap the returned arguments in a tuple, so as a workaround use + # the async callback stuff here even though we're not doing it + # asynchronously. + async_cb(*props) + + def update_network_info(self, ssid, auto, bssid, we_cipher, args): + if not isinstance(ssid, unicode): + raise ValueError("Invalid arguments; ssid must be unicode.") + if self._allowed_networks.has_key(ssid): + del self._allowed_networks[ssid] + net = Network(ssid) + try: + net.read_from_args(auto, bssid, we_cipher, args) + logging.debug("Updated network information for '%s'." % ssid) + self._allowed_networks[ssid] = net + self.save_config() + except NetworkInvalidError, e: + logging.debug("Error updating network information: %s" % e) + del net + + def get_key_for_network(self, dev_op, net_op, ssid, attempt, new_key, async_cb, async_err_cb): + if not isinstance(ssid, unicode): + raise ValueError("Invalid arguments; ssid must be unicode.") + if self._allowed_networks.has_key(ssid) and not new_key: + # We've got the info already + net = self._allowed_networks[ssid] + async_cb(tuple(net.get_security())) + return + + # Otherwise, ask the user for it + net = None + dev = self._nmclient.get_device(dev_op) + if not dev: + async_err_cb(NotFoundError("Device was unknown.")) + return + + if dev.get_type() == nmclient.DEVICE_TYPE_802_3_ETHERNET: + # We don't support wired 802.1x yet... + async_err_cb(UnsupportedError("Device type is unsupported by NMI.")) + return + + net = dev.get_network(net_op) + if not net: + async_err_cb(NotFoundError("Network was unknown.")) + return + + self._nmclient.get_key_for_network(net, async_cb, async_err_cb) + + def get_key_for_network_cb(self, net, key, auth_alg, async_cb, async_err_cb, canceled=False): + """ + Called by the NMClient when the Wireless Network Key dialog + is closed. + """ + if canceled: + e = CanceledKeyRequestError("Request was canceled.") + # key dialog dialog was canceled; send the error back to NM + async_err_cb(e) + return + + if not key or not auth_alg: + # no key returned, *** BUG ***; the key dialog + # should always return either a key + auth_alg, or a + #cancel error + raise RuntimeError("No key or auth alg given! Bug!") + + we_cipher = None + if len(key) == 26: + we_cipher = IW_AUTH_CIPHER_WEP104 + elif len(key) == 10: + we_cipher = IW_AUTH_CIPHER_WEP40 + else: + raise RuntimeError("Invalid key length!") + + # Stuff the returned key and auth algorithm into a security object + # and return it to NetworkManager + sec = Security.new_from_args(we_cipher, (key, auth_alg)) + if not sec: + raise RuntimeError("Invalid security arguments.") + props = sec.get_properties() + a = tuple(props) + async_cb(*a) + + def cancel_get_key_for_network(self): + # Tell the NMClient to close the key request dialog + self._nmclient.cancel_get_key_for_network() diff --git a/shell/model/devices/Makefile.am b/shell/model/devices/Makefile.am index c987bb7b..f798c96c 100644 --- a/shell/model/devices/Makefile.am +++ b/shell/model/devices/Makefile.am @@ -4,4 +4,5 @@ sugar_PYTHON = \ device.py \ devicesmodel.py \ battery.py \ - network.py + wirednetwork.py \ + wirelessnetwork.py diff --git a/shell/model/devices/battery.py b/shell/model/devices/battery.py index 1d325ac8..3e8d434c 100644 --- a/shell/model/devices/battery.py +++ b/shell/model/devices/battery.py @@ -5,7 +5,7 @@ class Device(device.Device): device.Device.__init__(self) def get_type(self): - return 'network' + return 'battery' def get_level(self): return 0 diff --git a/shell/model/devices/devicesmodel.py b/shell/model/devices/devicesmodel.py index e5a06389..2eab979b 100644 --- a/shell/model/devices/devicesmodel.py +++ b/shell/model/devices/devicesmodel.py @@ -1,8 +1,10 @@ import gobject from model.devices import device -from model.devices import network +from model.devices import wirednetwork +from model.devices import wirelessnetwork from model.devices import battery +from hardware import hardwaremanager class DevicesModel(gobject.GObject): __gsignals__ = { @@ -18,12 +20,26 @@ class DevicesModel(gobject.GObject): gobject.GObject.__init__(self) self._devices = [] - - self.add_device(network.Device()) self.add_device(battery.Device()) + self._observe_network_manager() + + def _observe_network_manager(self): + network_manager = hardwaremanager.get_network_manager() + for device in network_manager.get_devices(): + self._check_network_device(device) + + def _check_network_device(self, device): + if not device.is_valid(): + return + + if device.get_type() == DEVICE_TYPE_802_11_WIRELESS: + if device.get_active(): + self._add_device(device) + def __iter__(self): return iter(self._devices) def add_device(self, device): self._devices.append(device) + self.emit('device-appeared', device) diff --git a/shell/model/devices/network.py b/shell/model/devices/wirednetwork.py similarity index 56% rename from shell/model/devices/network.py rename to shell/model/devices/wirednetwork.py index 1d325ac8..871a552f 100644 --- a/shell/model/devices/network.py +++ b/shell/model/devices/wirednetwork.py @@ -1,11 +1,9 @@ from model.devices import device class Device(device.Device): - def __init__(self): + def __init__(self, nm_device): device.Device.__init__(self) + self._nm_device = device def get_type(self): - return 'network' - - def get_level(self): - return 0 + return 'wirednetwork' diff --git a/shell/model/devices/wirelessnetwork.py b/shell/model/devices/wirelessnetwork.py new file mode 100644 index 00000000..6a53d341 --- /dev/null +++ b/shell/model/devices/wirelessnetwork.py @@ -0,0 +1,12 @@ +from model.devices import device + +class Device(device.Device): + def __init__(self, nm_device): + device.Device.__init__(self) + self._nm_device = device + + def get_type(self): + return 'wirelessnetwork' + + def get_level(self): + return self._nm_device.get_strength() diff --git a/shell/view/devices/Makefile.am b/shell/view/devices/Makefile.am index 54290e03..d48504da 100644 --- a/shell/view/devices/Makefile.am +++ b/shell/view/devices/Makefile.am @@ -3,4 +3,5 @@ sugar_PYTHON = \ __init__.py \ battery.py \ deviceview.py \ - network.py + wirednetwork.py \ + wirelessnetwork.py diff --git a/shell/view/devices/battery.py b/shell/view/devices/battery.py index 149e9b24..a13304e2 100644 --- a/shell/view/devices/battery.py +++ b/shell/view/devices/battery.py @@ -1,6 +1,6 @@ from view.devices import deviceview -class DeviceView(deviceview.DeviceView) +class DeviceView(deviceview.DeviceView): def __init__(self, model): deviceview.DeviceView.__init__(self, model) self.props.icon_name = 'theme:stock-close' diff --git a/shell/view/devices/network.py b/shell/view/devices/wirednetwork.py similarity index 74% rename from shell/view/devices/network.py rename to shell/view/devices/wirednetwork.py index a13304e2..c484b75c 100644 --- a/shell/view/devices/network.py +++ b/shell/view/devices/wirednetwork.py @@ -3,4 +3,4 @@ from view.devices import deviceview class DeviceView(deviceview.DeviceView): def __init__(self, model): deviceview.DeviceView.__init__(self, model) - self.props.icon_name = 'theme:stock-close' + self.props.icon_name = 'theme:stock-net-wired' diff --git a/shell/view/devices/wirelessnetwork.py b/shell/view/devices/wirelessnetwork.py new file mode 100644 index 00000000..659ec6b2 --- /dev/null +++ b/shell/view/devices/wirelessnetwork.py @@ -0,0 +1,6 @@ +from view.devices import deviceview + +class DeviceView(deviceview.DeviceView): + def __init__(self, model): + deviceview.DeviceView.__init__(self, model) + self.props.icon_name = 'theme:stock-net-wireless-00'