A graphical desktop for the ZX Spectrum
zxdesk: A Graphical Desktop for the ZX Spectrum
zxdesk is a full-featured GUI operating system designed for the ZX Spectrum 48K, meticulously crafted using Z80 assembly.
🖥️ Project Overview
This project implements a sophisticated desktop environment on hardware from 1982. Despite the constraints, it features:
- Window Management: Overlapping windows with a defined -order and focus handling.
- Interface Elements: Pull-down menus, dialogues, and various controls.
- System Utilities: A notepad, a clock, a calendar, and a two-pane file manager.
- Core Logic: A dedicated heap, an event queue, and a settings panel that persists changes.
- Storage: A flexible storage layer supporting multiple swappable backends.
The system is highly optimized; it can execute a window drag operation within a single frame of T-states.
💡 The Motivation
Back in the 1980s, I craved an Atari ST but lacked the funds. What I truly desired was GEM—the desktop metaphor, the persistent menu bar, and the sensation that the computer was a place to inhabit rather than just a command prompt.
I wondered if such an experience could be replicated on a Spectrum. This project is the result of pushing a Z80 and of RAM to their limits, dealing with a one-bit display, attribute clash, and a video chip that competes with the CPU for cycles.
The development process revealed that empirical measurement of the hardware was far more valuable than theoretical reasoning. This project is a labor of love, documented extensively so others can benefit from the timing data I spent countless evenings collecting.
🛠️ Technical Specifications
| Feature | Implementation Details |
|---|---|
| Windows | Movable, resizable, overlapping (-ordered) with title bars, close boxes, and grips. Focus is tied to the top of the -order. |
| Menus | A static top menu bar featuring pull-down menus, "save under" functionality, and hit testing. |
| Input | Supports Kempston mouse/joystick and the full keyboard matrix (decoded via three tables with repeat). |
| Storage | RAM, tape (utilizing the original ROM loader), and 128K spare banks configured as a RAM disk. |
| Memory | A functional heap (8,112 bytes) using an owner byte; window buffers are allocated dynamically based on size. |
| Apps | Defined by a descriptor (init, event, paint) with per-instance state swapping. |
| Persistence | Settings are stored with a version number and a magic byte for boot-time retrieval. |
🚀 Execution and Build Process
Toolchain
The project uses a compact local toolchain, specifically pasmo 0.5.5 (built from source in tools/).
Build Commands
The following scripts are used to manage the project:
-
./build.shAssemblessrc/zxdesk.asmintobuild/zxdesk.tap. -
./run.shBuilds and loads the image onto the machine. -
MACHINE=128 ./run.shTargets the 128K model and restores settings.
Note on esxDOS: This environment runs on an emulated DivMMC with a card image. Since tools/ (containing pasmo, emulators, and the esxDOS ROM) cannot be redistributed, you must build your own image from an esxDOS release. Once ready, launch tools/esxdos/esxdos.szx; the NMI trigger opens the file browser.
Build Flags (--equ)
You can modify the build using these flags:
DEMO=1: Creates a self-dragging build for captures.NOWAIT=1: (Used withDEMO) Disables the beam scheduler.SCRIPT=1: Drives the UI via synthetic input for testing.MOUSETEST=1: Launches a raw Kempston mouse diagnostic.
Warning: build.sh requires an explicit --name because pasmo derives the tape header from the output path; without it, the header would incorrectly be named build/zxde. Additionally, the script prevents building if code overflows into the buffer region to avoid silent crashes into BASIC.
⚠️ Emulator Quirks & Workarounds
Using a mouse in Fuse for macOS (v1.9.2) is problematic. The emulator stops reporting Kempston mouse movement while a button is held, causing the pointer to freeze during drags.
The Solution:
The RiBtn function in ReadInput allows the SPACE bar to act as the mouse button. This ensures the desktop is usable without a mouse or on buggy emulators.
🏗️ Architecture & Logic
Input Simulation
When SCRIPT=1 is active, the system uses a list of synthetic events. This allows complex interactions (opening menus, typing, saving) to be perfectly reproducible and comparable across runs.
Hardware Abstraction Layer
To ensure portability, the system uses a boundary layer. The following functions handle drawing in byte columns and pixel rows, hiding the complex interleaved layout of the Spectrum screen:
DevFillRect, DevFillDesk, DeskFillCol, AddrAt, BlitRect, RectGrab.
The Main Loop
The system follows a strict execution order to maintain a precise scheduler:
By placing input reading at the very end of the frame, the time elapsed before the beam wait remains constant, ensuring the scheduler's accuracy. The process concludes with EvPoll.