[PATCH libevdev 2/2] Discard incomplete sequences in case of SYN_DROPPED
Peter Hutterer
peter.hutterer at who-t.net
Mon Feb 24 20:26:09 PST 2014
A event sequence emitted by the kernel in case of SYN_DROPPED may be:
ABS_X
SYN_REPORT
ABS_Y
SYN_DROPPED
ABS_Y
SYN_REPORT
Both ABS_Y events are parts of an incomplete sequence and must be
dropped, so the event sequence we return to the caller is.
ABS_X
ABS_Y
SYN_REPORT
SYN_DROPPED
This is a lot easier for callers to handle.
It requires us to keep track of incomplete event sequences, specifically
whether there is an EV_SYN event in the pipe. The kernel usually doesn't send
incomplete sequences but if our internal buffer is almost full, we may not
read off all the events of a sequence.
If there is no EV_SYN terminating the current sequence, we must pretend there
are no events waiting since the events we already do have may get discarded in
case of SYN_DROPPED.
Note that this patch only handles dropping events _before_ SYN_DROPPED, events
after SYN_DROPPED are already handled by sync_state (see the comment at the
top of the function).
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
libevdev/libevdev.c | 73 ++++++++++++++++++++++++++++++++++++++++++---
libevdev/libevdev.h | 6 ++++
test/test-libevdev-events.c | 64 +++++++++++++++++++++++++++++++++++----
3 files changed, 134 insertions(+), 9 deletions(-)
diff --git a/libevdev/libevdev.c b/libevdev/libevdev.c
index ead0205..04913fa 100644
--- a/libevdev/libevdev.c
+++ b/libevdev/libevdev.c
@@ -36,6 +36,12 @@
#define MAXEVENTS 64
+enum read_result {
+ READ_RESULT_NO_EVENTS = 1,
+ READ_RESULT_EVENTS,
+ READ_RESULT_NO_EV_SYN,
+};
+
static int sync_mt_state(struct libevdev *dev, int create_events);
static int
@@ -750,6 +756,52 @@ update_state(struct libevdev *dev, const struct input_event *e)
}
static int
+check_for_syn_dropped(struct libevdev *dev)
+{
+ size_t i;
+ int last_syn_report = -1,
+ syn_dropped = -1;
+
+ if (queue_num_elements(dev) == 0)
+ return READ_RESULT_NO_EVENTS;
+
+ /* SYN_DROPPED may come at any time, any event since the last EV_SYN
+ and up to the next EV_SYN must be dropped. see
+ https://www.kernel.org/doc/Documentation/input/event-codes.txt
+ */
+
+ for (i = 0; i < queue_num_elements(dev); i++) {
+ struct input_event ev;
+
+ queue_peek(dev, i, &ev);
+ if (ev.type == EV_SYN) {
+ if (ev.code == SYN_REPORT)
+ last_syn_report = i;
+ else if (ev.code == SYN_DROPPED) {
+ syn_dropped = i;
+ break;
+ }
+ }
+ }
+
+ if (syn_dropped == -1) {
+ /* We've read an incomplete event frame due to our own buffer
+ constraints, let's pretend there are no events ATM. The
+ next read will give us the SYN_REPORT of this frame */
+ if (last_syn_report == -1)
+ return READ_RESULT_NO_EV_SYN;
+
+ return READ_RESULT_EVENTS;
+ }
+
+ /* slice events out, but leave SYN_DROPPED in place */
+ if (syn_dropped > 0)
+ queue_slice(dev, last_syn_report + 1, syn_dropped - last_syn_report - 1);
+
+ return READ_RESULT_EVENTS;
+}
+
+static int
read_more_events(struct libevdev *dev)
{
int free_elem;
@@ -758,7 +810,7 @@ read_more_events(struct libevdev *dev)
free_elem = queue_num_free_elements(dev);
if (free_elem <= 0)
- return 0;
+ return READ_RESULT_NO_EVENTS;
next = queue_next_element(dev);
len = read(dev->fd, next, free_elem * sizeof(struct input_event));
@@ -769,9 +821,11 @@ read_more_events(struct libevdev *dev)
else if (len > 0) {
int nev = len/sizeof(struct input_event);
queue_set_num_elements(dev, queue_num_elements(dev) + nev);
+
+ return check_for_syn_dropped(dev);
}
- return 0;
+ return READ_RESULT_NO_EVENTS;
}
static int
@@ -856,6 +910,10 @@ libevdev_next_event(struct libevdev *dev, unsigned int flags, struct input_event
goto out;
}
+ if (rc == READ_RESULT_NO_EV_SYN) {
+ rc = -EAGAIN;
+ goto out;
+ }
if (queue_shift(dev, ev) != 0)
return -EAGAIN;
@@ -891,8 +949,15 @@ libevdev_has_event_pending(struct libevdev *dev)
} else if (dev->fd < 0)
return -EBADF;
- if (queue_num_elements(dev) != 0)
- return 1;
+ if (queue_num_elements(dev) != 0) {
+ size_t i;
+ for (i = 0; i < queue_num_elements(dev); i++) {
+ struct input_event ev;
+ queue_peek(dev, i, &ev);
+ if (ev.type == EV_SYN)
+ return 1;
+ }
+ }
rc = poll(&fds, 1, 0);
return (rc >= 0) ? rc : -errno;
diff --git a/libevdev/libevdev.h b/libevdev/libevdev.h
index 898e919..ad409dd 100644
--- a/libevdev/libevdev.h
+++ b/libevdev/libevdev.h
@@ -699,6 +699,12 @@ enum libevdev_read_status {
* dropped after libevdev updates its internal state and event processing
* continues as normal.
*
+ * libevdev transparently drops events that are part of an incomplete event
+ * sequence caused by a SYN_DROPPED event. The caller is guaranteed that the
+ * last event before a SYN_DROPPED event is an EV_SYN, and the next event
+ * after a SYN_DROPPED is the first event of the next valid event sequence
+ * (or another SYN_DROPPED) event.
+ *
* @param dev The evdev device, already initialized with libevdev_set_fd()
* @param flags Set of flags to determine behaviour. If @ref LIBEVDEV_READ_FLAG_NORMAL
* is set, the next event is read in normal mode. If @ref LIBEVDEV_READ_FLAG_SYNC is
diff --git a/test/test-libevdev-events.c b/test/test-libevdev-events.c
index 666b98d..1b68469 100644
--- a/test/test-libevdev-events.c
+++ b/test/test-libevdev-events.c
@@ -66,6 +66,14 @@ START_TEST(test_syn_dropped_event)
struct libevdev *dev;
int rc;
struct input_event ev;
+ struct input_event syn_dropped_sequence[] = {
+ { .type = EV_REL, .code = REL_X, .value = 1 },
+ { .type = EV_REL, .code = REL_Y, .value = 2 },
+ { .type = EV_SYN, .code = SYN_DROPPED, .value = 0 },
+ { .type = EV_REL, .code = REL_X, .value = 3 },
+ { .type = EV_REL, .code = REL_Y, .value = 4 },
+ { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+ };
int pipefd[2];
rc = test_create_device(&uidev, &dev,
@@ -95,11 +103,9 @@ START_TEST(test_syn_dropped_event)
ck_assert_int_eq(rc, 0);
libevdev_change_fd(dev, pipefd[0]);
- ev.type = EV_SYN;
- ev.code = SYN_DROPPED;
- ev.value = 0;
- rc = write(pipefd[1], &ev, sizeof(ev));
- ck_assert_int_eq(rc, sizeof(ev));
+ rc = write(pipefd[1], syn_dropped_sequence, sizeof(syn_dropped_sequence));
+ ck_assert_int_eq(rc, sizeof(syn_dropped_sequence));
+
rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
libevdev_change_fd(dev, uinput_device_get_fd(uidev));
@@ -112,6 +118,11 @@ START_TEST(test_syn_dropped_event)
ck_assert_int_eq(ev.type, EV_SYN);
ck_assert_int_eq(ev.code, SYN_DROPPED);
+ /* no delta, so expect EAGAIN since the REL_X/REL_Y we wrote after
+ * SYN_DROPPED are supposed to be dropped */
+ rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev);
+ ck_assert_int_eq(rc, -EAGAIN);
+
/* only check for the rc, nothing actually changed on the device */
libevdev_free(dev);
@@ -345,6 +356,48 @@ START_TEST(test_has_event_pending)
}
END_TEST
+
+START_TEST(test_next_event_incomplete)
+{
+ struct uinput_device* uidev;
+ struct libevdev *dev;
+ int rc;
+ struct input_event ev;
+ int pipefd[2];
+ struct input_event incomplete_sequence[] = {
+ { .type = EV_REL, .code = REL_X, .value = 1 },
+ { .type = EV_REL, .code = REL_Y, .value = 2 },
+ };
+
+ rc = test_create_device(&uidev, &dev,
+ EV_REL, REL_X,
+ EV_REL, REL_Y,
+ EV_KEY, BTN_LEFT,
+ -1);
+ ck_assert_msg(rc == 0, "Failed to create device: %s", strerror(-rc));
+
+ /* we can't write incomplete events through uinput, so let's use a
+ * pipe instead */
+ rc = pipe2(pipefd, O_NONBLOCK);
+ ck_assert_int_eq(rc, 0);
+
+ libevdev_change_fd(dev, pipefd[0]);
+
+ rc = write(pipefd[1], incomplete_sequence, sizeof(incomplete_sequence));
+ ck_assert_int_eq(rc, sizeof(incomplete_sequence));
+
+ rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
+ ck_assert_int_eq(rc, -EAGAIN);
+
+ ck_assert_int_eq(libevdev_has_event_pending(dev), 0);
+
+ libevdev_free(dev);
+ uinput_device_free(uidev);
+ close(pipefd[0]);
+ close(pipefd[1]);
+}
+END_TEST
+
START_TEST(test_syn_delta_button)
{
struct uinput_device* uidev;
@@ -1317,6 +1370,7 @@ libevdev_events(void)
tcase_add_test(tc, test_event_type_filtered);
tcase_add_test(tc, test_event_code_filtered);
tcase_add_test(tc, test_has_event_pending);
+ tcase_add_test(tc, test_next_event_incomplete);
suite_add_tcase(s, tc);
tc = tcase_create("SYN_DROPPED deltas");
--
1.8.4.2
More information about the Input-tools
mailing list