What it is
undroidwish is the desktop, batteries-included SDL2 wish from AndroWish — AndroWish without the Android. Instead of rendering Tk through a real X server, Cocoa, or Win32, it draws Tk via an X11-server emulation layer built on top of SDL2 (with Anti-Grain-Geometry for rasterization). That backend, called sdl2tk, is a fork of Tk's Unix platform layer.
This project builds undroidwish as a native Apple Silicon (arm64) macOS binary. The stock undroidwish is x86-64 and runs on Apple Silicon only through Rosetta 2, which Apple is retiring. A native arm64 binary allows AndroWish/Tk apps (for example, de1app) to keep running on the Mac after Rosetta is removed. On an M4 the arm64 build is about 1.5× faster on compute and 1.25× faster on cold startup than the same code under Rosetta.
The work spans two sibling repositories — one that ships Tcl/Tk 8.6 and one that ports the SDL2 backend forward to Tcl/Tk 9.1.
The two builds (8.6 & 9.1)
Both are batteries-included, native-arm64 undroidwish builds. They target different Tcl/Tk generations and are at different maturity levels.
Tcl/Tk 8.6 build — undroidwish-arm64-batteries-included
- Ships Tcl/Tk 8.6.10. Shipped, notarized production build.
- Native Cocoa desktop build, delivered as a self-contained single-file Mach-O (the
wishbinary with an embeddedassets.zip). - Distributed under the Tcl/Tk (BSD-style) license for the patches and docs; AndroWish and its bundled third-party libraries keep their own licenses.
- Bundles ~60 statically-linked Tcl/Tk extensions (Img, tls, tdom, BLT, tkpath, itk, treectrl, mpexpr, and more), plus the reintroduced desktop
borgcommand and a Bluetooth-LE (ble) demo.
Tcl/Tk 9.1 build — undroidwish-arm64-batteries-included-tcltk91
- Ports the
sdl2tkbackend forward to Tcl/Tk 9.1b0 (Tk 9.1 is the first Tk 9.x release able to run on the SDL2 backend). Tk 9 is a major release with an ABI break from 8.6 (Tcl_Size/TIP 494, removed deprecated APIs, renamed platform hooks,Tcl_ObjCmdProc2), so this is a port, not a version bump. - Runs natively on arm64 as a self-contained
.app, reproducing the undroidwish bare launch (console + Tk window + Demos menu). All 17 bare-launch demos work (it started at 1). 21 C-extension stacks are ported to Tcl 9.1. - Licensed under the Tcl/Tk license (matching upstream Tcl/Tk).
Both repositories contain patches, new sources, and documentation only — they do not redistribute the AndroWish, Tcl, Tk, SDL2, FreeType, or AGG sources.
Status
8.6 build — shipped:
- Multiple alpha releases, the latest notarized and stapled (passes Gatekeeper as a Notarized Developer ID app).
- The Tcl regression suite passes natively on arm64 (46090 total, 38920 passed, 7169 skipped, and 1 failure that is an environment/harness artifact — a locale-encoding test — not an arm64 bug).
- Native arm64 is about 1.46× faster overall than x86-under-Rosetta (startup 1.25×, math 1.47×, general Tcl 1.53×, Tk 1.31×). The two builds also differ by a Tcl minor bump (8.6.9 vs 8.6.10), so this is a build-to-build comparison, not pure architecture isolation.
- Includes a macOS Retina rendering fix (the UI fills the whole window on a built-in Retina display instead of drawing into a corner) and non-overlapping bare-launch windows.
9.1 build — port complete at spike level, production tail remaining:
- The entire SDL backend compiles clean against Tk 9.1 (all files, zero errors), and the bare
wishlinks and runs. - Tk 9.1-on-SDL renders a full, visible window natively on arm64, with all 17/17 demos working and 21 C-extension stacks ported. The
.appis self-contained with library auto-discovery, a bare-launch console, and a Demos menu. - Remaining for production: notarization, integration into the 8.6 build recipe, application validation, and roughly 40 optional non-demo batteries (an individual-fix tail). A window-management capture fix and a Bluetooth usage-description fix have landed.
Building
Both repositories are recipes: the upstream sources (AndroWish, Tcl, Tk, SDL2, FreeType, AGG) are fetched separately, the repo's patches applied, and its build script run. Neither repo redistributes upstream source.
8.6 build. Get an AndroWish checkout, apply the repo's patches with apply-patches.sh, then run AndroWish's build-undroidwish-macosx.sh out of tree (the script refuses to run inside the AndroWish source tree). It first inits by copying sources, then builds. Prerequisites: an Apple Silicon Mac (macOS 11+), Xcode command-line tools, and MacPorts for autoconf/automake/ pkg-config/cmake. For a notarizable single-file binary, the build uses an ebuild target that embeds assets.zip as a Mach-O __TEXT,__zipfs section (a plain appended zip leaves trailing data after the Mach-O and fails strict codesign validation).
9.1 build. Uses a direct build script (no autoconf): it compiles Tk 9.1's generic/ sources plus the SDL platform layer and AGG renderer, and links against a prebuilt static Tcl 9.1, SDL2, FreeType, and AGG. The script also packages a self-contained .app whose Tcl/Tk 9.1 script libraries live under Contents/Resources; the binary discovers them relative to itself, so TCL_LIBRARY / TK_LIBRARY need not be set.
A .app bundle is required on macOS for both builds. A bare binary launched from a terminal gets no window-server connection and renders nothing (CGContextFillRects: invalid context 0x0). Run via open.
The x86-64 half of the universal notarized macOS app (the Intel slice, for a lipo'd universal binary) is built separately by GitHub Actions on a native x86 runner and combined with the arm64 slice.
Implementation notes
The core arm64 fix (8.6). AndroWish's bundled SDL2 configure treated the Apple-Silicon Mac host triple arm64-apple-darwin* as iOS — its iOS branch label arm*-apple-darwin* swallowed arm64 Macs — so it enabled UIKit/OpenGL ES and disabled Cocoa, then failed with GLES/gl.h not found. x86 Macs never hit this. The fix narrows the iOS label so arm64 Macs take the macOS/Cocoa branch.
Modern-toolchain hardening (8.6). A chain of issues from a recent clang and old autoconf: implicit-int / implicit-function-declaration / int-conversion became hard errors; autoconf-2.13 cc -E probes aborted on SIGPIPE and mis-detected size_t/pid_t/sizeof as 0 (poisoning downstream builds), fixed by pre-seeding the autoconf cache; x86-only SIMD paths in jpeg-turbo; and bundled codec gaps in libpng (a NEON symbol) and libtiff (uncompiled PixarLog/ZIP codecs).
Why 9.1 forced a full Tk port. No Tk-9-on-SDL exists upstream, and Tk 9.1 only runs on Tcl 9.1 — so the 8.6 Tk backend cannot be paired with Tcl 9. The platform contract (Tkp*) is largely stable across 8.6→9.1 (one new stubbable hook, plus mostly mechanical signature tweaks: int→bool, ClientData→void*). AndroWish barely patched Tk's generic/, so the approach was to take Tk 9.1's generic/ wholesale, re-apply the small AndroWish hooks, and re-fork only the platform layer. The long pole was recompiling ~70 C batteries against Tcl 9 stubs.
The Tcl/Tk 9.1 window-manager capture fix. Early in the 9.1 port, window management was dead — you could not move, resize, or close a window after the first click landed in any widget. The root cause was a missing capture release (TkpSetCapture(NULL)); once fixed, decorative frames move, resize, and close correctly. Two lessons: (1) building on pristine Tk 9.1 generic/ silently drops every AndroWish #ifdef PLATFORM_SDL hunk, so those hunks (font aliases/DPI sizing, the sdltk ensemble and -sdl* options, the capture release) must be deliberately re-applied — and several files remain to be reviewed; and (2) the fix was verified with a headless UI test harness that injects synthetic SDL mouse events (uwsynthmouse) plus SDL event tracing, so window-management regressions can be tested without taking over the machine's real pointer.
The Tcl_Size class-of-bug (9.1). Tk 9 widened many length types to Tcl_Size (TIP 494). Any int length variable still receiving a value from Tcl_ListObjGetElements / Tcl_SplitList / Tcl_GetStringFromObj can overrun and crash (SIGBUS) as its code path executes. Several sites in the SDL layer are fixed; a sweep of the remainder is a tracked task.
The widget-extension configuration shim (9.1). Old extensions call the string-based Tk_ConfigureWidget(interp, tkwin, specs, argc, argv, rec, flags), which Tk 9 made object-based — a mismatch that segfaults. The fix is a shim the wish itself exports that converts char** argv into Tcl_Obj** and calls the real object-based function; the extension build then source-rewrites each call site to the shim. (A macro-based approach fought the stubs machinery and was abandoned in favor of the source rewrite.)
Headless / BLT rendering. undroidwish can boot a GUI script and render Tk — including BLT graphs — fully headless, with no display attached: SDL creates an offscreen surface, widgets map, and update idletasks paints. This was confirmed while benchmarking a live-chart redraw hot path — a script that did package require Tk → package require BLT → blt::graph → element create → time {update idletasks} returned redraw timings with winfo ismapped true and correct widget geometry, from a plain shell. (The package name is BLT v2.4, not tkblt.) The Tk GUI test suite cannot run headless on this SDL2 build — it has only Cocoa and dummy drivers, and modal dialogs block the run — so those tests need a real Cocoa session; that is a property of the reduced X11 backend, not an arm64 issue.
Repositories
- Tcl/Tk 8.6 (shipped, notarized):
johnbuckman/undroidwish-arm64-batteries-included - Tcl/Tk 9.1 (work in progress):
johnbuckman/undroidwish-arm64-batteries-included-tcltk91
Part of John Buckman's Tcl/Tk projects.