From 432fbc33cd6a2181ac213eeb66239df8312836a6 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 22 Mar 2017 16:16:21 +1000 Subject: [PATCH] touchpad: add touch-size based touch handling Apple touchpads don't use ABS_MT_PRESSURE but they are multitouch touchpads, so the current pressure-based handling code doesn't apply because it expects slot-based pressure for mt touchpads. Apple does however send useful data for ABS_MT_WIDTH_MAJOR/MINOR, so let's use that instead. The data provided in those is more-or-less random, so we need a hwdb entry to track the acceptable thresholds. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 115 +++++++++++++++++++++++++++- src/evdev-mt-touchpad.h | 12 +++ src/evdev.c | 2 +- test/litest-device-bcm5974.c | 12 +++ test/litest-device-magic-trackpad.c | 26 ++++++- test/test-touchpad.c | 106 +++++++++++++++++++++++++ udev/90-libinput-model-quirks.hwdb | 1 + udev/parse_hwdb.py | 1 + 8 files changed, 269 insertions(+), 6 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 14bb5c84..0f009ef6 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -348,6 +348,16 @@ tp_process_absolute(struct tp_dispatch *tp, t->dirty = true; tp->queued |= TOUCHPAD_EVENT_OTHERAXIS; break; + case ABS_MT_TOUCH_MAJOR: + t->major = e->value; + t->dirty = true; + tp->queued |= TOUCHPAD_EVENT_OTHERAXIS; + break; + case ABS_MT_TOUCH_MINOR: + t->minor = e->value; + t->dirty = true; + tp->queued |= TOUCHPAD_EVENT_OTHERAXIS; + break; } } @@ -1015,6 +1025,45 @@ tp_unhover_pressure(struct tp_dispatch *tp, uint64_t time) } } +static void +tp_unhover_size(struct tp_dispatch *tp, uint64_t time) +{ + struct tp_touch *t; + int low = tp->touch_size.low, + high = tp->touch_size.high; + int i; + + /* We require 5 slots for size handling, so we don't need to care + * about fake touches here */ + + for (i = 0; i < (int)tp->num_slots; i++) { + t = tp_get_touch(tp, i); + + if (t->state == TOUCH_NONE) + continue; + + if (!t->dirty) + continue; + + if (t->state == TOUCH_HOVERING) { + if ((t->major > high && t->minor > low) || + (t->major > low && t->minor > high)) { + evdev_log_debug(tp->device, + "touch-size: begin touch\n"); + /* avoid jumps when landing a finger */ + tp_motion_history_reset(t); + tp_begin_touch(tp, t, time); + } + } else { + if (t->major < low || t->minor < low) { + evdev_log_debug(tp->device, + "touch-size: end touch\n"); + tp_end_touch(tp, t, time); + } + } + } +} + static void tp_unhover_fake_touches(struct tp_dispatch *tp, uint64_t time) { @@ -1077,6 +1126,8 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time) { if (tp->pressure.use_pressure) tp_unhover_pressure(tp, time); + else if (tp->touch_size.use_touch_size) + tp_unhover_size(tp, time); else tp_unhover_fake_touches(tp, time); @@ -1512,6 +1563,15 @@ tp_sync_touch(struct tp_dispatch *tp, t->pressure = libevdev_get_event_value(evdev, EV_ABS, ABS_PRESSURE); + + libevdev_fetch_slot_value(evdev, + slot, + ABS_MT_TOUCH_MAJOR, + &t->major); + libevdev_fetch_slot_value(evdev, + slot, + ABS_MT_TOUCH_MINOR, + &t->minor); } static void @@ -2650,10 +2710,59 @@ tp_init_pressure(struct tp_dispatch *tp, "using pressure-based touch detection\n"); } +static bool +tp_init_touch_size(struct tp_dispatch *tp, + struct evdev_device *device) +{ + const char *prop; + int lo, hi; + + if (!libevdev_has_event_code(device->evdev, + EV_ABS, + ABS_MT_TOUCH_MAJOR)) { + return false; + } + + if (libevdev_get_num_slots(device->evdev) < 5) { + evdev_log_bug_libinput(device, + "Expected 5+ slots for touch size detection\n"); + return false; + } + + prop = udev_device_get_property_value(device->udev_device, + "LIBINPUT_ATTR_TOUCH_SIZE_RANGE"); + if (!prop) + return false; + + if (!parse_range_property(prop, &hi, &lo)) { + evdev_log_bug_client(device, + "discarding invalid touch size range '%s'\n", + prop); + return false; + } + + if (hi == 0 && lo == 0) { + evdev_log_info(device, + "touch size based touch detection disabled\n"); + return false; + } + + /* Thresholds apply for both major or minor */ + tp->touch_size.low = lo; + tp->touch_size.high = hi; + tp->touch_size.use_touch_size = true; + + evdev_log_debug(device, "using size-based touch detection\n"); + + return true; +} + static int tp_init(struct tp_dispatch *tp, struct evdev_device *device) { + bool use_touch_size = false; + tp->base.dispatch_type = DISPATCH_TOUCHPAD; tp->base.interface = &tp_interface; tp->device = device; @@ -2668,7 +2777,11 @@ tp_init(struct tp_dispatch *tp, evdev_device_init_abs_range_warnings(device); - tp_init_pressure(tp, device); + if (device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD) + use_touch_size = tp_init_touch_size(tp, device); + + if (!use_touch_size) + tp_init_pressure(tp, device); /* Set the dpi to that of the x axis, because that's what we normalize to when needed*/ diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index d601f7e5..7391e6ce 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -148,6 +148,7 @@ struct tp_touch { uint64_t time; int pressure; bool is_tool_palm; /* MT_TOOL_PALM */ + int major, minor; bool was_down; /* if distance == 0, false for pure hovering touches */ @@ -248,6 +249,17 @@ struct tp_dispatch { int low; } pressure; + /* If touch size (either axis) goes above high -> touch down, + if touch size (either axis) goes below low -> touch up */ + struct { + bool use_touch_size; + int high; + int low; + + /* convert device units to angle */ + double orientation_to_angle; + } touch_size; + struct device_coords hysteresis_margin; struct { diff --git a/src/evdev.c b/src/evdev.c index 3e8677bf..6e2d89dd 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -1896,7 +1896,7 @@ evdev_process_event(struct evdev_device *device, struct input_event *e) struct evdev_dispatch *dispatch = device->dispatch; uint64_t time = tv2us(&e->time); -#if 0 +#if 1 if (libevdev_event_is_code(e, EV_SYN, SYN_REPORT)) evdev_log_debug(device, "-------------- EV_SYN ------------\n"); diff --git a/test/litest-device-bcm5974.c b/test/litest-device-bcm5974.c index 7d872853..1e6a002f 100644 --- a/test/litest-device-bcm5974.c +++ b/test/litest-device-bcm5974.c @@ -40,6 +40,9 @@ static struct input_event down[] = { { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .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_ORIENTATION, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = LITEST_AUTO_ASSIGN }, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, { .type = -1, .code = -1 }, }; @@ -51,6 +54,9 @@ static struct input_event move[] = { { .type = EV_ABS, .code = ABS_PRESSURE, .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_ORIENTATION, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = LITEST_AUTO_ASSIGN }, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, { .type = -1, .code = -1 }, }; @@ -63,6 +69,12 @@ get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) case ABS_MT_PRESSURE: *value = 30; return 0; + case ABS_MT_TOUCH_MAJOR: + case ABS_MT_TOUCH_MINOR: + *value = 200; + return 0; + case ABS_MT_ORIENTATION: + return 0; } return 1; } diff --git a/test/litest-device-magic-trackpad.c b/test/litest-device-magic-trackpad.c index cc371633..56d42344 100644 --- a/test/litest-device-magic-trackpad.c +++ b/test/litest-device-magic-trackpad.c @@ -37,10 +37,11 @@ static struct input_event down[] = { { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, { .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_TOUCH_MAJOR, .value = 272 }, - { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 400 }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .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_ORIENTATION, .value = LITEST_AUTO_ASSIGN }, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, { .type = -1, .code = -1 }, }; @@ -49,17 +50,34 @@ static struct input_event move[] = { { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, - { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = 272 }, - { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 400 }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .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_ORIENTATION, .value = LITEST_AUTO_ASSIGN }, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, { .type = -1, .code = -1 }, }; +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_MT_TOUCH_MAJOR: + case ABS_MT_TOUCH_MINOR: + *value = 200; + return 0; + case ABS_MT_ORIENTATION: + return 0; + } + return 1; +} + static struct litest_device_interface interface = { .touch_down_events = down, .touch_move_events = move, + + .get_axis_default = get_axis_default, }; static struct input_absinfo absinfo[] = { diff --git a/test/test-touchpad.c b/test/test-touchpad.c index ffb214ec..77618155 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -5273,6 +5273,109 @@ START_TEST(touchpad_pressure_tap_2fg_1fg_light) } END_TEST +static inline bool +touchpad_has_touch_size(struct litest_device *dev) +{ + struct libevdev *evdev = dev->evdev; + + if (!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_TOUCH_MAJOR)) + return false; + + if (libevdev_get_id_vendor(evdev) == VENDOR_ID_APPLE) + return true; + + return false; +} + +START_TEST(touchpad_touch_size) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOUCH_MAJOR, 0 }, + { ABS_MT_TOUCH_MINOR, 0 }, + { ABS_MT_ORIENTATION, 0 }, + { -1, 0 } + }; + + if (!touchpad_has_touch_size(dev)) + return; + + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_MT_TOUCH_MAJOR, 1); + litest_axis_set_value(axes, ABS_MT_TOUCH_MINOR, 1); + litest_touch_down_extended(dev, 0, 50, 50, axes); + litest_touch_move_to_extended(dev, 0, + 50, 50, + 80, 80, + axes, 10, 1); + litest_touch_up(dev, 0); + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_MT_TOUCH_MAJOR, 15); + litest_axis_set_value(axes, ABS_MT_TOUCH_MINOR, 15); + litest_touch_down_extended(dev, 0, 50, 50, axes); + litest_touch_move_to_extended(dev, 0, + 50, 50, + 80, 80, + axes, 10, 1); + litest_touch_up(dev, 0); + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +START_TEST(touchpad_touch_size_2fg) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_MT_TOUCH_MAJOR, 0 }, + { ABS_MT_TOUCH_MINOR, 0 }, + { ABS_MT_ORIENTATION, 0 }, + { -1, 0 } + }; + + if (!touchpad_has_touch_size(dev)) + return; + + litest_drain_events(li); + litest_axis_set_value(axes, ABS_MT_TOUCH_MAJOR, 15); + litest_axis_set_value(axes, ABS_MT_TOUCH_MINOR, 15); + litest_touch_down_extended(dev, 0, 50, 50, axes); + litest_touch_move_to_extended(dev, 0, + 50, 50, + 80, 80, + axes, 10, 1); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + litest_axis_set_value(axes, ABS_MT_TOUCH_MAJOR, 1); + litest_axis_set_value(axes, ABS_MT_TOUCH_MINOR, 1); + litest_touch_down_extended(dev, 1, 70, 70, axes); + litest_touch_move_to_extended(dev, 1, + 70, 70, + 80, 90, + axes, 10, 1); + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_MT_TOUCH_MAJOR, 15); + litest_axis_set_value(axes, ABS_MT_TOUCH_MINOR, 15); + litest_touch_move_to_extended(dev, 0, + 80, 80, + 50, 50, + axes, 10, 1); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 1); + litest_touch_up(dev, 0); +} +END_TEST + void litest_setup_tests_touchpad(void) { @@ -5434,4 +5537,7 @@ litest_setup_tests_touchpad(void) litest_add("touchpad:pressure", touchpad_pressure_tap, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:pressure", touchpad_pressure_tap_2fg, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:pressure", touchpad_pressure_tap_2fg_1fg_light, LITEST_TOUCHPAD, LITEST_ANY); + + litest_add("touchpad:touch-size", touchpad_touch_size, LITEST_APPLE_CLICKPAD, LITEST_ANY); + litest_add("touchpad:touch-size", touchpad_touch_size_2fg, LITEST_APPLE_CLICKPAD, LITEST_ANY); } diff --git a/udev/90-libinput-model-quirks.hwdb b/udev/90-libinput-model-quirks.hwdb index 3ec1ac1f..5d9b2cbc 100644 --- a/udev/90-libinput-model-quirks.hwdb +++ b/udev/90-libinput-model-quirks.hwdb @@ -50,6 +50,7 @@ libinput:touchpad:input:b0003v05ACp* libinput:touchpad:input:b0005v05ACp* LIBINPUT_MODEL_APPLE_TOUCHPAD=1 LIBINPUT_ATTR_SIZE_HINT=104x75 + LIBINPUT_ATTR_TOUCH_SIZE_RANGE=150:130 libinput:name:*Apple Inc. Apple Internal Keyboard*:dmi:* LIBINPUT_ATTR_KEYBOARD_INTEGRATION=internal diff --git a/udev/parse_hwdb.py b/udev/parse_hwdb.py index 5de7eb48..102fb3e7 100755 --- a/udev/parse_hwdb.py +++ b/udev/parse_hwdb.py @@ -108,6 +108,7 @@ def property_grammar(): ('LIBINPUT_ATTR_SIZE_HINT', Group(dimension('SETTINGS*'))), ('LIBINPUT_ATTR_RESOLUTION_HINT', Group(dimension('SETTINGS*'))), ('LIBINPUT_ATTR_PRESSURE_RANGE', Group(crange('SETTINGS*'))), + ('LIBINPUT_ATTR_TOUCH_SIZE_RANGE', Group(crange('SETTINGS*'))), ('LIBINPUT_ATTR_TPKBCOMBO_LAYOUT', Or(('below'))), ('LIBINPUT_ATTR_LID_SWITCH_RELIABILITY', Or(('reliable', 'write_open'))),