| /*********************************************************** | |
| Copyright (C) 1994 Steen Lumholt. | |
| All Rights Reserved | |
| ******************************************************************/ | |
| /* _tkinter.c -- Interface to libtk.a and libtcl.a. */ | |
| /* TCL/TK VERSION INFO: | |
| Only Tcl/Tk 8.5.12 and later are supported. Older versions are not | |
| supported. Use Python 3.10 or older if you cannot upgrade your | |
| Tcl/Tk libraries. | |
| */ | |
| /* XXX Further speed-up ideas, involving Tcl 8.0 features: | |
| - Register a new Tcl type, "Python callable", which can be called more | |
| efficiently and passed to Tcl_EvalObj() directly (if this is possible). | |
| */ | |
| #ifndef Py_BUILD_CORE_BUILTIN | |
| # define Py_BUILD_CORE_MODULE 1 | |
| #endif | |
| #include "Python.h" | |
| #ifdef MS_WINDOWS | |
| # include "pycore_fileutils.h" // _Py_stat() | |
| #endif | |
| #include "pycore_long.h" // _PyLong_IsNegative() | |
| #include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8String | |
| #include <signal.h> // SIGINT | |
| #ifdef MS_WINDOWS | |
| # include <windows.h> | |
| #endif | |
| #define CHECK_SIZE(size, elemsize) \ | |
| ((size_t)(size) <= Py_MIN((size_t)INT_MAX, UINT_MAX / (size_t)(elemsize))) | |
| /* 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> | |
| #else | |
| # include <tcl.h> | |
| # include <tk.h> | |
| #endif | |
| #if defined(MS_WINDOWS) && TK_MAJOR_VERSION >= 9 | |
| # include <tkPlatDecls.h> | |
| #endif | |
| #include "tkinter.h" | |
| #if TK_HEX_VERSION < 0x0805020c | |
| #error "Tk older than 8.5.12 not supported" | |
| #endif | |
| #ifndef TCL_WITH_EXTERNAL_TOMMATH | |
| #define TCL_NO_TOMMATH_H | |
| #endif | |
| #include <tclTomMath.h> | |
| #if defined(TCL_WITH_EXTERNAL_TOMMATH) || (TK_HEX_VERSION >= 0x08070000) | |
| #define USE_DEPRECATED_TOMMATH_API 0 | |
| #else | |
| #define USE_DEPRECATED_TOMMATH_API 1 | |
| #endif | |
| // As suggested by https://core.tcl-lang.org/tcl/wiki?name=Migrating+C+extensions+to+Tcl+9 | |
| #ifndef TCL_SIZE_MAX | |
| typedef int Tcl_Size; | |
| #define TCL_SIZE_MAX INT_MAX | |
| #endif | |
| #if !(defined(MS_WINDOWS) || defined(__CYGWIN__)) | |
| #define HAVE_CREATEFILEHANDLER | |
| #endif | |
| #ifdef HAVE_CREATEFILEHANDLER | |
| /* If Tcl can wait for a Unix file descriptor, define the EventHook() routine | |
| which uses this to handle Tcl events while the user is typing commands. */ | |
| #define WAIT_FOR_STDIN | |
| #endif /* HAVE_CREATEFILEHANDLER */ | |
| /* Use OS native encoding for converting between Python strings and | |
| Tcl objects. | |
| On Windows use UTF-16 (or UTF-32 for 32-bit Tcl_UniChar) with the | |
| "surrogatepass" error handler for converting to/from Tcl Unicode objects. | |
| On Linux use UTF-8 with the "surrogateescape" error handler for converting | |
| to/from Tcl String objects. */ | |
| #ifdef MS_WINDOWS | |
| #define USE_TCL_UNICODE 1 | |
| #else | |
| #define USE_TCL_UNICODE 0 | |
| #endif | |
| #if PY_LITTLE_ENDIAN | |
| #define NATIVE_BYTEORDER -1 | |
| #else | |
| #define NATIVE_BYTEORDER 1 | |
| #endif | |
| #ifdef MS_WINDOWS | |
| #include <conio.h> | |
| #define WAIT_FOR_STDIN | |
| static PyObject * | |
| _get_tcl_lib_path(void) | |
| { | |
| static PyObject *tcl_library_path = NULL; | |
| static int already_checked = 0; | |
| if (already_checked == 0) { | |
| struct stat stat_buf; | |
| int stat_return_value; | |
| PyObject *prefix; | |
| (void) PySys_GetOptionalAttrString("base_prefix", &prefix); | |
| if (prefix == NULL) { | |
| return NULL; | |
| } | |
| /* Check expected location for an installed Python first */ | |
| PyObject* tmp_tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION); | |
| if (tmp_tcl_library_path == NULL) { | |
| Py_DECREF(prefix); | |
| return NULL; | |
| } | |
| tcl_library_path = PyUnicode_Concat(prefix, tmp_tcl_library_path); | |
| Py_DECREF(tmp_tcl_library_path); | |
| Py_DECREF(prefix); | |
| if (tcl_library_path == NULL) { | |
| return NULL; | |
| } | |
| stat_return_value = _Py_stat(tcl_library_path, &stat_buf); | |
| if (stat_return_value == -2) { | |
| Py_DECREF(tcl_library_path); | |
| return NULL; | |
| } | |
| if (stat_return_value == -1) { | |
| /* install location doesn't exist, reset errno and see if | |
| we're a repository build */ | |
| errno = 0; | |
| #ifdef Py_TCLTK_DIR | |
| tcl_library_path = PyUnicode_FromString( | |
| Py_TCLTK_DIR "\\lib\\tcl" TCL_VERSION); | |
| if (tcl_library_path == NULL) { | |
| return NULL; | |
| } | |
| stat_return_value = _Py_stat(tcl_library_path, &stat_buf); | |
| if (stat_return_value == -2) { | |
| Py_DECREF(tcl_library_path); | |
| return NULL; | |
| } | |
| if (stat_return_value == -1) { | |
| /* tcltkDir for a repository build doesn't exist either, | |
| reset errno and leave Tcl to its own devices */ | |
| errno = 0; | |
| Py_CLEAR(tcl_library_path); | |
| } | |
| #else | |
| Py_CLEAR(tcl_library_path); | |
| #endif | |
| } | |
| already_checked = 1; | |
| } | |
| return tcl_library_path; | |
| } | |
| #endif /* MS_WINDOWS */ | |
| #if defined(MS_WINDOWS) && TK_MAJOR_VERSION >= 9 | |
| static void | |
| mount_tk_dll_zip(void) | |
| { | |
| HINSTANCE tk_module = Tk_GetHINSTANCE(); | |
| wchar_t *tk_path = NULL; | |
| DWORD path_len = 0; | |
| for (DWORD buffer_len = 256; | |
| tk_path == NULL && buffer_len < (1024 * 1024); | |
| buffer_len *= 2) | |
| { | |
| tk_path = (wchar_t *)PyMem_RawMalloc( | |
| buffer_len * sizeof(*tk_path)); | |
| if (tk_path != NULL) { | |
| path_len = GetModuleFileNameW(tk_module, tk_path, buffer_len); | |
| if (path_len == buffer_len) { | |
| PyMem_RawFree(tk_path); | |
| tk_path = NULL; | |
| } | |
| } | |
| } | |
| if (tk_path == NULL || path_len == 0) { | |
| PyMem_RawFree(tk_path); | |
| return; | |
| } | |
| Tcl_DString utf8_path; | |
| Tcl_DStringInit(&utf8_path); | |
| Tcl_WCharToUtfDString(tk_path, path_len, &utf8_path); | |
| /* Failure is harmless if the DLL has no embedded ZIP or if another | |
| interpreter has already mounted it. */ | |
| (void) TclZipfs_Mount(NULL, Tcl_DStringValue(&utf8_path), | |
| "//zipfs:/lib/tk", NULL); | |
| Tcl_DStringFree(&utf8_path); | |
| PyMem_RawFree(tk_path); | |
| } | |
| #endif | |
| int | |
| Tkinter_TkInit(Tcl_Interp *interp) | |
| { | |
| #if defined(MS_WINDOWS) && TK_MAJOR_VERSION >= 9 | |
| /* Tcl/Tk 9 may embed the tk_library in the Tk DLL which tcl_findLibrary | |
| does not search. Mount the DLL using Zipfs if possible. */ | |
| mount_tk_dll_zip(); | |
| #endif | |
| #ifdef __APPLE__ | |
| /* Tk on macOS replaces the handlers of these signals with its own, | |
| which exits the process. Keep the handlers installed by Python | |
| (gh-157672). */ | |
| static const int signals[] = {SIGINT, SIGHUP, SIGTERM}; | |
| PyOS_sighandler_t handlers[Py_ARRAY_LENGTH(signals)]; | |
| for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) { | |
| handlers[i] = PyOS_getsig(signals[i]); | |
| } | |
| #endif | |
| int result = Tk_Init(interp); | |
| #ifdef __APPLE__ | |
| for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) { | |
| if (handlers[i] != SIG_DFL) { | |
| PyOS_setsig(signals[i], handlers[i]); | |
| } | |
| } | |
| #endif | |
| return result; | |
| } | |
| /* 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. | |
| To solve this problem, a separate lock for Tcl is introduced. | |
| Holding it is incompatible with holding Python's interpreter lock. | |
| The following four macros manipulate both locks together. | |
| ENTER_TCL and LEAVE_TCL are brackets, just like | |
| Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS. They should be | |
| used whenever a call into Tcl is made that could call an event | |
| handler, or otherwise affect the state of a Tcl interpreter. These | |
| assume that the surrounding code has the Python interpreter lock; | |
| inside the brackets, the Python interpreter lock has been released | |
| and the lock for Tcl has been acquired. | |
| Sometimes, it is necessary to have both the Python lock and the Tcl | |
| lock. (For example, when transferring data from the Tcl | |
| interpreter result to a Python string object.) This can be done by | |
| using different macros to close the ENTER_TCL block: ENTER_OVERLAP | |
| reacquires the Python lock (and restores the thread state) but | |
| doesn't release the Tcl lock; LEAVE_OVERLAP_TCL releases the Tcl | |
| lock. | |
| By contrast, ENTER_PYTHON and LEAVE_PYTHON are used in Tcl event | |
| handlers when the handler needs to use Python. Such event handlers | |
| are entered while the lock for Tcl is held; the event handler | |
| presumably needs to use Python. ENTER_PYTHON releases the lock for | |
| Tcl and acquires the Python interpreter lock, restoring the | |
| appropriate thread state, and LEAVE_PYTHON releases the Python | |
| interpreter lock and re-acquires the lock for Tcl. It is okay for | |
| ENTER_TCL/LEAVE_TCL pairs to be contained inside the code between | |
| ENTER_PYTHON and LEAVE_PYTHON. | |
| 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; | |
| #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 \ | |
| if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); \ | |
| tcl_tstate = tstate; | |
| #define LEAVE_TCL \ | |
| 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; if(tcl_lock)PyThread_release_lock(tcl_lock); } | |
| #define ENTER_PYTHON \ | |
| { PyThreadState *tstate = tcl_tstate; tcl_tstate = NULL; \ | |
| if(tcl_lock) \ | |
| PyThread_release_lock(tcl_lock); \ | |
| PyEval_RestoreThread((tstate)); } | |
| #define LEAVE_PYTHON \ | |
| { PyThreadState *tstate = PyEval_SaveThread(); \ | |
| if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); \ | |
| tcl_tstate = tstate; } | |
| #ifndef FREECAST | |
| #define FREECAST (char *) | |
| #endif | |
| /**** Tkapp Object Declaration ****/ | |
| static PyObject *Tkapp_Type; | |
| typedef struct { | |
| PyObject_HEAD | |
| Tcl_Interp *interp; | |
| int wantobjects; | |
| int threaded; /* True if tcl_platform[threaded] */ | |
| Tcl_ThreadId thread_id; | |
| int dispatching; | |
| PyObject *trace; | |
| /* We cannot include tclInt.h, as this is internal. | |
| So we cache interesting types here. */ | |
| const Tcl_ObjType *OldBooleanType; | |
| const Tcl_ObjType *BooleanType; | |
| const Tcl_ObjType *ByteArrayType; | |
| const Tcl_ObjType *DoubleType; | |
| const Tcl_ObjType *IntType; | |
| const Tcl_ObjType *WideIntType; | |
| const Tcl_ObjType *BignumType; | |
| const Tcl_ObjType *ListType; | |
| const Tcl_ObjType *StringType; | |
| const Tcl_ObjType *UTF32StringType; | |
| const Tcl_ObjType *PixelType; | |
| const Tcl_ObjType *IndexType; | |
| const Tcl_ObjType *ColorType; | |
| const Tcl_ObjType *BorderType; | |
| const Tcl_ObjType *FontType; | |
| const Tcl_ObjType *CursorType; | |
| const Tcl_ObjType *WindowType; | |
| const Tcl_ObjType *NsNameType; | |
| const Tcl_ObjType *ParsedVarNameType; | |
| const Tcl_ObjType *DictType; | |
| } TkappObject; | |
| #define TkappObject_CAST(op) ((TkappObject *)(op)) | |
| #define Tkapp_Interp(v) (TkappObject_CAST(v)->interp) | |
| static inline int | |
| check_tcl_appartment(TkappObject *app) | |
| { | |
| if (app->threaded && app->thread_id != Tcl_GetCurrentThread()) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "Calling Tcl from different apartment"); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| #define CHECK_TCL_APPARTMENT(APP) \ | |
| do { \ | |
| if (check_tcl_appartment(APP) < 0) { \ | |
| return 0; \ | |
| } \ | |
| } while (0) | |
| /**** Error Handling ****/ | |
| static PyObject *Tkinter_TclError; | |
| static int quitMainLoop = 0; | |
| static int errorInCmd = 0; | |
| static PyObject *excInCmd; | |
| static PyObject *Tkapp_UnicodeResult(TkappObject *); | |
| static PyObject * | |
| Tkinter_Error(TkappObject *self) | |
| { | |
| PyObject *res = Tkapp_UnicodeResult(self); | |
| if (res != NULL) { | |
| PyErr_SetObject(Tkinter_TclError, res); | |
| Py_DECREF(res); | |
| } | |
| return NULL; | |
| } | |
| /**** Utils ****/ | |
| static int Tkinter_busywaitinterval = 20; | |
| #ifndef MS_WINDOWS | |
| /* Millisecond sleep() for Unix platforms. */ | |
| static void | |
| Sleep(int milli) | |
| { | |
| /* XXX Too bad if you don't have select(). */ | |
| struct timeval t; | |
| t.tv_sec = milli/1000; | |
| t.tv_usec = (milli%1000) * 1000; | |
| select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &t); | |
| } | |
| #endif /* MS_WINDOWS */ | |
| /* Wait up to 1s for the mainloop to come up. */ | |
| static int | |
| WaitForMainloop(TkappObject* self) | |
| { | |
| int i; | |
| for (i = 0; i < 10; i++) { | |
| if (self->dispatching) | |
| return 1; | |
| Py_BEGIN_ALLOW_THREADS | |
| Sleep(100); | |
| Py_END_ALLOW_THREADS | |
| } | |
| if (self->dispatching) | |
| return 1; | |
| PyErr_SetString(PyExc_RuntimeError, "main thread is not in main loop"); | |
| return 0; | |
| } | |
| #define ARGSZ 64 | |
| static PyObject * | |
| unicodeFromTclStringAndSize(const char *s, Py_ssize_t size) | |
| { | |
| PyObject *r = PyUnicode_DecodeUTF8(s, size, NULL); | |
| if (r != NULL || !PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) { | |
| return r; | |
| } | |
| char *buf = NULL; | |
| PyErr_Clear(); | |
| /* Tcl encodes null character as \xc0\x80. | |
| https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 */ | |
| if (memchr(s, '\xc0', size)) { | |
| char *q; | |
| const char *e = s + size; | |
| q = buf = (char *)PyMem_Malloc(size); | |
| if (buf == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| while (s != e) { | |
| if (s + 1 != e && s[0] == '\xc0' && s[1] == '\x80') { | |
| *q++ = '\0'; | |
| s += 2; | |
| } | |
| else | |
| *q++ = *s++; | |
| } | |
| s = buf; | |
| size = q - s; | |
| } | |
| r = PyUnicode_DecodeUTF8(s, size, "surrogateescape"); | |
| if (buf != NULL) { | |
| PyMem_Free(buf); | |
| } | |
| if (r == NULL || PyUnicode_KIND(r) == PyUnicode_1BYTE_KIND) { | |
| return r; | |
| } | |
| /* In CESU-8 non-BMP characters are represented as a surrogate pair, | |
| like in UTF-16, and then each surrogate code point is encoded in UTF-8. | |
| https://en.wikipedia.org/wiki/CESU-8 */ | |
| Py_ssize_t len = PyUnicode_GET_LENGTH(r); | |
| Py_ssize_t i, j; | |
| /* All encoded surrogate characters start with \xED. */ | |
| i = PyUnicode_FindChar(r, 0xdcED, 0, len, 1); | |
| if (i == -2) { | |
| Py_DECREF(r); | |
| return NULL; | |
| } | |
| if (i == -1) { | |
| return r; | |
| } | |
| Py_UCS4 *u = PyUnicode_AsUCS4Copy(r); | |
| Py_DECREF(r); | |
| if (u == NULL) { | |
| return NULL; | |
| } | |
| Py_UCS4 ch; | |
| for (j = i; i < len; i++, u[j++] = ch) { | |
| Py_UCS4 ch1, ch2, ch3, high, low; | |
| /* Low surrogates U+D800 - U+DBFF are encoded as | |
| \xED\xA0\x80 - \xED\xAF\xBF. */ | |
| ch1 = ch = u[i]; | |
| if (ch1 != 0xdcED) continue; | |
| ch2 = u[i + 1]; | |
| if (!(0xdcA0 <= ch2 && ch2 <= 0xdcAF)) continue; | |
| ch3 = u[i + 2]; | |
| if (!(0xdc80 <= ch3 && ch3 <= 0xdcBF)) continue; | |
| high = 0xD000 | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F); | |
| assert(Py_UNICODE_IS_HIGH_SURROGATE(high)); | |
| /* High surrogates U+DC00 - U+DFFF are encoded as | |
| \xED\xB0\x80 - \xED\xBF\xBF. */ | |
| ch1 = u[i + 3]; | |
| if (ch1 != 0xdcED) continue; | |
| ch2 = u[i + 4]; | |
| if (!(0xdcB0 <= ch2 && ch2 <= 0xdcBF)) continue; | |
| ch3 = u[i + 5]; | |
| if (!(0xdc80 <= ch3 && ch3 <= 0xdcBF)) continue; | |
| low = 0xD000 | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F); | |
| assert(Py_UNICODE_IS_HIGH_SURROGATE(high)); | |
| ch = Py_UNICODE_JOIN_SURROGATES(high, low); | |
| i += 5; | |
| } | |
| r = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, u, j); | |
| PyMem_Free(u); | |
| return r; | |
| } | |
| static PyObject * | |
| unicodeFromTclString(const char *s) | |
| { | |
| return unicodeFromTclStringAndSize(s, strlen(s)); | |
| } | |
| static PyObject * | |
| unicodeFromTclObj(TkappObject *tkapp, Tcl_Obj *value) | |
| { | |
| Tcl_Size len; | |
| #if USE_TCL_UNICODE | |
| if (value->typePtr != NULL && tkapp != NULL && | |
| (value->typePtr == tkapp->StringType || | |
| value->typePtr == tkapp->UTF32StringType)) | |
| { | |
| int byteorder = NATIVE_BYTEORDER; | |
| const Tcl_UniChar *u = Tcl_GetUnicodeFromObj(value, &len); | |
| if (sizeof(Tcl_UniChar) == 2) | |
| return PyUnicode_DecodeUTF16((const char *)u, len * 2, | |
| "surrogatepass", &byteorder); | |
| else if (sizeof(Tcl_UniChar) == 4) | |
| return PyUnicode_DecodeUTF32((const char *)u, len * 4, | |
| "surrogatepass", &byteorder); | |
| else | |
| Py_UNREACHABLE(); | |
| } | |
| #endif /* USE_TCL_UNICODE */ | |
| const char *s = Tcl_GetStringFromObj(value, &len); | |
| return unicodeFromTclStringAndSize(s, len); | |
| } | |
| /*[clinic input] | |
| module _tkinter | |
| class _tkinter.tkapp "TkappObject *" "&Tkapp_Type_spec" | |
| class _tkinter.Tcl_Obj "PyTclObject *" "&PyTclObject_Type_spec" | |
| class _tkinter.tktimertoken "TkttObject *" "&Tktt_Type_spec" | |
| [clinic start generated code]*/ | |
| /*[clinic end generated code: output=da39a3ee5e6b4b0d input=b1ebf15c162ee229]*/ | |
| /**** Tkapp Object ****/ | |
| #if TK_MAJOR_VERSION >= 9 | |
| int Tcl_AppInit(Tcl_Interp *); | |
| #endif | |
| #ifndef WITH_APPINIT | |
| int | |
| Tcl_AppInit(Tcl_Interp *interp) | |
| { | |
| const char * _tkinter_skip_tk_init; | |
| if (Tcl_Init(interp) == TCL_ERROR) { | |
| PySys_WriteStderr("Tcl_Init error: %s\n", Tcl_GetStringResult(interp)); | |
| return TCL_ERROR; | |
| } | |
| _tkinter_skip_tk_init = Tcl_GetVar(interp, | |
| "_tkinter_skip_tk_init", TCL_GLOBAL_ONLY); | |
| if (_tkinter_skip_tk_init != NULL && | |
| strcmp(_tkinter_skip_tk_init, "1") == 0) { | |
| return TCL_OK; | |
| } | |
| if (Tkinter_TkInit(interp) == TCL_ERROR) { | |
| PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp)); | |
| return TCL_ERROR; | |
| } | |
| return TCL_OK; | |
| } | |
| #endif /* !WITH_APPINIT */ | |
| /* Initialize the Tk application; see the `main' function in | |
| * `tkMain.c'. | |
| */ | |
| static void EnableEventHook(void); /* Forward */ | |
| static void DisableEventHook(void); /* Forward */ | |
| static TkappObject * | |
| Tkapp_New(const char *screenName, const char *className, | |
| int interactive, int wantobjects, int wantTk, int sync, | |
| const char *use) | |
| { | |
| TkappObject *v; | |
| char *argv0; | |
| PyTypeObject *tp = (PyTypeObject *)Tkapp_Type; | |
| v = (TkappObject *)tp->tp_alloc(tp, 0); | |
| if (v == NULL) | |
| return NULL; | |
| v->interp = Tcl_CreateInterp(); | |
| v->wantobjects = wantobjects; | |
| #if TCL_MAJOR_VERSION >= 9 | |
| v->threaded = 1; | |
| #else | |
| v->threaded = Tcl_GetVar2Ex(v->interp, "tcl_platform", "threaded", | |
| TCL_GLOBAL_ONLY) != NULL; | |
| #endif | |
| v->thread_id = Tcl_GetCurrentThread(); | |
| v->dispatching = 0; | |
| v->trace = NULL; | |
| #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->OldBooleanType = Tcl_GetObjType("boolean"); | |
| { | |
| Tcl_Obj *value; | |
| int boolValue; | |
| /* Tcl 8.5 "booleanString" type is not registered | |
| and is renamed to "boolean" in Tcl 9.0. | |
| Based on approach suggested at | |
| https://core.tcl-lang.org/tcl/info/3bb3bcf2da5b */ | |
| value = Tcl_NewStringObj("true", -1); | |
| Tcl_GetBooleanFromObj(NULL, value, &boolValue); | |
| v->BooleanType = value->typePtr; | |
| Tcl_DecrRefCount(value); | |
| // "bytearray" type is not registered in Tcl 9.0 | |
| value = Tcl_NewByteArrayObj(NULL, 0); | |
| v->ByteArrayType = value->typePtr; | |
| Tcl_DecrRefCount(value); | |
| } | |
| v->DoubleType = Tcl_GetObjType("double"); | |
| /* TIP 484 suggests retrieving the "int" type without Tcl_GetObjType("int") | |
| since it is no longer registered in Tcl 9.0. But even though Tcl 8.7 | |
| only uses the "wideInt" type on platforms with 32-bit long, it still has | |
| a registered "int" type, which FromObj() should recognize just in case. */ | |
| v->IntType = Tcl_GetObjType("int"); | |
| if (v->IntType == NULL) { | |
| Tcl_Obj *value = Tcl_NewIntObj(0); | |
| v->IntType = value->typePtr; | |
| Tcl_DecrRefCount(value); | |
| } | |
| v->WideIntType = Tcl_GetObjType("wideInt"); | |
| v->ListType = Tcl_GetObjType("list"); | |
| v->StringType = Tcl_GetObjType("string"); | |
| v->UTF32StringType = Tcl_GetObjType("utf32string"); | |
| /* These types may not be registered until first used, so these may be NULL; | |
| FromObj() caches them lazily on first encounter. */ | |
| v->BignumType = Tcl_GetObjType("bignum"); | |
| v->PixelType = Tcl_GetObjType("pixel"); | |
| v->IndexType = Tcl_GetObjType("index"); | |
| v->ColorType = Tcl_GetObjType("color"); | |
| v->BorderType = Tcl_GetObjType("border"); | |
| v->FontType = Tcl_GetObjType("font"); | |
| v->CursorType = Tcl_GetObjType("cursor"); | |
| v->WindowType = Tcl_GetObjType("window"); | |
| v->NsNameType = Tcl_GetObjType("nsName"); | |
| v->ParsedVarNameType = Tcl_GetObjType("parsedVarName"); | |
| v->DictType = Tcl_GetObjType("dict"); | |
| /* Delete the 'exit' command, which can screw things up */ | |
| Tcl_DeleteCommand(v->interp, "exit"); | |
| if (screenName != NULL) | |
| Tcl_SetVar2(v->interp, "env", "DISPLAY", | |
| screenName, TCL_GLOBAL_ONLY); | |
| if (interactive) | |
| Tcl_SetVar(v->interp, "tcl_interactive", "1", TCL_GLOBAL_ONLY); | |
| else | |
| Tcl_SetVar(v->interp, "tcl_interactive", "0", TCL_GLOBAL_ONLY); | |
| /* This is used to get the application class for Tk 4.1 and up */ | |
| argv0 = (char*)PyMem_Malloc(strlen(className) + 1); | |
| if (!argv0) { | |
| PyErr_NoMemory(); | |
| Py_DECREF(v); | |
| return NULL; | |
| } | |
| strcpy(argv0, className); | |
| if (Py_ISUPPER(argv0[0])) | |
| argv0[0] = Py_TOLOWER(argv0[0]); | |
| Tcl_SetVar(v->interp, "argv0", argv0, TCL_GLOBAL_ONLY); | |
| PyMem_Free(argv0); | |
| if (! wantTk) { | |
| Tcl_SetVar(v->interp, | |
| "_tkinter_skip_tk_init", "1", TCL_GLOBAL_ONLY); | |
| } | |
| /* some initial arguments need to be in argv */ | |
| if (sync || use) { | |
| char *args; | |
| Py_ssize_t len = 0; | |
| if (sync) | |
| len += sizeof "-sync"; | |
| if (use) | |
| len += strlen(use) + sizeof "-use "; /* never overflows */ | |
| args = (char*)PyMem_Malloc(len); | |
| if (!args) { | |
| PyErr_NoMemory(); | |
| Py_DECREF(v); | |
| return NULL; | |
| } | |
| args[0] = '\0'; | |
| if (sync) | |
| strcat(args, "-sync"); | |
| if (use) { | |
| if (sync) | |
| strcat(args, " "); | |
| strcat(args, "-use "); | |
| strcat(args, use); | |
| } | |
| Tcl_SetVar(v->interp, "argv", args, TCL_GLOBAL_ONLY); | |
| PyMem_Free(args); | |
| } | |
| #ifdef MS_WINDOWS | |
| { | |
| PyObject *str_path; | |
| PyObject *utf8_path; | |
| DWORD ret; | |
| ret = GetEnvironmentVariableW(L"TCL_LIBRARY", NULL, 0); | |
| if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { | |
| str_path = _get_tcl_lib_path(); | |
| if (str_path == NULL && PyErr_Occurred()) { | |
| Py_DECREF(v); | |
| return NULL; | |
| } | |
| if (str_path != NULL) { | |
| utf8_path = PyUnicode_AsUTF8String(str_path); | |
| if (utf8_path == NULL) { | |
| Py_DECREF(v); | |
| return NULL; | |
| } | |
| Tcl_SetVar(v->interp, | |
| "tcl_library", | |
| PyBytes_AS_STRING(utf8_path), | |
| TCL_GLOBAL_ONLY); | |
| Py_DECREF(utf8_path); | |
| } | |
| } | |
| } | |
| #endif | |
| if (Tcl_AppInit(v->interp) != TCL_OK) { | |
| PyObject *result = Tkinter_Error(v); | |
| Py_DECREF((PyObject *)v); | |
| return (TkappObject *)result; | |
| } | |
| EnableEventHook(); | |
| return v; | |
| } | |
| 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 **/ | |
| typedef struct { | |
| PyObject_HEAD | |
| Tcl_Obj *value; | |
| PyObject *string; /* This cannot cause cycles. */ | |
| } PyTclObject; | |
| // TODO(picnixz): maybe assert that 'op' is really a PyTclObject (we might want | |
| // to also add a FAST_CAST macro to bypass the check if needed). | |
| #define PyTclObject_CAST(op) ((PyTclObject *)(op)) | |
| static PyObject *PyTclObject_Type; | |
| #define PyTclObject_Check(v) Py_IS_TYPE(v, (PyTypeObject *)PyTclObject_Type) | |
| static PyObject * | |
| newPyTclObject(Tcl_Obj *arg) | |
| { | |
| PyTclObject *self; | |
| self = PyObject_New(PyTclObject, (PyTypeObject *) PyTclObject_Type); | |
| if (self == NULL) | |
| return NULL; | |
| Tcl_IncrRefCount(arg); | |
| self->value = arg; | |
| self->string = NULL; | |
| return (PyObject*)self; | |
| } | |
| static void | |
| PyTclObject_dealloc(PyObject *_self) | |
| { | |
| PyTclObject *self = PyTclObject_CAST(_self); | |
| PyObject *tp = (PyObject *) Py_TYPE(self); | |
| Tcl_DecrRefCount(self->value); | |
| Py_XDECREF(self->string); | |
| PyObject_Free(self); | |
| Py_DECREF(tp); | |
| } | |
| /* Like _str, but create Unicode if necessary. */ | |
| PyDoc_STRVAR(PyTclObject_string__doc__, | |
| "the string representation of this object, either as str or bytes"); | |
| static PyObject * | |
| PyTclObject_string(PyObject *_self, void *Py_UNUSED(closure)) | |
| { | |
| PyTclObject *self = PyTclObject_CAST(_self); | |
| if (!self->string) { | |
| self->string = unicodeFromTclObj(NULL, self->value); | |
| if (!self->string) | |
| return NULL; | |
| } | |
| return Py_NewRef(self->string); | |
| } | |
| static PyObject * | |
| PyTclObject_str(PyObject *_self) | |
| { | |
| PyTclObject *self = PyTclObject_CAST(_self); | |
| if (self->string) { | |
| return Py_NewRef(self->string); | |
| } | |
| /* XXX Could cache result if it is non-ASCII. */ | |
| return unicodeFromTclObj(NULL, self->value); | |
| } | |
| static PyObject * | |
| PyTclObject_repr(PyObject *_self) | |
| { | |
| PyTclObject *self = PyTclObject_CAST(_self); | |
| PyObject *repr, *str = PyTclObject_str(_self); | |
| if (str == NULL) | |
| return NULL; | |
| repr = PyUnicode_FromFormat("<%s object: %R>", | |
| self->value->typePtr->name, str); | |
| Py_DECREF(str); | |
| return repr; | |
| } | |
| static PyObject * | |
| PyTclObject_richcompare(PyObject *self, PyObject *other, int op) | |
| { | |
| int result; | |
| /* neither argument should be NULL, unless something's gone wrong */ | |
| if (self == NULL || other == NULL) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| /* both arguments should be instances of PyTclObject */ | |
| if (!PyTclObject_Check(self) || !PyTclObject_Check(other)) { | |
| Py_RETURN_NOTIMPLEMENTED; | |
| } | |
| if (self == other) { | |
| /* fast path when self and other are identical */ | |
| result = 0; | |
| } | |
| else { | |
| // 'self' and 'other' are known to be of correct type, | |
| // so we can fast cast them (no need to use the macro) | |
| result = strcmp(Tcl_GetString(((PyTclObject *)self)->value), | |
| Tcl_GetString(((PyTclObject *)other)->value)); | |
| } | |
| Py_RETURN_RICHCOMPARE(result, 0, op); | |
| } | |
| PyDoc_STRVAR(get_typename__doc__, "name of the Tcl type"); | |
| static PyObject* | |
| get_typename(PyObject *self, void *Py_UNUSED(closure)) | |
| { | |
| PyTclObject *obj = PyTclObject_CAST(self); | |
| return unicodeFromTclString(obj->value->typePtr->name); | |
| } | |
| static PyGetSetDef PyTclObject_getsetlist[] = { | |
| {"typename", get_typename, NULL, get_typename__doc__}, | |
| {"string", PyTclObject_string, NULL, | |
| PyTclObject_string__doc__}, | |
| {0}, | |
| }; | |
| static PyType_Slot PyTclObject_Type_slots[] = { | |
| {Py_tp_dealloc, PyTclObject_dealloc}, | |
| {Py_tp_repr, PyTclObject_repr}, | |
| {Py_tp_str, PyTclObject_str}, | |
| {Py_tp_getattro, PyObject_GenericGetAttr}, | |
| {Py_tp_richcompare, PyTclObject_richcompare}, | |
| {Py_tp_getset, PyTclObject_getsetlist}, | |
| {0, 0} | |
| }; | |
| static PyType_Spec PyTclObject_Type_spec = { | |
| .name = "_tkinter.Tcl_Obj", | |
| .basicsize = sizeof(PyTclObject), | |
| .flags = ( | |
| Py_TPFLAGS_DEFAULT | |
| | Py_TPFLAGS_DISALLOW_INSTANTIATION | |
| | Py_TPFLAGS_IMMUTABLETYPE | |
| ), | |
| .slots = PyTclObject_Type_slots, | |
| }; | |
| #if SIZE_MAX > INT_MAX | |
| #define CHECK_STRING_LENGTH(s) do { \ | |
| if (s != NULL && strlen(s) >= INT_MAX) { \ | |
| PyErr_SetString(PyExc_OverflowError, "string is too long"); \ | |
| return NULL; \ | |
| } } while(0) | |
| #else | |
| #define CHECK_STRING_LENGTH(s) | |
| #endif | |
| static Tcl_Obj* | |
| asBignumObj(PyObject *value) | |
| { | |
| Tcl_Obj *result; | |
| int neg; | |
| PyObject *hexstr; | |
| const char *hexchars; | |
| mp_int bigValue; | |
| assert(PyLong_Check(value)); | |
| neg = _PyLong_IsNegative((PyLongObject *)value); | |
| hexstr = _PyLong_Format(value, 16); | |
| if (hexstr == NULL) | |
| return NULL; | |
| hexchars = PyUnicode_AsUTF8(hexstr); | |
| if (hexchars == NULL) { | |
| Py_DECREF(hexstr); | |
| return NULL; | |
| } | |
| hexchars += neg + 2; /* skip sign and "0x" */ | |
| if (mp_init(&bigValue) != MP_OKAY || | |
| mp_read_radix(&bigValue, hexchars, 16) != MP_OKAY) | |
| { | |
| mp_clear(&bigValue); | |
| Py_DECREF(hexstr); | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| Py_DECREF(hexstr); | |
| bigValue.sign = neg ? MP_NEG : MP_ZPOS; | |
| result = Tcl_NewBignumObj(&bigValue); | |
| mp_clear(&bigValue); | |
| if (result == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| return result; | |
| } | |
| static Tcl_Obj* AsObj(PyObject *value); | |
| static Tcl_Obj* | |
| TupleAsObj(PyObject *value, int wrapped) | |
| { | |
| Tcl_Obj *result = NULL; | |
| Py_ssize_t size = PyTuple_GET_SIZE(value); | |
| if (size == 0) { | |
| return Tcl_NewListObj(0, NULL); | |
| } | |
| if (!CHECK_SIZE(size, sizeof(Tcl_Obj *))) { | |
| PyErr_SetString(PyExc_OverflowError, | |
| wrapped ? "list is too long" : "tuple is too long"); | |
| return NULL; | |
| } | |
| Tcl_Obj **argv = (Tcl_Obj **)PyMem_Malloc(((size_t)size) * sizeof(Tcl_Obj *)); | |
| if (argv == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| for (Py_ssize_t i = 0; i < size; i++) { | |
| Tcl_Obj *item = AsObj(PyTuple_GET_ITEM(value, i)); | |
| if (item == NULL) { | |
| goto exit; | |
| } | |
| argv[i] = item; | |
| } | |
| result = Tcl_NewListObj((int)size, argv); | |
| exit: | |
| PyMem_Free(argv); | |
| return result; | |
| } | |
| static Tcl_Obj* | |
| AsObj(PyObject *value) | |
| { | |
| Tcl_Obj *result; | |
| if (PyBytes_Check(value)) { | |
| if (PyBytes_GET_SIZE(value) >= INT_MAX) { | |
| PyErr_SetString(PyExc_OverflowError, "bytes object is too long"); | |
| return NULL; | |
| } | |
| return Tcl_NewByteArrayObj((unsigned char *)PyBytes_AS_STRING(value), | |
| (int)PyBytes_GET_SIZE(value)); | |
| } | |
| if (PyBool_Check(value)) | |
| return Tcl_NewBooleanObj(PyObject_IsTrue(value)); | |
| if (PyLong_CheckExact(value)) { | |
| int overflow; | |
| long longValue; | |
| Tcl_WideInt wideValue; | |
| longValue = PyLong_AsLongAndOverflow(value, &overflow); | |
| if (!overflow) { | |
| return Tcl_NewLongObj(longValue); | |
| } | |
| /* If there is an overflow in the long conversion, | |
| fall through to wideInt handling. */ | |
| if (_PyLong_AsByteArray((PyLongObject *)value, | |
| (unsigned char *)(void *)&wideValue, | |
| sizeof(wideValue), | |
| PY_LITTLE_ENDIAN, | |
| /* signed */ 1, | |
| /* with_exceptions */ 1) == 0) { | |
| return Tcl_NewWideIntObj(wideValue); | |
| } | |
| PyErr_Clear(); | |
| /* If there is an overflow in the wideInt conversion, | |
| fall through to bignum handling. */ | |
| return asBignumObj(value); | |
| /* If there is no wideInt or bignum support, | |
| fall through to default object handling. */ | |
| } | |
| if (PyFloat_Check(value)) | |
| return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value)); | |
| if (PyTuple_Check(value)) { | |
| return TupleAsObj(value, false); | |
| } | |
| if (PyList_Check(value)) { | |
| PyObject *value_as_tuple = PyList_AsTuple(value); | |
| if (value_as_tuple == NULL) { | |
| return NULL; | |
| } | |
| result = TupleAsObj(value_as_tuple, true); | |
| Py_DECREF(value_as_tuple); | |
| return result; | |
| } | |
| if (PyUnicode_Check(value)) { | |
| Py_ssize_t size = PyUnicode_GET_LENGTH(value); | |
| if (size == 0) { | |
| return Tcl_NewStringObj("", 0); | |
| } | |
| if (!CHECK_SIZE(size, sizeof(Tcl_UniChar))) { | |
| PyErr_SetString(PyExc_OverflowError, "string is too long"); | |
| return NULL; | |
| } | |
| if (PyUnicode_IS_ASCII(value) && | |
| strlen(PyUnicode_DATA(value)) == (size_t)PyUnicode_GET_LENGTH(value)) | |
| { | |
| return Tcl_NewStringObj((const char *)PyUnicode_DATA(value), | |
| (int)size); | |
| } | |
| PyObject *encoded; | |
| #if USE_TCL_UNICODE | |
| if (sizeof(Tcl_UniChar) == 2) | |
| encoded = _PyUnicode_EncodeUTF16(value, | |
| "surrogatepass", NATIVE_BYTEORDER); | |
| else if (sizeof(Tcl_UniChar) == 4) | |
| encoded = _PyUnicode_EncodeUTF32(value, | |
| "surrogatepass", NATIVE_BYTEORDER); | |
| else | |
| Py_UNREACHABLE(); | |
| if (!encoded) { | |
| return NULL; | |
| } | |
| size = PyBytes_GET_SIZE(encoded); | |
| if (size > INT_MAX) { | |
| Py_DECREF(encoded); | |
| PyErr_SetString(PyExc_OverflowError, "string is too long"); | |
| return NULL; | |
| } | |
| result = Tcl_NewUnicodeObj((const Tcl_UniChar *)PyBytes_AS_STRING(encoded), | |
| (int)(size / sizeof(Tcl_UniChar))); | |
| #else | |
| encoded = _PyUnicode_AsUTF8String(value, "surrogateescape"); | |
| if (!encoded) { | |
| return NULL; | |
| } | |
| size = PyBytes_GET_SIZE(encoded); | |
| if (strlen(PyBytes_AS_STRING(encoded)) != (size_t)size) { | |
| /* The string contains embedded null characters. | |
| * Tcl needs a null character to be represented as \xc0\x80 in | |
| * the Modified UTF-8 encoding. Otherwise the string can be | |
| * truncated in some internal operations. | |
| * | |
| * NOTE: stringlib_replace() could be used here, but optimizing | |
| * this obscure case isn't worth it unless stringlib_replace() | |
| * was already exposed in the C API for other reasons. */ | |
| Py_SETREF(encoded, | |
| PyObject_CallMethod(encoded, "replace", "y#y#", | |
| "\0", (Py_ssize_t)1, | |
| "\xc0\x80", (Py_ssize_t)2)); | |
| if (!encoded) { | |
| return NULL; | |
| } | |
| size = PyBytes_GET_SIZE(encoded); | |
| } | |
| if (size > INT_MAX) { | |
| Py_DECREF(encoded); | |
| PyErr_SetString(PyExc_OverflowError, "string is too long"); | |
| return NULL; | |
| } | |
| result = Tcl_NewStringObj(PyBytes_AS_STRING(encoded), (int)size); | |
| #endif /* USE_TCL_UNICODE */ | |
| Py_DECREF(encoded); | |
| return result; | |
| } | |
| if (PyTclObject_Check(value)) { | |
| return ((PyTclObject*)value)->value; | |
| } | |
| { | |
| PyObject *v = PyObject_Str(value); | |
| if (!v) | |
| return 0; | |
| result = AsObj(v); | |
| Py_DECREF(v); | |
| return result; | |
| } | |
| } | |
| static PyObject * | |
| fromBoolean(TkappObject *tkapp, Tcl_Obj *value) | |
| { | |
| int boolValue; | |
| if (Tcl_GetBooleanFromObj(Tkapp_Interp(tkapp), value, &boolValue) == TCL_ERROR) | |
| return Tkinter_Error(tkapp); | |
| return PyBool_FromLong(boolValue); | |
| } | |
| static PyObject* | |
| fromWideIntObj(TkappObject *tkapp, Tcl_Obj *value) | |
| { | |
| Tcl_WideInt wideValue; | |
| if (Tcl_GetWideIntFromObj(Tkapp_Interp(tkapp), value, &wideValue) == TCL_OK) { | |
| if (sizeof(wideValue) <= SIZEOF_LONG_LONG) | |
| return PyLong_FromLongLong(wideValue); | |
| return _PyLong_FromByteArray((unsigned char *)(void *)&wideValue, | |
| sizeof(wideValue), | |
| PY_LITTLE_ENDIAN, | |
| /* signed */ 1); | |
| } | |
| return NULL; | |
| } | |
| static PyObject* | |
| fromBignumObj(TkappObject *tkapp, Tcl_Obj *value) | |
| { | |
| mp_int bigValue; | |
| mp_err err; | |
| #if USE_DEPRECATED_TOMMATH_API | |
| unsigned long numBytes; | |
| #else | |
| size_t numBytes; | |
| #endif | |
| unsigned char *bytes; | |
| PyObject *res; | |
| if (Tcl_GetBignumFromObj(Tkapp_Interp(tkapp), value, &bigValue) != TCL_OK) | |
| return Tkinter_Error(tkapp); | |
| #if USE_DEPRECATED_TOMMATH_API | |
| numBytes = mp_unsigned_bin_size(&bigValue); | |
| #else | |
| numBytes = mp_ubin_size(&bigValue); | |
| #endif | |
| bytes = PyMem_Malloc(numBytes); | |
| if (bytes == NULL) { | |
| mp_clear(&bigValue); | |
| return PyErr_NoMemory(); | |
| } | |
| #if USE_DEPRECATED_TOMMATH_API | |
| err = mp_to_unsigned_bin_n(&bigValue, bytes, &numBytes); | |
| #else | |
| err = mp_to_ubin(&bigValue, bytes, numBytes, NULL); | |
| #endif | |
| if (err != MP_OKAY) { | |
| mp_clear(&bigValue); | |
| PyMem_Free(bytes); | |
| return PyErr_NoMemory(); | |
| } | |
| res = _PyLong_FromByteArray(bytes, numBytes, | |
| /* big-endian */ 0, | |
| /* unsigned */ 0); | |
| PyMem_Free(bytes); | |
| if (res != NULL && bigValue.sign == MP_NEG) { | |
| PyObject *res2 = PyNumber_Negative(res); | |
| Py_SETREF(res, res2); | |
| } | |
| mp_clear(&bigValue); | |
| return res; | |
| } | |
| /* Convert a "pixel" screen distance to a Python object. A value with no unit | |
| suffix is screen independent and is returned as an int or float. A value with | |
| an m/c/i/p suffix needs a Tk_Window to be resolved to pixels, which is not | |
| available here, so it is kept as a Tcl_Obj. Tcl_Get*FromObj() only set the | |
| object's type on success, so a unit-bearing value does not shimmer. */ | |
| static PyObject* | |
| fromPixelObj(TkappObject *tkapp, Tcl_Obj *value) | |
| { | |
| PyObject *result = fromWideIntObj(tkapp, value); | |
| if (result != NULL || PyErr_Occurred()) { | |
| return result; | |
| } | |
| Tcl_ResetResult(Tkapp_Interp(tkapp)); | |
| double doubleValue; | |
| if (Tcl_GetDoubleFromObj(NULL, value, &doubleValue) == TCL_OK) { | |
| return PyFloat_FromDouble(doubleValue); | |
| } | |
| return newPyTclObject(value); | |
| } | |
| /* Convert an "index" object to a Python str. Index values are enumeration | |
| keywords from a small fixed set (e.g. "horizontal", "disabled"), so the result | |
| is interned to share one str object per keyword. */ | |
| static PyObject* | |
| fromIndexObj(TkappObject *tkapp, Tcl_Obj *value) | |
| { | |
| PyObject *result = unicodeFromTclObj(tkapp, value); | |
| if (result != NULL) { | |
| PyUnicode_InternInPlace(&result); | |
| } | |
| return result; | |
| } | |
| static PyObject* | |
| FromObj(TkappObject *tkapp, Tcl_Obj *value) | |
| { | |
| PyObject *result = NULL; | |
| Tcl_Interp *interp = Tkapp_Interp(tkapp); | |
| if (value->typePtr == NULL) { | |
| return unicodeFromTclObj(tkapp, value); | |
| } | |
| if (value->typePtr == tkapp->BooleanType || | |
| value->typePtr == tkapp->OldBooleanType) { | |
| return fromBoolean(tkapp, value); | |
| } | |
| if (value->typePtr == tkapp->ByteArrayType) { | |
| Tcl_Size size; | |
| char *data = (char*)Tcl_GetByteArrayFromObj(value, &size); | |
| return PyBytes_FromStringAndSize(data, size); | |
| } | |
| if (value->typePtr == tkapp->DoubleType) { | |
| return PyFloat_FromDouble(value->internalRep.doubleValue); | |
| } | |
| if (value->typePtr == tkapp->IntType || | |
| value->typePtr == tkapp->WideIntType) { | |
| result = fromWideIntObj(tkapp, value); | |
| if (result != NULL || PyErr_Occurred()) | |
| return result; | |
| Tcl_ResetResult(interp); | |
| /* If there is an error in the wideInt conversion, | |
| fall through to bignum handling. */ | |
| } | |
| if (value->typePtr == tkapp->IntType || | |
| value->typePtr == tkapp->WideIntType || | |
| value->typePtr == tkapp->BignumType) { | |
| return fromBignumObj(tkapp, value); | |
| } | |
| if (value->typePtr == tkapp->ListType) { | |
| Tcl_Size i, size; | |
| int status; | |
| PyObject *elem; | |
| Tcl_Obj *tcl_elem; | |
| status = Tcl_ListObjLength(interp, value, &size); | |
| if (status == TCL_ERROR) | |
| return Tkinter_Error(tkapp); | |
| result = PyTuple_New(size); | |
| if (!result) | |
| return NULL; | |
| for (i = 0; i < size; i++) { | |
| status = Tcl_ListObjIndex(interp, value, i, &tcl_elem); | |
| if (status == TCL_ERROR) { | |
| Py_DECREF(result); | |
| return Tkinter_Error(tkapp); | |
| } | |
| elem = FromObj(tkapp, tcl_elem); | |
| if (!elem) { | |
| Py_DECREF(result); | |
| return NULL; | |
| } | |
| PyTuple_SET_ITEM(result, i, elem); | |
| } | |
| return result; | |
| } | |
| if (value->typePtr == tkapp->StringType || | |
| value->typePtr == tkapp->UTF32StringType) | |
| { | |
| return unicodeFromTclObj(tkapp, value); | |
| } | |
| if (value->typePtr == tkapp->PixelType) { | |
| return fromPixelObj(tkapp, value); | |
| } | |
| if (value->typePtr == tkapp->IndexType) { | |
| return fromIndexObj(tkapp, value); | |
| } | |
| /* A dict is kept as a Tcl_Obj to preserve its structure for _splitdict(). */ | |
| if (value->typePtr == tkapp->DictType) { | |
| return newPyTclObject(value); | |
| } | |
| /* Resource types are kept as a Tcl_Obj so that Tk can reuse the parsed | |
| X/font resource when the object is passed back to it. */ | |
| if (value->typePtr == tkapp->ColorType || | |
| value->typePtr == tkapp->BorderType || | |
| value->typePtr == tkapp->FontType || | |
| value->typePtr == tkapp->CursorType) { | |
| return newPyTclObject(value); | |
| } | |
| /* These types are context-bound lookups (a cached window or namespace | |
| resolution, or a parsed variable name) with nothing worth preserving in | |
| a Tcl_Obj, so they are returned as str. */ | |
| if (value->typePtr == tkapp->WindowType || | |
| value->typePtr == tkapp->NsNameType || | |
| value->typePtr == tkapp->ParsedVarNameType) { | |
| return unicodeFromTclObj(tkapp, value); | |
| } | |
| /* The types below may not be registered until first used, so they are | |
| matched by name and cached on first encounter; the pointer checks above | |
| then catch them. */ | |
| const char *name = value->typePtr->name; | |
| if (tkapp->BignumType == NULL && strcmp(name, "bignum") == 0) { | |
| tkapp->BignumType = value->typePtr; | |
| return fromBignumObj(tkapp, value); | |
| } | |
| if (tkapp->DictType == NULL && strcmp(name, "dict") == 0) { | |
| tkapp->DictType = value->typePtr; | |
| return newPyTclObject(value); | |
| } | |
| if (tkapp->PixelType == NULL && strcmp(name, "pixel") == 0) { | |
| tkapp->PixelType = value->typePtr; | |
| return fromPixelObj(tkapp, value); | |
| } | |
| if (tkapp->IndexType == NULL && strcmp(name, "index") == 0) { | |
| tkapp->IndexType = value->typePtr; | |
| return fromIndexObj(tkapp, value); | |
| } | |
| if (tkapp->WindowType == NULL && strcmp(name, "window") == 0) { | |
| tkapp->WindowType = value->typePtr; | |
| return unicodeFromTclObj(tkapp, value); | |
| } | |
| if (tkapp->NsNameType == NULL && strcmp(name, "nsName") == 0) { | |
| tkapp->NsNameType = value->typePtr; | |
| return unicodeFromTclObj(tkapp, value); | |
| } | |
| if (tkapp->ParsedVarNameType == NULL && strcmp(name, "parsedVarName") == 0) { | |
| tkapp->ParsedVarNameType = value->typePtr; | |
| return unicodeFromTclObj(tkapp, value); | |
| } | |
| /* Any other type is returned as a Tcl_Obj. Cache the resource types so the | |
| pointer check above catches them next time. */ | |
| if (tkapp->ColorType == NULL && strcmp(name, "color") == 0) { | |
| tkapp->ColorType = value->typePtr; | |
| } | |
| else if (tkapp->BorderType == NULL && strcmp(name, "border") == 0) { | |
| tkapp->BorderType = value->typePtr; | |
| } | |
| else if (tkapp->FontType == NULL && strcmp(name, "font") == 0) { | |
| tkapp->FontType = value->typePtr; | |
| } | |
| else if (tkapp->CursorType == NULL && strcmp(name, "cursor") == 0) { | |
| tkapp->CursorType = value->typePtr; | |
| } | |
| return newPyTclObject(value); | |
| } | |
| /* 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; | |
| Tcl_Condition *done; | |
| } Tkapp_CallEvent; | |
| static void | |
| Tkapp_CallDeallocArgs(Tcl_Obj** objv, Tcl_Obj** objStore, Tcl_Size objc) | |
| { | |
| Tcl_Size i; | |
| for (i = 0; i < objc; i++) | |
| Tcl_DecrRefCount(objv[i]); | |
| if (objv != objStore) | |
| PyMem_Free(objv); | |
| } | |
| /* 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, Tcl_Size *pobjc) | |
| { | |
| Tcl_Obj **objv = objStore; | |
| Py_ssize_t objc = 0, i; | |
| if (args == NULL) | |
| /* do nothing */; | |
| else if (!(PyTuple_Check(args) || PyList_Check(args))) { | |
| objv[0] = AsObj(args); | |
| if (objv[0] == NULL) | |
| goto finally; | |
| objc = 1; | |
| Tcl_IncrRefCount(objv[0]); | |
| } | |
| else { | |
| objc = PySequence_Fast_GET_SIZE(args); | |
| if (objc > ARGSZ) { | |
| if (!CHECK_SIZE(objc, sizeof(Tcl_Obj *))) { | |
| PyErr_SetString(PyExc_OverflowError, | |
| PyTuple_Check(args) ? "tuple is too long" : | |
| "list is too long"); | |
| return NULL; | |
| } | |
| objv = (Tcl_Obj **)PyMem_Malloc(((size_t)objc) * sizeof(Tcl_Obj *)); | |
| if (objv == NULL) { | |
| PyErr_NoMemory(); | |
| objc = 0; | |
| goto finally; | |
| } | |
| } | |
| for (i = 0; i < objc; i++) { | |
| PyObject *v = PySequence_Fast_GET_ITEM(args, i); | |
| if (v == Py_None) { | |
| objc = i; | |
| break; | |
| } | |
| objv[i] = AsObj(v); | |
| if (!objv[i]) { | |
| /* Reset objc, so it attempts to clear | |
| objects only up to i. */ | |
| objc = i; | |
| goto finally; | |
| } | |
| Tcl_IncrRefCount(objv[i]); | |
| } | |
| } | |
| *pobjc = (Tcl_Size)objc; | |
| return objv; | |
| finally: | |
| Tkapp_CallDeallocArgs(objv, objStore, (Tcl_Size)objc); | |
| return NULL; | |
| } | |
| /* Convert the results of a command call into a Python string. */ | |
| static PyObject * | |
| Tkapp_UnicodeResult(TkappObject *self) | |
| { | |
| return unicodeFromTclObj(self, Tcl_GetObjResult(self->interp)); | |
| } | |
| /* Convert the results of a command call into a Python objects. */ | |
| static PyObject * | |
| Tkapp_ObjectResult(TkappObject *self) | |
| { | |
| PyObject *res = NULL; | |
| Tcl_Obj *value = Tcl_GetObjResult(self->interp); | |
| if (self->wantobjects) { | |
| /* 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); | |
| Tcl_DecrRefCount(value); | |
| } else { | |
| res = unicodeFromTclObj(self, value); | |
| } | |
| return res; | |
| } | |
| static int | |
| Tkapp_Trace(TkappObject *self, PyObject *args) | |
| { | |
| if (args == NULL) { | |
| return 0; | |
| } | |
| if (self->trace) { | |
| PyObject *res = PyObject_CallObject(self->trace, args); | |
| if (res == NULL) { | |
| Py_DECREF(args); | |
| return 0; | |
| } | |
| Py_DECREF(res); | |
| } | |
| Py_DECREF(args); | |
| return 1; | |
| } | |
| #define TRACE(_self, ARGS) do { \ | |
| if ((_self)->trace && !Tkapp_Trace((_self), Py_BuildValue ARGS)) { \ | |
| return NULL; \ | |
| } \ | |
| } while (0) | |
| /* 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. */ | |
| static int | |
| Tkapp_CallProc(Tcl_Event *evPtr, int flags) | |
| { | |
| Tkapp_CallEvent *e = (Tkapp_CallEvent *)evPtr; | |
| Tcl_Obj *objStore[ARGSZ]; | |
| Tcl_Obj **objv; | |
| Tcl_Size objc; | |
| int i; | |
| ENTER_PYTHON | |
| if (e->self->trace && !Tkapp_Trace(e->self, PyTuple_Pack(1, e->args))) { | |
| objv = NULL; | |
| } | |
| else { | |
| objv = Tkapp_CallArgs(e->args, objStore, &objc); | |
| } | |
| if (!objv) { | |
| *(e->exc) = PyErr_GetRaisedException(); | |
| *(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) = Tkinter_Error(e->self); | |
| } | |
| else { | |
| *(e->res) = Tkapp_ObjectResult(e->self); | |
| } | |
| if (*(e->res) == NULL) { | |
| *(e->exc) = PyErr_GetRaisedException(); | |
| } | |
| LEAVE_PYTHON | |
| Tkapp_CallDeallocArgs(objv, objStore, objc); | |
| 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. */ | |
| /*[clinic input] | |
| _tkinter.tkapp.call | |
| *args: tuple | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_call_impl(TkappObject *self, PyObject *args) | |
| /*[clinic end generated code: output=2af3af48d1927065 input=60052dc395d80297]*/ | |
| { | |
| Tcl_Obj *objStore[ARGSZ]; | |
| Tcl_Obj **objv = NULL; | |
| Tcl_Size objc; | |
| PyObject *res = NULL; | |
| int flags = TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL; | |
| /* If args is a single tuple, replace with contents of tuple */ | |
| if (PyTuple_GET_SIZE(args) == 1) { | |
| PyObject *item = PyTuple_GET_ITEM(args, 0); | |
| if (PyTuple_Check(item)) | |
| args = item; | |
| } | |
| 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; | |
| Tcl_Condition cond = NULL; | |
| PyObject *exc = NULL; // init to make static analyzers happy | |
| if (!WaitForMainloop(self)) | |
| return NULL; | |
| ev = (Tkapp_CallEvent*)attemptckalloc(sizeof(Tkapp_CallEvent)); | |
| if (ev == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| ev->ev.proc = Tkapp_CallProc; | |
| ev->self = self; | |
| ev->args = args; | |
| ev->res = &res; | |
| ev->exc = &exc; | |
| ev->done = &cond; | |
| Tkapp_ThreadSend(self, (Tcl_Event*)ev, &cond, &call_mutex); | |
| if (res == NULL) { | |
| if (exc) { | |
| PyErr_SetRaisedException(exc); | |
| } | |
| else { | |
| PyErr_SetObject(Tkinter_TclError, exc); | |
| } | |
| } | |
| Tcl_ConditionFinalize(&cond); | |
| } | |
| else | |
| { | |
| TRACE(self, ("(O)", args)); | |
| int i; | |
| 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_ObjectResult(self); | |
| LEAVE_OVERLAP_TCL | |
| Tkapp_CallDeallocArgs(objv, objStore, objc); | |
| } | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.eval | |
| script: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_eval_impl(TkappObject *self, const char *script) | |
| /*[clinic end generated code: output=24b79831f700dea0 input=481484123a455f22]*/ | |
| { | |
| PyObject *res = NULL; | |
| int err; | |
| CHECK_STRING_LENGTH(script); | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((ss))", "eval", script)); | |
| ENTER_TCL | |
| err = Tcl_Eval(Tkapp_Interp(self), script); | |
| ENTER_OVERLAP | |
| if (err == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else | |
| res = Tkapp_UnicodeResult(self); | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.evalfile | |
| fileName: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_evalfile_impl(TkappObject *self, const char *fileName) | |
| /*[clinic end generated code: output=63be88dcee4f11d3 input=873ab707e5e947e1]*/ | |
| { | |
| PyObject *res = NULL; | |
| int err; | |
| CHECK_STRING_LENGTH(fileName); | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((ss))", "source", fileName)); | |
| ENTER_TCL | |
| err = Tcl_EvalFile(Tkapp_Interp(self), fileName); | |
| ENTER_OVERLAP | |
| if (err == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else | |
| res = Tkapp_UnicodeResult(self); | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.record | |
| script: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_record_impl(TkappObject *self, const char *script) | |
| /*[clinic end generated code: output=0ffe08a0061730df input=c0b0db5a21412cac]*/ | |
| { | |
| PyObject *res = NULL; | |
| int err; | |
| CHECK_STRING_LENGTH(script); | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((ssss))", "history", "add", script, "exec")); | |
| ENTER_TCL | |
| err = Tcl_RecordAndEval(Tkapp_Interp(self), script, TCL_NO_EVAL); | |
| ENTER_OVERLAP | |
| if (err == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else | |
| res = Tkapp_UnicodeResult(self); | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.adderrorinfo | |
| msg: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_adderrorinfo_impl(TkappObject *self, const char *msg) | |
| /*[clinic end generated code: output=52162eaca2ee53cb input=f4b37aec7c7e8c77]*/ | |
| { | |
| CHECK_STRING_LENGTH(msg); | |
| CHECK_TCL_APPARTMENT(self); | |
| ENTER_TCL | |
| Tcl_AddErrorInfo(Tkapp_Interp(self), msg); | |
| LEAVE_TCL | |
| Py_RETURN_NONE; | |
| } | |
| /** Tcl Variable **/ | |
| typedef PyObject* (*EventFunc)(TkappObject *, PyObject *, int); | |
| TCL_DECLARE_MUTEX(var_mutex) | |
| typedef struct VarEvent { | |
| Tcl_Event ev; /* must be first */ | |
| TkappObject *self; | |
| PyObject *args; | |
| int flags; | |
| EventFunc func; | |
| PyObject **res; | |
| PyObject **exc; | |
| Tcl_Condition *cond; | |
| } VarEvent; | |
| /*[python] | |
| class varname_converter(CConverter): | |
| type = 'const char *' | |
| converter = 'varname_converter' | |
| [python]*/ | |
| /*[python checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ | |
| static int | |
| varname_converter(PyObject *in, void *_out) | |
| { | |
| const char *s; | |
| const char **out = (const char**)_out; | |
| if (PyBytes_Check(in)) { | |
| if (PyBytes_GET_SIZE(in) > INT_MAX) { | |
| PyErr_SetString(PyExc_OverflowError, "bytes object is too long"); | |
| return 0; | |
| } | |
| s = PyBytes_AS_STRING(in); | |
| if (strlen(s) != (size_t)PyBytes_GET_SIZE(in)) { | |
| PyErr_SetString(PyExc_ValueError, "embedded null byte"); | |
| return 0; | |
| } | |
| *out = s; | |
| return 1; | |
| } | |
| if (PyUnicode_Check(in)) { | |
| Py_ssize_t size; | |
| s = PyUnicode_AsUTF8AndSize(in, &size); | |
| if (s == NULL) { | |
| return 0; | |
| } | |
| if (size > INT_MAX) { | |
| PyErr_SetString(PyExc_OverflowError, "string is too long"); | |
| return 0; | |
| } | |
| if (strlen(s) != (size_t)size) { | |
| PyErr_SetString(PyExc_ValueError, "embedded null character"); | |
| return 0; | |
| } | |
| *out = s; | |
| return 1; | |
| } | |
| if (PyTclObject_Check(in)) { | |
| *out = Tcl_GetString(((PyTclObject *)in)->value); // safe fast cast | |
| return 1; | |
| } | |
| PyErr_Format(PyExc_TypeError, | |
| "must be str, bytes or Tcl_Obj, not %.50s", | |
| Py_TYPE(in)->tp_name); | |
| return 0; | |
| } | |
| static void | |
| var_perform(VarEvent *ev) | |
| { | |
| *(ev->res) = ev->func(ev->self, ev->args, ev->flags); | |
| if (!*(ev->res)) { | |
| *(ev->exc) = PyErr_GetRaisedException();; | |
| } | |
| } | |
| static int | |
| var_proc(Tcl_Event *evPtr, int flags) | |
| { | |
| VarEvent *ev = (VarEvent *)evPtr; | |
| ENTER_PYTHON | |
| var_perform(ev); | |
| Tcl_MutexLock(&var_mutex); | |
| Tcl_ConditionNotify(ev->cond); | |
| Tcl_MutexUnlock(&var_mutex); | |
| LEAVE_PYTHON | |
| return 1; | |
| } | |
| static PyObject* | |
| var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags) | |
| { | |
| TkappObject *self = TkappObject_CAST(selfptr); | |
| if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) { | |
| VarEvent *ev; | |
| // init 'res' and 'exc' to make static analyzers happy | |
| PyObject *res = NULL, *exc = NULL; | |
| Tcl_Condition cond = NULL; | |
| /* The current thread is not the interpreter thread. Marshal | |
| the call to the interpreter thread, then wait for | |
| completion. */ | |
| if (!WaitForMainloop(self)) | |
| return NULL; | |
| ev = (VarEvent*)attemptckalloc(sizeof(VarEvent)); | |
| if (ev == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| ev->self = self; | |
| ev->args = args; | |
| ev->flags = flags; | |
| ev->func = func; | |
| ev->res = &res; | |
| ev->exc = &exc; | |
| ev->cond = &cond; | |
| ev->ev.proc = var_proc; | |
| Tkapp_ThreadSend(self, (Tcl_Event*)ev, &cond, &var_mutex); | |
| Tcl_ConditionFinalize(&cond); | |
| if (!res) { | |
| PyErr_SetObject((PyObject*)Py_TYPE(exc), exc); | |
| Py_DECREF(exc); | |
| return NULL; | |
| } | |
| return res; | |
| } | |
| /* Tcl is not threaded, or this is the interpreter thread. */ | |
| return func(self, args, flags); | |
| } | |
| static PyObject * | |
| SetVar(TkappObject *self, PyObject *args, int flags) | |
| { | |
| const char *name1, *name2; | |
| PyObject *newValue; | |
| PyObject *res = NULL; | |
| Tcl_Obj *newval, *ok; | |
| switch (PyTuple_GET_SIZE(args)) { | |
| case 2: | |
| if (!PyArg_ParseTuple(args, "O&O:setvar", | |
| varname_converter, &name1, &newValue)) | |
| return NULL; | |
| /* XXX Acquire tcl lock??? */ | |
| newval = AsObj(newValue); | |
| if (newval == NULL) | |
| return NULL; | |
| if (flags & TCL_GLOBAL_ONLY) { | |
| TRACE(self, ("((ssssO))", "uplevel", "#0", "set", | |
| name1, newValue)); | |
| } | |
| else { | |
| TRACE(self, ("((ssO))", "set", name1, newValue)); | |
| } | |
| ENTER_TCL | |
| ok = Tcl_SetVar2Ex(Tkapp_Interp(self), name1, NULL, | |
| newval, flags); | |
| ENTER_OVERLAP | |
| if (!ok) | |
| Tkinter_Error(self); | |
| else { | |
| res = Py_NewRef(Py_None); | |
| } | |
| LEAVE_OVERLAP_TCL | |
| break; | |
| case 3: | |
| if (!PyArg_ParseTuple(args, "ssO:setvar", | |
| &name1, &name2, &newValue)) | |
| return NULL; | |
| CHECK_STRING_LENGTH(name1); | |
| CHECK_STRING_LENGTH(name2); | |
| /* XXX must hold tcl lock already??? */ | |
| newval = AsObj(newValue); | |
| if (self->trace) { | |
| if (flags & TCL_GLOBAL_ONLY) { | |
| TRACE(self, ("((sssNO))", "uplevel", "#0", "set", | |
| PyUnicode_FromFormat("%s(%s)", name1, name2), | |
| newValue)); | |
| } | |
| else { | |
| TRACE(self, ("((sNO))", "set", | |
| PyUnicode_FromFormat("%s(%s)", name1, name2), | |
| newValue)); | |
| } | |
| } | |
| ENTER_TCL | |
| ok = Tcl_SetVar2Ex(Tkapp_Interp(self), name1, name2, newval, flags); | |
| ENTER_OVERLAP | |
| if (!ok) | |
| Tkinter_Error(self); | |
| else { | |
| res = Py_NewRef(Py_None); | |
| } | |
| LEAVE_OVERLAP_TCL | |
| break; | |
| default: | |
| PyErr_SetString(PyExc_TypeError, "setvar requires 2 to 3 arguments"); | |
| return NULL; | |
| } | |
| return res; | |
| } | |
| static PyObject * | |
| Tkapp_SetVar(PyObject *self, PyObject *args) | |
| { | |
| return var_invoke(SetVar, self, args, TCL_LEAVE_ERR_MSG); | |
| } | |
| static PyObject * | |
| Tkapp_GlobalSetVar(PyObject *self, PyObject *args) | |
| { | |
| return var_invoke(SetVar, self, args, TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY); | |
| } | |
| static PyObject * | |
| GetVar(TkappObject *self, PyObject *args, int flags) | |
| { | |
| const char *name1, *name2=NULL; | |
| PyObject *res = NULL; | |
| Tcl_Obj *tres; | |
| if (!PyArg_ParseTuple(args, "O&|s:getvar", | |
| varname_converter, &name1, &name2)) | |
| return NULL; | |
| CHECK_STRING_LENGTH(name2); | |
| ENTER_TCL | |
| tres = Tcl_GetVar2Ex(Tkapp_Interp(self), name1, name2, flags); | |
| ENTER_OVERLAP | |
| if (tres == NULL) { | |
| Tkinter_Error(self); | |
| } else { | |
| if (self->wantobjects) { | |
| res = FromObj(self, tres); | |
| } | |
| else { | |
| res = unicodeFromTclObj(self, tres); | |
| } | |
| } | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| static PyObject * | |
| Tkapp_GetVar(PyObject *self, PyObject *args) | |
| { | |
| return var_invoke(GetVar, self, args, TCL_LEAVE_ERR_MSG); | |
| } | |
| static PyObject * | |
| Tkapp_GlobalGetVar(PyObject *self, PyObject *args) | |
| { | |
| return var_invoke(GetVar, self, args, TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY); | |
| } | |
| static PyObject * | |
| UnsetVar(TkappObject *self, PyObject *args, int flags) | |
| { | |
| char *name1, *name2=NULL; | |
| int code; | |
| PyObject *res = NULL; | |
| if (!PyArg_ParseTuple(args, "s|s:unsetvar", &name1, &name2)) { | |
| return NULL; | |
| } | |
| CHECK_STRING_LENGTH(name1); | |
| CHECK_STRING_LENGTH(name2); | |
| if (self->trace) { | |
| if (flags & TCL_GLOBAL_ONLY) { | |
| if (name2) { | |
| TRACE(self, ("((sssN))", "uplevel", "#0", "unset", | |
| PyUnicode_FromFormat("%s(%s)", name1, name2))); | |
| } | |
| else { | |
| TRACE(self, ("((ssss))", "uplevel", "#0", "unset", name1)); | |
| } | |
| } | |
| else { | |
| if (name2) { | |
| TRACE(self, ("((sN))", "unset", | |
| PyUnicode_FromFormat("%s(%s)", name1, name2))); | |
| } | |
| else { | |
| TRACE(self, ("((ss))", "unset", name1)); | |
| } | |
| } | |
| } | |
| ENTER_TCL | |
| code = Tcl_UnsetVar2(Tkapp_Interp(self), name1, name2, flags); | |
| ENTER_OVERLAP | |
| if (code == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else { | |
| res = Py_NewRef(Py_None); | |
| } | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| static PyObject * | |
| Tkapp_UnsetVar(PyObject *self, PyObject *args) | |
| { | |
| return var_invoke(UnsetVar, self, args, TCL_LEAVE_ERR_MSG); | |
| } | |
| static PyObject * | |
| Tkapp_GlobalUnsetVar(PyObject *self, PyObject *args) | |
| { | |
| return var_invoke(UnsetVar, self, args, | |
| TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY); | |
| } | |
| /** Tcl to Python **/ | |
| /*[clinic input] | |
| _tkinter.tkapp.getint | |
| arg: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_getint_impl(TkappObject *self, PyObject *arg) | |
| /*[clinic end generated code: output=5f75d31b260d4086 input=034026997c5b91f8]*/ | |
| { | |
| char *s; | |
| Tcl_Obj *value; | |
| PyObject *result; | |
| if (PyLong_Check(arg)) { | |
| return Py_NewRef(arg); | |
| } | |
| if (PyTclObject_Check(arg)) { | |
| value = ((PyTclObject*)arg)->value; | |
| Tcl_IncrRefCount(value); | |
| } | |
| else { | |
| if (!PyArg_Parse(arg, "s:getint", &s)) | |
| return NULL; | |
| CHECK_STRING_LENGTH(s); | |
| value = Tcl_NewStringObj(s, -1); | |
| if (value == NULL) | |
| return Tkinter_Error(self); | |
| } | |
| /* Don't use Tcl_GetInt() because it returns ambiguous result for value | |
| in ranges -2**32..-2**31-1 and 2**31..2**32-1 (on 32-bit platform). | |
| Prefer bignum because Tcl_GetWideIntFromObj returns ambiguous result for | |
| value in ranges -2**64..-2**63-1 and 2**63..2**64-1 (on 32-bit platform). | |
| */ | |
| result = fromBignumObj(self, value); | |
| Tcl_DecrRefCount(value); | |
| if (result != NULL || PyErr_Occurred()) | |
| return result; | |
| return Tkinter_Error(self); | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.getdouble | |
| arg: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_getdouble_impl(TkappObject *self, PyObject *arg) | |
| /*[clinic end generated code: output=432433f2f52b09b6 input=22015729ce9ef7f8]*/ | |
| { | |
| char *s; | |
| double v; | |
| if (PyFloat_Check(arg)) { | |
| return Py_NewRef(arg); | |
| } | |
| if (PyNumber_Check(arg)) { | |
| return PyNumber_Float(arg); | |
| } | |
| if (PyTclObject_Check(arg)) { | |
| if (Tcl_GetDoubleFromObj(Tkapp_Interp(self), | |
| ((PyTclObject*)arg)->value, | |
| &v) == TCL_ERROR) | |
| return Tkinter_Error(self); | |
| return PyFloat_FromDouble(v); | |
| } | |
| if (!PyArg_Parse(arg, "s:getdouble", &s)) | |
| return NULL; | |
| CHECK_STRING_LENGTH(s); | |
| if (Tcl_GetDouble(Tkapp_Interp(self), s, &v) == TCL_ERROR) | |
| return Tkinter_Error(self); | |
| return PyFloat_FromDouble(v); | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.getboolean | |
| arg: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_getboolean_impl(TkappObject *self, PyObject *arg) | |
| /*[clinic end generated code: output=3b05597cf2bfbd9f input=7f11248ef8f8776e]*/ | |
| { | |
| char *s; | |
| int v; | |
| if (PyLong_Check(arg)) { /* int or bool */ | |
| return PyBool_FromLong(!_PyLong_IsZero((PyLongObject *)arg)); | |
| } | |
| if (PyTclObject_Check(arg)) { | |
| if (Tcl_GetBooleanFromObj(Tkapp_Interp(self), | |
| ((PyTclObject*)arg)->value, | |
| &v) == TCL_ERROR) | |
| return Tkinter_Error(self); | |
| return PyBool_FromLong(v); | |
| } | |
| if (!PyArg_Parse(arg, "s:getboolean", &s)) | |
| return NULL; | |
| CHECK_STRING_LENGTH(s); | |
| if (Tcl_GetBoolean(Tkapp_Interp(self), s, &v) == TCL_ERROR) | |
| return Tkinter_Error(self); | |
| return PyBool_FromLong(v); | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.exprstring | |
| s: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_exprstring_impl(TkappObject *self, const char *s) | |
| /*[clinic end generated code: output=beda323d3ed0abb1 input=fa78f751afb2f21b]*/ | |
| { | |
| PyObject *res = NULL; | |
| int retval; | |
| CHECK_STRING_LENGTH(s); | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((ss))", "expr", s)); | |
| ENTER_TCL | |
| retval = Tcl_ExprString(Tkapp_Interp(self), s); | |
| ENTER_OVERLAP | |
| if (retval == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else | |
| res = Tkapp_UnicodeResult(self); | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.exprlong | |
| s: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_exprlong_impl(TkappObject *self, const char *s) | |
| /*[clinic end generated code: output=5d6a46b63c6ebcf9 input=11bd7eee0c57b4dc]*/ | |
| { | |
| PyObject *res = NULL; | |
| int retval; | |
| long v; | |
| CHECK_STRING_LENGTH(s); | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((ss))", "expr", s)); | |
| ENTER_TCL | |
| retval = Tcl_ExprLong(Tkapp_Interp(self), s, &v); | |
| ENTER_OVERLAP | |
| if (retval == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else | |
| res = PyLong_FromLong(v); | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.exprdouble | |
| s: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_exprdouble_impl(TkappObject *self, const char *s) | |
| /*[clinic end generated code: output=ff78df1081ea4158 input=ff02bc11798832d5]*/ | |
| { | |
| PyObject *res = NULL; | |
| double v; | |
| int retval; | |
| CHECK_STRING_LENGTH(s); | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((ss))", "expr", s)); | |
| ENTER_TCL | |
| retval = Tcl_ExprDouble(Tkapp_Interp(self), s, &v); | |
| ENTER_OVERLAP | |
| if (retval == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else | |
| res = PyFloat_FromDouble(v); | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.exprboolean | |
| s: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_exprboolean_impl(TkappObject *self, const char *s) | |
| /*[clinic end generated code: output=8b28038c22887311 input=c8c66022bdb8d5d3]*/ | |
| { | |
| PyObject *res = NULL; | |
| int retval; | |
| int v; | |
| CHECK_STRING_LENGTH(s); | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((ss))", "expr", s)); | |
| ENTER_TCL | |
| retval = Tcl_ExprBoolean(Tkapp_Interp(self), s, &v); | |
| ENTER_OVERLAP | |
| if (retval == TCL_ERROR) | |
| res = Tkinter_Error(self); | |
| else | |
| res = PyLong_FromLong(v); | |
| LEAVE_OVERLAP_TCL | |
| return res; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.splitlist | |
| arg: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_splitlist_impl(TkappObject *self, PyObject *arg) | |
| /*[clinic end generated code: output=e517f462159c3000 input=2b2e13351e3c0b53]*/ | |
| { | |
| char *list; | |
| Tcl_Size argc, i; | |
| const char **argv; | |
| PyObject *v; | |
| if (PyTclObject_Check(arg)) { | |
| Tcl_Size objc; | |
| Tcl_Obj **objv; | |
| if (Tcl_ListObjGetElements(Tkapp_Interp(self), | |
| ((PyTclObject*)arg)->value, | |
| &objc, &objv) == TCL_ERROR) { | |
| return Tkinter_Error(self); | |
| } | |
| if (!(v = PyTuple_New(objc))) | |
| return NULL; | |
| for (i = 0; i < objc; i++) { | |
| PyObject *s = FromObj(self, objv[i]); | |
| if (!s) { | |
| Py_DECREF(v); | |
| return NULL; | |
| } | |
| PyTuple_SET_ITEM(v, i, s); | |
| } | |
| return v; | |
| } | |
| if (PyTuple_Check(arg)) { | |
| return Py_NewRef(arg); | |
| } | |
| if (PyList_Check(arg)) { | |
| return PySequence_Tuple(arg); | |
| } | |
| if (!PyArg_Parse(arg, "et:splitlist", "utf-8", &list)) | |
| return NULL; | |
| if (strlen(list) >= INT_MAX) { | |
| PyErr_SetString(PyExc_OverflowError, "string is too long"); | |
| PyMem_Free(list); | |
| return NULL; | |
| } | |
| if (Tcl_SplitList(Tkapp_Interp(self), list, | |
| &argc, &argv) == TCL_ERROR) { | |
| PyMem_Free(list); | |
| return Tkinter_Error(self); | |
| } | |
| if (!(v = PyTuple_New(argc))) | |
| goto finally; | |
| for (i = 0; i < argc; i++) { | |
| PyObject *s = unicodeFromTclString(argv[i]); | |
| if (!s) { | |
| Py_SETREF(v, NULL); | |
| goto finally; | |
| } | |
| PyTuple_SET_ITEM(v, i, s); | |
| } | |
| finally: | |
| ckfree(FREECAST argv); | |
| PyMem_Free(list); | |
| return v; | |
| } | |
| /** Tcl Command **/ | |
| /* Client data struct */ | |
| typedef struct { | |
| TkappObject *self; | |
| PyObject *func; | |
| } PythonCmd_ClientData; | |
| static int | |
| PythonCmd_Error(Tcl_Interp *interp) | |
| { | |
| errorInCmd = 1; | |
| excInCmd = PyErr_GetRaisedException(); | |
| LEAVE_PYTHON | |
| return TCL_ERROR; | |
| } | |
| /* This is the Tcl command that acts as a wrapper for Python | |
| * function or method. | |
| */ | |
| static int | |
| PythonCmd(ClientData clientData, Tcl_Interp *interp, | |
| int objc, Tcl_Obj *const objv[]) | |
| { | |
| PythonCmd_ClientData *data = (PythonCmd_ClientData *)clientData; | |
| PyObject *args, *res; | |
| int i; | |
| Tcl_Obj *obj_res; | |
| int objargs = data->self->wantobjects >= 2; | |
| ENTER_PYTHON | |
| /* Create argument tuple (objv1, ..., objvN) */ | |
| if (!(args = PyTuple_New(objc - 1))) | |
| return PythonCmd_Error(interp); | |
| for (i = 0; i < (objc - 1); i++) { | |
| PyObject *s = objargs ? FromObj(data->self, objv[i + 1]) | |
| : unicodeFromTclObj(data->self, objv[i + 1]); | |
| if (!s) { | |
| Py_DECREF(args); | |
| return PythonCmd_Error(interp); | |
| } | |
| PyTuple_SET_ITEM(args, i, s); | |
| } | |
| res = PyObject_Call(data->func, args, NULL); | |
| Py_DECREF(args); | |
| if (res == NULL) | |
| return PythonCmd_Error(interp); | |
| obj_res = AsObj(res); | |
| if (obj_res == NULL) { | |
| Py_DECREF(res); | |
| return PythonCmd_Error(interp); | |
| } | |
| Tcl_SetObjResult(interp, obj_res); | |
| Py_DECREF(res); | |
| LEAVE_PYTHON | |
| return TCL_OK; | |
| } | |
| static void | |
| PythonCmdDelete(ClientData clientData) | |
| { | |
| PythonCmd_ClientData *data = (PythonCmd_ClientData *)clientData; | |
| ENTER_PYTHON | |
| /* data->self is borrowed. */ | |
| Py_XDECREF(data->func); | |
| PyMem_Free(data); | |
| LEAVE_PYTHON | |
| } | |
| TCL_DECLARE_MUTEX(command_mutex) | |
| typedef struct CommandEvent{ | |
| Tcl_Event ev; | |
| Tcl_Interp* interp; | |
| const char *name; | |
| int create; | |
| int *status; | |
| ClientData *data; | |
| Tcl_Condition *done; | |
| } CommandEvent; | |
| static int | |
| Tkapp_CommandProc(Tcl_Event *evPtr, int flags) | |
| { | |
| CommandEvent *ev = (CommandEvent *)evPtr; | |
| if (ev->create) | |
| *ev->status = Tcl_CreateObjCommand( | |
| 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; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.createcommand | |
| name: str | |
| func: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name, | |
| PyObject *func) | |
| /*[clinic end generated code: output=2a1c79a4ee2af410 input=255785cb70edc6a0]*/ | |
| { | |
| PythonCmd_ClientData *data; | |
| int err; | |
| CHECK_STRING_LENGTH(name); | |
| if (!PyCallable_Check(func)) { | |
| PyErr_SetString(PyExc_TypeError, "command not callable"); | |
| return NULL; | |
| } | |
| if (self->threaded && self->thread_id != Tcl_GetCurrentThread() && | |
| !WaitForMainloop(self)) | |
| return NULL; | |
| TRACE(self, ("((ss()O))", "proc", name, func)); | |
| data = PyMem_NEW(PythonCmd_ClientData, 1); | |
| if (!data) | |
| return PyErr_NoMemory(); | |
| /* Borrow the interpreter: a strong reference would form an uncollectable | |
| cycle (interp -> command -> data->self -> interp) and leak the command | |
| (gh-80937). The command cannot outlive the interpreter. */ | |
| data->self = self; | |
| data->func = Py_NewRef(func); | |
| if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) { | |
| err = 0; // init to make static analyzers happy | |
| Tcl_Condition cond = NULL; | |
| CommandEvent *ev = (CommandEvent*)attemptckalloc(sizeof(CommandEvent)); | |
| if (ev == NULL) { | |
| PyErr_NoMemory(); | |
| PyMem_Free(data); | |
| return NULL; | |
| } | |
| ev->ev.proc = Tkapp_CommandProc; | |
| ev->interp = self->interp; | |
| ev->create = 1; | |
| ev->name = name; | |
| ev->data = (ClientData)data; | |
| ev->status = &err; | |
| ev->done = &cond; | |
| Tkapp_ThreadSend(self, (Tcl_Event*)ev, &cond, &command_mutex); | |
| Tcl_ConditionFinalize(&cond); | |
| } | |
| else | |
| { | |
| ENTER_TCL | |
| err = Tcl_CreateObjCommand( | |
| Tkapp_Interp(self), name, PythonCmd, | |
| (ClientData)data, PythonCmdDelete) == NULL; | |
| LEAVE_TCL | |
| } | |
| if (err) { | |
| PyErr_SetString(Tkinter_TclError, "can't create Tcl command"); | |
| Py_DECREF(data->func); | |
| PyMem_Free(data); | |
| return NULL; | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.deletecommand | |
| name: str | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_deletecommand_impl(TkappObject *self, const char *name) | |
| /*[clinic end generated code: output=a67e8cb5845e0d2d input=53e9952eae1f85f5]*/ | |
| { | |
| int err; | |
| CHECK_STRING_LENGTH(name); | |
| TRACE(self, ("((sss))", "rename", name, "")); | |
| if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) { | |
| err = 0; // init to make static analyzers happy | |
| Tcl_Condition cond = NULL; | |
| CommandEvent *ev; | |
| ev = (CommandEvent*)attemptckalloc(sizeof(CommandEvent)); | |
| if (ev == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| ev->ev.proc = Tkapp_CommandProc; | |
| ev->interp = self->interp; | |
| ev->create = 0; | |
| ev->name = name; | |
| ev->status = &err; | |
| ev->done = &cond; | |
| Tkapp_ThreadSend(self, (Tcl_Event*)ev, &cond, | |
| &command_mutex); | |
| Tcl_ConditionFinalize(&cond); | |
| } | |
| else | |
| { | |
| ENTER_TCL | |
| err = Tcl_DeleteCommand(self->interp, name); | |
| LEAVE_TCL | |
| } | |
| if (err == -1) { | |
| PyErr_SetString(Tkinter_TclError, "can't delete Tcl command"); | |
| return NULL; | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| #ifdef HAVE_CREATEFILEHANDLER | |
| /** File Handler **/ | |
| typedef struct _fhcdata { | |
| PyObject *func; | |
| PyObject *file; | |
| int id; | |
| struct _fhcdata *next; | |
| } FileHandler_ClientData; | |
| static FileHandler_ClientData *HeadFHCD; | |
| static FileHandler_ClientData * | |
| NewFHCD(PyObject *func, PyObject *file, int id) | |
| { | |
| FileHandler_ClientData *p; | |
| p = PyMem_NEW(FileHandler_ClientData, 1); | |
| if (p != NULL) { | |
| p->func = Py_XNewRef(func); | |
| p->file = Py_XNewRef(file); | |
| p->id = id; | |
| p->next = HeadFHCD; | |
| HeadFHCD = p; | |
| } | |
| return p; | |
| } | |
| static void | |
| DeleteFHCD(int id) | |
| { | |
| FileHandler_ClientData *p, **pp; | |
| pp = &HeadFHCD; | |
| while ((p = *pp) != NULL) { | |
| if (p->id == id) { | |
| *pp = p->next; | |
| Py_XDECREF(p->func); | |
| Py_XDECREF(p->file); | |
| PyMem_Free(p); | |
| } | |
| else | |
| pp = &p->next; | |
| } | |
| } | |
| static void | |
| FileHandler(ClientData clientData, int mask) | |
| { | |
| FileHandler_ClientData *data = (FileHandler_ClientData *)clientData; | |
| PyObject *func, *file, *res; | |
| ENTER_PYTHON | |
| func = data->func; | |
| file = data->file; | |
| res = PyObject_CallFunction(func, "Oi", file, mask); | |
| if (res == NULL) { | |
| errorInCmd = 1; | |
| excInCmd = PyErr_GetRaisedException(); | |
| } | |
| Py_XDECREF(res); | |
| LEAVE_PYTHON | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.createfilehandler | |
| file: object | |
| mask: int | |
| func: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_createfilehandler_impl(TkappObject *self, PyObject *file, | |
| int mask, PyObject *func) | |
| /*[clinic end generated code: output=f73ce82de801c353 input=84943a5286e47947]*/ | |
| { | |
| FileHandler_ClientData *data; | |
| int tfile; | |
| CHECK_TCL_APPARTMENT(self); | |
| tfile = PyObject_AsFileDescriptor(file); | |
| if (tfile < 0) | |
| return NULL; | |
| if (!PyCallable_Check(func)) { | |
| PyErr_SetString(PyExc_TypeError, "bad argument list"); | |
| return NULL; | |
| } | |
| TRACE(self, ("((ssiiO))", "#", "createfilehandler", tfile, mask, func)); | |
| data = NewFHCD(func, file, tfile); | |
| if (data == NULL) | |
| return NULL; | |
| /* Ought to check for null Tcl_File object... */ | |
| ENTER_TCL | |
| Tcl_CreateFileHandler(tfile, mask, FileHandler, (ClientData) data); | |
| LEAVE_TCL | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.deletefilehandler | |
| file: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_deletefilehandler_impl(TkappObject *self, PyObject *file) | |
| /*[clinic end generated code: output=30b2c6ed195b0410 input=abbec19d66312e2a]*/ | |
| { | |
| int tfile; | |
| CHECK_TCL_APPARTMENT(self); | |
| tfile = PyObject_AsFileDescriptor(file); | |
| if (tfile < 0) | |
| return NULL; | |
| TRACE(self, ("((ssi))", "#", "deletefilehandler", tfile)); | |
| DeleteFHCD(tfile); | |
| /* Ought to check for null Tcl_File object... */ | |
| ENTER_TCL | |
| Tcl_DeleteFileHandler(tfile); | |
| LEAVE_TCL | |
| Py_RETURN_NONE; | |
| } | |
| #endif /* HAVE_CREATEFILEHANDLER */ | |
| /**** Tktt Object (timer token) ****/ | |
| static PyObject *Tktt_Type; | |
| #define TkttObject_Check(op) \ | |
| PyObject_TypeCheck((op), (PyTypeObject *)Tktt_Type) | |
| typedef struct { | |
| PyObject_HEAD | |
| Tcl_TimerToken token; | |
| PyObject *func; | |
| } TkttObject; | |
| #define TkttObject_CAST(op) (assert(TkttObject_Check(op)), (TkttObject *)(op)) | |
| /*[clinic input] | |
| _tkinter.tktimertoken.deletetimerhandler | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tktimertoken_deletetimerhandler_impl(TkttObject *self) | |
| /*[clinic end generated code: output=bd7fe17f328cfa55 input=40bd070ff85f5cf3]*/ | |
| { | |
| TkttObject *v = self; | |
| PyObject *func = v->func; | |
| if (v->token != NULL) { | |
| /* TRACE(...) */ | |
| Tcl_DeleteTimerHandler(v->token); | |
| v->token = NULL; | |
| } | |
| if (func != NULL) { | |
| v->func = NULL; | |
| Py_DECREF(func); | |
| Py_DECREF(v); /* See Tktt_New() */ | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| static TkttObject * | |
| Tktt_New(PyObject *func) | |
| { | |
| TkttObject *v; | |
| PyTypeObject *tp = (PyTypeObject *)Tktt_Type; | |
| v = (TkttObject *)tp->tp_alloc(tp, 0); | |
| if (v == NULL) | |
| return NULL; | |
| v->token = NULL; | |
| v->func = Py_NewRef(func); | |
| /* Extra reference, deleted when called or when handler is deleted */ | |
| return (TkttObject*)Py_NewRef(v); | |
| } | |
| /* Plain cast, not TkttObject_CAST: the GC can run at shutdown after | |
| module_clear() has cleared the global Tktt_Type the macro checks against. */ | |
| static int | |
| Tktt_Clear(PyObject *op) | |
| { | |
| TkttObject *self = (TkttObject *)op; | |
| Py_CLEAR(self->func); | |
| return 0; | |
| } | |
| static int | |
| Tktt_Traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| TkttObject *self = (TkttObject *)op; | |
| Py_VISIT(Py_TYPE(op)); | |
| /* Not the extra reference of a pending timer (see Tktt_New): it is owned | |
| by the Tcl event loop, so the timer is a GC root, not part of a cycle. */ | |
| Py_VISIT(self->func); | |
| return 0; | |
| } | |
| static void | |
| Tktt_Dealloc(PyObject *op) | |
| { | |
| TkttObject *self = (TkttObject *)op; /* see GC slots above */ | |
| PyTypeObject *tp = Py_TYPE(op); | |
| PyObject_GC_UnTrack(op); | |
| /* Cancel any pending timer so its callback cannot fire on freed memory. */ | |
| if (self->token != NULL) { | |
| Tcl_DeleteTimerHandler(self->token); | |
| self->token = NULL; | |
| } | |
| (void)Tktt_Clear(op); | |
| tp->tp_free(op); | |
| Py_DECREF(tp); | |
| } | |
| static PyObject * | |
| Tktt_Repr(PyObject *self) | |
| { | |
| TkttObject *v = TkttObject_CAST(self); | |
| return PyUnicode_FromFormat("<tktimertoken at %p%s>", | |
| v, | |
| v->func == NULL ? ", handler deleted" : ""); | |
| } | |
| /** Timer Handler **/ | |
| static void | |
| TimerHandler(ClientData clientData) | |
| { | |
| TkttObject *v = TkttObject_CAST(clientData); | |
| PyObject *func = v->func; | |
| PyObject *res; | |
| if (func == NULL) | |
| return; | |
| v->func = NULL; | |
| ENTER_PYTHON | |
| res = PyObject_CallNoArgs(func); | |
| Py_DECREF(func); | |
| Py_DECREF(v); /* See Tktt_New() */ | |
| if (res == NULL) { | |
| errorInCmd = 1; | |
| excInCmd = PyErr_GetRaisedException(); | |
| } | |
| else | |
| Py_DECREF(res); | |
| LEAVE_PYTHON | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.createtimerhandler | |
| milliseconds: int | |
| func: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_createtimerhandler_impl(TkappObject *self, int milliseconds, | |
| PyObject *func) | |
| /*[clinic end generated code: output=2da5959b9d031911 input=ba6729f32f0277a5]*/ | |
| { | |
| TkttObject *v; | |
| if (!PyCallable_Check(func)) { | |
| PyErr_SetString(PyExc_TypeError, "bad argument list"); | |
| return NULL; | |
| } | |
| CHECK_TCL_APPARTMENT(self); | |
| TRACE(self, ("((siO))", "after", milliseconds, func)); | |
| v = Tktt_New(func); | |
| if (v) { | |
| v->token = Tcl_CreateTimerHandler(milliseconds, TimerHandler, | |
| (ClientData)v); | |
| } | |
| return (PyObject *) v; | |
| } | |
| /** Event Loop **/ | |
| /*[clinic input] | |
| _tkinter.tkapp.mainloop | |
| threshold: int = 0 | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_mainloop_impl(TkappObject *self, int threshold) | |
| /*[clinic end generated code: output=0ba8eabbe57841b0 input=036bcdcf03d5eca0]*/ | |
| { | |
| PyThreadState *tstate = PyThreadState_Get(); | |
| CHECK_TCL_APPARTMENT(self); | |
| self->dispatching = 1; | |
| quitMainLoop = 0; | |
| while (Tk_GetNumMainWindows() > threshold && | |
| !quitMainLoop && | |
| !errorInCmd) | |
| { | |
| int result; | |
| 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(Tkinter_busywaitinterval); | |
| Py_END_ALLOW_THREADS | |
| } | |
| if (PyErr_CheckSignals() != 0) { | |
| self->dispatching = 0; | |
| return NULL; | |
| } | |
| if (result < 0) | |
| break; | |
| } | |
| self->dispatching = 0; | |
| quitMainLoop = 0; | |
| if (errorInCmd) { | |
| errorInCmd = 0; | |
| PyErr_SetRaisedException(excInCmd); | |
| excInCmd = NULL; | |
| return NULL; | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.dooneevent | |
| flags: int = 0 | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_dooneevent_impl(TkappObject *self, int flags) | |
| /*[clinic end generated code: output=27c6b2aa464cac29 input=6542b928e364b793]*/ | |
| { | |
| int rv; | |
| ENTER_TCL | |
| rv = Tcl_DoOneEvent(flags); | |
| LEAVE_TCL | |
| return PyLong_FromLong(rv); | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.quit | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_quit_impl(TkappObject *self) | |
| /*[clinic end generated code: output=7f21eeff481f754f input=e03020dc38aff23c]*/ | |
| { | |
| quitMainLoop = 1; | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.interpaddr | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_interpaddr_impl(TkappObject *self) | |
| /*[clinic end generated code: output=6caaae3273b3c95a input=2dd32cbddb55a111]*/ | |
| { | |
| return PyLong_FromVoidPtr(Tkapp_Interp(self)); | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.loadtk | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_loadtk_impl(TkappObject *self) | |
| /*[clinic end generated code: output=e9e10a954ce46d2a input=b5e82afedd6354f0]*/ | |
| { | |
| Tcl_Interp *interp = Tkapp_Interp(self); | |
| const char * _tk_exists = NULL; | |
| int err; | |
| /* We want to guard against calling Tk_Init() multiple times */ | |
| CHECK_TCL_APPARTMENT(self); | |
| ENTER_TCL | |
| err = Tcl_Eval(Tkapp_Interp(self), "info exists tk_version"); | |
| ENTER_OVERLAP | |
| if (err == TCL_ERROR) { | |
| /* This sets an exception, but we cannot return right | |
| away because we need to exit the overlap first. */ | |
| Tkinter_Error(self); | |
| } else { | |
| _tk_exists = Tcl_GetStringResult(Tkapp_Interp(self)); | |
| } | |
| LEAVE_OVERLAP_TCL | |
| if (err == TCL_ERROR) { | |
| return NULL; | |
| } | |
| if (_tk_exists == NULL || strcmp(_tk_exists, "1") != 0) { | |
| if (Tkinter_TkInit(interp) == TCL_ERROR) { | |
| Tkinter_Error(self); | |
| return NULL; | |
| } | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.wantobjects | |
| value: int = -1 | |
| / | |
| Return the current setting, or set it if the argument is not -1. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_wantobjects_impl(TkappObject *self, int value) | |
| /*[clinic end generated code: output=10ce2bffa4adb3a4 input=69a4045e2c202385]*/ | |
| { | |
| if (value == -1) { | |
| return PyLong_FromLong(self->wantobjects); | |
| } | |
| self->wantobjects = value; | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.settrace | |
| func: object | |
| / | |
| Set the tracing function. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_settrace_impl(TkappObject *self, PyObject *func) | |
| /*[clinic end generated code: output=8c59938bc9005607 input=31b260d46d3d018a]*/ | |
| { | |
| if (func == Py_None) { | |
| func = NULL; | |
| } | |
| else { | |
| Py_INCREF(func); | |
| } | |
| Py_XSETREF(self->trace, func); | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.gettrace | |
| Get the tracing function. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_gettrace_impl(TkappObject *self) | |
| /*[clinic end generated code: output=d4e2ba7d63e77bb5 input=ac2aea5be74e8c4c]*/ | |
| { | |
| PyObject *func = self->trace; | |
| if (!func) { | |
| func = Py_None; | |
| } | |
| Py_INCREF(func); | |
| return func; | |
| } | |
| /*[clinic input] | |
| _tkinter.tkapp.willdispatch | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_tkapp_willdispatch_impl(TkappObject *self) | |
| /*[clinic end generated code: output=0e3f46d244642155 input=d88f5970843d6dab]*/ | |
| { | |
| self->dispatching = 1; | |
| Py_RETURN_NONE; | |
| } | |
| /**** Tkapp Type Methods ****/ | |
| /* Plain casts -- see the Tktt GC slots above. */ | |
| static int | |
| Tkapp_Clear(PyObject *op) | |
| { | |
| TkappObject *self = (TkappObject *)op; | |
| Py_CLEAR(self->trace); | |
| return 0; | |
| } | |
| static int | |
| Tkapp_Traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| TkappObject *self = (TkappObject *)op; | |
| Py_VISIT(Py_TYPE(op)); | |
| Py_VISIT(self->trace); | |
| return 0; | |
| } | |
| static void | |
| Tkapp_Dealloc(PyObject *op) | |
| { | |
| TkappObject *self = (TkappObject *)op; | |
| PyTypeObject *tp = Py_TYPE(op); | |
| PyObject_GC_UnTrack(op); | |
| if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) { | |
| /* Deleting the interpreter from another thread aborts the process | |
| ("Tcl_AsyncDelete: async handler deleted by the wrong thread"). | |
| Leak it instead (gh-83274). */ | |
| if (PyErr_WarnEx(PyExc_RuntimeWarning, | |
| "the Tcl interpreter is leaked because it was " | |
| "deallocated in a thread other than the one it was " | |
| "created in (see gh-83274)", 1) < 0) | |
| { | |
| PyErr_FormatUnraisable("Exception ignored while finalizing " | |
| "a Tcl interpreter"); | |
| } | |
| } | |
| else { | |
| ENTER_TCL | |
| Tcl_DeleteInterp(Tkapp_Interp(self)); | |
| LEAVE_TCL | |
| } | |
| (void)Tkapp_Clear(op); | |
| tp->tp_free(op); | |
| Py_DECREF(tp); | |
| DisableEventHook(); | |
| } | |
| /**** Tkinter Module ****/ | |
| typedef struct { | |
| PyObject* tuple; | |
| Py_ssize_t size; /* current size */ | |
| Py_ssize_t maxsize; /* allocated size */ | |
| } FlattenContext; | |
| static int | |
| _bump(FlattenContext* context, Py_ssize_t size) | |
| { | |
| /* expand tuple to hold (at least) size new items. | |
| return true if successful, false if an exception was raised */ | |
| Py_ssize_t maxsize = context->maxsize * 2; /* never overflows */ | |
| if (maxsize < context->size + size) | |
| maxsize = context->size + size; /* never overflows */ | |
| context->maxsize = maxsize; | |
| return _PyTuple_Resize(&context->tuple, maxsize) >= 0; | |
| } | |
| static int | |
| _flatten1(FlattenContext* context, PyObject* item, int depth) | |
| { | |
| /* add tuple or list to argument tuple (recursively) */ | |
| Py_ssize_t i, size; | |
| if (depth > 1000) { | |
| PyErr_SetString(PyExc_ValueError, | |
| "nesting too deep in _flatten"); | |
| return 0; | |
| } else if (PyTuple_Check(item) || PyList_Check(item)) { | |
| size = PySequence_Fast_GET_SIZE(item); | |
| /* preallocate (assume no nesting) */ | |
| if (context->size + size > context->maxsize && | |
| !_bump(context, size)) | |
| return 0; | |
| /* copy items to output tuple */ | |
| for (i = 0; i < size; i++) { | |
| PyObject *o = PySequence_Fast_GET_ITEM(item, i); | |
| if (PyList_Check(o) || PyTuple_Check(o)) { | |
| if (!_flatten1(context, o, depth + 1)) | |
| return 0; | |
| } else if (o != Py_None) { | |
| if (context->size + 1 > context->maxsize && | |
| !_bump(context, 1)) | |
| return 0; | |
| PyTuple_SET_ITEM(context->tuple, | |
| context->size++, Py_NewRef(o)); | |
| } | |
| } | |
| } else { | |
| PyErr_SetString(PyExc_TypeError, "argument must be sequence"); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| /*[clinic input] | |
| _tkinter._flatten | |
| item: object | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter__flatten(PyObject *module, PyObject *item) | |
| /*[clinic end generated code: output=cad02a3f97f29862 input=6b9c12260aa1157f]*/ | |
| { | |
| FlattenContext context; | |
| context.maxsize = PySequence_Size(item); | |
| if (context.maxsize < 0) | |
| return NULL; | |
| if (context.maxsize == 0) | |
| return PyTuple_New(0); | |
| context.tuple = PyTuple_New(context.maxsize); | |
| if (!context.tuple) | |
| return NULL; | |
| context.size = 0; | |
| if (!_flatten1(&context, item, 0)) { | |
| Py_XDECREF(context.tuple); | |
| return NULL; | |
| } | |
| if (_PyTuple_Resize(&context.tuple, context.size)) | |
| return NULL; | |
| return context.tuple; | |
| } | |
| /*[clinic input] | |
| _tkinter.create | |
| screenName: str(accept={str, NoneType}) = None | |
| baseName: str = "" | |
| className: str = "Tk" | |
| interactive: bool = False | |
| wantobjects: int = 0 | |
| wantTk: bool = True | |
| if false, then Tk_Init() doesn't get called | |
| sync: bool = False | |
| if true, then pass -sync to wish | |
| use: str(accept={str, NoneType}) = None | |
| if not None, then pass -use to wish | |
| / | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_create_impl(PyObject *module, const char *screenName, | |
| const char *baseName, const char *className, | |
| int interactive, int wantobjects, int wantTk, int sync, | |
| const char *use) | |
| /*[clinic end generated code: output=e3315607648e6bb4 input=7e382ba431bed537]*/ | |
| { | |
| /* XXX baseName is not used anymore; | |
| * try getting rid of it. */ | |
| CHECK_STRING_LENGTH(screenName); | |
| CHECK_STRING_LENGTH(baseName); | |
| CHECK_STRING_LENGTH(className); | |
| CHECK_STRING_LENGTH(use); | |
| #if TCL_MAJOR_VERSION < 9 | |
| /* className is title-cased during Tk initialization. Tcl 8.x does not | |
| * support non-BMP characters (encoded as 4-byte UTF-8 sequences) there | |
| * and crashes in Tcl_UtfToTitle (see gh-126219). Reject them up front. */ | |
| for (const unsigned char *p = (const unsigned char *)className; *p; p++) { | |
| if (*p >= 0xF0) { | |
| PyErr_SetString(PyExc_ValueError, | |
| "className must not contain non-BMP characters " | |
| "with this version of Tcl/Tk"); | |
| return NULL; | |
| } | |
| } | |
| #endif | |
| return (PyObject *) Tkapp_New(screenName, className, | |
| interactive, wantobjects, wantTk, | |
| sync, use); | |
| } | |
| /*[clinic input] | |
| @permit_long_summary | |
| _tkinter.setbusywaitinterval | |
| new_val: int | |
| / | |
| Set the busy-wait interval in milliseconds between successive calls to Tcl_DoOneEvent in a threaded Python interpreter. | |
| It should be set to a divisor of the maximum time between frames in | |
| an animation. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| _tkinter_setbusywaitinterval_impl(PyObject *module, int new_val) | |
| /*[clinic end generated code: output=42bf7757dc2d0ab6 input=0360dd95c8bd8619]*/ | |
| { | |
| if (new_val < 0) { | |
| PyErr_SetString(PyExc_ValueError, | |
| "busywaitinterval must be >= 0"); | |
| return NULL; | |
| } | |
| Tkinter_busywaitinterval = new_val; | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| @permit_long_summary | |
| _tkinter.getbusywaitinterval -> int | |
| Return the current busy-wait interval between successive calls to Tcl_DoOneEvent in a threaded Python interpreter. | |
| [clinic start generated code]*/ | |
| static int | |
| _tkinter_getbusywaitinterval_impl(PyObject *module) | |
| /*[clinic end generated code: output=23b72d552001f5c7 input=62d5b36ddab3976b]*/ | |
| { | |
| return Tkinter_busywaitinterval; | |
| } | |
| #include "clinic/_tkinter.c.h" | |
| static PyMethodDef Tktt_methods[] = | |
| { | |
| _TKINTER_TKTIMERTOKEN_DELETETIMERHANDLER_METHODDEF | |
| {NULL, NULL} | |
| }; | |
| static PyType_Slot Tktt_Type_slots[] = { | |
| {Py_tp_dealloc, Tktt_Dealloc}, | |
| {Py_tp_traverse, Tktt_Traverse}, | |
| {Py_tp_clear, Tktt_Clear}, | |
| {Py_tp_repr, Tktt_Repr}, | |
| {Py_tp_methods, Tktt_methods}, | |
| {0, 0} | |
| }; | |
| static PyType_Spec Tktt_Type_spec = { | |
| .name = "_tkinter.tktimertoken", | |
| .basicsize = sizeof(TkttObject), | |
| .flags = ( | |
| Py_TPFLAGS_DEFAULT | |
| | Py_TPFLAGS_DISALLOW_INSTANTIATION | |
| | Py_TPFLAGS_IMMUTABLETYPE | |
| | Py_TPFLAGS_HAVE_GC | |
| ), | |
| .slots = Tktt_Type_slots, | |
| }; | |
| /**** Tkapp Method List ****/ | |
| static PyMethodDef Tkapp_methods[] = | |
| { | |
| _TKINTER_TKAPP_WILLDISPATCH_METHODDEF | |
| _TKINTER_TKAPP_WANTOBJECTS_METHODDEF | |
| _TKINTER_TKAPP_SETTRACE_METHODDEF | |
| _TKINTER_TKAPP_GETTRACE_METHODDEF | |
| _TKINTER_TKAPP_CALL_METHODDEF | |
| _TKINTER_TKAPP_EVAL_METHODDEF | |
| _TKINTER_TKAPP_EVALFILE_METHODDEF | |
| _TKINTER_TKAPP_RECORD_METHODDEF | |
| _TKINTER_TKAPP_ADDERRORINFO_METHODDEF | |
| {"setvar", Tkapp_SetVar, METH_VARARGS}, | |
| {"globalsetvar", Tkapp_GlobalSetVar, METH_VARARGS}, | |
| {"getvar", Tkapp_GetVar, METH_VARARGS}, | |
| {"globalgetvar", Tkapp_GlobalGetVar, METH_VARARGS}, | |
| {"unsetvar", Tkapp_UnsetVar, METH_VARARGS}, | |
| {"globalunsetvar", Tkapp_GlobalUnsetVar, METH_VARARGS}, | |
| _TKINTER_TKAPP_GETINT_METHODDEF | |
| _TKINTER_TKAPP_GETDOUBLE_METHODDEF | |
| _TKINTER_TKAPP_GETBOOLEAN_METHODDEF | |
| _TKINTER_TKAPP_EXPRSTRING_METHODDEF | |
| _TKINTER_TKAPP_EXPRLONG_METHODDEF | |
| _TKINTER_TKAPP_EXPRDOUBLE_METHODDEF | |
| _TKINTER_TKAPP_EXPRBOOLEAN_METHODDEF | |
| _TKINTER_TKAPP_SPLITLIST_METHODDEF | |
| _TKINTER_TKAPP_CREATECOMMAND_METHODDEF | |
| _TKINTER_TKAPP_DELETECOMMAND_METHODDEF | |
| _TKINTER_TKAPP_CREATEFILEHANDLER_METHODDEF | |
| _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF | |
| _TKINTER_TKAPP_CREATETIMERHANDLER_METHODDEF | |
| _TKINTER_TKAPP_MAINLOOP_METHODDEF | |
| _TKINTER_TKAPP_DOONEEVENT_METHODDEF | |
| _TKINTER_TKAPP_QUIT_METHODDEF | |
| _TKINTER_TKAPP_INTERPADDR_METHODDEF | |
| _TKINTER_TKAPP_LOADTK_METHODDEF | |
| {NULL, NULL} | |
| }; | |
| static PyType_Slot Tkapp_Type_slots[] = { | |
| {Py_tp_dealloc, Tkapp_Dealloc}, | |
| {Py_tp_traverse, Tkapp_Traverse}, | |
| {Py_tp_clear, Tkapp_Clear}, | |
| {Py_tp_methods, Tkapp_methods}, | |
| {0, 0} | |
| }; | |
| static PyType_Spec Tkapp_Type_spec = { | |
| .name = "_tkinter.tkapp", | |
| .basicsize = sizeof(TkappObject), | |
| .flags = ( | |
| Py_TPFLAGS_DEFAULT | |
| | Py_TPFLAGS_DISALLOW_INSTANTIATION | |
| | Py_TPFLAGS_IMMUTABLETYPE | |
| | Py_TPFLAGS_HAVE_GC | |
| ), | |
| .slots = Tkapp_Type_slots, | |
| }; | |
| static PyMethodDef moduleMethods[] = | |
| { | |
| _TKINTER__FLATTEN_METHODDEF | |
| _TKINTER_CREATE_METHODDEF | |
| _TKINTER_SETBUSYWAITINTERVAL_METHODDEF | |
| _TKINTER_GETBUSYWAITINTERVAL_METHODDEF | |
| {NULL, NULL} | |
| }; | |
| #ifdef WAIT_FOR_STDIN | |
| #ifndef MS_WINDOWS | |
| static int stdin_ready = 0; | |
| static void | |
| MyFileProc(void *clientData, int mask) | |
| { | |
| int tfile = (int)(Py_intptr_t)clientData; | |
| stdin_ready = 1; | |
| /* Stop watching stdin now that input is available. Doing it here rather | |
| than after the loop below ensures that a nested event loop (e.g. the one | |
| started by wait_variable) does not keep waking up on the same unread | |
| input, spinning at 100% CPU. */ | |
| Tcl_DeleteFileHandler(tfile); | |
| } | |
| #endif | |
| static PyThreadState *event_tstate = NULL; | |
| static int | |
| EventHook(void) | |
| { | |
| #ifdef MS_WINDOWS | |
| HANDLE hStdin; | |
| DWORD type; | |
| #else | |
| int tfile; | |
| stdin_ready = 0; | |
| #endif | |
| PyEval_RestoreThread(event_tstate); | |
| errorInCmd = 0; | |
| #ifdef MS_WINDOWS | |
| hStdin = GetStdHandle(STD_INPUT_HANDLE); | |
| type = GetFileType(hStdin); | |
| while (1) { | |
| #else | |
| tfile = fileno(stdin); | |
| Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc, | |
| (void *)(Py_intptr_t)tfile); | |
| while (!stdin_ready) { | |
| #endif | |
| int result; | |
| #ifdef MS_WINDOWS | |
| if (type == FILE_TYPE_CHAR) { | |
| if (_kbhit()) break; | |
| } | |
| else if (type == FILE_TYPE_PIPE) { | |
| DWORD available; | |
| if (PeekNamedPipe(hStdin, NULL, 0, NULL, &available, NULL)) { | |
| if (available > 0) break; | |
| } | |
| else { | |
| if (GetLastError() == ERROR_BROKEN_PIPE) break; | |
| } | |
| } | |
| else if (type == FILE_TYPE_DISK) { | |
| break; | |
| } | |
| #endif | |
| Py_BEGIN_ALLOW_THREADS | |
| if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); | |
| tcl_tstate = event_tstate; | |
| result = Tcl_DoOneEvent(TCL_DONT_WAIT); | |
| tcl_tstate = NULL; | |
| if(tcl_lock)PyThread_release_lock(tcl_lock); | |
| if (result == 0) | |
| Sleep(Tkinter_busywaitinterval); | |
| Py_END_ALLOW_THREADS | |
| /* Report an exception raised in a callback, but keep pumping events | |
| instead of returning to the prompt: without readline there is no | |
| input waiting on stdin yet, so returning here would block in fgets | |
| until the user hits enter, freezing later callbacks. */ | |
| if (errorInCmd) { | |
| errorInCmd = 0; | |
| PyErr_SetRaisedException(excInCmd); | |
| excInCmd = NULL; | |
| PyErr_Print(); | |
| } | |
| if (result < 0) | |
| break; | |
| } | |
| #ifndef MS_WINDOWS | |
| if (!stdin_ready) | |
| Tcl_DeleteFileHandler(tfile); | |
| #endif | |
| PyEval_SaveThread(); | |
| return 0; | |
| } | |
| #endif | |
| static void | |
| EnableEventHook(void) | |
| { | |
| #ifdef WAIT_FOR_STDIN | |
| if (PyOS_InputHook == NULL) { | |
| event_tstate = PyThreadState_Get(); | |
| PyOS_InputHook = EventHook; | |
| } | |
| #endif | |
| } | |
| static void | |
| DisableEventHook(void) | |
| { | |
| #ifdef WAIT_FOR_STDIN | |
| if (Tk_GetNumMainWindows() == 0 && PyOS_InputHook == EventHook) { | |
| PyOS_InputHook = NULL; | |
| } | |
| #endif | |
| } | |
| static int | |
| module_clear(PyObject *Py_UNUSED(mod)) | |
| { | |
| Py_CLEAR(Tkinter_TclError); | |
| Py_CLEAR(Tkapp_Type); | |
| Py_CLEAR(Tktt_Type); | |
| Py_CLEAR(PyTclObject_Type); | |
| return 0; | |
| } | |
| static int | |
| module_traverse(PyObject *Py_UNUSED(module), visitproc visit, void *arg) | |
| { | |
| Py_VISIT(Tkinter_TclError); | |
| Py_VISIT(Tkapp_Type); | |
| Py_VISIT(Tktt_Type); | |
| Py_VISIT(PyTclObject_Type); | |
| return 0; | |
| } | |
| static void | |
| module_free(void *mod) | |
| { | |
| (void)module_clear((PyObject *)mod); | |
| } | |
| static struct PyModuleDef _tkintermodule = { | |
| PyModuleDef_HEAD_INIT, | |
| .m_name = "_tkinter", | |
| .m_size = -1, | |
| .m_methods = moduleMethods, | |
| .m_traverse = module_traverse, | |
| .m_clear = module_clear, | |
| .m_free = module_free | |
| }; | |
| PyMODINIT_FUNC | |
| PyInit__tkinter(void) | |
| { | |
| PyABIInfo_VAR(abi_info); | |
| if (PyABIInfo_Check(&abi_info, "_tkinter") < 0) { | |
| return NULL; | |
| } | |
| PyObject *m, *uexe, *cexe; | |
| tcl_lock = PyThread_allocate_lock(); | |
| if (tcl_lock == NULL) | |
| return PyErr_NoMemory(); | |
| m = PyModule_Create(&_tkintermodule); | |
| if (m == NULL) | |
| return NULL; | |
| #ifdef Py_GIL_DISABLED | |
| PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); | |
| #endif | |
| Tkinter_TclError = PyErr_NewException("_tkinter.TclError", NULL, NULL); | |
| if (PyModule_AddObjectRef(m, "TclError", Tkinter_TclError)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "READABLE", TCL_READABLE)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "WRITABLE", TCL_WRITABLE)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "EXCEPTION", TCL_EXCEPTION)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "WINDOW_EVENTS", TCL_WINDOW_EVENTS)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "FILE_EVENTS", TCL_FILE_EVENTS)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "TIMER_EVENTS", TCL_TIMER_EVENTS)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "IDLE_EVENTS", TCL_IDLE_EVENTS)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "ALL_EVENTS", TCL_ALL_EVENTS)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddIntConstant(m, "DONT_WAIT", TCL_DONT_WAIT)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddStringConstant(m, "TK_VERSION", TK_VERSION)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddStringConstant(m, "TCL_VERSION", TCL_VERSION)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddStringConstant(m, "TK_PATCH_LEVEL", TK_PATCH_LEVEL)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (PyModule_AddStringConstant(m, "TCL_PATCH_LEVEL", TCL_PATCH_LEVEL)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| Tkapp_Type = PyType_FromSpec(&Tkapp_Type_spec); | |
| if (PyModule_AddObjectRef(m, "TkappType", Tkapp_Type)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| Tktt_Type = PyType_FromSpec(&Tktt_Type_spec); | |
| if (PyModule_AddObjectRef(m, "TkttType", Tktt_Type)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| PyTclObject_Type = PyType_FromSpec(&PyTclObject_Type_spec); | |
| if (PyModule_AddObjectRef(m, "Tcl_Obj", PyTclObject_Type)) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| /* This helps the dynamic loader; in Unicode aware Tcl versions | |
| it also helps Tcl find its encodings. */ | |
| (void) PySys_GetOptionalAttrString("executable", &uexe); | |
| if (uexe && PyUnicode_Check(uexe)) { // sys.executable can be None | |
| cexe = PyUnicode_EncodeFSDefault(uexe); | |
| Py_DECREF(uexe); | |
| if (cexe) { | |
| #ifdef MS_WINDOWS | |
| int set_var = 0; | |
| PyObject *str_path; | |
| wchar_t *wcs_path; | |
| DWORD ret; | |
| ret = GetEnvironmentVariableW(L"TCL_LIBRARY", NULL, 0); | |
| if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { | |
| str_path = _get_tcl_lib_path(); | |
| if (str_path == NULL && PyErr_Occurred()) { | |
| Py_DECREF(cexe); | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| if (str_path != NULL) { | |
| wcs_path = PyUnicode_AsWideCharString(str_path, NULL); | |
| if (wcs_path == NULL) { | |
| Py_DECREF(cexe); | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| SetEnvironmentVariableW(L"TCL_LIBRARY", wcs_path); | |
| set_var = 1; | |
| } | |
| } | |
| Tcl_FindExecutable(PyBytes_AS_STRING(cexe)); | |
| if (set_var) { | |
| SetEnvironmentVariableW(L"TCL_LIBRARY", NULL); | |
| PyMem_Free(wcs_path); | |
| } | |
| #else | |
| Tcl_FindExecutable(PyBytes_AS_STRING(cexe)); | |
| #endif /* MS_WINDOWS */ | |
| } | |
| Py_XDECREF(cexe); | |
| } | |
| else { | |
| Py_XDECREF(uexe); | |
| } | |
| if (PyErr_Occurred()) { | |
| Py_DECREF(m); | |
| return NULL; | |
| } | |
| return m; | |
| } |