undroidwish for Apple Silicon

undroidwish (SDL2 wish, full extension set) built natively for arm64 macOS, in both Tcl/Tk 8.6 and Tcl/Tk 9.1.

Tcl/Tk 8.6 build on GitHub → Tcl/Tk 9.1 build on GitHub →

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 buildundroidwish-arm64-batteries-included

Tcl/Tk 9.1 buildundroidwish-arm64-batteries-included-tcltk91

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:

9.1 build — port complete at spike level, production tail remaining:

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: intbool, ClientDatavoid*). 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 Tkpackage require BLTblt::graphelement createtime {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

Part of John Buckman's Tcl/Tk projects.