From d6c2b3d0de25f7442ec8629350a8d0b3f1bfaf50 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 13 Feb 2020 21:35:51 +1000 Subject: [PATCH] Update the BTN_TOOL bits correctly during SYN_DROPPED handling Where at least one touch ends during SYN_DROPPED, we send out two event frames: one with all applicable touch sequences ending (tracking id -1) and the second one with the whole device state *and* the applicable touch sequences starting (tracking id != -1). This requires us to also update the BTN_TOOL_ bits correctly so that they are correct after the first frame. For that we count the number of previously known touches and send a 0 event for the matching BTN_TOOL_ bit, together with a 1 event for the currently known touches. Signed-off-by: Peter Hutterer --- libevdev/libevdev.c | 54 +++++++++++- test/test-libevdev-events.c | 202 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 3 deletions(-) diff --git a/libevdev/libevdev.c b/libevdev/libevdev.c index 9d93e66..98645bb 100644 --- a/libevdev/libevdev.c +++ b/libevdev/libevdev.c @@ -61,6 +61,8 @@ struct slot_change_state { static int sync_mt_state(struct libevdev *dev, struct slot_change_state *changes_out); +static int +update_key_state(struct libevdev *dev, const struct input_event *e); static inline int* slot_value(const struct libevdev *dev, int slot, int axis) @@ -737,24 +739,70 @@ terminate_slots(struct libevdev *dev, const struct slot_change_state changes[dev->num_slots], int *last_reported_slot) { + const unsigned int map[] = {BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, + BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, + BTN_TOOL_QUINTTAP}; bool touches_stopped = false; + int ntouches_before = 0, ntouches_after = 0; + /* For BTN_TOOL_* emulation, we need to know how many touches we had + * before and how many we have left once we terminate all the ones + * that changed and all the ones that stopped. + */ for (int slot = 0; slot < dev->num_slots; slot++) { - if (changes[slot].state == TOUCH_CHANGED || - changes[slot].state == TOUCH_STOPPED) { + switch(changes[slot].state) { + case TOUCH_OFF: + break; + case TOUCH_CHANGED: + case TOUCH_STOPPED: queue_push_event(dev, EV_ABS, ABS_MT_SLOT, slot); queue_push_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); *last_reported_slot = slot; touches_stopped = true; + ntouches_before++; + break; + case TOUCH_ONGOING: + ntouches_before++; + ntouches_after++; + break; + case TOUCH_STARTED: + break; } } /* If any of the touches stopped, we need to split the sync state into two frames - one with all the stopped touches, one with the new touches starting (if any) */ - if (touches_stopped) + if (touches_stopped) { + /* Send through the required BTN_TOOL_ 0 and 1 events for + * the previous and current number of fingers. And update + * our own key state accordingly, so that during the second + * sync event frame sync_key_state() sets everything correctly + * for the *real* number of touches. + */ + if (ntouches_before <= 5) { + struct input_event ev = { + .type = EV_KEY, + .code = map[ntouches_before - 1], + .value = 0, + }; + queue_push_event(dev, ev.type, ev.code, ev.value); + update_key_state(dev, &ev); + } + + if (ntouches_after <= 5) { + struct input_event ev = { + .type = EV_KEY, + .code = map[ntouches_after - 1], + .value = 1, + }; + queue_push_event(dev, ev.type, ev.code, ev.value); + update_key_state(dev, &ev); + } + queue_push_event(dev, EV_SYN, SYN_REPORT, 0); + } } static int diff --git a/test/test-libevdev-events.c b/test/test-libevdev-events.c index 7634074..fe3938b 100644 --- a/test/test-libevdev-events.c +++ b/test/test-libevdev-events.c @@ -824,6 +824,207 @@ START_TEST(test_syn_delta_tracking_ids) } END_TEST +START_TEST(test_syn_delta_tracking_ids_btntool) +{ + struct uinput_device* uidev; + struct libevdev *dev; + int rc; + struct input_event ev; + const int num_slots = 5; + struct input_absinfo abs[6] = { + { .value = ABS_X, .maximum = 1000 }, + { .value = ABS_Y, .maximum = 1000 }, + { .value = ABS_MT_POSITION_X, .maximum = 1000 }, + { .value = ABS_MT_POSITION_Y, .maximum = 1000 }, + { .value = ABS_MT_SLOT, .maximum = num_slots }, + { .value = ABS_MT_TRACKING_ID, .minimum = -1, .maximum = 0xffff }, + }; + bool have_tripletap = false, + have_doubletap = false, + have_quadtap = false, + have_quinttap = false; + + test_create_abs_device(&uidev, &dev, + ARRAY_LENGTH(abs), abs, + EV_KEY, BTN_TOOL_FINGER, + EV_KEY, BTN_TOOL_DOUBLETAP, + EV_KEY, BTN_TOOL_TRIPLETAP, + EV_KEY, BTN_TOOL_QUADTAP, + EV_KEY, BTN_TOOL_QUINTTAP, + EV_SYN, SYN_REPORT, + -1); + + /* Test the sync process to make sure we get the BTN_TOOL bits for + * touches adjusted correctly when the tracking id changes: + * 1) start a bunch of touch points + * 2) read data into libevdev, make sure state is up-to-date + * 3) change touchpoints + * 3.1) change the tracking ID on some (indicating terminated and + * re-started touchpoint) + * 3.2) change the tracking ID to -1 on some (indicating termianted + * touchpoint) + * 3.3) just update the data on others + * 4) force a sync on the device + * 5) make sure we get the right BTN_TOOL_ changes in the caller + */ + for (int i = 0; i < num_slots; i++) { + uinput_device_event_multiple(uidev, + EV_ABS, ABS_MT_SLOT, i, + EV_ABS, ABS_MT_TRACKING_ID, 111, + EV_ABS, ABS_X, 100 + 10 * i, + EV_ABS, ABS_Y, 100 + 10 * i, + EV_ABS, ABS_MT_POSITION_X, 100, + EV_ABS, ABS_MT_POSITION_Y, 100, + -1, -1); + switch (i) { + case 0: + uinput_device_event(uidev, EV_KEY, BTN_TOOL_FINGER, 1); + break; + case 1: + uinput_device_event(uidev, EV_KEY, BTN_TOOL_FINGER, 0); + uinput_device_event(uidev, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + break; + case 2: + uinput_device_event(uidev, EV_KEY, BTN_TOOL_DOUBLETAP, 0); + uinput_device_event(uidev, EV_KEY, BTN_TOOL_TRIPLETAP, 1); + break; + case 3: + uinput_device_event(uidev, EV_KEY, BTN_TOOL_TRIPLETAP, 0); + uinput_device_event(uidev, EV_KEY, BTN_TOOL_QUADTAP, 1); + break; + case 4: + uinput_device_event(uidev, EV_KEY, BTN_TOOL_QUADTAP, 0); + uinput_device_event(uidev, EV_KEY, BTN_TOOL_QUINTTAP, 1); + break; + case 5: + uinput_device_event(uidev, EV_KEY, BTN_TOOL_QUINTTAP, 0); + break; + default: + ck_abort(); + } + 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); + + /* we have a bunch of touches now, and libevdev knows it. + * - stop touch 0 + * - stop and restart touch 1 and 4 + * - leave 2, 3 unchanged + */ + uinput_device_event_multiple(uidev, + EV_ABS, ABS_MT_SLOT, 0, + EV_ABS, ABS_MT_TRACKING_ID, -1, + EV_KEY, BTN_TOOL_QUINTTAP, 0, + EV_KEY, BTN_TOOL_QUADTAP, 1, + EV_SYN, SYN_REPORT, 0, + -1, -1); + uinput_device_event_multiple(uidev, + EV_ABS, ABS_MT_SLOT, 1, + EV_ABS, ABS_MT_TRACKING_ID, -1, + EV_KEY, BTN_TOOL_QUADTAP, 0, + EV_KEY, BTN_TOOL_TRIPLETAP, 1, + EV_SYN, SYN_REPORT, 0, + -1, -1); + uinput_device_event_multiple(uidev, + EV_ABS, ABS_MT_SLOT, 1, + EV_ABS, ABS_MT_TRACKING_ID, 666, + EV_ABS, ABS_X, 666, + EV_ABS, ABS_Y, 666, + EV_ABS, ABS_MT_POSITION_X, 666, + EV_ABS, ABS_MT_POSITION_Y, 666, + EV_KEY, BTN_TOOL_TRIPLETAP, 0, + EV_KEY, BTN_TOOL_QUADTAP, 1, + EV_SYN, SYN_REPORT, 0, + -1, -1); + uinput_device_event_multiple(uidev, + EV_ABS, ABS_MT_SLOT, 4, + EV_ABS, ABS_MT_TRACKING_ID, -1, + EV_KEY, BTN_TOOL_QUADTAP, 0, + EV_KEY, BTN_TOOL_TRIPLETAP, 1, + EV_SYN, SYN_REPORT, 0, + -1, -1); + uinput_device_event_multiple(uidev, + EV_ABS, ABS_MT_SLOT, 4, + EV_ABS, ABS_MT_TRACKING_ID, 777, + EV_ABS, ABS_X, 777, + EV_ABS, ABS_Y, 777, + EV_ABS, ABS_MT_POSITION_X, 777, + EV_ABS, ABS_MT_POSITION_Y, 777, + EV_KEY, BTN_TOOL_QUADTAP, 1, + EV_KEY, BTN_TOOL_TRIPLETAP, 0, + EV_SYN, SYN_REPORT, 0, + -1, -1); + + /* Force sync */ + rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); + ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SYNC); + + /* In the first sync frame, we expect us to drop to 2 touches - we + * started with 5, 1 stopped, 2 stopped+restarted */ + while ((rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev)) != -EAGAIN) { + if (libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_QUINTTAP)) { + ck_assert(!have_quinttap); + assert_event(&ev, EV_KEY, BTN_TOOL_QUINTTAP, 0); + have_quinttap = true; + } + + if (libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_DOUBLETAP)) { + ck_assert(!have_doubletap); + assert_event(&ev, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + have_doubletap = true; + } + + ck_assert(!libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_TRIPLETAP)); + ck_assert(!libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_QUADTAP)); + ck_assert(!libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_FINGER)); + + if (libevdev_event_is_code(&ev, EV_SYN, SYN_REPORT)) { + ck_assert(have_doubletap); + ck_assert(have_quinttap); + ck_assert(!have_tripletap); + break; + } + } + + have_tripletap = false; + have_doubletap = false; + have_quadtap = false; + + /* In the second sync frame, we expect to go back to 4 touches, + * recovering the two stopped+started touches */ + while ((rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev)) != -EAGAIN) { + if (libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_QUADTAP)) { + ck_assert(!have_quadtap); + assert_event(&ev, EV_KEY, BTN_TOOL_QUADTAP, 1); + have_quadtap = true; + } + + if (libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_DOUBLETAP)) { + ck_assert(!have_doubletap); + assert_event(&ev, EV_KEY, BTN_TOOL_DOUBLETAP, 0); + have_doubletap = true; + } + + ck_assert(!libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_TRIPLETAP)); + ck_assert(!libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_QUINTTAP)); + ck_assert(!libevdev_event_is_code(&ev, EV_KEY, BTN_TOOL_FINGER)); + + if (libevdev_event_is_code(&ev, EV_SYN, SYN_REPORT)) { + ck_assert(have_doubletap); + ck_assert(have_quadtap); + break; + } + } + + uinput_device_free(uidev); + libevdev_free(dev); +} +END_TEST + START_TEST(test_syn_delta_late_sync) { struct uinput_device* uidev; @@ -1875,6 +2076,7 @@ TEST_SUITE_ROOT_PRIVILEGES(libevdev_events) add_test(s, test_syn_delta_fake_mt); add_test(s, test_syn_delta_late_sync); add_test(s, test_syn_delta_tracking_ids); + add_test(s, test_syn_delta_tracking_ids_btntool); add_test(s, test_skipped_sync); add_test(s, test_incomplete_sync); -- 2.7.4