Paper-TTY Rust Project
Overview
Paper-TTY is a custom Rust application that drives the Waveshare 9.7” e-ink display inside the Macintosh Classic II mod. It started as a port of the IT8951 C driver to Rust, grew into a full terminal emulator, and now supports running a complete Sway Wayland desktop session on the e-ink display.
Repository: ~/paper-tty-rs on the development machine, deployed to macclassic:~/paper-tty-rs
History
IT8951 Driver Port (C to Rust)
The original e-ink display was driven by a C library using the bcm2835 GPIO/SPI library. The IT8951 controller chip handles the e-paper waveforms and communicates over SPI at 24 MHz.
The Rust port (drivers/it8951/) reimplements:
- SPI communication via Linux
spidev - GPIO control (HRDY, CS, RESET pins) via
gpio-cdev - IT8951 protocol (preamble-based command/data, register read/write, burst memory operations)
- Display operations (full/partial update, multiple display modes)
- Framebuffer abstraction (8bpp grayscale, pixel manipulation, scrolling)
- Image loading with pixel format support (2/3/4/8 bpp)
Key design decisions:
- Used
spidevandgpio-cdevcrates instead ofrppalfor broader Linux compatibility - Builder pattern for device initialisation
- Strong typing for display modes, pixel formats, rotations
thiserrorfor error handling
Paper-TTY Terminal Emulator
Built on top of the IT8951 driver, paper-tty is a terminal emulator optimised for e-ink:
- PTY-based terminal with shell spawning
- Font rendering (PSF/BDF fonts or system fonts via
fontdue) - Cursor rendering (block, underline, bar styles)
- Partial display updates to minimise e-ink refresh latency
- Viewport/margin system for positioning content on the display
- Light/dark theme support
- Configurable display modes (DU for fast mono, GC16 for quality, GL16 for balanced)
- Keyboard input via evdev with device auto-detection
Sway Wayland Session
The latest addition allows running a full Sway compositor on the e-ink display:
- Sway runs headless (
WLR_BACKENDS=headless) with an 800x600 virtual output paper-tty swayconnects as a Wayland client viawlr-screencopy-unstable-v1- Frames are captured, converted from XRGB/XBGR 8888 to 8bpp grayscale (BT.601), and pushed to the display
- Keyboard input forwarded from evdev to Sway via
zwp-virtual-keyboard-v1with modifier tracking - Pixel-level frame diffing avoids unnecessary refreshes
- Partial updates are 4-pixel aligned for IT8951 compatibility
- Nearest-neighbour scaling from 800x600 to the content area within margins
See Sway Wayland Session Configuration for full setup details.
Project Structure
paper-tty-rs/
├── drivers/
│ └── it8951/ # IT8951 e-ink controller driver
│ └── src/
│ ├── display/ # Display operations, refresh modes
│ ├── graphics/ # Framebuffer, drawing primitives
│ ├── hal/ # SPI + GPIO hardware abstraction
│ └── protocol/ # IT8951 command protocol
├── src/
│ ├── main.rs # CLI (terminal, sway, clear, test, info subcommands)
│ ├── lib.rs # Library root
│ ├── display/ # EinkDisplay wrapper, viewport/margins
│ ├── error.rs # Error types
│ ├── input/ # Evdev keyboard detection
│ ├── terminal/ # PTY terminal emulator
│ └── wayland/ # Sway capture (screencopy, shm, convert, input)
└── Cargo.toml # Features: default (terminal), sway Building
# Terminal mode only
cargo build --release
# With Sway support
cargo build --release --features sway Cross-compile or build directly on the Pi (macclassic).
Deployment
# Sync source to Pi
rsync -az --exclude target paper-tty-rs/ macclassic:~/paper-tty-rs/
# Build on Pi
ssh macclassic "source ~/.cargo/env && cd ~/paper-tty-rs && cargo build --release --features sway" Configuration
All runtime configuration is in /etc/paper-tty.conf on the Pi. Systemd services read this file for display mode, margins, theme, and other settings.
Lessons learned
DU (fast monochrome) mode produces invisible updates when the screen is mostly black. Use a light theme for e-ink and prefer GL16 for general use.
IT8951 partial-update widths have to be 4-pixel aligned or the display shows diagonal artefacts.
Wayland compositors may offer XBGR8888 instead of XRGB8888. You have to handle both with the correct R/B channel swap.
The zwp-virtual-keyboard-v1 protocol requires explicit modifiers() calls. The compositor does not infer modifier state from key events.
When forwarding keyboard input, call EVIOCGRAB to grab exclusive access to the evdev device, otherwise the kernel console also processes keystrokes and you get doubled input.
Only call copy() once per frame capture. If the compositor offers multiple pixel formats per frame, accept the first supported one.