From e3b933445fe4b18af6ec462fc40da5f482e447a0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 23 Sep 2011 17:43:01 +0200 Subject: [teisenbe] first attempt at a plugin system. --- plugins/screen_detach.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ plugins/test.py | 12 ++++++++++++ src/core.py | 27 +++++++++++++++++++++++++++ src/plugin.py | 33 +++++++++++++++++++++++++++++++++ src/plugin_manager.py | 25 +++++++++++++++++++++++++ 5 files changed, 142 insertions(+) create mode 100644 plugins/screen_detach.py create mode 100644 plugins/test.py create mode 100644 src/plugin.py create mode 100644 src/plugin_manager.py diff --git a/plugins/screen_detach.py b/plugins/screen_detach.py new file mode 100644 index 00000000..6ebc77f2 --- /dev/null +++ b/plugins/screen_detach.py @@ -0,0 +1,45 @@ +import os +import stat +import pyinotify + +SCREEN_DIR = '/var/run/screen/S-%s' % (os.getlogin(),) + +class Plugin(BasePlugin): + def init(self): + self.timed_event = None + sock_path = None + self.thread = None + for f in os.listdir(SCREEN_DIR): + path = os.path.join(SCREEN_DIR, f) + if screen_attached(path): + sock_path = path + self.attached = True + break + + # Only actually do something if we found an attached screen (assuming only one) + if sock_path: + wm = pyinotify.WatchManager() + wm.add_watch(sock_path, pyinotify.EventsCodes.ALL_FLAGS['IN_ATTRIB']) + self.thread = pyinotify.ThreadedNotifier(wm, default_proc_fun=HandleScreen(plugin=self)) + self.thread.start() + + def cleanup(self): + if self.thread: + self.thread.stop() + + def update_screen_state(self, socket): + attached = screen_attached(socket) + if attached != self.attached: + self.attached = attached + status = 'available' if self.attached else 'away' + self.core.command_status(status) + +def screen_attached(socket): + return (os.stat(socket).st_mode & stat.S_IXUSR) != 0 + +class HandleScreen(pyinotify.ProcessEvent): + def my_init(self, **kwargs): + self.plugin = kwargs['plugin'] + + def process_IN_ATTRIB(self, event): + self.plugin.update_screen_state(event.path) diff --git a/plugins/test.py b/plugins/test.py new file mode 100644 index 00000000..fc7aedc6 --- /dev/null +++ b/plugins/test.py @@ -0,0 +1,12 @@ +class Plugin(BasePlugin): + def init(self): + self.core.information("Plugin loaded") + + def cleanup(self): + self.core.information("Plugin unloaded") + + def on_message(self, message): + self.core.information("Test plugin received message: {}".format(message)) + + def command_plugintest(self, args): + self.core.information("Command! With args {}".format(args)) diff --git a/src/core.py b/src/core.py index 3cdc7592..d644c19b 100644 --- a/src/core.py +++ b/src/core.py @@ -36,6 +36,8 @@ import windows import connection import timed_events +from plugin_manager import PluginManager + from data_forms import DataFormsTab from config import config, options from logger import logger @@ -125,6 +127,8 @@ class Core(object): 'server_cycle': (self.command_server_cycle, _('Usage: /server_cycle [domain] [message]\nServer Cycle: disconnect and reconnects in all the rooms in domain.'), None), 'bind': (self.command_bind, _('Usage: /bind \nBind: bind a key to an other key or to a “command”. For example "/bind ^H KEY_UP" makes Control + h do the same same than the Up key.'), None), 'pubsub': (self.command_pubsub, _('Usage: /pubsub \nPubsub: Open a pubsub browser on the given domain'), None), + 'load': (self.command_load, _('Usage: /load \nLoad: Load the specified python script'), None), + 'unload': (self.command_unload, _('Usage: /unload \nUnload: Unload the specified python script'), None), } self.key_func = { @@ -169,6 +173,7 @@ class Core(object): self.xmpp.add_event_handler("chatstate_inactive", self.on_chatstate_inactive) self.timed_events = set() + self.plugin_manager = PluginManager(self) def coucou(self): self.command_pubsub('pubsub.louiz.org') @@ -1121,6 +1126,28 @@ class Core(object): def completion_status(self, the_input): return the_input.auto_completion([status for status in possible_show], ' ') + def command_load(self, arg): + """ + /load + """ + args = arg.split() + if len(args) != 1: + self.command_help('load') + return + filename = args[0] + self.plugin_manager.load(filename) + + def command_unload(self, arg): + """ + /unload + """ + args = arg.split() + if len(args) != 1: + self.command_help('unload') + return + filename = args[0] + self.plugin_manager.unload(filename) + def command_message(self, arg): """ /message [message] diff --git a/src/plugin.py b/src/plugin.py new file mode 100644 index 00000000..e8386d16 --- /dev/null +++ b/src/plugin.py @@ -0,0 +1,33 @@ +import inspect + +class BasePlugin(object): + """ + Class that all plugins derive from. Any methods beginning with command_ + are interpreted as a command and beginning with on_ are interpreted as + event handlers + """ + + def __init__(self, core): + self.core = core + for k, v in inspect.getmembers(self, inspect.ismethod): + if k.startswith('on_'): + core.xmpp.add_event_handler(k[3:], v) + elif k.startswith('command_'): + command = k[len('command_'):] + core.commands[command] = (v, v.__doc__, None) + self.init() + + def init(self): + pass + + def cleanup(self): + pass + + def unload(self): + for k, v in inspect.getmembers(self, inspect.ismethod): + if k.startswith('on_'): + self.core.xmpp.del_event_handler(k[3:], v) + elif k.startswith('command_'): + command = k[len('command_'):] + del self.core.commands[command] + self.cleanup() diff --git a/src/plugin_manager.py b/src/plugin_manager.py new file mode 100644 index 00000000..3f900e39 --- /dev/null +++ b/src/plugin_manager.py @@ -0,0 +1,25 @@ +class PluginManager(object): + def __init__(self, core): + self.core = core + self.plugins = {} + + def load(self, name): + if name in self.plugins: + self.plugins[name].unload() + + try: + code = compile(open(name).read(), name, 'exec') + from plugin import BasePlugin + globals = { 'BasePlugin' : BasePlugin } + exec(code, globals) + self.plugins[name] = globals['Plugin'](self.core) + except Exception as e: + self.core.information("Could not load plugin: %s" % (e,)) + + def unload(self, name): + if name in self.plugins: + try: + self.plugins[name].unload() + del self.plugins[name] + except Exception as e: + self.core.information("Could not unload plugin (may not be safe to try again): %s" % (e,)) -- cgit v1.2.3 From f27556747896aeb891ce71cfdd0ac349d68c5b3d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Sep 2011 22:26:31 +0200 Subject: [teisenbe] Use the imp module to import modules. Also add a simple translator module --- plugins/screen_detach.py | 1 + plugins/test.py | 4 +++ plugins/translate.py | 33 +++++++++++++++++++++ src/plugin.py | 26 +++++++--------- src/plugin_manager.py | 77 +++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 plugins/translate.py diff --git a/plugins/screen_detach.py b/plugins/screen_detach.py index 6ebc77f2..6ee96896 100644 --- a/plugins/screen_detach.py +++ b/plugins/screen_detach.py @@ -1,3 +1,4 @@ +from plugin import BasePlugin import os import stat import pyinotify diff --git a/plugins/test.py b/plugins/test.py index fc7aedc6..13ba1e9c 100644 --- a/plugins/test.py +++ b/plugins/test.py @@ -1,5 +1,9 @@ +from plugin import BasePlugin + class Plugin(BasePlugin): def init(self): + self.add_command('plugintest', self.command_plugintest, 'Test command') + self.add_event_handler('message', self.on_message) self.core.information("Plugin loaded") def cleanup(self): diff --git a/plugins/translate.py b/plugins/translate.py new file mode 100644 index 00000000..625d78e7 --- /dev/null +++ b/plugins/translate.py @@ -0,0 +1,33 @@ +from plugin import BasePlugin +import urllib.request +from urllib.parse import urlencode +import xhtml +import json + +TARGET_LANG = 'en' + +def translate(s, target=TARGET_LANG, source=''): + f = urllib.request.urlopen('http://ajax.googleapis.com/ajax/services/language/translate', urlencode({ 'v': '1.0', 'q': s, 'langpair': '%s|%s' % (source, target) })) + response = json.loads(str(f.read(), 'utf-8'))['responseData'] + return (response['translatedText'], response['detectedSourceLanguage']) + +class Plugin(BasePlugin): + def init(self): + self.add_event_handler('groupchat_message', self.on_groupchat_message) + + def on_groupchat_message(self, message): + try: + room_from = message.getMucroom() + if message['type'] == 'error': + return + + if room_from == 'poezio@kikoo.louiz.org': + nick_from = message['mucnick'] + body = xhtml.get_body_from_message_stanza(message) + room = self.core.get_room_by_name(room_from) + text, lang = translate(body) + if lang != TARGET_LANG: + room.add_message(text, nickname=nick_from) + except Exception as e: + import traceback + self.core.information("Exception in translator! %s" % (traceback.format_exc(),)) diff --git a/src/plugin.py b/src/plugin.py index e8386d16..728dfe21 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -1,5 +1,3 @@ -import inspect - class BasePlugin(object): """ Class that all plugins derive from. Any methods beginning with command_ @@ -7,14 +5,9 @@ class BasePlugin(object): event handlers """ - def __init__(self, core): + def __init__(self, plugin_manager, core): self.core = core - for k, v in inspect.getmembers(self, inspect.ismethod): - if k.startswith('on_'): - core.xmpp.add_event_handler(k[3:], v) - elif k.startswith('command_'): - command = k[len('command_'):] - core.commands[command] = (v, v.__doc__, None) + self.plugin_manager = plugin_manager self.init() def init(self): @@ -24,10 +17,13 @@ class BasePlugin(object): pass def unload(self): - for k, v in inspect.getmembers(self, inspect.ismethod): - if k.startswith('on_'): - self.core.xmpp.del_event_handler(k[3:], v) - elif k.startswith('command_'): - command = k[len('command_'):] - del self.core.commands[command] self.cleanup() + + def add_command(self, name, handler, help, completion=None): + return self.plugin_manager.add_command(self.__module__, name, handler, help, completion) + + def add_event_handler(self, event_name, handler): + return self.plugin_manager.add_event_handler(self.__module__, event_name, handler) + + def del_event_handler(self, event_name, handler): + return self.plugin_manager.del_event_handler(self.__module__, event_name, handler) diff --git a/src/plugin_manager.py b/src/plugin_manager.py index 3f900e39..8301f5f8 100644 --- a/src/plugin_manager.py +++ b/src/plugin_manager.py @@ -1,25 +1,86 @@ +import imp +import os +import sys +from config import config +from gettext import gettext as _ + +plugins_dir = config.get('plugins_dir', '') +plugins_dir = plugins_dir or\ + os.path.join(os.environ.get('XDG_DATA_HOME') or\ + os.path.join(os.environ.get('HOME'), '.local', 'share'), + 'poezio', 'plugins') +try: + os.makedirs(plugins_dir) +except OSError: + pass + +sys.path.append(plugins_dir) + class PluginManager(object): def __init__(self, core): self.core = core - self.plugins = {} + self.modules = {} # module name -> module object + self.plugins = {} # module name -> plugin object + self.commands = {} # module name -> dict of commands loaded for the module + self.event_handlers = {} # module name -> list of event_name/handler pairs loaded for the module def load(self, name): if name in self.plugins: self.plugins[name].unload() try: - code = compile(open(name).read(), name, 'exec') - from plugin import BasePlugin - globals = { 'BasePlugin' : BasePlugin } - exec(code, globals) - self.plugins[name] = globals['Plugin'](self.core) + if name in self.modules: + imp.acquire_lock() + module = imp.reload(self.modules[name]) + imp.release_lock() + else: + file, filename, info = imp.find_module(name, [plugins_dir]) + imp.acquire_lock() + module = imp.load_module(name, file, filename, info) + imp.release_lock() except Exception as e: - self.core.information("Could not load plugin: %s" % (e,)) + import traceback + self.core.information(_("Could not load plugin: ") + traceback.format_exc()) + return + finally: + if imp.lock_held(): + imp.release_lock() + + self.modules[name] = module + self.commands[name] = {} + self.event_handlers[name] = [] + self.plugins[name] = module.Plugin(self, self.core) def unload(self, name): if name in self.plugins: try: + for command in self.commands[name].keys(): + del self.core.commands[command] + for event_name, handler in self.event_handlers[name]: + self.core.xmpp.del_event_handler(event_name, handler) + self.plugins[name].unload() del self.plugins[name] + del self.commands[name] + del self.event_handlers[name] except Exception as e: - self.core.information("Could not unload plugin (may not be safe to try again): %s" % (e,)) + import traceback + self.core.information(_("Could not unload plugin (may not be safe to try again): ") + traceback.format_exc()) + + def add_command(self, module_name, name, handler, help, completion=None): + if name in self.core.commands: + raise Exception(_("Command '%s' already exists") % (name,)) + + commands = self.commands[module_name] + commands[name] = (handler, help, completion) + self.core.commands[name] = (handler, help, completion) + + def add_event_handler(self, module_name, event_name, handler): + eh = self.event_handlers[module_name] + eh.append((event_name, handler)) + self.core.xmpp.add_event_handler(event_name, handler) + + def del_event_handler(self, module_name, event_name, handler): + self.core.xmpp.del_event_handler(event_name, handler) + eh = self.event_handlers[module_name] + eh = list(filter(lambda e : e != (event_name, handler), eh)) -- cgit v1.2.3 From 9432c755ae0e06702e777990178163c8aa44d5c8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Sep 2011 22:29:02 +0200 Subject: Add plugins_dir to default conf --- data/default_config.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/default_config.cfg b/data/default_config.cfg index 0c16ef97..6ba36419 100644 --- a/data/default_config.cfg +++ b/data/default_config.cfg @@ -125,6 +125,10 @@ use_log = false # you want to use instead. This directory will be created if it doesn't exist log_dir = +# If plugins_dir is not set, plugins will be loaded from $XDG_DATA_HOME/poezio/plugins. +# You can specify an other directory to use. It will be created if it doesn't exist +plugins_dir = + # the full path to the photo (avatar) you want to use # it should be less than 16Ko # The avatar is not set by default, because it slows -- cgit v1.2.3 From 9a8d0bd5bfbbb850fb0233f2ae384dfd57ff3d44 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Sep 2011 22:35:57 +0200 Subject: Add teisenbe in THANKS section --- README | 1 + 1 file changed, 1 insertion(+) diff --git a/README b/README index 49d31384..3e8e2045 100644 --- a/README +++ b/README @@ -89,6 +89,7 @@ the Creative Commons BY license (http://creativecommons.org/licenses/by/2.0/) Thanks ======================= = People = + Todd Eisenberger (todd@teisen.be) - Plugin system Link Mauve - Code, testing Gaëtan Ribémont (http://www.bonbref.com) - Logo design Ovart - Testing -- cgit v1.2.3 From eb096892a9ab3429ba0dcb9654a356afcd01932d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Sep 2011 23:10:55 +0200 Subject: Completion for load and unload commands --- src/core.py | 6 +++--- src/plugin_manager.py | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/core.py b/src/core.py index d644c19b..55026967 100644 --- a/src/core.py +++ b/src/core.py @@ -103,6 +103,7 @@ class Core(object): # a completion function, taking a Input as argument. Can be None) # The completion function should return True if a completion was # made ; False otherwise + self.plugin_manager = PluginManager(self) self.commands = { 'help': (self.command_help, '\_o< KOIN KOIN KOIN', self.completion_help), 'join': (self.command_join, _("Usage: /join [room_name][@server][/nick] [password]\nJoin: Join the specified room. You can specify a nickname after a slash (/). If no nickname is specified, you will use the default_nick in the configuration file. You can omit the room name: you will then join the room you\'re looking at (useful if you were kicked). You can also provide a room_name without specifying a server, the server of the room you're currently in will be used. You can also provide a password to join the room.\nExamples:\n/join room@server.tld\n/join room@server.tld/John\n/join room2\n/join /me_again\n/join\n/join room@server.tld/my_nick password\n/join / password"), self.completion_join), @@ -127,8 +128,8 @@ class Core(object): 'server_cycle': (self.command_server_cycle, _('Usage: /server_cycle [domain] [message]\nServer Cycle: disconnect and reconnects in all the rooms in domain.'), None), 'bind': (self.command_bind, _('Usage: /bind \nBind: bind a key to an other key or to a “command”. For example "/bind ^H KEY_UP" makes Control + h do the same same than the Up key.'), None), 'pubsub': (self.command_pubsub, _('Usage: /pubsub \nPubsub: Open a pubsub browser on the given domain'), None), - 'load': (self.command_load, _('Usage: /load \nLoad: Load the specified python script'), None), - 'unload': (self.command_unload, _('Usage: /unload \nUnload: Unload the specified python script'), None), + 'load': (self.command_load, _('Usage: /load \nLoad: Load the specified python script'), self.plugin_manager.completion_load), + 'unload': (self.command_unload, _('Usage: /unload \nUnload: Unload the specified python script'), self.plugin_manager.completion_unload), } self.key_func = { @@ -173,7 +174,6 @@ class Core(object): self.xmpp.add_event_handler("chatstate_inactive", self.on_chatstate_inactive) self.timed_events = set() - self.plugin_manager = PluginManager(self) def coucou(self): self.command_pubsub('pubsub.louiz.org') diff --git a/src/plugin_manager.py b/src/plugin_manager.py index 8301f5f8..2a7a116f 100644 --- a/src/plugin_manager.py +++ b/src/plugin_manager.py @@ -40,7 +40,7 @@ class PluginManager(object): imp.release_lock() except Exception as e: import traceback - self.core.information(_("Could not load plugin: ") + traceback.format_exc()) + self.core.information(_("Could not load plugin: ") + traceback.format_exc(), 'Error') return finally: if imp.lock_held(): @@ -84,3 +84,22 @@ class PluginManager(object): self.core.xmpp.del_event_handler(event_name, handler) eh = self.event_handlers[module_name] eh = list(filter(lambda e : e != (event_name, handler), eh)) + + def completion_load(self, the_input): + """ + completion function that completes the name of the plugins, from + all .py files in plugins_dir + """ + try: + names = os.listdir(plugins_dir) + except OSError as e: + self.core.information(_('Completion failed: %s' % e), 'Error') + return + plugins_files = [name[:-3] for name in names if name.endswith('.py')] + return the_input.auto_completion(plugins_files, '') + + def completion_unload(self, the_input): + """ + completion function that completes the name of the plugins that are loaded + """ + return the_input.auto_completion(list(self.plugins.keys()), '') -- cgit v1.2.3 From cac130e7543b30be7fbec6484b29191a9f8b1665 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Sep 2011 23:44:52 +0200 Subject: Autoload plugins --- data/default_config.cfg | 3 +++ src/core.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/data/default_config.cfg b/data/default_config.cfg index 6ba36419..b061cbf4 100644 --- a/data/default_config.cfg +++ b/data/default_config.cfg @@ -129,6 +129,9 @@ log_dir = # You can specify an other directory to use. It will be created if it doesn't exist plugins_dir = +# Space separated list of plugins to load on startup +plugins_autoload = + # the full path to the photo (avatar) you want to use # it should be less than 16Ko # The avatar is not set by default, because it slows diff --git a/src/core.py b/src/core.py index 55026967..0b8442ff 100644 --- a/src/core.py +++ b/src/core.py @@ -174,6 +174,12 @@ class Core(object): self.xmpp.add_event_handler("chatstate_inactive", self.on_chatstate_inactive) self.timed_events = set() + self.autoload_plugins() + + def autoload_plugins(self): + plugins = config.get('plugins_autoload', '') + for plugin in plugins.split(): + self.plugin_manager.load(plugin) def coucou(self): self.command_pubsub('pubsub.louiz.org') -- cgit v1.2.3 From 1a6d903e34d505005836f6b8aee3552073a2397e Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Sep 2011 02:39:00 +0200 Subject: Add a config file to the plugins by default --- src/plugin.py | 28 +++++++++++++++++++++++++++- src/plugin_manager.py | 12 +++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/plugin.py b/src/plugin.py index 728dfe21..d64679a1 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -1,3 +1,26 @@ +import os +from configparser import ConfigParser + +class PluginConfig(ConfigParser): + def __init__(self, filename): + ConfigParser.__init__(self) + self.__config_file__ = filename + self.read() + + def read(self): + """Read the config file""" + ConfigParser.read(self, self.__config_file__) + + def write(self): + """Write the config to the disk""" + try: + fp = open(self.__config_file__, 'w') + ConfigParser.write(self, fp) + fp.close() + return True + except IOError: + return False + class BasePlugin(object): """ Class that all plugins derive from. Any methods beginning with command_ @@ -5,9 +28,11 @@ class BasePlugin(object): event handlers """ - def __init__(self, plugin_manager, core): + def __init__(self, plugin_manager, core, plugins_conf_dir): self.core = core self.plugin_manager = plugin_manager + conf = os.path.join(plugins_conf_dir, self.__module__+'.cfg') + self.config = PluginConfig(conf) self.init() def init(self): @@ -17,6 +42,7 @@ class BasePlugin(object): pass def unload(self): + self.cleanup() def add_command(self, name, handler, help, completion=None): diff --git a/src/plugin_manager.py b/src/plugin_manager.py index 2a7a116f..0ffee7ee 100644 --- a/src/plugin_manager.py +++ b/src/plugin_manager.py @@ -9,11 +9,21 @@ plugins_dir = plugins_dir or\ os.path.join(os.environ.get('XDG_DATA_HOME') or\ os.path.join(os.environ.get('HOME'), '.local', 'share'), 'poezio', 'plugins') + +plugins_conf_dir = os.path.join(os.environ.get('XDG_CONFIG_HOME'), 'poezio',\ + 'plugins') + try: os.makedirs(plugins_dir) except OSError: pass +try: + os.makedirs(plugins_conf_dir) +except OSError: + pass + + sys.path.append(plugins_dir) class PluginManager(object): @@ -49,7 +59,7 @@ class PluginManager(object): self.modules[name] = module self.commands[name] = {} self.event_handlers[name] = [] - self.plugins[name] = module.Plugin(self, self.core) + self.plugins[name] = module.Plugin(self, self.core, plugins_conf_dir) def unload(self, name): if name in self.plugins: -- cgit v1.2.3 From b63132d32d35fc4593d0a25fc95274891492a542 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Sep 2011 02:43:52 +0200 Subject: remove yet another unneeded call for GlobalInfoBar --- src/data_forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data_forms.py b/src/data_forms.py index 176c4669..f54e7493 100644 --- a/src/data_forms.py +++ b/src/data_forms.py @@ -32,7 +32,6 @@ class DataFormsTab(Tab): for field in self._form: self.fields.append(field) self.topic_win = windows.Topic() - self.tab_win = windows.GlobalInfoBar() self.form_win = FormWin(form, self.height-4, self.width, 1, 0) self.help_win = windows.HelpText("Ctrl+Y: send form, Ctrl+G: cancel") self.help_win_dyn = windows.HelpText() -- cgit v1.2.3 From fd99fb32bb6d696af9d792d2d6ea69bfe610501f Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Sep 2011 02:50:03 +0200 Subject: [teisenbe] Make the data forms more usable (add color to the labels) --- src/data_forms.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/data_forms.py b/src/data_forms.py index f54e7493..9510bdf8 100644 --- a/src/data_forms.py +++ b/src/data_forms.py @@ -130,6 +130,26 @@ class DummyInput(FieldInput, windows.Win): def is_dummy(self): return True +class ColoredLabel(windows.Win): + def __init__(self, text): + self.text = text + self.color = 14 + windows.Win.__init__(self) + + def resize(self, height, width, y, x): + self._resize(height, width, y, x) + + def set_color(self, color): + self.color = color + self.refresh() + + def refresh(self): + with g_lock: + self._win.attron(curses.color_pair(self.color)) + self.addstr(0, 0, self.text) + self._win.attroff(curses.color_pair(self.color)) + self._refresh() + class BooleanWin(FieldInput, windows.Win): def __init__(self, field): FieldInput.__init__(self, field) @@ -398,7 +418,7 @@ class FormWin(object): if field['type'] == 'fixed': label = field.getValue() inp = input_class(field) - self.inputs.append({'label':label, + self.inputs.append({'label':ColoredLabel(label), 'description': desc, 'input':inp}) @@ -427,6 +447,7 @@ class FormWin(object): return if self.current_input == len(self.inputs) - 1 or self.current_input >= self.height-1: return + self.inputs[self.current_input]['label'].set_color(14) self.inputs[self.current_input]['input'].set_color(14) self.current_input += 1 jump = 0 @@ -435,6 +456,7 @@ class FormWin(object): if self.inputs[self.current_input+jump]['input'].is_dummy(): return self.current_input += jump + self.inputs[self.current_input]['label'].set_color(13) self.inputs[self.current_input]['input'].set_color(13) def go_to_previous_input(self): @@ -442,6 +464,7 @@ class FormWin(object): return if self.current_input == 0: return + self.inputs[self.current_input]['label'].set_color(14) self.inputs[self.current_input]['input'].set_color(14) self.current_input -= 1 jump = 0 @@ -450,6 +473,7 @@ class FormWin(object): if self.inputs[self.current_input+jump]['input'].is_dummy(): return self.current_input -= jump + self.inputs[self.current_input]['label'].set_color(13) self.inputs[self.current_input]['input'].set_color(13) def on_input(self, key): @@ -465,8 +489,7 @@ class FormWin(object): for name, field in self._form.getFields(): if field['type'] == 'hidden': continue - label = self.inputs[i]['label'] - self._win.addstr(y, 0, label) + self.inputs[i]['label'].resize(1, self.width//3, y + 1, 0) self.inputs[i]['input'].resize(1, self.width//3, y+1, 2*self.width//3) # TODO: display the field description y += 1 @@ -477,10 +500,13 @@ class FormWin(object): for i, inp in enumerate(self.inputs): if i >= self.height: break + inp['label'].refresh() inp['input'].refresh() if self.current_input < self.height-1: self.inputs[self.current_input]['input'].set_color(13) self.inputs[self.current_input]['input'].refresh() + self.inputs[self.current_input]['label'].set_color(13) + self.inputs[self.current_input]['label'].refresh() def refresh_current_input(self): self.inputs[self.current_input]['input'].refresh() -- cgit v1.2.3 From 00ed9b4842169111238b86d0bfc1465176b7d2d8 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 25 Sep 2011 03:01:32 +0200 Subject: [teisenbe] Fix a bug in case of XDG_CONFIG_HOME not set --- src/plugin_manager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugin_manager.py b/src/plugin_manager.py index 0ffee7ee..1f0e89eb 100644 --- a/src/plugin_manager.py +++ b/src/plugin_manager.py @@ -10,8 +10,10 @@ plugins_dir = plugins_dir or\ os.path.join(os.environ.get('HOME'), '.local', 'share'), 'poezio', 'plugins') -plugins_conf_dir = os.path.join(os.environ.get('XDG_CONFIG_HOME'), 'poezio',\ - 'plugins') +config_home = os.environ.get("XDG_CONFIG_HOME") +if not config_home: + config_home = os.path.join(os.environ.get('HOME'), '.config') +plugins_conf_dir = os.path.join(config_home, 'poezio', 'plugins') try: os.makedirs(plugins_dir) @@ -23,7 +25,6 @@ try: except OSError: pass - sys.path.append(plugins_dir) class PluginManager(object): -- cgit v1.2.3