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