Commit 1962c6f2 authored by Peter Hutterer's avatar Peter Hutterer

tablet: add a quirk for the HUION PenTablet that doesn't send proximity out events

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's avatarPeter Hutterer <peter.hutterer@who-t.net>
Yay-by: Benjamin Tissoires's avatarBenjamin Tissoires <benjamin.tissoires@gmail.com>
parent 34f28a32
......@@ -162,4 +162,23 @@ model quirks hwdb for instructions.
This property must not be used for any other purpose, no specific behavior
is guaranteed.
@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.
*/
......@@ -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;
}
......
......@@ -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*
......
......@@ -2740,6 +2740,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 },
......
......@@ -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 {
......
......@@ -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,
};
......@@ -3339,6 +3339,12 @@ litest_timeout_trackpoint(void)
msleep(320);
}
void
litest_timeout_tablet_proxout(void)
{
msleep(70);
}
void
litest_push_event_frame(struct litest_device *dev)
{
......
......@@ -729,6 +729,9 @@ litest_timeout_gesture_scroll(void);
void
litest_timeout_trackpoint(void);
void
litest_timeout_tablet_proxout(void);
void
litest_push_event_frame(struct litest_device *dev);
......
......@@ -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);
}
......@@ -162,6 +162,25 @@ libinput:name:SYN1EDE:00 06CB:7442:dmi:*svnHewlett-Packard:pnHPStreamNotebookPC1
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
##########################################
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment