tablet: apply pressure offset handling for non-distance tablets
authorPeter Hutterer <peter.hutterer@who-t.net>
Tue, 13 Jun 2023 03:12:53 +0000 (13:12 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Fri, 23 Jun 2023 05:21:51 +0000 (05:21 +0000)
Previously we only applied pressure offset handling for tablets that
supported ABS_DISTANCE. Detecting a pressure offset when the tool
doesn't actually touch the surface is easy after all.

But tablets without distance handling may also have a pressure offset,
so let's try to detect this. This is obviously harder since the pen will
always touch the tablet's surface whenever it is in proximity and thus
will always have *some* pressure applied to it.

The process here is to merely observe the minimum pressure value during
the first two strokes of the pen. On the third prox in, that minimum
pressure value is taken as the offset. If the pressure drops below the
offset, the offset is adjusted downwards [1] so over time we'll
get closer to the pen's real offset.

[1] this is already done for distance tablets too

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

index 9cdb5896d4cf65d72061a2136ab6cb1b38cb8432..42be740726bfc5cce4c625ce7304300b80ac54c9 100644 (file)
@@ -1085,7 +1085,7 @@ tool_set_pressure_thresholds(struct tablet_dispatch *tablet,
                             struct libinput_tablet_tool *tool)
 {
        struct evdev_device *device = tablet->device;
-       const struct input_absinfo *pressure;
+       const struct input_absinfo *pressure, *distance;
        struct quirks_context *quirks = NULL;
        struct quirks *q = NULL;
        struct quirk_range r;
@@ -1101,7 +1101,14 @@ tool_set_pressure_thresholds(struct tablet_dispatch *tablet,
        quirks = evdev_libinput_context(device)->quirks;
        q = quirks_fetch_for_device(quirks, device->udev_device);
 
-       tool->pressure.offset = pressure->minimum;
+       distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE);
+       if (distance) {
+               tool->pressure.offset = pressure->minimum;
+               tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_DONE;
+       } else {
+               tool->pressure.offset = pressure->maximum;
+               tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1;
+       }
 
        /* 5 and 1% of the pressure range */
        hi = axis_range_percentage(pressure, 5);
@@ -1337,18 +1344,24 @@ update_pressure_offset(struct tablet_dispatch *tablet,
                libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
 
        if (!pressure ||
-           !bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE) ||
-           !tool->pressure.has_offset)
+           !bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE))
                return;
 
        /* If we have an event that falls below the current offset, adjust
         * the offset downwards. A fast contact can start with a
         * higher-than-needed pressure offset and then we'd be tied into a
         * high pressure offset for the rest of the session.
+        *
+        * If we are still pending the offset decision, only update the observed
+        * offset value, don't actually set it to have an offset.
         */
        int offset = pressure->value;
-       if (offset < tool->pressure.offset)
-               set_pressure_offset(tool, offset);
+       if (tool->pressure.has_offset) {
+               if (offset < tool->pressure.offset)
+                       set_pressure_offset(tool, offset);
+       } else if (tool->pressure.heuristic_state != PRESSURE_HEURISTIC_STATE_DONE) {
+               tool->pressure.offset = min(offset, tool->pressure.offset);
+       }
 }
 
 static void
@@ -1366,16 +1379,43 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
        pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
        distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE);
 
-       if (!pressure || !distance)
+       if (!pressure)
                return;
 
        offset = pressure->value;
        if (offset <= pressure->minimum)
                return;
 
-       /* If we're closer than 50% of the distance axis, skip pressure
-        * offset detection, too likely to be wrong */
-       if (distance->value < axis_range_percentage(distance, 50))
+       if (distance) {
+               /* If we're closer than 50% of the distance axis, skip pressure
+                * offset detection, too likely to be wrong */
+               if (distance->value < axis_range_percentage(distance, 50))
+                       return;
+       } else {
+                /* A device without distance will always have some pressure on
+                 * contact. Offset detection is delayed for a few proximity ins
+                 * in the hope we'll find the minimum value until then. That
+                 * offset is updated during motion events so by the time the
+                 * deciding prox-in arrives we should know the minimum offset.
+                 */
+                if (offset > pressure->minimum)
+                       tool->pressure.offset = min(offset, tool->pressure.offset);
+
+               switch (tool->pressure.heuristic_state) {
+               case PRESSURE_HEURISTIC_STATE_PROXIN1:
+               case PRESSURE_HEURISTIC_STATE_PROXIN2:
+                       tool->pressure.heuristic_state++;
+                       return;
+               case PRESSURE_HEURISTIC_STATE_DECIDE:
+                       tool->pressure.heuristic_state++;
+                       offset = tool->pressure.offset;
+                       break;
+               case PRESSURE_HEURISTIC_STATE_DONE:
+                       return;
+               }
+       }
+
+       if (offset <= pressure->minimum)
                return;
 
        if (offset > axis_range_percentage(pressure, 20)) {
index 0a75a3c317cfdf1a53eb00e79f32f509ec1a6abd..f3f441cb0140a4fcb63831b70f461b8c794f90f4 100644 (file)
@@ -442,6 +442,13 @@ struct tablet_axes {
        struct phys_ellipsis size;
 };
 
+enum pressure_heuristic_state {
+       PRESSURE_HEURISTIC_STATE_PROXIN1, /** First proximity in event */
+       PRESSURE_HEURISTIC_STATE_PROXIN2, /** Second proximity in event */
+       PRESSURE_HEURISTIC_STATE_DECIDE,  /** Decide on offset now */
+       PRESSURE_HEURISTIC_STATE_DONE,    /** Decision's been made, live with it */
+};
+
 struct libinput_tablet_tool {
        struct list link;
        uint32_t serial;
@@ -456,6 +463,8 @@ struct libinput_tablet_tool {
                struct threshold threshold; /* in device coordinates */
                int offset; /* in device coordinates */
                bool has_offset;
+
+               enum pressure_heuristic_state heuristic_state;
        } pressure;
 };
 
index 354d5d9ebb16f5bd7dc3be4d83d2ba51c58bacb2..edf47224d17a92d48a33a33bff5df58fa2f63566 100644 (file)
@@ -3829,6 +3829,22 @@ START_TEST(tablet_pressure_offset_set)
                { -1, -1 },
        };
 
