Продолжу рассказ о создании апплета к панели GNOME. В прошлый раз я написал "костяк" апплета, который ничего не делает. Сегодня на его основе напишу апплет, который уже чем-то будет полезен.
GConf
Напомню, что конечная цель - создание апплета для переключения прокси (вкл/выкл). Для этих целей есть диалог "Параметры прокси-серверов" (Система->Параметры->Сервис Прокси), но им не удобно пользоваться, поскольку нужно совершать много лишних действий.
Сам диалог настройки ничего не делает, он лишь изменяет значение соответствующего параметра в GConf (/system/proxy/mode
), а программы, использующие прокси, следят за значением этого параметра. Поэтому, для того чтобы включить или выключить прокси, апплету достаточно изменить нужное значение в GConf.
У GConf есть как положительные, так и отрицательные стороны. Из положительных хочу отметить такие приятные вещи как:
- информирование всех "подписанных" на параметр программ об изменении его значения (внешне это выглядит так: открываете диалог "Параметры прокси-серверов", меняете при помощи Python значение нужного параметра и переключатель в диалоге сам "прыгает" на нужную позицию)
- глобальность изменения: не нужно думать о том, что нужно еще проставить значение переменной
http_proxy
для wget
- GConf делает это автоматически. Ну а то, что все GNOME-программы берут настройки о использовании/не использовании прокси из GConf, я думаю, понятно без объяснений.
Из отрицательных я выделяю:
- gconfd "гадит" в журналы. Мне не нравится, что gconfd пишет в общесистемный журнал (
/var/log/messages
) на русском языке.
- несогласованность настроек различных программ. Например, Gajim и Firefox используют собственные настройки соединений. И если для Gajim можно найти оправдание (поддержка нескольких аккаунтов), то для Firefox я не вижу причин игнорировать GConf. Хотя для Gajim адекватен был бы выбор между "использовать глобальные настройки Gnome" и "использовать отдельные настройки для соединений".
- невозможность (или просто я не знаю о таком) изменить настройки только для отдельных приложений
Прежде чем писать код, советую "поиграться" в интерактивной сессии (в качестве индикатора о смене настроек можно использовать диалог "Параметры прокси-сервера", либо редактор `gconf-editor`, но на мой взгляд, диалог прокси более показателен)
>>> import gconf
>>> gc = gconf.client_get_default()
>>> gc.get_string('/system/proxy/mode')
'none'
>>> gc.set_string('/system/proxy/mode', 'manual')
True
>>> gc.get_string()
'manual'
Что происходит, если попытаться получить значение несуществующего ключа, или извлечь значение не того типа, предлагаю изучить самостоятельно, вооружившись gconf-editor
и Python.
Что касается возможных значений ключа ‘/system/proxy/mode’, то допустимые значения здесь таковы: none
, manual
, auto
. Если значение не входит в список разрешенных, оно интерпретируется как none
.
Последняя оговорка и можно приступать к написанию кода: для того, чтобы GConf сообщал об изменении того или иного ключа, нужно "подгрузить" одну из веток GConf для "прослушки" и добавить callback-функцию на изменение нужного ключа.
В коде изложено всё вышесказанное:
class ProxyGconfClient(object):
"""Get/set proxy states"""
proxy_dir = "/system/proxy"
proxy_key = "/system/proxy/mode"
on_state = 'manual'
off_state = 'none'
def __init__(self, callback=None):
"""
GConf client for getting/setting proxy states
@param callback: callback function. Executing
when proxy state changed. It calls with params:
* client - GConf client
* cnxn_id - connection ID
* entry - changed entry
* params - additional params
@type callback: callable
"""
if callback is None:
callback = lambda client, cnxn_id, entry, params: None
# make connection to GConfD
self.client = gconf.client_get_default()
# add proxy_dir for inspection, without preload
self.client.add_dir(self.proxy_dir,
gconf.CLIENT_PRELOAD_NONE)
# add callback for notifying about changes
self.client.notify_add(self.proxy_key,
callback)
def get_state(self):
"""Returns state of proxy"""
return self.client.get_string(self.proxy_key)
def set_state(self, value):
"""
Set state of proxy
@param value: state of proxy, may be
* 'none' - direct connection, proxy off
* 'manual' - manual settings, proxy on
* 'auto' - auto settings, proxy on
if value neither 'manual', no 'auto', it means
direct connection, i.e. proxy off.
@raise RuntimeError: cannot set value to GConf's key
"""
if not self.client.set_string(self.proxy_key,
value):
raise RuntimeError("Unable to change key %s" %
self.proxy_key)
def on(self):
"""Turn proxy on (i.e. set proxy mode 'manual')"""
self.set_state(self.on_state)
def is_on(self):
"""Is proxy on? (i.e. proxy in 'manual' mode)"""
return self.get_state() == self.on_state
def off(self):
"""Turn proxy off (i.e. set direct connection)"""
self.set_state(self.off_state)
Заполнение скелета
Собственно для реализации работающего апплета почти всё готово: скелет апплета, объект-переключатель -
class ProxyGnomeApplet(GnomeAppletSkeleton):
def after_init(self):
self.proxy = ProxyGconfClient(callback=self._cb_proxy_change)
self.proxy_state = self.proxy.get_state()
self.button_actions[1] = self.switch_proxy
self.label.set_text(self.proxy_state)
def _cb_proxy_change(self, client, cnxn_id, entry, params):
"""Callback for changing proxy"""
self.proxy_state = self.proxy.get_state()
self.label.set_text(self.proxy_state)
def on_enter(self, widget, event):
info = "Proxy mode: %s" % self.proxy_state
self.tooltips.set_tip(self.ev_box, info)
def switch_proxy(self):
if self.proxy.is_on():
self.proxy.off()
else:
self.proxy.on()
Поясняю написанное:
Во-первых, напоминаю, что after_init
специально создавался в скелете для переопределения в потомках, так что это правильное место для добавления прокси-переключателя (атрибут proxy
), определения действия на левую кнопку мыши (button_actions[1]
) и установки начального текста для label
.
Во-вторых, в качестве callback-функции, которая выполняется при смене состояния ключа GConf, я использую _cb_proxy_change
(сигнатура этой функции такова: GConf-клиент, идентификатор соединения, измененный ключ, дополнительные параметры). По идее, идеологически более правильно здесь использовать конструкцию entry.get_value().get_string()
, но мне не нравится такой стиль записи, он не Pythonic. Поэтому я использую информацию от объекта прокси-переключателя.
Далее, переопределенная callback-функция on_enter
, теперь она показывает состояние прокси, а не просто "Привет мир".
Ну и последний метод - switch_proxy
- выполняется по нажатию левой кнопки, переключает состояние прокси.
Действия по регистрации этого, уже работающего, апплета абсолютно аналогичны таковым для скелета, так что я не описываю их. Рабочий код можете взять отсюда.
Фактически, апплет уже функционирует, однако выглядит он не притязательно. В следующей части буду "шлифовать" внешний вид.