Turn your reMarkable's sleep screen into a moment of reflection.
One quiet, beautiful idea every time the device rests — rendered as a paper card, installed offline, and reversible down to the byte.
An independent compatibility project. Not affiliated with, or endorsed by, reMarkable.
The reMarkable's idle screen is the most-seen, least-used surface on the device. It is uncluttered, always in view, and already associated with thinking. Antara puts a single well-set idea there — no app to open, no notification, nothing to dismiss.
| What you need | What you get | |
|---|---|---|
| Build the cards | Just a computer | Rendered PNGs you can view, tweak, or sideload by hand |
| Port it to your device | A Paper Pro in Developer Mode | The cards installed as real sleep artwork, with one-command restore |
Start with the first. It needs no hardware and proves the whole pipeline works.
Runs on macOS and Linux. Requires Python 3.11+ and make. Nothing here touches the
network.
The renderer prefers the Liberation or DejaVu families and falls back to system fonts, so
a stock Mac works with no font installs (it will use Georgia/Times and Helvetica instead).
For output identical to the cards committed in generated/, install Liberation:
brew install --cask font-liberation # macOS
sudo apt-get install fonts-liberation # Debian/Ubuntugit clone https://github.com/humancto/marky.git ambient-quotes
cd ambient-quotes
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
make validate # check content, themes, and that no text overflows its card
make render # write device-sized PNGs to generated/
make preview # build the gallery and device mockup in docs/preview/That renders every enabled quote in the library to generated/. Open one — that is
exactly what the device will show.
The bundled library is 2,026 quotes, so a full
make renderwrites 2,026 PNGs (~200 MB) and takes a few minutes. To render a single card, use the renderer directly:python -m renderer.render --only stoic-001 --out generatedSelecting a subset per device is what rotation (Gate 2) is for; it does not exist yet.
make test # 84 tests, including a full end-to-end install/restore cycle| Target | What it does |
|---|---|
make validate |
Content + theme schema, and a text-fit pass over every quote × theme |
make packs |
List bundled collections and their sizes |
make rotation-set |
Render a rotation set (SAMPLE=, PACKS=, THEME=) |
make render |
Render the whole library through a theme into generated/ |
make preview |
Contact sheet and device mockup into docs/preview/ |
make test |
Full suite (unit + end-to-end against a simulated device) |
make test-e2e |
Just the end-to-end lifecycle tests, verbosely |
make discover |
Read-only device inspection (Gate 0) |
make install-dry-run |
Full install plan and preconditions — zero device writes |
make install |
Back up, install, verify |
make restore |
Put the original artwork back, checksum-proven |
Render a different theme or resolution:
make render THEME=themes/minimal.json
make render WIDTH=1404 HEIGHT=1872 # e.g. for a different panel size
- Use a device you can afford to wipe. Turning on Developer Mode factory-resets it.
- This is not an official reMarkable feature. It can break with any OS update.
- Every step below is reversible, and nothing writes to your device until you say so.
There are two ways in. Try the first one — it is dramatically safer, and on the firmware this project has validated it is also the one that works.
| Writes to | Needs a read-write remount | Survives an OS update | |
|---|---|---|---|
| A. Custom sleep screen (recommended) | /home — your own data partition |
No | Probably — nothing in the OS image is touched |
| B. Replace the system artwork | /usr/share/remarkable/suspended.png |
Yes | No — the OS image is restored on update |
On the tablet: Settings → General → Software → Advanced. This wipes the device, so let it finish and re-sync before going further.
Once it reboots, find the root password in Settings. The exact screen moves between firmware versions, so look rather than guess — it is shown alongside the device information, near the Developer Mode switch you just used.
Plug the tablet into your computer with a data USB-C cable. It appears as a network
device at 10.11.99.1:
ssh root@10.11.99.1 # paste the root password from Step 1Then install your key, so the scripts can run without prompting:
ssh-copy-id root@10.11.99.1The tablet drops off the network the moment it sleeps. This is the single most confusing thing about working with it — SSH dies mid-command and the interface disappears from your computer entirely. Wake it, and if that is not enough, unplug and replug the cable. Check with:
ping -c1 10.11.99.1
On Wi-Fi instead? Every script takes
--host, or setAQ_HOST=<device-ip>.
Do this before you look at any card, or you will think the install failed.
On the tablet, find Sleep Screen Illustrations in Settings and switch it off.
The sleep screen is a composite, not a single image. With this on, the firmware draws your most recent document as a panel on top of the sleep artwork, covering most of the quote. Turning it off leaves the card clean.
One thing it does not remove: if you have owner information set, the firmware still draws
it in the lower band. It has no toggle. themes/editorial-signature.json exists for this
— it reserves the bottom of the card so the two never collide.
The firmware can point its sleep screen at any file you like. Your cards live in /home
alongside your notebooks, no system file is modified, and rotation is just a matter of
changing which card is current.
make sleepscreen-probe # does your firmware support this?
make rotation-set # render 60 cards, spread across all 20 collections
make sleepscreen # copy them over, install the rotator, point the firmware at itPress the power button. A quote is your sleep screen, and it changes every 30 minutes.
That is the whole install. No remount, no backup to keep track of, nothing to restore.
make rotation-set SAMPLE=200 # a bigger library
make rotation-set THEME=themes/editorial.json # a different theme
make sleepscreen EVERY=120 # rotate every two hours
make sleepscreen EVERY=0 # no rotation, one fixed cardSAMPLE draws evenly across all 20 collections rather than taking whichever sorts first,
so a 60-card set is 3 from each. It is deterministic: the same SAMPLE and SEED always
produce the same set, so you can reproduce a library exactly.
make sleepscreen-next # skip to the next quote right now
make sleepscreen-status # what's installed, which card is showing, when it rotates next
make sleepscreen-remove # uninstallRemoving it deletes one line from a config file. Your original sleep screen returns because it was never touched.
| Path | What |
|---|---|
/home/root/.local/share/antara/cards/ |
your rendered cards |
/home/root/.local/share/antara/current.png |
the one being displayed |
/home/root/.local/bin/antara-rotate |
picks the next card, stages it, moves it into place |
/etc/systemd/system/antara-rotate.timer |
fires the rotator on your interval |
SleepScreenPath= in xochitl.conf |
one line pointing the firmware at current.png |
The rotator writes to a temporary file and then moves it, so the firmware can never catch a half-written image.
Why the interface restarts during install. xochitl reads its settings once at startup and serves them from memory afterwards. Editing the config while it runs does nothing — it will even rewrite your line back to disk without acting on it, which looks exactly like a failed install.
sleepscreen.shrestarts it for you. This takes a few seconds and closes whatever document is open; nothing is lost.
Use this only if Method A does not work on your firmware. It overwrites a file in the OS image, so an update will revert it, and it needs the root filesystem made writable.
Everything here runs behind a manifest gate: the installer will not contact your device
until a "confirmed" entry exists for your exact firmware.
make discover # read-only. Writes a report to docs/discovery-reports/Open the report and find the suspend artwork: its absolute path, its pixel size, and its
checksum. Record what you found in docs/DISCOVERY.md, then add your device to
platform/remarkable/manifest.json:
The manifest describes several devices. Find the profile for yours — ferrari (Paper
Pro), zero-sugar (rM2), zero-gravitas (rM1) — and fill in what the report told you:
The rM1 and rM2 profiles ship with empty machine_names on purpose, so the installer
refuses them until someone confirms one on real hardware. Their panel sizes are documented
values, not measurements — check them against your own discovery report before trusting
them.
VERSION_IDis not the version shown in Settings. Settings shows something like3.28.0.164; the manifest matches on the5.8.199-style string in the report.
bash platform/remarkable/detect.sh # must print: status: CONFIRMED
make install-dry-run # full plan, zero writesThe root filesystem is mounted read-only, so the install will stop with exit 7 until you make it writable. The adapter deliberately will not do this for you:
ssh root@10.11.99.1 'mount -o remount,rw /'
make install
ssh root@10.11.99.1 'sync; mount -o remount,ro /' # a reboot also restores thisRestore the factory artwork at any time — checksum-verified against the backup taken before the first write:
ssh root@10.11.99.1 'mount -o remount,rw /'
make restore
ssh root@10.11.99.1 'sync; mount -o remount,ro /'| What you see | What it means |
|---|---|
| The card is covered by a panel showing your notebook | Sleep Screen Illustrations is still on (Step 3) |
| A name still shows at the bottom | Owner information — no toggle exists. Use themes/editorial-signature.json |
| Method A changed nothing | The interface was not restarted. systemctl restart xochitl |
ssh: connect to host 10.11.99.1: Operation timed out |
The tablet slept. Wake it, then replug the cable |
Permission denied (publickey,password) |
Key not installed yet — ssh-copy-id root@10.11.99.1 |
Exit 3 |
Firmware has no confirmed manifest entry. This is the gate working |
Exit 7 |
Root filesystem is read-only, or too little space. Method B only |
Exit 6 |
A checksum did not match. The operation stopped before doing damage |
Only one device has been validated: reMarkable Paper Pro, reMarkable Ferrari,
firmware 5.8.199. Everything below is what you would need to check on an rM1, an rM2, or
a newer firmware — and where the honest unknowns are.
The good news is that nothing here requires the project to guess. The manifest gate fails closed per firmware, so an unvalidated device simply refuses to install until its owner confirms it. Adding a device is a contribution, not a fork.
Rendering. Panel size is a parameter, not an assumption. rM1 and rM2 are 1404×1872:
make rotation-set WIDTH=1404 HEIGHT=1872Themes use fractional margins and scale type from base_width, so a card composed for one
panel composes correctly for another. No theme changes are needed.
| Question | How to answer it | Paper Pro 5.8.199 |
|---|---|---|
| Does the firmware support a custom sleep screen? | make sleepscreen-probe |
✅ yes |
| Where is the sleep artwork? | make discover, read the report |
/usr/share/remarkable/suspended.png |
| What size is it? | the report lists dimensions | 1620×2160 |
| Is the root filesystem writable? | findmnt -n -o OPTIONS / in the report |
❌ read-only |
| Is there an overlay drawn on top? | sleep it and look | yes — "Sleep Screen Illustrations" |
Start with make sleepscreen-probe. It checks whether xochitl exports
SleepScreenPath. If it does, everything in Method A applies unchanged and you never need
the manifest, the backups, or a remount — that path is device-agnostic because it only
writes inside /home.
If the probe fails, the firmware predates the feature and you need Method B, which is where per-device confirmation matters.
make discover # read-only; writes a timestamped report
bash platform/remarkable/detect.sh # tells you what is missingFrom the report, take the exact model string and the exact VERSION_ID, then add an entry
to platform/remarkable/manifest.json. Record what you observed in docs/DISCOVERY.md —
paths, dimensions, whether a restart was needed, what the sleep screen actually showed.
Contributions of confirmed devices are the point. Open a pull request with your discovery report and manifest entry and the next person with your device gets a working install. Please do not add an entry you have not personally verified on hardware; the whole design assumes entries are empirical.
These are inferences from the Paper Pro findings, not verified, and should be treated as hypotheses to test rather than facts:
- rM1 and rM2 likely use the same
/usr/share/remarkable/suspended.pngpath. - Their root filesystems have historically been writable, which would make Method B simpler there than it is on Paper Pro — no remount needed.
SleepScreenPathis probably absent on older firmware; it appears to be a recent xochitl feature. Expect Method B on an rM2.- The sleep-screen carousel and the owner banner also look Paper Pro-era. On an older
device the card may simply fill the screen with nothing drawn over it, making
themes/editorial.jsonthe better choice overeditorial-signature.
manifest.json holds a list of device profiles, each with its own machine_names, panel
size, and confirmed firmware. The installer matches the model string the tablet reports,
picks that profile, and checks your card against that device's panel size — so a
1404×1872 card is refused on a Paper Pro with the command to re-render correctly.
Profiles ship for ferrari, zero-sugar, and zero-gravitas. Only ferrari is
validated. The other two are scaffolding with the gate closed: no machine_names, no
firmware, nothing installable until an owner fills them in from their own report.
Quotes live in packs under content/collections/, one file each. A pack is the unit you
choose between, install, and share.
make packs # what's bundled, and how big
make rotation-set PACKS=stoic,night # build a rotation from just these
make sleepscreen # put it on the device| Pack | Quotes | Pack | Quotes | |
|---|---|---|---|---|
east |
311 | kural |
62 | |
stoic |
288 | fragments |
55 | |
poets |
203 | dialogues |
42 | |
builders |
200 | orator |
42 | |
core |
200 | baobab |
41 | |
affirmations |
160 | flowersong |
35 | |
night |
120 | rosegarden |
23 | |
upanishads |
100 | highone |
20 | |
gita |
80 | maxims |
17 | |
lantern · minister |
16 · 11 |
2,026 quotes across 20 collections. Rights are per pack: 1,346 are attributed
historical sources, and 680 — core, builders, night, affirmations — are original
writing by this project, marked licensed rather than public domain.
Writing or contributing a pack is documented in
content/collections/README.md. A pack is one JSON file;
quote ids are generated, so contributors never collide.
{
"id": "stoic",
"name": "Stoic",
"rights": "public-domain-or-verified",
"quotes": [{ "text": "Confine yourself to the present.", "author": "Marcus Aurelius" }]
}make validate # schema, rights, and that every quote fits every themeA quote that cannot be typeset fails the build rather than surprising you on the device.
Themes are declarative JSON: margins as fractions of the panel, a font-size range the renderer fits within, tracking, alignment, and optional label, author, and footer.
| Theme | For |
|---|---|
editorial |
The default. Label, quote, author, footer rule |
editorial-signature |
Same, with the bottom ~22% reserved for the device's own owner banner |
minimal |
Quote and author only |
spiritual |
Centred, roomier margins |
Copy one and adjust. The renderer picks the largest size in your range at which the text fits, and refuses to render if nothing does.
make rotation-set THEME=themes/minimal.json
make rotation-set WIDTH=1404 HEIGHT=1872 # a different panel| Renderer | Adapter (install / restore) |
|
|---|---|---|
| macOS (Intel & Apple Silicon) | ✅ works with stock system fonts | ✅ uses shasum and BSD stat; runs on the bundled bash 3.2 |
| Linux | ✅ | ✅ |
| Windows | ✅ via WSL | ✅ via WSL |
The adapter's host-side helpers detect GNU vs. BSD userland at load time, and the scripts
avoid bash 4 features so they run on the /bin/bash Apple ships. Commands sent to the
device always target Linux, since that is what the reMarkable runs.
tests/test_portability.py guards all of this.
Cards rendered on macOS with system fonts differ cosmetically from the Linux-rendered
snapshots in generated/ — same layout and fitting, slightly different letterforms. The
snapshot-comparison test detects this and skips rather than failing.
content/collections/ ┐
├──► renderer ──► generated/*.png ──► platform adapter ──► device
themes/*.json ───────┘ (Python) 1620×2160 (backup → stage → sleep
move → verify) screen
Rendering is platform-independent and deterministic — the same inputs produce
byte-identical PNGs, so cards can be diffed and snapshot-tested. Nothing about reMarkable
leaks into it. All device-specific knowledge lives behind the adapter in
platform/remarkable/, and every path it uses comes from a manifest you confirmed, never
from a constant in the source.
content/ quotes and future collections
themes/ editorial · minimal · spiritual
renderer/ layout.py (pure fitting logic) · typography.py
render.py · validate.py · preview.py
platform/remarkable/ detect · discover · install · restore · lib · manifest.json
generated/ rendered cards
docs/ PRD.html · INSTALL.md · RESTORE.md · DISCOVERY.md · preview/
tests/ unit + end-to-end, with screenshot evidence
Four rules the code enforces rather than documents:
- Fail closed. Unknown model, unconfirmed firmware, missing path, read-only mount,
low space, or any checksum mismatch stops the run. Only firmware with a
"confirmed"manifest entry can be written to; the repository ships exactly one, for the firmware validated on real hardware. On any other firmware a fresh clone still cannot write. - Never overwrite without a verified backup. Backups are pulled back and checked against the device's own checksum before anything is written.
- Never bury the original. Re-installing a different card does not create a second backup of the first card — the pristine artwork stays the restore target. (There is a regression test for exactly this.)
- Every write is staged and atomic, verified before and after the move, and leaves no temporary files behind.
make test # 84 tests, ~45sThe end-to-end suite runs the real install.sh and restore.sh against a simulated
device: a temporary filesystem plus ssh/scp stubs that execute the adapter's genuine
commands. Backups, checksums, staging files, and atomic moves all really happen — only the
network hop is faked. It covers the full lifecycle, idempotency, reinstall-after-restore,
rejected writes, unreachable devices, firmware drift, and missing paths.
Tests write visual evidence to tests/screenshots/ as they run:
Left to right: the stock artwork, the installed card, and the restored original — captured from the simulated device during test_full_lifecycle_install_then_restore.
The build and safety layers are complete and tested, and the full install/restore cycle
has been validated on a physical reMarkable Paper Pro (reMarkable Ferrari, Codex Linux
5.8.199 / OS 3.28.0.164). The manifest ships with that one confirmed firmware entry,
so on that exact firmware Steps 2–3 can be skipped. Every other firmware still fails
closed — run discovery and confirm your own, as Steps 1–3 describe.
Two things that firmware taught us, both recorded in docs/DISCOVERY.md:
- The root filesystem is read-only. Installing needs
mount -o remount,rw /first. The adapter refuses (exit 7) rather than doing this for you. - The sleep screen is a composite. Xochitl draws the last-viewed document over the
suspend artwork. Turn off Sleep Screen Illustrations in device Settings, or use
themes/editorial-signature.json, which reserves the lower band for the device's own banner.
Roadmap follows the PRD's gates: rotation and collections (Gate 2), then a companion
configuration surface and curated packs (Gate 3). See docs/PRD.html for
full product context and AGENTS.md for the implementation checklist.
MIT — see LICENSE.
Bundled content is mixed: the historical collections are public domain, while the core,
builders, night, and affirmations collections are original Antara writing and are
not. See the rights field on every quote.
built with ♥ by HumanCTO


{ "codename": "zero-sugar", "machine_names": ["reMarkable 2"], // exact string from YOUR report "render": { "width": 1404, "height": 1872 }, // confirm against the report "firmware": [{ "os_version": "3.11.2.5", // exact VERSION_ID from the report "status": "confirmed", "sleep_paths": ["/usr/share/remarkable/suspended.png"], "requires_restart": "none" }] }