From 35035af44de1f7929cf02cd165f82c93a98c9575 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sat, 23 Jan 2010 15:39:03 +0000 Subject: Implement support for 3G modems (tch) #1622 --- (limited to 'extensions') diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am index dd0a6b8..a92b5dd 100644 --- a/extensions/cpsection/Makefile.am +++ b/extensions/cpsection/Makefile.am @@ -1,4 +1,5 @@ -SUBDIRS = aboutme aboutcomputer datetime frame keyboard language network power updater +SUBDIRS = aboutme aboutcomputer datetime frame keyboard language \ + modemconfiguration network power updater sugardir = $(pkgdatadir)/extensions/cpsection sugar_PYTHON = __init__.py diff --git a/extensions/cpsection/modemconfiguration/Makefile.am b/extensions/cpsection/modemconfiguration/Makefile.am new file mode 100644 index 0000000..3e2613e --- /dev/null +++ b/extensions/cpsection/modemconfiguration/Makefile.am @@ -0,0 +1,6 @@ +sugardir = $(pkgdatadir)/extensions/cpsection/modemconfiguration + +sugar_PYTHON = \ + __init__.py \ + model.py \ + view.py diff --git a/extensions/cpsection/modemconfiguration/__init__.py b/extensions/cpsection/modemconfiguration/__init__.py new file mode 100644 index 0000000..8a219dc --- /dev/null +++ b/extensions/cpsection/modemconfiguration/__init__.py @@ -0,0 +1,22 @@ +# 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 US + +from gettext import gettext as _ + +CLASS = 'ModemConfiguration' +ICON = 'module-modemconfiguration' +TITLE = _('Modem Configuration') + diff --git a/extensions/cpsection/modemconfiguration/model.py b/extensions/cpsection/modemconfiguration/model.py new file mode 100644 index 0000000..f96e88f --- /dev/null +++ b/extensions/cpsection/modemconfiguration/model.py @@ -0,0 +1,53 @@ +# 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 US + +import gconf + +from jarabe.model.network import GSM_USERNAME_PATH, GSM_PASSWORD_PATH, \ + GSM_NUMBER_PATH, GSM_APN_PATH + +def get_username(): + client = gconf.client_get_default() + return client.get_string(GSM_USERNAME_PATH) or '' + +def get_password(): + client = gconf.client_get_default() + return client.get_string(GSM_PASSWORD_PATH) or '' + +def get_number(): + client = gconf.client_get_default() + return client.get_string(GSM_NUMBER_PATH) or '' + +def get_apn(): + client = gconf.client_get_default() + return client.get_string(GSM_APN_PATH) or '' + +def set_username(username): + client = gconf.client_get_default() + client.set_string(GSM_USERNAME_PATH, username) + +def set_password(password): + client = gconf.client_get_default() + client.set_string(GSM_PASSWORD_PATH, password) + +def set_number(number): + client = gconf.client_get_default() + client.set_string(GSM_NUMBER_PATH, number) + +def set_apn(apn): + client = gconf.client_get_default() + client.set_string(GSM_APN_PATH, apn) + diff --git a/extensions/cpsection/modemconfiguration/view.py b/extensions/cpsection/modemconfiguration/view.py new file mode 100644 index 0000000..d66f1d5 --- /dev/null +++ b/extensions/cpsection/modemconfiguration/view.py @@ -0,0 +1,192 @@ +# 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 US + +import os +import logging +from gettext import gettext as _ + +import gtk +import gobject + +from sugar.graphics import style + +from jarabe.controlpanel.sectionview import SectionView + +APPLY_TIMEOUT = 1000 + +class EntryWithLabel(gtk.HBox): + __gtype_name__ = "SugarEntryWithLabel" + + def __init__(self, label_text): + gtk.HBox.__init__(self, spacing=style.DEFAULT_SPACING) + + self._timeout_sid = 0 + self._changed_handler = None + self._is_valid = True + + label = gtk.Label(label_text) + label.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + self.pack_start(label, True, True) + label.show() + + self._entry = gtk.Entry(25) + self._entry.connect('changed', self.__entry_changed_cb) + self._entry.set_width_chars(25) + self.pack_start(self._entry, expand=False) + self._entry.show() + + def __entry_changed_cb(self, widget, data=None): + if self._timeout_sid: + gobject.source_remove(self._timeout_sid) + self._timeout_sid = gobject.timeout_add(APPLY_TIMEOUT, + self.__timeout_cb) + + def __timeout_cb(self): + self._timeout_sid = 0 + + if self._entry.get_text() == self.get_value(): + return False + + try: + self.set_value(self._entry.get_text()) + except ValueError: + self._is_valid = False + else: + self._is_valid = True + + self.notify('is-valid') + + return False + + def set_text_from_model(self): + self._entry.set_text(self.get_value()) + + def get_value(self): + raise NotImplementedError + + def set_value(self): + raise NotImplementedError + + def _get_is_valid(self): + return self._is_valid + is_valid = gobject.property(type=bool, getter=_get_is_valid, default=True) + +class UsernameEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Username:')) + self._model = model + + def get_value(self): + return self._model.get_username() + + def set_value(self, username): + return self._model.set_username(username) + +class PasswordEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Password:')) + self._model = model + + def get_value(self): + return self._model.get_password() + + def set_value(self, password): + return self._model.set_password(password) + +class NumberEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Number:')) + self._model = model + + def get_value(self): + return self._model.get_number() + + def set_value(self, number): + return self._model.set_number(number) + +class ApnEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('APN:')) + self._model = model + + def get_value(self): + return self._model.get_apn() + + def set_value(self, apn): + return self._model.set_apn(apn) + +class ModemConfiguration(SectionView): + def __init__(self, model, alerts=None): + SectionView.__init__(self) + + self._model = model + self.restart_alerts = alerts + + self.set_border_width(style.DEFAULT_SPACING) + self.set_spacing(style.DEFAULT_SPACING) + + self._username_entry = UsernameEntry(model) + self._username_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self.pack_start(self._username_entry, expand=False) + self._username_entry.show() + + self._password_entry = PasswordEntry(model) + self._password_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self.pack_start(self._password_entry, expand=False) + self._password_entry.show() + + self._number_entry = NumberEntry(model) + self._number_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self.pack_start(self._number_entry, expand=False) + self._number_entry.show() + + self._apn_entry = ApnEntry(model) + self._apn_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self.pack_start(self._apn_entry, expand=False) + self._apn_entry.show() + + self.setup() + + def setup(self): + self._username_entry.set_text_from_model() + self._password_entry.set_text_from_model() + self._number_entry.set_text_from_model() + self._apn_entry.set_text_from_model() + + self.needs_restart = False + + def undo(self): + self._model.undo() + + def _validate(self): + if self._username_entry.is_valid and \ + self._password_entry.is_valid and \ + self._number_entry.is_valid and \ + self._apn_entry.is_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + def __notify_is_valid_cb(self, entry, pspec): + if entry.is_valid: + self.needs_restart = True + self._validate() + diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py index e40e196..2ca6a88 100644 --- a/extensions/deviceicon/network.py +++ b/extensions/deviceicon/network.py @@ -1,6 +1,7 @@ # # Copyright (C) 2008 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 @@ -55,16 +56,10 @@ _NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' _NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' -_NM_DEVICE_STATE_UNKNOWN = 0 -_NM_DEVICE_STATE_UNMANAGED = 1 -_NM_DEVICE_STATE_UNAVAILABLE = 2 -_NM_DEVICE_STATE_DISCONNECTED = 3 -_NM_DEVICE_STATE_PREPARE = 4 -_NM_DEVICE_STATE_CONFIG = 5 -_NM_DEVICE_STATE_NEED_AUTH = 6 -_NM_DEVICE_STATE_IP_CONFIG = 7 -_NM_DEVICE_STATE_ACTIVATED = 8 -_NM_DEVICE_STATE_FAILED = 9 +_GSM_STATE_NOT_READY = 0 +_GSM_STATE_DISCONNECTED = 1 +_GSM_STATE_CONNECTING = 2 +_GSM_STATE_CONNECTED = 3 def frequency_to_channel(frequency): ftoc = { 2412: 1, 2417: 2, 2422: 3, 2427: 4, @@ -215,6 +210,65 @@ class WiredPalette(Palette): 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_state(_GSM_STATE_NOT_READY) + + def set_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') + 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') + else: + raise ValueError('Invalid GSM state while emitting signal, %s' % \ + str(self._current_state)) + class WirelessDeviceView(ToolButton): @@ -451,11 +505,11 @@ class WirelessDeviceView(ToolButton): connection_name = format % nick connection_name += color_suffix - connection = network.find_connection(connection_name) + connection = network.find_connection_by_ssid(connection_name) if connection is None: settings = Settings() settings.connection.id = 'Auto ' + connection_name - settings.connection.uuid = unique_id() + uuid = settings.connection.uuid = unique_id() settings.connection.type = '802-11-wireless' settings.wireless.ssid = dbus.ByteArray(connection_name) settings.wireless.band = 'bg' @@ -463,7 +517,7 @@ class WirelessDeviceView(ToolButton): settings.ip4_config = IP4Config() settings.ip4_config.method = 'link-local' - connection = network.add_connection(connection_name, settings) + connection = network.add_connection(uuid, settings) obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) netmgr = dbus.Interface(obj, _NM_IFACE) @@ -623,6 +677,122 @@ class WiredDeviceView(TrayIcon): self._palette.set_connected(speed, address) +class GsmDeviceView(TrayIcon): + + _ICON_NAME = 'gsm-device' + FRAME_POSITION_RELATIVE = 303 + + def __init__(self, device): + 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._palette = None + self.set_palette_invoker(FrameWidgetInvoker(self)) + + self._bus.add_signal_receiver(self.__state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + def create_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) + + self._palette = palette + + 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): + 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 is network.DEVICE_STATE_ACTIVATED: + gsm_state = _GSM_STATE_CONNECTED + + elif state is network.DEVICE_STATE_DISCONNECTED: + gsm_state = _GSM_STATE_DISCONNECTED + + 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 + + if self._palette is not None: + self._palette.set_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) + class WirelessDeviceObserver(object): def __init__(self, device, tray, device_type): self._device = device @@ -633,6 +803,8 @@ class WirelessDeviceObserver(object): self._device_view = WirelessDeviceView(self._device) elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: self._device_view = OlpcMeshDeviceView(self._device) + else: + raise ValueError('Unimplemented device type %d' % device_type) self._tray.add_device(self._device_view) @@ -691,6 +863,19 @@ class WiredDeviceObserver(object): 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): @@ -735,6 +920,9 @@ class NetworkManagerObserver(object): network.DEVICE_TYPE_802_11_OLPC_MESH]: device = WirelessDeviceObserver(nm_device, self._tray, device_type) 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) -- cgit v0.9.1