From d2e026d8aef659e33f3df127ad1823cf38923a34 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 6 Feb 2014 15:05:36 +1000 Subject: [PATCH 01/22] Add the shell for a multitouch-compatible touchpad implementation Doesn't do anything but initialize and destroy. This is not a permanent separate implementation, it's just easier to start this way and then switch over than to add to the current one. Temporary measure: LIBINPUT_NEW_TOUCHPAD_DRIVER environment variable can be used to enable the new driver Signed-off-by: Peter Hutterer --- src/Makefile.am | 1 + src/evdev-mt-touchpad.c | 76 +++++++++++++++++++++++++++++++++++++++++ src/evdev.c | 5 ++- src/evdev.h | 3 ++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/evdev-mt-touchpad.c diff --git a/src/Makefile.am b/src/Makefile.am index 8c6d9354..ae1eed02 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ libinput_la_SOURCES = \ libinput-util.h \ evdev.c \ evdev.h \ + evdev-mt-touchpad.c \ evdev-touchpad.c \ filter.c \ filter.h \ diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c new file mode 100644 index 00000000..a3606513 --- /dev/null +++ b/src/evdev-mt-touchpad.c @@ -0,0 +1,76 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include "evdev.h" + +struct touchpad_dispatch { + struct evdev_dispatch base; + struct evdev_device *device; +}; + +static void +touchpad_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ +} + +static void +touchpad_destroy(struct evdev_dispatch *dispatch) +{ + free(dispatch); +} + +static struct evdev_dispatch_interface touchpad_interface = { + touchpad_process, + touchpad_destroy +}; + +static int +touchpad_init(struct touchpad_dispatch *touchpad, + struct evdev_device *device) +{ + touchpad->base.interface = &touchpad_interface; + touchpad->device = device; + + return 0; +} + +struct evdev_dispatch * +evdev_mt_touchpad_create(struct evdev_device *device) +{ + struct touchpad_dispatch *touchpad; + + touchpad = zalloc(sizeof *touchpad); + if (!touchpad) + return NULL; + + if (touchpad_init(touchpad, device) != 0) { + free(touchpad); + return NULL; + } + + return &touchpad->base; +} diff --git a/src/evdev.c b/src/evdev.c index 5d01e3bc..bb2913f8 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -600,7 +600,10 @@ evdev_configure_device(struct evdev_device *device) if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_FINGER) && !libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_PEN) && (has_abs || has_mt)) { - device->dispatch = evdev_touchpad_create(device); + if (getenv("LIBINPUT_NEW_TOUCHPAD_DRIVER") && has_mt) + device->dispatch = evdev_mt_touchpad_create(device); + else + device->dispatch = evdev_touchpad_create(device); } for (i = KEY_ESC; i < KEY_MAX; i++) { if (i >= BTN_MISC && i < KEY_OK) diff --git a/src/evdev.h b/src/evdev.h index b83a2f9d..0ab95720 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -118,6 +118,9 @@ evdev_device_create(struct libinput_seat *seat, struct evdev_dispatch * evdev_touchpad_create(struct evdev_device *device); +struct evdev_dispatch * +evdev_mt_touchpad_create(struct evdev_device *device); + void evdev_device_proces_event(struct libinput_event *event); From 37a04e0b0d99b1bc7a88b9c40e0bb556617b3e19 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 6 Feb 2014 15:32:32 +1000 Subject: [PATCH 02/22] touchpad: add a touchpad driver based on per-finger tracking This patch is a mixture of an experimental project (libtouchpad) and evdev-touchpad.c. It adds a new touchpad driver for multi-touch touchpads that tracks each touchpoint separately. This makes it a lot easier to handle multi-finger tapping, software button areas, etc. libtouchpad used a slightly different coding style, this is the attempt to get closer to the one used in libinput. Currently sends motion events for single-finger motion, button events only for physical buttons. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 306 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 284 insertions(+), 22 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index a3606513..856d54ff 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -22,38 +22,300 @@ #include "config.h" +#include +#include + #include "evdev.h" -struct touchpad_dispatch { - struct evdev_dispatch base; - struct evdev_device *device; +#define TOUCHPAD_HISTORY_LENGTH 4 + +#define tp_for_each_touch(_tp, _t) \ + for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++) + +enum touch_state { + TOUCH_NONE = 0, + TOUCH_BEGIN, + TOUCH_UPDATE, + TOUCH_END }; -static void -touchpad_process(struct evdev_dispatch *dispatch, - struct evdev_device *device, - struct input_event *e, - uint32_t time) +struct tp_motion { + int32_t x; + int32_t y; +}; + +struct tp_touch { + enum touch_state state; + bool dirty; + int32_t x; + int32_t y; + uint32_t millis; + + struct { + struct tp_motion samples[TOUCHPAD_HISTORY_LENGTH]; + unsigned int index; + unsigned int count; + } history; +}; + +struct tp_dispatch { + struct evdev_dispatch base; + struct evdev_device *device; + unsigned int nfingers_down; /* number of fingers down */ + unsigned int slot; /* current slot */ + + unsigned int ntouches; /* number of slots */ + struct tp_touch *touches; /* len == ntouches */ +}; + +static inline struct tp_motion * +tp_motion_history_offset(struct tp_touch *t, int offset) { + int offset_index = + (t->history.index - offset + TOUCHPAD_HISTORY_LENGTH) % + TOUCHPAD_HISTORY_LENGTH; + + return &t->history.samples[offset_index]; +} + +static inline void +tp_motion_history_push(struct tp_touch *t) +{ + int motion_index = (t->history.index + 1) % TOUCHPAD_HISTORY_LENGTH; + + if (t->history.count < TOUCHPAD_HISTORY_LENGTH) + t->history.count++; + + t->history.samples[motion_index].x = t->x; + t->history.samples[motion_index].y = t->y; + t->history.index = motion_index; +} + +static inline void +tp_motion_history_reset(struct tp_touch *t) +{ + t->history.count = 0; +} + +static inline struct tp_touch * +tp_current_touch(struct tp_dispatch *tp) +{ + return &tp->touches[min(tp->slot, tp->ntouches)]; +} + +static inline void +tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t) +{ + if (t->state != TOUCH_UPDATE) { + tp_motion_history_reset(t); + t->dirty = true; + t->state = TOUCH_BEGIN; + tp->nfingers_down++; + assert(tp->nfingers_down >= 1); + } +} + +static inline void +tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t) +{ + if (t->state == TOUCH_NONE) + return; + + t->dirty = true; + t->state = TOUCH_END; + assert(tp->nfingers_down >= 1); + tp->nfingers_down--; +} + +static double +tp_estimate_delta(int x0, int x1, int x2, int x3) +{ + return (x0 + x1 - x2 - x3) / 4; } static void -touchpad_destroy(struct evdev_dispatch *dispatch) +tp_get_delta(struct tp_touch *t, double *dx, double *dy) { - free(dispatch); + if (t->history.count < 4) { + *dx = 0; + *dy = 0; + return; + } + + *dx = tp_estimate_delta(tp_motion_history_offset(t, 0)->x, + tp_motion_history_offset(t, 1)->x, + tp_motion_history_offset(t, 2)->x, + tp_motion_history_offset(t, 3)->x); + *dy = tp_estimate_delta(tp_motion_history_offset(t, 0)->y, + tp_motion_history_offset(t, 1)->y, + tp_motion_history_offset(t, 2)->y, + tp_motion_history_offset(t, 3)->y); } -static struct evdev_dispatch_interface touchpad_interface = { - touchpad_process, - touchpad_destroy +static void +tp_process_absolute(struct tp_dispatch *tp, + const struct input_event *e, + uint32_t time) +{ + struct tp_touch *t = tp_current_touch(tp); + + switch(e->code) { + case ABS_MT_POSITION_X: + t->x = e->value; + t->millis = time; + t->dirty = true; + break; + case ABS_MT_POSITION_Y: + t->y = e->value; + t->millis = time; + t->dirty = true; + break; + case ABS_MT_SLOT: + tp->slot = e->value; + break; + case ABS_MT_TRACKING_ID: + t->millis = time; + if (e->value != -1) + tp_begin_touch(tp, t); + else + tp_end_touch(tp, t); + } +} + +static void +tp_process_key(struct tp_dispatch *tp, + const struct input_event *e, + uint32_t time) +{ + switch (e->code) { + case BTN_LEFT: + case BTN_MIDDLE: + case BTN_RIGHT: + pointer_notify_button( + &tp->device->base, + time, + e->code, + e->value ? LIBINPUT_POINTER_BUTTON_STATE_PRESSED : + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_process_state(struct tp_dispatch *tp, uint32_t time) +{ + struct tp_touch *t; + + tp_for_each_touch(tp, t) { + if (!t->dirty) + continue; + + tp_motion_history_push(t); + } +} + +static void +tp_post_process_state(struct tp_dispatch *tp, uint32_t time) +{ + struct tp_touch *t; + + tp_for_each_touch(tp, t) { + if (!t->dirty) + continue; + + if (t->state == TOUCH_END) + t->state = TOUCH_NONE; + else if (t->state == TOUCH_BEGIN) + t->state = TOUCH_UPDATE; + + t->dirty = false; + } +} + +static void +tp_post_events(struct tp_dispatch *tp, uint32_t time) +{ + struct tp_touch *t = tp_current_touch(tp); + double dx, dy; + + if (tp->nfingers_down != 1) + return; + + tp_get_delta(t, &dx, &dy); + + if (dx != 0 || dy != 0) + pointer_notify_motion( + &tp->device->base, + time, + li_fixed_from_double(dx), + li_fixed_from_double(dy)); +} + +static void +tp_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ + struct tp_dispatch *tp = + (struct tp_dispatch *)dispatch; + + switch (e->type) { + case EV_ABS: + tp_process_absolute(tp, e, time); + break; + case EV_KEY: + tp_process_key(tp, e, time); + break; + case EV_SYN: + tp_process_state(tp, time); + tp_post_events(tp, time); + tp_post_process_state(tp, time); + break; + } +} + +static void +tp_destroy(struct evdev_dispatch *dispatch) +{ + struct tp_dispatch *tp = + (struct tp_dispatch*)dispatch; + + free(tp->touches); + free(tp); +} + +static struct evdev_dispatch_interface tp_interface = { + tp_process, + tp_destroy }; static int -touchpad_init(struct touchpad_dispatch *touchpad, +tp_init_slots(struct tp_dispatch *tp, struct evdev_device *device) { - touchpad->base.interface = &touchpad_interface; - touchpad->device = device; + struct input_absinfo absinfo = {0}; + + ioctl(device->fd, EVIOCGABS(ABS_MT_SLOT), &absinfo); + + tp->ntouches = absinfo.maximum + 1; + tp->touches = calloc(tp->ntouches, + sizeof(struct tp_touch)); + tp->slot = absinfo.value; + + return 0; +} + + +static int +tp_init(struct tp_dispatch *tp, + struct evdev_device *device) +{ + tp->base.interface = &tp_interface; + tp->device = device; + + if (tp_init_slots(tp, device) != 0) + return -1; return 0; } @@ -61,16 +323,16 @@ touchpad_init(struct touchpad_dispatch *touchpad, struct evdev_dispatch * evdev_mt_touchpad_create(struct evdev_device *device) { - struct touchpad_dispatch *touchpad; + struct tp_dispatch *tp; - touchpad = zalloc(sizeof *touchpad); - if (!touchpad) + tp = zalloc(sizeof *tp); + if (!tp) return NULL; - if (touchpad_init(touchpad, device) != 0) { - free(touchpad); + if (tp_init(tp, device) != 0) { + tp_destroy(&tp->base); return NULL; } - return &touchpad->base; + return &tp->base; } From e75a0430bec79f1f0d8bcbd34af8e1924b90c5bb Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 6 Feb 2014 18:57:10 +1000 Subject: [PATCH 03/22] touchpad: add hysteresis smoothing for input coordinates Same algorithm as in evdev-touchpad. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 856d54ff..f6258144 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -23,10 +23,12 @@ #include "config.h" #include +#include #include #include "evdev.h" +#define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0 #define TOUCHPAD_HISTORY_LENGTH 4 #define tp_for_each_touch(_tp, _t) \ @@ -56,6 +58,11 @@ struct tp_touch { unsigned int index; unsigned int count; } history; + + struct { + int32_t center_x; + int32_t center_y; + } hysteresis; }; struct tp_dispatch { @@ -66,8 +73,27 @@ struct tp_dispatch { unsigned int ntouches; /* number of slots */ struct tp_touch *touches; /* len == ntouches */ + + struct { + int32_t margin_x; + int32_t margin_y; + } hysteresis; }; +static inline int +tp_hysteresis(int in, int center, int margin) +{ + int diff = in - center; + if (abs(diff) <= margin) + return center; + + if (diff > margin) + return center + diff - margin; + else if (diff < -margin) + return center + diff + margin; + return center + diff; +} + static inline struct tp_motion * tp_motion_history_offset(struct tp_touch *t, int offset) { @@ -91,6 +117,30 @@ tp_motion_history_push(struct tp_touch *t) t->history.index = motion_index; } +static inline void +tp_motion_hysteresis(struct tp_dispatch *tp, + struct tp_touch *t) +{ + int x = t->x, + y = t->y; + + if (t->history.count == 0) { + t->hysteresis.center_x = t->x; + t->hysteresis.center_y = t->y; + } else { + x = tp_hysteresis(x, + t->hysteresis.center_x, + tp->hysteresis.margin_x); + y = tp_hysteresis(y, + t->hysteresis.center_y, + tp->hysteresis.margin_y); + t->hysteresis.center_x = x; + t->hysteresis.center_y = y; + t->x = x; + t->y = y; + } +} + static inline void tp_motion_history_reset(struct tp_touch *t) { @@ -210,6 +260,7 @@ tp_process_state(struct tp_dispatch *tp, uint32_t time) if (!t->dirty) continue; + tp_motion_hysteresis(tp, t); tp_motion_history_push(t); } } @@ -311,12 +362,24 @@ static int tp_init(struct tp_dispatch *tp, struct evdev_device *device) { + int width, height; + double diagonal; + tp->base.interface = &tp_interface; tp->device = device; if (tp_init_slots(tp, device) != 0) return -1; + width = abs(device->abs.max_x - device->abs.min_x); + height = abs(device->abs.max_y - device->abs.min_y); + diagonal = sqrt(width*width + height*height); + + tp->hysteresis.margin_x = + diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; + tp->hysteresis.margin_y = + diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; + return 0; } From 8d867a2b8abc15be565dbcf3e8e771e6110a79e7 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 6 Feb 2014 19:43:48 +1000 Subject: [PATCH 04/22] touchpad: hook up the pointer acceleration Same algorithm as in evdev-touchpad.c Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index f6258144..19681991 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -27,7 +27,11 @@ #include #include "evdev.h" +#include "filter.h" +#define DEFAULT_CONSTANT_ACCEL_NUMERATOR 50 +#define DEFAULT_MIN_ACCEL_FACTOR 0.16 +#define DEFAULT_MAX_ACCEL_FACTOR 1.0 #define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0 #define TOUCHPAD_HISTORY_LENGTH 4 @@ -78,6 +82,14 @@ struct tp_dispatch { int32_t margin_x; int32_t margin_y; } hysteresis; + + struct motion_filter *filter; + + struct { + double constant_factor; + double min_factor; + double max_factor; + } accel; }; static inline int @@ -94,6 +106,27 @@ tp_hysteresis(int in, int center, int margin) return center + diff; } +static double +tp_accel_profile(struct motion_filter *filter, + void *data, + double velocity, + uint32_t time) +{ + struct tp_dispatch *tp = + (struct tp_dispatch *) data; + + double accel_factor; + + accel_factor = velocity * tp->accel.constant_factor; + + if (accel_factor > tp->accel.max_factor) + accel_factor = tp->accel.max_factor; + else if (accel_factor < tp->accel.min_factor) + accel_factor = tp->accel.min_factor; + + return accel_factor; +} + static inline struct tp_motion * tp_motion_history_offset(struct tp_touch *t, int offset) { @@ -104,6 +137,21 @@ tp_motion_history_offset(struct tp_touch *t, int offset) return &t->history.samples[offset_index]; } +static void +tp_filter_motion(struct tp_dispatch *tp, + double *dx, double *dy, uint32_t time) +{ + struct motion_params motion; + + motion.dx = *dx; + motion.dy = *dy; + + filter_dispatch(tp->filter, &motion, tp, time); + + *dx = motion.dx; + *dy = motion.dy; +} + static inline void tp_motion_history_push(struct tp_touch *t) { @@ -292,7 +340,12 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) if (tp->nfingers_down != 1) return; + + if (t->history.count < 4) + return; + tp_get_delta(t, &dx, &dy); + tp_filter_motion(tp, &dx, &dy, time); if (dx != 0 || dy != 0) pointer_notify_motion( @@ -332,6 +385,8 @@ tp_destroy(struct evdev_dispatch *dispatch) struct tp_dispatch *tp = (struct tp_dispatch*)dispatch; + if (tp->filter) + tp->filter->interface->destroy(tp->filter); free(tp->touches); free(tp); } @@ -357,6 +412,24 @@ tp_init_slots(struct tp_dispatch *tp, return 0; } +static int +tp_init_accel(struct tp_dispatch *touchpad, double diagonal) +{ + struct motion_filter *accel; + + touchpad->accel.constant_factor = + DEFAULT_CONSTANT_ACCEL_NUMERATOR / diagonal; + touchpad->accel.min_factor = DEFAULT_MIN_ACCEL_FACTOR; + touchpad->accel.max_factor = DEFAULT_MAX_ACCEL_FACTOR; + + accel = create_pointer_accelator_filter(tp_accel_profile); + if (accel == NULL) + return -1; + + touchpad->filter = accel; + + return 0; +} static int tp_init(struct tp_dispatch *tp, @@ -380,6 +453,9 @@ tp_init(struct tp_dispatch *tp, tp->hysteresis.margin_y = diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; + if (tp_init_accel(tp, diagonal) != 0) + return -1; + return 0; } From d22cdf1c47bc70a760fd449f131dee315f791c74 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 6 Feb 2014 19:17:22 +1000 Subject: [PATCH 05/22] touchpad: add two-finger average scrolling If two fingers are down and moving, take the average movement of both fingers and use that for scrolling. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 44 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 19681991..6d1793bd 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -331,14 +331,56 @@ tp_post_process_state(struct tp_dispatch *tp, uint32_t time) } } +static void +tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time) +{ + struct tp_touch *t; + int nchanged = 0; + double dx = 0, dy =0; + double tmpx, tmpy; + + tp_for_each_touch(tp, t) { + if (t->dirty) { + nchanged++; + tp_get_delta(t, &tmpx, &tmpy); + + dx += tmpx; + dy += tmpy; + } + } + + if (nchanged == 0) + return; + + dx /= nchanged; + dy /= nchanged; + + tp_filter_motion(tp, &dx, &dy, time); + + if (dx != 0.0) + pointer_notify_axis(&tp->device->base, + time, + LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL, + li_fixed_from_double(dx)); + if (dy != 0.0) + pointer_notify_axis(&tp->device->base, + time, + LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL, + li_fixed_from_double(dy)); +} + static void tp_post_events(struct tp_dispatch *tp, uint32_t time) { struct tp_touch *t = tp_current_touch(tp); double dx, dy; - if (tp->nfingers_down != 1) + if (tp->nfingers_down > 2) { return; + } else if (tp->nfingers_down == 2) { + tp_post_twofinger_scroll(tp, time); + return; + } if (t->history.count < 4) From dc13d3ec96659bdc8b89c76b3d5181b46e3d419e Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 7 Feb 2014 13:39:27 +1000 Subject: [PATCH 06/22] touchpad: move structs into a header file The tapping state implementation will be in a separate file, so let's make sure we can access the structs we need. Signed-off-by: Peter Hutterer --- src/Makefile.am | 1 + src/evdev-mt-touchpad.c | 61 +-------------------------- src/evdev-mt-touchpad.h | 91 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 60 deletions(-) create mode 100644 src/evdev-mt-touchpad.h diff --git a/src/Makefile.am b/src/Makefile.am index ae1eed02..da3a0480 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,6 +12,7 @@ libinput_la_SOURCES = \ evdev.c \ evdev.h \ evdev-mt-touchpad.c \ + evdev-mt-touchpad.h \ evdev-touchpad.c \ filter.c \ filter.h \ diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 6d1793bd..8a8586a3 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -26,71 +26,12 @@ #include #include -#include "evdev.h" -#include "filter.h" +#include "evdev-mt-touchpad.h" #define DEFAULT_CONSTANT_ACCEL_NUMERATOR 50 #define DEFAULT_MIN_ACCEL_FACTOR 0.16 #define DEFAULT_MAX_ACCEL_FACTOR 1.0 #define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0 -#define TOUCHPAD_HISTORY_LENGTH 4 - -#define tp_for_each_touch(_tp, _t) \ - for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++) - -enum touch_state { - TOUCH_NONE = 0, - TOUCH_BEGIN, - TOUCH_UPDATE, - TOUCH_END -}; - -struct tp_motion { - int32_t x; - int32_t y; -}; - -struct tp_touch { - enum touch_state state; - bool dirty; - int32_t x; - int32_t y; - uint32_t millis; - - struct { - struct tp_motion samples[TOUCHPAD_HISTORY_LENGTH]; - unsigned int index; - unsigned int count; - } history; - - struct { - int32_t center_x; - int32_t center_y; - } hysteresis; -}; - -struct tp_dispatch { - struct evdev_dispatch base; - struct evdev_device *device; - unsigned int nfingers_down; /* number of fingers down */ - unsigned int slot; /* current slot */ - - unsigned int ntouches; /* number of slots */ - struct tp_touch *touches; /* len == ntouches */ - - struct { - int32_t margin_x; - int32_t margin_y; - } hysteresis; - - struct motion_filter *filter; - - struct { - double constant_factor; - double min_factor; - double max_factor; - } accel; -}; static inline int tp_hysteresis(int in, int center, int margin) diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h new file mode 100644 index 00000000..f7f413b5 --- /dev/null +++ b/src/evdev-mt-touchpad.h @@ -0,0 +1,91 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef EVDEV_MT_TOUCHPAD_H +#define EVDEV_MT_TOUCHPAD_H + +#include + +#include "evdev.h" +#include "filter.h" + +#define TOUCHPAD_HISTORY_LENGTH 4 + +enum touch_state { + TOUCH_NONE = 0, + TOUCH_BEGIN, + TOUCH_UPDATE, + TOUCH_END +}; + +struct tp_motion { + int32_t x; + int32_t y; +}; + +struct tp_touch { + enum touch_state state; + bool dirty; + int32_t x; + int32_t y; + uint32_t millis; + + struct { + struct tp_motion samples[TOUCHPAD_HISTORY_LENGTH]; + unsigned int index; + unsigned int count; + } history; + + struct { + int32_t center_x; + int32_t center_y; + } hysteresis; +}; + +struct tp_dispatch { + struct evdev_dispatch base; + struct evdev_device *device; + unsigned int nfingers_down; /* number of fingers down */ + unsigned int slot; /* current slot */ + + unsigned int ntouches; /* number of slots */ + struct tp_touch *touches; /* len == ntouches */ + + struct { + int32_t margin_x; + int32_t margin_y; + } hysteresis; + + struct motion_filter *filter; + + struct { + double constant_factor; + double min_factor; + double max_factor; + } accel; +}; + +#define tp_for_each_touch(_tp, _t) \ + for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++) + +#endif From b9dda43c04904f54c4b9dcaf6c7d02827b03d45d Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 7 Feb 2014 13:48:06 +1000 Subject: [PATCH 07/22] touchpad: mark which events are currently pending processing Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 6 ++++++ src/evdev-mt-touchpad.h | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 8a8586a3..1d34df88 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -151,6 +151,7 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t) t->state = TOUCH_BEGIN; tp->nfingers_down++; assert(tp->nfingers_down >= 1); + tp->queued |= TOUCHPAD_EVENT_MOTION; } } @@ -164,6 +165,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t) t->state = TOUCH_END; assert(tp->nfingers_down >= 1); tp->nfingers_down--; + tp->queued |= TOUCHPAD_EVENT_MOTION; } static double @@ -203,11 +205,13 @@ tp_process_absolute(struct tp_dispatch *tp, t->x = e->value; t->millis = time; t->dirty = true; + tp->queued |= TOUCHPAD_EVENT_MOTION; break; case ABS_MT_POSITION_Y: t->y = e->value; t->millis = time; t->dirty = true; + tp->queued |= TOUCHPAD_EVENT_MOTION; break; case ABS_MT_SLOT: tp->slot = e->value; @@ -270,6 +274,8 @@ tp_post_process_state(struct tp_dispatch *tp, uint32_t time) t->dirty = false; } + + tp->queued = TOUCHPAD_EVENT_NONE; } static void diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index f7f413b5..52ad3ab0 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -31,6 +31,13 @@ #define TOUCHPAD_HISTORY_LENGTH 4 +enum touchpad_event { + TOUCHPAD_EVENT_NONE = 0, + TOUCHPAD_EVENT_MOTION = (1 << 0), + TOUCHPAD_EVENT_BUTTON_PRESS = (1 << 1), + TOUCHPAD_EVENT_BUTTON_RELEASE = (1 << 2), +}; + enum touch_state { TOUCH_NONE = 0, TOUCH_BEGIN, @@ -83,6 +90,8 @@ struct tp_dispatch { double min_factor; double max_factor; } accel; + + enum touchpad_event queued; }; #define tp_for_each_touch(_tp, _t) \ From 1f656f12cf1c208b035f676474c5a3c0a712331a Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 7 Feb 2014 13:59:38 +1000 Subject: [PATCH 08/22] touchpad: add a struct for handling physical button event state changes On ClickPads (touchpads without phys. middle/right buttons) it is important to know whether a physical click is queued up. The finger position or number of fingers decide which button event to send. This isn't currently used, we still just send the button number at the moment. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 54 ++++++++++++++++++++++++++++++++++++----- src/evdev-mt-touchpad.h | 5 ++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 1d34df88..e25997df 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -230,16 +230,20 @@ tp_process_key(struct tp_dispatch *tp, const struct input_event *e, uint32_t time) { + uint32_t mask; + switch (e->code) { case BTN_LEFT: case BTN_MIDDLE: case BTN_RIGHT: - pointer_notify_button( - &tp->device->base, - time, - e->code, - e->value ? LIBINPUT_POINTER_BUTTON_STATE_PRESSED : - LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + mask = 1 << (e->code - BTN_LEFT); + if (e->value) { + tp->buttons.state |= mask; + tp->queued |= TOUCHPAD_EVENT_BUTTON_PRESS; + } else { + tp->buttons.state &= ~mask; + tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE; + } break; } } @@ -275,6 +279,8 @@ tp_post_process_state(struct tp_dispatch *tp, uint32_t time) t->dirty = false; } + tp->buttons.old_state = tp->buttons.state; + tp->queued = TOUCHPAD_EVENT_NONE; } @@ -316,6 +322,40 @@ tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time) li_fixed_from_double(dy)); } +static void +tp_post_button_events(struct tp_dispatch *tp, uint32_t time) +{ + uint32_t current, old, button; + + if ((tp->queued & + (TOUCHPAD_EVENT_BUTTON_PRESS|TOUCHPAD_EVENT_BUTTON_RELEASE)) == 0) + return; + + current = tp->buttons.state; + old = tp->buttons.old_state; + button = BTN_LEFT; + + while (current || old) { + enum libinput_pointer_button_state state; + + if ((current & 0x1) ^ (old & 0x1)) { + if (!!(current & 0x1)) + state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; + else + state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; + + pointer_notify_button(&tp->device->base, + time, + button, + state); + } + + button++; + current >>= 1; + old >>= 1; + } +} + static void tp_post_events(struct tp_dispatch *tp, uint32_t time) { @@ -342,6 +382,8 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) time, li_fixed_from_double(dx), li_fixed_from_double(dy)); + + tp_post_button_events(tp, time); } static void diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 52ad3ab0..d12647de 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -91,6 +91,11 @@ struct tp_dispatch { double max_factor; } accel; + struct { + uint32_t state; + uint32_t old_state; + } buttons; /* physical buttons */ + enum touchpad_event queued; }; From 3df87f4fe34455ba0e716594f3ee07e330fe925b Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 7 Feb 2014 14:39:21 +1000 Subject: [PATCH 09/22] touchpad: Make touchpad_get_delta() available from other files No functional changes. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 2 +- src/evdev-mt-touchpad.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index e25997df..73ea8a8d 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -174,7 +174,7 @@ tp_estimate_delta(int x0, int x1, int x2, int x3) return (x0 + x1 - x2 - x3) / 4; } -static void +void tp_get_delta(struct tp_touch *t, double *dx, double *dy) { if (t->history.count < 4) { diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index d12647de..907aec83 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -102,4 +102,7 @@ struct tp_dispatch { #define tp_for_each_touch(_tp, _t) \ for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++) +void +tp_get_delta(struct tp_touch *t, double *dx, double *dy); + #endif From d4bd05184adcfe65602a451f24f244c107174e40 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 7 Feb 2014 15:18:17 +1000 Subject: [PATCH 10/22] touchpad: add support for multi-finger tapping Signed-off-by: Peter Hutterer --- doc/touchpad-tap-state-machine.svg | 771 +++++++++++++++++++++++++++++ src/Makefile.am | 1 + src/evdev-mt-touchpad-tap.c | 586 ++++++++++++++++++++++ src/evdev-mt-touchpad.c | 4 + src/evdev-mt-touchpad.h | 33 ++ 5 files changed, 1395 insertions(+) create mode 100644 doc/touchpad-tap-state-machine.svg create mode 100644 src/evdev-mt-touchpad-tap.c diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg new file mode 100644 index 00000000..50ebc713 --- /dev/null +++ b/doc/touchpad-tap-state-machine.svg @@ -0,0 +1,771 @@ +IDLETOUCHfirstfinger downfinger upbutton +1presstimeoutmove > thresholdsecondfinger downTOUCH_2secondfinger upbutton +2pressmove > +thresholdtimeoutbutton 1releasebutton +2releaseTAPPEDtimeoutfirstfinger downDRAGGINGfirstfinger upbtn1releaseIDLEthirdfinger downTOUCH_3secondfinger upbutton 3pressbutton 3releasemove > thresholdIDLEtimeoutthirdfinger upfirstfinger upIDLEfourthfinger downDRAGGING_OR_DOUBLETAPtimeoutfirstfinger upbutton +1releasebutton +1pressbtn1releasesecondfinger downmove > thresholdHOLDfirstfinger upsecondfinger downTOUCH_2_HOLDsecondfinger upfirstfinger upthirdfinger downTOUCH_3_HOLDsecondfinger upthirdfinger upfourthfinger downDEADsecondfinger upthirdfinger upfourthfinger downfirstfinger upfirstfinger upfirstfinger upIDLEif fingercount == 0secondfinger upDRAGGING_2firstfinger upsecondfinger downthirdfinger downbtn1releasephysbuttonpressfourthfinger downphysbuttonpressbutton 1releaseDRAGGING_WAITtimeoutfirstfinger down diff --git a/src/Makefile.am b/src/Makefile.am index da3a0480..579ed25f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,7 @@ libinput_la_SOURCES = \ evdev.h \ evdev-mt-touchpad.c \ evdev-mt-touchpad.h \ + evdev-mt-touchpad-tap.c \ evdev-touchpad.c \ filter.c \ filter.h \ diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c new file mode 100644 index 00000000..7ac592b3 --- /dev/null +++ b/src/evdev-mt-touchpad-tap.c @@ -0,0 +1,586 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of Red Hat + * not be used in advertising or publicity pertaining to distribution + * of the software without specific, written prior permission. Red + * Hat makes no representations about the suitability of this software + * for any purpose. It is provided "as is" without express or implied + * warranty. + * + * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "evdev-mt-touchpad.h" + +#define CASE_RETURN_STRING(a) case a: return #a; + +#define DEFAULT_TAP_TIMEOUT_PERIOD 180 +#define DEFAULT_TAP_MOVE_THRESHOLD 30 + +enum tap_event { + TAP_EVENT_TOUCH = 12, + TAP_EVENT_MOTION, + TAP_EVENT_RELEASE, + TAP_EVENT_BUTTON, + TAP_EVENT_TIMEOUT, +}; + +/***************************************** + * DO NOT EDIT THIS FILE! + * + * Look at the state diagram in doc/touchpad-tap-state-machine.svg, or + * online at + * https://drive.google.com/file/d/0B1NwWmji69noYTdMcU1kTUZuUVE/edit?usp=sharing + * (it's a http://draw.io diagram) + * + * Any changes in this file must be represented in the diagram. + */ + +static inline const char* +tap_state_to_str(enum tp_tap_state state) { + + switch(state) { + CASE_RETURN_STRING(TAP_STATE_IDLE); + CASE_RETURN_STRING(TAP_STATE_HOLD); + CASE_RETURN_STRING(TAP_STATE_TOUCH); + CASE_RETURN_STRING(TAP_STATE_TAPPED); + CASE_RETURN_STRING(TAP_STATE_TOUCH_2); + CASE_RETURN_STRING(TAP_STATE_TOUCH_2_HOLD); + CASE_RETURN_STRING(TAP_STATE_TOUCH_3); + CASE_RETURN_STRING(TAP_STATE_TOUCH_3_HOLD); + CASE_RETURN_STRING(TAP_STATE_DRAGGING); + CASE_RETURN_STRING(TAP_STATE_DRAGGING_WAIT); + CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_DOUBLETAP); + CASE_RETURN_STRING(TAP_STATE_DRAGGING_2); + CASE_RETURN_STRING(TAP_STATE_DEAD); + } + return NULL; +} + +static inline const char* +tap_event_to_str(enum tap_event event) { + + switch(event) { + CASE_RETURN_STRING(TAP_EVENT_TOUCH); + CASE_RETURN_STRING(TAP_EVENT_MOTION); + CASE_RETURN_STRING(TAP_EVENT_RELEASE); + CASE_RETURN_STRING(TAP_EVENT_TIMEOUT); + CASE_RETURN_STRING(TAP_EVENT_BUTTON); + } + return NULL; +} +#undef CASE_RETURN_STRING + +static void +tp_tap_notify(struct tp_dispatch *tp, + uint32_t time, + int nfingers, + enum libinput_pointer_button_state state) +{ + int32_t button; + + switch (nfingers) { + case 1: button = BTN_LEFT; break; + case 2: button = BTN_RIGHT; break; + case 3: button = BTN_MIDDLE; break; + default: + return; + } + + pointer_notify_button(&tp->device->base, + time, + button, + state); +} + +static void +tp_tap_set_timer(struct tp_dispatch *tp, uint32_t time) +{ + uint32_t timeout = time + DEFAULT_TAP_TIMEOUT_PERIOD; + struct itimerspec its; + + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = timeout / 1000; + its.it_value.tv_nsec = (timeout % 1000) * 1000 * 1000; + timerfd_settime(tp->tap.timer_fd, TFD_TIMER_ABSTIME, &its, NULL); + + tp->tap.timeout = timeout; +} + +static void +tp_tap_clear_timer(struct tp_dispatch *tp) +{ + tp->tap.timeout = 0; +} + +static void +tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + case TAP_EVENT_MOTION: + log_info("invalid event, no fingers are down\n"); + break; + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_2; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TAPPED; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_TIMEOUT: + case TAP_EVENT_MOTION: + tp->tap.state = TAP_STATE_HOLD; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_2; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_IDLE; + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_MOTION: + case TAP_EVENT_RELEASE: + log_info("invalid event when fingers are up\n"); + break; + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_3; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_HOLD; + tp_tap_notify(tp, time, 2, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, time, 2, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + tp_tap_clear_timer(tp); + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_TOUCH_3; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_HOLD; + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_TOUCH_3_HOLD; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + tp_tap_notify(tp, time, 3, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, time, 3, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TOUCH_2_HOLD; + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + } +} + +static void +tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_2; + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_DRAGGING; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_2; + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_DRAGGING_WAIT; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + /* noop */ + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_RELEASE: + case TAP_EVENT_MOTION: + break; + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_DRAGGING; + break; + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + /* noop */ + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + break; + } +} + +static void +tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + + switch (event) { + case TAP_EVENT_RELEASE: + if (tp->nfingers_down == 0) + tp->tap.state = TAP_STATE_IDLE; + break; + case TAP_EVENT_TOUCH: + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + case TAP_EVENT_BUTTON: + break; + } +} + +static void +tp_tap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +{ + enum tp_tap_state current; + if (!tp->tap.enabled) + return; + + current = tp->tap.state; + + switch(tp->tap.state) { + case TAP_STATE_IDLE: + tp_tap_idle_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH: + tp_tap_touch_handle_event(tp, event, time); + break; + case TAP_STATE_HOLD: + tp_tap_hold_handle_event(tp, event, time); + break; + case TAP_STATE_TAPPED: + tp_tap_tapped_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_2: + tp_tap_touch2_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_2_HOLD: + tp_tap_touch2_hold_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_3: + tp_tap_touch3_handle_event(tp, event, time); + break; + case TAP_STATE_TOUCH_3_HOLD: + tp_tap_touch3_hold_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING_OR_DOUBLETAP: + tp_tap_dragging_or_doubletap_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING: + tp_tap_dragging_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING_WAIT: + tp_tap_dragging_wait_handle_event(tp, event, time); + break; + case TAP_STATE_DRAGGING_2: + tp_tap_dragging2_handle_event(tp, event, time); + break; + case TAP_STATE_DEAD: + tp_tap_dead_handle_event(tp, event, time); + break; + } + + if (tp->tap.state == TAP_STATE_IDLE || tp->tap.state == TAP_STATE_DEAD) + tp_tap_clear_timer(tp); + + log_debug("%s → %s → %s\n", tap_state_to_str(current), tap_event_to_str(event), tap_state_to_str(tp->tap.state)); +} + +static bool +tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp, struct tp_touch *t) +{ + int threshold = DEFAULT_TAP_MOVE_THRESHOLD; + double dx, dy; + + tp_get_delta(t, &dx, &dy); + + return dx * dx + dy * dy > threshold * threshold; +} + +int +tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time) +{ + struct tp_touch *t; + + if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) + tp_tap_handle_event(tp, TAP_EVENT_BUTTON, time); + + tp_for_each_touch(tp, t) { + if (!t->dirty || t->state == TOUCH_NONE) + continue; + + if (t->state == TOUCH_BEGIN) + tp_tap_handle_event(tp, TAP_EVENT_TOUCH, time); + else if (t->state == TOUCH_END) + tp_tap_handle_event(tp, TAP_EVENT_RELEASE, time); + else if (tp->tap.state != TAP_STATE_IDLE && + tp_tap_exceeds_motion_threshold(tp, t)) + tp_tap_handle_event(tp, TAP_EVENT_MOTION, time); + } + + return 0; +} + +static void +tp_tap_timeout_handler(void *data) +{ + struct tp_dispatch *touchpad = data; + uint64_t expires; + int len; + struct timespec ts; + uint32_t now; + + len = read(touchpad->tap.timer_fd, &expires, sizeof expires); + if (len != sizeof expires) + /* This will only happen if the application made the fd + * non-blocking, but this function should only be called + * upon the timeout, so lets continue anyway. */ + fprintf(stderr, "timerfd read error: %m\n"); + + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + + tp_tap_handle_timeout(touchpad, now); +} + +unsigned int +tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time) +{ + if (!tp->tap.enabled) + return 0; + + if (tp->tap.timeout && tp->tap.timeout <= time) { + tp_tap_clear_timer(tp); + tp_tap_handle_event(tp, TAP_EVENT_TIMEOUT, time); + } + + return tp->tap.timeout; +} + +int +tp_init_tap(struct tp_dispatch *tp) +{ + tp->tap.state = TAP_STATE_IDLE; + tp->tap.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + + if (tp->tap.timer_fd == -1) + return -1; + + tp->tap.source = + libinput_add_fd(tp->device->base.seat->libinput, + tp->tap.timer_fd, + tp_tap_timeout_handler, + tp); + + if (tp->tap.source == NULL) { + close(tp->tap.timer_fd); + return -1; + } + + tp->tap.enabled = 1; /* FIXME */ + + return 0; +} diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 73ea8a8d..d1268f6e 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -369,6 +369,7 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) return; } + tp_tap_handle_state(tp, time); if (t->history.count < 4) return; @@ -487,6 +488,9 @@ tp_init(struct tp_dispatch *tp, if (tp_init_accel(tp, diagonal) != 0) return -1; + if (tp_init_tap(tp) != 0) + return -1; + return 0; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 907aec83..973b4784 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -45,6 +45,22 @@ enum touch_state { TOUCH_END }; +enum tp_tap_state { + TAP_STATE_IDLE = 4, + TAP_STATE_TOUCH, + TAP_STATE_HOLD, + TAP_STATE_TAPPED, + TAP_STATE_TOUCH_2, + TAP_STATE_TOUCH_2_HOLD, + TAP_STATE_TOUCH_3, + TAP_STATE_TOUCH_3_HOLD, + TAP_STATE_DRAGGING_OR_DOUBLETAP, + TAP_STATE_DRAGGING, + TAP_STATE_DRAGGING_WAIT, + TAP_STATE_DRAGGING_2, + TAP_STATE_DEAD, /**< finger count exceeded */ +}; + struct tp_motion { int32_t x; int32_t y; @@ -97,6 +113,14 @@ struct tp_dispatch { } buttons; /* physical buttons */ enum touchpad_event queued; + + struct { + bool enabled; + int timer_fd; + struct libinput_source *source; + unsigned int timeout; + enum tp_tap_state state; + } tap; }; #define tp_for_each_touch(_tp, _t) \ @@ -105,4 +129,13 @@ struct tp_dispatch { void tp_get_delta(struct tp_touch *t, double *dx, double *dy); +int +tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time); + +unsigned int +tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time); + +int +tp_init_tap(struct tp_dispatch *tp); + #endif From 0d759edc3f77c99f5940ddb5c5956db31d17c5ed Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 10 Feb 2014 07:44:59 +1000 Subject: [PATCH 11/22] touchpad: Filter motion in a certain number of tap states Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad-tap.c | 23 ++++++++++++++++++++++- src/evdev-mt-touchpad.c | 22 +++++++++++----------- src/evdev-mt-touchpad.h | 1 + 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 7ac592b3..bc7acbd0 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -504,6 +504,7 @@ int tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time) { struct tp_touch *t; + int filter_motion = 0; if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) tp_tap_handle_event(tp, TAP_EVENT_BUTTON, time); @@ -521,7 +522,27 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time) tp_tap_handle_event(tp, TAP_EVENT_MOTION, time); } - return 0; + /** + * In any state where motion exceeding the move threshold would + * move to the next state, filter that motion until we actually + * exceed it. This prevents small motion events while we're waiting + * on a decision if a tap is a tap. + */ + switch (tp->tap.state) { + case TAP_STATE_TOUCH: + case TAP_STATE_TAPPED: + case TAP_STATE_DRAGGING_OR_DOUBLETAP: + case TAP_STATE_TOUCH_2: + case TAP_STATE_TOUCH_3: + filter_motion = 1; + break; + + default: + break; + + } + + return filter_motion; } static void diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index d1268f6e..873ddf0c 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -369,20 +369,20 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) return; } - tp_tap_handle_state(tp, time); - - if (t->history.count < 4) + if (tp_tap_handle_state(tp, time) != 0) return; - tp_get_delta(t, &dx, &dy); - tp_filter_motion(tp, &dx, &dy, time); + if (t->history.count >= TOUCHPAD_MIN_SAMPLES) { + tp_get_delta(t, &dx, &dy); + tp_filter_motion(tp, &dx, &dy, time); - if (dx != 0 || dy != 0) - pointer_notify_motion( - &tp->device->base, - time, - li_fixed_from_double(dx), - li_fixed_from_double(dy)); + if (dx != 0 || dy != 0) + pointer_notify_motion( + &tp->device->base, + time, + li_fixed_from_double(dx), + li_fixed_from_double(dy)); + } tp_post_button_events(tp, time); } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 973b4784..a5cfaa6e 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -30,6 +30,7 @@ #include "filter.h" #define TOUCHPAD_HISTORY_LENGTH 4 +#define TOUCHPAD_MIN_SAMPLES 4 enum touchpad_event { TOUCHPAD_EVENT_NONE = 0, From c1a9b24a0a4e9bf5512f85bfe29aeae45084bc43 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 14 Feb 2014 12:31:26 +1000 Subject: [PATCH 12/22] touchpad: require minimum scroll distance and lock scroll direction This is a fairly rough approach, but can be handled more fine-grained later. Require a minimum of 1 unit to start scrolling and lock the scrolling in the initial direction, so further scroll events are limited to that direction only. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 82 ++++++++++++++++++++++++++++++++++------- src/evdev-mt-touchpad.h | 10 +++++ 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 873ddf0c..14fb7f3f 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -310,16 +310,64 @@ tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time) tp_filter_motion(tp, &dx, &dy, time); - if (dx != 0.0) - pointer_notify_axis(&tp->device->base, - time, - LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL, - li_fixed_from_double(dx)); - if (dy != 0.0) + if (tp->scroll.state == SCROLL_STATE_NONE) { + /* Require at least one px scrolling to start */ + if (dx <= -1.0 || dx >= 1.0) { + tp->scroll.state = SCROLL_STATE_SCROLLING; + tp->scroll.direction |= (1 << LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL); + } + + if (dy <= -1.0 || dy >= 1.0) { + tp->scroll.state = SCROLL_STATE_SCROLLING; + tp->scroll.direction |= (1 << LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL); + } + + if (tp->scroll.state == SCROLL_STATE_NONE) + return; + } + + if (dy != 0.0 && + (tp->scroll.direction & (1 << LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL))) { pointer_notify_axis(&tp->device->base, time, LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL, li_fixed_from_double(dy)); + } + + if (dx != 0.0 && + (tp->scroll.direction & (1 << LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL))) { + pointer_notify_axis(&tp->device->base, + time, + LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL, + li_fixed_from_double(dx)); + } +} + +static int +tp_post_scroll_events(struct tp_dispatch *tp, uint32_t time) +{ + if (tp->nfingers_down != 2) { + /* terminate scrolling with a zero scroll event to notify + * caller that it really ended now */ + if (tp->scroll.state != SCROLL_STATE_NONE) { + tp->scroll.state = SCROLL_STATE_NONE; + tp->scroll.direction = 0; + if (tp->scroll.direction & LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL) + pointer_notify_axis(&tp->device->base, + time, + LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL, + 0); + if (tp->scroll.direction & LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL) + pointer_notify_axis(&tp->device->base, + time, + LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL, + 0); + } + } else { + tp_post_twofinger_scroll(tp, time); + return 1; + } + return 0; } static void @@ -362,16 +410,12 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) struct tp_touch *t = tp_current_touch(tp); double dx, dy; - if (tp->nfingers_down > 2) { - return; - } else if (tp->nfingers_down == 2) { - tp_post_twofinger_scroll(tp, time); - return; - } - if (tp_tap_handle_state(tp, time) != 0) return; + if (tp_post_scroll_events(tp, time) != 0) + return; + if (t->history.count >= TOUCHPAD_MIN_SAMPLES) { tp_get_delta(t, &dx, &dy); tp_filter_motion(tp, &dx, &dy, time); @@ -463,6 +507,15 @@ tp_init_accel(struct tp_dispatch *touchpad, double diagonal) return 0; } +static int +tp_init_scroll(struct tp_dispatch *tp) +{ + tp->scroll.direction = 0; + tp->scroll.state = SCROLL_STATE_NONE; + + return 0; +} + static int tp_init(struct tp_dispatch *tp, struct evdev_device *device) @@ -485,6 +538,9 @@ tp_init(struct tp_dispatch *tp, tp->hysteresis.margin_y = diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; + if (tp_init_scroll(tp) != 0) + return -1; + if (tp_init_accel(tp, diagonal) != 0) return -1; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index a5cfaa6e..17e9055a 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -46,6 +46,11 @@ enum touch_state { TOUCH_END }; +enum scroll_state { + SCROLL_STATE_NONE, + SCROLL_STATE_SCROLLING +}; + enum tp_tap_state { TAP_STATE_IDLE = 4, TAP_STATE_TOUCH, @@ -113,6 +118,11 @@ struct tp_dispatch { uint32_t old_state; } buttons; /* physical buttons */ + struct { + enum scroll_state state; + enum libinput_pointer_axis direction; + } scroll; + enum touchpad_event queued; struct { From 8ea5cb75c1b786e904c341c8963202eac02c8e93 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 14 Feb 2014 15:48:49 +1000 Subject: [PATCH 13/22] touchpad: Only move the pointer when there's a single finger down Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 14fb7f3f..c4c4c41d 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -416,7 +416,8 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) if (tp_post_scroll_events(tp, time) != 0) return; - if (t->history.count >= TOUCHPAD_MIN_SAMPLES) { + if (t->history.count >= TOUCHPAD_MIN_SAMPLES && + tp->nfingers_down == 1) { tp_get_delta(t, &dx, &dy); tp_filter_motion(tp, &dx, &dy, time); From efc8d9517116acf4f5883dfe9698ca0bd7286ad0 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 14 Feb 2014 14:18:27 +1000 Subject: [PATCH 14/22] touchpad: support single-touch touchpads Touchpads without ABS_MT_SLOT create 5 slots by default (for up to QUINTTAP) and ABS_X/Y is mapped to the 0-slot touchpoint. This commit adds handling for a single finger, no BTN_TOOL_DOUBLETAP or similar is being processed yet. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 60 +++++++++++++++++++++++++++++++++++------ src/evdev-mt-touchpad.h | 2 ++ 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index c4c4c41d..07de481f 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -225,6 +225,28 @@ tp_process_absolute(struct tp_dispatch *tp, } } +static void +tp_process_absolute_st(struct tp_dispatch *tp, + const struct input_event *e, + uint32_t time) +{ + struct tp_touch *t = tp_current_touch(tp); + + switch(e->code) { + case ABS_X: + t->x = e->value; + t->millis = time; + t->dirty = true; + break; + case ABS_Y: + t->y = e->value; + t->millis = time; + t->dirty = true; + tp->queued |= TOUCHPAD_EVENT_MOTION; + break; + } +} + static void tp_process_key(struct tp_dispatch *tp, const struct input_event *e, @@ -245,6 +267,18 @@ tp_process_key(struct tp_dispatch *tp, tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE; } break; + case BTN_TOUCH: + if (!tp->has_mt) { + struct tp_touch *t = tp_current_touch(tp); + if (e->value) { + tp_begin_touch(tp, t); + t->fake = true; + } else { + tp_end_touch(tp, t); + } + t->millis = time; + } + break; } } @@ -271,9 +305,10 @@ tp_post_process_state(struct tp_dispatch *tp, uint32_t time) if (!t->dirty) continue; - if (t->state == TOUCH_END) + if (t->state == TOUCH_END) { t->state = TOUCH_NONE; - else if (t->state == TOUCH_BEGIN) + t->fake = false; + } else if (t->state == TOUCH_BEGIN) t->state = TOUCH_UPDATE; t->dirty = false; @@ -443,7 +478,10 @@ tp_process(struct evdev_dispatch *dispatch, switch (e->type) { case EV_ABS: - tp_process_absolute(tp, e, time); + if (tp->has_mt) + tp_process_absolute(tp, e, time); + else + tp_process_absolute_st(tp, e, time); break; case EV_KEY: tp_process_key(tp, e, time); @@ -477,14 +515,20 @@ static int tp_init_slots(struct tp_dispatch *tp, struct evdev_device *device) { - struct input_absinfo absinfo = {0}; + const struct input_absinfo *absinfo; - ioctl(device->fd, EVIOCGABS(ABS_MT_SLOT), &absinfo); - - tp->ntouches = absinfo.maximum + 1; + absinfo = libevdev_get_abs_info(device->evdev, ABS_MT_SLOT); + if (absinfo) { + tp->ntouches = absinfo->maximum + 1; + tp->slot = absinfo->value; + tp->has_mt = true; + } else { + tp->ntouches = 5; /* FIXME: based on DOUBLETAP, etc. */ + tp->slot = 0; + tp->has_mt = false; + } tp->touches = calloc(tp->ntouches, sizeof(struct tp_touch)); - tp->slot = absinfo.value; return 0; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 17e9055a..1e094978 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -75,6 +75,7 @@ struct tp_motion { struct tp_touch { enum touch_state state; bool dirty; + bool fake; /* a fake touch */ int32_t x; int32_t y; uint32_t millis; @@ -96,6 +97,7 @@ struct tp_dispatch { struct evdev_device *device; unsigned int nfingers_down; /* number of fingers down */ unsigned int slot; /* current slot */ + bool has_mt; unsigned int ntouches; /* number of slots */ struct tp_touch *touches; /* len == ntouches */ From 4e8f1259cc9ee74f55e0cf694976868f9ba5d876 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 14 Feb 2014 15:12:22 +1000 Subject: [PATCH 15/22] touchpad: add fake-touch support for BTN_TOOL_DOUBLETAP and friends This enables two-finger scrolling and two- and three-finger tapping on a single-touch touchpad if BTN_TOOL_DOUBLETAP and BTN_TOOL_TRIPLETAP is set. These require a bit of special processing: BTN_TOUCH is set with the first finger down, but somewhat randomly unset and re-set when switching between the various BTN_TOOL_*TAP values. BTN_TOOL_TAP is only set for N fingers down, thus a double->triple move will see a release for DOUBLETAP and a press for TRIPLETAP. This may happen in the same event, or across two consecutive events. This patch adds a fake_touches mask to the touchpad struct. The mask is set for each matching BTN_* event and used to count the number of expected fake touchpoints. From that we begin/end the number of actual touchpoints required. Fake touchpoints take their x/y coordinates from the first touchpoint, which reads ABS_X/ABS_Y. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 78 +++++++++++++++++++++++++++++++++++------ src/evdev-mt-touchpad.h | 1 + 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 07de481f..e8e7731b 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -142,6 +142,13 @@ tp_current_touch(struct tp_dispatch *tp) return &tp->touches[min(tp->slot, tp->ntouches)]; } +static inline struct tp_touch * +tp_get_touch(struct tp_dispatch *tp, unsigned int slot) +{ + assert(slot < tp->ntouches); + return &tp->touches[slot]; +} + static inline void tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t) { @@ -247,6 +254,54 @@ tp_process_absolute_st(struct tp_dispatch *tp, } } +static void +tp_process_fake_touch(struct tp_dispatch *tp, + const struct input_event *e, + uint32_t time) +{ + struct tp_touch *t; + unsigned int fake_touches; + unsigned int nfake_touches; + unsigned int i; + unsigned int shift; + + if (e->code != BTN_TOUCH && + (e->code < BTN_TOOL_DOUBLETAP || e->code > BTN_TOOL_QUADTAP)) + return; + + shift = e->code == BTN_TOUCH ? 0 : (e->code - BTN_TOOL_DOUBLETAP + 1); + + if (e->value) + tp->fake_touches |= 1 << shift; + else + tp->fake_touches &= ~(0x1 << shift); + + fake_touches = tp->fake_touches; + nfake_touches = 0; + while (fake_touches) { + nfake_touches++; + fake_touches >>= 1; + } + + for (i = 0; i < tp->ntouches; i++) { + t = tp_get_touch(tp, i); + if (i >= nfake_touches) { + if (t->state != TOUCH_NONE) { + tp_end_touch(tp, t); + t->millis = time; + } + } else if (t->state != TOUCH_UPDATE && + t->state != TOUCH_BEGIN) { + t->state = TOUCH_NONE; + tp_begin_touch(tp, t); + t->millis = time; + t->fake =true; + } + } + + assert(tp->nfingers_down == nfake_touches); +} + static void tp_process_key(struct tp_dispatch *tp, const struct input_event *e, @@ -268,16 +323,11 @@ tp_process_key(struct tp_dispatch *tp, } break; case BTN_TOUCH: - if (!tp->has_mt) { - struct tp_touch *t = tp_current_touch(tp); - if (e->value) { - tp_begin_touch(tp, t); - t->fake = true; - } else { - tp_end_touch(tp, t); - } - t->millis = time; - } + case BTN_TOOL_DOUBLETAP: + case BTN_TOOL_TRIPLETAP: + case BTN_TOOL_QUADTAP: + if (!tp->has_mt) + tp_process_fake_touch(tp, e, time); break; } } @@ -286,9 +336,15 @@ static void tp_process_state(struct tp_dispatch *tp, uint32_t time) { struct tp_touch *t; + struct tp_touch *first = tp_get_touch(tp, 0); tp_for_each_touch(tp, t) { - if (!t->dirty) + if (!tp->has_mt && t != first && first->fake) { + t->x = first->x; + t->y = first->y; + if (!t->dirty) + t->dirty = first->dirty; + } else if (!t->dirty) continue; tp_motion_hysteresis(tp, t); diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 1e094978..84d8cec7 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -101,6 +101,7 @@ struct tp_dispatch { unsigned int ntouches; /* number of slots */ struct tp_touch *touches; /* len == ntouches */ + unsigned int fake_touches; /* fake touch mask */ struct { int32_t margin_x; From 05f82c573ff54d4b92b76e4195d3218e246deea0 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 17 Feb 2014 11:14:29 +1000 Subject: [PATCH 16/22] touchpad: add support for clickfingers On touchpads without physical buttons, the number of fingers on the touchpad at the time the physical click happens decides the button type. 1/2/3 fingers is handled left/right/middle. We also swallow the motion event on the actual click event, this reduces erroneous motion events by a bit. More processing is needed here though. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 66 ++++++++++++++++++++++++++++++++++++----- src/evdev-mt-touchpad.h | 1 + test/pointer.c | 2 +- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index e8e7731b..93349870 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -461,14 +461,42 @@ tp_post_scroll_events(struct tp_dispatch *tp, uint32_t time) return 0; } -static void -tp_post_button_events(struct tp_dispatch *tp, uint32_t time) +static int +tp_post_clickfinger_buttons(struct tp_dispatch *tp, uint32_t time) { uint32_t current, old, button; + enum libinput_pointer_button_state state; - if ((tp->queued & - (TOUCHPAD_EVENT_BUTTON_PRESS|TOUCHPAD_EVENT_BUTTON_RELEASE)) == 0) - return; + current = tp->buttons.state; + old = tp->buttons.old_state; + + if (current == old) + return 0; + + switch (tp->nfingers_down) { + case 1: button = BTN_LEFT; break; + case 2: button = BTN_RIGHT; break; + case 3: button = BTN_MIDDLE; break; + default: + return 0; + } + + if (current) + state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; + else + state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; + + pointer_notify_button(&tp->device->base, + time, + button, + state); + return 1; +} + +static int +tp_post_physical_buttons(struct tp_dispatch *tp, uint32_t time) +{ + uint32_t current, old, button; current = tp->buttons.state; old = tp->buttons.old_state; @@ -493,6 +521,25 @@ tp_post_button_events(struct tp_dispatch *tp, uint32_t time) current >>= 1; old >>= 1; } + + return 0; +} + +static int +tp_post_button_events(struct tp_dispatch *tp, uint32_t time) +{ + int rc; + + if ((tp->queued & + (TOUCHPAD_EVENT_BUTTON_PRESS|TOUCHPAD_EVENT_BUTTON_RELEASE)) == 0) + return 0; + + if (tp->buttons.has_buttons) + rc = tp_post_physical_buttons(tp, time); + else + rc = tp_post_clickfinger_buttons(tp, time); + + return rc; } static void @@ -501,6 +548,9 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) struct tp_touch *t = tp_current_touch(tp); double dx, dy; + if (tp_post_button_events(tp, time) != 0) + return; + if (tp_tap_handle_state(tp, time) != 0) return; @@ -519,8 +569,6 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) li_fixed_from_double(dx), li_fixed_from_double(dy)); } - - tp_post_button_events(tp, time); } static void @@ -639,6 +687,10 @@ tp_init(struct tp_dispatch *tp, tp->hysteresis.margin_y = diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; + if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT) || + libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) + tp->buttons.has_buttons = true; + if (tp_init_scroll(tp) != 0) return -1; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 84d8cec7..327ce115 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -117,6 +117,7 @@ struct tp_dispatch { } accel; struct { + bool has_buttons; /* true for physical LMR buttons */ uint32_t state; uint32_t old_state; } buttons; /* physical buttons */ diff --git a/test/pointer.c b/test/pointer.c index e864169c..59fe8183 100644 --- a/test/pointer.c +++ b/test/pointer.c @@ -186,7 +186,7 @@ END_TEST int main (int argc, char **argv) { litest_add("pointer:motion", pointer_motion_relative, LITEST_POINTER, LITEST_ANY); - litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_CLICKPAD); litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_ANY); return litest_run(argc, argv); From f3accd3c05e071030b9d22a43b9922d63bfbbef8 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 14 Feb 2014 13:59:41 +1000 Subject: [PATCH 17/22] touchpad: mark the first finger as pointer-controlling finger Unused at the moment, but will be used later to determine if a finger should trigger motion events. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 4 ++++ src/evdev-mt-touchpad.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 93349870..e6b38647 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -159,6 +159,9 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t) tp->nfingers_down++; assert(tp->nfingers_down >= 1); tp->queued |= TOUCHPAD_EVENT_MOTION; + + if (tp->nfingers_down == 1) + t->is_pointer = true; } } @@ -169,6 +172,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t) return; t->dirty = true; + t->is_pointer = false; t->state = TOUCH_END; assert(tp->nfingers_down >= 1); tp->nfingers_down--; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 327ce115..df83b2c5 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -76,6 +76,7 @@ struct tp_touch { enum touch_state state; bool dirty; bool fake; /* a fake touch */ + bool is_pointer; /* the pointer-controlling touch */ int32_t x; int32_t y; uint32_t millis; From 92f5860bfaead1ebc3f956ac21964294b703e546 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 17 Feb 2014 14:24:20 +1000 Subject: [PATCH 18/22] touchpad: Support finger-pinnnig during physical button presses On a clickpad, one finger has be on the trackpad to trigger a physical button press. For drag and drop, we still want motion events though when a second finger is down. This patch adds finger-pinning. If the touchpad is pressed, the pressing finger is "pinned" and ignored for further motion events. A second finger may then be used to drag. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 87 +++++++++++++++++++++++++++++++++++++++-- src/evdev-mt-touchpad.h | 1 + 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index e6b38647..37920798 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -152,6 +152,8 @@ tp_get_touch(struct tp_dispatch *tp, unsigned int slot) static inline void tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t) { + struct tp_touch *tmp; + if (t->state != TOUCH_UPDATE) { tp_motion_history_reset(t); t->dirty = true; @@ -160,8 +162,14 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t) assert(tp->nfingers_down >= 1); tp->queued |= TOUCHPAD_EVENT_MOTION; - if (tp->nfingers_down == 1) + tp_for_each_touch(tp, tmp) { + if (tmp->is_pointer) + break; + } + + if (!tmp->is_pointer) { t->is_pointer = true; + } } } @@ -336,6 +344,53 @@ tp_process_key(struct tp_dispatch *tp, } } +static void +tp_unpin_finger(struct tp_dispatch *tp) +{ + struct tp_touch *t; + tp_for_each_touch(tp, t) { + if (t->is_pinned) { + t->is_pinned = false; + + if (t->state != TOUCH_END && + tp->nfingers_down == 1) + t->is_pointer = true; + break; + } + } +} + +static void +tp_pin_finger(struct tp_dispatch *tp) +{ + struct tp_touch *t, + *pinned = NULL; + + tp_for_each_touch(tp, t) { + if (t->is_pinned) { + pinned = t; + break; + } + } + + assert(!pinned); + + pinned = tp_current_touch(tp); + + if (tp->nfingers_down != 1) { + tp_for_each_touch(tp, t) { + if (t == pinned) + continue; + + if (t->y > pinned->y) + pinned = t; + } + } + + pinned->is_pinned = true; + pinned->is_pointer = false; +} + static void tp_process_state(struct tp_dispatch *tp, uint32_t time) { @@ -354,6 +409,15 @@ tp_process_state(struct tp_dispatch *tp, uint32_t time) tp_motion_hysteresis(tp, t); tp_motion_history_push(t); } + + /* We have a physical button down event on a clickpad. For drag and + drop, this means we try to identify which finger pressed the + physical button and "pin" it, i.e. remove pointer-moving + capabilities from it. + */ + if ((tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) && + !tp->buttons.has_buttons) + tp_pin_finger(tp); } static void @@ -376,6 +440,9 @@ tp_post_process_state(struct tp_dispatch *tp, uint32_t time) tp->buttons.old_state = tp->buttons.state; + if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE) + tp_unpin_finger(tp); + tp->queued = TOUCHPAD_EVENT_NONE; } @@ -441,6 +508,11 @@ tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time) static int tp_post_scroll_events(struct tp_dispatch *tp, uint32_t time) { + /* don't scroll if a clickpad is held down */ + if (!tp->buttons.has_buttons && + (tp->buttons.state || tp->buttons.old_state)) + return 0; + if (tp->nfingers_down != 2) { /* terminate scrolling with a zero scroll event to notify * caller that it really ended now */ @@ -561,8 +633,17 @@ tp_post_events(struct tp_dispatch *tp, uint32_t time) if (tp_post_scroll_events(tp, time) != 0) return; - if (t->history.count >= TOUCHPAD_MIN_SAMPLES && - tp->nfingers_down == 1) { + if (t->history.count >= TOUCHPAD_MIN_SAMPLES) { + if (!t->is_pointer) { + tp_for_each_touch(tp, t) { + if (t->is_pointer) + break; + } + } + + if (!t->is_pointer) + return; + tp_get_delta(t, &dx, &dy); tp_filter_motion(tp, &dx, &dy, time); diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index df83b2c5..c30dc9e4 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -77,6 +77,7 @@ struct tp_touch { bool dirty; bool fake; /* a fake touch */ bool is_pointer; /* the pointer-controlling touch */ + bool is_pinned; /* holds the phys. button */ int32_t x; int32_t y; uint32_t millis; From 6a6103262530d8fca66ee3847d5664824158c12b Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 17 Feb 2014 15:40:09 +1000 Subject: [PATCH 19/22] evdev: drop hook to init old touchpad driver Still leaving the driver itself in place for removal later, but only initialize the new driver now. Signed-off-by: Peter Hutterer --- src/evdev.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/evdev.c b/src/evdev.c index bb2913f8..9279829c 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -600,10 +600,7 @@ evdev_configure_device(struct evdev_device *device) if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_FINGER) && !libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_PEN) && (has_abs || has_mt)) { - if (getenv("LIBINPUT_NEW_TOUCHPAD_DRIVER") && has_mt) - device->dispatch = evdev_mt_touchpad_create(device); - else - device->dispatch = evdev_touchpad_create(device); + device->dispatch = evdev_mt_touchpad_create(device); } for (i = KEY_ESC; i < KEY_MAX; i++) { if (i >= BTN_MISC && i < KEY_OK) From ec2b4bbaff68643a2a442498bcdf7c0a4b82dcdf Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 24 Mar 2014 14:37:16 +1000 Subject: [PATCH 20/22] test: fix keyboard capabilities LITEST_KEYBOARD is the device type, not a feature. Signed-off-by: Peter Hutterer --- test/litest-keyboard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/litest-keyboard.c b/test/litest-keyboard.c index 5c142adf..dd911587 100644 --- a/test/litest-keyboard.c +++ b/test/litest-keyboard.c @@ -106,7 +106,7 @@ litest_create_keyboard(struct litest_device *d) struct litest_test_device litest_keyboard_device = { .type = LITEST_KEYBOARD, - .features = LITEST_KEYBOARD, + .features = LITEST_KEYS, .shortname = "default keyboard", .setup = litest_keyboard_setup, .teardown = litest_generic_device_teardown, From 0c5b8c7404774aeff347df2eab525305fc5440e9 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 21 Mar 2014 14:37:27 +1000 Subject: [PATCH 21/22] test: make sure BTN_TOOL_FINGER and BTN_TOUCH are down Signed-off-by: Peter Hutterer --- test/litest-bcm5974.c | 10 ++++++---- test/litest-synaptics.c | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/test/litest-bcm5974.c b/test/litest-bcm5974.c index 4c4ab00d..5a8ce8a3 100644 --- a/test/litest-bcm5974.c +++ b/test/litest-bcm5974.c @@ -42,6 +42,8 @@ litest_bcm5974_touch_down(struct litest_device *d, static int tracking_id; struct input_event *ev; struct input_event down[] = { + { .type = EV_KEY, .code = BTN_TOOL_FINGER, .value = 1 }, + { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 }, { .type = EV_ABS, .code = ABS_X, .value = x }, { .type = EV_ABS, .code = ABS_Y, .value = y }, { .type = EV_ABS, .code = ABS_PRESSURE, .value = 30 }, @@ -52,10 +54,10 @@ litest_bcm5974_touch_down(struct litest_device *d, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, }; - down[0].value = litest_scale(d, ABS_X, x); - down[1].value = litest_scale(d, ABS_Y, y); - down[5].value = litest_scale(d, ABS_X, x); - down[6].value = litest_scale(d, ABS_Y, y); + down[2].value = litest_scale(d, ABS_X, x); + down[3].value = litest_scale(d, ABS_Y, y); + down[7].value = litest_scale(d, ABS_X, x); + down[8].value = litest_scale(d, ABS_Y, y); ARRAY_FOR_EACH(down, ev) litest_event(d, ev->type, ev->code, ev->value); diff --git a/test/litest-synaptics.c b/test/litest-synaptics.c index f6fa84e2..e4a47835 100644 --- a/test/litest-synaptics.c +++ b/test/litest-synaptics.c @@ -42,6 +42,8 @@ litest_synaptics_clickpad_touch_down(struct litest_device *d, static int tracking_id; struct input_event *ev; struct input_event down[] = { + { .type = EV_KEY, .code = BTN_TOOL_FINGER, .value = 1 }, + { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 }, { .type = EV_ABS, .code = ABS_X, .value = x }, { .type = EV_ABS, .code = ABS_Y, .value = y }, { .type = EV_ABS, .code = ABS_PRESSURE, .value = 30 }, @@ -52,10 +54,10 @@ litest_synaptics_clickpad_touch_down(struct litest_device *d, { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, }; - down[0].value = litest_scale(d, ABS_X, x); - down[1].value = litest_scale(d, ABS_Y, y); - down[5].value = litest_scale(d, ABS_X, x); - down[6].value = litest_scale(d, ABS_Y, y); + down[2].value = litest_scale(d, ABS_X, x); + down[3].value = litest_scale(d, ABS_Y, y); + down[7].value = litest_scale(d, ABS_X, x); + down[8].value = litest_scale(d, ABS_Y, y); ARRAY_FOR_EACH(down, ev) litest_event(d, ev->type, ev->code, ev->value); From 5af33e16c9f2a783106abedc910dcec776243eb3 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 21 Mar 2014 14:41:32 +1000 Subject: [PATCH 22/22] test: add a couple of touchpad tests Signed-off-by: Peter Hutterer --- test/Makefile.am | 7 +- test/touchpad.c | 373 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 test/touchpad.c diff --git a/test/Makefile.am b/test/Makefile.am index a986c0b5..7bf02d18 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -17,7 +17,7 @@ liblitest_la_SOURCES = \ litest-generic-highres-touch.c \ litest.c -run_tests = test-udev test-path test-pointer test-touch test-log +run_tests = test-udev test-path test-pointer test-touch test-log test-touchpad build_tests = test-build-linker test-build-pedantic-c99 test-build-std-gnuc90 noinst_PROGRAMS = $(build_tests) $(run_tests) @@ -48,6 +48,11 @@ test_log_CFLAGS = $(AM_CPPFLAGS) test_log_LDADD = $(TEST_LIBS) test_log_LDFLAGS = -static +test_touchpad_SOURCES = touchpad.c +test_touchpad_CFLAGS = $(AM_CPPFLAGS) +test_touchpad_LDADD = $(TEST_LIBS) +test_touchpad_LDFLAGS = -static + # build-test only test_build_pedantic_c99_SOURCES = build-pedantic.c test_build_pedantic_c99_CFLAGS = $(AM_CPPFLAGS) -std=c99 -pedantic -Werror diff --git a/test/touchpad.c b/test/touchpad.c new file mode 100644 index 00000000..d32a296a --- /dev/null +++ b/test/touchpad.c @@ -0,0 +1,373 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "libinput-util.h" +#include "litest.h" + +START_TEST(touchpad_1fg_motion) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 50, 5); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + + event = libinput_get_event(li); + ck_assert(event != NULL); + + while (event) { + ck_assert_int_eq(libinput_event_get_type(event), + LIBINPUT_EVENT_POINTER_MOTION); + + ptrev = libinput_event_get_pointer_event(event); + ck_assert_int_ge(libinput_event_pointer_get_dx(ptrev), 0); + ck_assert_int_eq(libinput_event_pointer_get_dy(ptrev), 0); + libinput_event_destroy(event); + event = libinput_get_event(li); + } +} +END_TEST + +START_TEST(touchpad_2fg_no_motion) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 70); + litest_touch_move_to(dev, 0, 50, 50, 80, 50, 5); + litest_touch_move_to(dev, 1, 70, 70, 80, 50, 5); + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + libinput_dispatch(li); + + event = libinput_get_event(li); + while (event) { + ck_assert_int_ne(libinput_event_get_type(event), + LIBINPUT_EVENT_POINTER_MOTION); + libinput_event_destroy(event); + event = libinput_get_event(li); + } +} +END_TEST + +static void +assert_button_event(struct libinput *li, int button, + enum libinput_pointer_button_state state) +{ + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + + libinput_dispatch(li); + event = libinput_get_event(li); + + ck_assert(event != NULL); + ck_assert_int_eq(libinput_event_get_type(event), + LIBINPUT_EVENT_POINTER_BUTTON); + ptrev = libinput_event_get_pointer_event(event); + ck_assert_int_eq(libinput_event_pointer_get_button(ptrev), + button); + ck_assert_int_eq(libinput_event_pointer_get_button_state(ptrev), + state); + libinput_event_destroy(event); +} + +START_TEST(touchpad_1fg_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + usleep(300000); /* tap-n-drag timeout */ + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + + libinput_dispatch(li); + event = libinput_get_event(li); + ck_assert(event == NULL); +} +END_TEST + +START_TEST(touchpad_1fg_tap_n_drag) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + + libinput_dispatch(li); + while (libinput_next_event_type(li) == LIBINPUT_EVENT_POINTER_MOTION) { + event = libinput_get_event(li); + libinput_event_destroy(event); + libinput_dispatch(li); + } + + ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE); + + /* lift finger, set down again, should continue dragging */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + while (libinput_next_event_type(li) == LIBINPUT_EVENT_POINTER_MOTION) { + event = libinput_get_event(li); + libinput_event_destroy(event); + libinput_dispatch(li); + } + + ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE); + + usleep(300000); /* tap-n-drag timeout */ + + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + + libinput_dispatch(li); + event = libinput_get_event(li); + ck_assert(event == NULL); +} +END_TEST + +START_TEST(touchpad_2fg_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + + litest_drain_events(dev->libinput); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 70); + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + libinput_dispatch(li); + + assert_button_event(li, BTN_RIGHT, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + usleep(300000); /* tap-n-drag timeout */ + assert_button_event(li, BTN_RIGHT, + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + + libinput_dispatch(li); + event = libinput_get_event(li); + ck_assert(event == NULL); +} +END_TEST + +START_TEST(touchpad_1fg_clickfinger) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); +} +END_TEST + +START_TEST(touchpad_2fg_clickfinger) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 70); + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + libinput_dispatch(li); + + assert_button_event(li, BTN_RIGHT, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + assert_button_event(li, BTN_RIGHT, + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); +} +END_TEST + +START_TEST(touchpad_btn_left) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + enum libinput_pointer_button_state btnstate; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); +} +END_TEST + +START_TEST(clickpad_btn_left) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + /* A clickpad always needs a finger down to tell where the + click happens */ + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE); +} +END_TEST + +START_TEST(clickpad_click_n_drag) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_PRESSED); + + libinput_dispatch(li); + ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE); + + /* now put a second finger down */ + litest_touch_down(dev, 1, 70, 70); + litest_touch_move_to(dev, 1, 70, 70, 80, 50, 5); + litest_touch_up(dev, 1); + + libinput_dispatch(li); + ck_assert_int_eq(libinput_next_event_type(li), + LIBINPUT_EVENT_POINTER_MOTION); + do { + event = libinput_get_event(li); + libinput_event_destroy(event); + libinput_dispatch(li); + } while (libinput_next_event_type(li) == LIBINPUT_EVENT_POINTER_MOTION); + + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_touch_up(dev, 0); + + assert_button_event(li, BTN_LEFT, + LIBINPUT_POINTER_BUTTON_STATE_RELEASED); +} +END_TEST + +int main(int argc, char **argv) { + + litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:motion", touchpad_2fg_no_motion, LITEST_TOUCHPAD, LITEST_ANY); + + litest_add("touchpad:tap", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:tap", touchpad_1fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:tap", touchpad_2fg_tap, LITEST_TOUCHPAD, LITEST_ANY); + + litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_TOUCHPAD, LITEST_ANY); + + litest_add("touchpad:click", touchpad_btn_left, LITEST_TOUCHPAD, LITEST_CLICKPAD); + litest_add("touchpad:click", clickpad_btn_left, LITEST_CLICKPAD, LITEST_ANY); + litest_add("touchpad:click", clickpad_click_n_drag, LITEST_CLICKPAD, LITEST_ANY); + + return litest_run(argc, argv); +}