summaryrefslogtreecommitdiff
path: root/src/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/core.py')
-rw-r--r--src/core.py172
1 files changed, 146 insertions, 26 deletions
diff --git a/src/core.py b/src/core.py
index 8abaec63..e1bae799 100644
--- a/src/core.py
+++ b/src/core.py
@@ -16,6 +16,7 @@ import threading
import traceback
from datetime import datetime
+from inspect import getargspec
import common
import theming
@@ -36,6 +37,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
@@ -46,6 +49,7 @@ from contact import Contact, Resource
from text_buffer import TextBuffer
from keyboard import read_char
from theming import get_theme
+from fifo import Fifo
# http://xmpp.org/extensions/xep-0045.html#errorstatus
ERROR_AND_STATUS_CODES = {
@@ -76,6 +80,13 @@ class Core(object):
"""
User interface using ncurses
"""
+
+ # dict containing the name of the internal events
+ # used with the plugins, the key is the name of the event
+ # and the value is the number of arguments the handler must take
+ internal_events = {
+ 'enter': 2,
+ }
def __init__(self):
# All uncaught exception are given to this callback, instead
# of being displayed on the screen and exiting the program.
@@ -84,6 +95,7 @@ class Core(object):
sys.excepthook = self.on_exception
self.running = True
self.xmpp = singleton.Singleton(connection.Connection)
+ self.remote_fifo = None
# a unique buffer used to store global informations
# that are displayed in almost all tabs, in an
# information window.
@@ -102,6 +114,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),
@@ -111,11 +124,7 @@ class Core(object):
'prev': (self.rotate_rooms_left, _("Usage: /prev\nPrev: Go to the previous room."), None),
'win': (self.command_win, _("Usage: /win <number>\nWin: Go to the specified room."), self.completion_win),
'w': (self.command_win, _("Usage: /w <number>\nW: Go to the specified room."), self.completion_win),
- 'show': (self.command_status, _('Usage: /show <availability> [status message]\nShow: Sets your availability and (optionaly) your status message. The <availability> argument is one of \"available, chat, away, afk, dnd, busy, xa\" and the optional [status] argument will be your status message.'), self.completion_status),
'status': (self.command_status, _('Usage: /status <availability> [status message]\nStatus: Sets your availability and (optionaly) your status message. The <availability> argument is one of \"available, chat, away, afk, dnd, busy, xa\" and the optional [status] argument will be your status message.'), self.completion_status),
- 'away': (self.command_away, _("Usage: /away [message]\nAway: Sets your availability to away and (optionaly) your status message. This is equivalent to '/status away [message]'"), None),
- 'busy': (self.command_busy, _("Usage: /busy [message]\nBusy: Sets your availability to busy and (optionaly) your status message. This is equivalent to '/status busy [message]'"), None),
- 'available': (self.command_avail, _("Usage: /available [message]\nAvailable: Sets your availability to available and (optionaly) your status message. This is equivalent to '/status available [message]'"), None),
'bookmark': (self.command_bookmark, _("Usage: /bookmark [roomname][/nick]\nBookmark: Bookmark the specified room (you will then auto-join it on each poezio start). This commands uses the same syntaxe as /join. Type /help join for syntaxe examples. Note that when typing \"/bookmark\" on its own, the room will be bookmarked with the nickname you\'re currently using in this room (instead of default_nick)"), None),
'set': (self.command_set, _("Usage: /set <option> [value]\nSet: Sets the value to the option in your configuration file. You can, for example, change your default nickname by doing `/set default_nick toto` or your resource with `/set resource blabla`. You can also set an empty value (nothing) by providing no [value] after <option>."), None),
'theme': (self.command_theme, _('Usage: /theme\nTheme: Reload the theme defined in the config file.'), None),
@@ -125,7 +134,8 @@ class Core(object):
'connect': (self.command_reconnect, _('Usage: /connect\nConnect: disconnect from the remote server if you are currently connected and then connect to it again'), None),
'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),
-# nope 'pubsub': (self.command_pubsub, _('Usage: /pubsub <domain>\nPubsub: Open a pubsub browser on the given domain'), None),
+ 'load': (self.command_load, _('Usage: /load <plugin>\nLoad: Load the specified plugin'), self.plugin_manager.completion_load),
+ 'unload': (self.command_unload, _('Usage: /unload <plugin>\nUnload: Unload the specified plugin'), self.plugin_manager.completion_unload),
}
self.key_func = {
@@ -145,7 +155,6 @@ class Core(object):
'M-z': self.go_to_previous_tab,
'^L': self.full_screen_redraw,
'M-j': self.go_to_room_number,
-# 'M-c': self.coucou,
}
# Add handlers
@@ -171,8 +180,13 @@ class Core(object):
self.timed_events = set()
- def coucou(self):
- self.command_pubsub('pubsub.louiz.org')
+ self.connected_events = {}
+ self.autoload_plugins()
+
+ def autoload_plugins(self):
+ plugins = config.get('plugins_autoload', '')
+ for plugin in plugins.split():
+ self.plugin_manager.load(plugin)
def start(self):
"""
@@ -200,6 +214,57 @@ class Core(object):
))
self.refresh_window()
+ def connect(self, event, handler):
+ """
+ Connect an handler to an internal event of poezio
+ (eg "enter pressed in a chattab")
+ """
+ # Fail if the method doesn’t take at least the good number of arguments
+ # or if the event is unknown
+ if not event in self.internal_events \
+ or len(getargspec(handler).args) < self.internal_events[event]:
+ return False
+
+ module_name = handler.__module__
+ if not event in self.connected_events:
+ self.connected_events[event] = {}
+ if not module_name in self.connected_events[event]:
+ self.connected_events[event][module_name] = []
+
+ self.connected_events[event][module_name].append(handler)
+ return True
+
+ def run_event(self, event, **kwargs):
+ """
+ Call the handlers associated with an event
+ """
+ if event in self.connected_events:
+ for module in self.connected_events[event]:
+ for handler in self.connected_events[event][module]:
+ try:
+ handler(**kwargs)
+ except:
+ import traceback
+ tp = traceback.format_exc()
+ module_name = handler.__name__
+ log.debug('ERROR: in plugin %s, \n%s' % (module_name, tp))
+
+ def disconnect(self, event, handler):
+ """
+ Disconnect a handler from an event
+ """
+ if not event in self.internal_events:
+ return False
+
+ module_name = getmodule(handler).__name__
+ if not event in self.connected_events:
+ return False
+ if not module_name in self.connected_events[event]:
+ return False
+
+ self.connected_events[event][module_name].remove(handler)
+ return True
+
def resize_global_information_win(self):
"""
Resize the global_information_win only once at each resize.
@@ -1124,6 +1189,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 <plugin>
+ """
+ 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 <plugin>
+ """
+ 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]
@@ -1395,24 +1482,6 @@ class Core(object):
msg = "%s=%s" % (option, value)
self.information(msg, 'Info')
- def command_away(self, arg):
- """
- /away [msg]
- """
- self.command_status("away "+arg)
-
- def command_busy(self, arg):
- """
- /busy [msg]
- """
- self.command_status("busy "+arg)
-
- def command_avail(self, arg):
- """
- /avail [msg]
- """
- self.command_status("available "+arg)
-
def close_tab(self, tab=None):
"""
Close the given tab. If None, close the current one
@@ -1604,3 +1673,54 @@ class Core(object):
if not self.running or self.background is True:
return
curses.doupdate()
+
+ def send_message(self, msg):
+ """
+ Function to use in plugins to send a message in the current conversation.
+ Returns False if the current tab is not a conversation tab
+ """
+ if not isinstance(self.current_tab(), tabs.ChatTab):
+ return False
+ self.current_tab().command_say(msg)
+ return True
+
+ def exec_command(self, command):
+ """
+ Execute an external command on the local or a remote
+ machine, depending on the conf. For example, to open a link in a
+ browser, do exec_command("firefox http://poezio.eu"),
+ and this will call the command on the correct computer.
+ The remote execution is done by writing the command on a fifo.
+ That fifo has to be on the machine where poezio is running, and
+ accessible (through sshfs for example) from the local machine (where
+ poezio is not running). A very simple daemon reads on that fifo,
+ and executes any command that is read in it.
+ """
+ command = '%s\n' % (command,)
+ if config.get('exec_remote', 'false') == 'true':
+ # We just write the command in the fifo
+ if not self.remote_fifo:
+ try:
+ self.remote_fifo = Fifo(os.path.join(config.get('remote_fifo_path', './'), 'poezio.fifo'), 'w')
+ except (OSError, IOError) as e:
+ self.information('Could not open fifo file for writing: %s' % (e,), 'Error')
+ return
+ try:
+ self.remote_fifo.write(command)
+ except (IOError) as e:
+ self.information('Could not execute [%s]: %s' % (command.strip(), e,), 'Error')
+ self.remote_fifo = None
+ else:
+ pass
+
+ def get_conversation_messages(self):
+ """
+ Returns a list of all the messages in the current chat.
+ If the current tab is not a ChatTab, returns None.
+
+ Messages are namedtuples of the form
+ ('txt nick_color time str_time nickname user')
+ """
+ if not isinstance(self.current_tab(), tabs.ChatTab):
+ return None
+ return self.current_tab().get_conversation_messages()