[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