tablet: add tilt-based touch arbitration for screen tablets
authorPeter Hutterer <peter.hutterer@who-t.net>
Fri, 28 Sep 2018 01:47:32 +0000 (11:47 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Thu, 31 Jan 2019 05:17:28 +0000 (05:17 +0000)
If the tilt angle on tip down is not 0 set the touch arbitration to a
rectangle around the assumed position of the hand. This assumed position is
right of the tip for a rightwards tilt and left of the tip for a leftwards
tilt (i.e. left-handed mode). The rectangle is 200x200mm with a 20x50mm
NW of the tip or NE for left-handed. In other words, if the period below is
the tip, the rectangle looks like this:

    +-----------+                          +-----------+
    | .  | <- for rightwards tilt   |         . |
    |           |                          |           |
    |           |                          |           |
    |           |    for leftwards tilt -> |           |
    +-----------+                          +-----------+

Touches within that rectangle are canceled, new touches are ignored. As the
tip moves around the rectangle is updated but touches are only cancelled on
the original tip down. While the tip is down, new touches are ignored in the
exclusion area but pre-existing touches are not cancelled.

This is currently only implemented in the fallback interface, i.e. it will
only work for Cintiqs.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
src/evdev-fallback.c
src/evdev-tablet.c
src/evdev-tablet.h
test/litest.c
test/litest.h
test/test-tablet.c

index 50c4afd1f4c95921f015605c298fe02b459ef626..9d15a91fbeab3fb6a396d067d13032d71b43e884 100644 (file)
@@ -1218,6 +1218,24 @@ fallback_interface_sync_initial_state(struct evdev_device *device,
        }
 }
 
