[PATCH 12/18] Add pinch gesture support

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


Signed-off-by: Takashi Iwai <tiwai at suse.de>
---
 include/synaptics-properties.h |    3 +
 src/Makefile.am                |    3 +-
 src/keymap.c                   |  227 ++++++++++++++++++++++++++++++++++++++++
 src/properties.c               |   13 +++
 src/synaptics.c                |  105 ++++++++++++++++++
 src/synapticsstr.h             |   21 ++++-
 tools/synclient.c              |    2 +
 7 files changed, 372 insertions(+), 2 deletions(-)
 create mode 100644 src/keymap.c

diff --git a/include/synaptics-properties.h b/include/synaptics-properties.h
index 47f6bb1..7112bc0 100644
--- a/include/synaptics-properties.h
+++ b/include/synaptics-properties.h
@@ -170,4 +170,7 @@
 /* 8 bit (BOOL), double-tap action on LED corner (on/off) */
 #define SYNAPTICS_PROP_LED_DOUBLE_TAP "Synaptics LED Dobule Tap"
 
+/* 32 bit, 2 values, start, delta (0 disable) */
+#define SYNAPTICS_PROP_MULTI_TOUCH_PINCH "Synaptics Multi-Touch Pinch"
+
 #endif /* _SYNAPTICS_PROPERTIES_H_ */
diff --git a/src/Makefile.am b/src/Makefile.am
index 980ab5e..625fea6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -35,7 +35,8 @@ AM_CFLAGS = $(XORG_CFLAGS)
 	alpscomm.c alpscomm.h \
 	ps2comm.c ps2comm.h \
 	synproto.h \
-	properties.c
+	properties.c \
+	keymap.c
 
 if BUILD_EVENTCOMM
 @DRIVER_NAME at _drv_la_SOURCES += \
