Signed-off-by: Takashi Iwai <[email protected]>
---
 src/eventcomm.c    |   56 +++++++++++++++++++--
 src/synaptics.c    |  136 ++++++++++++++++++++++++++++++++++++++++++++++++----
 src/synapticsstr.h |   12 +++++
 src/synproto.h     |    6 ++
 4 files changed, 196 insertions(+), 14 deletions(-)

diff --git a/src/eventcomm.c b/src/eventcomm.c
index 76ff69d..5969448 100644
--- a/src/eventcomm.c
+++ b/src/eventcomm.c
@@ -53,6 +53,17 @@
 
 #define SYNAPTICS_LED_SYS_FILE "/sys/class/leds/psmouse::synaptics/brightness"
 
+#ifndef SYN_MT_REPORT
+#define SYN_MT_REPORT          2
+#endif
+#ifndef ABS_MT_POSITION_X
+#define ABS_MT_POSITION_X      0x35
+#define ABS_MT_POSITION_Y      0x36
+#endif
+#ifndef ABS_MT_PRESSURE
+#define ABS_MT_PRESSURE                0x3a
+#endif
+
 /*****************************************************************************
  *     Function Definitions
  ****************************************************************************/
@@ -168,6 +179,22 @@ event_query_info(InputInfoPtr pInfo)
     }
 }
 
