Infinite Mac: DingusPPC Explorations #

One of my goals for Infinite Mac is to learn more about computer architecture and other fundamentals. While fiddling with actual old hardware is not as interesting to me (the one classic Mac I own is a PowerBook 550c that I turn on about once a year), I do enjoy operating at lower levels of the stack than I normally encounter. Interacting with the emulators that I’ve ported to Emscripten has definitely exposed me to more of this world, but it’s still been pretty superficial. Even the FPU fidelity tweaks from last year amounted to just changing some build flags once I understood what was going on.

That being said, I’m not sure that spending more time with the current set of emulators would be the best use of my time. Basilisk II, SheepShaver and Mini vMac are mature (old enough to drink or rent a car) codebases. That in and of itself is not bad, but the original authors are gone, the vision has drifted, they have fragmented into forks or states of semi-abandonment and have fundamental limitations (aggressive ROM patching and lack of an MMU).

With that in mind, I went looking for other options. The Mac Emulation Matrix spreadsheet lists many choices, and I was particularly interested in options that would eventually allow (early) Mac OS X to be runnable. The obvious choice was QEMU – it has very broad guest OS support and is very actively developed. However, it’s also a beast of a project to build and navigate around; it didn’t seem like it would be something I would able to make much progress on while working on it for a few hours a week. There is also some work on Power Macintosh support in MAME, but it’s a similar large codebase, and it seemed to be much further behind QEMU, compatibility-wise. I also briefly considered PearPC (which is much more focused on early PowerMacs than QEMU), but it’s also in a state of abandonment (development mostly stopped in 2005, with a brief resurrection in 2015).

I then came across DingusPPC, development of which has been going on for a few years (chronicled in this Emaculation thread and these Reddit posts). It had picked up steam recently, getting to a somewhat usable state, with some hope of booting Mac OS X too. The codebase was much smaller than QEMU’s (as was the resulting binary - 1.5MB vs. 17MB) and seemed reasonably modern. I crossed my fingers that this was a KHTML-vs.-Gecko-in-2003 decision and decided to give DingusPPC a try.

My first task was figuring out what worked under DingusPPC — it emulates lots of machines but some are more fleshed out than others. I settled on the Power Macintosh 6100 running System 7.1.2 (via the Disk Tools floppy) both because it seemed to have had the most attention from the developers and because it’s a combination that’s unsupported by SheepShaver or QEMU (both of which emulate PCI-era Macs). Once I had a working native build and machine/OS configuration, I started to work on getting it to build under Emscripten. DingusPPC doesn’t have much cross-platform support beyond what’s provided for free by SDL, and SDL’s Emscripten compatibility layer does not work for Infinite Mac (it assumes direct DOM access is possible from the generated code, but I run all emulators in a worker for better performance). I ended up with an approach inspired by Chromium’s, where some classes have per-platform implementations which are included automatically based on filename suffixes.

Usually when bringing up a new emulator I implement video output first, because it makes debugging much easier — if the ROM is being read correctly I should at least see a happy or sad Mac. I did the same thing with DingusPPC (DPPC from here on out), but I was disappointed to end up with a black screen.

Black screen when tryig to boot DingusPPC
Black screens - it’s not just for graphics programmers.

To get a high-level overview of what was happening, I enabled DPPC’s CPU profiling support and added a periodic dump when host events were read (i.e. every 11ms). I could then compare the counters when booting the same configuration in both the native and Emscripten builds, and see where the divergence occurred. It became apparent that after around 300ms the Emscripten build was stuck in a loop, since the number of supervisor instructions remained flat, while it would occasionally increase in the native one.

I was stumped for a while until I increased the logging level to maximum verbosity, and noticed that a AMIC: Sound out DMA control set to 0x0 line was logged right before the divergence started. I had seen that the native build starts a background thread to pull data from the sound DMA channel, but I had left all of the sound support in the Emscripten build stubbed out, since it’s usually something that I tackle much later. In this case sound turns out the load-bearing — if the DMA channel does not get drained, the boot process hangs. I added a basic polling loop to read from the DMA channel and send it to the JS side, and I got the flashing question mark I was expecting:

Flashing question mark boot screen

The reason why the flashing question was expected is because DPPC had no way to read the startup floppy disk image — it needs to be bridged to the JS APIs that handle streaming of content over the network. I moved all of DPPC’s disk image reading behind an abstraction, added a JS-backed implementation, and then I had a successful boot from the floppy.

Welcome to Power Macintosh startup screen
System 7.1.2 was the only release to say “Welcome to Power Macintosh” when starting up one of those machines.

Once I had the system booting, input was the next task. I was able to send mouse updates pretty easily, but it became immediately apparent that the position was handled differently. Other emulators I’ve ported operate in terms of absolute coordinates, but DingusPPC pretends to have a physical mouse attached, and only sends deltas to the guest OS. While adding support for deltas was easy, I ran into the issue that the guest OS treats them as raw inputs, and then computes an extra acceleration curve based on them, thus movements were not 1:1. For now I chose to implement pointer lock support (another case where a modern web API makes things easier), but the likely better long term fix is to make DingusPPC support the extended ADB protocol, which includes the ability to send absolute coordinates (originally meant for graphics tablets).

I then went to make keyboard input work, which seemed easy enough, but it didn’t seem to work. A quick check in the native build showed that it wasn’t working there either, because the ADB keyboard implementation was actually a stub. I figured this was a good first contribution to upstream to the DPPC project, so I began to spend some quality time with tech note HW01 and the relevant chapter of Inside Macintosh. The protocol was straightforward enough, but it took me a while to catch on to the fact that there was a separate polling loop that needed to be triggered. I did eventually succeed, and the keyboard now works in both the native and Infinite Mac builds of DPPC (though I did have to make some followup fixes).

Key Caps desk accessory

During all of this development I observed that DingusPPC was extremely slow (as in, more than 3 minutes to go from boot to the Finder running). While this could be attributed to its CPU emulator being a pure interpreter and the fact that it more accurately emulates the timings of a floppy drive, the Emscripten build was particularly slow. I ran a profile and was surprised to see that a lot of time was being spent going between JavaScript and WebAssembly.

Bottom-profile results showing top functions
Top Functions
Flame graph showing call stacks
Sampled call stacks

While I do end up calling to JavaScript from the input, disk and display implementations, those hops are relatively rare. These were happening in the core CPU emulation loop, and thus every instruction. The js-to-wasm and wasm-to-js functions and the bouncing back-and-forth is done by Emscripten to implement setjmp/longjmp support, and DPPC uses those to implement exceptions in the CPU. While there is an option to use native Wasm exceptions, it’s only supported by relatively modern browsers, and I try be pretty conservative about which browsers are supported (the non-SharedArrayBuffer fallback mode is still being used by 5% of users).

