Gemini plugin for KOReader

Announcing a Gemini client for KOReader, implemented as a plugin. If you ever wanted to browse Gemini on an e-book reader, or want to now you come to think of it, here's your chance!

This is a first release, tested only on a couple of Kobo devices (thanks crynick). All the essential features are there, including client certificate support. It should be straightforward to install.

Gemini plugin for KOReader

Please let me know if you have any problems installing or using it, or any feedback. For now I'm publicising this only through this glogpost. After some more testing, I'll see about making a PR to have it included in KOReader -- so I'd also appreciate thoughts on how well it would work as someone's introduction to Gemini, and how to improve that aspect.

Warning

This will let you access Gemini on your e-book reader. Before installing, please think whether that's something you actually want. From accounts I've read, I suspect that some people would find that such a source of distraction could ruin their experience of concentrated reading. If this is you, think twice.

Retrospective

You can stop reading now. The remainder of this post is a self-indulgent personal account of this project's development.

Ever since I first considered getting a KOReader device, I've been thinking that Gemini would be a perfect fit. Text-centric and computationally undemanding, it can treat low-end hardware with e-ink displays as first-class citizens. One of the first things I did once I obtained such a device was to cross-compile some terminal clients for it, but in the end I mostly just sshed home to use diohsc (Haskell is a real pain to cross-compile). That suited me fine, but still I had a persistent itching feeling that a native client really ought to exist...

So the other month, I finally read through the Lua manual and hacked up a first minimally usable implementation. It essentially just called gmni to perform the actual request, converted the result to html, then opened it using KOReader's usual html renderer (CoolReader).

I came back to it after encouragement from a couple of Geminauts who read me mention it in a glogpost, and thought about how to do it less hackily. I came close to rewriting it along the lines of KOReader's Wikipedia reader -- with its own custom UI separate from KOReader's usual document viewer. But the advantages of integrating with KOReader are many, so in the end I stuck with my initial "hacky" design. Looking back, this saved me from many headaches, and gives the client a lot of functionality "for free".

After getting frustrated with gmni's (via BearSSL's) limited support for TLS1.3, and conscious that needing a non-standard binary would make distribution complicated, I wrote a pure lua client implementation of the gemini protocol itself using the "luasec" openssl wrapper. Other than that, it's mostly been a matter of designing a UI and integrating as far as possible into KOReader.

Some aspects of the UI are adapted from some of my favourite features of diohsc -- the queue and its use for offline reading, repeating input ("REPL mode"), and orienting things around performing commands on URLs rather than first loading a page and then performing a command on it. I think I found neat ways to translate these ideas from command-line to a touch interface, but I'll let you judge.

This was my first serious experience with Lua, and generally I was impressed. It's like python without the cruft. It took some time to get sufficiently familiar with the sparsely documented KOReader codebase, but it isn't bad. I had feared that many changes to KOReader would be necessary, but in the end everything could be done as a plugin (I did make some PRs, quickly merged, but only to support a little non-critical functionality).

The project was a lot of fun, and having it to lose myself in did a lot to maintain my sanity through a couple of painfully busy work weeks. This is the second gemini client I've written, and I enjoyed it just as thoroughly as the first. I've no plans for a third, but we'll see!