# libremarkable: driving the reMarkable tablet's eInk display from Rust

> libremarkable is a Rust library that gives you the reMarkable tablet's framebuffer, low latency partial refresh, multitouch, physical buttons and Wacom digitizer input, so you can write your own apps for the device instead of using the vendor's software. Building it means cross compiling for the tablet's ARM target, and the README is specific about how.

**canselcik/libremarkable** — The only public framework for developing applications with native refresh support for Remarkable Tablet

- Repository: https://github.com/canselcik/libremarkable
- Stars: 709 · Forks: 64
- Language: C
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/canselcik-libremarkable

## Why the tablet needs a framework at all

The reMarkable is an eInk writing tablet, and its official software is built on a display stack that most developers have never had to think about. The repository description calls libremarkable the only public framework for developing applications with native refresh support for the tablet, and the README spells out what that means in practice: low latency partial updates to the eInk display, plus multitouch, physical button and Wacom Digitizer input, are all understood, and implementations ranging from minimal to complete are in the repository. The core idea is a partial refresh. A full refresh on eInk means the whole panel flashes black and white, which is slow and unpleasant to watch. Partial refresh redraws only the changed region, which is fast and quiet, and getting that right requires talking to the framebuffer in the way the hardware expects. The library is a Rust implementation of that stack, and the README notes that the older project name, RemarkableFramebuffer, now redirects here.

## The crate, its feature flags and what each one pulls in

The Cargo.toml shows a crate named libremarkable at version 0.7.0, MIT licensed, edition 2021, authored by Can Selcik. What makes the dependency list interesting is that almost everything is optional and behind a feature flag, so you compile only the parts you use. The default feature set is large: scan, framebuffer-types, framebuffer, framebuffer-storage, framebuffer-drawing, image, framebuffer-text-drawing, input-types, input, battery, appctx and hlua. The dependencies map onto those features. The framebuffer side needs memmap2 for memory mapped access, ioctl-gen to generate the ioctl bindings and zstd for the compressed display format. Drawing needs rusttype for text, image for decoding bitmaps and JPEG and PNG and other formats, and line_drawing for primitives. Input needs evdev for the Linux input event device layer and epoll for the event loop, with fxhash for hashing and aabb-quadtree for spatial queries. A scripting layer, hlua, is also included by default, and a stopwatch crate is present for runtime benchmarking behind a feature flag. The practical consequence is that a minimal program that only draws to the framebuffer does not pull in a UI toolkit, and one that wants a scripting layer has it without writing a parser.

## Cross compiling for the tablet's ARM target

The build story is where most of the friction lives, and the README is unusually explicit about it. The first option is to use the toolchain from reMarkable itself, with a separate installation script for the rM2 and one for later models, then set up your Rust toolchain for cross compilation.

```toml
[target.armv7-unknown-linux-gnueabihf]
linker = "<toolchain_install_path>/sysroots/x86_64-codexsdk-linux/usr/bin/arm-remarkable-linux-gnueabi/arm-remarkable-linux-gnueabi-gcc"
rustflags = [
  "-C", "link-arg=-march=armv7-a",
  "-C", "link-arg=-marm",
  "-C", "link-arg=-mfpu=neon",
  "-C", "link-arg=-mfloat-abi=hard",
  "-C", "link-arg=-mcpu=cortex-a9",
  "-C", "link-arg=--sysroot=<toolchain_install_path>/sysroots/cortexa7hf-neon-remarkable-linux-gnueabi",
]
```

You can also add a build section to that file so the target is used by default, which saves repeating the flag on every command.

```bash
rustup target add armv7-unknown-linux-gnueabihf
```

For rust to use that toolchain, a .cargo/config file is needed, and the repository provides gen_cargo_config.py to generate it. The script reads environment variables from the sourced toolchain environment, so the toolchain has to be sourced first, and it writes a config with a linker path pointing into the toolchain sysroot and a set of rustflags for the Cortex-A9 with NEON and a hard float ABI.

## The Makefile verbs and how a program reaches the device

A Makefile wraps the cargo invocations for convenience, and it exposes a small set of verbs. The README lists examples, which builds the examples, library, which builds the library, and all, which is both. The Makefile itself defaults the target to armv7-unknown-linux-musleabihf and takes a device IP that defaults to 10.11.99.1, with the device host as root at that address. The test verbs are the ones that tell you how the project is developed. The run target builds and runs demo.rs on the device after stopping xochitl, which is the tablet's own user interface daemon, and start-xochitl puts the daemon back. The spy-xochitl target builds spy.rs and LD_PRELOADs it into a new xochitl instance, which the README explains is how you discover new enums used by official programs in their ioctl calls. That last one is a reverse engineering tool as much as a build target, and it explains a good deal about how the library was made.

## The cross route avoids the vendor toolchain entirely

