diff --git a/graalpython/lib-python/3/_tkinter/__init__.py b/graalpython/lib-python/3/_tkinter/__init__.py new file mode 100644 index 0000000000..3bd7970f82 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/__init__.py @@ -0,0 +1,55 @@ +# _tkinter package -- low-level interface to libtk and libtcl. +# +# This is an internal module, applications should "import tkinter" instead. +# +# This version is based PyPy which itself is based on cffi, and is a translation of _tkinter.c +# from CPython, version 2.7.4. + +import sys + +class TclError(Exception): + pass + +from .tklib_cffi import ffi as tkffi, lib as tklib + +from .app import TkApp +from .tclobj import TclObject as Tcl_Obj +from .app import FromTclString, ToTCLString + +TK_VERSION = FromTclString(tkffi.string(tklib.get_tk_version())) +TCL_VERSION = FromTclString(tkffi.string(tklib.get_tcl_version())) + +READABLE = tklib.TCL_READABLE +WRITABLE = tklib.TCL_WRITABLE +EXCEPTION = tklib.TCL_EXCEPTION +DONT_WAIT = tklib.TCL_DONT_WAIT + +def create(screenName=None, baseName=None, className=None, + interactive=False, wantobjects=False, wantTk=True, + sync=False, use=None): + return TkApp(screenName, baseName, className, + interactive, wantobjects, wantTk, sync, use) + +def dooneevent(flags=0): + return tklib.Tcl_DoOneEvent(flags) + + +def _flatten(item): + def _flatten1(output, item, depth): + if depth > 1000: + raise ValueError("nesting too deep in _flatten") + if not isinstance(item, (list, tuple)): + raise TypeError("argument must be sequence") + # copy items to output tuple + for o in item: + if isinstance(o, (list, tuple)): + _flatten1(output, o, depth + 1) + elif o is not None: + output.append(o) + + result = [] + _flatten1(result, item, 0) + return tuple(result) + +# Encoding is not specified explicitly, but "must be passed argv[0]" sounds like a simple conversion to raw bytes. +tklib.Tcl_FindExecutable(ToTCLString(sys.executable)) diff --git a/graalpython/lib-python/3/_tkinter/app.py b/graalpython/lib-python/3/_tkinter/app.py new file mode 100644 index 0000000000..cfa38d12b9 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/app.py @@ -0,0 +1,592 @@ +# The TkApp class. + +from .tklib_cffi import ffi as tkffi, lib as tklib +from . import TclError +from .tclobj import (TclObject, FromObj, FromTclString, ToTCLString, AsObj, TypeCache, + FromBignumObj, FromWideIntObj) + +import contextlib +import sys +import threading +import time + + +class _DummyLock(object): + "A lock-like object that does not do anything" + def acquire(self): + pass + def release(self): + pass + def __enter__(self): + pass + def __exit__(self, *exc): + pass + + +def varname_converter(input): + # Explicit handling of NUL character in strings a bytes here because tests require it. + if isinstance(input, bytes) and b'\0' in input: + raise ValueError("NUL character in string") + if isinstance(input, str) and '\0' in input: + raise ValueError("NUL character in string") + + if isinstance(input, TclObject): + return input.string + + return ToTCLString(input) + + +def Tcl_AppInit(app): + # For portable builds, try to load a local version of the libraries + from os.path import join, dirname, exists, sep + if sys.platform == 'win32': + lib_path = join(dirname(dirname(dirname(__file__))), 'tcl') + tcl_path = join(lib_path, 'tcl8.6') + tk_path = join(lib_path, 'tk8.6') + tcl_path = tcl_path.replace(sep, '/') + tk_path = tk_path.replace(sep, '/') + else: + lib_path = join(dirname(dirname(dirname(__file__))), 'lib') + tcl_path = join(lib_path, 'tcl') + tk_path = join(lib_path, 'tk') + if exists(tcl_path): + tklib.Tcl_Eval(app.interp, ToTCLString('set tcl_library "{0}"'.format(tcl_path))) + if exists(tk_path): + tklib.Tcl_Eval(app.interp, ToTCLString('set tk_library "{0}"'.format(tk_path))) + + if tklib.Tcl_Init(app.interp) == tklib.TCL_ERROR: + app.raiseTclError() + skip_tk_init = tklib.Tcl_GetVar( + app.interp, b"_tkinter_skip_tk_init", tklib.TCL_GLOBAL_ONLY) + if skip_tk_init and FromTclString(tkffi.string(skip_tk_init)) == "1": + return + + if tklib.Tk_Init(app.interp) == tklib.TCL_ERROR: + app.raiseTclError() + +class _CommandData(object): + def __new__(cls, app, name, func): + self = object.__new__(cls) + self.app = app + self.name = name + self.func = func + handle = tkffi.new_handle(self) + app._commands[name] = handle # To keep the command alive + return tkffi.cast("ClientData", handle) + + @tkffi.callback("Tcl_CmdProc") + def PythonCmd(clientData, interp, argc, argv): + self = tkffi.from_handle(clientData) + assert self.app.interp == interp + with self.app._tcl_lock_released(): + try: + args = [FromTclString(tkffi.string(arg)) for arg in argv[1:argc]] + result = self.func(*args) + obj = AsObj(result) + tklib.Tcl_SetObjResult(interp, obj) + except: + self.app.errorInCmd = True + self.app.exc_info = sys.exc_info() + return tklib.TCL_ERROR + else: + return tklib.TCL_OK + + @tkffi.callback("Tcl_CmdDeleteProc") + def PythonCmdDelete(clientData): + self = tkffi.from_handle(clientData) + app = self.app + del app._commands[self.name] + return + + +class TkApp(object): + _busywaitinterval = 0.02 # 20ms. + + def __new__(cls, screenName, baseName, className, + interactive, wantobjects, wantTk, sync, use): + if not wantobjects: + raise NotImplementedError("wantobjects=True only") + self = object.__new__(cls) + self.interp = tklib.Tcl_CreateInterp() + self._wantobjects = wantobjects + # "threaded" is an optionally present member of "tcl_platform" when TCL was compiled with threading. + # Tcl_GetVar2Ex should return NULL when "threaded" is not present, so casting to a bool here is doing an implicit NULL-check. + self.threaded = bool(tklib.Tcl_GetVar2Ex( + self.interp, b"tcl_platform", b"threaded", + tklib.TCL_GLOBAL_ONLY)) + self.thread_id = tklib.Tcl_GetCurrentThread() + self.dispatching = False + self.quitMainLoop = False + self.errorInCmd = False + + if not self.threaded: + # TCL is not thread-safe, calls needs to be serialized. + self._tcl_lock = threading.RLock() + else: + self._tcl_lock = _DummyLock() + + self._typeCache = TypeCache() + self._commands = {} + + # Delete the 'exit' command, which can screw things up + tklib.Tcl_DeleteCommand(self.interp, b"exit") + + if screenName is not None: + tklib.Tcl_SetVar2(self.interp, b"env", b"DISPLAY", screenName, + tklib.TCL_GLOBAL_ONLY) + + if interactive: + tklib.Tcl_SetVar(self.interp, b"tcl_interactive", b"1", + tklib.TCL_GLOBAL_ONLY) + else: + tklib.Tcl_SetVar(self.interp, b"tcl_interactive", b"0", + tklib.TCL_GLOBAL_ONLY) + + # This is used to get the application class for Tk 4.1 and up + argv0 = className.lower().encode('ascii') + tklib.Tcl_SetVar(self.interp, b"argv0", argv0, + tklib.TCL_GLOBAL_ONLY) + + if not wantTk: + tklib.Tcl_SetVar(self.interp, b"_tkinter_skip_tk_init", b"1", + tklib.TCL_GLOBAL_ONLY) + + # some initial arguments need to be in argv + if sync or use: + args = b"" + if sync: + args += b"-sync" + if use: + if sync: + args += b" " + args += b"-use " + use + + tklib.Tcl_SetVar(self.interp, b"argv", args, + tklib.TCL_GLOBAL_ONLY) + + Tcl_AppInit(self) + # EnableEventHook() + self._typeCache.add_extra_types(self) + return self + + def __del__(self): + tklib.Tcl_DeleteInterp(self.interp) + # DisableEventHook() + + def raiseTclError(self): + if self.errorInCmd: + self.errorInCmd = False + raise self.exc_info[0](self.exc_info[1]).with_traceback(self.exc_info[2]) + raise TclError(FromTclString(tkffi.string(tklib.Tcl_GetStringResult(self.interp)))) + + def wantobjects(self): + return self._wantobjects + + def _check_tcl_appartment(self): + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise RuntimeError("Calling Tcl from different appartment") + + @contextlib.contextmanager + def _tcl_lock_released(self): + "Context manager to temporarily release the tcl lock." + self._tcl_lock.release() + yield + self._tcl_lock.acquire() + + def loadtk(self): + # We want to guard against calling Tk_Init() multiple times + err = tklib.Tcl_Eval(self.interp, b"info exists tk_version") + if err == tklib.TCL_ERROR: + self.raiseTclError() + tk_exists = tklib.Tcl_GetStringResult(self.interp) + if not tk_exists or FromTclString(tkffi.string(tk_exists)) != "1": + err = tklib.Tk_Init(self.interp) + if err == tklib.TCL_ERROR: + self.raiseTclError() + + def interpaddr(self): + return int(tkffi.cast('size_t', self.interp)) + + def _var_invoke(self, func, *args, **kwargs): + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + # The current thread is not the interpreter thread. + # Marshal the call to the interpreter thread, then wait + # for completion. + raise NotImplementedError("Call from another thread") + return func(*args, **kwargs) + + def _getvar(self, name1, name2=None, global_only=False): + name1 = varname_converter(name1) + if not name2: + name2 = tkffi.NULL + flags=tklib.TCL_LEAVE_ERR_MSG + if global_only: + flags |= tklib.TCL_GLOBAL_ONLY + with self._tcl_lock: + # Name encoding not explicitly statet, assuming UTF-8 here due to other APIs. + res = tklib.Tcl_GetVar2Ex(self.interp, name1, name2, flags) + if not res: + self.raiseTclError() + assert self._wantobjects + return FromObj(self, res) + + def _setvar(self, name1, value, global_only=False): + name1 = varname_converter(name1) + # XXX Acquire tcl lock??? + newval = AsObj(value) + flags=tklib.TCL_LEAVE_ERR_MSG + if global_only: + flags |= tklib.TCL_GLOBAL_ONLY + with self._tcl_lock: + res = tklib.Tcl_SetVar2Ex(self.interp, name1, tkffi.NULL, + newval, flags) + if not res: + self.raiseTclError() + + def _unsetvar(self, name1, name2=None, global_only=False): + name1 = varname_converter(name1) + if not name2: + name2 = tkffi.NULL + flags=tklib.TCL_LEAVE_ERR_MSG + if global_only: + flags |= tklib.TCL_GLOBAL_ONLY + with self._tcl_lock: + res = tklib.Tcl_UnsetVar2(self.interp, name1, name2, flags) + if res == tklib.TCL_ERROR: + self.raiseTclError() + + def getvar(self, name1, name2=None): + return self._var_invoke(self._getvar, name1, name2) + + def globalgetvar(self, name1, name2=None): + return self._var_invoke(self._getvar, name1, name2, global_only=True) + + def setvar(self, name1, value): + return self._var_invoke(self._setvar, name1, value) + + def globalsetvar(self, name1, value): + return self._var_invoke(self._setvar, name1, value, global_only=True) + + def unsetvar(self, name1, name2=None): + return self._var_invoke(self._unsetvar, name1, name2) + + def globalunsetvar(self, name1, name2=None): + return self._var_invoke(self._unsetvar, name1, name2, global_only=True) + + # COMMANDS + + def createcommand(self, cmdName, func): + if not callable(func): + raise TypeError("command not callable") + + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise NotImplementedError("Call from another thread") + + clientData = _CommandData(self, cmdName, func) + + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise NotImplementedError("Call from another thread") + + with self._tcl_lock: + res = tklib.Tcl_CreateCommand( + self.interp, ToTCLString(cmdName), _CommandData.PythonCmd, + clientData, _CommandData.PythonCmdDelete) + if not res: + raise TclError(b"can't create Tcl command") + + def deletecommand(self, cmdName): + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + raise NotImplementedError("Call from another thread") + + with self._tcl_lock: + res = tklib.Tcl_DeleteCommand(self.interp, ToTCLString(cmdName)) + if res == -1: + raise TclError("can't delete Tcl command") + + def call(self, *args): + flags = tklib.TCL_EVAL_DIRECT | tklib.TCL_EVAL_GLOBAL + + # If args is a single tuple, replace with contents of tuple + if len(args) == 1 and isinstance(args[0], tuple): + args = args[0] + + if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread(): + # We cannot call the command directly. Instead, we must + # marshal the parameters to the interpreter thread. + raise NotImplementedError("Call from another thread") + + # Allocate new array of object pointers. + objects = tkffi.new("Tcl_Obj*[]", len(args)) + argc = len(args) + try: + for i, arg in enumerate(args): + if arg is None: + argc = i + break + obj = AsObj(arg) + tklib.Tcl_IncrRefCount(obj) + objects[i] = obj + + with self._tcl_lock: + res = tklib.Tcl_EvalObjv(self.interp, argc, objects, flags) + if res == tklib.TCL_ERROR: + self.raiseTclError() + else: + result = self._callResult() + finally: + for obj in objects: + if obj: + tklib.Tcl_DecrRefCount(obj) + return result + + def _callResult(self): + assert self._wantobjects + value = tklib.Tcl_GetObjResult(self.interp) + # Not sure whether the IncrRef is necessary, but something + # may overwrite the interpreter result while we are + # converting it. + tklib.Tcl_IncrRefCount(value) + res = FromObj(self, value) + tklib.Tcl_DecrRefCount(value) + return res + + def eval(self, script): + self._check_tcl_appartment() + with self._tcl_lock: + res = tklib.Tcl_Eval(self.interp, script) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return FromTclString(tkffi.string(tklib.Tcl_GetStringResult(self.interp))) + + def evalfile(self, filename): + self._check_tcl_appartment() + with self._tcl_lock: + res = tklib.Tcl_EvalFile(self.interp, filename) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return FromTclString(tkffi.string(tklib.Tcl_GetStringResult(self.interp))) + + def split(self, arg): + if isinstance(arg, TclObject): + objc = tkffi.new("int*") + objv = tkffi.new("Tcl_Obj***") + status = tklib.Tcl_ListObjGetElements(self.interp, arg._value, objc, objv) + if status == tklib.TCL_ERROR: + return FromObj(self, arg._value) + if objc == 0: + return '' + elif objc == 1: + return FromObj(self, objv[0][0]) + result = [] + for i in range(objc[0]): + result.append(FromObj(self, objv[0][i])) + return tuple(result) + elif isinstance(arg, tuple): + return self._splitObj(arg) + elif isinstance(arg, str): + arg = ToTCLString(arg) + return self._split(arg) + + def splitlist(self, arg): + if isinstance(arg, TclObject): + objc = tkffi.new("int*") + objv = tkffi.new("Tcl_Obj***") + status = tklib.Tcl_ListObjGetElements(self.interp, arg._value, objc, objv) + if status == tklib.TCL_ERROR: + self.raiseTclError() + result = [] + for i in range(objc[0]): + result.append(FromObj(self, objv[0][i])) + return tuple(result) + elif isinstance(arg, tuple): + return arg + elif isinstance(arg, str): + arg = ToTCLString(arg) + + argc = tkffi.new("int*") + argv = tkffi.new("char***") + res = tklib.Tcl_SplitList(self.interp, arg, argc, argv) + if res == tklib.TCL_ERROR: + self.raiseTclError() + + result = tuple(FromTclString(tkffi.string(argv[0][i])) + for i in range(argc[0])) + tklib.Tcl_Free(argv[0]) + return result + + def _splitObj(self, arg): + if isinstance(arg, tuple): + size = len(arg) + result = None + # Recursively invoke SplitObj for all tuple items. + # If this does not return a new object, no action is + # needed. + for i in range(size): + elem = arg[i] + newelem = self._splitObj(elem) + if result is None: + if newelem == elem: + continue + result = [None] * size + for k in range(i): + result[k] = arg[k] + result[i] = newelem + if result is not None: + return tuple(result) + elif isinstance(arg, str): + argc = tkffi.new("int*") + argv = tkffi.new("char***") + if isinstance(arg, str): + arg = ToTCLString(arg) + list_ = str(arg) + res = tklib.Tcl_SplitList(tkffi.NULL, list_, argc, argv) + if res != tklib.TCL_OK: + return arg + tklib.Tcl_Free(argv[0]) + if argc[0] > 1: + return self._split(list_) + return arg + + def _split(self, arg): + argc = tkffi.new("int*") + argv = tkffi.new("char***") + res = tklib.Tcl_SplitList(tkffi.NULL, arg, argc, argv) + if res == tklib.TCL_ERROR: + # Not a list. + # Could be a quoted string containing funnies, e.g. {"}. + # Return the string itself. + return arg + + # TODO: Is this method called from Python and Python str is expected, or are TCL strings expected? + try: + if argc[0] == 0: + return "" + elif argc[0] == 1: + return FromTclString(tkffi.string(argv[0][0])) + else: + return tuple(self._split(argv[0][i]) + for i in range(argc[0])) + finally: + tklib.Tcl_Free(argv[0]) + + def getboolean(self, s): + if isinstance(s, int): + return bool(s) + if isinstance(s, str): + s = ToTCLString(s) + if b'\x00' in s: + raise TypeError + v = tkffi.new("int*") + res = tklib.Tcl_GetBoolean(self.interp, s, v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return bool(v[0]) + + def getint(self, s): + if isinstance(s, int): + return s + if isinstance(s, str): + s = ToTCLString(s) + if b'\x00' in s: + raise TypeError + if tklib.HAVE_LIBTOMMATH or tklib.HAVE_WIDE_INT_TYPE: + value = tklib.Tcl_NewStringObj(s, -1) + if not value: + self.raiseTclError() + try: + if tklib.HAVE_LIBTOMMATH: + return FromBignumObj(self, value) + else: + return FromWideIntObj(self, value) + finally: + tklib.Tcl_DecrRefCount(value) + else: + v = tkffi.new("int*") + res = tklib.Tcl_GetInt(self.interp, s, v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def getdouble(self, s): + if isinstance(s, float): + return s + if isinstance(s, str): + s = ToTCLString(s) + if b'\x00' in s: + raise TypeError + v = tkffi.new("double*") + res = tklib.Tcl_GetDouble(self.interp, s, v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprboolean(self, s): + if b'\x00' in s: + raise TypeError + v = tkffi.new("int*") + res = tklib.Tcl_ExprBoolean(self.interp, ToTCLString(s), v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprlong(self, s): + if b'\x00' in s: + raise TypeError + v = tkffi.new("long*") + res = tklib.Tcl_ExprLong(self.interp, ToTCLString(s), v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprdouble(self, s): + if b'\x00' in s: + raise TypeError + v = tkffi.new("double*") + res = tklib.Tcl_ExprDouble(self.interp, ToTCLString(s), v) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return v[0] + + def exprstring(self, s): + if b'\x00' in s: + raise TypeError + res = tklib.Tcl_ExprString(self.interp, ToTCLString(s)) + if res == tklib.TCL_ERROR: + self.raiseTclError() + return tkffi.string(tklib.Tcl_GetStringResult(self.interp)) + + def mainloop(self, threshold): + self._check_tcl_appartment() + self.dispatching = True + while (tklib.Tk_GetNumMainWindows() > threshold and + not self.quitMainLoop and not self.errorInCmd): + + if self.threaded: + result = tklib.Tcl_DoOneEvent(0) + else: + with self._tcl_lock: + result = tklib.Tcl_DoOneEvent(tklib.TCL_DONT_WAIT) + if result == 0: + time.sleep(self._busywaitinterval) + + if result < 0: + break + self.dispatching = False + self.quitMainLoop = False + if self.errorInCmd: + self.errorInCmd = False + raise self.exc_info[0](self.exc_info[1]).with_traceback(self.exc_info[2]) + + def quit(self): + self.quitMainLoop = True + + def _createbytearray(self, buf): + """Convert Python string or any buffer compatible object to Tcl + byte-array object. Use it to pass binary data (e.g. image's + data) to Tcl/Tk commands.""" + cdata = tkffi.new("char[]", buf) + res = tklib.Tcl_NewByteArrayObj(cdata, len(buf)) + if not res: + self.raiseTclError() + return TclObject(res) + \ No newline at end of file diff --git a/graalpython/lib-python/3/_tkinter/tclobj.py b/graalpython/lib-python/3/_tkinter/tclobj.py new file mode 100644 index 0000000000..10408e7ed2 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/tclobj.py @@ -0,0 +1,236 @@ +# TclObject, conversions with Python objects + +from .tklib_cffi import ffi as tkffi, lib as tklib +import binascii + +class TypeCache(object): + def __init__(self): + self.OldBooleanType = tklib.Tcl_GetObjType(b"boolean") + self.BooleanType = None + self.ByteArrayType = tklib.Tcl_GetObjType(b"bytearray") + self.DoubleType = tklib.Tcl_GetObjType(b"double") + self.IntType = tklib.Tcl_GetObjType(b"int") + self.WideIntType = tklib.Tcl_GetObjType(b"wideInt") + self.BigNumType = None + self.ListType = tklib.Tcl_GetObjType(b"list") + self.ProcBodyType = tklib.Tcl_GetObjType(b"procbody") + self.StringType = tklib.Tcl_GetObjType(b"string") + + def add_extra_types(self, app): + # Some types are not registered in Tcl. + result = app.call('expr', 'true') + typePtr = AsObj(result).typePtr + if FromTclString(tkffi.string(typePtr.name)) == "booleanString": + self.BooleanType = typePtr + + result = app.call('expr', '2**63') + typePtr = AsObj(result).typePtr + if FromTclString(tkffi.string(typePtr.name)) == "bignum": + self.BigNumType = typePtr + + +# Interprets a TCL string (untyped char array) as a Python str using UTF-8. +# This assumes that TCL encodes its return values as UTF-8, not UTF-16. +# TODO: Find out whether this assumption is correct. +def FromTclString(s: bytes) -> str: + try: + return s.decode("utf-8") + except UnicodeDecodeError: + # Tcl encodes null character as \xc0\x80 + return s.replace(b'\xc0\x80', b'\x00')\ + .decode('utf-8') + +# Encodes a Python str as UTF-8 (assuming TCL encodes its API strings as UTF-8 as well, not UTF-16). +# TODO: Find out whether this is correct. +def ToTCLString(s: str) -> bytes: + return s.encode("utf-8")\ + .replace(b"\x00", b"\xc0\x80") + + +# Only when tklib.HAVE_WIDE_INT_TYPE. +def FromWideIntObj(app, value): + wide = tkffi.new("Tcl_WideInt*") + if tklib.Tcl_GetWideIntFromObj(app.interp, value, wide) != tklib.TCL_OK: + app.raiseTclError() + return int(wide[0]) + +# Only when tklib.HAVE_LIBTOMMATH! +def FromBignumObj(app, value): + bigValue = tkffi.new("mp_int*") + if tklib.Tcl_GetBignumFromObj(app.interp, value, bigValue) != tklib.TCL_OK: + app.raiseTclError() + try: + numBytes = tklib.mp_unsigned_bin_size(bigValue) + buf = tkffi.new("unsigned char[]", numBytes) + bufSize_ptr = tkffi.new("unsigned long*", numBytes) + if tklib.mp_to_unsigned_bin_n( + bigValue, buf, bufSize_ptr) != tklib.MP_OKAY: + raise MemoryError + if bufSize_ptr[0] == 0: + return 0 + bytes = tkffi.buffer(buf)[0:bufSize_ptr[0]] + sign = -1 if bigValue.sign == tklib.MP_NEG else 1 + return int(sign * int(binascii.hexlify(bytes), 16)) + finally: + tklib.mp_clear(bigValue) + +def AsBignumObj(value): + sign = -1 if value < 0 else 1 + hexstr = '%x' % abs(value) + bigValue = tkffi.new("mp_int*") + tklib.mp_init(bigValue) + try: + if tklib.mp_read_radix(bigValue, hexstr, 16) != tklib.MP_OKAY: + raise MemoryError + bigValue.sign = tklib.MP_NEG if value < 0 else tklib.MP_ZPOS + return tklib.Tcl_NewBignumObj(bigValue) + finally: + tklib.mp_clear(bigValue) + + +def FromObj(app, value): + """Convert a TclObj pointer into a Python object.""" + typeCache = app._typeCache + if not value.typePtr: + buf = tkffi.buffer(value.bytes, value.length) + return FromTclString(buf[:]) + + if value.typePtr in (typeCache.BooleanType, typeCache.OldBooleanType): + value_ptr = tkffi.new("int*") + if tklib.Tcl_GetBooleanFromObj( + app.interp, value, value_ptr) == tklib.TCL_ERROR: + app.raiseTclError() + return bool(value_ptr[0]) + if value.typePtr == typeCache.ByteArrayType: + size = tkffi.new('int*') + data = tklib.Tcl_GetByteArrayFromObj(value, size) + return tkffi.buffer(data, size[0])[:] + if value.typePtr == typeCache.DoubleType: + return value.internalRep.doubleValue + if value.typePtr == typeCache.IntType: + return value.internalRep.longValue + if value.typePtr == typeCache.WideIntType: + return FromWideIntObj(app, value) + if value.typePtr == typeCache.BigNumType and tklib.HAVE_LIBTOMMATH: + return FromBignumObj(app, value) + if value.typePtr == typeCache.ListType: + size = tkffi.new('int*') + status = tklib.Tcl_ListObjLength(app.interp, value, size) + if status == tklib.TCL_ERROR: + app.raiseTclError() + result = [] + tcl_elem = tkffi.new("Tcl_Obj**") + for i in range(size[0]): + status = tklib.Tcl_ListObjIndex(app.interp, + value, i, tcl_elem) + if status == tklib.TCL_ERROR: + app.raiseTclError() + result.append(FromObj(app, tcl_elem[0])) + return tuple(result) + if value.typePtr == typeCache.ProcBodyType: + pass # fall through and return tcl object. + if value.typePtr == typeCache.StringType: + buf = tklib.Tcl_GetUnicode(value) + length = tklib.Tcl_GetCharLength(value) + buf = tkffi.buffer(tkffi.cast("char*", buf), length*2)[:] + return buf.decode('utf-16') + + return TclObject(value) + +def AsObj(value): + if isinstance(value, str): + # TCL uses UTF-16 internally (https://www.tcl.tk/man/tcl8.4/TclCmd/encoding.html) + # But this function takes UTF-8 (https://linux.die.net/man/3/tcl_newstringobj#:~:text=array%20of%20UTF%2D8%2Dencoded%20bytes) + return tklib.Tcl_NewStringObj(ToTCLString(value), len(value)) + if isinstance(value, bool): + return tklib.Tcl_NewBooleanObj(value) + if isinstance(value, int): + try: + return tklib.Tcl_NewLongObj(value) + except OverflowError: + # 64-bit windows + if tklib.HAVE_WIDE_INT_TYPE: + return tklib.Tcl_NewWideIntObj(value) + else: + import sys + t, v, tb = sys.exc_info() + raise t(v).with_traceback(tb) + if isinstance(value, int): + try: + tkffi.new("long[]", [value]) + except OverflowError: + pass + else: + return tklib.Tcl_NewLongObj(value) + if tklib.HAVE_WIDE_INT_TYPE: + try: + tkffi.new("Tcl_WideInt[]", [value]) + except OverflowError: + pass + else: + return tklib.Tcl_NewWideIntObj(value) + if tklib.HAVE_LIBTOMMATH: + return AsBignumObj(value) + + if isinstance(value, float): + return tklib.Tcl_NewDoubleObj(value) + if isinstance(value, tuple): + argv = tkffi.new("Tcl_Obj*[]", len(value)) + for i in range(len(value)): + argv[i] = AsObj(value[i]) + return tklib.Tcl_NewListObj(len(value), argv) + if isinstance(value, str): + # TODO: Remnant of Python2's unicode type. What happens when our string contains unicode characters? + # Should we encode it as UTF-8 or UTF-16? + raise NotImplementedError + encoded = value.encode('utf-16')[2:] + buf = tkffi.new("char[]", encoded) + inbuf = tkffi.cast("Tcl_UniChar*", buf) + return tklib.Tcl_NewUnicodeObj(inbuf, len(encoded)/2) + if isinstance(value, TclObject): + return value._value + + return AsObj(str(value)) + +class TclObject(object): + def __new__(cls, value): + self = object.__new__(cls) + tklib.Tcl_IncrRefCount(value) + self._value = value + self._string = None + return self + + def __del__(self): + tklib.Tcl_DecrRefCount(self._value) + + def __str__(self): + if self._string and isinstance(self._string, str): + return self._string + return FromTclString(tkffi.string(tklib.Tcl_GetString(self._value))) + + def __repr__(self): + return "<%s object at 0x%x>" % ( + self.typename, tkffi.cast("intptr_t", self._value)) + + def __eq__(self, other): + if not isinstance(other, TclObject): + return NotImplemented + return self._value == other._value + + @property + def typename(self): + return FromTclString(tkffi.string(self._value.typePtr.name)) + + @property + def string(self): + if self._string is None: + length = tkffi.new("int*") + s = tklib.Tcl_GetStringFromObj(self._value, length) + value = tkffi.buffer(s, length[0])[:] + try: + value.decode('ascii') + except UnicodeDecodeError: + value = value.decode('utf8') + self._string = value + return self._string + \ No newline at end of file diff --git a/graalpython/lib-python/3/_tkinter/tklib_build.py b/graalpython/lib-python/3/_tkinter/tklib_build.py new file mode 100644 index 0000000000..0c3ba776e4 --- /dev/null +++ b/graalpython/lib-python/3/_tkinter/tklib_build.py @@ -0,0 +1,248 @@ +# C bindings with libtcl and libtk. + +from cffi import FFI +import sys, os + +# XXX find a better way to detect paths +# XXX pick up CPPFLAGS and LDFLAGS and add to these paths? +if sys.platform.startswith("openbsd"): + incdirs = ['/usr/local/include/tcl8.5', '/usr/local/include/tk8.5', '/usr/X11R6/include'] + linklibs = ['tk85', 'tcl85'] + libdirs = ['/usr/local/lib', '/usr/X11R6/lib'] +elif sys.platform.startswith("freebsd"): + incdirs = ['/usr/local/include/tcl8.6', '/usr/local/include/tk8.6', '/usr/local/include/X11', '/usr/local/include'] + linklibs = ['tk86', 'tcl86'] + libdirs = ['/usr/local/lib'] +elif sys.platform == 'win32': + incdirs = [] + linklibs = ['tcl86t', 'tk86t'] + libdirs = [] +elif sys.platform == 'darwin': + # homebrew + homebrew = os.environ.get('HOMEBREW_PREFIX', '') + incdirs = ['/usr/local/opt/tcl-tk/include/tcl-tk'] + linklibs = ['tcl8.6', 'tk8.6'] + libdirs = [] + if homebrew: + incdirs.append(homebrew + '/include/tcl-tk') + libdirs.append(homebrew + '/opt/tcl-tk/lib') +else: + # On some Linux distributions, the tcl and tk libraries are + # stored in /usr/include, so we must check this case also + libdirs = [] + found = False + for _ver in ['', '8.6', '8.5']: + incdirs = ['/usr/include/tcl' + _ver] + linklibs = ['tcl' + _ver, 'tk' + _ver] + if os.path.isdir(incdirs[0]): + found = True + break + if not found: + for _ver in ['8.6', '8.5', '']: + incdirs = [] + linklibs = ['tcl' + _ver, 'tk' + _ver] + for lib in ['/usr/lib/lib', '/usr/lib64/lib']: + if os.path.isfile(''.join([lib, linklibs[1], '.so'])): + found = True + break + if found: + break + if not found: + sys.stderr.write("*** TCL libraries not found! Falling back...\n") + incdirs = [] + linklibs = ['tcl', 'tk'] + +config_ffi = FFI() +config_ffi.cdef(""" +#define TK_HEX_VERSION ... +#define HAVE_WIDE_INT_TYPE ... +""") +config_lib = config_ffi.verify(""" +#include +#define TK_HEX_VERSION ((TK_MAJOR_VERSION << 24) | \ + (TK_MINOR_VERSION << 16) | \ + (TK_RELEASE_LEVEL << 8) | \ + (TK_RELEASE_SERIAL << 0)) +#ifdef TCL_WIDE_INT_TYPE +#define HAVE_WIDE_INT_TYPE 1 +#else +#define HAVE_WIDE_INT_TYPE 0 +#endif +""", +include_dirs=incdirs, +libraries=linklibs, +library_dirs = libdirs +) + +TK_HEX_VERSION = config_lib.TK_HEX_VERSION + +HAVE_LIBTOMMATH = int((0x08050208 <= TK_HEX_VERSION < 0x08060000) or + (0x08060200 <= TK_HEX_VERSION)) +HAVE_WIDE_INT_TYPE = config_lib.HAVE_WIDE_INT_TYPE + +tkffi = FFI() + +tkffi.cdef(""" +char *get_tk_version(); +char *get_tcl_version(); +#define HAVE_LIBTOMMATH ... +#define HAVE_WIDE_INT_TYPE ... + +#define TCL_READABLE ... +#define TCL_WRITABLE ... +#define TCL_EXCEPTION ... +#define TCL_ERROR ... +#define TCL_OK ... + +#define TCL_LEAVE_ERR_MSG ... +#define TCL_GLOBAL_ONLY ... +#define TCL_EVAL_DIRECT ... +#define TCL_EVAL_GLOBAL ... + +#define TCL_DONT_WAIT ... + +typedef unsigned short Tcl_UniChar; +typedef ... Tcl_Interp; +typedef ...* Tcl_ThreadId; +typedef ...* Tcl_Command; + +typedef struct Tcl_ObjType { + const char *name; + ...; +} Tcl_ObjType; +typedef struct Tcl_Obj { + char *bytes; + int length; + const Tcl_ObjType *typePtr; + union { /* The internal representation: */ + long longValue; /* - an long integer value. */ + double doubleValue; /* - a double-precision floating value. */ + struct { /* - internal rep as two pointers. */ + void *ptr1; + void *ptr2; + } twoPtrValue; + } internalRep; + ...; +} Tcl_Obj; + +Tcl_Interp *Tcl_CreateInterp(); +void Tcl_DeleteInterp(Tcl_Interp* interp); +int Tcl_Init(Tcl_Interp* interp); +int Tk_Init(Tcl_Interp* interp); + +void Tcl_Free(void* ptr); + +const char *Tcl_SetVar(Tcl_Interp* interp, const char* varName, const char* newValue, int flags); +const char *Tcl_SetVar2(Tcl_Interp* interp, const char* name1, const char* name2, const char* newValue, int flags); +const char *Tcl_GetVar(Tcl_Interp* interp, const char* varName, int flags); +Tcl_Obj *Tcl_SetVar2Ex(Tcl_Interp* interp, const char* name1, const char* name2, Tcl_Obj* newValuePtr, int flags); +Tcl_Obj *Tcl_GetVar2Ex(Tcl_Interp* interp, const char* name1, const char* name2, int flags); +int Tcl_UnsetVar2(Tcl_Interp* interp, const char* name1, const char* name2, int flags); +const Tcl_ObjType *Tcl_GetObjType(const char* typeName); + +Tcl_Obj *Tcl_NewStringObj(const char* bytes, int length); +Tcl_Obj *Tcl_NewUnicodeObj(const Tcl_UniChar* unicode, int numChars); +Tcl_Obj *Tcl_NewLongObj(long longValue); +Tcl_Obj *Tcl_NewBooleanObj(int boolValue); +Tcl_Obj *Tcl_NewDoubleObj(double doubleValue); + +void Tcl_IncrRefCount(Tcl_Obj* objPtr); +void Tcl_DecrRefCount(Tcl_Obj* objPtr); + +int Tcl_GetBoolean(Tcl_Interp* interp, const char* src, int* boolPtr); +int Tcl_GetInt(Tcl_Interp* interp, const char* src, int* intPtr); +int Tcl_GetDouble(Tcl_Interp* interp, const char* src, double* doublePtr); +int Tcl_GetBooleanFromObj(Tcl_Interp* interp, Tcl_Obj* objPtr, int* valuePtr); +char *Tcl_GetString(Tcl_Obj* objPtr); +char *Tcl_GetStringFromObj(Tcl_Obj* objPtr, int* lengthPtr); +unsigned char *Tcl_GetByteArrayFromObj(Tcl_Obj* objPtr, int* lengthPtr); +Tcl_Obj *Tcl_NewByteArrayObj(unsigned char *bytes, int length); + +int Tcl_ExprBoolean(Tcl_Interp* interp, const char *expr, int *booleanPtr); +int Tcl_ExprLong(Tcl_Interp* interp, const char *expr, long* longPtr); +int Tcl_ExprDouble(Tcl_Interp* interp, const char *expr, double* doublePtr); +int Tcl_ExprString(Tcl_Interp* interp, const char *expr); + +Tcl_UniChar *Tcl_GetUnicode(Tcl_Obj* objPtr); +int Tcl_GetCharLength(Tcl_Obj* objPtr); + +Tcl_Obj *Tcl_NewListObj(int objc, Tcl_Obj* const objv[]); +int Tcl_ListObjGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, int *objcPtr, Tcl_Obj ***objvPtr); +int Tcl_ListObjLength(Tcl_Interp* interp, Tcl_Obj* listPtr, int* intPtr); +int Tcl_ListObjIndex(Tcl_Interp* interp, Tcl_Obj* listPtr, int index, Tcl_Obj** objPtrPtr); +int Tcl_SplitList(Tcl_Interp* interp, char* list, int* argcPtr, const char*** argvPtr); + +int Tcl_Eval(Tcl_Interp* interp, const char* script); +int Tcl_EvalFile(Tcl_Interp* interp, const char* filename); +int Tcl_EvalObjv(Tcl_Interp* interp, int objc, Tcl_Obj** objv, int flags); +Tcl_Obj *Tcl_GetObjResult(Tcl_Interp* interp); +const char *Tcl_GetStringResult(Tcl_Interp* interp); +void Tcl_SetObjResult(Tcl_Interp* interp, Tcl_Obj* objPtr); + +typedef void* ClientData; +typedef int Tcl_CmdProc( + ClientData clientData, + Tcl_Interp *interp, + int argc, + const char *argv[]); +typedef void Tcl_CmdDeleteProc( + ClientData clientData); +Tcl_Command Tcl_CreateCommand(Tcl_Interp* interp, const char* cmdName, Tcl_CmdProc proc, ClientData clientData, Tcl_CmdDeleteProc deleteProc); +int Tcl_DeleteCommand(Tcl_Interp* interp, const char* cmdName); + +Tcl_ThreadId Tcl_GetCurrentThread(); +int Tcl_DoOneEvent(int flags); + +int Tk_GetNumMainWindows(); +void Tcl_FindExecutable(char *argv0); +""") + +if HAVE_WIDE_INT_TYPE: + tkffi.cdef(""" +typedef int... Tcl_WideInt; + +int Tcl_GetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *obj, Tcl_WideInt *value); +Tcl_Obj *Tcl_NewWideIntObj(Tcl_WideInt value); +""") + +if HAVE_LIBTOMMATH: + tkffi.cdef(""" +#define MP_OKAY ... +#define MP_ZPOS ... +#define MP_NEG ... +typedef struct { + int sign; + ...; +} mp_int; + +int Tcl_GetBignumFromObj(Tcl_Interp *interp, Tcl_Obj *obj, mp_int *value); +Tcl_Obj *Tcl_NewBignumObj(mp_int *value); + +int mp_unsigned_bin_size(mp_int *a); +int mp_to_unsigned_bin_n(mp_int * a, unsigned char *b, unsigned long *outlen); +int mp_read_radix(mp_int *a, const char *str, int radix); +int mp_init(mp_int *a); +void mp_clear(mp_int *a); +""") + +tkffi.set_source("_tkinter.tklib_cffi", """ +#define HAVE_LIBTOMMATH %(HAVE_LIBTOMMATH)s +#define HAVE_WIDE_INT_TYPE %(HAVE_WIDE_INT_TYPE)s +#include +#include + +#if HAVE_LIBTOMMATH +#include +#endif + +char *get_tk_version(void) { return TK_VERSION; } +char *get_tcl_version(void) { return TCL_VERSION; } +""" % globals(), +include_dirs=incdirs, +libraries=linklibs, +library_dirs = libdirs +) + +if __name__ == "__main__": + tkffi.compile(os.path.join(os.path.dirname(sys.argv[0]), '..')) + \ No newline at end of file