[Sugar-devel] [sugar PATCH] "Proxy" feature. Wiki page: http://wiki.sugarlabs.org/go/Features/Proxy_Settings

Ajay Garg ajay at activitycentral.com
Sun Feb 17 05:28:04 EST 2013


This patch is to be applied on the master-branch.

Some details of this patch ::

a)
The proxy-settings are stored both in the "gconf" schema; and "dconf"
schema.

b)
The package "gnome-vfs2" is a pre-requisite for this patch to work,
since the settings are (also) stored in the dconf schema.

c)
Also, a slightly unrelated change is the persistence of the values in
the "Network" control-panel.

Earlier, there was a timeout involved (of 3000 milliseconds), which
would trigger the saving of the values (for eg., "Collaboration  Server"
field was persisted this way).

Now, the values  are persisted SYNCHRONOUSLY, when the "tick"
toolbar-button is clicked (of course, all the changes are undone, if the
user instead decides to "Cancel changes").

d)
All the use-cases  of the proxy, are working, as per the wiki page
http://wiki.sugarlabs.org/go/Features/Proxy_Settings

Signed-off-by: Ajay Garg <ajay at activitycentral.com>
---
 extensions/cpsection/network/view.py   | 695 +++++++++++++++++++++++++++++++--
 src/jarabe/controlpanel/gui.py         |   2 +
 src/jarabe/controlpanel/sectionview.py |   8 +
 src/jarabe/main.py                     |  36 ++
 4 files changed, 707 insertions(+), 34 deletions(-)

diff --git a/extensions/cpsection/network/view.py b/extensions/cpsection/network/view.py
index b360759..99b792b 100644
--- a/extensions/cpsection/network/view.py
+++ b/extensions/cpsection/network/view.py
@@ -16,10 +16,19 @@
 
 from gi.repository import Gtk
 from gi.repository import Gdk
+from gi.repository import GConf
 from gi.repository import GObject
+from gi.repository import Gio
+from gi.repository import Pango
 from gettext import gettext as _
 
+import os
+import subprocess
+import logging
+
 from sugar3.graphics import style
+from sugar3.graphics.alert import Alert
+from sugar3.graphics.icon import Icon
 
 from jarabe.controlpanel.sectionview import SectionView
 from jarabe.controlpanel.inlinealert import InlineAlert
@@ -31,6 +40,471 @@ TITLE = _('Network')
 
 _APPLY_TIMEOUT = 3000
 
