diff --git a/terminatorconfig.py b/terminatorconfig.py new file mode 100755 index 00000000..0b880fb0 --- /dev/null +++ b/terminatorconfig.py @@ -0,0 +1,233 @@ +#!/usr/bin/python +# TerminatorConfig - layered config classes +# Copyright (C) 2006-2008 cmsj@tenshu.net +# +# 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, version 2 only. +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""TerminatorConfig by Chris Jones + +The config scheme works in layers, with defaults at the base, +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 standard python libs +import os, sys + +# import unix-lib +import pwd + +# set this to true to enable debugging output +debug = True + +def dbg (log = ""): + if debug: + print >> sys.stderr, log + +class TerminatorConfig: + callback = None + sources = [] + + def __init__ (self, sources = []): + for source in sources: + if isinstance(source, TerminatorConfValuestore): + self.sources.append (source) + + # We always add a default valuestore last so no valid config item ever goes unset + source = TerminatorConfValuestoreDefault () + self.sources.append (source) + + def __getattr__ (self, keyname): + dbg ("Config: Looking for: %s"%keyname) + for source in self.sources: + try: + val = getattr (source, keyname) + dbg ("Config: got: %s from a %s"%(val, source.type)) + return (val) + except: + dbg ("Config: no value found in %s."%source.type) + pass + dbg ("Config: Out of sources") + raise (AttributeError) + + def set_reconfigure_callback (self, function): + self.reconfigure_callback = function + return (True) + +class TerminatorConfValuestore: + type = "Base" + values = {} + reconfigure_callback = None + + # Our settings + # FIXME: Is it acceptable to not explicitly store the type, but + # instead infer it from defaults[key].__class__.__name__ + defaults = { + 'gt_dir' : [str, '/apps/gnome-terminal'], + 'profile_dir' : [str, '/apps/gnome-terminal/profiles'], + 'titlebars' : [bool, True], + 'titletips' : [bool, False], + 'allow_bold' : [bool, False], + 'silent_bell' : [bool, True], + 'background_color' : [str, '#000000'], + 'background_darkness' : [float, 0.5], + 'background_type' : [str, 'solid'], + 'backspace_binding' : [str, 'ascii-del'], + 'delete_binding' : [str, 'delete-sequence'], + 'cursor_blink' : [bool, False], + 'emulation' : [str, 'xterm'], + 'font' : [str, 'Serif 10'], + 'foreground_color' : [str, '#AAAAAA'], + 'scrollbar_position' : [str, "right"], + 'scroll_background' : [bool, True], + 'scroll_on_keystroke' : [bool, False], + 'scroll_on_output' : [bool, False], + 'scrollback_lines' : [int, 100], + 'focus' : [str, 'sloppy'], + 'exit_action' : [str, 'close'], + 'palette' : [str, '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF'], + 'word_chars' : [str, '-A-Za-z0-9,./?%&#:_'], + 'mouse_autohide' : [bool, True], + } + + def __getattr__ (self, keyname): + if self.values.has_key (keyname): + return self.values[keyname][1] + else: + raise (AttributeError) + +class TerminatorConfValuestoreDefault (TerminatorConfValuestore): + def __init__ (self): + self.type = "Default" + self.values = self.defaults + +class TerminatorConfValuestoreRC (TerminatorConfValuestore): + rcfilename = "" + #FIXME: use inotify to watch the rc, split __init__ into a parsing function + # that can be re-used when rc changes. + def __init__ (self): + self.type = "RCFile" + self.rcfilename = pwd.getpwuid (os.getuid ())[5] + "/.terminatorrc" + if os.path.exists (self.rcfilename): + rcfile = open (self.rcfilename) + rc = rcfile.readlines () + rcfile.close () + + for item in rc: + try: + item = item.strip () + if item and item[0] != '#': + (key, value) = item.split ("=") + dbg ("VS_RCFile: Setting value %s to %s"%(key, value)) + self.values[key] = [self.defaults[key][0], self.defaults[key][0](value)] + except: + pass + +class TerminatorConfValuestoreGConf (TerminatorConfValuestore): + profile = "" + client = None + + def __init__ (self, profile = None): + self.type = "GConf" + + self.client = gconf.client_get_default () + + # Grab a couple of values from base class to avoid recursing with our __getattr__ + self._gt_dir = self.defaults['gt_dir'][1] + self._profile_dir = self.defaults['profile_dir'][1] + + if not profile: + profile = self.client.get_string (self._gt_dir + '/global/default_profile') + profiles = self.client.get_list (self._gt_dir + '/global/profile_list','string') + + 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 ("No profile found, deleting __getattr__") + del (self.__getattr__) + + 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) + + def on_gconf_notify (self, client, cnxn_id, entry, what): + if self.reconfigure_callback: + self.reconfigure_callback () + + def __getattr__ (self, key = ""): + ret = None + + dbg ('VSGConf: preparing: %s/%s'%(self.profile, key)) + value = self.client.get ('%s/%s'%(self.profile, key)) + dbg ('VSGConf: getting: %s'%value) + if value: + funcname = "get_" + self.defaults[key][0].__name__ + # Special case for str + if funcname == "get_str": + funcname = "get_string" + typefunc = getattr (value, funcname) + ret = typefunc () + + return (ret) + else: + raise (AttributeError) + +if __name__ == '__main__': + + stores = [] + stores.append (TerminatorConfValuestoreRC ()) + + try: + import gconf + stores.append (TerminatorConfValuestoreGConf ()) + except: + pass + + foo = TerminatorConfig (stores) + + ## cmsj: this is my testing ground + ## ensure that font is set in the Default gconf profile + ## set titlebars in the RC file + ## remove titletips from gconf/RC + ## do not define blimnle in any way + + # This should come from gconf (it's set by gnome-terminal) + print foo.font + + # This should come from RC + print foo.titlebars + + # This should come from defaults + print foo.titletips + + # This should raise AttributeError + print foo.blimnle