}
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)
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;
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;
}
}
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:
* in NONE state */
break;
}
-
- slot->dirty = false;
}
return sent;
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;
--- /dev/null
+/*
+ * 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,
+)
}
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};
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);
}