diff --git a/graalpython/lib-python/3/_tkinter/__init__.py b/graalpython/lib-python/3/_tkinter/__init__.py
new file mode 100644
index 0000000000..44508ceb40
--- /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))
\ No newline at end of file
diff --git a/graalpython/lib-python/3/_tkinter/app.py b/graalpython/lib-python/3/_tkinter/app.py
new file mode 100644
index 0000000000..58dcfaa035
--- /dev/null
+++ b/graalpython/lib-python/3/_tkinter/app.py
@@ -0,0 +1,591 @@
+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
+
+import threading
+
+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..667204e661
--- /dev/null
+++ b/graalpython/lib-python/3/_tkinter/tclobj.py
@@ -0,0 +1,233 @@
+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..c079e3c20c
--- /dev/null
+++ b/graalpython/lib-python/3/_tkinter/tklib_build.py
@@ -0,0 +1,251 @@
+# 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 = [
+        '/opt/homebrew/opt/tcl-tk@8/include',
+        '/opt/homebrew/opt/tcl-tk@8/include/tcl-tk',
+    ]
+
+    linklibs = ['tcl8.6', 'tk8.6']
+    libdirs = []
+    if homebrew:
+        incdirs.append(homebrew + '/include')
+        libdirs.append(homebrew + '/opt/tcl-tk@8/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 <tk.h>
+#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 <tcl.h>
+#include <tk.h>
+
+#if HAVE_LIBTOMMATH
+#include <tclTomMath.h>
+#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
diff --git a/graalpython/lib-python/3/tkinter/__init__.py b/graalpython/lib-python/3/tkinter/__init__.py
index ac70d965f3..98abdc96a8 100644
--- a/graalpython/lib-python/3/tkinter/__init__.py
+++ b/graalpython/lib-python/3/tkinter/__init__.py
@@ -34,9 +34,142 @@
 import enum
 import sys
 import types
+import os
+import subprocess
+import importlib
+import shutil
+import platform
+
+GREEN = "\033[92m"
+RED = "\033[91m"
+RESET = "\033[0m"
+
+def check_package(name: str) -> bool:
+    try:
+        importlib.import_module(name)
+        return True
+    except ImportError:
+        return False
+
+def is_tcl_tk_installed( name: str ) -> bool:
+
+    if sys.platform == "darwin":
+        try:
+            subprocess.check_output(["brew", "list", "--versions", name])
+            return True
+        except subprocess.CalledProcessError:
+            return False
+    else:
+        try:
+            subprocess.check_output(
+                ["tclsh", "-c", "puts [info patchlevel]"],
+                stderr=subprocess.DEVNULL
+            )
+            return True
+        except subprocess.CalledProcessError:
+            return False
+
+def prompt_user_install(package_name: str) -> bool:
+
+    response = input(f"Would you like to install {package_name}? [Y/n]: ").strip().lower()
+    return response in ("", "y", "yes")
+
+def install_tcl_tk(name:str) -> bool:
+    if sys.platform == "darwin":
+        try:
+            subprocess.check_call(["brew", "install", name])
+            print(f"{GREEN}{name} installed successfully.{RESET}")
+        except subprocess.CalledProcessError:
+            print(f"{RED}Failed to install {name}.{RESET}")
+            sys.exit(1)
+
+    else:
+        subprocess.check_call(["sudo", "apt", "install", "-y", "tcl8.6-dev", "tk8.6-dev"])
+
+def install_system_dependencies():
+
+    if sys.platform == "darwin":
+        if not shutil.which("brew"):
+            if prompt_user_install("Homebrew"):
+                subprocess.check_call([
+                    "/bin/bash", "-c",
+                    "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+                ])
+            else:
+                print("Cannot continue without Homebrew. Please install it and rerun.")
+                sys.exit(1)
+
+    tcl_tk = "tcl-tk@8"
+    sys.stdout.write(f"Checking {tcl_tk}... ")
+    sys.stdout.flush()
+    if is_tcl_tk_installed(tcl_tk):
+        sys.stdout.write(f"{GREEN}Installed{RESET}\n")
+    else:
+        sys.stdout.write(f"{RED}Not installed{RESET}\n")
+        if prompt_user_install(tcl_tk):
+            install_tcl_tk(tcl_tk)
+        else:
+            print(f"{RED}Cannot continue without {tcl_tk}. Please install it and try again.{RESET}")
+            sys.exit(1)
+
+
+def run_tkinter_build_script():
+
+    current_dir = os.path.dirname(__file__)
+    system = platform.system().lower()
+    machine = platform.machine().lower()
+    if machine == 'x86_64':
+        machine = 'x64'
+    elif machine == 'arm64' or machine == 'aarch64':
+        machine = 'aarch64'
+
+    platform_id = f"{system}-{machine}"
+
+    py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
+    base_dir = os.path.abspath(os.path.join(current_dir, "..", "..", "..", "..", ".."))
+    script_path = os.path.join(
+        base_dir,
+        platform_id,
+        "GRAALPY_JVM_STANDALONE",
+        "lib",
+        f"python{py_version}",
+        "_tkinter",
+        "tklib_build.py"
+    )
+    if not os.path.exists(script_path):
+        raise FileNotFoundError(f"Build script not found: {script_path}")
+    subprocess.check_call([sys.executable, script_path])
+
+def setup_tkinter():
+
+    packages = ["cffi", "setuptools"]
+
+    for pkg in packages:
+        sys.stdout.write(f"Checking {pkg}...")
+        sys.stdout.flush()
+
+        if check_package(pkg):
+            sys.stdout.write(f"{GREEN}Installed{RESET}\n")
+        else:
+            sys.stdout.write(f"{RED}Not installed{RESET}\n")
+            if prompt_user_install(pkg):
+                subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])
+                print(f"{GREEN}{pkg} installed successfully.{RESET}")
+
+            else:
+                print(f"Cannot continue without {pkg}. Please install using: \"pip install {pkg}\"")
+                sys.exit(1)
+
+    install_system_dependencies()
+    run_tkinter_build_script()
+
+try:
+    import _tkinter
+except Exception:
+    setup_tkinter()
+    import _tkinter
+    TclError = _tkinter.TclError
 
-import _tkinter # If this fails your Python may not be configured for Tk
-TclError = _tkinter.TclError
 from tkinter.constants import *
 import re
 
