/**
* @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
/* ========================================================================= *
* 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;
}