[PATCH libinput 3/5] tablet: add pressure threshold handling
Jason Gerecke
killertofu at gmail.com
Tue Jan 5 11:39:15 PST 2016
'On Mon, Jan 4, 2016 at 5:21 PM, Peter Hutterer
<peter.hutterer at who-t.net> wrote:
> On tablets with ABS_PRESSURE use a pressure value to determine tip state, not
> BTN_TOUCH. This enables us (down the road) to have device-specific pressure
> thresholds. For now we use a 5% default for all devices.
>
> The threshold is a range, if we go past the upper range we initiate the tip
> down, if we go below the lower range we release the tip again.
>
> This affects two current tests:
> * Once we have pressure offsets and pressure thresholds, we're biased towards
> pressure. So we can only check that distance is zero when there is a pressure
> value, not the other way round.
> * When the pressure threshold is exceeded on proximity in with a nonzero
> distance, we can only warn and handle the pressure as normal. Since this is a
> niche case anyway anything fancier is likely unnecessary.
>
> Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
> ---
> doc/tablet-support.dox | 16 +++++++++
> src/evdev-tablet.c | 94 ++++++++++++++++++++++++++++++++++++++++----------
> src/libinput-private.h | 8 +++++
> test/tablet.c | 67 ++++++++++++-----------------------
> 4 files changed, 122 insertions(+), 63 deletions(-)
>
> diff --git a/doc/tablet-support.dox b/doc/tablet-support.dox
> index fbe778d..a3d4d7b 100644
> --- a/doc/tablet-support.dox
> +++ b/doc/tablet-support.dox
> @@ -33,6 +33,22 @@ Tablet tools may send button events; these are exclusively for extra buttons
> unrelated to the tip. A button event is independent of the tip and occur
> at any time.
>
> +Some tablet tools' pressure detection is too sensitive, causing phantom
> +touches when the user only slightly brushes the surfaces. For example, some
> +tools are capable of detecting 1 gram of pressure.
> +
> +libinput uses a device-specific pressure threshold to determine when the tip
> +is considered logically down. As a result, libinput may send a nonzero
> +pressure value while the tip is logically up. Most application can and
> +should ignore pressure information until they receive the event of type @ref
> +LIBINPUT_EVENT_TABLET_TOOL_TIP. Applications that require extremely
> +fine-grained pressure sensitivity should use the pressure data instead of
> +the tip events.
> +
> +Note that the pressure threshold to trigger a logical tip event may be zero
> +on some devices. On tools without pressure sensitivity, determining when a
> +tip is down is device-specific.
> +
> @section tablet-axes Special axes on tablet tools
>
> A tablet tool usually provides additional information beyond x/y positional
> diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
> index 7f6c860..2f57805 100644
> --- a/src/evdev-tablet.c
> +++ b/src/evdev-tablet.c
> @@ -617,10 +617,15 @@ tablet_process_key(struct tablet_dispatch *tablet,
> e->value);
> break;
> case BTN_TOUCH:
> - if (e->value)
> - tablet_set_status(tablet, TABLET_TOOL_ENTERING_CONTACT);
> - else
> - tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT);
> + if (!bit_is_set(tablet->axis_caps,
> + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) {
> + if (e->value)
> + tablet_set_status(tablet,
> + TABLET_TOOL_ENTERING_CONTACT);
> + else
> + tablet_set_status(tablet,
> + TABLET_TOOL_LEAVING_CONTACT);
> + }
> break;
> case BTN_LEFT:
> case BTN_RIGHT:
> @@ -844,6 +849,12 @@ tool_set_bits(const struct tablet_dispatch *tablet,
> }
> }
>
> +static inline int
> +axis_range_percentage(const struct input_absinfo *a, double percent)
> +{
> + return (a->maximum - a->minimum) * percent/100.0 + a->minimum;
> +}
> +
> static struct libinput_tablet_tool *
> tablet_get_tool(struct tablet_dispatch *tablet,
> enum libinput_tablet_tool_type type,
> @@ -894,12 +905,22 @@ tablet_get_tool(struct tablet_dispatch *tablet,
>
> tool->pressure_offset = 0;
> tool->has_pressure_offset = false;
> + tool->pressure_threshold.lower = 0;
> + tool->pressure_threshold.upper = 1;
>
> pressure = libevdev_get_abs_info(tablet->device->evdev,
> ABS_PRESSURE);
> - if (pressure)
> + if (pressure) {
> tool->pressure_offset = pressure->minimum;
>
> + /* 5% of the pressure range */
> + tool->pressure_threshold.upper =
> + pressure->minimum +
> + axis_range_percentage(pressure, 5);
The addition here seems incorrect. 'axis_range_percentage' already
accounts for the minimum, so you're effectively double-counting.
> + tool->pressure_threshold.lower =
> + pressure->minimum;
> + }
> +
> tool_set_bits(tablet, tool);
>
> list_insert(tool_list, &tool->link);
> @@ -965,7 +986,8 @@ tablet_notify_buttons(struct tablet_dispatch *tablet,
> }
>
> static void
> -sanitize_pressure_distance(struct tablet_dispatch *tablet)
> +sanitize_pressure_distance(struct tablet_dispatch *tablet,
> + struct libinput_tablet_tool *tool)
> {
> bool tool_in_contact;
> const struct input_absinfo *distance,
> @@ -974,9 +996,14 @@ sanitize_pressure_distance(struct tablet_dispatch *tablet)
> distance = libevdev_get_abs_info(tablet->device->evdev, ABS_DISTANCE);
> pressure = libevdev_get_abs_info(tablet->device->evdev, ABS_PRESSURE);
>
> - tool_in_contact = (tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT) ||
> - tablet_has_status(tablet,
> - TABLET_TOOL_ENTERING_CONTACT));
> + if (!pressure || !distance)
> + return;
> +
> + if (!bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_DISTANCE) &&
> + !bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE))
> + return;
> +
> + tool_in_contact = (pressure->value > tool->pressure_offset);
>
> /* Keep distance and pressure mutually exclusive */
> if (distance &&
> @@ -1017,16 +1044,11 @@ sanitize_mouse_lens_rotation(struct tablet_dispatch *tablet)
> set_bit(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z);
> }
>
> -static inline int
> -axis_range_percentage(const struct input_absinfo *a, int percent)
> -{
> - return (a->maximum - a->minimum) * percent/100 + a->minimum;
> -}
> -
> static void
> -sanitize_tablet_axes(struct tablet_dispatch *tablet)
> +sanitize_tablet_axes(struct tablet_dispatch *tablet,
> + struct libinput_tablet_tool *tool)
> {
> - sanitize_pressure_distance(tablet);
> + sanitize_pressure_distance(tablet, tool);
> sanitize_mouse_lens_rotation(tablet);
> }
>
> @@ -1086,6 +1108,41 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
> }
>
> static void
> +detect_tool_contact(struct tablet_dispatch *tablet,
> + struct evdev_device *device,
> + struct libinput_tablet_tool *tool)
> +{
> + int pressure;
> +
> + if (!bit_is_set(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE))
> + return;
> +
> + /* if we have pressure, always use that for contact, not BTN_TOUCH */
> + if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_CONTACT))
> + log_bug_libinput(device->base.seat->libinput,
> + "Invalid status: entering contact\n");
> + if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_CONTACT) &&
> + !tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY))
> + log_bug_libinput(device->base.seat->libinput,
> + "Invalid status: leaving contact\n");
> +
> + pressure = libevdev_get_event_value(tablet->device->evdev,
> + EV_ABS,
> + ABS_PRESSURE);
> +
> + if (tool->has_pressure_offset)
> + pressure -= tool->pressure_offset;
> +
> + if (pressure <= tool->pressure_threshold.lower &&
> + tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) {
> + tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT);
> + } else if (pressure >= tool->pressure_threshold.upper &&
> + !tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) {
> + tablet_set_status(tablet, TABLET_TOOL_ENTERING_CONTACT);
> + }
This probably doesn't work as expected if the device has a non-zero
minimum ABS_PRESSURE. For example, a tablet with a pressure range of
[100..200] will have both its lower threshold and pressure offset set
to 100, but because 'pressure' has the offset subtracted away it will
only ever take on values from [0..100]. Even maximum pressure would
not be sufficient to have the tool enter contact.
I believe you would either want to subtract away the ABS_PRESSURE
minimum from the thresholds here or when setting them in
'tablet_get_tool'.
Jason
---
Now instead of four in the eights place /
you’ve got three, ‘Cause you added one /
(That is to say, eight) to the two, /
But you can’t take seven from three, /
So you look at the sixty-fours....
> +}
> +
> +static void
> tablet_mark_all_axes_changed(struct tablet_dispatch *tablet,
> struct libinput_tablet_tool *tool)
> {
> @@ -1188,7 +1245,8 @@ tablet_flush(struct tablet_dispatch *tablet,
> TABLET_TOOL_ENTERING_PROXIMITY))
> tablet_mark_all_axes_changed(tablet, tool);
> detect_pressure_offset(tablet, device, tool);
> - sanitize_tablet_axes(tablet);
> + detect_tool_contact(tablet, device, tool);
> + sanitize_tablet_axes(tablet, tool);
> tablet_check_notify_axes(tablet, device, time, tool);
>
> tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
> diff --git a/src/libinput-private.h b/src/libinput-private.h
> index 3d714af..5238e64 100644
> --- a/src/libinput-private.h
> +++ b/src/libinput-private.h
> @@ -63,6 +63,12 @@ struct normalized_range_coords {
> double x, y;
> };
>
> +/* A threshold with an upper and lower limit */
> +struct threshold {
> + int upper;
> + int lower;
> +};
> +
> struct libinput_interface_backend {
> int (*resume)(struct libinput *libinput);
> void (*suspend)(struct libinput *libinput);
> @@ -277,6 +283,8 @@ struct libinput_tablet_tool {
> int refcount;
> void *user_data;
>
> + /* The pressure threshold assumes a pressure_offset of 0 */
> + struct threshold pressure_threshold;
> int pressure_offset; /* in device coordinates */
> bool has_pressure_offset;
> };
> diff --git a/test/tablet.c b/test/tablet.c
> index fc6d1df..0ddad77 100644
> --- a/test/tablet.c
> +++ b/test/tablet.c
> @@ -2528,7 +2528,7 @@ START_TEST(tablet_pressure_distance_exclusive)
> struct libinput_event_tablet_tool *tev;
> struct axis_replacement axes[] = {
> { ABS_DISTANCE, 10 },
> - { ABS_PRESSURE, 20 }, /* see the litest device */
> + { ABS_PRESSURE, 0 },
> { -1, -1 },
> };
> double pressure, distance;
> @@ -2536,6 +2536,7 @@ START_TEST(tablet_pressure_distance_exclusive)
> litest_tablet_proximity_in(dev, 5, 100, axes);
> litest_drain_events(li);
>
> + litest_axis_set_value(axes, ABS_PRESSURE, 2);
> litest_tablet_motion(dev, 70, 70, axes);
> libinput_dispatch(li);
>
> @@ -2545,28 +2546,8 @@ START_TEST(tablet_pressure_distance_exclusive)
> pressure = libinput_event_tablet_tool_get_pressure(tev);
> distance = libinput_event_tablet_tool_get_distance(tev);
>
> - ck_assert_double_eq(pressure, 0.0);
> - ck_assert_double_ne(distance, 0.0);
> -
> - libinput_event_destroy(event);
> -
> - litest_axis_set_value(axes, ABS_DISTANCE, 15);
> - litest_axis_set_value(axes, ABS_PRESSURE, 25);
> - litest_event(dev, EV_KEY, BTN_TOUCH, 1);
> - litest_tablet_motion(dev, 30, 30, axes);
> - libinput_dispatch(li);
> -
> - litest_wait_for_event_of_type(li,
> - LIBINPUT_EVENT_TABLET_TOOL_AXIS,
> - -1);
> - event = libinput_get_event(li);
> - tev = libinput_event_get_tablet_tool_event(event);
> -
> - pressure = libinput_event_tablet_tool_get_pressure(tev);
> - distance = libinput_event_tablet_tool_get_distance(tev);
> -
> - ck_assert_double_eq(distance, 0.0);
> ck_assert_double_ne(pressure, 0.0);
> + ck_assert_double_eq(distance, 0.0);
>
> libinput_event_destroy(event);
> }
> @@ -2933,6 +2914,18 @@ START_TEST(tablet_pressure_offset_increase)
> }
> END_TEST
>
> +static void pressure_threshold_warning(struct libinput *libinput,
> + enum libinput_log_priority priority,
> + const char *format,
> + va_list args)
> +{
> + int *warning_triggered = (int*)libinput_get_user_data(libinput);
> +
> + if (priority == LIBINPUT_LOG_PRIORITY_ERROR &&
> + strstr(format, "pressure offset greater"))
> + (*warning_triggered)++;
> +}
> +
> START_TEST(tablet_pressure_offset_exceed_threshold)
> {
> struct litest_device *dev = litest_current_device();
> @@ -2945,40 +2938,24 @@ START_TEST(tablet_pressure_offset_exceed_threshold)
> { -1, -1 },
> };
> double pressure;
> + int warning_triggered = 0;
>
> litest_drain_events(li);
>
> - litest_disable_log_handler(li);
> + libinput_set_user_data(li, &warning_triggered);
> +
> + libinput_log_set_handler(li, pressure_threshold_warning);
> litest_tablet_proximity_in(dev, 5, 100, axes);
> libinput_dispatch(li);
> event = libinput_get_event(li);
> tev = litest_is_tablet_event(event,
> LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
> pressure = libinput_event_tablet_tool_get_pressure(tev);
> - ck_assert_double_eq(pressure, 0.0);
> + ck_assert_double_gt(pressure, 0.0);
> libinput_event_destroy(event);
> +
> + ck_assert_int_eq(warning_triggered, 1);
> litest_restore_log_handler(li);
> -
> - litest_axis_set_value(axes, ABS_DISTANCE, 0);
> - litest_axis_set_value(axes, ABS_PRESSURE, 31);
> - litest_push_event_frame(dev);
> - litest_tablet_motion(dev, 70, 70, axes);
> - litest_event(dev, EV_KEY, BTN_TOUCH, 1);
> - litest_pop_event_frame(dev);
> - libinput_dispatch(li);
> - litest_drain_events(li);
> -
> - litest_axis_set_value(axes, ABS_PRESSURE, 30);
> - litest_tablet_motion(dev, 70, 70, axes);
> - libinput_dispatch(li);
> -
> - event = libinput_get_event(li);
> - tev = litest_is_tablet_event(event,
> - LIBINPUT_EVENT_TABLET_TOOL_AXIS);
> - pressure = libinput_event_tablet_tool_get_pressure(tev);
> - ck_assert_double_gt(pressure, 0.0);
> -
> - libinput_event_destroy(event);
> }
> END_TEST
>
> --
> 2.5.0
>
> _______________________________________________
> wayland-devel mailing list
> wayland-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/wayland-devel
More information about the wayland-devel
mailing list