diff --git a/src/keymap.c b/src/keymap.c
new file mode 100644
index 0000000..b1e7cc6
--- /dev/null
+++ b/src/keymap.c
@@ -0,0 +1,227 @@
+/*
+ * Minimal keyboard support; mostly copied from other drivers
+ *
+ * Copyright (c) 2010 Takashi Iwai
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <sys/shm.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/keysym.h>
+#include <X11/XF86keysym.h>
+#include <xkbsrv.h>
+
+#include "synaptics.h"
+#include "synapticsstr.h"
+
+#define ARRAY_SIZE(x)	(sizeof(x) / sizeof((x)[0]))
+
+static int get_keycode(LocalDevicePtr local, int keysym)
+{
+    KeySymsPtr ksr = XkbGetCoreMap(local->dev);
+    int c;
+
+    for (c = ksr->minKeyCode; c <= ksr->maxKeyCode; c++) {
+	if (ksr->map[(c - ksr->minKeyCode) * ksr->mapWidth] == keysym)
+	    break;
+    }
+    if (c > ksr->maxKeyCode)
+	c = -1;
+    free(ksr);
+    return c;
+}
+
+static int get_modifier(int keysym)
+{
+    static struct { KeySym keysym; CARD8 mask; } modifiers[] = {
+        { XK_Shift_L,           ShiftMask },
+        { XK_Shift_R,           ShiftMask },
+        { XK_Control_L,         ControlMask },
+        { XK_Control_R,         ControlMask },
+        { XK_Caps_Lock,         LockMask },
+        { XK_Alt_L,             Mod1Mask },
+        { XK_Alt_R,             Mod1Mask },
+        { XK_Num_Lock,          Mod2Mask },
+        { XK_Mode_switch,       Mod3Mask },
+        { XK_Scroll_Lock,       Mod5Mask },
+    };
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(modifiers); i++) {
+	if (modifiers[i].keysym == keysym)
+	    return modifiers[i].mask;
+    }
+    return 0;
+}
+
+static struct keysym_table {
+    const char *str;
+    int keysym;
+} keysym_ref[] = {
+    { "Control", XK_Control_L },
+    { "Shift", XK_Shift_L },
+    { "Alt", XK_Alt_L },
+    { "Mode_switch", XK_Mode_switch },
+    { "space", XK_space },
+    { "quotedbl", XK_quotedbl },
+    { "numbersign", XK_numbersign },
+    { "dollar", XK_dollar},
+    { "percent", XK_percent },
+    { "apostrophe", XK_apostrophe },
+    { "plus", XK_plus },
+    { "comma", XK_comma },
+    { "minus", XK_minus },
+    { "backslash", XK_backslash },
+    { "F1", XK_F1 },
+    { "F2", XK_F2 },
+    { "F3", XK_F3 },
+    { "F4", XK_F4 },
+    { "F5", XK_F5 },
+    { "F6", XK_F6 },
+    { "F7", XK_F7 },
+    { "F8", XK_F8 },
+    { "F9", XK_F9 },
+    { "F10", XK_F10 },
+    { "Home", XK_Home },
+    { "Up", XK_Up },
+    { "Prior", XK_Prior },
+    { "Left", XK_Left },
+    { "Begin", XK_Begin },
+    { "Right", XK_Right },
+    { "End", XK_End },
+    { "Down", XK_Down },
+    { "Next", XK_Next },
+    { "Insert", XK_Insert },
+    { "Delete", XK_Delete },
+    { "Return", XK_Return },
+    { "Print", XK_Print },
+    { "BackSpace", XK_BackSpace },
+    { "Tab", XK_Tab },
+    { NULL, NoSymbol }
+};
+
+static int string_to_keysym(const char *str)
+{
+    struct keysym_table *p;
+    for (p = keysym_ref; p->str; p++)
+	if (!strcmp(p->str, str))
+	    return p->keysym;
+    if (*str < 0x80)
+	return *str; /* FIXME: this is a hack; using the first letter */
+    return NoSymbol;
+}
+
+int SynapticsParseActionStr(LocalDevicePtr local, const char *_str,
+			    int *action, int max_actions)
+{
+    char *item;
+    char *str, *next;
+    int num_actions = 0;
+
+    str = xstrdup(_str);
+    if (!str)
+	return 0;
+    for (item = str; item && *item; item = next) {
+	int button, keysym, keycode;
+
+	next = strchr(item, '+');
+	if (next)
+	    *next++ = 0;
+	if (!*item)
+	    continue;
+	if (sscanf(item, "Button%d", &button) == 1) {
+	    action[num_actions++] = (ACTION_BUTTON << 16) | button;
+	} else {
+	    keysym = string_to_keysym(item);
+	    if (keysym == NoSymbol) {
+		xf86Msg(X_WARNING, "%s: invalid keysym '%s'\n",
+			local->name, item);
+		continue;
+	    }
+	    keycode = get_keycode(local, keysym);
+	    if (keycode < 0) {
+		xf86Msg(X_WARNING, "%s: can't get keycode for '%s'\n",
+			local->name, item);
+		continue;
+	    }
+	    if (get_modifier(keysym))
+		action[num_actions++] = (ACTION_KEYMOD << 16) | keycode;
+	    else
+		action[num_actions++] = (ACTION_KEY << 16) | keycode;
+	}
+	if (num_actions >= max_actions)
+	    break;
+    }
+    free(str);
+    return num_actions;
+}
+
+static void
+synaptics_kbdctrl(DeviceIntPtr device, KeybdCtrl *ctrl)
+{
+}
+
+Bool SynapticsInitKeyboard(DeviceIntPtr dev)
+{
+    return InitKeyboardDeviceStruct(dev, NULL, NULL, synaptics_kbdctrl);
+}
+
+void SynapticsSendAction(LocalDevicePtr local, int num_actions, int *action)
+{
+    int n;
+
+    for (n = 0; n < num_actions; n++) {
+	int val = action[n] & 0xffff;
+	switch ((action[n] >> 16) & 0xf) {
+	case ACTION_KEYMOD:
+	    xf86PostKeyboardEvent(local->dev, val, TRUE);
+	    break;
+	case ACTION_KEY:
+	    xf86PostKeyboardEvent(local->dev, val, TRUE);
+	    xf86PostKeyboardEvent(local->dev, val, FALSE);
+	    break;
+	case ACTION_BUTTON:
+	    xf86PostButtonEvent(local->dev, FALSE, val, TRUE, 0, 0);
+	    xf86PostButtonEvent(local->dev, FALSE, val, FALSE, 0, 0);
+	    break;
+	}
+    }
+    for (n = num_actions - 1; n >= 0; n--) {
+	int val = action[n] & 0xffff;
+	switch ((action[n] >> 16) & 0xf) {
+	case ACTION_KEYMOD:
+	    xf86PostKeyboardEvent(local->dev, val, FALSE);
+	    break;
+	}
+    }
+}
diff --git a/src/properties.c b/src/properties.c
index 4042edc..0f679dd 100644
--- a/src/properties.c
+++ b/src/properties.c
@@ -87,6 +87,7 @@ Atom prop_touch_button_sticky   = 0;
 Atom prop_led                   = 0;
 Atom prop_led_status            = 0;
 Atom prop_led_double_tap        = 0;