+# Please refer ::
+# http://developer.gnome.org/ProxyConfiguration/
+
+GSETTINGS_PROXY       = Gio.Settings.new('org.gnome.system.proxy')
+GSETTINGS_PROXY_FTP   = Gio.Settings.new('org.gnome.system.proxy.ftp')
+GSETTINGS_PROXY_HTTP  = Gio.Settings.new('org.gnome.system.proxy.http')
+GSETTINGS_PROXY_HTTPS = Gio.Settings.new('org.gnome.system.proxy.https')
+GSETTINGS_PROXY_SOCKS = Gio.Settings.new('org.gnome.system.proxy.socks')
+
+
+client = GConf.Client.get_default()
+
+
+class GConfMixin(object):
+    """Mix-in class for GTK widgets backed by GConf"""
+    def __init__(self, gconf_key, gsettings_dconf, dconf_key, widget=None, signal='changed'):
+        self._gconf_key = gconf_key
+        self._gsettings_dconf = gsettings_dconf
+        self._dconf_key = dconf_key
+        self._notify_id = client.notify_add(gconf_key, self.__gconf_notify_cb, None)
+        initial_value = self._get_gconf_value()
+        self._undo_value = initial_value
+        self.set_value_from_gconf(initial_value)
+        widget = widget or self
+
+    def undo(self):
+        """Revert to original value if modified"""
+        if not self.changed:
+            return
+        logging.debug('Reverting %r to %r', self._gconf_key, self._undo_value)
+        self._set_gconf_value(self._undo_value)
+
+    def get_value_for_gconf(self):
+        """
+        Return the current value of the widget in a format suitable for GConf
+
+        MUST be implemented by subclasses.
+        """
+        raise NotImplementedError()
+
+    def set_value_from_gconf(self, value):
+        """
+        Set the current value of the widget based on a value from GConf
+        MUST be implemented by subclasses.
+        """
+        raise NotImplementedError()
+
+    def __gconf_notify_cb(self, client, transaction_id_, entry, user_data_):
+        new_value = _gconf_value_to_python(entry.value)
+        self.set_value_from_gconf(new_value)
+
+    def _commit(self, widget):
+        new_value = self.get_value_for_gconf()
+        logging.debug('Setting %r to %r', self._gconf_key, new_value)
+
+        self._set_gconf_value(new_value)
+
+    def _set_gconf_value(self, new_value):
+        gconf_type = client.get(self._gconf_key).type
+        if gconf_type == GConf.ValueType.STRING:
+            client.set_string(self._gconf_key, new_value)
+            self._gsettings_dconf.set_string(self._dconf_key, new_value)
+        elif gconf_type == GConf.ValueType.INT:
+            client.set_int(self._gconf_key, new_value)
+            self._gsettings_dconf.set_int(self._dconf_key, new_value)
+        elif gconf_type == GConf.ValueType.FLOAT:
+            client.set_float(self._gconf_key, new_value)
+            self._gsettings_dconf.set_double(self._dconf_key, new_value)
+        elif gconf_type == GConf.ValueType.BOOL:
+            client.set_bool(self._gconf_key, new_value)
+            self._gsettings_dconf.set_boolean(self._dconf_key, new_value)
+        elif gconf_type == GConf.ValueType.LIST:
+            import traceback
+            list_type = client.get(self._gconf_key).get_list_type()
+
+            # Persisting the value of a "LIST" via shell, unless and
+            # until http://bugs.sugarlabs.org/ticket/3926 gets solved.
+            commit_list = []
+            for value in new_value:
+                translated_value = value.translate(None, "' ")
+                commit_list.append(translated_value)
+
+            environment = os.environ.copy()
+            try:
+                process = subprocess.Popen(['gconftool-2 '
+                                            '--type list '
+                                            '--list-type string '
+                                            '--set %s \'%s\'' % (self._gconf_key,
+                                                                 commit_list)],
+                                            stdout=subprocess.PIPE,
+                                            env=environment,
+                                            shell=True)
+                process.wait()
+
+                self._gsettings_dconf.set_strv(self._dconf_key, new_value)
+            except Exception, e:
+                logging.exception(e)
+            #client.set_list(self._gconf_key, list_type, new_value)
+        else:
+            raise TypeError('Cannot store %r in GConf' % (new_value, ))
+
+    def _get_gconf_value(self):
+        return _gconf_value_to_python(client.get(self._gconf_key))
+
+    def changed(self):
+        return self._undo_value != self.get_value_for_gconf()
+
+
+class GConfEntry(Gtk.Entry, GConfMixin):
+    """Text entry backed by GConf
+
+    It is the callers responsibility to call GConfClient.add_dir() for the
+    GConf directory containing the key.
+    """
+
+    def __init__(self, gconf_key, gsettings_dconf, dconf_key):
+        Gtk.Entry.__init__(self)
+        GConfMixin.__init__(self, gconf_key, gsettings_dconf, dconf_key)
+
+    def get_value_for_gconf(self):
+        return self.props.text
+
+    def set_value_from_gconf(self, value):
+        self.props.text = value
+
+
+class GConfIntegerSpinButton(Gtk.SpinButton, GConfMixin):
+    """Integer SpinButton backed by GConf
+    It is the callers responsibility to call GConfClient.add_dir() for the
+    GConf directory containing the key.
+    """
+
+    def __init__(self, gconf_key, gsettings_dconf, dconf_key, adjustment, climb_rate=0):
+        Gtk.SpinButton.__init__(self, adjustment=adjustment, climb_rate=climb_rate)
+        GConfMixin.__init__(self, gconf_key, gsettings_dconf, dconf_key)
+
+    def get_value_for_gconf(self):
+        return self.get_value_as_int()
+
+    def set_value_from_gconf(self, value):
+        self.set_value(value)
+
+
+class GConfStringListEntry(GConfEntry):
+    """Text entry backed by a GConf list of strings"""
+
+    def __init__(self, gconf_key, gsettings_dconf, dconf_key, separator=','):
+        self._separator = separator
+        GConfEntry.__init__(self, gconf_key, gsettings_dconf, dconf_key)
+
+    def get_value_for_gconf(self):
+        entries = self.props.text.split(self._separator)
+        return [entry for entry in entries if entry]
+
+    def set_value_from_gconf(self, value):
+        self.props.text = self._separator.join(value)
+
+
+class SettingBox(Gtk.HBox):
+    """
+    Base class for "lines" on the screen representing configuration settings
+    """
+
+    def __init__(self, name, size_group=None):
+        Gtk.HBox.__init__(self, spacing=style.DEFAULT_SPACING)
+        self.label = Gtk.Label(name)
+        self.label.modify_fg(Gtk.StateType.NORMAL,
+                             style.COLOR_SELECTION_GREY.get_gdk_color())
+        self.label.set_alignment(1, 0.5)
+        self.label.show()
+        self.pack_start(self.label, False, False, 0)
+
+        if size_group is not None:
+            size_group.add_widget(self.label)
+
+
+class GConfStringSettingBox(SettingBox):
+    """A configuration line for a GConf string setting"""
+
+    def __init__(self, name, gconf_key, gsettings_dconf, dconf_key, size_group=None):
+        SettingBox.__init__(self, name, size_group=size_group)
+        self.string_entry = GConfEntry(gconf_key, gsettings_dconf, dconf_key)
+        self.string_entry.show()
+        self.pack_start(self.string_entry, True, True, 0)
+
+    def undo(self):
+        """Revert to original value if modified"""
+        self.string_entry.undo()
+
+    def _commit(self, widget):
+        self.string_entry._commit(self.string_entry)
+
+    @property
+    def changed(self):
+        return self.string_entry.changed
+
+
+class GConfPasswordSettingBox(GConfStringSettingBox):
+    """A configuration line for a GConf password setting"""
+
+    def __init__(self, name, gconf_key, gsettings_dconf, dconf_key, size_group=None):
+        GConfStringSettingBox.__init__(self, name, gconf_key,
+                                       gsettings_dconf, dconf_key, size_group)
+        self.string_entry.set_visibility(False)
+
+
+class GConfHostListSettingBox(GConfStringSettingBox):
+    """A configuration line for a host list GConf setting"""
+
+    def __init__(self, name, gconf_key, gsettings_dconf, dconf_key, size_group=None):
+        SettingBox.__init__(self, name, size_group=size_group)
+        self.hosts_entry = GConfStringListEntry(gconf_key,
+                                                gsettings_dconf, dconf_key)
+        self.hosts_entry.show()
+        self.pack_start(self.hosts_entry, True, True, 0)
+
+    def undo(self):
+        """Revert to original value if modified"""
+        self.hosts_entry.undo()
+
+    def _commit(self, widget):
+        self.hosts_entry._commit(self.hosts_entry)
+
+    @property
+    def changed(self):
+        return self.hosts_entry.changed
+
+class GConfHostPortSettingBox(SettingBox):
+    """A configuration line for a combined host name and port GConf setting"""
+
+    def __init__(self, name, host_key, port_key, gsettings_dconf,
+                 dconf_host_key, dconf_port_key, size_group=None):
+        SettingBox.__init__(self, name, size_group=size_group)
+        self.host_name_entry = GConfEntry(host_key, gsettings_dconf,
+                                          dconf_host_key)
+        self.host_name_entry.show()
+        self.pack_start(self.host_name_entry, True, True, 0)
+
+        # port number 0 means n/a
+        adjustment = Gtk.Adjustment(0, 0, 65535, 1, 10)
+        self.port_spin_button = GConfIntegerSpinButton(port_key,
+                                                       gsettings_dconf,
+                                                       dconf_port_key,
+                                                       adjustment,
+                                                       climb_rate=0.1)
+        self.port_spin_button.show()
+        self.pack_start(self.port_spin_button, False, False, 0)
+
+    def undo(self):
+        """Revert to original values if modified"""
+        self.host_name_entry.undo()
+        self.port_spin_button.undo()
+
+    def _commit(self, widget):
+        self.host_name_entry._commit(self.host_name_entry)
+        self.port_spin_button._commit(self.port_spin_button)
+
+    @property
+    def changed(self):
+        return self.host_name_entry.changed or self.port_spin_button.changed
+
+
+class ExclusiveOptionSetsBox(Gtk.VBox):
+    """
+    Container for sets of different settings selected by a top-level setting
+    Renders the top level setting as a ComboBox. Only the currently
+    active set is shown on screen.
+    """
+    def __init__(self, top_name, option_sets, size_group=None):
+        """Initialize an ExclusiveOptionSetsBox instance
+
+        Arguments:
+
+        top_name -- text label used for the top-level selection
+        option_sets -- list of tuples containing text label and GTK
+                       widget to display for each of the option sets
+        size_group -- optional gtk.SizeGroup to use for the top-level label
+        """
+        Gtk.VBox.__init__(self, spacing=style.DEFAULT_SPACING)
+        self.label_size_group = size_group
+        top_box = Gtk.HBox(spacing=style.DEFAULT_SPACING)
+        top_box.show()
+        top_label = Gtk.Label(top_name)
+        top_label.modify_fg(Gtk.StateType.NORMAL,
+                            style.COLOR_SELECTION_GREY.get_gdk_color())
+        top_label.set_alignment(1, 0.5)
+        top_label.show()
+        self.label_size_group.add_widget(top_label)
+        top_box.pack_start(top_label, False, False, 0)
+
+        model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_OBJECT)
+        self._top_combo_box = Gtk.ComboBox(model=model)
+        self._top_combo_box.connect('changed', self.__combo_changed_cb)
+        self._top_combo_box.show()
+
+        cell_renderer = Gtk.CellRendererText()
+        cell_renderer.props.ellipsize = Pango.EllipsizeMode.MIDDLE
+        cell_renderer.props.ellipsize_set = True
+        self._top_combo_box.pack_start(cell_renderer, True)
+        self._top_combo_box.add_attribute(cell_renderer, 'text', 0)
+        top_box.pack_start(self._top_combo_box, True, True, 0)
+        self.pack_start(top_box, False, False, 0)
+
+        self._settings_box = Gtk.VBox()
+        self._settings_box.show()
+        self.pack_start(self._settings_box, False, False, 0)
+
+        for name, box in option_sets:
+            model.append((name, box))
+
+    def __combo_changed_cb(self, combobox):
+        giter = combobox.get_active_iter()
+        new_box = combobox.get_model().get(giter, 1)[0]
+        current_box = self._settings_box.get_children()
+        if current_box:
+            self._settings_box.remove(current_box[0])
+
+        self._settings_box.add(new_box)
+        new_box.show()
+
+
+class GConfExclusiveOptionSetsBox(ExclusiveOptionSetsBox, GConfMixin):
+    """
+    Container for sets of GConf settings based on a top-level setting
+    """
+
+    def __init__(self, top_name, top_gconf_key, gsettings_dconf,
+                 dconf_key, option_sets, size_group=None):
+        """Initialize a GConfExclusiveOptionSetsBox instance
+
+        Arguments:
+
+        top_name -- text label used for the top-level selection
+        top_gconf_key -- key for the GConf entry to use for the
+                         top-level selection
+        option_sets -- list of tuples containing text label, matching
+                       GConf value as well as the GTK widget to display
+                       for each of the option sets
+        size_group -- optional gtk.SizeGroup to use for the top-level label
+        """
+        display_sets = [(name, widget) for name, value, widget in option_sets]
+        self._top_mapping = dict([(name, value)
+                                  for name, value, widget in option_sets])
+        ExclusiveOptionSetsBox.__init__(self, top_name, display_sets,
+                                        size_group=size_group)
+        GConfMixin.__init__(self, top_gconf_key, gsettings_dconf,
+                            dconf_key, self._top_combo_box)
+
+    def get_value_for_gconf(self):
+        giter = self._top_combo_box.get_active_iter()
+        if giter is None:
+            return None
+        name = self._top_combo_box.get_model().get(giter, 0)[0]
+        return self._top_mapping[name]
+
+    def set_value_from_gconf(self, value):
+        for idx, (name, widget_) in enumerate(self._top_combo_box.get_model()):
+            if self._top_mapping[name] == value:
+                self._top_combo_box.set_active(idx)
+                return
+
+        raise ValueError('Invalid value %r' % (value, ))
+
+
+class SpecialGConfExclusiveOptionSetsBox(GConfExclusiveOptionSetsBox):
+    def __init__(self, top_name, top_gconf_key, gsettings_dconf,
+                 dconf_key, option_sets, size_group=None):
+        GConfExclusiveOptionSetsBox.__init__(self, top_name, top_gconf_key,
+                                             gsettings_dconf, dconf_key,
+                                             option_sets, size_group)
+        self._initial_enabled_value = True
+        if self._undo_value == 'none':
+            self._initial_enabled_value = False
+
+    def undo(self):
+        """Revert to original value if modified"""
+        if not self.changed:
+            return
+        logging.debug('Reverting %r to %r', self._gconf_key, self._undo_value)
+        self._set_gconf_value(self._undo_value)
+
+        # Also, set the initial value for "org.gnome.system.proxy.http enabled"
+        logging.debug('Reverting org.gnome.system.proxy.http enabled to %r', self._initial_enabled_value)
+        GSETTINGS_PROXY_HTTP.set_boolean('enabled', self._initial_enabled_value)
+
+    def _commit(self, widget):
+        """Commit the base key."""
+        super(SpecialGConfExclusiveOptionSetsBox, self)._commit(None)
+
+        """Plus commit the dconf key :: org.gnome.system.proxy.http enabled"""
+        # The logic to set the value is ::
+        #
+        # * If the mode is "none", the key is to  be set to false.
+        #
+        # * If the mode is not "none" (in other words, it is "manual"
+        #   or "automatic"), the key is to be set to true.
+        enabled_value = True
+        mode = self.get_value_for_gconf()
+        if mode == 'none':
+            enabled_value = False
+
+        logging.debug('Setting org.gnome.system.proxy.http enabled to %r', enabled_value)
+        GSETTINGS_PROXY_HTTP.set_boolean('enabled', enabled_value)
+
+    def changed(self):
+        return self._undo_value != self.get_value_for_gconf()
+
+
+class OptionalSettingsBox(Gtk.VBox):
+    """
+    Container for settings (de)activated by a top-level setting
+
+    Renders the top level setting as a CheckButton. The settings are only
+    shown on screen if the top-level setting is enabled.
+    """
+    def __init__(self, top_name, options):
+        """Initialize an OptionalSettingsBox instance
+        Arguments:
+
+        top_name -- text label used for the top-level selection
+        options -- list of GTK widgets to display for each of the options
+        """
+        Gtk.VBox.__init__(self, spacing=style.DEFAULT_SPACING)
+        self._top_check_button = Gtk.CheckButton()
+        self._top_check_button.props.label = top_name
+        self._top_check_button.connect('toggled', self.__button_changed_cb)
+        self._top_check_button.show()
+        self.pack_start(self._top_check_button, True, True, 0)
+        self._settings_box = Gtk.VBox(spacing=style.DEFAULT_SPACING)
+        for box in options:
+            self._settings_box.pack_start(box, True, True, 0)
+
+    def __button_changed_cb(self, check_button):
+        if check_button.get_active():
+            self._settings_box.show()
+        else:
+            self._settings_box.hide()
+
+
+class GConfOptionalSettingsBox(OptionalSettingsBox, GConfMixin):
+    """
+    Container for GConf settings (de)activated by a top-level setting
+    """
+    def __init__(self, top_name, top_gconf_key, gsettings_dconf,
+                 dconf_key, options):
+        """Initialize a GConfExclusiveOptionSetsBox instance
+        Arguments:
+
+        top_name -- text label used for the top-level selection
+        top_gconf_key -- key for the GConf entry to use for the
+                         top-level selection
+        options -- list of  GTK widgets to display for each of the options
+        """
+        OptionalSettingsBox.__init__(self, top_name, options)
+        GConfMixin.__init__(self, top_gconf_key, gsettings_dconf,
+                            dconf_key, self._top_check_button,
+                            signal='toggled')
+
+    def get_value_for_gconf(self):
+        return self._top_check_button.get_active()
+
+    def set_value_from_gconf(self, value):
+        self._top_check_button.set_active(value)
+        self.pack_start(self._settings_box, False, False, 0)
+
 
 class Network(SectionView):
     def __init__(self, model, alerts):
