#!/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 """Terminator by Chris Jones Classes relating to configuration >>> DEFAULTS['global_config']['focus'] 'click' >>> config = Config() >>> config['focus'] = 'sloppy' >>> config['focus'] 'sloppy' >>> DEFAULTS['global_config']['focus'] 'click' >>> config2 = Config() >>> config2['focus'] 'sloppy' >>> config2['focus'] = 'click' >>> config2['focus'] 'click' >>> config['focus'] 'click' >>> """ import platform import os import sys from copy import copy from configobj import ConfigObj from borg import Borg from factory import Factory from util import dbg, get_config_dir, dict_diff DEFAULTS = { 'global_config': { 'focus' : 'click', 'enable_real_transparency' : True, 'handle_size' : -1, 'geometry_hinting' : True, 'fullscreen' : False, 'borderless' : False, 'maximise' : False, 'hidden' : False, 'tab_position' : 'top', 'close_button_on_tab' : True, 'hide_tabbar' : False, 'scroll_tabbar' : False, 'try_posix_regexp' : platform.system() != 'Linux', }, 'keybindings': { 'zoom_in' : 'plus', 'zoom_out' : 'minus', 'zoom_normal' : '0', 'new_root_tab' : 'T', 'new_tab' : 'T', 'go_next' : ('N','Tab'), 'go_prev' : ('P','Tab'), 'go_up' : 'Up', 'go_down' : 'Down', 'go_left' : 'Left', 'go_right' : 'Right', 'split_horiz' : 'O', 'split_vert' : 'E', 'close_term' : 'W', 'copy' : 'C', 'paste' : 'V', 'toggle_scrollbar' : 'S', 'search' : 'F', 'close_window' : 'Q', 'resize_up' : 'Up', 'resize_down' : 'Down', 'resize_left' : 'Left', 'resize_right' : 'Right', 'move_tab_right' : 'Page_Down', 'move_tab_left' : 'Page_Up', 'toggle_zoom' : 'X', 'scaled_zoom' : 'Z', 'next_tab' : 'Page_Down', 'prev_tab' : 'Page_Up', 'switch_to_tab_1' : None, 'switch_to_tab_2' : None, 'switch_to_tab_3' : None, 'switch_to_tab_4' : None, 'switch_to_tab_5' : None, 'switch_to_tab_6' : None, 'switch_to_tab_7' : None, 'switch_to_tab_8' : None, 'switch_to_tab_9' : None, 'switch_to_tab_10' : None, 'full_screen' : 'F11', 'reset' : 'R', 'reset_clear' : 'G', 'hide_window' : 'a', 'group_all' : 'g', 'ungroup_all' : 'g', 'group_tab' : 't', 'ungroup_tab' : 'T', 'new_window' : 'I', }, 'profiles': { 'default': { 'titlebars' : True, 'zoomedtitlebar' : True, 'allow_bold' : True, 'audible_bell' : False, 'visible_bell' : True, 'urgent_bell' : False, 'background_color' : '#000000', 'background_darkness' : 0.5, 'background_type' : 'solid', 'background_image' : '', 'backspace_binding' : 'ascii-del', 'delete_binding' : 'delete-sequence', 'cursor_blink' : True, 'cursor_shape' : 'block', 'cursor_color' : '', 'emulation' : 'xterm', 'font' : 'Mono 10', 'foreground_color' : '#AAAAAA', 'scrollbar_position' : "right", 'scroll_background' : True, 'scroll_on_keystroke' : True, 'scroll_on_output' : True, 'scrollback_lines' : 500, '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, 'encoding' : 'UTF-8', 'active_encodings' : ['UTF-8', 'ISO-8859-1'], 'focus_on_close' : 'auto', 'force_no_bell' : False, 'cycle_term_tab' : True, 'copy_on_selection' : False, 'title_tx_txt_color' : '#FFFFFF', 'title_tx_bg_color' : '#C80003', 'title_rx_txt_color' : '#FFFFFF', 'title_rx_bg_color' : '#0076C9', 'title_ia_txt_color' : '#000000', 'title_ia_bg_color' : '#C0BEBF', 'alternate_screen_scroll': True, 'split_to_group' : False, 'autoclean_groups' : True, 'http_proxy' : '', 'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'], }, }, 'layouts': { }, 'plugins': { }, } class Config(object): """Class to provide a slightly richer config API above ConfigBase""" base = None profile = None def __init__(self, profile='default'): self.base = ConfigBase() self.profile = profile def __getitem__(self, key): """Look up a configuration item""" return(self.base.get_item(key, self.profile)) def __setitem__(self, key, value): """Set a particular configuration item""" return(self.base.set_item(key, value, self.profile)) def set_profile(self, profile): """Set our profile (which usually means change it)""" dbg('Config::set_profile: Changing profile to %s' % profile) self.profile = profile if not self.base.profiles.has_key(profile): dbg('Config::set_profile: %s does not exist, creating' % profile) self.base.profiles[profile] = copy(DEFAULTS['profiles']['default']) def del_profile(self, profile): """Delete a profile""" if self.base.profiles.has_key(profile): del(self.base.profiles[profile]) def list_profiles(self): """List all configured profiles""" return(self.base.profiles.keys()) def save(self): """Cause ConfigBase to save our config to file""" return(self.base.save()) class ConfigBase(Borg): """Class to provide access to our user configuration""" loaded = None sections = None global_config = None profiles = None keybindings = None plugins = None layouts = None def __init__(self): """Class initialiser""" Borg.__init__(self, self.__class__.__name__) self.prepare_attributes() self.load() def prepare_attributes(self): """Set up our borg environment""" if self.loaded is None: self.loaded = False if self.sections is None: self.sections = ['global_config', 'keybindings', 'profiles', 'layouts', 'plugins'] if self.global_config is None: self.global_config = copy(DEFAULTS['global_config']) if self.profiles is None: self.profiles = {} self.profiles['default'] = copy(DEFAULTS['profiles']['default']) if self.keybindings is None: self.keybindings = copy(DEFAULTS['keybindings']) if self.plugins is None: self.plugins = {} if self.layouts is None: self.layouts = copy(DEFAULTS['layouts']) def load(self): """Load configuration data from our various sources""" if self.loaded is True: dbg('ConfigBase::load: config already loaded') return filename = os.path.join(get_config_dir(), 'epic-config') try: configfile = open(filename, 'r') except Exception, ex: dbg('ConfigBase::load: Unable to open %s (%s)' % (filename, ex)) return parser = ConfigObj(configfile) for section_name in self.sections: dbg('ConfigBase::load: Processing section: %s' % section_name) section = getattr(self, section_name) if section_name == 'profiles': for profile in parser[section_name]: dbg('ConfigBase::load: Processing profile: %s' % profile) if not section.has_key(section_name): section[profile] = copy(DEFAULTS['profiles']['default']) section[profile].update(parser[section_name][profile]) elif section_name == ['layouts', 'plugins']: for part in parser[section_name]: dbg('ConfigBase::load: Processing %s: %s' % (section_name, part)) section[part] = parser[section_name][part] else: try: section.update(parser[section_name]) except KeyError, ex: dbg('ConfigBase::load: skipping loading missing section %s' % section_name) self.loaded = True def save(self): """Save the config to a file""" dbg('ConfigBase::save: saving config') parser = ConfigObj() parser.indent_type = ' ' for section_name in ['global_config', 'keybindings']: dbg('ConfigBase::save: Processing section: %s' % section_name) section = getattr(self, section_name) parser[section_name] = dict_diff(DEFAULTS[section_name], section) parser['profiles'] = {} for profile in self.profiles: dbg('ConfigBase::save: Processing profile: %s' % profile) parser['profiles'][profile] = dict_diff(DEFAULTS['profiles']['default'], self.profiles[profile]) parser['layouts'] = {} for layout in self.layouts: dbg('ConfigBase::save: Processing layout: %s' % layout) parser['layouts'][layout] = self.layouts[layout] parser['plugins'] = {} for plugin in self.plugins: dbg('ConfigBase::save: Processing plugin: %s' % plugin) parser['plugins'][plugin] = self.plugins[plugin] parser.write(open(os.path.join(get_config_dir(), 'epic-config'), 'w')) def get_item(self, key, profile='default', plugin=None): """Look up a configuration item""" dbg('ConfigBase::get_item: %s:%s' % (profile, key)) if self.global_config.has_key(key): dbg('ConfigBase::get_item: found in globals: %s' % self.global_config[key]) return(self.global_config[key]) elif self.profiles[profile].has_key(key): dbg('ConfigBase::get_item: found in profile %s (%s)' % ( profile, self.profiles[profile][key])) return(self.profiles[profile][key]) elif key == 'keybindings': return(self.keybindings) elif plugin is not None and self.plugins[plugin].has_key(key): dbg('ConfigBase::get_item: found in plugin %s (%s)' % ( plugin, self.plugins[plugin][key])) return(self.plugins[plugin][key]) else: raise KeyError('ConfigBase::get_item: unknown key %s' % key) def set_item(self, key, value, profile='default', plugin=None): """Set a configuration item""" dbg('ConfigBase::set_item: Setting %s=%s (profile=%s, plugin=%s)' % (key, value, profile, plugin)) if self.global_config.has_key(key): self.global_config[key] = value elif self.profiles[profile].has_key(key): self.profiles[profile][key] = value elif key == 'keybindings': self.keybindings = value elif plugin is not None and self.plugins[plugin].has_key(key): self.plugins[plugin][key] = value else: raise KeyError('ConfigBase::set_item: unknown key %s' % key) return(True) if __name__ == '__main__': import doctest (failed, attempted) = doctest.testmod() print "%d/%d tests failed" % (failed, attempted)