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 <peter.hutterer@who-t.net>
Acked-by: Jason Gerecke <jason.gerecke@wacom.com>
This commit is contained in:
Peter Hutterer 2015-12-11 17:40:36 +10:00
parent 8d79b718fd
commit 96fbf84862
4 changed files with 126 additions and 63 deletions

View file

@ -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

View file

@ -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);

View file

@ -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;
};

View file

@ -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