tablet: add tilt-based touch arbitration for screen tablets

If the tilt angle on tip down is not 0 set the touch arbitration to a
rectangle around the assumed position of the hand. This assumed position is
right of the tip for a rightwards tilt and left of the tip for a leftwards
tilt (i.e. left-handed mode). The rectangle is 200x200mm with a 20x50mm
NW of the tip or NE for left-handed. In other words, if the period below is
the tip, the rectangle looks like this:

    +-----------+                          +-----------+
    | . 	| <- for rightwards tilt   |         . |
    |           |                          |           |
    |           |                          |           |
    |           |    for leftwards tilt -> |           |
    +-----------+                          +-----------+

Touches within that rectangle are canceled, new touches are ignored. As the
tip moves around the rectangle is updated but touches are only cancelled on
the original tip down. While the tip is down, new touches are ignored in the
exclusion area but pre-existing touches are not cancelled.

This is currently only implemented in the fallback interface, i.e. it will
only work for Cintiqs.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2018-09-28 11:47:32 +10:00
parent f325ca921d
commit f612c1ef0c
6 changed files with 225 additions and 23 deletions

View file

@ -1218,6 +1218,24 @@ fallback_interface_sync_initial_state(struct evdev_device *device,
}
}
static void
fallback_interface_update_rect(struct evdev_dispatch *evdev_dispatch,
struct evdev_device *device,
const struct phys_rect *phys_rect,
uint64_t time)
{
struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
struct device_coord_rect rect = {0};
assert(phys_rect);
/* Existing touches do not change, we just update the rect and only
* new touches in these areas will be ignored. If you want to paint
* over your finger, be my guest. */
rect = evdev_phys_rect_to_units(device, phys_rect);
dispatch->arbitration.rect = rect;
}
static void
fallback_interface_toggle_touch(struct evdev_dispatch *evdev_dispatch,
struct evdev_device *device,
@ -1453,7 +1471,7 @@ struct evdev_dispatch_interface fallback_interface = {
.device_resumed = fallback_interface_device_added, /* treat as add */
.post_added = fallback_interface_sync_initial_state,
.touch_arbitration_toggle = fallback_interface_toggle_touch,
.touch_arbitration_update_rect = NULL,
.touch_arbitration_update_rect = fallback_interface_update_rect,
.get_switch_state = fallback_interface_get_switch_state,
};

View file

@ -1376,6 +1376,69 @@ tablet_update_proximity_state(struct tablet_dispatch *tablet,
tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
}
static struct phys_rect
tablet_calculate_arbitration_rect(struct tablet_dispatch *tablet)
{
struct evdev_device *device = tablet->device;
struct phys_rect r = {0};
struct phys_coords mm;
mm = evdev_device_units_to_mm(device, &tablet->axes.point);
/* The rect we disable is 20mm left of the tip, 50mm north of the
* tip, and 200x200mm large.
* If the stylus is tilted left (tip further right than the eraser
* end) assume left-handed mode.
*
* Obviously if we'd run out of the boundaries, we rescale the rect
* accordingly.
*/
if (tablet->axes.tilt.x > 0) {
r.x = mm.x - 20;
r.w = 200;
} else {
r.x = mm.x + 20;
r.w = 200;
r.x -= r.w;
}
if (r.x < 0) {
r.w -= r.x;
r.x = 0;
}
r.y = mm.y - 50;
r.h = 200;
if (r.y < 0) {
r.h -= r.y;
r.y = 0;
}
return r;
}
static inline void
tablet_update_touch_device_rect(struct tablet_dispatch *tablet,
const struct tablet_axes *axes,
uint64_t time)
{
struct evdev_dispatch *dispatch;
struct phys_rect rect = {0};
if (tablet->touch_device == NULL ||
tablet->arbitration != ARBITRATION_IGNORE_RECT)
return;
rect = tablet_calculate_arbitration_rect(tablet);
dispatch = tablet->touch_device->dispatch;
if (dispatch->interface->touch_arbitration_update_rect)
dispatch->interface->touch_arbitration_update_rect(dispatch,
tablet->touch_device,
&rect,
time);
}
static inline bool
tablet_send_proximity_in(struct tablet_dispatch *tablet,
struct libinput_tablet_tool *tool,
@ -1549,7 +1612,8 @@ tablet_send_events(struct tablet_dispatch *tablet,
* update */
tablet_unset_status(tablet, TABLET_AXES_UPDATED);
} else {
tablet_check_notify_axes(tablet, device, tool, &axes, time);
if (tablet_check_notify_axes(tablet, device, tool, &axes, time))
tablet_update_touch_device_rect(tablet, &axes, time);
}
assert(tablet->axes.delta.x == 0);
@ -1618,27 +1682,25 @@ tablet_flush(struct tablet_dispatch *tablet,
}
static inline void
tablet_set_touch_device_enabled(struct evdev_device *touch_device,
bool enable,
tablet_set_touch_device_enabled(struct tablet_dispatch *tablet,
enum evdev_arbitration_state which,
const struct phys_rect *rect,
uint64_t time)
{
struct evdev_device *touch_device = tablet->touch_device;
struct evdev_dispatch *dispatch;
enum evdev_arbitration_state which;
if (touch_device == NULL)
return;
if (enable)
which = ARBITRATION_NOT_ACTIVE;
else
which = ARBITRATION_IGNORE_ALL;
tablet->arbitration = which;
dispatch = touch_device->dispatch;
if (dispatch->interface->touch_arbitration_toggle)
dispatch->interface->touch_arbitration_toggle(dispatch,
touch_device,
which,
NULL,
rect,
time);
}
@ -1647,18 +1709,33 @@ tablet_toggle_touch_device(struct tablet_dispatch *tablet,
struct evdev_device *tablet_device,
uint64_t time)
{
bool enable_events;
enum evdev_arbitration_state which;
struct phys_rect r = {0};
struct phys_rect *rect = NULL;
enable_events = tablet_has_status(tablet,
TABLET_TOOL_OUT_OF_RANGE) ||
tablet_has_status(tablet, TABLET_NONE) ||
tablet_has_status(tablet,
TABLET_TOOL_LEAVING_PROXIMITY) ||
tablet_has_status(tablet,
TABLET_TOOL_OUT_OF_PROXIMITY);
if (tablet_has_status(tablet,
TABLET_TOOL_OUT_OF_RANGE) ||
tablet_has_status(tablet, TABLET_NONE) ||
tablet_has_status(tablet,
TABLET_TOOL_LEAVING_PROXIMITY) ||
tablet_has_status(tablet,
TABLET_TOOL_OUT_OF_PROXIMITY)) {
which = ARBITRATION_NOT_ACTIVE;
} else if (tablet->axes.tilt.x == 0) {
which = ARBITRATION_IGNORE_ALL;
} else if (tablet->arbitration != ARBITRATION_IGNORE_RECT) {
/* This enables rect-based arbitration, updates are sent
* elsewhere */
r = tablet_calculate_arbitration_rect(tablet);
rect = &r;
which = ARBITRATION_IGNORE_RECT;
} else {
return;
}
tablet_set_touch_device_enabled(tablet->touch_device,
enable_events,
tablet_set_touch_device_enabled(tablet,
which,
rect,
time);
}
@ -1837,7 +1914,10 @@ tablet_suspend(struct evdev_dispatch *dispatch,
struct libinput *li = tablet_libinput_context(tablet);
uint64_t now = libinput_now(li);
tablet_set_touch_device_enabled(tablet->touch_device, true, now);
tablet_set_touch_device_enabled(tablet,
ARBITRATION_NOT_ACTIVE,
NULL,
now);
if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) {
tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);

View file

@ -84,6 +84,7 @@ struct tablet_dispatch {
/* The paired touch device on devices with both pen & touch */
struct evdev_device *touch_device;
enum evdev_arbitration_state arbitration;
struct {
bool need_to_force_prox_out;

View file

@ -1884,6 +1884,23 @@ litest_slot_start(struct litest_device *d,
d->semi_mt.touches[slot].y = y;
}
void
litest_touch_sequence(struct litest_device *d,
unsigned int slot,
double x_from,
double y_from,
double x_to,
double y_to,
int steps)
{
litest_touch_down(d, slot, x_from, y_from);
litest_touch_move_to(d, slot,
x_from, y_from,
x_to, y_to,
steps);
litest_touch_up(d, slot);
}
void
litest_touch_down(struct litest_device *d,
unsigned int slot,

View file

@ -530,6 +530,15 @@ litest_touch_move_extended(struct litest_device *d,
double y,
struct axis_replacement *axes);
void
litest_touch_sequence(struct litest_device *d,
unsigned int slot,
double x1,
double y1,
double x2,
double y2,
int steps);
void
litest_touch_down(struct litest_device *d,
unsigned int slot,

View file

@ -4247,6 +4247,8 @@ START_TEST(touch_arbitration)
struct litest_device *finger;
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_TILT_X, 80 },
{ ABS_TILT_Y, 80 },
{ ABS_DISTANCE, 10 },
{ ABS_PRESSURE, 0 },
{ -1, -1 }
@ -4267,8 +4269,8 @@ START_TEST(touch_arbitration)
litest_tablet_motion(dev, 20, 40, axes);
litest_drain_events(li);
litest_touch_down(finger, 0, 30, 30);
litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10);
litest_touch_down(finger, 0, 21, 41);
litest_touch_move_to(finger, 0, 21, 41, 80, 80, 10);
litest_assert_empty_queue(li);
litest_tablet_motion(dev, 10, 10, axes);
@ -4306,6 +4308,80 @@ START_TEST(touch_arbitration)
}
END_TEST
START_TEST(touch_arbitration_outside_rect)
{
struct litest_device *dev = litest_current_device();
enum litest_device_type other;
struct litest_device *finger;
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_TILT_X, 80 },
{ ABS_TILT_Y, 80 },
{ ABS_DISTANCE, 10 },
{ ABS_PRESSURE, 0 },
{ -1, -1 }
};
double x, y;
bool is_touchpad;
other = paired_device(dev);
if (other == LITEST_NO_DEVICE)
return;
finger = litest_add_device(li, other);
litest_drain_events(li);
is_touchpad = !libevdev_has_property(finger->evdev, INPUT_PROP_DIRECT);
if (is_touchpad)
return;
x = 20;
y = 45;
litest_tablet_proximity_in(dev, x, y - 1, axes);
litest_drain_events(li);
/* these are in percent, but the pen/finger have different
* resolution and the rect works in mm, so the numbers below are
* hand-picked for the test device */
litest_tablet_motion(dev, x, y, axes);
litest_drain_events(li);
/* left of rect */
litest_touch_sequence(finger, 0, x - 10, y + 2, x - 10, y + 20, 30);
libinput_dispatch(li);
litest_assert_touch_sequence(li);
/* above rect */
litest_touch_sequence(finger, 0, x + 2, y - 35, x + 20, y - 10, 30);
libinput_dispatch(li);
litest_assert_touch_sequence(li);
/* right of rect */
litest_touch_sequence(finger, 0, x + 80, y + 2, x + 20, y + 10, 30);
libinput_dispatch(li);
litest_assert_touch_sequence(li);
#if 0
/* This *should* work but the Cintiq test devices is <200mm
high, so we can't test for anything below the tip */
x = 20;
y = 10;
litest_tablet_proximity_out(dev);
litest_tablet_motion(dev, x, y, axes);
litest_tablet_proximity_in(dev, x, y - 1, axes);
litest_drain_events(li);
/* below rect */
litest_touch_sequence(finger, 0, x + 2, y + 80, x + 20, y + 20, 30);
libinput_dispatch(li);
litest_assert_touch_sequence(li);
#endif
litest_delete_device(finger);
}
END_TEST
START_TEST(touch_arbitration_stop_touch)
{
struct litest_device *dev = litest_current_device();
@ -4913,6 +4989,7 @@ TEST_COLLECTION(tablet)
litest_add("tablet:touch-arbitration", touch_arbitration_remove_tablet, LITEST_TOUCH, LITEST_ANY);
litest_add("tablet:touch-arbitration", touch_arbitration_keep_ignoring, LITEST_TABLET, LITEST_ANY);
litest_add("tablet:touch-arbitration", touch_arbitration_late_touch_lift, LITEST_TABLET, LITEST_ANY);
litest_add("tablet:touch-arbitration", touch_arbitration_outside_rect, LITEST_TABLET | LITEST_DIRECT, LITEST_ANY);
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);