Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python unbind functionality #1650

Merged
merged 3 commits into from
Jul 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 69 additions & 26 deletions src/mod/python.mod/pycmds.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@
#include <tcl.h>
#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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;

Expand All @@ -430,6 +472,7 @@ PyMODINIT_FUNC PyInit_eggdrop(void) {
PyDict_SetItemString(pymoddict, "eggdrop.tcl", eggtclmodobj);

PyType_Ready(&TclFuncType);
PyType_Ready(&PythonBindType);

return pymodobj;
}