I made an attempt at retrofitting DPPC to not use setjmp/longjmp, but it became extremely invasive due to the need to abort execution in all of the codepaths that exceptions could happen. I then took a closer look at the stacks that that the profiler showed and noticed that ppc_exec_inner was not in them — it was getting inlined into the ppc_exec outer loop, which is also where the setjmp call was. I had a hunch that forcing the function to not be inlined would change when the JS ↔ WASM hops would happen, and I was right — they only happened once when starting the main loop, and boot times became 2.5 times faster with this 1-line change.

All my work until this point had been from floppy or CD-ROM images, but I was hoping to make hard drive images work too. It turned out that DPPC expects full device images (with a partition map), whereas I had previously operated only on bare HFS partitions/disk images. Coincidentally around this time the creator of BlueSCSI filed an issue asking for device image support, and had some technical pointers. I was able to automatically wrap my existing images and have them work for both BlueSCI uses and as boot devices for DPPC. Unfortunately DPPC’s SCSI hard drive support is still nascent, so while the hard drive boot process begins, it’s not able to complete.

The current status is that there are a few experimental DPPC-based machines that can be run, currently only accessible via the custom instance option. Some notable combinations:

Only the first three boot successfully, but it’s still pretty satisfying to to have gotten this far. Code-wide, I still have my own fork, but I’ve been able to upstream 20 or so changes, including quite a few that help the native build too. I hope to blog about the CPU emulation ones soon — they do scratch my itech of learning more about computer internals. The DPPC tracking issue has some notes on remaining work, and I’ll be rebasing my fork as the project makes progress.

On the non-DingusPPC front, I’ve made a few small improvements to Infinite Mac:

  • The aforementioned device image support means that it’s possible to import and export .hda images for use with BlueSCSI and other physical devices, here’s a demo from a user that tried it.
  • I added custom display size support (for Basilisk II and SheepShaver-based machines), so that you can pretend to have Portrait Display or have the Mac take up your entire window or screen.
  • I kept running into instability with the Infinite HD drive under System 6, seemingly due to a corrupt desktop database. The simplest solution turned out to be to generate a smaller disk image for older OSes (since a lot of 90s-era software didn’t run on them anyway). Chances are the Finder from this era was not really set up for a 2GB disk with tens of thousands of files.

Finally, I would remiss if I didn’t mention Software Applications Incorporated’s website, which takes Infinite Mac in a very interesting direction.

Infinite Mac: Improved Persistence #

Infinite Mac has supported a limited form of persistence since its initial launch. This was done by exporting the contents of the “Saved” folder in “The Outside World” to IndexedDB when the emulator was shut down. While this worked, it had several limitations:

  • Saving was best-effort during the page unload process. The beforeunload event that it relied on does not always fire, and it required an asynchronous postMessage step by which point the process for the page may have been terminated.
  • It relied on Basilisk II and SheepShaver’s ExtFS support (which creates a virtual drive using the File System Manager). This meant that it was not available in Mini vMac-based emulators (i.e. anything for System 6 or earlier).
  • Even when ExtFS/File System Manager is available, the virtual drive has several limitations: some software thinks it’s a networked drive and refuses to run on it, and it’s not bootable.

With the improved disk abstractions that enabled remote CD-ROM support, I realized that that I could mount another hard disk image in the emulated Mac, intercept read and write calls to it, and handle them from a persisted store. This was made possible by the new-ish origin private file system (OPFS) API, and specifically the ability to do synchronous file operations from a worker (without heroics). It’s supported in all modern browsers, including WebKit-based ones. The synchronous part is key because it allows all changes to be persisted as they happen, instead of during page unload.

The specific approach that I settled on was to have a mostly empty 1GB HFS disk image that serves as a “Saved HD”. Any modifications to it (tracked using the same 256KB chunk granularity that’s used for disk image streaming) are persisted in an OPFS file (a second file maintains the indexes of all modified chunks). The “mostly” empty part is because there is some metadata (desktop database, custom icon) as well as a Read Me file that are in the initial state of the disk image. This system makes the actual OPFS space that’s consumed to be proportional to the amount of data written, instead of ballooning to 1GB from the get go. It could also be extended to support a snapshotting/copy-on-write system down the line.

While OPFS is pretty broadly available, Infinite Mac still has enough visitors from older browsers that I wanted to detect support for it (the API is also not available in private Safari windows). One annoying gotcha is that the synchronous file API is only available in workers, and there’s no way to detect its presence from the main browser process. I therefore have to create a temporary worker to check for the existence of the createSyncAccessHandle and related functionality. The check is done as early as possible and the results are cached, so that most of the time we can synchronously determine the support level.

Settings dialog showing options to import and export the Saved HD

With all that in place, it’s possible to use the Saved HD as a “real” disk in the emulated Mac. This enables quite a few capabilities:

  • You can install system software on Saved HD (either by copying it from an existing disk or via a System Software CD-ROM from the library). It can then be used as a startup disk (if running a custom instance with no other disks), allowing custom extensions and control panels to be installed and persisted across sessions.
  • The contents can be exported and imported (from the emulator settings dialog), allowing backups and sharing across browsers and machines.
  • The contents can also be exported to a .dsk disk image file, so that they can be used in Basilisk II, SheepShaver and other native emulators.
  • The same disk is mounted in all instances, so it's also a way to move data from one to system disk to another.

System 7.5 install with a Kaleidoscope scheme and ResEdit editing an icon
Your own private System 7 install

The end result is a way to make Infinite Mac feel like “your Mac” while still keeping everything local and fast. That also lines up well with my goal of keeping the site a fun hobby project — while persisting disks on a server would be neat, it would also be a more expensive and complex operation.

Update on October 1, 2023: This has been extended to also support generating of disk device images (.hda files), which allows Infinite Mac prepare disks for BlueSCSI devices. Ron's Computer Videos has a walkthrough of the process.

20 Years of Blogging #

The first proper blog post on this site was 20 years ago today. It lived under a different URL, I didn’t register until later (when .info was a new-and-shiny top-level domain). It also looked different (I don’t have a screenshot of the first version, but I switched themes a few months later) and was published by very different software (Movable Type 2.64), but there is continuity from then to today.

"10 years, man!" scene from Gross Pointe Blank with the 10 crossed out and replaced by 20

