Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

CrossPoint Mac

A native macOS companion app for Xteink X3 and X4 e-readers running CrossPoint Reader firmware — flash the device, manage its library, and generate content it can actually read.

Not affiliated with the CrossPoint Reader project, Xteink, or any device manufacturer. This is an independent third-party client built against CrossPoint's open-source firmware.


Status: early

This is a working shell plus three complete, tested foundation modules. Firmware flashing is not implemented yet — it is the next major piece of work, not a shipped feature. Nothing here has been validated against physical hardware.

Area State
App shell — sidebar, cover grid, list view, inspector, device mirror Working
Library browsing — filter, search, sort, multi-select, keyboard nav Working, on sample data
BMP encode/decode mirroring the firmware's own constraints Complete, tested
Dithering — Floyd–Steinberg, Atkinson, Bayer 4×4/8×8, threshold Complete, tested
Sleep-screen generation Complete, tested, not yet wired to the UI
Device screenshot decoding Complete, tested, not yet wired to the UI
Local library database (GRDB), EPUB parsing Not started
Firmware flashing, backup, recovery Not started
PDF/CBZ → XTC, EPUB optimizer, RSS digests, .cpfont builder Not started

86 tests across 24 suites. Zero build warnings.


Why the design looks the way it does

The device constrains almost every decision, so a few facts are worth stating up front. All were verified against the firmware source rather than assumed.

  • ESP32-C3, ~380 KB usable SRAM, no PSRAM. The firmware lays out an entire paragraph in RAM, so a single multi-kilobyte paragraph can exhaust the device. Host-side splitting is a correctness requirement, not an optimisation.
  • The panel is 1-bit. Greyscale is 2-bit / four levels, synthesised from two bit planes. There is no 16-level greyscale. X3 is 792×528, X4 is 800×480.
  • Content lives on a microSD card, not internal flash. There is no USB mass storage and no MTP — files reach the device over Wi-Fi or by moving the card.
  • Sleep screens are stored portrait (X3 528×792, X4 480×800) while the panel is addressed landscape. This trips everyone. PanelGeometry stores panel, sleep-screen, and screenshot sizes as three independent constants precisely so a future refactor cannot "simplify" them into one.
  • The firmware's luma is (77R + 150G + 29B) >> 8. Those weights sum to 256, so a pure grey round-trips with zero error. Dithering host-side to 0/85/170/255 therefore controls the device's output exactly rather than approximately.
  • The host cannot tell an X3 from an X4 over USB. Both report ESP32C3; the distinction is made on-device by an I²C fingerprint. Model identity carries an explicit confidence level, and the app asks rather than guesses — a wrong panel geometry produces garbled output users blame on the app.
  • Flashing writes only the app image into the inactive OTA slot, then flips the otadata selector at 0xe000. The stock bootloader and partition table are never touched. Generic esptool guides that write at 0x0 destroy the partition table, which is the community's most common brick.

Architecture

A thin app shell over local Swift packages. SwiftPM still cannot produce a .app bundle, so the shipping app will eventually be a small Xcode target containing almost nothing; today the shell runs from a SwiftPM executable.

Packages/CrossPointKit/
  Sources/
    CPCore       — pure values: PixelSize, GreyLevel, PanelGeometry, GreyImage, FirmwareLuma
    CPBMP        — BMP encode/decode mirroring the firmware's accepted formats
    CPRender     — dithering, resampling, page rendering, sleep screens, screenshots
    CPDesign     — design tokens, glass surfaces, the pixel-exact panel preview
    CPUIShell    — sidebar, browser, inspector, device mirror
    CrossPointStudioDev — dev harness so the shell is runnable before the Xcode target exists
  Tests/         — one suite per module

Dependencies flow one way: CPCore knows nothing about file formats, rendering, or UI.

Two rules that are load-bearing

Glass is chrome, never canvas. The panel preview must be pixel-exact, so it sits on an opaque bezel that acts as a matte. No opacity, blur, shadow, or material is ever applied to the raster itself. Its four tone values are absolute sRGB and deliberately do not follow the system appearance — a physical e-ink panel reflects the same light whatever theme you prefer, and a preview that inverted in dark mode would be lying about the one thing this app exists to be truthful about.

Opacity is scoped to the detail column. NavigationSplitView gives the sidebar system Liquid Glass automatically; backgroundExtensionEffect() goes on the detail pane so content appears to continue underneath it. Applying containerBackground(_:for: .window) fills the whole window — including behind the sidebar — and silently kills the glass. (For the record, .navigation and .navigationSplitView are @available(macOS, unavailable), verified against MacOSX27.0.sdk, despite widespread advice to the contrary.)


Requirements

  • macOS 27.0 or later
  • Xcode 27 / Swift 6.4
  • No third-party dependencies

Running it

cd Packages/CrossPointKit
swift run CrossPointStudioDev

Tests

cd Packages/CrossPointKit
swift test

Everything is testable without hardware, which is deliberate: you will have at most one or two devices, and you cannot test the brick paths on them. Tests favour asserting decisions over behaviour — that a book at exactly 1.0 is finished rather than in-progress, that error diffusion leaves a flat native field untouched, that a rendered page contains both ink and paper (an all-white result meaning the text silently failed to draw).


Not yet verified against hardware

Everything below is currently an informed assumption. Each is a go/no-go input for later work:

  1. Where the firmware's four luma thresholds actually fall. All dithering is calibrated on the assumption they sit at 0/85/170/255. A single photograph of a test-card ramp settles it.
  2. The XTC page format's bit and plane ordering. Column-major, right-to-left, two sequential planes — a shape where several readings are plausible and only the device can arbitrate.
  3. The epub_<hash> cache directory naming, which is std::hash<std::string> of the file path on a 32-bit target. Every "preserve reading progress" feature depends on reproducing it exactly, so it must be validated per-device at runtime rather than assumed.

Roadmap

Near-term, in order: the hardware spikes above; a GRDB-backed library with EPUB parsing (which turns the inspector's compatibility panel from example text into the real audit); then firmware flashing, with mandatory backup and the full set of refusal gates.

Acknowledgements

Built against the excellent CrossPoint Reader firmware (MIT), which is entirely responsible for making these devices worth writing tools for.

About

Native macOS companion app for Xteink X3/X4 e-readers running CrossPoint Reader firmware. Unofficial.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages