[PATCH 10/18] Add multi-touch support

Peter Hutterer peter.hutterer at who-t.net
Tue Oct 12 21:25:16 PDT 2010


On Fri, Oct 08, 2010 at 07:22:34PM +0200, Takashi Iwai wrote:
> 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;

has_multitouch would be more in line with the has_left, has_right, etc.

> +    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);
> +    }
> +}
> +

I think we should just rely on MT_SLOTS here and wrap devices that don't do
it with mtdev. It'll likely make the code simpler (especially in regards to
tracking the primary) and I can blame Henrik if the tracking doesn't work ;)


>  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;

if I read this correctly this patch doesn't add multi-touch support but only
two-finger support, otherwise you'd be overwriting the value after the
second finger.

a different alternative to this would be to buffer all MT events in a
hw->mt[touchpoint] array of structs and then transfer the first to
hw->x/y/z at the end of HandleState/EV_SYN. it would greatly reduce the
number of if (multitouch) and improve readability.

but aside from this, this patch needs to be split into the actual MT
handling and the clickpad-related area handling. as for gestures in the
driver - see my other email.

Cheers,
  Peter
>  		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