[PATCH libinput] tablet: handle custom proximity handling

Peter Hutterer peter.hutterer at who-t.net
Wed Dec 2 15:57:57 PST 2015


For the puck/lens cursor tool we need to artificially reduce proximity
detection. These tools are usually used in a relative mode (i.e. like a mouse)
and thus require lifting and resetting the tool multiple times to move across
the screen. The tablets' distance detection goes too far, requiring the user
to lift the device several cm on every move. This is uncomfortable.

Introduce an artificial distance threshold for the devices with the default
value taken from the X.Org wacom driver. If a tool is in proximity but outside
of this range, fake proximity events accordingly.

If a button was pressed while we were out of range we discard that event and
send it later when we enter proximity again.

This is the simple implementation that only takes one proximity out value (the
one from the wacom driver) and applies it to all. Those devices that support a
button/lens tool and have a different default threshold are well out of date.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 src/evdev-tablet.c | 103 ++++++++++++++++++++++++-
 src/evdev-tablet.h |   3 +
 test/litest.c      |  18 +++++
 test/litest.h      |   3 +-
 test/tablet.c      | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 346 insertions(+), 2 deletions(-)

diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 7e3298c..777ffad 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -67,6 +67,22 @@ tablet_get_released_buttons(struct tablet_dispatch *tablet,
 					~(state->stylus_buttons[i]);
 }
 
+/* Merge the previous state with the current one so all buttons look like
+ * they just got pressed in this frame */
+static inline void
+tablet_force_button_presses(struct tablet_dispatch *tablet)
+{
+	struct button_state *state = &tablet->button_state,
+			    *prev_state = &tablet->prev_button_state;
+	size_t i;
+
+	for (i = 0; i < sizeof(state->stylus_buttons); i++) {
+		state->stylus_buttons[i] = state->stylus_buttons[i] |
+						prev_state->stylus_buttons[i];
+		prev_state->stylus_buttons[i] = 0;
+	}
+}
+
 static int
 tablet_device_has_axis(struct tablet_dispatch *tablet,
 		       enum libinput_tablet_tool_axis axis)
@@ -994,6 +1010,62 @@ sanitize_tablet_axes(struct tablet_dispatch *tablet)
 }
 
 static void
+tablet_update_proximity_state(struct tablet_dispatch *tablet,
+			      struct evdev_device *device)
+{
+	const struct input_absinfo *distance;
+	int dist_max = tablet->cursor_proximity_threshold;
+	int dist;
+
+	distance = libevdev_get_abs_info(tablet->device->evdev, ABS_DISTANCE);
+	if (!distance)
+		return;
+
+	dist = distance->value;
+	if (dist == 0)
+		return;
+
+	/* Tool got into permitted range */
+	if (dist < dist_max &&
+	    (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE) ||
+	     tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))) {
+		tablet_unset_status(tablet,
+				    TABLET_TOOL_OUT_OF_RANGE);
+		tablet_unset_status(tablet,
+				    TABLET_TOOL_OUT_OF_PROXIMITY);
+		tablet_set_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
+		tablet_mark_all_axes_changed(tablet, device);
+
+		tablet_set_status(tablet, TABLET_BUTTONS_PRESSED);
+		tablet_force_button_presses(tablet);
+		return;
+	}
+
+	if (dist < dist_max)
+		return;
+
+	/* Still out of range/proximity */
+	if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE) ||
+	    tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
+	    return;
+
+	/* Tool entered prox but is outside of permitted range */
+	if (tablet_has_status(tablet,
+			      TABLET_TOOL_ENTERING_PROXIMITY)) {
+		tablet_set_status(tablet,
+				  TABLET_TOOL_OUT_OF_RANGE);
+		tablet_unset_status(tablet,
+				    TABLET_TOOL_ENTERING_PROXIMITY);
+		return;
+	}
+
+	/* Tool was in prox and is now outside of range. Set leaving
+	 * proximity, on the next event it will be OUT_OF_PROXIMITY and thus
+	 * caught by the above conditions */
+	tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
+}
+
+static void
 tablet_flush(struct tablet_dispatch *tablet,
 	     struct evdev_device *device,
 	     uint64_t time)
@@ -1004,7 +1076,12 @@ tablet_flush(struct tablet_dispatch *tablet,
 				tablet->current_tool_id,
 				tablet->current_tool_serial);
 
-	if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
+	if (tool->type == LIBINPUT_TABLET_TOOL_TYPE_MOUSE ||
+	    tool->type == LIBINPUT_TABLET_TOOL_TYPE_LENS)
+		tablet_update_proximity_state(tablet, device);
+
+	if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY) ||
+	    tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE))
 		return;
 
 	if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