@@ -44,6 +518,10 @@ class Network(SectionView):
         self._jabber_change_handler = None
         self._radio_change_handler = None
         self._network_configuration_reset_handler = None
+        self._undo_objects = []
+
+        client.add_dir('/system/http_proxy', GConf.ClientPreloadType.PRELOAD_ONELEVEL)
+        client.add_dir('/system/proxy', GConf.ClientPreloadType.PRELOAD_ONELEVEL)
 
         self.set_border_width(style.DEFAULT_SPACING * 2)
         self.set_spacing(style.DEFAULT_SPACING)
@@ -174,10 +652,141 @@ class Network(SectionView):
         workspace.pack_start(box_mesh, False, True, 0)
         box_mesh.show()
 
+        proxy_separator = Gtk.HSeparator()
+        workspace.pack_start(proxy_separator, False, False, 0)
+        proxy_separator.show()
+
+        self._add_proxy_section(workspace)
+
         self.setup()
 
+    def _add_proxy_section(self, workspace):
+        proxy_title = Gtk.Label(_('Proxy'))
+        proxy_title.set_alignment(0, 0)
+        proxy_title.show()
+        workspace.pack_start(proxy_title, False, False, 0)
+
+        proxy_box = Gtk.VBox()
+        proxy_box.set_border_width(style.DEFAULT_SPACING * 2)
+        proxy_box.set_spacing(style.DEFAULT_SPACING)
+        proxy_box.show()
+
+        workspace.pack_start(proxy_box, True, True, 0)
+
+        size_group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+
+        automatic_proxy_box = Gtk.VBox()
+        automatic_proxy_box.set_spacing(style.DEFAULT_SPACING)
+
+        url_box = GConfStringSettingBox(_('Configuration URL:'),
+                                        '/system/proxy/autoconfig_url',
+                                        GSETTINGS_PROXY,
+                                        'autoconfig-url',
+                                        size_group)
+        url_box.show()
+        automatic_proxy_box.pack_start(url_box, True, True, 0)
+        self._undo_objects.append(url_box)
+
+        wpad_help_text = _('Web Proxy Autodiscovery (WPAD) is used when a'
+                           ' Configuration URL is not provided. This is not'
+                           ' recommended for untrusted public networks.')
+        automatic_proxy_help = Gtk.Label(wpad_help_text)
+        automatic_proxy_help.set_alignment(0, 0)
+        automatic_proxy_help.set_line_wrap(True)
+        automatic_proxy_help.show()
+        automatic_proxy_box.pack_start(automatic_proxy_help, True, True, 0)
+
+        manual_proxy_box = Gtk.VBox()
+        manual_proxy_box.set_spacing(style.DEFAULT_SPACING)
+
+        http_box = GConfHostPortSettingBox(_('HTTP Proxy:'),
+                                           '/system/http_proxy/host',
+                                           '/system/http_proxy/port',
+                                            GSETTINGS_PROXY_HTTP,
+                                            'host',
+                                            'port',
+                                           size_group)
+        http_box.show()
+        manual_proxy_box.pack_start(http_box, True, True, 0)
+        self._undo_objects.append(http_box)
+
+        user_name_box = GConfStringSettingBox(_('Username:'),
+            '/system/http_proxy/authentication_user',
+            GSETTINGS_PROXY_HTTP, 'authentication-user', size_group)
+        user_name_box.show()
+        self._undo_objects.append(user_name_box)
+
+        password_box = GConfPasswordSettingBox(_('Password:'),
+            '/system/http_proxy/authentication_password',
+            GSETTINGS_PROXY_HTTP, 'authentication-password', size_group)
+        password_box.show()
+        self._undo_objects.append(password_box)
+
+        auth_box = GConfOptionalSettingsBox(_('Use authentication'),
+            '/system/http_proxy/use_authentication',
+            GSETTINGS_PROXY_HTTP,
+            'use-authentication',
+            [user_name_box, password_box])
+        auth_box.show()
+        manual_proxy_box.pack_start(auth_box, True, True, 0)
+        self._undo_objects.append(auth_box)
+
+        https_box = GConfHostPortSettingBox(_('HTTPS Proxy:'),
+                                            '/system/proxy/secure_host',
+                                            '/system/proxy/secure_port',
+                                            GSETTINGS_PROXY_HTTPS,
+                                            'host',
+                                            'port',
+                                            size_group)
+        https_box.show()
+        manual_proxy_box.pack_start(https_box, True, True, 0)
+        self._undo_objects.append(https_box)
+
+        ftp_box = GConfHostPortSettingBox(_('FTP Proxy:'),
+                                          '/system/proxy/ftp_host',
+                                          '/system/proxy/ftp_port',
+                                          GSETTINGS_PROXY_FTP,
+                                          'host',
+                                          'port',
+                                          size_group)
+        ftp_box.show()
+        manual_proxy_box.pack_start(ftp_box, True, True, 0)
+        self._undo_objects.append(ftp_box)
+
+        socks_box = GConfHostPortSettingBox(_('SOCKS Proxy:'),
+                                            '/system/proxy/socks_host',
+                                            '/system/proxy/socks_port',
+                                            GSETTINGS_PROXY_SOCKS,
+                                            'host',
+                                            'port',
+                                            size_group)
+        socks_box.show()
+        manual_proxy_box.pack_start(socks_box, True, True, 0)
+        self._undo_objects.append(socks_box)
+
+        option_sets = [('None', 'none', Gtk.VBox()),
+                       ('Automatic', 'auto', automatic_proxy_box),
+                       ('Manual', 'manual', manual_proxy_box)]
+        option_sets_box = SpecialGConfExclusiveOptionSetsBox(_('Method:'),
+                                                      '/system/proxy/mode',
+                                                      GSETTINGS_PROXY,
+                                                      'mode',
+                                                      option_sets, size_group)
+        option_sets_box.show()
+        proxy_box.pack_start(option_sets_box, False, False, 0)
+        self._undo_objects.append(option_sets_box)
+
+        no_proxy_box = GConfHostListSettingBox(_('Ignored Hosts'),
+            '/system/http_proxy/ignore_hosts', GSETTINGS_PROXY,
+            'ignore-hosts', size_group)
+        no_proxy_box.show()
+        proxy_box.pack_start(no_proxy_box, False, False, 0)
+        self._undo_objects.append(no_proxy_box)
+
+
     def setup(self):
