From a329fa4675852886ba2bbc9637cebcac882f1575 Mon Sep 17 00:00:00 2001 From: Christopher Davis Date: Wed, 28 Jun 2006 04:40:18 +0000 Subject: added support for timeouts/IO sources git-svn-id: http://svn.irssi.org/repos/irssi-python@4294 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- Makefile | 2 +- constants.txt | 4 +- objects/pyscript-object.c | 89 +++++++++++++++++++++++++- objects/pyscript-object.h | 4 +- pyloader.c | 2 + pymodule.c | 72 +++++++++++++++++++-- pysignals.c | 121 ++++++++++++++++++++++++++++++----- pysignals.h | 1 + pysource.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++ pysource.h | 13 ++++ 10 files changed, 437 insertions(+), 27 deletions(-) create mode 100644 pysource.c create mode 100644 pysource.h diff --git a/Makefile b/Makefile index 1d004eb..fc70984 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ CFLAGS = -fpic -ggdb -Wall -I$(PYTHON) -I$(IRSSI) -I$(IRSSI)/src \ LDFLAGS = -fpic /usr/lib/libpython2.4.so -OBJ = pycore.o pyutils.o pymodule.o pyloader.o pysignals.o +OBJ = pycore.o pyutils.o pymodule.o pyloader.o pysignals.o pysource.o pyirssi: pyobjects.a $(OBJ) $(CC) -shared -o libirssi_python.so $(OBJ) objects/pyobjects.a $(LDFLAGS) diff --git a/constants.txt b/constants.txt index 39fca36..3baff40 100644 --- a/constants.txt +++ b/constants.txt @@ -1,5 +1,5 @@ -INPUT_READ -INPUT_WRITE +G_INPUT_READ +G_INPUT_WRITE IRSSI_GUI_GNOME IRSSI_GUI_GTK IRSSI_GUI_KDE diff --git a/objects/pyscript-object.c b/objects/pyscript-object.c index 665828c..2ab7b88 100644 --- a/objects/pyscript-object.c +++ b/objects/pyscript-object.c @@ -4,6 +4,7 @@ #include "pyirssi.h" #include "pysignals.h" #include "pymodule.h" +#include "pysource.h" /* handle cycles... Can't think of any reason why the user would put script into one of the lists @@ -31,7 +32,8 @@ static void PyScript_dealloc(PyScript* self) { PyScript_clear(self); pyscript_remove_signals((PyObject*)self); - + pyscript_remove_sources((PyObject*)self); + self->ob_type->tp_free((PyObject*)self); } @@ -265,6 +267,72 @@ static PyObject *PyScript_signal_unregister(PyScript *self, PyObject *args, PyOb Py_RETURN_NONE; } +PyDoc_STRVAR(PyScript_timeout_add_doc, + "timeout_add(msecs, func, data=None, once=False) -> int source handle\n" +); +static PyObject *PyScript_timeout_add(PyScript *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"msecs", "func", "data", "once", NULL}; + int msecs = 0; + PyObject *func = NULL; + PyObject *data = NULL; + int once = 0; + int ret; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "iO|Oi", kwlist, + &msecs, &func, &data, &once)) + return NULL; + + if (msecs < 10) + return PyErr_Format(PyExc_ValueError, "msecs must be at least 10"); + + ret = pysource_timeout_add_list(&self->sources, msecs, func, data, once); + + return PyInt_FromLong(ret); +} + +PyDoc_STRVAR(PyScript_input_add_doc, + "input_add(fd, func, data=None, once=False, condition=G_INPUT_READ) -> int source tag\n" +); +static PyObject *PyScript_input_add(PyScript *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"fd", "func", "data", "once", "condition", NULL}; + int fd = 0; + PyObject *func = NULL; + PyObject *data = NULL; + int once = 0; + int condition = G_INPUT_READ; + int ret; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "iO|Oii", kwlist, + &fd, &func, &data, &once, &condition)) + return NULL; + + ret = pysource_input_add_list(&self->sources, fd, condition, func, data, once); + + return PyInt_FromLong(ret); +} + +PyDoc_STRVAR(PyScript_source_remove_doc, + "source_remove(tag) -> None\n" + "\n" + "Remove IO or timeout source by tag\n" +); +static PyObject *PyScript_source_remove(PyScript *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"tag", NULL}; + int tag = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &tag)) + return NULL; + + if (!pysource_remove_tag(&self->sources, tag)) + return PyErr_Format(PyExc_KeyError, "source tag not found"); + + Py_RETURN_NONE; +} + /* Methods for object */ static PyMethodDef PyScript_methods[] = { {"command_bind", (PyCFunction)PyScript_command_bind, METH_VARARGS | METH_KEYWORDS, @@ -279,6 +347,12 @@ static PyMethodDef PyScript_methods[] = { PyScript_signal_register_doc}, {"signal_unregister", (PyCFunction)PyScript_signal_unregister, METH_VARARGS | METH_KEYWORDS, PyScript_signal_unregister_doc}, + {"timeout_add", (PyCFunction)PyScript_timeout_add, METH_VARARGS | METH_KEYWORDS, + PyScript_timeout_add_doc}, + {"input_add", (PyCFunction)PyScript_input_add, METH_VARARGS | METH_KEYWORDS, + PyScript_input_add_doc}, + {"source_remove", (PyCFunction)PyScript_source_remove, METH_VARARGS | METH_KEYWORDS, + PyScript_source_remove_doc}, {NULL} /* Sentinel */ }; @@ -390,6 +464,19 @@ void pyscript_remove_signals(PyObject *script) self->registered_signals = NULL; } +void pyscript_remove_sources(PyObject *script) +{ + PyScript *self; + + g_return_if_fail(pyscript_check(script)); + + self = (PyScript *) script; + + pysource_remove_list(self->sources); + g_slist_free(self->sources); + self->sources = NULL; +} + void pyscript_clear_modules(PyObject *script) { PyScript *self; diff --git a/objects/pyscript-object.h b/objects/pyscript-object.h index 8565053..fafa94b 100644 --- a/objects/pyscript-object.h +++ b/objects/pyscript-object.h @@ -10,6 +10,7 @@ typedef struct { PyObject *modules; /* dict of imported modules for script */ GSList *signals; /* list of bound signals and commands */ GSList *registered_signals; /* list of signal names registered */ + GSList *sources; /* list of io and timeout sources */ } PyScript; extern PyTypeObject PyScriptType; @@ -17,10 +18,11 @@ extern PyTypeObject PyScriptType; int pyscript_init(void); PyObject *pyscript_new(PyObject *module, char **argv); void pyscript_remove_signals(PyObject *script); +void pyscript_remove_sources(PyObject *script); void pyscript_clear_modules(PyObject *script); #define pyscript_check(op) PyObject_TypeCheck(op, &PyScriptType) #define pyscript_get_name(scr) PyModule_GetName(((PyScript*)scr)->module) #define pyscript_get_filename(scr) PyModule_GetFilename(((PyScript*)scr)->module) -#define pyscript_get_module(scr) ((PyScript*)scr)->module +#define pyscript_get_module(scr) (((PyScript*)scr)->module) #endif diff --git a/pyloader.c b/pyloader.c index cbe7784..6b3fd82 100644 --- a/pyloader.c +++ b/pyloader.c @@ -201,6 +201,7 @@ int pyloader_unload_script(const char *name) PySys_WriteStdout("unload %s, script -> 0x%x\n", name, script); pyscript_remove_signals(script); + pyscript_remove_sources(script); pyscript_clear_modules(script); if (PySequence_DelItem(script_modules, id) < 0) @@ -296,6 +297,7 @@ static void py_clear_scripts() { PyObject *scr = PyList_GET_ITEM(script_modules, i); pyscript_remove_signals(scr); + pyscript_remove_sources(scr); pyscript_clear_modules(scr); } diff --git a/pymodule.c b/pymodule.c index 0f6d97a..ac78186 100644 --- a/pymodule.c +++ b/pymodule.c @@ -851,20 +851,54 @@ PyDoc_STRVAR(py_signal_emit_doc, static PyObject *py_signal_emit(PyObject *self, PyObject *args) { PyObject *pysig; + PyObject *sigargs; + char *name; + int ret; - if (PyTuple_Size(args) < 1 || PyTuple_Size(args) > SIGNAL_MAX_ARGUMENTS) - return PyErr_Format(PyExc_TypeError, "need at least one argument for signal"); + if (PyTuple_Size(args) < 1) + return PyErr_Format(PyExc_TypeError, "signal name required"); + + if (PyTuple_Size(args) > SIGNAL_MAX_ARGUMENTS+1) + return PyErr_Format(PyExc_TypeError, + "no more than %d arguments for signal accepted", SIGNAL_MAX_ARGUMENTS); pysig = PyTuple_GET_ITEM(args, 0); if (!PyString_Check(pysig)) return PyErr_Format(PyExc_TypeError, "signal must be string"); + + name = PyString_AsString(pysig); + if (!name) + return NULL; - if (!pysignals_emit(PyString_AS_STRING(pysig), args)) + sigargs = PySequence_GetSlice(args, 1, PyTuple_Size(args)); + if (!sigargs) + return NULL; + + ret = pysignals_emit(name, sigargs); + Py_DECREF(sigargs); + if (!ret) return NULL; Py_RETURN_NONE; } +PyDoc_STRVAR(py_signal_continue_doc, + "signal_continue(*args) -> None\n" + "\n" + "Continue (reemit?) the current Irssi signal with up to 6 arguments\n" +); +static PyObject *py_signal_continue(PyObject *self, PyObject *args) +{ + if (PyTuple_Size(args) > SIGNAL_MAX_ARGUMENTS) + return PyErr_Format(PyExc_TypeError, + "no more than %d arguments for signal accepted", SIGNAL_MAX_ARGUMENTS); + + if (!pysignals_continue(args)) + return NULL; + + Py_RETURN_NONE; +} + PyDoc_STRVAR(py_signal_stop_doc, "signal_stop() -> None\n" "\n" @@ -895,9 +929,31 @@ static PyObject *py_signal_stop_by_name(PyObject *self, PyObject *args, PyObject Py_RETURN_NONE; } +PyDoc_STRVAR(py_signal_get_emitted_doc, + "signal_get_emmited() -> signal name string\n" + "\n" + "Get name of current signal\n" +); +static PyObject *py_signal_get_emitted(PyObject *self, PyObject *args) +{ + RET_AS_STRING_OR_NONE(signal_get_emitted()); +} + +PyDoc_STRVAR(py_signal_get_emitted_id_doc, + "signal_get_emmited_id() -> signal id int\n" + "\n" + "Get id of current signal\n" +); +static PyObject *py_signal_get_emitted_id(PyObject *self, PyObject *args) +{ + return PyInt_FromLong(signal_get_emitted_id()); +} + static PyMethodDef ModuleMethods[] = { - {"prnt", (PyCFunction)py_prnt, METH_VARARGS|METH_KEYWORDS, py_prnt_doc}, - {"get_script", (PyCFunction)py_get_script, METH_NOARGS, py_get_script_doc}, + {"prnt", (PyCFunction)py_prnt, METH_VARARGS | METH_KEYWORDS, + py_prnt_doc}, + {"get_script", (PyCFunction)py_get_script, METH_NOARGS, + py_get_script_doc}, {"chatnet_find", (PyCFunction)py_chatnet_find, METH_VARARGS | METH_KEYWORDS, py_chatnet_find_doc}, {"chatnets", (PyCFunction)py_chatnets, METH_NOARGS, @@ -1004,6 +1060,12 @@ static PyMethodDef ModuleMethods[] = { py_signal_stop_doc}, {"signal_stop_by_name", (PyCFunction)py_signal_stop_by_name, METH_VARARGS | METH_KEYWORDS, py_signal_stop_by_name_doc}, + {"signal_get_emitted", (PyCFunction)py_signal_get_emitted, METH_NOARGS, + py_signal_get_emitted_doc}, + {"signal_get_emitted_id", (PyCFunction)py_signal_get_emitted_id, METH_NOARGS, + py_signal_get_emitted_id_doc}, + {"signal_continue", (PyCFunction)py_signal_continue, METH_VARARGS, + py_signal_continue_doc}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/pysignals.c b/pysignals.c index daecea8..4e6d4ee 100644 --- a/pysignals.c +++ b/pysignals.c @@ -4,8 +4,32 @@ #include "factory.h" /* NOTE: - * Signals must be registered to be accessible to Python scripts. + * There are two different records used to store signal related data: + * PY_SIGNAL_SPEC_REC and PY_SIGNAL_REC. Each SPEC_REC declares a "plain" + * signal, or a type of "variable" signal. + * + * Plain signals are emitted by Irssi the same way every time, using the + * same text. They are never extended or altered in any way. Plain signals + * make up the vast majority of Irssi signals. These include (some random + * examples): "beep", "printtext", "log new", "ban new", etc. + * + * Variable signals are emitted by joining a common prefix with text that + * varies from emit to emit. These are signals that have a "" suffix + * in Irssi's signal listing. irssi-python uses the trailing space to + * distinguish between plain signals and variable signals. Example prefixes: + * "redir ", "command ", "event ". "command nick" would be emitted when the + * user types /nick yournick at the prompt. * + * While PY_SIGNAL_SPEC_REC stores data about each individual signal, + * PY_SIGNAL_REC stores data about each consumer of a signal (or command). + * A listing of SPEC_REC entries is stored globally in this module for each + * signal known to irssi-python. Each Script holds a list PY_SIGNAL_REC entries + * to remember signals and commands bound by the script. This allows for easy + * cleanup when the script is unloaded. The PY_SIGNAL_REC data includes + * references to a callable PyObject and a PY_SIGNAL_SPEC_REC entry for the + * signal. + * + * Signals must be registered to be accessible to Python scripts. * Registering a dynamic signal adds a new SPEC_REC with a refcount of 1. * If another script registers the same signal, refcount is incremented. * Binding to the signal also increments its reference count. Likewise, @@ -28,6 +52,26 @@ typedef struct _PY_SIGNAL_SPEC_REC #include "pysigmap.h" #define SIGNAME(sig) (sig->command? sig->command : sig->signal->name) +/* This macro is useful for PY_SIGNAL_REC entries bound to "variable" signals, + * whose names extend the name prefix stored in the SPEC_REC entry. + * + * Example: + * sig->command == "command nick" + * sig->signal->name == "command " + * + * The exact signal text differs from the prefix, so the text must be stored + * separately in each PY_SIGNAL_REC entry. + * + * However, entries for "plain" signals do not require any extra data stored, + * so sig->command is NULL. + * + * Example: + * sig->command == NULL; + * sig->signal->name == "massjoin"; + * + * "massjoin" is not a prefix for any signal, so any PY_SIGNAL_REC + * referencing the "massjoin" SPEC_REC entry will have a NULL command. + */ /* hashtable for normal signals, tree for variable signal prefixes. */ static GHashTable *py_sighash = NULL; @@ -42,11 +86,12 @@ static PY_SIGNAL_REC *py_signal_rec_new(const char *signal, PyObject *func, cons static void py_signal_rec_destroy(PY_SIGNAL_REC *sig); static PyObject *py_mkstrlist(void *iobj); static PyObject *py_i2py(char code, void *iobj); -static void *py_py2i(char code, PyObject *pobj, int arg); +static void *py_py2i(char code, PyObject *pobj, int arg, const char *signal); static void py_getstrlist(GList **list, PyObject *pylist); static int precmp(const char *spec, const char *test); static PY_SIGNAL_SPEC_REC *py_signal_lookup(const char *name); static void py_signal_remove(PY_SIGNAL_SPEC_REC *sig); +static int py_convert_args(void **args, PyObject *argtup, const char *signal); PY_SIGNAL_REC *pysignals_command_bind(const char *cmd, PyObject *func, const char *category, int priority) @@ -257,7 +302,7 @@ static PyObject *py_i2py(char code, void *iobj) } /* PyObject -> irssi obj*/ -static void *py_py2i(char code, PyObject *pobj, int arg) +static void *py_py2i(char code, PyObject *pobj, int arg, const char *signal) { char *type; @@ -266,7 +311,7 @@ static void *py_py2i(char code, PyObject *pobj, int arg) switch (code) { - /* XXX: string doesn't (necssarily) persist */ + /* XXX: string doesn't persist */ case 's': type = "str"; if (PyString_Check(pobj)) return PyString_AsString(pobj); @@ -363,8 +408,8 @@ static void *py_py2i(char code, PyObject *pobj, int arg) return NULL; } - PyErr_Format(PyExc_TypeError, "expected type %s for arg %d, but got %s", - type, arg, pobj->ob_type->tp_name); + PyErr_Format(PyExc_TypeError, "signal `%s': expected type %s for arg %d, but got %s", + signal, type, arg, pobj->ob_type->tp_name); return NULL; } @@ -420,7 +465,7 @@ static void py_run_handler(PY_SIGNAL_REC *rec, void **args) if (!ret) goto error; - /*XXX: reference arg handling not well tested */ + /*XXX: IN/OUT arg handling not well tested */ for (i = 0, j = 0; i < arglen; i++) { GList **list; @@ -474,16 +519,13 @@ static void py_sig_proxy(void *p1, void *p2, void *p3, void *p4, void *p5, void py_run_handler(rec, args); } -int pysignals_emit(const char *signal, PyObject *argtup) +static int py_convert_args(void **args, PyObject *argtup, const char *signal) { - PY_SIGNAL_SPEC_REC *spec; - void *args[6]; char *arglist; + PY_SIGNAL_SPEC_REC *spec; int i; int maxargs; - memset(args, 0, sizeof args); - spec = py_signal_lookup(signal); if (!spec) { @@ -491,17 +533,62 @@ int pysignals_emit(const char *signal, PyObject *argtup) return 0; } + /*XXX: specifying fewer signal args than in the format implicitly + sets overlooked args to NULL or 0 */ + arglist = spec->arglist; maxargs = strlen(arglist); - - for (i = 0; i < maxargs && i+1 < PyTuple_Size(argtup); i++) + for (i = 0; i < maxargs && i < PyTuple_Size(argtup); i++) { - args[i] = py_py2i(arglist[i], PyTuple_GET_ITEM(argtup, i+1), i+1); + args[i] = py_py2i(arglist[i], + PyTuple_GET_ITEM(argtup, i), + i+1, signal); + if (PyErr_Occurred()) /* XXX: any cleanup needed? */ - return 0; + return -1; } - signal_emit(signal, maxargs, + return maxargs; +} + +int pysignals_emit(const char *signal, PyObject *argtup) +{ + int arglen; + void *args[6]; + + memset(args, 0, sizeof args); + + arglen = py_convert_args(args, argtup, signal); + if (arglen < 0) + return 0; + + signal_emit(signal, arglen, + args[0], args[1], args[2], + args[3], args[4], args[5]); + + return 1; +} + +int pysignals_continue(PyObject *argtup) +{ + const char *signal; + int arglen; + void *args[6]; + + memset(args, 0, sizeof args); + + signal = signal_get_emitted(); + if (!signal) + { + PyErr_Format(PyExc_LookupError, "cannot determine current signal"); + return 0; + } + + arglen = py_convert_args(args, argtup, signal); + if (arglen < 0) + return 0; + + signal_continue(arglen, args[0], args[1], args[2], args[3], args[4], args[5]); diff --git a/pysignals.h b/pysignals.h index 7627d19..bec72c9 100644 --- a/pysignals.h +++ b/pysignals.h @@ -35,6 +35,7 @@ int pysignals_remove_search(GSList **siglist, const char *name, PyObject *func, PSG_TYPE type); void pysignals_remove_list(GSList *siglist); int pysignals_emit(const char *signal, PyObject *argtup); +int pysignals_continue(PyObject *argtup); int pysignals_register(const char *name, const char *arglist); int pysignals_unregister(const char *name); void pysignals_init(void); diff --git a/pysource.c b/pysource.c new file mode 100644 index 0000000..f3a0663 --- /dev/null +++ b/pysource.c @@ -0,0 +1,156 @@ +#include +#include "pyirssi.h" +#include "pysource.h" + +typedef struct _PY_SOURCE_REC +{ + int once; + int tag; + int fd; + GSList **container; /* "container" points to a list owned by a Script object */ + PyObject *handler; + PyObject *data; +} PY_SOURCE_REC; + +static PY_SOURCE_REC *py_source_new(GSList **list, int once, PyObject *handler, PyObject *data) +{ + PY_SOURCE_REC *rec; + + rec = g_new0(PY_SOURCE_REC, 1); + rec->once = once; + rec->fd = -1; + rec->handler = handler; + rec->data = data; + rec->container = list; + + Py_INCREF(handler); + Py_XINCREF(data); + + return rec; +} + +static void py_source_destroy(PY_SOURCE_REC *rec) +{ + g_source_remove(rec->tag); + Py_DECREF(rec->handler); + Py_XDECREF(rec->data); + g_free(rec); +} + +static int py_source_proxy(PY_SOURCE_REC *rec) +{ + char args[3] = {0,0,0}; + int fd; + PyObject *ret; + PyObject *handler, *data; + + /* Copy data out of the rec (there's not much). The rec may be deleted in + the if block below or by the Python code executed. Protect handler & data + with INCREF. + */ + + fd = rec->fd; + handler = rec->handler; + data = rec->data; + Py_INCREF(handler); + Py_XINCREF(data); + + if (rec->once) + { + *rec->container = g_slist_remove(*rec->container, rec); + py_source_destroy(rec); + } + + /* call python function with fd and/or data if available. + IO handler will be called with either "iO" or "i". + Timeout with "O" or "". + */ + + if (fd >= 0) + { + /* IO handler */ + args[0] = 'i'; + if (data) + args[1] = 'O'; + + ret = PyObject_CallFunction(handler, args, fd, data); + } + else + { + /* Timeout handler */ + if (data) + args[0] = 'O'; + + ret = PyObject_CallFunction(handler, args, data); + } + + if (!ret) + PyErr_Print(); + else + Py_DECREF(ret); + + Py_DECREF(handler); + Py_XDECREF(data); + + return 1; +} + +int pysource_timeout_add_list(GSList **list, int msecs, PyObject *func, PyObject *data, int once) +{ + PY_SOURCE_REC *rec; + + g_return_val_if_fail(func != NULL, -1); + + rec = py_source_new(list, once, func, data); + rec->tag = g_timeout_add(msecs, (GSourceFunc)py_source_proxy, rec); + + *list = g_slist_append(*list, rec); + + return rec->tag; +} + +int pysource_input_add_list(GSList **list, int fd, int cond, PyObject *func, PyObject *data, int once) +{ + PY_SOURCE_REC *rec; + GIOChannel *channel; + + g_return_val_if_fail(func != NULL, 1); + rec = py_source_new(list, once, func, data); + rec->fd = fd; + channel = g_io_channel_unix_new(fd); + rec->tag = g_input_add(channel, cond, (GInputFunction)py_source_proxy, rec); + g_io_channel_unref(channel); + + *list = g_slist_append(*list, rec); + + return rec->tag; +} + +int pysource_remove_tag(GSList **list, int handle) +{ + GSList *node; + + for (node = *list; node != NULL; node = node->next) + { + PY_SOURCE_REC *rec = node->data; + + if (rec->tag == handle) + { + py_source_destroy(rec); + *list = g_slist_delete_link(*list, node); + + return 1; + } + } + + return 0; +} + +void pysource_remove_list(GSList *list) +{ + GSList *node; + + for (node = list; node != NULL; node = node->next) + py_source_destroy(node->data); +} + diff --git a/pysource.h b/pysource.h new file mode 100644 index 0000000..23903d5 --- /dev/null +++ b/pysource.h @@ -0,0 +1,13 @@ +#ifndef _PYSOURCE_H_ +#define _PYSOURCE_H_ + +#include + +/* condition is G_INPUT_READ or G_INPUT_WRITE */ +int pysource_input_add_list(GSList **list, int fd, int cond, PyObject *func, PyObject *data, int once); +int pysource_timeout_add_list(GSList **list, int msecs, PyObject *func, PyObject *data, int once); + +int pysource_remove_tag(GSList **list, int handle); +void pysource_remove_list(GSList *list); + +#endif -- cgit