+static void
+fallback_interface_update_rect(struct evdev_dispatch *evdev_dispatch,
+                              struct evdev_device *device,
+                               const struct phys_rect *phys_rect,
+                               uint64_t time)
+{
+       struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+       struct device_coord_rect rect = {0};
+
+       assert(phys_rect);
+
+       /* Existing touches do not change, we just update the rect and only
+        * new touches in these areas will be ignored. If you want to paint
+        * over your finger, be my guest. */
+       rect = evdev_phys_rect_to_units(device, phys_rect);
+       dispatch->arbitration.rect = rect;
+}
+
 static void
 fallback_interface_toggle_touch(struct evdev_dispatch *evdev_dispatch,
                                struct evdev_device *device,
@@ -1453,7 +1471,7 @@ struct evdev_dispatch_interface fallback_interface = {
        .device_resumed = fallback_interface_device_added,   /* treat as add */
        .post_added = fallback_interface_sync_initial_state,
        .touch_arbitration_toggle = fallback_interface_toggle_touch,
-       .touch_arbitration_update_rect = NULL,
+       .touch_arbitration_update_rect = fallback_interface_update_rect,
        .get_switch_state = fallback_interface_get_switch_state,
 };
 
index e94fad53a3379c002f7bba00f62ffdf6ae008dbf..d6f12e71247c8e3c07894d1a0446f4dbd9518d84 100644 (file)
@@ -1376,6 +1376,69 @@ tablet_update_proximity_state(struct tablet_dispatch *tablet,
        tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
 }
 
+static struct phys_rect
+tablet_calculate_arbitration_rect(struct tablet_dispatch *tablet)
+{
+       struct evdev_device *device = tablet->device;
+       struct phys_rect r = {0};
+       struct phys_coords mm;
+
+       mm = evdev_device_units_to_mm(device, &tablet->axes.point);
+
+       /* The rect we disable is 20mm left of the tip, 50mm north of the
+        * tip, and 200x200mm large.
+        * If the stylus is tilted left (tip further right than the eraser
+        * end) assume left-handed mode.
+        *
+        * Obviously if we'd run out of the boundaries, we rescale the rect
+        * accordingly.
+        */
+       if (tablet->axes.tilt.x > 0) {
+               r.x = mm.x - 20;
+               r.w = 200;
+       } else {
+               r.x = mm.x + 20;
+               r.w = 200;
+               r.x -= r.w;
+       }
+
+       if (r.x < 0) {
+               r.w -= r.x;
+               r.x = 0;
+       }
+
+       r.y = mm.y - 50;
+       r.h = 200;
+       if (r.y < 0) {
+               r.h -= r.y;
+               r.y = 0;
+       }
+
+       return r;
+}
+
+static inline void
+tablet_update_touch_device_rect(struct tablet_dispatch *tablet,
+                               const struct tablet_axes *axes,
+                               uint64_t time)
+{
+       struct evdev_dispatch *dispatch;
+       struct phys_rect rect = {0};
+
+       if (tablet->touch_device == NULL ||
+           tablet->arbitration != ARBITRATION_IGNORE_RECT)
+               return;
+
+       rect = tablet_calculate_arbitration_rect(tablet);
+
+       dispatch = tablet->touch_device->dispatch;
+       if (dispatch->interface->touch_arbitration_update_rect)
+               dispatch->interface->touch_arbitration_update_rect(dispatch,
+                                                                  tablet->touch_device,
+                                                                  &rect,
+                                                                  time);
+}
+
 static inline bool
 tablet_send_proximity_in(struct tablet_dispatch *tablet,
                         struct libinput_tablet_tool *tool,
@@ -1549,7 +1612,8 @@ tablet_send_events(struct tablet_dispatch *tablet,
                 * update */
                tablet_unset_status(tablet, TABLET_AXES_UPDATED);
        } else {
-               tablet_check_notify_axes(tablet, device, tool, &axes, time);
+               if (tablet_check_notify_axes(tablet, device, tool, &axes, time))
+                       tablet_update_touch_device_rect(tablet, &axes, time);
        }
 
        assert(tablet->axes.delta.x == 0);
@@ -1618,27 +1682,25 @@ tablet_flush(struct tablet_dispatch *tablet,
 }
 
 static inline void
-tablet_set_touch_device_enabled(struct evdev_device *touch_device,
-                               bool enable,
+tablet_set_touch_device_enabled(struct tablet_dispatch *tablet,
+                               enum evdev_arbitration_state which,
+                               const struct phys_rect *rect,
                                uint64_t time)
 {
+       struct evdev_device *touch_device = tablet->touch_device;
        struct evdev_dispatch *dispatch;
-       enum evdev_arbitration_state which;
 
        if (touch_device == NULL)
                return;
 
-       if (enable)
-               which = ARBITRATION_NOT_ACTIVE;
-       else
-               which = ARBITRATION_IGNORE_ALL;
+       tablet->arbitration = which;
 
        dispatch = touch_device->dispatch;
        if (dispatch->interface->touch_arbitration_toggle)
                dispatch->interface->touch_arbitration_toggle(dispatch,
                                                              touch_device,
                                                              which,
-                                                             NULL,
+                                                             rect,
                                                              time);
 }
 
@@ -1647,18 +1709,33 @@ tablet_toggle_touch_device(struct tablet_dispatch *tablet,
                           struct evdev_device *tablet_device,
                           uint64_t time)
 {
-       bool enable_events;
+       enum evdev_arbitration_state which;
+       struct phys_rect r = {0};
+       struct phys_rect *rect = NULL;
 
-       enable_events = tablet_has_status(tablet,
-                                         TABLET_TOOL_OUT_OF_RANGE) ||
-                       tablet_has_status(tablet, TABLET_NONE) ||
-                       tablet_has_status(tablet,
-                                         TABLET_TOOL_LEAVING_PROXIMITY) ||
-                       tablet_has_status(tablet,
-                                         TABLET_TOOL_OUT_OF_PROXIMITY);
+       if (tablet_has_status(tablet,
+                             TABLET_TOOL_OUT_OF_RANGE) ||
+           tablet_has_status(tablet, TABLET_NONE) ||
+           tablet_has_status(tablet,
+                             TABLET_TOOL_LEAVING_PROXIMITY) ||
+           tablet_has_status(tablet,
+                             TABLET_TOOL_OUT_OF_PROXIMITY)) {
+               which = ARBITRATION_NOT_ACTIVE;
+       } else if (tablet->axes.tilt.x == 0) {
+               which = ARBITRATION_IGNORE_ALL;
+       } else if (tablet->arbitration != ARBITRATION_IGNORE_RECT) {
+               /* This enables rect-based arbitration, updates are sent
+                * elsewhere */
+               r = tablet_calculate_arbitration_rect(tablet);
+               rect = &r;
+               which = ARBITRATION_IGNORE_RECT;
+       } else {
+               return;
+       }
 
-       tablet_set_touch_device_enabled(tablet->touch_device,
-                                       enable_events,
+       tablet_set_touch_device_enabled(tablet,
+                                       which,
+                                       rect,
                                        time);
 }
 
@@ -1837,7 +1914,10 @@ tablet_suspend(struct evdev_dispatch *dispatch,
        struct libinput *li = tablet_libinput_context(tablet);
        uint64_t now = libinput_now(li);
 
-       tablet_set_touch_device_enabled(tablet->touch_device, true, now);
+       tablet_set_touch_device_enabled(tablet,
+                                       ARBITRATION_NOT_ACTIVE,
+                                       NULL,
+                                       now);
 
        if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) {
                tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
index 76f53959b23c9bb6cd1b27e04c2df94bfb15c53a..897619522dc70a6e90030f3233adeae210010ccf 100644 (file)
@@ -84,6 +84,7 @@ struct tablet_dispatch {
 
        /* The paired touch device on devices with both pen & touch */
        struct evdev_device *touch_device;
+       enum evdev_arbitration_state arbitration;
 
        struct {
                bool need_to_force_prox_out;
index b000a216c5ba2dbc5a2152b55a7d0008f1eb4c46..8fdf03eb5c27bdd2e67790145526f73fe5a98646 100644 (file)
@@ -1884,6 +1884,23 @@ litest_slot_start(struct litest_device *d,
        d->semi_mt.touches[slot].y = y;
 }
 
+void
+litest_touch_sequence(struct litest_device *d,
+                     unsigned int slot,
+                     double x_from,
+                     double y_from,
+                     double x_to,
+                     double y_to,
+                     int steps)
+{
+       litest_touch_down(d, slot, x_from, y_from);
+       litest_touch_move_to(d, slot,
+                            x_from, y_from,
+                            x_to, y_to,
+                            steps);
+       litest_touch_up(d, slot);
+}
+
 void
 litest_touch_down(struct litest_device *d,
                  unsigned int slot,
index 420fdc69b35a02cf8da5461b32bcdc2989357df1..07af2f879946a962598642a39a6e03b6727e0059 100644 (file)
@@ -530,6 +530,15 @@ litest_touch_move_extended(struct litest_device *d,
                           double y,
                           struct axis_replacement *axes);
 
+void
+litest_touch_sequence(struct litest_device *d,
+                     unsigned int slot,
+                     double x1,
+                     double y1,
+                     double x2,
+                     double y2,
+                     int steps);
+
 void
 litest_touch_down(struct litest_device *d,
                  unsigned int slot,
index 2d5b2d8b025d9b892292822a85a01648ef4129e8..d71fa0684aa2f120ecd7ae00c88217cf88c76069 100644 (file)
@@ -4247,6 +4247,8 @@ START_TEST(touch_arbitration)
        struct litest_device *finger;
        struct libinput *li = dev->libinput;
        struct axis_replacement axes[] = {
+               { ABS_TILT_X, 80 },
+               { ABS_TILT_Y, 80 },
                { ABS_DISTANCE, 10 },
                { ABS_PRESSURE, 0 },
                { -1, -1 }
@@ -4267,8 +4269,8 @@ START_TEST(touch_arbitration)
        litest_tablet_motion(dev, 20, 40, axes);
        litest_drain_events(li);
 
-       litest_touch_down(finger, 0, 30, 30);
-       litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10);
+       litest_touch_down(finger, 0, 21, 41);
+       litest_touch_move_to(finger, 0, 21, 41, 80, 80, 10);
        litest_assert_empty_queue(li);
 
        litest_tablet_motion(dev, 10, 10, axes);
@@ -4306,6 +4308,80 @@ START_TEST(touch_arbitration)
 }
 END_TEST
 
+START_TEST(touch_arbitration_outside_rect)
+{
+       struct litest_device *dev = litest_current_device();
+       enum litest_device_type other;
+       struct litest_device *finger;
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_TILT_X, 80 },
+               { ABS_TILT_Y, 80 },
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { -1, -1 }
+       };
+       double x, y;
+       bool is_touchpad;
+
+       other = paired_device(dev);
+       if (other == LITEST_NO_DEVICE)
+               return;
+
+       finger = litest_add_device(li, other);
+       litest_drain_events(li);
+
+       is_touchpad = !libevdev_has_property(finger->evdev, INPUT_PROP_DIRECT);
+       if (is_touchpad)
+               return;
+
+       x = 20;
+       y = 45;
+
+       litest_tablet_proximity_in(dev, x, y - 1, axes);
+       litest_drain_events(li);
+
+       /* these are in percent, but the pen/finger have different
+        * resolution and the rect works in mm, so the numbers below are
+        * hand-picked for the test device */
+       litest_tablet_motion(dev, x, y, axes);
+       litest_drain_events(li);
+
+       /* left of rect */
+       litest_touch_sequence(finger, 0, x - 10, y + 2, x - 10, y + 20, 30);
+       libinput_dispatch(li);
+       litest_assert_touch_sequence(li);
+
+       /* above rect */
+       litest_touch_sequence(finger, 0, x + 2, y - 35, x + 20, y - 10, 30);
+       libinput_dispatch(li);
+       litest_assert_touch_sequence(li);
+
+       /* right of rect */
+       litest_touch_sequence(finger, 0, x + 80, y + 2, x + 20, y + 10, 30);
+       libinput_dispatch(li);
+       litest_assert_touch_sequence(li);
+
+#if 0
+       /* This *should* work but the Cintiq test devices is <200mm
+          high, so we can't test for anything below the tip */
+       x = 20;
+       y = 10;
+       litest_tablet_proximity_out(dev);
+       litest_tablet_motion(dev, x, y, axes);
+       litest_tablet_proximity_in(dev, x, y - 1, axes);
+       litest_drain_events(li);
+
+       /* below rect */
+       litest_touch_sequence(finger, 0, x + 2, y + 80, x + 20, y + 20, 30);
+       libinput_dispatch(li);
+       litest_assert_touch_sequence(li);
+#endif
+
+       litest_delete_device(finger);
+}
+END_TEST
+
 START_TEST(touch_arbitration_stop_touch)
 {
        struct litest_device *dev = litest_current_device();
@@ -4913,6 +4989,7 @@ TEST_COLLECTION(tablet)
        litest_add("tablet:touch-arbitration", touch_arbitration_remove_tablet, LITEST_TOUCH, LITEST_ANY);
        litest_add("tablet:touch-arbitration", touch_arbitration_keep_ignoring, LITEST_TABLET, LITEST_ANY);
        litest_add("tablet:touch-arbitration", touch_arbitration_late_touch_lift, LITEST_TABLET, LITEST_ANY);
+       litest_add("tablet:touch-arbitration", touch_arbitration_outside_rect, LITEST_TABLET | LITEST_DIRECT, LITEST_ANY);
 
        litest_add_for_device("tablet:quirks", huion_static_btn_tool_pen, LITEST_HUION_TABLET);
        litest_add_for_device("tablet:quirks", huion_static_btn_tool_pen_no_timeout_during_usage, LITEST_HUION_TABLET);