-        self._entry.set_text(self._model.get_jabber())
+        self._old_jabber_entry = self._model.get_jabber()
+        self._entry.set_text(self._old_jabber_entry)
         try:
             radio_state = self._model.get_radio()
         except self._model.ReadError, detail:
@@ -191,24 +800,42 @@ class Network(SectionView):
         self.needs_restart = False
         self._radio_change_handler = self._button.connect( \
                 'toggled', self.__radio_toggled_cb)
-        self._jabber_change_handler = self._entry.connect( \
-                'changed', self.__jabber_changed_cb)
         self._network_configuration_reset_handler =  \
                 self._clear_history_button.connect( \
                         'clicked', self.__network_configuration_reset_cb)
 
     def undo(self):
         self._button.disconnect(self._radio_change_handler)
-        self._entry.disconnect(self._jabber_change_handler)
         self._model.undo()
         self._jabber_alert.hide()
+        self._entry.set_text(self._old_jabber_entry)
         self._radio_alert.hide()
+        for setting in self._undo_objects:
+            setting.undo()
+
+    # pylint: disable=E0202
+    @property
+    def needs_restart(self):
+        # Some parts of Sugar as well as many non-Gnome applications
+        # use environment variables rather than gconf for proxy
+        # settings, so we need to restart for the changes to take
+        # _full_ effect.
+        if self._entry.get_text() != self._old_jabber_entry:
+            return True
+
+        for setting in self._undo_objects:
+            if setting.changed():
+                return True
 
