From 96fbf848625c0fb2eb53bd7e55664a96155c46ed Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 11 Dec 2015 17:40:36 +1000 Subject: [PATCH] tablet: add pressure threshold handling 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 Acked-by: Jason Gerecke --- doc/tablet-support.dox | 16 +++++++ src/evdev-tablet.c | 98 ++++++++++++++++++++++++++++++++++-------- src/libinput-private.h | 8 ++++ test/tablet.c | 67 ++++++++++------------------- 4 files changed, 126 insertions(+), 63 deletions(-) diff --git a/doc/tablet-support.dox b/doc/tablet-support.dox index c95d8052..5e6f3932 100644 --- a/doc/tablet-support.dox +++ b/doc/tablet-support.dox @@ -35,6 +35,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 can while the tip is down or up. +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 f1b22f1f..070ac9a1 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -616,10 +616,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: @@ -843,6 +848,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, @@ -893,12 +904,21 @@ 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 = + axis_range_percentage(pressure, 5); + tool->pressure_threshold.lower = + pressure->minimum; + } + tool_set_bits(tablet, tool); list_insert(tool_list, &tool->link); @@ -964,7 +984,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, @@ -973,9 +994,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 && @@ -1016,16 +1042,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); } @@ -1084,6 +1105,46 @@ detect_pressure_offset(struct tablet_dispatch *tablet, tool->has_pressure_offset = true; } +static void +detect_tool_contact(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool) +{ + const struct input_absinfo *p; + 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"); + + p = libevdev_get_abs_info(tablet->device->evdev, ABS_PRESSURE); + if (!p) { + log_bug_libinput(device->base.seat->libinput, + "Missing pressure axis\n"); + return; + } + pressure = p->value; + + if (tool->has_pressure_offset) + pressure -= (tool->pressure_offset - p->minimum); + + 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); + } +} + static void tablet_mark_all_axes_changed(struct tablet_dispatch *tablet, struct libinput_tablet_tool *tool) @@ -1187,7 +1248,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 3d714af8..5238e646 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 76dfe804..421deb06 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); - libinput_event_destroy(event); - 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); + + ck_assert_int_eq(warning_triggered, 1); + litest_restore_log_handler(li); } END_TEST