There is a specific kind of affection reserved for old gadgets.
I have an old 2010s laptop which I am pretty comfortable working with despite weighing as much as an anvil. It comes wrapped in magnesium alloy, still compiles code like a champ, and features a really decent keyboard with a little rubber joystick nestled between the G, H, and B keys —— the nub.
I remember, back in the day, that the machine was able to read clicks through the nub, which was quite enabling and made life a lot easier and faster, since I almost never had to leave typing to deal with window managers or the web. I lost it along the way though, when I upgraded the OS and wasn't able to find an official workaround for this.
Having my muscle memory built on this beast of a machine makes me want to stick with it rather than working on my newer machines. I'd rather get creative with all sorts of remote communications to work on every server or machine just to stick with the comfortable space I have.
So, I thought, why not get the magic back?!
The Situation
Though the nub gives me control over the cursor, and even has comfortable scroll mechanics, it does not allow me to click, double-click, or drag.
The fact that it did click before tells me there must be some kind of pressure detection embedded in the hardware. To find the ghost in the machine, I turned to the raw event debugger of the Linux input subsystem: evtest.
I opened a root shell, pointed evtest at the device node for the AlpsPS/2 ALPS DualPoint Stick, and hovered my finger over the keyboard. I pressed straight down on the rubber cap.
The terminal output confirmed it immediately:
Look at those timestamps and values. From value 108 down to 0 in exactly 53 milliseconds.
Every single time I tap that little piece of rubber, the hardware speaks to the kernel.
The kernel's alps.c driver was dutifully packaging the pressure as an ABS_PRESSURE event and handing it up the chain to libinput (the modern Linux display server driver). And libinput, seeing that this device was a pointing stick and not a touchpad, politely ignored it and discarded the event.
The signal was there. It just had no brain on the other end.
The Goldilocks Dilemma
I decided to write the driver logic myself in userspace.
The plan was simple: listen to the pointing stick’s raw event stream in /dev/input/, detect downward pressure, and inject synthetic mouse clicks back into the desktop using /dev/uinput.
In Python, setting up this bridge requires only a few lines with evdev:
On paper, mapping pressure spikes to button clicks sounds like a three-line if statement. In practice, you run straight into a classic Goldilocks problem: every obvious threshold is either too sensitive or too stiff, and every blunt fix breaks something else.
Direct Pressure Threshold: The Pointer Wobble
The first prototype tested the simplest mental model: treat pressure crossing a fixed threshold as an immediate mouse press.
It clicked, but human fingers aren't hydraulic presses. Striking a 4mm rubber dome always imparts slight lateral shear. Because the click fired on touchdown while pressure climbed, the cursor twitched a couple of pixels sideways upon contact, slipping right off hyperlinks and buttons.
Exclusive Grabbing: Breaking Hardware Ballistics
If the cursor shifts during a tap, why not freeze it?
In Linux, a process can claim exclusive ownership of an input node using ioctl(fd, EVIOCGRAB, 1)—exposed in evdev as stick.grab(). This diverts all raw events solely to our script, hiding them from libinput and X11 so we can act as a man-in-the-middle filter:
Freezing motion during a tap cured the jitter, but broke cursor ballistics entirely. libinput treats pointing sticks with specialized dynamic acceleration curves so tiny physical tilts can traverse a 1080p display smoothly. Our generic /dev/uinput proxy only passed flat 1:1 scaling. Crossing the screen now felt like pushing a brick.
The physical hardware had to remain ungrabbed so libinput could manage acceleration natively.
Tuning the Floor: The Static Threshold Trap
With the stick left ungrabbed, the next attempt tried separating taps from navigation force using a higher static threshold:
A static threshold is a dead end. Set it low (20–30), and steering across the desktop tilts the stick enough to trigger accidental clicks. Set it high (45–70), and clicking requires deliberate, fatiguing force. A single scalar number cannot tell the difference between heavy steering and a tap.
Solving the problem required looking beyond pressure magnitude and analyzing how human touch actually unfolds across time and motion.
The Physics of a Tap
The flaw in the first three prototypes was a false assumption borrowed from physical mice: Down = Click.
On an analog strain gauge, touchdown is ambiguous—the machine cannot tell whether initial downward force is an intentional tap or the start of a steering tilt. Modern touchpads solve this by ignoring touchdown and registering taps on lift-off.
Looking back at the 53-millisecond spike in the initial evtest dump, a true tap is an impulse collision. By waiting for release, the entire contact window can be evaluated in retrospect:
- Steering: Continuous lateral motion or sustained duration (> 180ms).
- Tapping: Sharp downward pressure, negligible lateral tremor, completed in under 180ms.
Clicking on Release Instead of Touch
Firing on lift-off cures the pointer wobble mechanically: by the time BTN_LEFT emits, the finger has already left the rubber.
Because lateral tremor is capped at 6 counts, steering across the desktop can never trigger an unintended button press, regardless of downward force.
Immediate Button Lock for Dragging
Tap-and-drag required a second rule. Waiting for a hold timer on the second tap creates noticeable latency before text highlights. The solution: lock BTN_LEFT down the millisecond Tap 2 touches down:
Lift off quickly, and it releases as a clean double-click. Hold and steer, and your selection is already gripped from millisecond zero.
Motion-Coupled Drag Retention
Why did drags drop mid-flight? When tilting a stick sideways to steer, vertical downward pressure often momentarily drops to zero. The fix is letting active movement keep the button locked:
When motion stops and the finger lifts, a 120ms silence window cleanly releases the selection.
Going Native: The C Rewrite
The Python daemon proved the state machine logic, but running an interpreted runtime continuously for a low-level input loop was unnecessary resource overhead.
I reimplemented the state machine in C using <linux/input.h>, <linux/uinput.h>, and epoll:
Compiled with gcc -O2, the resulting binary is 25 KB and runs with a resident memory footprint of 380 KB. Managed by a systemd unit (alps-stick.service), it starts automatically on boot and handles device reconnects cleanly.
The complete implementation—including the C daemon, Python prototype, Makefile, and systemd service configuration—is available on GitHub:
👉 github.com/pccria/alps-trackstick-daemon
Mechanism, Not Policy
The Linux kernel was never broken. The alps.c driver detected the strain gauge and broadcast its pressure as ABS_PRESSURE all along.
The kernel provides the mechanism—it exposes what the hardware can do. Deciding what that pressure means is policy, and policy belongs in userspace. Because modern desktop environments prioritize touchpads, trackstick taps were simply left unmapped.
With evdev and uinput, the plumbing was already there. A small background daemon was all it took to turn that raw pressure into reliable clicks.
Now, a light bounce on the rubber cap clicks instantly, dragging feels natural, and my hands never have to leave the home row.