# I Turned My Kindle Into a Remote Desktop Client *Because sometimes you look at an e-ink screen and think, "this could be a monitor."* --- A few days ago I had one of those ideas that sounds absurd at first but won't leave your head: what if I could RDP into my Windows PC from my Kindle Paperwhite? Not a tablet. Not a phone. A *Kindle* — the thing designed to display static book pages at 1 frame per never. Turns out, with enough stubbornness and a jailbroken Kindle, you absolutely can. Here's how I built `kindle-rdp`, a native RDP client that runs directly on a Kindle's e-ink display. ![Kindle showing claude code instance via RDP](img/claude-code.jpg) *Yes, that's Claude Code. On a Kindle.* ## Why? The honest answer: because I wanted to see if it was possible. But there's a semi-practical angle too. E-ink displays are incredible in sunlight, use almost no power, and are easy on the eyes for text-heavy work. If you're SSH-ing into a server, reviewing code, or doing anything that's mostly text — an e-ink RDP session is actually kind of pleasant. Also, I had a Kindle Paperwhite sitting in a drawer doing nothing. ## The Tech Stack Here's what goes into making this work: - **FreeRDP 3.x** — the open-source RDP protocol library, handles all the Windows Remote Desktop communication - **FBInk** — a library purpose-built for writing to Kindle framebuffers, handles all the e-ink quirks - **evdev** — Linux input subsystem, reads raw touch events from the Kindle's touchscreen - **A custom on-screen keyboard** — because you need to type your password somehow Everything is cross-compiled for ARM and statically linked into a single binary. No dependencies on the device. Just drop it on the Kindle and run. ## The Build Process (a.k.a. Dependency Hell) Cross-compiling for Kindle is not for the faint of heart. The Kindle Paperwhite runs a stripped-down Linux on an ARM Cortex-A7 with a hard-float ABI. You need a specific toolchain (`kindlehf` from koxtoolchain) and every dependency has to be built from source as a static library. The dependency chain looks like this: ``` koxtoolchain → zlib → OpenSSL 3.x → FreeRDP 3.x → FBInk → kindle-rdp ``` Each one has its own build quirks. OpenSSL's cross-compile prefix concatenated with the exported `CC` variable, giving me gems like `arm-kindlehf-linux-gnueabihf-arm-kindlehf-linux-gnueabihf-gcc` (yes, the prefix doubled). FreeRDP wanted ICU, fuse3, systemd, and a dozen other things that make no sense on a Kindle. FBInk's `MINIMAL` build mode stripped out the font rendering I actually needed. Eventually, after fighting every `CMakeLists.txt` in the dependency tree, I got a 5.3MB static ARM binary. Not bad for a full RDP client. ## How It Works The architecture is deceptively simple: ``` RDP Server → FreeRDP → GDI Buffer (XRGB32) → Grayscale → FBInk → E-ink Touchscreen → evdev → Coordinate Scaling → RDP Mouse Events → Server ``` A single-threaded event loop multiplexes FreeRDP's network events with the touch input file descriptor using `WaitForMultipleObjects`. When the RDP server sends a screen update, FreeRDP's `EndPaint` callback fires. I grab the dirty region from the GDI buffer, convert the XRGB32 pixels to 8-bit grayscale using fixed-point luminance math, and blit it to the framebuffer through FBInk. ```c // The pixel conversion is straightforward gray = (77 * R + 150 * G + 29 * B) >> 8; ``` For touch input, I read raw evdev events, scale the coordinates from the touchscreen's axis range to the RDP session dimensions, and send them as mouse events to the server. ## The E-ink Challenge Here's where it gets interesting. E-ink displays were designed to show static content. They refresh slowly, they ghost, and they have multiple "waveform modes" that trade speed for quality: - **A2**: Blazing fast (~120ms), but only black and white. No grays. Leaves ghosting everywhere. - **DU**: Supports 16 gray levels, but noticeably slower (~260ms). - **GC16**: Full quality, accurate grays, but takes ~450ms and flashes the screen. I tried several strategies. Pure A2 was fast but the screen became an unreadable mess of ghost images within seconds. Pure DU was clean but felt sluggish. The solution I landed on: **A2 for all partial updates, with a time-based GC16 full-screen refresh every 3 seconds**. This gives you responsive interaction most of the time, and every few seconds the screen does a clean refresh that wipes away all the ghosting. It's not perfect — you can see artifacts accumulate between refreshes — but it's the best balance I found between speed and readability. ## The On-Screen Keyboard You need to enter an IP address, username, and password before connecting. The Kindle's built-in keyboard only works within the stock OS, which we've stopped at this point (you have to kill the Kindle framework to get direct framebuffer access). So I built a custom on-screen keyboard from scratch. And I mean *from scratch* — direct framebuffer mmap, a custom 5x7 bitmap font, pixel-by-pixel rendering. No UI toolkit, no text rendering library. Just raw pixel writes to `/dev/fb0`. ![On-screen keyboard on Kindle](img/keyboard.gif) *A full QWERTY keyboard drawn pixel by pixel on e-ink. With shift and symbols modes.* The keyboard supports lowercase, uppercase (shift), and a symbols layer for all the special characters you need in passwords. Each key is a touch target mapped to the evdev coordinate space. It's not pretty, but it works. One fun bug: I initially used characters like `!` and `^` as internal identifiers for special keys (shift, backspace, OK). Which meant you couldn't type `!` in your password. Took me longer to figure out than I'd like to admit. The fix was switching to control characters (`\x01` through `\x06`) for special keys. ## Touch Input Mapping a touchscreen to a mouse cursor introduces some interesting UX challenges. On a normal touchscreen, you expect a tap to be a click. But to the system, a tap is really: finger down → tiny involuntary finger movement → finger up. That tiny movement? Windows interprets it as a drag, and suddenly you've selected all the text on the screen instead of clicking a button. The fix is a dead zone. I buffer the initial touch-down position and don't send any mouse events until the finger either: - Lifts up (it was a tap → send click at the original position) - Moves more than 15 pixels (it's a drag → send button-down and start tracking movement) Long press (>500ms) maps to right-click. It's not perfect — you miss hover effects and scroll — but for basic desktop interaction it works surprisingly well. ## The KUAL Integration The Kindle needs to run our binary instead of its normal reading UI. This is handled through KUAL (Kindle Unified Application Launcher), which is basically a homebrew app launcher for jailbroken Kindles. The tricky part: KUAL runs *inside* the Kindle framework, and we need to *stop* the framework to get framebuffer access. If you just call `stop framework` from a KUAL script, you kill KUAL and your own script along with it. The solution is `setsid` — launch the worker script in its own process session before killing the framework: ```bash setsid sh -c "exec run.sh >crash.log 2>&1" & ``` This creates a fully detached process that survives the framework shutdown. When the RDP session ends, the script restarts the framework and you're back to your normal Kindle home screen. ## The Result ![Full RDP session on Kindle](img/steam.jpg) *Working RDP session. It's slow, it ghosts, and it's glorious.* It works. You can connect to a Windows PC, see the desktop, click on things, open applications. Is it a good experience? That depends on your definition. The refresh rate is measured in seconds, not frames per second. Ghosting is a constant companion. And without a physical keyboard, any serious text input requires a Bluetooth keyboard or a lot of patience with the on-screen one. But for what it is — a native RDP client running on a $30 e-reader with a 6" screen — it's honestly kind of magical. Checking a server dashboard, reading through logs, reviewing a PR in a web browser — these are all things you can legitimately do from a Kindle now. ## The Temperature Hack Here's a trick I discovered while SSH-ing into the Kindle: the e-ink controller (EPDC) uses the ambient temperature to decide how aggressively to drive the display. Warmer temperatures mean the e-ink particles move faster, so the controller can use shorter waveform timings. The Kindle exposes this as a sysfs parameter: ``` /sys/devices/soc0/soc/2000000.aips-bus/20f4000.epdc/temperature_override ``` By default, it reads the actual screen temperature — usually around 22°C indoors. But you can override it: ```bash echo 45 > /sys/devices/soc0/soc/2000000.aips-bus/20f4000.epdc/temperature_override ``` Telling the EPDC "it's 45°C" makes it use faster waveform timings because it thinks the particles are already moving quickly. The result: noticeably snappier screen updates. It's essentially overclocking the display by lying about the temperature. Is this safe? The display still uses the same waveform modes — it just uses the "warm weather" variant of each mode, which drives the pixels harder. In theory this could reduce display longevity over time, but for a project like this where you're using the Kindle as a secondary monitor for a few hours, it's a worthwhile tradeoff. The setting resets on reboot, so there's no permanent change. I set the override to 45°C in the launch script and the improvement in responsiveness is immediately noticeable. It won't turn your e-ink into an LCD, but it shaves off enough milliseconds to make the interaction feel less sluggish. ## What I Learned 1. **Cross-compilation is still painful in 2026.** Every library has its own build system quirks, and they compound when you're targeting an unusual platform. 2. **E-ink is fascinating but unforgiving.** The waveform mode system is a genuinely interesting engineering tradeoff. There's no "good" mode — just different compromises between speed, accuracy, and ghosting. 3. **Touch-to-mouse mapping is harder than it sounds.** The dead zone concept seems obvious in retrospect, but the first version just sent every touch event as a mouse event and the result was unusable. 4. **Static linking is your friend on embedded devices.** One binary, no dependencies, just copy and run. Worth the build complexity. ## Try It Yourself The code is open source. You'll need a jailbroken Kindle and some patience with the build process, but everything you need is in the repo: **[github.com/CNuhlar/kindle-rdp](https://github.com/CNuhlar/kindle-rdp)** Fair warning: this is very much a "works on my device" situation. It's been tested on a Kindle Paperwhite with firmware 5.16.3. Your mileage may vary. But if you've got a Kindle collecting dust and a taste for the absurd, give it a shot. --- *If you enjoyed this, I occasionally write about other projects that probably shouldn't exist. Follow me for more questionable engineering decisions.*