blob: 8011060e639af85c26a42c2475cdffec5cc6a1c7 [file] [log] [blame]
/***************************************************************************
*
* Multitouch X driver
* Copyright (C) 2008 Henrik Rydberg <rydberg@euromail.se>
*
* Gestures
* Copyright (C) 2008 Henrik Rydberg <rydberg@euromail.se>
* Copyright (C) 2010 Arturo Castro <mail@arturocastro.net>
*
* 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
**************************************************************************/
#include "gestures.h"
static const int FINGER_THUMB_MS = 600;
static const int BUTTON_HOLD_MS = 200;
/* After the button goes down, wait this long to decide if it's a left/right
click */
static const int BUTTON_EVAL_MS = 30;
/* Continuously evaluate point vs scroll for this time: */
static const int GESTURE_EVAL_MS = 200;
/* Suppress movement between frames with pressure greater or equal to: */
static const int PRESSURE_DIFF_SUPPRESS = 6;
static int two_fingers_close(struct MTouch* mt);
static int is_two_finger_scroll(struct MTouch* mt);
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/**
* finger_displacement_sq
*
* Returns the square of the displacement for the given figer from the
* previous frame of data.
* Returns 0 if unknown (i.e., there was no such finger in prev state)
*/
int finger_displacement_sq(struct MTouch *mt, int index, int *pressdiff)
{
int i;
struct FingerState* prev_finger = &mt->prev_state.finger;
struct FingerState* finger = &mt->state.finger;
int finger_id = finger[index].tracking_id;
// Find same finger in prev_state
for (i = 0; prev_finger[i].tracking_id != MT_ID_NULL; i++) {
if (prev_finger[i].tracking_id != finger_id)
continue;
int dx = prev_finger[i].position_x - finger[index].position_x;
int dy = prev_finger[i].position_y - finger[index].position_y;
if (pressdiff)
*pressdiff = prev_finger[i].pressure -
finger[index].pressure;
return dx * dx + dy * dy;
}
return 0;
}
/**
* finger_in_dampened_zone
*
* Returns 1 if the specified finger is in the bottom, dampened zone of the pad.
* Returns 0 otherwise.
*/
int finger_in_dampened_zone(struct MTouch* mt, int idx) {
int height = mt->state.finger[idx].position_y -
mt->caps.abs[MTDEV_POSITION_Y].minimum;
static int thresh_height = 0;
if (!thresh_height)
thresh_height = (get_cap_ysize(&mt->caps) * 3) / 4;
// fprintf(stderr, "damp? height: %d thresh: %d (y %d, min %d max %d)\n", height, thresh_height, mt->state.finger[idx].position_y, mt->caps.abs[MTDEV_POSITION_Y].minimum, mt->caps.abs[MTDEV_POSITION_Y].maximum);
return height > thresh_height;
}
/**
* any_pointer_in_dampened_zone
*
* Returns true iff any pointing finger in mt is in the bottom, dampened zone
* of the pad.
*/
int any_pointer_in_dampened_zone(struct MTouch* mt) {
int i;
foreach_bit(i, mt->mem.pointing) {
if (finger_in_dampened_zone(mt, i))
return 1;
}
return 0;
}
/**
* extract_state
*
* Given the current finger positions and existing state, compute the current
* state.
*/
static void extract_state(struct MTouch* mt) {
int two_pointers_close;
int npoint = bitcount(mt->mem.pointing);
if (mt->mem.tripletap) {
fprintf(stderr, "tripletap scrolling mode\n");
mt->mem.state = kGsStateScrolling;
} else if (npoint == 1) {
int idx = firstbit(mt->mem.pointing);
int time_delta = mt->state.evtime - mt->prev_state.evtime;
int thresh = get_cap_xsize(&mt->caps) / 48;
int pressdiff = 0;
int disp_sq = finger_displacement_sq(mt, idx, &pressdiff);
int speed_sq = disp_sq / (time_delta * time_delta);
int thresh_sq = (thresh * thresh) / (16 * 16);
if (!mt->mem.same)
speed_sq = 0;
// fprintf(stderr, "thumb sp^2: %d thr^2: %d\n", speed_sq, thresh_sq);
if (!finger_in_dampened_zone(mt, idx)) {
mt->mem.state = kGsStatePointing;
} else if ((abs(pressdiff) < PRESSURE_DIFF_SUPPRESS) &&
speed_sq > thresh_sq) {
mt->mem.state = kGsStatePointing;
} else if (mt->mem.state != kGsStatePointing) {
mt->mem.state = kGsStateUnknown;
}
} else if ((mt->state.evtime - mt->mem.ldtime) < GESTURE_EVAL_MS ||
mt->mem.state == kGsStateUnknown ||
mt->mem.state == kGsStateNone) {
// See if the two pointers are "close". Close pointers
// can scroll and right-click.
two_pointers_close = two_fingers_close(mt);
if (!two_pointers_close && npoint > 0)
mt->mem.state = kGsStatePointing;
if (two_pointers_close/* && mt->mem.state == kGsStateUnknown*/) {
// Should we switch to scrolling or pointing?
int newstate = is_two_finger_scroll(mt);
fprintf(stderr, "2 finger scroll: %d\n", newstate);
if (newstate >= 0) {
mt->mem.state = (enum gs_state_t)newstate;
}
}
}
// We disable scrolling if the mouse button is down
if (mt->mem.state == kGsStateScrolling && mt->state.button)
mt->mem.state = kGsStatePointing;
}
/**
* eval_btn_type
*
* Given an MTouch, decided if the pad looks like a left or right click.
* Will return BITMASK(MT_BUTTON_LEFT) unless unless it meets the criteria
* for a right click.
*/
static bitmask_t eval_btn_type(struct MTouch* mt)
{
int npoint = bitcount(mt->mem.pointing);
if (mt->mem.tripletap)
return BITMASK(MT_BUTTON_RIGHT);
if (npoint <= 1) {
// fprintf(stderr, "eval_btn_type: left b/c %d finger(s)\n", npoint);
return BITMASK(MT_BUTTON_LEFT);
}
// TODO(adlr): if 3 fingers -> return RIGHT
if (mt->mem.state == kGsStateScrolling) {
// fprintf(stderr, "eval_btn_type: right b/c state is scrolling\n");
return BITMASK(MT_BUTTON_RIGHT);
}
// fprintf(stderr, "eval_btn_type: defer to two_fingers_close\n");
return two_fingers_close(mt) ?
BITMASK(MT_BUTTON_RIGHT) : BITMASK(MT_BUTTON_LEFT);
}
/**
* extract_buttons
*
* Set the button gesture.
*
* Reset memory after use.
*
*/
static void extract_buttons(struct Gestures *gs, struct MTouch* mt)
{
bitmask_t btdata = mt->state.button & BITONES(DIM_BUTTON);
// fprintf(stderr, "button state %d prev %d (state %d)\n",
// mt->state.button,
// mt->prev_state.button, mt->mem.state);
// For now, squash the button
// mt->state.button = btdata = 0;
int npoint = bitcount(mt->mem.pointing);
int phys_down_edge = mt->state.button && !mt->prev_state.button;
int phys_up_edge = !mt->state.button && mt->prev_state.button;
if (mt->state.button == 0 && mt->prev_state.button == 0)
return;
if (phys_down_edge) {
// Button newly down
// fprintf(stderr, "phys button down\n");
mt->mem.btn_type = BITMASK(MT_BUTTON_LEFT);
mt->mem.btn_down_timeout = mt->state.evtime + BUTTON_EVAL_MS;
mt->mem.sent_btn_down = 0;
}
if (!mt->mem.sent_btn_down) {
if (mt->mem.btn_type) {
// See if we think this should be anything other
// than left.
if (mt->mem.btn_type != BITMASK(MT_BUTTON_LEFT)) {
fprintf(stderr, "Odd, shouldn't get here.\n");
}
mt->mem.btn_type = eval_btn_type(mt);
// fprintf(stderr, "evaluating button type. Got %d\n", mt->mem.btn_type);
}
if (mt->mem.btn_type != BITMASK(MT_BUTTON_LEFT) ||
(mt->mem.btn_type == BITMASK(MT_BUTTON_LEFT) &&
mt->state.evtime >= mt->mem.btn_down_timeout) ||
phys_up_edge) {
// Send button down in the following cases:
// - We decided we don't have a left click
// - We timed out on the left click
// - The physical button has gone back up
// fprintf(stderr, "going to send x btn down %d. %lld %lld %d\n", mt->mem.btn_type, mt->state.evtime, mt->mem.btn_down_timeout, phys_up_edge);
gs->btdown = mt->mem.btn_type;
mt->mem.sent_btn_down = 1;
}
}
if (phys_up_edge) {
// Send button up
// fprintf(stderr, "going to send x btn up %d\n", mt->mem.btn_type);
gs->btup = mt->mem.btn_type;
mt->mem.btn_type = 0;
mt->mem.btn_down_timeout = 0;
mt->mem.sent_btn_down = 0;
}
return;
if (mt->state.button && mt->state.button != mt->prev_state.button) {
if (mt->mem.state == kGsStateScrolling ||
mt->mem.state == kGsStateUnknown) {
// fprintf(stderr, "maybe right clicking\n");
if (npoint == 2 && two_fingers_close(mt) &&
!any_pointer_in_dampened_zone(mt)) {
// fprintf(stderr, "BT right click\n");
btdata = BITMASK(MT_BUTTON_RIGHT);
} else {
// fprintf(stderr, "BT left click %d %d %d\n",
// npoint, two_fingers_close(mt),
// !any_pointer_in_dampened_zone(mt));
btdata = BITMASK(MT_BUTTON_LEFT);
}
} else if (mt->mem.state == kGsStatePointing) {
// fprintf(stderr, "BT pointing so left click\n");
btdata = BITMASK(MT_BUTTON_LEFT);
} else {
// fprintf(stderr, "BT odd, no state? %d\n",
// mt->mem.state);
}
// if (npoint == 2)
// btdata = BITMASK(MT_BUTTON_RIGHT);
// if (npoint == 3)
// btdata = BITMASK(MT_BUTTON_MIDDLE);
}
if (mt->state.button != mt->prev_state.button) {
gs->btmask = (btdata ^ mt->mem.btdata) & BITONES(DIM_BUTTON);
gs->btdata = btdata;
mt->mem.btdata = btdata;
} else if (btdata == 0 && mt->mem.ntap) {
if (npoint == 1 && mt->mem.maxtap == 1)
btdata = BITMASK(MT_BUTTON_LEFT);
gs->btmask = (btdata ^ mt->mem.btdata) & BITONES(DIM_BUTTON);
gs->btdata = btdata;
mt->mem.btdata = btdata;
}
if (gs->btmask) {
mt_delay_movement(mt, BUTTON_HOLD_MS);
SETBIT(gs->type, GS_BUTTON);
}
}
// Apply acceleration; x and y are both in and out
// Acceleration concept from:
// http://www.microsoft.com/whdc/archive/pointer-bal.mspx?pf=true#point3
void accel(float* x, float* y, mstime_t timedelta) {
const float kOutDpi = 4;
const float kInDpi = 15;
float newmag;
float timescale = timedelta / 8.0;
int idx;
static float boundary[] = { 1.0,
3.0,
6.0,
1e10 };
static float multiplier[] = { 1.0,
1.5,
2.5,
5.0 };
static float intercept[] = { 0.0,
-0.5,
-3.5,
-18.5 };
// fprintf(stderr, "x: %f y: %f delta: %lld\n", *x, *y, timedelta);
float xin = *x;
float yin = *y;
float mag = sqrtf(xin * xin + yin * yin);
mag /= kInDpi;
mag /= timescale;
if (mag > 3000)
mag = 3000;
for (idx = 0; mag > boundary[idx]; idx++) {}
newmag = mag * multiplier[idx] + intercept[idx];
if (mag < 5) {
newmag = 0.5 * mag * mag;
} else {
newmag = 5 * mag - 12.5;
}
float ratio = newmag / mag;
fprintf(stderr, "mag: %f newmag: %f idx: %d ratio: %f\n", (double)mag, (double)newmag, idx, ratio);
*x = xin * ratio / kOutDpi;
*y = yin * ratio / kOutDpi;
}
/**
* close_angles
*
* Computes whether or not two angles in the range [-pi, pi) are close.
*
* Returns 1 if they are close, 0 otherwise.
*
*/
static int close_angles(float alpha, float beta) {
static const float kMaxSeparation = M_PI / 8.0;
return fmodf(fabsf(alpha - beta) + kMaxSeparation / 2.0, 2 * M_PI) <
kMaxSeparation;
}
/**
* close_angles
*
* Computes the mean (average) of two angles in the range [-pi, pi).
*
* Returns computed mean.
*
*/
static float mean_angle(float alpha, float beta) {
float smaller = fminf(alpha, beta);
float larger = fmaxf(alpha, beta);
if (larger - smaller > M_PI) {
return ((2 * M_PI + smaller) - larger) / 2.0;
} else {
return (larger - smaller) / 2.0;
}
}
static void cartesian_to_polar(float x, float y, float* theta, float* mag) {
*mag = sqrtf(x * x + y * y);
*theta = atan2f(y, x);
}
static void polar_to_cartesian(float theta, float mag, float* x, float* y) {
*x = cosf(theta) * mag;
*y = sinf(theta) * mag;
}
/**
* is_two_finger_scroll
*
* Looks at the accumulated movement of pointing fingers, and determines
* if it seems like a two finger scroll event.
*
* Returns state if known (pointing or scrolling) or -1 if unknown;
*
*/
// helper function:
static void accumulate(int val, int index,
int* large, int* small,
int* large_finger, int* small_finger) {
if (abs(val) > abs(*large)) {
*small = *large;
*small_finger = *large_finger;
*large = val;
*large_finger = index;
} else if (abs(val) > abs(*small)) {
*small = val;
*small_finger = index;
}
}
static int is_two_finger_scroll(struct MTouch* mt) {
static float kLargeThresh = 0.0;
if (kLargeThresh == 0.0) {
kLargeThresh = get_cap_ysize(&mt->caps) * 0.02;
}
int i;
int large_x = 0, large_y = 0;
int small_x = 0, small_y = 0;
int large_x_finger, large_y_finger;
int small_x_finger, small_y_finger;
float large_thresh = kLargeThresh *
(mt->state.evtime - mt->prev_state.evtime) / 12;
float small_thresh;
int xdist = 0; // x distance between two fingers
foreach_bit(i, mt->mem.pointing) {
xdist = mt->state.finger[i].position_x - xdist;
accumulate(mt->mem.dx[i], i,
&large_x, &small_x,
&large_x_finger, &small_x_finger);
accumulate(mt->mem.dy[i], i,
&large_y, &small_y,
&large_y_finger, &small_y_finger);
}
xdist = abs(xdist);
small_thresh = MAX(abs(large_x), abs(large_y)) * 0.35;
// Above the dampened zone, you can scroll by moving only
// one finger.
if (xdist > get_cap_xsize(&mt->caps) * 0.1 &&
!any_pointer_in_dampened_zone(mt)) {
// fprintf(stderr, "One finger scrolling\n");
return kGsStateScrolling;
}
if (abs(large_x) > abs(large_y)) {
// fprintf(stderr, "considering horiz 2f scroll\n");
if (abs(large_x) < large_thresh)
return -1;
// fprintf(stderr, "(lt %f) %d > %d? %d * %d > 0? %d >= %f?\n",
// large_thresh,
// abs(small_x), abs(small_y),
// large_x, small_x,
// abs(small_x), small_thresh);
return (/*(abs(small_x) > abs(small_y)) &&*/ // same axis
(large_x * small_x >= 0) && // same direction
(abs(small_x) >= small_thresh)) ? kGsStateScrolling :
kGsStatePointing;
} else {
// fprintf(stderr, "considering vert 2f scroll %d >= %f\n",
// abs(large_y), large_thresh);
if (abs(large_y) < large_thresh)
return -1;
// fprintf(stderr, "(lt %f) %d > %d? %d * %d > 0? %d >= %f?\n",
// large_thresh,
// abs(small_y), abs(small_x),
// large_y, small_y,
// abs(small_y), small_thresh);
return (/*(abs(small_y) > abs(small_x)) &&*/ // same axis
(large_y * small_y >= 0) && // same direction
(abs(small_y) >= small_thresh)) ? kGsStateScrolling :
kGsStatePointing;
}
}
static int two_fingers_close(struct MTouch* mt) {
int two_pointers_close = 0;
int npoint = bitcount(mt->mem.pointing);
int i;
if (npoint == 2) {
int xdist = 0;
int ydist = 0;
int dpress = 0; // delta in pressure
foreach_bit(i, mt->mem.pointing) {
xdist = mt->state.finger[i].position_x - xdist;
ydist = mt->state.finger[i].position_y - ydist;
dpress = mt->state.finger[i].pressure - dpress;
}
// fprintf(stderr, "depress: %d\n", dpress);
if (abs(dpress) > 17)
return 0;
float dist = sqrtf(xdist * xdist + ydist * ydist);
static float max_dist = -1;
if (max_dist < 0)
max_dist = get_cap_xsize(&mt->caps) * 0.5;
two_pointers_close = dist < max_dist;
// fprintf(stderr, "close: %f %s %f\n", dist, (dist < max_dist ? "<" : ">"), max_dist);
if (two_pointers_close) {
// If the are vertically aligned and any is in the
// dead zone (bottom of the touch pad), make it a
// left click
two_pointers_close = !(abs(xdist) < abs(ydist) &&
any_pointer_in_dampened_zone(mt));
// fprintf(stderr, "adjusted to %d\n", two_pointers_close);
}
}
return two_pointers_close;
}
/**
* extract_movement
*
* Set the movement gesture.
*
* Reset memory after use.
*
*/
static void extract_movement(struct Gestures *gs, struct MTouch* mt)
{
int npoint = bitcount(mt->mem.pointing);
int nmove = bitcount(mt->mem.moving);
int i;
float xp[DIM_FINGER], yp[DIM_FINGER];
float xm[DIM_FINGER], ym[DIM_FINGER];
float xpos = 0, ypos = 0; // mean x position, y position
int two_pointers_close = 0; // if two fingers are near each other
fprintf(stderr, "extract_movement: finger cnt: %d, mv cnd %d, st %d\n",
npoint, nmove, mt->mem.state);
// xmove, ymove are the mean deltas from previous frame (movement).
// move is the magnitude of xmove and ymove vectors added
float move, xmove = 0, ymove = 0;
float rad, rad2 = 0, scale = 0, rot = 0;
if (!nmove /* || nmove != npoint */ )
return;
if (mt->mem.state == kGsStatePointing) {
// handle pointing logic
// fprintf(stderr, "handle pointing\n");
float xmove, ymove;
int max_y = 0, max_y_idx = -1;
foreach_bit(i, mt->mem.pointing) {
if (max_y_idx < 0 ||
mt->state.finger[i].position_y < max_y) {
max_y_idx = i;
max_y = mt->state.finger[i].position_y;
}
}
if (max_y_idx < 0) {
fprintf(stderr, "no pointing??\n");
return;
}
if (GETBIT(mt->mem.pend_palm, max_y_idx)) {
// fprintf(stderr, "movement by pending palm, ignore\n");
return;
}
xmove = mt->state.finger[max_y_idx].position_x -
mt->prev_state.finger[max_y_idx].position_x;
ymove = mt->state.finger[max_y_idx].position_y -
mt->prev_state.finger[max_y_idx].position_y;
int pressure_diff = abs(mt->state.finger[max_y_idx].pressure -
mt->prev_state.finger[max_y_idx].pressure);
if (pressure_diff >= PRESSURE_DIFF_SUPPRESS) {
fprintf(stderr, "too much pressure change: %d - %d (%d)\n",
mt->state.finger[max_y_idx].pressure,
mt->prev_state.finger[max_y_idx].pressure,
pressure_diff);
return;
}
if (xmove == 0 && ymove == 0)
return;
// fprintf(stderr, "pre accel move: %f %f; %d %d\n", xmove, ymove,
// mt->state.finger[max_y_idx].tracking_id,
// mt->prev_state.finger[max_y_idx].tracking_id);
accel(&xmove, &ymove, gs->timedelta);
xmove += mt->mem.rdx;
ymove += mt->mem.rdy;
gs->dx = xmove;
gs->dy = ymove;
gs->fdx = xmove;
gs->fdy = ymove;
mt->mem.rdx = gs->fdx - gs->dx;
mt->mem.rdy = gs->fdy - gs->dy;
// fprintf(stderr, "point result: %d(%f) %d(%f)\n", gs->dx, mt->mem.rdx,
// gs->dy, mt->mem.rdy);
if (gs->dx || gs->dy)
SETBIT(gs->type, GS_MOVE);
return;
}
if (mt->mem.state == kGsStateScrolling) {
// handle scrolling
// fprintf(stderr, "handle scrolling\n");
float xmove = 0.0, ymove = 0.0;
foreach_bit(i, mt->mem.moving) {
xmove += mt->state.finger[i].position_x -
mt->prev_state.finger[i].position_x;
ymove += mt->state.finger[i].position_y -
mt->prev_state.finger[i].position_y;
}
if (xmove == 0 && ymove == 0)
return;
accel(&xmove, &ymove, gs->timedelta);
xmove += mt->mem.rdx;
ymove += mt->mem.rdy;
gs->dx = xmove;
gs->dy = ymove;
gs->fdx = xmove;
gs->fdy = ymove;
mt->mem.rdx = gs->fdx - gs->dx;
mt->mem.rdy = gs->fdy - gs->dy;
// fprintf(stderr, "scroll result: %d(%f) %d(%f)\n", gs->dx, mt->mem.rdx,
// gs->dy, mt->mem.rdy);
if (gs->dx || gs->dy) {
if (abs(gs->dx) > abs(gs->dy))
SETBIT(gs->type, GS_HSCROLL);
else
SETBIT(gs->type, GS_VSCROLL);
}
return;
}
// fprintf(stderr, "handle unknown\n");
return;
// Single pointer movement
int idx = -1;
int min_y = mt->caps.abs[MTDEV_POSITION_Y].maximum;
foreach_bit(i, mt->mem.pointing) {
if (mt->state.finger[i].position_y < min_y) {
idx = i;
min_y = mt->state.finger[i].position_y;
}
}
if (idx < 0) {
// fprintf(stderr, "ERR!!!!!\n");
return;
} else {
float xmove = mt->state.finger[idx].position_x -
mt->prev_state.finger[idx].position_x;
float ymove = mt->state.finger[idx].position_y -
mt->prev_state.finger[idx].position_y;
// fprintf(stderr, "idx: %d x: %f y: %f\n", idx, xmove, ymove);
if (xmove == 0.0 && ymove == 0.0)
return;
accel(&xmove, &ymove, gs->timedelta);
xmove += mt->mem.rdx;
ymove += mt->mem.rdy;
gs->dx = xmove;
gs->dy = ymove;
gs->fdx = xmove;
gs->fdy = ymove;
mt->mem.rdx = gs->fdx - gs->dx;
mt->mem.rdy = gs->fdy - gs->dy;
//fprintf(stderr, "result: %d(%f) %d(%f)\n", gs->dx, mt->mem.rdx,
// gs->dy, mt->mem.rdy);
if (gs->fdx != 0.0 || gs->fdy != 0.0)
SETBIT(gs->type, GS_MOVE);
return;
}
foreach_bit(i, mt->mem.moving) {
xp[i] = mt->state.finger[i].position_x - xpos;
yp[i] = mt->state.finger[i].position_y - ypos;
xm[i] = mt->mem.dx[i];
ym[i] = mt->mem.dy[i];
mt->mem.dx[i] = 0;
mt->mem.dy[i] = 0;
xpos += xp[i];
ypos += yp[i];
xmove += xm[i];
ymove += ym[i];
}
xpos /= nmove;
ypos /= nmove;
xmove /= nmove;
ymove /= nmove;
move = sqrt(xmove * xmove + ymove * ymove);
if (nmove == 1) {
// if (mt->mem.moving & mt->mem.thumb) {
// mt_skip_movement(mt, FINGER_THUMB_MS);
// return;
// }
accel(&xmove, &ymove, gs->timedelta);
gs->fdx = xmove + mt->mem.rdx;
gs->fdy = ymove + mt->mem.rdy;
gs->dx = gs->fdx;
gs->dy = gs->fdy;
mt->mem.rdx = gs->fdx - gs->dx;
mt->mem.rdy = gs->fdy - gs->dy;
if (gs->fdx != 0.0 || gs->fdy != 0.0)
SETBIT(gs->type, GS_MOVE);
return;
}
// printf("gesture moving: 0x%08x\n", mt->mem.moving);
foreach_bit(i, mt->mem.moving) {
xp[i] -= xpos;
yp[i] -= ypos;
rad2 += xp[i] * xp[i];
rad2 += yp[i] * yp[i];
scale += xp[i] * xm[i];
scale += yp[i] * ym[i];
rot += xp[i] * ym[i];
rot -= yp[i] * xm[i];
}
rad2 /= nmove;
scale /= nmove;
rot /= nmove;
rad = sqrt(rad2);
scale /= rad;
rot /= rad;
if (abs(rot) > move && abs(rot) > abs(scale)) {
gs->rot = rot;
if (gs->rot) {
if (nmove == 2)
SETBIT(gs->type, GS_ROTATE);
}
} else if (abs(scale) > move) {
gs->scale = scale;
if (gs->scale) {
if (nmove == 2)
SETBIT(gs->type, GS_SCALE);
}
} else {
// if (mt->mem.moving & mt->mem.thumb) {
// mt_skip_movement(mt, FINGER_THUMB_MS);
// return;
// }
gs->dx = xmove;
gs->dy = ymove;
if (abs(gs->dx) > abs(gs->dy)) {
if (nmove == 2)
SETBIT(gs->type, GS_HSCROLL);
if (nmove == 3)
SETBIT(gs->type, GS_HSWIPE);
}
if (abs(gs->dy) > abs(gs->dx)) {
if (nmove == 2)
SETBIT(gs->type, GS_VSCROLL);
if (nmove == 3)
SETBIT(gs->type, GS_VSWIPE);
}
}
}
/**
* extract_gestures
*
* Extract the gestures.
*
* Reset memory after use.
*
*/
void extract_gestures(struct Gestures *gs, struct MTouch* mt)
{
memset(gs, 0, sizeof(struct Gestures));
gs->same_fingers = mt->mem.same;
gs->timedelta = mt->state.evtime - mt->prev_state.evtime;
extract_state(mt);
extract_buttons(gs, mt);
extract_movement(gs, mt);
mt->prev_state = mt->state;
}
/**
* extract_delayed_gestures
*
* Extract delayed gestures, such as tapping
*
* Reset memory after use.
*
*/
void extract_delayed_gestures(struct Gestures *gs, struct MTouch* mt)
{
mt->mem.wait = 0;
if (mt->mem.tpdown < mt->mem.tpup) {
switch (mt->mem.maxtap) {
case 1:
gs->tapmask = BITMASK(MT_BUTTON_LEFT);
break;
case 2:
gs->tapmask = BITMASK(MT_BUTTON_RIGHT);
break;
case 3:
gs->tapmask = BITMASK(MT_BUTTON_MIDDLE);
break;
}
}
// if (gs->tapmask)
// SETBIT(gs->type, GS_TAP);
gs->ntap = mt->mem.ntap;
}
void output_gesture(const struct Gestures *gs)
{
int i;
foreach_bit(i, gs->btmask)
fprintf(stderr, "button bit: %d %d\n",
i, GETBIT(gs->btdata, i));
if (GETBIT(gs->type, GS_MOVE))
fprintf(stderr, "motion: %d %d\n", gs->dx, gs->dy);
if (GETBIT(gs->type, GS_VSCROLL))
fprintf(stderr, "vscroll: %d\n", gs->dy);
if (GETBIT(gs->type, GS_HSCROLL))
fprintf(stderr, "hscroll: %d\n", gs->dx);
if (GETBIT(gs->type, GS_VSWIPE))
fprintf(stderr, "vswipe: %d\n", gs->dy);
if (GETBIT(gs->type, GS_HSWIPE))
fprintf(stderr, "hswipe: %d\n", gs->dx);
if (GETBIT(gs->type, GS_SCALE))
fprintf(stderr, "scale: %d\n", gs->scale);
if (GETBIT(gs->type, GS_ROTATE))
fprintf(stderr, "rotate: %d\n", gs->rot);
foreach_bit(i, gs->tapmask)
fprintf(stderr, "tap: %d %d\n", i, gs->ntap);
}