On Sunday, December 13, 2015, Peter Hutterer <[email protected]> wrote:
> If a tool wears out, it may have a pre-loaded pressure offset. In that > case, > even when the tool is not physically in contact with the tablet surface it > will send pressure events. > > Use automatic pressure offset detection, similar to what the X.Org wacom > driver does. On proximity-in, check the pressure and if the distance is > above > 50% of the range and the pressure is nonzero but below 20% of the range, > use > that value as pressure offset. > > Signed-off-by: Peter Hutterer <[email protected] <javascript:;>> There is no logical change in this version. So, it is still Reviewed-by: Ping Cheng <[email protected]> Ping --- > Changes to v2: > - rather than using offset != INT_MIN everywhere, add a has_pressure_offset > boolean and check that. Makes the code much nicer > > doc/tablet-support.dox | 29 +++ > src/evdev-tablet.c | 86 ++++++++- > src/evdev-tablet.h | 21 +++ > src/libinput-private.h | 3 + > test/litest-device-wacom-intuos-tablet.c | 1 + > test/tablet.c | 313 > ++++++++++++++++++++++++++++++- > 6 files changed, 449 insertions(+), 4 deletions(-) > > diff --git a/doc/tablet-support.dox b/doc/tablet-support.dox > index 24d08d2..5468c6f 100644 > --- a/doc/tablet-support.dox > +++ b/doc/tablet-support.dox > @@ -92,4 +92,33 @@ if (value < min) { > } > @endcode > > +@section tablet-pressure-offset Pressure offset on worn-out tools > + > +When a tool is used for an extended period it can wear down physically. A > +worn-down tool may never return a zero pressure value. Even when hovering > +above the surface, the pressure value returned by the tool is nonzero, > +creating a fake surface touch and making interaction with the tablet less > +predictable. > + > +libinput automatically detects pressure offsets and rescales the remaining > +pressure range into the available range, making pressure-offsets > transparent > +to the caller. A tool with a pressure offset will thus send a 0 pressure > +value for the detected offset and nonzero pressure values for values > higher > +than that offset. > + > +Some limitations apply to avoid misdetection of pressure offsets, > +specifically: > +- pressure offset is only detected on proximity in, and if a device is > + capable of detection distances, > +- pressure offset is only detected if the distance between the tool and > the > + tablet is high enough, > +- pressure offset is only used if it is 20% or less of the pressure range > + available to the tool. A pressure offset higher than 20% indicates > either > + a misdetection or a tool that should be replaced, and > +- if a pressure value less than the current pressure offset is seen, the > + offset resets to that value. > + > +Pressure offsets are not detected on @ref LIBINPUT_TABLET_TOOL_TYPE_MOUSE > +and @ref LIBINPUT_TABLET_TOOL_TYPE_LENS tools. > + > */ > diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c > index cd208a8..2a45575 100644 > --- a/src/evdev-tablet.c > +++ b/src/evdev-tablet.c > @@ -21,9 +21,11 @@ > * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > */ > #include "config.h" > +#include "libinput-version.h" > #include "evdev-tablet.h" > > #include <assert.h> > +#include <inttypes.h> > #include <stdbool.h> > #include <string.h> > > @@ -202,7 +204,7 @@ tablet_update_tool(struct tablet_dispatch *tablet, > } > > static inline double > -normalize_pressure_dist_slider(const struct input_absinfo *absinfo) > +normalize_dist_slider(const struct input_absinfo *absinfo) > { > double range = absinfo->maximum - absinfo->minimum; > double value = (absinfo->value - absinfo->minimum) / range; > @@ -211,6 +213,18 @@ normalize_pressure_dist_slider(const struct > input_absinfo *absinfo) > } > > static inline double > +normalize_pressure(const struct input_absinfo *absinfo, > + struct libinput_tablet_tool *tool) > +{ > + double range = absinfo->maximum - absinfo->minimum; > + int offset = tool->has_pressure_offset ? > + tool->pressure_offset : 0; > + double value = (absinfo->value - offset - absinfo->minimum) / > range; > + > + return value; > +} > + > +static inline double > normalize_tilt(const struct input_absinfo *absinfo) > { > double range = absinfo->maximum - absinfo->minimum; > @@ -405,10 +419,12 @@ tablet_check_notify_axes(struct tablet_dispatch > *tablet, > else > tablet->axes[a] = absinfo->value; > break; > - case LIBINPUT_TABLET_TOOL_AXIS_DISTANCE: > case LIBINPUT_TABLET_TOOL_AXIS_PRESSURE: > + tablet->axes[a] = normalize_pressure(absinfo, > tool); > + break; > + case LIBINPUT_TABLET_TOOL_AXIS_DISTANCE: > case LIBINPUT_TABLET_TOOL_AXIS_SLIDER: > - tablet->axes[a] = > normalize_pressure_dist_slider(absinfo); > + tablet->axes[a] = normalize_dist_slider(absinfo); > break; > case LIBINPUT_TABLET_TOOL_AXIS_TILT_X: > case LIBINPUT_TABLET_TOOL_AXIS_TILT_Y: > @@ -816,6 +832,8 @@ tablet_get_tool(struct tablet_dispatch *tablet, > .refcount = 1, > }; > > + tool->pressure_offset = 0; > + tool->has_pressure_offset = false; > tool_set_bits(tablet, tool); > > list_insert(tool_list, &tool->link); > @@ -929,6 +947,67 @@ sanitize_tablet_axes(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 > +detect_pressure_offset(struct tablet_dispatch *tablet, > + struct evdev_device *device, > + struct libinput_tablet_tool *tool) > +{ > + const struct input_absinfo *pressure, *distance; > + int offset; > + > + if (!bit_is_set(tablet->changed_axes, > + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) > + return; > + > + pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); > + distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); > + > + if (!pressure || !distance) > + return; > + > + offset = pressure->value - pressure->minimum; > + > + if (tool->has_pressure_offset) { > + if (offset < tool->pressure_offset) > + tool->pressure_offset = offset; > + return; > + } > + > + /* we only set a pressure offset on proximity in */ > + if (!tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) > + 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)) > + return; > + > + if (offset > axis_range_percentage(pressure, 20)) { > + log_error(device->base.seat->libinput, > + "Ignoring pressure offset greater than 20%% > detected on tool %s (serial %#x). " > + "See > http://wayland.freedesktop.org/libinput/doc/%s/tablet-support.html\n", > + tablet_tool_type_to_string(tool->type), > + tool->serial, > + LIBINPUT_VERSION); > + return; > + } > + > + log_info(device->base.seat->libinput, > + "Pressure offset detected on tool %s (serial %#x). " > + "See > http://wayland.freedesktop.org/libinput/doc/%s/tablet-support.html\n", > + tablet_tool_type_to_string(tool->type), > + tool->serial, > + LIBINPUT_VERSION); > + tool->pressure_offset = offset; > + tool->has_pressure_offset = true; > +} > + > static void > tablet_flush(struct tablet_dispatch *tablet, > struct evdev_device *device, > @@ -953,6 +1032,7 @@ tablet_flush(struct tablet_dispatch *tablet, > tablet_set_status(tablet, > TABLET_TOOL_LEAVING_CONTACT); > } else if (tablet_has_status(tablet, TABLET_AXES_UPDATED) || > tablet_has_status(tablet, > TABLET_TOOL_ENTERING_PROXIMITY)) { > + detect_pressure_offset(tablet, device, tool); > sanitize_tablet_axes(tablet); > tablet_check_notify_axes(tablet, device, time, tool); > > diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h > index 162b536..4dcbccc 100644 > --- a/src/evdev-tablet.h > +++ b/src/evdev-tablet.h > @@ -178,4 +178,25 @@ tablet_tool_to_evcode(enum libinput_tablet_tool_type > type) > > return code; > } > + > +static inline const char * > +tablet_tool_type_to_string(enum libinput_tablet_tool_type type) > +{ > + const char *str; > + > + switch (type) { > + case LIBINPUT_TABLET_TOOL_TYPE_PEN: str = "pen"; > break; > + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: str = "eraser"; > break; > + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: str = "brush"; > break; > + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: str = "pencil"; > break; > + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: str = "airbrush"; > break; > + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: str = "mouse"; > break; > + case LIBINPUT_TABLET_TOOL_TYPE_LENS: str = "lens"; > break; > + default: > + abort(); > + } > + > + return str; > +} > + > #endif > diff --git a/src/libinput-private.h b/src/libinput-private.h > index 38a14b8..f5b2648 100644 > --- a/src/libinput-private.h > +++ b/src/libinput-private.h > @@ -259,6 +259,9 @@ struct libinput_tablet_tool { > unsigned char buttons[NCHARS(KEY_MAX) + 1]; > int refcount; > void *user_data; > + > + int pressure_offset; > + bool has_pressure_offset; > }; > > struct libinput_event { > diff --git a/test/litest-device-wacom-intuos-tablet.c > b/test/litest-device-wacom-intuos-tablet.c > index e0e1d44..ef0a1f1 100644 > --- a/test/litest-device-wacom-intuos-tablet.c > +++ b/test/litest-device-wacom-intuos-tablet.c > @@ -37,6 +37,7 @@ static struct input_event proximity_in[] = { > { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, > { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, > { .type = EV_ABS, .code = ABS_DISTANCE, .value = > LITEST_AUTO_ASSIGN }, > + { .type = EV_ABS, .code = ABS_PRESSURE, .value = > LITEST_AUTO_ASSIGN }, > { .type = EV_ABS, .code = ABS_TILT_X, .value = LITEST_AUTO_ASSIGN > }, > { .type = EV_ABS, .code = ABS_TILT_Y, .value = LITEST_AUTO_ASSIGN > }, > { .type = EV_ABS, .code = ABS_MISC, .value = 1050626 }, > diff --git a/test/tablet.c b/test/tablet.c > index 18f08b2..9cfc42f 100644 > --- a/test/tablet.c > +++ b/test/tablet.c > @@ -2313,7 +2313,7 @@ START_TEST(tablet_pressure_distance_exclusive) > struct libinput_event_tablet_tool *tev; > struct axis_replacement axes[] = { > { ABS_DISTANCE, 10 }, > - { ABS_PRESSURE, 20 }, > + { ABS_PRESSURE, 20 }, /* see the litest device */ > { -1, -1 }, > }; > double pressure, distance; > @@ -2530,6 +2530,310 @@ START_TEST(tablet_calibration_set_matrix) > } > END_TEST > > +START_TEST(tablet_pressure_offset) > +{ > + struct litest_device *dev = litest_current_device(); > + struct libinput *li = dev->libinput; > + struct libinput_event *event; > + struct libinput_event_tablet_tool *tev; > + struct axis_replacement axes[] = { > + { ABS_DISTANCE, 70 }, > + { ABS_PRESSURE, 20 }, > + { -1, -1 }, > + }; > + double pressure; > + > + litest_tablet_proximity_in(dev, 5, 100, axes); > + litest_drain_events(li); > + > + axes[0].value = 0; > + axes[1].value = 21; > + 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); > + > + axes[1].value = 20; > + 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + ck_assert_double_eq(pressure, 0.0); > + > + libinput_event_destroy(event); > + litest_drain_events(li); > + > + axes[1].value = 21; > + 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + > + /* can't use the double_eq here, the pressure value is too tiny */ > + ck_assert(pressure > 0.0); > + ck_assert(pressure < 1.0); > + libinput_event_destroy(event); > +} > +END_TEST > + > +START_TEST(tablet_pressure_offset_decrease) > +{ > + struct litest_device *dev = litest_current_device(); > + struct libinput *li = dev->libinput; > + struct libinput_event *event; > + struct libinput_event_tablet_tool *tev; > + struct axis_replacement axes[] = { > + { ABS_DISTANCE, 70 }, > + { ABS_PRESSURE, 20 }, > + { -1, -1 }, > + }; > + double pressure; > + > + /* offset 20 on prox in */ > + litest_tablet_proximity_in(dev, 5, 100, axes); > + litest_drain_events(li); > + > + /* a reduced pressure value must reduce the offset */ > + axes[0].value = 0; > + axes[1].value = 10; > + 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); > + > + event = libinput_get_event(li); > + tev = litest_is_tablet_event(event, > + LIBINPUT_EVENT_TABLET_TOOL_AXIS); > + pressure = libinput_event_tablet_tool_get_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + ck_assert_double_eq(pressure, 0.0); > + > + libinput_event_destroy(event); > + litest_drain_events(li); > + > + axes[1].value = 11; > + 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + > + /* can't use the double_eq here, the pressure value is too tiny */ > + ck_assert(pressure > 0.0); > + ck_assert(pressure < 1.0); > + libinput_event_destroy(event); > +} > +END_TEST > + > +START_TEST(tablet_pressure_offset_increase) > +{ > + struct litest_device *dev = litest_current_device(); > + struct libinput *li = dev->libinput; > + struct libinput_event *event; > + struct libinput_event_tablet_tool *tev; > + struct axis_replacement axes[] = { > + { ABS_DISTANCE, 70 }, > + { ABS_PRESSURE, 20 }, > + { -1, -1 }, > + }; > + double pressure; > + > + /* offset 20 on first prox in */ > + litest_tablet_proximity_in(dev, 5, 100, axes); > + litest_tablet_proximity_out(dev); > + litest_drain_events(li); > + > + /* offset 30 on second prox in - must not change the offset */ > + axes[1].value = 30; > + litest_tablet_proximity_in(dev, 5, 100, axes); > + litest_drain_events(li); > + > + axes[0].value = 0; > + axes[1].value = 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); > + > + axes[1].value = 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + /* can't use the double_eq here, the pressure value is too tiny */ > + ck_assert(pressure > 0.0); > + ck_assert(pressure < 1.0); > + libinput_event_destroy(event); > + > + litest_drain_events(li); > + > + axes[1].value = 20; > + 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + > + ck_assert_double_eq(pressure, 0.0); > + libinput_event_destroy(event); > +} > +END_TEST > + > +START_TEST(tablet_pressure_offset_exceed_threshold) > +{ > + struct litest_device *dev = litest_current_device(); > + struct libinput *li = dev->libinput; > + struct libinput_event *event; > + struct libinput_event_tablet_tool *tev; > + struct axis_replacement axes[] = { > + { ABS_DISTANCE, 70 }, > + { ABS_PRESSURE, 30 }, > + { -1, -1 }, > + }; > + double pressure; > + > + litest_drain_events(li); > + > + litest_disable_log_handler(li); > + 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + ck_assert_double_eq(pressure, 0.0); > + libinput_event_destroy(event); > + litest_restore_log_handler(li); > + > + axes[0].value = 0; > + axes[1].value = 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); > + > + axes[1].value = 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + ck_assert_double_gt(pressure, 0.0); > + > + libinput_event_destroy(event); > +} > +END_TEST > + > +START_TEST(tablet_pressure_offset_none_for_zero_distance) > +{ > + struct litest_device *dev = litest_current_device(); > + struct libinput *li = dev->libinput; > + struct libinput_event *event; > + struct libinput_event_tablet_tool *tev; > + struct axis_replacement axes[] = { > + { ABS_DISTANCE, 0 }, > + { ABS_PRESSURE, 20 }, > + { -1, -1 }, > + }; > + double pressure; > + > + litest_drain_events(li); > + > + /* we're going straight to touch on proximity, make sure we don't > + * offset the pressure here */ > + litest_push_event_frame(dev); > + litest_tablet_proximity_in(dev, 5, 100, axes); > + litest_event(dev, EV_KEY, BTN_TOUCH, 1); > + litest_pop_event_frame(dev); > + 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + ck_assert_double_gt(pressure, 0.0); > + > + libinput_event_destroy(event); > +} > +END_TEST > + > +START_TEST(tablet_pressure_offset_none_for_small_distance) > +{ > + struct litest_device *dev = litest_current_device(); > + struct libinput *li = dev->libinput; > + struct libinput_event *event; > + struct libinput_event_tablet_tool *tev; > + struct axis_replacement axes[] = { > + { ABS_DISTANCE, 20 }, > + { ABS_PRESSURE, 20 }, > + { -1, -1 }, > + }; > + double pressure; > + > + /* stylus too close to the tablet on the proximity in, ignore any > + * pressure offset */ > + litest_tablet_proximity_in(dev, 5, 100, axes); > + litest_drain_events(li); > + libinput_dispatch(li); > + > + axes[0].value = 0; > + axes[1].value = 21; > + 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); > + litest_drain_events(li); > + > + axes[1].value = 20; > + litest_tablet_motion(dev, 70, 70, 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_axis_value(tev, > + > LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); > + ck_assert_double_gt(pressure, 0.0); > + > + libinput_event_destroy(event); > +} > +END_TEST > + > void > litest_setup_tests(void) > { > @@ -2578,4 +2882,11 @@ litest_setup_tests(void) > litest_add("tablet:calibration", tablet_calibration_has_matrix, > LITEST_TABLET, LITEST_ANY); > litest_add("tablet:calibration", tablet_calibration_set_matrix, > LITEST_TABLET, LITEST_ANY); > litest_add("tablet:calibration", > tablet_calibration_set_matrix_delta, LITEST_TABLET, LITEST_ANY); > + > + litest_add_for_device("tablet:pressure", tablet_pressure_offset, > LITEST_WACOM_INTUOS); > + litest_add_for_device("tablet:pressure", > tablet_pressure_offset_decrease, LITEST_WACOM_INTUOS); > + litest_add_for_device("tablet:pressure", > tablet_pressure_offset_increase, LITEST_WACOM_INTUOS); > + litest_add_for_device("tablet:pressure", > tablet_pressure_offset_exceed_threshold, LITEST_WACOM_INTUOS); > + litest_add_for_device("tablet:pressure", > tablet_pressure_offset_none_for_zero_distance, LITEST_WACOM_INTUOS); > + litest_add_for_device("tablet:pressure", > tablet_pressure_offset_none_for_small_distance, LITEST_WACOM_INTUOS); > } > -- > 2.5.0 > >
_______________________________________________ wayland-devel mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/wayland-devel
