terminator/terminatorlib/config.py

400 lines
14 KiB
Python
Raw Normal View History

2008-03-08 00:04:41 +00:00
#!/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 <cmsj@tenshu.net>
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 os, platform, sys, re
2008-03-08 00:04:41 +00:00
import pwd
# set this to true to enable debugging output
# These should be moved somewhere better.
debug = False
2008-03-08 00:04:41 +00:00
def dbg (log = ""):
2008-07-22 11:03:30 +00:00
"""Print a message if debugging is enabled"""
2008-03-08 00:04:41 +00:00
if debug:
print >> sys.stderr, log
2008-06-16 23:10:44 +00:00
def err (log = ""):
2008-07-22 11:03:30 +00:00
"""Print an error message"""
2008-06-16 23:10:44 +00:00
print >> sys.stderr, log
2008-03-08 00:04:41 +00:00
from configfile import ConfigFile, ConfigSyntaxError
2008-08-15 12:40:10 +00:00
Defaults = {
'gt_dir' : '/apps/gnome-terminal',
'profile_dir' : '/apps/gnome-terminal/profiles',
'titlebars' : True,
'titletips' : False,
'allow_bold' : True,
'silent_bell' : True,
'background_color' : '#000000',
'background_darkness' : 0.5,
'background_type' : 'solid',
'background_image' : '',
'backspace_binding' : 'ascii-del',
'delete_binding' : 'delete-sequence',
'cursor_blink' : True,
'emulation' : 'xterm',
'font' : 'Mono 8',
'foreground_color' : '#AAAAAA',
'scrollbar_position' : "right",
'scroll_background' : True,
'scroll_on_keystroke' : True,
'scroll_on_output' : True,
'scrollback_lines' : 500,
'focus' : 'click',
'exit_action' : 'close',
'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF',
'word_chars' : '-A-Za-z0-9,./?%&#:_',
'mouse_autohide' : True,
'update_records' : True,
'login_shell' : False,
'use_custom_command' : False,
'custom_command' : '',
'use_system_font' : True,
'use_theme_colors' : False,
'http_proxy' : '',
'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'],
'encoding' : 'UTF-8',
'active_encodings' : ['UTF-8', 'ISO-8859-1'],
'extreme_tabs' : False,
'fullscreen' : False,
'borderless' : False,
'maximise' : False,
'handle_size' : -1,
'focus_on_close' : 'auto',
'f11_modifier' : False,
'force_no_bell' : False,
'cycle_term_tab' : True,
'copy_on_selection' : False,
'close_button_on_tab' : True,
'enable_real_transparency' : False,
'try_posix_regexp' : platform.system() != 'Linux',
'keybindings' : {
'zoom_in' : '<Ctrl>plus',
'zoom_out' : '<Ctrl>minus',
'zoom_normal' : '<Ctrl>0',
'new_root_tab' : '<Ctrl><Shift><Alt>T',
'new_tab' : '<Ctrl><Shift>T',
'go_next' : '<Ctrl><Shift>N',
'go_prev' : '<Ctrl><Shift>P',
'split_horiz' : '<Ctrl><Shift>O',
'split_vert' : '<Ctrl><Shift>E',
'close_term' : '<Ctrl><Shift>W',
'copy' : '<Ctrl><Shift>C',
'paste' : '<Ctrl><Shift>V',
'toggle_scrollbar' : '<Ctrl><Shift>S',
'search' : '<Ctrl><Shift>F',
'close_window' : '<Ctrl><Shift>Q',
'resize_up' : '<Ctrl><Shift>Up',
'resize_down' : '<Ctrl><Shift>Down',
'resize_left' : '<Ctrl><Shift>Left',
'resize_right' : '<Ctrl><Shift>Right',
'move_tab_right' : '<Ctrl><Shift>Page_Down',
'move_tab_left' : '<Ctrl><Shift>Page_Up',
'toggle_zoom' : '<Ctrl><Shift>X',
'scaled_zoom' : '<Ctrl><Shift>Z',
'next_tab' : '<Ctrl>Page_Down',
'prev_tab' : '<Ctrl>Page_Up',
'go_prev' : '<Ctrl><Shift>Tab',
'go_next' : '<Ctrl>Tab',
'full_screen' : 'F11',
}
}
2008-03-08 00:04:41 +00:00
class TerminatorConfig:
2008-07-22 11:03:30 +00:00
"""This class is used as the base point of the config system"""
2008-03-08 00:04:41 +00:00
callback = None
sources = []
2008-07-22 11:03:30 +00:00
def __init__ (self, sources):
self._keys = None
2008-03-08 00:04:41 +00:00
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 _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)
2008-03-08 00:04:41 +00:00
def __getattr__ (self, keyname):
for source in self.sources:
dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type))
2008-03-08 00:04:41 +00:00
try:
val = source[keyname]
dbg (" TConfig: got: '%s' from a '%s'"%(val, source.type))
2008-03-08 00:04:41 +00:00
return (val)
except KeyError:
2008-03-08 00:04:41 +00:00
pass
dbg (" TConfig: Out of sources")
2008-03-08 00:04:41 +00:00
raise (AttributeError)
class TerminatorConfValuestore:
type = "Base"
values = {}
2008-03-08 01:38:42 +00:00
reconfigure_callback = None
2008-03-08 00:04:41 +00:00
# Our settings
def __getitem__ (self, keyname):
2008-03-08 00:04:41 +00:00
if self.values.has_key (keyname):
dbg ("Returning '%s'"%keyname)
return self.values[keyname]
2008-03-08 00:04:41 +00:00
else:
dbg ("Failed to find '%s'"%keyname)
raise (KeyError)
2008-03-08 00:04:41 +00:00
class TerminatorConfValuestoreDefault (TerminatorConfValuestore):
def __init__ (self):
self.type = "Default"
2008-08-15 12:40:10 +00:00
self.values = Defaults
2008-03-08 00:04:41 +00:00
class TerminatorConfValuestoreRC (TerminatorConfValuestore):
rcfilename = ""
type = "RCFile"
2008-03-08 00:04:41 +00:00
#FIXME: use inotify to watch the rc, split __init__ into a parsing function
# that can be re-used when rc changes.
def __init__ (self):
try:
directory = os.environ['XDG_CONFIG_HOME']
except KeyError, e:
dbg(" VS_RCFile: Environment variable %s not found. defaulting to ~/.config" % e.message)
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)
2008-03-08 00:04:41 +00:00
if os.path.exists (self.rcfilename):
ini = ConfigFile(self.rcfilename)
try:
ini.parse()
except ConfigSyntaxError, e:
print "There was an error parsing your configuration"
print str(e)
sys.exit()
2008-03-08 00:04:41 +00:00
for key in ini.settings:
2008-03-08 00:04:41 +00:00
try:
value = ini.settings[key]
# Check if this is actually a key we care about
2008-08-15 12:40:10 +00:00
if not Defaults.has_key (key):
# We should really mention this to the user
continue
2008-08-15 12:40:10 +00:00
deftype = Defaults[key].__class__.__name__
if 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 AttributeError
elif deftype == 'int':
self.values[key] = int (value)
elif deftype == 'float':
self.values[key] = float (value)
elif deftype == 'list':
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
continue
elif deftype == 'dict':
if type(value) != dict:
raise AttributeError
self.values[key] = value
else:
self.values[key] = value
dbg (" VS_RCFile: Set value '%s' to '%s'" % (key, self.values[key]))
except Exception, e:
2008-08-15 12:40:10 +00:00
dbg (" VS_RCFile: %s Exception handling: %s" % (type(e), key))
raise e
2008-03-08 00:04:41 +00:00
pass
class TerminatorConfValuestoreGConf (TerminatorConfValuestore):
profile = ""
client = None
2008-04-11 12:09:34 +00:00
cache = {}
2008-03-08 00:04:41 +00:00
def __init__ (self, profile = None):
self.type = "GConf"
2008-04-02 21:10:32 +00:00
import gconf
2008-03-08 00:04:41 +00:00
self.client = gconf.client_get_default ()
# Grab a couple of values from base class to avoid recursing with our __getattr__
2008-08-15 16:27:16 +00:00
self._gt_dir = Defaults['gt_dir']
self._profile_dir = Defaults['profile_dir']
2008-03-08 00:04:41 +00:00
dbg ('VSGConf: Profile requested is: "%s"'%profile)
2008-03-08 01:38:42 +00:00
if not profile:
profile = self.client.get_string (self._gt_dir + '/global/default_profile')
dbg ('VSGConf: Profile bet on is: "%s"'%profile)
2008-03-08 01:38:42 +00:00
profiles = self.client.get_list (self._gt_dir + '/global/profile_list','string')
dbg ('VSGConf: Found profiles: "%s"'%profiles)
2008-03-08 01:38:42 +00:00
#set up the active encoding list
self.active_encodings = self.client.get_list (self._gt_dir + '/global/active_encodings', 'string')
2008-05-20 23:37:04 +00:00
#need to handle the list of Gconf.value
2008-03-08 01:38:42 +00:00
if profile in profiles:
dbg (" VSGConf: Found profile '%s' in profile_list"%profile)
2008-03-08 01:38:42 +00:00
self.profile = '%s/%s'%(self._profile_dir, profile)
elif "Default" in profiles:
dbg (" VSGConf: profile '%s' not found, but 'Default' exists"%profile)
2008-03-08 01:38:42 +00:00
self.profile = '%s/%s'%(self._profile_dir, "Default")
2008-03-08 00:04:41 +00:00
else:
2008-03-08 01:38:42 +00:00
# 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, deleting __getattr__")
2008-03-08 01:38:42 +00:00
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)
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)
2008-04-02 21:10:32 +00:00
# FIXME: Do we need to watch more non-profile stuff here?
def set_reconfigure_callback (self, function):
dbg (" VSConf: setting callback to: %s"%function)
2008-04-02 21:10:32 +00:00
self.reconfigure_callback = function
return (True)
2008-03-08 01:38:42 +00:00
def on_gconf_notify (self, client, cnxn_id, entry, what):
2008-04-11 12:09:34 +00:00
dbg (" VSGConf: invalidating cache")
self.cache = {}
dbg (" VSGConf: gconf changed, callback is: %s"%self.reconfigure_callback)
2008-03-08 01:38:42 +00:00
if self.reconfigure_callback:
self.reconfigure_callback ()
2008-03-08 00:04:41 +00:00
def __getitem__ (self, key = ""):
2008-04-11 12:09:34 +00:00
if self.cache.has_key (key):
dbg (" VSGConf: returning cached value: %s"%self.cache[key])
return (self.cache[key])
2008-03-08 00:04:41 +00:00
ret = None
value = None
2008-03-08 00:04:41 +00:00
dbg (' VSGConf: preparing: %s/%s'%(self.profile, key))
2008-05-20 23:37:04 +00:00
# FIXME: Ugly special cases we should look to fix in some other way.
2008-04-07 22:28:11 +00:00
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 ('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'))
2008-04-02 21:10:32 +00:00
else:
value = self.client.get ('%s/%s'%(self.profile, key))
2008-03-08 00:04:41 +00:00
if value:
2008-08-15 16:27:56 +00:00
funcname = "get_" + Defaults[key].__class__.__name__
dbg (' GConf: picked function: %s'%funcname)
2008-03-08 00:04:41 +00:00
# Special case for str
if funcname == "get_str":
funcname = "get_string"
# Special case for strlist
if funcname == "get_strlist":
funcname = "get_list"
2008-03-08 00:04:41 +00:00
typefunc = getattr (value, funcname)
ret = typefunc ()
2008-04-11 12:09:34 +00:00
self.cache[key] = ret
2008-03-08 00:04:41 +00:00
return (ret)
else:
raise (KeyError)
2008-03-08 00:04:41 +00:00
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
# http_proxy is a value that is allowed to not exist
print "final proxy: %s"%foo.http_proxy