summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libevdev/libevdev.c58
-rw-r--r--libevdev/libevdev.h45
-rw-r--r--test/test-libevdev-events.c219
3 files changed, 302 insertions, 20 deletions
diff --git a/libevdev/libevdev.c b/libevdev/libevdev.c
index 7e8d3db..a316831 100644
--- a/libevdev/libevdev.c
+++ b/libevdev/libevdev.c
@@ -726,32 +726,50 @@ read_more_events(struct libevdev *dev)
return 0;
}
-static int
-sync_state(struct libevdev *dev)
+static inline void
+drain_events(struct libevdev *dev)
{
- int i;
- int rc = 0;
- struct input_event *ev;
+ int rc;
+ size_t nelem;
+ int iterations = 0;
+ const int max_iterations = 8; /* EVDEV_BUF_PACKETS in
+ kernel/drivers/input/evedev.c */
+
+ queue_shift_multiple(dev, queue_num_elements(dev), NULL);
- /* FIXME: if we have events in the queue after the SYN_DROPPED (which was
- queue[0]) we need to shift this backwards. Except that chances are that the
- queue may be either full or too full to prepend all the events needed for
- SYNC_IN_PROGRESS.
+ do {
+ rc = read_more_events(dev);
+ if (rc == -EAGAIN)
+ return;
- so we search for the last sync event in the queue and drop everything before
- including that event and rely on the kernel to tell us the right value for that
- bitfield during the sync process.
+ if (rc < 0) {
+ log_error("Failed to drain events before sync.\n");
+ return;
+ }
+
+ nelem = queue_num_elements(dev);
+ queue_shift_multiple(dev, nelem, NULL);
+ } while (iterations++ < max_iterations && nelem >= queue_size(dev));
+
+ /* Our buffer should be roughly the same or bigger than the kernel
+ buffer in most cases, so we usually don't expect to recurse. If
+ we do, make sure we stop after max_iterations and proceed with
+ what we have. This could happen if events queue up faster than
+ we can drain them.
*/
+ if (iterations >= max_iterations)
+ log_info("Unable to drain events, buffer size mismatch.\n");
+}
- for (i = queue_num_elements(dev) - 1; i >= 0; i--) {
- struct input_event e = {{0,0}, 0, 0, 0};
- queue_peek(dev, i, &e);
- if (e.type == EV_SYN)
- break;
- }
+static int
+sync_state(struct libevdev *dev)
+{
+ int rc = 0;
+ struct input_event *ev;
- if (i > 0)
- queue_shift_multiple(dev, i + 1, NULL);
+ /* see section "Discarding events before synchronizing" in
+ * libevdev/libevdev.h */
+ drain_events(dev);
if (libevdev_has_event_type(dev, EV_KEY))
rc = sync_key_state(dev);
diff --git a/libevdev/libevdev.h b/libevdev/libevdev.h
index 9ec1cb8..ef142ea 100644
--- a/libevdev/libevdev.h
+++ b/libevdev/libevdev.h
@@ -368,6 +368,51 @@ extern "C" {
* 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:
+ *
+ * @code
+ 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
+ * @endcode
+ * 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 @ref 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:
+ * @code
+ EV_SYN SYN_DROPPED
+ EV_ABS ABS_X 6
+ EV_SYN SYN_REPORT 0
+ @endcode
*/
/**
diff --git a/test/test-libevdev-events.c b/test/test-libevdev-events.c
index dc412a7..ddc509d 100644
--- a/test/test-libevdev-events.c
+++ b/test/test-libevdev-events.c
@@ -927,6 +927,224 @@ START_TEST(test_syn_delta_tracking_ids)
}
END_TEST
+START_TEST(test_syn_delta_late_sync)
+{
+ struct uinput_device* uidev;
+ struct libevdev *dev;
+ int rc;
+ struct input_event ev;
+ struct input_absinfo abs[6];
+ int i, slot;
+
+ memset(abs, 0, sizeof(abs));
+ abs[0].value = ABS_X;
+ abs[0].maximum = 1000;
+ abs[1].value = ABS_MT_POSITION_X;
+ abs[1].maximum = 1000;
+
+ abs[2].value = ABS_Y;
+ abs[2].maximum = 1000;
+ abs[3].value = ABS_MT_POSITION_Y;
+ abs[3].maximum = 1000;
+
+ abs[4].value = ABS_MT_SLOT;
+ abs[4].maximum = 1;
+
+ abs[5].minimum = -1;
+ abs[5].maximum = 255;
+ abs[5].value = ABS_MT_TRACKING_ID;
+
+ test_create_abs_device(&uidev, &dev,
+ 6, abs,
+ EV_SYN, SYN_REPORT,
+ -1);
+
+ /* emulate a touch down, make sure libevdev sees it */
+ uinput_device_event(uidev, EV_ABS, ABS_MT_SLOT, 0);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ uinput_device_event(uidev, EV_ABS, ABS_X, 100);
+ uinput_device_event(uidev, EV_ABS, ABS_Y, 500);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_X, 100);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_Y, 500);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+ do {
+ rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
+ ck_assert_int_ne(rc, LIBEVDEV_READ_STATUS_SYNC);
+ } while (rc >= 0);
+
+ /* force enough events to trigger a SYN_DROPPED */
+ for (i = 0; i < 100; i++) {
+ uinput_device_event(uidev, EV_ABS, ABS_X, 100 + i);
+ uinput_device_event(uidev, EV_ABS, ABS_Y, 500 + i);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_X, 100 + i);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_Y, 500 + i);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+ }
+
+ rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
+ ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SYNC);
+
+ /* trigger the tracking ID change after getting the SYN_DROPPED */
+ uinput_device_event(uidev, EV_ABS, ABS_MT_SLOT, 0);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ uinput_device_event(uidev, EV_ABS, ABS_X, 200);
+ uinput_device_event(uidev, EV_ABS, ABS_Y, 600);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_X, 200);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_Y, 600);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+
+ slot = 0;
+
+ /* Now sync the device, expect the data to be equal to the last event*/
+ while ((rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev)) != -EAGAIN) {
+ if (ev.type == EV_SYN)
+ continue;
+
+ ck_assert_int_eq(ev.type, EV_ABS);
+ switch(ev.code) {
+ case ABS_MT_SLOT:
+ slot = ev.value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if (slot == 0)
+ ck_assert_int_eq(ev.value, -1);
+ break;
+ case ABS_X:
+ case ABS_MT_POSITION_X:
+ ck_assert_int_eq(ev.value, 200);
+ break;
+ case ABS_Y:
+ case ABS_MT_POSITION_Y:
+ ck_assert_int_eq(ev.value, 600);
+ break;
+ }
+ }
+
+ /* And a new tracking ID */
+ uinput_device_event(uidev, EV_ABS, ABS_MT_SLOT, 0);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_TRACKING_ID, 2);
+ uinput_device_event(uidev, EV_ABS, ABS_X, 201);
+ uinput_device_event(uidev, EV_ABS, ABS_Y, 601);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_X, 201);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_Y, 601);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+
+ while ((rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev)) != -EAGAIN) {
+ ck_assert_int_ne(rc, LIBEVDEV_READ_STATUS_SYNC);
+
+ if (ev.type == EV_SYN)
+ continue;
+
+ ck_assert_int_eq(ev.type, EV_ABS);
+
+ switch(ev.code) {
+ case ABS_MT_SLOT:
+ ck_assert_int_eq(ev.value, 0);
+ break;
+ case ABS_MT_TRACKING_ID:
+ ck_assert_int_eq(ev.value, 2);
+ break;
+ case ABS_X:
+ case ABS_MT_POSITION_X:
+ ck_assert_int_eq(ev.value, 201);
+ break;
+ case ABS_Y:
+ case ABS_MT_POSITION_Y:
+ ck_assert_int_eq(ev.value, 601);
+ break;
+ }
+ }
+
+
+ /* Now we basically re-do the exact same test, just with the
+ tracking ID order inverted */
+
+ /* drop the tracking ID, make sure libevdev sees it */
+ uinput_device_event(uidev, EV_ABS, ABS_MT_SLOT, 0);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+ do {
+ rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
+ ck_assert_int_ne(rc, LIBEVDEV_READ_STATUS_SYNC);
+ } while (rc >= 0);
+
+ /* force enough events to trigger a SYN_DROPPED */
+ for (i = 0; i < 100; i++) {
+ uinput_device_event(uidev, EV_ABS, ABS_X, 100 + i);
+ uinput_device_event(uidev, EV_ABS, ABS_Y, 500 + i);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_X, 100 + i);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_Y, 500 + i);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+ }
+
+ rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
+ ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SYNC);
+
+ /* trigger the new tracking ID after getting the SYN_DROPPED */
+ uinput_device_event(uidev, EV_ABS, ABS_MT_SLOT, 0);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_TRACKING_ID, 5);
+ uinput_device_event(uidev, EV_ABS, ABS_X, 200);
+ uinput_device_event(uidev, EV_ABS, ABS_Y, 600);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_X, 200);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_POSITION_Y, 600);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+
+ slot = 0;
+
+ /* Now sync the device, expect the data to be equal to the last event*/
+ while ((rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev)) != -EAGAIN) {
+ if (ev.type == EV_SYN)
+ continue;
+
+ ck_assert_int_eq(ev.type, EV_ABS);
+ switch(ev.code) {
+ case ABS_MT_SLOT:
+ slot = ev.value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if (slot == 0)
+ ck_assert_int_eq(ev.value, 5);
+ break;
+ case ABS_X:
+ case ABS_MT_POSITION_X:
+ ck_assert_int_eq(ev.value, 200);
+ break;
+ case ABS_Y:
+ case ABS_MT_POSITION_Y:
+ ck_assert_int_eq(ev.value, 600);
+ break;
+ }
+ }
+
+ /* Drop the tracking ID */
+ uinput_device_event(uidev, EV_ABS, ABS_MT_SLOT, 0);
+ uinput_device_event(uidev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ uinput_device_event(uidev, EV_SYN, SYN_REPORT, 0);
+
+ while ((rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev)) != -EAGAIN) {
+ ck_assert_int_ne(rc, LIBEVDEV_READ_STATUS_SYNC);
+
+ if (ev.type == EV_SYN)
+ continue;
+
+ ck_assert_int_eq(ev.type, EV_ABS);
+
+ switch(ev.code) {
+ case ABS_MT_SLOT:
+ ck_assert_int_eq(ev.value, 0);
+ break;
+ case ABS_MT_TRACKING_ID:
+ ck_assert_int_eq(ev.value, -1);
+ break;
+ }
+ }
+
+
+ uinput_device_free(uidev);
+ libevdev_free(dev);
+}
+END_TEST
+
START_TEST(test_syn_delta_fake_mt)
{
struct uinput_device* uidev;
@@ -1863,6 +2081,7 @@ libevdev_events(void)
tcase_add_test(tc, test_syn_delta_sw);
tcase_add_test(tc, test_syn_delta_fake_mt);
tcase_add_test(tc, test_syn_delta_tracking_ids);
+ tcase_add_test(tc, test_syn_delta_late_sync);
suite_add_tcase(s, tc);
tc = tcase_create("skipped syncs");