blob: cf91b9fa5cf7ca59e2a7a2ed21f116e30fef31bb [file] [log] [blame]
/*
* Copyright © 1999 Henry Davies
* Copyright © 2001 Stefan Gmeiner
* Copyright © 2002 S. Lehner
* Copyright © 2002 Peter Osterlund
* Copyright © 2002 Linuxcare Inc. David Kennedy
* Copyright © 2003 Hartwig Felger
* Copyright © 2003 Jörg Bösner
* Copyright © 2003 Fred Hucht
* Copyright © 2004 Alexei Gilchrist
* Copyright © 2004 Matthias Ihmig
* Copyright © 2006 Stefan Bethge
* Copyright © 2006 Christian Thaeter
* Copyright © 2007 Joseph P. Skudlarek
* Copyright © 2008 Fedor P. Goncharov
* Copyright © 2008-2009 Red Hat, Inc.
*
* 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.
*
* Authors:
* Joseph P. Skudlarek <Jskud@Jskud.com>
* Christian Thaeter <chth@gmx.net>
* Stefan Bethge <stefan.bethge@web.de>
* Matthias Ihmig <m.ihmig@gmx.net>
* Alexei Gilchrist <alexei@physics.uq.edu.au>
* Jörg Bösner <ich@joerg-boesner.de>
* Hartwig Felger <hgfelger@hgfelger.de>
* Peter Osterlund <petero2@telia.com>
* S. Lehner <sam_x@bluemail.ch>
* Stefan Gmeiner <riddlebox@freesurf.ch>
* Henry Davies <hdavies@ameritech.net> for the
* Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
* Fred Hucht <fred@thp.Uni-Duisburg.de>
* Fedor P. Goncharov <fedgo@gorodok.net>
* Simon Thum <simon.thum@gmx.de>
*
* Trademarks are the property of their respective owners.
*/
#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 <math.h>
#include <stdio.h>
#include <xf86_OSproc.h>
#include <xf86Xinput.h>
#include <exevents.h>
#include "synaptics.h"
#include "synapticsstr.h"
#include "synaptics-properties.h"
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
#include <X11/Xatom.h>
#include <xserver-properties.h>
#include <ptrveloc.h>
#endif
typedef enum {
NO_EDGE = 0,
BOTTOM_EDGE = 1,
TOP_EDGE = 2,
LEFT_EDGE = 4,
RIGHT_EDGE = 8,
LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
} edge_type;
#define MAX(a, b) (((a)>(b))?(a):(b))
#define MIN(a, b) (((a)<(b))?(a):(b))
#define TIME_DIFF(a, b) ((int)((a)-(b)))
#define SQR(x) ((x) * (x))
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef M_SQRT1_2
#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
#endif
#define INPUT_BUFFER_SIZE 200
/*****************************************************************************
* Forward declaration
****************************************************************************/
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 12
static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
#else
static InputInfoPtr SynapticsPreInit(InputDriverPtr drv, IDevPtr dev, int flags);
#endif
static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
static Bool DeviceControl(DeviceIntPtr, int);
static void ReadInput(InputInfoPtr);
static int HandleState(InputInfoPtr, struct SynapticsHwState*);
static int ControlProc(InputInfoPtr, xDeviceCtl*);
static int SwitchMode(ClientPtr, DeviceIntPtr, int);
static Bool DeviceInit(DeviceIntPtr);
static Bool DeviceOn(DeviceIntPtr);
static Bool DeviceOff(DeviceIntPtr);
static Bool DeviceClose(DeviceIntPtr);
static Bool QueryHardware(InputInfoPtr);
static void ReadDevDimensions(InputInfoPtr);
static void ScaleCoordinates(SynapticsPrivate *priv, struct SynapticsHwState *hw);
static void CalculateScalingCoeffs(SynapticsPrivate *priv);
static void SanitizeDimensions(InputInfoPtr pInfo);
void InitDeviceProperties(InputInfoPtr pInfo);
int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
BOOL checkonly);
const static struct {
const char *name;
struct SynapticsProtocolOperations *proto_ops;
} protocols[] = {
#ifdef BUILD_EVENTCOMM
{"event", &event_proto_operations},
#endif
#ifdef BUILD_PSMCOMM
{"psm", &psm_proto_operations},
#endif
#ifdef BUILD_PS2COMM
{"psaux", &psaux_proto_operations},
{"alps", &alps_proto_operations},
#endif
{NULL, NULL}
};
InputDriverRec SYNAPTICS = {
1,
"synaptics",
NULL,
SynapticsPreInit,
SynapticsUnInit,
NULL,
};
static XF86ModuleVersionInfo VersionRec = {
"synaptics",
MODULEVENDORSTRING,
MODINFOSTRING1,
MODINFOSTRING2,
XORG_VERSION_CURRENT,
PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
ABI_CLASS_XINPUT,
ABI_XINPUT_VERSION,
MOD_CLASS_XINPUT,
{0, 0, 0, 0}
};
static pointer
SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
{
xf86AddInputDriver(&SYNAPTICS, module, 0);
return module;
}
_X_EXPORT XF86ModuleData synapticsModuleData = {
&VersionRec,
&SetupProc,
NULL
};
/*****************************************************************************
* Function Definitions
****************************************************************************/
/**
* Fill in default dimensions for backends that cannot query the hardware.
* Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
* These values are based so that calculate_edge_widths() will give us the
* right values.
*
* The default values 1900, etc. come from the dawn of time, when men where
* men, or possibly apes.
*/
static void
SanitizeDimensions(InputInfoPtr pInfo)
{
SynapticsPrivate *priv = (SynapticsPrivate *)pInfo->private;
if (priv->minx >= priv->maxx)
{
priv->minx = 1615;
priv->maxx = 5685;
priv->resx = 0;
xf86IDrvMsg(pInfo, X_PROBED,
"invalid x-axis range. defaulting to %d - %d\n",
priv->minx, priv->maxx);
}
if (priv->miny >= priv->maxy)
{
priv->miny = 1729;
priv->maxy = 4171;
priv->resy = 0;
xf86IDrvMsg(pInfo, X_PROBED,
"invalid y-axis range. defaulting to %d - %d\n",
priv->miny, priv->maxy);
}
if (priv->minp >= priv->maxp)
{
priv->minp = 0;
priv->maxp = 256;
xf86IDrvMsg(pInfo, X_PROBED,
"invalid pressure range. defaulting to %d - %d\n",
priv->minp, priv->maxp);
}
if (priv->minw >= priv->maxw)
{
priv->minw = 0;
priv->maxw = 16;
xf86IDrvMsg(pInfo, X_PROBED,
"invalid finger width range. defaulting to %d - %d\n",
priv->minw, priv->maxw);
}
}
static void
SetDeviceAndProtocol(InputInfoPtr pInfo)
{
SynapticsPrivate *priv = pInfo->private;
char *proto, *device;
int i;
proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
device = xf86SetStrOption(pInfo->options, "Device", NULL);
for (i = 0; protocols[i].name; i++) {
if ((!device || !proto) &&
protocols[i].proto_ops->AutoDevProbe &&
protocols[i].proto_ops->AutoDevProbe(pInfo, device))
break;
else if (proto && !strcmp(proto, protocols[i].name))
break;
}
free(proto);
free(device);
priv->proto_ops = protocols[i].proto_ops;
}
/*
* Allocate and initialize read-only memory for the SynapticsParameters data to hold
* driver settings.
* The function will allocate shared memory if priv->shm_config is TRUE.
*/
static Bool
alloc_shm_data(InputInfoPtr pInfo)
{
int shmid;
SynapticsPrivate *priv = pInfo->private;
if (priv->synshm)
return TRUE; /* Already allocated */
if (priv->shm_config) {
if ((shmid = shmget(SHM_SYNAPTICS, 0, 0)) != -1)
shmctl(shmid, IPC_RMID, NULL);
if ((shmid = shmget(SHM_SYNAPTICS, sizeof(SynapticsSHM),
0774 | IPC_CREAT)) == -1) {
xf86IDrvMsg(pInfo, X_ERROR, "error shmget\n");
return FALSE;
}
if ((priv->synshm = (SynapticsSHM*)shmat(shmid, NULL, 0)) == NULL) {
xf86IDrvMsg(pInfo, X_ERROR, "error shmat\n");
return FALSE;
}
} else {
priv->synshm = calloc(1, sizeof(SynapticsSHM));
if (!priv->synshm)
return FALSE;
}
return TRUE;
}
/*
* Free SynapticsParameters data previously allocated by alloc_shm_data().
*/
static void
free_shm_data(SynapticsPrivate *priv)
{
int shmid;
if (!priv->synshm)
return;
if (priv->shm_config) {
if ((shmid = shmget(SHM_SYNAPTICS, 0, 0)) != -1)
shmctl(shmid, IPC_RMID, NULL);
} else {
free(priv->synshm);
}
priv->synshm = NULL;
}
static void
calculate_edge_widths(SynapticsPrivate *priv, int *l, int *r, int *t, int *b)
{
int width, height;
int ewidth, eheight; /* edge width/height */
width = abs(priv->maxx - priv->minx);
height = abs(priv->maxy - priv->miny);
if (priv->model == MODEL_SYNAPTICS)
{
ewidth = width * .07;
eheight = height * .07;
} else if (priv->model == MODEL_ALPS)
{
ewidth = width * .15;
eheight = height * .15;
} else if (priv->model == MODEL_APPLETOUCH)
{
ewidth = width * .085;
eheight = height * .085;
} else
{
ewidth = width * .04;
eheight = height * .054;
}
*l = priv->minx + ewidth;
*r = priv->maxx - ewidth;
*t = priv->miny + eheight;
*b = priv->maxy - eheight;
}
/* Area options support both percent values and absolute values. This is
* awkward. The xf86Set* calls will print to the log, but they'll
* also print an error if we request a percent value but only have an
* int. So - check first for percent, then call xf86Set* again to get
* the log message.
*/
static int set_percent_option(pointer options, const char* optname,
const int range, const int offset,
const int default_value)
{
int result;
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 11
double percent = xf86CheckPercentOption(options, optname, -1);
if (percent >= 0.0) {
percent = xf86SetPercentOption(options, optname, -1);
result = percent/100.0 * range + offset;
} else
#endif
result = xf86SetIntOption(options, optname, default_value);
return result;
}
static void set_default_parameters(InputInfoPtr pInfo)
{
SynapticsPrivate *priv = pInfo->private; /* read-only */
pointer opts = pInfo->options; /* read-only */
SynapticsParameters *pars = &priv->synpara; /* modified */
int horizScrollDelta, vertScrollDelta; /* pixels */
int tapMove; /* pixels */
int l, r, t, b; /* left, right, top, bottom */
int edgeMotionMinSpeed, edgeMotionMaxSpeed; /* pixels/second */
double accelFactor; /* 1/pixels */
int fingerLow, fingerHigh, fingerPress; /* pressure */
int emulateTwoFingerMinZ; /* pressure */
int emulateTwoFingerMinW; /* width */
int edgeMotionMinZ, edgeMotionMaxZ; /* pressure */
int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
int palmMinWidth, palmMinZ; /* pressure */
int tapButton1, tapButton2, tapButton3;
int clickFinger1, clickFinger2, clickFinger3;
Bool vertEdgeScroll, horizEdgeScroll;
Bool vertTwoFingerScroll, horizTwoFingerScroll;
int horizResolution = 1;
int vertResolution = 1;
int width, height, diag, range;
int horizHyst, vertHyst;
/* read the parameters */
if (priv->synshm)
priv->synshm->version = (PACKAGE_VERSION_MAJOR*10000+PACKAGE_VERSION_MINOR*100+PACKAGE_VERSION_PATCHLEVEL);
/* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
* y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
* Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
* 2008]. We use 7% for both instead for synaptics devices, and 15% for
* ALPS models.
* http://bugs.freedesktop.org/show_bug.cgi?id=21214
*
* If the range was autodetected, apply these edge widths to all four
* sides.
*/
width = abs(priv->maxx - priv->minx);
height = abs(priv->maxy - priv->miny);
diag = sqrt(width * width + height * height);
calculate_edge_widths(priv, &l, &r, &t, &b);
/* Again, based on typical x/y range and defaults */
horizScrollDelta = diag * .020;
vertScrollDelta = diag * .020;
tapMove = diag * .044;
edgeMotionMinSpeed = 1;
edgeMotionMaxSpeed = diag * .080;
accelFactor = 200.0 / diag; /* trial-and-error */
/* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
range = priv->maxp - priv->minp;
/* scaling based on defaults and a pressure of 256 */
fingerLow = priv->minp + range * (25.0/256);
fingerHigh = priv->minp + range * (30.0/256);
fingerPress = priv->minp + range * 1.000;
emulateTwoFingerMinZ = priv->minp + range * (282.0/256);
edgeMotionMinZ = priv->minp + range * (30.0/256);
edgeMotionMaxZ = priv->minp + range * (160.0/256);
pressureMotionMinZ = priv->minp + range * (30.0/256);
pressureMotionMaxZ = priv->minp + range * (160.0/256);
palmMinZ = priv->minp + range * (200.0/256);
range = priv->maxw - priv->minw;
/* scaling based on defaults below and a tool width of 16 */
palmMinWidth = priv->minw + range * (10.0/16);
emulateTwoFingerMinW = priv->minw + range * (7.0/16);
/* Enable tap if we don't have a phys left button */
tapButton1 = priv->has_left ? 0 : 1;
tapButton2 = priv->has_left ? 0 : 3;
tapButton3 = priv->has_left ? 0 : 2;
/* Enable multifinger-click if only have one physical button,
otherwise clickFinger is always button 1. */
clickFinger1 = 1;
clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
/* Enable vert edge scroll if we can't detect doubletap */
vertEdgeScroll = priv->has_double ? FALSE : TRUE;
horizEdgeScroll = FALSE;
/* Enable twofinger scroll if we can detect doubletap */
vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
horizTwoFingerScroll = FALSE;
/* Use resolution reported by hardware if available */
if ((priv->resx > 0) && (priv->resy > 0)) {
horizResolution = priv->resx;
vertResolution = priv->resy;
}
/* set the parameters */
pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
pars->area_top_edge = set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
pars->area_bottom_edge = set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
pars->area_left_edge = set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
pars->area_right_edge = set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
pars->hyst_x = set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
pars->hyst_y = set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
pars->finger_press = xf86SetIntOption(opts, "FingerPress", fingerPress);
pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
pars->fast_taps = xf86SetBoolOption(opts, "FastTaps", FALSE);
pars->emulate_mid_button_time = xf86SetIntOption(opts, "EmulateMidButtonTime", 75);
pars->emulate_twofinger_z = xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
pars->emulate_twofinger_w = xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
pars->scroll_dist_vert = xf86SetIntOption(opts, "VertScrollDelta", horizScrollDelta);
pars->scroll_dist_horiz = xf86SetIntOption(opts, "HorizScrollDelta", vertScrollDelta);
pars->scroll_edge_vert = xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
pars->scroll_edge_horiz = xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
pars->scroll_twofinger_vert = xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
pars->scroll_twofinger_horiz = xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
pars->edge_motion_min_z = xf86SetIntOption(opts, "EdgeMotionMinZ", edgeMotionMinZ);
pars->edge_motion_max_z = xf86SetIntOption(opts, "EdgeMotionMaxZ", edgeMotionMaxZ);
pars->edge_motion_min_speed = xf86SetIntOption(opts, "EdgeMotionMinSpeed", edgeMotionMinSpeed);
pars->edge_motion_max_speed = xf86SetIntOption(opts, "EdgeMotionMaxSpeed", edgeMotionMaxSpeed);
pars->edge_motion_use_always = xf86SetBoolOption(opts, "EdgeMotionUseAlways", FALSE);
if (priv->has_scrollbuttons) {
pars->updown_button_scrolling = xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
pars->leftright_button_scrolling = xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
pars->updown_button_repeat = xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
pars->leftright_button_repeat = xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
}
pars->scroll_button_repeat = xf86SetIntOption(opts,"ScrollButtonRepeat", 100);
pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", 0);
pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 0);
pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 0);
pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
pars->click_action[F1_CLICK1] = xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
pars->click_action[F2_CLICK1] = xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
pars->click_action[F3_CLICK1] = xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
pars->circular_scrolling = xf86SetBoolOption(opts, "CircularScrolling", FALSE);
pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
pars->press_motion_min_z = xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
pars->press_motion_max_z = xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
pars->trackstick_speed = xf86SetRealOption(opts, "TrackstickSpeed", 40);
pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
pars->press_motion_min_factor = xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
pars->press_motion_max_factor = xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", TRUE);
pars->tap_and_drag_gesture = xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
pars->resolution_horiz = xf86SetIntOption(opts, "HorizResolution", horizResolution);
pars->resolution_vert = xf86SetIntOption(opts, "VertResolution", vertResolution);
/* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
if (pars->top_edge > pars->bottom_edge) {
int tmp = pars->top_edge;
pars->top_edge = pars->bottom_edge;
pars->bottom_edge = tmp;
xf86IDrvMsg(pInfo, X_WARNING, "TopEdge is bigger than BottomEdge. Fixing.\n");
}
}
static float SynapticsAccelerationProfile(DeviceIntPtr dev,
DeviceVelocityPtr vel,
float velocity,
float thr,
float acc) {
InputInfoPtr pInfo = dev->public.devicePrivate;
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
SynapticsParameters* para = &priv->synpara;
double accelfct;
/*
* synaptics accel was originally base on device coordinate based
* velocity, which we recover this way so para->accl retains its scale.
*/
velocity /= vel->const_acceleration;
/* speed up linear with finger velocity */
accelfct = velocity * para->accl;
/* clip acceleration factor */
if (accelfct > para->max_speed * acc)
accelfct = para->max_speed * acc;
else if (accelfct < para->min_speed)
accelfct = para->min_speed;
/* modify speed according to pressure */
if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
int minZ = para->press_motion_min_z;
int maxZ = para->press_motion_max_z;
double minFctr = para->press_motion_min_factor;
double maxFctr = para->press_motion_max_factor;
if (priv->hwState.z <= minZ) {
accelfct *= minFctr;
} else if (priv->hwState.z >= maxZ) {
accelfct *= maxFctr;
} else {
accelfct *= minFctr + (priv->hwState.z - minZ) * (maxFctr - minFctr) / (maxZ - minZ);
}
}
return accelfct;
}
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 12
static int
NewSynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
/*
* called by the module loader for initialization
*/
static InputInfoPtr
SynapticsPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
{
InputInfoPtr pInfo;
/* Allocate a new InputInfoRec and add it to the head xf86InputDevs. */
pInfo = xf86AllocateInput(drv, 0);
if (!pInfo) {
return NULL;
}
/* initialize the InputInfoRec */
pInfo->name = dev->identifier;
pInfo->reverse_conversion_proc = NULL;
pInfo->dev = NULL;
pInfo->private_flags = 0;
pInfo->flags = XI86_SEND_DRAG_EVENTS;
pInfo->conf_idev = dev;
pInfo->always_core_feedback = 0;
xf86CollectInputOptions(pInfo, NULL, NULL);
if (NewSynapticsPreInit(drv, pInfo, flags) != Success)
return NULL;
pInfo->flags |= XI86_CONFIGURED;
return pInfo;
}
static int
NewSynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
#else
static int
SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
#endif
{
SynapticsPrivate *priv;
/* allocate memory for SynapticsPrivateRec */
priv = calloc(1, sizeof(SynapticsPrivate));
if (!priv)
return BadAlloc;
pInfo->type_name = XI_TOUCHPAD;
pInfo->device_control = DeviceControl;
pInfo->read_input = ReadInput;
pInfo->control_proc = ControlProc;
pInfo->switch_mode = SwitchMode;
pInfo->private = priv;
/* allocate now so we don't allocate in the signal handler */
priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
if (!priv->timer) {
free(priv);
return BadAlloc;
}
/* may change pInfo->options */
SetDeviceAndProtocol(pInfo);
if (priv->proto_ops == NULL) {
xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to detect protocol\n");
goto SetupProc_fail;
}
priv->device = xf86FindOptionValue(pInfo->options, "Device");
/* open the touchpad device */
pInfo->fd = xf86OpenSerial(pInfo->options);
if (pInfo->fd == -1) {
xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
goto SetupProc_fail;
}
xf86ErrorFVerb(6, "port opened successfully\n");
/* initialize variables */
priv->repeatButtons = 0;
priv->nextRepeat = 0;
priv->count_packet_finger = 0;
priv->tap_state = TS_START;
priv->tap_button = 0;
priv->tap_button_state = TBS_BUTTON_UP;
priv->touch_on.millis = 0;
priv->synpara.hyst_x = -1;
priv->synpara.hyst_y = -1;
/* read hardware dimensions */
ReadDevDimensions(pInfo);
/* install shared memory or normal memory for parameters */
priv->shm_config = xf86SetBoolOption(pInfo->options, "SHMConfig", FALSE);
set_default_parameters(pInfo);
CalculateScalingCoeffs(priv);
if (!alloc_shm_data(pInfo))
goto SetupProc_fail;
priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
if (!QueryHardware(pInfo)) {
xf86IDrvMsg(pInfo, X_ERROR, "Unable to query/initialize Synaptics hardware.\n");
goto SetupProc_fail;
}
xf86ProcessCommonOptions(pInfo, pInfo->options);
if (pInfo->fd != -1) {
if (priv->comm.buffer) {
XisbFree(priv->comm.buffer);
priv->comm.buffer = NULL;
}
xf86CloseSerial(pInfo->fd);
}
pInfo->fd = -1;
return Success;
SetupProc_fail:
if (pInfo->fd >= 0) {
xf86CloseSerial(pInfo->fd);
pInfo->fd = -1;
}
if (priv->comm.buffer)
XisbFree(priv->comm.buffer);
free_shm_data(priv);
free(priv->proto_data);
free(priv->timer);
free(priv);
pInfo->private = NULL;
return BadAlloc;
}
/*
* Uninitialize the device.
*/
static void SynapticsUnInit(InputDriverPtr drv,
InputInfoPtr pInfo,
int flags)
{
SynapticsPrivate *priv = ((SynapticsPrivate *)pInfo->private);
if (priv && priv->timer)
free(priv->timer);
if (priv && priv->proto_data)
free(priv->proto_data);
free(pInfo->private);
pInfo->private = NULL;
xf86DeleteInput(pInfo, 0);
}
/*
* Alter the control parameters for the mouse. Note that all special
* protocol values are handled by dix.
*/
static void
SynapticsCtrl(DeviceIntPtr device, PtrCtrl *ctrl)
{
}
static Bool
DeviceControl(DeviceIntPtr dev, int mode)
{
Bool RetValue;
switch (mode) {
case DEVICE_INIT:
RetValue = DeviceInit(dev);
break;
case DEVICE_ON:
RetValue = DeviceOn(dev);
break;
case DEVICE_OFF:
RetValue = DeviceOff(dev);
break;
case DEVICE_CLOSE:
RetValue = DeviceClose(dev);
break;
default:
RetValue = BadValue;
}
return RetValue;
}
static Bool
DeviceOn(DeviceIntPtr dev)
{
InputInfoPtr pInfo = dev->public.devicePrivate;
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
DBG(3, "Synaptics DeviceOn called\n");
pInfo->fd = xf86OpenSerial(pInfo->options);
if (pInfo->fd == -1) {
xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
return !Success;
}
if (priv->proto_ops->DeviceOnHook)
priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara);
priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
if (!priv->comm.buffer) {
xf86CloseSerial(pInfo->fd);
pInfo->fd = -1;
return !Success;
}
xf86FlushInput(pInfo->fd);
/* reinit the pad */
if (!QueryHardware(pInfo))
{
XisbFree(priv->comm.buffer);
priv->comm.buffer = NULL;
xf86CloseSerial(pInfo->fd);
pInfo->fd = -1;
return !Success;
}
xf86AddEnabledDevice(pInfo);
dev->public.on = TRUE;
return Success;
}
static Bool
DeviceOff(DeviceIntPtr dev)
{
InputInfoPtr pInfo = dev->public.devicePrivate;
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
DBG(3, "Synaptics DeviceOff called\n");
if (pInfo->fd != -1) {
TimerCancel(priv->timer);
xf86RemoveEnabledDevice(pInfo);
if (priv->proto_ops->DeviceOffHook)
priv->proto_ops->DeviceOffHook(pInfo);
if (priv->comm.buffer) {
XisbFree(priv->comm.buffer);
priv->comm.buffer = NULL;
}
xf86CloseSerial(pInfo->fd);
pInfo->fd = -1;
}
dev->public.on = FALSE;
return Success;
}
static Bool
DeviceClose(DeviceIntPtr dev)
{
Bool RetValue;
InputInfoPtr pInfo = dev->public.devicePrivate;
SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
RetValue = DeviceOff(dev);
TimerFree(priv->timer);
priv->timer = NULL;
free_shm_data(priv);
return RetValue;
}
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
static void InitAxesLabels(Atom *labels, int nlabels)
{
memset(labels, 0, nlabels * sizeof(Atom));
switch(nlabels)
{
default:
case 2:
labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
case 1:
labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
break;
}
}
static void InitButtonLabels(Atom *labels, int nlabels)
{
memset(labels, 0, nlabels * sizeof(Atom));
switch(nlabels)
{
default:
case 7:
labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
case 6:
labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
case 5:
labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
case 4:
labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
case 3:
labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
case 2:
labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
case 1:
labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
break;
}
}
#endif
static Bool
DeviceInit(DeviceIntPtr dev)
{
InputInfoPtr pInfo = dev->public.devicePrivate;
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
Atom float_type, prop;
float tmpf;
unsigned char map[SYN_MAX_BUTTONS + 1];
int i;
int min, max;
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
Atom axes_labels[2] = { 0 };
DeviceVelocityPtr pVel;
InitAxesLabels(axes_labels, 2);
InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
#endif
DBG(3, "Synaptics DeviceInit called\n");
for (i = 0; i <= SYN_MAX_BUTTONS; i++)
map[i] = i;
dev->public.on = FALSE;
InitPointerDeviceStruct((DevicePtr)dev, map,
SYN_MAX_BUTTONS,
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
btn_labels,
#endif
SynapticsCtrl,
GetMotionHistorySize(), 2
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
, axes_labels
#endif
);
/*
* setup dix acceleration to match legacy synaptics settings, and
* etablish a device-specific profile to do stuff like pressure-related
* acceleration.
*/
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
SetDeviceSpecificAccelerationProfile(pVel,
SynapticsAccelerationProfile);
/* float property type */
float_type = XIGetKnownProperty(XATOM_FLOAT);
/* translate MinAcc to constant deceleration.
* May be overridden in xf86InitValuatorDefaults */
tmpf = 1.0 / priv->synpara.min_speed;
xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MinSpeed is now constant deceleration "
"%.1f\n", tmpf);
prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
XIChangeDeviceProperty(dev, prop, float_type, 32,
PropModeReplace, 1, &tmpf, FALSE);
/* adjust accordingly */
priv->synpara.max_speed /= priv->synpara.min_speed;
priv->synpara.min_speed = 1.0;
/* synaptics seems to report 80 packet/s, but dix scales for
* 100 packet/s by default. */
pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
xf86IDrvMsg(pInfo, X_CONFIG, "MaxSpeed is now %.2f\n",
priv->synpara.max_speed);
xf86IDrvMsg(pInfo, X_CONFIG, "AccelFactor is now %.3f\n",
priv->synpara.accl);
prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
i = AccelProfileDeviceSpecific;
XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
PropModeReplace, 1, &i, FALSE);
}
#endif
/* X valuator */
if (priv->minx < priv->maxx)
{
min = priv->minx;
max = priv->maxx;
} else
{
min = 0;
max = -1;
}
xf86InitValuatorAxisStruct(dev, 0,
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
axes_labels[0],
#endif
min, max, priv->resx * 1000, 0, priv->resx * 1000
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 12
, Relative
#endif
);
xf86InitValuatorDefaults(dev, 0);
/* Y valuator */
if (priv->miny < priv->maxy)
{
min = priv->miny;
max = priv->maxy;
} else
{
min = 0;
max = -1;
}
xf86InitValuatorAxisStruct(dev, 1,
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
axes_labels[1],
#endif
min, max, priv->resy * 1000, 0, priv->resy * 1000
#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 12
, Relative
#endif
);
xf86InitValuatorDefaults(dev, 1);
if (!alloc_shm_data(pInfo))
return !Success;
InitDeviceProperties(pInfo);
XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
return Success;
}
/*
* Convert from absolute X/Y coordinates to a coordinate system where
* -1 corresponds to the left/upper edge and +1 corresponds to the
* right/lower edge.
*/
static void
relative_coords(SynapticsPrivate *priv, int x, int y,
double *relX, double *relY)
{
int minX = priv->synpara.left_edge;
int maxX = priv->synpara.right_edge;
int minY = priv->synpara.top_edge;
int maxY = priv->synpara.bottom_edge;
double xCenter = (minX + maxX) / 2.0;
double yCenter = (minY + maxY) / 2.0;
if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
*relX = (x - xCenter) / (maxX - xCenter);
*relY = (y - yCenter) / (maxY - yCenter);
} else {
*relX = 0;
*relY = 0;
}
}
/* return angle of point relative to center */
static double
angle(SynapticsPrivate *priv, int x, int y)
{
double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
return atan2(-(y - yCenter), x - xCenter);
}
/* return angle difference */
static double
diffa(double a1, double a2)
{
double da = fmod(a2 - a1, 2 * M_PI);
if (da < 0)
da += 2 * M_PI;
if (da > M_PI)
da -= 2 * M_PI;
return da;
}
static edge_type
circular_edge_detection(SynapticsPrivate *priv, int x, int y)
{
edge_type edge = 0;
double relX, relY, relR;
relative_coords(priv, x, y, &relX, &relY);
relR = SQR(relX) + SQR(relY);
if (relR > 1) {
/* we are outside the ellipse enclosed by the edge parameters */
if (relX > M_SQRT1_2)
edge |= RIGHT_EDGE;
else if (relX < -M_SQRT1_2)
edge |= LEFT_EDGE;
if (relY < -M_SQRT1_2)
edge |= TOP_EDGE;
else if (relY > M_SQRT1_2)
edge |= BOTTOM_EDGE;
}
return edge;
}
static edge_type
edge_detection(SynapticsPrivate *priv, int x, int y)
{
edge_type edge = NO_EDGE;
if (priv->synpara.circular_pad)
return circular_edge_detection(priv, x, y);
if (x > priv->synpara.right_edge)
edge |= RIGHT_EDGE;
else if (x < priv->synpara.left_edge)
edge |= LEFT_EDGE;
if (y < priv->synpara.top_edge)
edge |= TOP_EDGE;
else if (y > priv->synpara.bottom_edge)
edge |= BOTTOM_EDGE;
return edge;
}
/* Checks whether coordinates are in the Synaptics Area
* or not. If no Synaptics Area is defined (i.e. if
* priv->synpara.area_{left|right|top|bottom}_edge are
* all set to zero), the function returns TRUE.
*/
static Bool
is_inside_active_area(SynapticsPrivate *priv, int x, int y)
{
Bool inside_area = TRUE;
if ((priv->synpara.area_left_edge != 0) && (x < priv->synpara.area_left_edge))
inside_area = FALSE;
else if ((priv->synpara.area_right_edge != 0) && (x > priv->synpara.area_right_edge))
inside_area = FALSE;
if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
inside_area = FALSE;
else if ((priv->synpara.area_bottom_edge != 0) && (y > priv->synpara.area_bottom_edge))
inside_area = FALSE;
return inside_area;
}
static CARD32
timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
{
InputInfoPtr pInfo = arg;
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
struct SynapticsHwState hw;
int delay;
int sigstate;
CARD32 wakeUpTime;
sigstate = xf86BlockSIGIO();
hw = priv->hwState;
hw.millis = now;
delay = HandleState(pInfo, &hw);
/*
* Workaround for wraparound bug in the TimerSet function. This bug is already
* fixed in CVS, but this driver needs to work with XFree86 versions 4.2.x and
* 4.3.x too.
*/
wakeUpTime = now + delay;
if (wakeUpTime <= now)
wakeUpTime = 0xffffffffL;
priv->timer = TimerSet(priv->timer, TimerAbsolute, wakeUpTime, timerFunc, pInfo);
xf86UnblockSIGIO(sigstate);
return 0;
}
static int
clamp(int val, int min, int max)
{
if (val < min)
return min;
else if (val < max)
return val;
else
return max;
}
static Bool
SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate *priv,
struct SynapticsHwState *hw)
{
return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
}
/*
* called for each full received packet from the touchpad
*/
static void
ReadInput(InputInfoPtr pInfo)
{
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
struct SynapticsHwState hw;
int delay = 0;
Bool newDelay = FALSE;
while (SynapticsGetHwState(pInfo, priv, &hw)) {
hw.millis = GetTimeInMillis();
priv->hwState = hw;
delay = HandleState(pInfo, &hw);
newDelay = TRUE;
}
if (newDelay)
priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
}
static int
HandleMidButtonEmulation(SynapticsPrivate *priv, struct SynapticsHwState *hw, int *delay)
{
SynapticsParameters *para = &priv->synpara;
Bool done = FALSE;
int timeleft;
int mid = 0;
while (!done) {
switch (priv->mid_emu_state) {
case MBE_LEFT_CLICK:
case MBE_RIGHT_CLICK:
case MBE_OFF:
priv->button_delay_millis = hw->millis;
if (hw->left) {
priv->mid_emu_state = MBE_LEFT;
} else if (hw->right) {
priv->mid_emu_state = MBE_RIGHT;
} else {
done = TRUE;
}
break;
case MBE_LEFT:
timeleft = TIME_DIFF(priv->button_delay_millis + para->emulate_mid_button_time,
hw->millis);
if (timeleft > 0)
*delay = MIN(*delay, timeleft);
/* timeout, but within the same ReadInput cycle! */
if ((timeleft <= 0) && !hw->left) {
priv->mid_emu_state = MBE_LEFT_CLICK;
done = TRUE;
} else if ((!hw->left) || (timeleft <= 0)) {
hw->left = TRUE;
priv->mid_emu_state = MBE_TIMEOUT;
done = TRUE;
} else if (hw->right) {
priv->mid_emu_state = MBE_MID;
} else {
hw->left = FALSE;
done = TRUE;
}
break;
case MBE_RIGHT:
timeleft = TIME_DIFF(priv->button_delay_millis + para->emulate_mid_button_time,
hw->millis);
if (timeleft > 0)
*delay = MIN(*delay, timeleft);
/* timeout, but within the same ReadInput cycle! */
if ((timeleft <= 0) && !hw->right) {
priv->mid_emu_state = MBE_RIGHT_CLICK;
done = TRUE;
} else if (!hw->right || (timeleft <= 0)) {
hw->right = TRUE;
priv->mid_emu_state = MBE_TIMEOUT;
done = TRUE;
} else if (hw->left) {
priv->mid_emu_state = MBE_MID;
} else {
hw->right = FALSE;
done = TRUE;
}
break;
case MBE_MID:
if (!hw->left && !hw->right) {
priv->mid_emu_state = MBE_OFF;
} else {
mid = TRUE;
hw->left = hw->right = FALSE;
done = TRUE;
}
break;
case MBE_TIMEOUT:
if (!hw->left && !hw->right) {
priv->mid_emu_state = MBE_OFF;
} else {
done = TRUE;
}
}
}
return mid;
}
static enum FingerState
SynapticsDetectFinger(SynapticsPrivate *priv, struct SynapticsHwState *hw)
{
SynapticsParameters *para = &priv->synpara;
enum FingerState finger;
/* finger detection thru pressure and threshold */
if (hw->z > para->finger_press && priv->finger_state < FS_PRESSED)
finger = FS_PRESSED;
else if (hw->z > para->finger_high && priv->finger_state < FS_TOUCHED)
finger = FS_TOUCHED;
else if (hw->z < para->finger_low && priv->finger_state > FS_UNTOUCHED)
finger = FS_UNTOUCHED;
else
finger = priv->finger_state;
if (!para->palm_detect)
return finger;
/* palm detection */
if (finger) {
if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
priv->palm = TRUE;
} else {
priv->palm = FALSE;
}
if (hw->x == 0)
priv->avg_width = 0;
else
priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
if (finger && !priv->finger_state) {
int safe_width = MAX(hw->fingerWidth, priv->avg_width);
if (hw->numFingers > 1 || /* more than one finger -> not a palm */
((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
((safe_width < 7) && (priv->prev_z < para->finger_high / 2)))/* thin finger, distinct touch -> not a palm */
{
/* leave finger value as is */
} else if (hw->z > priv->prev_z + 1) /* z not stable, may be a palm */
finger = FS_UNTOUCHED;
else if (hw->z < priv->prev_z - 5) /* z not stable, may be a palm */
finger = FS_UNTOUCHED;
else if (hw->fingerWidth > para->palm_min_width) /* finger width too large -> probably palm */
finger = FS_UNTOUCHED;
}
priv->prev_z = hw->z;
if (priv->palm)
finger = FS_UNTOUCHED;
return finger;
}
static void
SelectTapButton(SynapticsPrivate *priv, edge_type edge)
{
TapEvent tap;
if (priv->synpara.touchpad_off == 2) {
priv->tap_button = 0;
return;
}
switch (priv->tap_max_fingers) {
case 1:
default:
switch (edge) {
case RIGHT_TOP_EDGE:
DBG(7, "right top edge\n");
tap = RT_TAP;
break;
case RIGHT_BOTTOM_EDGE:
DBG(7, "right bottom edge\n");
tap = RB_TAP;
break;
case LEFT_TOP_EDGE:
DBG(7, "left top edge\n");
tap = LT_TAP;
break;
case LEFT_BOTTOM_EDGE:
DBG(7, "left bottom edge\n");
tap = LB_TAP;
break;
default:
DBG(7, "no edge\n");
tap = F1_TAP;
break;
}
break;
case 2:
DBG(7, "two finger tap\n");
tap = F2_TAP;
break;
case 3:
DBG(7, "three finger tap\n");
tap = F3_TAP;
break;
}
priv->tap_button = priv->synpara.tap_action[tap];
priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
}
static void
SetTapState(SynapticsPrivate *priv, enum TapState tap_state, int millis)
{
SynapticsParameters *para = &priv->synpara;
DBG(7, "SetTapState - %d -> %d (millis:%d)\n", priv->tap_state, tap_state, millis);
switch (tap_state) {
case TS_START:
priv->tap_button_state = TBS_BUTTON_UP;
priv->tap_max_fingers = 0;
break;
case TS_1:
priv->tap_button_state = TBS_BUTTON_UP;
break;
case TS_2A:
if (para->fast_taps)
priv->tap_button_state = TBS_BUTTON_DOWN;
else
priv->tap_button_state = TBS_BUTTON_UP;
break;
case TS_2B:
priv->tap_button_state = TBS_BUTTON_UP;
break;
case TS_3:
if (para->tap_and_drag_gesture)
priv->tap_button_state = TBS_BUTTON_DOWN;
else
priv->tap_button_state = TBS_BUTTON_UP;
break;
case TS_SINGLETAP:
if (para->fast_taps)
priv->tap_button_state = TBS_BUTTON_UP;
else
priv->tap_button_state = TBS_BUTTON_DOWN;
priv->touch_on.millis = millis;
break;
default:
break;
}
priv->tap_state = tap_state;
}
static void
SetMovingState(SynapticsPrivate *priv, enum MovingState moving_state, int millis)
{
DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%d)\n", priv->moving_state,
moving_state,priv->hwState.x, priv->hwState.y, millis);
if (moving_state == MS_TRACKSTICK) {
priv->trackstick_neutral_x = priv->hwState.x;
priv->trackstick_neutral_y = priv->hwState.y;
}
priv->moving_state = moving_state;
}
static int
GetTimeOut(SynapticsPrivate *priv)
{
SynapticsParameters *para = &priv->synpara;
switch (priv->tap_state) {
case TS_1:
case TS_3:
case TS_5:
return para->tap_time;
case TS_SINGLETAP:
return para->click_time;
case TS_2A:
return para->single_tap_timeout;
case TS_2B:
return para->tap_time_2;
case TS_4:
return para->locked_drag_time;
default:
return -1; /* No timeout */
}
}
static int
HandleTapProcessing(SynapticsPrivate *priv, struct SynapticsHwState *hw,
enum FingerState finger, Bool inside_active_area)
{
SynapticsParameters *para = &priv->synpara;
Bool touch, release, is_timeout, move;
int timeleft, timeout;
edge_type edge;
int delay = 1000000000;
if (priv->palm)
return delay;
touch = finger && !priv->finger_state;
release = !finger && priv->finger_state;
move = (finger &&
(priv->tap_max_fingers <= ((priv->horiz_scroll_twofinger_on || priv->vert_scroll_twofinger_on)? 2 : 1)) &&
((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
(abs(hw->y - priv->touch_on.y) >= para->tap_move)));
if (touch) {
priv->touch_on.x = hw->x;
priv->touch_on.y = hw->y;
priv->touch_on.millis = hw->millis;
} else if (release) {
priv->touch_on.millis = hw->millis;
}
if (hw->z > para->finger_high)
if (priv->tap_max_fingers < hw->numFingers)
priv->tap_max_fingers = hw->numFingers;
timeout = GetTimeOut(priv);
timeleft = TIME_DIFF(priv->touch_on.millis + timeout, hw->millis);
is_timeout = timeleft <= 0;
restart:
switch (priv->tap_state) {
case TS_START:
if (touch)
SetTapState(priv, TS_1, hw->millis);
break;
case TS_1:
if (move) {
SetMovingState(priv, MS_TOUCHPAD_RELATIVE, hw->millis);
SetTapState(priv, TS_MOVE, hw->millis);
goto restart;
} else if (is_timeout) {
if (finger == FS_TOUCHED) {
SetMovingState(priv, MS_TOUCHPAD_RELATIVE, hw->millis);
} else if (finger == FS_PRESSED) {
SetMovingState(priv, MS_TRACKSTICK, hw->millis);
}
SetTapState(priv, TS_MOVE, hw->millis);
goto restart;
} else if (release) {
edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
SelectTapButton(priv, edge);
/* Disable taps outside of the active area */
if (!inside_active_area) {
priv->tap_button = 0;
}
SetTapState(priv, TS_2A, hw->millis);
}
break;
case TS_MOVE:
if (move && priv->moving_state == MS_TRACKSTICK) {
SetMovingState(priv, MS_TOUCHPAD_RELATIVE, hw->millis);
}
if (release) {
SetMovingState(priv, MS_FALSE, hw->millis);
SetTapState(priv, TS_START, hw->millis);
}
break;
case TS_2A:
if (touch)
SetTapState(priv, TS_3, hw->millis);
else if (is_timeout)
SetTapState(priv, TS_SINGLETAP, hw->millis);
break;
case TS_2B:
if (touch) {
SetTapState(priv, TS_3, hw->millis);
} else if (is_timeout) {
SetTapState(priv, TS_START, hw->millis);
priv->tap_button_state = TBS_BUTTON_DOWN_UP;
}
break;
case TS_SINGLETAP:
if (touch)
SetTapState(priv, TS_1, hw->millis);
else if (is_timeout)
SetTapState(priv, TS_START, hw->millis);
break;
case TS_3:
if (move) {
if (para->tap_and_drag_gesture) {
SetMovingState(priv, MS_TOUCHPAD_RELATIVE, hw->millis);
SetTapState(priv, TS_DRAG, hw->millis);
} else {
SetTapState(priv, TS_1, hw->millis);
}
goto restart;
} else if (is_timeout) {
if (para->tap_and_drag_gesture) {
if (finger == FS_TOUCHED) {
SetMovingState(priv, MS_TOUCHPAD_RELATIVE, hw->millis);
} else if (finger == FS_PRESSED) {
SetMovingState(priv, MS_TRACKSTICK, hw->millis);
}
SetTapState(priv, TS_DRAG, hw->millis);
} else {
SetTapState(priv, TS_1, hw->millis);
}
goto restart;
} else if (release) {
SetTapState(priv, TS_2B, hw->millis);
}
break;
case TS_DRAG:
if (move)
SetMovingState(priv, MS_TOUCHPAD_RELATIVE, hw->millis);
if (release) {
SetMovingState(priv, MS_FALSE, hw->millis);
if (para->locked_drags) {
SetTapState(priv, TS_4, hw->millis);
} else {
SetTapState(priv, TS_START, hw->millis);
}
}
break;
case TS_4:
if (is_timeout) {
SetTapState(priv, TS_START, hw->millis);
goto restart;
}
if (touch)
SetTapState(priv, TS_5, hw->millis);
break;
case TS_5:
if (is_timeout || move) {
SetTapState(priv, TS_DRAG, hw->millis);
goto restart;
} else if (release) {
SetMovingState(priv, MS_FALSE, hw->millis);
SetTapState(priv, TS_START, hw->millis);
}
break;
}
timeout = GetTimeOut(priv);
if (timeout >= 0) {
timeleft = TIME_DIFF(priv->touch_on.millis + timeout, hw->millis);
delay = clamp(timeleft, 1, delay);
}
return delay;
}
#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
static void
store_history(SynapticsPrivate *priv, int x, int y, unsigned int millis)
{
int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
priv->move_hist[idx].x = x;
priv->move_hist[idx].y = y;
priv->move_hist[idx].millis = millis;
priv->hist_index = idx;
}
/*
* Estimate the slope for the data sequence [x3, x2, x1, x0] by using
* linear regression to fit a line to the data and use the slope of the
* line.
*/
static double
estimate_delta(double x0, double x1, double x2, double x3)
{
return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
}
/**
* Applies hysteresis. center is shifted such that it is in range with
* in by the margin again. The new center is returned.
* @param in the current value
* @param center the current center
* @param margin the margin to center in which no change is applied
* @return the new center (which might coincide with the previous)
*/
static int hysteresis(int in, int center, int margin) {
int diff = in - center;
if (abs(diff) <= margin) {
diff = 0;
} else if (diff > margin) {
diff -= margin;
} else if (diff < -margin) {
diff += margin;
}
return center + diff;
}
static void
get_delta_for_trackstick(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
double *dx, double *dy)
{
SynapticsParameters *para = &priv->synpara;
double dtime = (hw->millis - HIST(0).millis) / 1000.0;
*dx = (hw->x - priv->trackstick_neutral_x);
*dy = (hw->y - priv->trackstick_neutral_y);
*dx = *dx * dtime * para->trackstick_speed;
*dy = *dy * dtime * para->trackstick_speed;
}
static void
get_edge_speed(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
edge_type edge, int *x_edge_speed, int *y_edge_speed)
{
SynapticsParameters *para = &priv->synpara;
int minZ = para->edge_motion_min_z;
int maxZ = para->edge_motion_max_z;
int minSpd = para->edge_motion_min_speed;
int maxSpd = para->edge_motion_max_speed;
int edge_speed;
if (hw->z <= minZ) {
edge_speed = minSpd;
} else if (hw->z >= maxZ) {
edge_speed = maxSpd;
} else {
edge_speed = minSpd + (hw->z - minZ) * (maxSpd - minSpd) / (maxZ - minZ);
}
if (!priv->synpara.circular_pad) {
/* on rectangular pad */
if (edge & RIGHT_EDGE) {
*x_edge_speed = edge_speed;
} else if (edge & LEFT_EDGE) {
*x_edge_speed = -edge_speed;
}
if (edge & TOP_EDGE) {
*y_edge_speed = -edge_speed;
} else if (edge & BOTTOM_EDGE) {
*y_edge_speed = edge_speed;
}
} else if (edge) {
/* at edge of circular pad */
double relX, relY;
relative_coords(priv, hw->x, hw->y, &relX, &relY);
*x_edge_speed = (int)(edge_speed * relX);
*y_edge_speed = (int)(edge_speed * relY);
}
}
static void
get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
edge_type edge, double *dx, double *dy)
{
SynapticsParameters *para = &priv->synpara;
double dtime = (hw->millis - HIST(0).millis) / 1000.0;
double integral;
double tmpf;
int x_edge_speed = 0;
int y_edge_speed = 0;
/* HIST is full enough: priv->count_packet_finger > 3 */
*dx = estimate_delta(hw->x, HIST(0).x, HIST(1).x, HIST(2).x);
*dy = estimate_delta(hw->y, HIST(0).y, HIST(1).y, HIST(2).y);
if ((priv->tap_state == TS_DRAG) || para->edge_motion_use_always)
get_edge_speed(priv, hw, edge, &x_edge_speed, &y_edge_speed);
/* report edge speed as synthetic motion. Of course, it would be
* cooler to report floats than to buffer, but anyway. */
tmpf = *dx + x_edge_speed * dtime + priv->frac_x;
priv->frac_x = modf(tmpf, &integral);
*dx = integral;
tmpf = *dy + y_edge_speed * dtime + priv->frac_y;
priv->frac_y = modf(tmpf, &integral);
*dy = integral;
}
/**
* Compute relative motion ('deltas') including edge motion xor trackstick.
*/
static int
ComputeDeltas(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
edge_type edge, int *dxP, int *dyP, Bool inside_area)
{
enum MovingState moving_state;
double dx, dy;
int delay = 1000000000;
dx = dy = 0;
moving_state = priv->moving_state;
if (moving_state == MS_FALSE) {
switch (priv->tap_state) {
case TS_MOVE:
case TS_DRAG:
moving_state = MS_TOUCHPAD_RELATIVE;
break;
case TS_1:
case TS_3:
case TS_5:
if (hw->numFingers == 1)
moving_state = MS_TOUCHPAD_RELATIVE;
break;
default:
break;
}
}
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)
{
/* reset packet counter. */
priv->count_packet_finger = 0;
goto out;
}
/* to create fluid edge motion, call back 'soon'
* even in the absence of new hardware events */
delay = MIN(delay, 13);
if (priv->count_packet_finger <= 3) /* min. 3 packets, see get_delta() */
goto skip; /* skip the lot */
if (priv->moving_state == MS_TRACKSTICK)
get_delta_for_trackstick(priv, hw, &dx, &dy);
else if (moving_state == MS_TOUCHPAD_RELATIVE)
get_delta(priv, hw, edge, &dx, &dy);
skip:
priv->count_packet_finger++;
out:
priv->prevFingers = hw->numFingers;
*dxP = dx;
*dyP = dy;
return delay;
}
struct ScrollData {
int left, right, up, down;
};
static double
estimate_delta_circ(SynapticsPrivate *priv)
{
double a1 = angle(priv, HIST(3).x, HIST(3).y);
double a2 = angle(priv, HIST(2).x, HIST(2).y);
double a3 = angle(priv, HIST(1).x, HIST(1).y);
double a4 = angle(priv, HIST(0).x, HIST(0).y);
double d1 = diffa(a2, a1);
double d2 = d1 + diffa(a3, a2);
double d3 = d2 + diffa(a4, a3);
return estimate_delta(d3, d2, d1, 0);
}
/* vert and horiz are to know which direction to start coasting
* circ is true if the user had been circular scrolling.
*/
static void
start_coasting(SynapticsPrivate *priv, struct SynapticsHwState *hw,
Bool vert, Bool horiz, Bool circ)
{
SynapticsParameters *para = &priv->synpara;
priv->autoscroll_y = 0.0;
priv->autoscroll_x = 0.0;
if ((priv->scroll_packet_count > 3) && (para->coasting_speed > 0.0)) {
double pkt_time = (HIST(0).millis - HIST(3).millis) / 1000.0;
if (vert && !circ) {
double dy = estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
int sdelta = para->scroll_dist_vert;
if (pkt_time > 0 && sdelta > 0) {
double scrolls_per_sec = dy / pkt_time / sdelta;
if (fabs(scrolls_per_sec) >= para->coasting_speed) {
priv->autoscroll_yspd = scrolls_per_sec;
priv->autoscroll_y = (hw->y - priv->scroll_y) / (double)sdelta;
}
}
}
if (horiz && !circ){
double dx = estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
int sdelta = para->scroll_dist_horiz;
if (pkt_time > 0 && sdelta > 0) {
double scrolls_per_sec = dx / pkt_time / sdelta;
if (fabs(scrolls_per_sec) >= para->coasting_speed) {
priv->autoscroll_xspd = scrolls_per_sec;
priv->autoscroll_x = (hw->x - priv->scroll_x) / (double)sdelta;
}
}
}
if (circ) {
double da = estimate_delta_circ(priv);
double sdelta = para->scroll_dist_circ;
if (pkt_time > 0 && sdelta > 0) {
double scrolls_per_sec = da / pkt_time / sdelta;
if (fabs(scrolls_per_sec) >= para->coasting_speed) {
if (vert) {
priv->autoscroll_yspd = scrolls_per_sec;
priv->autoscroll_y = diffa(priv->scroll_a, angle(priv, hw->x, hw->y)) / sdelta;
}
else if (horiz) {
priv->autoscroll_xspd = scrolls_per_sec;
priv->autoscroll_x = diffa(priv->scroll_a, angle(priv, hw->x, hw->y)) / sdelta;
}
}
}
}
}
priv->scroll_packet_count = 0;
}
static void
stop_coasting(SynapticsPrivate *priv)
{
priv->autoscroll_xspd = 0;
priv->autoscroll_yspd = 0;
priv->scroll_packet_count = 0;
}
static int
HandleScrolling(SynapticsPrivate *priv, struct SynapticsHwState *hw,
edge_type edge, Bool finger, struct ScrollData *sd)
{
SynapticsParameters *para = &priv->synpara;
int delay = 1000000000;
sd->left = sd->right = sd->up = sd->down = 0;
if (priv->synpara.touchpad_off == 2) {
stop_coasting(priv);
priv->circ_scroll_on = FALSE;
priv->vert_scroll_edge_on = FALSE;
priv->horiz_scroll_edge_on = FALSE;
priv->vert_scroll_twofinger_on = FALSE;
priv->horiz_scroll_twofinger_on = FALSE;
return delay;
}
/* scroll detection */
if (finger && !priv->finger_state) {
stop_coasting(priv);
if (para->circular_scrolling) {
if ((para->circular_trigger == 0 && edge) ||
(para->circular_trigger == 1 && edge & TOP_EDGE) ||
(para->circular_trigger == 2 && edge & TOP_EDGE && edge & RIGHT_EDGE) ||
(para->circular_trigger == 3 && edge & RIGHT_EDGE) ||
(para->circular_trigger == 4 && edge & RIGHT_EDGE && edge & BOTTOM_EDGE) ||
(para->circular_trigger == 5 && edge & BOTTOM_EDGE) ||
(para->circular_trigger == 6 && edge & BOTTOM_EDGE && edge & LEFT_EDGE) ||
(para->circular_trigger == 7 && edge & LEFT_EDGE) ||
(para->circular_trigger == 8 && edge & LEFT_EDGE && edge & TOP_EDGE)) {
priv->circ_scroll_on = TRUE;
priv->circ_scroll_vert = TRUE;
priv->scroll_a = angle(priv, hw->x, hw->y);
DBG(7, "circular scroll detected on edge\n");
}
}
}
if (!priv->circ_scroll_on) {
if (finger) {
if (hw->numFingers == 2) {
if (!priv->vert_scroll_twofinger_on &&
(para->scroll_twofinger_vert) && (para->scroll_dist_vert != 0)) {
priv->vert_scroll_twofinger_on = TRUE;
priv->vert_scroll_edge_on = FALSE;
priv->scroll_y = hw->y;
DBG(7, "vert two-finger scroll detected\n");
}
if (!priv->horiz_scroll_twofinger_on &&
(para->scroll_twofinger_horiz) && (para->scroll_dist_horiz != 0)) {
priv->horiz_scroll_twofinger_on = TRUE;
priv->horiz_scroll_edge_on = FALSE;
priv->scroll_x = hw->x;
DBG(7, "horiz two-finger scroll detected\n");
}
}
}
if (finger && !priv->finger_state) {
if (!priv->vert_scroll_twofinger_on && !priv->horiz_scroll_twofinger_on) {
if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
(edge & RIGHT_EDGE)) {
priv->vert_scroll_edge_on = TRUE;
priv->scroll_y = hw->y;
DBG(7, "vert edge scroll detected on right edge\n");
}
if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0) &&
(edge & BOTTOM_EDGE)) {
priv->horiz_scroll_edge_on = TRUE;
priv->scroll_x = hw->x;
DBG(7, "horiz edge scroll detected on bottom edge\n");
}
}
}
}
{
Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on ||
(priv->circ_scroll_on && priv->circ_scroll_vert);
Bool oldh = priv->horiz_scroll_twofinger_on || priv->horiz_scroll_edge_on ||
(priv->circ_scroll_on && !priv->circ_scroll_vert);
Bool oldc = priv->circ_scroll_on;
if (priv->circ_scroll_on && !finger) {
/* circular scroll locks in until finger is raised */
DBG(7, "cicular scroll off\n");
priv->circ_scroll_on = FALSE;
}
if (!finger || hw->numFingers != 2) {
if (priv->vert_scroll_twofinger_on) {
DBG(7, "vert two-finger scroll off\n");
priv->vert_scroll_twofinger_on = FALSE;
}
if (priv->horiz_scroll_twofinger_on) {
DBG(7, "horiz two-finger scroll off\n");
priv->horiz_scroll_twofinger_on = FALSE;
}
}
if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
DBG(7, "vert edge scroll off\n");
priv->vert_scroll_edge_on = FALSE;
}
if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
DBG(7, "horiz edge scroll off\n");
priv->horiz_scroll_edge_on = FALSE;
}
/* If we were corner edge scrolling (coasting),
* but no longer in corner or raised a finger, then stop coasting. */
if (para->scroll_edge_corner && (priv->autoscroll_xspd || priv->autoscroll_yspd)) {
Bool is_in_corner =
((edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) ;
if (!is_in_corner || !finger) {
DBG(7, "corner edge scroll off\n");
stop_coasting(priv);
}
}
/* if we were scrolling, but couldn't corner edge scroll,
* and are no longer scrolling, then start coasting */
oldv = oldv && !(priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on ||
(priv->circ_scroll_on && priv->circ_scroll_vert));
oldh = oldh && !(priv->horiz_scroll_twofinger_on || priv->horiz_scroll_edge_on ||
(priv->circ_scroll_on && !priv->circ_scroll_vert));
oldc = oldc && !priv->circ_scroll_on;
if ((oldv || oldh) && !para->scroll_edge_corner) {
start_coasting(priv, hw, oldv, oldh, oldc);
}
}
/* if hitting a corner (top right or bottom right) while vertical
* scrolling is active, consider starting corner edge scrolling or
* switching over to circular scrolling smoothly */
if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
(edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
if (para->scroll_edge_corner) {
if (priv->autoscroll_yspd == 0) {
/* FYI: We can generate multiple start_coasting requests if
* we're in the corner, but we were moving so slowly when we
* got here that we didn't actually start coasting. */
DBG(7, "corner edge scroll on\n");
start_coasting(priv, hw, TRUE, FALSE, FALSE);
}
} else if (para->circular_scrolling) {
priv->vert_scroll_edge_on = FALSE;
priv->circ_scroll_on = TRUE;
priv->circ_scroll_vert = TRUE;
priv->scroll_a = angle(priv, hw->x, hw->y);
DBG(7, "switching to circular scrolling\n");
}
}
/* Same treatment for horizontal scrolling */
if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
(edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
if (para->scroll_edge_corner) {
if (priv->autoscroll_xspd == 0) {
/* FYI: We can generate multiple start_coasting requests if
* we're in the corner, but we were moving so slowly when we
* got here that we didn't actually start coasting. */
DBG(7, "corner edge scroll on\n");
start_coasting(priv, hw, FALSE, TRUE, FALSE);
}
} else if (para->circular_scrolling) {
priv->horiz_scroll_edge_on = FALSE;
priv->circ_scroll_on = TRUE;
priv->circ_scroll_vert = FALSE;
priv->scroll_a = angle(priv, hw->x, hw->y);
DBG(7, "switching to circular scrolling\n");
}
}
if (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->scroll_packet_count++;
}
if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
/* + = down, - = up */
int delta = para->scroll_dist_vert;
if (delta > 0) {
while (hw->y - priv->scroll_y > delta) {
sd->down++;
priv->scroll_y += delta;
}
while (hw->y - priv->scroll_y < -delta) {
sd->up++;
priv->scroll_y -= delta;
}
}
}
if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
/* + = right, - = left */
int delta = para->scroll_dist_horiz;
if (delta > 0) {
while (hw->x - priv->scroll_x > delta) {
sd->right++;
priv->scroll_x += delta;
}
while (hw->x - priv->scroll_x < -delta) {
sd->left++;
priv->scroll_x -= delta;
}
}
}
if (priv->circ_scroll_on) {
/* + = counter clockwise, - = clockwise */
double delta = para->scroll_dist_circ;
if (delta >= 0.005) {
while (diffa(priv->scroll_a, angle(priv, hw->x, hw->y)) > delta) {
if (priv->circ_scroll_vert)
sd->up++;
else
sd->right++;
priv->scroll_a += delta;
if (priv->scroll_a > M_PI)
priv->scroll_a -= 2 * M_PI;
}
while (diffa(priv->scroll_a, angle(priv, hw->x, hw->y)) < -delta) {
if (priv->circ_scroll_vert)
sd->down++;
else
sd->left++;
priv->scroll_a -= delta;
if (priv->scroll_a < -M_PI)
priv->scroll_a += 2 * M_PI;
}
}
}
if (priv->autoscroll_yspd) {
double dtime = (hw->millis - HIST(0).millis) / 1000.0;
double ddy = para->coasting_friction * dtime;
priv->autoscroll_y += priv->autoscroll_yspd * dtime;
delay = MIN(delay, 20);
while (priv->autoscroll_y > 1.0) {
sd->down++;
priv->autoscroll_y -= 1.0;
}
while (priv->autoscroll_y < -1.0) {
sd->up++;
priv->autoscroll_y += 1.0;
}
if (abs(priv->autoscroll_yspd) < ddy) {
priv->autoscroll_yspd = 0;
priv->scroll_packet_count = 0;
} else {
priv->autoscroll_yspd += (priv->autoscroll_yspd < 0 ? ddy : -1*ddy);
}
}
if (priv->autoscroll_xspd) {
double dtime = (hw->millis - HIST(0).millis) / 1000.0;
double ddx = para->coasting_friction * dtime;
priv->autoscroll_x += priv->autoscroll_xspd * dtime;
delay = MIN(delay, 20);
while (priv->autoscroll_x > 1.0) {
sd->right++;
priv->autoscroll_x -= 1.0;
}
while (priv->autoscroll_x < -1.0) {
sd->left++;
priv->autoscroll_x += 1.0;
}
if (abs(priv->autoscroll_xspd) < ddx) {
priv->autoscroll_xspd = 0;
priv->scroll_packet_count = 0;
} else {
priv->autoscroll_xspd += (priv->autoscroll_xspd < 0 ? ddx : -1*ddx);
}
}
return delay;
}
static void
handle_clickfinger(SynapticsParameters *para, struct SynapticsHwState *hw)
{
int action = 0;
switch(hw->numFingers){
case 1:
action = para->click_action[F1_CLICK1];
break;
case 2:
action = para->click_action[F2_CLICK1];
break;
case 3:
action = para->click_action[F3_CLICK1];
break;
}
switch(action){
case 1:
hw->left = 1;
break;
case 2:
hw->left = 0;
hw->middle = 1;
break;
case 3:
hw->left = 0;
hw->right = 1;
break;
}
}
/* Update the hardware state in shared memory. This is read-only these days,
* nothing in the driver reads back from SHM. SHM configuration is a thing of the past.
*/
static void
update_shm(const InputInfoPtr pInfo, const struct SynapticsHwState *hw)
{
int i;
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
SynapticsSHM *shm = priv->synshm;
if (!shm)
return;
shm->x = hw->x;
shm->y = hw->y;
shm->z = hw->z;
shm->numFingers = hw->numFingers;
shm->fingerWidth = hw->fingerWidth;
shm->left = hw->left;
shm->right = hw->right;
shm->up = hw->up;
shm->down = hw->down;
for (i = 0; i < 8; i++)
shm->multi[i] = hw->multi[i];
shm->middle = hw->middle;
}
/* Adjust the hardware state according to the extra buttons (if the touchpad
* has any and not many touchpads do these days). These buttons are up/down
* tilt buttons and/or left/right buttons that then map into a specific
* function (or scrolling into).
*/
static Bool
adjust_state_from_scrollbuttons(const InputInfoPtr pInfo, struct SynapticsHwState *hw)
{
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
SynapticsParameters *para = &priv->synpara;
Bool double_click = FALSE;
if (!para->updown_button_scrolling) {
if (hw->down) { /* map down button to middle button */
hw->middle = TRUE;
}
if (hw->up) { /* up button generates double click */
if (!priv->prev_up)
double_click = TRUE;
}
priv->prev_up = hw->up;
/* reset up/down button events */
hw->up = hw->down = FALSE;
}
/* Left/right button scrolling, or middle clicks */
if (!para->leftright_button_scrolling) {
if (hw->multi[2] || hw->multi[3])
hw->middle = TRUE;
/* reset left/right button events */
hw->multi[2] = hw->multi[3] = FALSE;
}
return double_click;
}
static void
update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw, int *delay)
{
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
SynapticsParameters *para = &priv->synpara;
/* Treat the first two multi buttons as up/down for now. */
hw->up |= hw->multi[0];
hw->down |= hw->multi[1];
/* 3rd button emulation */
hw->middle |= HandleMidButtonEmulation(priv, hw, delay);
/* Fingers emulate other buttons */
if(hw->left && hw->numFingers >= 1){
handle_clickfinger(para, hw);
}
/* Two finger emulation */
if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
hw->fingerWidth >= para->emulate_twofinger_w) {
hw->numFingers = 2;
}
}
static void
post_button_click(const InputInfoPtr pInfo, const int button)
{
xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
}
static void
post_scroll_events(const InputInfoPtr pInfo, struct ScrollData scroll)
{
while (scroll.up-- > 0)
post_button_click(pInfo, 4);
while (scroll.down-- > 0)
post_button_click(pInfo, 5);
while (scroll.left-- > 0)
post_button_click(pInfo, 6);
while (scroll.right-- > 0)
post_button_click(pInfo, 7);
}
static inline int
repeat_scrollbuttons(const InputInfoPtr pInfo,
const struct SynapticsHwState *hw,
int buttons, int delay)
{
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
SynapticsParameters *para = &priv->synpara;
int repeat_delay, timeleft;
int rep_buttons = ((para->updown_button_repeat ? 0x18 : 0) |
(para->leftright_button_repeat ? 0x60 : 0));
/* Handle auto repeat buttons */
repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
if (((hw->up || hw->down) && para->updown_button_repeat &&
para->updown_button_scrolling) ||
((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
para->leftright_button_scrolling)) {
priv->repeatButtons = buttons & rep_buttons;
if (!priv->nextRepeat) {
priv->nextRepeat = hw->millis + repeat_delay * 2;
}
} else {
priv->repeatButtons = 0;
priv->nextRepeat = 0;
}
if (priv->repeatButtons) {
timeleft = TIME_DIFF(priv->nextRepeat, hw->millis);
if (timeleft > 0)
delay = MIN(delay, timeleft);
if (timeleft <= 0) {
int change, id;
change = priv->repeatButtons;
while (change) {
id = ffs(change);
change &= ~(1 << (id - 1));
xf86PostButtonEvent(pInfo->dev, FALSE, id, FALSE, 0, 0);
xf86PostButtonEvent(pInfo->dev, FALSE, id, TRUE, 0, 0);
}
priv->nextRepeat = hw->millis + repeat_delay;
delay = MIN(delay, repeat_delay);
}
}
return delay;
}
/*
* React on changes in the hardware state. This function is called every time
* the hardware state changes. The return value is used to specify how many
* milliseconds to wait before calling the function again if no state change
* occurs.
*/
static int
HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
{
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
SynapticsParameters *para = &priv->synpara;
int finger;
int dx, dy, buttons, id;
edge_type edge = NO_EDGE;
int change;
struct ScrollData scroll;
int double_click = FALSE;
int delay = 1000000000;
int timeleft;
Bool inside_active_area;
update_shm(pInfo, hw);
/* If touchpad is switched off, we skip the whole thing and return delay */
if (para->touchpad_off == 1)
return delay;
/* apply hysteresis before doing anything serious. This cancels
* out a lot of noise which might surface in strange phenomena
* like flicker in scrolling or noise motion. */
priv->hyst_center_x = hysteresis(hw->x, priv->hyst_center_x, para->hyst_x);
priv->hyst_center_y = hysteresis(hw->y, priv->hyst_center_y, para->hyst_y);
hw->x = priv->hyst_center_x;
hw->y = priv->hyst_center_y;
inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
/* now we know that these _coordinates_ aren't in the area.
invalid are: x, y, z, numFingers, fingerWidth
valid are: millis, left/right/middle/up/down/etc.
*/
if (!inside_active_area)
{
hw->x = 0;
hw->y = 0;
hw->z = 0;
hw->numFingers = 0;
hw->fingerWidth = 0;
/* FIXME: if finger accidentally moves into the area and doesn't
* really release, the finger should remain down. */
finger = FS_UNTOUCHED;
edge = NO_EDGE;
dx = dy = 0;
}
/* these two just update hw->left, right, etc. */
update_hw_button_state(pInfo, hw, &delay);
if (priv->has_scrollbuttons)
double_click = adjust_state_from_scrollbuttons(pInfo, hw);
/* no edge or finger detection outside of area */
if (inside_active_area) {
edge = edge_detection(priv, hw->x, hw->y);
finger = SynapticsDetectFinger(priv, hw);
}
/* tap and drag detection. Needs to be performed even if the finger is in
* the dead area to reset the state. */
timeleft = HandleTapProcessing(priv, hw, finger, inside_active_area);
if (timeleft > 0)
delay = MIN(delay, timeleft);
if (inside_active_area)
{
/* Don't bother about scrolling in the dead area of the touchpad. */
timeleft = HandleScrolling(priv, hw, edge, finger, &scroll);
if (timeleft > 0)
delay = MIN(delay, timeleft);
/*
* Compensate for unequal x/y resolution. This needs to be done after
* calculations that require unadjusted coordinates, for example edge
* detection.
*/
ScaleCoordinates(priv, hw);
}
dx = dy = 0;
if (!priv->absolute_events) {
timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
delay = MIN(delay, timeleft);
}
buttons = ((hw->left ? 0x01 : 0) |
(hw->middle ? 0x02 : 0) |
(hw->right ? 0x04 : 0) |
(hw->up ? 0x08 : 0) |
(hw->down ? 0x10 : 0) |
(hw->multi[2] ? 0x20 : 0) |
(hw->multi[3] ? 0x40 : 0));
if (priv->tap_button > 0) {
int tap_mask = 1 << (priv->tap_button - 1);
if (priv->tap_button_state == TBS_BUTTON_DOWN_UP) {
if (tap_mask != (priv->lastButtons & tap_mask)) {
xf86PostButtonEvent(pInfo->dev, FALSE, priv->tap_button, TRUE, 0, 0);
priv->lastButtons |= tap_mask;
}
priv->tap_button_state = TBS_BUTTON_UP;
}
if (priv->tap_button_state == TBS_BUTTON_DOWN)
buttons |= tap_mask;
}
/* Post events */
if (finger > FS_UNTOUCHED) {
if (priv->absolute_events && inside_active_area) {
xf86PostMotionEvent(pInfo->dev, 1, 0, 2, hw->x, hw->y);
} else if (dx || dy) {
xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
}
}
if (priv->mid_emu_state == MBE_LEFT_CLICK)
{
post_button_click(pInfo, 1);
priv->mid_emu_state = MBE_OFF;
} else if (priv->mid_emu_state == MBE_RIGHT_CLICK)
{
post_button_click(pInfo, 3);
priv->mid_emu_state = MBE_OFF;
}
change = buttons ^ priv->lastButtons;
while (change) {
id = ffs(change); /* number of first set bit 1..32 is returned */
change &= ~(1 << (id - 1));
xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))), 0, 0);
}
/* Process scroll events only if coordinates are
* in the Synaptics Area
*/
if (inside_active_area)
post_scroll_events(pInfo, scroll);
if (double_click) {
post_button_click(pInfo, 1);
post_button_click(pInfo, 1);
}
if (priv->has_scrollbuttons)
delay = repeat_scrollbuttons(pInfo, hw, buttons, delay);
/* Save old values of some state variables */
priv->finger_state = finger;
priv->lastButtons = buttons;
/* generate a history of the absolute positions */
if (inside_active_area)
store_history(priv, hw->x, hw->y, hw->millis);
return delay;
}
static int
ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
{
DBG(3, "Control Proc called\n");
return Success;
}
static int
SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
{
InputInfoPtr pInfo = (InputInfoPtr) dev->public.devicePrivate;
SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
DBG(3, "SwitchMode called\n");
switch (mode) {
case Absolute:
priv->absolute_events = TRUE;
break;
case Relative:
priv->absolute_events = FALSE;
break;
default:
return XI_BadMode;
}
return Success;
}
static void
ReadDevDimensions(InputInfoPtr pInfo)
{
SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
if (priv->proto_ops->ReadDevDimensions)
priv->proto_ops->ReadDevDimensions(pInfo);
SanitizeDimensions(pInfo);
}
static Bool
QueryHardware(InputInfoPtr pInfo)
{
SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
priv->comm.protoBufTail = 0;
if (!priv->proto_ops->QueryHardware(pInfo)) {
xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
if (priv->proto_ops->DeviceOffHook)
priv->proto_ops->DeviceOffHook(pInfo);
return FALSE;
}
return TRUE;
}
static void
ScaleCoordinates(SynapticsPrivate *priv, struct SynapticsHwState *hw)
{
int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
}
void
CalculateScalingCoeffs(SynapticsPrivate *priv)
{
int vertRes = priv->synpara.resolution_vert;
int horizRes = priv->synpara.resolution_horiz;
if ((horizRes > vertRes) && (horizRes > 0)) {
priv->horiz_coeff = vertRes / (double)horizRes;
priv->vert_coeff = 1;
} else if ((horizRes < vertRes) && (vertRes > 0)) {
priv->horiz_coeff = 1;
priv->vert_coeff = horizRes / (double)vertRes;
} else {
priv->horiz_coeff = 1;
priv->vert_coeff = 1;
}
}