+Atom prop_multi_touch_pinch     = 0;
 
 static Atom
 InitAtom(DeviceIntPtr dev, char *name, int format, int nvalues, int *values)
@@ -292,6 +293,9 @@ InitDeviceProperties(InputInfoPtr pInfo)
 
     prop_led_double_tap = InitAtom(local->dev, SYNAPTICS_PROP_LED_DOUBLE_TAP, 8, 1, &para->led_double_tap);
 
+    values[0] =  para->multi_touch_pinch_start;
+    values[1] =  para->multi_touch_pinch_dist;
+    prop_multi_touch_pinch = InitAtom(local->dev, SYNAPTICS_PROP_MULTI_TOUCH_PINCH, 32, 2, values);
 }
 
 int
@@ -529,6 +533,15 @@ SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
             return BadMatch;
  
         para->led_double_tap = *(CARD8*)prop->data;
+    } else if (property == prop_multi_touch_pinch)
+    {
+        INT32 *dist;
+        if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        dist = (INT32*)prop->data;
+	para->multi_touch_pinch_start = dist[0];
+	para->multi_touch_pinch_dist = dist[1];
 
     } else if (property == prop_gestures)
     {
diff --git a/src/synaptics.c b/src/synaptics.c
index 05df1c8..902db53 100644
--- a/src/synaptics.c
+++ b/src/synaptics.c
@@ -598,6 +598,8 @@ static void set_default_parameters(InputInfoPtr pInfo)
     pars->touch_button_area = xf86SetIntOption(opts, "TouchButtonArea", 20);
     pars->touch_button_sticky = xf86SetIntOption(opts, "TouchButtonSticky", 64);
     pars->led_double_tap = xf86SetBoolOption(opts, "LEDDoubleTap", TRUE);
+    pars->multi_touch_pinch_start = xf86SetIntOption(opts, "MultiTouchPinchStart", 0);
+    pars->multi_touch_pinch_dist = xf86SetIntOption(opts, "MultiTouchPinchDelta", 0);
 
     /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
     if (pars->top_edge > pars->bottom_edge) {
@@ -751,6 +753,13 @@ SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
 
     CalculateScalingCoeffs(priv);
 
+    if (!SynapticsInitKeyboard(dev)) {
+	ErrorF("unable to init keyboard device\n");
+	return !Success;
+    }
+
+    setup_zoom_actions(local);
+
     if (!alloc_param_data(pInfo))
 	goto SetupProc_fail;
 
@@ -792,6 +801,23 @@ SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
 }
 
 
+static void setup_zoom_actions(LocalDevicePtr local)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (local->private);
+    char *action;
+
+    action = xf86FindOptionValue(local->options, "ZoomInAction");
+    if (!action)
+	action = "Control+Button4";
+    priv->zoom_in_num_actions =
+	SynapticsParseActionStr(local, action, priv->zoom_in_action, MAX_ACTIONS);
+    action = xf86FindOptionValue(local->options, "ZoomOutAction");
+    if (!action)
+	action = "Control+Button5";
+    priv->zoom_out_num_actions =
+	SynapticsParseActionStr(local, action, priv->zoom_out_action, MAX_ACTIONS);
+}
+
 /*
  *  Uninitialize the device.
  */
@@ -2133,6 +2159,12 @@ stop_coasting(SynapticsPrivate *priv)
     priv->scroll_packet_count = 0;
 }
 
+static inline int
+multi_touch_distance(struct SynapticsHwState *hw)
+{
+    return sqrt(SQR(hw->x - hw->multi_touch_x) + SQR(hw->y - hw->multi_touch_y));
+}
+
 static int
 HandleScrolling(SynapticsPrivate *priv, struct SynapticsHwState *hw,
 		edge_type edge, Bool finger, struct ScrollData *sd)