@@ -1197,6 +1274,29 @@ tablet_init_calibration(struct tablet_dispatch *tablet,
 		evdev_init_calibration(device, &tablet->base);
 }
 
+static void
+tablet_init_proximity_threshold(struct tablet_dispatch *tablet,
+				struct evdev_device *device)
+{
+	/* This rules out most of the bamboos and other devices, we're
+	 * pretty much down to
+	 */
+	if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_MOUSE) &&
+	    !libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_LENS))
+		return;
+
+	/* 42 is the default proximity threshold the xf86-input-wacom driver
+	 * uses for Intuos/Cintiq models. Graphire models have a threshold
+	 * of 10 but since they haven't been manufactured in ages and the
+	 * intersection of users having a graphire, running libinput and
+	 * wanting to use the mouse/lens cursor tool is small enough to not
+	 * worry about it for now. If we need to, we can introduce a udev
+	 * property later.
+	 */
+
+	tablet->cursor_proximity_threshold = 42;
+}
+
 static int
 tablet_init(struct tablet_dispatch *tablet,
 	    struct evdev_device *device)
@@ -1210,6 +1310,7 @@ tablet_init(struct tablet_dispatch *tablet,
 	list_init(&tablet->tool_list);
 
 	tablet_init_calibration(tablet, device);
+	tablet_init_proximity_threshold(tablet, device);
 
 	for (axis = LIBINPUT_TABLET_TOOL_AXIS_X;
 	     axis <= LIBINPUT_TABLET_TOOL_AXIS_MAX;
diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h
index 162b536..918bd51 100644
--- a/src/evdev-tablet.h
+++ b/src/evdev-tablet.h
@@ -41,6 +41,7 @@ enum tablet_status {
 	TABLET_TOOL_ENTERING_PROXIMITY = 1 << 6,
 	TABLET_TOOL_ENTERING_CONTACT = 1 << 7,
 	TABLET_TOOL_LEAVING_CONTACT = 1 << 8,
+	TABLET_TOOL_OUT_OF_RANGE = 1 << 9,
 };
 
 struct button_state {
@@ -65,6 +66,8 @@ struct tablet_dispatch {
 	enum libinput_tablet_tool_type current_tool_type;
 	uint32_t current_tool_id;
 	uint32_t current_tool_serial;
+
+	uint32_t cursor_proximity_threshold;
 };
 
 static inline enum libinput_tablet_tool_axis
diff --git a/test/litest.c b/test/litest.c
index 01d97d9..48291e5 100644
--- a/test/litest.c
+++ b/test/litest.c
@@ -2370,6 +2370,24 @@ litest_assert_tablet_button_event(struct libinput *li, unsigned int button,
 	libinput_event_destroy(event);
 }
 
+void litest_assert_tablet_proximity_event(struct libinput *li,
+					  enum libinput_tablet_tool_proximity_state state)
+{
+	struct libinput_event *event;
+	struct libinput_event_tablet_tool *tev;
+	enum libinput_event_type type = LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY;
+
+	litest_wait_for_event(li);
+	event = libinput_get_event(li);
+
+	litest_assert_notnull(event);
+	litest_assert_int_eq(libinput_event_get_type(event), type);
+	tev = libinput_event_get_tablet_tool_event(event);
+	litest_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev),
+			     state);
+	libinput_event_destroy(event);
+}
+
 void
 litest_assert_scroll(struct libinput *li,
 		     enum libinput_pointer_axis axis,
diff --git a/test/litest.h b/test/litest.h
index ed9ddfe..f1ba4ae 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -419,7 +419,8 @@ void litest_assert_only_typed_events(struct libinput *li,
 void litest_assert_tablet_button_event(struct libinput *li,
 				       unsigned int button,
 				       enum libinput_button_state state);
-
+void litest_assert_tablet_proximity_event(struct libinput *li,
+					  enum libinput_tablet_tool_proximity_state state);
 struct libevdev_uinput * litest_create_uinput_device(const char *name,
 						     struct input_id *id,
 						     ...);
diff --git a/test/tablet.c b/test/tablet.c
index c82be49..ba5b1ea 100644
--- a/test/tablet.c
+++ b/test/tablet.c
@@ -854,6 +854,222 @@ START_TEST(proximity_has_axes)
 }
 END_TEST
 
+START_TEST(proximity_range_enter)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_DISTANCE, 60 },
+		{ -1, -1 }
+	};
+
+	if (!libevdev_has_event_code(dev->evdev,
+				    EV_KEY,
+				    BTN_TOOL_MOUSE))
+		return;
+
+	litest_drain_events(li);
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+	litest_pop_event_frame(dev);
+	litest_assert_empty_queue(li);
+
+	axes[0].value = 20;
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+	axes[0].value = 60;
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+
+	litest_tablet_proximity_out(dev);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_in_out)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_DISTANCE, 20 },
+		{ -1, -1 }
+	};
+
+	if (!libevdev_has_event_code(dev->evdev,
+				    EV_KEY,
+				    BTN_TOOL_MOUSE))
+		return;
+
+	litest_drain_events(li);
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+	litest_pop_event_frame(dev);
+	libinput_dispatch(li);
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+
+	axes[0].value = 60;
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+
+	litest_tablet_motion(dev, 30, 30, axes);
+	litest_assert_empty_queue(li);
+
+	axes[0].value = 20;
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+
+	litest_tablet_proximity_out(dev);
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_button_click)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_DISTANCE, 60 },
+		{ -1, -1 }
+	};
+
+	if (!libevdev_has_event_code(dev->evdev,
+				    EV_KEY,
+				    BTN_TOOL_MOUSE))
+		return;
+
+	litest_drain_events(li);
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+	litest_pop_event_frame(dev);
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_STYLUS, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	libinput_dispatch(li);
+	litest_event(dev, EV_KEY, BTN_STYLUS, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	libinput_dispatch(li);
+
+	litest_tablet_proximity_out(dev);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_button_press)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_DISTANCE, 20 },
+		{ -1, -1 }
+	};
+
+	if (!libevdev_has_event_code(dev->evdev,
+				    EV_KEY,
+				    BTN_TOOL_MOUSE))
+		return;
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+	litest_pop_event_frame(dev);
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_STYLUS, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	libinput_dispatch(li);
+
+	litest_assert_tablet_button_event(li,
+					  BTN_STYLUS,
+					  LIBINPUT_BUTTON_STATE_PRESSED);
+
+	axes[0].value = 60;
+	litest_tablet_motion(dev, 15, 15, axes);
+	libinput_dispatch(li);
+
+	/* expect fake button release */
+	litest_assert_tablet_button_event(li,
+					  BTN_STYLUS,
+					  LIBINPUT_BUTTON_STATE_RELEASED);
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+
+	litest_event(dev, EV_KEY, BTN_STYLUS, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	libinput_dispatch(li);
+
+	litest_tablet_proximity_out(dev);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(proximity_range_button_release)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_DISTANCE, 60 },
+		{ -1, -1 }
+	};
+
+	if (!libevdev_has_event_code(dev->evdev,
+				    EV_KEY,
+				    BTN_TOOL_MOUSE))
+		return;
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+	litest_pop_event_frame(dev);
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_STYLUS, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_assert_empty_queue(li);
+
+	axes[0].value = 20;
+	litest_tablet_motion(dev, 15, 15, axes);
+	libinput_dispatch(li);
+
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_IN);
+	/* expect fake button press */
+	litest_assert_tablet_button_event(li,
+					  BTN_STYLUS,
+					  LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_empty_queue(li);
+
+	litest_event(dev, EV_KEY, BTN_STYLUS, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	libinput_dispatch(li);
+	litest_assert_tablet_button_event(li,
+					  BTN_STYLUS,
+					  LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_tablet_proximity_out(dev);
+	litest_assert_tablet_proximity_event(li,
+					     LIBINPUT_TABLET_TOOL_PROXIMITY_OUT);
+}
+END_TEST
+
 START_TEST(motion)
 {
 	struct litest_device *dev = litest_current_device();
@@ -2695,6 +2911,11 @@ litest_setup_tests(void)
 	litest_add("tablet:proximity", proximity_in_out, LITEST_TABLET, LITEST_ANY);
 	litest_add("tablet:proximity", proximity_has_axes, LITEST_TABLET, LITEST_ANY);
 	litest_add("tablet:proximity", bad_distance_events, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+	litest_add("tablet:proximity", proximity_range_enter, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+	litest_add("tablet:proximity", proximity_range_in_out, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+	litest_add("tablet:proximity", proximity_range_button_click, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+	litest_add("tablet:proximity", proximity_range_button_press, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
+	litest_add("tablet:proximity", proximity_range_button_release, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
 	litest_add("tablet:tip", tip_down_up, LITEST_TABLET, LITEST_ANY);
 	litest_add("tablet:tip", tip_down_prox_in, LITEST_TABLET, LITEST_ANY);
 	litest_add("tablet:tip", tip_up_prox_out, LITEST_TABLET, LITEST_ANY);
-- 
2.5.0



More information about the wayland-devel mailing list