From 06cb94bc8419b9a24df6b0d724fcd8e40c6971d6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 4 Oct 2019 13:09:52 +0300 Subject: [PATCH] bpo-13153: Use OS native encoding for converting between Python and Tcl. (GH-16545) 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. Converting strings from Tcl to Python and back now never fails (except MemoryError). --- Lib/idlelib/editor.py | 29 +- Lib/idlelib/idle_test/test_editor.py | 12 - Lib/idlelib/pyshell.py | 18 - Lib/idlelib/runscript.py | 3 +- Lib/test/test_tcl.py | 5 + Lib/tkinter/test/test_tkinter/test_misc.py | 22 + Lib/tkinter/test/test_ttk/test_widgets.py | 13 +- .../2019-09-29-22-47-37.bpo-13153.0mO9qR.rst | 4 + Modules/_tkinter.c | 377 ++++++++++-------- 9 files changed, 241 insertions(+), 242 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-09-29-22-47-37.bpo-13153.0mO9qR.rst diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index b969f8c493b..adeed74059f 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -358,21 +358,6 @@ class EditorWindow(object): Font(text, font=text.cget('font')).measure('0') self.width = pixel_width // zero_char_width - def _filename_to_unicode(self, filename): - """Return filename as BMP unicode so displayable in Tk.""" - # Decode bytes to unicode. - if isinstance(filename, bytes): - try: - filename = filename.decode(self.filesystemencoding) - except UnicodeDecodeError: - try: - filename = filename.decode(self.encoding) - except UnicodeDecodeError: - # byte-to-byte conversion - filename = filename.decode('iso8859-1') - # Replace non-BMP char with diamond questionmark. - return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename) - def new_callback(self, event): dirname, basename = self.io.defaultfilename() self.flist.new(dirname) @@ -963,10 +948,8 @@ class EditorWindow(object): menu.delete(0, END) # clear, and rebuild: for i, file_name in enumerate(rf_list): file_name = file_name.rstrip() # zap \n - # make unicode string to display non-ASCII chars correctly - ufile_name = self._filename_to_unicode(file_name) callback = instance.__recent_file_callback(file_name) - menu.add_command(label=ulchars[i] + " " + ufile_name, + menu.add_command(label=ulchars[i] + " " + file_name, command=callback, underline=0) @@ -1004,16 +987,10 @@ class EditorWindow(object): def short_title(self): filename = self.io.filename - if filename: - filename = os.path.basename(filename) - else: - filename = "untitled" - # return unicode string to display non-ASCII chars correctly - return self._filename_to_unicode(filename) + return os.path.basename(filename) if filename else "untitled" def long_title(self): - # return unicode string to display non-ASCII chars correctly - return self._filename_to_unicode(self.io.filename or "") + return self.io.filename or "" def center_insert_event(self, event): self.center() diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 4af4ff0242d..240db71747a 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -30,18 +30,6 @@ class EditorWindowTest(unittest.TestCase): e._close() -class EditorFunctionTest(unittest.TestCase): - - def test_filename_to_unicode(self): - func = Editor._filename_to_unicode - class dummy(): - filesystemencoding = 'utf-8' - pairs = (('abc', 'abc'), ('a\U00011111c', 'a\ufffdc'), - (b'abc', 'abc'), (b'a\xf0\x91\x84\x91c', 'a\ufffdc')) - for inp, out in pairs: - self.assertEqual(func(dummy, inp), out) - - class TestGetLineIndent(unittest.TestCase): def test_empty_lines(self): for tabwidth in [1, 2, 4, 6, 8]: diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 2e4dfad4012..bc87d24eaab 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -679,14 +679,6 @@ class ModifiedInterpreter(InteractiveInterpreter): self.more = 0 # at the moment, InteractiveInterpreter expects str assert isinstance(source, str) - #if isinstance(source, str): - # from idlelib import iomenu - # try: - # source = source.encode(iomenu.encoding) - # except UnicodeError: - # self.tkconsole.resetoutput() - # self.write("Unsupported characters in input\n") - # return # InteractiveInterpreter.runsource() calls its runcode() method, # which is overridden (see below) return InteractiveInterpreter.runsource(self, source, filename) @@ -1298,16 +1290,6 @@ class PyShell(OutputWindow): self.set_line_and_column() def write(self, s, tags=()): - if isinstance(s, str) and len(s) and max(s) > '\uffff': - # Tk doesn't support outputting non-BMP characters - # Let's assume what printed string is not very long, - # find first non-BMP character and construct informative - # UnicodeEncodeError exception. - for start, char in enumerate(s): - if char > '\uffff': - break - raise UnicodeEncodeError("UCS-2", char, start, start+1, - 'Non-BMP character not supported in Tk') try: self.text.mark_gravity("iomark", "right") count = OutputWindow.write(self, s, tags, "iomark") diff --git a/Lib/idlelib/runscript.py b/Lib/idlelib/runscript.py index de73bf84581..e99d0d2e027 100644 --- a/Lib/idlelib/runscript.py +++ b/Lib/idlelib/runscript.py @@ -147,8 +147,7 @@ class ScriptBinding: interp = self.shell.interp if pyshell.use_subprocess and restart: interp.restart_subprocess( - with_cwd=False, filename= - self.editwin._filename_to_unicode(filename)) + with_cwd=False, filename=filename) dirname = os.path.dirname(filename) argv = [filename] if self.cli_args: diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index 80f1668bcec..3183ea850f7 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -429,9 +429,12 @@ class TclTest(unittest.TestCase): self.assertEqual(passValue(False), False if self.wantobjects else '0') self.assertEqual(passValue('string'), 'string') self.assertEqual(passValue('string\u20ac'), 'string\u20ac') + self.assertEqual(passValue('string\U0001f4bb'), 'string\U0001f4bb') self.assertEqual(passValue('str\x00ing'), 'str\x00ing') self.assertEqual(passValue('str\x00ing\xbd'), 'str\x00ing\xbd') self.assertEqual(passValue('str\x00ing\u20ac'), 'str\x00ing\u20ac') + self.assertEqual(passValue('str\x00ing\U0001f4bb'), + 'str\x00ing\U0001f4bb') self.assertEqual(passValue(b'str\x00ing'), b'str\x00ing' if self.wantobjects else 'str\x00ing') self.assertEqual(passValue(b'str\xc0\x80ing'), @@ -490,6 +493,7 @@ class TclTest(unittest.TestCase): check('string') check('string\xbd') check('string\u20ac') + check('string\U0001f4bb') check('') check(b'string', 'string') check(b'string\xe2\x82\xac', 'string\xe2\x82\xac') @@ -531,6 +535,7 @@ class TclTest(unittest.TestCase): ('a\n b\t\r c\n ', ('a', 'b', 'c')), (b'a\n b\t\r c\n ', ('a', 'b', 'c')), ('a \u20ac', ('a', '\u20ac')), + ('a \U0001f4bb', ('a', '\U0001f4bb')), (b'a \xe2\x82\xac', ('a', '\u20ac')), (b'a\xc0\x80b c\xc0\x80d', ('a\x00b', 'c\x00d')), ('a {b c}', ('a', 'b c')), diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 9d5a93ef6fb..1e089747a91 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -170,6 +170,28 @@ class MiscTest(AbstractTkTest, unittest.TestCase): with self.assertRaises(tkinter.TclError): root.tk.call('after', 'info', idle1) + def test_clipboard(self): + root = self.root + root.clipboard_clear() + root.clipboard_append('Ùñî') + self.assertEqual(root.clipboard_get(), 'Ùñî') + root.clipboard_append('çōđě') + self.assertEqual(root.clipboard_get(), 'Ùñîçōđě') + root.clipboard_clear() + with self.assertRaises(tkinter.TclError): + root.clipboard_get() + + def test_clipboard_astral(self): + root = self.root + root.clipboard_clear() + root.clipboard_append('𝔘𝔫𝔦') + self.assertEqual(root.clipboard_get(), '𝔘𝔫𝔦') + root.clipboard_append('𝔠𝔬𝔡𝔢') + self.assertEqual(root.clipboard_get(), '𝔘𝔫𝔦𝔠𝔬𝔡𝔢') + root.clipboard_clear() + with self.assertRaises(tkinter.TclError): + root.clipboard_get() + tests_gui = (MiscTest, ) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index 69e4b3ffa17..2598bc67652 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -489,8 +489,7 @@ class ComboboxTest(EntryTest, unittest.TestCase): expected=('mon', 'tue', 'wed', 'thur')) self.checkParam(self.combo, 'values', ('mon', 'tue', 'wed', 'thur')) self.checkParam(self.combo, 'values', (42, 3.14, '', 'any string')) - self.checkParam(self.combo, 'values', '', - expected='' if get_tk_patchlevel() < (8, 5, 10) else ()) + self.checkParam(self.combo, 'values', '') self.combo['values'] = ['a', 1, 'c'] @@ -1245,12 +1244,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): expected=('mon', 'tue', 'wed', 'thur')) self.checkParam(self.spin, 'values', ('mon', 'tue', 'wed', 'thur')) self.checkParam(self.spin, 'values', (42, 3.14, '', 'any string')) - self.checkParam( - self.spin, - 'values', - '', - expected='' if get_tk_patchlevel() < (8, 5, 10) else () - ) + self.checkParam(self.spin, 'values', '') self.spin['values'] = ['a', 1, 'c'] @@ -1308,8 +1302,7 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): self.checkParam(widget, 'columns', 'a b c', expected=('a', 'b', 'c')) self.checkParam(widget, 'columns', ('a', 'b', 'c')) - self.checkParam(widget, 'columns', (), - expected='' if get_tk_patchlevel() < (8, 5, 10) else ()) + self.checkParam(widget, 'columns', '') def test_displaycolumns(self): widget = self.create() diff --git a/Misc/NEWS.d/next/Library/2019-09-29-22-47-37.bpo-13153.0mO9qR.rst b/Misc/NEWS.d/next/Library/2019-09-29-22-47-37.bpo-13153.0mO9qR.rst new file mode 100644 index 00000000000..6f9561539d8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-09-29-22-47-37.bpo-13153.0mO9qR.rst @@ -0,0 +1,4 @@ +OS native encoding is now used for converting between Python strings and +Tcl objects. This allows to display, copy and paste to clipboard emoji and +other non-BMP characters. Converting strings from Tcl to Python and back +now never fails (except MemoryError). diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 94d5b649e04..c431d611d4d 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -96,6 +96,24 @@ Copyright (C) 1994 Steen Lumholt. #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 #define WAIT_FOR_STDIN @@ -290,7 +308,6 @@ typedef struct { } TkappObject; #define Tkapp_Interp(v) (((TkappObject *) (v))->interp) -#define Tkapp_Result(v) Tcl_GetStringResult(Tkapp_Interp(v)) #define DEBUG_REFCNT(v) (printf("DEBUG: id=%p, refcnt=%i\n", \ (void *) v, Py_REFCNT(v))) @@ -311,10 +328,16 @@ static int tk_load_failed = 0; #endif +static PyObject *Tkapp_UnicodeResult(TkappObject *); + static PyObject * -Tkinter_Error(PyObject *v) +Tkinter_Error(TkappObject *self) { - PyErr_SetString(Tkinter_TclError, Tkapp_Result(v)); + PyObject *res = Tkapp_UnicodeResult(self); + if (res != NULL) { + PyErr_SetObject(Tkinter_TclError, res); + Py_DECREF(res); + } return NULL; } @@ -368,30 +391,35 @@ static PyObject * unicodeFromTclStringAndSize(const char *s, Py_ssize_t size) { PyObject *r = PyUnicode_DecodeUTF8(s, size, NULL); - if (!r && PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) { - /* Tcl encodes null character as \xc0\x80 */ - if (memchr(s, '\xc0', size)) { - char *buf, *q; - const char *e = s + size; - PyErr_Clear(); - 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, NULL); - PyMem_Free(buf); + if (r != NULL || !PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) { + return r; + } + + char *buf = NULL; + PyErr_Clear(); + /* Tcl encodes null character as \xc0\x80 */ + 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); } return r; } @@ -406,8 +434,21 @@ static PyObject * unicodeFromTclObj(Tcl_Obj *value) { int len; - char *s = Tcl_GetStringFromObj(value, &len); +#if USE_TCL_UNICODE + 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(); +#else + const char *s = Tcl_GetStringFromObj(value, &len); return unicodeFromTclStringAndSize(s, len); +#endif } @@ -746,7 +787,7 @@ Tkapp_New(const char *screenName, const char *className, #endif if (Tcl_AppInit(v->interp) != TCL_OK) { - PyObject *result = Tkinter_Error((PyObject *)v); + PyObject *result = Tkinter_Error(v); #ifdef TKINTER_PROTECT_LOADTK if (wantTk) { const char *_tkinter_tk_failed; @@ -817,12 +858,6 @@ PyTclObject_dealloc(PyTclObject *self) Py_DECREF(tp); } -static const char * -PyTclObject_TclString(PyObject *self) -{ - return Tcl_GetString(((PyTclObject*)self)->value); -} - /* Like _str, but create Unicode if necessary. */ PyDoc_STRVAR(PyTclObject_string__doc__, "the string representation of this object, either as str or bytes"); @@ -1048,53 +1083,51 @@ AsObj(PyObject *value) } if (PyUnicode_Check(value)) { - void *inbuf; - Py_ssize_t size; - int kind; - Tcl_UniChar *outbuf = NULL; - Py_ssize_t i; - size_t allocsize; - if (PyUnicode_READY(value) == -1) return NULL; - inbuf = PyUnicode_DATA(value); - size = PyUnicode_GET_LENGTH(value); - if (size == 0) - return Tcl_NewUnicodeObj((const void *)"", 0); + 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; } - kind = PyUnicode_KIND(value); - if (kind == sizeof(Tcl_UniChar)) - return Tcl_NewUnicodeObj(inbuf, (int)size); - allocsize = ((size_t)size) * sizeof(Tcl_UniChar); - outbuf = (Tcl_UniChar*)PyMem_Malloc(allocsize); - /* Else overflow occurred, and we take the next exit */ - if (!outbuf) { - PyErr_NoMemory(); + if (PyUnicode_IS_ASCII(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(); +#else + encoded = _PyUnicode_AsUTF8String(value, "surrogateescape"); +#endif + if (!encoded) { return NULL; } - for (i = 0; i < size; i++) { - Py_UCS4 ch = PyUnicode_READ(kind, inbuf, i); - /* We cannot test for sizeof(Tcl_UniChar) directly, - so we test for UTF-8 size instead. */ -#if TCL_UTF_MAX == 3 - if (ch >= 0x10000) { - /* Tcl doesn't do UTF-16, yet. */ - PyErr_Format(Tkinter_TclError, - "character U+%x is above the range " - "(U+0000-U+FFFF) allowed by Tcl", - ch); - PyMem_Free(outbuf); - return NULL; - } -#endif - outbuf[i] = ch; + 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(outbuf, (int)size); - PyMem_Free(outbuf); +#if USE_TCL_UNICODE + result = Tcl_NewUnicodeObj((const Tcl_UniChar *)PyBytes_AS_STRING(encoded), + (int)(size / sizeof(Tcl_UniChar))); +#else + result = Tcl_NewStringObj(PyBytes_AS_STRING(encoded), (int)size); +#endif + Py_DECREF(encoded); return result; } @@ -1113,7 +1146,7 @@ AsObj(PyObject *value) } static PyObject * -fromBoolean(PyObject* tkapp, Tcl_Obj *value) +fromBoolean(TkappObject *tkapp, Tcl_Obj *value) { int boolValue; if (Tcl_GetBooleanFromObj(Tkapp_Interp(tkapp), value, &boolValue) == TCL_ERROR) @@ -1122,7 +1155,7 @@ fromBoolean(PyObject* tkapp, Tcl_Obj *value) } static PyObject* -fromWideIntObj(PyObject* tkapp, Tcl_Obj *value) +fromWideIntObj(TkappObject *tkapp, Tcl_Obj *value) { Tcl_WideInt wideValue; if (Tcl_GetWideIntFromObj(Tkapp_Interp(tkapp), value, &wideValue) == TCL_OK) { @@ -1138,7 +1171,7 @@ fromWideIntObj(PyObject* tkapp, Tcl_Obj *value) #ifdef HAVE_LIBTOMMAMTH static PyObject* -fromBignumObj(PyObject* tkapp, Tcl_Obj *value) +fromBignumObj(TkappObject *tkapp, Tcl_Obj *value) { mp_int bigValue; unsigned long numBytes; @@ -1174,32 +1207,31 @@ fromBignumObj(PyObject* tkapp, Tcl_Obj *value) #endif static PyObject* -FromObj(PyObject* tkapp, Tcl_Obj *value) +FromObj(TkappObject *tkapp, Tcl_Obj *value) { PyObject *result = NULL; - TkappObject *app = (TkappObject*)tkapp; Tcl_Interp *interp = Tkapp_Interp(tkapp); if (value->typePtr == NULL) { - return unicodeFromTclStringAndSize(value->bytes, value->length); + return unicodeFromTclObj(value); } - if (value->typePtr == app->BooleanType || - value->typePtr == app->OldBooleanType) { + if (value->typePtr == tkapp->BooleanType || + value->typePtr == tkapp->OldBooleanType) { return fromBoolean(tkapp, value); } - if (value->typePtr == app->ByteArrayType) { + if (value->typePtr == tkapp->ByteArrayType) { int size; char *data = (char*)Tcl_GetByteArrayFromObj(value, &size); return PyBytes_FromStringAndSize(data, size); } - if (value->typePtr == app->DoubleType) { + if (value->typePtr == tkapp->DoubleType) { return PyFloat_FromDouble(value->internalRep.doubleValue); } - if (value->typePtr == app->IntType) { + if (value->typePtr == tkapp->IntType) { long longValue; if (Tcl_GetLongFromObj(interp, value, &longValue) == TCL_OK) return PyLong_FromLong(longValue); @@ -1207,8 +1239,8 @@ FromObj(PyObject* tkapp, Tcl_Obj *value) fall through to wideInt handling. */ } - if (value->typePtr == app->IntType || - value->typePtr == app->WideIntType) { + if (value->typePtr == tkapp->IntType || + value->typePtr == tkapp->WideIntType) { result = fromWideIntObj(tkapp, value); if (result != NULL || PyErr_Occurred()) return result; @@ -1218,14 +1250,14 @@ FromObj(PyObject* tkapp, Tcl_Obj *value) } #ifdef HAVE_LIBTOMMAMTH - if (value->typePtr == app->IntType || - value->typePtr == app->WideIntType || - value->typePtr == app->BignumType) { + if (value->typePtr == tkapp->IntType || + value->typePtr == tkapp->WideIntType || + value->typePtr == tkapp->BignumType) { return fromBignumObj(tkapp, value); } #endif - if (value->typePtr == app->ListType) { + if (value->typePtr == tkapp->ListType) { int size; int i, status; PyObject *elem; @@ -1253,30 +1285,28 @@ FromObj(PyObject* tkapp, Tcl_Obj *value) return result; } - if (value->typePtr == app->ProcBodyType) { + if (value->typePtr == tkapp->ProcBodyType) { /* fall through: return tcl object. */ } - if (value->typePtr == app->StringType) { - return PyUnicode_FromKindAndData( - sizeof(Tcl_UniChar), Tcl_GetUnicode(value), - Tcl_GetCharLength(value)); + if (value->typePtr == tkapp->StringType) { + return unicodeFromTclObj(value); } #if TK_HEX_VERSION >= 0x08050000 - if (app->BooleanType == NULL && + if (tkapp->BooleanType == NULL && strcmp(value->typePtr->name, "booleanString") == 0) { /* booleanString type is not registered in Tcl */ - app->BooleanType = value->typePtr; + tkapp->BooleanType = value->typePtr; return fromBoolean(tkapp, value); } #endif #ifdef HAVE_LIBTOMMAMTH - if (app->BignumType == NULL && + if (tkapp->BignumType == NULL && strcmp(value->typePtr->name, "bignum") == 0) { /* bignum type is not registered in Tcl */ - app->BignumType = value->typePtr; + tkapp->BignumType = value->typePtr; return fromBignumObj(tkapp, value); } #endif @@ -1366,19 +1396,28 @@ finally: return NULL; } +/* Convert the results of a command call into a Python string. */ + +static PyObject * +Tkapp_UnicodeResult(TkappObject *self) +{ + return unicodeFromTclObj(Tcl_GetObjResult(self->interp)); +} + + /* Convert the results of a command call into a Python objects. */ -static PyObject* -Tkapp_CallResult(TkappObject *self) +static PyObject * +Tkapp_ObjectResult(TkappObject *self) { PyObject *res = NULL; Tcl_Obj *value = Tcl_GetObjResult(self->interp); - if(self->wantobjects) { + 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((PyObject*)self, value); + res = FromObj(self, value); Tcl_DecrRefCount(value); } else { res = unicodeFromTclObj(value); @@ -1410,15 +1449,13 @@ Tkapp_CallProc(Tkapp_CallEvent *e, int flags) i = Tcl_EvalObjv(e->self->interp, objc, objv, e->flags); ENTER_PYTHON if (i == TCL_ERROR) { - *(e->res) = NULL; - *(e->exc_type) = NULL; - *(e->exc_tb) = NULL; - *(e->exc_value) = PyObject_CallFunction( - Tkinter_TclError, "s", - Tcl_GetStringResult(e->self->interp)); + *(e->res) = Tkinter_Error(e->self); } else { - *(e->res) = Tkapp_CallResult(e->self); + *(e->res) = Tkapp_ObjectResult(e->self); + } + if (*(e->res) == NULL) { + PyErr_Fetch(e->exc_type, e->exc_value, e->exc_tb); } LEAVE_PYTHON @@ -1506,9 +1543,9 @@ Tkapp_Call(PyObject *selfptr, PyObject *args) ENTER_OVERLAP if (i == TCL_ERROR) - Tkinter_Error(selfptr); + Tkinter_Error(self); else - res = Tkapp_CallResult(self); + res = Tkapp_ObjectResult(self); LEAVE_OVERLAP_TCL @@ -1540,9 +1577,9 @@ _tkinter_tkapp_eval_impl(TkappObject *self, const char *script) err = Tcl_Eval(Tkapp_Interp(self), script); ENTER_OVERLAP if (err == TCL_ERROR) - res = Tkinter_Error((PyObject *)self); + res = Tkinter_Error(self); else - res = unicodeFromTclString(Tkapp_Result(self)); + res = Tkapp_UnicodeResult(self); LEAVE_OVERLAP_TCL return res; } @@ -1569,9 +1606,9 @@ _tkinter_tkapp_evalfile_impl(TkappObject *self, const char *fileName) err = Tcl_EvalFile(Tkapp_Interp(self), fileName); ENTER_OVERLAP if (err == TCL_ERROR) - res = Tkinter_Error((PyObject *)self); + res = Tkinter_Error(self); else - res = unicodeFromTclString(Tkapp_Result(self)); + res = Tkapp_UnicodeResult(self); LEAVE_OVERLAP_TCL return res; } @@ -1598,9 +1635,9 @@ _tkinter_tkapp_record_impl(TkappObject *self, const char *script) err = Tcl_RecordAndEval(Tkapp_Interp(self), script, TCL_NO_EVAL); ENTER_OVERLAP if (err == TCL_ERROR) - res = Tkinter_Error((PyObject *)self); + res = Tkinter_Error(self); else - res = unicodeFromTclString(Tkapp_Result(self)); + res = Tkapp_UnicodeResult(self); LEAVE_OVERLAP_TCL return res; } @@ -1631,13 +1668,13 @@ _tkinter_tkapp_adderrorinfo_impl(TkappObject *self, const char *msg) /** Tcl Variable **/ -typedef PyObject* (*EventFunc)(PyObject*, PyObject *args, int flags); +typedef PyObject* (*EventFunc)(TkappObject *, PyObject *, int); TCL_DECLARE_MUTEX(var_mutex) typedef struct VarEvent { Tcl_Event ev; /* must be first */ - PyObject *self; + TkappObject *self; PyObject *args; int flags; EventFunc func; @@ -1692,7 +1729,7 @@ varname_converter(PyObject *in, void *_out) return 1; } if (PyTclObject_Check(in)) { - *out = PyTclObject_TclString(in); + *out = Tcl_GetString(((PyTclObject *)in)->value); return 1; } PyErr_Format(PyExc_TypeError, @@ -1750,7 +1787,7 @@ var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags) PyErr_NoMemory(); return NULL; } - ev->self = selfptr; + ev->self = self; ev->args = args; ev->flags = flags; ev->func = func; @@ -1770,11 +1807,11 @@ var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags) return res; } /* Tcl is not threaded, or this is the interpreter thread. */ - return func(selfptr, args, flags); + return func(self, args, flags); } static PyObject * -SetVar(PyObject *self, PyObject *args, int flags) +SetVar(TkappObject *self, PyObject *args, int flags) { const char *name1, *name2; PyObject *newValue; @@ -1843,7 +1880,7 @@ Tkapp_GlobalSetVar(PyObject *self, PyObject *args) static PyObject * -GetVar(PyObject *self, PyObject *args, int flags) +GetVar(TkappObject *self, PyObject *args, int flags) { const char *name1, *name2=NULL; PyObject *res = NULL; @@ -1858,10 +1895,9 @@ GetVar(PyObject *self, PyObject *args, int flags) tres = Tcl_GetVar2Ex(Tkapp_Interp(self), name1, name2, flags); ENTER_OVERLAP if (tres == NULL) { - PyErr_SetString(Tkinter_TclError, - Tcl_GetStringResult(Tkapp_Interp(self))); + Tkinter_Error(self); } else { - if (((TkappObject*)self)->wantobjects) { + if (self->wantobjects) { res = FromObj(self, tres); } else { @@ -1887,7 +1923,7 @@ Tkapp_GlobalGetVar(PyObject *self, PyObject *args) static PyObject * -UnsetVar(PyObject *self, PyObject *args, int flags) +UnsetVar(TkappObject *self, PyObject *args, int flags) { char *name1, *name2=NULL; int code; @@ -1959,7 +1995,7 @@ _tkinter_tkapp_getint(TkappObject *self, PyObject *arg) CHECK_STRING_LENGTH(s); value = Tcl_NewStringObj(s, -1); if (value == NULL) - return Tkinter_Error((PyObject *)self); + 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). @@ -1968,14 +2004,14 @@ _tkinter_tkapp_getint(TkappObject *self, PyObject *arg) value in ranges -2**64..-2**63-1 and 2**63..2**64-1 (on 32-bit platform). */ #ifdef HAVE_LIBTOMMAMTH - result = fromBignumObj((PyObject *)self, value); + result = fromBignumObj(self, value); #else - result = fromWideIntObj((PyObject *)self, value); + result = fromWideIntObj(self, value); #endif Tcl_DecrRefCount(value); if (result != NULL || PyErr_Occurred()) return result; - return Tkinter_Error((PyObject *)self); + return Tkinter_Error(self); } /*[clinic input] @@ -2006,7 +2042,7 @@ _tkinter_tkapp_getdouble(TkappObject *self, PyObject *arg) if (Tcl_GetDoubleFromObj(Tkapp_Interp(self), ((PyTclObject*)arg)->value, &v) == TCL_ERROR) - return Tkinter_Error((PyObject *)self); + return Tkinter_Error(self); return PyFloat_FromDouble(v); } @@ -2014,7 +2050,7 @@ _tkinter_tkapp_getdouble(TkappObject *self, PyObject *arg) return NULL; CHECK_STRING_LENGTH(s); if (Tcl_GetDouble(Tkapp_Interp(self), s, &v) == TCL_ERROR) - return Tkinter_Error((PyObject *)self); + return Tkinter_Error(self); return PyFloat_FromDouble(v); } @@ -2041,7 +2077,7 @@ _tkinter_tkapp_getboolean(TkappObject *self, PyObject *arg) if (Tcl_GetBooleanFromObj(Tkapp_Interp(self), ((PyTclObject*)arg)->value, &v) == TCL_ERROR) - return Tkinter_Error((PyObject *)self); + return Tkinter_Error(self); return PyBool_FromLong(v); } @@ -2049,7 +2085,7 @@ _tkinter_tkapp_getboolean(TkappObject *self, PyObject *arg) return NULL; CHECK_STRING_LENGTH(s); if (Tcl_GetBoolean(Tkapp_Interp(self), s, &v) == TCL_ERROR) - return Tkinter_Error((PyObject *)self); + return Tkinter_Error(self); return PyBool_FromLong(v); } @@ -2075,9 +2111,9 @@ _tkinter_tkapp_exprstring_impl(TkappObject *self, const char *s) retval = Tcl_ExprString(Tkapp_Interp(self), s); ENTER_OVERLAP if (retval == TCL_ERROR) - res = Tkinter_Error((PyObject *)self); + res = Tkinter_Error(self); else - res = unicodeFromTclString(Tkapp_Result(self)); + res = Tkapp_UnicodeResult(self); LEAVE_OVERLAP_TCL return res; } @@ -2105,7 +2141,7 @@ _tkinter_tkapp_exprlong_impl(TkappObject *self, const char *s) retval = Tcl_ExprLong(Tkapp_Interp(self), s, &v); ENTER_OVERLAP if (retval == TCL_ERROR) - res = Tkinter_Error((PyObject *)self); + res = Tkinter_Error(self); else res = PyLong_FromLong(v); LEAVE_OVERLAP_TCL @@ -2136,7 +2172,7 @@ _tkinter_tkapp_exprdouble_impl(TkappObject *self, const char *s) ENTER_OVERLAP PyFPE_END_PROTECT(retval) if (retval == TCL_ERROR) - res = Tkinter_Error((PyObject *)self); + res = Tkinter_Error(self); else res = PyFloat_FromDouble(v); LEAVE_OVERLAP_TCL @@ -2165,7 +2201,7 @@ _tkinter_tkapp_exprboolean_impl(TkappObject *self, const char *s) retval = Tcl_ExprBoolean(Tkapp_Interp(self), s, &v); ENTER_OVERLAP if (retval == TCL_ERROR) - res = Tkinter_Error((PyObject *)self); + res = Tkinter_Error(self); else res = PyLong_FromLong(v); LEAVE_OVERLAP_TCL @@ -2198,12 +2234,12 @@ _tkinter_tkapp_splitlist(TkappObject *self, PyObject *arg) if (Tcl_ListObjGetElements(Tkapp_Interp(self), ((PyTclObject*)arg)->value, &objc, &objv) == TCL_ERROR) { - return Tkinter_Error((PyObject *)self); + return Tkinter_Error(self); } if (!(v = PyTuple_New(objc))) return NULL; for (i = 0; i < objc; i++) { - PyObject *s = FromObj((PyObject*)self, objv[i]); + PyObject *s = FromObj(self, objv[i]); if (!s) { Py_DECREF(v); return NULL; @@ -2231,7 +2267,7 @@ _tkinter_tkapp_splitlist(TkappObject *self, PyObject *arg) if (Tcl_SplitList(Tkapp_Interp(self), list, &argc, &argv) == TCL_ERROR) { PyMem_Free(list); - return Tkinter_Error((PyObject *)self); + return Tkinter_Error(self); } if (!(v = PyTuple_New(argc))) @@ -2275,16 +2311,16 @@ _tkinter_tkapp_split(TkappObject *self, PyObject *arg) int i; if (Tcl_ListObjGetElements(Tkapp_Interp(self), value, &objc, &objv) == TCL_ERROR) { - return FromObj((PyObject*)self, value); + return FromObj(self, value); } if (objc == 0) return PyUnicode_FromString(""); if (objc == 1) - return FromObj((PyObject*)self, objv[0]); + return FromObj(self, objv[0]); if (!(v = PyTuple_New(objc))) return NULL; for (i = 0; i < objc; i++) { - PyObject *s = FromObj((PyObject*)self, objv[i]); + PyObject *s = FromObj(self, objv[i]); if (!s) { Py_DECREF(v); return NULL; @@ -2331,34 +2367,31 @@ PythonCmd_Error(Tcl_Interp *interp) * function or method. */ static int -PythonCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]) +PythonCmd(ClientData clientData, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]) { PythonCmd_ClientData *data = (PythonCmd_ClientData *)clientData; - PyObject *func, *arg, *res; - int i, rv; + PyObject *args, *res; + int i; Tcl_Obj *obj_res; ENTER_PYTHON - /* TBD: no error checking here since we know, via the - * Tkapp_CreateCommand() that the client data is a two-tuple - */ - func = data->func; - - /* Create argument list (argv1, ..., argvN) */ - if (!(arg = PyTuple_New(argc - 1))) + /* Create argument tuple (objv1, ..., objvN) */ + if (!(args = PyTuple_New(objc - 1))) return PythonCmd_Error(interp); - for (i = 0; i < (argc - 1); i++) { - PyObject *s = unicodeFromTclString(argv[i + 1]); + for (i = 0; i < (objc - 1); i++) { + PyObject *s = unicodeFromTclObj(objv[i + 1]); if (!s) { - Py_DECREF(arg); + Py_DECREF(args); return PythonCmd_Error(interp); } - PyTuple_SET_ITEM(arg, i, s); + PyTuple_SET_ITEM(args, i, s); } - res = PyObject_Call(func, arg, NULL); - Py_DECREF(arg); + + res = PyObject_Call(data->func, args, NULL); + Py_DECREF(args); if (res == NULL) return PythonCmd_Error(interp); @@ -2368,18 +2401,15 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[ Py_DECREF(res); return PythonCmd_Error(interp); } - else { - Tcl_SetObjResult(interp, obj_res); - rv = TCL_OK; - } - + Tcl_SetObjResult(interp, obj_res); Py_DECREF(res); LEAVE_PYTHON - return rv; + return TCL_OK; } + static void PythonCmdDelete(ClientData clientData) { @@ -2411,7 +2441,7 @@ static int Tkapp_CommandProc(CommandEvent *ev, int flags) { if (ev->create) - *ev->status = Tcl_CreateCommand( + *ev->status = Tcl_CreateObjCommand( ev->interp, ev->name, PythonCmd, ev->data, PythonCmdDelete) == NULL; else @@ -2477,7 +2507,7 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name, else { ENTER_TCL - err = Tcl_CreateCommand( + err = Tcl_CreateObjCommand( Tkapp_Interp(self), name, PythonCmd, (ClientData)data, PythonCmdDelete) == NULL; LEAVE_TCL @@ -2953,9 +2983,9 @@ _tkinter_tkapp_loadtk_impl(TkappObject *self) if (err == TCL_ERROR) { /* This sets an exception, but we cannot return right away because we need to exit the overlap first. */ - Tkinter_Error((PyObject *)self); + Tkinter_Error(self); } else { - _tk_exists = Tkapp_Result(self); + _tk_exists = Tcl_GetStringResult(Tkapp_Interp(self)); } LEAVE_OVERLAP_TCL if (err == TCL_ERROR) { @@ -2963,8 +2993,7 @@ _tkinter_tkapp_loadtk_impl(TkappObject *self) } if (_tk_exists == NULL || strcmp(_tk_exists, "1") != 0) { if (Tk_Init(interp) == TCL_ERROR) { - PyErr_SetString(Tkinter_TclError, - Tcl_GetStringResult(Tkapp_Interp(self))); + Tkinter_Error(self); #ifdef TKINTER_PROTECT_LOADTK tk_load_failed = 1; #endif