-    def _validate(self):
-        if self._jabber_valid and self._radio_valid:
-            self.props.is_valid = True
-        else:
-            self.props.is_valid = False
+        return False
+
+    # pylint: disable=E0102,E1101
+    @needs_restart.setter
+    def needs_restart(self, value):
+        # needs_restart is a property (i.e. gets calculated) in this Control
+        # Panel, but SectionView.__init__() wants to initialise it to False,
+        # so we need to provide a (fake) setter.
+        pass
 
     def __radio_toggled_cb(self, widget, data=None):
         radio_state = widget.get_active()
@@ -225,34 +852,34 @@ class Network(SectionView):
         self._validate()
         return False
 
-    def __jabber_changed_cb(self, widget, data=None):
-        if self._jabber_sid:
-            GObject.source_remove(self._jabber_sid)
-        self._jabber_sid = GObject.timeout_add(_APPLY_TIMEOUT,
-                                               self.__jabber_timeout_cb,
-                                               widget)
-
-    def __jabber_timeout_cb(self, widget):
-        self._jabber_sid = 0
-        if widget.get_text() == self._model.get_jabber:
-            return
-        try:
-            self._model.set_jabber(widget.get_text())
-        except self._model.ReadError, detail:
-            self._jabber_alert.props.msg = detail
-            self._jabber_valid = False
-            self._jabber_alert.show()
-            self.restart_alerts.append('jabber')
-        else:
-            self._jabber_valid = True
-            self._jabber_alert.hide()
-
-        self._validate()
-        return False
-
     def __network_configuration_reset_cb(self, widget):
         # FIXME: takes effect immediately, not after CP is closed with
         # confirmation button
         self._model.clear_networks()
         if not self._model.have_networks():
             self._clear_history_button.set_sensitive(False)
