summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristopher Davis <loafier@gmail.com>2006-06-28 04:40:18 +0000
committerChristopher Davis <loafier@gmail.com>2006-06-28 04:40:18 +0000
commita329fa4675852886ba2bbc9637cebcac882f1575 (patch)
treeefae8b550ed154c5928c6d9edc0ec77ff71ed81e
parent96280edc8fda9cc591bc0833398e576bf680931d (diff)
downloadirssi-python-a329fa4675852886ba2bbc9637cebcac882f1575.tar.gz
irssi-python-a329fa4675852886ba2bbc9637cebcac882f1575.tar.xz
irssi-python-a329fa4675852886ba2bbc9637cebcac882f1575.zip
added support for timeouts/IO sources
git-svn-id: http://svn.irssi.org/repos/irssi-python@4294 dbcabf3a-b0e7-0310-adc4-f8d773084564
-rw-r--r--Makefile2
-rw-r--r--constants.txt4
-rw-r--r--objects/pyscript-object.c89
-rw-r--r--objects/pyscript-object.h4
-rw-r--r--pyloader.c2
-rw-r--r--pymodule.c72
-rw-r--r--pysignals.c121
-rw-r--r--pysignals.h1
-rw-r--r--pysource.c156
-rw-r--r--pysource.h13
10 files changed, 437 insertions, 27 deletions
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 "<cmd>" 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 <Python.h>
+#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 <Python.h>
+
+/* 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