From a27e21682f827c890d1f4830577cfeaa826a93df Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Thu, 7 Jan 2016 10:41:40 +0200 Subject: [PATCH] [touch] Add handler for multitouch protocol A and B. JB#33644 The existing touch screen input event handling in mce is implemented in several places and in general does handle differences between A and B protocols in robust manner - and assumes that there will be only one touch/mouse input device available. Implement logic for tracking touch input state / double tap detection in a manner that allows the code to be re-used for several input devices when/if needed. --- .depend | 8 + Makefile | 3 + multitouch.c | 557 +++++++++++++++++++++++++++++++++++++++++++++++++++ multitouch.h | 40 ++++ 4 files changed, 608 insertions(+) create mode 100644 multitouch.c create mode 100644 multitouch.h diff --git a/.depend b/.depend index 4107ed6c..adae40da 100644 --- a/.depend +++ b/.depend @@ -898,6 +898,14 @@ modules/usbmode.pic.o:\ mce-log.h\ mce.h\ +multitouch.o:\ + multitouch.c\ + multitouch.h\ + +multitouch.pic.o:\ + multitouch.c\ + multitouch.h\ + powerkey.o:\ powerkey.c\ builtin-gconf.h\ diff --git a/Makefile b/Makefile index a4e0919c..107c4ba6 100644 --- a/Makefile +++ b/Makefile @@ -291,6 +291,7 @@ MCE_CORE += mce-command-line.c MCE_CORE += mce-conf.c MCE_CORE += datapipe.c MCE_CORE += mce-modules.c +MCE_CORE += multitouch.c MCE_CORE += mce-io.c MCE_CORE += mce-lib.c MCE_CORE += evdev.c @@ -575,6 +576,8 @@ NORMALIZE_USES_SPC =\ modules/radiostates.h\ modules/sensor-gestures.c\ modules/usbmode.c\ + multitouch.c\ + multitouch.h\ ofono-dbus-names.h\ powerkey.c\ powerkey.h\ diff --git a/multitouch.c b/multitouch.c new file mode 100644 index 00000000..6191aed3 --- /dev/null +++ b/multitouch.c @@ -0,0 +1,557 @@ +/** + * @file multitouch.c + * + * Mode Control Entity - Tracking for evdev based multitouch devices + * + *

+ * + * Copyright (C) 2015 Jolla Ltd. + * + *

+ * + * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#include "multitouch.h" + +#include +#include +#include +#include +#include + +/* ========================================================================= * + * TYPES & FUNCTIONS + * ========================================================================= */ + +/* ------------------------------------------------------------------------- * + * TOUCH_POINT + * ------------------------------------------------------------------------- */ + +/** Value to use for invalid touch point id */ +#define MT_POINT_ID_INVAL -1 + +/** Value to use for touch point id when protocol does not provide ids */ +#define MT_POINT_ID_DUMMY 0 + +/** Value to use for invalid touch point x & y coordinates */ +#define MT_POINT_XY_INVAL -1 + +typedef struct mt_point_t mt_point_t; + +/** Data for one touch point */ +struct mt_point_t +{ + /** Touch point id from ABS_MT_TRACKING_ID event */ + int mtp_id; + + /** Touch point x-coordinate from ABS_MT_POSITION_X event */ + int mtp_x; + + /** Touch point y-coordinate from ABS_MT_POSITION_Y event */ + int mtp_y; +}; + +static void mt_point_invalidate (mt_point_t *self); +static int mt_point_distance2 (const mt_point_t *a, const mt_point_t *b); + +/* ------------------------------------------------------------------------- * + * TOUCH_VECTOR + * ------------------------------------------------------------------------- */ + +typedef struct mt_touch_t mt_touch_t; + +/** Tracking data for start and end position of one touch sequence */ +struct mt_touch_t +{ + /* Coordinate where first finger was detected on screen */ + mt_point_t mtt_beg_point; + + /* Coordinate where last finger was lifted from screen */ + mt_point_t mtt_end_point; + + /* Timestamp for: Touch started */ + int64_t mtt_beg_tick; + + /* Timestamp for: Touch ended */ + int64_t mtt_end_tick; + + /* Maximum number of fingers seen during the touch */ + size_t mtt_max_fingers; +}; + +/** Maximum jitter allowed in double tap (pixel) coordinates */ +#define MT_TOUCH_DBLTAP_DIST_MAX 100 + +/** Maximum delay between double tap presses and releases [ms] */ +#define MT_TOUCH_DBLTAP_DELAY_MAX 500 + +/** Minimum delay between double tap presses and releases [ms] */ +#define MT_TOUCH_DBLTAP_DELAY_MIN 1 + +static bool mt_touch_is_single_tap(const mt_touch_t *self); +static bool mt_touch_is_double_tap(const mt_touch_t *self, const mt_touch_t *prev); + +/* ------------------------------------------------------------------------- * + * TOUCH_STATE + * ------------------------------------------------------------------------- */ + +/** Maximum number of simultaneous touch points to support */ +#define MT_STATE_POINTS_MAX 16 + +typedef struct mt_state_t mt_state_t; + +/** Tracking data for one multitouch/mouse input device */ +struct mt_state_t +{ + /** Touch point constructed from MT protocol A events */ + mt_point_t mts_accum; + + /** Touch point constructed from mouse events (in SDK emulator) */ + mt_point_t mts_mouse; + + /** Array of touch points */ + mt_point_t mts_point_array[MT_STATE_POINTS_MAX]; + + /** Index to currently constructed touch point + * + * MT protocol B uses explicit ABS_MT_SLOT events while + * on protocol A increment on SYN_MT_REPORT / reset on + * SYN_REPORT event is used. */ + size_t mts_point_slot; + + /** Number of currently active touch points */ + size_t mts_point_count; + + /** Currently tracked primary touch point */ + mt_point_t mts_point_tracked; + + /** Stats for the last 3 taps, used for double tap detection */ + mt_touch_t mts_tap_arr[3]; + + /** Device type / protocol specific input event handler function */ + void (*mts_event_handler_cb)(mt_state_t *, const struct input_event *); + + /** Timestamp from latest evdev input event */ + struct timeval mts_event_time; +}; + +static void mt_state_reset (mt_state_t *self); +static bool mt_state_update (mt_state_t *self); + +static void mt_state_handle_event_a (mt_state_t *self, const struct input_event *ev); +static void mt_state_handle_event_b (mt_state_t *self, const struct input_event *ev); + +mt_state_t *mt_state_create (bool protocol_b); +void mt_state_delete (mt_state_t *self); + +bool mt_state_handle_event (mt_state_t *self, const struct input_event *ev); + +bool mt_state_touching (const mt_state_t *self); + +/* ========================================================================= * + * TOUCH_POINT + * ========================================================================= */ + +/** Reset multi touch point to invalid state + * + * @param self Multi touch point object + */ +static void +mt_point_invalidate(mt_point_t *self) +{ + self->mtp_id = MT_POINT_ID_INVAL; + self->mtp_x = MT_POINT_XY_INVAL; + self->mtp_y = MT_POINT_XY_INVAL; +} + +/** Squared distance between two multi touch point objects + * + * @param a 1st multi touch point object + * @param b 2nd multi touch point object + * + * @return Distance between the two points, squared. + */ +static int mt_point_distance2(const mt_point_t *a, const mt_point_t *b) +{ + int x = b->mtp_x - a->mtp_x; + int y = b->mtp_y - a->mtp_y; + return x*x + y*y; +} + +/* ========================================================================= * + * TOUCH_VECTOR + * ========================================================================= */ + +/** Predicate for: Touch vector represents a single tap + * + * @param self Touch vector object + * + * @return true if touch vector is tap, false otherwise + */ +static bool mt_touch_is_single_tap(const mt_touch_t *self) +{ + bool is_single_tap = false; + + if( !self ) + goto EXIT; + + /* A tap is done using one finger */ + if( self->mtt_max_fingers != 1 ) + goto EXIT; + + /* Touch release must happen close to the point of initial contact */ + int d2 = mt_point_distance2(&self->mtt_beg_point, &self->mtt_end_point); + if( d2 > MT_TOUCH_DBLTAP_DIST_MAX * MT_TOUCH_DBLTAP_DIST_MAX ) + goto EXIT; + + /* The touch duration must not be too short or too long */ + int64_t t = self->mtt_end_tick - self->mtt_beg_tick; + if( t < MT_TOUCH_DBLTAP_DELAY_MIN || t > MT_TOUCH_DBLTAP_DELAY_MAX ) + goto EXIT; + + is_single_tap = true; + +EXIT: + return is_single_tap; +} + +/** Predicate for: Two touch vectors represent a double tap + * + * @param self Current touch vector object + * @param prev Previous touch vector object + * + * @return true if touch vector is double tap, false otherwise + */ +static bool mt_touch_is_double_tap(const mt_touch_t *self, const mt_touch_t *prev) +{ + bool is_double_tap = false; + + /* Both touch vectors must classify as single taps */ + if( !mt_touch_is_single_tap(self) || !mt_touch_is_single_tap(prev) ) + goto EXIT; + + /* The second tap must start near to the end point of the 1st one */ + int d2 = mt_point_distance2(&self->mtt_beg_point, &prev->mtt_end_point); + if( d2 > MT_TOUCH_DBLTAP_DIST_MAX * MT_TOUCH_DBLTAP_DIST_MAX ) + goto EXIT; + + /* The delay between the taps must be sufficiently small too */ + int64_t t = self->mtt_beg_tick - prev->mtt_end_tick; + if( t < MT_TOUCH_DBLTAP_DELAY_MIN || t > MT_TOUCH_DBLTAP_DELAY_MAX ) + goto EXIT; + + is_double_tap = true; + +EXIT: + return is_double_tap; +} + +/* ========================================================================= * + * TOUCH_STATE + * ========================================================================= */ + +/** Reset all tracked multitouch points back to invalid state + * + * @param self Multitouch state object + */ +static void +mt_state_reset(mt_state_t *self) +{ + mt_point_invalidate(&self->mts_accum); + + for( size_t i = 0; i < MT_STATE_POINTS_MAX; ++i ) + mt_point_invalidate(self->mts_point_array + i); + + self->mts_point_slot = 0; +} + +/** Update touch position tracking state + * + * @param self Multitouch state object + * + * @return true if a double tap was just detected, false otherwise + */ +static bool +mt_state_update(mt_state_t *self) +{ + bool dbltap_seen = false; + size_t finger_count = 0; + + /* Count fingers on screen and update position of one finger touch */ + for( size_t i = 0; i < MT_STATE_POINTS_MAX; ++i ) { + if( self->mts_point_array[i].mtp_id == MT_POINT_ID_INVAL ) + continue; + + if( ++finger_count == 1 ) + self->mts_point_tracked = self->mts_point_array[i]; + } + + /* Treat mouse device in SDK simulation as one finger */ + if( self->mts_mouse.mtp_id != MT_POINT_ID_INVAL ) { + if( ++finger_count == 1 ) + self->mts_point_tracked = self->mts_mouse; + } + + /* Skip the rest if the number of fingers on screen does not change */ + if( self->mts_point_count == finger_count ) + goto EXIT; + + /* Convert timestamp from input event to 1ms accurate tick counter */ + int64_t tick = self->mts_event_time.tv_sec * 1000LL + self->mts_event_time.tv_usec / 1000; + + /* When initial touch is detected, update the history buffer to reflect + * the current state of affairs */ + if( self->mts_point_count == 0 ) { + memmove(self->mts_tap_arr+1, self->mts_tap_arr+0, + sizeof self->mts_tap_arr - sizeof *self->mts_tap_arr); + + self->mts_tap_arr[0].mtt_max_fingers = finger_count; + self->mts_tap_arr[0].mtt_beg_point = self->mts_point_tracked; + self->mts_tap_arr[0].mtt_beg_tick = tick; + } + + /* Maintain maximum number of fingers seen on screen */ + if( self->mts_tap_arr[0].mtt_max_fingers < finger_count ) + self->mts_tap_arr[0].mtt_max_fingers = finger_count; + + /* Update touch end position and time */ + self->mts_tap_arr[0].mtt_end_point = self->mts_point_tracked; + self->mts_tap_arr[0].mtt_end_tick = tick; + + /* When final finger is lifted, check if the history buffer content + * looks like a double tap */ + if( finger_count == 0 ) { + if( mt_touch_is_double_tap(self->mts_tap_arr+0, self->mts_tap_arr+1) ) { + if( ! mt_touch_is_double_tap(self->mts_tap_arr+1, self->mts_tap_arr+2) ) + dbltap_seen = true; + } + } + + self->mts_point_count = finger_count; + +EXIT: + return dbltap_seen; +} + +/** Handle multitouch protocol A event stream + * + * Also used for handling event streams from mouse devices + * + * @param self Multitouch state object + * @param ev Input event + */ +static void +mt_state_handle_event_a(mt_state_t *self, const struct input_event *ev) +{ + switch( ev->type ) { + case EV_KEY: + switch( ev->code ) { + case BTN_TOUCH: + if( ev->value == 0 ) + mt_state_reset(self); + break; + + case BTN_MOUSE: + if( ev->value > 0 ) + self->mts_mouse.mtp_id = MT_POINT_ID_DUMMY; + else + self->mts_mouse.mtp_id = MT_POINT_ID_INVAL; + break; + + default: + break; + } + break; + + case EV_REL: + switch( ev->code ) { + case REL_X: + self->mts_mouse.mtp_x += ev->value; + break; + case REL_Y: + self->mts_mouse.mtp_y += ev->value; + break; + default: + break; + } + break; + + case EV_ABS: + switch( ev->code ) { + case ABS_X: + self->mts_mouse.mtp_x = ev->value; + break; + case ABS_Y: + self->mts_mouse.mtp_y = ev->value; + break; + + case ABS_MT_POSITION_X: + self->mts_accum.mtp_x = ev->value; + break; + case ABS_MT_POSITION_Y: + self->mts_accum.mtp_y = ev->value; + break; + case ABS_MT_TRACKING_ID: + self->mts_accum.mtp_id = ev->value; + break; + default: + break; + } + break; + + case EV_SYN: + switch( ev->code ) { + case SYN_MT_REPORT: + if( self->mts_point_slot < MT_STATE_POINTS_MAX && + self->mts_accum.mtp_x != MT_POINT_XY_INVAL && + self->mts_accum.mtp_y != MT_POINT_XY_INVAL ) { + if( self->mts_accum.mtp_id == MT_POINT_ID_INVAL ) + self->mts_accum.mtp_id = MT_POINT_ID_DUMMY; + self->mts_point_array[self->mts_point_slot++] = self->mts_accum; + } + mt_point_invalidate(&self->mts_accum); + break; + + case SYN_REPORT: + for( size_t i = self->mts_point_slot; i < MT_STATE_POINTS_MAX; ++i ) + mt_point_invalidate(self->mts_point_array + i); + self->mts_point_slot = 0; + break; + + default: + break; + } + break; + + default: + break; + } +} + +/** Handle multitouch protocol B event stream + * + * @param self Multitouch state object + * @param ev Input event + */ +static void +mt_state_handle_event_b(mt_state_t *self, const struct input_event *ev) +{ + switch( ev->type ) { + case EV_ABS: + switch( ev->code ) { + case ABS_MT_SLOT: + self->mts_point_slot = ev->value; + if( self->mts_point_slot >= MT_STATE_POINTS_MAX ) + self->mts_point_slot = MT_STATE_POINTS_MAX - 1; + break; + case ABS_MT_TRACKING_ID: + self->mts_point_array[self->mts_point_slot].mtp_id = ev->value; + break; + case ABS_MT_POSITION_X: + self->mts_point_array[self->mts_point_slot].mtp_x = ev->value; + break; + case ABS_MT_POSITION_Y: + self->mts_point_array[self->mts_point_slot].mtp_y = ev->value; + break; + + default: + break; + } + break; + + default: + break; + } +} + +/** Handle input event + * + * @param self Multitouch state object + * @param ev Input event + */ +bool +mt_state_handle_event(mt_state_t *self, const struct input_event *ev) +{ + bool dbltap = false; + + self->mts_event_time = ev->time; + + self->mts_event_handler_cb(self, ev); + + if( ev->type == EV_SYN && ev->code == SYN_REPORT ) + dbltap = mt_state_update(self); + + return dbltap; +} + +/** Check if there is at least one finger on screen at the momement + * + * @param self Multitouch state object + * + * @return true if fingers are on screen, false otherwise + */ +bool +mt_state_touching (const mt_state_t *self) +{ + return self && self->mts_point_count > 0; +} + +/** Release multitouch state object + * + * @param self Multitouch state object, or NULL + */ +void +mt_state_delete(mt_state_t *self) +{ + if( !self ) + goto EXIT; + + free(self); + +EXIT: + return; +} + +/** Allocate multitouch state object + * + * @param protocol_b true if used for tracking multitouch protocol B device + * + * @return multitouch state object + */ +mt_state_t * +mt_state_create(bool protocol_b) +{ + mt_state_t *self = calloc(1, sizeof *self); + + if( !self ) + goto EXIT; + + self->mts_point_count = 0; + + mt_state_reset(self); + + mt_point_invalidate(&self->mts_mouse); + + mt_point_invalidate(&self->mts_point_tracked); + + if( protocol_b ) + self->mts_event_handler_cb = mt_state_handle_event_b; + else + self->mts_event_handler_cb = mt_state_handle_event_a; + +EXIT: + return self; +} diff --git a/multitouch.h b/multitouch.h new file mode 100644 index 00000000..b65a554d --- /dev/null +++ b/multitouch.h @@ -0,0 +1,40 @@ +/** + * @file multitouch.h + * + * Mode Control Entity - Tracking for evdev based multitouch devices + * + *

+ * + * Copyright (C) 2015 Jolla Ltd. + * + *

+ * + * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#ifndef MCE_MULTITOUCH_H_ +# define MCE_MULTITOUCH_H_ + +# include +# include + +typedef struct mt_state_t mt_state_t; + +mt_state_t *mt_state_create (bool protocol_b); +void mt_state_delete (mt_state_t *self); +bool mt_state_handle_event (mt_state_t *self, const struct input_event *ev); +bool mt_state_touching (const mt_state_t *self); + +#endif /* MCE_MULTITOUCH_H_ */