+
+    def perform_accept_actions(self):
+        current_jabber_entry = self._entry.get_text()
+        if current_jabber_entry != self._old_jabber_entry:
+            self._model.set_jabber(current_jabber_entry)
+
+        for setting in self._undo_objects:
+            setting._commit(setting)
+
+
+def _gconf_value_to_python(gconf_value):
+    if gconf_value.type == GConf.ValueType.STRING:
+        return gconf_value.get_string()
+    elif gconf_value.type == GConf.ValueType.INT:
+        return gconf_value.get_int()
+    elif gconf_value.type == GConf.ValueType.FLOAT:
+        return gconf_value.get_float()
+    elif gconf_value.type == GConf.ValueType.BOOL:
+        return gconf_value.get_bool()
+    elif gconf_value.type == GConf.ValueType.LIST:
+        return [_gconf_value_to_python(entry)
+                for entry in gconf_value.get_list()]
+    else:
+        raise TypeError("Don't know how to handle GConf value"
+                        " type %r" % (gconf_value.type, ))
diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py
index f28b248..7805bb2 100644
--- a/src/jarabe/controlpanel/gui.py
+++ b/src/jarabe/controlpanel/gui.py
@@ -302,12 +302,14 @@ class ControlPanel(Gtk.Window):
         return options
 
     def __cancel_clicked_cb(self, widget):
