8000 [3.12] gh-71592: Add ability to trace Tcl commands executed by Tkinte… · python/cpython@bee1c32 · GitHub
[go: up one dir, main page]

Skip to content

Commit bee1c32

Browse files
[3.12] gh-71592: Add ability to trace Tcl commands executed by Tkinter (GH-118291) (GH-118662)
This is an experimental feature, for internal use. Setting tkinter._debug = True before creating the root window enables printing every executed Tcl command (or a Tcl command equivalent to the used Tcl C API). This will help to convert a Tkinter example into Tcl script to check whether the issue is caused by Tkinter or exists in the underlying Tcl/Tk library. (cherry picked from commit 1ff626e) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent f6c2b04 commit bee1c32

File tree

3 files changed

+191
-6
lines changed

3 files changed

+191
-6
lines changed

Lib/tkinter/__init__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import re
4242

4343
wantobjects = 1
44+
_debug = False # set to True to print executed Tcl/Tk commands
4445

4546
TkVersion = float(_tkinter.TK_VERSION)
4647
TclVersion = float(_tkinter.TCL_VERSION)
@@ -69,7 +70,10 @@ def _stringify(value):
6970
else:
7071
value = '{%s}' % _join(value)
7172
else:
72-
value = str(value)
73+
if isinstance(value, bytes):
74+
value = str(value, 'latin1')
75+
else:
76+
value = str(value)
7377
if not value:
7478
value = '{}'
7579
elif _magic_re.search(value):
@@ -411,7 +415,6 @@ def __del__(self):
411415
self._tk.globalunsetvar(self._name)
412416
if self._tclCommands is not None:
413417
for name in self._tclCommands:
414-
#print '- Tkinter: deleted command', name
415418
self._tk.deletecommand(name)
416419
self._tclCommands = None
417420

