libevdev: SYN_DROPPED handling

Loading...
Searching...
No Matches
SYN_DROPPED handling

This page describes how libevdev handles SYN_DROPPED events.

# Receiving SYN_DROPPED events

The kernel sends evdev events separated by an event of type EV_SYN and code SYN_REPORT. Such an event marks the end of a frame of hardware events. The number of events between SYN_REPORT events is arbitrary and depends on the hardware. An example event sequence may look like this:

EV_ABS ABS_X 9
EV_ABS ABS_Y 8
EV_SYN SYN_REPORT 0
------------------------
EV_ABS ABS_X 10
EV_ABS ABS_Y 10
EV_KEY BTN_TOUCH 1
EV_SYN SYN_REPORT 0
------------------------
EV_ABS ABS_X 11
EV_SYN SYN_REPORT 0

Events are handed to the client buffer as they appear, the kernel adjusts the buffer size to handle at least one full event. In the normal case, the client reads the event and the kernel can place the next event in the buffer. If the client is not fast enough, the kernel places an event of type EV_SYN and code SYN_DROPPED into the buffer, effectively notifying the client that some events were lost. The above example event sequence may look like this (note the missing/repeated events):

EV_ABS ABS_X 9
EV_ABS ABS_Y 8
EV_SYN SYN_REPORT 0
------------------------
EV_ABS ABS_X 10
EV_ABS ABS_Y 10
EV_SYN SYN_DROPPED 0
EV_ABS ABS_Y 15
EV_SYN SYN_REPORT 0
------------------------
EV_ABS ABS_X 11
EV_KEY BTN_TOUCH 0
EV_SYN SYN_REPORT 0

A SYN_DROPPED event may be recieved at any time in the event sequence. When a SYN_DROPPED event is received, the client must:

- discard all events since the last SYN_REPORT
- discard all events until including the next SYN_REPORT These event are part of incomplete event frames.

# Synchronizing the state of the device

The handling of the device after a SYN_DROPPED depends on the available event codes. For all event codes of type EV_REL, no handling is necessary, there is no state attached. For all event codes of type EV_KEY, EV_SW, EV_LED and EV_SND, the matching evdev ioctls retrieve the current state. The caller must then compare the last-known state to the retrieved state and handle the deltas accordingly. libevdev simplifies this approach: if the state of the device has changed, libevdev generates an event for each code with the new value and passes it to the caller during libevdev_next_event() if LIBEVDEV_READ_FLAG_SYNC is set.

For events of type EV_ABS and an event code less than ABS_MT_SLOT, the handling of state changes is as described above. For events between ABS_MT_SLOT and ABS_MAX, the event handling differs. Slots are the vehicles to transport information for multiple simultaneous touchpoints on a device. Slots are re-used once a touchpoint has ended. The kernel sends an ABS_MT_SLOT event whenever the current slot changes; any event in the above axis range applies only to the currently active slot. Thus, an event sequence from a slot-capable device may look like this:

EV_ABS ABS_MT_POSITION_Y 10
EV_ABS ABS_MT_SLOT 1
EV_ABS ABS_MT_POSITION_X 100
EV_ABS ABS_MT_POSITION_Y 80
EV_SYN SYN_REPORT 0

Note the lack of ABS_MT_SLOT: the first ABS_MT_POSITION_Y applies to a slot opened previously, and is the only axis that changed for that slot. The touchpoint in slot 1 now has position 100/80. The kernel does not provide events if a value does not change, and does not send ABS_MT_SLOT events if the slot does not change, or none of the values within a slot changes. A client must thus keep the state for each slot.

If a SYN_DROPPED is received, the client must sync all slots individually and update its internal state. libevdev simplifies this by generating multiple events:

