/* * Copyright (c) 2002-2003 Gustavo Niemeyer * * 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 #include #include #include #ifdef WIN32 #include #include "../../src/dirent/dirent-win32.h" #include "../../config-win32.h" #else #include #include #endif #include "hexchat-plugin.h" #undef _POSIX_C_SOURCE /* Avoid warning: also in /usr/include/features.hfrom glib.h */ #include "Python.h" #include "structmember.h" #include "pythread.h" #define VERSION_MAJOR 0 #define VERSION_MINOR 9 #ifdef WIN32 #undef WITH_THREAD /* Thread support locks up xchat on Win32. */ #define VERSION "0.9/2.7" /* Linked to python27.dll */ #else #define VERSION "0.9" #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 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 hexchat_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; hexchat_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(hexchat_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_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 */ 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 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 \n\ UNLOAD \n\ RELOAD \n\ LIST\n\ EXEC \n\ CONSOLE\n\ ABOUT\n\ \n"; static const char about[] = "\ \n\ X-Chat Python Interface " VERSION "\n\ \n\ Copyright (c) 2002-2003 Gustavo Niemeyer \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() { const char *xdir; char *sub_dir; /* we need local filesystem encoding for chdir, opendir etc */ xdir = hexchat_get_info(ph, "configdir"); /* don't pollute the filesystem with script files, this only causes misuse of the folders * only use ~/.config/hexchat/addons/ and %APPDATA%\HexChat\addons */ #if 0 /* auto-load from ~/.config/hexchat/ or %APPDATA%\HexChat\ */ Util_Autoload_from(hexchat_get_info(ph, "configdir")); #endif /* auto-load from subdirectory addons */ sub_dir = malloc (strlen (xdir) + 8); strcpy (sub_dir, xdir); strcat (sub_dir, "/addons"); Util_Autoload_from(sub_dir); free (sub_dir); #if 0 #ifdef WIN32 /* also auto-load C:\Program Files\HexChat\Plugins\*.py */ Util_Autoload_from(HEXCHATLIBDIR"/plugins"); #endif #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 ~/.config/hexchat/addons/ 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_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 = HEXCHAT_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; /* 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] = ""; 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 = HEXCHAT_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) { 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} }; statichere PyTypeObject XChatOut_Type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "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*/ }; /* ===================================================================== */ /* 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); 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) { 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); hexchat_set_context(ph, self->context); res = hexchat_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); 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 PyString_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}, {"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*/ "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); 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*/ "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 (!PyString_Check(o)) { \ hexchat_print(ph, "Variable __module_" #x "__ " \ "must be a string"); \ goto error; \ } \ plugin->x = g_strdup(PyString_AsString(o)); \ if (plugin->x == NULL) { \ hexchat_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, *hm, *xm, *o; char *argv[] = {"", 0}; 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)); /* Start a new interpreter environment for this plugin. */ PyEval_AcquireLock(); 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); /* Add xchat module to the environment. */ xm = Py_InitModule("xchat", xchat_methods); if (xm == NULL) { hexchat_print(ph, "Can't create xchat module"); goto error; } hm = Py_InitModule("hexchat", xchat_methods); if (hm == NULL) { hexchat_print(ph, "Can't create hexchat module"); goto error; } 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); PyModule_AddIntConstant(hm, "EAT_NONE", HEXCHAT_EAT_NONE); PyModule_AddIntConstant(hm, "EAT_HEXCHAT", HEXCHAT_EAT_HEXCHAT); 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); o = Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR); if (o == NULL) { hexchat_print(ph, "Can't create version tuple"); goto error; } PyObject_SetAttrString(xm, "__version__", o); PyObject_SetAttrString(hm, "__version__", o); if (filename) { #ifdef WIN32 PyObject* PyFileObject = PyFile_FromString(filename, "r"); if (PyFileObject == NULL) { hexchat_printf(ph, "Can't open file %s: %s\n", filename, strerror(errno)); goto error; } if (PyRun_SimpleFile(PyFile_AsFile(PyFileObject), filename) != 0) { hexchat_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) { 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) 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); 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); 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); hexchat_unhook(ph, (hexchat_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); hexchat_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*/ "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) { 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 = hexchat_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_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; } 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 = 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 = 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_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) { PyObject *result; char *var; PyObject *value; if (!PyArg_ParseTuple(args, "sO:set_pluginpref", &var, &value)) return NULL; if (PyInt_Check(value)) { int intvalue = PyInt_AsLong(value); result = PyInt_FromLong(hexchat_pluginpref_set_int(ph, var, intvalue)); } else if (PyString_Check(value)) { char *charvalue = PyString_AsString(value); result = PyInt_FromLong(hexchat_pluginpref_set_str(ph, var, charvalue)); } else result = PyInt_FromLong(0); return result; } static PyObject * Module_hexchat_pluginpref_get(PyObject *self, PyObject *args) { PyObject *ret; char *var; char retstr[512]; int retint; if (!PyArg_ParseTuple(args, "s:get_pluginpref", &var)) return NULL; // This will always return numbers as integers. if (hexchat_pluginpref_get_str(ph, var, retstr)) { if (strlen (retstr) <= 12) { retint = hexchat_pluginpref_get_int(ph, var); if ((retint == 0) && (strcmp(retstr, "0") != 0)) ret = PyString_FromString(retstr); else ret = PyInt_FromLong(retint); } else ret = PyString_FromString(retstr); } else ret = Py_None; return ret; } static PyObject * Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args) { char *var; int result; if (!PyArg_ParseTuple(args, "s:del_pluginpref", &var)) return NULL; result = hexchat_pluginpref_delete(ph, var); return PyInt_FromLong(result); } static PyObject * Module_hexchat_pluginpref_list(PyObject *self, PyObject *args) { char list[512]; char* token; PyObject *pylist; pylist = PyList_New(0); if (hexchat_pluginpref_list(ph, list)) { token = strtok(list, ","); while (token != NULL) { PyList_Append(pylist, PyString_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, 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 PyInt_FromLong((long)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); if (hook == NULL) return NULL; BEGIN_XCHAT_CALLS(NONE); hook->data = (void*)hexchat_hook_server(ph, name, priority, Callback_Command, hook); END_XCHAT_CALLS(); return PyInt_FromLong((long)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, 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 PyInt_FromLong((long)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); if (hook == NULL) return NULL; BEGIN_XCHAT_CALLS(NONE); hook->data = (void*)hexchat_hook_timer(ph, timeout, Callback_Timer, hook); END_XCHAT_CALLS(); return PyInt_FromLong((long)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); if (hook == NULL) return NULL; return PyInt_FromLong((long)hook); } static PyObject * Module_hexchat_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) { 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; switch(fields[i][0]) { case 's': sattr = hexchat_list_str(ph, list, (char*)fld); attr = PyString_FromString(sattr?sattr:""); break; case 'i': iattr = hexchat_list_int(ph, list, (char*)fld); attr = PyInt_FromLong((long)iattr); 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 = 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_hexchat_nickcmp(PyObject *self, PyObject *args) { char *s1, *s2; if (!PyArg_ParseTuple(args, "ss:nickcmp", &s1, &s2)) return NULL; return PyInt_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 = PyString_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", Module_hexchat_emit_print, METH_VARARGS}, {"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_print", (PyCFunction)Module_hexchat_hook_print, 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} }; /* ===================================================================== */ /* 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; 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 *) 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 : "", basename, *plg->description ? plg->description : ""); 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, 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) { 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); /* cppcheck-suppress deallocDealloc */ 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_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) { char *argv[] = {"", 0}; 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; *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) { 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 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) { 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); #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) { 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; }