[PATCH 10/18] Add multi-touch support

Takashi Iwai tiwai at suse.de
Fri Oct 8 10:22:34 PDT 2010


Signed-off-by: Takashi Iwai <tiwai at suse.de>
---
 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



More information about the xorg-devel mailing list