- for each slot on the device, libevdev generates an ABS_MT_SLOT event with the value set to the slot number
- for each event code between ABS_MT_SLOT + 1 and ABS_MAX that changed state for this slot, libevdev generates an event for the new state
- libevdev sends a final ABS_MT_SLOT event for the current slot as seen by the kernel
- libevdev terminates this sequence with an EV_SYN SYN_REPORT event

An example event sequence for such a sync may look like this:

EV_ABS ABS_MT_SLOT 0
EV_ABS ABS_MT_POSITION_Y 10
EV_ABS ABS_MT_SLOT 1
EV_ABS ABS_MT_POSITION_X 100
EV_ABS ABS_MT_POSITION_Y 80
EV_ABS ABS_MT_SLOT 2
EV_ABS ABS_MT_POSITION_Y 8
EV_ABS ABS_MT_PRESSURE 12
EV_ABS ABS_MT_SLOT 1
EV_SYN SYN_REPORT 0

Note the terminating ABS_MT_SLOT event, this indicates that the kernel currently has slot 1 active.

# Synchronizing ABS_MT_TRACKING_ID

The event code ABS_MT_TRACKING_ID is used to denote the start and end of a touch point within a slot. An ABS_MT_TRACKING_ID of zero or greater denotes the start of a touchpoint, an ABS_MT_TRACKING_ID of -1 denotes the end of a touchpoint within this slot. During SYN_DROPPED, a touch point may have ended and re-started within a slot - a client must check the ABS_MT_TRACKING_ID. libevdev simplifies this by emulating extra events if the ABS_MT_TRACKING_ID has changed:

- if the ABS_MT_TRACKING_ID was valid and is -1, libevdev enqueues an ABS_MT_TRACKING_ID event with value -1.
- if the ABS_MT_TRACKING_ID was -1 and is now a valid ID, libevdev enqueues an ABS_MT_TRACKING_ID event with the current value.
- if the ABS_MT_TRACKING_ID was a valid ID and is now a different valid ID, libevev enqueues an ABS_MT_TRACKING_ID event with value -1 and another ABS_MT_TRACKING_ID event with the new value.

An example event sequence for such a sync may look like this:

EV_ABS ABS_MT_SLOT 0
EV_ABS ABS_MT_TRACKING_ID -1
EV_ABS ABS_MT_SLOT 2
EV_ABS ABS_MT_TRACKING_ID -1
EV_SYN SYN_REPORT 0
------------------------
EV_ABS ABS_MT_SLOT 1
EV_ABS ABS_MT_POSITION_X 100
EV_ABS ABS_MT_POSITION_Y 80
EV_ABS ABS_MT_SLOT 2
EV_ABS ABS_MT_TRACKING_ID 45
EV_ABS ABS_MT_POSITION_Y 8
EV_ABS ABS_MT_PRESSURE 12
EV_ABS ABS_MT_SLOT 1
EV_SYN SYN_REPORT 0

Note how the touchpoint in slot 0 was terminated, the touchpoint in slot 2 was terminated and then started with a new ABS_MT_TRACKING_ID. The touchpoint in slot 1 maintained the same ABS_MT_TRACKING_ID and only updated the coordinates. Slot 1 is the currently active slot.

In the case of a SYN_DROPPED event, a touch point may be invisible to a client if it started after SYN_DROPPED and finished before the client handles events again. The below example shows an example event sequence and what libevdev sees in the case of a SYN_DROPPED event:

kernel | userspace
|
EV_ABS ABS_MT_SLOT 0 | EV_ABS ABS_MT_SLOT 0
EV_ABS ABS_MT_TRACKING_ID -1 | EV_ABS ABS_MT_TRACKING_ID -1
EV_SYN SYN_REPORT 0 | EV_SYN SYN_REPORT 0
------------------------ | ------------------------
EV_ABS ABS_MT_TRACKING_ID 30 |
EV_ABS ABS_MT_POSITION_X 100 |
EV_ABS ABS_MT_POSITION_Y 80 |
EV_SYN SYN_REPORT 0 | SYN_DROPPED
------------------------ |
EV_ABS ABS_MT_TRACKING_ID -1 |
EV_SYN SYN_REPORT 0 |
------------------------ | ------------------------
EV_ABS ABS_MT_SLOT 1 | EV_ABS ABS_MT_SLOT 1
EV_ABS ABS_MT_POSITION_X 90 | EV_ABS ABS_MT_POSITION_X 90
EV_ABS ABS_MT_POSITION_Y 10 | EV_ABS ABS_MT_POSITION_Y 10
EV_SYN SYN_REPORT 0 | EV_SYN SYN_REPORT 0

