[PATCH libinput 6/8] tablet: support tool-specific pressure offsets

Peter Hutterer peter.hutterer at who-t.net
Tue Dec 1 17:46:30 PST 2015


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.

The X.Org wacom driver has automatic pressure preload detection, but it is
unreliable. A quick tap can trigger the detection, making the pen unusable
(see xf86-input-wacom-0.23.0-43-g10cc765).

Since this is a hardware-specific property, add a new udev property with the
prefix TABLET_TOOL_PRESSURE_OFFSET_ and let it be set system-wide through the
hwdb or some other process. Use the value of this property to offset any
incoming pressure values and scale into the original normalized axis range.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 doc/device-configuration-via-udev.dox    |   4 +
 doc/tablet-support.dox                   |  30 ++++++
 src/evdev-tablet.c                       |  71 +++++++++++++-
 src/libinput-private.h                   |   2 +
 test/litest-device-wacom-intuos-tablet.c |  12 +++
 test/tablet.c                            | 155 ++++++++++++++++++++++++++++++-
 6 files changed, 270 insertions(+), 4 deletions(-)

diff --git a/doc/device-configuration-via-udev.dox b/doc/device-configuration-via-udev.dox
index f615cc1..ba44e58 100644
--- a/doc/device-configuration-via-udev.dox
+++ b/doc/device-configuration-via-udev.dox
@@ -63,6 +63,10 @@ libinput_pointer_get_axis_source() for details.
 <dt>POINTINGSTICK_CONST_ACCEL</dt>
 <dd>A constant (linear) acceleration factor to apply to pointingstick deltas
 to normalize them.
+<dt>TABLET_TOOL_PRESSURE_OFFSET_*</dt>
+<dd>Specifies the pressure offset in device coordinates for the tool whose
+name is given as part of the property name. See @ref tablet-pressure-offset
+for details.</dd>
 <dt>LIBINPUT_MODEL_*</dt>
 <dd><b>This prefix is reserved as private API, do not use.</b> See @ref
 model_specific_configuration for details.
diff --git a/doc/tablet-support.dox b/doc/tablet-support.dox
index 24d08d2..27f970a 100644
--- a/doc/tablet-support.dox
+++ b/doc/tablet-support.dox
@@ -92,4 +92,34 @@ if (value < min) {
 }
 @endcode
 
+ at 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 allows for a static configuration to be applied to negate this
+effect. If set, the <b>TABLET_TOOL_PRESSURE_OFFSET_...</b> udev property
+specifies the tool-specific offset to be applied to any pressure
+calculation. The calculation is done transparent to the caller.
+
+The udev property name contains the type and serial number of the tool, the
+value is the offset in device coordinate range. The type of the tool is the
+string as defined in linux/input.h without the BTN_TOOL_ prefix (e.g. "PEN",
+"RUBBER", ...). The serial number is the uppercase hexadecimal serial number
+without the 0x prefix.
+
+For example, if a tool with the serial number ab1234 has a pressure offset
+of 20 on the pen and 30 on the eraser tool, the properties should be
+
+ at code
+TABLET_TOOL_PRESSURE_OFFSET_PEN_AB1234=20
+TABLET_TOOL_PRESSURE_OFFSET_RUBBER_AB1234=30
+ at endcode
+
+Pressure offsets are not available 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 e9473e8..d3ec1da 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -202,7 +202,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 +211,17 @@ 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_val = absinfo->value - tool->pressure_offset;
+	double value = (offset_val - absinfo->minimum) / range;
+
+	return value;
+}
+
+static inline double
 normalize_tilt(const struct input_absinfo *absinfo)
 {
 	double range = absinfo->maximum - absinfo->minimum;
@@ -368,10 +379,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:
@@ -733,6 +746,57 @@ tool_set_bits(const struct tablet_dispatch *tablet,
 	}
 }
 
+static void
+tool_set_pressure_offset(const struct tablet_dispatch *tablet,
+			 struct libinput_tablet_tool *tool)
+{
+	struct evdev_device *device = tablet->device;
+	char prop_name[100];
+	const char *strtype;
+	const char *prop_value;
+	int offset;
+
+	switch (tool->type) {
+	case LIBINPUT_TABLET_TOOL_TYPE_PEN:
+		strtype = "PEN";
+		break;
+	case LIBINPUT_TABLET_TOOL_TYPE_BRUSH:
+		strtype = "BRUSH";
+		break;
+	case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH:
+		strtype = "AIRBRUSH";
+		break;
+	case LIBINPUT_TABLET_TOOL_TYPE_PENCIL:
+		strtype = "PENCIL";
+		break;
+	case LIBINPUT_TABLET_TOOL_TYPE_ERASER:
+		strtype = "RUBBER";
+		break;
+	default:
+		return;
+	}
+
+	snprintf(prop_name,
+		 sizeof(prop_name),
+		 "TABLET_TOOL_PRESSURE_OFFSET_%s_%X",
+		 strtype,
+		 tool->serial);
+
+	prop_value = udev_device_get_property_value(device->udev_device,
+						    prop_name);
+
+	if (!prop_value)
+		return;
+
+	if (!atoi_safe(prop_value, &offset)) {
+		log_error(device->base.seat->libinput,
+			  "Invalid pressure offset value %s\n", prop_value);
+		return;
+	}
+
+	tool->pressure_offset = offset;
+}
+
 static struct libinput_tablet_tool *
 tablet_get_tool(struct tablet_dispatch *tablet,
 		enum libinput_tablet_tool_type type,
@@ -780,6 +844,7 @@ tablet_get_tool(struct tablet_dispatch *tablet,
 		};
 
 		tool_set_bits(tablet, tool);
+		tool_set_pressure_offset(tablet, tool);
 
 		list_insert(tool_list, &tool->link);
 	}