The README offers a second build path with cross, and frames it as the easier setup, because it does not require the reMarkable toolchain and does not require building on Ubuntu 16.04 with Docker. You install it with cargo install cross, make sure the reMarkable toolchain is not in use, and then build, deploy and run the demo through a single make invocation.

```bash
make TARGET=armv7-unknown-linux-gnueabihf deploy-x-demo
# This builds with
#   cross build --example demo --release --target=armv7-unknown-linux-gnueabihf
# then deploys the demo
```

A musl variant is documented too, with a separate armv7-unknown-linux-musleabihf target. Deployment is over SSH: the Makefile kills any running demo, stops xochitl, copies the binary with scp and then runs it with RUST_BACKTRACE=1 and RUST_LOG=debug, which is how you get logs out of a program running on a device with no terminal. The Makefile also carries a docker-env target that builds an image from the docker-toolchain/ directory and an examples-docker target that compiles inside that container with a cargo registry volume, so the vendor toolchain can be reproduced on a modern host without a container older than your build machine.

## Release mode is not optional on this hardware

One practical note in the README deserves more attention than it gets. The --release argument is important because it enables optimizations, and without optimizations the framework runs at roughly 70 percent CPU utilization even when idle, while with optimizations the README says it runs at 0 percent when idle and 1 to 2 percent at peak. That is the whole argument for shipping a release build to a tablet with a battery: an unoptimised binary will drain it. The same section shows what a manual build looks like without the Makefile, with the release and target flags in the cargo invocation. Since 0.7.0 the minimum supported Rust version is 1.80, and the project states the MSRV explicitly, so the toolchain floor is a documented number rather than an accident.

## What the library does not do, and the older C branch

The boundary of the project is clear from the README. It is a framework for the display, input and application context, not a document editor. There is no document format handling, no notebook management and no sync, because all of that lives in the vendor client that you stop with systemctl stop xochitl when you run your own program. The README also mentions a possibility rather than a plan: a piston backend might be created for the tablet, which would allow the use of conrod to simplify UI creation, and that is phrased as a maybe rather than a commitment. The other thing to know is the history. The first draft of libremarkable was a C library, built while reverse engineering the tablet, and the repository listing names C as the primary language for that reason. That implementation is no longer maintained and lives on the legacy-c-impl branch, with the README making the distinction explicit. The current crate is the Rust rewrite, and the branch exists if you want to see how the reverse engineering went.

## Conclusion

Adopt libremarkable if you want to write your own applications for a reMarkable tablet and need direct access to the framebuffer, partial refresh, multitouch and the Wacom digitizer, since no other public framework covers that combination. Do not adopt it if you only want to read or annotate documents, because the vendor client already does that and the library gives you no document model. Before you start, install the reMarkable toolchain and generate your .cargo/config with gen_cargo_config.py, or take the cross route with cargo install cross, and plan on deploying over SSH to a device at 10.11.99.1 with key based authentication set up as the Makefile assumes.

## FAQ

### What does libremarkable let me do on a reMarkable tablet?

The README says low latency partial updates to the eInk display, multitouch, physical button and Wacom Digitizer input are all understood, with minimal to complete implementations in the repository. It is a Rust library, published on crates.io, and the project describes itself as the only publicly available framework for applications on the tablet with low latency partial refresh support.

### How do I build libremarkable for the reMarkable tablet?

The README gives two routes. You can install the toolchain from reMarkable, add the Rust target with rustup target add armv7-unknown-linux-gnueabihf and generate a .cargo/config with gen_cargo_config.py, or you can install cross with cargo install cross and build without the vendor toolchain, which the README says is easier to set up.

### How do I run a libremarkable program on the device?

The Makefile assumes the device is reachable at 10.11.99.1 with SSH key based authentication already set up, so no password prompt appears. The deploy target copies the binary over scp and runs it over SSH with RUST_BACKTRACE=1 and RUST_LOG=debug, and the run target does the same for demo.rs after stopping xochitl.

### Why does the build need the release flag?

The README says the --release argument is important because it enables optimizations, and that without optimizations you would be looking at roughly 70 percent CPU utilization even when idle. With optimizations the framework runs at 0 percent CPU when idle and 1 to 2 percent at peak.

### What is the minimum supported Rust version for libremarkable?

The README states that since libremarkable 0.7.0 the minimum supported rust version is 1.80. The crate version in Cargo.toml is 0.7.0, and the 0.7.0 release tag is dated 2025-09-11.

## Sources

- [canselcik/libremarkable on GitHub](https://github.com/canselcik/libremarkable)
- [Issues](https://github.com/canselcik/libremarkable/issues)
- [License: MIT](https://github.com/canselcik/libremarkable/blob/master/LICENSE)
- [README](https://github.com/canselcik/libremarkable/blob/master/README.md)
- [Releases](https://github.com/canselcik/libremarkable/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/canselcik-libremarkable
