Commit 2f481ba4 authored by Peter Hutterer's avatar Peter Hutterer

tablet: handle custom proximity handling

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.
Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>

[rebased, tests updated for new axis percentage behavior (8d76734f)]
Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
parent c124209c
......@@ -68,6 +68,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)
......@@ -1063,6 +1079,63 @@ tablet_mark_all_axes_changed(struct tablet_dispatch *tablet,
sizeof(tablet->changed_axes));
}
static void
tablet_update_proximity_state(struct tablet_dispatch *tablet,
struct evdev_device *device,
struct libinput_tablet_tool *tool)
{
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, tool);
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,
......@@ -1074,7 +1147,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, tool);
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)) {
......@@ -1271,6 +1349,30 @@ 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.
*
* Value is in device coordinates.
*/
tablet->cursor_proximity_threshold = 42;
}
static int
tablet_init(struct tablet_dispatch *tablet,
struct evdev_device *device)
......@@ -1284,6 +1386,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;
......
......@@ -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
......
......@@ -2403,6 +2403,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,
......
......@@ -457,7 +457,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,
...);
......
......@@ -811,6 +811,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, 90 },
{ -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 = 90;
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 = 90;
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, 90 },
{ -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 = 90;
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, 90 },
{ -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();
......@@ -2872,6 +3088,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);
......
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