Refactor the config. For now it's defaults only
This commit is contained in:
parent
d00e2fe9dd
commit
1f156248ee
|
@ -15,38 +15,12 @@
|
||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
"""TerminatorConfig by Chris Jones <cmsj@tenshu.net>
|
"""Terminator by Chris Jones <cmsj@tenshu.net>
|
||||||
|
|
||||||
The config scheme works in layers, with defaults at the base,
|
Classes relating to configuratoin"""
|
||||||
and a simple/flexible class which can be placed over the top
|
|
||||||
in multiple layers. This was written for Terminator, but
|
|
||||||
could be used generically. Its original use is to guarantee
|
|
||||||
default values for any config item, while allowing them to be
|
|
||||||
overridden by at least two other stores of configuration values.
|
|
||||||
Those being gconf and a plain config file.
|
|
||||||
In addition to the value, the default layer must also provide
|
|
||||||
the datatype (str, int, float and bool are currently supported).
|
|
||||||
values are found as attributes of the TerminatorConfig object.
|
|
||||||
Trying to read a value that doesn't exist will raise an
|
|
||||||
AttributeError. This is by design. If you want to look something
|
|
||||||
up, set a default for it first."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import platform
|
import platform
|
||||||
import sys
|
from borg import Borg
|
||||||
import re
|
|
||||||
import pwd
|
|
||||||
import gtk
|
|
||||||
import pango
|
|
||||||
|
|
||||||
try:
|
|
||||||
import gconf
|
|
||||||
except ImportError:
|
|
||||||
gconf = None
|
|
||||||
|
|
||||||
from terminatorlib import translation
|
|
||||||
from terminatorlib.util import dbg, err
|
|
||||||
from terminatorlib.configfile import ConfigFile, ParsedWithErrors
|
|
||||||
|
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
'gt_dir' : '/apps/gnome-terminal',
|
'gt_dir' : '/apps/gnome-terminal',
|
||||||
|
@ -77,7 +51,12 @@ DEFAULTS = {
|
||||||
'scrollback_lines' : 500,
|
'scrollback_lines' : 500,
|
||||||
'focus' : 'click',
|
'focus' : 'click',
|
||||||
'exit_action' : 'close',
|
'exit_action' : 'close',
|
||||||
'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF',
|
'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:\
|
||||||
|
#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:\
|
||||||
|
#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:\
|
||||||
|
#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:\
|
||||||
|
#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:\
|
||||||
|
#FFFFFFFFFFFF',
|
||||||
'word_chars' : '-A-Za-z0-9,./?%&#:_',
|
'word_chars' : '-A-Za-z0-9,./?%&#:_',
|
||||||
'mouse_autohide' : True,
|
'mouse_autohide' : True,
|
||||||
'update_records' : True,
|
'update_records' : True,
|
||||||
|
@ -166,367 +145,16 @@ DEFAULTS = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Config(Borg, dict):
|
||||||
|
"""Class to provide access to our user configuration"""
|
||||||
|
|
||||||
class TerminatorConfig(object):
|
def __init__(self):
|
||||||
"""This class is used as the base point of the config system"""
|
"""Class initialiser"""
|
||||||
callback = None
|
|
||||||
sources = None
|
|
||||||
_keys = None
|
|
||||||
|
|
||||||
def __init__ (self, sources):
|
Borg.__init__(self)
|
||||||
self.sources = []
|
dict.__init__(self)
|
||||||
|
|
||||||
for source in sources:
|
def __getitem__(self, key):
|
||||||
if isinstance(source, TerminatorConfValuestore):
|
"""Look up a configuration item"""
|
||||||
self.sources.append (source)
|
return(DEFAULTS[key])
|
||||||
|
|
||||||
# We always add a default valuestore last so no valid config item ever
|
|
||||||
# goes unset
|
|
||||||
source = TerminatorConfValuestoreDefault ()
|
|
||||||
self.sources.append (source)
|
|
||||||
|
|
||||||
def _merge_keybindings(self):
|
|
||||||
if self._keys:
|
|
||||||
return self._keys
|
|
||||||
|
|
||||||
self._keys = {}
|
|
||||||
for source in reversed(self.sources):
|
|
||||||
try:
|
|
||||||
val = source['keybindings']
|
|
||||||
self._keys.update(val)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return self._keys
|
|
||||||
|
|
||||||
keybindings = property(_merge_keybindings)
|
|
||||||
|
|
||||||
def __getattr__ (self, keyname):
|
|
||||||
for source in self.sources:
|
|
||||||
dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type))
|
|
||||||
try:
|
|
||||||
val = source[keyname]
|
|
||||||
dbg (" TConfig: got: '%s' from a '%s'"%(val, source.type))
|
|
||||||
return (val)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
dbg (" TConfig: Out of sources")
|
|
||||||
raise (AttributeError)
|
|
||||||
|
|
||||||
class TerminatorConfValuestore(object):
|
|
||||||
type = "Base"
|
|
||||||
values = None
|
|
||||||
reconfigure_callback = None
|
|
||||||
|
|
||||||
def __init__ (self):
|
|
||||||
self.values = {}
|
|
||||||
|
|
||||||
# Our settings
|
|
||||||
def __getitem__ (self, keyname):
|
|
||||||
if self.values.has_key (keyname):
|
|
||||||
value = self.values[keyname]
|
|
||||||
dbg ("Returning '%s':'%s'"%(keyname, value))
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
dbg ("Failed to find '%s'"%keyname)
|
|
||||||
raise (KeyError)
|
|
||||||
|
|
||||||
class TerminatorConfValuestoreDefault (TerminatorConfValuestore):
|
|
||||||
def __init__ (self):
|
|
||||||
TerminatorConfValuestore.__init__ (self)
|
|
||||||
self.type = "Default"
|
|
||||||
self.values = DEFAULTS
|
|
||||||
|
|
||||||
class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
|
||||||
rcfilename = ""
|
|
||||||
type = "RCFile"
|
|
||||||
def __init__ (self):
|
|
||||||
TerminatorConfValuestore.__init__ (self)
|
|
||||||
try:
|
|
||||||
directory = os.environ['XDG_CONFIG_HOME']
|
|
||||||
except KeyError:
|
|
||||||
dbg(" VS_RCFile: XDG_CONFIG_HOME not found. defaulting to ~/.config")
|
|
||||||
directory = os.path.join (os.path.expanduser("~"), ".config")
|
|
||||||
self.rcfilename = os.path.join(directory, "terminator/config")
|
|
||||||
dbg(" VS_RCFile: config file located at %s" % self.rcfilename)
|
|
||||||
self.call_parser(True)
|
|
||||||
|
|
||||||
def set_reconfigure_callback (self, function):
|
|
||||||
dbg (" VS_RCFile: setting callback to: %s"%function)
|
|
||||||
self.reconfigure_callback = function
|
|
||||||
return (True)
|
|
||||||
|
|
||||||
def call_parser (self, is_init = False):
|
|
||||||
dbg (" VS_RCFile: parsing config file")
|
|
||||||
try:
|
|
||||||
ini = ConfigFile(self.rcfilename, self._rc_set_callback())
|
|
||||||
ini.parse()
|
|
||||||
except IOError, ex:
|
|
||||||
dbg (" VS_RCFile: unable to open %s (%r)" % (self.rcfilename, ex))
|
|
||||||
except ParsedWithErrors, ex:
|
|
||||||
# We don't really want to produce an error dialog every run
|
|
||||||
if not is_init:
|
|
||||||
pass
|
|
||||||
msg = _("""<big><b>Configuration error</b></big>
|
|
||||||
|
|
||||||
Errors were encountered while parsing terminator_config(5) file:
|
|
||||||
|
|
||||||
<b>%s</b>
|
|
||||||
|
|
||||||
%d line(s) have been ignored.""") % (self.rcfilename, len(ex.errors))
|
|
||||||
|
|
||||||
dialog = gtk.Dialog(_("Configuration error"), None, gtk.DIALOG_MODAL,
|
|
||||||
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
|
|
||||||
dialog.set_has_separator(False)
|
|
||||||
dialog.set_resizable(False)
|
|
||||||
|
|
||||||
image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING,
|
|
||||||
gtk.ICON_SIZE_DIALOG)
|
|
||||||
image.set_alignment (0.5, 0)
|
|
||||||
dmsg = gtk.Label(msg)
|
|
||||||
dmsg.set_use_markup(True)
|
|
||||||
dmsg.set_alignment(0, 0.5)
|
|
||||||
|
|
||||||
textbuff = gtk.TextBuffer()
|
|
||||||
textbuff.set_text("\n".join(map(lambda ex: str(ex), ex.errors)))
|
|
||||||
textview = gtk.TextView(textbuff)
|
|
||||||
textview.set_editable(False)
|
|
||||||
|
|
||||||
textview.modify_font(pango.FontDescription(DEFAULTS['font']))
|
|
||||||
textscroll = gtk.ScrolledWindow()
|
|
||||||
textscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
|
||||||
textscroll.add(textview)
|
|
||||||
# This should be scaled with the size of the text and font
|
|
||||||
textscroll.set_size_request(600, 200)
|
|
||||||
|
|
||||||
root = gtk.VBox()
|
|
||||||
root.pack_start(dmsg, padding = 6)
|
|
||||||
root.pack_start(textscroll, padding = 6)
|
|
||||||
|
|
||||||
box = gtk.HBox()
|
|
||||||
box.pack_start (image, False, False, 6)
|
|
||||||
box.pack_start (root, False, False, 6)
|
|
||||||
|
|
||||||
vbox = dialog.get_content_area()
|
|
||||||
vbox.pack_start (box, False, False, 12)
|
|
||||||
dialog.show_all()
|
|
||||||
|
|
||||||
dialog.run()
|
|
||||||
dialog.destroy()
|
|
||||||
|
|
||||||
dbg("ConfigFile settings are: %r" % self.values)
|
|
||||||
|
|
||||||
def _rc_set_callback(self):
|
|
||||||
def callback(sections, key, value):
|
|
||||||
dbg("Setting: section=%r with %r => %r" % (sections, key, value))
|
|
||||||
section = None
|
|
||||||
if len(sections) > 0:
|
|
||||||
section = sections[0]
|
|
||||||
if section is None:
|
|
||||||
# handle some deprecated configs
|
|
||||||
if key == 'silent_bell':
|
|
||||||
err ("silent_bell config option is deprecated, for the new bell related config options, see: man terminator_config")
|
|
||||||
if value:
|
|
||||||
self.values['audible_bell'] = False
|
|
||||||
else:
|
|
||||||
self.values['audible_bell'] = True
|
|
||||||
key = 'visible_bell'
|
|
||||||
|
|
||||||
if not DEFAULTS.has_key (key):
|
|
||||||
raise ValueError("Unknown configuration option %r" % key)
|
|
||||||
deftype = DEFAULTS[key].__class__.__name__
|
|
||||||
if key.endswith('_color'):
|
|
||||||
try:
|
|
||||||
gtk.gdk.color_parse(value)
|
|
||||||
self.values[key] = value
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(_("Setting %r value %r not a valid colour; ignoring") % (key, value))
|
|
||||||
elif key == 'tab_position':
|
|
||||||
if value.lower() in ('top', 'left', 'bottom', 'right'):
|
|
||||||
self.values[key] = value.lower()
|
|
||||||
else:
|
|
||||||
raise ValueError(_("%s must be one of: top, left, right, bottom") % key)
|
|
||||||
elif deftype == 'bool':
|
|
||||||
if value.lower () in ('true', 'yes', 'on'):
|
|
||||||
self.values[key] = True
|
|
||||||
elif value.lower () in ('false', 'no', 'off'):
|
|
||||||
self.values[key] = False
|
|
||||||
else:
|
|
||||||
raise ValueError(_("Boolean setting %s expecting one of: yes, no, true, false, on, off") % key)
|
|
||||||
elif deftype == 'int':
|
|
||||||
self.values[key] = int (value)
|
|
||||||
elif deftype == 'float':
|
|
||||||
self.values[key] = float (value)
|
|
||||||
elif deftype == 'list':
|
|
||||||
raise ValueError(_("Reading list values from terminator_config(5) is not currently supported"))
|
|
||||||
elif deftype == 'dict':
|
|
||||||
if type(value) != dict:
|
|
||||||
raise ValueError(_("Setting %r should be a section name") % key)
|
|
||||||
self.values[key] = value
|
|
||||||
else:
|
|
||||||
self.values[key] = value
|
|
||||||
|
|
||||||
dbg (" VS_RCFile: Set value %r to %r" % (key, self.values[key]))
|
|
||||||
elif section == 'keybindings':
|
|
||||||
self.values.setdefault(section, {})
|
|
||||||
if not DEFAULTS[section].has_key(key):
|
|
||||||
raise ValueError("Keybinding name %r is unknown" % key)
|
|
||||||
else:
|
|
||||||
self.values[section][key] = value
|
|
||||||
else:
|
|
||||||
raise ValueError("Section name %r is unknown" % section)
|
|
||||||
return callback
|
|
||||||
|
|
||||||
class TerminatorConfValuestoreGConf (TerminatorConfValuestore):
|
|
||||||
profile = ""
|
|
||||||
client = None
|
|
||||||
cache = None
|
|
||||||
notifies = None
|
|
||||||
|
|
||||||
def __init__ (self, profileName = None):
|
|
||||||
TerminatorConfValuestore.__init__ (self)
|
|
||||||
self.type = "GConf"
|
|
||||||
self.inactive = False
|
|
||||||
self.cache = {}
|
|
||||||
self.notifies = {}
|
|
||||||
|
|
||||||
import gconf
|
|
||||||
|
|
||||||
self.client = gconf.client_get_default ()
|
|
||||||
|
|
||||||
# Grab a couple of values from base class to avoid recursing with our __getattr__
|
|
||||||
self._gt_dir = DEFAULTS['gt_dir']
|
|
||||||
self._profile_dir = DEFAULTS['profile_dir']
|
|
||||||
|
|
||||||
dbg ('VSGConf: Profile bet on is: "%s"'%profileName)
|
|
||||||
profiles = self.client.get_list (self._gt_dir + '/global/profile_list','string')
|
|
||||||
dbg ('VSGConf: Found profiles: "%s"'%profiles)
|
|
||||||
|
|
||||||
dbg ('VSGConf: Profile requested is: "%s"'%profileName)
|
|
||||||
if not profileName:
|
|
||||||
profile = self.client.get_string (self._gt_dir + '/global/default_profile')
|
|
||||||
else:
|
|
||||||
profile = profileName
|
|
||||||
# In newer gnome-terminal, the profile keys are named Profile0/1 etc.
|
|
||||||
# We have to match using visible_name instead
|
|
||||||
for p in profiles:
|
|
||||||
profileName2 = self.client.get_string (
|
|
||||||
self._profile_dir + '/' + p + '/visible_name')
|
|
||||||
if profileName == profileName2:
|
|
||||||
profile = p
|
|
||||||
|
|
||||||
#need to handle the list of Gconf.value
|
|
||||||
if profile in profiles:
|
|
||||||
dbg (" VSGConf: Found profile '%s' in profile_list"%profile)
|
|
||||||
self.profile = '%s/%s' % (self._profile_dir, profile)
|
|
||||||
elif "Default" in profiles:
|
|
||||||
dbg (" VSGConf: profile '%s' not found, but 'Default' exists" % profile)
|
|
||||||
self.profile = '%s/%s'%(self._profile_dir, "Default")
|
|
||||||
else:
|
|
||||||
# We're a bit stuck, there is no profile in the list
|
|
||||||
# FIXME: Find a better way to handle this than setting a non-profile
|
|
||||||
dbg ("VSGConf: No profile found, marking inactive")
|
|
||||||
self.inactive = True
|
|
||||||
return
|
|
||||||
|
|
||||||
#set up the active encoding list
|
|
||||||
self.active_encodings = self.client.get_list (self._gt_dir + '/global/active_encodings', 'string')
|
|
||||||
|
|
||||||
self.client.add_dir (self.profile, gconf.CLIENT_PRELOAD_RECURSIVE)
|
|
||||||
if self.on_gconf_notify:
|
|
||||||
self.client.notify_add (self.profile, self.on_gconf_notify)
|
|
||||||
|
|
||||||
self.client.add_dir ('/apps/metacity/general', gconf.CLIENT_PRELOAD_RECURSIVE)
|
|
||||||
self.client.notify_add ('/apps/metacity/general/focus_mode', self.on_gconf_notify)
|
|
||||||
self.client.add_dir ('/desktop/gnome/interface', gconf.CLIENT_PRELOAD_RECURSIVE)
|
|
||||||
self.client.notify_add ('/desktop/gnome/interface/monospace_font_name', self.on_gconf_notify)
|
|
||||||
# FIXME: Do we need to watch more non-profile stuff here?
|
|
||||||
|
|
||||||
def set_reconfigure_callback (self, function):
|
|
||||||
dbg (" VSConf: setting callback to: %s"%function)
|
|
||||||
self.reconfigure_callback = function
|
|
||||||
return (True)
|
|
||||||
|
|
||||||
def on_gconf_notify (self, client, cnxn_id, entry, what):
|
|
||||||
dbg (" VSGConf: invalidating cache")
|
|
||||||
self.cache = {}
|
|
||||||
dbg (" VSGConf: gconf changed, may run a callback. %s, %s"%(entry.key, entry.value))
|
|
||||||
if entry.key[-12:] == 'visible_name':
|
|
||||||
dbg (" VSGConf: only a visible_name change, ignoring")
|
|
||||||
return False
|
|
||||||
if self.reconfigure_callback:
|
|
||||||
dbg (" VSGConf: callback is: %s"%self.reconfigure_callback)
|
|
||||||
self.reconfigure_callback ()
|
|
||||||
|
|
||||||
def __getitem__ (self, key = ""):
|
|
||||||
if self.inactive:
|
|
||||||
raise KeyError
|
|
||||||
|
|
||||||
if self.cache.has_key (key):
|
|
||||||
dbg (" VSGConf: returning cached value: %s"%self.cache[key])
|
|
||||||
return (self.cache[key])
|
|
||||||
|
|
||||||
ret = None
|
|
||||||
value = None
|
|
||||||
|
|
||||||
dbg (' VSGConf: preparing: %s/%s'%(self.profile, key))
|
|
||||||
|
|
||||||
# FIXME: Ugly special cases we should look to fix in some other way.
|
|
||||||
if key == 'font' and self['use_system_font']:
|
|
||||||
value = self.client.get ('/desktop/gnome/interface/monospace_font_name')
|
|
||||||
elif key == 'focus':
|
|
||||||
value = self.client.get ('/apps/metacity/general/focus_mode')
|
|
||||||
elif key == 'http_proxy':
|
|
||||||
if self.client.get_bool ('/system/http_proxy/use_http_proxy'):
|
|
||||||
dbg ('HACK: Mangling http_proxy')
|
|
||||||
|
|
||||||
if self.client.get_bool ('/system/http_proxy/use_authentication'):
|
|
||||||
dbg ('HACK: Using proxy authentication')
|
|
||||||
value = 'http://%s:%s@%s:%s/' % (
|
|
||||||
self.client.get_string ('/system/http_proxy/authentication_user'),
|
|
||||||
self.client.get_string ('/system/http_proxy/authentication_password'),
|
|
||||||
self.client.get_string ('/system/http_proxy/host'),
|
|
||||||
self.client.get_int ('/system/http_proxy/port'))
|
|
||||||
else:
|
|
||||||
dbg ('HACK: Not using proxy authentication')
|
|
||||||
value = 'http://%s:%s/' % (
|
|
||||||
self.client.get_string ('/system/http_proxy/host'),
|
|
||||||
self.client.get_int ('/system/http_proxy/port'))
|
|
||||||
elif key == 'cursor_blink':
|
|
||||||
tmp = self.client.get_string('%s/cursor_blink_mode' % self.profile)
|
|
||||||
if tmp in ['on', 'off'] and self.notifies.has_key ('cursor_blink'):
|
|
||||||
self.client.notify_remove (self.notifies['cursor_blink'])
|
|
||||||
del (self.notifies['cursor_blink'])
|
|
||||||
if tmp == 'on':
|
|
||||||
value = True
|
|
||||||
elif tmp == 'off':
|
|
||||||
value = False
|
|
||||||
elif tmp == 'system':
|
|
||||||
value = self.client.get_bool ('/desktop/gnome/interface/cursor_blink')
|
|
||||||
self.notifies['cursor_blink'] = self.client.notify_add ('/desktop/gnome/interface/cursor_blink', self.on_gconf_notify)
|
|
||||||
else:
|
|
||||||
value = self.client.get ('%s/%s'%(self.profile, key))
|
|
||||||
else:
|
|
||||||
value = self.client.get ('%s/%s'%(self.profile, key))
|
|
||||||
|
|
||||||
if value != None:
|
|
||||||
from types import StringType, BooleanType
|
|
||||||
if type(value) in [StringType, BooleanType]:
|
|
||||||
ret = value
|
|
||||||
else:
|
|
||||||
funcname = "get_" + DEFAULTS[key].__class__.__name__
|
|
||||||
dbg (' GConf: picked function: %s'%funcname)
|
|
||||||
# Special case for str
|
|
||||||
if funcname == "get_str":
|
|
||||||
funcname = "get_string"
|
|
||||||
# Special case for strlist
|
|
||||||
if funcname == "get_strlist":
|
|
||||||
funcname = "get_list"
|
|
||||||
typefunc = getattr (value, funcname)
|
|
||||||
ret = typefunc ()
|
|
||||||
|
|
||||||
self.cache[key] = ret
|
|
||||||
return (ret)
|
|
||||||
else:
|
|
||||||
raise (KeyError)
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue