[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