diff options
author | Ajay Garg <ajay@activitycentral.com> | 2012-10-29 13:08:35 (GMT) |
---|---|---|
committer | Ajay Garg <ajay@activitycentral.com> | 2012-10-29 13:10:12 (GMT) |
commit | 2d204f6e449af15462bdbf24a1295b650061d8ac (patch) | |
tree | 37a196f5c1aa2ccec1a4c13a8b946ec921c33c45 | |
parent | f20eb8551afd90b784ca5876a780b68bc4d1bdfc (diff) |
Proxy
Signed-off-by: Ajay Garg <ajay@activitycentral.com>
-rwxr-xr-x | bin/sugar-session | 29 | ||||
-rw-r--r-- | extensions/cpsection/network/view.py | 558 |
2 files changed, 584 insertions, 3 deletions
diff --git a/bin/sugar-session b/bin/sugar-session index 05493f2..0977b1f 100755 --- a/bin/sugar-session +++ b/bin/sugar-session @@ -289,6 +289,35 @@ def setup_accessibility_cb(): accessibility_manager = accessibility.AccessibilityManager() accessibility_manager.setup_accessibility() +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 = client.get_list('/system/http_proxy/ignore_hosts', + gconf.VALUE_STRING) + os.environ['no_proxy'] = ','.join(ignore_hosts) + def main(): try: diff --git a/extensions/cpsection/network/view.py b/extensions/cpsection/network/view.py index e4332e4..9b34337 100644 --- a/extensions/cpsection/network/view.py +++ b/extensions/cpsection/network/view.py @@ -16,10 +16,15 @@ from gi.repository import Gtk from gi.repository import Gdk -from gi.repository import GObject from gi.repository import GConf +from gi.repository import GObject from gettext import gettext as _ +import os +import subprocess +import pango +import logging + from sugar3.graphics import style from jarabe.controlpanel.sectionview import SectionView @@ -33,7 +38,399 @@ TITLE = _('Network') _APPLY_TIMEOUT = 3000 EXPLICIT_REBOOT_MESSAGE = _('Please restart your computer for changes to take effect.') -gconf_client = GConf.Client.get_default() +client = GConf.Client.get_default() + + +class GConfMixin(object): + """Mix-in class for GTK widgets backed by GConf""" + def __init__(self, gconf_key, widget=None, signal='changed'): + self._timeout_id = None + self._gconf_key = gconf_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 + widget.connect(signal, self.__changed_cb) + + 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 __changed_cb(self, widget): + if self._timeout_id is not None: + GObject.source_remove(self._timeout_id) + self._timeout_id = GObject.timeout_add(_APPLY_TIMEOUT, self._commit, + widget) + + 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) + + widget.handler_block_by_func(self.__changed_cb) + try: + self._set_gconf_value(new_value) + finally: + widget.handler_unblock_by_func(self.__changed_cb) + + 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) + elif gconf_type == GConf.ValueType.INT: + client.set_int(self._gconf_key, new_value) + elif gconf_type == GConf.ValueType.FLOAT: + client.set_float(self._gconf_key, new_value) + elif gconf_type == GConf.ValueType.BOOL: + client.set_bool(self._gconf_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() + 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): + Gtk.Entry.__init__(self) + GConfMixin.__init__(self, gconf_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, adjustment, climb_rate=0): + Gtk.SpinButton.__init__(self, adjustment=adjustment, climb_rate=climb_rate) + GConfMixin.__init__(self, gconf_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, separator=','): + self._separator = separator + GConfEntry.__init__(self, gconf_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, size_group=None): + SettingBox.__init__(self, name, size_group=size_group) + self.string_entry = GConfEntry(gconf_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() + + @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, size_group=None): + GConfStringSettingBox.__init__(self, name, gconf_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, size_group=None): + SettingBox.__init__(self, name, size_group=size_group) + self.hosts_entry = GConfStringListEntry(gconf_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() + + @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, size_group=None): + SettingBox.__init__(self, name, size_group=size_group) + self.host_name_entry = GConfEntry(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, 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() + + @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.ELLIPSIZE_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, 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, 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 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, 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, 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): @@ -48,6 +445,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) @@ -183,7 +584,13 @@ 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: + proxy_separator = Gtk.HSeparator() + workspace.pack_start(proxy_separator, False, False, 0) + proxy_separator.show() + + self._add_proxy_section(workspace) + + if 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() @@ -231,6 +638,109 @@ class Network(SectionView): workspace.pack_start(box_nm_connection_editor, False, True, 0) box_nm_connection_editor.show_all() + + 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', + 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', + 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', size_group) + user_name_box.show() + self._undo_objects.append(user_name_box) + + password_box = GConfPasswordSettingBox(_('Password:'), + '/system/http_proxy/authentication_password', size_group) + password_box.show() + self._undo_objects.append(password_box) + + auth_box = GConfOptionalSettingsBox(_('Use authentication'), + '/system/http_proxy/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', + 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', + 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', + 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 = GConfExclusiveOptionSetsBox(_('Method:'), + '/system/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', 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()) try: @@ -258,6 +768,29 @@ class Network(SectionView): self._model.undo() self._jabber_alert.hide() 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. + for setting in self._undo_objects: + if setting.changed: + return True + + 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 _validate(self): if self._jabber_valid and self._radio_valid: @@ -302,6 +835,9 @@ class Network(SectionView): self._jabber_valid = True self._jabber_alert.hide() + for setting in self._undo_objects: + setting.undo() + self._validate() return False @@ -315,3 +851,19 @@ class Network(SectionView): def __launch_button_clicked_cb(self, launch_button): self._model.launch_nm_connection_editor() + +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, )) |