logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·30.10.26
← Back to news

A graphical desktop for the ZX Spectrum

Original article
github.com|94 points|76 comments|by graemep|Sep 19, 2026

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 zzz-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 69,88869,88869,888 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 3.5 MHz3.5\text{ MHz}3.5 MHz Z80 and 48 KB48\text{ KB}48 KB 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

FeatureImplementation Details
WindowsMovable, resizable, overlapping (zzz-ordered) with title bars, close boxes, and grips. Focus is tied to the top of the zzz-order.
MenusA static top menu bar featuring pull-down menus, "save under" functionality, and hit testing.
InputSupports Kempston mouse/joystick and the full keyboard matrix (decoded via three tables with repeat).
StorageRAM, tape (utilizing the original ROM loader), and 128K spare banks configured as a RAM disk.
MemoryA functional heap (8,112 bytes) using an owner byte; window buffers are allocated dynamically based on size.
AppsDefined by a descriptor (init, event, paint) with per-instance state swapping.
PersistenceSettings 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.sh →\rightarrow→ Assembles src/zxdesk.asm into build/zxdesk.tap.
  • ./run.sh →\rightarrow→ Builds and loads the image onto the machine.
  • MACHINE=128 ./run.sh →\rightarrow→ Targets the 128K model and restores settings.

Note on esxDOS: This environment runs on an emulated DivMMC with a 64 MB64\text{ MB}64 MB 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 with DEMO) 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.