Later that day I imported a bunch of older programming journals, thus the entries go back almost 25 years. The first few years are not really blog posts, but it’s been nice to have snapshots of early work, even if they’re not all gems. I’ve been backfilling a projects page, and the posts also serve as tangible artifacts to link to, even if the project itself is long-dead.

Year 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023
Posts 13 3 4 2 4 1 2 5 7 12

The trend for posts per year is looking encouraging when looking at the past decade, here’s to a few more (while computers still work).

Infinite Mac: Disks, CD-ROMs and Custom Instances #

Here are a few highlights of what’s been happening with Infinite Mac since my last update.

Improving Disk Abstractions

My broad goal this time around was to make it easier to load external software. To lay the groundwork for this, I first needed to improve some of the disk abstractions that the project used. One of the things that makes the emulated Macs relatively fast to boot is that they stream in their disk images, since much of it is not needed at startup (e.g. Mac OS 9 only reads 75MB out of a 150MB install). I had implemented this streamed/chunk approach pretty early in the project, when I understood the emulator internals (and Emscripten) less well. It worked by monkey-patching the read/write operations in Emscripten’s MEMFS filesystem (the default when doing an Emscripten build).

Besides being somewhat hacky, this had the downside of requiring that all mounted disks be loaded into memory as ArrayBuffers (hence the name MEMFS). While the system software disks are relatively small, the Infinite HD disk with pre-loaded software is 1 GB, thus this was leading to significant memory use. I had added some basic instrumentation of emulator errors, and running out of memory was surprisingly common (6.5% of emulator starts), presumably due to low-memory iOS devices or 32-bit Chrome OS devices. If I was going to add the ability to load additional (CD-ROM-sized) disk images, the memory use would increase even more.

I briefly explored Emscripten’s other file system options, but they all didn’t seem like quite the right fit. Fundamentally the issue was that all disk access was going through too many layers of abstraction, and the intent was lost. The emulator was operating against the POSIX file system API (open, read, on a file descriptor etc.) and by the time Infinite Mac’s code ran, it required extra bookkeeping to know which file was being requested (it could be a non-disk file), and the choice for the backing store for that file (as an ArrayBuffer) had already been made by Emscripten.

5 layers of abstraction
3 layers of abstraction

While many problems in computer science can be solved with a layer of indirection, in this case it was a matter of removing one or two. The emulators are cross-platform systems themselves, and they have a way of swapping out the POSIX file backing for disks with others for other platforms. By adding more direct “JS” disk implementations in both Basilisk II/SheepShaver (hereafter “Basilisk”) and Mini vMac and then implementing the JS APIs on the Infinite Mac side, the entire POSIX and Emscripten layers could be bypassed. We now only need to use as much RAM as the number of 256K chunks that have been accessed (and there is enough flexibility in the system that a LRU system could also be implemented to keep the working set fixed in size). After this change the out-of-emory error rate went to 0.3%.

The next thing to tackle was avoiding the need to restart the emulators when mounting additional disks. This only affected the Basilisk version, since Mini vMac already had built-in support for mounting disks on demand. I assumed that this was not a fundamental limitation, all the emulators supporting mounting (real) removal media on demand already. It turned out to be a missing feature in Basilisk’s disk abstraction, and once I added that it was possible to mount disks immediately there too.

CD-ROM Library

With this in place, I was able to start working on the CD-ROM “library” feature I had envisioned. This was based on the observation that the Internet Archive has a large collection of disk images in the .iso format, include many for the Mac. Furthermore, the files are accessible without any kind of authentication, and support HTTP range requests. This meant that it’s possible to take the same chunked/streaming approach I was using for my disk images and use it for these files without any kind of pre-processing. It is rather amusing that download speeds and latencies over the Internet are better than those of 1-2x CD-ROM drives from the early 90s (150-300 KB/sec and 100-200ms seek times).

I do these range requests in simple handler in a Cloudflare Worker, mostly so that they end up being same-origin requests. I later found that there is a CORS gateway for the Internet Archive, but it appears to be a secondary service, and doing the fetching myself means that I can benefit from Cloudlare’s edge caching. However, switching to doing direct client-side loading is something to consider if the bandwidth costs become prohibitive (though for now donations are more than covering the costs).

One gotcha that I ran into this that some disk images are compressed. While there ways to make .zip files seekable, they require re-compressing, which would defeat the purpose of making this be a minimal setup/low overhead feature. For now I’ve chosen to skip over compressed files (or support other disk image formats that support compression, such as .dmg).

Continuing with the project’s curation philosophy, I wanted to have a nice browsable UI of CD-ROMs, ready to be mounted in the emulated Mac. I ended with a collection of metadata files that specify disk image URLs and cover art that is processed by a simple importing script. The generated catalog is rendered in a pop-up folder-inspired UI:

Infinite Mac with CD-ROM library pop-up window open

I added some of my favorites and got a few more suggestions via Mastodon. Around this same time a pretty throughly researched article on the first CD-ROMs appeared, and I hoped to include the ones referenced too. However, most of the early CD-ROMs were actually just taking advantage of the ability to include audio CD tracks, which is something that I have not gotten to work yet, so I was not able to add them.

I did also add the ability to mount arbitrary CD-ROMs via a dialog, so if you come across something on the Internet Archive that catches your eye, you can give it a try (you can even drag-and-drop the link from another tab). I also made the Infinite HD disk image 2GB (i.e. with 1GB of free space), so that it’s available as a destination for any software that requires installation to a local hard drive. This is another way in which the change to not require disk images to be loaded into RAM paid off.

Custom Instances

Around the time that I was wrapping up CD-ROM support, I came across the recently-launched Classic Macintosh Game Demos site. It’s a “kindred spirit”, in that it’s trying to make things easily accessible and has a curatorial bent. Jules Graybill (the author) was sourcing the game demos from CD-ROMs that came with magazines, images of which were also uploaded to the Internet Archive. I added the ability to specify a CD-ROM URL via a query parameter and reached out to him to see if he wanted to make use of it (to allow the games to be played in-browser). He quickly implemented it, which is a great example of the flexibility of web-based projects — they can be made to work together (some might even say “linked” or “mashed up”) with minimal coordination.

Shortly after, Jules filed an issue asking about an additional system extension he needed included on the system disk. Extrapolating from the ability to load CD-ROMs from URLs, I wondered if the same could be done for the system disk, so that he could provide his own image with exactly the extensions that he needed. And while we’re at it, why not allow the type of machine to be chosen via a URL parameter too, and control over other Infinite Mac features (AppleTalk support or the extra Infinite HD disk). Getting perhaps a bit carried away, I ended up building a Calculator Construction Set equivalent for emulated Macs, available at