+        self._section_view.perform_cancel_actions()
         self._section_view.undo()
         self._options[self._current_option]['alerts'] = []
         self._section_toolbar.accept_button.set_sensitive(True)
         self._show_main_view()
 
     def __accept_clicked_cb(self, widget):
+        self._section_view.perform_accept_actions()
         if self._section_view.needs_restart:
             self._section_toolbar.accept_button.set_sensitive(False)
             self._section_toolbar.cancel_button.set_sensitive(False)
diff --git a/src/jarabe/controlpanel/sectionview.py b/src/jarabe/controlpanel/sectionview.py
index cbf4768..bee64e0 100644
--- a/src/jarabe/controlpanel/sectionview.py
+++ b/src/jarabe/controlpanel/sectionview.py
@@ -52,3 +52,11 @@ class SectionView(Gtk.VBox):
     def undo(self):
         """Undo here the changes that have been made in this section."""
         pass
+
+    def perform_cancel_actions(self):
+        """Perform additional actions, when the "Cancel" button is clicked."""
+        pass
+
+    def perform_accept_actions(self):
+        """Perform additional actions, when the "Ok" button is clicked."""
+        pass
diff --git a/src/jarabe/main.py b/src/jarabe/main.py
index 44880ba..e4893a1 100755
--- a/src/jarabe/main.py
+++ b/src/jarabe/main.py
@@ -230,6 +230,41 @@ def setup_theme():
     icons_path = os.path.join(config.data_path, 'icons')
     Gtk.IconTheme.get_default().append_search_path(icons_path)
 
