Support threads-enabled Tcl installations.
This commit is contained in:
parent
d9a6ad3beb
commit
b5bfb9f38c
|
@ -9,17 +9,13 @@ Copyright (C) 1994 Steen Lumholt.
|
|||
|
||||
/* TCL/TK VERSION INFO:
|
||||
|
||||
Only Tcl/Tk 8.0 and later are supported. Older versions are not
|
||||
supported. (Use Python 1.5.2 if you cannot upgrade your Tcl/Tk
|
||||
Only Tcl/Tk 8.2 and later are supported. Older versions are not
|
||||
supported. (Use Python 2.2 if you cannot upgrade your Tcl/Tk
|
||||
libraries.)
|
||||
*/
|
||||
|
||||
/* XXX Further speed-up ideas, involving Tcl 8.0 features:
|
||||
|
||||
- In Tcl_Call(), create Tcl objects from the arguments, possibly using
|
||||
intelligent mappings between Python objects and Tcl objects (e.g. ints,
|
||||
floats and Tcl window pointers could be handled specially).
|
||||
|
||||
- Register a new Tcl type, "Python callable", which can be called more
|
||||
efficiently and passed to Tcl_EvalObj() directly (if this is possible).
|
||||
|
||||
|
@ -48,6 +44,11 @@ Copyright (C) 1994 Steen Lumholt.
|
|||
anymore, this should go. */
|
||||
#define USE_COMPAT_CONST
|
||||
|
||||
/* If Tcl is compiled for threads, we must also define TCL_THREAD. We define
|
||||
it always; if Tcl is not threaded, the thread functions in
|
||||
Tcl are empty. */
|
||||
#define TCL_THREADS
|
||||
|
||||
#ifdef TK_FRAMEWORK
|
||||
#include <Tcl/tcl.h>
|
||||
#include <Tk/tk.h>
|
||||
|
@ -111,9 +112,8 @@ Copyright (C) 1994 Steen Lumholt.
|
|||
|
||||
#ifdef WITH_THREAD
|
||||
|
||||
/* The threading situation is complicated. Tcl is not thread-safe, except for
|
||||
Tcl 8.1, which will probably remain in alpha status for another 6 months
|
||||
(and the README says that Tk will probably remain thread-unsafe forever).
|
||||
/* The threading situation is complicated. Tcl is not thread-safe, except
|
||||
when configured with --enable-threads.
|
||||
So we need to use a lock around all uses of Tcl. Previously, the Python
|
||||
interpreter lock was used for this. However, this causes problems when
|
||||
other Python threads need to run while Tcl is blocked waiting for events.
|
||||
|
@ -148,31 +148,58 @@ Copyright (C) 1994 Steen Lumholt.
|
|||
These locks expand to several statements and brackets; they should not be
|
||||
used in branches of if statements and the like.
|
||||
|
||||
If Tcl is threaded, this approach won't work anymore. The Tcl interpreter is
|
||||
only valid in the thread that created it, and all Tk activity must happen in this
|
||||
thread, also. That means that the mainloop must be invoked in the thread that
|
||||
created the interpreter. Invoking commands from other threads is possible;
|
||||
_tkinter will queue an event for the interpreter thread, which will then
|
||||
execute the command and pass back the result. If the main thread is not in the
|
||||
mainloop, and invoking commands causes an exception; if the main loop is running
|
||||
but not processing events, the command invocation will block.
|
||||
|
||||
In addition, for a threaded Tcl, a single global tcl_tstate won't be sufficient
|
||||
anymore, since multiple Tcl interpreters may simultaneously dispatch in different
|
||||
threads. So we use the Tcl TLS API.
|
||||
|
||||
*/
|
||||
|
||||
static PyThread_type_lock tcl_lock = 0;
|
||||
|
||||
#ifdef TCL_THREADS
|
||||
static Tcl_ThreadDataKey state_key;
|
||||
typedef PyThreadState *ThreadSpecificData;
|
||||
#define tcl_tstate (*(PyThreadState**)Tcl_GetThreadData(&state_key, sizeof(PyThreadState*)))
|
||||
#else
|
||||
static PyThreadState *tcl_tstate = NULL;
|
||||
#endif
|
||||
|
||||
#define ENTER_TCL \
|
||||
{ PyThreadState *tstate = PyThreadState_Get(); Py_BEGIN_ALLOW_THREADS \
|
||||
PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate;
|
||||
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate;
|
||||
|
||||
#define LEAVE_TCL \
|
||||
tcl_tstate = NULL; PyThread_release_lock(tcl_lock); Py_END_ALLOW_THREADS}
|
||||
tcl_tstate = NULL; if(tcl_lock)PyThread_release_lock(tcl_lock); Py_END_ALLOW_THREADS}
|
||||
|
||||
#define ENTER_OVERLAP \
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
#define LEAVE_OVERLAP_TCL \
|
||||
tcl_tstate = NULL; PyThread_release_lock(tcl_lock); }
|
||||
tcl_tstate = NULL; if(tcl_lock)PyThread_release_lock(tcl_lock); }
|
||||
|
||||
#define ENTER_PYTHON \
|
||||
{ PyThreadState *tstate = tcl_tstate; tcl_tstate = NULL; \
|
||||
PyThread_release_lock(tcl_lock); PyEval_RestoreThread((tstate)); }
|
||||
if(tcl_lock)PyThread_release_lock(tcl_lock); PyEval_RestoreThread((tstate)); }
|
||||
|
||||
#define LEAVE_PYTHON \
|
||||
{ PyThreadState *tstate = PyEval_SaveThread(); \
|
||||
PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate; }
|
||||
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); tcl_tstate = tstate; }
|
||||
|
||||
#define CHECK_TCL_APPARTMENT \
|
||||
if (((TkappObject *)self)->threaded && \
|
||||
((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { \
|
||||
PyErr_SetString(PyExc_RuntimeError, "Calling Tcl from different appartment"); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
|
@ -182,6 +209,7 @@ static PyThreadState *tcl_tstate = NULL;
|
|||
#define LEAVE_OVERLAP_TCL
|
||||
#define ENTER_PYTHON
|
||||
#define LEAVE_PYTHON
|
||||
#define CHECK_TCL_APPARTMENT
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -220,6 +248,9 @@ typedef struct {
|
|||
PyObject_HEAD
|
||||
Tcl_Interp *interp;
|
||||
int wantobjects;
|
||||
int threaded; /* True if tcl_platform[threaded] */
|
||||
Tcl_ThreadId thread_id;
|
||||
int dispatching;
|
||||
/* We cannot include tclInt.h, as this is internal.
|
||||
So we cache interesting types here. */
|
||||
Tcl_ObjType *BooleanType;
|
||||
|
@ -279,6 +310,7 @@ Sleep(int milli)
|
|||
#endif /* MS_WINDOWS */
|
||||
#endif /* WITH_THREAD */
|
||||
|
||||
|
||||
|
||||
static char *
|
||||
AsString(PyObject *value, PyObject *tmp)
|
||||
|
@ -541,6 +573,23 @@ Tkapp_New(char *screenName, char *baseName, char *className,
|
|||
|
||||
v->interp = Tcl_CreateInterp();
|
||||
v->wantobjects = wantobjects;
|
||||
v->threaded = Tcl_GetVar2Ex(v->interp, "tcl_platform", "threaded",
|
||||
TCL_GLOBAL_ONLY) != NULL;
|
||||
v->thread_id = Tcl_GetCurrentThread();
|
||||
v->dispatching = 0;
|
||||
|
||||
#ifndef TCL_THREADS
|
||||
if (v->threaded) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Tcl is threaded but _tkinter is not");
|
||||
Py_DECREF(v);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (v->threaded && tcl_lock) {
|
||||
/* If Tcl is threaded, we don't need the lock. */
|
||||
PyThread_free_lock(tcl_lock);
|
||||
tcl_lock = NULL;
|
||||
}
|
||||
|
||||
v->BooleanType = Tcl_GetObjType("boolean");
|
||||
v->ByteArrayType = Tcl_GetObjType("bytearray");
|
||||
|
@ -591,6 +640,19 @@ Tkapp_New(char *screenName, char *baseName, char *className,
|
|||
}
|
||||
|
||||
|
||||
static void
|
||||
Tkapp_ThreadSend(TkappObject *self, Tcl_Event *ev,
|
||||
Tcl_Condition *cond, Tcl_Mutex *mutex)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
Tcl_MutexLock(mutex);
|
||||
Tcl_ThreadQueueEvent(self->thread_id, ev, TCL_QUEUE_TAIL);
|
||||
Tcl_ThreadAlert(self->thread_id);
|
||||
Tcl_ConditionWait(cond, mutex, NULL);
|
||||
Tcl_MutexUnlock(mutex);
|
||||
Py_END_ALLOW_THREADS
|
||||
}
|
||||
|
||||
|
||||
/** Tcl Eval **/
|
||||
|
||||
|
@ -848,7 +910,7 @@ FromObj(PyObject* tkapp, Tcl_Obj *value)
|
|||
}
|
||||
|
||||
if (value->typePtr == app->ProcBodyType) {
|
||||
/* fall through: return tcl object. */
|
||||
/* fall through: return tcl object. */
|
||||
}
|
||||
|
||||
if (value->typePtr == app->StringType) {
|
||||
|
@ -883,19 +945,38 @@ FromObj(PyObject* tkapp, Tcl_Obj *value)
|
|||
return newPyTclObject(value);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
Tkapp_Call(PyObject *self, PyObject *args)
|
||||
/* This mutex synchronizes inter-thread command calls. */
|
||||
|
||||
TCL_DECLARE_MUTEX(call_mutex)
|
||||
|
||||
typedef struct Tkapp_CallEvent {
|
||||
Tcl_Event ev; /* Must be first */
|
||||
TkappObject *self;
|
||||
PyObject *args;
|
||||
int flags;
|
||||
PyObject **res;
|
||||
PyObject **exc_type, **exc_value, **exc_tb;
|
||||
Tcl_Condition done;
|
||||
} Tkapp_CallEvent;
|
||||
|
||||
void
|
||||
Tkapp_CallDeallocArgs(Tcl_Obj** objv, Tcl_Obj** objStore, int objc)
|
||||
{
|
||||
Tcl_Obj *objStore[ARGSZ];
|
||||
Tcl_Obj **objv = NULL;
|
||||
int objc = 0, i;
|
||||
PyObject *res = NULL;
|
||||
Tcl_Interp *interp = Tkapp_Interp(self);
|
||||
/* Could add TCL_EVAL_GLOBAL if wrapped by GlobalCall... */
|
||||
int flags = TCL_EVAL_DIRECT;
|
||||
int i;
|
||||
for (i = 0; i < objc; i++)
|
||||
Tcl_DecrRefCount(objv[i]);
|
||||
if (objv != objStore)
|
||||
ckfree(FREECAST objv);
|
||||
}
|
||||
|
||||
objv = objStore;
|
||||
/* Convert Python objects to Tcl objects. This must happen in the
|
||||
interpreter thread, which may or may not be the calling thread. */
|
||||
|
||||
static Tcl_Obj**
|
||||
Tkapp_CallArgs(PyObject *args, Tcl_Obj** objStore, int *pobjc)
|
||||
{
|
||||
Tcl_Obj **objv = objStore;
|
||||
int objc, i;
|
||||
if (args == NULL)
|
||||
/* do nothing */;
|
||||
|
||||
|
@ -934,24 +1015,29 @@ Tkapp_Call(PyObject *self, PyObject *args)
|
|||
Tcl_IncrRefCount(objv[i]);
|
||||
}
|
||||
}
|
||||
*pobjc = objc;
|
||||
return objv;
|
||||
finally:
|
||||
Tkapp_CallDeallocArgs(objv, objStore, objc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ENTER_TCL
|
||||
/* Convert the results of a command call into a Python objects. */
|
||||
|
||||
i = Tcl_EvalObjv(interp, objc, objv, flags);
|
||||
|
||||
ENTER_OVERLAP
|
||||
if (i == TCL_ERROR)
|
||||
Tkinter_Error(self);
|
||||
else if(((TkappObject*)self)->wantobjects) {
|
||||
Tcl_Obj *value = Tcl_GetObjResult(interp);
|
||||
static PyObject*
|
||||
Tkapp_CallResult(TkappObject *self)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
if(self->wantobjects) {
|
||||
Tcl_Obj *value = Tcl_GetObjResult(self->interp);
|
||||
/* Not sure whether the IncrRef is necessary, but something
|
||||
may overwrite the interpreter result while we are
|
||||
converting it. */
|
||||
Tcl_IncrRefCount(value);
|
||||
res = FromObj(self, value);
|
||||
res = FromObj((PyObject*)self, value);
|
||||
Tcl_DecrRefCount(value);
|
||||
} else {
|
||||
const char *s = Tcl_GetStringResult(interp);
|
||||
const char *s = Tcl_GetStringResult(self->interp);
|
||||
const char *p = s;
|
||||
|
||||
/* If the result contains any bytes with the top bit set,
|
||||
|
@ -970,8 +1056,8 @@ Tkapp_Call(PyObject *self, PyObject *args)
|
|||
p = strchr(p, '\0');
|
||||
res = PyUnicode_DecodeUTF8(s, (int)(p-s), "strict");
|
||||
if (res == NULL) {
|
||||
PyErr_Clear();
|
||||
res = PyString_FromStringAndSize(s, (int)(p-s));
|
||||
PyErr_Clear();
|
||||
res = PyString_FromStringAndSize(s, (int)(p-s));
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
@ -979,14 +1065,124 @@ Tkapp_Call(PyObject *self, PyObject *args)
|
|||
res = PyString_FromStringAndSize(s, (int)(p-s));
|
||||
#endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
LEAVE_OVERLAP_TCL
|
||||
/* Tkapp_CallProc is the event procedure that is executed in the context of
|
||||
the Tcl interpreter thread. Initially, it holds the Tcl lock, and doesn't
|
||||
hold the Python lock. */
|
||||
|
||||
finally:
|
||||
for (i = 0; i < objc; i++)
|
||||
Tcl_DecrRefCount(objv[i]);
|
||||
if (objv != objStore)
|
||||
ckfree(FREECAST objv);
|
||||
static int
|
||||
Tkapp_CallProc(Tkapp_CallEvent *e, int flags)
|
||||
{
|
||||
Tcl_Obj *objStore[ARGSZ];
|
||||
Tcl_Obj **objv;
|
||||
int objc;
|
||||
int i;
|
||||
ENTER_PYTHON
|
||||
objv = Tkapp_CallArgs(e->args, objStore, &objc);
|
||||
if (!objv) {
|
||||
PyErr_Fetch(e->exc_type, e->exc_value, e->exc_tb);
|
||||
*(e->res) = NULL;
|
||||
}
|
||||
LEAVE_PYTHON
|
||||
if (!objv)
|
||||
goto done;
|
||||
i = Tcl_EvalObjv(e->self->interp, objc, objv, e->flags);
|
||||
ENTER_PYTHON
|
||||
if (i == TCL_ERROR) {
|
||||
*(e->res) = NULL;
|
||||
*(e->exc_type) = NULL;
|
||||
*(e->exc_tb) = NULL;
|
||||
*(e->exc_value) = PyObject_CallFunction(
|
||||
Tkinter_TclError, "s",
|
||||
Tcl_GetStringResult(e->self->interp));
|
||||
}
|
||||
else {
|
||||
*(e->res) = Tkapp_CallResult(e->self);
|
||||
}
|
||||
LEAVE_PYTHON
|
||||
done:
|
||||
/* Wake up calling thread. */
|
||||
Tcl_MutexLock(&call_mutex);
|
||||
Tcl_ConditionNotify(&e->done);
|
||||
Tcl_MutexUnlock(&call_mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This is the main entry point for calling a Tcl command.
|
||||
It supports three cases, with regard to threading:
|
||||
1. Tcl is not threaded: Must have the Tcl lock, then can invoke command in
|
||||
the context of the calling thread.
|
||||
2. Tcl is threaded, caller of the command is in the interpreter thread:
|
||||
Execute the command in the calling thread. Since the Tcl lock will
|
||||
not be used, we can merge that with case 1.
|
||||
3. Tcl is threaded, caller is in a different thread: Must queue an event to
|
||||
the interpreter thread. Allocation of Tcl objects needs to occur in the
|
||||
interpreter thread, so we ship the PyObject* args to the target thread,
|
||||
and perform processing there. */
|
||||
|
||||
static PyObject *
|
||||
Tkapp_Call(PyObject *_self, PyObject *args)
|
||||
{
|
||||
Tcl_Obj *objStore[ARGSZ];
|
||||
Tcl_Obj **objv = NULL;
|
||||
int objc, i;
|
||||
PyObject *res = NULL;
|
||||
TkappObject *self = (TkappObject*)_self;
|
||||
/* Could add TCL_EVAL_GLOBAL if wrapped by GlobalCall... */
|
||||
int flags = TCL_EVAL_DIRECT;
|
||||
|
||||
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
|
||||
/* We cannot call the command directly. Instead, we must
|
||||
marshal the parameters to the interpreter thread. */
|
||||
Tkapp_CallEvent *ev;
|
||||
PyObject *exc_type, *exc_value, *exc_tb;
|
||||
if (!self->dispatching) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"main thread is not in main loop");
|
||||
return NULL;
|
||||
}
|
||||
ev = (Tkapp_CallEvent*)ckalloc(sizeof(Tkapp_CallEvent));
|
||||
ev->ev.proc = (Tcl_EventProc*)Tkapp_CallProc;
|
||||
ev->self = self;
|
||||
ev->args = args;
|
||||
ev->res = &res;
|
||||
ev->exc_type = &exc_type;
|
||||
ev->exc_value = &exc_value;
|
||||
ev->exc_tb = &exc_tb;
|
||||
ev->done = (Tcl_Condition)0;
|
||||
|
||||
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->done, &call_mutex);
|
||||
|
||||
if (res == NULL) {
|
||||
if (exc_type)
|
||||
PyErr_Restore(exc_type, exc_value, exc_tb);
|
||||
else
|
||||
PyErr_SetObject(Tkinter_TclError, exc_value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
objv = Tkapp_CallArgs(args, objStore, &objc);
|
||||
if (!objv)
|
||||
return NULL;
|
||||
|
||||
ENTER_TCL
|
||||
|
||||
i = Tcl_EvalObjv(self->interp, objc, objv, flags);
|
||||
|
||||
ENTER_OVERLAP
|
||||
|
||||
if (i == TCL_ERROR)
|
||||
Tkinter_Error(_self);
|
||||
else
|
||||
res = Tkapp_CallResult(self);
|
||||
|
||||
LEAVE_OVERLAP_TCL
|
||||
|
||||
Tkapp_CallDeallocArgs(objv, objStore, objc);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -1003,6 +1199,8 @@ Tkapp_GlobalCall(PyObject *self, PyObject *args)
|
|||
char *cmd;
|
||||
PyObject *res = NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
cmd = Merge(args);
|
||||
if (cmd) {
|
||||
int err;
|
||||
|
@ -1030,6 +1228,8 @@ Tkapp_Eval(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "s:eval", &script))
|
||||
return NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
err = Tcl_Eval(Tkapp_Interp(self), script);
|
||||
ENTER_OVERLAP
|
||||
|
@ -1051,6 +1251,8 @@ Tkapp_GlobalEval(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "s:globaleval", &script))
|
||||
return NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
err = Tcl_GlobalEval(Tkapp_Interp(self), script);
|
||||
ENTER_OVERLAP
|
||||
|
@ -1072,6 +1274,8 @@ Tkapp_EvalFile(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "s:evalfile", &fileName))
|
||||
return NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
err = Tcl_EvalFile(Tkapp_Interp(self), fileName);
|
||||
ENTER_OVERLAP
|
||||
|
@ -1094,6 +1298,8 @@ Tkapp_Record(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "s", &script))
|
||||
return NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
err = Tcl_RecordAndEval(Tkapp_Interp(self), script, TCL_NO_EVAL);
|
||||
ENTER_OVERLAP
|
||||
|
@ -1112,6 +1318,8 @@ Tkapp_AddErrorInfo(PyObject *self, PyObject *args)
|
|||
|
||||
if (!PyArg_ParseTuple(args, "s:adderrorinfo", &msg))
|
||||
return NULL;
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
Tcl_AddErrorInfo(Tkapp_Interp(self), msg);
|
||||
LEAVE_TCL
|
||||
|
@ -1124,11 +1332,144 @@ Tkapp_AddErrorInfo(PyObject *self, PyObject *args)
|
|||
|
||||
/** Tcl Variable **/
|
||||
|
||||
TCL_DECLARE_MUTEX(var_mutex)
|
||||
|
||||
typedef const char* (*EventFunc1)(Tcl_Interp*, const char*, int);
|
||||
typedef const char* (*EventFunc2)(Tcl_Interp*, const char*, const char*, int);
|
||||
typedef const char* (*EventFunc3)(Tcl_Interp*, const char*, const char*, const char*, int);
|
||||
typedef struct VarEvent {
|
||||
Tcl_Event ev; /* must be first */
|
||||
TkappObject *self;
|
||||
char* arg1;
|
||||
char* arg2;
|
||||
char* arg3;
|
||||
int flags;
|
||||
EventFunc1 func1;
|
||||
EventFunc2 func2;
|
||||
EventFunc3 func3;
|
||||
PyObject **res;
|
||||
PyObject **exc;
|
||||
Tcl_Condition cond;
|
||||
int coderesult;
|
||||
} VarEvent;
|
||||
|
||||
static const char*
|
||||
var_perform(VarEvent *ev)
|
||||
{
|
||||
if (!ev->arg2 && !ev->arg2)
|
||||
return ev->func1(ev->self->interp, ev->arg1, ev->flags);
|
||||
if (!ev->arg3)
|
||||
return ev->func2(ev->self->interp, ev->arg1,
|
||||
ev->arg2, ev->flags);
|
||||
return ev->func3(ev->self->interp, ev->arg1, ev->arg2,
|
||||
ev->arg3, ev->flags);
|
||||
}
|
||||
|
||||
static void
|
||||
var_fill_result(VarEvent *ev, const char* res)
|
||||
{
|
||||
if (ev->coderesult) {
|
||||
if ((int)res != TCL_ERROR) {
|
||||
Py_INCREF(Py_None);
|
||||
*(ev->res) = Py_None;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (res) {
|
||||
*(ev->res) = PyString_FromString(res);
|
||||
return;
|
||||
}
|
||||
|
||||
*(ev->res) = NULL;
|
||||
*(ev->exc) = PyObject_CallFunction(
|
||||
Tkinter_TclError, "s",
|
||||
Tcl_GetStringResult(ev->self->interp));
|
||||
|
||||
}
|
||||
|
||||
static int
|
||||
var_proc(VarEvent* ev, int flags)
|
||||
{
|
||||
const char *result = var_perform(ev);
|
||||
ENTER_PYTHON
|
||||
var_fill_result(ev, result);
|
||||
Tcl_MutexLock(&var_mutex);
|
||||
Tcl_ConditionNotify(&ev->cond);
|
||||
Tcl_MutexUnlock(&var_mutex);
|
||||
LEAVE_PYTHON
|
||||
return 1;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
var_invoke(PyObject *_self, char* arg1, char* arg2, char* arg3, int flags,
|
||||
EventFunc1 func1, EventFunc2 func2, EventFunc3 func3,
|
||||
int coderesult)
|
||||
{
|
||||
VarEvent _ev;
|
||||
TkappObject *self = (TkappObject*)_self;
|
||||
VarEvent *ev = self->threaded ?
|
||||
(VarEvent*)ckalloc(sizeof(VarEvent)) : &_ev;
|
||||
PyObject *res, *exc;
|
||||
|
||||
ev->self = self;
|
||||
ev->arg1 = arg1;
|
||||
ev->arg2 = arg2;
|
||||
ev->arg3 = arg3;
|
||||
ev->flags = flags;
|
||||
ev->func1 = func1;
|
||||
ev->func2 = func2;
|
||||
ev->func3 = func3;
|
||||
ev->coderesult = coderesult;
|
||||
ev->res = &res;
|
||||
ev->exc = &exc;
|
||||
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
|
||||
/* The current thread is not the interpreter thread. Marshal
|
||||
the call to the interpreter thread, then wait for
|
||||
completion. */
|
||||
|
||||
if (!self->dispatching) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"main thread is not in main loop");
|
||||
return NULL;
|
||||
}
|
||||
ev->cond = NULL;
|
||||
ev->ev.proc = (Tcl_EventProc*)var_proc;
|
||||
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->cond, &var_mutex);
|
||||
}
|
||||
else {
|
||||
/* Tcl is not threaded, or this is the interpreter thread. To
|
||||
perform the call, we must hold the TCL lock. To receive the
|
||||
results, we must also hold the Python lock. */
|
||||
const char *result;
|
||||
ENTER_TCL
|
||||
result = var_perform(ev);
|
||||
ENTER_OVERLAP
|
||||
var_fill_result(ev, result);
|
||||
LEAVE_OVERLAP_TCL
|
||||
}
|
||||
if (!res) {
|
||||
PyErr_SetObject(Tkinter_TclError, exc);
|
||||
return NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
var_invoke2(PyObject *_self, char* arg1, char* arg2, char* arg3, int flags,
|
||||
int (*func1)(Tcl_Interp*, const char*, int),
|
||||
int (*func2)(Tcl_Interp*, const char*, const char*, int),
|
||||
int (*func3)(Tcl_Interp*, const char*, const char*, const char*, int))
|
||||
{
|
||||
return var_invoke(_self, arg1, arg2, arg3, flags,
|
||||
(EventFunc1)func1, (EventFunc2)func2,
|
||||
(EventFunc3)func3, 1);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
SetVar(PyObject *self, PyObject *args, int flags)
|
||||
{
|
||||
char *name1, *name2, *s;
|
||||
const char *ok;
|
||||
PyObject *res;
|
||||
PyObject *newValue;
|
||||
PyObject *tmp;
|
||||
|
||||
|
@ -1141,9 +1482,8 @@ SetVar(PyObject *self, PyObject *args, int flags)
|
|||
s = AsString(newValue, tmp);
|
||||
if (s == NULL)
|
||||
return NULL;
|
||||
ENTER_TCL
|
||||
ok = Tcl_SetVar(Tkapp_Interp(self), name1, s, flags);
|
||||
LEAVE_TCL
|
||||
res = var_invoke(self, name1, s, NULL, flags,
|
||||
NULL, Tcl_SetVar, NULL, 0);
|
||||
}
|
||||
else {
|
||||
PyErr_Clear();
|
||||
|
@ -1152,10 +1492,8 @@ SetVar(PyObject *self, PyObject *args, int flags)
|
|||
s = AsString(newValue, tmp);
|
||||
if (s == NULL)
|
||||
return NULL;
|
||||
ENTER_TCL
|
||||
ok = Tcl_SetVar2(Tkapp_Interp(self), name1, name2,
|
||||
s, flags);
|
||||
LEAVE_TCL
|
||||
res = var_invoke(self, name1, name2, s, flags,
|
||||
NULL, NULL, Tcl_SetVar2, 0);
|
||||
}
|
||||
else {
|
||||
Py_DECREF(tmp);
|
||||
|
@ -1164,9 +1502,10 @@ SetVar(PyObject *self, PyObject *args, int flags)
|
|||
}
|
||||
Py_DECREF(tmp);
|
||||
|
||||
if (!ok)
|
||||
return Tkinter_Error(self);
|
||||
if (!res)
|
||||
return NULL;
|
||||
|
||||
Py_DECREF(res);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
@ -1189,24 +1528,13 @@ static PyObject *
|
|||
GetVar(PyObject *self, PyObject *args, int flags)
|
||||
{
|
||||
char *name1, *name2=NULL;
|
||||
const char *s;
|
||||
PyObject *res = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s|s:getvar", &name1, &name2))
|
||||
return NULL;
|
||||
ENTER_TCL
|
||||
if (name2 == NULL)
|
||||
s = Tcl_GetVar(Tkapp_Interp(self), name1, flags);
|
||||
|
||||
else
|
||||
s = Tcl_GetVar2(Tkapp_Interp(self), name1, name2, flags);
|
||||
ENTER_OVERLAP
|
||||
|
||||
if (s == NULL)
|
||||
res = Tkinter_Error(self);
|
||||
else
|
||||
res = PyString_FromString(s);
|
||||
LEAVE_OVERLAP_TCL
|
||||
res = var_invoke(self, name1, name2, NULL, flags,
|
||||
Tcl_GetVar, Tcl_GetVar2, NULL, 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -1229,25 +1557,12 @@ UnsetVar(PyObject *self, PyObject *args, int flags)
|
|||
{
|
||||
char *name1, *name2=NULL;
|
||||
PyObject *res = NULL;
|
||||
int code;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s|s:unsetvar", &name1, &name2))
|
||||
return NULL;
|
||||
ENTER_TCL
|
||||
if (name2 == NULL)
|
||||
code = Tcl_UnsetVar(Tkapp_Interp(self), name1, flags);
|
||||
|
||||
else
|
||||
code = Tcl_UnsetVar2(Tkapp_Interp(self), name1, name2, flags);
|
||||
ENTER_OVERLAP
|
||||
|
||||
if (code == TCL_ERROR)
|
||||
res = Tkinter_Error(self);
|
||||
else {
|
||||
Py_INCREF(Py_None);
|
||||
res = Py_None;
|
||||
}
|
||||
LEAVE_OVERLAP_TCL
|
||||
res = var_invoke2(self, name1, name2, NULL, flags,
|
||||
Tcl_UnsetVar, Tcl_UnsetVar2, NULL);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -1336,6 +1651,9 @@ Tkapp_ExprString(PyObject *self, PyObject *args)
|
|||
|
||||
if (!PyArg_ParseTuple(args, "s:exprstring", &s))
|
||||
return NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
retval = Tcl_ExprString(Tkapp_Interp(self), s);
|
||||
ENTER_OVERLAP
|
||||
|
@ -1357,6 +1675,9 @@ Tkapp_ExprLong(PyObject *self, PyObject *args)
|
|||
|
||||
if (!PyArg_ParseTuple(args, "s:exprlong", &s))
|
||||
return NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
retval = Tcl_ExprLong(Tkapp_Interp(self), s, &v);
|
||||
ENTER_OVERLAP
|
||||
|
@ -1378,6 +1699,7 @@ Tkapp_ExprDouble(PyObject *self, PyObject *args)
|
|||
|
||||
if (!PyArg_ParseTuple(args, "s:exprdouble", &s))
|
||||
return NULL;
|
||||
CHECK_TCL_APPARTMENT;
|
||||
PyFPE_START_PROTECT("Tkapp_ExprDouble", return 0)
|
||||
ENTER_TCL
|
||||
retval = Tcl_ExprDouble(Tkapp_Interp(self), s, &v);
|
||||
|
@ -1401,6 +1723,7 @@ Tkapp_ExprBoolean(PyObject *self, PyObject *args)
|
|||
|
||||
if (!PyArg_ParseTuple(args, "s:exprboolean", &s))
|
||||
return NULL;
|
||||
CHECK_TCL_APPARTMENT;
|
||||
ENTER_TCL
|
||||
retval = Tcl_ExprBoolean(Tkapp_Interp(self), s, &v);
|
||||
ENTER_OVERLAP
|
||||
|
@ -1575,13 +1898,42 @@ PythonCmdDelete(ClientData clientData)
|
|||
|
||||
|
||||
|
||||
static PyObject *
|
||||
Tkapp_CreateCommand(PyObject *self, PyObject *args)
|
||||
|
||||
TCL_DECLARE_MUTEX(command_mutex)
|
||||
|
||||
typedef struct CommandEvent{
|
||||
Tcl_Event ev;
|
||||
Tcl_Interp* interp;
|
||||
char *name;
|
||||
int create;
|
||||
int *status;
|
||||
ClientData *data;
|
||||
Tcl_Condition done;
|
||||
} CommandEvent;
|
||||
|
||||
static int
|
||||
Tkapp_CommandProc(CommandEvent *ev, int flags)
|
||||
{
|
||||
if (ev->create)
|
||||
*ev->status = Tcl_CreateCommand(
|
||||
ev->interp, ev->name, PythonCmd,
|
||||
ev->data, PythonCmdDelete) == NULL;
|
||||
else
|
||||
*ev->status = Tcl_DeleteCommand(ev->interp, ev->name);
|
||||
Tcl_MutexLock(&command_mutex);
|
||||
Tcl_ConditionNotify(&ev->done);
|
||||
Tcl_MutexUnlock(&command_mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
Tkapp_CreateCommand(PyObject *_self, PyObject *args)
|
||||
{
|
||||
TkappObject *self = (TkappObject*)_self;
|
||||
PythonCmd_ClientData *data;
|
||||
char *cmdName;
|
||||
PyObject *func;
|
||||
Tcl_Command err;
|
||||
int err;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "sO:createcommand", &cmdName, &func))
|
||||
return NULL;
|
||||
|
@ -1590,19 +1942,40 @@ Tkapp_CreateCommand(PyObject *self, PyObject *args)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (self->threaded && self->thread_id != Tcl_GetCurrentThread() &&
|
||||
!self->dispatching) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"main thread is not in main loop");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data = PyMem_NEW(PythonCmd_ClientData, 1);
|
||||
if (!data)
|
||||
return NULL;
|
||||
Py_XINCREF(self);
|
||||
Py_XINCREF(func);
|
||||
data->self = self;
|
||||
data->self = _self;
|
||||
data->func = func;
|
||||
|
||||
ENTER_TCL
|
||||
err = Tcl_CreateCommand(Tkapp_Interp(self), cmdName, PythonCmd,
|
||||
(ClientData)data, PythonCmdDelete);
|
||||
LEAVE_TCL
|
||||
if (err == NULL) {
|
||||
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
|
||||
CommandEvent *ev = (CommandEvent*)ckalloc(sizeof(CommandEvent));
|
||||
ev->ev.proc = (Tcl_EventProc*)Tkapp_CommandProc;
|
||||
ev->interp = self->interp;
|
||||
ev->create = 1;
|
||||
ev->name = cmdName;
|
||||
ev->data = (ClientData)data;
|
||||
ev->status = &err;
|
||||
ev->done = NULL;
|
||||
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->done, &command_mutex);
|
||||
}
|
||||
else {
|
||||
ENTER_TCL
|
||||
err = Tcl_CreateCommand(
|
||||
Tkapp_Interp(self), cmdName, PythonCmd,
|
||||
(ClientData)data, PythonCmdDelete) == NULL;
|
||||
LEAVE_TCL
|
||||
}
|
||||
if (err) {
|
||||
PyErr_SetString(Tkinter_TclError, "can't create Tcl command");
|
||||
PyMem_DEL(data);
|
||||
return NULL;
|
||||
|
@ -1615,16 +1988,31 @@ Tkapp_CreateCommand(PyObject *self, PyObject *args)
|
|||
|
||||
|
||||
static PyObject *
|
||||
Tkapp_DeleteCommand(PyObject *self, PyObject *args)
|
||||
Tkapp_DeleteCommand(PyObject *_self, PyObject *args)
|
||||
{
|
||||
TkappObject *self = (TkappObject*)_self;
|
||||
char *cmdName;
|
||||
int err;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s:deletecommand", &cmdName))
|
||||
return NULL;
|
||||
ENTER_TCL
|
||||
err = Tcl_DeleteCommand(Tkapp_Interp(self), cmdName);
|
||||
LEAVE_TCL
|
||||
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
|
||||
CommandEvent *ev;
|
||||
ev = (CommandEvent*)ckalloc(sizeof(CommandEvent));
|
||||
ev->ev.proc = (Tcl_EventProc*)Tkapp_CommandProc;
|
||||
ev->interp = self->interp;
|
||||
ev->create = 0;
|
||||
ev->name = cmdName;
|
||||
ev->status = &err;
|
||||
ev->done = NULL;
|
||||
Tkapp_ThreadSend(self, (Tcl_Event*)ev, &ev->done,
|
||||
&command_mutex);
|
||||
}
|
||||
else {
|
||||
ENTER_TCL
|
||||
err = Tcl_DeleteCommand(self->interp, cmdName);
|
||||
LEAVE_TCL
|
||||
}
|
||||
if (err == -1) {
|
||||
PyErr_SetString(Tkinter_TclError, "can't delete Tcl command");
|
||||
return NULL;
|
||||
|
@ -1715,6 +2103,7 @@ Tkapp_CreateFileHandler(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "OiO:createfilehandler",
|
||||
&file, &mask, &func))
|
||||
return NULL;
|
||||
CHECK_TCL_APPARTMENT;
|
||||
tfile = PyObject_AsFileDescriptor(file);
|
||||
if (tfile < 0)
|
||||
return NULL;
|
||||
|
@ -1743,6 +2132,7 @@ Tkapp_DeleteFileHandler(PyObject *self, PyObject *args)
|
|||
|
||||
if (!PyArg_ParseTuple(args, "O:deletefilehandler", &file))
|
||||
return NULL;
|
||||
CHECK_TCL_APPARTMENT;
|
||||
tfile = PyObject_AsFileDescriptor(file);
|
||||
if (tfile < 0)
|
||||
return NULL;
|
||||
|
@ -1918,9 +2308,10 @@ Tkapp_CreateTimerHandler(PyObject *self, PyObject *args)
|
|||
/** Event Loop **/
|
||||
|
||||
static PyObject *
|
||||
Tkapp_MainLoop(PyObject *self, PyObject *args)
|
||||
Tkapp_MainLoop(PyObject *_self, PyObject *args)
|
||||
{
|
||||
int threshold = 0;
|
||||
TkappObject *self = (TkappObject*)_self;
|
||||
#ifdef WITH_THREAD
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
#endif
|
||||
|
@ -1928,7 +2319,10 @@ Tkapp_MainLoop(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "|i:mainloop", &threshold))
|
||||
return NULL;
|
||||
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
quitMainLoop = 0;
|
||||
self->dispatching = 1;
|
||||
while (Tk_GetNumMainWindows() > threshold &&
|
||||
!quitMainLoop &&
|
||||
!errorInCmd)
|
||||
|
@ -1936,24 +2330,35 @@ Tkapp_MainLoop(PyObject *self, PyObject *args)
|
|||
int result;
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
PyThread_acquire_lock(tcl_lock, 1);
|
||||
tcl_tstate = tstate;
|
||||
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
|
||||
tcl_tstate = NULL;
|
||||
PyThread_release_lock(tcl_lock);
|
||||
if (result == 0)
|
||||
Sleep(20);
|
||||
Py_END_ALLOW_THREADS
|
||||
if (self->threaded) {
|
||||
/* Allow other Python threads to run. */
|
||||
ENTER_TCL
|
||||
result = Tcl_DoOneEvent(0);
|
||||
LEAVE_TCL
|
||||
}
|
||||
else {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
|
||||
tcl_tstate = tstate;
|
||||
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
|
||||
tcl_tstate = NULL;
|
||||
if(tcl_lock)PyThread_release_lock(tcl_lock);
|
||||
if (result == 0)
|
||||
Sleep(20);
|
||||
Py_END_ALLOW_THREADS
|
||||
}
|
||||
#else
|
||||
result = Tcl_DoOneEvent(0);
|
||||
#endif
|
||||
|
||||
if (PyErr_CheckSignals() != 0)
|
||||
if (PyErr_CheckSignals() != 0) {
|
||||
self->dispatching = 0;
|
||||
return NULL;
|
||||
}
|
||||
if (result < 0)
|
||||
break;
|
||||
}
|
||||
self->dispatching = 0;
|
||||
quitMainLoop = 0;
|
||||
|
||||
if (errorInCmd) {
|
||||
|
@ -1974,6 +2379,7 @@ Tkapp_DoOneEvent(PyObject *self, PyObject *args)
|
|||
|
||||
if (!PyArg_ParseTuple(args, "|i:dooneevent", &flags))
|
||||
return NULL;
|
||||
CHECK_TCL_APPARTMENT;
|
||||
|
||||
ENTER_TCL
|
||||
rv = Tcl_DoOneEvent(flags);
|
||||
|
@ -2068,6 +2474,7 @@ static PyMethodDef Tkapp_methods[] =
|
|||
static void
|
||||
Tkapp_Dealloc(PyObject *self)
|
||||
{
|
||||
//CHECK_TCL_APPARTMENT;
|
||||
ENTER_TCL
|
||||
Tcl_DeleteInterp(Tkapp_Interp(self));
|
||||
LEAVE_TCL
|
||||
|
@ -2292,13 +2699,13 @@ EventHook(void)
|
|||
#endif
|
||||
#if defined(WITH_THREAD) || defined(MS_WINDOWS)
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
PyThread_acquire_lock(tcl_lock, 1);
|
||||
if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1);
|
||||
tcl_tstate = event_tstate;
|
||||
|
||||
result = Tcl_DoOneEvent(TCL_DONT_WAIT);
|
||||
|
||||
tcl_tstate = NULL;
|
||||
PyThread_release_lock(tcl_lock);
|
||||
if(tcl_lock)PyThread_release_lock(tcl_lock);
|
||||
if (result == 0)
|
||||
Sleep(20);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
|
Loading…
Reference in New Issue