Custom configuration dialog, matching a System 7 startup disk

Custom configuration dialog, matching a Mac OS 8.1 startup disk

To reduce the cognitive dissonance (and to have a bit of fun), I made the UI resemble the look-and-feel of the OS that is being booted. I had already added some classic- and Platinum-style controls for other bits of configuration UI, but this dialog also required implementing popup menus and checkboxes (the Appearance Sample app from the Appearance SDK was invaluable in getting a view of all “modern” Mac OS controls). There’s still a bit of a an uncanny valley feel, perhaps due to fonts not being the same and modern screens having higher logical DPI (thus everything feeling smaller than it used to), but hopefully it will get closer over time.

You can also use this customization mode to have “Frankenstein” instances. For example, you can load the System 1.0 image while booting with System 7, allowing you to use a modern version of ResEdit to poke around the initial versions of the System and Finder. Or you can use this with any system disks that you may happen to come across (e.g. if you wanted to see what the Slovenian version of System 7.5 looked like).

Odds and Ends

While the goal of the site is to make as many versions of classic Mac OS available in the browser, I realized some time after launch that 40+ releases is a lot to scroll through, and that more notable ones might get lost among all of the point releases. I therefore added a simple filter toggle to make the list that’s initially shown more manageable.

A longstanding issue was that dissolve animations in HyperCard would run very slowly, something that affected the native build of Basilisk II too. It turned out to be due to a missing implementation of high-resolution timers, which was recently fixed on the native side. While simply updating my Basilisk II fork did not get the fix for free, I did now have enough clues to implement my own version of it.

There was the usual round of upgrades for the software stack: a new way to install Emscripten, transitioning from create-react-app to Vite (that one was a yak shave), and switching to the latest version of TypeScript, React, and other dependencies.  Though site performance hasn’t really been an issue, the switch to Vite made the initial bundle size more visible, so I spent a bit of time adding moving some things to dynamic imports and some preloading to pick up some easy wins.

I haven’t entirely decided what I’m going to focus on next, but I’m leaning towards improving the ability to persist changes to disks — it’s a shame to spend time installing software from CD-ROMs and then lose it all when closing the tab. The improved file system abstractions should make it easier to implement some of my ideas here.

Slack Canvas In The Streets, Quip In The Sheets #

I finally got access to the recently-launched Slack canvas feature. This project was the last thing I worked on before I left Quip/Slack/Salesforce in April of 2022, and I was curious how it had evolved since then.

Canvas started as a prototype in mid-2021 to reuse Quip technology to power a collaborative editing surface inside of Slack. The initial phase involved Michael Hahn and I doing unspeakable things with iframes1 to get the two large codebases to work togetherwith minimal changes. This allowed a relatively rich feature set to be explored quickly, but it was not something that was designed to be generally available. At the time of my departure the work on productionizing (the second 90% of the project) had just begun.

The first thing that becomes apparent is that the roots of canvas in Quip are entirely hidden from the user — no Quip references in the announcement or anywhere in the UI. This makes sense from a “don’t ship your org chart” perspective — no user should care how a feature is implemented. However, if you peek under the hood, you can start to see the some Quip tidbits. The most obvious place to start is to look for network requests with quip in them — a couple of which happen when loading a Slack canvas:

Slack canvas network requests

The “controller” is the core logic of Slack canvas editor, and we if load one of those URLs, we see even more Quip references:

Slack canvas Quip minified JavaScript

The DOM also resembles Quip’s, down to the same CSS class names being used. The need to scope/namespace them to avoid colliding with Slack’s was one of the open questions when I left, but I guess Slack has a BEM-like structure which ensures that Quip’s simpler class names don’t collide (as long as they don’t integrate another similar un-prefixed codebase). There are also no iframes in sight, which is great.

Slack canvas DOM structure

Quip also had extensive in-product debugging tools, and I was curious if they also made the transition to Slack canvas. They’re normally only enabled for employee accounts, but as hinted there is a way to enable them as a “civilian” user too. A couple of commands in the dev tools, and I was greeted by the green UI that I had spent so many years in:

Slack canvas showing Quip's debug tools

I was also hoping that copying/pasting content from Quip into Slack canvas was a backdoor way to get some of features that have not (yet?) made the transition (spreadsheets, date mentions, etc.), but it does not appear to work.

On the mobile side, I had explored reusing Quip’s hybrid editing approach in the Slack iOS app, including the core Syncer library. Hooking up to an iOS device shows that the Syncer (and thus Quip) are still involved whenever a canvas is loaded.

Slack canvas iOS logging showing Quip references

One of the open questions on mobile at the time of my departure was how to render Slack content that’s embedded in a document. Quip’s mobile editor is a (heavily customized) web view, so that we can reuse the same rendering logic on all platforms. It's possible to see that the canvas rendering is still web-based by inspecting the Slack app bundle (Emerge Tools provides a nice online tree view) – there is a mobile_collab.js file which implements document rendering:

CollabSdk.framework embedded in the Slack iOS app (as of June 2022)

Slack on the other hand is an entirely native app. Porting Quip’s editor to native components didn’t seem feasible on any sort of reasonable timeframe. It was also not appealing to reuse Slack’s web components on mobile, since they weren’t designed for that (either from a UI or data loading perspective). I had speculated that we could leave a “placeholder” element in the web view for Slack-provided UI (like a message card), and then overlay the native component on top of it. But I wasn’t sure if it would be feasible, especially when the document is scrolled (and the native view overlay would have to be repositioned continuously).

It’s not as easy to inspect the view hierarchy of an iOS app (without jailbreaking), so I can’t verify this directly, but it would appear that this placeholder/overlay approach was indeed chosen. Most of the time, the native Slack UI is positioned perfectly over the document. However, in some edge cases (e.g when a scroll is triggered because the keyboard is being brought up), things end up slightly out of sync, and the placeholder is visible:

Message embed in Slack canvas Offset message embed in Slack canvas, captured during keyboard scroll

This is my first time being on the outside of a project while significant work on it continued (unlike other times), and it’s been fascinating to observe. I congratulate all the people involved in shipping Slack canvas, and will cheer them on3.

  1. I later realized I had done the same thing 15 years earlier, getting Reader iframed into Gmail as another proof-of-concept.
  2. At one point we had iframing which in turn was iframing again (so that we could show Slack UI components inside documents), an architecture we took to calling “turducken.”
  3. Especially if they bring back syntax highlighting for code blocks.

10th Anniversary of Google Reader Shutdown #

