Status note (2026-08-29): this article describes v0.1, the first working version. The project has since moved on — windows are now moved to 3-px edge stubs instead of hidden, there is per-window restore, a control panel, a window-overview mode, a 68000 build, and a verified run on Mac OS 9.0.4. The current state and every measurement are in the plan article with its progress log and at github.com/LoetLuemmel/Expose.
In November 2021, a 68kMLA member who uses a PowerBook 170 with System 7.1 as a daily machine posted a wish: the one feature of Mac OS X he still missed on it was Exposé’s show desktop — a hot corner or a key that sweeps every window to the edges of the screen and brings them back afterwards. He attached a frame-accurate mock-up made in Macromind Director, a twelve-step algorithm, and a high-speed-camera analysis of the Finder’s own “zoom rect” animation. A second member, Crutch, picked the idea up and carried an extension to beta 5 over the following four months; it then stalled, and the thread’s last posts, years later, ask whether it will ever be finished.
This article describes a fresh implementation of that request: a System 7 extension, written and tested on a System 7.6.1 machine running under Basilisk II, driven end to end through the AppleBridge toolchain. It reports what was measured along the way, including two emulator crashes whose cause is instructive, and it is explicit about what the first version does not do.


Above: the same screen before and after a single F11. The Finder’s window, the AppleBridge daemon’s console and a control-panel application’s window belong to three different processes; all three leave. The Finder’s menu bar in the second image is the Finder’s — the front application was not changed.
What the extension does
- Trigger: F11, Command-Option-D, or the top-right hot corner.
- Effect: every visible window of every running application animates outward along the axis from its centre to the screen centre, using the Finder’s zoom-rect animation (twelve grey XOR rectangles, three on screen at a time, the accelerating spacing the original poster measured), and is then hidden. The desktop is fully exposed.
- Restore: the same trigger, a mouse click within five pixels of a screen edge, or switching to another application — the rule the original poster specified so that the desktop can be inspected without leaving the current program, while opening a document or launching something brings the windows back on its own.
- Scope: all versions from System 7 on a 68020 or later (the C body is compiled with native long arithmetic, which excludes the 68000 — the same limit Crutch’s betas had). Main screen only, as agreed in the thread.
The problem underneath: whose windows are these?
Under System 7 the Window Manager’s WindowList is per process. When the Process Manager switches applications it swaps that low-memory global, so an application — or an extension running in an application’s context — sees only the current program’s windows. Crutch’s own thread on the question (“How to get the Window Manager’s WindowList in a multitasking environment?”) arrived, with cheesestraws, at the answer: the undocumented Layer Manager. Each process owns a layer; a layer is a fake WindowRecord whose txSize field holds the marker $DEAD, whose dataHandle slot points to the parent layer and whose windowPic slot points to the head of its subWindows list; sibling windows chain through nextWindow as usual. The layer of a process is reachable through the Process Manager’s private entry (_OSDispatch selector $4F, PEntryFromProcessSerialNumber, at offset $70 of the entry). Walk up the parent chain to the root layer, then down again, and every window of every application appears, front to back.
The offsets were not in the thread — they were in an attachment and in the source of zydeco’s rootless patch for Basilisk II, which uses the same walk to cut windows out of the host desktop. The first thing built here was therefore a small MPW tool that performs the walk and prints what it finds:
root layer 00019C00
LAYER 00019C00
LAYER 0001F830
LAYER 00019CA8
LAYER 07B8E34C owner=Finder
WIN kind=20 vis=1 [0,0,0,0] "Desktop"
LAYER 06E54DE8 owner=AppleBridge
WIN kind=8 vis=1 [23,3,404,486] "AppleBridge - Verbose"
LAYER 06741850 owner=ToolServer
WIN kind=2 vis=0 [0,0,0,0] "Request"
The bounds of the AppleBridge window matched a screenshot taken in the same second; the Finder’s desktop is a window of kind 20 with an empty structure region, and a faceless process that never called InitWindows has no layer at all. Both cases are filtered. One more fact from the thread was confirmed rather than assumed: ShowHide on a window that belongs to another process simply works — the Window Manager is handed a valid pointer and does not care whose heap it lives in.
flowchart TD P["PEntryFromPSN(current)"] --> L["process layer (+$70)"] L -->|"+$82 parent, repeat"| R["root layer"] R -->|"+$94 subWindows"| A["layer: Finder"] R --> B["layer: AppleBridge"] R --> C["layer: ToolServer"] A -->|"+$94"| W1["Desktop (kind 20, empty rect - skip)"] B -->|"+$94"| W2["Verbose window"] C -->|"+$94"| W3["hidden dialogs (vis=0 - skip)"]
Architecture of the extension
The extension is a single INIT resource, loaded into the system heap and locked, containing three parts linked as one position-independent block:
- Boot-time install (assembly). Rather than copying itself somewhere, the INIT makes the block it already occupies permanent:
RecoverHandleon its own code,HLock,DetachResource. INIT 31’s subsequentReleaseResourcethen finds nothing to release. The previous value of the GetNextEvent filter vector$029Ais saved and the vector is pointed at the shim. - jGNE shim (assembly). Every
GetNextEvent/WaitNextEventin every application passes through this vector, in the context of the application that made the call. The shim saves all registers, passes the event record and a pointer to the block’s globals to the C body, and — if the body reports that it consumed the event — returns “no event” to the caller; otherwise it chains to whatever filter was there before. - Body (C, compiled A5-free). No statics, no string literals, no library glue: every Toolbox call is an inline trap and all state lives in the block. It decides whether the event is a trigger, whether a restore is due, and — the condition that keeps it safe — whether the calling application is the front process. Only then does it walk the layers, compute the destination rectangles, draw the animation into the Window Manager port and call
ShowHide. A background process’s event fetch never causes drawing in that process’s context.
The restore-on-switch rule is implemented by recording the front process’s serial number at hide time and comparing it, on the next non-null event, with the current front process. Because the comparison only happens in the front process’s own context, the windows return exactly when the newly-front application first looks for an event.
Two details protect against the ways this can go wrong. Before restoring, the body walks the hierarchy again and only shows windows that still exist and are still hidden: a window closed while hidden would otherwise be a dangling pointer handed to the Window Manager in the front application’s process. And the animation clips itself to the desktop below the menu bar and restores the port’s clip region afterwards.
What it cost to find out
The visible mechanics — the walk, the geometry, the animation, hide and restore — were first exercised from an MPW tool with one verb per step, so that each mechanism could be run and photographed in isolation before any of it went into an INIT. That discipline paid for itself immediately, in an unexpected way.
The first run of the tool’s hide verb took the emulator down. macOS reported EXC_BAD_INSTRUCTION inside SDL’s window teardown, which looked like the host-side crash class already known from an earlier AppleBridge spike. Two frames deeper in the same report, however, stood catch_exception_raise → sigsegv_dump_state → QuitEmulator: Basilisk II had caught a guest memory access outside its mapped range and quit — the SDL crash was merely its exit path. A staged rerun placed the fault at the first QuickDraw call. The cause was mundane: an MPW tool has no QuickDraw globals unless it declares QDGlobals qd and calls InitGraf, and the linker had said so earlier — Undefined entry "qd" — a message I had worked around with a private pattern instead of reading. GetPort then dereferenced garbage through A5, an address the emulator could not survive. The extension is not affected: a jGNE filter runs with the front application’s globals, which is why the front-process check is not an optimisation but a precondition. The rule recorded for the project: stage every new mechanism behind a verb, and run the non-drawing verb first — a guest fault costs a reboot and risks the disk image.
Two smaller findings from the build itself: an assembler source copied in through the shared folder arrives without a file type and the assembler refuses it as “not a text file” until SetFile -t TEXT is run; and the link takes ToolServer long enough that the bridge daemon drops its connection and redials about ten seconds later, so the error file must be read after the script, not queued behind it.
Verification
Each of the following was confirmed by a screenshot of the emulated screen, taken through the bridge after the action:
| Action | Result |
|---|---|
| F11 with a control-panel application in front | Finder window, daemon console and the application’s window gone; desktop shown |
| F11 again | all three back, original stacking order, same front window |
| Windows hidden, click on the desktop | Finder becomes front; every window restored without a key press |
| Mouse parked in the top-right corner | hidden; leaving the corner and returning restores |
| Reboot with the INIT installed | boot completes, bridge daemon and ToolServer come up normally |
The animation itself is the one thing a screenshot cannot show: twelve frames in about a quarter of a second. It ran in the spike tool without leaving XOR residue, and a host-side video capture is the way to inspect it.
What the first version does not do
- The window-overview half of Exposé, which Crutch had prototyped for a “1.1”.
- A control panel; the key and the corner are constants in the source.
- The original poster’s “three pixels still visible at the edge”: this version hides the windows rather than moving them, which is Crutch’s fast path and leaves nothing at the edge to draw. Drawing partial window frames there is a later step.
- 68000 machines, and multiple monitors.
GitHub
The source, the build script, the two spike tools and a log of every measurement are public at github.com/LoetLuemmel/Expose; the verified build ships there as bin/Expose.bin, a MacBinary that mac_put_file can drop straight into a guest’s Extensions folder.
Sources
- PB170, Idea/dream/wish: Exposé for classic Mac OS, 68kMLA, 8 Nov 2021 — 68kmla.org/bb/threads/…39438: the specification, the mock-up, the zoom-rect measurements (post #15), Crutch’s betas 3–5 (2021-12 to 2022-02).
- Crutch, How to get the Window Manager’s WindowList in a multitasking environment?, 68kMLA, 30 Oct 2021 — 68kmla.org/bb/threads/…39361: the Layer Manager route (cheesestraws, post #6).
- zydeco, Basilisk II rootless branch,
BasiliskII/src/SDL/video_rootless.cpp— the concrete selector and offsets. - AppleBridge: the bridge, build tooling and journaling/INIT templates used here — td5.390er.de/applebridge/.
- All measurements: System 7.6.1 under Basilisk II (SDL2 port), macOS 15.7, 29 August 2026.
