I’ve done the long ass rant in my previous post so I can focus on my road to solution in this one. Long story short:
- I got a touchpad
- I have multiple monitors, which means I have to drag my finger across the touchpad a lot
- Cursor gliding is a solution
- (Linux) drivers don’t do cursor gliding. They do coasting, but they’re shit at it.
- I want to have that feature, but nobody’s going to write it for me.
We have a challenge, then.
What is cursor gliding?
Coasting a feature that was around as far as 10 years ago, but seems to have disappeared. The gist of it is: if you move the cursor fast enough with your touchpad, the cursor will continue moving even after you have lifted your finger. Since picure is worth a thousand words (and video a thousand more), here’s a quick demonstration:
Let’s get it on
I am, of course, not very familiar with C. I know some basics, but that’s about it. Writing proper drivers is beyond my skillset, and it sounds complicated. I don’t like complicated, nor do I want to spend time learning stuff for just one projects. I want a quick solution, so “proper driver” way route is … it can’t go.
I have had some previous experience with
xinput, though (back from when I tried to hack together support for certain keyboard keys that weren’t supported under Linux). As it turns out,
xinput has a debug mode that will print all events emitted by a given input device — and we’ll be using that.
I have tried to find a proper-er way to intercept movement events, but I ultimately haven’t found any. There was one C library that appears to be broken for the past few years, and then there’s Xlib. I’m sure that if I spent some time reading the documentation, I could implement this proper with it. I just don’t want to lose a week doing so.
xinput output to our program it is, then.
We still need Xlib to actually move the pointer (and get pointer position, since
xinput tells us only how far the cursor has moved, but not its position). Scouting ahead, the pointer can be moved with
XWarpPointer(). That function requires absolute coordinates of where the mouse cursor is to be moved, and this will be important later.
Parsing the data
As it turns out, piping output of one program to the output of another is fairly easy in C (although finding some examples of how to do that turned out to be a bit harder). You fork the program and in one of the forks. In the first fork, it takes about five lines of code to set everything up:
In the main thread, receiving said stream in something that can be parsed with
getline() isn’t hard, either:
Things get a little bit more complicated from this point forward. We have to write a parser that will parse whatever
xinput gives us. This is less easy, because goodies that other languages give us — such as
startsWith() — do not exist in C. Fortunately for us, stackoverflow exists and provides us with copy-pasteable solutions which are ready for the taking.
With that in mind, we can continue with the main loop that will parse our events. Before we do anything at all, though, we need to look at the output we’re going to parse:
EVENT type 17 (RawMotion) device: 12 (12) detail: 0 flags: valuators: 0: -1.93 (-2.00) 1: -1.93 (-2.00)
The first line tells which event xinput gave us, so we ensure it matches
EVENT type 17 (RawMotion). Then we ignore the next three lines. The data we want is below the
valuators: line, which we have to parse. It’s a fairly straightforward affair. Once the valuators section is over, we start moving the cursor.
Don’t t(h)read on me
Now that we have a place where we can start moving the cursor, we want to start think about moving it. We don’t actually want to start cursor on every movement, just when we lift the finger — which means that we need to define what “lifting a finger” means for us.
Turns out that the only way to detect that finger was lifted is to wait a bit and see if any other movement events happen while we wait. This is a problem: if we just throw a
usleep(<50 ms>) into our code, then our program will first wait 50 milliseconds, decide that it’s been more than 50 milliseconds since the last event, move the cursor and only then process the next event. Solution? Threads.
With parsing and threads sorted out, we can finally start moving the cursor.
I like to move it
This is not a rocket science, either, but it did come with some trial and error attached. You start by calculating the speed at which the pointer moves. There’s two ways to do handle speed:
- track speed and angle for the pointer
- track speed of pointer along each axis
We don’t need to know absolute speed of the pointer, neither do we need to know the angle. More importantly, moving the mouse requires us to know x/y coordinates of where we want to move our mouse, which makes the second option the more sensible one.
We can’t just determine the speed from the final amount of movement we received from xinput. Most notably, since we don’t know when the finger was lifted, we cannot rely on the last measurement of movement to be accurate. For this reason, we keep a record of the last few measurements and — when end of finger movement was detected — average them. This will also help with the fact that touchpad may produce noisy data: calculating average speed from last few samples will help us to ensure that the speed at which the pointer continues to glide across the screen about as fast as we’d expect.
Moving the pointer is easy enough — every 1/60th of a second, we update the cursor position. This is simple enough: we store the last position (preferably in a float or double) and increment it by the movement speed. We also don’t want the mouse pointer to scroll forever, so we decrease it by some amount after every update. After the cursor speed has been reduced to near-zero, we can also stop moving the mouse and exit the thread since the speed is now too insignificant to actually matter.
Tada we’re done. Time to push it to github and call it a day. Thxbai we’re done here.
But then thing stops working 20 minutes down the line.
And memory leaks aren’t all — as I tweak around with touchpad sensitivity settings, it quickly realize that, at the sensitivity I find ideal, the diagonal movement of finger across the touchpad results in a very pronounced staircase-shaped movement of the cursor.
I guess it’s time to stop coding and start doing warranty returns.