[PATCH libinput 1/2] tablet: add a quirk for the HUION PenTablet that doesn't send proximity out events
Peter Hutterer
peter.hutterer at who-t.net
Thu Sep 14 01:04:47 UTC 2017
Could be fixed in the kernel, but these tablets are effectively abandoned and
fixing them is a one-by-one issue. Let's put the infrastructure in place to
have this fixed once for this type of device and move on.
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
doc/device-configuration-via-udev.dox | 19 +++++
src/evdev-tablet.c | 113 +++++++++++++++++++++++++++++
src/evdev-tablet.h | 7 ++
src/evdev.c | 1 +
src/evdev.h | 1 +
test/litest-device-huion-pentablet.c | 15 ++--
test/litest.c | 6 ++
test/litest.h | 3 +
test/test-tablet.c | 132 ++++++++++++++++++++++++++++++++++
udev/90-libinput-model-quirks.hwdb | 19 +++++
10 files changed, 312 insertions(+), 4 deletions(-)
diff --git a/doc/device-configuration-via-udev.dox b/doc/device-configuration-via-udev.dox
index 2ebfa321..3050cd80 100644
--- a/doc/device-configuration-via-udev.dox
+++ b/doc/device-configuration-via-udev.dox
@@ -162,4 +162,23 @@ model quirks hwdb for instructions.
This property must not be used for any other purpose, no specific behavior
is guaranteed.
+ at subsection model_specific_configuration_huion_tablets Graphics tablets without BTN_TOOL_PEN proximity events
+
+On graphics tablets, the <b>BTN_TOOL_PEN</b> bit signals that the pen is in
+detectable range and will send events. When the pen leaves the sensor range,
+the bit must be unset to signal that the tablet is out of proximity again.
+Some HUION PenTablet devices are buggy and do not send this event. To a
+caller, it thus looks like the pen is constantly in proximity. This causes
+unexpected behavior in applications that rely on tablet device proximity.
+
+The property <b>LIBINPUT_MODEL_TABLET_NO_PROXIMITY_OUT</b> may be set
+by a user in a local hwdb file. This property designates the tablet
+to be buggy and that libinput should work around this bug.
+
+Many of the affected tablets cannot be detected automatically by libinput
+because HUION tablets reuse USB IDs. Local configuration is required to set
+this property. Refer to the libinput model quirks hwdb for instructions.
+
+This property must not be used for any other purpose, no specific behavior
+is guaranteed.
*/
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 77540496..057b498c 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -32,6 +32,10 @@
#include <libwacom/libwacom.h>
#endif
+/* The tablet sends events every ~2ms , 50ms should be plenty enough to
+ detect out-of-range */
+#define FORCED_PROXOUT_TIMEOUT ms2us(50)
+
#define tablet_set_status(tablet_,s_) (tablet_)->status |= (s_)
#define tablet_unset_status(tablet_,s_) (tablet_)->status &= ~(s_)
#define tablet_has_status(tablet_,s_) (!!((tablet_)->status & (s_)))
@@ -1633,6 +1637,97 @@ tablet_reset_state(struct tablet_dispatch *tablet)
sizeof(tablet->button_state));
}
+static inline void
+tablet_proximity_out_quirk_set_timer(struct tablet_dispatch *tablet,
+ uint64_t time)
+{
+ libinput_timer_set(&tablet->quirks.prox_out_timer,
+ time + FORCED_PROXOUT_TIMEOUT);
+}
+
+static void
+tablet_proximity_out_quirk_timer_func(uint64_t now, void *data)
+{
+ struct tablet_dispatch *tablet = data;
+ struct input_event events[2] = {
+ { .time = us2tv(now),
+ .type = EV_KEY,
+ .code = BTN_TOOL_PEN,
+ .value = 0 },
+ { .time = us2tv(now),
+ .type = EV_SYN,
+ .code = SYN_REPORT,
+ .value = 0 },
+ };
+ struct input_event *e;
+
+ if (tablet->quirks.last_event_time > now - FORCED_PROXOUT_TIMEOUT) {
+ tablet_proximity_out_quirk_set_timer(tablet,
+ tablet->quirks.last_event_time);
+ return;
+ }
+
+ ARRAY_FOR_EACH(events, e) {
+ tablet->base.interface->process(&tablet->base,
+ tablet->device,
+ e,
+ now);
+ }
+
+ tablet->quirks.proximity_out_forced = true;
+}
+
+/**
+ * Handling for the proximity out workaround. Some tablets only send
+ * BTN_TOOL_PEN on the very first event, then leave it set even when the pen
+ * leaves the detectable range. To libinput this looks like we always have
+ * the pen in proximity.
+ *
+ * To avoid this, we set a timer on BTN_TOOL_PEN in. We expect the tablet to
+ * continuously send events, and while it's doing so we keep updating the
+ * timer. Once we go Xms without an event we assume proximity out and inject
+ * a BTN_TOOL_PEN event into the sequence through the timer func.
+ *
+ * We need to remember that we did that, on the first event after the
+ * timeout we need to inject a BTN_TOOL_PEN event again to force proximity
+ * in.
+ */
+static inline void
+tablet_proximity_out_quirk_update(struct tablet_dispatch *tablet,
+ struct evdev_device *device,
+ struct input_event *e,
+ uint64_t time)
+{
+ if (!tablet->quirks.need_to_force_prox_out)
+ return;
+
+ if (e->type == EV_SYN) {
+ /* If the timer function forced prox out before,
+ fake a BTN_TOOL_PEN event */
+ if (tablet->quirks.proximity_out_forced) {
+
+ struct input_event fake_event = {
+ .time = us2tv(time),
+ .type = EV_KEY,
+ .code = BTN_TOOL_PEN,
+ .value = 1,
+ };
+
+ tablet->base.interface->process(&tablet->base,
+ device,
+ &fake_event,
+ time);
+ tablet->quirks.proximity_out_forced = false;
+ }
+ tablet->quirks.last_event_time = time;
+ } else if (e->type == EV_KEY && e->code == BTN_TOOL_PEN) {
+ if (e->value)
+ tablet_proximity_out_quirk_set_timer(tablet, time);
+ else
+ libinput_timer_cancel(&tablet->quirks.prox_out_timer);
+ }
+}
+
static void
tablet_process(struct evdev_dispatch *dispatch,
struct evdev_device *device,
@@ -1641,6 +1736,9 @@ tablet_process(struct evdev_dispatch *dispatch,
{
struct tablet_dispatch *tablet = tablet_dispatch(dispatch);
+ /* Warning: this may inject events */
+ tablet_proximity_out_quirk_update(tablet, device, e, time);
+
switch (e->type) {
case EV_ABS:
tablet_process_absolute(tablet, device, e, time);
@@ -1683,6 +1781,9 @@ tablet_destroy(struct evdev_dispatch *dispatch)
struct tablet_dispatch *tablet = tablet_dispatch(dispatch);
struct libinput_tablet_tool *tool, *tmp;
+ libinput_timer_cancel(&tablet->quirks.prox_out_timer);
+ libinput_timer_destroy(&tablet->quirks.prox_out_timer);
+
list_for_each_safe(tool, tmp, &tablet->tool_list, link) {
libinput_tablet_tool_unref(tool);
}
@@ -1722,6 +1823,7 @@ tablet_check_initial_proximity(struct evdev_device *device,
struct evdev_dispatch *dispatch)
{
struct tablet_dispatch *tablet = tablet_dispatch(dispatch);
+ struct libinput *li = tablet_libinput_context(tablet);
bool tool_in_prox = false;
int code, state;
enum libinput_tablet_tool_type tool;
@@ -1743,6 +1845,8 @@ tablet_check_initial_proximity(struct evdev_device *device,
return;
tablet_update_tool(tablet, device, tool, state);
+ if (tablet->quirks.need_to_force_prox_out)
+ tablet_proximity_out_quirk_set_timer(tablet, libinput_now(li));
tablet->current_tool_id =
libevdev_get_event_value(device->evdev,
@@ -1922,6 +2026,15 @@ tablet_init(struct tablet_dispatch *tablet,
tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY);
+ if (device->model_flags & EVDEV_MODEL_TABLET_NO_PROXIMITY_OUT) {
+ tablet->quirks.need_to_force_prox_out = true;
+ libinput_timer_init(&tablet->quirks.prox_out_timer,
+ tablet_libinput_context(tablet),
+ "proxout",
+ tablet_proximity_out_quirk_timer_func,
+ tablet);
+ }
+
return 0;
}
diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h
index 7d17e366..ba05e88c 100644
--- a/src/evdev-tablet.h
+++ b/src/evdev-tablet.h
@@ -83,6 +83,13 @@ struct tablet_dispatch {
/* The paired touch device on devices with both pen & touch */
struct evdev_device *touch_device;
+
+ struct {
+ bool need_to_force_prox_out;
+ struct libinput_timer prox_out_timer;
+ bool proximity_out_forced;
+ uint64_t last_event_time;
+ } quirks;
};
static inline struct tablet_dispatch*
diff --git a/src/evdev.c b/src/evdev.c
index 46f8ad57..a9ffd9de 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -2742,6 +2742,7 @@ evdev_read_model_flags(struct evdev_device *device)
MODEL(HP_PAVILION_DM4_TOUCHPAD),
MODEL(APPLE_TOUCHPAD_ONEBUTTON),
MODEL(LOGITECH_MARBLE_MOUSE),
+ MODEL(TABLET_NO_PROXIMITY_OUT),
#undef MODEL
{ "ID_INPUT_TRACKBALL", EVDEV_MODEL_TRACKBALL },
{ NULL, EVDEV_MODEL_DEFAULT },
diff --git a/src/evdev.h b/src/evdev.h
index 5192927c..bb4c3a03 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -127,6 +127,7 @@ enum evdev_device_model {
EVDEV_MODEL_HP_PAVILION_DM4_TOUCHPAD = (1 << 24),
EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON = (1 << 25),
EVDEV_MODEL_LOGITECH_MARBLE_MOUSE = (1 << 26),
+ EVDEV_MODEL_TABLET_NO_PROXIMITY_OUT = (1 << 27),
};
enum evdev_button_scroll_state {
diff --git a/test/litest-device-huion-pentablet.c b/test/litest-device-huion-pentablet.c
index e37d5c09..dbbdcb01 100644
--- a/test/litest-device-huion-pentablet.c
+++ b/test/litest-device-huion-pentablet.c
@@ -42,10 +42,6 @@ static struct input_event proximity_in[] = {
};
static struct input_event proximity_out[] = {
- { .type = EV_ABS, .code = ABS_X, .value = 0 },
- { .type = EV_ABS, .code = ABS_Y, .value = 0 },
- { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 },
- { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
{ .type = -1, .code = -1 },
};
@@ -98,6 +94,16 @@ static int events[] = {
-1, -1,
};
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"huion_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"huion_end\"\n"
+"ENV{ID_INPUT_TABLET}==\"\", GOTO=\"huion_end\"\n"
+"\n"
+"ATTRS{name}==\"litest HUION PenTablet Pen\","
+" ENV{LIBINPUT_MODEL_TABLET_NO_PROXIMITY_OUT}=\"1\"\n"
+"\n"
+"LABEL=\"huion_end\"";
+
struct litest_test_device litest_huion_tablet_device = {
.type = LITEST_HUION_TABLET,
.features = LITEST_TABLET,
@@ -109,4 +115,5 @@ struct litest_test_device litest_huion_tablet_device = {
.id = &input_id,
.events = events,
.absinfo = absinfo,
+ .udev_rule = udev_rule,
};
diff --git a/test/litest.c b/test/litest.c
index 1f238431..868dfb69 100644
--- a/test/litest.c
+++ b/test/litest.c
@@ -3340,6 +3340,12 @@ litest_timeout_trackpoint(void)
}
void
+litest_timeout_tablet_proxout(void)
+{
+ msleep(70);
+}
+
+void
litest_push_event_frame(struct litest_device *dev)
{
litest_assert(dev->skip_ev_syn >= 0);
diff --git a/test/litest.h b/test/litest.h
index 679406dc..8f3c3bc9 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -730,6 +730,9 @@ void
litest_timeout_trackpoint(void);
void
+litest_timeout_tablet_proxout(void);
+
+void
litest_push_event_frame(struct litest_device *dev);
void
diff --git a/test/test-tablet.c b/test/test-tablet.c
index a9ad9592..4a36c79a 100644
--- a/test/test-tablet.c
+++ b/test/test-tablet.c
@@ -250,6 +250,22 @@ START_TEST(tip_down_prox_in)
}
END_TEST
+static inline bool
+tablet_has_proxout_quirk(struct litest_device *dev)
+{
+ struct udev_device *udev_device;
+ bool has_quirk;
+
+ udev_device = libinput_device_get_udev_device(dev->libinput_device);
+
+ has_quirk = !!udev_device_get_property_value(udev_device,
+ "LIBINPUT_MODEL_TABLET_NO_PROXIMITY_OUT");
+
+ udev_device_unref(udev_device);
+
+ return has_quirk;
+}
+
START_TEST(tip_up_prox_out)
{
struct litest_device *dev = litest_current_device();
@@ -262,6 +278,9 @@ START_TEST(tip_up_prox_out)
{ -1, -1 }
};
+ if (tablet_has_proxout_quirk(dev))
+ return;
+
litest_tablet_proximity_in(dev, 10, 10, axes);
litest_event(dev, EV_KEY, BTN_TOUCH, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
@@ -616,6 +635,9 @@ START_TEST(tip_state_proximity)
litest_tablet_proximity_out(dev);
libinput_dispatch(li);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+
event = libinput_get_event(li);
tablet_event = litest_is_tablet_event(event,
LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
@@ -808,6 +830,9 @@ START_TEST(proximity_in_out)
litest_tablet_proximity_out(dev);
libinput_dispatch(li);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+
while ((event = libinput_get_event(li))) {
if (libinput_event_get_type(event) ==
LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY) {
@@ -917,7 +942,9 @@ START_TEST(proximity_out_clear_buttons)
litest_event(dev, EV_KEY, button, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_tablet_proximity_out(dev);
+ libinput_dispatch(li);
+ litest_timeout_tablet_proxout();
libinput_dispatch(li);
while ((event = libinput_get_event(li))) {
@@ -1042,6 +1069,9 @@ START_TEST(proximity_has_axes)
litest_tablet_proximity_out(dev);
libinput_dispatch(li);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+
event = libinput_get_event(li);
tablet_event = litest_is_tablet_event(event,
LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
@@ -2506,6 +2536,10 @@ START_TEST(tool_in_prox_before_start)
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_BUTTON);
litest_tablet_proximity_out(dev);
+ libinput_dispatch(li);
+
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
litest_wait_for_event_of_type(li,
LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY,
@@ -4314,6 +4348,101 @@ START_TEST(cintiq_touch_arbitration_remove_tablet)
}
END_TEST
+START_TEST(huion_static_btn_tool_pen)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ int i;
+
+ litest_drain_events(li);
+
+ litest_event(dev, EV_ABS, ABS_X, 20000);
+ litest_event(dev, EV_ABS, ABS_Y, 20000);
+ litest_event(dev, EV_ABS, ABS_PRESSURE, 100);
+ litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_drain_events(li);
+
+ for (i = 0; i < 10; i++) {
+ litest_event(dev, EV_ABS, ABS_X, 20000 + 10 * i);
+ litest_event(dev, EV_ABS, ABS_Y, 20000 - 10 * i);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ }
+ litest_assert_only_typed_events(li,
+ LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+
+ /* Wait past the timeout to expect a proximity out */
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
+ libinput_dispatch(li);
+
+ /* New events should fake a proximity in again */
+ litest_event(dev, EV_ABS, ABS_X, 20000);
+ litest_event(dev, EV_ABS, ABS_Y, 20000);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
+ libinput_dispatch(li);
+
+ for (i = 0; i < 10; i++) {
+ litest_event(dev, EV_ABS, ABS_X, 20000 + 10 * i);
+ litest_event(dev, EV_ABS, ABS_Y, 20000 - 10 * i);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ }
+ litest_assert_only_typed_events(li,
+ LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
+ libinput_dispatch(li);
+
+ /* New events, just to ensure cleanup paths are correct */
+ litest_event(dev, EV_ABS, ABS_X, 20000);
+ litest_event(dev, EV_ABS, ABS_Y, 20000);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+}
+END_TEST
+
+START_TEST(huion_static_btn_tool_pen_no_timeout_during_usage)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ int i;
+
+ litest_drain_events(li);
+
+ litest_event(dev, EV_ABS, ABS_X, 20000);
+ litest_event(dev, EV_ABS, ABS_Y, 20000);
+ litest_event(dev, EV_ABS, ABS_PRESSURE, 100);
+ litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_drain_events(li);
+
+ /* take longer than the no-activity timeout */
+ for (i = 0; i < 50; i++) {
+ litest_event(dev, EV_ABS, ABS_X, 20000 + 10 * i);
+ litest_event(dev, EV_ABS, ABS_Y, 20000 - 10 * i);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ msleep(5);
+ }
+ litest_assert_only_typed_events(li,
+ LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+ litest_assert_tablet_proximity_event(li,
+ LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
+ libinput_dispatch(li);
+}
+END_TEST
+
void
litest_setup_tests_tablet(void)
{
@@ -4409,4 +4538,7 @@ litest_setup_tests_tablet(void)
litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_suspend_touch_device, LITEST_WACOM_CINTIQ_13HDT_FINGER);
litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_remove_touch, LITEST_WACOM_CINTIQ_13HDT_PEN);
litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_remove_tablet, LITEST_WACOM_CINTIQ_13HDT_FINGER);
+
+ litest_add_for_device("tablet:quirks", huion_static_btn_tool_pen, LITEST_HUION_TABLET);
+ litest_add_for_device("tablet:quirks", huion_static_btn_tool_pen_no_timeout_during_usage, LITEST_HUION_TABLET);
}
diff --git a/udev/90-libinput-model-quirks.hwdb b/udev/90-libinput-model-quirks.hwdb
index 72fcdca1..867ffa53 100644
--- a/udev/90-libinput-model-quirks.hwdb
+++ b/udev/90-libinput-model-quirks.hwdb
@@ -163,6 +163,25 @@ libinput:name:AlpsPS/2 ALPS GlidePoint:dmi:*svnHP:pnHPZBookStudioG3:*
LIBINPUT_MODEL_HP_ZBOOK_STUDIO_G3=1
##########################################
+# HUION
+##########################################
+#
+# HUION PenTablet device. Some of these devices send a BTN_TOOL_PEN event
+# with value 1 on the first event received by the device but never send the
+# matching BTN_TOOL_PEN value 0 event. The device appears as if it was
+# permanently in proximity.
+#
+# If the tablet is affected by this bug, copy the two lines below into a new
+# file
+# /etc/udev/hwdb.d/90-libinput-huion-pentablet-proximity-quirk.hwdb, then run
+# sudo udevadm hwdb --update and reboot.
+#
+# Note that HUION re-uses USB IDs for its devices, not ever HUION tablet is
+# affected by this bug.
+#libinput:name:PenTablet Pen:dmi:*
+# LIBINPUT_MODEL_TABLET_NO_PROXIMITY_OUT=1
+
+##########################################
# LENOVO
##########################################
--
2.13.5
More information about the wayland-devel
mailing list