diff options
-rw-r--r-- | plugins/screen_detach.py | 45 | ||||
-rw-r--r-- | plugins/test.py | 12 | ||||
-rw-r--r-- | src/core.py | 27 | ||||
-rw-r--r-- | src/plugin.py | 33 | ||||
-rw-r--r-- | src/plugin_manager.py | 25 |
5 files changed, 142 insertions, 0 deletions
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 <key> <equ>\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 <domain>\nPubsub: Open a pubsub browser on the given domain'), None), + 'load': (self.command_load, _('Usage: /load <script.py>\nLoad: Load the specified python script'), None), + 'unload': (self.command_unload, _('Usage: /unload <script.py>\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 <script.py> + """ + 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 <script.py> + """ + 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 <jid> [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,)) |