If such an event sequence occurs, libevdev will send all updated axes during the sync process. Axis events may thus be generated for devices without a currently valid ABS_MT_TRACKING_ID. Specifically for the above example, the client would receive the following event sequence:

EV_ABS ABS_MT_SLOT 0 ← LIBEVDEV_READ_FLAG_NORMAL
EV_ABS ABS_MT_TRACKING_ID -1
EV_SYN SYN_REPORT 0
------------------------
EV_SYN SYN_DROPPED 0 → LIBEVDEV_READ_STATUS_SYNC
------------------------
EV_ABS ABS_MT_POSITION_X 100 ← LIBEVDEV_READ_FLAG_SYNC
EV_ABS ABS_MT_POSITION_Y 80
EV_SYN SYN_REPORT 0
----------------------------- → -EGAIN
EV_ABS ABS_MT_SLOT 1 ← LIBEVDEV_READ_FLAG_NORMAL
EV_ABS ABS_MT_POSITION_X 90
EV_ABS ABS_MT_POSITION_Y 10
EV_SYN SYN_REPORT 0
-------------------
LIBEVDEV_READ_STATUS_SYNC
@ LIBEVDEV_READ_STATUS_SYNC
Depending on the libevdev_next_event() read flag:
Definition libevdev.h:1094
LIBEVDEV_READ_FLAG_SYNC
@ LIBEVDEV_READ_FLAG_SYNC
Process data in sync mode.
Definition libevdev.h:765
LIBEVDEV_READ_FLAG_NORMAL
@ LIBEVDEV_READ_FLAG_NORMAL
Process data in normal mode.
Definition libevdev.h:766

The axis events do not reflect the position of a current touch point, a client must take care not to generate a new touch point based on those updates.

# Discarding events before synchronizing

The kernel implements the client buffer as a ring buffer. SYN_DROPPED events are handled when the buffer is full and a new event is received from a device. All existing events are discarded, a SYN_DROPPED is added to the buffer followed by the actual device event. Further events will be appended to the buffer until it is either read by the client, or filled again, at which point the sequence repeats.

When the client reads the buffer, the buffer will thus always consist of exactly one SYN_DROPPED event followed by an unspecified number of real events. The data the ioctls return is the current state of the device, i.e. the state after all these events have been processed. For example, assume the buffer contains the following sequence:

EV_SYN SYN_DROPPED
EV_ABS ABS_X 1
EV_SYN SYN_REPORT 0
EV_ABS ABS_X 2
EV_SYN SYN_REPORT 0
EV_ABS ABS_X 3
EV_SYN SYN_REPORT 0
EV_ABS ABS_X 4
EV_SYN SYN_REPORT 0
EV_ABS ABS_X 5
EV_SYN SYN_REPORT 0
EV_ABS ABS_X 6
EV_SYN SYN_REPORT 0

An ioctl at any time in this sequence will return a value of 6 for ABS_X.

libevdev discards all events after a SYN_DROPPED to ensure the events during LIBEVDEV_READ_FLAG_SYNC represent the last known state of the device. This loses some granularity of the events especially as the time between the SYN_DROPPED and the sync process increases. It does however avoid spurious cursor movements. In the above example, the event sequence by libevdev is:

EV_SYN SYN_DROPPED
EV_ABS ABS_X 6
EV_SYN SYN_REPORT 0