From da0fbb580f26aec37ccac9eeb8856e1717d29802 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 13 Jul 2018 16:08:54 +1000 Subject: [PATCH] fallback: add support for ABS_MT_TOOL_TYPE for touch screens Cancel any touches that trigger MT_TOOL_PALM. Fixes https://gitlab.freedesktop.org/libinput/libinput/issues/25 Signed-off-by: Peter Hutterer --- meson.build | 1 + src/evdev-fallback.c | 119 +++++++++++++++-- src/evdev-fallback.h | 10 +- src/libinput-private.h | 6 + src/libinput.c | 24 ++++ test/litest-device-touchscreen-mt-tool.c | 88 ++++++++++++ test/litest.h | 1 + test/test-touch.c | 222 +++++++++++++++++++++++++++++++ 8 files changed, 456 insertions(+), 15 deletions(-) create mode 100644 test/litest-device-touchscreen-mt-tool.c diff --git a/meson.build b/meson.build index 6f04e4d..3fc49bd 100644 --- a/meson.build +++ b/meson.build @@ -742,6 +742,7 @@ if get_option('tests') 'test/litest-device-touch-screen.c', 'test/litest-device-touchscreen-invalid-range.c', 'test/litest-device-touchscreen-fuzz.c', + 'test/litest-device-touchscreen-mt-tool.c', 'test/litest-device-uclogic-tablet.c', 'test/litest-device-wacom-bamboo-2fg-finger.c', 'test/litest-device-wacom-bamboo-2fg-pad.c', diff --git a/src/evdev-fallback.c b/src/evdev-fallback.c index 5c0777a..bf5a0f6 100644 --- a/src/evdev-fallback.c +++ b/src/evdev-fallback.c @@ -381,6 +381,34 @@ fallback_flush_mt_up(struct fallback_dispatch *dispatch, } static bool +fallback_flush_mt_cancel(struct fallback_dispatch *dispatch, + struct evdev_device *device, + int slot_idx, + uint64_t time) +{ + struct libinput_device *base = &device->base; + struct libinput_seat *seat = base->seat; + struct mt_slot *slot; + int seat_slot; + + if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) + return false; + + slot = &dispatch->mt.slots[slot_idx]; + seat_slot = slot->seat_slot; + slot->seat_slot = -1; + + if (seat_slot == -1) + return false; + + seat->slot_map &= ~(1 << seat_slot); + + touch_notify_touch_cancel(base, time, slot_idx, seat_slot); + + return true; +} + +static bool fallback_flush_st_down(struct fallback_dispatch *dispatch, struct evdev_device *device, uint64_t time) @@ -554,6 +582,21 @@ fallback_process_touch(struct fallback_dispatch *dispatch, if (e->value >= 0) { dispatch->pending_event |= EVDEV_ABSOLUTE_MT; slot->state = SLOT_STATE_BEGIN; + if (dispatch->mt.has_palm) { + int v; + v = libevdev_get_slot_value(device->evdev, + dispatch->mt.slot, + ABS_MT_TOOL_TYPE); + switch (v) { + case MT_TOOL_PALM: + /* new touch, no cancel needed */ + slot->palm_state = PALM_WAS_PALM; + break; + default: + slot->palm_state = PALM_NONE; + break; + } + } } else { dispatch->pending_event |= EVDEV_ABSOLUTE_MT; slot->state = SLOT_STATE_END; @@ -572,6 +615,26 @@ fallback_process_touch(struct fallback_dispatch *dispatch, dispatch->pending_event |= EVDEV_ABSOLUTE_MT; slot->dirty = true; break; + case ABS_MT_TOOL_TYPE: + /* The transitions matter - we (may) need to send a touch + * cancel event if we just switched to a palm touch. And the + * kernel may switch back to finger but we keep the touch as + * palm - but then we need to reset correctly on a new touch + * sequence. + */ + switch (e->value) { + case MT_TOOL_PALM: + if (slot->palm_state == PALM_NONE) + slot->palm_state = PALM_NEW; + break; + default: + if (slot->palm_state == PALM_IS_PALM) + slot->palm_state = PALM_WAS_PALM; + break; + } + dispatch->pending_event |= EVDEV_ABSOLUTE_MT; + slot->dirty = true; + break; } } @@ -794,25 +857,52 @@ fallback_flush_mt_events(struct fallback_dispatch *dispatch, if (!slot->dirty) continue; + slot->dirty = false; + + /* Any palm state other than PALM_NEW means we've either + * already cancelled the touch or the touch was never + * a finger anyway and we didn't send the being. + */ + if (slot->palm_state == PALM_NEW) { + if (slot->state != SLOT_STATE_BEGIN) + sent = fallback_flush_mt_cancel(dispatch, + device, + i, + time); + slot->palm_state = PALM_IS_PALM; + } else if (slot->palm_state == PALM_NONE) { + switch (slot->state) { + case SLOT_STATE_BEGIN: + sent = fallback_flush_mt_down(dispatch, + device, + i, + time); + break; + case SLOT_STATE_UPDATE: + sent = fallback_flush_mt_motion(dispatch, + device, + i, + time); + break; + case SLOT_STATE_END: + sent = fallback_flush_mt_up(dispatch, + device, + i, + time); + break; + case SLOT_STATE_NONE: + break; + } + } + + /* State machine continues independent of the palm state */ switch (slot->state) { case SLOT_STATE_BEGIN: - sent = fallback_flush_mt_down(dispatch, - device, - i, - time); slot->state = SLOT_STATE_UPDATE; break; case SLOT_STATE_UPDATE: - sent = fallback_flush_mt_motion(dispatch, - device, - i, - time); break; case SLOT_STATE_END: - sent = fallback_flush_mt_up(dispatch, - device, - i, - time); slot->state = SLOT_STATE_NONE; break; case SLOT_STATE_NONE: @@ -821,8 +911,6 @@ fallback_flush_mt_events(struct fallback_dispatch *dispatch, * in NONE state */ break; } - - slot->dirty = false; } return sent; @@ -1421,6 +1509,9 @@ fallback_dispatch_init_slots(struct fallback_dispatch *dispatch, dispatch->mt.slots = slots; dispatch->mt.slots_len = num_slots; dispatch->mt.slot = active_slot; + dispatch->mt.has_palm = libevdev_has_event_code(evdev, + EV_ABS, + ABS_MT_TOOL_TYPE); if (device->abs.absinfo_x->fuzz || device->abs.absinfo_y->fuzz) { dispatch->mt.want_hysteresis = true; diff --git a/src/evdev-fallback.h b/src/evdev-fallback.h index 514db96..6f00816 100644 --- a/src/evdev-fallback.h +++ b/src/evdev-fallback.h @@ -52,13 +52,20 @@ enum mt_slot_state { SLOT_STATE_END, }; +enum palm_state { + PALM_NONE, + PALM_NEW, + PALM_IS_PALM, + PALM_WAS_PALM, /* this touch sequence was a palm but isn't now */ +}; + struct mt_slot { bool dirty; enum mt_slot_state state; int32_t seat_slot; struct device_coords point; struct device_coords hysteresis_center; - bool is_palm; + enum palm_state palm_state; }; struct fallback_dispatch { @@ -85,6 +92,7 @@ struct fallback_dispatch { size_t slots_len; bool want_hysteresis; struct device_coords hysteresis_margin; + bool has_palm; } mt; struct device_coords rel; diff --git a/src/libinput-private.h b/src/libinput-private.h index a012c11..34cf90d 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -549,6 +549,12 @@ touch_notify_touch_up(struct libinput_device *device, int32_t seat_slot); void +touch_notify_touch_cancel(struct libinput_device *device, + uint64_t time, + int32_t slot, + int32_t seat_slot); + +void touch_notify_frame(struct libinput_device *device, uint64_t time); diff --git a/src/libinput.c b/src/libinput.c index fc3dd24..01f5397 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -2447,6 +2447,30 @@ touch_notify_touch_up(struct libinput_device *device, } void +touch_notify_touch_cancel(struct libinput_device *device, + uint64_t time, + int32_t slot, + int32_t seat_slot) +{ + struct libinput_event_touch *touch_event; + + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH)) + return; + + touch_event = zalloc(sizeof *touch_event); + + *touch_event = (struct libinput_event_touch) { + .time = time, + .slot = slot, + .seat_slot = seat_slot, + }; + + post_device_event(device, time, + LIBINPUT_EVENT_TOUCH_CANCEL, + &touch_event->base); +} + +void touch_notify_frame(struct libinput_device *device, uint64_t time) { diff --git a/test/litest-device-touchscreen-mt-tool.c b/test/litest-device-touchscreen-mt-tool.c new file mode 100644 index 0000000..811e95d --- /dev/null +++ b/test/litest-device-touchscreen-mt-tool.c @@ -0,0 +1,88 @@ +/* + * Copyright © 2015 Canonical, Ltd. + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "litest.h" +#include "litest-int.h" + +static struct input_event down[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOOL_TYPE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event move[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOOL_TYPE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct litest_device_interface interface = { + .touch_down_events = down, + .touch_move_events = move, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 1000, 2500, 0, 0, 10 }, + { ABS_Y, 2000, 4500, 0, 0, 10 }, + { ABS_MT_SLOT, 0, 9, 0, 0, 0 }, + { ABS_MT_TOOL_TYPE, 0, 2, 0, 0, 0 }, + { ABS_MT_POSITION_X, 1000, 2500, 0, 0, 10 }, + { ABS_MT_POSITION_Y, 2000, 4500, 0, 0, 10 }, + { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x1, + .vendor = 0x0, + .product = 0x25, +}; + +static int events[] = { + EV_KEY, BTN_TOUCH, + INPUT_PROP_MAX, INPUT_PROP_DIRECT, + -1, -1 +}; + +TEST_DEVICE("touchscreen-mt-tool-type", + .type = LITEST_TOUCHSCREEN_MT_TOOL_TYPE, + .features = LITEST_TOUCH, + .interface = &interface, + + .name = "touchscreen-mt-tool-type", + .id = &input_id, + .events = events, + .absinfo = absinfo, +) diff --git a/test/litest.h b/test/litest.h index 87513bf..657ef6f 100644 --- a/test/litest.h +++ b/test/litest.h @@ -288,6 +288,7 @@ enum litest_device_type { LITEST_MS_NANO_TRANSCEIVER_MOUSE, LITEST_AIPTEK, LITEST_TOUCHSCREEN_INVALID_RANGE, + LITEST_TOUCHSCREEN_MT_TOOL_TYPE, }; enum litest_device_feature { diff --git a/test/test-touch.c b/test/test-touch.c index 7c1aa4c..3388d5d 100644 --- a/test/test-touch.c +++ b/test/test-touch.c @@ -1061,6 +1061,221 @@ START_TEST(touch_count_invalid) } END_TEST +static inline bool +touch_has_tool_palm(struct litest_device *dev) +{ + return libevdev_has_event_code(dev->evdev, EV_ABS, ABS_MT_TOOL_TYPE); +} + +START_TEST(touch_palm_detect_tool_palm) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOOL_TYPE, MT_TOOL_PALM }, + { -1, 0 } + }; + + if (!touch_has_tool_palm(dev)) + return; + + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 1); + litest_drain_events(li); + + litest_touch_move_to_extended(dev, 0, 50, 50, 70, 70, axes, 10, 1); + libinput_dispatch(li); + litest_assert_touch_cancel(li); + + litest_touch_move_to(dev, 0, 70, 70, 50, 40, 10, 1); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touch_palm_detect_tool_palm_on_off) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOOL_TYPE, MT_TOOL_PALM }, + { -1, 0 } + }; + + if (!touch_has_tool_palm(dev)) + return; + + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 1); + litest_drain_events(li); + + litest_touch_move_to_extended(dev, 0, 50, 50, 70, 70, axes, 10, 1); + libinput_dispatch(li); + litest_assert_touch_cancel(li); + + litest_touch_move_to(dev, 0, 70, 70, 50, 40, 10, 1); + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + litest_touch_move_to_extended(dev, 0, 50, 40, 70, 70, axes, 10, 1); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touch_palm_detect_tool_palm_keep_type) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOOL_TYPE, MT_TOOL_PALM }, + { -1, 0 } + }; + + if (!touch_has_tool_palm(dev)) + return; + + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 1); + litest_touch_move_to_extended(dev, 0, 50, 50, 70, 70, axes, 10, 1); + litest_touch_up(dev, 0); + litest_drain_events(li); + + /* ABS_MT_TOOL_TYPE never reset to finger, so a new touch + should be ignored outright */ + litest_touch_down_extended(dev, 0, 50, 50, axes); + + /* Test the revert to finger case too */ + litest_axis_set_value(axes, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + litest_touch_move_to(dev, 0, 70, 70, 50, 40, 10, 1); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touch_palm_detect_tool_palm_2fg) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOOL_TYPE, MT_TOOL_PALM }, + { -1, 0 } + }; + + if (!touch_has_tool_palm(dev)) + return; + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 1); + litest_drain_events(li); + + litest_touch_move_to_extended(dev, 0, 50, 50, 70, 70, axes, 10, 1); + libinput_dispatch(li); + litest_assert_touch_cancel(li); + + litest_touch_move_to(dev, 1, 50, 50, 70, 70, 10, 1); + libinput_dispatch(li); + litest_assert_touch_motion_frame(li); + + litest_touch_up(dev, 1); + libinput_dispatch(li); + litest_assert_touch_up_frame(li); + + litest_touch_move_to(dev, 0, 70, 70, 50, 40, 10, 1); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touch_palm_detect_tool_palm_on_off_2fg) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOOL_TYPE, MT_TOOL_PALM }, + { -1, 0 } + }; + + if (!touch_has_tool_palm(dev)) + return; + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 1); + litest_drain_events(li); + + litest_touch_move_to_extended(dev, 0, 50, 50, 70, 70, axes, 10, 1); + libinput_dispatch(li); + litest_assert_touch_cancel(li); + + litest_touch_move_to(dev, 1, 50, 50, 70, 70, 10, 1); + libinput_dispatch(li); + litest_assert_touch_motion_frame(li); + + litest_axis_set_value(axes, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + litest_touch_move_to_extended(dev, 0, 50, 40, 70, 70, axes, 10, 1); + litest_assert_empty_queue(li); + + litest_touch_move_to(dev, 1, 70, 70, 50, 40, 10, 1); + libinput_dispatch(li); + litest_assert_touch_motion_frame(li); + + litest_touch_up(dev, 1); + libinput_dispatch(li); + litest_assert_touch_up_frame(li); + + litest_touch_move_to(dev, 0, 70, 70, 50, 40, 10, 1); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touch_palm_detect_tool_palm_keep_type_2fg) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOOL_TYPE, MT_TOOL_PALM }, + { -1, 0 } + }; + + if (!touch_has_tool_palm(dev)) + return; + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 1); + litest_touch_move_to_extended(dev, 0, 50, 50, 70, 70, axes, 10, 1); + litest_touch_up(dev, 0); + litest_drain_events(li); + + litest_touch_move_to(dev, 1, 50, 50, 70, 70, 10, 1); + libinput_dispatch(li); + litest_assert_touch_motion_frame(li); + + /* ABS_MT_TOOL_TYPE never reset to finger, so a new touch + should be ignored outright */ + litest_touch_down_extended(dev, 0, 50, 50, axes); + + /* Test the revert to finger case too */ + litest_axis_set_value(axes, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + litest_touch_move_to(dev, 0, 70, 70, 50, 40, 10, 1); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); + + litest_touch_up(dev, 1); + libinput_dispatch(li); + litest_assert_touch_up_frame(li); +} +END_TEST + TEST_COLLECTION(touch) { struct range axes = { ABS_X, ABS_Y + 1}; @@ -1103,4 +1318,11 @@ TEST_COLLECTION(touch) litest_add("touch:count", touch_count_mt, LITEST_TOUCH, LITEST_SINGLE_TOUCH|LITEST_PROTOCOL_A); litest_add("touch:count", touch_count_unknown, LITEST_PROTOCOL_A, LITEST_ANY); litest_add("touch:count", touch_count_invalid, LITEST_ANY, LITEST_TOUCH|LITEST_SINGLE_TOUCH|LITEST_PROTOCOL_A); + + litest_add("touch:tool", touch_palm_detect_tool_palm, LITEST_TOUCH, LITEST_ANY); + litest_add("touch:tool", touch_palm_detect_tool_palm_on_off, LITEST_TOUCH, LITEST_ANY); + litest_add("touch:tool", touch_palm_detect_tool_palm_keep_type, LITEST_TOUCH, LITEST_ANY); + litest_add("touch:tool", touch_palm_detect_tool_palm_2fg, LITEST_TOUCH, LITEST_SINGLE_TOUCH); + litest_add("touch:tool", touch_palm_detect_tool_palm_on_off_2fg, LITEST_TOUCH, LITEST_SINGLE_TOUCH); + litest_add("touch:tool", touch_palm_detect_tool_palm_keep_type_2fg, LITEST_TOUCH, LITEST_ANY); } -- 2.7.4