It doesn't feel like it's been 5 years since my last post about Reader, but I guess the past few years have suffered from time compression. For this anniversary I don't have any cool projects to unveil, but that's OK, because David Pierce wrote a great article – Who killed Google Reader? – that serves as a nice encapsulation of the entire saga.

Other Reader team members and I had a chance to talk to David, and the article captures all of the major moments. Some things ended up being dropped though; there's enough twists and turns (in the social strategy alone) that a a whole book could be written. Here's some more "fun" tidbits from Reader's history:

The article talks about "Fusion" being Reader's original/internal name (and how the "Reader" changed how it was perceived and limited its scope). The reason why "Fusion" was not used was because Google "wanted the name [Fusion] for another product and demanded the team pick another one. That product never launched, and nobody I spoke to could even remember what it was.". Fusion was the initial name that iGoogle launched under, as can be seen in this article from mid-2005 (iGoogle itself was went through some naming changes, changing from Fusion to Google Personalized Homepage before ending up as iGoogle (its codename) in 2007). Finding the breadcrumbs of this story was somewhat difficult because Google later launched a product called Google Fusion Tables (not surprisingly, it was also shut down).

In terms of naming, these were other names that were considered, so "Reader" was as worst-except-for-all-the-rest sort of thing:

  • Google Scoop (which team took to referring to as "Scooper", as in pooper scooper)
  • Google Viewer
  • Google Finder
  • Google Post

At one point during the (re-)naming saga Chris put in "Transmogrifier" as a placeholder name, with the logo being one of Calvin's cardboard boxes. During the next UI review Marissa Mayer was not amused (or perhaps it was hard to tell what the logo was in those pre-retina days), and the feedback that we got was "logo: no trash".

A low point in the interal dynamics was hit in 2011. I had made some small tweaks (in my 20% time) to fix an annoying usability regression where links were black (and thus not obviously clickable). Since we were getting a lot of flack for it on Twitter, I tweeted from the Reader account saying that it was fixed. A few hours later, I got a friendly-but-not-really ping from a marketing person saying that I need to run all future tweets by them, since there was an express request from Vic Gundotra to limit all communication about Reader, lest users think that it's still being actively worked on. That was the second-to-last tweet from the official account, the next one was the shutdown announcement.

After Twitter blew up at SXSW 2007 there was a start of a "I don't need Reader/RSS, Twitter does it for me" vibe amongst some of the "influencers" of the time. I posted a somewhat oblique tweet comparing the Google Trends rankings of "google reader" and "twitter" (with "toenails" being a neutral term to set a baseline), showing that Reader dwarfed them all (the graph looks very different nowadays). I couldn't understand why someone would want to replace Reader with a product that had no read state, limited posts to 140 characters, and didn't even linkify URLs, let alone unfurl them. In retrospect this was a case of low-end disruption.

Tailscale battery life instrumentation #

I wrote a valedictory blog post about the instrumentation we're adding to the Tailscale client to get a handle on battery life issues. It's been an interesting "full-stack" project, involving thinking about cell phone internals, hacking on the Go standard library, and exploring visualization options.

You won't believe what this one weird ioctl will do #

Darwin was one of the first things Apple open-sourced (24 years ago). It's been mostly a "throw it over the wall" approach, but being able to peek under the hood has been very handy.

I wrote a short blog post showing how we've been able to improve Tailscale's macOS and iOS apps by seeing how things are implemented.

Infinite Mac: #

tl;dr: Infinite Mac has a new home at Using a new Emscripten port of Mini vMac, it is now able to run almost every notable version of Mac OS, from 1984’s System 1.0 to 2000’s Mac OS 9.0.4. The project is also now accepting donations (via GitHub Sponsors or PayPal).

3 emulated Macs and a real one on a desk
Spot the odd one out.

Mini vMac

As I mentioned in my last post on the project, my immediate plans were to make even older versions of System Software runnable in the browser. Em Adespoton maintains a compatibility matrix spreadsheet showing which emulators can run specific OS versions, and it looked like my best bet was Mini vMac. There is an existing Emscripten port, but I decided to start from scratch for two reasons:

  1. It used Emscripten’s SDL compatibility layer, presumably to minimize the work to get something up and running. However, that’s both less efficient (as far as additional layers of abstraction to go through) and would not benefit from the JavaScript/browser API wrappers that I’d already developed for Basilisk II and SheepShaver.
  2. It first ran Mini vMac’s custom build system to generate the Makefile and other configuration data for Linux/SDL, and then modified the generated code to get things working. This seemed like a not very maintainable approach for the long run, since any re-runs of the generation system (e.g. to generate with different options) would require the modifications to be redone.

The first step in doing it myself was to get Mini vMac building. It’s an “interesting” project for a few reasons:

  • It doesn’t use version control (source code is available as tarballs for major versions).
  • Rather than a traditional autoconf + make setup, it has its own build system generator (also written in C).
  • Almost all options are specified at compile time (to minimize binary size and runtime cost presumably), so there's a lot of branching that relies on the C preprocessor.
  • It can be built by very old compilers for a broad range of platforms (even classic Mac OS, Carbon, Nintendo DS). Therefore it targets C89 and makes heavy use of macros and typedefs to make things work in such a broad range of environments.

This blog post has a rather negative take on the whole setup, but I have some empathy for Paul (Mini vMac’s author). This is clearly a passion project for him (dating back to 2001), and he’s set things up in a way that suits him. Incidentally, Paul himself has not been heard from for a couple of years, hence Mini vMac is in a bit of a limbo state. An import of the codebase and website to a GitHub org was done in 2022, in case Paul’s site does eventually go down.

With that context out of the way, getting it to build was actually not as painful as it sounds. While the build system is custom, it’s also arbitrary C code, so it’s very easy to hack on (vs. dealing with m4 for autoconf). Since it targets some very low-end platforms it’s also very fast to build, so the iteration speed was great. The initial bring-up involved adding an alternate Makefile generation mode that used the Emscripten toolchain and a glue file to get configuration data in and the framebuffer out. With that scaffolding in place, adding input, disk image mounting, color and sound was pretty straightforward.

Since the emulator allows the speed to be controlled, I exposed that as well. I knew that modern machines emulate classic Mac OS significantly faster than 68K-era Macs ran it (even when targeting WebAssembly and not being able to use JIT), but it was made truly apparent when running in 1x speed mode. I was a lot more patient back in the 90s, back when you had to be aware of how big the event queue was and make sure the computer could keep up.

The framebuffer updating in Mini vMac keeps track of dirty rectangles (another example of its focus on low-end host machines), and it was a nice bit of API “mechanical sympathy” to be able to hook that up to the dirty region option of CanvasRenderingContext2D.putImageData(). Though as it turns out that code path is perhaps not well-tested on some versions of Chrome — non-macOS users reported glitches and I had to add some opt-outs.

