rmkit

a site about remarkable app dev

last month, i posted to /r/remarkable, looking for advice on how to create a multi-tasking launcher for the remarkable. after a lot of banging my head against the wall and consulting with others, i think there’s a reasonable solution for multitasking.

click to see a video of remux in action
click to see another video of remux in action

background

the remarkable tablet runs linux, but unlike normal distributions, the remarkable is not running an x server1 - instead, the main application on the remarkable is xochitl, a qt application that runs the whole system.

problem

because xochitl is the only app running on the remarkable, xochitl directly grabs the framebuffer and input devices and reads and writes to them as it wants. unfortunately, this means that multiple applications can’t run at the same time because there is no coordinating process to help them not stomp on each other.

the result is that if two apps are running at once, they will both draw to the screen and they will both handle all input events (not just the ones meant for them) - this is truly a multitasking environment, but perhaps too much so.

solution

insight 1: using SIGSTOP/SIGCONT

using SIGSTOP/SIGCONT, we can effectively pause any apps that are not in the foreground and resume them when they are focused. this solves the problem of multiple apps drawing to the screen at once. unfortunately it also means an app can’t run in the background, but as a first step, it’s good.

trying this idea out by hand, i ran killall -SIGCONT and killall -SIGSTOP on a process to understand how it works. i noticed that when the process wakes up, it still receives input events. this means that events will be replayed when the process wakes, causing random and unwanted behavior.

insight 2: using EVIOCGRAB

using EVIOCGRAB, a process can gain exclusive control of a device, preventing other processes from reading or writing to it. this is almost enough for the launcher to work, but if an app grabs the devices, then the launcher is no longer able to listen for events and launch when requested.

however, EVIOCGRAB is useful for when the launcher wants to display itself: it can use EVIOCGRAB to grab the device inputs and display itself on top of the currently running app.

insight 3: flooding the kernel event queues

the next obstacle to overcome is how to prevent the kernel from buffering input events for each process. after talking to bokluk, we decided that flushing the queue when a process wakes would be nice and started looking into how to achieve that. the first idea was a kernel module, but as we browsed the source, we realized the events are kept in a circular ring buffer for each client. if we can figure out the size of the ring buffer and fill it up with our own no-op events, the buffered events will be kicked out.

after hacking on the injection code for an evening, a simple proof of concept appeared and it looks like events are now no longer shared between running applications.

appendix A: failed solutions

multiplex the input devices using sockets

the first idea we pursued was trying to arrange the sockets using LD_PRELOAD. the idea is to intercept calls to open() by any managed processes and if the file is an input device, we actually open a socket. the launcher process will then only send events to that socket when the app is focused.

unfortunately, xochitl queries the returned file descriptor for its capabilities and a socket has much different capabilities than an event descriptor. after learning more about the input subsystem, now i would use a uinput device instead of socket because its possible to clone capabilities with the uinput device, making the capability check less of a problem.

flush the kernel event queue via kernel module

one idea we kicked around was to write a kernel module for flushing the event queue, but we decided this is not ideal because it will require installing a kernel module on other people’s tablets

notes

  1. likely to save battery