@@ -683,15 +686,13 @@ def destroy(self):
683686
this widget in the Tcl interpreter."""
684687
if self._tclCommands is not None:
685688
for name in self._tclCommands:
686-
#print '- Tkinter: deleted command', name
687689
self.tk.deletecommand(name)
688690
self._tclCommands = None
689691

690692
def deletecommand(self, name):
691693
"""Internal function.
692694
693695
Delete the Tcl command provided in NAME."""
694-
#print '- Tkinter: deleted command', name
695696
self.tk.deletecommand(name)
696697
try:
697698
self._tclCommands.remove(name)
@@ -2343,6 +2344,8 @@ def __init__(self, screenName=None, baseName=None, className='Tk',
23432344
baseName = baseName + ext
23442345
interactive = False
23452346
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
2347+
if _debug:
2348+
self.tk.settrace(_print_command)
23462349
if useTk:
23472350
self._loadtk()
23482351
if not sys.flags.ignore_environment:
@@ -2429,6 +2432,14 @@ def __getattr__(self, attr):
24292432
"Delegate attribute access to the interpreter object"
24302433
return getattr(self.tk, attr)
24312434

2435+
2436+
def _print_command(cmd, *, file=sys.stderr):
2437+
# Print executed Tcl/Tk commands.
2438+
assert isinstance(cmd, tuple)
2439+
cmd = _join(cmd)
2440+
print(cmd, file=file)
2441+
2442+
24322443
# Ideally, the classes Pack, Place and Grid disappear, the
24332444
# pack/place/grid methods are defined on the Widget class, and
24342445
# everybody uses w.pack_whatever(...) instead of Pack.whatever(w,

Modules/_tkinter.c

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ typedef struct {
309309
int threaded; /* True if tcl_platform[threaded] */
310310
Tcl_ThreadId thread_id;
311311
int dispatching;
312+
PyObject *trace;
312313
/* We cannot include tclInt.h, as this is internal.
313314
So we cache interesting types here. */
314315
const Tcl_ObjType *OldBooleanType;
@@ -574,6 +575,7 @@ Tkapp_New(const char *screenName, const char *className,
574575
TCL_GLOBAL_ONLY) != NULL;
575576
v->thread_id = Tcl_GetCurrentThread();
576577
v->dispatching = 0;
578+
v->trace = NULL;
577579

578580
#ifndef TCL_THREADS
579581
if (v->threaded) {
@@ -1316,6 +1318,29 @@ Tkapp_ObjectResult(TkappObject *self)
13161318
return res;
13171319
}
13181320

1321+
static int
1322+
Tkapp_Trace(TkappObject *self, PyObject *args)
1323+
{
1324+
if (args == NULL) {
1325+
return 0;
1326+
}
1327+
if (self->trace) {
1328+
PyObject *res = PyObject_CallObject(self->trace, args);
1329+
if (res == NULL) {
1330+
Py_DECREF(args);
1331+
return 0;
1332+
}
1333+
Py_DECREF(res);
1334+
}
1335+
Py_DECREF(args);
1336+
return 1;
1337+
}
1338+
1339+
#define TRACE(_self, ARGS) do { \
1340+
if ((_self)->trace && !Tkapp_Trace((_self), Py_BuildValue ARGS)) { \
1341+
return NULL; \
1342+
} \
1343+
} while (0)
13191344

13201345
/* Tkapp_CallProc is the event procedure that is executed in the context of
13211346
the Tcl interpreter thread. Initially, it holds the Tcl lock, and doesn't
@@ -1329,7 +1354,12 @@ Tkapp_CallProc(Tkapp_CallEvent *e, int flags)
13291354
int objc;
13301355
int i;
13311356
ENTER_PYTHON
1332-
objv = Tkapp_CallArgs(e->args, objStore, &objc);
1357+
if (e->self->trace && !Tkapp_Trace(e->self, PyTuple_Pack(1, e->args))) {
1358+
objv = NULL;
1359+
}
1360+
else {
1361+
objv = Tkapp_CallArgs(e->args, objStore, &objc);
1362+
}
13331363
if (!objv) {
13341364
*(e->exc) = PyErr_GetRaisedException();
13351365
*(e->res) = NULL;
@@ -1422,6 +1452,7 @@ Tkapp_Call(PyObject *selfptr, PyObject *args)
14221452
}
14231453
else
14241454
{
1455+
TRACE(self, ("(O)", args));
14251456

14261457
objv = Tkapp_CallArgs(args, objStore, &objc);
14271458
if (!objv)
@@ -1464,6 +1495,8 @@ _tkinter_tkapp_eval_impl(TkappObject *self, const char *script)
14641495
CHECK_STRING_LENGTH(script);
14651496
CHECK_TCL_APPARTMENT;
14661497

1498+
TRACE(self, ("((ss))", "eval", script));
1499+
14671500
ENTER_TCL
14681501
err = Tcl_Eval(Tkapp_Interp(self), script);
14691502
ENTER_OVERLAP
@@ -1493,6 +1526,8 @@ _tkinter_tkapp_evalfile_impl(TkappObject *self, const char *fileName)
14931526
CHECK_STRING_LENGTH(fileName);
14941527
CHECK_TCL_APPARTMENT;
14951528

1529+
TRACE(self, ("((ss))", "source", fileName));
1530+
14961531
ENTER_TCL
14971532
err = Tcl_EvalFile(Tkapp_Interp(self), fileName);
14981533
ENTER_OVERLAP
@@ -1522,6 +1557,8 @@ _tkinter_tkapp_record_impl(TkappObject *self, const char *script)
15221557
CHECK_STRING_LENGTH(script);
15231558
CHECK_TCL_APPARTMENT;
15241559

1560+
TRACE(self, ("((ssss))", "history", "add", script, "exec"));
1561+
15251562
ENTER_TCL
15261563
err = Tcl_RecordAndEval(Tkapp_Interp(self), script, TCL_NO_EVAL);
15271564
ENTER_OVERLAP
@@ -1710,6 +1747,15 @@ SetVar(TkappObject *self, PyObject *args, int flags)
17101747
newval = AsObj(newValue);
17111748
if (newval == NULL)
17121749
return NULL;
1750+
1751+
if (flags & TCL_GLOBAL_ONLY) {
1752+
TRACE((TkappObject *)self, ("((ssssO))", "uplevel", "#0", "set",
1753+
name1, newValue));
1754+
}
1755+
else {
1756+
TRACE((TkappObject *)self, ("((ssO))", "set", name1, newValue));
1757+
}
1758+
17131759
ENTER_TCL
17141760
ok = Tcl_SetVar2Ex(Tkapp_Interp(self), name1, NULL,
17151761
newval, flags);
@@ -1727,8 +1773,22 @@ SetVar(TkappObject *self, PyObject *args, int flags)
17271773
return NULL;
17281774
CHECK_STRING_LENGTH(name1);
17291775
CHECK_STRING_LENGTH(name2);
1776+
17301777
/* XXX must hold tcl lock already??? */
17311778
newval = AsObj(newValue);
1779+
if (((TkappObject *)self)->trace) {
1780+
if (flags & TCL_GLOBAL_ONLY) {
1781+
TRACE((TkappObject *)self, ("((sssNO))", "uplevel", "#0", "set",
1782+
PyUnicode_FromFormat("%s(%s)", name1, name2),
1783+
newValue));
1784+
}
1785+
else {
1786+
TRACE((TkappObject *)self, ("((sNO))", "set",
1787+
PyUnicode_FromFormat("%s(%s)", name1, name2),
1788+
newValue));
1789+
}
1790+
}
1791+
17321792
ENTER_TCL
17331793
ok = Tcl_SetVar2Ex(Tkapp_Interp(self), name1, name2, newval, flags);
17341794
ENTER_OVERLAP
@@ -1815,6 +1875,28 @@ UnsetVar(TkappObject *self, PyObject *args, int flags)
18151875

18161876
CHECK_STRING_LENGTH(name1);
18171877
CHECK_STRING_LENGTH(name2);
1878+
1879+
if (((TkappObject *)self)->trace) {
1880+
if (flags & TCL_GLOBAL_ONLY) {
1881+
if (name2) {
1882+
TRACE((TkappObject *)self, ("((sssN))", "uplevel", "#0", "unset",
1883+
PyUnicode_FromFormat("%s(%s)", name1, name2)));
1884+
}
1885+
else {
1886+
TRACE((TkappObject *)self, ("((ssss))", "uplevel", "#0", "unset", name1));
1887+
}
1888+
}
1889+
else {
1890+
if (name2) {
1891+
TRACE((TkappObject *)self, ("((sN))", "unset",
1892+
PyUnicode_FromFormat("%s(%s)", name1, name2)));
1893+
}
1894+
else {
1895+
TRACE((TkappObject *)self, ("((ss))", "unset", name1));
1896+
}
1897+
}
1898+
}
1899+
18181900
ENTER_TCL
18191901
code = Tcl_UnsetVar2(Tkapp_Interp(self), name1, name2, flags);
18201902
ENTER_OVERLAP
@@ -1981,6 +2063,8 @@ _tkinter_tkapp_exprstring_impl(TkappObject *self, const char *s)
19812063
CHECK_STRING_LENGTH(s);
19822064
CHECK_TCL_APPARTMENT;
19832065

2066+
TRACE(self, ("((ss))", "expr", s));
2067+
19842068
ENTER_TCL
19852069
retval = Tcl_ExprString(Tkapp_Interp(self), s);
19862070
ENTER_OVERLAP
@@ -2011,6 +2095,8 @@ _tkinter_tkapp_exprlong_impl(TkappObject *self, const char *s)
20112095
CHECK_STRING_LENGTH(s);
20122096
CHECK_TCL_APPARTMENT;
20132097

2098+
TRACE(self, ("((ss))", "expr", s));
2099+
20142100
ENTER_TCL
20152101
retval = Tcl_ExprLong(Tkapp_Interp(self), s, &v);
20162102
ENTER_OVERLAP
@@ -2040,6 +2126,9 @@ _tkinter_tkapp_exprdouble_impl(TkappObject *self, const char *s)
20402126

20412127
CHECK_STRING_LENGTH(s);
20422128
CHECK_TCL_APPARTMENT;
2129+
2130+
TRACE(self, ("((ss))", "expr", s));
2131+
20432132
ENTER_TCL
20442133
retval = Tcl_ExprDouble(Tkapp_Interp(self), s, &v);
20452134
ENTER_OVERLAP
@@ -2069,6 +2158,9 @@ _tkinter_tkapp_exprboolean_impl(TkappObject *self, const char *s)
20692158

20702159
CHECK_STRING_LENGTH(s);
20712160
CHECK_TCL_APPARTMENT;
2161+
2162+
TRACE(self, ("((ss))", "expr", s));
2163+
20722164
ENTER_TCL
20732165
retval = Tcl_ExprBoolean(Tkapp_Interp(self), s, &v);
20742166
ENTER_OVERLAP
@@ -2293,6 +2385,8 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
22932385
!WaitForMainloop(self))
22942386
return NULL;
22952387

2388+
TRACE(self, ("((ss()O))", "proc", name, func));
2389+
22962390
data = PyMem_NEW(PythonCmd_ClientData, 1);
22972391
if (!data)
22982392
return PyErr_NoMemory();
@@ -2351,6 +2445,8 @@ _tkinter_tkapp_deletecommand_impl(TkappObject *self, const char *name)
23512445

23522446
CHECK_STRING_LENGTH(name);
23532447

2448+
TRACE(self, ("((sss))", "rename", name, ""));
2449+
23542450
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
23552451
Tcl_Condition cond = NULL;
23562452
CommandEvent *ev;
@@ -2476,6 +2572,8 @@ _tkinter_tkapp_createfilehandler_impl(TkappObject *self, PyObject *file,
24762572
return NULL;
24772573
}
24782574

2575+
TRACE(self, ("((ssiiO))", "#", "createfilehandler", tfile, mask, func));
2576+
24792577
data = NewFHCD(func, file, tfile);
24802578
if (data == NULL)
24812579
return NULL;
@@ -2507,6 +2605,8 @@ _tkinter_tkapp_deletefilehandler(TkappObject *self, PyObject *file)
25072605
if (tfile < 0)
25082606
return NULL;
25092607

2608+
TRACE(self, ("((ssi))", "#", "deletefilehandler", tfile));
2609+
25102610
DeleteFHCD(tfile);
25112611

25122612
/* Ought to check for null Tcl_File object... */
@@ -2541,6 +2641,7 @@ _tkinter_tktimertoken_deletetimerhandler_impl(TkttObject *self)
25412641
PyObject *func = v->func;
25422642

25432643
if (v->token != NULL) {
2644+
/* TRACE(...) */
25442645
Tcl_DeleteTimerHandler(v->token);
25452646
v->token = NULL;
25462647
}
@@ -2643,6 +2744,8 @@ _tkinter_tkapp_createtimerhandler_impl(TkappObject *self, int milliseconds,
26432744

26442745
CHECK_TCL_APPARTMENT;
26452746

2747+
TRACE(self, ("((siO))", "after", milliseconds, func));
2748+
26462749
v = Tktt_New(func);
26472750
if (v) {
26482751
v->token = Tcl_CreateTimerHandler(milliseconds, TimerHandler,
@@ -2810,6 +2913,47 @@ Tkapp_WantObjects(PyObject *self, PyObject *args)
28102913
Py_RETURN_NONE;
28112914
}
28122915

2916+
/*[clinic input]
2917+
_tkinter.tkapp.settrace
2918+
2919+
func: object
2920+
/
2921+
2922+
Set the tracing function.
2923+
[clinic start generated code]*/
2924+
2925+
static PyObject *
2926+
_tkinter_tkapp_settrace(TkappObject *self, PyObject *func)
2927+
/*[clinic end generated code: output=847f6ebdf46e84fa input=31b260d46d3d018a]*/
2928+
{
2929+
if (func == Py_None) {
2930+
func = NULL;
2931+
}
2932+
else {
2933+
Py_INCREF(func);
2934+
}
2935+
Py_XSETREF(self->trace, func);
2936+
Py_RETURN_NONE;
2937+
}
2938+
2939+
/*[clinic input]
2940+
_tkinter.tkapp.gettrace
2941+
2942+
Get the tracing function.
2943+
[clinic start generated code]*/
2944+
2945+
static PyObject *
2946+
_tkinter_tkapp_gettrace_impl(TkappObject *self)
2947+
/*[clinic end generated code: output=d4e2ba7d63e77bb5 input=ac2aea5be74e8c4c]*/
2948+
{
2949+
PyObject *func = self->trace;
2950+
if (!func) {
2951+
func = Py_None;
2952+
}
2953+
Py_INCREF(func);
2954+
return func;
2955+
}
2956+
28132957
/*[clinic input]
28142958
_tkinter.tkapp.willdispatch
28152959
@@ -2835,6 +2979,7 @@ Tkapp_Dealloc(PyObject *self)
28352979
ENTER_TCL
28362980
Tcl_DeleteInterp(Tkapp_Interp(self));
28372981
LEAVE_TCL
2982+
Py_XDECREF(((TkappObject *)self)->trace);
28382983
PyObject_Free(self);
28392984
Py_DECREF(tp);
28402985
DisableEventHook();
@@ -3045,6 +3190,8 @@ static PyMethodDef Tkapp_methods[] =
30453190
{
30463191
_TKINTER_TKAPP_WILLDISPATCH_METHODDEF
30473192
{"wantobjects", Tkapp_WantObjects, METH_VARARGS},
3193+
_TKINTER_TKAPP_SETTRACE_METHODDEF
3194+
_TKINTER_TKAPP_GETTRACE_METHODDEF
30483195
{"call", Tkapp_Call, METH_VARARGS},
30493196
_TKINTER_TKAPP_EVAL_METHODDEF
30503197
_TKINTER_TKAPP_EVALFILE_METHODDEF

0 commit comments

Comments
 (0)
0