summary refs log tree commit diff stats
path: root/plugins/python/python.c
diff options
context:
space:
mode:
authorberkeviktor@aol.com <berkeviktor@aol.com>2011-02-24 04:14:30 +0100
committerberkeviktor@aol.com <berkeviktor@aol.com>2011-02-24 04:14:30 +0100
commit4a6ceffb98a0b785494f680d3776c4bfc4052f9e (patch)
tree850703c1c841ccd99f58d0b06084615aaebe782c /plugins/python/python.c
parentf16af8be941b596dedac3bf4e371ee2d21f4b598 (diff)
add xchat r1489
Diffstat (limited to 'plugins/python/python.c')
-rw-r--r--plugins/python/python.c2257
1 files changed, 2257 insertions, 0 deletions
diff --git a/plugins/python/python.c b/plugins/python/python.c
new file mode 100644
index 00000000..fd682082
--- /dev/null
+++ b/plugins/python/python.c
@@ -0,0 +1,2257 @@
+/*
+* Copyright (c) 2002-2003  Gustavo Niemeyer <niemeyer@conectiva.com>
+*
+* XChat Python Plugin Interface
+*
+* Xchat Python Plugin Interface 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; either version 2 of the
+* License, or (at your option) any later version.
+*
+* pybot 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 file; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/* Thread support
+ * ==============
+ *
+ * The python interpreter has a global interpreter lock. Any thread
+ * executing must acquire it before working with data accessible from
+ * python code. Here we must also care about xchat not being
+ * thread-safe. We do this by using an xchat lock, which protects
+ * xchat instructions from being executed out of time (when this
+ * plugin is not "active").
+ *
+ * When xchat calls python code:
+ *   - Change the current_plugin for the executing plugin;
+ *   - Release xchat lock
+ *   - Acquire the global interpreter lock
+ *   - Make the python call
+ *   - Release the global interpreter lock
+ *   - Acquire xchat lock
+ *
+ * When python code calls xchat:
+ *   - Release the global interpreter lock
+ *   - Acquire xchat lock
+ *   - Restore context, if necessary
+ *   - Make the xchat call
+ *   - Release xchat lock
+ *   - Acquire the global interpreter lock
+ *
+ * Inside a timer, so that individual threads have a chance to run:
+ *   - Release xchat lock
+ *   - Go ahead threads. Have a nice time!
+ *   - Acquire xchat lock
+ *
+ */
+
+#include <glib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "xchat-plugin.h"
+#include "Python.h"
+#include "structmember.h"
+#include "pythread.h"
+
+#define VERSION_MAJOR 0
+#define VERSION_MINOR 1
+
+#ifdef WIN32
+#undef WITH_THREAD /* Thread support locks up xchat on Win32. */
+#define VERSION "0.8/2.4"	/* Linked to python24.dll */
+#else
+#define VERSION "0.8"
+#endif
+
+#define NONE 0
+#define ALLOW_THREADS 1
+#define RESTORE_CONTEXT 2
+
+#ifdef WITH_THREAD
+#define ACQUIRE_XCHAT_LOCK() PyThread_acquire_lock(xchat_lock, 1)
+#define RELEASE_XCHAT_LOCK() PyThread_release_lock(xchat_lock)
+#define BEGIN_XCHAT_CALLS(x) \
+	do { \
+		PyObject *calls_plugin = NULL; \
+		PyThreadState *calls_thread; \
+		if ((x) & RESTORE_CONTEXT) \
+			calls_plugin = Plugin_GetCurrent(); \
+		calls_thread = PyEval_SaveThread(); \
+		ACQUIRE_XCHAT_LOCK(); \
+		if (!((x) & ALLOW_THREADS)) { \
+			PyEval_RestoreThread(calls_thread); \
+			calls_thread = NULL; \
+		} \
+		if (calls_plugin) \
+			xchat_set_context(ph, \
+				Plugin_GetContext(calls_plugin)); \
+		while (0)
+#define END_XCHAT_CALLS() \
+		RELEASE_XCHAT_LOCK(); \
+		if (calls_thread) \
+			PyEval_RestoreThread(calls_thread); \
+	} while(0)
+#else
+#define ACQUIRE_XCHAT_LOCK()
+#define RELEASE_XCHAT_LOCK()
+#define BEGIN_XCHAT_CALLS(x)
+#define END_XCHAT_CALLS()
+#endif
+
+#ifdef WITH_THREAD
+
+#define BEGIN_PLUGIN(plg) \
+	do { \
+	xchat_context *begin_plugin_ctx = xchat_get_context(ph); \
+	RELEASE_XCHAT_LOCK(); \
+	Plugin_AcquireThread(plg); \
+	Plugin_SetContext(plg, begin_plugin_ctx); \
+	} while (0)
+#define END_PLUGIN(plg) \
+	do { \
+	Plugin_ReleaseThread(plg); \
+	ACQUIRE_XCHAT_LOCK(); \
+	} while (0)
+
+#else /* !WITH_THREAD (win32) */
+
+static PyThreadState *pTempThread;
+
+#define BEGIN_PLUGIN(plg) \
+	do { \
+	xchat_context *begin_plugin_ctx = xchat_get_context(ph); \
+	RELEASE_XCHAT_LOCK(); \
+	PyEval_AcquireLock(); \
+	pTempThread = PyThreadState_Swap(((PluginObject *)(plg))->tstate); \
+	Plugin_SetContext(plg, begin_plugin_ctx); \
+	} while (0)
+#define END_PLUGIN(plg) \
+	do { \
+	((PluginObject *)(plg))->tstate = PyThreadState_Swap(pTempThread); \
+	PyEval_ReleaseLock(); \
+	ACQUIRE_XCHAT_LOCK(); \
+	} while (0)
+
+#endif /* !WITH_THREAD */
+
+#define Plugin_Swap(x) \
+	PyThreadState_Swap(((PluginObject *)(x))->tstate)
+#define Plugin_AcquireThread(x) \
+	PyEval_AcquireThread(((PluginObject *)(x))->tstate)
+#define Plugin_ReleaseThread(x) \
+	Util_ReleaseThread(((PluginObject *)(x))->tstate)
+#define Plugin_GetFilename(x) \
+	(((PluginObject *)(x))->filename)
+#define Plugin_GetName(x) \
+	(((PluginObject *)(x))->name)
+#define Plugin_GetVersion(x) \
+	(((PluginObject *)(x))->version)
+#define Plugin_GetDesc(x) \
+	(((PluginObject *)(x))->description)
+#define Plugin_GetHooks(x) \
+	(((PluginObject *)(x))->hooks)
+#define Plugin_GetContext(x) \
+	(((PluginObject *)(x))->context)
+#define Plugin_SetFilename(x, y) \
+	((PluginObject *)(x))->filename = (y);
+#define Plugin_SetName(x, y) \
+	((PluginObject *)(x))->name = (y);
+#define Plugin_SetVersion(x, y) \
+	((PluginObject *)(x))->version = (y);
+#define Plugin_SetDescription(x, y) \
+	((PluginObject *)(x))->description = (y);
+#define Plugin_SetHooks(x, y) \
+	((PluginObject *)(x))->hooks = (y);
+#define Plugin_SetContext(x, y) \
+	((PluginObject *)(x))->context = (y);
+
+#define HOOK_XCHAT  1
+#define HOOK_UNLOAD 2
+
+/* ===================================================================== */
+/* Object definitions */
+
+typedef struct {
+	PyObject_HEAD
+	int softspace; /* We need it for print support. */
+} XChatOutObject;
+
+typedef struct {
+	PyObject_HEAD
+	xchat_context *context;
+} ContextObject;
+
+typedef struct {
+	PyObject_HEAD
+	const char *listname;
+	PyObject *dict;
+} ListItemObject;
+
+typedef struct {
+	PyObject_HEAD
+	char *name;
+	char *version;
+	char *filename;
+	char *description;
+	GSList *hooks;
+	PyThreadState *tstate;
+	xchat_context *context;
+	void *gui;
+} PluginObject;
+
+typedef struct {
+	int type;
+	PyObject *plugin;
+	PyObject *callback;
+	PyObject *userdata;
+	void *data; /* A handle, when type == HOOK_XCHAT */
+} Hook;
+
+
+/* ===================================================================== */
+/* Function declarations */
+
+static PyObject *Util_BuildList(char *word[]);
+static void Util_Autoload();
+static char *Util_Expand(char *filename);
+
+static int Callback_Command(char *word[], char *word_eol[], void *userdata);
+static int Callback_Print(char *word[], void *userdata);
+static int Callback_Timer(void *userdata);
+static int Callback_ThreadTimer(void *userdata);
+
+static PyObject *XChatOut_New();
+static PyObject *XChatOut_write(PyObject *self, PyObject *args);
+static void XChatOut_dealloc(PyObject *self);
+
+static void Context_dealloc(PyObject *self);
+static PyObject *Context_set(ContextObject *self, PyObject *args);
+static PyObject *Context_command(ContextObject *self, PyObject *args);
+static PyObject *Context_prnt(ContextObject *self, PyObject *args);
+static PyObject *Context_get_info(ContextObject *self, PyObject *args);
+static PyObject *Context_get_list(ContextObject *self, PyObject *args);
+static PyObject *Context_compare(ContextObject *a, ContextObject *b, int op);
+static PyObject *Context_FromContext(xchat_context *context);
+static PyObject *Context_FromServerAndChannel(char *server, char *channel);
+
+static PyObject *Plugin_New(char *filename, PyMethodDef *xchat_methods,
+			    PyObject *xcoobj);
+static PyObject *Plugin_GetCurrent();
+static PluginObject *Plugin_ByString(char *str);
+static Hook *Plugin_AddHook(int type, PyObject *plugin, PyObject *callback,
+			    PyObject *userdata, void *data);
+static void Plugin_RemoveHook(PyObject *plugin, Hook *hook);
+static void Plugin_RemoveAllHooks(PyObject *plugin);
+
+static PyObject *Module_xchat_command(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_prnt(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_get_context(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_find_context(PyObject *self, PyObject *args,
+					   PyObject *kwargs);
+static PyObject *Module_xchat_get_info(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_hook_command(PyObject *self, PyObject *args,
+					   PyObject *kwargs);
+static PyObject *Module_xchat_hook_server(PyObject *self, PyObject *args,
+					  PyObject *kwargs);
+static PyObject *Module_xchat_hook_print(PyObject *self, PyObject *args,
+					 PyObject *kwargs);
+static PyObject *Module_xchat_hook_timer(PyObject *self, PyObject *args,
+					 PyObject *kwargs);
+static PyObject *Module_xchat_unhook(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_get_info(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_get_list(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_get_lists(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_nickcmp(PyObject *self, PyObject *args);
+static PyObject *Module_xchat_strip(PyObject *self, PyObject *args);
+
+static void IInterp_Exec(char *command);
+static int IInterp_Cmd(char *word[], char *word_eol[], void *userdata);
+
+static void Command_PyList();
+static void Command_PyLoad(char *filename);
+static void Command_PyUnload(char *name);
+static void Command_PyReload(char *name);
+static void Command_PyAbout();
+static int Command_Py(char *word[], char *word_eol[], void *userdata);
+
+/* ===================================================================== */
+/* Static declarations and definitions */
+
+staticforward PyTypeObject Plugin_Type;
+staticforward PyTypeObject XChatOut_Type;
+staticforward PyTypeObject Context_Type;
+staticforward PyTypeObject ListItem_Type;
+
+static PyThreadState *main_tstate = NULL;
+static void *thread_timer = NULL;
+
+static xchat_plugin *ph;
+static GSList *plugin_list = NULL;
+
+static PyObject *interp_plugin = NULL;
+static PyObject *xchatout = NULL;
+
+#ifdef WITH_THREAD
+static PyThread_type_lock xchat_lock = NULL;
+#endif
+
+static const char usage[] = "\
+Usage: /PY LOAD   <filename>\n\
+           UNLOAD <filename|name>\n\
+           RELOAD <filename|name>\n\
+           LIST\n\
+           EXEC <command>\n\
+           CONSOLE\n\
+           ABOUT\n\
+\n";
+
+static const char about[] = "\
+\n\
+X-Chat Python Interface " VERSION "\n\
+\n\
+Copyright (c) 2002-2003  Gustavo Niemeyer <niemeyer@conectiva.com>\n\
+\n";
+
+
+/* ===================================================================== */
+/* Utility functions */
+
+static PyObject *
+Util_BuildList(char *word[])
+{
+	PyObject *list;
+	int listsize = 0;
+	int i;
+	while (word[listsize] && word[listsize][0])
+		listsize++;
+	list = PyList_New(listsize);
+	if (list == NULL) {
+                PyErr_Print();
+		return NULL;
+	}
+	for (i = 0; i != listsize; i++) {
+		PyObject *o = PyString_FromString(word[i]);
+		if (o == NULL) {
+			Py_DECREF(list);
+			PyErr_Print();
+			return NULL;
+		}
+		PyList_SetItem(list, i, o);
+	}
+	return list;
+}
+
+static void
+Util_Autoload_from (const char *dir_name)
+{
+#ifndef PATH_MAX
+#define PATH_MAX 1024	/* Hurd doesn't define it */
+#endif
+	char oldcwd[PATH_MAX];
+	struct dirent *ent;
+	DIR *dir;
+	if (getcwd(oldcwd, PATH_MAX) == NULL)
+		return;
+	if (chdir(dir_name) != 0)
+		return;
+	dir = opendir(".");
+	if (dir == NULL)
+		return;
+	while ((ent = readdir(dir))) {
+		int len = strlen(ent->d_name);
+		if (len > 3 && strcmp(".py", ent->d_name+len-3) == 0)
+			Command_PyLoad(ent->d_name);
+	}
+	closedir(dir);
+	chdir(oldcwd);
+}
+
+static void
+Util_Autoload()
+{
+	/* we need local filesystem encoding for chdir, opendir etc */
+
+	/* auto-load from ~/.xchat2/ or %APPDATA%\X-Chat 2\ */
+	Util_Autoload_from(xchat_get_info(ph, "xchatdirfs"));
+
+#ifdef WIN32	/* also auto-load C:\Program Files\XChat\Plugins\*.py */
+	Util_Autoload_from(XCHATLIBDIR"/plugins");
+#endif
+}
+
+static char *
+Util_Expand(char *filename)
+{
+	char *expanded;
+
+	/* Check if this is an absolute path. */
+	if (g_path_is_absolute(filename)) {
+		if (g_file_test(filename, G_FILE_TEST_EXISTS))
+			return g_strdup(filename);
+		else
+			return NULL;
+	}
+
+	/* Check if it starts with ~/ and expand the home if positive. */
+	if (*filename == '~' && *(filename+1) == '/') {
+		expanded = g_build_filename(g_get_home_dir(),
+					    filename+2, NULL);
+		if (g_file_test(expanded, G_FILE_TEST_EXISTS))
+			return expanded;
+		else {
+			g_free(expanded);
+			return NULL;
+		}
+	}
+
+	/* Check if it's in the current directory. */
+	expanded = g_build_filename(g_get_current_dir(),
+				    filename, NULL);
+	if (g_file_test(expanded, G_FILE_TEST_EXISTS))
+		return expanded;
+	g_free(expanded);
+
+	/* Check if ~/.xchat2/<filename> exists. */
+	expanded = g_build_filename(xchat_get_info(ph, "xchatdir"),
+				    filename, NULL);
+	if (g_file_test(expanded, G_FILE_TEST_EXISTS))
+		return expanded;
+	g_free(expanded);
+
+	return NULL;
+}
+
+/* Similar to PyEval_ReleaseThread, but accepts NULL thread states. */
+static void
+Util_ReleaseThread(PyThreadState *tstate)
+{
+	PyThreadState *old_tstate;
+	if (tstate == NULL)
+		Py_FatalError("PyEval_ReleaseThread: NULL thread state");
+	old_tstate = PyThreadState_Swap(NULL);
+	if (old_tstate != tstate && old_tstate != NULL)
+		Py_FatalError("PyEval_ReleaseThread: wrong thread state");
+	PyEval_ReleaseLock();
+}
+
+/* ===================================================================== */
+/* Hookable functions. These are the entry points to python code, besides
+ * the load function, and the hooks for interactive interpreter. */
+
+static int
+Callback_Command(char *word[], char *word_eol[], void *userdata)
+{
+	Hook *hook = (Hook *) userdata;
+	PyObject *retobj;
+	PyObject *word_list, *word_eol_list;
+	int ret = 0;
+
+	BEGIN_PLUGIN(hook->plugin);
+
+	word_list = Util_BuildList(word+1);
+	if (word_list == NULL) {
+		END_PLUGIN(hook->plugin);
+		return 0;
+	}
+	word_eol_list = Util_BuildList(word_eol+1);
+	if (word_eol_list == NULL) {
+		Py_DECREF(word_list);
+		END_PLUGIN(hook->plugin);
+		return 0;
+	}
+
+	retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list,
+				       word_eol_list, hook->userdata);
+	Py_DECREF(word_list);
+	Py_DECREF(word_eol_list);
+
+	if (retobj == Py_None) {
+		ret = XCHAT_EAT_NONE;
+		Py_DECREF(retobj);
+	} else if (retobj) {
+		ret = PyInt_AsLong(retobj);
+		Py_DECREF(retobj);
+	} else {
+		PyErr_Print();
+	}
+
+	END_PLUGIN(hook->plugin);
+
+	return ret;
+}
+
+/* No Callback_Server() here. We use Callback_Command() as well. */
+
+static int
+Callback_Print(char *word[], void *userdata)
+{
+	Hook *hook = (Hook *) userdata;
+	PyObject *retobj;
+	PyObject *word_list;
+	PyObject *word_eol_list;
+	char **word_eol;
+	char *word_eol_raw;
+	int listsize = 0;
+	int next = 0;
+	int i;
+	int ret = 0;
+
+	/* Cut off the message identifier. */
+	word += 1;
+
+	/* XChat doesn't provide a word_eol for print events, so we
+	 * build our own here. */
+	while (word[listsize] && word[listsize][0])
+		listsize++;
+	word_eol = (char **) g_malloc(sizeof(char*)*(listsize+1));
+	if (word_eol == NULL) {
+		xchat_print(ph, "Not enough memory to alloc word_eol "
+				"for python plugin callback.");
+		return 0;
+	}
+	/* First build a word clone, but NULL terminated. */
+	memcpy(word_eol, word, listsize*sizeof(char*));
+	word_eol[listsize] = NULL;
+	/* Then join it. */
+	word_eol_raw = g_strjoinv(" ", word_eol);
+	if (word_eol_raw == NULL) {
+		xchat_print(ph, "Not enough memory to alloc word_eol_raw "
+				"for python plugin callback.");
+		return 0;
+	}
+	/* And rebuild the real word_eol. */
+	for (i = 0; i != listsize; i++) {
+		word_eol[i] = word_eol_raw+next;
+		next += strlen(word[i])+1;
+	}
+	word_eol[i] = "";
+
+	BEGIN_PLUGIN(hook->plugin);
+
+	word_list = Util_BuildList(word);
+	if (word_list == NULL) {
+		g_free(word_eol_raw);
+		g_free(word_eol);
+		END_PLUGIN(hook->plugin);
+		return 0;
+	}
+	word_eol_list = Util_BuildList(word_eol);
+	if (word_eol_list == NULL) {
+		g_free(word_eol_raw);
+		g_free(word_eol);
+		Py_DECREF(word_list);
+		END_PLUGIN(hook->plugin);
+		return 0;
+	}
+
+	retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list,
+				       word_eol_list, hook->userdata);
+	Py_DECREF(word_list);
+	Py_DECREF(word_eol_list);
+
+	g_free(word_eol_raw);
+	g_free(word_eol);
+	if (retobj == Py_None) {
+		ret = XCHAT_EAT_NONE;
+		Py_DECREF(retobj);
+	} else if (retobj) {
+		ret = PyInt_AsLong(retobj);
+		Py_DECREF(retobj);
+	} else {
+		PyErr_Print();
+	}
+
+	END_PLUGIN(hook->plugin);
+
+	return ret;
+}
+
+static int
+Callback_Timer(void *userdata)
+{
+	Hook *hook = (Hook *) userdata;
+	PyObject *retobj;
+	int ret = 0;
+	PyObject *plugin;
+
+	plugin = hook->plugin;
+
+	BEGIN_PLUGIN(hook->plugin);
+
+	retobj = PyObject_CallFunction(hook->callback, "(O)", hook->userdata);
+
+	if (retobj) {
+		ret = PyObject_IsTrue(retobj);
+		Py_DECREF(retobj);
+	} else {
+		PyErr_Print();
+	}
+
+	/* Returning 0 for this callback unhooks itself. */
+	if (ret == 0)
+		Plugin_RemoveHook(plugin, hook);
+
+	END_PLUGIN(plugin);
+
+	return ret;
+}
+
+#ifdef WITH_THREAD
+static int
+Callback_ThreadTimer(void *userdata)
+{
+	RELEASE_XCHAT_LOCK();
+	usleep(1);
+	ACQUIRE_XCHAT_LOCK();
+	return 1;
+}
+#endif
+
+/* ===================================================================== */
+/* XChatOut object */
+
+/* We keep this information global, so we can reset it when the
+ * deinit function is called. */
+/* XXX This should be somehow bound to the printing context. */
+static char *xchatout_buffer = NULL;
+static int xchatout_buffer_size = 0;
+static int xchatout_buffer_pos = 0;
+
+static PyObject *
+XChatOut_New()
+{
+	XChatOutObject *xcoobj;
+	xcoobj = PyObject_New(XChatOutObject, &XChatOut_Type);
+	if (xcoobj != NULL)
+		xcoobj->softspace = 0;
+	return (PyObject *) xcoobj;
+}
+
+static void
+XChatOut_dealloc(PyObject *self)
+{
+	self->ob_type->tp_free((PyObject *)self);
+}
+
+/* This is a little bit complex because we have to buffer data
+ * until a \n is received, since xchat breaks the line automatically.
+ * We also crop the last \n for this reason. */
+static PyObject *
+XChatOut_write(PyObject *self, PyObject *args)
+{
+	int new_buffer_pos, data_size, print_limit, add_space;
+	char *data, *pos;
+	if (!PyArg_ParseTuple(args, "s#:write", &data, &data_size))
+		return NULL;
+	if (!data_size) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+	BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
+	if (((XChatOutObject *)self)->softspace) {
+		add_space = 1;
+		((XChatOutObject *)self)->softspace = 0;
+	} else {
+		add_space = 0;
+	}
+	if (xchatout_buffer_size-xchatout_buffer_pos < data_size+add_space) {
+		char *new_buffer;
+		/* This buffer grows whenever needed, and does not
+		 * shrink. If we ever implement unloading of the
+		 * python interface, we must find some way to free
+		 * this buffer as well. */
+		xchatout_buffer_size += data_size*2+16;
+		new_buffer = g_realloc(xchatout_buffer, xchatout_buffer_size);
+		if (new_buffer == NULL) {
+			xchat_print(ph, "Not enough memory to print");
+			/* The system is out of resources. Let's help. */
+			g_free(xchatout_buffer);
+			xchatout_buffer = NULL;
+			xchatout_buffer_size = 0;
+			xchatout_buffer_pos = 0;
+			/* Return something valid, since we have
+			 * already warned the user, and he probably
+			 * won't be able to notice this exception. */
+			goto exit;
+		}
+		xchatout_buffer = new_buffer;
+	}
+	memcpy(xchatout_buffer+xchatout_buffer_pos, data, data_size);
+	print_limit = new_buffer_pos = xchatout_buffer_pos+data_size;
+	pos = xchatout_buffer+print_limit;
+	if (add_space && *(pos-1) != '\n') {
+		*pos = ' ';
+		*(pos+1) = 0;
+		new_buffer_pos++;
+	}
+	while (*pos != '\n' && print_limit > xchatout_buffer_pos) {
+		pos--;
+		print_limit--;
+	}
+	if (*pos == '\n') {
+		/* Crop it, inserting the string limiter there. */
+		*pos = 0;
+		xchat_print(ph, xchatout_buffer);
+		if (print_limit < new_buffer_pos) {
+			/* There's still data to be printed. */
+			print_limit += 1; /* Include the limiter. */
+			xchatout_buffer_pos = new_buffer_pos-print_limit;
+			memmove(xchatout_buffer, xchatout_buffer+print_limit,
+				xchatout_buffer_pos);
+		} else {
+			xchatout_buffer_pos = 0;
+		}
+	} else {
+		xchatout_buffer_pos = new_buffer_pos;
+	}
+
+exit:
+	END_XCHAT_CALLS();
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+#define OFF(x) offsetof(XChatOutObject, x)
+
+static PyMemberDef XChatOut_members[] = {
+	{"softspace", T_INT, OFF(softspace), 0},
+	{0}
+};
+
+static PyMethodDef XChatOut_methods[] = {
+	{"write", XChatOut_write, METH_VARARGS},
+	{NULL, NULL}
+};
+
+statichere PyTypeObject XChatOut_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"xchat.XChatOut",	/*tp_name*/
+	sizeof(XChatOutObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	XChatOut_dealloc,	/*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	0,			/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+        0,                      /*tp_call*/
+        0,                      /*tp_str*/
+        PyObject_GenericGetAttr,/*tp_getattro*/
+        PyObject_GenericSetAttr,/*tp_setattro*/
+        0,                      /*tp_as_buffer*/
+        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
+        0,                      /*tp_doc*/
+        0,                      /*tp_traverse*/
+        0,                      /*tp_clear*/
+        0,                      /*tp_richcompare*/
+        0,                      /*tp_weaklistoffset*/
+        0,                      /*tp_iter*/
+        0,                      /*tp_iternext*/
+        XChatOut_methods,       /*tp_methods*/
+        XChatOut_members,       /*tp_members*/
+        0,                      /*tp_getset*/
+        0,                      /*tp_base*/
+        0,                      /*tp_dict*/
+        0,                      /*tp_descr_get*/
+        0,                      /*tp_descr_set*/
+        0,                      /*tp_dictoffset*/
+        0,                      /*tp_init*/
+        PyType_GenericAlloc,    /*tp_alloc*/
+        PyType_GenericNew,      /*tp_new*/
+      	_PyObject_Del,          /*tp_free*/
+        0,                      /*tp_is_gc*/
+};
+
+
+/* ===================================================================== */
+/* Context object */
+
+static void
+Context_dealloc(PyObject *self)
+{
+	self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *
+Context_set(ContextObject *self, PyObject *args)
+{
+	PyObject *plugin = Plugin_GetCurrent();
+	Plugin_SetContext(plugin, self->context);
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *
+Context_command(ContextObject *self, PyObject *args)
+{
+	char *text;
+	if (!PyArg_ParseTuple(args, "s:command", &text))
+		return NULL;
+	BEGIN_XCHAT_CALLS(ALLOW_THREADS);
+	xchat_set_context(ph, self->context);
+	xchat_command(ph, text);
+	END_XCHAT_CALLS();
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *
+Context_prnt(ContextObject *self, PyObject *args)
+{
+	char *text;
+	if (!PyArg_ParseTuple(args, "s:prnt", &text))
+		return NULL;
+	BEGIN_XCHAT_CALLS(ALLOW_THREADS);
+	xchat_set_context(ph, self->context);
+	xchat_print(ph, text);
+	END_XCHAT_CALLS();
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *
+Context_emit_print(ContextObject *self, PyObject *args)
+{
+	char *argv[10];
+	char *name;
+	int res;
+	memset(&argv, 0, sizeof(char*)*10);
+	if (!PyArg_ParseTuple(args, "s|ssssss:print_event", &name,
+			      &argv[0], &argv[1], &argv[2],
+			      &argv[3], &argv[4], &argv[5],
+			      &argv[6], &argv[7], &argv[8]))
+		return NULL;
+	BEGIN_XCHAT_CALLS(ALLOW_THREADS);
+	xchat_set_context(ph, self->context);
+	res = xchat_emit_print(ph, name, argv[0], argv[1], argv[2],
+					 argv[3], argv[4], argv[5],
+					 argv[6], argv[7], argv[8]);
+	END_XCHAT_CALLS();
+	return PyInt_FromLong(res);
+}
+
+static PyObject *
+Context_get_info(ContextObject *self, PyObject *args)
+{
+	const char *info;
+	char *name;
+	if (!PyArg_ParseTuple(args, "s:get_info", &name))
+		return NULL;
+	BEGIN_XCHAT_CALLS(NONE);
+	xchat_set_context(ph, self->context);
+	info = xchat_get_info(ph, name);
+	END_XCHAT_CALLS();
+	if (info == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+	return PyString_FromString(info);
+}
+
+static PyObject *
+Context_get_list(ContextObject *self, PyObject *args)
+{
+	PyObject *plugin = Plugin_GetCurrent();
+	xchat_context *saved_context = Plugin_GetContext(plugin);
+	PyObject *ret;
+	Plugin_SetContext(plugin, self->context);
+	ret = Module_xchat_get_list((PyObject*)self, args);
+	Plugin_SetContext(plugin, saved_context);
+	return ret;
+}
+
+/* needed to make context1 == context2 work */
+static PyObject *
+Context_compare(ContextObject *a, ContextObject *b, int op)
+{
+	PyObject *ret;
+	/* check for == */
+	if (op == Py_EQ)
+		ret = (a->context == b->context ? Py_True : Py_False);
+	/* check for != */
+	else if (op == Py_NE)
+		ret = (a->context != b->context ? Py_True : Py_False);
+	/* only makes sense as == and != */
+	else
+	{
+		PyErr_SetString(PyExc_TypeError, "contexts are either equal or not equal");
+		ret = NULL;
+	}
+
+	return ret;
+}
+
+static PyMethodDef Context_methods[] = {
+	{"set", (PyCFunction) Context_set, METH_NOARGS},
+	{"command", (PyCFunction) Context_command, METH_VARARGS},
+	{"prnt", (PyCFunction) Context_prnt, METH_VARARGS},
+	{"emit_print", (PyCFunction) Context_emit_print, METH_VARARGS},
+	{"get_info", (PyCFunction) Context_get_info, METH_VARARGS},
+	{"get_list", (PyCFunction) Context_get_list, METH_VARARGS},
+	{NULL, NULL}
+};
+
+statichere PyTypeObject Context_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"xchat.Context",	/*tp_name*/
+	sizeof(ContextObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	Context_dealloc,        /*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	0,			/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+        0,                      /*tp_call*/
+        0,                      /*tp_str*/
+        PyObject_GenericGetAttr,/*tp_getattro*/
+        PyObject_GenericSetAttr,/*tp_setattro*/
+        0,                      /*tp_as_buffer*/
+        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
+        0,                      /*tp_doc*/
+        0,                      /*tp_traverse*/
+        0,                      /*tp_clear*/
+        (richcmpfunc)Context_compare,    /*tp_richcompare*/
+        0,                      /*tp_weaklistoffset*/
+        0,                      /*tp_iter*/
+        0,                      /*tp_iternext*/
+        Context_methods,        /*tp_methods*/
+        0,                      /*tp_members*/
+        0,                      /*tp_getset*/
+        0,                      /*tp_base*/
+        0,                      /*tp_dict*/
+        0,                      /*tp_descr_get*/
+        0,                      /*tp_descr_set*/
+        0,                      /*tp_dictoffset*/
+        0,                      /*tp_init*/
+        PyType_GenericAlloc,    /*tp_alloc*/
+        PyType_GenericNew,      /*tp_new*/
+      	_PyObject_Del,          /*tp_free*/
+        0,                      /*tp_is_gc*/
+};
+
+static PyObject *
+Context_FromContext(xchat_context *context)
+{
+	ContextObject *ctxobj = PyObject_New(ContextObject, &Context_Type);
+	if (ctxobj != NULL)
+		ctxobj->context = context;
+	return (PyObject *) ctxobj;
+}
+
+static PyObject *
+Context_FromServerAndChannel(char *server, char *channel)
+{
+	ContextObject *ctxobj;
+	xchat_context *context;
+	BEGIN_XCHAT_CALLS(NONE);
+	context = xchat_find_context(ph, server, channel);
+	END_XCHAT_CALLS();
+	if (context == NULL)
+		return NULL;
+	ctxobj = PyObject_New(ContextObject, &Context_Type);
+	if (ctxobj == NULL)
+		return NULL;
+	ctxobj->context = context;
+	return (PyObject *) ctxobj;
+}
+
+
+/* ===================================================================== */
+/* ListItem object */
+
+#undef OFF
+#define OFF(x) offsetof(ListItemObject, x)
+
+static PyMemberDef ListItem_members[] = {
+	{"__dict__", T_OBJECT, OFF(dict), 0},
+	{0}
+};
+
+static void
+ListItem_dealloc(PyObject *self)
+{
+	Py_DECREF(((ListItemObject*)self)->dict);
+	self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *
+ListItem_repr(PyObject *self)
+{
+	return PyString_FromFormat("<%s list item at %p>",
+			    	   ((ListItemObject*)self)->listname, self);
+}
+
+statichere PyTypeObject ListItem_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"xchat.ListItem",	/*tp_name*/
+	sizeof(ListItemObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	ListItem_dealloc,	/*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	ListItem_repr,		/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+        0,                      /*tp_call*/
+        0,                      /*tp_str*/
+        PyObject_GenericGetAttr,/*tp_getattro*/
+        PyObject_GenericSetAttr,/*tp_setattro*/
+        0,                      /*tp_as_buffer*/
+        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
+        0,                      /*tp_doc*/
+        0,                      /*tp_traverse*/
+        0,                      /*tp_clear*/
+        0,                      /*tp_richcompare*/
+        0,                      /*tp_weaklistoffset*/
+        0,                      /*tp_iter*/
+        0,                      /*tp_iternext*/
+        0,                      /*tp_methods*/
+        ListItem_members,       /*tp_members*/
+        0,                      /*tp_getset*/
+        0,                      /*tp_base*/
+        0,                      /*tp_dict*/
+        0,                      /*tp_descr_get*/
+        0,                      /*tp_descr_set*/
+        OFF(dict),              /*tp_dictoffset*/
+        0,                      /*tp_init*/
+        PyType_GenericAlloc,    /*tp_alloc*/
+        PyType_GenericNew,      /*tp_new*/
+      	_PyObject_Del,          /*tp_free*/
+        0,                      /*tp_is_gc*/
+};
+
+static PyObject *
+ListItem_New(const char *listname)
+{
+	ListItemObject *item;
+	item = PyObject_New(ListItemObject, &ListItem_Type);
+	if (item != NULL) {
+		/* listname parameter must be statically allocated. */
+		item->listname = listname;
+		item->dict = PyDict_New();
+		if (item->dict == NULL) {
+			Py_DECREF(item);
+			item = NULL;
+		}
+	}
+	return (PyObject *) item;
+}
+
+
+/* ===================================================================== */
+/* Plugin object */
+
+#define GET_MODULE_DATA(x, force) \
+	o = PyObject_GetAttrString(m, "__module_" #x "__"); \
+	if (o == NULL) { \
+		if (force) { \
+			xchat_print(ph, "Module has no __module_" #x "__ " \
+					"defined"); \
+			goto error; \
+		} \
+		plugin->x = g_strdup(""); \
+	} else {\
+		if (!PyString_Check(o)) { \
+			xchat_print(ph, "Variable __module_" #x "__ " \
+					"must be a string"); \
+			goto error; \
+		} \
+		plugin->x = g_strdup(PyString_AsString(o)); \
+		if (plugin->x == NULL) { \
+			xchat_print(ph, "Not enough memory to allocate " #x); \
+			goto error; \
+		} \
+	}
+
+static PyObject *
+Plugin_New(char *filename, PyMethodDef *xchat_methods, PyObject *xcoobj)
+{
+	PluginObject *plugin = NULL;
+	PyObject *m, *o;
+	char *argv[] = {"<xchat>", 0};
+
+	if (filename) {
+		char *old_filename = filename;
+		filename = Util_Expand(filename);
+		if (filename == NULL) {
+			xchat_printf(ph, "File not found: %s", old_filename);
+			return NULL;
+		}
+	}
+
+	/* Allocate plugin structure. */
+	plugin = PyObject_New(PluginObject, &Plugin_Type);
+	if (plugin == NULL) {
+		xchat_print(ph, "Can't create plugin object");
+		goto error;
+	}
+
+	Plugin_SetName(plugin, NULL);
+	Plugin_SetVersion(plugin, NULL);
+	Plugin_SetFilename(plugin, NULL);
+	Plugin_SetDescription(plugin, NULL);
+	Plugin_SetHooks(plugin, NULL);
+	Plugin_SetContext(plugin, xchat_get_context(ph));
+
+	/* Start a new interpreter environment for this plugin. */
+	PyEval_AcquireLock();
+	plugin->tstate = Py_NewInterpreter();
+	if (plugin->tstate == NULL) {
+		xchat_print(ph, "Can't create interpreter state");
+		goto error;
+	}
+
+	PySys_SetArgv(1, argv);
+	PySys_SetObject("__plugin__", (PyObject *) plugin);
+
+	/* Set stdout and stderr to xchatout. */
+	Py_INCREF(xcoobj);
+	PySys_SetObject("stdout", xcoobj);
+	Py_INCREF(xcoobj);
+	PySys_SetObject("stderr", xcoobj);
+
+	/* Add xchat module to the environment. */
+	m = Py_InitModule("xchat", xchat_methods);
+	if (m == NULL) {
+		xchat_print(ph, "Can't create xchat module");
+		goto error;
+	}
+
+	PyModule_AddIntConstant(m, "EAT_NONE", XCHAT_EAT_NONE);
+	PyModule_AddIntConstant(m, "EAT_XCHAT", XCHAT_EAT_XCHAT);
+	PyModule_AddIntConstant(m, "EAT_PLUGIN", XCHAT_EAT_PLUGIN);
+	PyModule_AddIntConstant(m, "EAT_ALL", XCHAT_EAT_ALL);
+	PyModule_AddIntConstant(m, "PRI_HIGHEST", XCHAT_PRI_HIGHEST);
+	PyModule_AddIntConstant(m, "PRI_HIGH", XCHAT_PRI_HIGH);
+	PyModule_AddIntConstant(m, "PRI_NORM", XCHAT_PRI_NORM);
+	PyModule_AddIntConstant(m, "PRI_LOW", XCHAT_PRI_LOW);
+	PyModule_AddIntConstant(m, "PRI_LOWEST", XCHAT_PRI_LOWEST);
+
+	o = Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR);
+	if (o == NULL) {
+		xchat_print(ph, "Can't create version tuple");
+		goto error;
+	}
+	PyObject_SetAttrString(m, "__version__", o);
+
+	if (filename) {
+#ifdef WIN32
+		PyObject* PyFileObject = PyFile_FromString(filename, "r");
+		if (PyFileObject == NULL) {
+			xchat_printf(ph, "Can't open file %s: %s\n",
+				     filename, strerror(errno));
+			goto error;
+		}
+
+		if (PyRun_SimpleFile(PyFile_AsFile(PyFileObject), filename) != 0) {
+			xchat_printf(ph, "Error loading module %s\n",
+				     filename);
+			goto error;
+		}
+
+		plugin->filename = filename;
+		filename = NULL;
+#else
+		FILE *fp;
+
+		plugin->filename = filename;
+
+		/* It's now owned by the plugin. */
+		filename = NULL;
+
+		/* Open the plugin file. */
+		fp = fopen(plugin->filename, "r");
+		if (fp == NULL) {
+			xchat_printf(ph, "Can't open file %s: %s\n",
+				     plugin->filename, strerror(errno));
+			goto error;
+		}
+
+		/* Run the plugin. */
+		if (PyRun_SimpleFile(fp, plugin->filename) != 0) {
+			xchat_printf(ph, "Error loading module %s\n",
+				     plugin->filename);
+			fclose(fp);
+			goto error;
+		}
+		fclose(fp);
+#endif
+		m = PyDict_GetItemString(PyImport_GetModuleDict(),
+					 "__main__");
+		if (m == NULL) {
+			xchat_print(ph, "Can't get __main__ module");
+			goto error;
+		}
+		GET_MODULE_DATA(name, 1);
+		GET_MODULE_DATA(version, 0);
+		GET_MODULE_DATA(description, 0);
+		plugin->gui = xchat_plugingui_add(ph, plugin->filename,
+						  plugin->name,
+						  plugin->description,
+						  plugin->version, NULL);
+	}
+
+	PyEval_ReleaseThread(plugin->tstate);
+
+	return (PyObject *) plugin;
+
+error:
+	g_free(filename);
+
+	if (plugin) {
+		if (plugin->tstate)
+			Py_EndInterpreter(plugin->tstate);
+		Py_DECREF(plugin);
+	}
+	PyEval_ReleaseLock();
+
+	return NULL;
+}
+
+static PyObject *
+Plugin_GetCurrent()
+{
+	PyObject *plugin;
+	plugin = PySys_GetObject("__plugin__");
+	if (plugin == NULL)
+		PyErr_SetString(PyExc_RuntimeError, "lost sys.__plugin__");
+	return plugin;
+}
+
+static PluginObject *
+Plugin_ByString(char *str)
+{
+	GSList *list;
+	PluginObject *plugin;
+	char *basename;
+	list = plugin_list;
+	while (list != NULL) {
+		plugin = (PluginObject *) list->data;
+		basename = g_path_get_basename(plugin->filename);
+		if (basename == NULL)
+			break;
+		if (strcasecmp(plugin->name, str) == 0 ||
+		    strcasecmp(plugin->filename, str) == 0 ||
+		    strcasecmp(basename, str) == 0) {
+			g_free(basename);
+			return plugin;
+		}
+		g_free(basename);
+		list = list->next;
+	}
+	return NULL;
+}
+
+static Hook *
+Plugin_AddHook(int type, PyObject *plugin, PyObject *callback,
+	       PyObject *userdata, void *data)
+{
+	Hook *hook = (Hook *) g_malloc(sizeof(Hook));
+	if (hook == NULL) {
+		PyErr_NoMemory();
+		return NULL;
+	}
+	hook->type = type;
+	hook->plugin = plugin;
+	Py_INCREF(callback);
+	hook->callback = callback;
+	Py_INCREF(userdata);
+	hook->userdata = userdata;
+	hook->data = NULL;
+	Plugin_SetHooks(plugin, g_slist_append(Plugin_GetHooks(plugin),
+					       hook));
+
+	return hook;
+}
+
+static void
+Plugin_RemoveHook(PyObject *plugin, Hook *hook)
+{
+	GSList *list;
+	/* Is this really a hook of the running plugin? */
+	list = g_slist_find(Plugin_GetHooks(plugin), hook);
+	if (list) {
+		/* Ok, unhook it. */
+		if (hook->type == HOOK_XCHAT) {
+			/* This is an xchat hook. Unregister it. */
+			BEGIN_XCHAT_CALLS(NONE);
+			xchat_unhook(ph, (xchat_hook*)hook->data);
+			END_XCHAT_CALLS();
+		}
+		Plugin_SetHooks(plugin,
+				g_slist_remove(Plugin_GetHooks(plugin),
+					       hook));
+		Py_DECREF(hook->callback);
+		Py_DECREF(hook->userdata);
+		g_free(hook);
+	}
+}
+
+static void
+Plugin_RemoveAllHooks(PyObject *plugin)
+{
+	GSList *list = Plugin_GetHooks(plugin);
+	while (list) {
+		Hook *hook = (Hook *) list->data;
+		if (hook->type == HOOK_XCHAT) {
+			/* This is an xchat hook. Unregister it. */
+			BEGIN_XCHAT_CALLS(NONE);
+			xchat_unhook(ph, (xchat_hook*)hook->data);
+			END_XCHAT_CALLS();
+		}
+		Py_DECREF(hook->callback);
+		Py_DECREF(hook->userdata);
+		g_free(hook);
+		list = list->next;
+	}
+	Plugin_SetHooks(plugin, NULL);
+}
+
+static void
+Plugin_Delete(PyObject *plugin)
+{
+	PyThreadState *tstate = ((PluginObject*)plugin)->tstate;
+	GSList *list = Plugin_GetHooks(plugin);
+	while (list) {
+		Hook *hook = (Hook *) list->data;
+		if (hook->type == HOOK_UNLOAD) {
+			PyObject *retobj;
+			retobj = PyObject_CallFunction(hook->callback, "(O)",
+						       hook->userdata);
+			if (retobj) {
+				Py_DECREF(retobj);
+			} else {
+				PyErr_Print();
+				PyErr_Clear();
+			}
+		}
+		list = list->next;
+	}
+	Plugin_RemoveAllHooks(plugin);
+	xchat_plugingui_remove(ph, ((PluginObject *)plugin)->gui);
+	Py_DECREF(plugin);
+	/*PyThreadState_Swap(tstate); needed? */
+	Py_EndInterpreter(tstate);
+}
+
+static void
+Plugin_dealloc(PluginObject *self)
+{
+	g_free(self->filename);
+	g_free(self->name);
+	g_free(self->version);
+	g_free(self->description);
+	self->ob_type->tp_free((PyObject *)self);
+}
+
+statichere PyTypeObject Plugin_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"xchat.Plugin",		/*tp_name*/
+	sizeof(PluginObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	(destructor)Plugin_dealloc, /*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	0,			/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+        0,                      /*tp_call*/
+        0,                      /*tp_str*/
+        PyObject_GenericGetAttr,/*tp_getattro*/
+        PyObject_GenericSetAttr,/*tp_setattro*/
+        0,                      /*tp_as_buffer*/
+        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
+        0,                      /*tp_doc*/
+        0,                      /*tp_traverse*/
+        0,                      /*tp_clear*/
+        0,                      /*tp_richcompare*/
+        0,                      /*tp_weaklistoffset*/
+        0,                      /*tp_iter*/
+        0,                      /*tp_iternext*/
+        0,                      /*tp_methods*/
+        0,                      /*tp_members*/
+        0,                      /*tp_getset*/
+        0,                      /*tp_base*/
+        0,                      /*tp_dict*/
+        0,                      /*tp_descr_get*/
+        0,                      /*tp_descr_set*/
+        0,                      /*tp_dictoffset*/
+        0,                      /*tp_init*/
+        PyType_GenericAlloc,    /*tp_alloc*/
+        PyType_GenericNew,      /*tp_new*/
+      	_PyObject_Del,          /*tp_free*/
+        0,                      /*tp_is_gc*/
+};
+
+
+/* ===================================================================== */
+/* XChat module */
+
+static PyObject *
+Module_xchat_command(PyObject *self, PyObject *args)
+{
+	char *text;
+	if (!PyArg_ParseTuple(args, "s:command", &text))
+		return NULL;
+	BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
+	xchat_command(ph, text);
+	END_XCHAT_CALLS();
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *
+Module_xchat_prnt(PyObject *self, PyObject *args)
+{
+	char *text;
+	if (!PyArg_ParseTuple(args, "s:prnt", &text))
+		return NULL;
+	BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
+	xchat_print(ph, text);
+	END_XCHAT_CALLS();
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *
+Module_xchat_emit_print(PyObject *self, PyObject *args)
+{
+	char *argv[10];
+	char *name;
+	int res;
+	memset(&argv, 0, sizeof(char*)*10);
+	if (!PyArg_ParseTuple(args, "s|ssssss:print_event", &name,
+			      &argv[0], &argv[1], &argv[2],
+			      &argv[3], &argv[4], &argv[5],
+			      &argv[6], &argv[7], &argv[8]))
+		return NULL;
+	BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
+	res = xchat_emit_print(ph, name, argv[0], argv[1], argv[2],
+					 argv[3], argv[4], argv[5],
+					 argv[6], argv[7], argv[8]);
+	END_XCHAT_CALLS();
+	return PyInt_FromLong(res);
+}
+
+static PyObject *
+Module_xchat_get_info(PyObject *self, PyObject *args)
+{
+	const char *info;
+	char *name;
+	if (!PyArg_ParseTuple(args, "s:get_info", &name))
+		return NULL;
+	BEGIN_XCHAT_CALLS(RESTORE_CONTEXT);
+	info = xchat_get_info(ph, name);
+	END_XCHAT_CALLS();
+	if (info == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+	return PyString_FromString(info);
+}
+
+static PyObject *
+Module_xchat_get_prefs(PyObject *self, PyObject *args)
+{
+	PyObject *res;
+	const char *info;
+	int integer;
+	char *name;
+	int type;
+	if (!PyArg_ParseTuple(args, "s:get_prefs", &name))
+		return NULL;
+	BEGIN_XCHAT_CALLS(NONE);
+	type = xchat_get_prefs(ph, name, &info, &integer);
+	END_XCHAT_CALLS();
+	switch (type) {
+		case 0:
+			Py_INCREF(Py_None);
+			res = Py_None;
+			break;
+		case 1:
+			res = PyString_FromString((char*)info);
+			break;
+		case 2:
+		case 3:
+			res = PyInt_FromLong(integer);
+			break;
+		default:
+			PyErr_Format(PyExc_RuntimeError,
+				     "unknown get_prefs type (%d), "
+				     "please report", type);
+			res = NULL;
+			break;
+	}
+	return res;
+}
+
+static PyObject *
+Module_xchat_get_context(PyObject *self, PyObject *args)
+{
+	PyObject *plugin;
+	PyObject *ctxobj;
+	plugin = Plugin_GetCurrent();
+	if (plugin == NULL)
+		return NULL;
+	ctxobj = Context_FromContext(Plugin_GetContext(plugin));
+	if (ctxobj == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+	return ctxobj;
+}
+
+static PyObject *
+Module_xchat_find_context(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+	char *server = NULL;
+	char *channel = NULL;
+	PyObject *ctxobj;
+	char *kwlist[] = {"server", "channel", 0};
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|zz:find_context",
+					 kwlist, &server, &channel))
+		return NULL;
+	ctxobj = Context_FromServerAndChannel(server, channel);
+	if (ctxobj == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+	return ctxobj;
+}
+
+static PyObject *
+Module_xchat_hook_command(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+	char *name;
+	PyObject *callback;
+	PyObject *userdata = Py_None;
+	int priority = XCHAT_PRI_NORM;
+	char *help = NULL;
+	PyObject *plugin;
+	Hook *hook;
+	char *kwlist[] = {"name", "callback", "userdata",
+			  "priority", "help", 0};
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oiz:hook_command",
+					 kwlist, &name, &callback, &userdata,
+					 &priority, &help))
+		return NULL;
+
+	plugin = Plugin_GetCurrent();
+	if (plugin == NULL)
+		return NULL;
+	if (!PyCallable_Check(callback)) {
+		PyErr_SetString(PyExc_TypeError, "callback is not callable");
+		return NULL;
+	}
+
+	hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL);
+	if (hook == NULL)
+		return NULL;
+
+	BEGIN_XCHAT_CALLS(NONE);
+	hook->data = (void*)xchat_hook_command(ph, name, priority,
+					       Callback_Command, help, hook);
+	END_XCHAT_CALLS();
+
+	return PyInt_FromLong((long)hook);
+}
+
+static PyObject *
+Module_xchat_hook_server(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+	char *name;
+	PyObject *callback;
+	PyObject *userdata = Py_None;
+	int priority = XCHAT_PRI_NORM;
+	PyObject *plugin;
+	Hook *hook;
+	char *kwlist[] = {"name", "callback", "userdata", "priority", 0};
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_server",
+					 kwlist, &name, &callback, &userdata,
+					 &priority))
+		return NULL;
+
+	plugin = Plugin_GetCurrent();
+	if (plugin == NULL)
+		return NULL;
+	if (!PyCallable_Check(callback)) {
+		PyErr_SetString(PyExc_TypeError, "callback is not callable");
+		return NULL;
+	}
+
+	hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL);
+	if (hook == NULL)
+		return NULL;
+
+	BEGIN_XCHAT_CALLS(NONE);
+	hook->data = (void*)xchat_hook_server(ph, name, priority,
+					      Callback_Command, hook);
+	END_XCHAT_CALLS();
+
+	return PyInt_FromLong((long)hook);
+}
+
+static PyObject *
+Module_xchat_hook_print(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+	char *name;
+	PyObject *callback;
+	PyObject *userdata = Py_None;
+	int priority = XCHAT_PRI_NORM;
+	PyObject *plugin;
+	Hook *hook;
+	char *kwlist[] = {"name", "callback", "userdata", "priority", 0};
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print",
+					 kwlist, &name, &callback, &userdata,
+					 &priority))
+		return NULL;
+
+	plugin = Plugin_GetCurrent();
+	if (plugin == NULL)
+		return NULL;
+	if (!PyCallable_Check(callback)) {
+		PyErr_SetString(PyExc_TypeError, "callback is not callable");
+		return NULL;
+	}
+
+	hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL);
+	if (hook == NULL)
+		return NULL;
+
+	BEGIN_XCHAT_CALLS(NONE);
+	hook->data = (void*)xchat_hook_print(ph, name, priority,
+					     Callback_Print, hook);
+	END_XCHAT_CALLS();
+
+	return PyInt_FromLong((long)hook);
+}
+
+
+static PyObject *
+Module_xchat_hook_timer(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+	int timeout;
+	PyObject *callback;
+	PyObject *userdata = Py_None;
+	PyObject *plugin;
+	Hook *hook;
+	char *kwlist[] = {"timeout", "callback", "userdata", 0};
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO|O:hook_timer",
+					 kwlist, &timeout, &callback,
+					 &userdata))
+		return NULL;
+
+	plugin = Plugin_GetCurrent();
+	if (plugin == NULL)
+		return NULL;
+	if (!PyCallable_Check(callback)) {
+		PyErr_SetString(PyExc_TypeError, "callback is not callable");
+		return NULL;
+	}
+
+	hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL);
+	if (hook == NULL)
+		return NULL;
+
+	BEGIN_XCHAT_CALLS(NONE);
+	hook->data = (void*)xchat_hook_timer(ph, timeout,
+					     Callback_Timer, hook);
+	END_XCHAT_CALLS();
+
+	return PyInt_FromLong((long)hook);
+}
+
+static PyObject *
+Module_xchat_hook_unload(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+	PyObject *callback;
+	PyObject *userdata = Py_None;
+	PyObject *plugin;
+	Hook *hook;
+	char *kwlist[] = {"callback", "userdata", 0};
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O:hook_unload",
+					 kwlist, &callback, &userdata))
+		return NULL;
+
+	plugin = Plugin_GetCurrent();
+	if (plugin == NULL)
+		return NULL;
+	if (!PyCallable_Check(callback)) {
+		PyErr_SetString(PyExc_TypeError, "callback is not callable");
+		return NULL;
+	}
+
+	hook = Plugin_AddHook(HOOK_UNLOAD, plugin, callback, userdata, NULL);
+	if (hook == NULL)
+		return NULL;
+
+	return PyInt_FromLong((long)hook);
+}
+
+static PyObject *
+Module_xchat_unhook(PyObject *self, PyObject *args)
+{
+	PyObject *plugin;
+	Hook *hook;
+	if (!PyArg_ParseTuple(args, "l:unhook", &hook))
+		return NULL;
+	plugin = Plugin_GetCurrent();
+	if (plugin == NULL)
+		return NULL;
+	Plugin_RemoveHook(plugin, hook);
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *
+Module_xchat_get_list(PyObject *self, PyObject *args)
+{
+	xchat_list *list;
+	PyObject *l;
+	const char *name;
+	const char *const *fields;
+	int i;
+
+	if (!PyArg_ParseTuple(args, "s:get_list", &name))
+		return NULL;
+	/* This function is thread safe, and returns statically
+	 * allocated data. */
+	fields = xchat_list_fields(ph, "lists");
+	for (i = 0; fields[i]; i++) {
+		if (strcmp(fields[i], name) == 0) {
+			/* Use the static allocated one. */
+			name = fields[i];
+			break;
+		}
+	}
+	if (fields[i] == NULL) {
+		PyErr_SetString(PyExc_KeyError, "list not available");
+		return NULL;
+	}
+	l = PyList_New(0);
+	if (l == NULL)
+		return NULL;
+	BEGIN_XCHAT_CALLS(RESTORE_CONTEXT);
+	list = xchat_list_get(ph, (char*)name);
+	if (list == NULL)
+		goto error;
+	fields = xchat_list_fields(ph, (char*)name);
+	while (xchat_list_next(ph, list)) {
+		PyObject *o = ListItem_New(name);
+		if (o == NULL || PyList_Append(l, o) == -1) {
+			Py_XDECREF(o);
+			goto error;
+		}
+		Py_DECREF(o); /* l is holding a reference */
+		for (i = 0; fields[i]; i++) {
+			const char *fld = fields[i]+1;
+			PyObject *attr = NULL;
+			const char *sattr;
+			int iattr;
+			switch(fields[i][0]) {
+			case 's':
+				sattr = xchat_list_str(ph, list, (char*)fld);
+				attr = PyString_FromString(sattr?sattr:"");
+				break;
+			case 'i':
+				iattr = xchat_list_int(ph, list, (char*)fld);
+				attr = PyInt_FromLong((long)iattr);
+				break;
+			case 'p':
+				sattr = xchat_list_str(ph, list, (char*)fld);
+				if (strcmp(fld, "context") == 0) {
+					attr = Context_FromContext(
+						(xchat_context*)sattr);
+					break;
+				}
+			default: /* ignore unknown (newly added?) types */
+				continue;
+			}
+			if (attr == NULL)
+				goto error;
+			PyObject_SetAttrString(o, (char*)fld, attr); /* add reference on attr in o */
+			Py_DECREF(attr); /* make o own attr */
+		}
+	}
+	xchat_list_free(ph, list);
+	goto exit;
+error:
+	if (list)
+		xchat_list_free(ph, list);
+	Py_DECREF(l);
+	l = NULL;
+
+exit:
+	END_XCHAT_CALLS();
+	return l;
+}
+
+static PyObject *
+Module_xchat_get_lists(PyObject *self, PyObject *args)
+{
+	PyObject *l, *o;
+	const char *const *fields;
+	int i;
+	/* This function is thread safe, and returns statically
+	 * allocated data. */
+	fields = xchat_list_fields(ph, "lists");
+	l = PyList_New(0);
+	if (l == NULL)
+		return NULL;
+	for (i = 0; fields[i]; i++) {
+		o = PyString_FromString(fields[i]);
+		if (o == NULL || PyList_Append(l, o) == -1) {
+			Py_DECREF(l);
+			Py_XDECREF(o);
+			return NULL;
+		}
+		Py_DECREF(o); /* l is holding a reference */
+	}
+	return l;
+}
+
+static PyObject *
+Module_xchat_nickcmp(PyObject *self, PyObject *args)
+{
+	char *s1, *s2;
+	if (!PyArg_ParseTuple(args, "ss:nickcmp", &s1, &s2))
+		return NULL;
+	return PyInt_FromLong((long) xchat_nickcmp(ph, s1, s2));
+}
+
+static PyObject *
+Module_xchat_strip(PyObject *self, PyObject *args)
+{
+	PyObject *result;
+	char *str, *str2;
+	int len = -1, flags = 1 | 2;
+	if (!PyArg_ParseTuple(args, "s|ii:strip", &str, &len, &flags))
+		return NULL;
+	str2 = xchat_strip(ph, str, len, flags);
+	result = PyString_FromString(str2);
+	xchat_free(ph, str2);
+	return result;
+}
+
+static PyMethodDef Module_xchat_methods[] = {
+	{"command",		Module_xchat_command,
+		METH_VARARGS},
+	{"prnt",		Module_xchat_prnt,
+		METH_VARARGS},
+	{"emit_print",		Module_xchat_emit_print,
+		METH_VARARGS},
+	{"get_info",		Module_xchat_get_info,
+		METH_VARARGS},
+	{"get_prefs",		Module_xchat_get_prefs,
+		METH_VARARGS},
+	{"get_context",		Module_xchat_get_context,
+		METH_NOARGS},
+	{"find_context",	(PyCFunction)Module_xchat_find_context,
+		METH_VARARGS|METH_KEYWORDS},
+	{"hook_command",	(PyCFunction)Module_xchat_hook_command,
+		METH_VARARGS|METH_KEYWORDS},
+	{"hook_server",		(PyCFunction)Module_xchat_hook_server,
+		METH_VARARGS|METH_KEYWORDS},
+	{"hook_print",		(PyCFunction)Module_xchat_hook_print,
+		METH_VARARGS|METH_KEYWORDS},
+	{"hook_timer",		(PyCFunction)Module_xchat_hook_timer,
+		METH_VARARGS|METH_KEYWORDS},
+	{"hook_unload",		(PyCFunction)Module_xchat_hook_unload,
+		METH_VARARGS|METH_KEYWORDS},
+	{"unhook",		Module_xchat_unhook,
+		METH_VARARGS},
+	{"get_list",		Module_xchat_get_list,
+		METH_VARARGS},
+	{"get_lists",		Module_xchat_get_lists,
+		METH_NOARGS},
+	{"nickcmp",		Module_xchat_nickcmp,
+		METH_VARARGS},
+	{"strip",		Module_xchat_strip,
+		METH_VARARGS},
+	{NULL, NULL}
+};
+
+
+/* ===================================================================== */
+/* Python interactive interpreter functions */
+
+static void
+IInterp_Exec(char *command)
+{
+        PyObject *m, *d, *o;
+	char *buffer;
+	int len;
+
+	BEGIN_PLUGIN(interp_plugin);
+
+        m = PyImport_AddModule("__main__");
+        if (m == NULL) {
+		xchat_print(ph, "Can't get __main__ module");
+		goto fail;
+	}
+        d = PyModule_GetDict(m);
+	len = strlen(command);
+	buffer = (char *) g_malloc(len+2);
+	if (buffer == NULL) {
+		xchat_print(ph, "Not enough memory for command buffer");
+		goto fail;
+	}
+	memcpy(buffer, command, len);
+	buffer[len] = '\n';
+	buffer[len+1] = 0;
+        o = PyRun_StringFlags(buffer, Py_single_input, d, d, NULL);
+	g_free(buffer);
+        if (o == NULL) {
+                PyErr_Print();
+		goto fail;
+        }
+        Py_DECREF(o);
+        if (Py_FlushLine())
+                PyErr_Clear();
+
+fail:
+	END_PLUGIN(interp_plugin);
+
+        return;
+}
+
+static int
+IInterp_Cmd(char *word[], char *word_eol[], void *userdata)
+{
+	char *channel = (char *) xchat_get_info(ph, "channel");
+	if (channel && channel[0] == '>' && strcmp(channel, ">>python<<") == 0) {
+		xchat_printf(ph, ">>> %s\n", word_eol[1]);
+		IInterp_Exec(word_eol[1]);
+		return 1;
+	}
+	return 0;
+}
+
+
+/* ===================================================================== */
+/* Python command handling */
+
+static void
+Command_PyList()
+{
+	GSList *list;
+	list = plugin_list;
+	if (list == NULL) {
+		xchat_print(ph, "No python modules loaded");
+	} else {
+		xchat_print(ph,
+		   "Name         Version  Filename             Description\n"
+		   "----         -------  --------             -----------\n");
+		while (list != NULL) {
+			PluginObject *plg = (PluginObject *) list->data;
+			char *basename = g_path_get_basename(plg->filename);
+			xchat_printf(ph, "%-12s %-8s %-20s %-10s\n",
+				     plg->name,
+				     *plg->version ? plg->version
+				     		  : "<none>",
+				     basename,
+				     *plg->description ? plg->description
+				     		      : "<none>");
+			g_free(basename);
+			list = list->next;
+		}
+		xchat_print(ph, "\n");
+	}
+}
+
+static void
+Command_PyLoad(char *filename)
+{
+	PyObject *plugin;
+	RELEASE_XCHAT_LOCK();
+	plugin = Plugin_New(filename, Module_xchat_methods, xchatout);
+	ACQUIRE_XCHAT_LOCK();
+	if (plugin)
+		plugin_list = g_slist_append(plugin_list, plugin);
+}
+
+static void
+Command_PyUnload(char *name)
+{
+	PluginObject *plugin = Plugin_ByString(name);
+	if (!plugin) {
+		xchat_print(ph, "Can't find a python plugin with that name");
+	} else {
+		BEGIN_PLUGIN(plugin);
+		Plugin_Delete((PyObject*)plugin);
+		END_PLUGIN(plugin);
+		plugin_list = g_slist_remove(plugin_list, plugin);
+	}
+}
+
+static void
+Command_PyReload(char *name)
+{
+	PluginObject *plugin = Plugin_ByString(name);
+	if (!plugin) {
+		xchat_print(ph, "Can't find a python plugin with that name");
+	} else {
+		char *filename = strdup(plugin->filename);
+		Command_PyUnload(filename);
+		Command_PyLoad(filename);
+		g_free(filename);
+	}
+}
+
+static void
+Command_PyAbout()
+{
+	xchat_print(ph, about);
+}
+
+static int
+Command_Py(char *word[], char *word_eol[], void *userdata)
+{
+	char *cmd = word[2];
+	int ok = 0;
+	if (strcasecmp(cmd, "LIST") == 0) {
+		ok = 1;
+		Command_PyList();
+	} else if (strcasecmp(cmd, "EXEC") == 0) {
+		if (word[3][0]) {
+			ok = 1;
+			IInterp_Exec(word_eol[3]);
+		}
+	} else if (strcasecmp(cmd, "LOAD") == 0) {
+		if (word[3][0]) {
+			ok = 1;
+			Command_PyLoad(word[3]);
+		}
+	} else if (strcasecmp(cmd, "UNLOAD") == 0) {
+		if (word[3][0]) {
+			ok = 1;
+			Command_PyUnload(word[3]);
+		}
+	} else if (strcasecmp(cmd, "RELOAD") == 0) {
+		if (word[3][0]) {
+			ok = 1;
+			Command_PyReload(word[3]);
+		}
+	} else if (strcasecmp(cmd, "CONSOLE") == 0) {
+		ok = 1;
+		xchat_command(ph, "QUERY >>python<<");
+	} else if (strcasecmp(cmd, "ABOUT") == 0) {
+		ok = 1;
+		Command_PyAbout();
+	}
+	if (!ok)
+		xchat_print(ph, usage);
+	return XCHAT_EAT_ALL;
+}
+
+static int
+Command_Load(char *word[], char *word_eol[], void *userdata)
+{
+	int len = strlen(word[2]);
+	if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) {
+		Command_PyLoad(word[2]);
+		return XCHAT_EAT_XCHAT;
+	}
+	return XCHAT_EAT_NONE;
+}
+
+static int
+Command_Unload(char *word[], char *word_eol[], void *userdata)
+{
+	int len = strlen(word[2]);
+	if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) {
+		Command_PyUnload(word[2]);
+		return XCHAT_EAT_XCHAT;
+	}
+	return XCHAT_EAT_NONE;
+}
+
+/* ===================================================================== */
+/* Autoload function */
+
+/* ===================================================================== */
+/* (De)initialization functions */
+
+static int initialized = 0;
+static int reinit_tried = 0;
+
+void
+xchat_plugin_get_info(char **name, char **desc, char **version, void **reserved)
+{
+	*name = "Python";
+	*version = VERSION;
+	*desc = "Python scripting interface";
+   if (reserved)
+      *reserved = NULL;
+}
+
+int
+xchat_plugin_init(xchat_plugin *plugin_handle,
+		  char **plugin_name,
+		  char **plugin_desc,
+		  char **plugin_version,
+		  char *arg)
+{
+	char *argv[] = {"<xchat>", 0};
+
+	ph = plugin_handle;
+
+	/* Block double initalization. */
+	if (initialized != 0) {
+		xchat_print(ph, "Python interface already loaded");
+		/* deinit is called even when init fails, so keep track
+		 * of a reinit failure. */
+		reinit_tried++;
+		return 0;
+	}
+	initialized = 1;
+
+	*plugin_name = "Python";
+	*plugin_version = VERSION;
+	*plugin_desc = "Python scripting interface";
+
+	/* Initialize python. */
+	Py_SetProgramName("xchat");
+	Py_Initialize();
+	PySys_SetArgv(1, argv);
+
+	Plugin_Type.ob_type = &PyType_Type;
+	Context_Type.ob_type = &PyType_Type;
+	XChatOut_Type.ob_type = &PyType_Type;
+
+	xchatout = XChatOut_New();
+	if (xchatout == NULL) {
+		xchat_print(ph, "Can't allocate xchatout object");
+		return 0;
+	}
+
+#ifdef WITH_THREAD
+	PyEval_InitThreads();
+	xchat_lock = PyThread_allocate_lock();
+	if (xchat_lock == NULL) {
+		xchat_print(ph, "Can't allocate xchat lock");
+		Py_DECREF(xchatout);
+		xchatout = NULL;
+		return 0;
+	}
+#endif
+
+	main_tstate = PyEval_SaveThread();
+
+	interp_plugin = Plugin_New(NULL, Module_xchat_methods, xchatout);
+	if (interp_plugin == NULL) {
+		xchat_print(ph, "Plugin_New() failed.\n");
+#ifdef WITH_THREAD
+		PyThread_free_lock(xchat_lock);
+#endif
+		Py_DECREF(xchatout);
+		xchatout = NULL;
+		return 0;
+	}
+
+
+	xchat_hook_command(ph, "", XCHAT_PRI_NORM, IInterp_Cmd, 0, 0);
+	xchat_hook_command(ph, "PY", XCHAT_PRI_NORM, Command_Py, usage, 0);
+	xchat_hook_command(ph, "LOAD", XCHAT_PRI_NORM, Command_Load, 0, 0);
+	xchat_hook_command(ph, "UNLOAD", XCHAT_PRI_NORM, Command_Unload, 0, 0);
+#ifdef WITH_THREAD
+	thread_timer = xchat_hook_timer(ph, 300, Callback_ThreadTimer, NULL);
+#endif
+
+	xchat_print(ph, "Python interface loaded\n");
+
+	Util_Autoload();
+	return 1;
+}
+
+int
+xchat_plugin_deinit()
+{
+	GSList *list;
+
+	/* A reinitialization was tried. Just give up and live the
+	 * environment as is. We are still alive. */
+	if (reinit_tried) {
+		reinit_tried--;
+		return 1;
+	}
+
+	list = plugin_list;
+	while (list != NULL) {
+		PyObject *plugin = (PyObject *) list->data;
+		BEGIN_PLUGIN(plugin);
+		Plugin_Delete(plugin);
+		END_PLUGIN(plugin);
+		list = list->next;
+	}
+	g_slist_free(plugin_list);
+	plugin_list = NULL;
+
+	/* Reset xchatout buffer. */
+	g_free(xchatout_buffer);
+	xchatout_buffer = NULL;
+	xchatout_buffer_size = 0;
+	xchatout_buffer_pos = 0;
+
+	if (interp_plugin) {
+		Py_DECREF(interp_plugin);
+		interp_plugin = NULL;
+	}
+
+	/* Switch back to the main thread state. */
+	if (main_tstate) {
+		PyThreadState_Swap(main_tstate);
+		main_tstate = NULL;
+	}
+	Py_Finalize();
+
+#ifdef WITH_THREAD
+	if (thread_timer != NULL) {
+		xchat_unhook(ph, thread_timer);
+		thread_timer = NULL;
+	}
+	PyThread_free_lock(xchat_lock);
+#endif
+
+	xchat_print(ph, "Python interface unloaded\n");
+	initialized = 0;
+
+	return 1;
+}
+