@@ -2413,6 +2445,51 @@ HandleScrolling(SynapticsPrivate *priv, struct SynapticsHwState *hw,
     return delay;
 }
 
+ static void
+handle_multi_touch_pinch(SynapticsPrivate *priv, struct SynapticsHwState *hw,
+			 int *zoom_in, int *zoom_out)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int width = abs(priv->maxx - priv->minx);
+    int dist, dist_diff, abs_diff;
+
+    *zoom_in = *zoom_out = 0;
+
+    if (hw->multi_touch <= 1 || hw->numFingers < 2 ||
+	(priv->multi_touch_mode < MULTI_TOUCH_MODE_GESTURE &&
+	 priv->multi_touch_mode != MULTI_TOUCH_MODE_START))
+	return; /* no multi-touch or in other mode */
+    if (para->multi_touch_pinch_dist <= 0 ||
+	para->multi_touch_pinch_start <= 0)
+	return; /* pinch disabled */
+
+    dist = multi_touch_distance(hw);
+    dist_diff = dist - priv->multi_touch_distance;
+    abs_diff = abs(dist_diff);
+    if (priv->prev_multi_touch_distance > 0 &&
+	abs(dist - priv->prev_multi_touch_distance) > width / 4)
+	return; /* jump? */
+
+    if (abs_diff > para->multi_touch_pinch_start) {
+	if (priv->multi_touch_mode != MULTI_TOUCH_MODE_PINCH) {
+	    priv->multi_touch_mode = MULTI_TOUCH_MODE_PINCH;
+	    priv->vert_scroll_twofinger_on = FALSE;
+	    priv->horiz_scroll_twofinger_on = FALSE;
+	}
+    }
+    if (abs_diff > para->multi_touch_pinch_dist) {
+	int num = abs_diff / para->multi_touch_pinch_dist;
+	if (dist_diff > 0) {
+	    *zoom_in += num;
+	    priv->multi_touch_distance += num * para->multi_touch_pinch_dist;
+	} else {
+	    *zoom_out += num;
+	    priv->multi_touch_distance -= num * para->multi_touch_pinch_dist;
+	}
+    }
+    priv->prev_multi_touch_distance = dist;
+}
+
 static void
 handle_clickfinger(SynapticsParameters *para, struct SynapticsHwState *hw)
 {
@@ -2551,6 +2628,26 @@ update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw, in
    }
 }
 
