Distributing classic Mac applications from PowerPC Mac to Apple Silicon, bringing retro Mac experiences to modern systems. Why throw away a Mac that still works? True sustainability is using what we have.
An ultra-lightweight 3-pane web reader for PowerPC Macs (G3/G4/G5) running Mac OS X 10.4 Tiger. Modern web pages are full of JavaScript, ads, and huge images that hardware from the mid-2000s simply can't handle. Kodama loads none of that — it parses raw HTML with libxml2, keeps only the headings, body text, and image links, and draws them directly with stock Cocoa components. No JavaScript, no CSS engine. The goal is to stay usable even on a 450MHz G3.
[ Show image N ] links and only fetched on click.curl now ships inside the
app itself, so Tiger's ancient system curl is never used. (Older versions needed a separate curl
installed via Tigerbrew
or MacPorts — that's no longer necessary, though Kodama will still fall back to one if present.)
Kodama.dmg and open it.Kodama.app to /Applications or wherever you like on the target PowerPC Mac.| Environment | PowerPC Mac / Mac OS X 10.4.11 (Tiger) |
|---|---|
| Verified hardware | iBook G4 |
A personal hobby project provided with no warranty, MIT licensed. See GitHub for source code and technical details.
Kodama is basically just a lightweight parser that turns raw HTML into structure using libxml2. No WebKit, no JavaScript, no CSS engine at all. The goal from day one was staying usable on a 450MHz G3. Here's how it actually came together, and some of the real-site landmines along the way.
Modern web pages are full of JS, ads, and huge images that a G3-era CPU just can't render. So Kodama skips WebKit entirely — it parses HTML with libxml2, pulls out just the headings, body text, and image links, and draws them with plain Cocoa components. No JS, no CSS to execute, so there's nothing there to be slow.
Even in the first pass at the parser, real-hardware testing turned up one old-environment wall after another. The libxml2 that ships here (2.6.16) doesn't even have the HTML_PARSE_RECOVER constant. Foundation on 10.4 doesn't have componentsSeparatedByCharactersInSet:, so that had to be rewritten around NSScanner instead. The most annoying one was gcc 4.0.0's Objective-C string literals — an @"..." literal containing multi-byte characters like Japanese just doesn't encode correctly and comes out garbled. Ran into that enough times that a small macro, PWRJPStr, was born — it builds strings from a plain C literal via stringWithUTF8String: instead, and every UI string uses it from then on.
Tiger has neither GCD nor blocks, so even calling curl asynchronously took some doing. CurlTaskRunner waits on both NSTaskDidTerminateNotification and NSFileHandleReadCompletionNotification at once and reports back through a delegate — the old-school way. On real hardware it pulled off actual HTTPS fetches using a portable curl from Tigerbrew (7.58.0 + OpenSSL 1.0.2l), which is what proved the core idea of the whole project: dodging the TLS 1.2/1.3 handshake problem was actually possible.
v0.2's requirement for a Tigerbrew/MacPorts curl worked, but it meant a plain Tiger install with
neither of those was dead in the water — confirmed by an actual user hitting exactly that on a
real Power Mac G4. v0.2.1 bundles a static PPC build of curl 8.9.1 (against LibreSSL 3.8.4) plus a
current cacert.pem straight inside Kodama.app/Contents/Resources.
CurlTaskRunner's path detection now checks for that bundled copy first, before falling
back to the old search order (/opt/local → /usr/local/bin → a
portable curl on the user's PATH) for anyone who still has one installed. The
--cacert flag only gets passed when the bundled curl is the one actually running —
fall back to a system-installed curl and it keeps trusting whatever CA store that one already trusts,
rather than overriding it.
The right pane (image preview) sits at zero width by default and only opens when you click an image link. Except Tiger's NSSplitView (10.4) doesn't have setPosition:ofDividerAtIndex:, so the modern approach was off the table. Had to fall back to the old way: calculating subview frames by hand inside splitView:resizeSubviewsWithOldSize: to collapse and expand it.
This is probably where most of the actual time went.
No HTML form support yet (search boxes, etc.) — the address bar's DuckDuckGo fallback is the only workaround for now, still thinking about the right way to handle it.
Full source code is on GitHub.