AndroWish on Tcl/Tk 9.1

AndroWish (Tcl/Tk on Android, SDL2/AGG) rebuilt on Tcl/Tk 9.1b0, with the full AndroWish extension set.

androwish-tcl91 on GitHub →

What it is

A port of AndroWish — Tcl/Tk on Android, via an SDL2 + AGG X11-emulation backend — rebuilt on Tcl/Tk 9.1b0 (the 2026-06-30 beta), targeting the 9.1 release.

Upstream AndroWish ships Tcl/Tk 8.6.10 on both its trunk and wtf-8-experiment Fossil branches, and there is no Tk-9-on-SDL upstream. The method: take stock Tk 9.1 generic/ and re-insert the SDL-specific regions that Christian Werner guards behind #ifdef PLATFORM_SDL — about 177 markers across 37 files — then port the self-contained sdl/ backend (~49k lines) and xlib/ (~1.8k lines) to Tk 9.1 internals (Tcl_Size, changed structs, and the Tcl 9 object-based widget-option APIs). It is not a three-way merge: the sdl2tk generic/ is forked from an older 8.6.x, so diffing against 8.6.10 is misleading.

Status

0.1-alpha. A signed arm64 APK boots on Android to an interactive Tcl console (File/Edit menu, % prompt) plus a main window with a Demos menu (12 entries) whose demos launch. All 84 native libraries build against Tcl/Tk 9.1b0 with zero errors, including the full "batteries included" extension set.

Runtime is verified end to end: the Tcl 9.1b0 interpreter runs on Android arm64 (tcl_patchLevel = 9.1b0, bignum arithmetic via libtommath), and the signed APK installs, launches, and renders the Tk root window. Outstanding: a default icon, and the empty placeholder . window.

A signed build is attached to the project's Releases.

Building (NDK/toolchain)

The upstream sources are supplied by the builder; this repo carries documentation and build configuration only.

Requirements:

The core Tcl module (libtcl.so) is regenerated from the Tcl 9.1 tarball into an ndk-build module. Seven changes distinguish it from the 8.6 module, including: regenerating the source list from the Tcl 9.1 unix/Makefile.in OBJS; adding utf8proc with -DUTF8PROC_STATIC (a new 9.x encoding dependency); pointing TCL_LIBRARY at the staged tcl9.1 assets; and dropping TCL_UTF_MAX=6. The standalone stock-configure route fails on macOS (a Darwin define leak makes tclUnixTime #error); the ndk-build path is the supported one.

export ANDROID_NDK_HOME=.../ndk/27.3.13750724
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
export PATH="$ANDROID_NDK_HOME:$PATH"

"$ANDROID_NDK_HOME/ndk-build" NDK_PROJECT_PATH=. \
    APP_BUILD_SCRIPT=./jni/Android.mk \
    NDK_APPLICATION_MK=./jni/Application64.mk APP_ABI=arm64-v8a -j8
./gradlew assembleRelease

Two gotchas:

Packaging (APK)

The alpha APK is Tcl/Tk 9.1b0, arm64-v8a only, minSdkVersion 21, targetSdkVersion 36, versionName 0.1-alpha, with 84 native libraries and the full batteries-included extension set. Its applicationId is tk.tcl.wish.tcl91, so it installs alongside stock AndroWish (tk.tcl.wish) rather than replacing it. The release build is 32,145,360 bytes (sha256 8651613169…f959).

Two Android packaging facts:

Under Tcl 9, zipfs mounts live under //zipfs:/ (TIP 430), so the boot directory and TCL_LIBRARY/TK_LIBRARY are rewritten to //zipfs:/assets/... for the 9.x build.

Implementation notes

Four runtime bugs stood between "it links" and "it runs". All are written up in docs/PORT.md.

  1. Tcl_GetIntFromObj failed for every non-negative integer on arm64, surfacing as bad level "#0" from upvar #0 inside tkInit — no console and no menu. TCL_WIDE_INT_IS_LONG was undefined on this LP64 build, so Tcl_GetLongFromObj took its 32-bit-long branch where (Tcl_WideInt)(ULONG_MAX) overflows to -1, making the range check w <= -1 reject everything. Arithmetic still worked, because bytecode literals are parsed at compile time; only runtime string→integer conversion broke. Fix: define the macro.

  2. Stack corruption (SIGABRT, -fstack-protector) in wm title. The 8.6-era X11-emulation layer passed int* where Tcl 9 expects Tcl_Size* (8-byte) out-params, smashing the stack. These are silent -Wincompatible-pointer-types warnings; every Tcl_GetStringFromObj, Tcl_ListObjGetElements, Tcl_GetByteArrayFromObj and Tcl_SplitList in the SDL layer had to be checked for an int out-param.

  3. Pointer-truncation SIGSEGV. -Wno-implicit-function-declaration had masked missing prototypes for SDL-backend calls into pointer-returning Tk internals, so their results were int-truncated. Removing the flag and adding the missing prototypes fixed it. Implicit-declaration warnings on a 64-bit build hide pointer truncation.

  4. Fonts. Several failures stacked up: an uninitialized font hash before first window-decoration draw; the bundled TTFs not being staged (see above); and a wildcard XLFD that could not match a concrete family, panicking with "cannot get any font".

A separate issue: everything rendered tiny on high-DPI. Tk was correct — it computed 44.5 px for a 10 pt font at 320 dpi. The bug was in the emulated XListFonts, which answered generic family requests (Helvetica, courier) with a hardcoded XLFD carrying pixel size 12. In X11 a non-zero pixel size means a fixed bitmap font, so Tk loaded it as-is and discarded the requested size. Synthesized font-list entries must use pixel size 0 (= scalable).

A 100%-reproducible launch crash on API 34+: addGpsStatusListener/addNmeaListener throw UnsupportedOperationException on every launch, killing the console process where wish runs, so the app never appears. The fix guards both registrations (and their teardown) behind an SDK-version check with try/catch. The modern GnssStatus/OnNmeaMessageListener replacements are not yet wired to Tcl, so borg gps/NMEA is inactive on API 34+.

On-device verification uses a repeatable check: a smoke script asserts all four bugs and the console wiring on-device and passes 15/15 at both 320 dpi / API 34 and 420 dpi / API 31. Not yet tested on physical hardware.

Repository

The repo contains documentation and ndk-build configuration only; it does not redistribute the Tcl, Tk, SDL2 or AndroWish sources. Docs: docs/PORT.md (roadmap and every runtime fix), docs/AGENT-BOOTSTRAP.md (build/test commands and environment traps), docs/SDL2TK-GRAFT.md (how the backend was grafted onto stock Tk 9.1), and docs/BATTERIES-TRIAGE.md (the per-extension Tcl 9 migration matrix). Licensed under the same terms as Tcl/Tk. AndroWish is by Christian Werner; Tcl/Tk by the Tcl Core Team.