From 4d33c04f15e60e21a537edd635c9ac130312a3cb Mon Sep 17 00:00:00 2001 From: Christopher Davis Date: Fri, 23 Jun 2006 03:51:52 +0000 Subject: Began work on signal handler/map system. It seems to be working; however, events with varying text aren't handled because it does a straight lookup in the hashtable with the signal name to locate the argument list. Will need to change the system to accomodate the events. The basics for reference arg support is there but not well tested. git-svn-id: http://svn.irssi.org/repos/irssi-python@4289 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- Makefile | 3 + classes.txt | 2 - objects/pyscript-object.c | 63 +++++-- objects/pyscript-object.h | 1 + pycore.c | 4 +- pyloader.c | 18 +- pymodule.c | 75 +++++++- pysigmap.h | 177 ++++++++++++++++++ pysignals.c | 463 ++++++++++++++++++++++++++++++++++++++++------ pysignals.h | 23 ++- sig2code.py | 124 +++++++++++++ 11 files changed, 866 insertions(+), 87 deletions(-) create mode 100644 pysigmap.h create mode 100644 sig2code.py diff --git a/Makefile b/Makefile index d706a0f..9cb3d0f 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,9 @@ pyobjects.a: %.o: %.c $(CC) -c $< $(CFLAGS) +signalmap: + python sig2code.py < ~/irssi-0.8.10/docs/signals.txt > pysigmap.h + clean: rm -f *.o *.so cd objects/ && make clean diff --git a/classes.txt b/classes.txt index e94fd97..169d1d7 100644 --- a/classes.txt +++ b/classes.txt @@ -3,8 +3,6 @@ Basic types (source): IrssiChatObject/ Chatnet/ (core/chatnets.h) IrcChatnet (irc/core/irc-chatnets.h) - #ServerSetup/ (core/servers-setup.h) - # IrcServerSetup (irc/core/irc-servers-setup.h) ServerConnect/ (core/servers.h) IrcServerConnect (irc/core/irc-servers.h) Server/ (core/servers.h) diff --git a/objects/pyscript-object.c b/objects/pyscript-object.c index f74861f..d4ec5f1 100644 --- a/objects/pyscript-object.c +++ b/objects/pyscript-object.c @@ -65,24 +65,60 @@ static PyObject *PyScript_new(PyTypeObject *type, PyObject *args, PyObject *kwds return (PyObject *)self; } +//FIXME: add prioriety as arg +PyDoc_STRVAR(PyScript_command_bind_doc, + "Bind a command" +); static PyObject *PyScript_command_bind(PyScript *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"cmd", "func", "category", NULL}; + static char *kwlist[] = {"cmd", "func", "category", "priority", NULL}; char *cmd; PyObject *func; char *category = NULL; + int priority = SIGNAL_PRIORITY_DEFAULT; PY_SIGNAL_REC *srec; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|s", kwlist, &cmd, &func, &category)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|si", kwlist, + &cmd, &func, &category, &priority)) return NULL; if (!PyCallable_Check(func)) return PyErr_Format(PyExc_TypeError, "func must be callable"); - srec = py_command_bind(cmd, func, category); - + srec = pysignals_command_bind(cmd, func, category, priority); + if (!srec) + return PyErr_Format(PyExc_RuntimeError, "unable to bind command"); + /* add record to internal list*/ - self->signals = g_slist_append(self->signals, crec); + self->signals = g_slist_append(self->signals, srec); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyScript_signal_add_doc, + "add signal" +); +static PyObject *PyScript_signal_add(PyScript *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"signal", "func", "category", "priority", NULL}; + char *signal; + PyObject *func; + char *category = NULL; + int priority = SIGNAL_PRIORITY_DEFAULT; + PY_SIGNAL_REC *srec; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|si", kwlist, + &signal, &func, &category, &priority)) + return NULL; + + if (!PyCallable_Check(func)) + return PyErr_Format(PyExc_TypeError, "func must be callable"); + + srec = pysignals_signal_add(signal, func, priority); + if (!srec) + return PyErr_Format(PyExc_KeyError, "unable to find signal, '%s'", signal); + + self->signals = g_slist_append(self->signals, srec); Py_RETURN_NONE; } @@ -96,8 +132,10 @@ static PyMemberDef PyScript_members[] = { /* Methods for object */ static PyMethodDef PyScript_methods[] = { - {"command_bind", (PyCFunction)PyScript_command_bind, - METH_VARARGS | METH_KEYWORDS, "Bind a command"}, + {"command_bind", (PyCFunction)PyScript_command_bind, METH_VARARGS | METH_KEYWORDS, + PyScript_command_bind_doc}, + {"signal_add", (PyCFunction)PyScript_signal_add, METH_VARARGS | METH_KEYWORDS, + PyScript_signal_add_doc}, {NULL} /* Sentinel */ }; @@ -187,15 +225,8 @@ void pyscript_remove_signals(PyObject *script) self = (PyScript *) script; for (node = self->signals; node != NULL; node = node->next) - { - PY_SIGNAL_REC *crec = node->data; - - py_command_unbind(crec); - g_free(crec->name); - Py_DECREF(crec->handler); - g_free(crec); - } - + pysignals_remove_generic((PY_SIGNAL_REC *)node->data); + g_slist_free(self->signals); self->signals = NULL; } diff --git a/objects/pyscript-object.h b/objects/pyscript-object.h index 260c6da..ee32d8c 100644 --- a/objects/pyscript-object.h +++ b/objects/pyscript-object.h @@ -3,6 +3,7 @@ #include #include +//FIXME: add list of registered dynamic signal names typedef struct { PyObject_HEAD PyObject *module; /* module object */ diff --git a/pycore.c b/pycore.c index 63cd2c6..6963076 100644 --- a/pycore.c +++ b/pycore.c @@ -6,9 +6,9 @@ #include "pycore.h" #include "pyloader.h" #include "pymodule.h" +#include "pysignals.h" #include "factory.h" - /*XXX: copy parse into utils */ static void cmd_exec(const char *data) { @@ -117,6 +117,7 @@ void irssi_python_init(void) { Py_InitializeEx(0); + pysignals_init(); if (!pyloader_init() || !pymodule_init() || !factory_init()) { printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Failed to load Python"); @@ -148,5 +149,6 @@ void irssi_python_deinit(void) pymodule_deinit(); pyloader_deinit(); + pysignals_deinit(); Py_Finalize(); } diff --git a/pyloader.c b/pyloader.c index 6c7fdfe..cbe7784 100644 --- a/pyloader.c +++ b/pyloader.c @@ -288,6 +288,20 @@ int pyloader_init(void) return 1; } +static void py_clear_scripts() +{ + int i; + + for (i = 0; i < PyList_Size(script_modules); i++) + { + PyObject *scr = PyList_GET_ITEM(script_modules, i); + pyscript_remove_signals(scr); + pyscript_clear_modules(scr); + } + + Py_DECREF(script_modules); +} + void pyloader_deinit(void) { GSList *node; @@ -300,7 +314,5 @@ void pyloader_deinit(void) g_slist_free(script_paths); script_paths = NULL; - /* script specific resources (cmd + signal handlers) should be removed - by the object's destructor */ - Py_DECREF(script_modules); + py_clear_scripts(); } diff --git a/pymodule.c b/pymodule.c index 308772e..713511f 100644 --- a/pymodule.c +++ b/pymodule.c @@ -773,11 +773,75 @@ static PyObject *py_notifylist_find(PyObject *self, PyObject *args, PyObject *kw rec = notifylist_find(mask, ircnet); if (rec) - pynotifylist_new(rec); + return pynotifylist_new(rec); Py_RETURN_NONE; } +PyDoc_STRVAR(py_commands_doc, + "Return a list of all commands." +); +static PyObject *py_commands(PyObject *self, PyObject *args) +{ + return py_irssi_objlist_new(commands, 1, (InitFunc)pycommand_new); +} + +PyDoc_STRVAR(py_level2bits_doc, + "Level string -> number" +); +static PyObject *py_level2bits(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"level", NULL}; + char *level = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &level)) + return NULL; + + return PyLong_FromUnsignedLong(level2bits(level)); +} + +PyDoc_STRVAR(py_bits2level_doc, + "Level number -> string" +); +static PyObject *py_bits2level(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"bits", NULL}; + unsigned bits; + char *str; + PyObject *ret; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "I", kwlist, + &bits)) + return NULL; + + str = bits2level(bits); + if (str) + { + ret = PyString_FromString(str); + g_free(str); + return ret; + } + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_combine_level_doc, + "Combine level number to level string ('+level -level'). Return new level number." +); +static PyObject *py_combine_level(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"level", "str", NULL}; + int level = 0; + char *str = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "is", kwlist, + &level, &str)) + return NULL; + + return PyLong_FromUnsignedLong(combine_level(level, str)); +} + 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}, @@ -873,9 +937,18 @@ static PyMethodDef ModuleMethods[] = { py_notifylist_ison_doc}, {"notifylist_find", (PyCFunction)py_notifylist_find, METH_VARARGS | METH_KEYWORDS, py_notifylist_find_doc}, + {"commands", (PyCFunction)py_commands, METH_NOARGS, + py_commands_doc}, + {"level2bits", (PyCFunction)py_level2bits, METH_VARARGS | METH_KEYWORDS, + py_level2bits_doc}, + {"bits2level", (PyCFunction)py_bits2level, METH_VARARGS | METH_KEYWORDS, + py_bits2level_doc}, + {"combine_level", (PyCFunction)py_combine_level, METH_VARARGS | METH_KEYWORDS, + py_combine_level_doc}, {NULL, NULL, 0, NULL} /* Sentinel */ }; +/*XXX: move to pyloader.c??*/ /* Traverse stack backwards to find the nearest _script object in globals */ static PyObject *find_script(void) { diff --git a/pysigmap.h b/pysigmap.h new file mode 100644 index 0000000..0e37935 --- /dev/null +++ b/pysigmap.h @@ -0,0 +1,177 @@ +/* Include in your C module */ +static PY_SIGNAL_SPEC_REC py_sigmap[] = { + {"gui dialog", "ss", 0, 0, 0}, + {"send command", "sSW", 0, 0, 0}, + {"chat protocol created", "?", 0, 0, 0}, + {"chat protocol updated", "?", 0, 0, 0}, + {"chat protocol destroyed", "?", 0, 0, 0}, + {"channel created", "Ci", 0, 0, 0}, + {"channel destroyed", "C", 0, 0, 0}, + {"chatnet created", "c", 0, 0, 0}, + {"chatnet destroyed", "c", 0, 0, 0}, + {"commandlist new", "o", 0, 0, 0}, + {"commandlist remove", "o", 0, 0, 0}, + {"error command", "is", 0, 0, 0}, + {"send command", "sSW", 0, 0, 0}, + {"send text", "sSW", 0, 0, 0}, + {"default command", "sSW", 0, 0, 0}, + {"ignore created", "g", 0, 0, 0}, + {"ignore destroyed", "g", 0, 0, 0}, + {"ignore changed", "g", 0, 0, 0}, + {"log new", "l", 0, 0, 0}, + {"log remove", "l", 0, 0, 0}, + {"log create failed", "l", 0, 0, 0}, + {"log locked", "l", 0, 0, 0}, + {"log started", "l", 0, 0, 0}, + {"log stopped", "l", 0, 0, 0}, + {"log rotated", "l", 0, 0, 0}, + {"log written", "ls", 0, 0, 0}, + {"module loaded", "??", 0, 0, 0}, + {"module unloaded", "??", 0, 0, 0}, + {"module error", "isss", 0, 0, 0}, + {"nicklist new", "Cn", 0, 0, 0}, + {"nicklist remove", "Cn", 0, 0, 0}, + {"nicklist changed", "Cns", 0, 0, 0}, + {"nicklist host changed", "Cn", 0, 0, 0}, + {"nicklist gone changed", "Cn", 0, 0, 0}, + {"nicklist serverop changed", "Cn", 0, 0, 0}, + {"pidwait", "ii", 0, 0, 0}, + {"query created", "qi", 0, 0, 0}, + {"query destroyed", "q", 0, 0, 0}, + {"query nick changed", "qs", 0, 0, 0}, + {"window item name changed", "W", 0, 0, 0}, + {"query address changed", "q", 0, 0, 0}, + {"query server changed", "qS", 0, 0, 0}, + {"rawlog", "as", 0, 0, 0}, + {"server looking", "S", 0, 0, 0}, + {"server connected", "S", 0, 0, 0}, + {"server connecting", "Su", 0, 0, 0}, + {"server connect failed", "S", 0, 0, 0}, + {"server disconnected", "S", 0, 0, 0}, + {"server quit", "Ss", 0, 0, 0}, + {"server sendmsg", "Sssi", 0, 0, 0}, + {"setup reread", "s", 0, 0, 0}, + {"setup saved", "si", 0, 0, 0}, + {"ban type changed", "s", 0, 0, 0}, + {"channel joined", "C", 0, 0, 0}, + {"channel wholist", "C", 0, 0, 0}, + {"channel sync", "C", 0, 0, 0}, + {"channel topic changed", "C", 0, 0, 0}, + {"ctcp msg", "Sssss", 0, 0, 0}, + {"default ctcp msg", "Sssss", 0, 0, 0}, + {"ctcp reply", "Sssss", 0, 0, 0}, + {"default ctcp reply", "Sssss", 0, 0, 0}, + {"ctcp action", "Sssss", 0, 0, 0}, + {"awaylog show", "lii", 0, 0, 0}, + {"server nick changed", "S", 0, 0, 0}, + {"event connected", "S", 0, 0, 0}, + {"server event", "Ssss", 0, 0, 0}, + {"default event", "Ssss", 0, 0, 0}, + {"whois default event", "Ssss", 0, 0, 0}, + {"server incoming", "Ss", 0, 0, 0}, + {"server lag", "S", 0, 0, 0}, + {"server lag disconnect", "S", 0, 0, 0}, + {"massjoin", "CL", 0, 0, 0}, + {"ban new", "Cb", 0, 0, 0}, + {"ban remove", "Cbs", 0, 0, 0}, + {"channel mode changed", "Cs", 0, 0, 0}, + {"nick mode changed", "Cnsss", 0, 0, 0}, + {"user mode changed", "Ss", 0, 0, 0}, + {"away mode changed", "S", 0, 0, 0}, + {"netsplit server new", "Se", 0, 0, 0}, + {"netsplit server remove", "Se", 0, 0, 0}, + {"netsplit new", "N", 0, 0, 0}, + {"netsplit remove", "N", 0, 0, 0}, + {"default dcc ctcp", "sd", 0, 0, 0}, + {"dcc unknown ctcp", "sss", 0, 0, 0}, + {"default dcc reply", "sd", 0, 0, 0}, + {"dcc unknown reply", "sss", 0, 0, 0}, + {"dcc chat message", "ds", 0, 0, 0}, + {"dcc created", "d", 0, 0, 0}, + {"dcc destroyed", "d", 0, 0, 0}, + {"dcc connected", "d", 0, 0, 0}, + {"dcc rejecting", "d", 0, 0, 0}, + {"dcc closed", "d", 0, 0, 0}, + {"dcc request", "ds", 0, 0, 0}, + {"dcc request send", "d", 0, 0, 0}, + {"dcc chat message", "ds", 0, 0, 0}, + {"dcc transfer update", "d", 0, 0, 0}, + {"dcc get receive", "d", 0, 0, 0}, + {"dcc error connect", "d", 0, 0, 0}, + {"dcc error file create", "ds", 0, 0, 0}, + {"dcc error file open", "ssi", 0, 0, 0}, + {"dcc error get not found", "s", 0, 0, 0}, + {"dcc error send exists", "ss", 0, 0, 0}, + {"dcc error unknown type", "s", 0, 0, 0}, + {"dcc error close not found", "sss", 0, 0, 0}, + {"autoignore new", "S?", 0, 0, 0}, + {"autoignore remove", "S?", 0, 0, 0}, + {"flood", "Sssis", 0, 0, 0}, + {"notifylist new", "O", 0, 0, 0}, + {"notifylist remove", "O", 0, 0, 0}, + {"notifylist joined", "Ssssss", 0, 0, 0}, + {"notifylist away changed", "Ssssss", 0, 0, 0}, + {"notifylist unidle", "Ssssss", 0, 0, 0}, + {"notifylist left", "Ssssss", 0, 0, 0}, + {"proxy client connected", "?", 0, 0, 0}, + {"proxy client disconnected", "?", 0, 0, 0}, + {"gui print text", "wiiist", 0, 0, 0}, + {"gui print text finished", "w", 0, 0, 0}, + {"complete word", "GwssI", 0, 0, 0}, + {"exec new", "p", 0, 0, 0}, + {"exec remove", "pi", 0, 0, 0}, + {"exec input", "ps", 0, 0, 0}, + {"message public", "Sssss", 0, 0, 0}, + {"message private", "Ssss", 0, 0, 0}, + {"message own_public", "Sss", 0, 0, 0}, + {"message own_private", "Ssss", 0, 0, 0}, + {"message join", "Ssss", 0, 0, 0}, + {"message part", "Sssss", 0, 0, 0}, + {"message quit", "Ssss", 0, 0, 0}, + {"message kick", "Ssssss", 0, 0, 0}, + {"message nick", "Ssss", 0, 0, 0}, + {"message own_nick", "Ssss", 0, 0, 0}, + {"message invite", "Ssss", 0, 0, 0}, + {"message topic", "Sssss", 0, 0, 0}, + {"keyinfo created", "?", 0, 0, 0}, + {"keyinfo destroyed", "?", 0, 0, 0}, + {"print text", "tss", 0, 0, 0}, + {"theme created", "?", 0, 0, 0}, + {"theme destroyed", "?", 0, 0, 0}, + {"window hilight", "w", 0, 0, 0}, + {"window activity", "wi", 0, 0, 0}, + {"window item hilight", "W", 0, 0, 0}, + {"window item activity", "Wi", 0, 0, 0}, + {"window item new", "wW", 0, 0, 0}, + {"window item remove", "wW", 0, 0, 0}, + {"window item changed", "wW", 0, 0, 0}, + {"window item server changed", "wW", 0, 0, 0}, + {"window created", "w", 0, 0, 0}, + {"window destroyed", "w", 0, 0, 0}, + {"window changed", "ww", 0, 0, 0}, + {"window changed automatic", "w", 0, 0, 0}, + {"window server changed", "wS", 0, 0, 0}, + {"window refnum changed", "wi", 0, 0, 0}, + {"window name changed", "w", 0, 0, 0}, + {"window history changed", "ws", 0, 0, 0}, + {"window level changed", "w", 0, 0, 0}, + {"message irc op_public", "Sssss", 0, 0, 0}, + {"message irc own_wall", "Sss", 0, 0, 0}, + {"message irc own_action", "Sss", 0, 0, 0}, + {"message irc action", "Sssss", 0, 0, 0}, + {"message irc own_notice", "Sss", 0, 0, 0}, + {"message irc notice", "Sssss", 0, 0, 0}, + {"message irc own_ctcp", "Ssss", 0, 0, 0}, + {"message irc ctcp", "Ssssss", 0, 0, 0}, + {"message irc mode", "Sssss", 0, 0, 0}, + {"message dcc own", "ds", 0, 0, 0}, + {"message dcc own_action", "ds", 0, 0, 0}, + {"message dcc own_ctcp", "dss", 0, 0, 0}, + {"message dcc", "ds", 0, 0, 0}, + {"message dcc action", "ds", 0, 0, 0}, + {"message dcc ctcp", "dss", 0, 0, 0}, + {"gui key pressed", "i", 0, 0, 0}, + {NULL} +}; + +#define py_sigmap_len() (sizeof(py_sigmap) / sizeof(py_sigmap[0]) - 1) diff --git a/pysignals.c b/pysignals.c index c3764fb..fb4a3d9 100644 --- a/pysignals.c +++ b/pysignals.c @@ -3,93 +3,438 @@ #include "pysignals.h" #include "factory.h" -static void py_command_proxy(char *data, SERVER_REC *server, WI_ITEM_REC *witem); +/* NOTE: + * 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, + * unregistering or unbinding a dynamic signal will decrement its refcount. + * When refcount hits 0, the dynamic signal is removed from the list, and + * scripts can no longer bind to it. + */ -/* crec should be owned by a Script object */ -void py_command_bind(const char *category, PY_COMMAND_REC *crec) +//FIXME: does not handle binding of signals with varying text + +typedef struct _PY_SIGNAL_SPEC_REC +{ + char *name; + char *arglist; + int id; + int refcount; + int dynamic; +} PY_SIGNAL_SPEC_REC; + +#include "pysigmap.h" + +static GHashTable *py_sighash = NULL; + +static void py_run_handler(PY_SIGNAL_REC *rec, void **args); +static void py_sig_proxy(void *p1, void *p2, void *p3, void *p4, void *p5, void *p6); +static void py_signal_ref(PY_SIGNAL_SPEC_REC *sig); +static int py_signal_unref(PY_SIGNAL_SPEC_REC *sig); +static void py_signal_add(PY_SIGNAL_SPEC_REC *sig); +static PY_SIGNAL_REC *py_signal_rec_new(const char *signal, PyObject *func, const char *command); +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_getstrlist(GList **list, PyObject *pylist); + +PY_SIGNAL_REC *pysignals_command_bind(const char *cmd, PyObject *func, + const char *category, int priority) { - command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT, crec->name, - -1, category, (SIGNAL_FUNC)py_command_proxy, crec); + PY_SIGNAL_REC *rec = py_signal_rec_new("send command", func, cmd); + g_return_val_if_fail(rec != NULL, NULL); + + command_bind_full(MODULE_NAME, priority, cmd, + -1, category, (SIGNAL_FUNC)py_sig_proxy, rec); + + return rec; } -void py_command_unbind(PY_COMMAND_REC *crec) +/* return NULL if signal is invalid */ +PY_SIGNAL_REC *pysignals_signal_add(const char *signal, PyObject *func, int priority) { - command_unbind_full(crec->name, (SIGNAL_FUNC)py_command_proxy, crec); + PY_SIGNAL_REC *rec = py_signal_rec_new(signal, func, NULL); + + if (rec == NULL) + return NULL; + + signal_add_full_id(MODULE_NAME, priority, rec->signal->id, + (SIGNAL_FUNC)py_sig_proxy, rec); + + return rec; } -/* This is just for testing. A complete version would use a signal map, a better - wrapper object factory system, and a generic signal handler like in the Perl - bindings */ -static void py_command_proxy(char *data, SERVER_REC *server, WI_ITEM_REC *witem) +void pysignals_command_unbind(PY_SIGNAL_REC *rec) { - PY_COMMAND_REC *crec; - PyObject *ret, *pyserver, *pywitem; + g_return_if_fail(rec->command != NULL); -#if 0 - if (server) - { - CHAT_PROTOCOL_REC *chat = chat_protocol_find_id(server->chat_type); - if (chat && !strcmp(chat->name, "IRC")) - pyserver = pyirc_server_new(server); - else - pyserver = pyserver_new(server); - g_assert(pyserver != NULL); - } + command_unbind_full(rec->command, (SIGNAL_FUNC)py_sig_proxy, rec); + py_signal_rec_destroy(rec); +} + +void pysignals_signal_remove(PY_SIGNAL_REC *rec) +{ + g_return_if_fail(rec->command == NULL); + + signal_remove_id(rec->signal->id, (SIGNAL_FUNC)py_sig_proxy, rec); + py_signal_rec_destroy(rec); +} + +void pysignals_remove_generic(PY_SIGNAL_REC *rec) +{ + if (rec->command) + pysignals_command_unbind(rec); else + pysignals_signal_remove(rec); +} + +static PyObject *py_mkstrlist(void *iobj) +{ + PyObject *list; + GList *node, **ptr; + ptr = iobj; + + list = PyList_New(0); + if (!list) + return NULL; + + for (node = *ptr; node != NULL; node = node->next) { - pyserver = Py_None; - Py_INCREF(pyserver); + int ret; + PyObject *str; + + str = PyString_FromString(node->data); + if (!str) + { + Py_DECREF(list); + return NULL; + } + + ret = PyList_Append(list, str); + Py_DECREF(str); + if (ret != 0) + { + Py_DECREF(list); + return NULL; + } } - if (witem) + + return list; +} + +/* irssi obj -> PyObject */ +static PyObject *py_i2py(char code, void *iobj) +{ + if (iobj == NULL) + Py_RETURN_NONE; + + switch (code) { - char *type = module_find_id_str("WINDOW ITEM TYPE", witem->type); - g_assert(type != NULL); + case '?': + Py_RETURN_NONE; + + case 's': + return PyString_FromString((char *)iobj); + case 'u': + return PyLong_FromUnsignedLong(*(unsigned long*)iobj); + case 'I': + return PyInt_FromLong(*(int *)iobj); + case 'i': + return PyInt_FromLong((int)iobj); + + case 'G': + return py_mkstrlist(iobj); + case 'L': /* list of nicks */ + return py_irssi_chatlist_new((GSList *)iobj, 1); - if (!strcmp("CHANNEL", type)) - pywitem = pychannel_new(witem); - else if (!strcmp("QUERY", type)) - pywitem = pyquery_new(witem); - else - pywitem = pywindow_item_new(witem); + case 'c': + case 'S': + case 'C': + case 'q': + case 'n': + case 'W': + return py_irssi_chat_new(iobj, 1); - g_assert(pywitem != NULL); + case 'd': + return py_irssi_new(iobj, 1); + + case 'r': + return pyreconnect_new(iobj); + case 'o': + return pycommand_new(iobj); + case 'l': + return pylog_new(iobj); + case 'a': + return pyrawlog_new(iobj); + case 'g': + return pyignore_new(iobj); + case 'b': + return pyban_new(iobj); + case 'N': + return pynetsplit_new(iobj); + case 'e': + return pynetsplit_server_new(iobj); + case 'O': + return pynotifylist_new(iobj); + case 'p': + return pyprocess_new(iobj); + case 't': + return pytextdest_new(iobj); + case 'w': + return pywindow_new(iobj); } - else + + return PyErr_Format(PyExc_TypeError, "unknown code %c", code); +} + +static void py_getstrlist(GList **list, PyObject *pylist) +{ + GList *out = NULL; + int i; + PyObject *str; + char *cstr; + + for (i = 0; i < PyList_Size(pylist); i++) { - pywitem = Py_None; - Py_INCREF(pywitem); + str = PyList_GET_ITEM(pylist, i); + if (!PyString_Check(str)) + { + PyErr_SetString(PyExc_TypeError, "string list contains invalid elements"); + PyErr_Print(); + return; + } + + cstr = g_strdup(PyString_AS_STRING(str)); + out = g_list_append(out, cstr); } -#endif - if (server) + g_list_foreach(*list, (GFunc)g_free, NULL); + g_list_free(*list); + *list = out; +} + +static void py_run_handler(PY_SIGNAL_REC *rec, void **args) +{ + PyObject *argtup, *ret; + char *arglist = rec->signal->arglist; + int arglen, i, j; + + arglen = strlen(arglist); + g_return_if_fail(arglen <= SIGNAL_MAX_ARGUMENTS); + + argtup = PyTuple_New(arglen); + if (!argtup) + goto error; + + for (i = 0; i < arglen; i++) { - pyserver = py_irssi_chat_new(server, 1); - g_assert(pyserver != NULL); + PyObject *arg = py_i2py(arglist[i], args[i]); + if (!arg) + goto error; + + PyTuple_SET_ITEM(argtup, i, arg); } - else + + ret = PyObject_CallObject(rec->handler, argtup); + if (!ret) + goto error; + + /*XXX: reference arg handling not well tested */ + for (i = 0, j = 0; i < arglen; i++) { - pyserver = Py_None; - Py_INCREF(Py_None); + GList **list; + PyObject *pyarg = PyTuple_GET_ITEM(argtup, i); + + switch (arglist[i]) + { + case 'G': + list = args[i]; + py_getstrlist(list, pyarg); + break; + + case 'I': + if (ret != Py_None) + { + PyObject *value; + int *intarg = args[i]; + + /* expect a proper return value to set reference arg. + * if return is a tuple, find the next item + */ + if (PyTuple_Check(ret)) + value = PyTuple_GET_ITEM(ret, j++); + else + value = ret; + + if (!PyInt_Check(value)) + continue; + + *intarg = PyInt_AS_LONG(value); + } + break; + } } + + Py_XDECREF(ret); + +error: + Py_XDECREF(argtup); + if (PyErr_Occurred()) + PyErr_Print(); +} - if (witem) +static void py_sig_proxy(void *p1, void *p2, void *p3, void *p4, void *p5, void *p6) +{ + PY_SIGNAL_REC *rec = signal_get_user_data(); + void *args[6]; + + args[0] = p1; args[1] = p2; args[2] = p3; + args[3] = p4; args[4] = p5; args[5] = p6; + py_run_handler(rec, args); +} + +/* returns NULL if signal is invalid, incr reference to func */ +static PY_SIGNAL_REC *py_signal_rec_new(const char *signal, PyObject *func, const char *command) +{ + PY_SIGNAL_REC *rec; + PY_SIGNAL_SPEC_REC *spec; + + g_return_val_if_fail(func != NULL, NULL); + + spec = g_hash_table_lookup(py_sighash, signal); + if (!spec) + return NULL; + + rec = g_new(PY_SIGNAL_REC, 1); + rec->command = g_strdup(command); + rec->signal = spec; + rec->handler = func; + Py_INCREF(func); + + py_signal_ref(spec); + + return rec; +} + +static void py_signal_rec_destroy(PY_SIGNAL_REC *sig) +{ + py_signal_unref(sig->signal); + + Py_DECREF(sig->handler); + g_free(sig->command); + g_free(sig); +} + +static void py_signal_add(PY_SIGNAL_SPEC_REC *sig) +{ + g_hash_table_insert(py_sighash, sig->name, sig); +} + +static void py_signal_ref(PY_SIGNAL_SPEC_REC *sig) +{ + g_return_if_fail(sig->refcount >= 0); + + sig->refcount++; +} + +static int py_signal_unref(PY_SIGNAL_SPEC_REC *sig) +{ + g_return_val_if_fail(sig->refcount >= 1, 0); + + sig->refcount--; + + if (sig->refcount == 0) { - pywitem = py_irssi_chat_new(witem, 1); - g_assert(pywitem != NULL); + if (sig->dynamic) + { + g_hash_table_remove(py_sighash, sig->name); + + /* freeing name also takes care of the key */ + g_free(sig->name); + g_free(sig->arglist); + g_free(sig); + } + + /* non-dynamic signals are not removed */ + + return 1; } - else + + return 0; +} + +/* returns 0 when signal already exists, but with different args */ +int pysignals_register(const char *name, const char *arglist) +{ + PY_SIGNAL_SPEC_REC *spec; + + spec = g_hash_table_lookup(py_sighash, name); + if (!spec) { - pywitem = Py_None; - Py_INCREF(Py_None); + spec = g_new0(PY_SIGNAL_SPEC_REC, 1); + spec->dynamic = 1; + spec->refcount = 0; + spec->name = g_strdup(name); + spec->arglist = g_strdup(arglist); + spec->id = signal_get_uniq_id(name); + + g_hash_table_insert(py_sighash, spec->name, spec); } + else if (strcmp(spec->arglist, arglist) != 0) + return 0; + + spec->refcount++; + + return 1; +} + +/* returns 0 when name doesn't exist */ +int pysignals_unregister(const char *name) +{ + PY_SIGNAL_SPEC_REC *spec; + + spec = g_hash_table_lookup(py_sighash, name); + if (!spec) + return 0; + + py_signal_unref(spec); + return 1; +} + +void pysignals_init(void) +{ + int i; - crec = signal_get_user_data(); - ret = PyObject_CallFunction(crec->handler, "(sOO)", data, pyserver, pywitem); - if (!ret) - PyErr_Print(); - else - Py_DECREF(ret); + g_return_if_fail(py_sighash == NULL); + + py_sighash = g_hash_table_new((GHashFunc)g_str_hash, (GCompareFunc)g_str_equal); + + for (i = 0; i < py_sigmap_len(); i++) + { + py_sigmap[i].refcount = 1; + py_sigmap[i].dynamic = 0; + py_sigmap[i].id = signal_get_uniq_id(py_sigmap[i].name); + py_signal_add(&py_sigmap[i]); + } +} + +static int py_check_sig(char *key, PY_SIGNAL_SPEC_REC *value, void *data) +{ + /* shouldn't need to deallocate any script recs -- all remaining at + this point should not be dynamic. non dynamic signals should have + no outstanding references */ + g_return_val_if_fail(value->dynamic == 0, TRUE); + g_return_val_if_fail(value->refcount == 1, TRUE); - Py_DECREF(pyserver); - Py_DECREF(pywitem); + return TRUE; +} + +void pysignals_deinit(void) +{ + g_return_if_fail(py_sighash != NULL); + g_hash_table_foreach_remove(py_sighash, (GHRFunc)py_check_sig, NULL); + g_hash_table_destroy(py_sighash); + py_sighash = NULL; } diff --git a/pysignals.h b/pysignals.h index 823472e..f9ace24 100644 --- a/pysignals.h +++ b/pysignals.h @@ -2,13 +2,26 @@ #define _PYSIGNALS_H_ #include -typedef struct +/* forward */ +struct _PY_SIGNAL_SPEC_REC; + +typedef struct _PY_SIGNAL_REC { - char *name; + struct _PY_SIGNAL_SPEC_REC *signal; + char *command; /* NULL if this is signal */ PyObject *handler; -} PY_COMMAND_REC; +} PY_SIGNAL_REC; -void py_command_bind(const char *category, PY_COMMAND_REC *crec); -void py_command_unbind(PY_COMMAND_REC *crec); +PY_SIGNAL_REC *pysignals_command_bind(const char *cmd, PyObject *func, + const char *category, int priority); +PY_SIGNAL_REC *pysignals_signal_add(const char *signal, PyObject *func, + int priority); +void pysignals_command_unbind(PY_SIGNAL_REC *rec); +void pysignals_signal_remove(PY_SIGNAL_REC *rec); +void pysignals_remove_generic(PY_SIGNAL_REC *rec); +int pysignals_register(const char *name, const char *arglist); +int pysignals_unregister(const char *name); +void pysignals_init(void); +void pysignals_deinit(void); #endif diff --git a/sig2code.py b/sig2code.py new file mode 100644 index 0000000..48cee71 --- /dev/null +++ b/sig2code.py @@ -0,0 +1,124 @@ +import re +import sys + +code_text = """ + Conversion codes notes + I = int *arg IN/OUT + G = string GList **arg IN/OUT (list must be reconstructed) + L = list of nicks + + Scalars + s -> char * + u -> ulong * + I -> int * + i -> int + + Lists of things (completion.c and massjoin.c) + G -> GList * of char* + L -> GSList of NICK_RECs + + Chat objects + c -> CHATNET_REC + S -> SERVER_REC + C -> CHANNEL_REC + q -> QUERY_REC + n -> NICK_REC + W -> WI_ITEM_REC + + Irssi objects + d -> DCC_REC + + Other objects + r -> RECONNECT_REC + o -> COMMAND_REC + l -> LOG_REC + a -> RAWLOG_REC + g -> IGNORE_REC + ? -> MODULE_REC + b -> BAN_REC + N -> NETSPLIT_REC + e -> NETSPLIT_SERVER_REC + ? -> AUTOIGNORE_REC + O -> NOTIFYLIST_REC + ? -> THEME_REC + ? -> KEYINFO_REC + p -> PROCESS_REC + t -> TEXT_DEST_REC + w -> WINDOW_REC +""" + +#generate body of transcode function from the list of codes above +def prepair(): + lines = code_text.split('\n') + codes = [] + for ln in lines: + m = re.match('^.*([a-zA-Z\?]) -> (.*)$', ln) + if not m: continue + code, match = m.groups() + code = code.strip() + match = match.strip() + + assert code == '?' or code not in codes, "dupe code, " + code + codes.append(code) + + print "if arg.startswith('%s'): return '%s'" % (match, code) + +def transcode(arg): + if arg.startswith('char *'): return 's' + if arg.startswith('ulong *'): return 'u' + if arg.startswith('int *'): return 'I' + if arg.startswith('int'): return 'i' + if arg.startswith('GList * of char*'): return 'G' + if arg.startswith('GSList of NICK_RECs'): return 'L' + if arg.startswith('CHATNET_REC'): return 'c' + if arg.startswith('SERVER_REC'): return 'S' + if arg.startswith('RECONNECT_REC'): return 'r' + if arg.startswith('CHANNEL_REC'): return 'C' + if arg.startswith('QUERY_REC'): return 'q' + if arg.startswith('COMMAND_REC'): return 'o' + if arg.startswith('NICK_REC'): return 'n' + if arg.startswith('LOG_REC'): return 'l' + if arg.startswith('RAWLOG_REC'): return 'a' + if arg.startswith('IGNORE_REC'): return 'g' + if arg.startswith('MODULE_REC'): return '?' + if arg.startswith('BAN_REC'): return 'b' + if arg.startswith('NETSPLIT_REC'): return 'N' + if arg.startswith('NETSPLIT_SERVER_REC'): return 'e' + if arg.startswith('DCC_REC'): return 'd' + if arg.startswith('AUTOIGNORE_REC'): return '?' + if arg.startswith('NOTIFYLIST_REC'): return 'O' + if arg.startswith('THEME_REC'): return '?' + if arg.startswith('KEYINFO_REC'): return '?' + if arg.startswith('PROCESS_REC'): return 'p' + if arg.startswith('TEXT_DEST_REC'): return 't' + if arg.startswith('WINDOW_REC'): return 'w' + if arg.startswith('WI_ITEM_REC'): return 'W' + return '?' + +def main(): + + print "/* Include in your C module */" + print "static PY_SIGNAL_SPEC_REC py_sigmap[] = {" + + for ln in sys.stdin: + ln = ln.strip() + m = re.match('^\"([^\"]+)\",(.*)$', ln) + if not m: continue + + signal, args = m.groups() + + if signal.startswith('script '): continue + + argv = [transcode(a.strip()) for a in args.split(',')] + argv = ''.join(argv) + + print ' {"%s", "%s", 0, 0, 0},' % (signal, argv) + + print " {NULL}" + print "};" + print + print "#define py_sigmap_len() (sizeof(py_sigmap) / sizeof(py_sigmap[0]) - 1)" + +if __name__ == '__main__': + main() + -- cgit