System 6

Once I had Mini vMac up and running in the browser, I booted a System 6.0.8 image. I only started using a Mac with System 7.1, so this was my first time spending any significant amount of time with this older version. I recall that System 7 had a reputation for being more RAM hungry and slower than its predecessor, but by modern standards they seem equally snappy and stingy with their memory use (with MultiFinder enabled System 6 uses the same amount of RAM as System 7).

What was more surprising was that things that I took from granted from System 7 (and assumed had been there for ages) were in fact recent additions. Two that I ran into very quickly were typing into Finder windows to select items with that prefix and being able to drag files onto application icons to open them. Around this same time Quinn had a thread about how earlier versions of the Mac UI were not as refined as people remembered them, which was a nice confirmation of my experience.

One of the other surprises that I ran into was that the Infinite HD disk (with its 1GB of Mac software) was taking a very long time to mount. I eventually figured out that its desktop file was being rebuilt (having a visible progress bar for this is another System7 refinement). It turned out that the Desktop DB/Desktop DF files that I was used to were another System 7 advance, while System 6 used a simpler (single) “Desktop” file, which causes problems when switching back and forth. The solution for my situation turned out to rebuild the desktop files twice, first under System 6 and then under Mac OS 8, so that both sets would be included in the generated image.

Once I had it all working, it was a matter of registering and configuring it.

System 6 showing the “About the Finder…” window
System 6.0.8 running on an emulated Mac Plus.


System 6 showing the “About the Finder…” window
The mid-90s were a tough time.

With Mini vMac running, running older OSes was mostly a matter of figuring out which machines to build and tracking down install disks. MARCHintosh was coming up, and I figured it would be a worthwhile goal to get a gallery up and running in time for that.

But first, I needed a domain name. While,, etc. were all very catchy and memorable, was somehow less so (this was the first version to add support for HFS, and thus notable, even if it was a point release). I initially considered (to further extend my use of more obscure TLDs), but it’s a premium domain name costing $2,700/year, and thus not really within my budget. was a more affordable $84/year, but it was only one of the few macos.<TLD> domains that was unclaimed, leading me to conclude that Apple’s legal department may have an interest in it (“Mac OS” is also technically an anachronistic way to refer to pre-System 7.5.1 releases). In the end the “obvious” won out, both for its affordability ($12/year) and because it matched the project name. The .app domains names will remain as shortcuts/redirects.

I used the word “gallery” above, and that was the experience I was going for. I wanted a curated experience, with pristine, period-accurate installations. I appreciate what the Internet Archive has done (and will often use it as a source) but at some point the amount of choice (400+ bootable images) becomes overwhelming. I wanted to have a place where you can experience first-hand what someone means by “spatial Finder”. Or if you come across some trivia about option-double clicking only getting its modern behavior (close windows behind you) in System 6.0.4, you can open it and its predecessor side-by-side to see for yourself.

A reference point I had was Stephen Hackett’s macOS screenshot gallery (or his more recent Rogue Amoeba one), or this one of early Mac systems. While they were certainly curated, even a great set of screenshots can’t capture what it’s like to use one of these systems (e.g. the feeling of zoom rects going across the screen).

My other inspiration was UTM’s gallery (which does provide one-click access, but requires native software and waiting for download and installation) and PCjs Machines (which run in the browser, but being about PCs is inherently inferior in the eyes of this 90s Mac fanboy).

For getting pristine versions of each OS, I tried to use “primary” sources as much as possible, i.e. directly from Apple. In some cases Apple still hosts the downloads (unclear why Mac OS 8.6 gets such treatment). In other cases, there were “archive” releases from the 90s that have multiple operating systems (e.g. the 1991 E.T.O. Essentials CD has System 7.0 and earlier, and the 1999 Legacy Recovery CD has some later releases).

The one release that was surprisingly hard to track down was System 0.97/1.0 that shipped with the original Macintosh 128K. I was not the first to run into this, and it appears to boil down to it being a short-lived release (1.1 came out a few months later), relatively few 128K Macs being sold, and the system disk also being the primary data storage mechanism for users. I tracked down all the disk images I could find, and put together a combined one that matched the original’s spirit.

The other thing that was surprisingly hard to find was a detailed changelog for each release. Perhaps there are comp.sys.mac.system posts from the era or Apple press releases, but my archeology skills do not extend to Usenet or wire services. I mostly used Macworld’s Mac Secrets (which being from 1996 is somewhat contemporary) and Mac 512’s System Showcase.

As previously mentioned HFS was introduced with System 2.1, thus earlier versions (that only support MFS) cannot mount the Infinite HD HFS image with the software library. I briefly considered making an MFS generator too, but the only MFS libraries I could find were either read-only or embedded inside sample code from 20 years ago, thus it seemed like a yak too far. I ended up hand-creating an image with some representative programs.

