
24 January 2026
Today I finally got around to coding an event handler to take libevdev input events and emit simpler touchscreen events.
The touchscreen module I bought has five touch slots, but only sends touch locations. When working with a Wacom graphics tablet, I noticed other events: ABS_MT_TOUCH_MAJOR and ABS_MT_TOUCH_MINOR. These are apparently about the shape of a touch on the sensor so you can infer things like pressure and angle of touch from a squidgy finger.
There are also button events which I don't care about. All I want is touch-start, touch-move and touch-finish events so I can implement a basic 1-, 2- and maybe 3-touch gesture system. I do, however, want to handle EV_SYN_DROPPED events properly because a Pi Zero 2 might end up dropping events if it gets stressed.
In my TouchEvent struct I've got a time stamp (seconds and microseconds), the slot number, x and y coordinates and the event type (start/move/finish).
I have written an EvTouchHandler class which can be given a device path (e.g. /dev/input/event0) or told to pick the first device which supports multi-touch events. It has an event dispatcher thread which can be started and stopped as necessary.
I initially tried to process the multi-touch events without keeping the state of each touch slot, figuring I can build a message and dispatch it whenever the slot ID changed or the EV_SYN_REPORT message came in. However, occasionally one of the axis values is omitted if it does not change when the other does. This would put spurious zero values in the outgoing events, so I had to change tack.
Now, it keeps a separate state for each of the five slots (hardcoded at five, because that's all my touchscreen can do and I really don't care about making it flexible,) and determines the outgoing event type depending on the incoming events.
The graphics tablet I have does not send a ABS_MT_SLOT event for a single touch, so on startup (and if the current slot is ever changed to -1) I assume the current slot is 0.
If the dispatcher receives an ABS_MT_SLOT event, it will dispatch a message for the changes to the current slot (if there are any), then change to the new slot.
Handling a ABS_MT_TRACKING_ID message was slightly trickier. If I don't miss any incoming events, I could dispatch a touch-finish event whenever the tracking ID was changed to -1. However, I can't be sure that I will receive an ID of -1 if I've missed a bunch of messages and the touch slot has been re-used in the gap. So my logic is: if the trackingID is different from the previous one, then...if the previous trackingID was not -1, dispatch a touch-finish event; if the new trackingID is not -1, dispatch a touch-start event. That way, if the tracking ID changes from one valid value to another without a -1 in between, both a touch-finish and a touch-start event will be sent.
The ABS_MT_POSITION_X/Y events change the x and y values in the state, and set the event type and time if it is not already set as a touch-start or touch-finish event.
Finally, a SYN_REPORT event will trigger an outgoing event, and a SYN_DROPPED event will change the read flags for libevdev_next_event to LIBEVDEV_READ_FLAG_SYNC until the next time the dispatcher runs out of incoming events.
When there are no more incoming events, the dispatcher resets the read flag and sleeps for 10ms.
If there are no events pending from the device, the docs say libevdev_next_event() should return -EAGAIN but for some reason I couldn't get this to work reliably.
I imagine it was just me being stupid, but I got it to work by using libevdev_has_event_pending() instead.
So now my test listener instance gets a tidy stream of start/move/finish events with positions and microsecond timestamps!
Next step is to nail down some logic for discerning events/gestured like clicks, long-clicks, drags, swipes and pinch-rotate-zoom. It will likely be tricky to keep it flexible enough to allow multiple simultaneous gestures, but I am sure I will want it to cope with that. I don't want to have to re-engineer the foundations just to detect a click somewhere while pinch-zooming somewhere else, or multiple drags to emulate twin-joysticks on-screen.