+def export_proxy_settings():
+    """
+    Export manual proxy settings from GConf as environment variables
+
+    Some applications and tools and even some parts of Sugar will use
+    the http_proxy environment variable if set, but don't use the Gnome
+    (GConf) proxy settings.
+    """
+    client = GConf.Client.get_default()
+
+    # Note: See https://dev.laptop.org.au/issues/1179#note-9
+    if client.get_string('/system/proxy/mode') == 'none':
+        return
+
+    http_host = client.get_string('/system/http_proxy/host')
+    http_port = client.get_int('/system/http_proxy/port')
+    use_auth = client.get_bool('/system/http_proxy/use_authentication')
+    if use_auth:
+        user = client.get_string('/system/http_proxy/authentication_user')
+        pw = client.get_string('/system/http_proxy/authentication_password')
+        http_proxy = 'http://%s:%s@%s:%d/' % (user, pw, http_host, http_port)
+    else:
+        http_proxy = 'http://%s:%d/' % (http_host, http_port)
+
+    os.environ['http_proxy'] = http_proxy
+
+    ignore_hosts = []
+    ignore_hosts_list = client.get('/system/http_proxy/ignore_hosts')
+
+    # Process, only if the "ignore_hosts_list" is non-empty.
+    if ignore_hosts_list:
+        for entry in ignore_hosts_list.get_list():
+            ignore_hosts.append(entry.get_string())
+        os.environ['no_proxy'] = ','.join(ignore_hosts)
+
 def _start_intro():
     window = IntroWindow()
     window.connect('done', __intro_window_done_cb)
@@ -246,6 +281,7 @@ def main():
 
     _start_window_manager()
 
+    export_proxy_settings()
     setup_locale()
     setup_fonts()
     setup_theme()
-- 
1.8.1.2



More information about the Sugar-devel mailing list