summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristopher Davis <loafier@gmail.com>2006-06-23 03:51:52 +0000
committerChristopher Davis <loafier@gmail.com>2006-06-23 03:51:52 +0000
commit4d33c04f15e60e21a537edd635c9ac130312a3cb (patch)
tree9530c793afd67ef5c8319c452d33011c7f7fe197
parentc76f11d4ead827d8e87e265131a3dee534bc0792 (diff)
downloadirssi-python-4d33c04f15e60e21a537edd635c9ac130312a3cb.tar.gz
irssi-python-4d33c04f15e60e21a537edd635c9ac130312a3cb.tar.xz
irssi-python-4d33c04f15e60e21a537edd635c9ac130312a3cb.zip
Began work on signal handler/map system. It seems to be working;
however, events with varying <cmd> 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 <cmd> 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
-rw-r--r--Makefile3
-rw-r--r--classes.txt2
-rw-r--r--objects/pyscript-object.c63
-rw-r--r--objects/pyscript-object.h1
-rw-r--r--pycore.c4
-rw-r--r--pyloader.c18
-rw-r--r--pymodule.c75
-rw-r--r--pysigmap.h177
-rw-r--r--pysignals.c463
-rw-r--r--pysignals.h23
-rw-r--r--sig2code.py124
11 files changed, 866 insertions, 87 deletions
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 <Python.h>
#include <glib.h>
+//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 <cmd> 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 <Python.h>
-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()
+