+       litest_drain_events(li);
+
+       if (!libevdev_has_event_code(dev->evdev, EV_ABS, ABS_DISTANCE)) {
+               /* First two prox ins won't do anything, coming with 10% should give
+                * us ~10% pressure */
+               for (int i = 0; i < 2; i++) {
+                       litest_tablet_proximity_in(dev, 5, 100, axes);
+                       libinput_dispatch(li);
+
+                       assert_pressure(li, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, 0.20);
+                       assert_pressure(li, LIBINPUT_EVENT_TABLET_TOOL_TIP, 0.20);
+                       litest_tablet_proximity_out(dev);
+                       litest_drain_events(li);
+               }
+       }
+
        /* This activates the pressure offset */
        litest_tablet_proximity_in(dev, 5, 100, axes);
        litest_drain_events(li);
@@ -3900,6 +3916,13 @@ START_TEST(tablet_pressure_offset_decrease)
        litest_tablet_proximity_out(dev);
        litest_drain_events(li);
 
+       /* offset 15 on prox in - this one is so we trigger on the next prox
+        * in for the no-distance tablets */
+       litest_axis_set_value(axes, ABS_PRESSURE, 15);
+       litest_tablet_proximity_in(dev, 5, 100, axes);
+       litest_tablet_proximity_out(dev);
+       litest_drain_events(li);
+
        /* a reduced pressure value must reduce the offset */
        litest_axis_set_value(axes, ABS_PRESSURE, 10);
        litest_tablet_proximity_in(dev, 5, 100, axes);
@@ -3959,7 +3982,13 @@ START_TEST(tablet_pressure_offset_increase)
        litest_tablet_proximity_out(dev);
        litest_drain_events(li);
 
-       /* offset 30 on second prox in - must not change the offset */
+       /* offset 25 on second prox in - must not change the offset */
+       litest_axis_set_value(axes, ABS_PRESSURE, 25);
+       litest_tablet_proximity_in(dev, 5, 100, axes);
+       litest_tablet_proximity_out(dev);
+       litest_drain_events(li);
+
+       /* offset 30 on third prox in - must not change the offset */
        litest_axis_set_value(axes, ABS_PRESSURE, 30);
        litest_tablet_proximity_in(dev, 5, 100, axes);
        litest_drain_events(li);
@@ -4092,6 +4121,15 @@ START_TEST(tablet_pressure_offset_exceed_threshold)
        int warning_triggered = 0;
        struct litest_user_data *user_data = libinput_get_user_data(li);
 
+       /* Tablet without distance: offset takes effect on third prox-in */
+       if (!libevdev_has_event_code(dev->evdev, EV_ABS, ABS_DISTANCE)) {
+               for (int i = 0; i < 2; i++) {
+                       litest_tablet_proximity_in(dev, 5, 100, axes);
+                       litest_tablet_proximity_out(dev);
+                       libinput_dispatch(li);
+               }
+       }
+
        litest_drain_events(li);
 
        user_data->private = &warning_triggered;
@@ -6150,6 +6188,7 @@ TEST_COLLECTION(tablet)
        litest_add(tablet_calibration_set_matrix_delta, LITEST_TABLET, LITEST_TOTEM|LITEST_PRECALIBRATED);
 
        litest_add(tablet_pressure_min_max, LITEST_TABLET, LITEST_ANY);
+       /* Tests for pressure offset with distance */
        litest_add_for_device(tablet_pressure_range, LITEST_WACOM_INTUOS);
        litest_add_for_device(tablet_pressure_offset_set, LITEST_WACOM_INTUOS);
        litest_add_for_device(tablet_pressure_offset_decrease, LITEST_WACOM_INTUOS);
@@ -6157,6 +6196,14 @@ TEST_COLLECTION(tablet)
        litest_add_for_device(tablet_pressure_offset_exceed_threshold, LITEST_WACOM_INTUOS);
        litest_add_for_device(tablet_pressure_offset_none_for_zero_distance, LITEST_WACOM_INTUOS);
        litest_add_for_device(tablet_pressure_offset_none_for_small_distance, LITEST_WACOM_INTUOS);
+       /* Tests for pressure offset without distance */
+       litest_add_for_device(tablet_pressure_range, LITEST_WACOM_HID4800_PEN);
+       litest_add_for_device(tablet_pressure_offset_set, LITEST_WACOM_HID4800_PEN);
+       litest_add_for_device(tablet_pressure_offset_decrease, LITEST_WACOM_HID4800_PEN);
+       litest_add_for_device(tablet_pressure_offset_increase, LITEST_WACOM_HID4800_PEN);
+       litest_add_for_device(tablet_pressure_offset_exceed_threshold, LITEST_WACOM_HID4800_PEN);
+
+
        litest_add_for_device(tablet_distance_range, LITEST_WACOM_INTUOS);
 
        litest_add(relative_no_profile, LITEST_TABLET, LITEST_ANY);