Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'extensions')
-rw-r--r--extensions/cpsection/aboutcomputer/model.py129
-rw-r--r--extensions/cpsection/aboutcomputer/view.py155
-rw-r--r--extensions/cpsection/datetime/model.py47
-rw-r--r--extensions/cpsection/datetime/view.py185
-rw-r--r--extensions/cpsection/modemconfiguration/Makefile.am1
-rw-r--r--extensions/cpsection/modemconfiguration/config.py25
-rwxr-xr-xextensions/cpsection/modemconfiguration/model.py187
-rw-r--r--extensions/cpsection/modemconfiguration/view.py198
-rw-r--r--extensions/cpsection/network/model.py17
-rw-r--r--extensions/cpsection/network/view.py55
-rw-r--r--extensions/deviceicon/Makefile.am1
-rw-r--r--extensions/deviceicon/network.py222
-rw-r--r--extensions/deviceicon/resources.py217
13 files changed, 1291 insertions, 148 deletions
diff --git a/extensions/cpsection/aboutcomputer/model.py b/extensions/cpsection/aboutcomputer/model.py
index 86d2e3f..5d327ea 100644
--- a/extensions/cpsection/aboutcomputer/model.py
+++ b/extensions/cpsection/aboutcomputer/model.py
@@ -1,4 +1,5 @@
# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2010 Plan Ceibal <comunidad@plan.ceibal.edu.uy>
#
# 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
@@ -18,9 +19,12 @@
import os
import logging
import re
+import ConfigParser
+import time
import subprocess
from gettext import gettext as _
import errno
+from datetime import datetime
import dbus
@@ -39,6 +43,9 @@ _DMI_DIRECTORY = '/sys/class/dmi/id'
_SN = 'serial-number'
_MODEL = 'openprom/model'
+_XO_1_0_LEASE_PATH = '/security/lease.sig'
+_XO_1_5_LEASE_PATH = '/bootpart/boot/security/lease.sig'
+
_logger = logging.getLogger('ControlPanel - AboutComputer')
_not_available = _('Not available')
@@ -53,6 +60,31 @@ def print_aboutcomputer():
print get_aboutcomputer()
+def _get_lease_path():
+ if os.path.exists(_XO_1_0_LEASE_PATH):
+ return _XO_1_0_LEASE_PATH
+ elif os.path.exists(_XO_1_5_LEASE_PATH):
+ return _XO_1_5_LEASE_PATH
+ else:
+ return ''
+
+
+def get_lease_days():
+ lease_file = _read_file(_get_lease_path())
+ if lease_file is None:
+ return _not_available
+
+ encoded_date = str(str.split(lease_file)[3])
+ expiry_date = datetime.strptime(encoded_date
+ , '%Y%m%dT%H%M%SZ')
+ current_date = datetime.today()
+ days_remaining = (expiry_date - current_date).days
+
+ # TRANS: Do not translate %s
+ str_days_remaining = _('%s days remaining' % str(days_remaining))
+ return str_days_remaining
+
+
def get_serial_number():
serial_no = None
if os.path.exists(os.path.join(_OFW_TREE, _SN)):
@@ -72,7 +104,10 @@ def print_serial_number():
def get_build_number():
- build_no = _read_file('/boot/olpc_build')
+ if os.path.isfile('/boot/olpc_build'):
+ build_no = _read_file('/boot/olpc_build')
+ elif os.path.isfile('/bootpart/olpc_build'):
+ build_no = _read_file('/bootpart/olpc_build')
if build_no is None:
build_no = _read_file('/etc/redhat-release')
@@ -97,6 +132,15 @@ def print_build_number():
print get_build_number()
+def get_model_laptop():
+ from ceibal import laptop
+
+ model_laptop = laptops.get_model_laptop()
+ if model_laptop is None or not model_laptop:
+ model_laptop = _not_available
+ return model_laptop
+
+
def _parse_firmware_number(firmware_no):
if firmware_no is None:
firmware_no = _not_available
@@ -226,3 +270,86 @@ def get_license():
except IOError:
license_text = _not_available
return license_text
+
+
+def get_last_updated_on_field():
+
+ # Get the number of UNIX seconds of the last update date.
+ last_update_unix_seconds = {}
+ try:
+ last_update_unix_seconds = int(os.stat('/var/lib/rpm/Packages').st_mtime)
+ except:
+ msg_str = _('Information not available.')
+ _logger.exception(msg_str)
+ return msg_str
+
+
+ NO_UPDATE_MESSAGE = _('No update yet!')
+
+
+ # Check once again that 'last_update_unix_seconds' is not empty.
+ # You never know !
+ if not last_update_unix_seconds:
+ return NO_UPDATE_MESSAGE
+
+ if int(last_update_unix_seconds) == 1194004800:
+ return NO_UPDATE_MESSAGE
+
+
+ # If we reached here, we have the last-update-time, but it's in
+ # timestamp format.
+ # Using python-subprocess-module (no shell involved), to convert
+ # it into readable date-format; the hack being used (after
+ # removing '-u' option) is the first one mentioned at :
+ # http://www.commandlinefu.com/commands/view/3719/convert-unix-timestamp-to-date
+ environment = os.environ.copy()
+ environment['PATH'] = '%s:/usr/sbin' % (environment['PATH'], )
+
+ last_update_readable_format = {}
+ try:
+ last_update_readable_format = \
+ subprocess.Popen(['date', '-d',
+ '1970-01-01 + ' +
+ str(last_update_unix_seconds) +
+ ' seconds'],
+ stdout=subprocess.PIPE,
+ env=environment).stdout.readlines()[0]
+ except:
+ msg_str = _('Information not available.')
+ _logger.exception(msg_str)
+ return msg_str
+
+ if not last_update_readable_format:
+ return _('Information not available.')
+
+ # Everything should be fine (hopefully :-) )
+ return last_update_readable_format
+
+
+def get_sugar_version():
+ return config.version
+
+
+def get_plazo():
+ from ceibal import env
+ path_plazo = env.get_security_root()
+ try:
+ plazo = _read_file(os.path.join(path_plazo, "blacklist")).split("\n")[0].strip()
+ plazo = time.strftime( "%d-%m-%Y",time.strptime(plazo,'%Y%m%d'))
+ except:
+ plazo = _not_available
+
+ return plazo
+
+def get_act():
+ from ceibal import env
+ path_act = env.get_updates_root()
+ parser = ConfigParser.ConfigParser()
+ salida = parser.read(os.path.join(path_act, "mi_version"))
+ if salida == []:
+ version = _not_available
+ else:
+ version = ''
+ for seccion in parser.sections():
+ version = "%s%s: %s\n" %(version,seccion,parser.get(seccion,'version'))
+ return version
diff --git a/extensions/cpsection/aboutcomputer/view.py b/extensions/cpsection/aboutcomputer/view.py
index f44ca51..1e11301 100644
--- a/extensions/cpsection/aboutcomputer/view.py
+++ b/extensions/cpsection/aboutcomputer/view.py
@@ -23,7 +23,6 @@ from gi.repository import Gdk
from sugar3.graphics import style
-from jarabe import config
from jarabe.controlpanel.sectionview import SectionView
@@ -65,24 +64,51 @@ class AboutComputer(SectionView):
vbox_identity.set_border_width(style.DEFAULT_SPACING * 2)
vbox_identity.set_spacing(style.DEFAULT_SPACING)
- box_identity = Gtk.HBox(spacing=style.DEFAULT_SPACING)
- label_serial = Gtk.Label(label=_('Serial Number:'))
- label_serial.set_alignment(1, 0)
- label_serial.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_identity.pack_start(label_serial, False, True, 0)
- self._group.add_widget(label_serial)
- label_serial.show()
- label_serial_no = Gtk.Label(label=self._model.get_serial_number())
- label_serial_no.set_alignment(0, 0)
- box_identity.pack_start(label_serial_no, False, True, 0)
- label_serial_no.show()
- vbox_identity.pack_start(box_identity, False, True, 0)
- box_identity.show()
+ self._setup_component_if_applicable(None,
+ _('Serial Number:'),
+ self._model.get_serial_number,
+ vbox_identity)
+
+ self._setup_component_if_applicable('/desktop/sugar/extensions/aboutcomputer/display_lease',
+ _('Lease:'),
+ self._model.get_lease_days,
+ vbox_identity)
self._vbox.pack_start(vbox_identity, False, True, 0)
vbox_identity.show()
+ def _is_feature_to_be_shown(slf, gconf_key):
+ if gconf_key is None:
+ return True
+
+ from gi.repository import GConf
+ client = GConf.Client.get_default()
+
+ return client.get_bool(gconf_key) is True
+
+ def _setup_component_if_applicable(self, gconf_key, key, value_func, packer):
+ if not self._is_feature_to_be_shown(gconf_key):
+ return
+
+ # Now that we do need to show, fetch the value.
+ print value_func
+ value = value_func()
+
+ box = Gtk.HBox(spacing=style.DEFAULT_SPACING)
+ key_label = Gtk.Label(label=key)
+ key_label.set_alignment(1, 0)
+ key_label.modify_fg(Gtk.StateType.NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box.pack_start(key_label, False, True, 0)
+ self._group.add_widget(key_label)
+ key_label.show()
+ value_label = Gtk.Label(label=value)
+ value_label.set_alignment(0, 0)
+ box.pack_start(value_label, False, True, 0)
+ value_label.show()
+ packer.pack_start(box, False, True, 0)
+ box.show()
+
def _setup_software(self):
separator_software = Gtk.HSeparator()
self._vbox.pack_start(separator_software, False, True, 0)
@@ -96,66 +122,45 @@ class AboutComputer(SectionView):
box_software.set_border_width(style.DEFAULT_SPACING * 2)
box_software.set_spacing(style.DEFAULT_SPACING)
- box_build = Gtk.HBox(spacing=style.DEFAULT_SPACING)
- label_build = Gtk.Label(label=_('Build:'))
- label_build.set_alignment(1, 0)
- label_build.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_build.pack_start(label_build, False, True, 0)
- self._group.add_widget(label_build)
- label_build.show()
- label_build_no = Gtk.Label(label=self._model.get_build_number())
- label_build_no.set_alignment(0, 0)
- box_build.pack_start(label_build_no, False, True, 0)
- label_build_no.show()
- box_software.pack_start(box_build, False, True, 0)
- box_build.show()
-
- box_sugar = Gtk.HBox(spacing=style.DEFAULT_SPACING)
- label_sugar = Gtk.Label(label=_('Sugar:'))
- label_sugar.set_alignment(1, 0)
- label_sugar.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_sugar.pack_start(label_sugar, False, True, 0)
- self._group.add_widget(label_sugar)
- label_sugar.show()
- label_sugar_ver = Gtk.Label(label=config.version)
- label_sugar_ver.set_alignment(0, 0)
- box_sugar.pack_start(label_sugar_ver, False, True, 0)
- label_sugar_ver.show()
- box_software.pack_start(box_sugar, False, True, 0)
- box_sugar.show()
-
- box_firmware = Gtk.HBox(spacing=style.DEFAULT_SPACING)
- label_firmware = Gtk.Label(label=_('Firmware:'))
- label_firmware.set_alignment(1, 0)
- label_firmware.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_firmware.pack_start(label_firmware, False, True, 0)
- self._group.add_widget(label_firmware)
- label_firmware.show()
- label_firmware_no = Gtk.Label(label=self._model.get_firmware_number())
- label_firmware_no.set_alignment(0, 0)
- box_firmware.pack_start(label_firmware_no, False, True, 0)
- label_firmware_no.show()
- box_software.pack_start(box_firmware, False, True, 0)
- box_firmware.show()
-
- box_wireless_fw = Gtk.HBox(spacing=style.DEFAULT_SPACING)
- label_wireless_fw = Gtk.Label(label=_('Wireless Firmware:'))
- label_wireless_fw.set_alignment(1, 0)
- label_wireless_fw.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_wireless_fw.pack_start(label_wireless_fw, False, True, 0)
- self._group.add_widget(label_wireless_fw)
- label_wireless_fw.show()
- wireless_fw_no = self._model.get_wireless_firmware()
- label_wireless_fw_no = Gtk.Label(label=wireless_fw_no)
- label_wireless_fw_no.set_alignment(0, 0)
- box_wireless_fw.pack_start(label_wireless_fw_no, False, True, 0)
- label_wireless_fw_no.show()
- box_software.pack_start(box_wireless_fw, False, True, 0)
- box_wireless_fw.show()
+ self._setup_component_if_applicable('/desktop/sugar/extensions/aboutcomputer/display_model',
+ _('Model:'),
+ self._model.get_model_laptop,
+ box_software)
+
+ self._setup_component_if_applicable(None,
+ _('Build:'),
+ self._model.get_build_number,
+ box_software)
+
+ self._setup_component_if_applicable(None,
+ _('Sugar:'),
+ self._model.get_sugar_version,
+ box_software)
+
+ self._setup_component_if_applicable(None,
+ _('Firmware:'),
+ self._model.get_firmware_number,
+ box_software)
+
+ self._setup_component_if_applicable('/desktop/sugar/extensions/aboutcomputer/display_wireless_firmware',
+ _('Wireless Firmware:'),
+ self._model.get_wireless_firmware,
+ box_software)
+
+ self._setup_component_if_applicable('/desktop/sugar/extensions/aboutcomputer/display_plazo',
+ _('Plazo:'),
+ self._model.get_plazo,
+ box_software)
+
+ self._setup_component_if_applicable('/desktop/sugar/extensions/aboutcomputer/display_version_de_actual',
+ _('VersiĆ³n de ActualizaciĆ³n:'),
+ self._model.get_act,
+ box_software)
+
+ self._setup_component_if_applicable(None,
+ _('Last Updated On:'),
+ self._model.get_last_updated_on_field,
+ box_software)
self._vbox.pack_start(box_software, False, True, 0)
box_software.show()
diff --git a/extensions/cpsection/datetime/model.py b/extensions/cpsection/datetime/model.py
index c9b4586..f73bef7 100644
--- a/extensions/cpsection/datetime/model.py
+++ b/extensions/cpsection/datetime/model.py
@@ -21,10 +21,57 @@
#
import os
+import logging
+
from gettext import gettext as _
from gi.repository import GConf
_zone_tab = '/usr/share/zoneinfo/zone.tab'
+NTPDATE_PATH = '/usr/sbin/ntpdate'
+NTP_SERVER_CONFIG_FILENAME = '/etc/ntp/step-tickers'
+
+_logger = logging.getLogger('ControlPanel - TimeZone')
+
+
+def is_ntp_servers_config_feature_available():
+ return os.path.exists(NTPDATE_PATH)
+
+
+def get_ntp_servers():
+ servers = []
+
+ # If the file does not exist, return.
+ if not os.path.exists(NTP_SERVER_CONFIG_FILENAME):
+ return servers
+
+ f = open(NTP_SERVER_CONFIG_FILENAME, 'r')
+ for server in f.readlines():
+ servers.append(server.rstrip('\n'))
+ f.close()
+
+ return servers
+
+
+def set_ntp_servers(servers):
+
+ # First remove the old ssid-file, if it exists.
+ if os.path.exists(NTP_SERVER_CONFIG_FILENAME):
+ try:
+ os.remove(NTP_SERVER_CONFIG_FILENAME)
+ except:
+ _logger.exception('Error removing file.')
+ return
+
+ # Do nothing and return, if the values-list is empty
+ if len(servers) == 0:
+ return
+
+ # If we reach here, we have a non-empty ssid-values-list.
+ f = open(NTP_SERVER_CONFIG_FILENAME, 'w')
+ for server in servers:
+ if len(server) > 0:
+ f.write(server + '\n')
+ f.close()
def _initialize():
diff --git a/extensions/cpsection/datetime/view.py b/extensions/cpsection/datetime/view.py
index 64789b4..4ad94ca 100644
--- a/extensions/cpsection/datetime/view.py
+++ b/extensions/cpsection/datetime/view.py
@@ -20,11 +20,130 @@ from gettext import gettext as _
from sugar3.graphics import style
from sugar3.graphics import iconentry
+from sugar3.graphics.icon import Icon
from jarabe.controlpanel.sectionview import SectionView
from jarabe.controlpanel.inlinealert import InlineAlert
+class AddRemoveWidget(Gtk.HBox):
+
+ def __init__(self, label, add_button_clicked_cb,
+ remove_button_clicked_cb, index):
+ Gtk.Box.__init__(self)
+ self.set_homogeneous(False)
+ self.set_spacing(10)
+
+ self._index = index
+ self._add_button_added = False
+ self._remove_button_added = False
+
+ self._entry_box = Gtk.Entry()
+ self._entry_box.set_text(label)
+ self._entry_box.set_width_chars(40)
+ self.pack_start(self._entry_box, False, False, 0)
+ self._entry_box.show()
+
+ add_icon = Icon(icon_name='list-add')
+ self._add_button = Gtk.Button()
+ self._add_button.set_image(add_icon)
+ self._add_button.connect('clicked',
+ add_button_clicked_cb,
+ self)
+
+ remove_icon = Icon(icon_name='list-remove')
+ self._remove_button = Gtk.Button()
+ self._remove_button.set_image(remove_icon)
+ self._remove_button.connect('clicked',
+ remove_button_clicked_cb,
+ self)
+ self.__add_add_button()
+ self.__add_remove_button()
+
+ def _get_index(self):
+ return self._index
+
+ def _set_index(self, value):
+ self._index = value
+
+ def _get_entry(self):
+ return self._entry_box.get_text()
+
+ def __add_add_button(self):
+ self.pack_start(self._add_button, False, False, 0)
+ self._add_button.show()
+ self._add_button_added = True
+
+ def _remove_remove_button_if_not_already(self):
+ if self._remove_button_added:
+ self.__remove_remove_button()
+
+ def __remove_remove_button(self):
+ self.remove(self._remove_button)
+ self._remove_button_added = False
+
+ def _add_remove_button_if_not_already(self):
+ if not self._remove_button_added:
+ self.__add_remove_button()
+
+ def __add_remove_button(self):
+ self.pack_start(self._remove_button, False, False, 0)
+ self._remove_button.show()
+ self._remove_button_added = True
+
+
+class MultiWidget(Gtk.VBox):
+
+ def __init__(self):
+ Gtk.VBox.__init__(self)
+
+ def _add_widget(self, label):
+ new_widget = AddRemoveWidget(label,
+ self.__add_button_clicked_cb,
+ self.__remove_button_clicked_cb,
+ len(self.get_children()))
+ self.add(new_widget)
+ new_widget.show()
+ self.show()
+ self._update_remove_button_statuses()
+
+ def __add_button_clicked_cb(self, add_button,
+ add_button_container):
+ self._add_widget('')
+ self._update_remove_button_statuses()
+
+ def __remove_button_clicked_cb(self, remove_button,
+ remove_button_container):
+ for child in self.get_children():
+ if child._get_index() > remove_button_container._get_index():
+ child._set_index(child._get_index() - 1)
+
+ self.remove(remove_button_container)
+ self._update_remove_button_statuses()
+
+ def _update_remove_button_statuses(self):
+ children = self.get_children()
+
+ # Now, if there is only one entry, remove-button
+ # should not be shown.
+ if len(children) == 1:
+ children[0]._remove_remove_button_if_not_already()
+
+ # Alternatively, if there are more than 1 entries,
+ # remove-button should be shown for all.
+ if len(children) > 1:
+ for child in children:
+ child._add_remove_button_if_not_already()
+
+
+ def _get_entries(self):
+ entries = []
+ for child in self.get_children():
+ entries.append(child._get_entry())
+
+ return entries
+
+
class TimeZone(SectionView):
def __init__(self, model, alerts):
SectionView.__init__(self)
@@ -64,7 +183,10 @@ class TimeZone(SectionView):
self._treeview.set_search_entry(self._entry)
self._treeview.set_search_equal_func(self._search, None)
self._treeview.set_search_column(0)
- self._scrolled_window.add(self._treeview)
+ self._timezone_box = Gtk.VBox()
+ self._scrolled_window.add(self._timezone_box)
+ self._timezone_box.show_all()
+ self._timezone_box.add(self._treeview)
self._treeview.show()
self._timezone_column = Gtk.TreeViewColumn(_('Timezone'))
@@ -74,19 +196,29 @@ class TimeZone(SectionView):
self._timezone_column.set_sort_column_id(0)
self._treeview.append_column(self._timezone_column)
- self.pack_start(self._scrolled_window, True, True, 0)
- self._scrolled_window.show()
+ self._container = Gtk.VBox()
+ self._container.set_homogeneous(False)
+ self._container.pack_start(self._scrolled_window, True, True, 0)
+ self._container.set_spacing(style.DEFAULT_SPACING)
+ self._container.show_all()
+ self.pack_start(self._container, True, True, 0)
self._zone_alert_box = Gtk.HBox(spacing=style.DEFAULT_SPACING)
- self.pack_start(self._zone_alert_box, False, False, 0)
-
+ self._timezone_box.pack_start(self._zone_alert_box, False, False, 0)
self._zone_alert = InlineAlert()
self._zone_alert_box.pack_start(self._zone_alert, True, True, 0)
if 'zone' in self.restart_alerts:
self._zone_alert.props.msg = self.restart_msg
self._zone_alert.show()
+
+ # Not showing this, as this hides the selected timezone.
+ # Instead, the alert will anyways be shown when user clicks
+ # on "Ok".
+ #self._zone_alert.show()
self._zone_alert_box.show()
+ self._ntp_ui_setup = False
+
self.setup()
def setup(self):
@@ -102,6 +234,45 @@ class TimeZone(SectionView):
self.needs_restart = False
self._cursor_change_handler = self._treeview.connect( \
'cursor-changed', self.__zone_changed_cd)
+ if self._model.is_ntp_servers_config_feature_available():
+ self.setup_ui_for_ntp_server_config()
+
+ def setup_ui_for_ntp_server_config(self):
+ if self._ntp_ui_setup:
+ return
+ self._ntp_ui_setup = True
+
+ self._ntp_scrolled_window = Gtk.ScrolledWindow()
+ self._ntp_scrolled_window.set_policy(Gtk.PolicyType.NEVER,
+ Gtk.PolicyType.AUTOMATIC)
+ box_ntp_servers_config = Gtk.VBox()
+ box_ntp_servers_config.set_spacing(style.DEFAULT_SPACING)
+
+ separator_ntp_servers_config= Gtk.HSeparator()
+ self._container.pack_start(separator_ntp_servers_config,
+ False, False, 0)
+ separator_ntp_servers_config.show()
+
+ label_ntp_servers_config = Gtk.Label(_('NTP Servers Configuration'))
+ label_ntp_servers_config.set_alignment(0, 0)
+ self._container.pack_start(label_ntp_servers_config,
+ False, False, 0)
+ label_ntp_servers_config.show()
+
+ self._widget_table = MultiWidget()
+ box_ntp_servers_config.pack_start(self._widget_table, False, False, 0)
+ box_ntp_servers_config.show_all()
+
+ self._ntp_scrolled_window.add_with_viewport(box_ntp_servers_config)
+ self._container.pack_start(self._ntp_scrolled_window, True, True, 0)
+ self._ntp_scrolled_window.show_all()
+
+ ntp_servers = self._model.get_ntp_servers()
+ if len(ntp_servers) == 0:
+ self._widget_table._add_widget('')
+ else:
+ for server in ntp_servers:
+ self._widget_table._add_widget(server)
def undo(self):
self._treeview.disconnect(self._cursor_change_handler)
@@ -136,5 +307,7 @@ class TimeZone(SectionView):
self.restart_alerts.append('zone')
self.needs_restart = True
self._zone_alert.props.msg = self.restart_msg
- self._zone_alert.show()
return False
+
+ def perform_accept_actions(self):
+ self._model.set_ntp_servers(self._widget_table._get_entries())
diff --git a/extensions/cpsection/modemconfiguration/Makefile.am b/extensions/cpsection/modemconfiguration/Makefile.am
index 3e2613e..46f8e70 100644
--- a/extensions/cpsection/modemconfiguration/Makefile.am
+++ b/extensions/cpsection/modemconfiguration/Makefile.am
@@ -3,4 +3,5 @@ sugardir = $(pkgdatadir)/extensions/cpsection/modemconfiguration
sugar_PYTHON = \
__init__.py \
model.py \
+ config.py \
view.py
diff --git a/extensions/cpsection/modemconfiguration/config.py b/extensions/cpsection/modemconfiguration/config.py
new file mode 100644
index 0000000..9e27814
--- /dev/null
+++ b/extensions/cpsection/modemconfiguration/config.py
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+# Copyright (C) 2010 Andres Ambrois
+#
+# 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
+
+
+PROVIDERS_PATH = "/usr/share/mobile-broadband-provider-info/serviceproviders.xml"
+PROVIDERS_FORMAT_SUPPORTED = "2.0"
+COUNTRY_CODES_PATH = "/usr/share/zoneinfo/iso3166.tab"
+
+GSM_COUNTRY_PATH = '/desktop/sugar/network/gsm/country'
+GSM_PROVIDERS_PATH = '/desktop/sugar/network/gsm/providers'
+GSM_PLAN_PATH = '/desktop/sugar/network/gsm/plan'
diff --git a/extensions/cpsection/modemconfiguration/model.py b/extensions/cpsection/modemconfiguration/model.py
index f457293..e33d881 100755
--- a/extensions/cpsection/modemconfiguration/model.py
+++ b/extensions/cpsection/modemconfiguration/model.py
@@ -19,14 +19,27 @@ import logging
import dbus
from gi.repository import Gtk
+import os
+import locale
+import logging
+import gconf
+
+from xml.etree.cElementTree import ElementTree
+from gettext import gettext as _
+
from jarabe.model import network
+from cpsection.modemconfiguration.config import PROVIDERS_PATH, \
+ PROVIDERS_FORMAT_SUPPORTED, \
+ COUNTRY_CODES_PATH
+
+
def get_connection():
return network.find_gsm_connection()
-def get_modem_settings():
+def get_modem_settings(callback):
modem_settings = {}
connection = get_connection()
if not connection:
@@ -48,6 +61,10 @@ def get_modem_settings():
modem_settings['password'] = gsm_secrets.get('password', '')
modem_settings['pin'] = gsm_secrets.get('pin', '')
+ # sl#3800: We return the settings, via the "_secrets_cb()
+ # method", instead of busy-waiting.
+ callback(modem_settings)
+
def _secrets_err_cb(err):
secrets_call_done[0] = True
if isinstance(err, dbus.exceptions.DBusException) and \
@@ -57,14 +74,11 @@ def get_modem_settings():
logging.error('Error retrieving GSM secrets: %s', err)
# must be called asynchronously as this re-enters the GTK main loop
+ #
+ # sl#3800: We return the settings, via the "_secrets_cb()" method,
+ # instead of busy-waiting.
connection.get_secrets('gsm', _secrets_cb, _secrets_err_cb)
- # wait til asynchronous execution completes
- while not secrets_call_done[0]:
- Gtk.main_iteration()
-
- return modem_settings
-
def _set_or_clear(_dict, key, value):
"""Update a dictionary value for a specific key. If value is None or
@@ -98,3 +112,162 @@ def set_modem_settings(modem_settings):
_set_or_clear(gsm_settings, 'apn', apn)
_set_or_clear(gsm_settings, 'pin', pin)
connection.update_settings(settings)
+
+
+def has_providers_db():
+ if not os.path.isfile(COUNTRY_CODES_PATH):
+ logging.debug("Mobile broadband provider database: Country " \
+ "codes path %s not found.", COUNTRY_CODES_PATH)
+ return False
+ try:
+ tree = ElementTree(file=PROVIDERS_PATH)
+ except (IOError, SyntaxError), e:
+ logging.debug("Mobile broadband provider database: Could not read " \
+ "provider information %s error=%s", PROVIDERS_PATH)
+ return False
+ else:
+ elem = tree.getroot()
+ if elem is None or elem.get('format') != PROVIDERS_FORMAT_SUPPORTED:
+ logging.debug("Mobile broadband provider database: Could not " \
+ "read provider information. %s is wrong format.",
+ elem.get('format'))
+ return False
+ return True
+
+
+class CountryListStore(Gtk.ListStore):
+ COUNTRY_CODE = locale.getdefaultlocale()[0][3:5].lower()
+
+ def __init__(self):
+ Gtk.ListStore.__init__(self, str, object)
+ codes = {}
+ with open(COUNTRY_CODES_PATH) as codes_file:
+ for line in codes_file:
+ if line.startswith('#'):
+ continue
+ code, name = line.split('\t')[:2]
+ codes[code.lower()] = name.strip()
+ etree = ElementTree(file=PROVIDERS_PATH).getroot()
+ self._country_idx = None
+ i = 0
+
+ # This dictionary wil store the values, with "country-name" as
+ # the key, and "country-code" as the value.
+ temp_dict = {}
+
+ for elem in etree.findall('.//country'):
+ code = elem.attrib['code']
+ if code == self.COUNTRY_CODE:
+ self._country_idx = i
+ else:
+ i += 1
+ if code in codes:
+ temp_dict[codes[code]] = elem
+ else:
+ temp_dict[code] = elem
+
+ # Now, sort the list by country-names.
+ country_name_keys = temp_dict.keys()
+ country_name_keys.sort()
+
+ for country_name in country_name_keys:
+ self.append((country_name, temp_dict[country_name]))
+
+ def get_row_providers(self, row):
+ return self[row][1]
+
+ def guess_country_row(self):
+ if self._country_idx is not None:
+ return self._country_idx
+ else:
+ return 0
+
+ def search_index_by_code(self, code):
+ for index in range(0, len(self)):
+ if self[index][0] == code:
+ return index
+ return -1
+
+
+class ProviderListStore(Gtk.ListStore):
+ def __init__(self, elem):
+ Gtk.ListStore.__init__(self, str, object)
+ for provider_elem in elem.findall('.//provider'):
+ apns = provider_elem.findall('.//apn')
+ if not apns:
+ # Skip carriers with CDMA entries only
+ continue
+ self.append((provider_elem.find('.//name').text, apns))
+
+ def get_row_plans(self, row):
+ return self[row][1]
+
+ def guess_providers_row(self):
+ # Simply return the first entry as the default.
+ return 0
+
+ def search_index_by_code(self, code):
+ for index in range(0, len(self)):
+ if self[index][0] == code:
+ return index
+ return -1
+
+
+class PlanListStore(Gtk.ListStore):
+ LANG_NS_ATTR = '{http://www.w3.org/XML/1998/namespace}lang'
+ LANG = locale.getdefaultlocale()[0][:2]
+ DEFAULT_NUMBER = '*99#'
+
+ def __init__(self, elems):
+ Gtk.ListStore.__init__(self, str, object)
+ for apn_elem in elems:
+ plan = {}
+ names = apn_elem.findall('.//name')
+ if names:
+ for name in names:
+ if name.get(self.LANG_NS_ATTR) is None:
+ # serviceproviders.xml default value
+ plan['name'] = name.text
+ elif name.get(self.LANG_NS_ATTR) == self.LANG:
+ # Great! We found a name value for our locale!
+ plan['name'] = name.text
+ break
+ else:
+ plan['name'] = _('Default')
+ plan['apn'] = apn_elem.get('value')
+ user = apn_elem.find('.//username')
+ if user is not None:
+ plan['username'] = user.text
+ else:
+ plan['username'] = ''
+ passwd = apn_elem.find('.//password')
+ if passwd is not None:
+ plan['password'] = passwd.text
+ else:
+ plan['password'] = ''
+
+ plan['number'] = self.DEFAULT_NUMBER
+
+ self.append((plan['name'], plan))
+
+ def get_row_plan(self, row):
+ return self[row][1]
+
+ def guess_plan_row(self):
+ # Simply return the first entry as the default.
+ return 0
+
+ def search_index_by_code(self, code):
+ for index in range(0, len(self)):
+ if self[index][0] == code:
+ return index
+ return -1
+
+
+def get_gconf_setting_string(gconf_key):
+ client = gconf.client_get_default()
+ return client.get_string(gconf_key) or ''
+
+def set_gconf_setting_string(gconf_key, gconf_setting_string_value):
+ client = gconf.client_get_default()
+ client.set_string(gconf_key, gconf_setting_string_value)
diff --git a/extensions/cpsection/modemconfiguration/view.py b/extensions/cpsection/modemconfiguration/view.py
index d5aa399..d218330 100644
--- a/extensions/cpsection/modemconfiguration/view.py
+++ b/extensions/cpsection/modemconfiguration/view.py
@@ -24,6 +24,10 @@ from sugar3.graphics import style
from jarabe.controlpanel.sectionview import SectionView
+from cpsection.modemconfiguration.config import GSM_COUNTRY_PATH, \
+ GSM_PROVIDERS_PATH, \
+ GSM_PLAN_PATH
+
APPLY_TIMEOUT = 1000
@@ -64,6 +68,17 @@ class ModemConfiguration(SectionView):
self.set_border_width(style.DEFAULT_SPACING)
self.set_spacing(style.DEFAULT_SPACING)
self._group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+ self._combo_group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+
+ scrolled_win = Gtk.ScrolledWindow()
+ scrolled_win.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+ scrolled_win.show()
+ self.add(scrolled_win)
+
+ main_box = Gtk.VBox(spacing=style.DEFAULT_SPACING)
+ main_box.set_border_width(style.DEFAULT_SPACING)
+ main_box.show()
+ scrolled_win.add_with_viewport(main_box)
explanation = _('You will need to provide the following information'
' to set up a mobile broadband connection to a'
@@ -71,41 +86,85 @@ class ModemConfiguration(SectionView):
self._text = Gtk.Label(label=explanation)
self._text.set_line_wrap(True)
self._text.set_alignment(0, 0)
- self.pack_start(self._text, False, False, 0)
+ main_box.pack_start(self._text, False, False, 0)
self._text.show()
+ if model.has_providers_db():
+ self._upper_box = Gtk.VBox(spacing=style.DEFAULT_SPACING)
+ self._upper_box.set_border_width(style.DEFAULT_SPACING)
+ main_box.pack_start(self._upper_box, True, True, 0)
+ self._upper_box.show()
+
+ # Do not attach any 'change'-handlers for now.
+ # They will be attached (once per combobox), once the
+ # individual combobox is processed at startup.
+ self._country_store = model.CountryListStore()
+ self._country_combo = Gtk.ComboBox(model=self._country_store)
+ self._attach_combobox_widget(_('Country:'),
+ self._country_combo)
+
+ self._providers_combo = Gtk.ComboBox()
+ self._attach_combobox_widget(_('Provider:'),
+ self._providers_combo)
+
+ self._plan_combo = Gtk.ComboBox()
+ self._attach_combobox_widget(_('Plan:'),
+ self._plan_combo)
+
+ separator = Gtk.HSeparator()
+ main_box.pack_start(separator, True, True, 0)
+ separator.show()
+
+ self._lower_box = Gtk.VBox(spacing=style.DEFAULT_SPACING)
+ self._lower_box.set_border_width(style.DEFAULT_SPACING)
+ main_box.pack_start(self._lower_box, True, True, 0)
+ self._lower_box.show()
+
self._username_entry = EntryWithLabel(_('Username:'))
- self._username_entry.entry.connect('changed', self.__entry_changed_cb)
- self._group.add_widget(self._username_entry.label)
- self.pack_start(self._username_entry, False, True, 0)
- self._username_entry.show()
+ self._attach_entry_widget(self._username_entry)
self._password_entry = EntryWithLabel(_('Password:'))
- self._password_entry.entry.connect('changed', self.__entry_changed_cb)
- self._group.add_widget(self._password_entry.label)
- self.pack_start(self._password_entry, False, True, 0)
- self._password_entry.show()
+ self._attach_entry_widget(self._password_entry)
self._number_entry = EntryWithLabel(_('Number:'))
- self._number_entry.entry.connect('changed', self.__entry_changed_cb)
- self._group.add_widget(self._number_entry.label)
- self.pack_start(self._number_entry, False, True, 0)
- self._number_entry.show()
+ self._attach_entry_widget(self._number_entry)
self._apn_entry = EntryWithLabel(_('Access Point Name (APN):'))
- self._apn_entry.entry.connect('changed', self.__entry_changed_cb)
- self._group.add_widget(self._apn_entry.label)
- self.pack_start(self._apn_entry, False, True, 0)
- self._apn_entry.show()
+ self._attach_entry_widget(self._apn_entry)
self._pin_entry = EntryWithLabel(_('Personal Identity Number (PIN):'))
- self._pin_entry.entry.connect('changed', self.__entry_changed_cb)
- self._group.add_widget(self._pin_entry.label)
- self.pack_start(self._pin_entry, False, True, 0)
- self._pin_entry.show()
+ self._attach_entry_widget(self._pin_entry)
self.setup()
+ def _attach_combobox_widget(self, label_text, combobox_obj):
+ box = Gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label = Gtk.Label(label_text)
+ self._group.add_widget(label)
+ label.set_alignment(1, 0.5)
+ box.pack_start(label, False, False, 0)
+ label.show()
+
+ self._combo_group.add_widget(combobox_obj)
+ cell = Gtk.CellRendererText()
+ cell.props.xalign = 0.5
+
+ cell.set_property('width-chars', 30)
+
+ combobox_obj.pack_start(cell, True)
+ combobox_obj.add_attribute(cell, 'text', 0)
+
+ box.pack_start(combobox_obj, False, False, 0)
+ combobox_obj.show()
+ self._upper_box.pack_start(box, False, False, 0)
+ box.show()
+
+ def _attach_entry_widget(self, entry_with_label_obj):
+ entry_with_label_obj.entry.connect('changed', self.__entry_changed_cb)
+ self._group.add_widget(entry_with_label_obj.label)
+ self._lower_box.pack_start(entry_with_label_obj, True, True, 0)
+ entry_with_label_obj.show()
+
def undo(self):
self._model.undo()
@@ -113,12 +172,31 @@ class ModemConfiguration(SectionView):
"""Populate an entry with text, without triggering its 'changed'
handler."""
entry = entrywithlabel.entry
- entry.handler_block_by_func(self.__entry_changed_cb)
+
+ # Do not block/unblock the callback functions.
+ #
+ # Thus, the savings will be persisted to the NM settings,
+ # whenever any setting on the UI changes (by user-intervention,
+ # or otherwise).
+ #entry.handler_block_by_func(self.__entry_changed_cb)
entry.set_text(text)
- entry.handler_unblock_by_func(self.__entry_changed_cb)
+ #entry.handler_unblock_by_func(self.__entry_changed_cb)
def setup(self):
- settings = self._model.get_modem_settings()
+ if self._model.has_providers_db():
+ persisted_country = self._model.get_gconf_setting_string(GSM_COUNTRY_PATH)
+ if (self._model.has_providers_db()) and (persisted_country != ''):
+ self._country_combo.set_active(self._country_store.search_index_by_code(persisted_country))
+ else:
+ self._country_combo.set_active(self._country_store.guess_country_row())
+
+ # Call the selected callback anyway, so as to chain-set the
+ # default values for providers and the plans.
+ self.__country_selected_cb(self._country_combo, setup=True)
+
+ self._model.get_modem_settings(self.populate_entries)
+
+ def populate_entries(self, settings):
self._populate_entry(self._username_entry,
settings.get('username', ''))
self._populate_entry(self._number_entry, settings.get('number', ''))
@@ -133,6 +211,78 @@ class ModemConfiguration(SectionView):
self._timeout_sid = GObject.timeout_add(APPLY_TIMEOUT,
self.__timeout_cb)
+ def _get_selected_text(self, combo):
+ active_iter = combo.get_active_iter()
+ return combo.get_model().get(active_iter, 0)[0]
+
+ def __country_selected_cb(self, combo, setup=False):
+ country = self._get_selected_text(combo)
+ self._model.set_gconf_setting_string(GSM_COUNTRY_PATH, country)
+
+ model = combo.get_model()
+ providers = model.get_row_providers(combo.get_active())
+ self._providers_liststore = self._model.ProviderListStore(providers)
+ self._providers_combo.set_model(self._providers_liststore)
+
+ # Set the default provider as well.
+ if setup:
+ persisted_provider = self._model.get_gconf_setting_string(GSM_PROVIDERS_PATH)
+ if persisted_provider == '':
+ self._providers_combo.set_active(self._providers_liststore.guess_providers_row())
+ else:
+ self._providers_combo.set_active(self._providers_liststore.search_index_by_code(persisted_provider))
+ else:
+ self._providers_combo.set_active(self._providers_liststore.guess_providers_row())
+
+ # Country-combobox processed once at startip; now, attach the
+ # change-handler.
+ self._country_combo.connect('changed', self.__country_selected_cb, False)
+
+ # Call the callback, so that default provider may be set.
+ self.__provider_selected_cb(self._providers_combo, setup)
+
+ def __provider_selected_cb(self, combo, setup=False):
+ provider = self._get_selected_text(combo)
+ self._model.set_gconf_setting_string(GSM_PROVIDERS_PATH, provider)
+
+ model = combo.get_model()
+ plans = model.get_row_plans(combo.get_active())
+ self._plan_liststore = self._model.PlanListStore(plans)
+ self._plan_combo.set_model(self._plan_liststore)
+
+ # Set the default plan as well.
+ if setup:
+ persisted_plan = self._model.get_gconf_setting_string(GSM_PLAN_PATH)
+ if persisted_plan == '':
+ self._plan_combo.set_active(self._plan_liststore.guess_plan_row())
+ else:
+ self._plan_combo.set_active(self._plan_liststore.search_index_by_code(persisted_plan))
+ else:
+ self._plan_combo.set_active(self._plan_liststore.guess_plan_row())
+
+ # Providers-combobox processed once at startip; now, attach the
+ # change-handler.
+ self._providers_combo.connect('changed', self.__provider_selected_cb, False)
+
+ # Call the callback, so that the default plan is set.
+ self.__plan_selected_cb(self._plan_combo, setup)
+
+ def __plan_selected_cb(self, combo, setup=False):
+ plan = self._get_selected_text(combo)
+ self._model.set_gconf_setting_string(GSM_PLAN_PATH, plan)
+
+ # Plan-combobox processed once at startip; now, attach the
+ # change-handler.
+ self._plan_combo.connect('changed', self.__plan_selected_cb, False)
+
+ model = combo.get_model()
+ plan = model.get_row_plan(combo.get_active())
+
+ self._populate_entry(self._username_entry, plan['username'])
+ self._populate_entry(self._password_entry, plan['password'])
+ self._populate_entry(self._apn_entry, plan['apn'])
+ self._populate_entry(self._number_entry, plan['number'])
+
def __timeout_cb(self):
self._timeout_sid = 0
settings = {}
diff --git a/extensions/cpsection/network/model.py b/extensions/cpsection/network/model.py
index ae9e64d..83c3cf1 100644
--- a/extensions/cpsection/network/model.py
+++ b/extensions/cpsection/network/model.py
@@ -18,6 +18,10 @@
import logging
import dbus
+import os
+import subprocess
+import logging
+
from gettext import gettext as _
from gi.repository import GConf
@@ -30,6 +34,8 @@ _NM_IFACE = 'org.freedesktop.NetworkManager'
KEYWORDS = ['network', 'jabber', 'radio', 'server']
+_logger = logging.getLogger('ControlPanel - Network')
+
class ReadError(Exception):
def __init__(self, value):
@@ -154,3 +160,14 @@ def set_publish_information(value):
client = GConf.Client.get_default()
client.set_bool('/desktop/sugar/collaboration/publish_gadget', value)
return 0
+
+
+def launch_nm_connection_editor():
+ environment = os.environ.copy()
+ environment['PATH'] = '%s:/usr/sbin' % (environment['PATH'], )
+
+ try:
+ subprocess.Popen(['-c', 'sudo nm-connection-editor --type=802-11-wireless'],
+ shell=True)
+ except:
+ _logger.exception('Error running nm-connection-editor')
diff --git a/extensions/cpsection/network/view.py b/extensions/cpsection/network/view.py
index 9b89375..e4332e4 100644
--- a/extensions/cpsection/network/view.py
+++ b/extensions/cpsection/network/view.py
@@ -17,6 +17,7 @@
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GObject
+from gi.repository import GConf
from gettext import gettext as _
from sugar3.graphics import style
@@ -30,6 +31,9 @@ ICON = 'module-network'
TITLE = _('Network')
_APPLY_TIMEOUT = 3000
+EXPLICIT_REBOOT_MESSAGE = _('Please restart your computer for changes to take effect.')
+
+gconf_client = GConf.Client.get_default()
class Network(SectionView):
@@ -51,6 +55,7 @@ class Network(SectionView):
self._radio_alert_box = Gtk.HBox(spacing=style.DEFAULT_SPACING)
self._jabber_alert_box = Gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._nm_connection_editor_alert_box = Gtk.HBox(spacing=style.DEFAULT_SPACING)
scrolled = Gtk.ScrolledWindow()
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
@@ -178,8 +183,54 @@ class Network(SectionView):
workspace.pack_start(box_mesh, False, True, 0)
box_mesh.show()
+ if gconf_client.get_bool('/desktop/sugar/extensions/network/show_nm_connection_editor') is True:
+ box_nm_connection_editor = self.add_nm_connection_editor_launcher(workspace)
+
self.setup()
+ def add_nm_connection_editor_launcher(self, workspace):
+ separator_nm_connection_editor = Gtk.HSeparator()
+ workspace.pack_start(separator_nm_connection_editor, False, True, 0)
+ separator_nm_connection_editor.show()
+
+ label_nm_connection_editor = Gtk.Label(_('Advanced Network Settings'))
+ label_nm_connection_editor.set_alignment(0, 0)
+ workspace.pack_start(label_nm_connection_editor, False, True, 0)
+ label_nm_connection_editor.show()
+
+ box_nm_connection_editor = Gtk.VBox()
+ box_nm_connection_editor.set_border_width(style.DEFAULT_SPACING * 2)
+ box_nm_connection_editor.set_spacing(style.DEFAULT_SPACING)
+
+ info = Gtk.Label(_("For more specific network settings, use "
+ "the NetworkManager Connection Editor."))
+
+ info.set_alignment(0, 0)
+ info.set_line_wrap(True)
+ box_nm_connection_editor.pack_start(info, False, True, 0)
+
+ self._nm_connection_editor_alert = InlineAlert()
+ self._nm_connection_editor_alert.props.msg = EXPLICIT_REBOOT_MESSAGE
+ self._nm_connection_editor_alert_box.pack_start(self._nm_connection_editor_alert,
+ False, True, 0)
+ box_nm_connection_editor.pack_end(self._nm_connection_editor_alert_box,
+ False, True, 0)
+ self._nm_connection_editor_alert_box.show()
+ self._nm_connection_editor_alert.show()
+
+ launch_button = Gtk.Button()
+ launch_button.set_alignment(0, 0)
+ launch_button.set_label(_('Launch'))
+ launch_button.connect('clicked', self.__launch_button_clicked_cb)
+ box_launch_button = Gtk.HBox()
+ box_launch_button.set_homogeneous(False)
+ box_launch_button.pack_start(launch_button, False, True, 0)
+ box_launch_button.show_all()
+
+ box_nm_connection_editor.pack_start(box_launch_button, False, True, 0)
+ workspace.pack_start(box_nm_connection_editor, False, True, 0)
+ box_nm_connection_editor.show_all()
+
def setup(self):
self._entry.set_text(self._model.get_jabber())
try:
@@ -260,3 +311,7 @@ class Network(SectionView):
self._model.clear_networks()
if not self._model.have_networks():
self._clear_history_button.set_sensitive(False)
+
+ def __launch_button_clicked_cb(self, launch_button):
+ self._model.launch_nm_connection_editor()
+
diff --git a/extensions/deviceicon/Makefile.am b/extensions/deviceicon/Makefile.am
index 96a1753..42f3a4b 100644
--- a/extensions/deviceicon/Makefile.am
+++ b/extensions/deviceicon/Makefile.am
@@ -5,6 +5,7 @@ sugar_PYTHON = \
battery.py \
frame.py \
network.py \
+ resources.py \
speaker.py \
speech.py \
touchpad.py \
diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py
index 79bc764..8ca9077 100644
--- a/extensions/deviceicon/network.py
+++ b/extensions/deviceicon/network.py
@@ -23,6 +23,8 @@ import logging
import hashlib
import socket
import struct
+import random
+import re
import datetime
import time
from gi.repository import Gtk
@@ -30,6 +32,7 @@ import glib
from gi.repository import GObject
from gi.repository import GConf
import dbus
+import uuid
from sugar3.graphics.icon import get_icon_state
from sugar3.graphics import style
@@ -55,6 +58,14 @@ _GSM_STATE_CONNECTING = 2
_GSM_STATE_CONNECTED = 3
_GSM_STATE_FAILED = 4
+_GSM_SHARING_PRIVATE = 0
+_GSM_SHARING_TRYING = 1
+_GSM_SHARING_NEIGHBORHOOD = 2
+
+_GSM_SHARING_CHANNELS = [2,3,4,5,7,8,9,10,12,13]
+
+_wifi_device = None
+
class WirelessPalette(Palette):
__gtype_name__ = 'SugarWirelessPalette'
@@ -200,6 +211,8 @@ class GsmPalette(Palette):
__gsignals__ = {
'gsm-connect': (GObject.SignalFlags.RUN_FIRST, None, ([])),
'gsm-disconnect': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'gsm-private': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'gsm-neighborhood': (GObject.SignalFlags.RUN_FIRST, None, ([])),
}
def __init__(self):
@@ -208,6 +221,7 @@ class GsmPalette(Palette):
self._current_state = None
self._failed_connection = False
+ self._sharing_state = _GSM_SHARING_PRIVATE
self.info_box = Gtk.VBox()
@@ -216,6 +230,11 @@ class GsmPalette(Palette):
self.info_box.pack_start(self._toggle_state_item, True, True, 0)
self._toggle_state_item.show()
+ self._sharing_box = Gtk.VBox()
+ self.info_box.pack_start(self._sharing_box, True, True, 0)
+ self.__update_sharing_toggle_widget(_('Private (Click to share)'), 'zoom-home')
+ self._sharing_box.hide()
+
self.error_title_label = Gtk.Label(label="")
self.error_title_label.set_alignment(0, 0.5)
self.error_title_label.set_line_wrap(True)
@@ -298,6 +317,9 @@ class GsmPalette(Palette):
icon = Icon(icon_name='media-eject', \
icon_size=Gtk.IconSize.MENU)
self._toggle_state_item.set_image(icon)
+ self.sharing_update_text()
+ self._sharing_toggle_item.show()
+ return
elif self._current_state == _GSM_STATE_FAILED:
message_error = self._get_error_by_nm_reason(reason)
@@ -306,6 +328,8 @@ class GsmPalette(Palette):
raise ValueError('Invalid GSM state while updating label and ' \
'text, %s' % str(self._current_state))
+ self._sharing_toggle_item.hide()
+
def __toggle_state_cb(self, menuitem):
if self._current_state == _GSM_STATE_NOT_READY:
pass
@@ -366,6 +390,50 @@ class GsmPalette(Palette):
message_tuple = (network.get_error_by_reason(reason), message)
return message_tuple
+ def sharing_update_text(self):
+ if self._sharing_state == _GSM_SHARING_PRIVATE:
+ self.__update_sharing_toggle_widget(_('Private (Click to share)'), 'zoom-home')
+
+ elif self._sharing_state == _GSM_SHARING_TRYING:
+ self.__update_sharing_toggle_widget(_('Please wait...'), 'zoom-home')
+
+ elif self._sharing_state == _GSM_SHARING_NEIGHBORHOOD:
+ self.__update_sharing_toggle_widget(_('Neighborhood (Click to unshare)'), 'zoom-neighborhood')
+
+ else:
+ raise ValueError('Invalid GSM sharing state while updating, %s' % \
+ str(self._sharing_state))
+
+ def __update_sharing_toggle_widget(self, label, icon_name):
+ for child_widget in self._sharing_box.get_children():
+ self._sharing_box.remove(child_widget)
+
+ self._sharing_toggle_item = PaletteMenuItem('')
+ self._sharing_toggle_item.connect('activate', self.__sharing_toggle_cb)
+ self._sharing_toggle_item.set_label(label)
+ icon = Icon(icon_name=icon_name, icon_size=Gtk.IconSize.MENU)
+ self._sharing_toggle_item.set_image(icon)
+ icon.show()
+ self._sharing_box.pack_start(self._sharing_toggle_item, True, True, 0)
+ separator = PaletteMenuItemSeparator()
+ self._sharing_box.pack_start(separator, True, True, 0)
+ separator.show()
+ self._sharing_box.show_all()
+
+ def __sharing_toggle_cb(self, menuitem):
+ if self._sharing_state == _GSM_SHARING_PRIVATE:
+ self.emit('gsm-neighborhood')
+
+ elif self._sharing_state == _GSM_SHARING_TRYING:
+ pass
+
+ elif self._sharing_state == _GSM_SHARING_NEIGHBORHOOD:
+ self.emit('gsm-private')
+
+ else:
+ raise ValueError('Invalid GSM sharing state, %s' % \
+ str(self._sharing_state))
+
class WirelessDeviceView(ToolButton):
@@ -527,17 +595,8 @@ class WirelessDeviceView(ToolButton):
else:
state = network.NM_DEVICE_STATE_UNKNOWN
- if self._mode != network.NM_802_11_MODE_ADHOC and \
- network.is_sugar_adhoc_network(self._ssid) == False:
- if state == network.NM_DEVICE_STATE_ACTIVATED:
- icon_name = '%s-connected' % 'network-wireless'
- else:
- icon_name = 'network-wireless'
-
- icon_name = get_icon_state(icon_name, self._strength)
- if icon_name:
- self._icon.props.icon_name = icon_name
- else:
+ if self._mode == network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(self._ssid):
channel = network.frequency_to_channel(self._frequency)
if state == network.NM_DEVICE_STATE_ACTIVATED:
self._icon.props.icon_name = 'network-adhoc-%s-connected' \
@@ -729,8 +788,9 @@ class GsmDeviceView(TrayIcon):
def __init__(self, device):
self._connection_time_handler = None
-
self._connection_timestamp = 0
+ self._shared_connection_path = None
+ self._target_dev_path = None
client = GConf.Client.get_default()
color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color'))
@@ -758,6 +818,8 @@ class GsmDeviceView(TrayIcon):
palette.set_group_id('frame')
palette.connect('gsm-connect', self.__gsm_connect_cb)
palette.connect('gsm-disconnect', self.__gsm_disconnect_cb)
+ palette.connect('gsm-neighborhood', self.__gsm_start_sharing_cb)
+ palette.connect('gsm-private', self.__gsm_stop_sharing_cb)
self._palette = palette
@@ -775,42 +837,32 @@ class GsmDeviceView(TrayIcon):
def __gsm_connect_cb(self, palette, data=None):
connection = network.find_gsm_connection()
if connection is not None:
- connection.activate(self._device.object_path,
- reply_handler=self.__connect_cb,
- error_handler=self.__connect_error_cb)
+ network.activate_connection_by_path(connection.get_path(),
+ self._device.object_path,
+ reply_handler=self._connect_cb,
+ error_handler=self._connect_error_cb)
else:
self._palette.add_alert(_('No GSM connection available.'), \
_('Create a connection in the ' \
'control panel.'))
- def __connect_cb(self, active_connection):
+ def _connect_cb(self, active_connection_path):
+ self._base_gsm_connection_path = active_connection_path
logging.debug('Connected successfully to gsm device, %s',
- active_connection)
+ active_connection_path)
- def __connect_error_cb(self, error):
+ 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(network.NM_SERVICE, network.NM_PATH)
- netmgr = dbus.Interface(obj, network.NM_IFACE)
- netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE)
- active_connections_o = netmgr_props.Get(network.NM_IFACE, 'ActiveConnections')
+ network.get_manager().DeactivateConnection(self._base_gsm_connection_path,
+ reply_handler=self._disconnect_cb,
+ error_handler=self._disconnect_error_cb)
- for conn_o in active_connections_o:
- obj = self._bus.get_object(network.NM_IFACE, conn_o)
- props = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
- devices = props.Get(network.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):
+ def _disconnect_cb(self):
logging.debug('Disconnected successfully gsm device')
- def __disconnect_error_cb(self, error):
+ 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):
@@ -831,6 +883,10 @@ class GsmDeviceView(TrayIcon):
gsm_state = _GSM_STATE_CONNECTED
connection = network.find_gsm_connection()
if connection is not None:
+ # Introspect the settings's keys once; else sometimes
+ # the key 'timestamp' gets missed.
+ connection.get_settings('connection').keys()
+
self._connection_timestamp = time.time() - \
connection.get_settings('connection')['timestamp']
self._connection_time_handler = GObject.timeout_add_seconds( \
@@ -879,6 +935,95 @@ class GsmDeviceView(TrayIcon):
self._palette.update_connection_time(connection_time)
return True
+ def __gsm_start_sharing_cb(self, palette):
+ if self._palette._sharing_state == _GSM_SHARING_PRIVATE:
+ logging.debug('GSM will start sharing now')
+ self._palette._sharing_state = _GSM_SHARING_TRYING
+ self._palette.sharing_update_text()
+
+ self._target_device = _wifi_device
+ self._target_device_path = self._target_device.object_path
+
+ client = GConf.Client.get_default()
+ nick = client.get_string('/desktop/sugar/user/nick')
+ nick = re.sub('\W', '', nick)
+
+ name_format = '%s network'
+ format_length = len(name_format) - len('%s')
+ nick_length = 31 - format_length
+ name = name_format % nick[:nick_length]
+
+ connection = network.find_connection_by_ssid(name)
+ if connection == None:
+ settings = network.Settings()
+ settings.connection.id = name
+ settings.connection.uuid = str(uuid.uuid4())
+ settings.connection.type = '802-11-wireless'
+ settings.wireless.ssid = dbus.ByteArray(name)
+ settings.wireless.mode = 'adhoc'
+ settings.wireless.band = 'bg'
+ chosen_channel = random.randrange(len(_GSM_SHARING_CHANNELS))
+ settings.wireless.channel = _GSM_SHARING_CHANNELS[chosen_channel]
+ settings.ip4_config = network.IP4Config()
+ settings.ip4_config.method = 'shared'
+ network.add_and_activate_connection(self._target_device,
+ settings,
+ '/',
+ self._gsm_sharing_ok_cb_for_add_and_activate,
+ self._gsm_sharing_error_cb)
+ else:
+ network.activate_connection_by_path(connection.get_path(),
+ self._target_device,
+ self._gsm_sharing_ok_cb,
+ self._gsm_sharing_error_cb)
+
+ def _gsm_sharing_ok_cb_for_add_and_activate(self,
+ new_connection_path,
+ active_connection_path):
+ self._gsm_sharing_ok_cb(active_connection_path)
+
+ def _gsm_sharing_ok_cb(self, connection_path):
+ logging.debug('GSM sharing is enabled')
+ self._shared_connection_path = connection_path
+ self._bus.add_signal_receiver(self._gsm_sharing_changed_cb,
+ signal_name='StateChanged',
+ path=self._target_device_path,
+ dbus_interface=network.NM_DEVICE_IFACE)
+ self._palette._sharing_state = _GSM_SHARING_NEIGHBORHOOD
+ self._palette.sharing_update_text()
+
+ def _gsm_sharing_changed_cb(self, new_state, old_state, reason):
+ if new_state == network.NM_DEVICE_STATE_DISCONNECTED:
+ self._gsm_sharing_reset()
+
+ def _gsm_sharing_reset(self):
+ logging.debug('GSM sharing is disabled')
+ if self._target_dev_path != None:
+ self._bus.remove_signal_receiver(self._gsm_sharing_changed_cb,
+ signal_name='StateChanged',
+ path=self._target_dev_path,
+ dbus_interface=network.NM_DEVICE_IFACE)
+ self._shared_connection_path = None
+ self._target_dev_path = None
+ self._palette._sharing_state = _GSM_SHARING_PRIVATE
+ self._palette.sharing_update_text()
+
+ def _gsm_sharing_error_cb(self, error):
+ logging.debug('GSM sharing could not start: %s' % str(error))
+ self._gsm_sharing_reset()
+
+ def __gsm_stop_sharing_cb(self, palette):
+ logging.debug('GSM will stop sharing now')
+ network.get_manager().DeactivateConnection(self._shared_connection_path,
+ reply_handler=self._gsm_stop_sharing_ok_cb,
+ error_handler=self._gsm_stop_sharing_error_cb)
+
+ def _gsm_stop_sharing_ok_cb(self):
+ self._gsm_sharing_reset()
+
+ def _gsm_stop_sharing_error_cb(self):
+ logging.debug('GSM sharing could not stop')
+
class WirelessDeviceObserver(object):
def __init__(self, device, tray):
@@ -1056,6 +1201,8 @@ class NetworkManagerObserver(object):
device = WiredDeviceObserver(nm_device, self._tray)
self._devices[device_op] = device
elif device_type == network.NM_DEVICE_TYPE_WIFI:
+ global _wifi_device
+ _wifi_device = nm_device
device = WirelessDeviceObserver(nm_device, self._tray)
self._devices[device_op] = device
elif device_type == network.NM_DEVICE_TYPE_OLPC_MESH:
@@ -1075,5 +1222,10 @@ class NetworkManagerObserver(object):
del self._devices[device_op]
+def get_wifi_device():
+ global _wifi_device
+ return _wifi_device
+
+
def setup(tray):
device_observer = NetworkManagerObserver(tray)
diff --git a/extensions/deviceicon/resources.py b/extensions/deviceicon/resources.py
new file mode 100644
index 0000000..ef4adac
--- /dev/null
+++ b/extensions/deviceicon/resources.py
@@ -0,0 +1,217 @@
+# Copyright (C) Anish Mangal <anishmangal2002@gmail.com>
+#
+# 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 os
+
+from gi.repository import GObject
+from gi.repository import Gtk
+from gi.repository import GConf
+
+from sugar3.graphics.tray import TrayIcon
+from sugar3.graphics.xocolor import XoColor
+from sugar3.graphics.palette import Palette
+from sugar3.graphics import style
+
+
+_SYSTEM_MOODS = ['-sad', '-normal', '-happy']
+_ICON_NAME = 'computer'
+_UPDATE_INTERVAL = 5 * 1000
+
+
+class DeviceView(TrayIcon):
+
+ FRAME_POSITION_RELATIVE = 500
+
+ def __init__(self):
+ client = GConf.Client.get_default()
+ self._color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color)
+ self.create_palette()
+ self._icon_widget.connect('button-release-event', self._click_cb)
+
+ def create_palette(self):
+ self.palette = ResourcePalette(_('System resources'))
+ self.palette.set_group_id('frame')
+ self.palette.add_timer()
+ self.palette.connect('system-mood-changed',
+ self._system_mood_changed_cb)
+ return self.palette
+
+ def _system_mood_changed_cb(self, palette_, mood):
+ self.icon.props.icon_name = _ICON_NAME + mood
+
+ def _click_cb(self, widget, event):
+ self.palette_invoker.notify_right_click()
+
+
+class ResourcePalette(Palette):
+ __gsignals__ = {
+ 'system-mood-changed': (GObject.SignalFlags.RUN_FIRST, None, ([str])),
+ }
+
+ def __init__(self, primary_text):
+ Palette.__init__(self, label=primary_text)
+
+ self.vbox = Gtk.VBox()
+ self.set_content(self.vbox)
+
+ self._cpu_text = Gtk.Label()
+ self.vbox.pack_start(self._cpu_text, True, True, 0)
+ self._cpu_bar = Gtk.ProgressBar()
+ self._cpu_bar.set_size_request(
+ style.zoom(style.GRID_CELL_SIZE * 4), -1)
+ self.vbox.pack_start(self._cpu_bar, True, True, 0)
+
+ self._memory_text = Gtk.Label()
+ self.vbox.pack_start(self._memory_text, True, True, 0)
+ self._memory_bar = Gtk.ProgressBar()
+ self._memory_bar.set_size_request(
+ style.zoom(style.GRID_CELL_SIZE * 4), -1)
+ self.vbox.pack_start(self._memory_bar, True, True, 0)
+
+ self._system_mood = None
+ try:
+ self._cpu_times = self._get_cpu_times_list()
+ except IOError:
+ logging.exception('An error ocurred while attempting to '
+ 'read /proc/stat')
+ self._stop_computing_statistics()
+
+ self.vbox.show()
+ self._cpu_text.show()
+ self._cpu_bar.show()
+ self._memory_text.show()
+ self._memory_bar.show()
+
+ def add_timer(self):
+ GObject.timeout_add(_UPDATE_INTERVAL, self.__timer_cb)
+
+ def _get_cpu_times_list(self):
+ """Return various cpu times as read from /proc/stat
+
+ This method returns the following cpu times measured
+ in jiffies (1/100 of a second for x86 systems)
+ as an ordered list of numbers - [user, nice,
+ system, idle, iowait] where,
+
+ user: normal processes executing in user mode
+ nice: niced processes executing in user mode
+ system: processes executing in kernel mode
+ idle: twiddling thumbs
+ iowait: waiting for I/O to complete
+
+ Note: For systems having 2 or more CPU's, the above
+ numbers would be the cumulative sum of these times
+ for all CPU's present in the system.
+
+ """
+ return [int(count)
+ for count in file('/proc/stat').readline().split()[1:6]]
+
+ def _percentage_cpu_available(self):
+ """
+ Return free CPU resources as a percentage
+
+ """
+ _cpu_times_new = self._get_cpu_times_list()
+ _cpu_times_current = [(new - old)
+ for new, old in zip(_cpu_times_new, self._cpu_times)]
+ user_, nice_, system_, idle, iowait = _cpu_times_current
+ cpu_free = (idle + iowait) * 100.0 / sum(_cpu_times_current)
+ self._cpu_times = self._get_cpu_times_list()
+ return cpu_free
+
+ def _percentage_memory_available(self):
+ """
+ Return free memory as a percentage
+ """
+
+ for line in file('/proc/meminfo'):
+ name, value, unit_ = line.split()[:3]
+ if 'MemTotal:' == name:
+ total = int(value)
+ elif 'MemFree:' == name:
+ free = int(value)
+ elif 'Buffers:' == name:
+ buffers = int(value)
+ elif 'Cached:' == name:
+ cached = int(value)
+ elif 'Active:' == name:
+ break
+ return (free + buffers + cached) * 100.0 / total
+
+ def __timer_cb(self):
+ try:
+ cpu_in_use = 100 - self._percentage_cpu_available()
+ memory_in_use = 100 - self._percentage_memory_available()
+ except IOError:
+ logging.exception('An error ocurred while trying to '
+ 'retrieve resource usage statistics')
+ self._stop_and_show_error()
+ return False
+ else:
+ self._cpu_text.set_label(_('CPU in use: %d%%' % cpu_in_use))
+ self._cpu_bar.set_fraction(float(cpu_in_use) / 100)
+ self._memory_text.set_label(_('Memory in use: %d%%' %
+ memory_in_use))
+ self._memory_bar.set_fraction(float(memory_in_use) / 100)
+
+ # both cpu_free and memory_free lie between 0-100
+ system_mood = _SYSTEM_MOODS[
+ int(300 - (cpu_in_use + 2 * memory_in_use)) // 100]
+
+ # check if self._system_mood exists
+ try:
+ if self._system_mood != system_mood:
+ self.emit('system-mood-changed', system_mood)
+ self._system_mood = system_mood
+ except AttributeError:
+ self.emit('system-mood-changed', system_mood)
+ self._system_mood = system_mood
+
+ return True
+
+ def _stop_and_show_error(self):
+ """
+ Stop computing usage statistics and display an error message
+ since we've hit an exception.
+
+ """
+ # Use the existing _cpu_text label to display the error. Remove
+ # everything else.
+ self._cpu_text.set_size_request(
+ style.zoom(style.GRID_CELL_SIZE * 4), -1)
+ self._cpu_text.set_line_wrap(True)
+ self._cpu_text.set_text(_('Cannot compute CPU and memory usage '
+ 'statistics!'))
+ self.vbox.remove(self._cpu_bar)
+ self.vbox.remove(self._memory_text)
+ self.vbox.remove(self._memory_bar)
+ self.emit('system-mood-changed', '-error')
+
+
+def setup(tray):
+ client = GConf.Client.get_default()
+ if not client.get_bool('/desktop/sugar/frame/show_network_resources'):
+ return
+
+ if not (os.path.exists('/proc/stat') and os.path.exists('/proc/meminfo')):
+ logging.warning('Either /proc/stat or /proc/meminfo not present. Not '
+ 'adding the CPU and memory usage icon to the frame')
+ return
+ tray.add_device(DeviceView())