The final gallery includes every notable system software release (I'm sorry, fans of System 7.0.1). Perhaps over time I'll go back and backfill truly everything, but for now generating 36 bootable images was plenty. I tried to make the browsing experience as pleasant as possible — each release has a permalink, and you can command-click on the “Run” button to open things in new windows. Some releases only work with specific Macs, and some can run on multiple (I used the Apple ROMs spreadsheet and Mac ROMan to build up a library of machines) — which machine is used can be chosen via the “Customize” button.

Other Improvements

The focus on gathering together all of the OS releases meant that I didn’t have as much time to work on the emulation aspects. The one thing I spent some time on was improving the audio playback system. Audio is one of the areas that I know less about, and I had kept most of the structure from James Friend’s original Basilisk II port in place. However, as I was bringing up the audio subsystem for Mini vMac I remembered that audio worklets are now widely supported, and they also have an API “mechanical sympathy” with what the emulator expects. I’d also already set up a ring buffer system for the networking support, and in fact the library mentioned emulation as a use-case. The current implementation is a system that is quite a bit simpler, has lower latency, and I understand better, though there are still bugs. Between the audio worklet, the service worker, and the dedicated worker, I’ve managed to use most of the worker types — I now just need an excuse to use a paint worklet to catch them all.

One small tweak that I made was related to scaling. Nearest-neighbor is nice for preserving a crisp look and feel, but it’s not appropriate when trying to fill the screen in full-screen mode or when the host screen itself is doing non-integer scaling. I can see why emulators end up with configuration settings to allow for more historical visual accuracy, but for now I’ve managed to stay away from too many fiddly settings.

I made a few improvements to the experience on mobile/touch devices — there’s now a way to bring up the keyboard and import files (without drag and drop) and controls are visible even when the screen chrome is in its smallest mode. There are still fundamental limitations, these are all desktop UIs that were designed for very precise pointing devices, and they rely on actions like double-clicking that are hard to do precisely on mobile. PCjs appears to have taken a slightly different approach that may be worth considering: touches appear to drive a virtual trackpad that moves the cursor and allows more complex operation (like drag-and-drop) to be accomplished.

I also ran into a few cases where the emulator would not load, especially in-app web views. Those turned out to be caused by the service worker trying to do caching (but not being allowed to), but in general some basic error reporting seemed appropriate. Perhaps setting up Sentry or the like would be appropriate, though I would like to minimize the number of other services I depend on.

I had mentioned wanting to have a way to cover the infrastructure costs of the project ($332.48 so far), and I got around to setting up GitHub Sponsors and PayPal donation accounts. A few people have already donated, for which I am very grateful. I will try not to blow it all on domain names.

What’s Next

First, a break. I’ve been working almost every day on the project to hit my (self-imposed) MARCHintosh deadline of having all OS releases, and it’s gotten to be a bit of a grind.

Beyond that, there are a bunch of software requests that I could incorporate into the Infinite HD image. Though making it even easier to load software (even if I don’t include it directly) would be even better — I recently came across Discmaster and something that lets you load anything from the Internet Archive with one click would be great.

Improving the persistence support is another area I’d like to explore — the “Saved” folder in “The Outside World” is a bit too obscure, and because it uses the File System Manager some programs don’t work well with it. Ideally there should be a whole HFS volume that can be persisted. That would also integrate nicel with the browser’s file system APIs.

Finally, though I said I would take a break from adding additional operating system images, there are pre-release and obscure ones that might be fun to get running. Or perhaps even parallel universe alternatives.

Update: See also the discussion on Hacker News.

Reducing Tailscale’s Binary Size on macOS #

How I Consume Mastodon #

tl;dr: I use Masto Feeder to generate a nicely-formatted RSS feed of my timeline, and then I can read it in my preferred feed reader.

A bit more than 10 years ago I wrote a post called “How I Consume Twitter”. It described how I contort Twitter to my completionist tendencies by generating RSS¹ feeds for my timeline (for more fine-grained updates) and lists (to read some accounts in a digest). That allowed me to treat Twitter as just another set of feeds in my feed reader, have read state, not need another app, and all the other benefits that RSS provides.

A bunch of things have changed since then: (lots of) Twitter drama, product changes and feed reader churn (I’m currently using NetNewsWire), but the Bird Feeder and Tweet Digest tools in Stream Spigot have continued to serve me well, with the occasional tweaks.

When the people I follow started their migration to Mastodon in early November, I initially relied on Mastodon’s built-in RSS feeds for any user. While that worked as a stopgap solution until I decided what instance to use², it proved unsatisfying almost immediately. The feeds are very bare bones (no rendering of images, polls, or other fancier features) and do not include boosts. Additionally, having potentially hundreds of feeds to crawl independently seemed wasteful, and would require manual management to track people if they ever moved servers.

I saw that there was a Mastodon feature request for a single feed for the entire timeline, but it didn’t seem to getting any traction. After a couple weeks of waffling, I decided to fix it for myself. The Stream Spigot set of tools I had set up for Twitter feed generation were pretty easily adaptable³ to also handle Mastodon’s API. Over the next few weeks I added boost, spoiler/CW, and poll rendering, as well as list and digest support.

The end result is available at Masto Feeder, a tool to generate RSS feeds from your Mastodon timeline and lists. It can generate both one-item-per-post as well as once-a-day digests. It’s been working well for me for the past few weeks, and should be usable with any Mastodon instance (or anything else that implements its API). The main thing that would make it better is exclusive lists, but there’s some hope of that happening.

Mastodon viewed as a feed in NetNewsWire

As for Twitter, with the upcoming removal of all free API access it likely means the end for Bird Feeder and Tweet Digest. What this means for me is that I’ll most likely stop reading Twitter altogether — I certainly have no interest in using the first-party client with its algorithmic timeline. However, I’ve been enjoying Mastodon more lately anyway ( should find me), and with being able to consume⁴ it in my feed reader, it’s truly more than a 1:1 replacement for Twitter.

Update on June 5, 2023: Twitter API access for Bird Feeder and Tweet Digest was eventually revoked on May 22.

  1. Technically Atom feeds, but I’ve decided to use the term RSS generically since it’s not 2004 anymore.
  2. My dilly-dallying until I settled on mean that someone else got @mihai a few days before I signed up.
  3. It did pain me to have to write a bunch more Python 2.7 code that will eventually be unsupported, but that’s a future Mihai problem.
  4. For posting I end up using the built-in web UI on desktop, or Ivory on my iPhone and iPad. Opener is also handy when I want to open a specific post from NetNewsWire in Ivory (e.g. to reply to it).

Tailscale iOS and macOS Shortcuts #

I worked on adding iOS and macOS Shortcuts support for Tailscale's latest release. I wrote a blog post with examples of shortcuts and automations that the Tailscale actions could be combined with. One that didn't make the cut was using sound recognition, for things like “In case of an emergency, break glass to activate Tailscale”.

iOS Shortcuts automation that connects Tailscale when the sound of breaking glass is detected Shortcut automation sound triggers: baby crying, shouting, kettle, and others

Other possibilities include "Cry/shout/scream to connect" and "Let your pet control your connection state".

Places Mihai Has Committed Code From #

I've been running lolcommits for 10 years, and it's captured some interesting moments from my time at Quip and Tailscale.

I've put together a gallery, it was a fun nostalgia trip to revisit so many places and coworkers.

Infinite Mac: 2022 In Review #

I've come to think of Infinite Mac as my forever project. There's always something to work on, whether it's expanding the library, improving compatibility, adding more platforms, improving performance, debugging data structures, bridging APIs from 30 years ago with modern web platform features, or fighting with frontend tooling. With that in mind, here's where things stand at the end of the year — there have been quite a few changes since my last post on the project.


Befitting a long-term endeavor, I invested some time into maintainability. This included small changes like setting up auto-formatting and bigger ones around code organization. I moved all of the browser-specific audio, video, clipboard and other subsystem implementations into their own modules, instead of adding lots of branching to existing ones.

That cleaner separation, combined with changes to reduce diffs with the upstream, made it possible to rebase the repo on a more recent version of Basilisk II — I had still been basing my work on James Friend’s initial Emscripten port, which was a snapshot as of 2017. Most Basilisk II development is happening in the kanjitalk755’s fork, and I switched to building on top of that.

Finally, I made it easier to do native (macOS) builds of Basilisk II (and SheepShaver) from the same repo. This reduced the friction when tracking down behavioral differences between the native and web-based versions: I can instrument or modify shared code and then run it in both builds to see how it differs.

SheepShaver and Mac OS 9

With things on a more maintainable path, I decided to tackle a bigger project: PowerPC support (which would allow Mac OS 8.5 and later to run). This involved porting SheepShaver to WebAssembly/Emscripten. Luckily, it shares a lot of code and architectural decisions with Basilisk II (not surprising, since they were both created by Christian Bauer). The initial bringup and integration involved similar autoconf tweaks and #define changes to get the Emscripten build on the right code path. After that it was a matter of hooking up each subsytem to the existing implementations that bridged to the JavaScript/browser world.

The end result is running at My main takeaway is that it feels more sluggish than System 7 or Mac OS 8. A lot of that appears to be due to bloat in Mac OS 9 itself, running a PowerPC version of System 7 feels snappier. There’s probably low-hanging fruit in the emulation itself when targeting WebAssembly, but I have not done any investigations in that area.


Infinite Mac Mac OS 9 ScreenshotMac OS 9 in an Apple Cinema Display bezel with dynamically-generated Stickies

A somewhat silly feature I wanted to implement was to show the changelog in the set of stickies that is shown at startup. I had been previously been embedding the data by hand (by booting each image and editing the Stickies file), but this was becoming tedious now that there were four separate variants and more frequent edits. I therefore reverse engineered the Stickies data format and then switched to dynamically generating it, including the changelog. A bit over-engineered perhaps (see the caveat below), but it was fun to reconstruct what Jens Alfke had implemented almost 30 years ago.

Another “because I felt like it” feature was adding era-appropriate screen bezels to the ersatz monitor that is shown around the screen. Technically beige was no longer in use by the time System 7 was released (the Snow White design language was fully rolled out a few years prior), so this may be something to revisit if older OS versions are supported.

I also added a couple of useful features: the ability to swap the control and command keys (so that shortcuts like Command-Q and Command-W can be used even when not in full screen mode) and clipboard syncing support. The latter has some interface impedance mismatch issues, since the clipboard API is asynchronous and has gesture trigger requirements. However, it seems to work well enough to get text in and out of the emulator in a more natural fashion than files saved in the “Uploads” folder.

Improved Compatibility

For the emulators to be more than just a curiosity (that gets played with for a few minutes and then forgotten), having compatibility that’s at least as good as the native builds is important. I spent some time fixing small bugs, like missing thousands/16-bit support, handling screen resolution changes, and making the audio less laggy.

A bigger task was improving the accuracy of FPU emulation. This manifested itself as two bugs that initially seemed unrelated - a calculator program failed when doing any operation, and scroll thumbs did not move in Mac OS 8. A tip from a user pointed out that native builds of Basilisk II used to have the latter problem too, back in 2018. Running with a build from that era reproduced the scrollbar behavior, and the calculator issue too, when I tried it on a hunch (presumably the scroll thumb drawing routines do a floating point division to compute the offset).

I then did a bisect of changes to find where it got fixed, and ended up with the switch in FPU implementations to a standard IEEE 754-based one (instead of a custom implementation). However, the WebAssembly version was already using the IEEE 754 implementation, thus it should be on the same code path. I eventually realized that the native build (on an Intel Mac) was on the USE_LONG_DOUBLE sub-path, while the WebAssembly one ends up with vanilla 64-bit doubles. Both x87 and the 68881 support extended precision (80-bit specifically), which makes IEEE 754 a good match for them, but that’s not the case for the WebAssemby virtual machine. I then checked to see what the arm64 port of Basilisk II does (as an example of another platform without extended precision support), and it uses a different FPU implementation, based on mpfr. Switching to it resolved the issue, albeit with a performance hit (hopefully no one is doing long Infini-D renders in a browser).

Another place where an external tip provided a key clue was in tracking down the case of missing sound support in Mac OS 8 and 9. A user that was hosting their own instance of the emulator reported that sound worked for them, which was surprising. I initially thought it was due to a different system image, but I could still not get it to work even when I used theirs. I eventually realized that they had a simpler build process, and were not using machfs to do the dynamic Stickies insertion mentioned above. When I switched to passing through my system image unmodified, sound began to work.

There’s probably another subtle bug in the HFS data structures that machfs emits (I have already fixed a couple), but I did not feel like diving into Inside Macintosh: Files again just yet. Instead I switched to a lower-tech way of inserting the dynamically-generated Stickies file into the disk image: a placeholder file whose contents can then replaced by operating at the raw bytes level.


Somewhat surprisingly, the SheepShaver/Mac OS 9 work ended up on Hacker News before it was fully ready. The discussion was nice, though mostly in a nostalgic/“they don’t make them like they used to” vein. Amusingly, the TL of the .app domain project noticed their use. The discussion also inspired me to change the default alert sound to Sosumi.

The Register also had an article about Mac OS 9 where the author actually reached out and got some quotes from me — I appreciated the effort. A Japanese site had a pretty in-depth article about, it’s nice that the localized version was noticed.

My favorite was a very thorough YouTube video about the project, including a demo of the LAN/AppleTalk functionality that I cobbled together over the summer.

What’s Next

I’d like to broaden the versions of System Software/Mac OS that can be run in a browser. There is some amount of “gap filling” between the 7.5, 8.1 and 9.0.4 images (Mac OS 8.5 has a special place in my heart because I got my start doing real development for the 32-bit icns icons that it introduced). However, older versions (System 6 and earlier) are only supported in other emulators, as this handy spreadsheet shows. There is an existing mini vMac Emscripten port that could serve as a starting point.

There is some cost involved with all this. Currently this includes 4 .app domain names at $10/each per year, the Cloudflare Workers Paid plan at $5/month (to get Durable Objects that are used for LAN) and a GitHub Git LFS data pack at $5/month. This is still at a point where it’s a reasonable “hobby” budget (especially compared to woodworking or 3D printing or photography, as examples of hobbies where gear cost can escalate), but I’m considering setting up a Patreon or GitHub sponsorship in case others do want to support the work.

Though I’m enjoying the solo aspects of this work (as far as working on whatever I want with no coordination overhead), I’m not opposed to outside contributions — it was nice to get a PR to improve the scrolling behavior on small screens. Hopefully there’ll be more of that in 2023.

But really the main goal is to continue to have fun.