+static void event_query_multi_touch(LocalDevicePtr local)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *)local->private;
+    unsigned long absbits[NBITS(ABS_MAX)] = {0};
+    int rc;
+
+    priv->can_multi_touch = FALSE;
+    if (priv->model != MODEL_SYNAPTICS)
+       return;
+    SYSCALL(rc = ioctl(local->fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), 
absbits));
+    if (rc >= 0 && TEST_BIT(ABS_MT_POSITION_X, absbits)) {
+       priv->can_multi_touch = TRUE;
+       xf86Msg(X_INFO, "%s: supports multi-touch finger detection\n", 
local->name);
+    }
+}
+
 static void
 event_query_clickpad(LocalDevicePtr local)
 {
@@ -175,7 +202,7 @@ event_query_clickpad(LocalDevicePtr local)
 
     /* clickpad device reports only the single left button mask */
     if (priv->has_left && !priv->has_right && !priv->has_middle &&
-       !priv->has_double &&
+       (!priv->has_double || priv->can_multi_touch) &&
        priv->model == MODEL_SYNAPTICS) {
        priv->is_clickpad = TRUE;
        /* enable right/middle button caps; otherwise gnome-settings-daemon
@@ -383,21 +410,27 @@ EventReadHwState(InputInfoPtr pInfo,
        switch (ev.type) {
        case EV_SYN:
            switch (ev.code) {
+           case SYN_MT_REPORT:
+               hw->multi_touch_count++;
+               break;
            case SYN_REPORT:
                if (comm->oneFinger)
-                   hw->numFingers = 1;
+                   hw->numFingers = hw->multi_touch_count ? 
hw->multi_touch_count : 1;
                else if (comm->twoFingers)
                    hw->numFingers = 2;
                else if (comm->threeFingers)
                    hw->numFingers = 3;
                else
                    hw->numFingers = 0;
+               hw->multi_touch = hw->multi_touch_count;
+               hw->multi_touch_count = 0;
                /* if the coord is out of range, we filter it out */
                if (priv->is_clickpad && hw->z > 0 && (hw->x < minx || hw->x > 
maxx || hw->y < miny || hw->y > maxy))
                        return FALSE;
                *hwRet = *hw;
                return TRUE;
            }
+           break;
        case EV_KEY:
            v = (ev.value ? TRUE : FALSE);
            switch (ev.code) {
@@ -458,13 +491,25 @@ EventReadHwState(InputInfoPtr pInfo,
        case EV_ABS:
            switch (ev.code) {
            case ABS_X:
-               hw->x = ev.value;
+           case ABS_MT_POSITION_X:
+               if (hw->multi_touch_count)
+                   hw->multi_touch_x = ev.value;
+               else
+                   hw->x = ev.value;
                break;
            case ABS_Y:
-               hw->y = ev.value;
+           case ABS_MT_POSITION_Y:
+               if (hw->multi_touch_count)
+                   hw->multi_touch_y = ev.value;
+               else
+                   hw->y = ev.value;
                break;
            case ABS_PRESSURE:
-               hw->z = ev.value;
+           case ABS_MT_PRESSURE:
+               if (hw->multi_touch_count)
+                   hw->multi_touch_z = ev.value;
+               else
+                   hw->z = ev.value;
                break;
            case ABS_TOOL_WIDTH:
                hw->fingerWidth = ev.value;
@@ -493,6 +538,7 @@ EventReadDevDimensions(InputInfoPtr pInfo)
     if (event_query_is_touchpad(pInfo->fd, (need_grab) ? *need_grab : TRUE))
        event_query_axis_ranges(pInfo);
     event_query_info(pInfo);
+    event_query_multi_touch(local);
     event_query_clickpad(local);
     event_query_led(local);
 }
diff --git a/src/synaptics.c b/src/synaptics.c
index bd52730..05df1c8 100644
--- a/src/synaptics.c
+++ b/src/synaptics.c
@@ -1198,6 +1198,37 @@ static inline int get_touch_button_area(SynapticsPrivate 
*priv)
 
 #define is_main_bottom_edge(hw, priv) \
     ((hw)->y >= get_touch_button_area(priv))
+#define is_multi_touch_bottom_edge(hw, priv) \
+    ((hw)->multi_touch_y >= get_touch_button_area(priv))
+
+static void swap_hw_pts(struct SynapticsHwState *hw);
+
+/* if only main ptr is in button area, track another ptr as primary */
+static void
+track_clickpad_multi_touch(SynapticsPrivate *priv, struct SynapticsHwState *hw)
+{
+    if (hw->multi_touch > 1 && is_main_bottom_edge(hw, priv) &&
+       !is_multi_touch_bottom_edge(hw, priv) &&
+       (hw->left || priv->multi_touch_mode < MULTI_TOUCH_MODE_GESTURE)) {
+       swap_hw_pts(hw);
+       priv->count_packet_finger = 0; /* to avoid jump */
+    }
+}
+
+static int
+clickpad_init_multi_touch_mode(SynapticsPrivate *priv, struct SynapticsHwState 
*hw)
+{
+    if (hw->multi_touch <= 1)
+       return MULTI_TOUCH_MODE_NONE;
+    /* both fingers in button-area; likely multi-finger gestures */
+    if (is_main_bottom_edge(hw, priv))
+       return MULTI_TOUCH_MODE_START;
+    /* no fingers in button area; normal multi-touch */
+    if (!is_multi_touch_bottom_edge(hw, priv))
+       return MULTI_TOUCH_MODE_START;
+    /* suppress gestures */
+    return MULTI_TOUCH_MODE_BUTTON;
+}
 
 static void reset_state_as_moving(SynapticsPrivate *priv, struct 
SynapticsHwState *hw)
 {
@@ -1219,9 +1250,14 @@ handle_clickpad(LocalDevicePtr local, struct 
SynapticsHwState *hw)
 {
     SynapticsPrivate *priv = (SynapticsPrivate *) (local->private);
     SynapticsParameters *para = &priv->synpara;
-    int in_main_button;
+    int in_main_button, in_multi_button;
 
     in_main_button = is_main_bottom_edge(hw, priv);
+    if (hw->multi_touch > 1)
+       in_multi_button = is_multi_touch_bottom_edge(hw, priv);
+    else
+       in_multi_button = 0;
+
     if (in_main_button) {
        if (hw->left) {
            /* when button is pressed solely, don't move and ignore tapping */
@@ -1239,27 +1275,34 @@ handle_clickpad(LocalDevicePtr local, struct 
SynapticsHwState *hw)
     }
 
     if (hw->left) { /* clicked? */
-       if (priv->prev_hw.left || priv->prev_hw.right || priv->prev_hw.middle) {
+       if (priv->multi_touch_mode == MULTI_TOUCH_MODE_DRAG) {
            /* already dragging, just copy the previous button state */
            hw->left = priv->prev_hw.left;
            hw->right = priv->prev_hw.right;
            hw->middle = priv->prev_hw.middle;
-       } else if (in_main_button) {
+       } else if (in_main_button || in_multi_button) {
            /* start dragging */
            hw->left = 0;
            if (in_main_button)
                get_clickpad_button(priv, hw, hw->x);
+           if (in_multi_button)
+               get_clickpad_button(priv, hw, hw->multi_touch_x);
+           priv->multi_touch_mode = MULTI_TOUCH_MODE_DRAG;
        }
     } else {
        /* button being released, reset dragging if necessary */
-       if (priv->prev_hw.left || priv->prev_hw.right || priv->prev_hw.middle) {
+       if (priv->multi_touch_mode == MULTI_TOUCH_MODE_DRAG) {
+           priv->multi_touch_mode = clickpad_init_multi_touch_mode(priv, hw);
            priv->count_packet_finger = 0;
-           reset_state_as_moving(priv, hw);
+           if (priv->multi_touch_mode != MULTI_TOUCH_MODE_START) {
+               reset_state_as_moving(priv, hw);
+           }
        }
        hw->left = hw->right = hw->middle = 0;
     }
 
-    if (in_main_button && para->touch_button_sticky > 0) {
+    if (priv->multi_touch_mode == MULTI_TOUCH_MODE_NONE &&
+       in_main_button && para->touch_button_sticky > 0) {
        if (!priv->count_packet_finger)  {
            /* if the primary track point is in the button area, be sticky */
            priv->clickpad_threshold = para->touch_button_sticky;
@@ -1962,7 +2005,8 @@ ComputeDeltas(SynapticsPrivate *priv, const struct 
SynapticsHwState *hw,
     if (inside_area && moving_state && !priv->palm &&
        !priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
        !priv->vert_scroll_twofinger_on && !priv->horiz_scroll_twofinger_on &&
-       !priv->circ_scroll_on && priv->prevFingers == hw->numFingers) {
+       !priv->circ_scroll_on && priv->prevFingers == hw->numFingers &&
+       priv->multi_touch_mode < MULTI_TOUCH_MODE_GESTURE) {
        /* FIXME: Wtf?? what's with 13? */
        delay = MIN(delay, 13);
        if (priv->count_packet_finger > 3) { /* min. 3 packets */
@@ -2130,7 +2174,8 @@ HandleScrolling(SynapticsPrivate *priv, struct 
SynapticsHwState *hw,
     }
     if (!priv->circ_scroll_on) {
        if (finger) {
-           if (hw->numFingers == 2) {
+           if (hw->numFingers == 2 &&
+               priv->multi_touch_mode <= MULTI_TOUCH_MODE_START) {
                if (!priv->vert_scroll_twofinger_on &&
                    (para->scroll_twofinger_vert) && (para->scroll_dist_vert != 
0)) {
                    priv->vert_scroll_twofinger_on = TRUE;
@@ -2269,10 +2314,14 @@ HandleScrolling(SynapticsPrivate *priv, struct 
SynapticsHwState *hw,
            while (hw->y - priv->scroll_y > delta) {
                sd->down++;
                priv->scroll_y += delta;
+               if (priv->multi_touch_mode == MULTI_TOUCH_MODE_START)
+                   priv->multi_touch_mode = MULTI_TOUCH_MODE_SCROLL;
            }
            while (hw->y - priv->scroll_y < -delta) {
                sd->up++;
                priv->scroll_y -= delta;
+               if (priv->multi_touch_mode == MULTI_TOUCH_MODE_START)
+                   priv->multi_touch_mode = MULTI_TOUCH_MODE_SCROLL;
            }
        }
     }
@@ -2283,10 +2332,14 @@ HandleScrolling(SynapticsPrivate *priv, struct 
SynapticsHwState *hw,
            while (hw->x - priv->scroll_x > delta) {
                sd->right++;
                priv->scroll_x += delta;
+               if (priv->multi_touch_mode == MULTI_TOUCH_MODE_START)
+                   priv->multi_touch_mode = MULTI_TOUCH_MODE_SCROLL;
            }
            while (hw->x - priv->scroll_x < -delta) {
                sd->left++;
                priv->scroll_x -= delta;
+               if (priv->multi_touch_mode == MULTI_TOUCH_MODE_START)
+                   priv->multi_touch_mode = MULTI_TOUCH_MODE_SCROLL;
            }
        }
     }
@@ -2477,7 +2530,8 @@ update_hw_button_state(const InputInfoPtr pInfo, struct 
SynapticsHwState *hw, in
     hw->middle |= HandleMidButtonEmulation(priv, hw, delay);
 
     /* Fingers emulate other buttons */
-    if(hw->left && hw->numFingers >= 1){
+    if(hw->left && hw->numFingers >= 1 &&
+       priv->multi_touch_mode < MULTI_TOUCH_MODE_BUTTON) {
         handle_clickfinger(para, hw);
     }
 
@@ -2486,6 +2540,65 @@ update_hw_button_state(const InputInfoPtr pInfo, struct 
SynapticsHwState *hw, in
         hw->fingerWidth >= para->emulate_twofinger_w) {
        hw->numFingers = 2;
     }
+
+    /* don't handle as multi-touching if a finger is in the click zone */
+    if (hw->numFingers > 1 &&
+       (priv->multi_touch_mode == MULTI_TOUCH_MODE_BUTTON ||
+        priv->multi_touch_mode == MULTI_TOUCH_MODE_DRAG)) {
+       hw->numFingers = 1;
+       priv->vert_scroll_twofinger_on = FALSE;
+       priv->horiz_scroll_twofinger_on = FALSE;
+   }
+}
+
+#define SWAP(a, b) do { int _tmp = (a); (a) = (b); (b) = _tmp; } while (0)
+static void swap_hw_pts(struct SynapticsHwState *hw)
+{
+    SWAP(hw->x, hw->multi_touch_x);
+    SWAP(hw->y, hw->multi_touch_y);
+    SWAP(hw->z, hw->multi_touch_z);
+}
+
+static inline int square_distance(int x0, int y0, int x1, int y1)
+{
+    return SQR(x0 - x1) + SQR(y0 - y1);
+}
+
+static void
+update_multi_touch(SynapticsPrivate *priv, struct SynapticsHwState *hw)
+{
+    if (hw->multi_touch > 1) {
+       /* track the new multi-touch */
+       if (square_distance(priv->prev_hw.x, priv->prev_hw.y, hw->x, hw->y) >
+           square_distance(priv->prev_hw.x, priv->prev_hw.y,
+                           hw->multi_touch_x, hw->multi_touch_y))
+           swap_hw_pts(hw);
+       if (priv->is_clickpad)
+           track_clickpad_multi_touch(priv, hw);
+       if (priv->multi_touch_mode == MULTI_TOUCH_MODE_NONE) {
+           if (priv->is_clickpad)
+               priv->multi_touch_mode = clickpad_init_multi_touch_mode(priv, 
hw);
+           else
+               priv->multi_touch_mode = MULTI_TOUCH_MODE_START;
+       }
+    } else {
+       if (priv->multi_touch_mode != MULTI_TOUCH_MODE_NONE) {
+           if (priv->multi_touch_mode == MULTI_TOUCH_MODE_DRAG)
+               reset_state_as_moving(priv, hw);
+           /* Reset some states to avoid the unexpected jump after
+            * releasing the multi-touch finger.
+            * This is rather hackish, so a cleaner way is needed...
+            */
+           if (priv->finger_state && hw->z) {
+               priv->touch_on.x = hw->x;
+               priv->touch_on.x = hw->y;
+           }
+           priv->count_packet_finger = 0;
+           priv->multi_touch_mode = MULTI_TOUCH_MODE_NONE;
+           priv->vert_scroll_twofinger_on = FALSE;
+           priv->horiz_scroll_twofinger_on = FALSE;
+       }
+    }
 }
 
 static void
@@ -2581,6 +2694,8 @@ HandleState(InputInfoPtr pInfo, struct SynapticsHwState 
*hw)
     int timeleft;
     Bool inside_active_area;
 
+    update_multi_touch(priv, hw);
+
     update_shm(pInfo, hw);
 
     /* If touchpad is switched off, we skip the whole thing and return delay */
@@ -2661,6 +2776,9 @@ HandleState(InputInfoPtr pInfo, struct SynapticsHwState 
*hw)
     }
     delay = MIN(delay, timeleft);
 
+    /* don't move pointer while multi-touching (except for clickpad dragging) 
*/
+    if (priv->multi_touch_mode != MULTI_TOUCH_MODE_NONE && hw->numFingers > 1)
+       dx = dy = 0;
 
     buttons = ((hw->left     ? 0x01 : 0) |
               (hw->middle   ? 0x02 : 0) |
diff --git a/src/synapticsstr.h b/src/synapticsstr.h
index 44925e5..34038ae 100644
--- a/src/synapticsstr.h
+++ b/src/synapticsstr.h
@@ -168,6 +168,15 @@ typedef struct _SynapticsParameters
 } SynapticsParameters;
 
 
+enum MultiTouchMode {
+    MULTI_TOUCH_MODE_NONE,
+    MULTI_TOUCH_MODE_START,
+    MULTI_TOUCH_MODE_BUTTON,
+    MULTI_TOUCH_MODE_DRAG,
+    MULTI_TOUCH_MODE_SCROLL,
+    MULTI_TOUCH_MODE_GESTURE = MULTI_TOUCH_MODE_SCROLL,
+};
+
 typedef struct _SynapticsPrivateRec
 {
     SynapticsParameters synpara;            /* Default parameter settings, 
read from
@@ -241,6 +250,7 @@ typedef struct _SynapticsPrivateRec
     Bool has_width;                    /* device reports finger width */
     Bool has_scrollbuttons;            /* device has physical scrollbuttons */
     Bool is_clickpad;                  /* is Clickpad device (one-button) */
+    Bool can_multi_touch;              /* support multi-touch */
     Bool ignore_tapping;
     unsigned int clickpad_threshold;
     int clickpad_dx, clickpad_dy;
@@ -252,6 +262,8 @@ typedef struct _SynapticsPrivateRec
     int led_touch_millis;
     int led_tap_millis;
 
+    enum MultiTouchMode multi_touch_mode;
+
     enum TouchpadModel model;          /* The detected model */
 } SynapticsPrivate;
 
diff --git a/src/synproto.h b/src/synproto.h
index eee56e2..a430699 100644
--- a/src/synproto.h
+++ b/src/synproto.h
@@ -50,6 +50,12 @@ struct SynapticsHwState {
 
     Bool multi[8];
     Bool middle;               /* Some ALPS touchpads have a middle button */
+
+    int multi_touch;
+    int multi_touch_count;
+    int multi_touch_x;
+    int multi_touch_y;
+    int multi_touch_z;
 };
 
 struct CommData {
-- 
1.7.3.1

_______________________________________________
[email protected]: X.Org development
Archives: http://lists.x.org/archives/xorg-devel
Info: http://lists.x.org/mailman/listinfo/xorg-devel

Reply via email to