mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-31 22:50:11 +01:00
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:
parent
8d79b718fd
commit
96fbf84862
4 changed files with 126 additions and 63 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue