Update the BTN_TOOL bits correctly during SYN_DROPPED handling
authorPeter Hutterer <peter.hutterer@who-t.net>
Thu, 13 Feb 2020 11:35:51 +0000 (21:35 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Wed, 19 Feb 2020 01:09:31 +0000 (11:09 +1000)
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 <peter.hutterer@who-t.net>
libevdev/libevdev.c
test/test-libevdev-events.c

index 9d93e665d9a5207525505c3bfd14b4331b224e43..98645bb17c125434c7cd8d05be75d3bebca5f466 100644 (file)
@@ -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
index 7634074a34d1465878009ad030fc2b3b4d3e199c..fe3938bf21df380cae1abc0bbd6ae4114b7e4702 100644 (file)
@@ -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);