+static void send_zoom_event(LocalDevicePtr local, int zoom_in, int zoom_out)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (local->private);
+    int dir = zoom_out - zoom_in;
+    int num_actions;
+    int *action;
+
+    if (!dir)
+	return;
+    if (dir < 0) {
+	num_actions = priv->zoom_in_num_actions;
+	action = priv->zoom_in_action;
+    } else {
+	num_actions = priv->zoom_out_num_actions;
+	action = priv->zoom_out_action;
+    }
+
+    SynapticsSendAction(local, num_actions, action);
+}
+
 #define SWAP(a, b) do { int _tmp = (a); (a) = (b); (b) = _tmp; } while (0)
 static void swap_hw_pts(struct SynapticsHwState *hw)
 {
@@ -2580,6 +2677,8 @@ update_multi_touch(SynapticsPrivate *priv, struct SynapticsHwState *hw)
 		priv->multi_touch_mode = clickpad_init_multi_touch_mode(priv, hw);
 	    else
 		priv->multi_touch_mode = MULTI_TOUCH_MODE_START;
+	    priv->multi_touch_distance = multi_touch_distance(hw);
+	    priv->prev_multi_touch_distance = 0;
 	}
     } else {
 	if (priv->multi_touch_mode != MULTI_TOUCH_MODE_NONE) {
@@ -2693,6 +2792,7 @@ HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
     int delay = 1000000000;
     int timeleft;
     Bool inside_active_area;
+    int zoom_in = 0, zoom_out = 0;
 
     update_multi_touch(priv, hw);
 
@@ -2757,6 +2857,8 @@ HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
 	if (timeleft > 0)
 	    delay = MIN(delay, timeleft);
 
+	handle_multi_touch_pinch(priv, hw, &zoom_in, &zoom_out);
+
 	/*
 	 * Compensate for unequal x/y resolution. This needs to be done after
 	 * calculations that require unadjusted coordinates, for example edge
@@ -2822,6 +2924,9 @@ HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
 	xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))), 0, 0);
     }
 
+    send_zoom_event(local, zoom_in, zoom_out);
+
+
     /* Process scroll events only if coordinates are
      * in the Synaptics Area
      */
diff --git a/src/synapticsstr.h b/src/synapticsstr.h
index 34038ae..31756cb 100644
--- a/src/synapticsstr.h
+++ b/src/synapticsstr.h
@@ -165,6 +165,8 @@ typedef struct _SynapticsParameters
     Bool has_led;                           /* has an embedded LED */
     Bool led_status;                        /* Current status of LED (1=on) */
     Bool led_double_tap;		    /* double-tap period in ms for touchpad LED control */
+    int multi_touch_pinch_start;            /* multi-touch pinch sthard threshold distance */
+    int multi_touch_pinch_dist;             /* multi-touch pinch delta threshold distance */
 } SynapticsParameters;
 
 
@@ -173,10 +175,14 @@ enum MultiTouchMode {
     MULTI_TOUCH_MODE_START,
     MULTI_TOUCH_MODE_BUTTON,
     MULTI_TOUCH_MODE_DRAG,
+    MULTI_TOUCH_MODE_PINCH,
+    MULTI_TOUCH_MODE_GESTURE = MULTI_TOUCH_MODE_PINCH,
     MULTI_TOUCH_MODE_SCROLL,
-    MULTI_TOUCH_MODE_GESTURE = MULTI_TOUCH_MODE_SCROLL,
 };
 
+#define MAX_ACTIONS	4
+enum { ACTION_BUTTON = 1, ACTION_KEY, ACTION_KEYMOD };
+
 typedef struct _SynapticsPrivateRec
 {
     SynapticsParameters synpara;            /* Default parameter settings, read from
@@ -263,6 +269,13 @@ typedef struct _SynapticsPrivateRec
     int led_tap_millis;
 
     enum MultiTouchMode multi_touch_mode;
+    int multi_touch_distance;
+    int prev_multi_touch_distance;
+
+    int zoom_in_num_actions;
+    int zoom_in_action[MAX_ACTIONS];
+    int zoom_out_num_actions;
+    int zoom_out_action[MAX_ACTIONS];
 
     enum TouchpadModel model;          /* The detected model */
 } SynapticsPrivate;
@@ -270,4 +283,10 @@ typedef struct _SynapticsPrivateRec
 
 extern void SynapticsDefaultDimensions(InputInfoPtr pInfo);
 
+/* keymap.c */
+Bool SynapticsInitKeyboard(DeviceIntPtr dev);
+int SynapticsParseActionStr(LocalDevicePtr local, const char *_str,
+			    int *action, int max_actions);
+void SynapticsSendAction(LocalDevicePtr local, int num_actions, int *action);
+
 #endif /* _SYNAPTICSSTR_H_ */
diff --git a/tools/synclient.c b/tools/synclient.c
index b51fe42..ab737dc 100644
--- a/tools/synclient.c
+++ b/tools/synclient.c
@@ -147,6 +147,8 @@ static struct Parameter params[] = {
     {"TouchButtonSticky",     PT_INT,    0, 1000,  SYNAPTICS_PROP_TOUCH_BUTTON_STICKY,	32,	0},
     {"LEDStatus",             PT_BOOL,   0, 1,     SYNAPTICS_PROP_LED_STATUS,	8,	0},
     {"LEDDoubleTap",          PT_BOOL,   0, 1,     SYNAPTICS_PROP_LED_DOUBLE_TAP,	8,	0},
+    {"MultiTouchPinchStart",  PT_INT,    0, 1000,  SYNAPTICS_PROP_MULTI_TOUCH_PINCH,	32,	0},
+    {"MultiTouchPinchDelta",  PT_INT,    0, 4000,  SYNAPTICS_PROP_MULTI_TOUCH_PINCH,	32,	1},
     { NULL, 0, 0, 0, 0 }
 };
 
-- 
1.7.3.1



More information about the xorg-devel mailing list