From 463c5d1effa3505d54a5a520d143919ad1befc69 Mon Sep 17 00:00:00 2001 From: Ping Cheng Date: Tue, 15 Dec 2009 21:05:38 -0800 Subject: Support 2FGT gesture There are 3 gesture features: second finger tap to right click (GESTURE_TAP_MODE) vertical/horizontal scroll (GESTURE_SCROLL_MODE) and zoom in/out (GESTURE_ZOOM_MODE) Signed-off-by: Ping Cheng Signed-off-by: Peter Hutterer --- src/Makefile.am | 2 +- src/wcmCommon.c | 44 ++++++ src/wcmTouchFilter.c | 406 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/xf86WacomDefs.h | 3 + 4 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 src/wcmTouchFilter.c diff --git a/src/Makefile.am b/src/Makefile.am index df1481b..461089f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -36,5 +36,5 @@ INCLUDES=-I$(top_srcdir)/include/ wcmCommon.c wcmConfig.c wcmISDV4.c \ wcmFilter.c wcmFilter.h xf86WacomDefs.h \ wcmTilt2Rotation.c wcmUSB.c wcmXCommand.c \ - wcmValidateDevice.c + wcmValidateDevice.c wcmTouchFilter.c diff --git a/src/wcmCommon.c b/src/wcmCommon.c index c5c4a57..434715d 100644 --- a/src/wcmCommon.c +++ b/src/wcmCommon.c @@ -34,6 +34,7 @@ extern void xf86WcmInitialCoordinates(LocalDevicePtr local, int axes); extern void xf86WcmVirtualTabletSize(LocalDevicePtr local); extern void xf86WcmVirtualTabletPadding(LocalDevicePtr local); extern void xf86WcmTilt2R(WacomDeviceStatePtr ds); +extern void xf86WcmFingerTapToClick(WacomCommonPtr common); /***************************************************************************** * Static functions @@ -1211,7 +1212,50 @@ void xf86WcmEvent(WacomCommonPtr common, unsigned int channel, pChannel->valid.state = ds; /*save last raw sample */ if (pChannel->nSamples < common->wcmRawSample) ++pChannel->nSamples; + /* process second finger data if exists + * and both touch and geature are enabled */ + if ((ds.device_type == TOUCH_ID) && + common->wcmTouch && common->wcmGesture) + { + WacomChannelPtr pOtherChannel; + WacomDeviceState dsOther; + + /* exit gesture mode when both fingers are out */ + if (channel) + pOtherChannel = common->wcmChannel; + else + pOtherChannel = common->wcmChannel + 1; + dsOther = pOtherChannel->valid.state; + + /* This is the only place to reset gesture mode + * once a gesture mode is entered */ + if (!ds.proximity && !dsOther.proximity) + { + common->wcmGestureMode = 0; + + /* send a touch out-prox event here + * in case the FF was out before the SF */ + channel = 0; + } + else + { + /* don't move the cursor if in gesture mode + * wait for second finger data to process gestures */ + if (!channel && common->wcmGestureMode) + goto ret; + + /* process gesture */ + if (channel) + { + xf86WcmFingerTapToClick(common); + goto ret; + } + } + } + + /* everything else falls here */ commonDispatchDevice(common,channel,pChannel, suppress); +ret: resetSampleCounter(pChannel); } diff --git a/src/wcmTouchFilter.c b/src/wcmTouchFilter.c new file mode 100644 index 0000000..ebf7ba7 --- /dev/null +++ b/src/wcmTouchFilter.c @@ -0,0 +1,406 @@ +/* + * Copyright 2009 by Ping Cheng, Wacom Technology. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "xf86Wacom.h" +#include + +/* Defines for 2FC Gesture */ +#define WACOM_DIST_IN_POINT 300 +#define WACOM_APART_IN_POINT 350 +#define WACOM_MOTION_IN_POINT 50 +#define WACOM_PARA_MOTION_IN_POINT 50 +#define WACOM_DOWN_TIME_IN_MS 800 +#define WACOM_TIME_BETWEEN_IN_MS 400 +#define WACOM_HORIZ_ALLOWED 1 +#define WACOM_VERT_ALLOWED 2 + +#define GESTURE_TAP_MODE 1 +#define GESTURE_SCROLL_MODE 2 +#define GESTURE_ZOOM_MODE 4 + +#define WCM_SCROLL_UP 5 /* vertical up */ +#define WCM_SCROLL_DOWN 4 /* vertical down */ + + +/* Defines for Tap Add-a-Finger to Click */ +#define WACOM_TAP_TIME_IN_MS 150 + +void xf86WcmFingerTapToClick(WacomCommonPtr common); + +extern void xf86WcmRotateCoordinates(LocalDevicePtr local, int x, int y); +extern void emitKeysym (DeviceIntPtr keydev, int keysym, int state); + +static void xf86WcmFingerScroll(WacomDevicePtr priv); +static void xf86WcmFingerZoom(WacomDevicePtr priv); + +static double touchDistance(WacomDeviceState ds0, WacomDeviceState ds1) +{ + int xDelta = ds0.x - ds1.x; + int yDelta = ds0.y - ds1.y; + double distance = sqrt((double)(xDelta*xDelta + yDelta*yDelta)); + return distance; +} + +static Bool pointsInLine(WacomDeviceState ds0, WacomDeviceState ds1, + int *direction) +{ + Bool ret = FALSE; + + if (*direction == 0) + { + if (abs(ds0.x - ds1.x) < WACOM_PARA_MOTION_IN_POINT) + { + *direction = WACOM_VERT_ALLOWED; + ret = TRUE; + } + else if (abs(ds0.y - ds1.y) < WACOM_PARA_MOTION_IN_POINT) + { + *direction = WACOM_HORIZ_ALLOWED; + ret = TRUE; + } + } + else if (*direction == WACOM_HORIZ_ALLOWED) + { + if (abs(ds0.y - ds1.y) < WACOM_PARA_MOTION_IN_POINT) + ret = TRUE; + } + else if (*direction == WACOM_VERT_ALLOWED) + { + if (abs(ds0.x - ds1.x) < WACOM_PARA_MOTION_IN_POINT) + ret = TRUE; + } + return ret; +} + +static Bool pointsInLineAfter(int p1, int p2) +{ + Bool ret = FALSE; + + if (abs(p1 - p2) < WACOM_PARA_MOTION_IN_POINT) + ret = TRUE; + + return ret; +} + +static void xf86WcmSwitchLeftClick(WacomDevicePtr priv) +{ + WacomCommonPtr common = priv->common; + + if (common->wcmGestureMode) + { + /* send button one up */ + xf86PostButtonEvent(priv->local->dev, + priv->flags & ABSOLUTE_FLAG, + 1,0,0,priv->naxes, priv->oldX, + priv->oldY,0,0,0,0); + priv->oldButtons = 0; + } +} + +/***************************************************************************** + * translate second finger tap to right click + ****************************************************************************/ + +void xf86WcmFingerTapToClick(WacomCommonPtr common) +{ + WacomDevicePtr priv = common->wcmDevices; + WacomChannelPtr firstChannel = common->wcmChannel; + WacomChannelPtr secondChannel = common->wcmChannel + 1; + WacomDeviceState ds[2] = { firstChannel->valid.states[0], + secondChannel->valid.states[0] }; + WacomDeviceState dsLast[2] = { firstChannel->valid.states[1], + secondChannel->valid.states[1] }; + int direction = 0; + + DBG(10, priv->debugLevel, ErrorF("xf86WcmFingerTapToClick \n")); + + /* skip initial second finger event */ + if (!dsLast[1].proximity) + goto skipGesture; + + if (!IsTouch(priv)) + { + /* go through the shared port */ + for (; priv; priv = priv->next) + if (IsTouch(priv)) + break; + } + + if (priv) /* found the first finger */ + { + /* allow only second finger tap */ + if ((dsLast[0].sample < dsLast[1].sample) && ((GetTimeInMillis() - + dsLast[1].sample) <= WACOM_TAP_TIME_IN_MS)) + { + /* send right click when second finger taps within WACOM_TAP_TIMEms + * and both fingers stay within WACOM_DIST */ + if (!ds[1].proximity && dsLast[1].proximity) + { + if (touchDistance(ds[0], dsLast[1]) <= WACOM_DIST_IN_POINT) + { + /* send left up before sending right down */ + if (!common->wcmGestureMode) + { + common->wcmGestureMode = GESTURE_TAP_MODE; + xf86WcmSwitchLeftClick(priv); + } + + /* right button down */ + xf86PostButtonEvent(priv->local->dev, + priv->flags & ABSOLUTE_FLAG, + 3,1,0,priv->naxes, priv->oldX, + priv->oldY,0,0,0,0); + /* right button up */ + xf86PostButtonEvent(priv->local->dev, + priv->flags & ABSOLUTE_FLAG, + 3,0,0,priv->naxes, priv->oldX, + priv->oldY,0,0,0,0); + } + } + } + else if ((WACOM_TAP_TIME_IN_MS < (GetTimeInMillis() - dsLast[0].sample)) + && (WACOM_TAP_TIME_IN_MS < (GetTimeInMillis() - dsLast[1].sample)) + && ds[0].proximity && ds[1].proximity) + { + if (abs(touchDistance(ds[0], ds[1])) >= WACOM_APART_IN_POINT && + common->wcmGestureMode != GESTURE_TAP_MODE && + common->wcmGestureMode != GESTURE_SCROLL_MODE) + { + /* fingers moved apart more than WACOM_APART_IN_POINT + * zoom mode is entered */ + if (!common->wcmGestureMode) + { + common->wcmGestureMode = GESTURE_ZOOM_MODE; + xf86WcmSwitchLeftClick(priv); + } + xf86WcmFingerZoom(priv); + } + + if ( pointsInLine(ds[0], dsLast[0], &direction) && + pointsInLine(ds[1], dsLast[1], &direction) && + common->wcmGestureMode != GESTURE_ZOOM_MODE && + common->wcmGestureMode != GESTURE_TAP_MODE) + { + /* send scroll event when both fingers move in + * the same direction */ + if (!common->wcmGestureMode) + { + common->wcmGestureMode = GESTURE_SCROLL_MODE; + xf86WcmSwitchLeftClick(priv); + } + xf86WcmFingerScroll(priv); + } + } + } + else + /* this should never happen */ + xf86Msg(X_ERROR, "WACOM: No touch device found for %s \n", common->wcmDevice); + +skipGesture: + /* keep the initial in-prox time */ + ds[0].sample = dsLast[0].sample; + ds[1].sample = dsLast[1].sample; + + /* keep the initial states for both fingers */ + if ( !(common->wcmGestureMode && (GESTURE_SCROLL_MODE | GESTURE_ZOOM_MODE)) + && ds[0].proximity && ds[1].proximity) + { + common->wcmGestureState[0] = ds[0]; + common->wcmGestureState[1] = ds[1]; + } +} + +static void xf86WcmSendVerticalScrollEvent(WacomDevicePtr priv, + int dist, int up, int dn) +{ + int i = 0; + + for (i=0; i<(int)(((double)abs(dist)/ + (double)WACOM_MOTION_IN_POINT) + 0.5); i++) + { + if (dist > 0) + { + /* button down */ + xf86PostButtonEvent(priv->local->dev, + priv->flags & ABSOLUTE_FLAG, + up,1,0,priv->naxes, priv->oldX, + priv->oldY,0,0,0,0); + /* button up */ + xf86PostButtonEvent(priv->local->dev, + priv->flags & ABSOLUTE_FLAG, + up,0,0,priv->naxes, priv->oldX, + priv->oldY,0,0,0,0); + } + else + { + /* button down */ + xf86PostButtonEvent(priv->local->dev, + priv->flags & ABSOLUTE_FLAG, + dn,1,0,priv->naxes, priv->oldX, + priv->oldY,0,0,0,0); + /* button up */ + xf86PostButtonEvent(priv->local->dev, + priv->flags & ABSOLUTE_FLAG, + dn,0,0,priv->naxes, priv->oldX, + priv->oldY,0,0,0,0); + } + } +} + +static void xf86WcmSendHorizontalScrollEvent(WacomDevicePtr priv, + int dist, int left, int right) +{ + int i = 0; + + for (i=0; i<(int)(((double)abs(dist)/ + (double)WACOM_MOTION_IN_POINT) + 0.5); i++) + { + if (dist > 0) + { + emitKeysym (priv->local->dev, left, 1); + emitKeysym (priv->local->dev, left, 0); + } + else + { + emitKeysym (priv->local->dev, right, 1); + emitKeysym (priv->local->dev, right, 0); + } + } +} + +static void xf86WcmFingerScroll(WacomDevicePtr priv) +{ + WacomCommonPtr common = priv->common; + WacomChannelPtr firstChannel = common->wcmChannel; + WacomChannelPtr secondChannel = common->wcmChannel + 1; + WacomDeviceState ds[2] = { firstChannel->valid.states[0], + secondChannel->valid.states[0] }; + WacomDeviceState dsLast[2] = { firstChannel->valid.states[1], + secondChannel->valid.states[1] }; + int midPoint_new = 0; + int midPoint_old = 0; + int i = 0, dist =0; + int gesture = 0; + WacomFilterState filterd; /* borrow this struct */ + + DBG(10, priv->debugLevel, ErrorF("xf86WcmFingerScroll \n")); + + /* initialize the points so we can rotate them */ + filterd.x[0] = ds[0].x; + filterd.y[0] = ds[0].y; + filterd.x[1] = ds[1].x; + filterd.y[1] = ds[1].y; + filterd.x[2] = common->wcmGestureState[0].x; + filterd.y[2] = common->wcmGestureState[0].y; + filterd.x[3] = common->wcmGestureState[1].x; + filterd.y[3] = common->wcmGestureState[1].y; + filterd.x[4] = dsLast[0].x; + filterd.y[4] = dsLast[0].y; + filterd.x[5] = dsLast[1].x; + filterd.y[5] = dsLast[1].y; + + /* rotate the coordinates first */ + for (i=0; i<6; i++) + xf86WcmRotateCoordinates(priv->local, filterd.x[i], filterd.y[i]); + + /* check vertical direction */ + midPoint_old = (((double)filterd.x[4] + (double)filterd.x[5]) / 2.); + midPoint_new = (((double)filterd.x[0] + (double)filterd.x[1]) / 2.); + if (pointsInLineAfter(midPoint_old, midPoint_new)) + { + midPoint_old = (((double)filterd.y[2] + (double)filterd.y[3]) / 2.); + midPoint_new = (((double)filterd.y[0] + (double)filterd.y[1]) / 2.); + dist = midPoint_old - midPoint_new; + + if (abs(dist) > WACOM_PARA_MOTION_IN_POINT) + { + gesture = 1; + xf86WcmSendVerticalScrollEvent(priv, dist, + WCM_SCROLL_UP, WCM_SCROLL_DOWN); + } + + /* check horizontal direction */ + if (!gesture) + { + midPoint_old = (((double)filterd.y[4] + (double)filterd.y[5]) / 2.); + midPoint_new = (((double)filterd.y[0] + (double)filterd.y[1]) / 2.); + if (pointsInLineAfter(midPoint_old, midPoint_new)) + { + midPoint_old = (((double)filterd.x[2] + (double)filterd.x[3]) / 2.); + midPoint_new = (((double)filterd.x[0] + (double)filterd.x[1]) / 2.); + dist = midPoint_old - midPoint_new; + + if (abs(dist) > WACOM_PARA_MOTION_IN_POINT) + { + gesture = 1; + xf86WcmSendHorizontalScrollEvent(priv, dist, XK_Left, XK_Right); + } + } + } + if (gesture) + { + /* reset initial states */ + common->wcmGestureState[0] = ds[0]; + common->wcmGestureState[1] = ds[1]; + } + } +} + +static void xf86WcmFingerZoom(WacomDevicePtr priv) +{ + WacomCommonPtr common = priv->common; + WacomChannelPtr firstChannel = common->wcmChannel; + WacomChannelPtr secondChannel = common->wcmChannel + 1; + WacomDeviceState ds[2] = { firstChannel->valid.states[0], + secondChannel->valid.states[0] }; + int i = 0; + int dist = touchDistance(common->wcmGestureState[0], + common->wcmGestureState[1]); + + DBG(10, priv->debugLevel, ErrorF("xf86WcmFingerZoom \n")); + + dist = touchDistance(ds[0], ds[1]) - dist; + + /* zooming? */ + if (abs(dist) > WACOM_MOTION_IN_POINT) + { + for (i=0; i<(int)(((double)abs(dist)/ + (double)WACOM_MOTION_IN_POINT) + 0.5); i++) + { + emitKeysym (priv->local->dev, XK_Control_L, 1); + /* zooming in */ + if (dist > 0) + { + emitKeysym (priv->local->dev, XK_plus, 1); + emitKeysym (priv->local->dev, XK_plus, 0); + } + else /* zooming out */ + { + emitKeysym (priv->local->dev, XK_minus, 1); + emitKeysym (priv->local->dev, XK_minus, 0); + } + emitKeysym (priv->local->dev, XK_Control_L, 0); + } + + /* reset initial states */ + common->wcmGestureState[0] = ds[0]; + common->wcmGestureState[1] = ds[1]; + } +} + +/* vim: set noexpandtab shiftwidth=8: */ diff --git a/src/xf86WacomDefs.h b/src/xf86WacomDefs.h index 2412692..6634d5c 100644 --- a/src/xf86WacomDefs.h +++ b/src/xf86WacomDefs.h @@ -366,6 +366,7 @@ struct _WacomDeviceClass #define DEVICE_ISDV4 0x000C #define MAX_CHANNELS 2 +#define MAX_FINGERS 2 struct _WacomCommonRec { @@ -426,6 +427,8 @@ struct _WacomCommonRec int wcmTouchDefault; /* default to disable when not supported */ int wcmGesture; /* disable/enable touch gesture */ int wcmGestureDefault; /* default touch gesture to disable when not supported */ + int wcmGestureMode; /* data is in Gesture Mode? */ + WacomDeviceState wcmGestureState[MAX_FINGERS]; /* inital state when in gesture mode */ int wcmCapacity; /* disable/enable capacity */ int wcmCapacityDefault; /* default to -1 when capacity isn't supported/disabled */ /* 3 when capacity is supported */ -- cgit v1.2.1