/*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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 <stdlib.h>
#include <sys/types.h>
#ifdef WIN32
#include <direct.h>
#include <glib/gstdio.h>
#include "../../src/dirent/dirent-win32.h"
#include "../../config-win32.h"
#else
#include <unistd.h>
#include <dirent.h>
#endif
#include "hexchat-plugin.h"
#undef _POSIX_C_SOURCE /* Avoid warning: also in /usr/include/features.h from glib.h */
#include <Python.h>
#include <structmember.h>
#include <pythread.h>
/* Macros to convert version macros into string literals.
* The indirect macro is a well-known preprocessor trick to force X to be evaluated before the # operator acts to make it a string literal.
* If STRINGIZE were to be directly defined as #X instead, VERSION would be "VERSION_MAJOR" instead of "1".
*/
#define STRINGIZE2(X) #X
#define STRINGIZE(X) STRINGIZE2(X)
/* Version number macros */
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
/* Version string macro */
#ifdef WIN32
#if PY_MAJOR_VERSION == 2
#define VERSION STRINGIZE(VERSION_MAJOR) "." STRINGIZE(VERSION_MINOR) "/2.7" /* Linked to python27.dll */
#elif PY_MAJOR_VERSION == 3
#define VERSION STRINGIZE(VERSION_MAJOR) "." STRINGIZE(VERSION_MINOR) "/3.3" /* Linked to python33.dll */
#endif
#endif
#ifndef VERSION
#define VERSION STRINGIZE(VERSION_MAJOR) "." STRINGIZE(VERSION_MINOR)
#endif
/* #define's for Python 2 */
#if PY_MAJOR_VERSION == 2
#undef PyLong_Check
#define PyLong_Check PyInt_Check
#define PyLong_AsLong PyInt_AsLong
#define PyLong_FromLong PyInt_FromLong
#undef PyUnicode_Check
#undef PyUnicode_FromString
#undef PyUnicode_FromFormat
#define PyUnicode_Check PyString_Check
#define PyUnicode_AsFormat PyString_AsFormat
#define PyUnicode_FromFormat PyString_FromFormat
#define PyUnicode_FromString PyString_FromString
#define PyUnicode_AsUTF8 PyString_AsString
#ifdef WIN32
#undef WITH_THREAD
#endif
#endif
/* #define for Python 3 */
#if PY_MAJOR_VERSION == 3
#define IS_PY3K
#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) \
hexchat_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 { \
hexchat_context *begin_plugin_ctx = hexchat_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 { \
hexchat_context *begin_plugin_ctx = hexchat_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 Plugin_SetGui(x, y) \
((PluginObject *)(x))->gui = (y);
#define HOOK_XCHAT 1
#define HOOK_XCHAT_ATTR 2
#define HOOK_UNLOAD 3
/* ===================================================================== */
/* Object definitions */
typedef struct {
PyObject_HEAD
int softspace; /* We need it for print support. */
} XChatOutObject;
typedef struct {
PyObject_HEAD
hexchat_context *context;
} ContextObject;
typedef struct {
PyObject_HEAD
PyObject *time;
} AttributeObject;
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;
hexchat_context *context;
void *gui;
} PluginObject;
typedef struct {
int type;
PyObject *plugin;
PyObject *callback;
PyObject *userdata;
char *name;
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_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata);
static int Callback_Command(char *word[], char *word_eol[], void *userdata);
static int Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, 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 PyObject *Attribute_New(hexchat_event_attrs *attrs);
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(hexchat_context *context);
static PyObject *Context_FromServerAndChannel(char *server, char *channel);
static PyObject *Plugin_New(char *filename, PyObject *xcoobj);
static PyObject *Plugin_GetCurrent();
static PluginObject *Plugin_ByString(char *str);
static Hook *Plugin_AddHook(int type, PyObject *plugin, PyObject *callback,
PyObject *userdata, char *name, void *data);
static Hook *Plugin_FindHook(PyObject *plugin, char *name);
static void Plugin_RemoveHook(PyObject *plugin, Hook *hook);
static void Plugin_RemoveAllHooks(PyObject *plugin);
static PyObject *Module_hexchat_command(PyObject *self, PyObject *args);
static PyObject *Module_xchat_prnt(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_get_context(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_find_context(PyObject *self, PyObject *args,
PyObject *kwargs);
static PyObject *Module_hexchat_get_info(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_hook_command(PyObject *self, PyObject *args,
PyObject *kwargs);
static PyObject *Module_hexchat_hook_server(PyObject *self, PyObject *args,
PyObject *kwargs);
static PyObject *Module_hexchat_hook_print(PyObject *self, PyObject *args,
PyObject *kwargs);
static PyObject *Module_hexchat_hook_timer(PyObject *self, PyObject *args,
PyObject *kwargs);
static PyObject *Module_hexchat_unhook(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_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_hexchat_nickcmp(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_strip(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_pluginpref_set(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_pluginpref_get(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args);
static PyObject *Module_hexchat_pluginpref_list(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 */
static PyTypeObject Plugin_Type;
static PyTypeObject XChatOut_Type;
static PyTypeObject Context_Type;
static PyTypeObject ListItem_Type;
static PyTypeObject Attribute_Type;
static PyThreadState *main_tstate = NULL;
static void *thread_timer = NULL;
static hexchat_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[] = "HexChat Python " PY_VERSION " Interface Version " VERSION "\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 = PyUnicode_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()
{
const char *xdir;
char *sub_dir;
/* we need local filesystem encoding for chdir, opendir etc */
xdir = hexchat_get_info(ph, "configdir");
/* auto-load from subdirectory addons */
sub_dir = g_build_filename (xdir, "addons", NULL);
Util_Autoload_from(sub_dir);
g_free (sub_dir);
}
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 ~/.config/hexchat/addons/<filename> exists. */
expanded = g_build_filename(hexchat_get_info(ph, "configdir"),
"addons", 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_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata)
{
Hook *hook = (Hook *) userdata;
PyObject *retobj;
PyObject *word_list, *word_eol_list;
PyObject *attributes;
int ret = 0;
PyObject *plugin;
plugin = hook->plugin;
BEGIN_PLUGIN(plugin);
word_list = Util_BuildList(word+1);
if (word_list == NULL) {
END_PLUGIN(plugin);
return 0;
}
word_eol_list = Util_BuildList(word_eol+1);
if (word_eol_list == NULL) {
Py_DECREF(word_list);
END_PLUGIN(plugin);
return 0;
}
attributes = Attribute_New(attrs);
if (hook->type == HOOK_XCHAT_ATTR)
retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list,
word_eol_list, hook->userdata, attributes);
else
retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list,
word_eol_list, hook->userdata);
Py_DECREF(word_list);
Py_DECREF(word_eol_list);
Py_DECREF(attributes);
if (retobj == Py_None) {
ret = HEXCHAT_EAT_NONE;
Py_DECREF(retobj);
} else if (retobj) {
ret = PyLong_AsLong(retobj);
Py_DECREF(retobj);
} else {
PyErr_Print();
}
END_PLUGIN(plugin);
return ret;
}
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;
PyObject *plugin;
plugin = hook->plugin;
BEGIN_PLUGIN(plugin);
word_list = Util_BuildList(word+1);
if (word_list == NULL) {
END_PLUGIN(plugin);
return 0;
}
word_eol_list = Util_BuildList(word_eol+1);
if (word_eol_list == NULL) {
Py_DECREF(word_list);
END_PLUGIN(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 = HEXCHAT_EAT_NONE;
Py_DECREF(retobj);
} else if (retobj) {
ret = PyLong_AsLong(retobj);
Py_DECREF(retobj);
} else {
PyErr_Print();
}
END_PLUGIN(plugin);
return ret;
}
static int
Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, void *userdata)
{
Hook *hook = (Hook *) userdata;
PyObject *retobj;
PyObject *word_list;
PyObject *word_eol_list;
PyObject *attributes;
char **word_eol;
char *word_eol_raw;
int listsize = 0;
int next = 0;
int i;
int ret = 0;
PyObject *plugin;
/* Cut off the message identifier. */
word += 1;
/* HexChat 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) {
hexchat_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) {
hexchat_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] = "";
plugin = hook->plugin;
BEGIN_PLUGIN(plugin);
word_list = Util_BuildList(word);
if (word_list == NULL) {
g_free(word_eol_raw);
g_free(word_eol);
END_PLUGIN(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(plugin);
return 0;
}
attributes = Attribute_New(attrs);
retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list,
word_eol_list, hook->userdata, attributes);
Py_DECREF(word_list);
Py_DECREF(word_eol_list);
Py_DECREF(attributes);
g_free(word_eol_raw);
g_free(word_eol);
if (retobj == Py_None) {
ret = HEXCHAT_EAT_NONE;
Py_DECREF(retobj);
} else if (retobj) {
ret = PyLong_AsLong(retobj);
Py_DECREF(retobj);
} else {
PyErr_Print();
}
END_PLUGIN(plugin);
return ret;
}
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;
PyObject *plugin;
/* Cut off the message identifier. */
word += 1;
/* HexChat 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) {
hexchat_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) {
hexchat_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] = "";
plugin = hook->plugin;
BEGIN_PLUGIN(plugin);
word_list = Util_BuildList(word);
if (word_list == NULL) {
g_free(word_eol_raw);
g_free(word_eol);
END_PLUGIN(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(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 = HEXCHAT_EAT_NONE;
Py_DECREF(retobj);
} else if (retobj) {
ret = PyLong_AsLong(retobj);
Py_DECREF(retobj);
} else {
PyErr_Print();
}
END_PLUGIN(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();
#ifndef WIN32
usleep(1);
#endif
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)
{
Py_TYPE(self)->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) {
hexchat_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;
hexchat_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}
};
static PyTypeObject XChatOut_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"hexchat.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*/
};
/* ===================================================================== */
/* Attribute object */
#undef OFF
#define OFF(x) offsetof(AttributeObject, x)
static PyMemberDef Attribute_members[] = {
{"time", T_OBJECT, OFF(time), 0},
{0}
};
static void
Attribute_dealloc(PyObject *self)
{
Py_DECREF(((AttributeObject*)self)->time);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *
Attribute_repr(PyObject *self)
{
return PyUnicode_FromFormat("<Attribute object at %p>", self);
}
static PyTypeObject Attribute_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"hexchat.Attribute", /*tp_name*/
sizeof(AttributeObject), /*tp_basicsize*/
0, /*tp_itemsize*/
Attribute_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
Attribute_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*/
Attribute_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*/
};
static PyObject *
Attribute_New(hexchat_event_attrs *attrs)
{
AttributeObject *attr;
attr = PyObject_New(AttributeObject, &Attribute_Type);
if (attr != NULL) {
attr->time = PyLong_FromLong((long)attrs->server_time_utc);
}
return (PyObject *) attr;
}
/* ===================================================================== */
/* Context object */
static void
Context_dealloc(PyObject *self)
{
Py_TYPE(self)->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);
hexchat_set_context(ph, self->context);
hexchat_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);
hexchat_set_context(ph, self->context);
hexchat_print(ph, text);
END_XCHAT_CALLS();
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
Context_emit_print(ContextObject *self, PyObject *args, PyObject *kwargs)
{
char *argv[6];
char *name;
int res;
long time = 0;
hexchat_event_attrs *attrs;
char *kwlist[] = {"name", "arg1", "arg2", "arg3",
"arg4", "arg5", "arg6",
"time", NULL};
memset(&argv, 0, sizeof(char*)*6);
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name,
&argv[0], &argv[1], &argv[2],
&argv[3], &argv[4], &argv[5],
&time))
return NULL;
BEGIN_XCHAT_CALLS(ALLOW_THREADS);
hexchat_set_context(ph, self->context);
attrs = hexchat_event_attrs_create(ph);
attrs->server_time_utc = (time_t)time;
res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2],
argv[3], argv[4], argv[5], NULL);
hexchat_event_attrs_free(ph, attrs);
END_XCHAT_CALLS();
return PyLong_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);
hexchat_set_context(ph, self->context);
info = hexchat_get_info(ph, name);
END_XCHAT_CALLS();
if (info == NULL) {
Py_INCREF(Py_None);
return Py_None;
}
return PyUnicode_FromString(info);
}
static PyObject *
Context_get_list(ContextObject *self, PyObject *args)
{
PyObject *plugin = Plugin_GetCurrent();
hexchat_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 = Py_None;
}
Py_INCREF(ret);
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|METH_KEYWORDS},
{"get_info", (PyCFunction) Context_get_info, METH_VARARGS},
{"get_list", (PyCFunction) Context_get_list, METH_VARARGS},
{NULL, NULL}
};
static PyTypeObject Context_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"hexchat.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(hexchat_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;
hexchat_context *context;
BEGIN_XCHAT_CALLS(NONE);
context = hexchat_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);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *
ListItem_repr(PyObject *self)
{
return PyUnicode_FromFormat("<%s list item at %p>",
((ListItemObject*)self)->listname, self);
}
static PyTypeObject ListItem_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"hexchat.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) { \
hexchat_print(ph, "Module has no __module_" #x "__ " \
"defined"); \
goto error; \
} \
plugin->x = g_strdup(""); \
} else {\
if (!PyUnicode_Check(o)) { \
hexchat_print(ph, "Variable __module_" #x "__ " \
"must be a string"); \
goto error; \
} \
plugin->x = g_strdup(PyUnicode_AsUTF8(o)); \
if (plugin->x == NULL) { \
hexchat_print(ph, "Not enough memory to allocate " #x); \
goto error; \
} \
}
static PyObject *
Plugin_GetCurrent()
{
PyObject *plugin;
plugin = PySys_GetObject("__plugin__");
if (plugin == NULL)
PyErr_SetString(PyExc_RuntimeError, "lost sys.__plugin__");
return plugin;
}
static hexchat_plugin *
Plugin_GetHandle(PluginObject *plugin)
{
/* This works but the issue is that the script must be ran to get
* the name of it thus upon first use it will use the wrong handler
* work around would be to run a fake script once to get name? */
#if 0
/* return fake handle for pluginpref */
if (plugin->gui != NULL)
return plugin->gui;
else
#endif
return ph;
}
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, char *name, 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->name = g_strdup (name);
hook->data = NULL;
Plugin_SetHooks(plugin, g_slist_append(Plugin_GetHooks(plugin),
hook));
return hook;
}
static Hook *
Plugin_FindHook(PyObject *plugin, char *name)
{
Hook *hook = NULL;
GSList *plugin_hooks = Plugin_GetHooks(plugin);
while (plugin_hooks)
{
if (g_strcmp0 (((Hook *)plugin_hooks->data)->name, name) == 0)
{
hook = (Hook *)plugin_hooks->data;
break;
}
plugin_hooks = g_slist_next(plugin_hooks);
}
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_UNLOAD) {
/* This is an xchat hook. Unregister it. */
BEGIN_XCHAT_CALLS(NONE);
hexchat_unhook(ph, (hexchat_hook*)hook->data);
END_XCHAT_CALLS();
}
Plugin_SetHooks(plugin,
g_slist_remove(Plugin_GetHooks(plugin),
hook));
Py_DECREF(hook->callback);
Py_DECREF(hook->userdata);
if (hook->name)
g_free(hook->name);
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_UNLOAD) {
/* This is an xchat hook. Unregister it. */
BEGIN_XCHAT_CALLS(NONE);
hexchat_unhook(ph, (hexchat_hook*)hook->data);
END_XCHAT_CALLS();
}
Py_DECREF(hook->callback);
Py_DECREF(hook->userdata);
if (hook->name)
g_free(hook->name);
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);
if (((PluginObject *)plugin)->gui != NULL)
hexchat_plugingui_remove(ph, ((PluginObject *)plugin)->gui);
Py_DECREF(plugin);
/*PyThreadState_Swap(tstate); needed? */
Py_EndInterpreter(tstate);
}
static PyObject *
Plugin_New(char *filename, PyObject *xcoobj)
{
PluginObject *plugin = NULL;
PyObject *m, *o;
#ifdef IS_PY3K
wchar_t *argv[] = { L"<hexchat>", 0 };
#else
char *argv[] = { "<hexchat>", 0 };
#endif
if (filename) {
char *old_filename = filename;
filename = Util_Expand(filename);
if (filename == NULL) {
hexchat_printf(ph, "File not found: %s", old_filename);
return NULL;
}
}
/* Allocate plugin structure. */
plugin = PyObject_New(PluginObject, &Plugin_Type);
if (plugin == NULL) {
hexchat_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, hexchat_get_context(ph));
Plugin_SetGui(plugin, NULL);
/* Start a new interpreter environment for this plugin. */
PyEval_AcquireThread(main_tstate);
plugin->tstate = Py_NewInterpreter();
if (plugin->tstate == NULL) {
hexchat_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);
if (filename) {
#ifdef WIN32
char *file;
if (!g_file_get_contents_utf8(filename, &file, NULL, NULL)) {
hexchat_printf(ph, "Can't open file %s: %s\n",
filename, strerror(errno));
goto error;
}
if (PyRun_SimpleString(file) != 0) {
hexchat_printf(ph, "Error loading module %s\n",
filename);
g_free (file);
goto error;
}
plugin->filename = filename;
filename = NULL;
g_free (file);
#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) {
hexchat_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) {
hexchat_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) {
hexchat_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 = hexchat_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)
Plugin_Delete((PyObject *)plugin);
else
Py_DECREF(plugin);
}
PyEval_ReleaseLock();
return NULL;
}
static void
Plugin_dealloc(PluginObject *self)
{
g_free(self->filename);
g_free(self->name);
g_free(self->version);
g_free(self->description);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyTypeObject Plugin_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"hexchat.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_hexchat_command(PyObject *self, PyObject *args)
{
char *text;
if (!PyArg_ParseTuple(args, "s:command", &text))
return NULL;
BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
hexchat_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);
hexchat_print(ph, text);
END_XCHAT_CALLS();
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
Module_hexchat_emit_print(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *argv[6];
char *name;
int res;
long time = 0;
hexchat_event_attrs *attrs;
char *kwlist[] = {"name", "arg1", "arg2", "arg3",
"arg4", "arg5", "arg6",
"time", NULL};
memset(&argv, 0, sizeof(char*)*6);
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name,
&argv[0], &argv[1], &argv[2],
&argv[3], &argv[4], &argv[5],
&time))
return NULL;
BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
attrs = hexchat_event_attrs_create(ph);
attrs->server_time_utc = (time_t)time;
res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2],
argv[3], argv[4], argv[5], NULL);
hexchat_event_attrs_free(ph, attrs);
END_XCHAT_CALLS();
return PyLong_FromLong(res);
}
static PyObject *
Module_hexchat_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 = hexchat_get_info(ph, name);
END_XCHAT_CALLS();
if (info == NULL) {
Py_INCREF(Py_None);
return Py_None;
}
if (strcmp (name, "gtkwin_ptr") == 0)
return PyUnicode_FromFormat("%p", info); /* format as pointer */
else
return PyUnicode_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 = hexchat_get_prefs(ph, name, &info, &integer);
END_XCHAT_CALLS();
switch (type) {
case 0:
Py_INCREF(Py_None);
res = Py_None;
break;
case 1:
res = PyUnicode_FromString((char*)info);
break;
case 2:
case 3:
res = PyLong_FromLong(integer);
break;
default:
PyErr_Format(PyExc_RuntimeError,
"unknown get_prefs type (%d), "
"please report", type);
res = NULL;
break;
}
return res;
}
static PyObject *
Module_hexchat_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_hexchat_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_hexchat_pluginpref_set(PyObject *self, PyObject *args)
{
PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
hexchat_plugin *prefph = Plugin_GetHandle(plugin);
int result;
char *var;
PyObject *value;
if (!PyArg_ParseTuple(args, "sO:set_pluginpref", &var, &value))
return NULL;
if (PyLong_Check(value)) {
int intvalue = PyLong_AsLong(value);
BEGIN_XCHAT_CALLS(NONE);
result = hexchat_pluginpref_set_int(prefph, var, intvalue);
END_XCHAT_CALLS();
}
else if (PyUnicode_Check(value)) {
char *charvalue = PyUnicode_AsUTF8(value);
BEGIN_XCHAT_CALLS(NONE);
result = hexchat_pluginpref_set_str(prefph, var, charvalue);
END_XCHAT_CALLS();
}
else
result = 0;
return PyBool_FromLong(result);
}
static PyObject *
Module_hexchat_pluginpref_get(PyObject *self, PyObject *args)
{
PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
hexchat_plugin *prefph = Plugin_GetHandle(plugin);
PyObject *ret;
char *var;
char retstr[512];
int retint;
int result;
if (!PyArg_ParseTuple(args, "s:get_pluginpref", &var))
return NULL;
// This will always return numbers as integers.
BEGIN_XCHAT_CALLS(NONE);
result = hexchat_pluginpref_get_str(prefph, var, retstr);
END_XCHAT_CALLS();
if (result) {
if (strlen (retstr) <= 12) {
BEGIN_XCHAT_CALLS(NONE);
retint = hexchat_pluginpref_get_int(prefph, var);
END_XCHAT_CALLS();
if ((retint == 0) && (strcmp(retstr, "0") != 0))
ret = PyUnicode_FromString(retstr);
else
ret = PyLong_FromLong(retint);
} else
ret = PyUnicode_FromString(retstr);
}
else
{
Py_INCREF(Py_None);
ret = Py_None;
}
return ret;
}
static PyObject *
Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args)
{
PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
hexchat_plugin *prefph = Plugin_GetHandle(plugin);
char *var;
int result;
if (!PyArg_ParseTuple(args, "s:del_pluginpref", &var))
return NULL;
BEGIN_XCHAT_CALLS(NONE);
result = hexchat_pluginpref_delete(prefph, var);
END_XCHAT_CALLS();
return PyBool_FromLong(result);
}
static PyObject *
Module_hexchat_pluginpref_list(PyObject *self, PyObject *args)
{
PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
hexchat_plugin *prefph = Plugin_GetHandle(plugin);
char list[512];
char* token;
int result;
PyObject *pylist;
pylist = PyList_New(0);
BEGIN_XCHAT_CALLS(NONE);
result = hexchat_pluginpref_list(prefph, list);
END_XCHAT_CALLS();
if (result) {
token = strtok(list, ",");
while (token != NULL) {
PyList_Append(pylist, PyUnicode_FromString(token));
token = strtok (NULL, ",");
}
}
return pylist;
}
static PyObject *
Module_hexchat_hook_command(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *name;
PyObject *callback;
PyObject *userdata = Py_None;
int priority = HEXCHAT_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, name, NULL);
if (hook == NULL)
return NULL;
BEGIN_XCHAT_CALLS(NONE);
hook->data = (void*)hexchat_hook_command(ph, name, priority,
Callback_Command, help, hook);
END_XCHAT_CALLS();
return PyLong_FromVoidPtr(hook);
}
static PyObject *
Module_hexchat_hook_server(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *name;
PyObject *callback;
PyObject *userdata = Py_None;
int priority = HEXCHAT_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, NULL);
if (hook == NULL)
return NULL;
BEGIN_XCHAT_CALLS(NONE);
hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority,
Callback_Server, hook);
END_XCHAT_CALLS();
return PyLong_FromVoidPtr(hook);
}
static PyObject *
Module_hexchat_hook_server_attrs(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *name;
PyObject *callback;
PyObject *userdata = Py_None;
int priority = HEXCHAT_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_ATTR, plugin, callback, userdata, NULL, NULL);
if (hook == NULL)
return NULL;
BEGIN_XCHAT_CALLS(NONE);
hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority,
Callback_Server, hook);
END_XCHAT_CALLS();
return PyLong_FromVoidPtr(hook);
}
static PyObject *
Module_hexchat_hook_print(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *name;
PyObject *callback;
PyObject *userdata = Py_None;
int priority = HEXCHAT_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, name, NULL);
if (hook == NULL)
return NULL;
BEGIN_XCHAT_CALLS(NONE);
hook->data = (void*)hexchat_hook_print(ph, name, priority,
Callback_Print, hook);
END_XCHAT_CALLS();
return PyLong_FromVoidPtr(hook);
}
static PyObject *
Module_hexchat_hook_print_attrs(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *name;
PyObject *callback;
PyObject *userdata = Py_None;
int priority = HEXCHAT_PRI_NORM;
PyObject *plugin;
Hook *hook;
char *kwlist[] = {"name", "callback", "userdata", "priority", 0};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print_attrs",
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_ATTR, plugin, callback, userdata, name, NULL);
if (hook == NULL)
return NULL;
BEGIN_XCHAT_CALLS(NONE);
hook->data = (void*)hexchat_hook_print_attrs(ph, name, priority,
Callback_Print_Attrs, hook);
END_XCHAT_CALLS();
return PyLong_FromVoidPtr(hook);
}
static PyObject *
Module_hexchat_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, NULL);
if (hook == NULL)
return NULL;
BEGIN_XCHAT_CALLS(NONE);
hook->data = (void*)hexchat_hook_timer(ph, timeout,
Callback_Timer, hook);
END_XCHAT_CALLS();
return PyLong_FromVoidPtr(hook);
}
static PyObject *
Module_hexchat_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, NULL);
if (hook == NULL)
return NULL;
return PyLong_FromVoidPtr(hook);
}
static PyObject *
Module_hexchat_unhook(PyObject *self, PyObject *args)
{
PyObject *plugin;
PyObject *obj;
Hook *hook;
if (!PyArg_ParseTuple(args, "O:unhook", &obj))
return NULL;
plugin = Plugin_GetCurrent();
if (plugin == NULL)
return NULL;
if (PyUnicode_Check (obj))
{
hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj));
while (hook)
{
Plugin_RemoveHook(plugin, hook);
hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj));
}
}
else
{
hook = (Hook *)PyLong_AsVoidPtr(obj);
Plugin_RemoveHook(plugin, hook);
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
Module_xchat_get_list(PyObject *self, PyObject *args)
{
hexchat_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 = hexchat_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 = hexchat_list_get(ph, (char*)name);
if (list == NULL)
goto error;
fields = hexchat_list_fields(ph, (char*)name);
while (hexchat_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;
time_t tattr;
switch(fields[i][0]) {
case 's':
sattr = hexchat_list_str(ph, list, (char*)fld);
attr = PyUnicode_FromString(sattr?sattr:"");
break;
case 'i':
iattr = hexchat_list_int(ph, list, (char*)fld);
attr = PyLong_FromLong((long)iattr);
break;
case 't':
tattr = hexchat_list_time(ph, list, (char*)fld);
attr = PyLong_FromLong((long)tattr);
break;
case 'p':
sattr = hexchat_list_str(ph, list, (char*)fld);
if (strcmp(fld, "context") == 0) {
attr = Context_FromContext(
(hexchat_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 */
}
}
hexchat_list_free(ph, list);
goto exit;
error:
if (list)
hexchat_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 = hexchat_list_fields(ph, "lists");
l = PyList_New(0);
if (l == NULL)
return NULL;
for (i = 0; fields[i]; i++) {
o = PyUnicode_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_hexchat_nickcmp(PyObject *self, PyObject *args)
{
char *s1, *s2;
if (!PyArg_ParseTuple(args, "ss:nickcmp", &s1, &s2))
return NULL;
return PyLong_FromLong((long) hexchat_nickcmp(ph, s1, s2));
}
static PyObject *
Module_hexchat_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 = hexchat_strip(ph, str, len, flags);
result = PyUnicode_FromString(str2);
hexchat_free(ph, str2);
return result;
}
static PyMethodDef Module_xchat_methods[] = {
{"command", Module_hexchat_command,
METH_VARARGS},
{"prnt", Module_xchat_prnt,
METH_VARARGS},
{"emit_print", (PyCFunction)Module_hexchat_emit_print,
METH_VARARGS|METH_KEYWORDS},
{"get_info", Module_hexchat_get_info,
METH_VARARGS},
{"get_prefs", Module_xchat_get_prefs,
METH_VARARGS},
{"get_context", Module_hexchat_get_context,
METH_NOARGS},
{"find_context", (PyCFunction)Module_hexchat_find_context,
METH_VARARGS|METH_KEYWORDS},
{"set_pluginpref", Module_hexchat_pluginpref_set,
METH_VARARGS},
{"get_pluginpref", Module_hexchat_pluginpref_get,
METH_VARARGS},
{"del_pluginpref", Module_hexchat_pluginpref_delete,
METH_VARARGS},
{"list_pluginpref", Module_hexchat_pluginpref_list,
METH_VARARGS},
{"hook_command", (PyCFunction)Module_hexchat_hook_command,
METH_VARARGS|METH_KEYWORDS},
{"hook_server", (PyCFunction)Module_hexchat_hook_server,
METH_VARARGS|METH_KEYWORDS},
{"hook_server_attrs", (PyCFunction)Module_hexchat_hook_server_attrs,
METH_VARARGS|METH_KEYWORDS},
{"hook_print", (PyCFunction)Module_hexchat_hook_print,
METH_VARARGS|METH_KEYWORDS},
{"hook_print_attrs", (PyCFunction)Module_hexchat_hook_print_attrs,
METH_VARARGS|METH_KEYWORDS},
{"hook_timer", (PyCFunction)Module_hexchat_hook_timer,
METH_VARARGS|METH_KEYWORDS},
{"hook_unload", (PyCFunction)Module_hexchat_hook_unload,
METH_VARARGS|METH_KEYWORDS},
{"unhook", Module_hexchat_unhook,
METH_VARARGS},
{"get_list", Module_xchat_get_list,
METH_VARARGS},
{"get_lists", Module_xchat_get_lists,
METH_NOARGS},
{"nickcmp", Module_hexchat_nickcmp,
METH_VARARGS},
{"strip", Module_hexchat_strip,
METH_VARARGS},
{NULL, NULL}
};
#ifdef IS_PY3K
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"hexchat", /* m_name */
"HexChat Scripting Interface", /* m_doc */
-1, /* m_size */
Module_xchat_methods, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
static struct PyModuleDef xchat_moduledef = {
PyModuleDef_HEAD_INIT,
"xchat", /* m_name */
"HexChat Scripting Interface", /* m_doc */
-1, /* m_size */
Module_xchat_methods, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
#endif
static PyObject *
moduleinit_hexchat(void)
{
PyObject *hm;
#ifdef IS_PY3K
hm = PyModule_Create(&moduledef);
#else
hm = Py_InitModule3("hexchat", Module_xchat_methods, "HexChat Scripting Interface");
#endif
PyModule_AddIntConstant(hm, "EAT_NONE", HEXCHAT_EAT_NONE);
PyModule_AddIntConstant(hm, "EAT_HEXCHAT", HEXCHAT_EAT_HEXCHAT);
PyModule_AddIntConstant(hm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT); /* for compat */
PyModule_AddIntConstant(hm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN);
PyModule_AddIntConstant(hm, "EAT_ALL", HEXCHAT_EAT_ALL);
PyModule_AddIntConstant(hm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST);
PyModule_AddIntConstant(hm, "PRI_HIGH", HEXCHAT_PRI_HIGH);
PyModule_AddIntConstant(hm, "PRI_NORM", HEXCHAT_PRI_NORM);
PyModule_AddIntConstant(hm, "PRI_LOW", HEXCHAT_PRI_LOW);
PyModule_AddIntConstant(hm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST);
PyObject_SetAttrString(hm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR));
return hm;
}
static PyObject *
moduleinit_xchat(void)
{
PyObject *xm;
#ifdef IS_PY3K
xm = PyModule_Create(&xchat_moduledef);
#else
xm = Py_InitModule3("xchat", Module_xchat_methods, "HexChat Scripting Interface");
#endif
PyModule_AddIntConstant(xm, "EAT_NONE", HEXCHAT_EAT_NONE);
PyModule_AddIntConstant(xm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT);
PyModule_AddIntConstant(xm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN);
PyModule_AddIntConstant(xm, "EAT_ALL", HEXCHAT_EAT_ALL);
PyModule_AddIntConstant(xm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST);
PyModule_AddIntConstant(xm, "PRI_HIGH", HEXCHAT_PRI_HIGH);
PyModule_AddIntConstant(xm, "PRI_NORM", HEXCHAT_PRI_NORM);
PyModule_AddIntConstant(xm, "PRI_LOW", HEXCHAT_PRI_LOW);
PyModule_AddIntConstant(xm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST);
PyObject_SetAttrString(xm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR));
return xm;
}
#ifdef IS_PY3K
PyMODINIT_FUNC
PyInit_hexchat(void)
{
return moduleinit_hexchat();
}
PyMODINIT_FUNC
PyInit_xchat(void)
{
return moduleinit_xchat();
}
#else
PyMODINIT_FUNC
inithexchat(void)
{
moduleinit_hexchat();
}
PyMODINIT_FUNC
initxchat(void)
{
moduleinit_xchat();
}
#endif
/* ===================================================================== */
/* 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) {
hexchat_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) {
hexchat_print(ph, "Not enough memory for command buffer");
goto fail;
}
memcpy(buffer, command, len);
buffer[len] = '\n';
buffer[len+1] = 0;
PyRun_SimpleString("import hexchat");
o = PyRun_StringFlags(buffer, Py_single_input, d, d, NULL);
g_free(buffer);
if (o == NULL) {
PyErr_Print();
goto fail;
}
Py_DECREF(o);
fail:
END_PLUGIN(interp_plugin);
return;
}
static int
IInterp_Cmd(char *word[], char *word_eol[], void *userdata)
{
char *channel = (char *) hexchat_get_info(ph, "channel");
if (channel && channel[0] == '>' && strcmp(channel, ">>python<<") == 0) {
hexchat_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) {
hexchat_print(ph, "No python modules loaded");
} else {
hexchat_print(ph,
"Name Version Filename Description\n"
"---- ------- -------- -----------\n");
while (list != NULL) {
PluginObject *plg = (PluginObject *) list->data;
char *basename = g_path_get_basename(plg->filename);
hexchat_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;
}
hexchat_print(ph, "\n");
}
}
static void
Command_PyLoad(char *filename)
{
PyObject *plugin;
RELEASE_XCHAT_LOCK();
plugin = Plugin_New(filename, 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) {
hexchat_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) {
hexchat_print(ph, "Can't find a python plugin with that name");
} else {
char *filename = g_strdup(plugin->filename);
Command_PyUnload(filename);
Command_PyLoad(filename);
g_free(filename);
}
}
static void
Command_PyAbout()
{
hexchat_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;
hexchat_command(ph, "QUERY >>python<<");
} else if (strcasecmp(cmd, "ABOUT") == 0) {
ok = 1;
Command_PyAbout();
}
if (!ok)
hexchat_print(ph, usage);
return HEXCHAT_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 HEXCHAT_EAT_HEXCHAT;
}
return HEXCHAT_EAT_NONE;
}
static int
Command_Reload(char *word[], char *word_eol[], void *userdata)
{
int len = strlen(word[2]);
if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) {
Command_PyReload(word[2]);
return HEXCHAT_EAT_HEXCHAT;
}
return HEXCHAT_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 HEXCHAT_EAT_HEXCHAT;
}
return HEXCHAT_EAT_NONE;
}
/* ===================================================================== */
/* Autoload function */
/* ===================================================================== */
/* (De)initialization functions */
static int initialized = 0;
static int reinit_tried = 0;
void
hexchat_plugin_get_info(char **name, char **desc, char **version, void **reserved)
{
*name = "Python";
*version = VERSION;
*desc = "Python scripting interface";
if (reserved)
*reserved = NULL;
}
int
hexchat_plugin_init(hexchat_plugin *plugin_handle,
char **plugin_name,
char **plugin_desc,
char **plugin_version,
char *arg)
{
#ifdef IS_PY3K
wchar_t *argv[] = { L"<hexchat>", 0 };
#else
char *argv[] = { "<hexchat>", 0 };
#endif
ph = plugin_handle;
/* Block double initalization. */
if (initialized != 0) {
hexchat_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;
/* FIXME You can't free this since it's used as long as the plugin's
* loaded, but if you unload it, everything belonging to the plugin is
* supposed to be freed anyway.
*/
*plugin_desc = g_strdup_printf ("Python %d scripting interface", PY_MAJOR_VERSION);
/* Initialize python. */
#ifdef IS_PY3K
Py_SetProgramName(L"hexchat");
PyImport_AppendInittab("hexchat", PyInit_hexchat);
PyImport_AppendInittab("xchat", PyInit_xchat);
#else
Py_SetProgramName("hexchat");
PyImport_AppendInittab("hexchat", inithexchat);
PyImport_AppendInittab("xchat", initxchat);
#endif
Py_Initialize();
PySys_SetArgv(1, argv);
xchatout = XChatOut_New();
if (xchatout == NULL) {
hexchat_print(ph, "Can't allocate xchatout object");
return 0;
}
#ifdef WITH_THREAD
PyEval_InitThreads();
xchat_lock = PyThread_allocate_lock();
if (xchat_lock == NULL) {
hexchat_print(ph, "Can't allocate hexchat lock");
Py_DECREF(xchatout);
xchatout = NULL;
return 0;
}
#endif
main_tstate = PyEval_SaveThread();
interp_plugin = Plugin_New(NULL, xchatout);
if (interp_plugin == NULL) {
hexchat_print(ph, "Plugin_New() failed.\n");
#ifdef WITH_THREAD
PyThread_free_lock(xchat_lock);
#endif
Py_DECREF(xchatout);
xchatout = NULL;
return 0;
}
hexchat_hook_command(ph, "", HEXCHAT_PRI_NORM, IInterp_Cmd, 0, 0);
hexchat_hook_command(ph, "PY", HEXCHAT_PRI_NORM, Command_Py, usage, 0);
hexchat_hook_command(ph, "LOAD", HEXCHAT_PRI_NORM, Command_Load, 0, 0);
hexchat_hook_command(ph, "UNLOAD", HEXCHAT_PRI_NORM, Command_Unload, 0, 0);
hexchat_hook_command(ph, "RELOAD", HEXCHAT_PRI_NORM, Command_Reload, 0, 0);
#ifdef WITH_THREAD
thread_timer = hexchat_hook_timer(ph, 300, Callback_ThreadTimer, NULL);
#endif
hexchat_print(ph, "Python interface loaded\n");
Util_Autoload();
return 1;
}
int
hexchat_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) {
PyEval_RestoreThread(main_tstate);
PyThreadState_Swap(main_tstate);
main_tstate = NULL;
}
Py_Finalize();
#ifdef WITH_THREAD
if (thread_timer != NULL) {
hexchat_unhook(ph, thread_timer);
thread_timer = NULL;
}
PyThread_free_lock(xchat_lock);
#endif
hexchat_print(ph, "Python interface unloaded\n");
initialized = 0;
return 1;
}