diff --git a/src/mod/python.mod/pycmds.c b/src/mod/python.mod/pycmds.c index 1e55088c4..9f6754adb 100644 --- a/src/mod/python.mod/pycmds.c +++ b/src/mod/python.mod/pycmds.c @@ -25,23 +25,23 @@ #include #include "src/mod/module.h" -struct py_bind { - tcl_bind_list_t *bindtable; - char tclcmdname[512]; - PyObject *callback; - struct py_bind *next; -}; - typedef struct { PyObject_HEAD - char tclcmdname[512]; + char tclcmdname[128]; } TclFunc; + +typedef struct { + PyObject_HEAD + char tclcmdname[128]; + char *flags; + char *mask; + tcl_bind_list_t *bindtable; + PyObject *callback; +} PythonBind; -static PyTypeObject TclFuncType; +static PyTypeObject TclFuncType, PythonBindType; static int eval_idx = -1; -static struct py_bind *py_bindlist; - static PyObject *EggdropError; //create static Python Exception object static Tcl_Obj *py_to_tcl_obj(PyObject *o); // generic conversion function @@ -141,13 +141,13 @@ static PyObject *py_findircuser(PyObject *self, PyObject *args) { static int tcl_call_python(ClientData cd, Tcl_Interp *irp, int objc, Tcl_Obj *const objv[]) { PyObject *args = PyTuple_New(objc > 1 ? objc - 1: 0); - struct py_bind *bindinfo = cd; + PythonBind *bind = cd; // objc[0] is procname for (int i = 1; i < objc; i++) { PyTuple_SET_ITEM(args, i - 1, Py_BuildValue("s", Tcl_GetStringFromObj(objv[i], NULL))); } - if (!PyObject_Call(bindinfo->callback, args, NULL)) { + if (!PyObject_Call(bind->callback, args, NULL)) { PyErr_Print(); Tcl_SetResult(irp, "Error calling python code", TCL_STATIC); return TCL_ERROR; @@ -199,6 +199,7 @@ static PyObject *py_parse_tcl_dict(PyObject *self, PyObject *args) { strobj = Tcl_NewStringObj(str, -1); if (Tcl_DictObjFirst(tclinterp, strobj, &search, &key, &value, &done) != TCL_OK) { PyErr_SetString(EggdropError, "Supplied string is not a Tcl dictionary"); + return NULL; } result = PyDict_New(); while (!done) { @@ -212,10 +213,34 @@ static PyObject *py_parse_tcl_dict(PyObject *self, PyObject *args) { return result; } +static PyObject *py_unbind(PyObject *self, PyObject *args) { + PythonBind *bind; + + if (!PyObject_TypeCheck(self, &PythonBindType)) { + PyErr_SetString(EggdropError, "Invalid argument for unbind method"); + return NULL; + } + + bind = (PythonBind *)self; + unbind_bind_entry(bind->bindtable, bind->flags, bind->mask, bind->tclcmdname); + // cleanup in python_bind_destroyed callback when Tcl command is destroyed + Py_RETURN_NONE; +} + +void python_bind_destroyed(ClientData cd) { + PythonBind *bind = cd; + + Py_DECREF(bind->callback); + nfree(bind->mask); + nfree(bind->flags); + Py_DECREF((PyObject *)bind); +} + static PyObject *py_bind(PyObject *self, PyObject *args) { PyObject *callback; + PythonBind *bind; + Py_hash_t hash; char *bindtype, *mask, *flags; - struct py_bind *bindinfo; tcl_bind_list_t *tl; // type flags mask callback @@ -237,17 +262,18 @@ static PyObject *py_bind(PyObject *self, PyObject *args) { } Py_IncRef(callback); - bindinfo = nmalloc(sizeof *bindinfo); - bindinfo->bindtable = tl; - bindinfo->callback = callback; - bindinfo->next = py_bindlist; - snprintf(bindinfo->tclcmdname, sizeof bindinfo->tclcmdname, "*python:%s:%s:%" PRIxPTR, bindtype, mask, (uintptr_t)callback); - py_bindlist = bindinfo; - // TODO: deleteproc - Tcl_CreateObjCommand(tclinterp, bindinfo->tclcmdname, tcl_call_python, bindinfo, NULL); - // TODO: flags? - bind_bind_entry(tl, flags, mask, bindinfo->tclcmdname); - Py_RETURN_NONE; + bind = PyObject_New(PythonBind, &PythonBindType); + bind->mask = strdup(mask); + bind->flags = strdup(flags); + bind->bindtable = tl; + bind->callback = callback; + hash = PyObject_Hash((PyObject *)bind); + snprintf(bind->tclcmdname, sizeof bind->tclcmdname, "*python:%s:%" PRIx64, bindtype, (int64_t)hash); + + Tcl_CreateObjCommand(tclinterp, bind->tclcmdname, tcl_call_python, bind, python_bind_destroyed); + bind_bind_entry(tl, flags, mask, bind->tclcmdname); + + return (PyObject *)bind; } static Tcl_Obj *py_list_to_tcl_obj(PyObject *o) { @@ -358,7 +384,7 @@ static PyObject *py_findtclfunc(PyObject *self, PyObject *args) { return NULL; } result = PyObject_New(TclFunc, &TclFuncType); - strcpy(result->tclcmdname, cmdname); + strlcpy(result->tclcmdname, cmdname, sizeof result->tclcmdname); return (PyObject *)result; } @@ -405,6 +431,22 @@ static PyTypeObject TclFuncType = { .tp_call = python_call_tcl, }; +static PyMethodDef PythonBindMethods[] = { + {"unbind", py_unbind, METH_VARARGS, "deregister an eggdrop python bind"}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +static PyTypeObject PythonBindType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "eggdrop.PythonBind", + .tp_doc = "Eggdrop bind to a python callback", + .tp_basicsize = sizeof(PythonBind), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_methods = PythonBindMethods +}; + PyMODINIT_FUNC PyInit_eggdrop(void) { PyObject *pymodobj, *eggtclmodobj, *pymoddict; @@ -430,6 +472,7 @@ PyMODINIT_FUNC PyInit_eggdrop(void) { PyDict_SetItemString(pymoddict, "eggdrop.tcl", eggtclmodobj); PyType_Ready(&TclFuncType); + PyType_Ready(&PythonBindType); return pymodobj; }