diff --git a/src/libinput-private.h b/src/libinput-private.h
index 38a14b8..091fd98 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -259,6 +259,8 @@ struct libinput_tablet_tool {
 	unsigned char buttons[NCHARS(KEY_MAX) + 1];
 	int refcount;
 	void *user_data;
+
+	int 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..8b9b59e 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 },
@@ -127,6 +128,16 @@ static int events[] = {
 	-1, -1,
 };
 
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"intuos_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"intuos_end\"\n"
+"\n"
+"ATTRS{name}==\"Wacom Intuos5 touch M Pen\",\n"
+"    ENV{TABLET_TOOL_PRESSURE_OFFSET_PEN_123456}=\"40\",\n"
+"    ENV{TABLET_TOOL_PRESSURE_OFFSET_RUBBER_123456}=\"70\"\n"
+"\n"
+"LABEL=\"intuos_end\"";
+
 struct litest_test_device litest_wacom_intuos_tablet_device = {
 	.type = LITEST_WACOM_INTUOS,
 	.features = LITEST_TABLET | LITEST_DISTANCE | LITEST_TOOL_SERIAL,
@@ -138,4 +149,5 @@ struct litest_test_device litest_wacom_intuos_tablet_device = {
 	.id = &input_id,
 	.events = events,
 	.absinfo = absinfo,
+	.udev_rule = udev_rule,
 };
diff --git a/test/tablet.c b/test/tablet.c
index 125ab4b..97740f7 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;
@@ -2364,6 +2364,156 @@ START_TEST(tablet_pressure_distance_exclusive)
 }
 END_TEST
 
+START_TEST(tablet_pressure_offset_none_for_default_pen)
+{
+	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, 1 },
+		{ -1, -1 },
+	};
+	double pressure;
+
+	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);
+	litest_drain_events(li);
+
+	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_eq(pressure, 0.0);
+
+	libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(tablet_pressure_offset_pen)
+{
+	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, 40 }, /* see the litest device */
+		{ -1, -1 },
+	};
+	double pressure;
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 5, 100, axes);
+	/* the serial number to trigger the pen offset */
+	litest_event(dev, EV_MSC, MSC_SERIAL, 0x123456);
+	litest_event(dev, EV_KEY, BTN_TOUCH, 1);
+	litest_pop_event_frame(dev);
+	litest_drain_events(li);
+
+	litest_push_event_frame(dev);
+	litest_tablet_motion(dev, 70, 70, axes);
+	litest_event(dev, EV_MSC, MSC_SERIAL, 0x123456);
+	litest_pop_event_frame(dev);
+	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_eq(pressure, 0.0);
+
+	libinput_event_destroy(event);
+
+	axes[0].value = 0;
+	axes[1].value = 80;
+	litest_push_event_frame(dev);
+	litest_tablet_motion(dev, 30, 30, axes);
+	litest_event(dev, EV_MSC, MSC_SERIAL, 0x123456);
+	litest_pop_event_frame(dev);
+	libinput_dispatch(li);
+	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
+
+START_TEST(tablet_pressure_offset_rubber)
+{
+	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, 70 },
+		{ -1, -1 },
+	};
+	double pressure;
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 5, 100, axes);
+	/* the serial number to trigger the pen offset */
+	litest_event(dev, EV_MSC, MSC_SERIAL, 0x123456);
+	litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 1);
+	litest_event(dev, EV_KEY, BTN_TOUCH, 1);
+	litest_pop_event_frame(dev);
+	litest_drain_events(li);
+
+	litest_push_event_frame(dev);
+	litest_tablet_motion(dev, 70, 70, axes);
+	litest_event(dev, EV_MSC, MSC_SERIAL, 0x123456);
+	litest_pop_event_frame(dev);
+	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_eq(pressure, 0.0);
+
+	libinput_event_destroy(event);
+
+	axes[0].value = 0;
+	axes[1].value = 80;
+	litest_push_event_frame(dev);
+	litest_tablet_motion(dev, 30, 30, axes);
+	litest_event(dev, EV_MSC, MSC_SERIAL, 0x123456);
+	litest_pop_event_frame(dev);
+	libinput_dispatch(li);
+	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)
 {
@@ -2408,4 +2558,7 @@ litest_setup_tests(void)
 
 	litest_add("tablet:time", tablet_time_usec, LITEST_TABLET, LITEST_ANY);
 	litest_add("tablet:pressure", tablet_pressure_distance_exclusive, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+	litest_add_for_device("tablet:pressure", tablet_pressure_offset_none_for_default_pen, LITEST_WACOM_INTUOS);
+	litest_add_for_device("tablet:pressure", tablet_pressure_offset_pen, LITEST_WACOM_INTUOS);
+	litest_add_for_device("tablet:pressure", tablet_pressure_offset_rubber, LITEST_WACOM_INTUOS);
 }
-- 
2.5.0



More information about the wayland-devel mailing list