--- xserver-xorg-input-synaptics-1.9.1.orig/.pc/.quilt_patches +++ xserver-xorg-input-synaptics-1.9.1/.pc/.quilt_patches @@ -0,0 +1 @@ +debian/patches --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/.quilt_series +++ xserver-xorg-input-synaptics-1.9.1/.pc/.quilt_series @@ -0,0 +1 @@ +series --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/.version +++ xserver-xorg-input-synaptics-1.9.1/.pc/.version @@ -0,0 +1 @@ +2 --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/02-do-not-use-synaptics-for-keyboards.patch/conf/11-x11-synaptics.fdi +++ xserver-xorg-input-synaptics-1.9.1/.pc/02-do-not-use-synaptics-for-keyboards.patch/conf/11-x11-synaptics.fdi @@ -0,0 +1,40 @@ + + + + + + synaptics + + + 50% 0 82% 0 0 0 0 0 + 58% 0 0 8% 42% 58% 0 8% + + 0 0 0 0 0 0 0 0 + + + + --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/03-bug-return-val.patch/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/03-bug-return-val.patch/src/synaptics.c @@ -0,0 +1,3246 @@ +/* + * 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-2012 Red Hat, Inc. + * Copyright © 2011 The Chromium OS Authors + * + * 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 + * Christian Thaeter + * Stefan Bethge + * Matthias Ihmig + * Alexei Gilchrist + * Jörg Bösner + * Hartwig Felger + * Peter Osterlund + * S. Lehner + * Stefan Gmeiner + * Henry Davies for the + * Linuxcare Inc. David Kennedy + * Fred Hucht + * Fedor P. Goncharov + * Simon Thum + * + * Trademarks are the property of their respective owners. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +enum EdgeType { + 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 +}; + +/* + * We expect to be receiving a steady 80 packets/sec (which gives 40 + * reports/sec with more than one finger on the pad, as Advanced Gesture Mode + * requires two PS/2 packets per report). Instead of a random scattering of + * magic 13 and 20ms numbers scattered throughout the driver, introduce + * POLL_MS as 14ms, which is slightly less than 80Hz. 13ms is closer to + * 80Hz, but if the kernel event reporting was even slightly delayed, + * we would produce synthetic motion followed immediately by genuine + * motion, so use 14. + * + * We use this to call back at a constant rate to at least produce the + * illusion of smooth motion. It works a lot better than you'd expect. +*/ +#define POLL_MS 14 + +#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 + +#define INPUT_BUFFER_SIZE 200 + +/***************************************************************************** + * Forward declaration + ****************************************************************************/ +static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static Bool DeviceControl(DeviceIntPtr, int); +static void ReadInput(InputInfoPtr); +static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now, + Bool from_timer); +static int ControlProc(InputInfoPtr, xDeviceCtl *); +static int SwitchMode(ClientPtr, DeviceIntPtr, int); +static int DeviceInit(DeviceIntPtr); +static int DeviceOn(DeviceIntPtr); +static int DeviceOff(DeviceIntPtr); +static int DeviceClose(DeviceIntPtr); +static Bool QueryHardware(InputInfoPtr); +static void ReadDevDimensions(InputInfoPtr); +#ifndef NO_DRIVER_SCALING +static void ScaleCoordinates(SynapticsPrivate * priv, + struct SynapticsHwState *hw); +static void CalculateScalingCoeffs(SynapticsPrivate * priv); +#endif +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, + NULL, +#ifdef XI86_DRV_CAP_SERVER_FD + XI86_DRV_CAP_SERVER_FD +#endif +}; + +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 + ****************************************************************************/ +static inline void +SynapticsCloseFd(InputInfoPtr pInfo) +{ + if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) { + xf86CloseSerial(pInfo->fd); + pInfo->fd = -1; + } +} + +/** + * 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 = 255; + + 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 = 15; + + xf86IDrvMsg(pInfo, X_PROBED, + "invalid finger width range. defaulting to %d - %d\n", + priv->minw, priv->maxw); + } +} + +static Bool +SetDeviceAndProtocol(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = pInfo->private; + char *proto, *device; + int i; + + proto = xf86SetStrOption(pInfo->options, "Protocol", NULL); + device = xf86SetStrOption(pInfo->options, "Device", NULL); + + /* If proto is auto-dev, unset and let the code do the rest */ + if (proto && !strcmp(proto, "auto-dev")) { + free(proto); + proto = 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; + + return (priv->proto_ops != 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 || + priv->model == MODEL_UNIBODY_MACBOOK) { + 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; +} + +static void +calculate_tap_hysteresis(SynapticsPrivate * priv, int range, + int *fingerLow, int *fingerHigh) +{ + switch (priv->model) { + case MODEL_ELANTECH: + /* All Elantech touchpads don't need the Z filtering to get the + * number of fingers correctly. See Documentation/elantech.txt + * in the kernel. + */ + *fingerLow = priv->minp + 1; + *fingerHigh = priv->minp + 1; + break; + case MODEL_UNIBODY_MACBOOK: + *fingerLow = 70; + *fingerHigh = 75; + break; + default: + *fingerLow = priv->minp + range * (25.0 / 256); + *fingerHigh = priv->minp + range * (30.0 / 256); + break; + } +} + +/* 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; + double percent = xf86CheckPercentOption(options, optname, -1); + + if (percent >= 0.0) { + percent = xf86SetPercentOption(options, optname, -1); + result = percent / 100.0 * range + offset; + } else + result = xf86SetIntOption(options, optname, default_value); + + return result; +} + +Bool +SynapticsIsSoftButtonAreasValid(int *values) +{ + Bool right_disabled = FALSE; + Bool middle_disabled = FALSE; + + enum { + /* right button left, right, top, bottom */ + RBL = 0, + RBR = 1, + RBT = 2, + RBB = 3, + /* middle button left, right, top, bottom */ + MBL = 4, + MBR = 5, + MBT = 6, + MBB = 7, + }; + + /* Check right button area */ + if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) || + (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB]))) + return FALSE; + + /* Check middle button area */ + if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) || + (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB]))) + return FALSE; + + if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0) + right_disabled = TRUE; + + if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0) + middle_disabled = TRUE; + + if (!right_disabled && + ((values[RBL] && values[RBL] == values[RBR]) || + (values[RBT] && values[RBT] == values[RBB]))) + return FALSE; + + if (!middle_disabled && + ((values[MBL] && values[MBL] == values[MBR]) || + (values[MBT] && values[MBT] == values[MBB]))) + return FALSE; + + /* Check for overlapping button areas */ + if (!right_disabled && !middle_disabled) { + int right_left = values[RBL] ? values[RBL] : INT_MIN; + int right_right = values[RBR] ? values[RBR] : INT_MAX; + int right_top = values[RBT] ? values[RBT] : INT_MIN; + int right_bottom = values[RBB] ? values[RBB] : INT_MAX; + int middle_left = values[MBL] ? values[MBL] : INT_MIN; + int middle_right = values[MBR] ? values[MBR] : INT_MAX; + int middle_top = values[MBT] ? values[MBT] : INT_MIN; + int middle_bottom = values[MBB] ? values[MBB] : INT_MAX; + + /* If areas overlap in the Y axis */ + if ((right_bottom <= middle_bottom && right_bottom >= middle_top) || + (right_top <= middle_bottom && right_top >= middle_top)) { + /* Check for overlapping left edges */ + if ((right_left < middle_left && right_right > middle_left) || + (middle_left < right_left && middle_right > right_left)) + return FALSE; + + /* Check for overlapping right edges */ + if ((right_right > middle_right && right_left < middle_right) || + (middle_right > right_right && middle_left < right_right)) + return FALSE; + } + + /* If areas overlap in the X axis */ + if ((right_left >= middle_left && right_left <= middle_right) || + (right_right >= middle_left && right_right <= middle_right)) { + /* Check for overlapping top edges */ + if ((right_top < middle_top && right_bottom > middle_top) || + (middle_top < right_top && middle_bottom > right_top)) + return FALSE; + + /* Check for overlapping bottom edges */ + if ((right_bottom > middle_bottom && right_top < middle_bottom) || + (middle_bottom > right_bottom && middle_top < right_bottom)) + return FALSE; + } + } + + return TRUE; +} + +static void +set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset) +{ + SynapticsPrivate *priv = pInfo->private; + SynapticsParameters *pars = &priv->synpara; + int values[8]; + int in_percent = 0; /* bitmask for which ones are in % */ + char *option_string; + char *next_num; + char *end_str; + int i; + int width, height; + + if (!pars->clickpad) + return; + + option_string = xf86SetStrOption(pInfo->options, option_name, NULL); + if (!option_string) + return; + + next_num = option_string; + + for (i = 0; i < 8 && *next_num != '\0'; i++) { + long int value = strtol(next_num, &end_str, 0); + + if (value > INT_MAX || value < -INT_MAX) + goto fail; + + values[i] = value; + + if (next_num != end_str) { + if (*end_str == '%') { + in_percent |= 1 << i; + end_str++; + } + next_num = end_str; + } + else + goto fail; + } + + if (i < 8 || *next_num != '\0') + goto fail; + + width = priv->maxx - priv->minx; + height = priv->maxy - priv->miny; + + for (i = 0; in_percent && i < 8; i++) { + int base, size; + + if ((in_percent & (1 << i)) == 0 || values[i] == 0) + continue; + + size = ((i % 4) < 2) ? width : height; + base = ((i % 4) < 2) ? priv->minx : priv->miny; + values[i] = base + size * values[i] / 100.0; + } + + if (!SynapticsIsSoftButtonAreasValid(values)) + goto fail; + + memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int)); + memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int)); + + free(option_string); + + return; + + fail: + xf86IDrvMsg(pInfo, X_ERROR, + "invalid %s value '%s', keeping defaults\n", + option_name, option_string); + free(option_string); +} + +static void +set_primary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA); +} + +static void +set_secondary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA); +} + +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 */ + double accelFactor; /* 1/pixels */ + int fingerLow, fingerHigh; /* pressure */ + int emulateTwoFingerMinZ; /* pressure */ + int emulateTwoFingerMinW; /* width */ + 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; + int middle_button_timeout; + int grab_event_device = 0; + const char *source; + + /* 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; + 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 + 1; + + calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh); + + /* scaling based on defaults and a pressure of 256 */ + emulateTwoFingerMinZ = priv->minp + range * (282.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 + 1; + + /* 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->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->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad); /* Probed */ + if (pars->clickpad) + pars->has_secondary_buttons = xf86SetBoolOption(opts, + "HasSecondarySoftButtons", + pars->has_secondary_buttons); + pars->clickpad_ignore_motion_time = 100; /* ms */ + /* middle mouse button emulation on a clickpad? nah, you're joking */ + middle_button_timeout = pars->clickpad ? 0 : 75; + pars->emulate_mid_button_time = + xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout); + pars->emulate_twofinger_z = + xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ); + pars->emulate_twofinger_w = + xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW); + pars->scroll_dist_vert = + xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta); + pars->scroll_dist_horiz = + xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta); + 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->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON); + + 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->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->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); + + /* Only grab the device by default if it's not coming from a config + backend. This way we avoid the device being added twice and sending + duplicate events. + */ + source = xf86CheckStrOption(opts, "_source", NULL); + if (source == NULL || strncmp(source, "server/", 7) != 0) + grab_event_device = TRUE; + pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device); + + pars->tap_and_drag_gesture = + xf86SetBoolOption(opts, "TapAndDragGesture", TRUE); + pars->resolution_horiz = + xf86SetIntOption(opts, "HorizResolution", horizResolution); + pars->resolution_vert = + xf86SetIntOption(opts, "VertResolution", vertResolution); + if (pars->resolution_horiz <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid X resolution, using 1 instead.\n"); + pars->resolution_horiz = 1; + } + if (pars->resolution_vert <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid Y resolution, using 1 instead.\n"); + pars->resolution_vert = 1; + } + + /* Touchpad sampling rate is too low to detect all movements. + A user may lift one finger and put another one down within the same + EV_SYN or even between samplings so the driver doesn't notice at all. + + We limit the movement to 20 mm within one event, that is more than + recordings showed is needed (17mm on a T440). + */ + if (pars->resolution_horiz > 1 && + pars->resolution_vert > 1) + pars->maxDeltaMM = 20; + else { + /* on devices without resolution set the vector length to 0.25 of + the touchpad diagonal */ + pars->maxDeltaMM = diag * 0.25; + } + + + /* 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"); + } + + set_primary_softbutton_areas_option(pInfo); + if (pars->has_secondary_buttons) + set_secondary_softbutton_areas_option(pInfo); +} + +static double +SynapticsAccelerationProfile(DeviceIntPtr dev, + DeviceVelocityPtr vel, + double velocity, double thr, double 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; +} + +static int +SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + 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 */ + if (!SetDeviceAndProtocol(pInfo)) { + 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); + + set_default_parameters(pInfo); + +#ifndef NO_DRIVER_SCALING + CalculateScalingCoeffs(priv); +#endif + + + 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 (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + + return Success; + + SetupProc_fail: + SynapticsCloseFd(pInfo); + + if (priv->comm.buffer) + XisbFree(priv->comm.buffer); + 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); + if (priv && priv->scroll_events_mask) + valuator_mask_free(&priv->scroll_events_mask); + if (priv && priv->open_slots) + free(priv->open_slots); + 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 int +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 int +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)) + goto error; + + priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE); + if (!priv->comm.buffer) + goto error; + + xf86FlushInput(pInfo->fd); + + /* reinit the pad */ + if (!QueryHardware(pInfo)) + goto error; + + xf86AddEnabledDevice(pInfo); + dev->public.on = TRUE; + + return Success; + +error: + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + return !Success; +} + +static void +SynapticsReset(SynapticsPrivate * priv) +{ + int i; + + SynapticsResetHwState(priv->hwState); + SynapticsResetHwState(priv->local_hw_state); + SynapticsResetHwState(priv->comm.hwState); + + memset(priv->move_hist, 0, sizeof(priv->move_hist)); + priv->hyst_center_x = 0; + priv->hyst_center_y = 0; + memset(&priv->scroll, 0, sizeof(priv->scroll)); + priv->count_packet_finger = 0; + priv->finger_state = FS_UNTOUCHED; + priv->last_motion_millis = 0; + priv->clickpad_click_millis = 0; + priv->last_button_area = NO_BUTTON_AREA; + priv->tap_state = TS_START; + priv->tap_button = 0; + priv->tap_button_state = TBS_BUTTON_UP; + priv->moving_state = MS_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; + priv->circ_scroll_on = FALSE; + priv->circ_scroll_vert = FALSE; + priv->mid_emu_state = MBE_OFF; + priv->nextRepeat = 0; + priv->lastButtons = 0; + priv->prev_z = 0; + priv->prevFingers = 0; + priv->num_active_touches = 0; + + for (i = 0; i < priv->num_slots; i++) + priv->open_slots[i] = -1; +} + +static int +DeviceOff(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + Bool rc = Success; + + DBG(3, "Synaptics DeviceOff called\n"); + + if (pInfo->fd != -1) { + TimerCancel(priv->timer); + xf86RemoveEnabledDevice(pInfo); + SynapticsReset(priv); + + if (priv->proto_ops->DeviceOffHook && + !priv->proto_ops->DeviceOffHook(pInfo)) + rc = !Success; + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + } + dev->public.on = FALSE; + return rc; +} + +static int +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(priv->touch_axes); + priv->touch_axes = NULL; + SynapticsHwStateFree(&priv->hwState); + SynapticsHwStateFree(&priv->local_hw_state); + SynapticsHwStateFree(&priv->comm.hwState); + return RetValue; +} + +static void +InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv) +{ + int i; + + memset(labels, 0, nlabels * sizeof(Atom)); + switch (nlabels) { + default: + case 4: + labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL); + case 3: + labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL); + case 2: + labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y); + case 1: + labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X); + break; + } + + for (i = 0; i < priv->num_mt_axes; i++) { + SynapticsTouchAxisRec *axis = &priv->touch_axes[i]; + int axnum = nlabels - priv->num_mt_axes + i; + + labels[axnum] = XIGetKnownProperty(axis->label); + } +} + +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; + } +} + +static void +DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + if (!priv->has_touch) + return; + + priv->num_slots = + priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES; + + priv->open_slots = malloc(priv->num_slots * sizeof(int)); + if (!priv->open_slots) { + xf86IDrvMsg(pInfo, X_ERROR, + "failed to allocate open touch slots array\n"); + priv->has_touch = 0; + priv->num_slots = 0; + } +} + +static int +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; + int num_axes = 2; + Atom btn_labels[SYN_MAX_BUTTONS] = { 0 }; + Atom *axes_labels; + DeviceVelocityPtr pVel; + + num_axes += 2; + + num_axes += priv->num_mt_axes; + + axes_labels = calloc(num_axes, sizeof(Atom)); + if (!axes_labels) { + xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n"); + return !Success; + } + + InitAxesLabels(axes_labels, num_axes, priv); + InitButtonLabels(btn_labels, SYN_MAX_BUTTONS); + + 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, + btn_labels, + SynapticsCtrl, + GetMotionHistorySize(), num_axes, axes_labels); + + /* + * setup dix acceleration to match legacy synaptics settings, and + * etablish a device-specific profile to do stuff like pressure-related + * acceleration. + */ + 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, "(accel) MaxSpeed is now %.2f\n", + priv->synpara.max_speed); + xf86IDrvMsg(pInfo, X_CONFIG, "(accel) 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); + } + + /* X valuator */ + if (priv->minx < priv->maxx) { + min = priv->minx; + max = priv->maxx; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max, + priv->resx * 1000, 0, priv->resx * 1000, + Relative); + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ + if (priv->miny < priv->maxy) { + min = priv->miny; + max = priv->maxy; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max, + priv->resy * 1000, 0, priv->resy * 1000, + Relative); + xf86InitValuatorDefaults(dev, 1); + + xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_horiz = 2; + xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_vert = 3; + priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS); + if (!priv->scroll_events_mask) { + free(axes_labels); + return !Success; + } + + SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL, + priv->synpara.scroll_dist_horiz, 0); + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + priv->synpara.scroll_dist_vert, 0); + + DeviceInitTouch(dev, axes_labels); + + free(axes_labels); + + priv->hwState = SynapticsHwStateAlloc(priv); + if (!priv->hwState) + goto fail; + + priv->local_hw_state = SynapticsHwStateAlloc(priv); + if (!priv->local_hw_state) + goto fail; + + priv->comm.hwState = SynapticsHwStateAlloc(priv); + + InitDeviceProperties(pInfo); + XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL); + + SynapticsReset(priv); + + return Success; + + fail: + free(priv->local_hw_state); + free(priv->hwState); + free(priv->open_slots); + 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 enum EdgeType +circular_edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 enum EdgeType +edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 a finger is down, then it must have started inside the active_area, + allow the motion to complete using the entire area */ + if (priv->finger_state >= FS_TOUCHED) + return 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 Bool +is_inside_button_area(SynapticsParameters * para, int which, int x, int y) +{ + Bool inside_area = TRUE; + + if (para->softbutton_areas[which][LEFT] == 0 && + para->softbutton_areas[which][RIGHT] == 0 && + para->softbutton_areas[which][TOP] == 0 && + para->softbutton_areas[which][BOTTOM] == 0) + return FALSE; + + if (para->softbutton_areas[which][LEFT] && + x < para->softbutton_areas[which][LEFT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][RIGHT] && + x > para->softbutton_areas[which][RIGHT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][TOP] && + y < para->softbutton_areas[which][TOP]) + inside_area = FALSE; + else if (para->softbutton_areas[which][BOTTOM] && + y > para->softbutton_areas[which][BOTTOM]) + inside_area = FALSE; + + return inside_area; +} + +static Bool +is_inside_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset, + int x, int y) +{ + Bool inside_area = TRUE; + Bool right_valid, middle_valid; + int top, bottom; + + /* We don't have a left button area, so we only check the y axis */ + right_valid = para->softbutton_areas[offset][TOP] || + para->softbutton_areas[offset][BOTTOM]; + middle_valid = para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset + 1][BOTTOM]; + + if (!right_valid && !middle_valid) + return FALSE; + + /* Check both buttons are horizontally aligned */ + if (right_valid && middle_valid && ( + para->softbutton_areas[offset][TOP] != + para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset][BOTTOM] != + para->softbutton_areas[offset + 1][BOTTOM])) + return FALSE; + + if (right_valid) { + top = para->softbutton_areas[offset][TOP]; + bottom = para->softbutton_areas[offset][BOTTOM]; + } + else { + top = para->softbutton_areas[offset + 1][TOP]; + bottom = para->softbutton_areas[offset + 1][BOTTOM]; + } + + if (top && y < top) + inside_area = FALSE; + else if (bottom && y > bottom) + inside_area = FALSE; + + return inside_area; +} + +static enum SoftButtonAreas +current_button_area(SynapticsParameters * para, int x, int y) +{ + if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y)) + return BOTTOM_BUTTON_AREA; + else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y)) + return TOP_BUTTON_AREA; + else + return NO_BUTTON_AREA; +} + +static CARD32 +timerFunc(OsTimerPtr timer, CARD32 now, pointer arg) +{ + InputInfoPtr pInfo = arg; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + struct SynapticsHwState *hw = priv->local_hw_state; + int delay; +#if !HAVE_THREADED_INPUT + int sigstate = xf86BlockSIGIO(); +#else + input_lock(); +#endif + + priv->hwState->millis += now - priv->timer_time; + SynapticsCopyHwState(hw, priv->hwState); + SynapticsResetTouchHwState(hw, FALSE); + delay = HandleState(pInfo, hw, hw->millis, TRUE); + + priv->timer_time = now; + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + +#if !HAVE_THREADED_INPUT + xf86UnblockSIGIO(sigstate); +#else + input_unlock(); +#endif + + 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 = priv->local_hw_state; + int delay = 0; + Bool newDelay = FALSE; + + SynapticsResetTouchHwState(hw, FALSE); + + while (SynapticsGetHwState(pInfo, priv, hw)) { + /* Semi-mt device touch slots do not track touches. When there is a + * change in the number of touches, we must disregard the temporary + * motion changes. */ + if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) { + hw->cumulative_dx = priv->hwState->cumulative_dx; + hw->cumulative_dy = priv->hwState->cumulative_dy; + } + + /* timer may cause actual events to lag behind (#48777) */ + if (priv->hwState->millis > hw->millis) + hw->millis = priv->hwState->millis; + + SynapticsCopyHwState(priv->hwState, hw); + delay = HandleState(pInfo, hw, hw->millis, FALSE); + newDelay = TRUE; + } + + if (newDelay) { + priv->timer_time = GetTimeInMillis(); + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + } +} + +static int +HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw, + CARD32 now, int *delay) +{ + SynapticsParameters *para = &priv->synpara; + Bool done = FALSE; + int timeleft; + int mid = 0; + + if (para->emulate_mid_button_time <= 0) + return mid; + + while (!done) { + switch (priv->mid_emu_state) { + case MBE_LEFT_CLICK: + case MBE_RIGHT_CLICK: + case MBE_OFF: + priv->button_delay_millis = now; + 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, now); + 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, now); + 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_low) + return FS_UNTOUCHED; + + if (priv->finger_state == FS_BLOCKED) + return FS_BLOCKED; + + if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED) + finger = FS_TOUCHED; + else + finger = priv->finger_state; + + if (!para->palm_detect) + return finger; + + /* palm detection */ + + if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width)) + return FS_BLOCKED; + + if (priv->has_mt_palm_detect) + return finger; + + if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED) + priv->avg_width = 0; + else + priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2; + + if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) { + 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; + + return finger; +} + +static void +SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge) +{ + enum TapEvent tap; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) { + priv->tap_button = 0; + return; + } + + switch (priv->tap_max_fingers) { + case 1: + 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; + default: + priv->tap_button = 0; + return; + } + + 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, CARD32 millis) +{ + DBG(3, "SetTapState - %d -> %d (millis:%u)\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: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_2B: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_3: + priv->tap_button_state = TBS_BUTTON_DOWN; + break; + case TS_SINGLETAP: + 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, + CARD32 millis) +{ + DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n", + priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y, + millis); + + 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, + CARD32 now, enum FingerState finger, + Bool inside_active_area) +{ + SynapticsParameters *para = &priv->synpara; + Bool touch, release, is_timeout, move, press; + int timeleft, timeout; + enum EdgeType edge; + int delay = 1000000000; + + if (para->touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) + return delay; + + touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED; + release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED; + move = (finger >= FS_TOUCHED && + (priv->tap_max_fingers <= + ((priv->horiz_scroll_twofinger_on || + priv->vert_scroll_twofinger_on) ? 2 : 1)) && + (priv->prevFingers == hw->numFingers && + ((abs(hw->x - priv->touch_on.x) >= para->tap_move) || + (abs(hw->y - priv->touch_on.y) >= para->tap_move)))); + press = (hw->left || hw->right || hw->middle); + + if (touch) { + priv->touch_on.x = hw->x; + priv->touch_on.y = hw->y; + priv->touch_on.millis = now; + } + else if (release) { + priv->touch_on.millis = now; + } + 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, now); + is_timeout = timeleft <= 0; + + restart: + switch (priv->tap_state) { + case TS_START: + if (touch) + SetTapState(priv, TS_1, now); + break; + case TS_1: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_MOVE, now); + goto restart; + } + else if (is_timeout) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_MOVE, now); + 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, now); + } + break; + case TS_MOVE: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_2A: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_2B: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_SINGLETAP: + if (touch) + SetTapState(priv, TS_1, now); + else if (is_timeout) + SetTapState(priv, TS_START, now); + break; + case TS_3: + if (move) { + if (para->tap_and_drag_gesture) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (is_timeout) { + if (para->tap_and_drag_gesture) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (release) { + SetTapState(priv, TS_2B, now); + } + break; + case TS_DRAG: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (release) { + SetMovingState(priv, MS_FALSE, now); + if (para->locked_drags) { + SetTapState(priv, TS_4, now); + } + else { + SetTapState(priv, TS_START, now); + } + } + break; + case TS_4: + if (is_timeout) { + SetTapState(priv, TS_START, now); + goto restart; + } + if (touch) + SetTapState(priv, TS_5, now); + break; + case TS_5: + if (is_timeout || move) { + SetTapState(priv, TS_DRAG, now); + goto restart; + } + else if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_CLICKPAD_MOVE: + /* Disable scrolling once a button is pressed on a clickpad */ + priv->vert_scroll_edge_on = FALSE; + priv->horiz_scroll_edge_on = FALSE; + priv->vert_scroll_twofinger_on = FALSE; + priv->horiz_scroll_twofinger_on = FALSE; + + /* Assume one touch is only for holding the clickpad button down */ + if (hw->numFingers > 1) + hw->numFingers--; + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (!press) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_MOVE, now); + priv->count_packet_finger = 0; + } + break; + } + + timeout = GetTimeOut(priv); + if (timeout >= 0) { + timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now); + delay = clamp(timeleft, 1, delay); + } + return delay; +} + +#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)]) +#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e)) + +static void +store_history(SynapticsPrivate * priv, int x, int y, CARD32 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; + if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY) + priv->count_packet_finger++; +} + +/* + * 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(SynapticsPrivate *priv, const struct SynapticsHwState *hw, + enum EdgeType edge, double *dx, double *dy) +{ + *dx = hw->x - HIST(0).x; + *dy = hw->y - HIST(0).y; +} + +/* Vector length, but not sqrt'ed, we only need it for comparison */ +static inline double +vlenpow2(double x, double y) +{ + return x * x + y * y; +} + +/** + * Compute relative motion ('deltas') including edge motion. + */ +static int +ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw, + enum EdgeType edge, int *dxP, int *dyP, Bool inside_area) +{ + enum MovingState moving_state; + double dx, dy; + double vlen; + 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: + moving_state = MS_TOUCHPAD_RELATIVE; + break; + default: + break; + } + } + + if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED || + 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 || + (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) { + /* reset packet counter. */ + priv->count_packet_finger = 0; + goto out; + } + + /* To create the illusion of fluid motion, call back at roughly the report + * rate, even in the absence of new hardware events; see comment above + * POLL_MS declaration. */ + delay = MIN(delay, POLL_MS); + + if (priv->count_packet_finger <= 1) + goto out; /* skip the lot */ + + if (moving_state == MS_TOUCHPAD_RELATIVE) + get_delta(priv, hw, edge, &dx, &dy); + + out: + priv->prevFingers = hw->numFingers; + + vlen = vlenpow2(dx/priv->synpara.resolution_horiz, + dy/priv->synpara.resolution_vert); + + if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) { + dx = 0; + dy = 0; + } + + *dxP = dx; + *dyP = dy; + + return delay; +} + +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->scroll.coast_delta_y = 0.0; + priv->scroll.coast_delta_x = 0.0; + + if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) { + double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0; + + if (vert && !circ) { + double dy = + estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y); + if (pkt_time > 0) { + double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y); + } + } + } + if (horiz && !circ) { + double dx = + estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x); + if (pkt_time > 0) { + double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x); + } + } + } + if (circ) { + double da = estimate_delta_circ(priv); + + if (pkt_time > 0) { + double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + if (vert) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + else if (horiz) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + } + } + } + } + priv->scroll.packets_this_scroll = 0; +} + +static void +stop_coasting(SynapticsPrivate * priv) +{ + priv->scroll.coast_speed_x = 0; + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; +} + +static int +HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw, + enum EdgeType edge, Bool finger) +{ + SynapticsParameters *para = &priv->synpara; + int delay = 1000000000; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF || + priv->synpara.touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) { + 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 == FS_UNTOUCHED) { + stop_coasting(priv); + priv->scroll.delta_y = 0; + priv->scroll.delta_x = 0; + 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.last_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)) { + stop_coasting(priv); + priv->vert_scroll_twofinger_on = TRUE; + priv->vert_scroll_edge_on = FALSE; + priv->scroll.last_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)) { + stop_coasting(priv); + priv->horiz_scroll_twofinger_on = TRUE; + priv->horiz_scroll_edge_on = FALSE; + priv->scroll.last_x = hw->x; + DBG(7, "horiz two-finger scroll detected\n"); + } + } + } + if (finger && priv->finger_state == FS_UNTOUCHED) { + 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.last_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.last_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->scroll.coast_speed_x || priv->scroll.coast_speed_y)) { + 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->scroll.coast_speed_y == 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.last_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->scroll.coast_speed_x == 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.last_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.packets_this_scroll++; + } + + if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) { + /* + = down, - = up */ + if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) { + priv->scroll.delta_y += (hw->y - priv->scroll.last_y); + priv->scroll.last_y = hw->y; + } + } + if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) { + /* + = right, - = left */ + if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) { + priv->scroll.delta_x += (hw->x - priv->scroll.last_x); + priv->scroll.last_x = hw->x; + } + } + if (priv->circ_scroll_on) { + /* + = counter clockwise, - = clockwise */ + double delta = para->scroll_dist_circ; + double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y)); + + if (delta >= 0.005 && diff != 0.0) { + if (priv->circ_scroll_vert) + priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert; + else + priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz; + priv->scroll.last_a = angle(priv, hw->x, hw->y); + } + } + + if (priv->scroll.coast_speed_y) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddy = para->coasting_friction * dtime; + + priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_y) < ddy) { + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_y += + (priv->scroll.coast_speed_y < 0 ? ddy : -ddy); + } + } + + if (priv->scroll.coast_speed_x) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddx = para->coasting_friction * dtime; + priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_x) < ddx) { + priv->scroll.coast_speed_x = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_x += + (priv->scroll.coast_speed_x < 0 ? ddx : -ddx); + } + } + + return delay; +} + +/** + * Check if any 2+ fingers are close enough together to assume this is a + * ClickFinger action. + */ +static int +clickpad_guess_clickfingers(SynapticsPrivate * priv, + struct SynapticsHwState *hw) +{ + int nfingers = 0; + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; + + if (hw->slot_state[i] == SLOTSTATE_EMPTY || + hw->slot_state[i] == SLOTSTATE_CLOSE) + continue; + + f1 = hw->mt_mask[i]; + + for (j = i + 1; j < hw->num_mt_mask; j++) { + ValuatorMask *f2; + double x1, x2, y1, y2; + + if (hw->slot_state[j] == SLOTSTATE_EMPTY || + hw->slot_state[j] == SLOTSTATE_CLOSE) + continue; + + f2 = hw->mt_mask[j]; + + x1 = valuator_mask_get_double(f1, 0); + y1 = valuator_mask_get_double(f1, 1); + + x2 = valuator_mask_get_double(f2, 0); + y2 = valuator_mask_get_double(f2, 1); + + /* FIXME: fingers closer together than 30% of touchpad width, but + * really, this should be dependent on the touchpad size. Also, + * you'll need to find a touchpad that doesn't lie about it's + * size. Good luck. */ + if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 && + abs(y1 - y2) < (priv->maxy - priv->miny) * .3) { + close_point |= (1 << j); + close_point |= (1 << i); + } + } + } + + while (close_point > 0) { + nfingers += close_point & 0x1; + close_point >>= 1; + } + + /* Some trackpads touchpad only track two touchpoints but announce + * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens + * the user likely intents to do a 3 finger click, so handle it as such. + */ + if (hw->numFingers >= 3 && hw->num_mt_mask < 3) + nfingers = 3; + + return nfingers; +} + +static void +handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw) +{ + SynapticsParameters *para = &priv->synpara; + int action = 0; + int nfingers = hw->numFingers; + + /* if this is a clickpad, clickfinger handling is: + * one finger down: no action, this is a normal click + * two fingers down: F2_CLICK + * three fingers down: F3_CLICK + */ + + if (para->clickpad) + nfingers = clickpad_guess_clickfingers(priv, hw); + + switch (nfingers) { + 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 | BTN_EMULATED_FLAG; + break; + case 2: + hw->left = 0; + hw->middle = 1 | BTN_EMULATED_FLAG; + break; + case 3: + hw->left = 0; + hw->right = 1 | BTN_EMULATED_FLAG; + break; + } +} + +/* 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, + CARD32 now, 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, now, delay); + + /* If this is a clickpad and the user clicks in a soft button area, press + * the soft button instead. */ + if (para->clickpad) { + /* hw->left is down, but no other buttons were already down */ + if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) { + /* If the finger down event is delayed, the x and y + * coordinates are stale so we delay processing the click */ + if (hw->z < para->finger_low) { + hw->left = 0; + goto out; + } + if (is_inside_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + priv->clickpad_click_millis = now; + } + else if (hw->left) { + hw->left = (priv->lastButtons & 1) ? 1 : 0; + hw->middle = (priv->lastButtons & 2) ? 1 : 0; + hw->right = (priv->lastButtons & 4) ? 1 : 0; + } + } + + /* Fingers emulate other buttons. ClickFinger can only be + triggered on transition, when left is pressed + */ + if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1) + handle_clickfinger(priv, hw); + +out: + /* 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) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + valuator_mask_zero(priv->scroll_events_mask); + + if (priv->scroll.delta_y != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_vert, priv->scroll.delta_y); + priv->scroll.delta_y = 0; + } + if (priv->scroll.delta_x != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_horiz, priv->scroll.delta_x); + priv->scroll.delta_x = 0; + } + if (valuator_mask_num_valuators(priv->scroll_events_mask)) + xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask); +} + +static inline int +repeat_scrollbuttons(const InputInfoPtr pInfo, + const struct SynapticsHwState *hw, + int buttons, CARD32 now, int delay) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + int repeat_delay, timeleft; + int rep_buttons = 0; + + if (para->updown_button_repeat) + rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1)); + if (para->leftright_button_repeat) + rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1)); + + /* 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 = now + repeat_delay * 2; + } + } + else { + priv->repeatButtons = 0; + priv->nextRepeat = 0; + } + + if (priv->repeatButtons) { + timeleft = TIME_DIFF(priv->nextRepeat, now); + 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)); + if (id == 4) + priv->scroll.delta_y -= para->scroll_dist_vert; + else if (id == 5) + priv->scroll.delta_y += para->scroll_dist_vert; + else if (id == 6) + priv->scroll.delta_x -= para->scroll_dist_horiz; + else if (id == 7) + priv->scroll.delta_x += para->scroll_dist_horiz; + } + + priv->nextRepeat = now + repeat_delay; + delay = MIN(delay, repeat_delay); + } + } + + return delay; +} + +/* Update the open slots and number of active touches */ +static void +UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + int i; + + for (i = 0; i < hw->num_mt_mask; i++) { + if (hw->slot_state[i] == SLOTSTATE_OPEN) { + priv->open_slots[priv->num_active_touches] = i; + priv->num_active_touches++; + BUG_WARN(priv->num_active_touches > priv->num_slots); + } + else if (hw->slot_state[i] == SLOTSTATE_CLOSE) { + Bool found = FALSE; + int j; + + for (j = 0; j < priv->num_active_touches - 1; j++) { + if (priv->open_slots[j] == i) + found = TRUE; + + if (found) + priv->open_slots[j] = priv->open_slots[j + 1]; + } + + BUG_WARN(priv->num_active_touches == 0); + if (priv->num_active_touches > 0) + priv->num_active_touches--; + } + } + + SynapticsResetTouchHwState(hw, FALSE); +} + +static void +filter_jitter(SynapticsPrivate * priv, int *x, int *y) +{ + SynapticsParameters *para = &priv->synpara; + + priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x); + priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y); + *x = priv->hyst_center_x; + *y = priv->hyst_center_y; +} + +static void +reset_hw_state(struct SynapticsHwState *hw) +{ + hw->x = 0; + hw->y = 0; + hw->z = 0; + hw->numFingers = 0; + hw->fingerWidth = 0; +} + +/* + * 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. + * + * from_timer denotes if HandleState was triggered from a timer (e.g. to + * generate fake motion events, or for the tap-to-click state machine), rather + * than from having received a motion event. + */ +static int +HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now, + Bool from_timer) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + enum FingerState finger = FS_UNTOUCHED; + int dx = 0, dy = 0, buttons, id; + enum EdgeType edge = NO_EDGE; + int change; + int double_click = FALSE; + int delay = 1000000000; + int timeleft; + Bool inside_active_area; + Bool using_cumulative_coords = FALSE; + Bool ignore_motion; + + /* We need both and x/y, the driver can't handle just one of the two + * yet. But since it's possible to hit a phys button on non-clickpads + * without ever getting motion data first, we must continue with 0/0 for + * that case. */ + if (hw->x == INT_MIN || hw->y == INT_MAX) { + if (para->clickpad) + return delay; + else if (hw->left || hw->right || hw->middle) { + hw->x = (hw->x == INT_MIN) ? 0 : hw->x; + hw->y = (hw->y == INT_MIN) ? 0 : hw->y; + } + } + + /* If a physical button is pressed on a clickpad or a two-finger scrolling + * is ongoing, use cumulative relative touch movements for motion */ + if (para->clickpad && + ((priv->lastButtons & 7) || + (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) && + priv->last_button_area != TOP_BUTTON_AREA) { + hw->x = hw->cumulative_dx; + hw->y = hw->cumulative_dy; + using_cumulative_coords = TRUE; + } + + /* 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. */ + filter_jitter(priv, &hw->x, &hw->y); + + inside_active_area = is_inside_active_area(priv, hw->x, hw->y); + + /* Ignore motion *starting* inside softbuttonareas */ + if (priv->finger_state < FS_TOUCHED) + priv->last_button_area = current_button_area(para, hw->x, hw->y); + /* If we already have a finger down, clear last_button_area if it goes + outside of the softbuttonareas */ + else if (priv->last_button_area != NO_BUTTON_AREA && + current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA) + priv->last_button_area = NO_BUTTON_AREA; + + ignore_motion = para->touchpad_off == TOUCHPAD_OFF || + (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA); + + /* these two just update hw->left, right, etc. */ + update_hw_button_state(pInfo, hw, now, &delay); + if (priv->has_scrollbuttons) + double_click = adjust_state_from_scrollbuttons(pInfo, hw); + + /* Ignore motion the first X ms after a clickpad click */ + if (priv->clickpad_click_millis) { + if(TIME_DIFF(priv->clickpad_click_millis + + para->clickpad_ignore_motion_time, now) > 0) + ignore_motion = TRUE; + else + priv->clickpad_click_millis = 0; + } + + /* 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) + reset_hw_state(hw); + + /* no edge or finger detection outside of area */ + if (inside_active_area) { + edge = edge_detection(priv, hw->x, hw->y); + if (!from_timer) + finger = SynapticsDetectFinger(priv, hw); + else + finger = priv->finger_state; + } + + /* 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, now, 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 >= FS_TOUCHED)); + 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. + */ +#ifndef NO_DRIVER_SCALING + ScaleCoordinates(priv, hw); +#endif + } + + dx = dy = 0; + + 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 && priv->tap_button_state == TBS_BUTTON_DOWN) + buttons |= 1 << (priv->tap_button - 1); + + /* Post events */ + if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion) + 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); + } + + if (priv->has_scrollbuttons) + delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay); + + /* Process scroll events only if coordinates are + * in the Synaptics Area + */ + if (inside_active_area && + (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) { + post_scroll_events(pInfo); + priv->scroll.last_millis = hw->millis; + } + + if (double_click) { + post_button_click(pInfo, 1); + post_button_click(pInfo, 1); + } + + UpdateTouchState(pInfo, hw); + + /* 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) +{ + DBG(3, "SwitchMode called\n"); + + return XI_BadMode; +} + +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; +} + +#ifndef NO_DRIVER_SCALING +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; + } +} +#endif --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/101_resolution_detect_option.patch/include/synaptics-properties.h +++ xserver-xorg-input-synaptics-1.9.1/.pc/101_resolution_detect_option.patch/include/synaptics-properties.h @@ -0,0 +1,161 @@ +/* + * Copyright © 2008 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: Peter Hutterer + */ + +#ifndef _SYNAPTICS_PROPERTIES_H_ +#define _SYNAPTICS_PROPERTIES_H_ + +/** + * Properties exported by the synaptics driver. These properties are + * recognized by the driver and will change its behavior when modified. + * For a description of what each property does, see synaptics.h. + */ + +/* 32 bit, 4 values, left, right, top, bottom */ +#define SYNAPTICS_PROP_EDGES "Synaptics Edges" + +/* 32 bit, 3 values, low, high, */ +#define SYNAPTICS_PROP_FINGER "Synaptics Finger" + +/* 32 bit */ +#define SYNAPTICS_PROP_TAP_TIME "Synaptics Tap Time" + +/* 32 bit */ +#define SYNAPTICS_PROP_TAP_MOVE "Synaptics Tap Move" + +/* 32 bit, 3 values, single touch timeout, max tapping time for double + * taps, duration of a single click */ +#define SYNAPTICS_PROP_TAP_DURATIONS "Synaptics Tap Durations" + +/* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_CLICKPAD "Synaptics ClickPad" + +/* 8 bit (BOOL), */ +#define SYNAPTICS_PROP_TAP_FAST "Synaptics Tap FastTap" + +/* 32 bit */ +#define SYNAPTICS_PROP_MIDDLE_TIMEOUT "Synaptics Middle Button Timeout" + +/* 32 bit */ +#define SYNAPTICS_PROP_TWOFINGER_PRESSURE "Synaptics Two-Finger Pressure" + +/* 32 bit */ +#define SYNAPTICS_PROP_TWOFINGER_WIDTH "Synaptics Two-Finger Width" + +/* 32 bit, 2 values, vert, horiz */ +#define SYNAPTICS_PROP_SCROLL_DISTANCE "Synaptics Scrolling Distance" + +/* 8 bit (BOOL), 3 values, vertical, horizontal, corner */ +#define SYNAPTICS_PROP_SCROLL_EDGE "Synaptics Edge Scrolling" + +/* 8 bit (BOOL), 2 values, vertical, horizontal */ +#define SYNAPTICS_PROP_SCROLL_TWOFINGER "Synaptics Two-Finger Scrolling" + +/* FLOAT, 4 values, min, max, accel, */ +#define SYNAPTICS_PROP_SPEED "Synaptics Move Speed" + +/* 8 bit (BOOL), 2 values, updown, leftright */ +#define SYNAPTICS_PROP_BUTTONSCROLLING "Synaptics Button Scrolling" + +/* 8 bit (BOOL), 2 values, updown, leftright */ +#define SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT "Synaptics Button Scrolling Repeat" + +/* 32 bit */ +#define SYNAPTICS_PROP_BUTTONSCROLLING_TIME "Synaptics Button Scrolling Time" + +/* 8 bit, valid values (0, 1, 2) */ +#define SYNAPTICS_PROP_OFF "Synaptics Off" + +/* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_GUESTMOUSE "Synaptics Guestmouse Off" + +/* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_LOCKED_DRAGS "Synaptics Locked Drags" + +/* 32 bit */ +#define SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT "Synaptics Locked Drags Timeout" + +/* 8 bit, up to MAX_TAP values (see synaptics.h), 0 disables an + * element. order: RT, RB, LT, LB, F1, F2, F3 */ +#define SYNAPTICS_PROP_TAP_ACTION "Synaptics Tap Action" + +/* 8 bit, up to MAX_CLICK values (see synaptics.h), 0 disables an + * element. order: Finger 1, 2, 3 */ +#define SYNAPTICS_PROP_CLICK_ACTION "Synaptics Click Action" + +/* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_CIRCULAR_SCROLLING "Synaptics Circular Scrolling" + +/* FLOAT */ +#define SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST "Synaptics Circular Scrolling Distance" + +/* 8 bit, valid values 0..8 (inclusive) + * order: any edge, top, top + right, right, right + bottom, bottom, bottom + + * left, left, left + top */ +#define SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER "Synaptics Circular Scrolling Trigger" + +/* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_CIRCULAR_PAD "Synaptics Circular Pad" + +/* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_PALM_DETECT "Synaptics Palm Detection" + +/* 32 bit, 2 values, width, z */ +#define SYNAPTICS_PROP_PALM_DIMENSIONS "Synaptics Palm Dimensions" + +/* FLOAT, 2 values, speed, friction */ +#define SYNAPTICS_PROP_COASTING_SPEED "Synaptics Coasting Speed" + +/* CARD32, 2 values, min, max */ +#define SYNAPTICS_PROP_PRESSURE_MOTION "Synaptics Pressure Motion" + +/* FLOAT, 2 values, min, max */ +#define SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR "Synaptics Pressure Motion Factor" + +/* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_GRAB "Synaptics Grab Event Device" + +/* 8 bit (BOOL), 1 value, tap-and-drag */ +#define SYNAPTICS_PROP_GESTURES "Synaptics Gestures" + +/* 8 bit (BOOL), 7 values (read-only), has_left, has_middle, has_right, + * has_double, has_triple, has_pressure, has_width */ +#define SYNAPTICS_PROP_CAPABILITIES "Synaptics Capabilities" + +/* 32 bit unsigned, 2 values, vertical, horizontal in units/millimeter */ +#define SYNAPTICS_PROP_RESOLUTION "Synaptics Pad Resolution" + +/* 32 bit, 4 values, left, right, top, bottom */ +#define SYNAPTICS_PROP_AREA "Synaptics Area" + +/* 32 bit, 4 values, left, right, top, buttom */ +#define SYNAPTICS_PROP_SOFTBUTTON_AREAS "Synaptics Soft Button Areas" + +/* 32 bit, 4 values, left, right, top, buttom */ +#define SYNAPTICS_PROP_SECONDARY_SOFTBUTTON_AREAS "Synaptics Secondary Soft Button Areas" + +/* 32 Bit Integer, 2 values, horizontal hysteresis, vertical hysteresis */ +#define SYNAPTICS_PROP_NOISE_CANCELLATION "Synaptics Noise Cancellation" + +#endif /* _SYNAPTICS_PROPERTIES_H_ */ --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/101_resolution_detect_option.patch/man/synaptics.man +++ xserver-xorg-input-synaptics-1.9.1/.pc/101_resolution_detect_option.patch/man/synaptics.man @@ -0,0 +1,985 @@ +.\" shorthand for double quote that works everywhere. +.ds q \N'34' +.TH SYNAPTICS __drivermansuffix__ __vendorversion__ +.SH NAME +synaptics \- touchpad input driver +.SH SYNOPSIS +.nf +.B "Section \*qInputDevice\*q" +.BI " Identifier \*q" devname \*q +.B " Driver \*qsynaptics\*q" +.BI " Option \*qDevice\*q \*q" devpath \*q +.BI " Option \*qPath\*q \*q" path \*q +\ \ ... +.B EndSection +.fi +.SH DESCRIPTION +.B synaptics +is an __xservername__ input driver for touchpads. +Even though touchpads can be handled by the normal evdev or mouse drivers, +this driver allows more advanced features of the +touchpad to become available. Some benefits would be: +.IP \(bu 4 +Movement with adjustable, non-linear acceleration and speed. +.IP \(bu 4 +Button events through short touching of the touchpad. +.IP \(bu 4 +Double-Button events through double short touching of the touchpad. +.IP \(bu 4 +Dragging through short touching and holding down the finger on the +touchpad (tap-and-drag gesture). +.IP \(bu 4 +Middle and right button events on the upper and lower corner of the +touchpad. +.IP \(bu 4 +Vertical scrolling (button four and five events) through moving the +finger on the right side of the touchpad. +.IP \(bu 4 +The up/down button sends button four/five events. +.IP \(bu 4 +Horizontal scrolling (button six and seven events) through moving the +finger on the lower side of the touchpad. +.IP \(bu 4 +The multi-buttons send button four/five events for vertical scrolling +and button six/seven events for horizontal scrolling. +.IP \(bu 4 +Adjustable finger detection. +.IP \(bu 4 +Multifinger taps: two finger for right button and three finger for +middle button events. +. +(Needs hardware support. +. +Not all models implement this feature.) +.IP \(bu 4 +Pressure-dependent motion speed. +.LP +Note that depending on the touchpad firmware, some of these features +might be available even without using the synaptics driver. Note also +that some functions are not available on all touchpad models, because +they need support from the touchpad hardware/firmware. (Multifinger +taps for example.) +.PP +The name "synaptics" is historical and the driver still provides the +synaptics protocol parsing code. Under Linux however, the hardware-specifics +are handled by the kernel and this driver will work for any touchpad that +has a working kernel driver. If your device is recognized as \*qPS/2 +Mouse\*q or similar, the kernel driver does not support your device and this +driver will only provide limited functionality. + +.SH CONFIGURATION OPTIONS +Please refer to __xconfigfile__(__filemansuffix__) for general configuration +details and for options that can be used with all input drivers. This +section only covers configuration details specific to this driver. +.PP +The following driver +.B Options +are supported: +.TP 7 +.BI "Option \*qDevice\*q \*q" string \*q +This option specifies the device file in your \*q/dev\*q directory which will +be used to access the physical device. Normally you should use something like +\*q/dev/input/eventX\*q, where X is some integer. +.TP 7 +.BI "Option \*qProtocol\*q \*q" string \*q +Specifies which kernel driver will be used by this driver. This is the list of +supported drivers and their default use scenarios. +.TS +l l. +auto-dev automatic, default (recommend) +event Linux 2.6 kernel events +psaux raw device access (Linux 2.4) +psm FreeBSD psm driver +.TE +.TP 7 +.BI "Option \*qLeftEdge\*q \*q" integer \*q +X coordinate for left edge. Property: "Synaptics Edges" +.TP 7 +.BI "Option \*qRightEdge\*q \*q" integer \*q +X coordinate for right edge. Property: "Synaptics Edges" +.TP 7 +.BI "Option \*qTopEdge\*q \*q" integer \*q +Y coordinate for top edge. Property: "Synaptics Edges" +.TP 7 +.BI "Option \*qBottomEdge\*q \*q" integer \*q +Y coordinate for bottom edge. Property: "Synaptics Edges" +.TP 7 +.BI "Option \*qFingerLow\*q \*q" integer \*q +When finger pressure drops below this value, the driver counts it as a +release. Property: "Synaptics Finger" +.TP 7 +.BI "Option \*qFingerHigh\*q \*q" integer \*q +When finger pressure goes above this value, the driver counts it as a +touch. Property: "Synaptics Finger" +.TP 7 +.BI "Option \*qMaxTapTime\*q \*q" integer \*q +Maximum time (in milliseconds) for detecting a tap. Property: "Synaptics Tap +Durations" +.TP 7 +.BI "Option \*qMaxTapMove\*q \*q" integer \*q +Maximum movement of the finger for detecting a tap. Property: "Synaptics Tap +Move" +.TP 7 +.BI "Option \*qMaxDoubleTapTime\*q \*q" integer \*q +Maximum time (in milliseconds) for detecting a double tap. Property: +"Synaptics Tap Durations" +.TP 7 +.BI "Option \*qClickTime\*q \*q" integer \*q +The duration of the mouse click generated by tapping. Property: "Synaptics Tap +Durations" +.TP 7 +.BI "Option \*qClickPad\*q \*q" boolean \*q +Whether the device is a click pad. See +.B ClickPad support +for more details. Property: "Synaptics ClickPad" +.TP 7 +.BI "Option \*qVertEdgeScroll\*q \*q" boolean \*q +Enable vertical scrolling when dragging along the right edge. Property: +"Synaptics Edge Scrolling" +.TP 7 +.BI "Option \*qHorizEdgeScroll\*q \*q" boolean \*q +Enable horizontal scrolling when dragging along the bottom edge. Property: +"Synaptics Edge Scrolling" +.TP 7 +.BI "Option \*qCornerCoasting\*q \*q" boolean \*q +Enable edge scrolling to continue while the finger stays in an edge corner. +Property: "Synaptics Edge Scrolling" +.TP 7 +.BI "Option \*qVertTwoFingerScroll\*q \*q" boolean \*q +Enable vertical scrolling when dragging with two fingers anywhere on +the touchpad. Property: "Synaptics Two-Finger Scrolling" +.TP 7 +.BI "Option \*qHorizTwoFingerScroll\*q \*q" boolean \*q +Enable horizontal scrolling when dragging with two fingers anywhere on +the touchpad. Property: "Synaptics Two-Finger Scrolling" +.TP 7 +.BI "Option \*qVertScrollDelta\*q \*q" integer \*q +Move distance of the finger for a scroll event. Property: "Synaptics Scrolling +Distance" +.TP 7 +.BI "Option \*qHorizScrollDelta\*q \*q" integer \*q +Move distance of the finger for a scroll event. Property: "Synaptics Scrolling +Distance" +.TP +.BI "Option \*qMinSpeed\*q \*q" float \*q +Minimum speed factor. Property: "Synaptics Move Speed" +.TP +.BI "Option \*qMaxSpeed\*q \*q" float \*q +Maximum speed factor. Property: "Synaptics Move Speed" +.TP +.BI "Option \*qAccelFactor\*q \*q" float \*q +Acceleration factor for normal pointer movements. Property: "Synaptics Move +Speed" +.TP +.BI "Option \*qPressureMotionMinZ\*q \*q" integer \*q +Finger pressure at which minimum pressure motion factor is applied. Property: +"Synaptics Pressure Motion" +.TP +.BI "Option \*qPressureMotionMaxZ\*q \*q" integer \*q +Finger pressure at which maximum pressure motion factor is applied. Property: +"Synaptics Pressure Motion" +.TP +.BI "Option \*qPressureMotionMinFactor\*q \*q" integer \*q +Lowest setting for pressure motion factor. Property: "Synaptics Pressure +Motion Factor" +.TP +.BI "Option \*qPressureMotionMaxFactor\*q \*q" integer \*q +Greatest setting for pressure motion factor. Property: "Synaptics Pressure +Motion Factor" +.TP +.BI "Option \*qHorizHysteresis\*q \*q" integer \*q +The minimum horizontal HW distance required to generate motion events. Can be +specified as a percentage. Increase if noise motion is a problem for you. Zero +is disabled. +Default: 0.5 percent of the diagonal or (in case of evdev) the appropriate +"fuzz" as advertised by the device. +.TP +.BI "Option \*qVertHysteresis\*q \*q" integer \*q +The minimum vertical HW distance required to generate motion events. See +\fBHorizHysteresis\fR. +.TP +.BI "Option \*qUpDownScrolling\*q \*q" boolean \*q +If on, the up/down buttons generate button 4/5 events. +. +If off, the up button generates a double click and the down button +generates a button 2 event. This option is only available for touchpads with +physical scroll buttons. +Property: "Synaptics Button Scrolling" +.TP +.BI "Option \*qLeftRightScrolling\*q \*q" boolean \*q +If on, the left/right buttons generate button 6/7 events. +. +If off, the left/right buttons both generate button 2 events. +This option is only available for touchpads with physical scroll buttons. +Property: "Synaptics Button Scrolling" +.TP +.BI "Option \*qUpDownScrollRepeat\*q \*q" boolean \*q +If on, and the up/down buttons are used for scrolling +(\fBUpDownScrolling\fR), these buttons will send auto-repeating 4/5 events, +with the delay between repeats determined by \fBScrollButtonRepeat\fR. +This option is only available for touchpads with physical scroll buttons. +Property: "Synaptics Button Scrolling Repeat" +.TP +.BI "Option \*qLeftRightScrollRepeat\*q \*q" boolean \*q +If on, and the left/right buttons are used for scrolling +(\fBLeftRightScrolling\fR), these buttons will send auto-repeating 6/7 events, +with the delay between repeats determined by \fBScrollButtonRepeat\fR. +This option is only available for touchpads with physical scroll buttons. +Property: "Synaptics Button Scrolling Repeat" +.TP +.BI "Option \*qScrollButtonRepeat\*q \*q" integer \*q +The number of milliseconds between repeats of button events 4-7 from the +up/down/left/right scroll buttons. +This option is only available for touchpads with physical scroll buttons. +Property: "Synaptics Button Scrolling Time" +.TP +.BI "Option \*qEmulateMidButtonTime\*q \*q" integer \*q +Maximum time (in milliseconds) for middle button emulation. Property: +"Synaptics Middle Button Timeout" +.TP +.BI "Option \*qEmulateTwoFingerMinZ\*q \*q" integer \*q +For touchpads not capable of detecting multiple fingers but are capable +of detecting finger pressure and width, this sets the +Z pressure threshold. When both Z pressure and W width thresholds +are crossed, a two finger press will be emulated. This defaults +to a value that disables emulation on touchpads with real two-finger detection +and defaults to a value that enables emulation on remaining touchpads that +support pressure and width support. +Property: "Synaptics Two-Finger Pressure" +.TP +.BI "Option \*qEmulateTwoFingerMinW\*q \*q" integer \*q +For touchpads not capable of detecting multiple fingers but are +capable of detecting finger width and pressure, this sets the +W width threshold. When both W width and Z pressure thresholds +are crossed, a two finger press will be emulated. This feature works best +with (\fBPalmDetect\fR) off. Property: "Synaptics Two-Finger Width" +.TP +.BI "Option \*qTouchpadOff\*q \*q" integer \*q +Switch off the touchpad. +. +Valid values are: +.TS +l l. +0 Touchpad is enabled +1 Touchpad is switched off (physical clicks still work) +2 Only tapping and scrolling is switched off +.TE +When the touchpad is switched off, button events caused by a physical +button press are still interpreted. On a ClickPad, this includes +software-emulated middle and right buttons as defined by +the SoftButtonAreas setting. +.TP +Property: "Synaptics Off" +.TP +.BI "Option \*qLockedDrags\*q \*q" boolean \*q +If off, a tap-and-drag gesture ends when you release the finger. +. +If on, the gesture is active until you tap a second time, or until +LockedDragTimeout expires. Property: "Synaptics Locked Drags" +.TP +.BI "Option \*qLockedDragTimeout\*q \*q" integer \*q +This parameter specifies how long it takes (in milliseconds) for the +LockedDrags mode to be automatically turned off after the finger is +released from the touchpad. Property: "Synaptics Locked Drags Timeout" +.TP +.BI "Option \*qRTCornerButton\*q \*q" integer \*q +. +Which mouse button is reported on a right top corner tap. +. +Set to 0 to disable. Property: "Synaptics Tap Action" +.TP +.BI "Option \*qRBCornerButton\*q \*q" integer \*q +Which mouse button is reported on a right bottom corner tap. +. +Set to 0 to disable. Property: "Synaptics Tap Action" +.TP +.BI "Option \*qLTCornerButton\*q \*q" integer \*q +Which mouse button is reported on a left top corner tap. +. +Set to 0 to disable. Property: "Synaptics Tap Action" +.TP +.BI "Option \*qLBCornerButton\*q \*q" integer \*q +Which mouse button is reported on a left bottom corner tap. +. +Set to 0 to disable. Property: "Synaptics Tap Action" +.TP +.BI "Option \*qTapButton1\*q \*q" integer \*q +Which mouse button is reported on a non-corner one-finger tap. +. +Set to 0 to disable. Property: "Synaptics Tap Action" +.TP +.BI "Option \*qTapButton2\*q \*q" integer \*q +Which mouse button is reported on a non-corner two-finger tap. +. +Set to 0 to disable. Property: "Synaptics Tap Action" +.TP +.BI "Option \*qTapButton3\*q \*q" integer \*q +Which mouse button is reported on a non-corner three-finger tap. +. +Set to 0 to disable. Property: "Synaptics Tap Action" +.TP +.BI "Option \*qClickFinger1\*q \*q" integer \*q +Which mouse button is reported when left-clicking with one finger. +. +Set to 0 to disable. Property: "Synaptics Click Action" +.TP +.BI "Option \*qClickFinger2\*q \*q" integer \*q +Which mouse button is reported when left-clicking with two fingers. +. +Set to 0 to disable. Property: "Synaptics Click Action" +.TP +.BI "Option \*qClickFinger3\*q \*q" integer \*q +Which mouse button is reported when left-clicking with three fingers. +. +Set to 0 to disable. Property: "Synaptics Click Action" +.TP +.BI "Option \*qCircularScrolling\*q \*q" boolean \*q +If on, circular scrolling is used. Property: "Synaptics Circular Scrolling" +.TP +.BI "Option \*qCircScrollDelta\*q \*q" float \*q +Move angle (radians) of finger to generate a scroll event. Property: "Synaptics +Circular Scrolling Distance" +.TP +.BI "Option \*qCircScrollTrigger\*q \*q" integer \*q +Trigger region on the touchpad to start circular scrolling +.TS +l l. +0 All Edges +1 Top Edge +2 Top Right Corner +3 Right Edge +4 Bottom Right Corner +5 Bottom Edge +6 Bottom Left Corner +7 Left Edge +8 Top Left Corner +.TE +Property: "Synaptics Circular Scrolling Trigger" +.TP +.BI "Option \*qCircularPad\*q \*q" boolean \*q +. +Instead of being a rectangle, the edge is the ellipse enclosed by the +Left/Right/Top/BottomEdge parameters. +. +For circular touchpads. Property: "Synaptics Circular Pad" +.TP +.BI "Option \*qPalmDetect\*q \*q" boolean \*q +If palm detection should be enabled. +. +Note that this also requires hardware/firmware support from the +touchpad. Property: "Synaptics Palm Detection" +.TP +.BI "Option \*qPalmMinWidth\*q \*q" integer \*q +Minimum finger width at which touch is considered a palm. Property: "Synaptics +Palm Dimensions" +.TP +.BI "Option \*qPalmMinZ\*q \*q" integer \*q +Minimum finger pressure at which touch is considered a palm. Property: +"Synaptics Palm Dimensions" +.TP +.BI "Option \*qCoastingSpeed\*q \*q" float \*q +Your finger needs to produce this many scrolls per second in order to start +coasting. The default is 20 which should prevent you from starting coasting +unintentionally. +. +0 disables coasting. Property: "Synaptics Coasting Speed" +.TP +.BI "Option \*qCoastingFriction\*q \*q" float \*q +Number of scrolls/second² to decrease the coasting speed. Default +is 50. +Property: "Synaptics Coasting Speed" +.TP +.BI "Option \*qSingleTapTimeout\*q \*q" integer \*q +Timeout after a tap to recognize it as a single tap. Property: "Synaptics Tap +Durations" +.TP +.BI "Option \*qGrabEventDevice\*q \*q" boolean \*q +If GrabEventDevice is true, the driver will grab the event device for +exclusive use when using the linux 2.6 event protocol. +. +When using other protocols, this option has no effect. +. +Grabbing the event device means that no other user space or kernel +space program sees the touchpad events. +. +This is desirable if the X config file includes /dev/input/mice as an +input device, but is undesirable if you want to monitor the device +from user space. +. +When changing this parameter with the synclient program, the change +will not take effect until the synaptics driver is disabled and +reenabled. +. +This can be achieved by switching to a text console and then switching +back to X. +. +. +.TP +.BI "Option \*qTapAndDragGesture\*q \*q" boolean \*q +Switch on/off the tap-and-drag gesture. +. +This gesture is an alternative way of dragging. +. +It is performed by tapping (touching and releasing the finger), then +touching again and moving the finger on the touchpad. +. +The gesture is enabled by default and can be disabled by setting the +TapAndDragGesture option to false. Property: "Synaptics Gestures" +. +.TP +.BI "Option \*qVertResolution\*q \*q" integer \*q +Resolution of X coordinates in units/millimeter. The value is used +together with HorizResolution to compensate unequal vertical and +horizontal sensitivity. Setting VertResolution and HorizResolution +equal values means no compensation. Default value is read from +the touchpad or set to 1 if value could not be read. +Property: "Synaptics Pad Resolution" +. +.TP +.BI "Option \*qHorizResolution\*q \*q" integer \*q +Resolution of Y coordinates in units/millimeter. The value is used +together with VertResolution to compensate unequal vertical and +horizontal sensitivity. Setting VertResolution and HorizResolution +equal values means no compensation. Default value is read from +the touchpad or set to 1 if value could not be read. +Property: "Synaptics Pad Resolution" +. +.TP +.BI "Option \*qAreaLeftEdge\*q \*q" integer \*q +Ignore movements, scrolling and tapping which start left of this edge. +. +The option is disabled by default and can be enabled by setting the +AreaLeftEdge option to any integer value other than zero. If supported by the +server (version 1.9 and later), the edge may be specified in percent of +the total width of the touchpad. Property: "Synaptics Area" +. +.TP +.BI "Option \*qAreaRightEdge\*q \*q" integer \*q +Ignore movements, scrolling and tapping which start right of this edge. +. +The option is disabled by default and can be enabled by setting the +AreaRightEdge option to any integer value other than zero. If supported by the +server (version 1.9 and later), the edge may be specified in percent of +the total width of the touchpad. Property: "Synaptics Area" +. +.TP +.BI "Option \*qAreaTopEdge\*q \*q" integer \*q +Ignore movements, scrolling and tapping which start above this edge. +. +The option is disabled by default and can be enabled by setting the +AreaTopEdge option to any integer value other than zero. If supported by the +server (version 1.9 and later), the edge may be specified in percent of +the total height of the touchpad. Property: "Synaptics Area" +. +.TP +.BI "Option \*qAreaBottomEdge\*q \*q" integer \*q +Ignore movements, scrolling and tapping which start below this edge. +. +The option is disabled by default and can be enabled by setting the +AreaBottomEdge option to any integer value other than zero. If supported by the +server (version 1.9 and later), the edge may be specified in percent of +the total height of the touchpad. Property: "Synaptics Area" +. +.TP +.BI "Option \*qSoftButtonAreas\*q \*q" "RBL RBR RBT RBB MBL MBR MBT MBB" \*q +This option is only available on ClickPad devices. +Enable soft button click area support on ClickPad devices. +The first four parameters are the left, right, top, bottom edge of the right +button, respectively, the second four parameters are the left, right, top, +bottom edge of the middle button, respectively. Any of the values may be +given as percentage of the touchpad width or height, whichever applies. +If any edge is set to 0 (not 0%), the button is assumed to extend to +infinity in the given direction. Setting all values to 0 (not 0%) disables +soft button areas. Button areas may not overlap, however it is permitted for two +buttons to share an edge value. +Property: "Synaptics Soft Button Areas" +. +.TP +.BI "Option \*qHasSecondarySoftButtons\*q \*q" boolean \*q +This option is only available on ClickPad devices. +Enable the secondary software button area support. The exact area must be +set in option \*qSecondarySoftButtonAreas\*q. See +.B ClickPad support +for more details. +. +.TP +.BI "Option \*qSecondarySoftButtonAreas\*q \*q" "RBL RBR RBT RBB MBL MBR MBT MBB" \*q +This option is only available on ClickPad devices and only if +.B Option \*qHasSecondarySoftButtons\*q +is enabled. +Define the secondary soft button click areas on ClickPad devices (usually on +top of the device). +For the allowed values for this option, see +.B Option \*qSoftButtonAreas\*q. +Primary and secondary soft button areas must not overlap each other. If they do, +the behavior of the driver is undefined. +Property: "Synaptics Secondary Soft Button Areas". This property is only +initialized if +.B Option \*qHasSecondarySoftButtons\*q +is enabled and this option is set in the __xconfigfile__(__filemansuffix__). +. + +.SH CONFIGURATION DETAILS +.SS Area handling +The LeftEdge, RightEdge, TopEdge and BottomEdge parameters are used to +define the edge and corner areas of the touchpad. +. +The parameters split the touchpad area in 9 pieces, like this: +.LP +.TS +l|l|lsls +--- +|c|cw(5P)|c|l +--- +|c|c|c|l +|c|c|c|l +|c|c|c|l +--- +|c|c|c|l +--- +|lsl|ll. + LeftEdge RightEdge + Physical top edge +1 2 3 + TopEdge + +4 5 6 + + BottomEdge +7 8 9 + Physical bottom edge +Physical left edge Physical right edge +.TE +.LP +Coordinates to the left of LeftEdge are part of the left edge (areas +1, 4 and 7), coordinates to the left of LeftEdge and above TopEdge +(area 1) are part of the upper left corner, etc. +.PP +A good way to find appropriate edge parameters is to use evtest(1) on the +device to see the x/y coordinates corresponding to different positions on +the touchpad. +.PP +The perceived physical edges may be adjusted with the AreaLeftEdge, +AreaRightEdge, AreaTopEdge, and AreaBottomEdge options. If these values are +set to something other than the physical edges, input that starts in the +space between the area edge and the respective physical edge is ignored. +Note that this reduces the available space on the touchpad to start motions +in. +.SS Tapping +A tap event happens when the finger is touched and released in a time +interval shorter than MaxTapTime, and the touch and release +coordinates are less than MaxTapMove units apart. +. +A "touch" event happens when the Z value goes above FingerHigh, and an +"untouch" event happens when the Z value goes below FingerLow. +. +.LP +The MaxDoubleTapTime parameter has the same function as the MaxTapTime +parameter, but for the second, third, etc tap in a tap sequence. +. +If you can't perform double clicks fast enough (for example, xmms +depends on fast double clicks), try reducing this parameter. +. +If you can't get word selection to work in xterm (ie button down, +button up, button down, move mouse), try increasing this parameter. +. +.LP +The ClickTime parameter controls the delay between the button down and +button up X events generated in response to a tap event. +. +A too long value can cause undesirable autorepeat in scroll bars and a +too small value means that visual feedback from the gui application +you are interacting with is harder to see. +. +.SS Acceleration +The MinSpeed, MaxSpeed and AccelFactor parameters control the pointer +motion speed. +. +The speed value defines the scaling between touchpad coordinates and +screen coordinates. +. +When moving the finger very slowly, the MinSpeed value is used, when +moving very fast the MaxSpeed value is used. +. +When moving the finger at moderate speed, you get a pointer motion +speed somewhere between MinSpeed and MaxSpeed. +. +If you don't want any acceleration, set MinSpeed and MaxSpeed to the +same value. +. +.LP +The MinSpeed, MaxSpeed and AccelFactor parameters don't have any +effect on scrolling speed. +. +Scrolling speed is determined solely from the VertScrollDelta and +HorizScrollDelta parameters. +. +To invert the direction of vertical or horizontal scrolling, set +VertScrollDelta or HorizScrollDelta to a negative value. +. +.LP +Acceleration is mostly handled outside the driver, thus the driver will +translate MinSpeed into constant deceleration and adapt MaxSpeed at +startup time. This ensures you can user the other acceleration profiles, albeit +without pressure motion. However the numbers at runtime will likely be different +from any options you may have set. + +.SS Pressure motion +When pressure motion is activated, the cursor motion speed depends +on the pressure exerted on the touchpad (the more pressure exerted on +the touchpad, the faster the pointer). +. +More precisely the speed is first calculated according to MinSpeed, +MaxSpeed and AccelFactor, and then is multiplied by a sensitivity +factor. +. +.LP +The sensitivity factor can be adjusted using the PressureMotion +parameters. +. +If the pressure is below PressureMotionMinZ, PressureMotionMinFactor +is used, and if the pressure is greater than PressureMotionMaxZ, +PressureMotionMaxFactor is used. +. +For a pressure value between PressureMotionMinZ and +PressureMotionMaxZ, the factor is increased linearly. +. +.SS Middle button emulation +Since most synaptics touchpad models don't have a button that +corresponds to the middle button on a mouse, the driver can emulate +middle mouse button events. +. +If you press both the left and right mouse buttons at almost the same +time (no more than EmulateMidButtonTime milliseconds apart) the driver +generates a middle mouse button event. +. +.SS Circular scrolling +Circular scrolling acts like a scrolling wheel on the touchpad. +. +Scrolling is engaged when a drag starts in the given CircScrollTrigger +region, which can be all edges, a particular side, or a particular +corner. +. +Once scrolling is engaged, moving your finger in clockwise circles +around the center of the touchpad will generate scroll down events and +counter clockwise motion will generate scroll up events. +. +Lifting your finger will disengage circular scrolling. +. +Use tight circles near the center of the pad for fast scrolling and +large circles for better control. +. +When used together with vertical scrolling, hitting the upper or lower +right corner will seamlessly switch over from vertical to circular +scrolling. + +.SS Coasting +Coasting is enabled by setting the CoastingSpeed parameter to a +non-zero value. +. +Coasting comes in two flavors: conventional (finger off) coasting, and +corner (finger on) coasting. +.LP +Conventional coasting is enabled when coasting is enabled, +and CornerCoasting is set to false. +. +When conventional coasting is enabled, horizontal/vertical scrolling +can continue after the finger is released from the lower/right edge of +the touchpad. +. +The driver computes the scrolling speed corresponding to the finger +speed immediately before the finger leaves the touchpad. +. +If this scrolling speed is larger than the CoastingSpeed parameter +(measured in scroll events per second), the scrolling will continue +with the same speed in the same direction until the finger touches the +touchpad again. +. +.LP +Corner coasting is enabled when coasting is enabled, and +CornerCoasting is set to true. +. +When corner coasting is enabled, edge scrolling can continue as long +as the finger stays in a corner. +. +Coasting begins when the finger enters the corner, and continues until +the finger leaves the corner. +. +CornerCoasting takes precedence over the seamless switch from edge +scrolling to circular scrolling. That is, if CornerCoasting is +active, scrolling will stop, and circular scrolling will not start, +when the finger leaves the corner. + +.SS Noise cancellation +The synaptics has a built-in noise cancellation based on hysteresis. This means +that incoming coordinates actually shift a box of predefined dimensions such +that it covers the incoming coordinate, and only the boxes own center is used +as input. Obviously, the smaller the box the better, but the likelyhood of +noise motion coming through also increases. + +.SS ClickPad support +A click pad device has button(s) integrated into the touchpad surface. The +user must press downward on the touchpad in order to generated a button +press. ClickPad support is enabled if the option +.B ClickPad +is set or the property is set at runtime. On some platforms, this option +will be set automatically if the kernel detects a matching device. On Linux, +the device must have the INPUT_PROP_BUTTONPAD property set. +.LP +ClickPads do not support middle mouse button emulation. If enabling ClickPad +support at runime, the user must also set the middle mouse button timeout to +0. If auto-detected, middle mouse button emulation is disabled by the +driver. +.LP +ClickPads provide software emulated buttons through +.B Option \*qSoftButtonAreas\*q. +These buttons enable areas on the touchpad to perform as right or middle +mouse button. When the user performs a click within a defined soft button +area, a right or middle click is performed. +.LP +Some laptops, most notably the Lenovo T440, T540 and x240 series, provide a +pointing stick without physical buttons. On those laptops, the top of the +touchpad acts as software-emulated button area. This area can be enabled +with +.B Option \*qHasSecondarySoftButtons\*q +and configured +with +.B Option \*qSecondarySoftButtonAreas\*q. +On some platforms, this option +will be set automatically if the kernel detects a matching device. On Linux, +the device must have the INPUT_PROP_TOPBUTTONPAD property set. + +.SH "DEVICE PROPERTIES" +Synaptics 1.0 and higher support input device properties if the driver is +running on X server 1.6 or higher. The synclient tool +shipped with synaptics version 1.1 uses input device properties by default. +. +Properties supported: +.TP 7 +.BI "Synaptics Edges" +32 bit, 4 values, left, right, top, bottom. + +.TP 7 +.BI "Synaptics Finger" +32 bit, 3 values, low, high, press. + +.TP 7 +.BI "Synaptics Tap Time" +32 bit. + +.TP 7 +.BI "Synaptics Tap Move" +32 bit. + +.TP 7 +.BI "Synaptics Tap Durations" +32 bit, 3 values, single touch timeout, max tapping time for double taps, +duration of a single click. + +.TP 7 +.BI "Synaptics ClickPad" +8 bit (Bool). + +.TP 7 +.BI "Synaptics Middle Button Timeout" +32 bit. + +.TP 7 +.BI "Synaptics Two-Finger Pressure" +32 bit. + +.TP 7 +.BI "Synaptics Two-Finger Width" +32 bit. + +.TP 7 +.BI "Synaptics Scrolling Distance" +32 bit, 2 values, vert, horiz. + +.TP 7 +.BI "Synaptics Edge Scrolling" +8 bit (BOOL), 3 values, vertical, horizontal, corner. + +.TP 7 +.BI "Synaptics Two-Finger Scrolling" +8 bit (BOOL), 2 values, vertical, horizontal. + +.TP 7 +.BI "Synaptics Move Speed" +FLOAT, 4 values, min, max, accel, + +.TP 7 +.BI "Synaptics Button Scrolling" +8 bit (BOOL), 2 values, updown, leftright. + +.TP 7 +.BI "Synaptics Button Scrolling Repeat" +8 bit (BOOL), 2 values, updown, leftright. + +.TP 7 +.BI "Synaptics Button Scrolling Time" +32 bit. + +.TP 7 +.BI "Synaptics Off" +8 bit, valid values (0, 1, 2). + +.TP 7 +.BI "Synaptics Locked Drags" +8 bit (BOOL). + +.TP 7 +.BI "Synaptics Locked Drags Timeout" +32 bit. + +.TP 7 +.BI "Synaptics Tap Action" +8 bit, up to MAX_TAP values (see synaptics.h), 0 disables an element. order: +RT, RB, LT, LB, F1, F2, F3. + +.TP 7 +.BI "Synaptics Click Action" +8 bit, up to MAX_CLICK values (see synaptics.h), 0 disables an element. +order: Finger 1, 2, 3. + +.TP 7 +.BI "Synaptics Circular Scrolling" +8 bit (BOOL). + +.TP 7 +.BI "Synaptics Circular Scrolling Distance" +FLOAT. + +.TP 7 +.BI "Synaptics Circular Scrolling Trigger" +8 bit, valid values 0..8 (inclusive) order: any edge, top, top + right, +right, right + bottom, bottom, bottom + left, left, left + top. + +.TP 7 +.BI "Synaptics Circular Pad" +8 bit (BOOL). + +.TP 7 +.BI "Synaptics Palm Detection" +8 bit (BOOL). + +.TP 7 +.BI "Synaptics Palm Dimensions" +32 bit, 2 values, width, z. + +.TP 7 +.BI "Synaptics Coasting Speed" +FLOAT, 2 values, speed, friction. + +.TP 7 +.BI "Synaptics Pressure Motion" +32 bit, 2 values, min, max. + +.TP 7 +.BI "Synaptics Pressure Motion Factor" +FLOAT, 2 values, min, max. + +.TP 7 +.BI "Synaptics Grab Event Device" +8 bit (BOOL). + +.TP 7 +.BI "Synaptics Gestures" +8 bit (BOOL), 1 value, tap-and-drag. + +.TP 7 +.BI "Synaptics Area" +The AreaLeftEdge, AreaRightEdge, AreaTopEdge and AreaBottomEdge parameters are used to +define the edges of the active area of the touchpad. All movements, scrolling and tapping +which take place outside of this area will be ignored. This property is disabled by +default. + +32 bit, 4 values, left, right, top, bottom. 0 disables an element. + +.TP 7 +.BI "Synaptics Soft Button Areas" +This property is only available on ClickPad devices. +The Right and middle soft button areas are used to support right and middle +click actions on a ClickPad device. Providing 0 for all values of a given button +disables the button area. + +32 bit, 8 values, RBL, RBR, RBT, RBB, MBL, MBR, MBT, MBB. + +.TP 7 +.BI "Synaptics Capabilities" +This read-only property expresses the physical capability of the touchpad, +most notably whether the touchpad hardware supports multi-finger tapping and +scrolling. + +8 bit (BOOL), 7 values (read-only), has left button, has middle button, has +right button, two-finger detection, three-finger detection, pressure detection, and finger/palm width detection. + +.TP 7 +.BI "Synaptics Pad Resolution" +32 bit unsigned, 2 values (read-only), vertical, horizontal in units/millimeter. + +.SH "NOTES" +Configuration through +.I InputClass +sections is recommended in X servers 1.8 and later. See xorg.conf.d(5) for +more details. An example xorg.conf.d snippet is provided in +.I ${sourcecode}/conf/70-synaptics.conf +.LP +Configuration through hal fdi files is recommended in X servers 1.5, 1.6 and +1.7. An example hal policy file is provided in +.I ${sourcecode}/conf/11-x11-synaptics.fdi +.LP +If either of +.BI "Protocol \*q" auto-dev \*q +(default) or +.BI "Protocol \*q" event \*q +is used, the driver initializes defaults based on the capabilities reported by +the kernel driver. Acceleration, edges and resolution are based on the dimensions +reported by the kernel. If the kernel reports multi-finger detection, two-finger +vertical scrolling is enabled, horizontal two-finger scrolling is disabled and +edge scrolling is disabled. If no multi-finger capabilities are reported, +edge scrolling is enabled for both horizontal and vertical scrolling. +Tapping is disabled by default for touchpads with one or more physical buttons. +To enable it you need to map tap actions to buttons. See the "TapButton1", +"TapButton2" and "TapButton3" options. +.LP +Button mapping for physical buttons is handled in the server. +If the device is switched to left-handed (an in-server mapping of physical +buttons 1, 2, 3 to the logical buttons 3, 2, 1, respectively), both physical +and TapButtons are affected. To counteract this, the TapButtons need to be set +up in reverse order (TapButton1=3, TapButton2=1). + +.SH "REMOVED OPTIONS" +The following options are no longer part of the driver configuration: +.TP +.BI "Option \*qRepeater\*q \*q" string \*q +.TP +.BI "Option \*qHistorySize\*q \*q" integer \*q +.TP +.BI "Option \*qSpecialScrollAreaRight\*q \*q" boolean \*q +.TP +.BI "Option \*qGuestMouseOff\*q \*q" boolean \*q +.TP +.BI "Option \*qSHMConfig\*q \*q" boolean \*q +.TP +.BI "Option \*qFingerPress\*q \*q" integer \*q +.TP +.BI "Option \*qTrackstickSpeed\*q \*q" float \*q +.TP +.BI "Option \*qEdgeMotionMinZ\*q \*q" integer \*q +.TP +.BI "Option \*qEdgeMotionMaxZ\*q \*q" integer \*q +.TP +.BI "Option \*qEdgeMotionMinSpeed\*q \*q" integer \*q +.TP +.BI "Option \*qEdgeMotionMaxSpeed\*q \*q" integer \*q +.TP +.BI "Option \*qEdgeMotionUseAlways\*q \*q" boolean \*q +.TP + +.SH "AUTHORS" +.LP +Peter Osterlund and many others. +.SH "SEE ALSO" +.LP +__xservername__(__appmansuffix__), __xconfigfile__(__filemansuffix__), Xserver(__appmansuffix__), X(__miscmansuffix__), synclient(__appmansuffix__), syndaemon(__appmansuffix__) --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/101_resolution_detect_option.patch/src/properties.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/101_resolution_detect_option.patch/src/properties.c @@ -0,0 +1,832 @@ +/* + * Copyright © 2008-2012 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: Peter Hutterer + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "xf86Module.h" + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +#ifndef XATOM_FLOAT +#define XATOM_FLOAT "FLOAT" +#endif + +#ifndef XI_PROP_PRODUCT_ID +#define XI_PROP_PRODUCT_ID "Device Product ID" +#endif + +#ifndef XI_PROP_DEVICE_NODE +#define XI_PROP_DEVICE_NODE "Device Node" +#endif + +static Atom float_type; + +Atom prop_edges = 0; +Atom prop_finger = 0; +Atom prop_tap_time = 0; +Atom prop_tap_move = 0; +Atom prop_tap_durations = 0; +Atom prop_clickpad = 0; +Atom prop_middle_timeout = 0; +Atom prop_twofinger_pressure = 0; +Atom prop_twofinger_width = 0; +Atom prop_scrolldist = 0; +Atom prop_scrolledge = 0; +Atom prop_scrolltwofinger = 0; +Atom prop_speed = 0; +Atom prop_edgemotion_pressure = 0; +Atom prop_edgemotion_speed = 0; +Atom prop_edgemotion_always = 0; +Atom prop_buttonscroll = 0; +Atom prop_buttonscroll_repeat = 0; +Atom prop_buttonscroll_time = 0; +Atom prop_off = 0; +Atom prop_lockdrags = 0; +Atom prop_lockdrags_time = 0; +Atom prop_tapaction = 0; +Atom prop_clickaction = 0; +Atom prop_circscroll = 0; +Atom prop_circscroll_dist = 0; +Atom prop_circscroll_trigger = 0; +Atom prop_circpad = 0; +Atom prop_palm = 0; +Atom prop_palm_dim = 0; +Atom prop_coastspeed = 0; +Atom prop_pressuremotion = 0; +Atom prop_pressuremotion_factor = 0; +Atom prop_grab = 0; +Atom prop_gestures = 0; +Atom prop_capabilities = 0; +Atom prop_resolution = 0; +Atom prop_area = 0; +Atom prop_softbutton_areas = 0; +Atom prop_secondary_softbutton_areas = 0; +Atom prop_noise_cancellation = 0; +Atom prop_product_id = 0; +Atom prop_device_node = 0; + +static Atom +InitTypedAtom(DeviceIntPtr dev, char *name, Atom type, int format, int nvalues, + int *values) +{ + int i; + Atom atom; + uint8_t val_8[9]; /* we never have more than 9 values in an atom */ + uint16_t val_16[9]; + uint32_t val_32[9]; + pointer converted; + + for (i = 0; i < nvalues; i++) { + switch (format) { + case 8: + val_8[i] = values[i]; + break; + case 16: + val_16[i] = values[i]; + break; + case 32: + val_32[i] = values[i]; + break; + } + } + + switch (format) { + case 8: + converted = val_8; + break; + case 16: + converted = val_16; + break; + case 32: + default: + converted = val_32; + break; + } + + atom = MakeAtom(name, strlen(name), TRUE); + XIChangeDeviceProperty(dev, atom, type, format, PropModeReplace, nvalues, + converted, FALSE); + XISetDevicePropertyDeletable(dev, atom, FALSE); + return atom; +} + +static Atom +InitAtom(DeviceIntPtr dev, char *name, int format, int nvalues, int *values) +{ + return InitTypedAtom(dev, name, XA_INTEGER, format, nvalues, values); +} + +static Atom +InitFloatAtom(DeviceIntPtr dev, char *name, int nvalues, float *values) +{ + Atom atom; + + atom = MakeAtom(name, strlen(name), TRUE); + XIChangeDeviceProperty(dev, atom, float_type, 32, PropModeReplace, + nvalues, values, FALSE); + XISetDevicePropertyDeletable(dev, atom, FALSE); + return atom; +} + +static void +InitSoftButtonProperty(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + SynapticsParameters *para = &priv->synpara; + int values[8]; + + values[0] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][LEFT]; + values[1] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][RIGHT]; + values[2] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][TOP]; + values[3] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][BOTTOM]; + values[4] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][LEFT]; + values[5] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][RIGHT]; + values[6] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][TOP]; + values[7] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][BOTTOM]; + prop_softbutton_areas = + InitAtom(pInfo->dev, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 8, values); + + if (!para->has_secondary_buttons) + return; + + values[0] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][LEFT]; + values[1] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][RIGHT]; + values[2] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][TOP]; + values[3] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][BOTTOM]; + values[4] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][LEFT]; + values[5] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][RIGHT]; + values[6] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][TOP]; + values[7] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][BOTTOM]; + + if (values[0] || values[1] || values[2] || values[4] || + values[5] || values[6] || values[7]) + prop_secondary_softbutton_areas = + InitAtom(pInfo->dev, SYNAPTICS_PROP_SECONDARY_SOFTBUTTON_AREAS, 32, 8, values); +} + +void +InitDeviceProperties(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + SynapticsParameters *para = &priv->synpara; + int values[9]; /* we never have more than 9 values in an atom */ + float fvalues[4]; /* never have more than 4 float values */ + + float_type = XIGetKnownProperty(XATOM_FLOAT); + if (!float_type) { + float_type = MakeAtom(XATOM_FLOAT, strlen(XATOM_FLOAT), TRUE); + if (!float_type) { + xf86IDrvMsg(pInfo, X_ERROR, "Failed to init float atom. " + "Disabling property support.\n"); + return; + } + } + + values[0] = para->left_edge; + values[1] = para->right_edge; + values[2] = para->top_edge; + values[3] = para->bottom_edge; + + prop_edges = InitAtom(pInfo->dev, SYNAPTICS_PROP_EDGES, 32, 4, values); + + values[0] = para->finger_low; + values[1] = para->finger_high; + values[2] = 0; + + prop_finger = InitAtom(pInfo->dev, SYNAPTICS_PROP_FINGER, 32, 3, values); + prop_tap_time = + InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_TIME, 32, 1, ¶->tap_time); + prop_tap_move = + InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_MOVE, 32, 1, ¶->tap_move); + + values[0] = para->single_tap_timeout; + values[1] = para->tap_time_2; + values[2] = para->click_time; + + prop_tap_durations = + InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_DURATIONS, 32, 3, values); + prop_clickpad = + InitAtom(pInfo->dev, SYNAPTICS_PROP_CLICKPAD, 8, 1, ¶->clickpad); + prop_middle_timeout = + InitAtom(pInfo->dev, SYNAPTICS_PROP_MIDDLE_TIMEOUT, 32, 1, + ¶->emulate_mid_button_time); + prop_twofinger_pressure = + InitAtom(pInfo->dev, SYNAPTICS_PROP_TWOFINGER_PRESSURE, 32, 1, + ¶->emulate_twofinger_z); + prop_twofinger_width = + InitAtom(pInfo->dev, SYNAPTICS_PROP_TWOFINGER_WIDTH, 32, 1, + ¶->emulate_twofinger_w); + + values[0] = para->scroll_dist_vert; + values[1] = para->scroll_dist_horiz; + prop_scrolldist = + InitAtom(pInfo->dev, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 2, values); + + values[0] = para->scroll_edge_vert; + values[1] = para->scroll_edge_horiz; + values[2] = para->scroll_edge_corner; + prop_scrolledge = + InitAtom(pInfo->dev, SYNAPTICS_PROP_SCROLL_EDGE, 8, 3, values); + values[0] = para->scroll_twofinger_vert; + values[1] = para->scroll_twofinger_horiz; + prop_scrolltwofinger = + InitAtom(pInfo->dev, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 2, values); + + fvalues[0] = para->min_speed; + fvalues[1] = para->max_speed; + fvalues[2] = para->accl; + fvalues[3] = 0; + prop_speed = InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_SPEED, 4, fvalues); + + if (priv->has_scrollbuttons) { + values[0] = para->updown_button_scrolling; + values[1] = para->leftright_button_scrolling; + prop_buttonscroll = + InitAtom(pInfo->dev, SYNAPTICS_PROP_BUTTONSCROLLING, 8, 2, values); + + values[0] = para->updown_button_repeat; + values[1] = para->leftright_button_repeat; + prop_buttonscroll_repeat = + InitAtom(pInfo->dev, SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT, 8, 2, + values); + prop_buttonscroll_time = + InitAtom(pInfo->dev, SYNAPTICS_PROP_BUTTONSCROLLING_TIME, 32, 1, + ¶->scroll_button_repeat); + } + + prop_off = + InitAtom(pInfo->dev, SYNAPTICS_PROP_OFF, 8, 1, ¶->touchpad_off); + prop_lockdrags = + InitAtom(pInfo->dev, SYNAPTICS_PROP_LOCKED_DRAGS, 8, 1, + ¶->locked_drags); + prop_lockdrags_time = + InitAtom(pInfo->dev, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT, 32, 1, + ¶->locked_drag_time); + + memcpy(values, para->tap_action, MAX_TAP * sizeof(int)); + prop_tapaction = + InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_ACTION, 8, MAX_TAP, values); + + memcpy(values, para->click_action, MAX_CLICK * sizeof(int)); + prop_clickaction = + InitAtom(pInfo->dev, SYNAPTICS_PROP_CLICK_ACTION, 8, MAX_CLICK, values); + + prop_circscroll = + InitAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_SCROLLING, 8, 1, + ¶->circular_scrolling); + + fvalues[0] = para->scroll_dist_circ; + prop_circscroll_dist = + InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST, 1, + fvalues); + + prop_circscroll_trigger = + InitAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER, 8, 1, + ¶->circular_trigger); + prop_circpad = + InitAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_PAD, 8, 1, + ¶->circular_pad); + prop_palm = + InitAtom(pInfo->dev, SYNAPTICS_PROP_PALM_DETECT, 8, 1, + ¶->palm_detect); + + values[0] = para->palm_min_width; + values[1] = para->palm_min_z; + + prop_palm_dim = + InitAtom(pInfo->dev, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 2, values); + + fvalues[0] = para->coasting_speed; + fvalues[1] = para->coasting_friction; + prop_coastspeed = + InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_COASTING_SPEED, 2, fvalues); + + values[0] = para->press_motion_min_z; + values[1] = para->press_motion_max_z; + prop_pressuremotion = + InitTypedAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION, XA_CARDINAL, + 32, 2, values); + + fvalues[0] = para->press_motion_min_factor; + fvalues[1] = para->press_motion_max_factor; + + prop_pressuremotion_factor = + InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 2, + fvalues); + + prop_grab = + InitAtom(pInfo->dev, SYNAPTICS_PROP_GRAB, 8, 1, + ¶->grab_event_device); + + values[0] = para->tap_and_drag_gesture; + prop_gestures = InitAtom(pInfo->dev, SYNAPTICS_PROP_GESTURES, 8, 1, values); + + values[0] = priv->has_left; + values[1] = priv->has_middle; + values[2] = priv->has_right; + values[3] = priv->has_double; + values[4] = priv->has_triple; + values[5] = priv->has_pressure; + values[6] = priv->has_width; + prop_capabilities = + InitAtom(pInfo->dev, SYNAPTICS_PROP_CAPABILITIES, 8, 7, values); + + values[0] = para->resolution_vert; + values[1] = para->resolution_horiz; + prop_resolution = + InitAtom(pInfo->dev, SYNAPTICS_PROP_RESOLUTION, 32, 2, values); + + values[0] = para->area_left_edge; + values[1] = para->area_right_edge; + values[2] = para->area_top_edge; + values[3] = para->area_bottom_edge; + prop_area = InitAtom(pInfo->dev, SYNAPTICS_PROP_AREA, 32, 4, values); + + if (para->clickpad) + InitSoftButtonProperty(pInfo); + + values[0] = para->hyst_x; + values[1] = para->hyst_y; + prop_noise_cancellation = InitAtom(pInfo->dev, + SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 2, + values); + + /* only init product_id property if we actually know them */ + if (priv->id_vendor || priv->id_product) { + values[0] = priv->id_vendor; + values[1] = priv->id_product; + prop_product_id = + InitAtom(pInfo->dev, XI_PROP_PRODUCT_ID, 32, 2, values); + } + + if (priv->device) { + prop_device_node = + MakeAtom(XI_PROP_DEVICE_NODE, strlen(XI_PROP_DEVICE_NODE), TRUE); + XIChangeDeviceProperty(pInfo->dev, prop_device_node, XA_STRING, 8, + PropModeReplace, strlen(priv->device), + (pointer) priv->device, FALSE); + XISetDevicePropertyDeletable(pInfo->dev, prop_device_node, FALSE); + } + +} + +int +SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop, + BOOL checkonly) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + SynapticsParameters *para = &priv->synpara; + SynapticsParameters tmp; + + /* If checkonly is set, no parameters may be changed. So just let the code + * change temporary variables and forget about it. */ + if (checkonly) { + tmp = *para; + para = &tmp; + } + + if (property == prop_edges) { + INT32 *edges; + + if (prop->size != 4 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + edges = (INT32 *) prop->data; + if (edges[0] > edges[1] || edges[2] > edges[3]) + return BadValue; + + para->left_edge = edges[0]; + para->right_edge = edges[1]; + para->top_edge = edges[2]; + para->bottom_edge = edges[3]; + + } + else if (property == prop_finger) { + INT32 *finger; + + if (prop->size != 3 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + finger = (INT32 *) prop->data; + if (finger[0] > finger[1]) + return BadValue; + + para->finger_low = finger[0]; + para->finger_high = finger[1]; + } + else if (property == prop_tap_time) { + if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + para->tap_time = *(INT32 *) prop->data; + + } + else if (property == prop_tap_move) { + if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + para->tap_move = *(INT32 *) prop->data; + } + else if (property == prop_tap_durations) { + INT32 *timeouts; + + if (prop->size != 3 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + timeouts = (INT32 *) prop->data; + + para->single_tap_timeout = timeouts[0]; + para->tap_time_2 = timeouts[1]; + para->click_time = timeouts[2]; + } + else if (property == prop_clickpad) { + BOOL value; + + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + value = *(BOOL *) prop->data; + if (!para->clickpad && value && !prop_softbutton_areas) + InitSoftButtonProperty(pInfo); + else if (para->clickpad && !value && prop_softbutton_areas) { + XIDeleteDeviceProperty(dev, prop_softbutton_areas, FALSE); + prop_softbutton_areas = 0; + } + + para->clickpad = *(BOOL *) prop->data; + } + else if (property == prop_middle_timeout) { + if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + para->emulate_mid_button_time = *(INT32 *) prop->data; + } + else if (property == prop_twofinger_pressure) { + if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + para->emulate_twofinger_z = *(INT32 *) prop->data; + } + else if (property == prop_twofinger_width) { + if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + para->emulate_twofinger_w = *(INT32 *) prop->data; + } + else if (property == prop_scrolldist) { + INT32 *dist; + + if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + dist = (INT32 *) prop->data; + if (dist[0] == 0 || dist[1] == 0) + return BadValue; + + if (para->scroll_dist_vert != dist[0]) { + para->scroll_dist_vert = dist[0]; + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + para->scroll_dist_vert, 0); + } + if (para->scroll_dist_horiz != dist[1]) { + para->scroll_dist_horiz = dist[1]; + SetScrollValuator(dev, priv->scroll_axis_horiz, + SCROLL_TYPE_HORIZONTAL, para->scroll_dist_horiz, + 0); + } + } + else if (property == prop_scrolledge) { + CARD8 *edge; + + if (prop->size != 3 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + edge = (BOOL *) prop->data; + para->scroll_edge_vert = edge[0]; + para->scroll_edge_horiz = edge[1]; + para->scroll_edge_corner = edge[2]; + } + else if (property == prop_scrolltwofinger) { + CARD8 *twofinger; + + if (prop->size != 2 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + twofinger = (BOOL *) prop->data; + para->scroll_twofinger_vert = twofinger[0]; + para->scroll_twofinger_horiz = twofinger[1]; + } + else if (property == prop_speed) { + float *speed; + + if (prop->size != 4 || prop->format != 32 || prop->type != float_type) + return BadMatch; + + speed = (float *) prop->data; + para->min_speed = speed[0]; + para->max_speed = speed[1]; + para->accl = speed[2]; + } + else if (property == prop_buttonscroll) { + BOOL *scroll; + + if (!priv->has_scrollbuttons) + return BadMatch; + + if (prop->size != 2 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + scroll = (BOOL *) prop->data; + para->updown_button_scrolling = scroll[0]; + para->leftright_button_scrolling = scroll[1]; + + } + else if (property == prop_buttonscroll_repeat) { + BOOL *repeat; + + if (!priv->has_scrollbuttons) + return BadMatch; + + if (prop->size != 2 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + repeat = (BOOL *) prop->data; + para->updown_button_repeat = repeat[0]; + para->leftright_button_repeat = repeat[1]; + } + else if (property == prop_buttonscroll_time) { + if (!priv->has_scrollbuttons) + return BadMatch; + + if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + para->scroll_button_repeat = *(INT32 *) prop->data; + + } + else if (property == prop_off) { + CARD8 off; + + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + off = *(CARD8 *) prop->data; + + if (off > 2) + return BadValue; + + para->touchpad_off = off; + } + else if (property == prop_gestures) { + BOOL *gestures; + + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + gestures = (BOOL *) prop->data; + para->tap_and_drag_gesture = gestures[0]; + } + else if (property == prop_lockdrags) { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + para->locked_drags = *(BOOL *) prop->data; + } + else if (property == prop_lockdrags_time) { + if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + para->locked_drag_time = *(INT32 *) prop->data; + } + else if (property == prop_tapaction) { + int i; + CARD8 *action; + + if (prop->size > MAX_TAP || prop->format != 8 || + prop->type != XA_INTEGER) + return BadMatch; + + action = (CARD8 *) prop->data; + + for (i = 0; i < MAX_TAP; i++) + para->tap_action[i] = action[i]; + } + else if (property == prop_clickaction) { + int i; + CARD8 *action; + + if (prop->size > MAX_CLICK || prop->format != 8 || + prop->type != XA_INTEGER) + return BadMatch; + + action = (CARD8 *) prop->data; + + for (i = 0; i < MAX_CLICK; i++) + para->click_action[i] = action[i]; + } + else if (property == prop_circscroll) { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + para->circular_scrolling = *(BOOL *) prop->data; + + } + else if (property == prop_circscroll_dist) { + float circdist; + + if (prop->size != 1 || prop->format != 32 || prop->type != float_type) + return BadMatch; + + circdist = *(float *) prop->data; + if (circdist == 0) + return BadValue; + + para->scroll_dist_circ = circdist; + } + else if (property == prop_circscroll_trigger) { + int trigger; + + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + trigger = *(CARD8 *) prop->data; + if (trigger > 8) + return BadValue; + + para->circular_trigger = trigger; + + } + else if (property == prop_circpad) { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + para->circular_pad = *(BOOL *) prop->data; + } + else if (property == prop_palm) { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + para->palm_detect = *(BOOL *) prop->data; + } + else if (property == prop_palm_dim) { + INT32 *dim; + + if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + dim = (INT32 *) prop->data; + + para->palm_min_width = dim[0]; + para->palm_min_z = dim[1]; + } + else if (property == prop_coastspeed) { + float *coast_speeds; + + if (prop->size != 2 || prop->format != 32 || prop->type != float_type) + return BadMatch; + + coast_speeds = (float *) prop->data; + para->coasting_speed = coast_speeds[0]; + para->coasting_friction = coast_speeds[1]; + } + else if (property == prop_pressuremotion) { + CARD32 *press; + + if (prop->size != 2 || prop->format != 32 || prop->type != XA_CARDINAL) + return BadMatch; + + press = (CARD32 *) prop->data; + if (press[0] > press[1]) + return BadValue; + + para->press_motion_min_z = press[0]; + para->press_motion_max_z = press[1]; + } + else if (property == prop_pressuremotion_factor) { + float *press; + + if (prop->size != 2 || prop->format != 32 || prop->type != float_type) + return BadMatch; + + press = (float *) prop->data; + if (press[0] > press[1]) + return BadValue; + + para->press_motion_min_factor = press[0]; + para->press_motion_max_factor = press[1]; + } + else if (property == prop_grab) { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + para->grab_event_device = *(BOOL *) prop->data; + } + else if (property == prop_capabilities) { + /* read-only */ + return BadValue; + } + else if (property == prop_resolution) { + /* read-only */ + return BadValue; + } + else if (property == prop_area) { + INT32 *area; + + if (prop->size != 4 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + area = (INT32 *) prop->data; + if ((((area[0] != 0) && (area[1] != 0)) && (area[0] > area[1])) || + (((area[2] != 0) && (area[3] != 0)) && (area[2] > area[3]))) + return BadValue; + + para->area_left_edge = area[0]; + para->area_right_edge = area[1]; + para->area_top_edge = area[2]; + para->area_bottom_edge = area[3]; + } + else if (property == prop_softbutton_areas) { + int *areas; + + if (prop->size != 8 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + areas = (int *) prop->data; + if (!SynapticsIsSoftButtonAreasValid(areas)) + return BadValue; + + memcpy(para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA], areas, 4 * sizeof(int)); + memcpy(para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA], areas + 4, 4 * sizeof(int)); + } + else if (property == prop_secondary_softbutton_areas) { + int *areas; + + if (prop->size != 8 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + areas = (int *) prop->data; + if (!SynapticsIsSoftButtonAreasValid(areas)) + return BadValue; + + memcpy(para->softbutton_areas[TOP_RIGHT_BUTTON_AREA], areas, 4 * sizeof(int)); + memcpy(para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA], areas + 4, 4 * sizeof(int)); + } + else if (property == prop_noise_cancellation) { + INT32 *hyst; + + if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER) + return BadMatch; + + hyst = (INT32 *) prop->data; + if (hyst[0] < 0 || hyst[1] < 0) + return BadValue; + para->hyst_x = hyst[0]; + para->hyst_y = hyst[1]; + } + else if (property == prop_product_id || property == prop_device_node) + return BadValue; /* read-only */ + else { /* unknown property */ + if (strcmp(SYNAPTICS_PROP_SOFTBUTTON_AREAS, NameForAtom(property)) == 0) + { + prop_softbutton_areas = property; + if (SetProperty(dev, property, prop, checkonly) != Success) + prop_softbutton_areas = 0; + else if (!checkonly) + XISetDevicePropertyDeletable(dev, property, FALSE); + } + } + + return Success; +} --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/101_resolution_detect_option.patch/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/101_resolution_detect_option.patch/src/synaptics.c @@ -0,0 +1,3248 @@ +/* + * 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-2012 Red Hat, Inc. + * Copyright © 2011 The Chromium OS Authors + * + * 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 + * Christian Thaeter + * Stefan Bethge + * Matthias Ihmig + * Alexei Gilchrist + * Jörg Bösner + * Hartwig Felger + * Peter Osterlund + * S. Lehner + * Stefan Gmeiner + * Henry Davies for the + * Linuxcare Inc. David Kennedy + * Fred Hucht + * Fedor P. Goncharov + * Simon Thum + * + * Trademarks are the property of their respective owners. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +enum EdgeType { + 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 +}; + +/* + * We expect to be receiving a steady 80 packets/sec (which gives 40 + * reports/sec with more than one finger on the pad, as Advanced Gesture Mode + * requires two PS/2 packets per report). Instead of a random scattering of + * magic 13 and 20ms numbers scattered throughout the driver, introduce + * POLL_MS as 14ms, which is slightly less than 80Hz. 13ms is closer to + * 80Hz, but if the kernel event reporting was even slightly delayed, + * we would produce synthetic motion followed immediately by genuine + * motion, so use 14. + * + * We use this to call back at a constant rate to at least produce the + * illusion of smooth motion. It works a lot better than you'd expect. +*/ +#define POLL_MS 14 + +#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 + +#define INPUT_BUFFER_SIZE 200 + +/***************************************************************************** + * Forward declaration + ****************************************************************************/ +static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static Bool DeviceControl(DeviceIntPtr, int); +static void ReadInput(InputInfoPtr); +static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now, + Bool from_timer); +static int ControlProc(InputInfoPtr, xDeviceCtl *); +static int SwitchMode(ClientPtr, DeviceIntPtr, int); +static int DeviceInit(DeviceIntPtr); +static int DeviceOn(DeviceIntPtr); +static int DeviceOff(DeviceIntPtr); +static int DeviceClose(DeviceIntPtr); +static Bool QueryHardware(InputInfoPtr); +static void ReadDevDimensions(InputInfoPtr); +#ifndef NO_DRIVER_SCALING +static void ScaleCoordinates(SynapticsPrivate * priv, + struct SynapticsHwState *hw); +static void CalculateScalingCoeffs(SynapticsPrivate * priv); +#endif +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, + NULL, +#ifdef XI86_DRV_CAP_SERVER_FD + XI86_DRV_CAP_SERVER_FD +#endif +}; + +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 + ****************************************************************************/ +static inline void +SynapticsCloseFd(InputInfoPtr pInfo) +{ + if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) { + xf86CloseSerial(pInfo->fd); + pInfo->fd = -1; + } +} + +/** + * 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 = 255; + + 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 = 15; + + xf86IDrvMsg(pInfo, X_PROBED, + "invalid finger width range. defaulting to %d - %d\n", + priv->minw, priv->maxw); + } +} + +static Bool +SetDeviceAndProtocol(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = pInfo->private; + char *proto, *device; + int i; + + proto = xf86SetStrOption(pInfo->options, "Protocol", NULL); + device = xf86SetStrOption(pInfo->options, "Device", NULL); + + /* If proto is auto-dev, unset and let the code do the rest */ + if (proto && !strcmp(proto, "auto-dev")) { + free(proto); + proto = 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; + + return (priv->proto_ops != 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 || + priv->model == MODEL_UNIBODY_MACBOOK) { + 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; +} + +static void +calculate_tap_hysteresis(SynapticsPrivate * priv, int range, + int *fingerLow, int *fingerHigh) +{ + switch (priv->model) { + case MODEL_ELANTECH: + /* All Elantech touchpads don't need the Z filtering to get the + * number of fingers correctly. See Documentation/elantech.txt + * in the kernel. + */ + *fingerLow = priv->minp + 1; + *fingerHigh = priv->minp + 1; + break; + case MODEL_UNIBODY_MACBOOK: + *fingerLow = 70; + *fingerHigh = 75; + break; + default: + *fingerLow = priv->minp + range * (25.0 / 256); + *fingerHigh = priv->minp + range * (30.0 / 256); + break; + } +} + +/* 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; + double percent = xf86CheckPercentOption(options, optname, -1); + + if (percent >= 0.0) { + percent = xf86SetPercentOption(options, optname, -1); + result = percent / 100.0 * range + offset; + } else + result = xf86SetIntOption(options, optname, default_value); + + return result; +} + +Bool +SynapticsIsSoftButtonAreasValid(int *values) +{ + Bool right_disabled = FALSE; + Bool middle_disabled = FALSE; + + enum { + /* right button left, right, top, bottom */ + RBL = 0, + RBR = 1, + RBT = 2, + RBB = 3, + /* middle button left, right, top, bottom */ + MBL = 4, + MBR = 5, + MBT = 6, + MBB = 7, + }; + + /* Check right button area */ + if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) || + (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB]))) + return FALSE; + + /* Check middle button area */ + if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) || + (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB]))) + return FALSE; + + if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0) + right_disabled = TRUE; + + if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0) + middle_disabled = TRUE; + + if (!right_disabled && + ((values[RBL] && values[RBL] == values[RBR]) || + (values[RBT] && values[RBT] == values[RBB]))) + return FALSE; + + if (!middle_disabled && + ((values[MBL] && values[MBL] == values[MBR]) || + (values[MBT] && values[MBT] == values[MBB]))) + return FALSE; + + /* Check for overlapping button areas */ + if (!right_disabled && !middle_disabled) { + int right_left = values[RBL] ? values[RBL] : INT_MIN; + int right_right = values[RBR] ? values[RBR] : INT_MAX; + int right_top = values[RBT] ? values[RBT] : INT_MIN; + int right_bottom = values[RBB] ? values[RBB] : INT_MAX; + int middle_left = values[MBL] ? values[MBL] : INT_MIN; + int middle_right = values[MBR] ? values[MBR] : INT_MAX; + int middle_top = values[MBT] ? values[MBT] : INT_MIN; + int middle_bottom = values[MBB] ? values[MBB] : INT_MAX; + + /* If areas overlap in the Y axis */ + if ((right_bottom <= middle_bottom && right_bottom >= middle_top) || + (right_top <= middle_bottom && right_top >= middle_top)) { + /* Check for overlapping left edges */ + if ((right_left < middle_left && right_right > middle_left) || + (middle_left < right_left && middle_right > right_left)) + return FALSE; + + /* Check for overlapping right edges */ + if ((right_right > middle_right && right_left < middle_right) || + (middle_right > right_right && middle_left < right_right)) + return FALSE; + } + + /* If areas overlap in the X axis */ + if ((right_left >= middle_left && right_left <= middle_right) || + (right_right >= middle_left && right_right <= middle_right)) { + /* Check for overlapping top edges */ + if ((right_top < middle_top && right_bottom > middle_top) || + (middle_top < right_top && middle_bottom > right_top)) + return FALSE; + + /* Check for overlapping bottom edges */ + if ((right_bottom > middle_bottom && right_top < middle_bottom) || + (middle_bottom > right_bottom && middle_top < right_bottom)) + return FALSE; + } + } + + return TRUE; +} + +static void +set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset) +{ + SynapticsPrivate *priv = pInfo->private; + SynapticsParameters *pars = &priv->synpara; + int values[8]; + int in_percent = 0; /* bitmask for which ones are in % */ + char *option_string; + char *next_num; + char *end_str; + int i; + int width, height; + + if (!pars->clickpad) + return; + + option_string = xf86SetStrOption(pInfo->options, option_name, NULL); + if (!option_string) + return; + + next_num = option_string; + + for (i = 0; i < 8 && *next_num != '\0'; i++) { + long int value = strtol(next_num, &end_str, 0); + + if (value > INT_MAX || value < -INT_MAX) + goto fail; + + values[i] = value; + + if (next_num != end_str) { + if (*end_str == '%') { + in_percent |= 1 << i; + end_str++; + } + next_num = end_str; + } + else + goto fail; + } + + if (i < 8 || *next_num != '\0') + goto fail; + + width = priv->maxx - priv->minx; + height = priv->maxy - priv->miny; + + for (i = 0; in_percent && i < 8; i++) { + int base, size; + + if ((in_percent & (1 << i)) == 0 || values[i] == 0) + continue; + + size = ((i % 4) < 2) ? width : height; + base = ((i % 4) < 2) ? priv->minx : priv->miny; + values[i] = base + size * values[i] / 100.0; + } + + if (!SynapticsIsSoftButtonAreasValid(values)) + goto fail; + + memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int)); + memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int)); + + free(option_string); + + return; + + fail: + xf86IDrvMsg(pInfo, X_ERROR, + "invalid %s value '%s', keeping defaults\n", + option_name, option_string); + free(option_string); +} + +static void +set_primary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA); +} + +static void +set_secondary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA); +} + +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 */ + double accelFactor; /* 1/pixels */ + int fingerLow, fingerHigh; /* pressure */ + int emulateTwoFingerMinZ; /* pressure */ + int emulateTwoFingerMinW; /* width */ + 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; + int middle_button_timeout; + int grab_event_device = 0; + const char *source; + + /* 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; + 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 + 1; + + calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh); + + /* scaling based on defaults and a pressure of 256 */ + emulateTwoFingerMinZ = priv->minp + range * (282.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 + 1; + + /* 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->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->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad); /* Probed */ + if (pars->clickpad) + pars->has_secondary_buttons = xf86SetBoolOption(opts, + "HasSecondarySoftButtons", + pars->has_secondary_buttons); + pars->clickpad_ignore_motion_time = 100; /* ms */ + /* middle mouse button emulation on a clickpad? nah, you're joking */ + middle_button_timeout = pars->clickpad ? 0 : 75; + pars->emulate_mid_button_time = + xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout); + pars->emulate_twofinger_z = + xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ); + pars->emulate_twofinger_w = + xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW); + pars->scroll_dist_vert = + xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta); + pars->scroll_dist_horiz = + xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta); + 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->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON); + + 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->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->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); + + /* Only grab the device by default if it's not coming from a config + backend. This way we avoid the device being added twice and sending + duplicate events. + */ + source = xf86CheckStrOption(opts, "_source", NULL); + if (source == NULL || strncmp(source, "server/", 7) != 0) + grab_event_device = TRUE; + pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device); + + pars->tap_and_drag_gesture = + xf86SetBoolOption(opts, "TapAndDragGesture", TRUE); + pars->resolution_horiz = + xf86SetIntOption(opts, "HorizResolution", horizResolution); + pars->resolution_vert = + xf86SetIntOption(opts, "VertResolution", vertResolution); + if (pars->resolution_horiz <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid X resolution, using 1 instead.\n"); + pars->resolution_horiz = 1; + } + if (pars->resolution_vert <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid Y resolution, using 1 instead.\n"); + pars->resolution_vert = 1; + } + + /* Touchpad sampling rate is too low to detect all movements. + A user may lift one finger and put another one down within the same + EV_SYN or even between samplings so the driver doesn't notice at all. + + We limit the movement to 20 mm within one event, that is more than + recordings showed is needed (17mm on a T440). + */ + if (pars->resolution_horiz > 1 && + pars->resolution_vert > 1) + pars->maxDeltaMM = 20; + else { + /* on devices without resolution set the vector length to 0.25 of + the touchpad diagonal */ + pars->maxDeltaMM = diag * 0.25; + } + + + /* 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"); + } + + set_primary_softbutton_areas_option(pInfo); + if (pars->has_secondary_buttons) + set_secondary_softbutton_areas_option(pInfo); +} + +static double +SynapticsAccelerationProfile(DeviceIntPtr dev, + DeviceVelocityPtr vel, + double velocity, double thr, double 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; +} + +static int +SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + 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 */ + if (!SetDeviceAndProtocol(pInfo)) { + 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); + + set_default_parameters(pInfo); + +#ifndef NO_DRIVER_SCALING + CalculateScalingCoeffs(priv); +#endif + + + 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 (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + + return Success; + + SetupProc_fail: + SynapticsCloseFd(pInfo); + + if (priv->comm.buffer) + XisbFree(priv->comm.buffer); + 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); + if (priv && priv->scroll_events_mask) + valuator_mask_free(&priv->scroll_events_mask); + if (priv && priv->open_slots) + free(priv->open_slots); + 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 int +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 int +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)) + goto error; + + priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE); + if (!priv->comm.buffer) + goto error; + + xf86FlushInput(pInfo->fd); + + /* reinit the pad */ + if (!QueryHardware(pInfo)) + goto error; + + xf86AddEnabledDevice(pInfo); + dev->public.on = TRUE; + + return Success; + +error: + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + return !Success; +} + +static void +SynapticsReset(SynapticsPrivate * priv) +{ + int i; + + SynapticsResetHwState(priv->hwState); + SynapticsResetHwState(priv->local_hw_state); + SynapticsResetHwState(priv->comm.hwState); + + memset(priv->move_hist, 0, sizeof(priv->move_hist)); + priv->hyst_center_x = 0; + priv->hyst_center_y = 0; + memset(&priv->scroll, 0, sizeof(priv->scroll)); + priv->count_packet_finger = 0; + priv->finger_state = FS_UNTOUCHED; + priv->last_motion_millis = 0; + priv->clickpad_click_millis = 0; + priv->last_button_area = NO_BUTTON_AREA; + priv->tap_state = TS_START; + priv->tap_button = 0; + priv->tap_button_state = TBS_BUTTON_UP; + priv->moving_state = MS_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; + priv->circ_scroll_on = FALSE; + priv->circ_scroll_vert = FALSE; + priv->mid_emu_state = MBE_OFF; + priv->nextRepeat = 0; + priv->lastButtons = 0; + priv->prev_z = 0; + priv->prevFingers = 0; + priv->num_active_touches = 0; + + for (i = 0; i < priv->num_slots; i++) + priv->open_slots[i] = -1; +} + +static int +DeviceOff(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + Bool rc = Success; + + DBG(3, "Synaptics DeviceOff called\n"); + + if (pInfo->fd != -1) { + TimerCancel(priv->timer); + xf86RemoveEnabledDevice(pInfo); + SynapticsReset(priv); + + if (priv->proto_ops->DeviceOffHook && + !priv->proto_ops->DeviceOffHook(pInfo)) + rc = !Success; + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + } + dev->public.on = FALSE; + return rc; +} + +static int +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(priv->touch_axes); + priv->touch_axes = NULL; + SynapticsHwStateFree(&priv->hwState); + SynapticsHwStateFree(&priv->local_hw_state); + SynapticsHwStateFree(&priv->comm.hwState); + return RetValue; +} + +static void +InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv) +{ + int i; + + memset(labels, 0, nlabels * sizeof(Atom)); + switch (nlabels) { + default: + case 4: + labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL); + case 3: + labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL); + case 2: + labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y); + case 1: + labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X); + break; + } + + for (i = 0; i < priv->num_mt_axes; i++) { + SynapticsTouchAxisRec *axis = &priv->touch_axes[i]; + int axnum = nlabels - priv->num_mt_axes + i; + + labels[axnum] = XIGetKnownProperty(axis->label); + } +} + +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; + } +} + +static void +DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + if (!priv->has_touch) + return; + + priv->num_slots = + priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES; + + priv->open_slots = malloc(priv->num_slots * sizeof(int)); + if (!priv->open_slots) { + xf86IDrvMsg(pInfo, X_ERROR, + "failed to allocate open touch slots array\n"); + priv->has_touch = 0; + priv->num_slots = 0; + } +} + +static int +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; + int num_axes = 2; + Atom btn_labels[SYN_MAX_BUTTONS] = { 0 }; + Atom *axes_labels; + DeviceVelocityPtr pVel; + + num_axes += 2; + + num_axes += priv->num_mt_axes; + + axes_labels = calloc(num_axes, sizeof(Atom)); + if (!axes_labels) { + xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n"); + return !Success; + } + + InitAxesLabels(axes_labels, num_axes, priv); + InitButtonLabels(btn_labels, SYN_MAX_BUTTONS); + + 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, + btn_labels, + SynapticsCtrl, + GetMotionHistorySize(), num_axes, axes_labels); + + /* + * setup dix acceleration to match legacy synaptics settings, and + * etablish a device-specific profile to do stuff like pressure-related + * acceleration. + */ + 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, "(accel) MaxSpeed is now %.2f\n", + priv->synpara.max_speed); + xf86IDrvMsg(pInfo, X_CONFIG, "(accel) 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); + } + + /* X valuator */ + if (priv->minx < priv->maxx) { + min = priv->minx; + max = priv->maxx; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max, + priv->resx * 1000, 0, priv->resx * 1000, + Relative); + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ + if (priv->miny < priv->maxy) { + min = priv->miny; + max = priv->maxy; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max, + priv->resy * 1000, 0, priv->resy * 1000, + Relative); + xf86InitValuatorDefaults(dev, 1); + + xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_horiz = 2; + xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_vert = 3; + priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS); + if (!priv->scroll_events_mask) { + free(axes_labels); + return !Success; + } + + SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL, + priv->synpara.scroll_dist_horiz, 0); + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + priv->synpara.scroll_dist_vert, 0); + + DeviceInitTouch(dev, axes_labels); + + free(axes_labels); + + priv->hwState = SynapticsHwStateAlloc(priv); + if (!priv->hwState) + goto fail; + + priv->local_hw_state = SynapticsHwStateAlloc(priv); + if (!priv->local_hw_state) + goto fail; + + priv->comm.hwState = SynapticsHwStateAlloc(priv); + + InitDeviceProperties(pInfo); + XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL); + + SynapticsReset(priv); + + return Success; + + fail: + free(priv->local_hw_state); + free(priv->hwState); + free(priv->open_slots); + 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 enum EdgeType +circular_edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 enum EdgeType +edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 a finger is down, then it must have started inside the active_area, + allow the motion to complete using the entire area */ + if (priv->finger_state >= FS_TOUCHED) + return 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 Bool +is_inside_button_area(SynapticsParameters * para, int which, int x, int y) +{ + Bool inside_area = TRUE; + + if (para->softbutton_areas[which][LEFT] == 0 && + para->softbutton_areas[which][RIGHT] == 0 && + para->softbutton_areas[which][TOP] == 0 && + para->softbutton_areas[which][BOTTOM] == 0) + return FALSE; + + if (para->softbutton_areas[which][LEFT] && + x < para->softbutton_areas[which][LEFT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][RIGHT] && + x > para->softbutton_areas[which][RIGHT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][TOP] && + y < para->softbutton_areas[which][TOP]) + inside_area = FALSE; + else if (para->softbutton_areas[which][BOTTOM] && + y > para->softbutton_areas[which][BOTTOM]) + inside_area = FALSE; + + return inside_area; +} + +static Bool +is_inside_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset, + int x, int y) +{ + Bool inside_area = TRUE; + Bool right_valid, middle_valid; + int top, bottom; + + /* We don't have a left button area, so we only check the y axis */ + right_valid = para->softbutton_areas[offset][TOP] || + para->softbutton_areas[offset][BOTTOM]; + middle_valid = para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset + 1][BOTTOM]; + + if (!right_valid && !middle_valid) + return FALSE; + + /* Check both buttons are horizontally aligned */ + if (right_valid && middle_valid && ( + para->softbutton_areas[offset][TOP] != + para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset][BOTTOM] != + para->softbutton_areas[offset + 1][BOTTOM])) + return FALSE; + + if (right_valid) { + top = para->softbutton_areas[offset][TOP]; + bottom = para->softbutton_areas[offset][BOTTOM]; + } + else { + top = para->softbutton_areas[offset + 1][TOP]; + bottom = para->softbutton_areas[offset + 1][BOTTOM]; + } + + if (top && y < top) + inside_area = FALSE; + else if (bottom && y > bottom) + inside_area = FALSE; + + return inside_area; +} + +static enum SoftButtonAreas +current_button_area(SynapticsParameters * para, int x, int y) +{ + if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y)) + return BOTTOM_BUTTON_AREA; + else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y)) + return TOP_BUTTON_AREA; + else + return NO_BUTTON_AREA; +} + +static CARD32 +timerFunc(OsTimerPtr timer, CARD32 now, pointer arg) +{ + InputInfoPtr pInfo = arg; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + struct SynapticsHwState *hw = priv->local_hw_state; + int delay; +#if !HAVE_THREADED_INPUT + int sigstate = xf86BlockSIGIO(); +#else + input_lock(); +#endif + + priv->hwState->millis += now - priv->timer_time; + SynapticsCopyHwState(hw, priv->hwState); + SynapticsResetTouchHwState(hw, FALSE); + delay = HandleState(pInfo, hw, hw->millis, TRUE); + + priv->timer_time = now; + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + +#if !HAVE_THREADED_INPUT + xf86UnblockSIGIO(sigstate); +#else + input_unlock(); +#endif + + 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 = priv->local_hw_state; + int delay = 0; + Bool newDelay = FALSE; + + SynapticsResetTouchHwState(hw, FALSE); + + while (SynapticsGetHwState(pInfo, priv, hw)) { + /* Semi-mt device touch slots do not track touches. When there is a + * change in the number of touches, we must disregard the temporary + * motion changes. */ + if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) { + hw->cumulative_dx = priv->hwState->cumulative_dx; + hw->cumulative_dy = priv->hwState->cumulative_dy; + } + + /* timer may cause actual events to lag behind (#48777) */ + if (priv->hwState->millis > hw->millis) + hw->millis = priv->hwState->millis; + + SynapticsCopyHwState(priv->hwState, hw); + delay = HandleState(pInfo, hw, hw->millis, FALSE); + newDelay = TRUE; + } + + if (newDelay) { + priv->timer_time = GetTimeInMillis(); + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + } +} + +static int +HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw, + CARD32 now, int *delay) +{ + SynapticsParameters *para = &priv->synpara; + Bool done = FALSE; + int timeleft; + int mid = 0; + + if (para->emulate_mid_button_time <= 0) + return mid; + + while (!done) { + switch (priv->mid_emu_state) { + case MBE_LEFT_CLICK: + case MBE_RIGHT_CLICK: + case MBE_OFF: + priv->button_delay_millis = now; + 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, now); + 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, now); + 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_low) + return FS_UNTOUCHED; + + if (priv->finger_state == FS_BLOCKED) + return FS_BLOCKED; + + if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED) + finger = FS_TOUCHED; + else + finger = priv->finger_state; + + if (!para->palm_detect) + return finger; + + /* palm detection */ + + if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width)) + return FS_BLOCKED; + + if (priv->has_mt_palm_detect) + return finger; + + if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED) + priv->avg_width = 0; + else + priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2; + + if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) { + 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; + + return finger; +} + +static void +SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge) +{ + enum TapEvent tap; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) { + priv->tap_button = 0; + return; + } + + switch (priv->tap_max_fingers) { + case 1: + 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; + default: + priv->tap_button = 0; + return; + } + + 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, CARD32 millis) +{ + DBG(3, "SetTapState - %d -> %d (millis:%u)\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: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_2B: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_3: + priv->tap_button_state = TBS_BUTTON_DOWN; + break; + case TS_SINGLETAP: + 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, + CARD32 millis) +{ + DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n", + priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y, + millis); + + 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, + CARD32 now, enum FingerState finger, + Bool inside_active_area) +{ + SynapticsParameters *para = &priv->synpara; + Bool touch, release, is_timeout, move, press; + int timeleft, timeout; + enum EdgeType edge; + int delay = 1000000000; + + if (para->touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) + return delay; + + touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED; + release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED; + move = (finger >= FS_TOUCHED && + (priv->tap_max_fingers <= + ((priv->horiz_scroll_twofinger_on || + priv->vert_scroll_twofinger_on) ? 2 : 1)) && + (priv->prevFingers == hw->numFingers && + ((abs(hw->x - priv->touch_on.x) >= para->tap_move) || + (abs(hw->y - priv->touch_on.y) >= para->tap_move)))); + press = (hw->left || hw->right || hw->middle); + + if (touch) { + priv->touch_on.x = hw->x; + priv->touch_on.y = hw->y; + priv->touch_on.millis = now; + } + else if (release) { + priv->touch_on.millis = now; + } + 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, now); + is_timeout = timeleft <= 0; + + restart: + switch (priv->tap_state) { + case TS_START: + if (touch) + SetTapState(priv, TS_1, now); + break; + case TS_1: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_MOVE, now); + goto restart; + } + else if (is_timeout) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_MOVE, now); + 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, now); + } + break; + case TS_MOVE: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_2A: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_2B: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_SINGLETAP: + if (touch) + SetTapState(priv, TS_1, now); + else if (is_timeout) + SetTapState(priv, TS_START, now); + break; + case TS_3: + if (move) { + if (para->tap_and_drag_gesture) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (is_timeout) { + if (para->tap_and_drag_gesture) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (release) { + SetTapState(priv, TS_2B, now); + } + break; + case TS_DRAG: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (release) { + SetMovingState(priv, MS_FALSE, now); + if (para->locked_drags) { + SetTapState(priv, TS_4, now); + } + else { + SetTapState(priv, TS_START, now); + } + } + break; + case TS_4: + if (is_timeout) { + SetTapState(priv, TS_START, now); + goto restart; + } + if (touch) + SetTapState(priv, TS_5, now); + break; + case TS_5: + if (is_timeout || move) { + SetTapState(priv, TS_DRAG, now); + goto restart; + } + else if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_CLICKPAD_MOVE: + /* Disable scrolling once a button is pressed on a clickpad */ + priv->vert_scroll_edge_on = FALSE; + priv->horiz_scroll_edge_on = FALSE; + priv->vert_scroll_twofinger_on = FALSE; + priv->horiz_scroll_twofinger_on = FALSE; + + /* Assume one touch is only for holding the clickpad button down */ + if (hw->numFingers > 1) + hw->numFingers--; + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (!press) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_MOVE, now); + priv->count_packet_finger = 0; + } + break; + } + + timeout = GetTimeOut(priv); + if (timeout >= 0) { + timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now); + delay = clamp(timeleft, 1, delay); + } + return delay; +} + +#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)]) +#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e)) + +static void +store_history(SynapticsPrivate * priv, int x, int y, CARD32 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; + if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY) + priv->count_packet_finger++; +} + +/* + * 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(SynapticsPrivate *priv, const struct SynapticsHwState *hw, + enum EdgeType edge, double *dx, double *dy) +{ + *dx = hw->x - HIST(0).x; + *dy = hw->y - HIST(0).y; +} + +/* Vector length, but not sqrt'ed, we only need it for comparison */ +static inline double +vlenpow2(double x, double y) +{ + return x * x + y * y; +} + +/** + * Compute relative motion ('deltas') including edge motion. + */ +static int +ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw, + enum EdgeType edge, int *dxP, int *dyP, Bool inside_area) +{ + enum MovingState moving_state; + double dx, dy; + double vlen; + 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: + moving_state = MS_TOUCHPAD_RELATIVE; + break; + default: + break; + } + } + + if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED || + 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 || + (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) { + /* reset packet counter. */ + priv->count_packet_finger = 0; + goto out; + } + + /* To create the illusion of fluid motion, call back at roughly the report + * rate, even in the absence of new hardware events; see comment above + * POLL_MS declaration. */ + delay = MIN(delay, POLL_MS); + + if (priv->count_packet_finger <= 1) + goto out; /* skip the lot */ + + if (moving_state == MS_TOUCHPAD_RELATIVE) + get_delta(priv, hw, edge, &dx, &dy); + + out: + priv->prevFingers = hw->numFingers; + + vlen = vlenpow2(dx/priv->synpara.resolution_horiz, + dy/priv->synpara.resolution_vert); + + if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) { + dx = 0; + dy = 0; + } + + *dxP = dx; + *dyP = dy; + + return delay; +} + +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->scroll.coast_delta_y = 0.0; + priv->scroll.coast_delta_x = 0.0; + + if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) { + double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0; + + if (vert && !circ) { + double dy = + estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y); + if (pkt_time > 0) { + double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y); + } + } + } + if (horiz && !circ) { + double dx = + estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x); + if (pkt_time > 0) { + double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x); + } + } + } + if (circ) { + double da = estimate_delta_circ(priv); + + if (pkt_time > 0) { + double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + if (vert) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + else if (horiz) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + } + } + } + } + priv->scroll.packets_this_scroll = 0; +} + +static void +stop_coasting(SynapticsPrivate * priv) +{ + priv->scroll.coast_speed_x = 0; + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; +} + +static int +HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw, + enum EdgeType edge, Bool finger) +{ + SynapticsParameters *para = &priv->synpara; + int delay = 1000000000; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF || + priv->synpara.touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) { + 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 == FS_UNTOUCHED) { + stop_coasting(priv); + priv->scroll.delta_y = 0; + priv->scroll.delta_x = 0; + 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.last_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)) { + stop_coasting(priv); + priv->vert_scroll_twofinger_on = TRUE; + priv->vert_scroll_edge_on = FALSE; + priv->scroll.last_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)) { + stop_coasting(priv); + priv->horiz_scroll_twofinger_on = TRUE; + priv->horiz_scroll_edge_on = FALSE; + priv->scroll.last_x = hw->x; + DBG(7, "horiz two-finger scroll detected\n"); + } + } + } + if (finger && priv->finger_state == FS_UNTOUCHED) { + 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.last_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.last_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->scroll.coast_speed_x || priv->scroll.coast_speed_y)) { + 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->scroll.coast_speed_y == 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.last_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->scroll.coast_speed_x == 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.last_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.packets_this_scroll++; + } + + if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) { + /* + = down, - = up */ + if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) { + priv->scroll.delta_y += (hw->y - priv->scroll.last_y); + priv->scroll.last_y = hw->y; + } + } + if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) { + /* + = right, - = left */ + if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) { + priv->scroll.delta_x += (hw->x - priv->scroll.last_x); + priv->scroll.last_x = hw->x; + } + } + if (priv->circ_scroll_on) { + /* + = counter clockwise, - = clockwise */ + double delta = para->scroll_dist_circ; + double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y)); + + if (delta >= 0.005 && diff != 0.0) { + if (priv->circ_scroll_vert) + priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert; + else + priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz; + priv->scroll.last_a = angle(priv, hw->x, hw->y); + } + } + + if (priv->scroll.coast_speed_y) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddy = para->coasting_friction * dtime; + + priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_y) < ddy) { + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_y += + (priv->scroll.coast_speed_y < 0 ? ddy : -ddy); + } + } + + if (priv->scroll.coast_speed_x) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddx = para->coasting_friction * dtime; + priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_x) < ddx) { + priv->scroll.coast_speed_x = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_x += + (priv->scroll.coast_speed_x < 0 ? ddx : -ddx); + } + } + + return delay; +} + +/** + * Check if any 2+ fingers are close enough together to assume this is a + * ClickFinger action. + */ +static int +clickpad_guess_clickfingers(SynapticsPrivate * priv, + struct SynapticsHwState *hw) +{ + int nfingers = 0; + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + +#ifdef BUG_RETURN_VAL + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); +#endif + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; + + if (hw->slot_state[i] == SLOTSTATE_EMPTY || + hw->slot_state[i] == SLOTSTATE_CLOSE) + continue; + + f1 = hw->mt_mask[i]; + + for (j = i + 1; j < hw->num_mt_mask; j++) { + ValuatorMask *f2; + double x1, x2, y1, y2; + + if (hw->slot_state[j] == SLOTSTATE_EMPTY || + hw->slot_state[j] == SLOTSTATE_CLOSE) + continue; + + f2 = hw->mt_mask[j]; + + x1 = valuator_mask_get_double(f1, 0); + y1 = valuator_mask_get_double(f1, 1); + + x2 = valuator_mask_get_double(f2, 0); + y2 = valuator_mask_get_double(f2, 1); + + /* FIXME: fingers closer together than 30% of touchpad width, but + * really, this should be dependent on the touchpad size. Also, + * you'll need to find a touchpad that doesn't lie about it's + * size. Good luck. */ + if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 && + abs(y1 - y2) < (priv->maxy - priv->miny) * .3) { + close_point |= (1 << j); + close_point |= (1 << i); + } + } + } + + while (close_point > 0) { + nfingers += close_point & 0x1; + close_point >>= 1; + } + + /* Some trackpads touchpad only track two touchpoints but announce + * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens + * the user likely intents to do a 3 finger click, so handle it as such. + */ + if (hw->numFingers >= 3 && hw->num_mt_mask < 3) + nfingers = 3; + + return nfingers; +} + +static void +handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw) +{ + SynapticsParameters *para = &priv->synpara; + int action = 0; + int nfingers = hw->numFingers; + + /* if this is a clickpad, clickfinger handling is: + * one finger down: no action, this is a normal click + * two fingers down: F2_CLICK + * three fingers down: F3_CLICK + */ + + if (para->clickpad) + nfingers = clickpad_guess_clickfingers(priv, hw); + + switch (nfingers) { + 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 | BTN_EMULATED_FLAG; + break; + case 2: + hw->left = 0; + hw->middle = 1 | BTN_EMULATED_FLAG; + break; + case 3: + hw->left = 0; + hw->right = 1 | BTN_EMULATED_FLAG; + break; + } +} + +/* 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, + CARD32 now, 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, now, delay); + + /* If this is a clickpad and the user clicks in a soft button area, press + * the soft button instead. */ + if (para->clickpad) { + /* hw->left is down, but no other buttons were already down */ + if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) { + /* If the finger down event is delayed, the x and y + * coordinates are stale so we delay processing the click */ + if (hw->z < para->finger_low) { + hw->left = 0; + goto out; + } + if (is_inside_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + priv->clickpad_click_millis = now; + } + else if (hw->left) { + hw->left = (priv->lastButtons & 1) ? 1 : 0; + hw->middle = (priv->lastButtons & 2) ? 1 : 0; + hw->right = (priv->lastButtons & 4) ? 1 : 0; + } + } + + /* Fingers emulate other buttons. ClickFinger can only be + triggered on transition, when left is pressed + */ + if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1) + handle_clickfinger(priv, hw); + +out: + /* 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) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + valuator_mask_zero(priv->scroll_events_mask); + + if (priv->scroll.delta_y != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_vert, priv->scroll.delta_y); + priv->scroll.delta_y = 0; + } + if (priv->scroll.delta_x != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_horiz, priv->scroll.delta_x); + priv->scroll.delta_x = 0; + } + if (valuator_mask_num_valuators(priv->scroll_events_mask)) + xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask); +} + +static inline int +repeat_scrollbuttons(const InputInfoPtr pInfo, + const struct SynapticsHwState *hw, + int buttons, CARD32 now, int delay) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + int repeat_delay, timeleft; + int rep_buttons = 0; + + if (para->updown_button_repeat) + rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1)); + if (para->leftright_button_repeat) + rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1)); + + /* 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 = now + repeat_delay * 2; + } + } + else { + priv->repeatButtons = 0; + priv->nextRepeat = 0; + } + + if (priv->repeatButtons) { + timeleft = TIME_DIFF(priv->nextRepeat, now); + 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)); + if (id == 4) + priv->scroll.delta_y -= para->scroll_dist_vert; + else if (id == 5) + priv->scroll.delta_y += para->scroll_dist_vert; + else if (id == 6) + priv->scroll.delta_x -= para->scroll_dist_horiz; + else if (id == 7) + priv->scroll.delta_x += para->scroll_dist_horiz; + } + + priv->nextRepeat = now + repeat_delay; + delay = MIN(delay, repeat_delay); + } + } + + return delay; +} + +/* Update the open slots and number of active touches */ +static void +UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + int i; + + for (i = 0; i < hw->num_mt_mask; i++) { + if (hw->slot_state[i] == SLOTSTATE_OPEN) { + priv->open_slots[priv->num_active_touches] = i; + priv->num_active_touches++; + BUG_WARN(priv->num_active_touches > priv->num_slots); + } + else if (hw->slot_state[i] == SLOTSTATE_CLOSE) { + Bool found = FALSE; + int j; + + for (j = 0; j < priv->num_active_touches - 1; j++) { + if (priv->open_slots[j] == i) + found = TRUE; + + if (found) + priv->open_slots[j] = priv->open_slots[j + 1]; + } + + BUG_WARN(priv->num_active_touches == 0); + if (priv->num_active_touches > 0) + priv->num_active_touches--; + } + } + + SynapticsResetTouchHwState(hw, FALSE); +} + +static void +filter_jitter(SynapticsPrivate * priv, int *x, int *y) +{ + SynapticsParameters *para = &priv->synpara; + + priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x); + priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y); + *x = priv->hyst_center_x; + *y = priv->hyst_center_y; +} + +static void +reset_hw_state(struct SynapticsHwState *hw) +{ + hw->x = 0; + hw->y = 0; + hw->z = 0; + hw->numFingers = 0; + hw->fingerWidth = 0; +} + +/* + * 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. + * + * from_timer denotes if HandleState was triggered from a timer (e.g. to + * generate fake motion events, or for the tap-to-click state machine), rather + * than from having received a motion event. + */ +static int +HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now, + Bool from_timer) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + enum FingerState finger = FS_UNTOUCHED; + int dx = 0, dy = 0, buttons, id; + enum EdgeType edge = NO_EDGE; + int change; + int double_click = FALSE; + int delay = 1000000000; + int timeleft; + Bool inside_active_area; + Bool using_cumulative_coords = FALSE; + Bool ignore_motion; + + /* We need both and x/y, the driver can't handle just one of the two + * yet. But since it's possible to hit a phys button on non-clickpads + * without ever getting motion data first, we must continue with 0/0 for + * that case. */ + if (hw->x == INT_MIN || hw->y == INT_MAX) { + if (para->clickpad) + return delay; + else if (hw->left || hw->right || hw->middle) { + hw->x = (hw->x == INT_MIN) ? 0 : hw->x; + hw->y = (hw->y == INT_MIN) ? 0 : hw->y; + } + } + + /* If a physical button is pressed on a clickpad or a two-finger scrolling + * is ongoing, use cumulative relative touch movements for motion */ + if (para->clickpad && + ((priv->lastButtons & 7) || + (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) && + priv->last_button_area != TOP_BUTTON_AREA) { + hw->x = hw->cumulative_dx; + hw->y = hw->cumulative_dy; + using_cumulative_coords = TRUE; + } + + /* 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. */ + filter_jitter(priv, &hw->x, &hw->y); + + inside_active_area = is_inside_active_area(priv, hw->x, hw->y); + + /* Ignore motion *starting* inside softbuttonareas */ + if (priv->finger_state < FS_TOUCHED) + priv->last_button_area = current_button_area(para, hw->x, hw->y); + /* If we already have a finger down, clear last_button_area if it goes + outside of the softbuttonareas */ + else if (priv->last_button_area != NO_BUTTON_AREA && + current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA) + priv->last_button_area = NO_BUTTON_AREA; + + ignore_motion = para->touchpad_off == TOUCHPAD_OFF || + (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA); + + /* these two just update hw->left, right, etc. */ + update_hw_button_state(pInfo, hw, now, &delay); + if (priv->has_scrollbuttons) + double_click = adjust_state_from_scrollbuttons(pInfo, hw); + + /* Ignore motion the first X ms after a clickpad click */ + if (priv->clickpad_click_millis) { + if(TIME_DIFF(priv->clickpad_click_millis + + para->clickpad_ignore_motion_time, now) > 0) + ignore_motion = TRUE; + else + priv->clickpad_click_millis = 0; + } + + /* 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) + reset_hw_state(hw); + + /* no edge or finger detection outside of area */ + if (inside_active_area) { + edge = edge_detection(priv, hw->x, hw->y); + if (!from_timer) + finger = SynapticsDetectFinger(priv, hw); + else + finger = priv->finger_state; + } + + /* 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, now, 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 >= FS_TOUCHED)); + 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. + */ +#ifndef NO_DRIVER_SCALING + ScaleCoordinates(priv, hw); +#endif + } + + dx = dy = 0; + + 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 && priv->tap_button_state == TBS_BUTTON_DOWN) + buttons |= 1 << (priv->tap_button - 1); + + /* Post events */ + if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion) + 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); + } + + if (priv->has_scrollbuttons) + delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay); + + /* Process scroll events only if coordinates are + * in the Synaptics Area + */ + if (inside_active_area && + (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) { + post_scroll_events(pInfo); + priv->scroll.last_millis = hw->millis; + } + + if (double_click) { + post_button_click(pInfo, 1); + post_button_click(pInfo, 1); + } + + UpdateTouchState(pInfo, hw); + + /* 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) +{ + DBG(3, "SwitchMode called\n"); + + return XI_BadMode; +} + +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; +} + +#ifndef NO_DRIVER_SCALING +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; + } +} +#endif --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/101_resolution_detect_option.patch/src/synapticsstr.h +++ xserver-xorg-input-synaptics-1.9.1/.pc/101_resolution_detect_option.patch/src/synapticsstr.h @@ -0,0 +1,334 @@ +/* + * 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. + */ + +#ifndef _SYNAPTICSSTR_H_ +#define _SYNAPTICSSTR_H_ + +#include "synproto.h" + +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 18 +#define LogMessageVerbSigSafe xf86MsgVerb +#endif + +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) > 19 +#define NO_DRIVER_SCALING 1 +#elif GET_ABI_MAJOR(ABI_XINPUT_VERSION) == 19 && GET_ABI_MINOR(ABI_XINPUT_VERSION) >= 2 +/* as of 19.2, the server takes device resolution into account when scaling + relative events from abs device, so we must not scale in synaptics. */ +#define NO_DRIVER_SCALING 1 +#endif + +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 23 +#define HAVE_THREADED_INPUT 1 +#endif + +#ifdef DBG +#undef DBG +#endif + +#ifdef DEBUG +#define DBG(verb, ...) \ + xf86MsgVerb(X_INFO, verb, __VA_ARGS__) +#else +#define DBG(verb, msg, ...) /* */ +#endif + +/****************************************************************************** + * Definitions + * structs, typedefs, #defines, enums + *****************************************************************************/ +#define SYNAPTICS_MOVE_HISTORY 5 +#define SYNAPTICS_MAX_TOUCHES 10 +#define SYN_MAX_BUTTONS 12 /* Max number of mouse buttons */ + +/* Minimum and maximum values for scroll_button_repeat */ +#define SBR_MIN 10 +#define SBR_MAX 1000 + +enum OffState { + TOUCHPAD_ON = 0, + TOUCHPAD_OFF = 1, + TOUCHPAD_TAP_OFF = 2, +}; + +enum TapEvent { + RT_TAP = 0, /* Right top corner */ + RB_TAP, /* Right bottom corner */ + LT_TAP, /* Left top corner */ + LB_TAP, /* Left bottom corner */ + F1_TAP, /* Non-corner tap, one finger */ + F2_TAP, /* Non-corner tap, two fingers */ + F3_TAP, /* Non-corner tap, three fingers */ + MAX_TAP +}; + +enum ClickFingerEvent { + F1_CLICK1 = 0, /* Click left, one finger */ + F2_CLICK1, /* Click left, two fingers */ + F3_CLICK1, /* Click left, three fingers */ + MAX_CLICK +}; + + +typedef struct _SynapticsMoveHist { + int x, y; + CARD32 millis; +} SynapticsMoveHistRec; + +typedef struct _SynapticsTouchAxis { + const char *label; + int min; + int max; + int res; +} SynapticsTouchAxisRec; + +enum FingerState { /* Note! The order matters. Compared with < operator. */ + FS_BLOCKED = -1, + FS_UNTOUCHED = 0, /* this is 0 so it's the initialized value. */ + FS_TOUCHED = 1, + FS_PRESSED = 2, +}; + +enum MovingState { + MS_FALSE, + MS_TOUCHPAD_RELATIVE, +}; + +enum MidButtonEmulation { + MBE_OFF, /* No button pressed */ + MBE_LEFT, /* Left button pressed, waiting for right button or timeout */ + MBE_RIGHT, /* Right button pressed, waiting for left button or timeout */ + MBE_MID, /* Left and right buttons pressed, waiting for both buttons + to be released */ + MBE_TIMEOUT, /* Waiting for both buttons to be released. */ + MBE_LEFT_CLICK, /* Emulate left button click. */ + MBE_RIGHT_CLICK, /* Emulate right button click. */ +}; + +/* See docs/tapndrag.dia for a state machine diagram */ +enum TapState { + TS_START, /* No tap/drag in progress */ + TS_1, /* After first touch */ + TS_MOVE, /* Pointer movement enabled */ + TS_2A, /* After first release */ + TS_2B, /* After second/third/... release */ + TS_SINGLETAP, /* After timeout after first release */ + TS_3, /* After second touch */ + TS_DRAG, /* Pointer drag enabled */ + TS_4, /* After release when "locked drags" enabled */ + TS_5, /* After touch when "locked drags" enabled */ + TS_CLICKPAD_MOVE, /* After left button press on a clickpad */ +}; + +enum TapButtonState { + TBS_BUTTON_UP, /* "Virtual tap button" is up */ + TBS_BUTTON_DOWN, /* "Virtual tap button" is down */ +}; + +enum TouchpadModel { + MODEL_UNKNOWN = 0, + MODEL_SYNAPTICS, + MODEL_ALPS, + MODEL_APPLETOUCH, + MODEL_ELANTECH, + MODEL_UNIBODY_MACBOOK +}; + +enum SoftButtonAreas { + NO_BUTTON_AREA = -1, + BOTTOM_BUTTON_AREA = 0, + BOTTOM_RIGHT_BUTTON_AREA = 0, + BOTTOM_MIDDLE_BUTTON_AREA = 1, + TOP_BUTTON_AREA = 2, + TOP_RIGHT_BUTTON_AREA = 2, + TOP_MIDDLE_BUTTON_AREA = 3 +}; + +enum SoftButtonAreaEdges { + LEFT = 0, + RIGHT = 1, + TOP = 2, + BOTTOM = 3 +}; + +typedef struct _SynapticsParameters { + /* Parameter data */ + int left_edge, right_edge, top_edge, bottom_edge; /* edge coordinates absolute */ + int finger_low, finger_high, finger_press; /* finger detection values in Z-values */ + int tap_time; + int tap_move; /* max. tapping time and movement in packets and coord. */ + int single_tap_timeout; /* timeout to recognize a single tap */ + int tap_time_2; /* max. tapping time for double taps */ + int click_time; /* The duration of a single click */ + Bool clickpad; /* Device is a has integrated buttons */ + Bool has_secondary_buttons; /* Device has a top soft-button area */ + int clickpad_ignore_motion_time; /* Ignore motion for X ms after a click */ + int emulate_mid_button_time; /* Max time between left and right button presses to + emulate a middle button press. */ + int emulate_twofinger_z; /* pressure threshold to emulate two finger touch (for Alps) */ + int emulate_twofinger_w; /* Finger width threshold to emulate two finger touch */ + int scroll_dist_vert; /* Scrolling distance in absolute coordinates */ + int scroll_dist_horiz; /* Scrolling distance in absolute coordinates */ + Bool scroll_edge_vert; /* Enable/disable vertical scrolling on right edge */ + Bool scroll_edge_horiz; /* Enable/disable horizontal scrolling on left edge */ + Bool scroll_edge_corner; /* Enable/disable continuous edge scrolling when in the corner */ + Bool scroll_twofinger_vert; /* Enable/disable vertical two-finger scrolling */ + Bool scroll_twofinger_horiz; /* Enable/disable horizontal two-finger scrolling */ + double min_speed, max_speed, accl; /* movement parameters */ + + Bool updown_button_scrolling; /* Up/Down-Button scrolling or middle/double-click */ + Bool leftright_button_scrolling; /* Left/right-button scrolling, or two lots of middle button */ + Bool updown_button_repeat; /* If up/down button being used to scroll, auto-repeat? */ + Bool leftright_button_repeat; /* If left/right button being used to scroll, auto-repeat? */ + int scroll_button_repeat; /* time, in milliseconds, between scroll events being + * sent when holding down scroll buttons */ + int touchpad_off; /* Switches the touchpad off + * 0 : Not off + * 1 : Off + * 2 : Only tapping and scrolling off + */ + Bool locked_drags; /* Enable locked drags */ + int locked_drag_time; /* timeout for locked drags */ + int tap_action[MAX_TAP]; /* Button to report on tap events */ + int click_action[MAX_CLICK]; /* Button to report on click with fingers */ + Bool circular_scrolling; /* Enable circular scrolling */ + double scroll_dist_circ; /* Scrolling angle radians */ + int circular_trigger; /* Trigger area for circular scrolling */ + Bool circular_pad; /* Edge has an oval or circular shape */ + Bool palm_detect; /* Enable Palm Detection */ + int palm_min_width; /* Palm detection width */ + int palm_min_z; /* Palm detection depth */ + double coasting_speed; /* Coasting threshold scrolling speed in scrolls/s */ + double coasting_friction; /* Number of scrolls per second per second to change coasting speed */ + int press_motion_min_z; /* finger pressure at which minimum pressure motion factor is applied */ + int press_motion_max_z; /* finger pressure at which maximum pressure motion factor is applied */ + double press_motion_min_factor; /* factor applied on speed when finger pressure is at minimum */ + double press_motion_max_factor; /* factor applied on speed when finger pressure is at minimum */ + Bool grab_event_device; /* grab event device for exclusive use? */ + Bool tap_and_drag_gesture; /* Switches the tap-and-drag gesture on/off */ + unsigned int resolution_horiz; /* horizontal resolution of touchpad in units/mm */ + unsigned int resolution_vert; /* vertical resolution of touchpad in units/mm */ + int area_left_edge, area_right_edge, area_top_edge, area_bottom_edge; /* area coordinates absolute */ + int softbutton_areas[4][4]; /* soft button area coordinates, 0 => right, 1 => middle , 2 => secondary right, 3 => secondary middle button */ + int hyst_x, hyst_y; /* x and y width of hysteresis box */ + + int maxDeltaMM; /* maximum delta movement (vector length) in mm */ +} SynapticsParameters; + +struct _SynapticsPrivateRec { + SynapticsParameters synpara; /* Default parameter settings, read from + the X config file */ + struct SynapticsProtocolOperations *proto_ops; + void *proto_data; /* protocol-specific data */ + + struct SynapticsHwState *hwState; + + const char *device; /* device node */ + CARD32 timer_time; /* when timer last fired */ + OsTimerPtr timer; /* for up/down-button repeat, tap processing, etc */ + + struct CommData comm; + + struct SynapticsHwState *local_hw_state; /* used in place of local hw state variables */ + + SynapticsMoveHistRec move_hist[SYNAPTICS_MOVE_HISTORY]; /* movement history */ + int hist_index; /* Last added entry in move_hist[] */ + int hyst_center_x; /* center x of hysteresis */ + int hyst_center_y; /* center y of hysteresis */ + struct { + int last_x; /* last x-scroll position */ + int last_y; /* last y-scroll position */ + double delta_x; /* accumulated horiz scroll delta */ + double delta_y; /* accumulated vert scroll delta */ + double last_a; /* last angle-scroll position */ + CARD32 last_millis; /* time last scroll event posted */ + double coast_speed_x; /* Horizontal coasting speed in scrolls/s */ + double coast_speed_y; /* Vertical coasting speed in scrolls/s */ + double coast_delta_x; /* Accumulated horizontal coast delta */ + double coast_delta_y; /* Accumulated vertical coast delta */ + int packets_this_scroll; /* Events received for this scroll */ + } scroll; + int count_packet_finger; /* packet counter with finger on the touchpad */ + int button_delay_millis; /* button delay for 3rd button emulation */ + Bool prev_up; /* Previous up button value, for double click emulation */ + enum FingerState finger_state; /* previous finger state */ + CARD32 last_motion_millis; /* time of the last motion */ + enum SoftButtonAreas last_button_area; /* Last button area we were in */ + int clickpad_click_millis; /* Time of last clickpad click */ + + enum TapState tap_state; /* State of tap processing */ + int tap_max_fingers; /* Max number of fingers seen since entering start state */ + int tap_button; /* Which button started the tap processing */ + enum TapButtonState tap_button_state; /* Current tap action */ + SynapticsMoveHistRec touch_on; /* data when the touchpad is touched/released */ + + enum MovingState moving_state; /* previous moving state */ + Bool vert_scroll_edge_on; /* Keeps track of currently active scroll modes */ + Bool horiz_scroll_edge_on; /* Keeps track of currently active scroll modes */ + Bool vert_scroll_twofinger_on; /* Keeps track of currently active scroll modes */ + Bool horiz_scroll_twofinger_on; /* Keeps track of currently active scroll modes */ + Bool circ_scroll_on; /* Keeps track of currently active scroll modes */ + Bool circ_scroll_vert; /* True: Generate vertical scroll events + False: Generate horizontal events */ + enum MidButtonEmulation mid_emu_state; /* emulated 3rd button */ + int repeatButtons; /* buttons for repeat */ + int nextRepeat; /* Time when to trigger next auto repeat event */ + int lastButtons; /* last state of the buttons */ + int prev_z; /* previous z value, for palm detection */ + int prevFingers; /* previous numFingers, for transition detection */ + int avg_width; /* weighted average of previous fingerWidth values */ +#ifndef NO_DRIVER_SCALING + double horiz_coeff; /* normalization factor for x coordintes */ + double vert_coeff; /* normalization factor for y coordintes */ +#endif + + int minx, maxx, miny, maxy; /* min/max dimensions as detected */ + int minp, maxp, minw, maxw; /* min/max pressure and finger width as detected */ + int resx, resy; /* resolution of coordinates as detected in units/mm */ + Bool has_left; /* left button detected for this device */ + Bool has_right; /* right button detected for this device */ + Bool has_middle; /* middle button detected for this device */ + Bool has_double; /* double click detected for this device */ + Bool has_triple; /* triple click detected for this device */ + Bool has_pressure; /* device reports pressure */ + Bool has_width; /* device reports finger width */ + Bool has_scrollbuttons; /* device has physical scrollbuttons */ + Bool has_semi_mt; /* device is only semi-multitouch capable */ + Bool has_mt_palm_detect; /* device reports per finger width and pressure */ + + enum TouchpadModel model; /* The detected model */ + unsigned short id_vendor; /* vendor id */ + unsigned short id_product; /* product id */ + + int scroll_axis_horiz; /* Horizontal smooth-scrolling axis */ + int scroll_axis_vert; /* Vertical smooth-scrolling axis */ + ValuatorMask *scroll_events_mask; /* ValuatorMask for smooth-scrolling */ + + Bool has_touch; /* Device has multitouch capabilities */ + int max_touches; /* Number of touches supported */ + int num_mt_axes; /* Number of multitouch axes other than X, Y */ + SynapticsTouchAxisRec *touch_axes; /* Touch axis information other than X, Y */ + int num_slots; /* Number of touch slots allocated */ + int *open_slots; /* Array of currently open touch slots */ + int num_active_touches; /* Number of active touches on device */ +}; + +#endif /* _SYNAPTICSSTR_H_ */ --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/101_resolution_detect_option.patch/tools/synclient.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/101_resolution_detect_option.patch/tools/synclient.c @@ -0,0 +1,523 @@ +/* + * Copyright © 2002-2005,2007 Peter Osterlund + * + * 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: + * Peter Osterlund (petero2@telia.com) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "synaptics-properties.h" + +#ifndef XATOM_FLOAT +#define XATOM_FLOAT "FLOAT" +#endif + +#define SYN_MAX_BUTTONS 12 +#define SBR_MIN 10 +#define SBR_MAX 1000 + +union flong { /* Xlibs 64-bit property handling madness */ + long l; + float f; +}; + +enum ParaType { + PT_INT, + PT_BOOL, + PT_DOUBLE +}; + +struct Parameter { + char *name; /* Name of parameter */ + enum ParaType type; /* Type of parameter */ + double min_val; /* Minimum allowed value */ + double max_val; /* Maximum allowed value */ + char *prop_name; /* Property name */ + int prop_format; /* Property format (0 for floats) */ + int prop_offset; /* Offset inside property */ +}; + +static struct Parameter params[] = { + {"LeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 0}, + {"RightEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 1}, + {"TopEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 2}, + {"BottomEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 3}, + {"FingerLow", PT_INT, 0, 255, SYNAPTICS_PROP_FINGER, 32, 0}, + {"FingerHigh", PT_INT, 0, 255, SYNAPTICS_PROP_FINGER, 32, 1}, + {"MaxTapTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_TIME, 32, 0}, + {"MaxTapMove", PT_INT, 0, 2000, SYNAPTICS_PROP_TAP_MOVE, 32, 0}, + {"MaxDoubleTapTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 1}, + {"SingleTapTimeout", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 0}, + {"ClickTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 2}, + {"EmulateMidButtonTime", PT_INT, 0, 1000, SYNAPTICS_PROP_MIDDLE_TIMEOUT,32, 0}, + {"EmulateTwoFingerMinZ", PT_INT, 0, 1000, SYNAPTICS_PROP_TWOFINGER_PRESSURE, 32, 0}, + {"EmulateTwoFingerMinW", PT_INT, 0, 15, SYNAPTICS_PROP_TWOFINGER_WIDTH, 32, 0}, + {"VertScrollDelta", PT_INT, -1000, 1000, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 0}, + {"HorizScrollDelta", PT_INT, -1000, 1000, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 1}, + {"VertEdgeScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 0}, + {"HorizEdgeScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 1}, + {"CornerCoasting", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 2}, + {"VertTwoFingerScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 0}, + {"HorizTwoFingerScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 1}, + {"MinSpeed", PT_DOUBLE, 0, 255.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 0}, + {"MaxSpeed", PT_DOUBLE, 0, 255.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 1}, + {"AccelFactor", PT_DOUBLE, 0, 1.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 2}, + {"UpDownScrolling", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING, 8, 0}, + {"LeftRightScrolling", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING, 8, 1}, + {"UpDownScrollRepeat", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT, 8, 0}, + {"LeftRightScrollRepeat", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT, 8, 1}, + {"ScrollButtonRepeat", PT_INT, SBR_MIN , SBR_MAX, SYNAPTICS_PROP_BUTTONSCROLLING_TIME, 32, 0}, + {"TouchpadOff", PT_INT, 0, 2, SYNAPTICS_PROP_OFF, 8, 0}, + {"LockedDrags", PT_BOOL, 0, 1, SYNAPTICS_PROP_LOCKED_DRAGS, 8, 0}, + {"LockedDragTimeout", PT_INT, 0, 30000, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT, 32, 0}, + {"RTCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 0}, + {"RBCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 1}, + {"LTCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 2}, + {"LBCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 3}, + {"TapButton1", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 4}, + {"TapButton2", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 5}, + {"TapButton3", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 6}, + {"ClickFinger1", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 0}, + {"ClickFinger2", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 1}, + {"ClickFinger3", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 2}, + {"CircularScrolling", PT_BOOL, 0, 1, SYNAPTICS_PROP_CIRCULAR_SCROLLING, 8, 0}, + {"CircScrollDelta", PT_DOUBLE, .01, 3, SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST, 0 /* float */, 0}, + {"CircScrollTrigger", PT_INT, 0, 8, SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER, 8, 0}, + {"CircularPad", PT_BOOL, 0, 1, SYNAPTICS_PROP_CIRCULAR_PAD, 8, 0}, + {"PalmDetect", PT_BOOL, 0, 1, SYNAPTICS_PROP_PALM_DETECT, 8, 0}, + {"PalmMinWidth", PT_INT, 0, 15, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 0}, + {"PalmMinZ", PT_INT, 0, 255, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 1}, + {"CoastingSpeed", PT_DOUBLE, 0, 255, SYNAPTICS_PROP_COASTING_SPEED, 0 /* float*/, 0}, + {"CoastingFriction", PT_DOUBLE, 0, 255, SYNAPTICS_PROP_COASTING_SPEED, 0 /* float*/, 1}, + {"PressureMotionMinZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 0}, + {"PressureMotionMaxZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 1}, + {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 0}, + {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 1}, + {"GrabEventDevice", PT_BOOL, 0, 1, SYNAPTICS_PROP_GRAB, 8, 0}, + {"TapAndDragGesture", PT_BOOL, 0, 1, SYNAPTICS_PROP_GESTURES, 8, 0}, + {"AreaLeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 0}, + {"AreaRightEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 1}, + {"AreaTopEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 2}, + {"AreaBottomEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 3}, + {"HorizHysteresis", PT_INT, 0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 0}, + {"VertHysteresis", PT_INT, 0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 1}, + {"ClickPad", PT_BOOL, 0, 1, SYNAPTICS_PROP_CLICKPAD, 8, 0}, + {"RightButtonAreaLeft", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 0}, + {"RightButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 1}, + {"RightButtonAreaTop", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 2}, + {"RightButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 3}, + {"MiddleButtonAreaLeft", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 4}, + {"MiddleButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 5}, + {"MiddleButtonAreaTop", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 6}, + {"MiddleButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 7}, + { NULL, 0, 0, 0, 0 } +}; + +static double +parse_cmd(char *cmd, struct Parameter **par) +{ + char *eqp = strchr(cmd, '='); + + *par = NULL; + + if (eqp) { + int j; + int found = 0; + + *eqp = 0; + for (j = 0; params[j].name; j++) { + if (strcasecmp(cmd, params[j].name) == 0) { + found = 1; + break; + } + } + if (found) { + double val = atof(&eqp[1]); + + *par = ¶ms[j]; + + if (val < (*par)->min_val) + val = (*par)->min_val; + if (val > (*par)->max_val) + val = (*par)->max_val; + + return val; + } + else { + printf("Unknown parameter %s\n", cmd); + } + } + else { + printf("Invalid command: %s\n", cmd); + } + + return 0; +} + +/** Init display connection or NULL on error */ +static Display * +dp_init() +{ + Display *dpy = NULL; + XExtensionVersion *v = NULL; + Atom touchpad_type = 0; + Atom synaptics_property = 0; + int error = 0; + + dpy = XOpenDisplay(NULL); + if (!dpy) { + fprintf(stderr, "Failed to connect to X Server.\n"); + error = 1; + goto unwind; + } + + v = XGetExtensionVersion(dpy, INAME); + if (!v->present || + (v->major_version * 1000 + v->minor_version) < + (XI_Add_DeviceProperties_Major * 1000 + + XI_Add_DeviceProperties_Minor)) { + fprintf(stderr, "X server supports X Input %d.%d. I need %d.%d.\n", + v->major_version, v->minor_version, + XI_Add_DeviceProperties_Major, XI_Add_DeviceProperties_Minor); + error = 1; + goto unwind; + } + + /* We know synaptics sets XI_TOUCHPAD for all the devices. */ + touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); + if (!touchpad_type) { + fprintf(stderr, "XI_TOUCHPAD not initialised.\n"); + error = 1; + goto unwind; + } + + synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True); + if (!synaptics_property) { + fprintf(stderr, "Couldn't find synaptics properties. No synaptics " + "driver loaded?\n"); + error = 1; + goto unwind; + } + + unwind: + XFree(v); + if (error && dpy) { + XCloseDisplay(dpy); + dpy = NULL; + } + return dpy; +} + +static XDevice * +dp_get_device(Display * dpy) +{ + XDevice *dev = NULL; + XDeviceInfo *info = NULL; + int ndevices = 0; + Atom touchpad_type = 0; + Atom synaptics_property = 0; + Atom *properties = NULL; + int nprops = 0; + int error = 0; + + touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); + synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True); + info = XListInputDevices(dpy, &ndevices); + + while (ndevices--) { + if (info[ndevices].type == touchpad_type) { + dev = XOpenDevice(dpy, info[ndevices].id); + if (!dev) { + fprintf(stderr, "Failed to open device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + properties = XListDeviceProperties(dpy, dev, &nprops); + if (!properties || !nprops) { + fprintf(stderr, "No properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + while (nprops--) { + if (properties[nprops] == synaptics_property) + break; + } + if (!nprops) { + fprintf(stderr, "No synaptics properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + break; /* Yay, device is suitable */ + } + } + + unwind: + XFree(properties); + XFreeDeviceList(info); + if (!dev) + fprintf(stderr, "Unable to find a synaptics device.\n"); + else if (error && dev) { + XCloseDevice(dpy, dev); + dev = NULL; + } + return dev; +} + +static void +dp_set_variables(Display * dpy, XDevice * dev, int argc, char *argv[], + int first_cmd) +{ + int i; + double val; + struct Parameter *par; + Atom prop, type, float_type; + int format; + unsigned char *data; + unsigned long nitems, bytes_after; + + union flong *f; + long *n; + char *b; + + float_type = XInternAtom(dpy, XATOM_FLOAT, True); + if (!float_type) + fprintf(stderr, "Float properties not available.\n"); + + for (i = first_cmd; i < argc; i++) { + val = parse_cmd(argv[i], &par); + if (!par) + continue; + + prop = XInternAtom(dpy, par->prop_name, True); + if (!prop) { + fprintf(stderr, "Property for '%s' not available. Skipping.\n", + par->name); + continue; + + } + + XGetDeviceProperty(dpy, dev, prop, 0, 1000, False, AnyPropertyType, + &type, &format, &nitems, &bytes_after, &data); + + if (type == None) { + fprintf(stderr, "Property for '%s' not available. Skipping.\n", + par->name); + continue; + } + + switch (par->prop_format) { + case 8: + if (format != par->prop_format || type != XA_INTEGER) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + b = (char *) data; + b[par->prop_offset] = rint(val); + break; + case 32: + if (format != par->prop_format || + (type != XA_INTEGER && type != XA_CARDINAL)) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + n = (long *) data; + n[par->prop_offset] = rint(val); + break; + case 0: /* float */ + if (!float_type) + continue; + if (format != 32 || type != float_type) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + f = (union flong *) data; + f[par->prop_offset].f = val; + break; + } + + XChangeDeviceProperty(dpy, dev, prop, type, format, + PropModeReplace, data, nitems); + XFlush(dpy); + } +} + +/* FIXME: horribly inefficient. */ +static void +dp_show_settings(Display * dpy, XDevice * dev) +{ + int j; + Atom a, type, float_type; + int format; + unsigned long nitems, bytes_after; + unsigned char *data; + int len; + + union flong *f; + long *i; + char *b; + + float_type = XInternAtom(dpy, XATOM_FLOAT, True); + if (!float_type) + fprintf(stderr, "Float properties not available.\n"); + + printf("Parameter settings:\n"); + for (j = 0; params[j].name; j++) { + struct Parameter *par = ¶ms[j]; + + a = XInternAtom(dpy, par->prop_name, True); + if (!a) + continue; + + len = + 1 + + ((par->prop_offset * (par->prop_format ? par->prop_format : 32) / + 8)) / 4; + + XGetDeviceProperty(dpy, dev, a, 0, len, False, + AnyPropertyType, &type, &format, + &nitems, &bytes_after, &data); + if (type == None) + continue; + + switch (par->prop_format) { + case 8: + if (format != par->prop_format || type != XA_INTEGER) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + + b = (char *) data; + printf(" %-23s = %d\n", par->name, b[par->prop_offset]); + break; + case 32: + if (format != par->prop_format || + (type != XA_INTEGER && type != XA_CARDINAL)) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + + i = (long *) data; + printf(" %-23s = %ld\n", par->name, i[par->prop_offset]); + break; + case 0: /* Float */ + if (!float_type) + continue; + if (format != 32 || type != float_type) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + + f = (union flong *) data; + printf(" %-23s = %g\n", par->name, f[par->prop_offset].f); + break; + } + + XFree(data); + } +} + +static void +usage(void) +{ + fprintf(stderr, "Usage: synclient [-h] [-l] [-V] [-?] [var1=value1 [var2=value2] ...]\n"); + fprintf(stderr, " -l List current user settings\n"); + fprintf(stderr, " -V Print synclient version string and exit\n"); + fprintf(stderr, " -? Show this help message\n"); + fprintf(stderr, " var=value Set user parameter 'var' to 'value'.\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int c; + int dump_settings = 0; + int first_cmd; + + Display *dpy; + XDevice *dev; + + if (argc == 1) + dump_settings = 1; + + /* Parse command line parameters */ + while ((c = getopt(argc, argv, "lV?")) != -1) { + switch (c) { + case 'l': + dump_settings = 1; + break; + case 'V': + printf("%s\n", VERSION); + exit(0); + case '?': + default: + usage(); + } + } + + first_cmd = optind; + if (!dump_settings && first_cmd == argc) + usage(); + + dpy = dp_init(); + if (!dpy || !(dev = dp_get_device(dpy))) + return 1; + + dp_set_variables(dpy, dev, argc, argv, first_cmd); + if (dump_settings) + dp_show_settings(dpy, dev); + + XCloseDevice(dpy, dev); + XCloseDisplay(dpy); + + return 0; +} --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/103_enable_cornertapping.patch/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/103_enable_cornertapping.patch/src/synaptics.c @@ -0,0 +1,3249 @@ +/* + * 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-2012 Red Hat, Inc. + * Copyright © 2011 The Chromium OS Authors + * + * 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 + * Christian Thaeter + * Stefan Bethge + * Matthias Ihmig + * Alexei Gilchrist + * Jörg Bösner + * Hartwig Felger + * Peter Osterlund + * S. Lehner + * Stefan Gmeiner + * Henry Davies for the + * Linuxcare Inc. David Kennedy + * Fred Hucht + * Fedor P. Goncharov + * Simon Thum + * + * Trademarks are the property of their respective owners. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +enum EdgeType { + 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 +}; + +/* + * We expect to be receiving a steady 80 packets/sec (which gives 40 + * reports/sec with more than one finger on the pad, as Advanced Gesture Mode + * requires two PS/2 packets per report). Instead of a random scattering of + * magic 13 and 20ms numbers scattered throughout the driver, introduce + * POLL_MS as 14ms, which is slightly less than 80Hz. 13ms is closer to + * 80Hz, but if the kernel event reporting was even slightly delayed, + * we would produce synthetic motion followed immediately by genuine + * motion, so use 14. + * + * We use this to call back at a constant rate to at least produce the + * illusion of smooth motion. It works a lot better than you'd expect. +*/ +#define POLL_MS 14 + +#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 + +#define INPUT_BUFFER_SIZE 200 + +/***************************************************************************** + * Forward declaration + ****************************************************************************/ +static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static Bool DeviceControl(DeviceIntPtr, int); +static void ReadInput(InputInfoPtr); +static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now, + Bool from_timer); +static int ControlProc(InputInfoPtr, xDeviceCtl *); +static int SwitchMode(ClientPtr, DeviceIntPtr, int); +static int DeviceInit(DeviceIntPtr); +static int DeviceOn(DeviceIntPtr); +static int DeviceOff(DeviceIntPtr); +static int DeviceClose(DeviceIntPtr); +static Bool QueryHardware(InputInfoPtr); +static void ReadDevDimensions(InputInfoPtr); +#ifndef NO_DRIVER_SCALING +static void ScaleCoordinates(SynapticsPrivate * priv, + struct SynapticsHwState *hw); +static void CalculateScalingCoeffs(SynapticsPrivate * priv); +#endif +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, + NULL, +#ifdef XI86_DRV_CAP_SERVER_FD + XI86_DRV_CAP_SERVER_FD +#endif +}; + +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 + ****************************************************************************/ +static inline void +SynapticsCloseFd(InputInfoPtr pInfo) +{ + if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) { + xf86CloseSerial(pInfo->fd); + pInfo->fd = -1; + } +} + +/** + * 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 = 255; + + 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 = 15; + + xf86IDrvMsg(pInfo, X_PROBED, + "invalid finger width range. defaulting to %d - %d\n", + priv->minw, priv->maxw); + } +} + +static Bool +SetDeviceAndProtocol(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = pInfo->private; + char *proto, *device; + int i; + + proto = xf86SetStrOption(pInfo->options, "Protocol", NULL); + device = xf86SetStrOption(pInfo->options, "Device", NULL); + + /* If proto is auto-dev, unset and let the code do the rest */ + if (proto && !strcmp(proto, "auto-dev")) { + free(proto); + proto = 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; + + return (priv->proto_ops != 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 || + priv->model == MODEL_UNIBODY_MACBOOK) { + 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; +} + +static void +calculate_tap_hysteresis(SynapticsPrivate * priv, int range, + int *fingerLow, int *fingerHigh) +{ + switch (priv->model) { + case MODEL_ELANTECH: + /* All Elantech touchpads don't need the Z filtering to get the + * number of fingers correctly. See Documentation/elantech.txt + * in the kernel. + */ + *fingerLow = priv->minp + 1; + *fingerHigh = priv->minp + 1; + break; + case MODEL_UNIBODY_MACBOOK: + *fingerLow = 70; + *fingerHigh = 75; + break; + default: + *fingerLow = priv->minp + range * (25.0 / 256); + *fingerHigh = priv->minp + range * (30.0 / 256); + break; + } +} + +/* 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; + double percent = xf86CheckPercentOption(options, optname, -1); + + if (percent >= 0.0) { + percent = xf86SetPercentOption(options, optname, -1); + result = percent / 100.0 * range + offset; + } else + result = xf86SetIntOption(options, optname, default_value); + + return result; +} + +Bool +SynapticsIsSoftButtonAreasValid(int *values) +{ + Bool right_disabled = FALSE; + Bool middle_disabled = FALSE; + + enum { + /* right button left, right, top, bottom */ + RBL = 0, + RBR = 1, + RBT = 2, + RBB = 3, + /* middle button left, right, top, bottom */ + MBL = 4, + MBR = 5, + MBT = 6, + MBB = 7, + }; + + /* Check right button area */ + if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) || + (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB]))) + return FALSE; + + /* Check middle button area */ + if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) || + (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB]))) + return FALSE; + + if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0) + right_disabled = TRUE; + + if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0) + middle_disabled = TRUE; + + if (!right_disabled && + ((values[RBL] && values[RBL] == values[RBR]) || + (values[RBT] && values[RBT] == values[RBB]))) + return FALSE; + + if (!middle_disabled && + ((values[MBL] && values[MBL] == values[MBR]) || + (values[MBT] && values[MBT] == values[MBB]))) + return FALSE; + + /* Check for overlapping button areas */ + if (!right_disabled && !middle_disabled) { + int right_left = values[RBL] ? values[RBL] : INT_MIN; + int right_right = values[RBR] ? values[RBR] : INT_MAX; + int right_top = values[RBT] ? values[RBT] : INT_MIN; + int right_bottom = values[RBB] ? values[RBB] : INT_MAX; + int middle_left = values[MBL] ? values[MBL] : INT_MIN; + int middle_right = values[MBR] ? values[MBR] : INT_MAX; + int middle_top = values[MBT] ? values[MBT] : INT_MIN; + int middle_bottom = values[MBB] ? values[MBB] : INT_MAX; + + /* If areas overlap in the Y axis */ + if ((right_bottom <= middle_bottom && right_bottom >= middle_top) || + (right_top <= middle_bottom && right_top >= middle_top)) { + /* Check for overlapping left edges */ + if ((right_left < middle_left && right_right > middle_left) || + (middle_left < right_left && middle_right > right_left)) + return FALSE; + + /* Check for overlapping right edges */ + if ((right_right > middle_right && right_left < middle_right) || + (middle_right > right_right && middle_left < right_right)) + return FALSE; + } + + /* If areas overlap in the X axis */ + if ((right_left >= middle_left && right_left <= middle_right) || + (right_right >= middle_left && right_right <= middle_right)) { + /* Check for overlapping top edges */ + if ((right_top < middle_top && right_bottom > middle_top) || + (middle_top < right_top && middle_bottom > right_top)) + return FALSE; + + /* Check for overlapping bottom edges */ + if ((right_bottom > middle_bottom && right_top < middle_bottom) || + (middle_bottom > right_bottom && middle_top < right_bottom)) + return FALSE; + } + } + + return TRUE; +} + +static void +set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset) +{ + SynapticsPrivate *priv = pInfo->private; + SynapticsParameters *pars = &priv->synpara; + int values[8]; + int in_percent = 0; /* bitmask for which ones are in % */ + char *option_string; + char *next_num; + char *end_str; + int i; + int width, height; + + if (!pars->clickpad) + return; + + option_string = xf86SetStrOption(pInfo->options, option_name, NULL); + if (!option_string) + return; + + next_num = option_string; + + for (i = 0; i < 8 && *next_num != '\0'; i++) { + long int value = strtol(next_num, &end_str, 0); + + if (value > INT_MAX || value < -INT_MAX) + goto fail; + + values[i] = value; + + if (next_num != end_str) { + if (*end_str == '%') { + in_percent |= 1 << i; + end_str++; + } + next_num = end_str; + } + else + goto fail; + } + + if (i < 8 || *next_num != '\0') + goto fail; + + width = priv->maxx - priv->minx; + height = priv->maxy - priv->miny; + + for (i = 0; in_percent && i < 8; i++) { + int base, size; + + if ((in_percent & (1 << i)) == 0 || values[i] == 0) + continue; + + size = ((i % 4) < 2) ? width : height; + base = ((i % 4) < 2) ? priv->minx : priv->miny; + values[i] = base + size * values[i] / 100.0; + } + + if (!SynapticsIsSoftButtonAreasValid(values)) + goto fail; + + memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int)); + memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int)); + + free(option_string); + + return; + + fail: + xf86IDrvMsg(pInfo, X_ERROR, + "invalid %s value '%s', keeping defaults\n", + option_name, option_string); + free(option_string); +} + +static void +set_primary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA); +} + +static void +set_secondary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA); +} + +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 */ + double accelFactor; /* 1/pixels */ + int fingerLow, fingerHigh; /* pressure */ + int emulateTwoFingerMinZ; /* pressure */ + int emulateTwoFingerMinW; /* width */ + 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; + int middle_button_timeout; + int grab_event_device = 0; + const char *source; + + /* 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; + 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 + 1; + + calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh); + + /* scaling based on defaults and a pressure of 256 */ + emulateTwoFingerMinZ = priv->minp + range * (282.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 + 1; + + /* 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->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->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad); /* Probed */ + if (pars->clickpad) + pars->has_secondary_buttons = xf86SetBoolOption(opts, + "HasSecondarySoftButtons", + pars->has_secondary_buttons); + pars->clickpad_ignore_motion_time = 100; /* ms */ + /* middle mouse button emulation on a clickpad? nah, you're joking */ + middle_button_timeout = pars->clickpad ? 0 : 75; + pars->emulate_mid_button_time = + xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout); + pars->emulate_twofinger_z = + xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ); + pars->emulate_twofinger_w = + xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW); + pars->scroll_dist_vert = + xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta); + pars->scroll_dist_horiz = + xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta); + 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->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON); + + 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->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->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE); + + pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4); + pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7); + pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor); + 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); + + /* Only grab the device by default if it's not coming from a config + backend. This way we avoid the device being added twice and sending + duplicate events. + */ + source = xf86CheckStrOption(opts, "_source", NULL); + if (source == NULL || strncmp(source, "server/", 7) != 0) + grab_event_device = TRUE; + pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device); + + pars->tap_and_drag_gesture = + xf86SetBoolOption(opts, "TapAndDragGesture", TRUE); + pars->resolution_horiz = + xf86SetIntOption(opts, "HorizResolution", horizResolution); + pars->resolution_vert = + xf86SetIntOption(opts, "VertResolution", vertResolution); + if (pars->resolution_horiz <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid X resolution, using 1 instead.\n"); + pars->resolution_horiz = 1; + } + if (pars->resolution_vert <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid Y resolution, using 1 instead.\n"); + pars->resolution_vert = 1; + } + + /* Touchpad sampling rate is too low to detect all movements. + A user may lift one finger and put another one down within the same + EV_SYN or even between samplings so the driver doesn't notice at all. + + We limit the movement to 20 mm within one event, that is more than + recordings showed is needed (17mm on a T440). + */ + if (pars->resolution_horiz > 1 && + pars->resolution_vert > 1) + pars->maxDeltaMM = 20; + else { + /* on devices without resolution set the vector length to 0.25 of + the touchpad diagonal */ + pars->maxDeltaMM = diag * 0.25; + } + + + /* 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"); + } + + set_primary_softbutton_areas_option(pInfo); + if (pars->has_secondary_buttons) + set_secondary_softbutton_areas_option(pInfo); +} + +static double +SynapticsAccelerationProfile(DeviceIntPtr dev, + DeviceVelocityPtr vel, + double velocity, double thr, double 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; +} + +static int +SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + 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 */ + if (!SetDeviceAndProtocol(pInfo)) { + 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); + + set_default_parameters(pInfo); + +#ifndef NO_DRIVER_SCALING + CalculateScalingCoeffs(priv); +#endif + + + 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 (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + + return Success; + + SetupProc_fail: + SynapticsCloseFd(pInfo); + + if (priv->comm.buffer) + XisbFree(priv->comm.buffer); + 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); + if (priv && priv->scroll_events_mask) + valuator_mask_free(&priv->scroll_events_mask); + if (priv && priv->open_slots) + free(priv->open_slots); + 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 int +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 int +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)) + goto error; + + priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE); + if (!priv->comm.buffer) + goto error; + + xf86FlushInput(pInfo->fd); + + /* reinit the pad */ + if (!QueryHardware(pInfo)) + goto error; + + xf86AddEnabledDevice(pInfo); + dev->public.on = TRUE; + + return Success; + +error: + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + return !Success; +} + +static void +SynapticsReset(SynapticsPrivate * priv) +{ + int i; + + SynapticsResetHwState(priv->hwState); + SynapticsResetHwState(priv->local_hw_state); + SynapticsResetHwState(priv->comm.hwState); + + memset(priv->move_hist, 0, sizeof(priv->move_hist)); + priv->hyst_center_x = 0; + priv->hyst_center_y = 0; + memset(&priv->scroll, 0, sizeof(priv->scroll)); + priv->count_packet_finger = 0; + priv->finger_state = FS_UNTOUCHED; + priv->last_motion_millis = 0; + priv->clickpad_click_millis = 0; + priv->last_button_area = NO_BUTTON_AREA; + priv->tap_state = TS_START; + priv->tap_button = 0; + priv->tap_button_state = TBS_BUTTON_UP; + priv->moving_state = MS_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; + priv->circ_scroll_on = FALSE; + priv->circ_scroll_vert = FALSE; + priv->mid_emu_state = MBE_OFF; + priv->nextRepeat = 0; + priv->lastButtons = 0; + priv->prev_z = 0; + priv->prevFingers = 0; + priv->num_active_touches = 0; + + for (i = 0; i < priv->num_slots; i++) + priv->open_slots[i] = -1; +} + +static int +DeviceOff(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + Bool rc = Success; + + DBG(3, "Synaptics DeviceOff called\n"); + + if (pInfo->fd != -1) { + TimerCancel(priv->timer); + xf86RemoveEnabledDevice(pInfo); + SynapticsReset(priv); + + if (priv->proto_ops->DeviceOffHook && + !priv->proto_ops->DeviceOffHook(pInfo)) + rc = !Success; + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + } + dev->public.on = FALSE; + return rc; +} + +static int +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(priv->touch_axes); + priv->touch_axes = NULL; + SynapticsHwStateFree(&priv->hwState); + SynapticsHwStateFree(&priv->local_hw_state); + SynapticsHwStateFree(&priv->comm.hwState); + return RetValue; +} + +static void +InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv) +{ + int i; + + memset(labels, 0, nlabels * sizeof(Atom)); + switch (nlabels) { + default: + case 4: + labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL); + case 3: + labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL); + case 2: + labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y); + case 1: + labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X); + break; + } + + for (i = 0; i < priv->num_mt_axes; i++) { + SynapticsTouchAxisRec *axis = &priv->touch_axes[i]; + int axnum = nlabels - priv->num_mt_axes + i; + + labels[axnum] = XIGetKnownProperty(axis->label); + } +} + +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; + } +} + +static void +DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + if (!priv->has_touch) + return; + + priv->num_slots = + priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES; + + priv->open_slots = malloc(priv->num_slots * sizeof(int)); + if (!priv->open_slots) { + xf86IDrvMsg(pInfo, X_ERROR, + "failed to allocate open touch slots array\n"); + priv->has_touch = 0; + priv->num_slots = 0; + } +} + +static int +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; + int num_axes = 2; + Atom btn_labels[SYN_MAX_BUTTONS] = { 0 }; + Atom *axes_labels; + DeviceVelocityPtr pVel; + + num_axes += 2; + + num_axes += priv->num_mt_axes; + + axes_labels = calloc(num_axes, sizeof(Atom)); + if (!axes_labels) { + xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n"); + return !Success; + } + + InitAxesLabels(axes_labels, num_axes, priv); + InitButtonLabels(btn_labels, SYN_MAX_BUTTONS); + + 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, + btn_labels, + SynapticsCtrl, + GetMotionHistorySize(), num_axes, axes_labels); + + /* + * setup dix acceleration to match legacy synaptics settings, and + * etablish a device-specific profile to do stuff like pressure-related + * acceleration. + */ + 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, "(accel) MaxSpeed is now %.2f\n", + priv->synpara.max_speed); + xf86IDrvMsg(pInfo, X_CONFIG, "(accel) 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); + } + + /* X valuator */ + if (priv->minx < priv->maxx && priv->synpara.resolution_detect) { + min = priv->minx; + max = priv->maxx; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max, + priv->resx * 1000, 0, priv->resx * 1000, + Relative); + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ + if (priv->miny < priv->maxy && priv->synpara.resolution_detect) { + min = priv->miny; + max = priv->maxy; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max, + priv->resy * 1000, 0, priv->resy * 1000, + Relative); + xf86InitValuatorDefaults(dev, 1); + + xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_horiz = 2; + xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_vert = 3; + priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS); + if (!priv->scroll_events_mask) { + free(axes_labels); + return !Success; + } + + SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL, + priv->synpara.scroll_dist_horiz, 0); + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + priv->synpara.scroll_dist_vert, 0); + + DeviceInitTouch(dev, axes_labels); + + free(axes_labels); + + priv->hwState = SynapticsHwStateAlloc(priv); + if (!priv->hwState) + goto fail; + + priv->local_hw_state = SynapticsHwStateAlloc(priv); + if (!priv->local_hw_state) + goto fail; + + priv->comm.hwState = SynapticsHwStateAlloc(priv); + + InitDeviceProperties(pInfo); + XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL); + + SynapticsReset(priv); + + return Success; + + fail: + free(priv->local_hw_state); + free(priv->hwState); + free(priv->open_slots); + 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 enum EdgeType +circular_edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 enum EdgeType +edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 a finger is down, then it must have started inside the active_area, + allow the motion to complete using the entire area */ + if (priv->finger_state >= FS_TOUCHED) + return 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 Bool +is_inside_button_area(SynapticsParameters * para, int which, int x, int y) +{ + Bool inside_area = TRUE; + + if (para->softbutton_areas[which][LEFT] == 0 && + para->softbutton_areas[which][RIGHT] == 0 && + para->softbutton_areas[which][TOP] == 0 && + para->softbutton_areas[which][BOTTOM] == 0) + return FALSE; + + if (para->softbutton_areas[which][LEFT] && + x < para->softbutton_areas[which][LEFT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][RIGHT] && + x > para->softbutton_areas[which][RIGHT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][TOP] && + y < para->softbutton_areas[which][TOP]) + inside_area = FALSE; + else if (para->softbutton_areas[which][BOTTOM] && + y > para->softbutton_areas[which][BOTTOM]) + inside_area = FALSE; + + return inside_area; +} + +static Bool +is_inside_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset, + int x, int y) +{ + Bool inside_area = TRUE; + Bool right_valid, middle_valid; + int top, bottom; + + /* We don't have a left button area, so we only check the y axis */ + right_valid = para->softbutton_areas[offset][TOP] || + para->softbutton_areas[offset][BOTTOM]; + middle_valid = para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset + 1][BOTTOM]; + + if (!right_valid && !middle_valid) + return FALSE; + + /* Check both buttons are horizontally aligned */ + if (right_valid && middle_valid && ( + para->softbutton_areas[offset][TOP] != + para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset][BOTTOM] != + para->softbutton_areas[offset + 1][BOTTOM])) + return FALSE; + + if (right_valid) { + top = para->softbutton_areas[offset][TOP]; + bottom = para->softbutton_areas[offset][BOTTOM]; + } + else { + top = para->softbutton_areas[offset + 1][TOP]; + bottom = para->softbutton_areas[offset + 1][BOTTOM]; + } + + if (top && y < top) + inside_area = FALSE; + else if (bottom && y > bottom) + inside_area = FALSE; + + return inside_area; +} + +static enum SoftButtonAreas +current_button_area(SynapticsParameters * para, int x, int y) +{ + if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y)) + return BOTTOM_BUTTON_AREA; + else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y)) + return TOP_BUTTON_AREA; + else + return NO_BUTTON_AREA; +} + +static CARD32 +timerFunc(OsTimerPtr timer, CARD32 now, pointer arg) +{ + InputInfoPtr pInfo = arg; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + struct SynapticsHwState *hw = priv->local_hw_state; + int delay; +#if !HAVE_THREADED_INPUT + int sigstate = xf86BlockSIGIO(); +#else + input_lock(); +#endif + + priv->hwState->millis += now - priv->timer_time; + SynapticsCopyHwState(hw, priv->hwState); + SynapticsResetTouchHwState(hw, FALSE); + delay = HandleState(pInfo, hw, hw->millis, TRUE); + + priv->timer_time = now; + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + +#if !HAVE_THREADED_INPUT + xf86UnblockSIGIO(sigstate); +#else + input_unlock(); +#endif + + 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 = priv->local_hw_state; + int delay = 0; + Bool newDelay = FALSE; + + SynapticsResetTouchHwState(hw, FALSE); + + while (SynapticsGetHwState(pInfo, priv, hw)) { + /* Semi-mt device touch slots do not track touches. When there is a + * change in the number of touches, we must disregard the temporary + * motion changes. */ + if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) { + hw->cumulative_dx = priv->hwState->cumulative_dx; + hw->cumulative_dy = priv->hwState->cumulative_dy; + } + + /* timer may cause actual events to lag behind (#48777) */ + if (priv->hwState->millis > hw->millis) + hw->millis = priv->hwState->millis; + + SynapticsCopyHwState(priv->hwState, hw); + delay = HandleState(pInfo, hw, hw->millis, FALSE); + newDelay = TRUE; + } + + if (newDelay) { + priv->timer_time = GetTimeInMillis(); + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + } +} + +static int +HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw, + CARD32 now, int *delay) +{ + SynapticsParameters *para = &priv->synpara; + Bool done = FALSE; + int timeleft; + int mid = 0; + + if (para->emulate_mid_button_time <= 0) + return mid; + + while (!done) { + switch (priv->mid_emu_state) { + case MBE_LEFT_CLICK: + case MBE_RIGHT_CLICK: + case MBE_OFF: + priv->button_delay_millis = now; + 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, now); + 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, now); + 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_low) + return FS_UNTOUCHED; + + if (priv->finger_state == FS_BLOCKED) + return FS_BLOCKED; + + if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED) + finger = FS_TOUCHED; + else + finger = priv->finger_state; + + if (!para->palm_detect) + return finger; + + /* palm detection */ + + if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width)) + return FS_BLOCKED; + + if (priv->has_mt_palm_detect) + return finger; + + if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED) + priv->avg_width = 0; + else + priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2; + + if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) { + 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; + + return finger; +} + +static void +SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge) +{ + enum TapEvent tap; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) { + priv->tap_button = 0; + return; + } + + switch (priv->tap_max_fingers) { + case 1: + 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; + default: + priv->tap_button = 0; + return; + } + + 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, CARD32 millis) +{ + DBG(3, "SetTapState - %d -> %d (millis:%u)\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: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_2B: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_3: + priv->tap_button_state = TBS_BUTTON_DOWN; + break; + case TS_SINGLETAP: + 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, + CARD32 millis) +{ + DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n", + priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y, + millis); + + 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, + CARD32 now, enum FingerState finger, + Bool inside_active_area) +{ + SynapticsParameters *para = &priv->synpara; + Bool touch, release, is_timeout, move, press; + int timeleft, timeout; + enum EdgeType edge; + int delay = 1000000000; + + if (para->touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) + return delay; + + touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED; + release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED; + move = (finger >= FS_TOUCHED && + (priv->tap_max_fingers <= + ((priv->horiz_scroll_twofinger_on || + priv->vert_scroll_twofinger_on) ? 2 : 1)) && + (priv->prevFingers == hw->numFingers && + ((abs(hw->x - priv->touch_on.x) >= para->tap_move) || + (abs(hw->y - priv->touch_on.y) >= para->tap_move)))); + press = (hw->left || hw->right || hw->middle); + + if (touch) { + priv->touch_on.x = hw->x; + priv->touch_on.y = hw->y; + priv->touch_on.millis = now; + } + else if (release) { + priv->touch_on.millis = now; + } + 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, now); + is_timeout = timeleft <= 0; + + restart: + switch (priv->tap_state) { + case TS_START: + if (touch) + SetTapState(priv, TS_1, now); + break; + case TS_1: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_MOVE, now); + goto restart; + } + else if (is_timeout) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_MOVE, now); + 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, now); + } + break; + case TS_MOVE: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_2A: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_2B: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_SINGLETAP: + if (touch) + SetTapState(priv, TS_1, now); + else if (is_timeout) + SetTapState(priv, TS_START, now); + break; + case TS_3: + if (move) { + if (para->tap_and_drag_gesture) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (is_timeout) { + if (para->tap_and_drag_gesture) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (release) { + SetTapState(priv, TS_2B, now); + } + break; + case TS_DRAG: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (release) { + SetMovingState(priv, MS_FALSE, now); + if (para->locked_drags) { + SetTapState(priv, TS_4, now); + } + else { + SetTapState(priv, TS_START, now); + } + } + break; + case TS_4: + if (is_timeout) { + SetTapState(priv, TS_START, now); + goto restart; + } + if (touch) + SetTapState(priv, TS_5, now); + break; + case TS_5: + if (is_timeout || move) { + SetTapState(priv, TS_DRAG, now); + goto restart; + } + else if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_CLICKPAD_MOVE: + /* Disable scrolling once a button is pressed on a clickpad */ + priv->vert_scroll_edge_on = FALSE; + priv->horiz_scroll_edge_on = FALSE; + priv->vert_scroll_twofinger_on = FALSE; + priv->horiz_scroll_twofinger_on = FALSE; + + /* Assume one touch is only for holding the clickpad button down */ + if (hw->numFingers > 1) + hw->numFingers--; + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (!press) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_MOVE, now); + priv->count_packet_finger = 0; + } + break; + } + + timeout = GetTimeOut(priv); + if (timeout >= 0) { + timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now); + delay = clamp(timeleft, 1, delay); + } + return delay; +} + +#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)]) +#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e)) + +static void +store_history(SynapticsPrivate * priv, int x, int y, CARD32 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; + if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY) + priv->count_packet_finger++; +} + +/* + * 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(SynapticsPrivate *priv, const struct SynapticsHwState *hw, + enum EdgeType edge, double *dx, double *dy) +{ + *dx = hw->x - HIST(0).x; + *dy = hw->y - HIST(0).y; +} + +/* Vector length, but not sqrt'ed, we only need it for comparison */ +static inline double +vlenpow2(double x, double y) +{ + return x * x + y * y; +} + +/** + * Compute relative motion ('deltas') including edge motion. + */ +static int +ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw, + enum EdgeType edge, int *dxP, int *dyP, Bool inside_area) +{ + enum MovingState moving_state; + double dx, dy; + double vlen; + 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: + moving_state = MS_TOUCHPAD_RELATIVE; + break; + default: + break; + } + } + + if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED || + 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 || + (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) { + /* reset packet counter. */ + priv->count_packet_finger = 0; + goto out; + } + + /* To create the illusion of fluid motion, call back at roughly the report + * rate, even in the absence of new hardware events; see comment above + * POLL_MS declaration. */ + delay = MIN(delay, POLL_MS); + + if (priv->count_packet_finger <= 1) + goto out; /* skip the lot */ + + if (moving_state == MS_TOUCHPAD_RELATIVE) + get_delta(priv, hw, edge, &dx, &dy); + + out: + priv->prevFingers = hw->numFingers; + + vlen = vlenpow2(dx/priv->synpara.resolution_horiz, + dy/priv->synpara.resolution_vert); + + if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) { + dx = 0; + dy = 0; + } + + *dxP = dx; + *dyP = dy; + + return delay; +} + +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->scroll.coast_delta_y = 0.0; + priv->scroll.coast_delta_x = 0.0; + + if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) { + double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0; + + if (vert && !circ) { + double dy = + estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y); + if (pkt_time > 0) { + double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y); + } + } + } + if (horiz && !circ) { + double dx = + estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x); + if (pkt_time > 0) { + double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x); + } + } + } + if (circ) { + double da = estimate_delta_circ(priv); + + if (pkt_time > 0) { + double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + if (vert) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + else if (horiz) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + } + } + } + } + priv->scroll.packets_this_scroll = 0; +} + +static void +stop_coasting(SynapticsPrivate * priv) +{ + priv->scroll.coast_speed_x = 0; + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; +} + +static int +HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw, + enum EdgeType edge, Bool finger) +{ + SynapticsParameters *para = &priv->synpara; + int delay = 1000000000; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF || + priv->synpara.touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) { + 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 == FS_UNTOUCHED) { + stop_coasting(priv); + priv->scroll.delta_y = 0; + priv->scroll.delta_x = 0; + 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.last_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)) { + stop_coasting(priv); + priv->vert_scroll_twofinger_on = TRUE; + priv->vert_scroll_edge_on = FALSE; + priv->scroll.last_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)) { + stop_coasting(priv); + priv->horiz_scroll_twofinger_on = TRUE; + priv->horiz_scroll_edge_on = FALSE; + priv->scroll.last_x = hw->x; + DBG(7, "horiz two-finger scroll detected\n"); + } + } + } + if (finger && priv->finger_state == FS_UNTOUCHED) { + 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.last_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.last_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->scroll.coast_speed_x || priv->scroll.coast_speed_y)) { + 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->scroll.coast_speed_y == 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.last_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->scroll.coast_speed_x == 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.last_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.packets_this_scroll++; + } + + if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) { + /* + = down, - = up */ + if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) { + priv->scroll.delta_y += (hw->y - priv->scroll.last_y); + priv->scroll.last_y = hw->y; + } + } + if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) { + /* + = right, - = left */ + if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) { + priv->scroll.delta_x += (hw->x - priv->scroll.last_x); + priv->scroll.last_x = hw->x; + } + } + if (priv->circ_scroll_on) { + /* + = counter clockwise, - = clockwise */ + double delta = para->scroll_dist_circ; + double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y)); + + if (delta >= 0.005 && diff != 0.0) { + if (priv->circ_scroll_vert) + priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert; + else + priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz; + priv->scroll.last_a = angle(priv, hw->x, hw->y); + } + } + + if (priv->scroll.coast_speed_y) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddy = para->coasting_friction * dtime; + + priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_y) < ddy) { + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_y += + (priv->scroll.coast_speed_y < 0 ? ddy : -ddy); + } + } + + if (priv->scroll.coast_speed_x) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddx = para->coasting_friction * dtime; + priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_x) < ddx) { + priv->scroll.coast_speed_x = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_x += + (priv->scroll.coast_speed_x < 0 ? ddx : -ddx); + } + } + + return delay; +} + +/** + * Check if any 2+ fingers are close enough together to assume this is a + * ClickFinger action. + */ +static int +clickpad_guess_clickfingers(SynapticsPrivate * priv, + struct SynapticsHwState *hw) +{ + int nfingers = 0; + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + +#ifdef BUG_RETURN_VAL + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); +#endif + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; + + if (hw->slot_state[i] == SLOTSTATE_EMPTY || + hw->slot_state[i] == SLOTSTATE_CLOSE) + continue; + + f1 = hw->mt_mask[i]; + + for (j = i + 1; j < hw->num_mt_mask; j++) { + ValuatorMask *f2; + double x1, x2, y1, y2; + + if (hw->slot_state[j] == SLOTSTATE_EMPTY || + hw->slot_state[j] == SLOTSTATE_CLOSE) + continue; + + f2 = hw->mt_mask[j]; + + x1 = valuator_mask_get_double(f1, 0); + y1 = valuator_mask_get_double(f1, 1); + + x2 = valuator_mask_get_double(f2, 0); + y2 = valuator_mask_get_double(f2, 1); + + /* FIXME: fingers closer together than 30% of touchpad width, but + * really, this should be dependent on the touchpad size. Also, + * you'll need to find a touchpad that doesn't lie about it's + * size. Good luck. */ + if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 && + abs(y1 - y2) < (priv->maxy - priv->miny) * .3) { + close_point |= (1 << j); + close_point |= (1 << i); + } + } + } + + while (close_point > 0) { + nfingers += close_point & 0x1; + close_point >>= 1; + } + + /* Some trackpads touchpad only track two touchpoints but announce + * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens + * the user likely intents to do a 3 finger click, so handle it as such. + */ + if (hw->numFingers >= 3 && hw->num_mt_mask < 3) + nfingers = 3; + + return nfingers; +} + +static void +handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw) +{ + SynapticsParameters *para = &priv->synpara; + int action = 0; + int nfingers = hw->numFingers; + + /* if this is a clickpad, clickfinger handling is: + * one finger down: no action, this is a normal click + * two fingers down: F2_CLICK + * three fingers down: F3_CLICK + */ + + if (para->clickpad) + nfingers = clickpad_guess_clickfingers(priv, hw); + + switch (nfingers) { + 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 | BTN_EMULATED_FLAG; + break; + case 2: + hw->left = 0; + hw->middle = 1 | BTN_EMULATED_FLAG; + break; + case 3: + hw->left = 0; + hw->right = 1 | BTN_EMULATED_FLAG; + break; + } +} + +/* 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, + CARD32 now, 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, now, delay); + + /* If this is a clickpad and the user clicks in a soft button area, press + * the soft button instead. */ + if (para->clickpad) { + /* hw->left is down, but no other buttons were already down */ + if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) { + /* If the finger down event is delayed, the x and y + * coordinates are stale so we delay processing the click */ + if (hw->z < para->finger_low) { + hw->left = 0; + goto out; + } + if (is_inside_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + priv->clickpad_click_millis = now; + } + else if (hw->left) { + hw->left = (priv->lastButtons & 1) ? 1 : 0; + hw->middle = (priv->lastButtons & 2) ? 1 : 0; + hw->right = (priv->lastButtons & 4) ? 1 : 0; + } + } + + /* Fingers emulate other buttons. ClickFinger can only be + triggered on transition, when left is pressed + */ + if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1) + handle_clickfinger(priv, hw); + +out: + /* 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) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + valuator_mask_zero(priv->scroll_events_mask); + + if (priv->scroll.delta_y != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_vert, priv->scroll.delta_y); + priv->scroll.delta_y = 0; + } + if (priv->scroll.delta_x != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_horiz, priv->scroll.delta_x); + priv->scroll.delta_x = 0; + } + if (valuator_mask_num_valuators(priv->scroll_events_mask)) + xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask); +} + +static inline int +repeat_scrollbuttons(const InputInfoPtr pInfo, + const struct SynapticsHwState *hw, + int buttons, CARD32 now, int delay) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + int repeat_delay, timeleft; + int rep_buttons = 0; + + if (para->updown_button_repeat) + rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1)); + if (para->leftright_button_repeat) + rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1)); + + /* 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 = now + repeat_delay * 2; + } + } + else { + priv->repeatButtons = 0; + priv->nextRepeat = 0; + } + + if (priv->repeatButtons) { + timeleft = TIME_DIFF(priv->nextRepeat, now); + 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)); + if (id == 4) + priv->scroll.delta_y -= para->scroll_dist_vert; + else if (id == 5) + priv->scroll.delta_y += para->scroll_dist_vert; + else if (id == 6) + priv->scroll.delta_x -= para->scroll_dist_horiz; + else if (id == 7) + priv->scroll.delta_x += para->scroll_dist_horiz; + } + + priv->nextRepeat = now + repeat_delay; + delay = MIN(delay, repeat_delay); + } + } + + return delay; +} + +/* Update the open slots and number of active touches */ +static void +UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + int i; + + for (i = 0; i < hw->num_mt_mask; i++) { + if (hw->slot_state[i] == SLOTSTATE_OPEN) { + priv->open_slots[priv->num_active_touches] = i; + priv->num_active_touches++; + BUG_WARN(priv->num_active_touches > priv->num_slots); + } + else if (hw->slot_state[i] == SLOTSTATE_CLOSE) { + Bool found = FALSE; + int j; + + for (j = 0; j < priv->num_active_touches - 1; j++) { + if (priv->open_slots[j] == i) + found = TRUE; + + if (found) + priv->open_slots[j] = priv->open_slots[j + 1]; + } + + BUG_WARN(priv->num_active_touches == 0); + if (priv->num_active_touches > 0) + priv->num_active_touches--; + } + } + + SynapticsResetTouchHwState(hw, FALSE); +} + +static void +filter_jitter(SynapticsPrivate * priv, int *x, int *y) +{ + SynapticsParameters *para = &priv->synpara; + + priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x); + priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y); + *x = priv->hyst_center_x; + *y = priv->hyst_center_y; +} + +static void +reset_hw_state(struct SynapticsHwState *hw) +{ + hw->x = 0; + hw->y = 0; + hw->z = 0; + hw->numFingers = 0; + hw->fingerWidth = 0; +} + +/* + * 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. + * + * from_timer denotes if HandleState was triggered from a timer (e.g. to + * generate fake motion events, or for the tap-to-click state machine), rather + * than from having received a motion event. + */ +static int +HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now, + Bool from_timer) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + enum FingerState finger = FS_UNTOUCHED; + int dx = 0, dy = 0, buttons, id; + enum EdgeType edge = NO_EDGE; + int change; + int double_click = FALSE; + int delay = 1000000000; + int timeleft; + Bool inside_active_area; + Bool using_cumulative_coords = FALSE; + Bool ignore_motion; + + /* We need both and x/y, the driver can't handle just one of the two + * yet. But since it's possible to hit a phys button on non-clickpads + * without ever getting motion data first, we must continue with 0/0 for + * that case. */ + if (hw->x == INT_MIN || hw->y == INT_MAX) { + if (para->clickpad) + return delay; + else if (hw->left || hw->right || hw->middle) { + hw->x = (hw->x == INT_MIN) ? 0 : hw->x; + hw->y = (hw->y == INT_MIN) ? 0 : hw->y; + } + } + + /* If a physical button is pressed on a clickpad or a two-finger scrolling + * is ongoing, use cumulative relative touch movements for motion */ + if (para->clickpad && + ((priv->lastButtons & 7) || + (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) && + priv->last_button_area != TOP_BUTTON_AREA) { + hw->x = hw->cumulative_dx; + hw->y = hw->cumulative_dy; + using_cumulative_coords = TRUE; + } + + /* 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. */ + filter_jitter(priv, &hw->x, &hw->y); + + inside_active_area = is_inside_active_area(priv, hw->x, hw->y); + + /* Ignore motion *starting* inside softbuttonareas */ + if (priv->finger_state < FS_TOUCHED) + priv->last_button_area = current_button_area(para, hw->x, hw->y); + /* If we already have a finger down, clear last_button_area if it goes + outside of the softbuttonareas */ + else if (priv->last_button_area != NO_BUTTON_AREA && + current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA) + priv->last_button_area = NO_BUTTON_AREA; + + ignore_motion = para->touchpad_off == TOUCHPAD_OFF || + (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA); + + /* these two just update hw->left, right, etc. */ + update_hw_button_state(pInfo, hw, now, &delay); + if (priv->has_scrollbuttons) + double_click = adjust_state_from_scrollbuttons(pInfo, hw); + + /* Ignore motion the first X ms after a clickpad click */ + if (priv->clickpad_click_millis) { + if(TIME_DIFF(priv->clickpad_click_millis + + para->clickpad_ignore_motion_time, now) > 0) + ignore_motion = TRUE; + else + priv->clickpad_click_millis = 0; + } + + /* 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) + reset_hw_state(hw); + + /* no edge or finger detection outside of area */ + if (inside_active_area) { + edge = edge_detection(priv, hw->x, hw->y); + if (!from_timer) + finger = SynapticsDetectFinger(priv, hw); + else + finger = priv->finger_state; + } + + /* 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, now, 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 >= FS_TOUCHED)); + 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. + */ +#ifndef NO_DRIVER_SCALING + ScaleCoordinates(priv, hw); +#endif + } + + dx = dy = 0; + + 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 && priv->tap_button_state == TBS_BUTTON_DOWN) + buttons |= 1 << (priv->tap_button - 1); + + /* Post events */ + if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion) + 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); + } + + if (priv->has_scrollbuttons) + delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay); + + /* Process scroll events only if coordinates are + * in the Synaptics Area + */ + if (inside_active_area && + (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) { + post_scroll_events(pInfo); + priv->scroll.last_millis = hw->millis; + } + + if (double_click) { + post_button_click(pInfo, 1); + post_button_click(pInfo, 1); + } + + UpdateTouchState(pInfo, hw); + + /* 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) +{ + DBG(3, "SwitchMode called\n"); + + return XI_BadMode; +} + +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; +} + +#ifndef NO_DRIVER_SCALING +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; + } +} +#endif --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/104_always_enable_tapping.patch/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/104_always_enable_tapping.patch/src/synaptics.c @@ -0,0 +1,3249 @@ +/* + * 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-2012 Red Hat, Inc. + * Copyright © 2011 The Chromium OS Authors + * + * 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 + * Christian Thaeter + * Stefan Bethge + * Matthias Ihmig + * Alexei Gilchrist + * Jörg Bösner + * Hartwig Felger + * Peter Osterlund + * S. Lehner + * Stefan Gmeiner + * Henry Davies for the + * Linuxcare Inc. David Kennedy + * Fred Hucht + * Fedor P. Goncharov + * Simon Thum + * + * Trademarks are the property of their respective owners. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +enum EdgeType { + 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 +}; + +/* + * We expect to be receiving a steady 80 packets/sec (which gives 40 + * reports/sec with more than one finger on the pad, as Advanced Gesture Mode + * requires two PS/2 packets per report). Instead of a random scattering of + * magic 13 and 20ms numbers scattered throughout the driver, introduce + * POLL_MS as 14ms, which is slightly less than 80Hz. 13ms is closer to + * 80Hz, but if the kernel event reporting was even slightly delayed, + * we would produce synthetic motion followed immediately by genuine + * motion, so use 14. + * + * We use this to call back at a constant rate to at least produce the + * illusion of smooth motion. It works a lot better than you'd expect. +*/ +#define POLL_MS 14 + +#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 + +#define INPUT_BUFFER_SIZE 200 + +/***************************************************************************** + * Forward declaration + ****************************************************************************/ +static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static Bool DeviceControl(DeviceIntPtr, int); +static void ReadInput(InputInfoPtr); +static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now, + Bool from_timer); +static int ControlProc(InputInfoPtr, xDeviceCtl *); +static int SwitchMode(ClientPtr, DeviceIntPtr, int); +static int DeviceInit(DeviceIntPtr); +static int DeviceOn(DeviceIntPtr); +static int DeviceOff(DeviceIntPtr); +static int DeviceClose(DeviceIntPtr); +static Bool QueryHardware(InputInfoPtr); +static void ReadDevDimensions(InputInfoPtr); +#ifndef NO_DRIVER_SCALING +static void ScaleCoordinates(SynapticsPrivate * priv, + struct SynapticsHwState *hw); +static void CalculateScalingCoeffs(SynapticsPrivate * priv); +#endif +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, + NULL, +#ifdef XI86_DRV_CAP_SERVER_FD + XI86_DRV_CAP_SERVER_FD +#endif +}; + +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 + ****************************************************************************/ +static inline void +SynapticsCloseFd(InputInfoPtr pInfo) +{ + if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) { + xf86CloseSerial(pInfo->fd); + pInfo->fd = -1; + } +} + +/** + * 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 = 255; + + 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 = 15; + + xf86IDrvMsg(pInfo, X_PROBED, + "invalid finger width range. defaulting to %d - %d\n", + priv->minw, priv->maxw); + } +} + +static Bool +SetDeviceAndProtocol(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = pInfo->private; + char *proto, *device; + int i; + + proto = xf86SetStrOption(pInfo->options, "Protocol", NULL); + device = xf86SetStrOption(pInfo->options, "Device", NULL); + + /* If proto is auto-dev, unset and let the code do the rest */ + if (proto && !strcmp(proto, "auto-dev")) { + free(proto); + proto = 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; + + return (priv->proto_ops != 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 || + priv->model == MODEL_UNIBODY_MACBOOK) { + 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; +} + +static void +calculate_tap_hysteresis(SynapticsPrivate * priv, int range, + int *fingerLow, int *fingerHigh) +{ + switch (priv->model) { + case MODEL_ELANTECH: + /* All Elantech touchpads don't need the Z filtering to get the + * number of fingers correctly. See Documentation/elantech.txt + * in the kernel. + */ + *fingerLow = priv->minp + 1; + *fingerHigh = priv->minp + 1; + break; + case MODEL_UNIBODY_MACBOOK: + *fingerLow = 70; + *fingerHigh = 75; + break; + default: + *fingerLow = priv->minp + range * (25.0 / 256); + *fingerHigh = priv->minp + range * (30.0 / 256); + break; + } +} + +/* 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; + double percent = xf86CheckPercentOption(options, optname, -1); + + if (percent >= 0.0) { + percent = xf86SetPercentOption(options, optname, -1); + result = percent / 100.0 * range + offset; + } else + result = xf86SetIntOption(options, optname, default_value); + + return result; +} + +Bool +SynapticsIsSoftButtonAreasValid(int *values) +{ + Bool right_disabled = FALSE; + Bool middle_disabled = FALSE; + + enum { + /* right button left, right, top, bottom */ + RBL = 0, + RBR = 1, + RBT = 2, + RBB = 3, + /* middle button left, right, top, bottom */ + MBL = 4, + MBR = 5, + MBT = 6, + MBB = 7, + }; + + /* Check right button area */ + if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) || + (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB]))) + return FALSE; + + /* Check middle button area */ + if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) || + (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB]))) + return FALSE; + + if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0) + right_disabled = TRUE; + + if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0) + middle_disabled = TRUE; + + if (!right_disabled && + ((values[RBL] && values[RBL] == values[RBR]) || + (values[RBT] && values[RBT] == values[RBB]))) + return FALSE; + + if (!middle_disabled && + ((values[MBL] && values[MBL] == values[MBR]) || + (values[MBT] && values[MBT] == values[MBB]))) + return FALSE; + + /* Check for overlapping button areas */ + if (!right_disabled && !middle_disabled) { + int right_left = values[RBL] ? values[RBL] : INT_MIN; + int right_right = values[RBR] ? values[RBR] : INT_MAX; + int right_top = values[RBT] ? values[RBT] : INT_MIN; + int right_bottom = values[RBB] ? values[RBB] : INT_MAX; + int middle_left = values[MBL] ? values[MBL] : INT_MIN; + int middle_right = values[MBR] ? values[MBR] : INT_MAX; + int middle_top = values[MBT] ? values[MBT] : INT_MIN; + int middle_bottom = values[MBB] ? values[MBB] : INT_MAX; + + /* If areas overlap in the Y axis */ + if ((right_bottom <= middle_bottom && right_bottom >= middle_top) || + (right_top <= middle_bottom && right_top >= middle_top)) { + /* Check for overlapping left edges */ + if ((right_left < middle_left && right_right > middle_left) || + (middle_left < right_left && middle_right > right_left)) + return FALSE; + + /* Check for overlapping right edges */ + if ((right_right > middle_right && right_left < middle_right) || + (middle_right > right_right && middle_left < right_right)) + return FALSE; + } + + /* If areas overlap in the X axis */ + if ((right_left >= middle_left && right_left <= middle_right) || + (right_right >= middle_left && right_right <= middle_right)) { + /* Check for overlapping top edges */ + if ((right_top < middle_top && right_bottom > middle_top) || + (middle_top < right_top && middle_bottom > right_top)) + return FALSE; + + /* Check for overlapping bottom edges */ + if ((right_bottom > middle_bottom && right_top < middle_bottom) || + (middle_bottom > right_bottom && middle_top < right_bottom)) + return FALSE; + } + } + + return TRUE; +} + +static void +set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset) +{ + SynapticsPrivate *priv = pInfo->private; + SynapticsParameters *pars = &priv->synpara; + int values[8]; + int in_percent = 0; /* bitmask for which ones are in % */ + char *option_string; + char *next_num; + char *end_str; + int i; + int width, height; + + if (!pars->clickpad) + return; + + option_string = xf86SetStrOption(pInfo->options, option_name, NULL); + if (!option_string) + return; + + next_num = option_string; + + for (i = 0; i < 8 && *next_num != '\0'; i++) { + long int value = strtol(next_num, &end_str, 0); + + if (value > INT_MAX || value < -INT_MAX) + goto fail; + + values[i] = value; + + if (next_num != end_str) { + if (*end_str == '%') { + in_percent |= 1 << i; + end_str++; + } + next_num = end_str; + } + else + goto fail; + } + + if (i < 8 || *next_num != '\0') + goto fail; + + width = priv->maxx - priv->minx; + height = priv->maxy - priv->miny; + + for (i = 0; in_percent && i < 8; i++) { + int base, size; + + if ((in_percent & (1 << i)) == 0 || values[i] == 0) + continue; + + size = ((i % 4) < 2) ? width : height; + base = ((i % 4) < 2) ? priv->minx : priv->miny; + values[i] = base + size * values[i] / 100.0; + } + + if (!SynapticsIsSoftButtonAreasValid(values)) + goto fail; + + memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int)); + memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int)); + + free(option_string); + + return; + + fail: + xf86IDrvMsg(pInfo, X_ERROR, + "invalid %s value '%s', keeping defaults\n", + option_name, option_string); + free(option_string); +} + +static void +set_primary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA); +} + +static void +set_secondary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA); +} + +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 */ + double accelFactor; /* 1/pixels */ + int fingerLow, fingerHigh; /* pressure */ + int emulateTwoFingerMinZ; /* pressure */ + int emulateTwoFingerMinW; /* width */ + 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; + int middle_button_timeout; + int grab_event_device = 0; + const char *source; + + /* 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; + 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 + 1; + + calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh); + + /* scaling based on defaults and a pressure of 256 */ + emulateTwoFingerMinZ = priv->minp + range * (282.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 + 1; + + /* 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->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->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad); /* Probed */ + if (pars->clickpad) + pars->has_secondary_buttons = xf86SetBoolOption(opts, + "HasSecondarySoftButtons", + pars->has_secondary_buttons); + pars->clickpad_ignore_motion_time = 100; /* ms */ + /* middle mouse button emulation on a clickpad? nah, you're joking */ + middle_button_timeout = pars->clickpad ? 0 : 75; + pars->emulate_mid_button_time = + xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout); + pars->emulate_twofinger_z = + xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ); + pars->emulate_twofinger_w = + xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW); + pars->scroll_dist_vert = + xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta); + pars->scroll_dist_horiz = + xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta); + 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->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON); + + 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->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE); + pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000); + pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2); + pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3); + 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->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE); + + pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4); + pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7); + pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor); + 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); + + /* Only grab the device by default if it's not coming from a config + backend. This way we avoid the device being added twice and sending + duplicate events. + */ + source = xf86CheckStrOption(opts, "_source", NULL); + if (source == NULL || strncmp(source, "server/", 7) != 0) + grab_event_device = TRUE; + pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device); + + pars->tap_and_drag_gesture = + xf86SetBoolOption(opts, "TapAndDragGesture", TRUE); + pars->resolution_horiz = + xf86SetIntOption(opts, "HorizResolution", horizResolution); + pars->resolution_vert = + xf86SetIntOption(opts, "VertResolution", vertResolution); + if (pars->resolution_horiz <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid X resolution, using 1 instead.\n"); + pars->resolution_horiz = 1; + } + if (pars->resolution_vert <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid Y resolution, using 1 instead.\n"); + pars->resolution_vert = 1; + } + + /* Touchpad sampling rate is too low to detect all movements. + A user may lift one finger and put another one down within the same + EV_SYN or even between samplings so the driver doesn't notice at all. + + We limit the movement to 20 mm within one event, that is more than + recordings showed is needed (17mm on a T440). + */ + if (pars->resolution_horiz > 1 && + pars->resolution_vert > 1) + pars->maxDeltaMM = 20; + else { + /* on devices without resolution set the vector length to 0.25 of + the touchpad diagonal */ + pars->maxDeltaMM = diag * 0.25; + } + + + /* 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"); + } + + set_primary_softbutton_areas_option(pInfo); + if (pars->has_secondary_buttons) + set_secondary_softbutton_areas_option(pInfo); +} + +static double +SynapticsAccelerationProfile(DeviceIntPtr dev, + DeviceVelocityPtr vel, + double velocity, double thr, double 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; +} + +static int +SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + 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 */ + if (!SetDeviceAndProtocol(pInfo)) { + 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); + + set_default_parameters(pInfo); + +#ifndef NO_DRIVER_SCALING + CalculateScalingCoeffs(priv); +#endif + + + 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 (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + + return Success; + + SetupProc_fail: + SynapticsCloseFd(pInfo); + + if (priv->comm.buffer) + XisbFree(priv->comm.buffer); + 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); + if (priv && priv->scroll_events_mask) + valuator_mask_free(&priv->scroll_events_mask); + if (priv && priv->open_slots) + free(priv->open_slots); + 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 int +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 int +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)) + goto error; + + priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE); + if (!priv->comm.buffer) + goto error; + + xf86FlushInput(pInfo->fd); + + /* reinit the pad */ + if (!QueryHardware(pInfo)) + goto error; + + xf86AddEnabledDevice(pInfo); + dev->public.on = TRUE; + + return Success; + +error: + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + return !Success; +} + +static void +SynapticsReset(SynapticsPrivate * priv) +{ + int i; + + SynapticsResetHwState(priv->hwState); + SynapticsResetHwState(priv->local_hw_state); + SynapticsResetHwState(priv->comm.hwState); + + memset(priv->move_hist, 0, sizeof(priv->move_hist)); + priv->hyst_center_x = 0; + priv->hyst_center_y = 0; + memset(&priv->scroll, 0, sizeof(priv->scroll)); + priv->count_packet_finger = 0; + priv->finger_state = FS_UNTOUCHED; + priv->last_motion_millis = 0; + priv->clickpad_click_millis = 0; + priv->last_button_area = NO_BUTTON_AREA; + priv->tap_state = TS_START; + priv->tap_button = 0; + priv->tap_button_state = TBS_BUTTON_UP; + priv->moving_state = MS_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; + priv->circ_scroll_on = FALSE; + priv->circ_scroll_vert = FALSE; + priv->mid_emu_state = MBE_OFF; + priv->nextRepeat = 0; + priv->lastButtons = 0; + priv->prev_z = 0; + priv->prevFingers = 0; + priv->num_active_touches = 0; + + for (i = 0; i < priv->num_slots; i++) + priv->open_slots[i] = -1; +} + +static int +DeviceOff(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + Bool rc = Success; + + DBG(3, "Synaptics DeviceOff called\n"); + + if (pInfo->fd != -1) { + TimerCancel(priv->timer); + xf86RemoveEnabledDevice(pInfo); + SynapticsReset(priv); + + if (priv->proto_ops->DeviceOffHook && + !priv->proto_ops->DeviceOffHook(pInfo)) + rc = !Success; + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + } + dev->public.on = FALSE; + return rc; +} + +static int +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(priv->touch_axes); + priv->touch_axes = NULL; + SynapticsHwStateFree(&priv->hwState); + SynapticsHwStateFree(&priv->local_hw_state); + SynapticsHwStateFree(&priv->comm.hwState); + return RetValue; +} + +static void +InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv) +{ + int i; + + memset(labels, 0, nlabels * sizeof(Atom)); + switch (nlabels) { + default: + case 4: + labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL); + case 3: + labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL); + case 2: + labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y); + case 1: + labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X); + break; + } + + for (i = 0; i < priv->num_mt_axes; i++) { + SynapticsTouchAxisRec *axis = &priv->touch_axes[i]; + int axnum = nlabels - priv->num_mt_axes + i; + + labels[axnum] = XIGetKnownProperty(axis->label); + } +} + +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; + } +} + +static void +DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + if (!priv->has_touch) + return; + + priv->num_slots = + priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES; + + priv->open_slots = malloc(priv->num_slots * sizeof(int)); + if (!priv->open_slots) { + xf86IDrvMsg(pInfo, X_ERROR, + "failed to allocate open touch slots array\n"); + priv->has_touch = 0; + priv->num_slots = 0; + } +} + +static int +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; + int num_axes = 2; + Atom btn_labels[SYN_MAX_BUTTONS] = { 0 }; + Atom *axes_labels; + DeviceVelocityPtr pVel; + + num_axes += 2; + + num_axes += priv->num_mt_axes; + + axes_labels = calloc(num_axes, sizeof(Atom)); + if (!axes_labels) { + xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n"); + return !Success; + } + + InitAxesLabels(axes_labels, num_axes, priv); + InitButtonLabels(btn_labels, SYN_MAX_BUTTONS); + + 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, + btn_labels, + SynapticsCtrl, + GetMotionHistorySize(), num_axes, axes_labels); + + /* + * setup dix acceleration to match legacy synaptics settings, and + * etablish a device-specific profile to do stuff like pressure-related + * acceleration. + */ + 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, "(accel) MaxSpeed is now %.2f\n", + priv->synpara.max_speed); + xf86IDrvMsg(pInfo, X_CONFIG, "(accel) 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); + } + + /* X valuator */ + if (priv->minx < priv->maxx && priv->synpara.resolution_detect) { + min = priv->minx; + max = priv->maxx; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max, + priv->resx * 1000, 0, priv->resx * 1000, + Relative); + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ + if (priv->miny < priv->maxy && priv->synpara.resolution_detect) { + min = priv->miny; + max = priv->maxy; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max, + priv->resy * 1000, 0, priv->resy * 1000, + Relative); + xf86InitValuatorDefaults(dev, 1); + + xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_horiz = 2; + xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_vert = 3; + priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS); + if (!priv->scroll_events_mask) { + free(axes_labels); + return !Success; + } + + SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL, + priv->synpara.scroll_dist_horiz, 0); + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + priv->synpara.scroll_dist_vert, 0); + + DeviceInitTouch(dev, axes_labels); + + free(axes_labels); + + priv->hwState = SynapticsHwStateAlloc(priv); + if (!priv->hwState) + goto fail; + + priv->local_hw_state = SynapticsHwStateAlloc(priv); + if (!priv->local_hw_state) + goto fail; + + priv->comm.hwState = SynapticsHwStateAlloc(priv); + + InitDeviceProperties(pInfo); + XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL); + + SynapticsReset(priv); + + return Success; + + fail: + free(priv->local_hw_state); + free(priv->hwState); + free(priv->open_slots); + 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 enum EdgeType +circular_edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 enum EdgeType +edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 a finger is down, then it must have started inside the active_area, + allow the motion to complete using the entire area */ + if (priv->finger_state >= FS_TOUCHED) + return 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 Bool +is_inside_button_area(SynapticsParameters * para, int which, int x, int y) +{ + Bool inside_area = TRUE; + + if (para->softbutton_areas[which][LEFT] == 0 && + para->softbutton_areas[which][RIGHT] == 0 && + para->softbutton_areas[which][TOP] == 0 && + para->softbutton_areas[which][BOTTOM] == 0) + return FALSE; + + if (para->softbutton_areas[which][LEFT] && + x < para->softbutton_areas[which][LEFT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][RIGHT] && + x > para->softbutton_areas[which][RIGHT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][TOP] && + y < para->softbutton_areas[which][TOP]) + inside_area = FALSE; + else if (para->softbutton_areas[which][BOTTOM] && + y > para->softbutton_areas[which][BOTTOM]) + inside_area = FALSE; + + return inside_area; +} + +static Bool +is_inside_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset, + int x, int y) +{ + Bool inside_area = TRUE; + Bool right_valid, middle_valid; + int top, bottom; + + /* We don't have a left button area, so we only check the y axis */ + right_valid = para->softbutton_areas[offset][TOP] || + para->softbutton_areas[offset][BOTTOM]; + middle_valid = para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset + 1][BOTTOM]; + + if (!right_valid && !middle_valid) + return FALSE; + + /* Check both buttons are horizontally aligned */ + if (right_valid && middle_valid && ( + para->softbutton_areas[offset][TOP] != + para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset][BOTTOM] != + para->softbutton_areas[offset + 1][BOTTOM])) + return FALSE; + + if (right_valid) { + top = para->softbutton_areas[offset][TOP]; + bottom = para->softbutton_areas[offset][BOTTOM]; + } + else { + top = para->softbutton_areas[offset + 1][TOP]; + bottom = para->softbutton_areas[offset + 1][BOTTOM]; + } + + if (top && y < top) + inside_area = FALSE; + else if (bottom && y > bottom) + inside_area = FALSE; + + return inside_area; +} + +static enum SoftButtonAreas +current_button_area(SynapticsParameters * para, int x, int y) +{ + if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y)) + return BOTTOM_BUTTON_AREA; + else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y)) + return TOP_BUTTON_AREA; + else + return NO_BUTTON_AREA; +} + +static CARD32 +timerFunc(OsTimerPtr timer, CARD32 now, pointer arg) +{ + InputInfoPtr pInfo = arg; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + struct SynapticsHwState *hw = priv->local_hw_state; + int delay; +#if !HAVE_THREADED_INPUT + int sigstate = xf86BlockSIGIO(); +#else + input_lock(); +#endif + + priv->hwState->millis += now - priv->timer_time; + SynapticsCopyHwState(hw, priv->hwState); + SynapticsResetTouchHwState(hw, FALSE); + delay = HandleState(pInfo, hw, hw->millis, TRUE); + + priv->timer_time = now; + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + +#if !HAVE_THREADED_INPUT + xf86UnblockSIGIO(sigstate); +#else + input_unlock(); +#endif + + 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 = priv->local_hw_state; + int delay = 0; + Bool newDelay = FALSE; + + SynapticsResetTouchHwState(hw, FALSE); + + while (SynapticsGetHwState(pInfo, priv, hw)) { + /* Semi-mt device touch slots do not track touches. When there is a + * change in the number of touches, we must disregard the temporary + * motion changes. */ + if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) { + hw->cumulative_dx = priv->hwState->cumulative_dx; + hw->cumulative_dy = priv->hwState->cumulative_dy; + } + + /* timer may cause actual events to lag behind (#48777) */ + if (priv->hwState->millis > hw->millis) + hw->millis = priv->hwState->millis; + + SynapticsCopyHwState(priv->hwState, hw); + delay = HandleState(pInfo, hw, hw->millis, FALSE); + newDelay = TRUE; + } + + if (newDelay) { + priv->timer_time = GetTimeInMillis(); + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + } +} + +static int +HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw, + CARD32 now, int *delay) +{ + SynapticsParameters *para = &priv->synpara; + Bool done = FALSE; + int timeleft; + int mid = 0; + + if (para->emulate_mid_button_time <= 0) + return mid; + + while (!done) { + switch (priv->mid_emu_state) { + case MBE_LEFT_CLICK: + case MBE_RIGHT_CLICK: + case MBE_OFF: + priv->button_delay_millis = now; + 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, now); + 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, now); + 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_low) + return FS_UNTOUCHED; + + if (priv->finger_state == FS_BLOCKED) + return FS_BLOCKED; + + if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED) + finger = FS_TOUCHED; + else + finger = priv->finger_state; + + if (!para->palm_detect) + return finger; + + /* palm detection */ + + if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width)) + return FS_BLOCKED; + + if (priv->has_mt_palm_detect) + return finger; + + if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED) + priv->avg_width = 0; + else + priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2; + + if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) { + 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; + + return finger; +} + +static void +SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge) +{ + enum TapEvent tap; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) { + priv->tap_button = 0; + return; + } + + switch (priv->tap_max_fingers) { + case 1: + 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; + default: + priv->tap_button = 0; + return; + } + + 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, CARD32 millis) +{ + DBG(3, "SetTapState - %d -> %d (millis:%u)\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: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_2B: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_3: + priv->tap_button_state = TBS_BUTTON_DOWN; + break; + case TS_SINGLETAP: + 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, + CARD32 millis) +{ + DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n", + priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y, + millis); + + 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, + CARD32 now, enum FingerState finger, + Bool inside_active_area) +{ + SynapticsParameters *para = &priv->synpara; + Bool touch, release, is_timeout, move, press; + int timeleft, timeout; + enum EdgeType edge; + int delay = 1000000000; + + if (para->touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) + return delay; + + touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED; + release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED; + move = (finger >= FS_TOUCHED && + (priv->tap_max_fingers <= + ((priv->horiz_scroll_twofinger_on || + priv->vert_scroll_twofinger_on) ? 2 : 1)) && + (priv->prevFingers == hw->numFingers && + ((abs(hw->x - priv->touch_on.x) >= para->tap_move) || + (abs(hw->y - priv->touch_on.y) >= para->tap_move)))); + press = (hw->left || hw->right || hw->middle); + + if (touch) { + priv->touch_on.x = hw->x; + priv->touch_on.y = hw->y; + priv->touch_on.millis = now; + } + else if (release) { + priv->touch_on.millis = now; + } + 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, now); + is_timeout = timeleft <= 0; + + restart: + switch (priv->tap_state) { + case TS_START: + if (touch) + SetTapState(priv, TS_1, now); + break; + case TS_1: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_MOVE, now); + goto restart; + } + else if (is_timeout) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_MOVE, now); + 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, now); + } + break; + case TS_MOVE: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_2A: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_2B: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_SINGLETAP: + if (touch) + SetTapState(priv, TS_1, now); + else if (is_timeout) + SetTapState(priv, TS_START, now); + break; + case TS_3: + if (move) { + if (para->tap_and_drag_gesture) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (is_timeout) { + if (para->tap_and_drag_gesture) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (release) { + SetTapState(priv, TS_2B, now); + } + break; + case TS_DRAG: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (release) { + SetMovingState(priv, MS_FALSE, now); + if (para->locked_drags) { + SetTapState(priv, TS_4, now); + } + else { + SetTapState(priv, TS_START, now); + } + } + break; + case TS_4: + if (is_timeout) { + SetTapState(priv, TS_START, now); + goto restart; + } + if (touch) + SetTapState(priv, TS_5, now); + break; + case TS_5: + if (is_timeout || move) { + SetTapState(priv, TS_DRAG, now); + goto restart; + } + else if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_CLICKPAD_MOVE: + /* Disable scrolling once a button is pressed on a clickpad */ + priv->vert_scroll_edge_on = FALSE; + priv->horiz_scroll_edge_on = FALSE; + priv->vert_scroll_twofinger_on = FALSE; + priv->horiz_scroll_twofinger_on = FALSE; + + /* Assume one touch is only for holding the clickpad button down */ + if (hw->numFingers > 1) + hw->numFingers--; + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (!press) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_MOVE, now); + priv->count_packet_finger = 0; + } + break; + } + + timeout = GetTimeOut(priv); + if (timeout >= 0) { + timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now); + delay = clamp(timeleft, 1, delay); + } + return delay; +} + +#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)]) +#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e)) + +static void +store_history(SynapticsPrivate * priv, int x, int y, CARD32 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; + if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY) + priv->count_packet_finger++; +} + +/* + * 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(SynapticsPrivate *priv, const struct SynapticsHwState *hw, + enum EdgeType edge, double *dx, double *dy) +{ + *dx = hw->x - HIST(0).x; + *dy = hw->y - HIST(0).y; +} + +/* Vector length, but not sqrt'ed, we only need it for comparison */ +static inline double +vlenpow2(double x, double y) +{ + return x * x + y * y; +} + +/** + * Compute relative motion ('deltas') including edge motion. + */ +static int +ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw, + enum EdgeType edge, int *dxP, int *dyP, Bool inside_area) +{ + enum MovingState moving_state; + double dx, dy; + double vlen; + 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: + moving_state = MS_TOUCHPAD_RELATIVE; + break; + default: + break; + } + } + + if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED || + 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 || + (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) { + /* reset packet counter. */ + priv->count_packet_finger = 0; + goto out; + } + + /* To create the illusion of fluid motion, call back at roughly the report + * rate, even in the absence of new hardware events; see comment above + * POLL_MS declaration. */ + delay = MIN(delay, POLL_MS); + + if (priv->count_packet_finger <= 1) + goto out; /* skip the lot */ + + if (moving_state == MS_TOUCHPAD_RELATIVE) + get_delta(priv, hw, edge, &dx, &dy); + + out: + priv->prevFingers = hw->numFingers; + + vlen = vlenpow2(dx/priv->synpara.resolution_horiz, + dy/priv->synpara.resolution_vert); + + if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) { + dx = 0; + dy = 0; + } + + *dxP = dx; + *dyP = dy; + + return delay; +} + +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->scroll.coast_delta_y = 0.0; + priv->scroll.coast_delta_x = 0.0; + + if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) { + double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0; + + if (vert && !circ) { + double dy = + estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y); + if (pkt_time > 0) { + double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y); + } + } + } + if (horiz && !circ) { + double dx = + estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x); + if (pkt_time > 0) { + double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x); + } + } + } + if (circ) { + double da = estimate_delta_circ(priv); + + if (pkt_time > 0) { + double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + if (vert) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + else if (horiz) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + } + } + } + } + priv->scroll.packets_this_scroll = 0; +} + +static void +stop_coasting(SynapticsPrivate * priv) +{ + priv->scroll.coast_speed_x = 0; + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; +} + +static int +HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw, + enum EdgeType edge, Bool finger) +{ + SynapticsParameters *para = &priv->synpara; + int delay = 1000000000; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF || + priv->synpara.touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) { + 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 == FS_UNTOUCHED) { + stop_coasting(priv); + priv->scroll.delta_y = 0; + priv->scroll.delta_x = 0; + 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.last_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)) { + stop_coasting(priv); + priv->vert_scroll_twofinger_on = TRUE; + priv->vert_scroll_edge_on = FALSE; + priv->scroll.last_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)) { + stop_coasting(priv); + priv->horiz_scroll_twofinger_on = TRUE; + priv->horiz_scroll_edge_on = FALSE; + priv->scroll.last_x = hw->x; + DBG(7, "horiz two-finger scroll detected\n"); + } + } + } + if (finger && priv->finger_state == FS_UNTOUCHED) { + 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.last_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.last_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->scroll.coast_speed_x || priv->scroll.coast_speed_y)) { + 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->scroll.coast_speed_y == 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.last_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->scroll.coast_speed_x == 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.last_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.packets_this_scroll++; + } + + if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) { + /* + = down, - = up */ + if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) { + priv->scroll.delta_y += (hw->y - priv->scroll.last_y); + priv->scroll.last_y = hw->y; + } + } + if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) { + /* + = right, - = left */ + if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) { + priv->scroll.delta_x += (hw->x - priv->scroll.last_x); + priv->scroll.last_x = hw->x; + } + } + if (priv->circ_scroll_on) { + /* + = counter clockwise, - = clockwise */ + double delta = para->scroll_dist_circ; + double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y)); + + if (delta >= 0.005 && diff != 0.0) { + if (priv->circ_scroll_vert) + priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert; + else + priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz; + priv->scroll.last_a = angle(priv, hw->x, hw->y); + } + } + + if (priv->scroll.coast_speed_y) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddy = para->coasting_friction * dtime; + + priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_y) < ddy) { + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_y += + (priv->scroll.coast_speed_y < 0 ? ddy : -ddy); + } + } + + if (priv->scroll.coast_speed_x) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddx = para->coasting_friction * dtime; + priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_x) < ddx) { + priv->scroll.coast_speed_x = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_x += + (priv->scroll.coast_speed_x < 0 ? ddx : -ddx); + } + } + + return delay; +} + +/** + * Check if any 2+ fingers are close enough together to assume this is a + * ClickFinger action. + */ +static int +clickpad_guess_clickfingers(SynapticsPrivate * priv, + struct SynapticsHwState *hw) +{ + int nfingers = 0; + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + +#ifdef BUG_RETURN_VAL + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); +#endif + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; + + if (hw->slot_state[i] == SLOTSTATE_EMPTY || + hw->slot_state[i] == SLOTSTATE_CLOSE) + continue; + + f1 = hw->mt_mask[i]; + + for (j = i + 1; j < hw->num_mt_mask; j++) { + ValuatorMask *f2; + double x1, x2, y1, y2; + + if (hw->slot_state[j] == SLOTSTATE_EMPTY || + hw->slot_state[j] == SLOTSTATE_CLOSE) + continue; + + f2 = hw->mt_mask[j]; + + x1 = valuator_mask_get_double(f1, 0); + y1 = valuator_mask_get_double(f1, 1); + + x2 = valuator_mask_get_double(f2, 0); + y2 = valuator_mask_get_double(f2, 1); + + /* FIXME: fingers closer together than 30% of touchpad width, but + * really, this should be dependent on the touchpad size. Also, + * you'll need to find a touchpad that doesn't lie about it's + * size. Good luck. */ + if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 && + abs(y1 - y2) < (priv->maxy - priv->miny) * .3) { + close_point |= (1 << j); + close_point |= (1 << i); + } + } + } + + while (close_point > 0) { + nfingers += close_point & 0x1; + close_point >>= 1; + } + + /* Some trackpads touchpad only track two touchpoints but announce + * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens + * the user likely intents to do a 3 finger click, so handle it as such. + */ + if (hw->numFingers >= 3 && hw->num_mt_mask < 3) + nfingers = 3; + + return nfingers; +} + +static void +handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw) +{ + SynapticsParameters *para = &priv->synpara; + int action = 0; + int nfingers = hw->numFingers; + + /* if this is a clickpad, clickfinger handling is: + * one finger down: no action, this is a normal click + * two fingers down: F2_CLICK + * three fingers down: F3_CLICK + */ + + if (para->clickpad) + nfingers = clickpad_guess_clickfingers(priv, hw); + + switch (nfingers) { + 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 | BTN_EMULATED_FLAG; + break; + case 2: + hw->left = 0; + hw->middle = 1 | BTN_EMULATED_FLAG; + break; + case 3: + hw->left = 0; + hw->right = 1 | BTN_EMULATED_FLAG; + break; + } +} + +/* 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, + CARD32 now, 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, now, delay); + + /* If this is a clickpad and the user clicks in a soft button area, press + * the soft button instead. */ + if (para->clickpad) { + /* hw->left is down, but no other buttons were already down */ + if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) { + /* If the finger down event is delayed, the x and y + * coordinates are stale so we delay processing the click */ + if (hw->z < para->finger_low) { + hw->left = 0; + goto out; + } + if (is_inside_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + priv->clickpad_click_millis = now; + } + else if (hw->left) { + hw->left = (priv->lastButtons & 1) ? 1 : 0; + hw->middle = (priv->lastButtons & 2) ? 1 : 0; + hw->right = (priv->lastButtons & 4) ? 1 : 0; + } + } + + /* Fingers emulate other buttons. ClickFinger can only be + triggered on transition, when left is pressed + */ + if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1) + handle_clickfinger(priv, hw); + +out: + /* 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) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + valuator_mask_zero(priv->scroll_events_mask); + + if (priv->scroll.delta_y != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_vert, priv->scroll.delta_y); + priv->scroll.delta_y = 0; + } + if (priv->scroll.delta_x != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_horiz, priv->scroll.delta_x); + priv->scroll.delta_x = 0; + } + if (valuator_mask_num_valuators(priv->scroll_events_mask)) + xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask); +} + +static inline int +repeat_scrollbuttons(const InputInfoPtr pInfo, + const struct SynapticsHwState *hw, + int buttons, CARD32 now, int delay) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + int repeat_delay, timeleft; + int rep_buttons = 0; + + if (para->updown_button_repeat) + rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1)); + if (para->leftright_button_repeat) + rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1)); + + /* 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 = now + repeat_delay * 2; + } + } + else { + priv->repeatButtons = 0; + priv->nextRepeat = 0; + } + + if (priv->repeatButtons) { + timeleft = TIME_DIFF(priv->nextRepeat, now); + 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)); + if (id == 4) + priv->scroll.delta_y -= para->scroll_dist_vert; + else if (id == 5) + priv->scroll.delta_y += para->scroll_dist_vert; + else if (id == 6) + priv->scroll.delta_x -= para->scroll_dist_horiz; + else if (id == 7) + priv->scroll.delta_x += para->scroll_dist_horiz; + } + + priv->nextRepeat = now + repeat_delay; + delay = MIN(delay, repeat_delay); + } + } + + return delay; +} + +/* Update the open slots and number of active touches */ +static void +UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + int i; + + for (i = 0; i < hw->num_mt_mask; i++) { + if (hw->slot_state[i] == SLOTSTATE_OPEN) { + priv->open_slots[priv->num_active_touches] = i; + priv->num_active_touches++; + BUG_WARN(priv->num_active_touches > priv->num_slots); + } + else if (hw->slot_state[i] == SLOTSTATE_CLOSE) { + Bool found = FALSE; + int j; + + for (j = 0; j < priv->num_active_touches - 1; j++) { + if (priv->open_slots[j] == i) + found = TRUE; + + if (found) + priv->open_slots[j] = priv->open_slots[j + 1]; + } + + BUG_WARN(priv->num_active_touches == 0); + if (priv->num_active_touches > 0) + priv->num_active_touches--; + } + } + + SynapticsResetTouchHwState(hw, FALSE); +} + +static void +filter_jitter(SynapticsPrivate * priv, int *x, int *y) +{ + SynapticsParameters *para = &priv->synpara; + + priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x); + priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y); + *x = priv->hyst_center_x; + *y = priv->hyst_center_y; +} + +static void +reset_hw_state(struct SynapticsHwState *hw) +{ + hw->x = 0; + hw->y = 0; + hw->z = 0; + hw->numFingers = 0; + hw->fingerWidth = 0; +} + +/* + * 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. + * + * from_timer denotes if HandleState was triggered from a timer (e.g. to + * generate fake motion events, or for the tap-to-click state machine), rather + * than from having received a motion event. + */ +static int +HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now, + Bool from_timer) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + enum FingerState finger = FS_UNTOUCHED; + int dx = 0, dy = 0, buttons, id; + enum EdgeType edge = NO_EDGE; + int change; + int double_click = FALSE; + int delay = 1000000000; + int timeleft; + Bool inside_active_area; + Bool using_cumulative_coords = FALSE; + Bool ignore_motion; + + /* We need both and x/y, the driver can't handle just one of the two + * yet. But since it's possible to hit a phys button on non-clickpads + * without ever getting motion data first, we must continue with 0/0 for + * that case. */ + if (hw->x == INT_MIN || hw->y == INT_MAX) { + if (para->clickpad) + return delay; + else if (hw->left || hw->right || hw->middle) { + hw->x = (hw->x == INT_MIN) ? 0 : hw->x; + hw->y = (hw->y == INT_MIN) ? 0 : hw->y; + } + } + + /* If a physical button is pressed on a clickpad or a two-finger scrolling + * is ongoing, use cumulative relative touch movements for motion */ + if (para->clickpad && + ((priv->lastButtons & 7) || + (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) && + priv->last_button_area != TOP_BUTTON_AREA) { + hw->x = hw->cumulative_dx; + hw->y = hw->cumulative_dy; + using_cumulative_coords = TRUE; + } + + /* 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. */ + filter_jitter(priv, &hw->x, &hw->y); + + inside_active_area = is_inside_active_area(priv, hw->x, hw->y); + + /* Ignore motion *starting* inside softbuttonareas */ + if (priv->finger_state < FS_TOUCHED) + priv->last_button_area = current_button_area(para, hw->x, hw->y); + /* If we already have a finger down, clear last_button_area if it goes + outside of the softbuttonareas */ + else if (priv->last_button_area != NO_BUTTON_AREA && + current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA) + priv->last_button_area = NO_BUTTON_AREA; + + ignore_motion = para->touchpad_off == TOUCHPAD_OFF || + (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA); + + /* these two just update hw->left, right, etc. */ + update_hw_button_state(pInfo, hw, now, &delay); + if (priv->has_scrollbuttons) + double_click = adjust_state_from_scrollbuttons(pInfo, hw); + + /* Ignore motion the first X ms after a clickpad click */ + if (priv->clickpad_click_millis) { + if(TIME_DIFF(priv->clickpad_click_millis + + para->clickpad_ignore_motion_time, now) > 0) + ignore_motion = TRUE; + else + priv->clickpad_click_millis = 0; + } + + /* 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) + reset_hw_state(hw); + + /* no edge or finger detection outside of area */ + if (inside_active_area) { + edge = edge_detection(priv, hw->x, hw->y); + if (!from_timer) + finger = SynapticsDetectFinger(priv, hw); + else + finger = priv->finger_state; + } + + /* 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, now, 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 >= FS_TOUCHED)); + 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. + */ +#ifndef NO_DRIVER_SCALING + ScaleCoordinates(priv, hw); +#endif + } + + dx = dy = 0; + + 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 && priv->tap_button_state == TBS_BUTTON_DOWN) + buttons |= 1 << (priv->tap_button - 1); + + /* Post events */ + if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion) + 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); + } + + if (priv->has_scrollbuttons) + delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay); + + /* Process scroll events only if coordinates are + * in the Synaptics Area + */ + if (inside_active_area && + (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) { + post_scroll_events(pInfo); + priv->scroll.last_millis = hw->millis; + } + + if (double_click) { + post_button_click(pInfo, 1); + post_button_click(pInfo, 1); + } + + UpdateTouchState(pInfo, hw); + + /* 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) +{ + DBG(3, "SwitchMode called\n"); + + return XI_BadMode; +} + +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; +} + +#ifndef NO_DRIVER_SCALING +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; + } +} +#endif --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/106_always_enable_vert_edge_scroll.patch/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/106_always_enable_vert_edge_scroll.patch/src/synaptics.c @@ -0,0 +1,3249 @@ +/* + * 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-2012 Red Hat, Inc. + * Copyright © 2011 The Chromium OS Authors + * + * 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 + * Christian Thaeter + * Stefan Bethge + * Matthias Ihmig + * Alexei Gilchrist + * Jörg Bösner + * Hartwig Felger + * Peter Osterlund + * S. Lehner + * Stefan Gmeiner + * Henry Davies for the + * Linuxcare Inc. David Kennedy + * Fred Hucht + * Fedor P. Goncharov + * Simon Thum + * + * Trademarks are the property of their respective owners. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +enum EdgeType { + 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 +}; + +/* + * We expect to be receiving a steady 80 packets/sec (which gives 40 + * reports/sec with more than one finger on the pad, as Advanced Gesture Mode + * requires two PS/2 packets per report). Instead of a random scattering of + * magic 13 and 20ms numbers scattered throughout the driver, introduce + * POLL_MS as 14ms, which is slightly less than 80Hz. 13ms is closer to + * 80Hz, but if the kernel event reporting was even slightly delayed, + * we would produce synthetic motion followed immediately by genuine + * motion, so use 14. + * + * We use this to call back at a constant rate to at least produce the + * illusion of smooth motion. It works a lot better than you'd expect. +*/ +#define POLL_MS 14 + +#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 + +#define INPUT_BUFFER_SIZE 200 + +/***************************************************************************** + * Forward declaration + ****************************************************************************/ +static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static Bool DeviceControl(DeviceIntPtr, int); +static void ReadInput(InputInfoPtr); +static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now, + Bool from_timer); +static int ControlProc(InputInfoPtr, xDeviceCtl *); +static int SwitchMode(ClientPtr, DeviceIntPtr, int); +static int DeviceInit(DeviceIntPtr); +static int DeviceOn(DeviceIntPtr); +static int DeviceOff(DeviceIntPtr); +static int DeviceClose(DeviceIntPtr); +static Bool QueryHardware(InputInfoPtr); +static void ReadDevDimensions(InputInfoPtr); +#ifndef NO_DRIVER_SCALING +static void ScaleCoordinates(SynapticsPrivate * priv, + struct SynapticsHwState *hw); +static void CalculateScalingCoeffs(SynapticsPrivate * priv); +#endif +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, + NULL, +#ifdef XI86_DRV_CAP_SERVER_FD + XI86_DRV_CAP_SERVER_FD +#endif +}; + +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 + ****************************************************************************/ +static inline void +SynapticsCloseFd(InputInfoPtr pInfo) +{ + if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) { + xf86CloseSerial(pInfo->fd); + pInfo->fd = -1; + } +} + +/** + * 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 = 255; + + 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 = 15; + + xf86IDrvMsg(pInfo, X_PROBED, + "invalid finger width range. defaulting to %d - %d\n", + priv->minw, priv->maxw); + } +} + +static Bool +SetDeviceAndProtocol(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = pInfo->private; + char *proto, *device; + int i; + + proto = xf86SetStrOption(pInfo->options, "Protocol", NULL); + device = xf86SetStrOption(pInfo->options, "Device", NULL); + + /* If proto is auto-dev, unset and let the code do the rest */ + if (proto && !strcmp(proto, "auto-dev")) { + free(proto); + proto = 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; + + return (priv->proto_ops != 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 || + priv->model == MODEL_UNIBODY_MACBOOK) { + 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; +} + +static void +calculate_tap_hysteresis(SynapticsPrivate * priv, int range, + int *fingerLow, int *fingerHigh) +{ + switch (priv->model) { + case MODEL_ELANTECH: + /* All Elantech touchpads don't need the Z filtering to get the + * number of fingers correctly. See Documentation/elantech.txt + * in the kernel. + */ + *fingerLow = priv->minp + 1; + *fingerHigh = priv->minp + 1; + break; + case MODEL_UNIBODY_MACBOOK: + *fingerLow = 70; + *fingerHigh = 75; + break; + default: + *fingerLow = priv->minp + range * (25.0 / 256); + *fingerHigh = priv->minp + range * (30.0 / 256); + break; + } +} + +/* 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; + double percent = xf86CheckPercentOption(options, optname, -1); + + if (percent >= 0.0) { + percent = xf86SetPercentOption(options, optname, -1); + result = percent / 100.0 * range + offset; + } else + result = xf86SetIntOption(options, optname, default_value); + + return result; +} + +Bool +SynapticsIsSoftButtonAreasValid(int *values) +{ + Bool right_disabled = FALSE; + Bool middle_disabled = FALSE; + + enum { + /* right button left, right, top, bottom */ + RBL = 0, + RBR = 1, + RBT = 2, + RBB = 3, + /* middle button left, right, top, bottom */ + MBL = 4, + MBR = 5, + MBT = 6, + MBB = 7, + }; + + /* Check right button area */ + if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) || + (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB]))) + return FALSE; + + /* Check middle button area */ + if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) || + (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB]))) + return FALSE; + + if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0) + right_disabled = TRUE; + + if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0) + middle_disabled = TRUE; + + if (!right_disabled && + ((values[RBL] && values[RBL] == values[RBR]) || + (values[RBT] && values[RBT] == values[RBB]))) + return FALSE; + + if (!middle_disabled && + ((values[MBL] && values[MBL] == values[MBR]) || + (values[MBT] && values[MBT] == values[MBB]))) + return FALSE; + + /* Check for overlapping button areas */ + if (!right_disabled && !middle_disabled) { + int right_left = values[RBL] ? values[RBL] : INT_MIN; + int right_right = values[RBR] ? values[RBR] : INT_MAX; + int right_top = values[RBT] ? values[RBT] : INT_MIN; + int right_bottom = values[RBB] ? values[RBB] : INT_MAX; + int middle_left = values[MBL] ? values[MBL] : INT_MIN; + int middle_right = values[MBR] ? values[MBR] : INT_MAX; + int middle_top = values[MBT] ? values[MBT] : INT_MIN; + int middle_bottom = values[MBB] ? values[MBB] : INT_MAX; + + /* If areas overlap in the Y axis */ + if ((right_bottom <= middle_bottom && right_bottom >= middle_top) || + (right_top <= middle_bottom && right_top >= middle_top)) { + /* Check for overlapping left edges */ + if ((right_left < middle_left && right_right > middle_left) || + (middle_left < right_left && middle_right > right_left)) + return FALSE; + + /* Check for overlapping right edges */ + if ((right_right > middle_right && right_left < middle_right) || + (middle_right > right_right && middle_left < right_right)) + return FALSE; + } + + /* If areas overlap in the X axis */ + if ((right_left >= middle_left && right_left <= middle_right) || + (right_right >= middle_left && right_right <= middle_right)) { + /* Check for overlapping top edges */ + if ((right_top < middle_top && right_bottom > middle_top) || + (middle_top < right_top && middle_bottom > right_top)) + return FALSE; + + /* Check for overlapping bottom edges */ + if ((right_bottom > middle_bottom && right_top < middle_bottom) || + (middle_bottom > right_bottom && middle_top < right_bottom)) + return FALSE; + } + } + + return TRUE; +} + +static void +set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset) +{ + SynapticsPrivate *priv = pInfo->private; + SynapticsParameters *pars = &priv->synpara; + int values[8]; + int in_percent = 0; /* bitmask for which ones are in % */ + char *option_string; + char *next_num; + char *end_str; + int i; + int width, height; + + if (!pars->clickpad) + return; + + option_string = xf86SetStrOption(pInfo->options, option_name, NULL); + if (!option_string) + return; + + next_num = option_string; + + for (i = 0; i < 8 && *next_num != '\0'; i++) { + long int value = strtol(next_num, &end_str, 0); + + if (value > INT_MAX || value < -INT_MAX) + goto fail; + + values[i] = value; + + if (next_num != end_str) { + if (*end_str == '%') { + in_percent |= 1 << i; + end_str++; + } + next_num = end_str; + } + else + goto fail; + } + + if (i < 8 || *next_num != '\0') + goto fail; + + width = priv->maxx - priv->minx; + height = priv->maxy - priv->miny; + + for (i = 0; in_percent && i < 8; i++) { + int base, size; + + if ((in_percent & (1 << i)) == 0 || values[i] == 0) + continue; + + size = ((i % 4) < 2) ? width : height; + base = ((i % 4) < 2) ? priv->minx : priv->miny; + values[i] = base + size * values[i] / 100.0; + } + + if (!SynapticsIsSoftButtonAreasValid(values)) + goto fail; + + memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int)); + memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int)); + + free(option_string); + + return; + + fail: + xf86IDrvMsg(pInfo, X_ERROR, + "invalid %s value '%s', keeping defaults\n", + option_name, option_string); + free(option_string); +} + +static void +set_primary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA); +} + +static void +set_secondary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA); +} + +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 */ + double accelFactor; /* 1/pixels */ + int fingerLow, fingerHigh; /* pressure */ + int emulateTwoFingerMinZ; /* pressure */ + int emulateTwoFingerMinW; /* width */ + 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; + int middle_button_timeout; + int grab_event_device = 0; + const char *source; + + /* 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; + 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 + 1; + + calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh); + + /* scaling based on defaults and a pressure of 256 */ + emulateTwoFingerMinZ = priv->minp + range * (282.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 + 1; + + /* 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 */ + tapButton1 = 1; + tapButton2 = 3; + tapButton3 = 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->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->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad); /* Probed */ + if (pars->clickpad) + pars->has_secondary_buttons = xf86SetBoolOption(opts, + "HasSecondarySoftButtons", + pars->has_secondary_buttons); + pars->clickpad_ignore_motion_time = 100; /* ms */ + /* middle mouse button emulation on a clickpad? nah, you're joking */ + middle_button_timeout = pars->clickpad ? 0 : 75; + pars->emulate_mid_button_time = + xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout); + pars->emulate_twofinger_z = + xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ); + pars->emulate_twofinger_w = + xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW); + pars->scroll_dist_vert = + xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta); + pars->scroll_dist_horiz = + xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta); + 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->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON); + + 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->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE); + pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000); + pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2); + pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3); + 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->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE); + + pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4); + pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7); + pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor); + 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); + + /* Only grab the device by default if it's not coming from a config + backend. This way we avoid the device being added twice and sending + duplicate events. + */ + source = xf86CheckStrOption(opts, "_source", NULL); + if (source == NULL || strncmp(source, "server/", 7) != 0) + grab_event_device = TRUE; + pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device); + + pars->tap_and_drag_gesture = + xf86SetBoolOption(opts, "TapAndDragGesture", TRUE); + pars->resolution_horiz = + xf86SetIntOption(opts, "HorizResolution", horizResolution); + pars->resolution_vert = + xf86SetIntOption(opts, "VertResolution", vertResolution); + if (pars->resolution_horiz <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid X resolution, using 1 instead.\n"); + pars->resolution_horiz = 1; + } + if (pars->resolution_vert <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid Y resolution, using 1 instead.\n"); + pars->resolution_vert = 1; + } + + /* Touchpad sampling rate is too low to detect all movements. + A user may lift one finger and put another one down within the same + EV_SYN or even between samplings so the driver doesn't notice at all. + + We limit the movement to 20 mm within one event, that is more than + recordings showed is needed (17mm on a T440). + */ + if (pars->resolution_horiz > 1 && + pars->resolution_vert > 1) + pars->maxDeltaMM = 20; + else { + /* on devices without resolution set the vector length to 0.25 of + the touchpad diagonal */ + pars->maxDeltaMM = diag * 0.25; + } + + + /* 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"); + } + + set_primary_softbutton_areas_option(pInfo); + if (pars->has_secondary_buttons) + set_secondary_softbutton_areas_option(pInfo); +} + +static double +SynapticsAccelerationProfile(DeviceIntPtr dev, + DeviceVelocityPtr vel, + double velocity, double thr, double 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; +} + +static int +SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + 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 */ + if (!SetDeviceAndProtocol(pInfo)) { + 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); + + set_default_parameters(pInfo); + +#ifndef NO_DRIVER_SCALING + CalculateScalingCoeffs(priv); +#endif + + + 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 (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + + return Success; + + SetupProc_fail: + SynapticsCloseFd(pInfo); + + if (priv->comm.buffer) + XisbFree(priv->comm.buffer); + 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); + if (priv && priv->scroll_events_mask) + valuator_mask_free(&priv->scroll_events_mask); + if (priv && priv->open_slots) + free(priv->open_slots); + 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 int +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 int +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)) + goto error; + + priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE); + if (!priv->comm.buffer) + goto error; + + xf86FlushInput(pInfo->fd); + + /* reinit the pad */ + if (!QueryHardware(pInfo)) + goto error; + + xf86AddEnabledDevice(pInfo); + dev->public.on = TRUE; + + return Success; + +error: + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + return !Success; +} + +static void +SynapticsReset(SynapticsPrivate * priv) +{ + int i; + + SynapticsResetHwState(priv->hwState); + SynapticsResetHwState(priv->local_hw_state); + SynapticsResetHwState(priv->comm.hwState); + + memset(priv->move_hist, 0, sizeof(priv->move_hist)); + priv->hyst_center_x = 0; + priv->hyst_center_y = 0; + memset(&priv->scroll, 0, sizeof(priv->scroll)); + priv->count_packet_finger = 0; + priv->finger_state = FS_UNTOUCHED; + priv->last_motion_millis = 0; + priv->clickpad_click_millis = 0; + priv->last_button_area = NO_BUTTON_AREA; + priv->tap_state = TS_START; + priv->tap_button = 0; + priv->tap_button_state = TBS_BUTTON_UP; + priv->moving_state = MS_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; + priv->circ_scroll_on = FALSE; + priv->circ_scroll_vert = FALSE; + priv->mid_emu_state = MBE_OFF; + priv->nextRepeat = 0; + priv->lastButtons = 0; + priv->prev_z = 0; + priv->prevFingers = 0; + priv->num_active_touches = 0; + + for (i = 0; i < priv->num_slots; i++) + priv->open_slots[i] = -1; +} + +static int +DeviceOff(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + Bool rc = Success; + + DBG(3, "Synaptics DeviceOff called\n"); + + if (pInfo->fd != -1) { + TimerCancel(priv->timer); + xf86RemoveEnabledDevice(pInfo); + SynapticsReset(priv); + + if (priv->proto_ops->DeviceOffHook && + !priv->proto_ops->DeviceOffHook(pInfo)) + rc = !Success; + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + } + dev->public.on = FALSE; + return rc; +} + +static int +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(priv->touch_axes); + priv->touch_axes = NULL; + SynapticsHwStateFree(&priv->hwState); + SynapticsHwStateFree(&priv->local_hw_state); + SynapticsHwStateFree(&priv->comm.hwState); + return RetValue; +} + +static void +InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv) +{ + int i; + + memset(labels, 0, nlabels * sizeof(Atom)); + switch (nlabels) { + default: + case 4: + labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL); + case 3: + labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL); + case 2: + labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y); + case 1: + labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X); + break; + } + + for (i = 0; i < priv->num_mt_axes; i++) { + SynapticsTouchAxisRec *axis = &priv->touch_axes[i]; + int axnum = nlabels - priv->num_mt_axes + i; + + labels[axnum] = XIGetKnownProperty(axis->label); + } +} + +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; + } +} + +static void +DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + if (!priv->has_touch) + return; + + priv->num_slots = + priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES; + + priv->open_slots = malloc(priv->num_slots * sizeof(int)); + if (!priv->open_slots) { + xf86IDrvMsg(pInfo, X_ERROR, + "failed to allocate open touch slots array\n"); + priv->has_touch = 0; + priv->num_slots = 0; + } +} + +static int +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; + int num_axes = 2; + Atom btn_labels[SYN_MAX_BUTTONS] = { 0 }; + Atom *axes_labels; + DeviceVelocityPtr pVel; + + num_axes += 2; + + num_axes += priv->num_mt_axes; + + axes_labels = calloc(num_axes, sizeof(Atom)); + if (!axes_labels) { + xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n"); + return !Success; + } + + InitAxesLabels(axes_labels, num_axes, priv); + InitButtonLabels(btn_labels, SYN_MAX_BUTTONS); + + 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, + btn_labels, + SynapticsCtrl, + GetMotionHistorySize(), num_axes, axes_labels); + + /* + * setup dix acceleration to match legacy synaptics settings, and + * etablish a device-specific profile to do stuff like pressure-related + * acceleration. + */ + 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, "(accel) MaxSpeed is now %.2f\n", + priv->synpara.max_speed); + xf86IDrvMsg(pInfo, X_CONFIG, "(accel) 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); + } + + /* X valuator */ + if (priv->minx < priv->maxx && priv->synpara.resolution_detect) { + min = priv->minx; + max = priv->maxx; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max, + priv->resx * 1000, 0, priv->resx * 1000, + Relative); + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ + if (priv->miny < priv->maxy && priv->synpara.resolution_detect) { + min = priv->miny; + max = priv->maxy; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max, + priv->resy * 1000, 0, priv->resy * 1000, + Relative); + xf86InitValuatorDefaults(dev, 1); + + xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_horiz = 2; + xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_vert = 3; + priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS); + if (!priv->scroll_events_mask) { + free(axes_labels); + return !Success; + } + + SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL, + priv->synpara.scroll_dist_horiz, 0); + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + priv->synpara.scroll_dist_vert, 0); + + DeviceInitTouch(dev, axes_labels); + + free(axes_labels); + + priv->hwState = SynapticsHwStateAlloc(priv); + if (!priv->hwState) + goto fail; + + priv->local_hw_state = SynapticsHwStateAlloc(priv); + if (!priv->local_hw_state) + goto fail; + + priv->comm.hwState = SynapticsHwStateAlloc(priv); + + InitDeviceProperties(pInfo); + XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL); + + SynapticsReset(priv); + + return Success; + + fail: + free(priv->local_hw_state); + free(priv->hwState); + free(priv->open_slots); + 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 enum EdgeType +circular_edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 enum EdgeType +edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 a finger is down, then it must have started inside the active_area, + allow the motion to complete using the entire area */ + if (priv->finger_state >= FS_TOUCHED) + return 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 Bool +is_inside_button_area(SynapticsParameters * para, int which, int x, int y) +{ + Bool inside_area = TRUE; + + if (para->softbutton_areas[which][LEFT] == 0 && + para->softbutton_areas[which][RIGHT] == 0 && + para->softbutton_areas[which][TOP] == 0 && + para->softbutton_areas[which][BOTTOM] == 0) + return FALSE; + + if (para->softbutton_areas[which][LEFT] && + x < para->softbutton_areas[which][LEFT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][RIGHT] && + x > para->softbutton_areas[which][RIGHT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][TOP] && + y < para->softbutton_areas[which][TOP]) + inside_area = FALSE; + else if (para->softbutton_areas[which][BOTTOM] && + y > para->softbutton_areas[which][BOTTOM]) + inside_area = FALSE; + + return inside_area; +} + +static Bool +is_inside_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset, + int x, int y) +{ + Bool inside_area = TRUE; + Bool right_valid, middle_valid; + int top, bottom; + + /* We don't have a left button area, so we only check the y axis */ + right_valid = para->softbutton_areas[offset][TOP] || + para->softbutton_areas[offset][BOTTOM]; + middle_valid = para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset + 1][BOTTOM]; + + if (!right_valid && !middle_valid) + return FALSE; + + /* Check both buttons are horizontally aligned */ + if (right_valid && middle_valid && ( + para->softbutton_areas[offset][TOP] != + para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset][BOTTOM] != + para->softbutton_areas[offset + 1][BOTTOM])) + return FALSE; + + if (right_valid) { + top = para->softbutton_areas[offset][TOP]; + bottom = para->softbutton_areas[offset][BOTTOM]; + } + else { + top = para->softbutton_areas[offset + 1][TOP]; + bottom = para->softbutton_areas[offset + 1][BOTTOM]; + } + + if (top && y < top) + inside_area = FALSE; + else if (bottom && y > bottom) + inside_area = FALSE; + + return inside_area; +} + +static enum SoftButtonAreas +current_button_area(SynapticsParameters * para, int x, int y) +{ + if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y)) + return BOTTOM_BUTTON_AREA; + else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y)) + return TOP_BUTTON_AREA; + else + return NO_BUTTON_AREA; +} + +static CARD32 +timerFunc(OsTimerPtr timer, CARD32 now, pointer arg) +{ + InputInfoPtr pInfo = arg; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + struct SynapticsHwState *hw = priv->local_hw_state; + int delay; +#if !HAVE_THREADED_INPUT + int sigstate = xf86BlockSIGIO(); +#else + input_lock(); +#endif + + priv->hwState->millis += now - priv->timer_time; + SynapticsCopyHwState(hw, priv->hwState); + SynapticsResetTouchHwState(hw, FALSE); + delay = HandleState(pInfo, hw, hw->millis, TRUE); + + priv->timer_time = now; + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + +#if !HAVE_THREADED_INPUT + xf86UnblockSIGIO(sigstate); +#else + input_unlock(); +#endif + + 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 = priv->local_hw_state; + int delay = 0; + Bool newDelay = FALSE; + + SynapticsResetTouchHwState(hw, FALSE); + + while (SynapticsGetHwState(pInfo, priv, hw)) { + /* Semi-mt device touch slots do not track touches. When there is a + * change in the number of touches, we must disregard the temporary + * motion changes. */ + if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) { + hw->cumulative_dx = priv->hwState->cumulative_dx; + hw->cumulative_dy = priv->hwState->cumulative_dy; + } + + /* timer may cause actual events to lag behind (#48777) */ + if (priv->hwState->millis > hw->millis) + hw->millis = priv->hwState->millis; + + SynapticsCopyHwState(priv->hwState, hw); + delay = HandleState(pInfo, hw, hw->millis, FALSE); + newDelay = TRUE; + } + + if (newDelay) { + priv->timer_time = GetTimeInMillis(); + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + } +} + +static int +HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw, + CARD32 now, int *delay) +{ + SynapticsParameters *para = &priv->synpara; + Bool done = FALSE; + int timeleft; + int mid = 0; + + if (para->emulate_mid_button_time <= 0) + return mid; + + while (!done) { + switch (priv->mid_emu_state) { + case MBE_LEFT_CLICK: + case MBE_RIGHT_CLICK: + case MBE_OFF: + priv->button_delay_millis = now; + 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, now); + 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, now); + 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_low) + return FS_UNTOUCHED; + + if (priv->finger_state == FS_BLOCKED) + return FS_BLOCKED; + + if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED) + finger = FS_TOUCHED; + else + finger = priv->finger_state; + + if (!para->palm_detect) + return finger; + + /* palm detection */ + + if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width)) + return FS_BLOCKED; + + if (priv->has_mt_palm_detect) + return finger; + + if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED) + priv->avg_width = 0; + else + priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2; + + if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) { + 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; + + return finger; +} + +static void +SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge) +{ + enum TapEvent tap; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) { + priv->tap_button = 0; + return; + } + + switch (priv->tap_max_fingers) { + case 1: + 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; + default: + priv->tap_button = 0; + return; + } + + 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, CARD32 millis) +{ + DBG(3, "SetTapState - %d -> %d (millis:%u)\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: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_2B: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_3: + priv->tap_button_state = TBS_BUTTON_DOWN; + break; + case TS_SINGLETAP: + 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, + CARD32 millis) +{ + DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n", + priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y, + millis); + + 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, + CARD32 now, enum FingerState finger, + Bool inside_active_area) +{ + SynapticsParameters *para = &priv->synpara; + Bool touch, release, is_timeout, move, press; + int timeleft, timeout; + enum EdgeType edge; + int delay = 1000000000; + + if (para->touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) + return delay; + + touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED; + release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED; + move = (finger >= FS_TOUCHED && + (priv->tap_max_fingers <= + ((priv->horiz_scroll_twofinger_on || + priv->vert_scroll_twofinger_on) ? 2 : 1)) && + (priv->prevFingers == hw->numFingers && + ((abs(hw->x - priv->touch_on.x) >= para->tap_move) || + (abs(hw->y - priv->touch_on.y) >= para->tap_move)))); + press = (hw->left || hw->right || hw->middle); + + if (touch) { + priv->touch_on.x = hw->x; + priv->touch_on.y = hw->y; + priv->touch_on.millis = now; + } + else if (release) { + priv->touch_on.millis = now; + } + 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, now); + is_timeout = timeleft <= 0; + + restart: + switch (priv->tap_state) { + case TS_START: + if (touch) + SetTapState(priv, TS_1, now); + break; + case TS_1: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_MOVE, now); + goto restart; + } + else if (is_timeout) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_MOVE, now); + 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, now); + } + break; + case TS_MOVE: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_2A: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_2B: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_SINGLETAP: + if (touch) + SetTapState(priv, TS_1, now); + else if (is_timeout) + SetTapState(priv, TS_START, now); + break; + case TS_3: + if (move) { + if (para->tap_and_drag_gesture) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (is_timeout) { + if (para->tap_and_drag_gesture) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (release) { + SetTapState(priv, TS_2B, now); + } + break; + case TS_DRAG: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (release) { + SetMovingState(priv, MS_FALSE, now); + if (para->locked_drags) { + SetTapState(priv, TS_4, now); + } + else { + SetTapState(priv, TS_START, now); + } + } + break; + case TS_4: + if (is_timeout) { + SetTapState(priv, TS_START, now); + goto restart; + } + if (touch) + SetTapState(priv, TS_5, now); + break; + case TS_5: + if (is_timeout || move) { + SetTapState(priv, TS_DRAG, now); + goto restart; + } + else if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_CLICKPAD_MOVE: + /* Disable scrolling once a button is pressed on a clickpad */ + priv->vert_scroll_edge_on = FALSE; + priv->horiz_scroll_edge_on = FALSE; + priv->vert_scroll_twofinger_on = FALSE; + priv->horiz_scroll_twofinger_on = FALSE; + + /* Assume one touch is only for holding the clickpad button down */ + if (hw->numFingers > 1) + hw->numFingers--; + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (!press) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_MOVE, now); + priv->count_packet_finger = 0; + } + break; + } + + timeout = GetTimeOut(priv); + if (timeout >= 0) { + timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now); + delay = clamp(timeleft, 1, delay); + } + return delay; +} + +#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)]) +#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e)) + +static void +store_history(SynapticsPrivate * priv, int x, int y, CARD32 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; + if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY) + priv->count_packet_finger++; +} + +/* + * 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(SynapticsPrivate *priv, const struct SynapticsHwState *hw, + enum EdgeType edge, double *dx, double *dy) +{ + *dx = hw->x - HIST(0).x; + *dy = hw->y - HIST(0).y; +} + +/* Vector length, but not sqrt'ed, we only need it for comparison */ +static inline double +vlenpow2(double x, double y) +{ + return x * x + y * y; +} + +/** + * Compute relative motion ('deltas') including edge motion. + */ +static int +ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw, + enum EdgeType edge, int *dxP, int *dyP, Bool inside_area) +{ + enum MovingState moving_state; + double dx, dy; + double vlen; + 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: + moving_state = MS_TOUCHPAD_RELATIVE; + break; + default: + break; + } + } + + if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED || + 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 || + (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) { + /* reset packet counter. */ + priv->count_packet_finger = 0; + goto out; + } + + /* To create the illusion of fluid motion, call back at roughly the report + * rate, even in the absence of new hardware events; see comment above + * POLL_MS declaration. */ + delay = MIN(delay, POLL_MS); + + if (priv->count_packet_finger <= 1) + goto out; /* skip the lot */ + + if (moving_state == MS_TOUCHPAD_RELATIVE) + get_delta(priv, hw, edge, &dx, &dy); + + out: + priv->prevFingers = hw->numFingers; + + vlen = vlenpow2(dx/priv->synpara.resolution_horiz, + dy/priv->synpara.resolution_vert); + + if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) { + dx = 0; + dy = 0; + } + + *dxP = dx; + *dyP = dy; + + return delay; +} + +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->scroll.coast_delta_y = 0.0; + priv->scroll.coast_delta_x = 0.0; + + if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) { + double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0; + + if (vert && !circ) { + double dy = + estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y); + if (pkt_time > 0) { + double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y); + } + } + } + if (horiz && !circ) { + double dx = + estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x); + if (pkt_time > 0) { + double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x); + } + } + } + if (circ) { + double da = estimate_delta_circ(priv); + + if (pkt_time > 0) { + double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + if (vert) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + else if (horiz) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + } + } + } + } + priv->scroll.packets_this_scroll = 0; +} + +static void +stop_coasting(SynapticsPrivate * priv) +{ + priv->scroll.coast_speed_x = 0; + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; +} + +static int +HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw, + enum EdgeType edge, Bool finger) +{ + SynapticsParameters *para = &priv->synpara; + int delay = 1000000000; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF || + priv->synpara.touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) { + 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 == FS_UNTOUCHED) { + stop_coasting(priv); + priv->scroll.delta_y = 0; + priv->scroll.delta_x = 0; + 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.last_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)) { + stop_coasting(priv); + priv->vert_scroll_twofinger_on = TRUE; + priv->vert_scroll_edge_on = FALSE; + priv->scroll.last_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)) { + stop_coasting(priv); + priv->horiz_scroll_twofinger_on = TRUE; + priv->horiz_scroll_edge_on = FALSE; + priv->scroll.last_x = hw->x; + DBG(7, "horiz two-finger scroll detected\n"); + } + } + } + if (finger && priv->finger_state == FS_UNTOUCHED) { + 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.last_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.last_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->scroll.coast_speed_x || priv->scroll.coast_speed_y)) { + 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->scroll.coast_speed_y == 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.last_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->scroll.coast_speed_x == 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.last_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.packets_this_scroll++; + } + + if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) { + /* + = down, - = up */ + if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) { + priv->scroll.delta_y += (hw->y - priv->scroll.last_y); + priv->scroll.last_y = hw->y; + } + } + if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) { + /* + = right, - = left */ + if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) { + priv->scroll.delta_x += (hw->x - priv->scroll.last_x); + priv->scroll.last_x = hw->x; + } + } + if (priv->circ_scroll_on) { + /* + = counter clockwise, - = clockwise */ + double delta = para->scroll_dist_circ; + double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y)); + + if (delta >= 0.005 && diff != 0.0) { + if (priv->circ_scroll_vert) + priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert; + else + priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz; + priv->scroll.last_a = angle(priv, hw->x, hw->y); + } + } + + if (priv->scroll.coast_speed_y) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddy = para->coasting_friction * dtime; + + priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_y) < ddy) { + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_y += + (priv->scroll.coast_speed_y < 0 ? ddy : -ddy); + } + } + + if (priv->scroll.coast_speed_x) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddx = para->coasting_friction * dtime; + priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_x) < ddx) { + priv->scroll.coast_speed_x = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_x += + (priv->scroll.coast_speed_x < 0 ? ddx : -ddx); + } + } + + return delay; +} + +/** + * Check if any 2+ fingers are close enough together to assume this is a + * ClickFinger action. + */ +static int +clickpad_guess_clickfingers(SynapticsPrivate * priv, + struct SynapticsHwState *hw) +{ + int nfingers = 0; + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + +#ifdef BUG_RETURN_VAL + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); +#endif + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; + + if (hw->slot_state[i] == SLOTSTATE_EMPTY || + hw->slot_state[i] == SLOTSTATE_CLOSE) + continue; + + f1 = hw->mt_mask[i]; + + for (j = i + 1; j < hw->num_mt_mask; j++) { + ValuatorMask *f2; + double x1, x2, y1, y2; + + if (hw->slot_state[j] == SLOTSTATE_EMPTY || + hw->slot_state[j] == SLOTSTATE_CLOSE) + continue; + + f2 = hw->mt_mask[j]; + + x1 = valuator_mask_get_double(f1, 0); + y1 = valuator_mask_get_double(f1, 1); + + x2 = valuator_mask_get_double(f2, 0); + y2 = valuator_mask_get_double(f2, 1); + + /* FIXME: fingers closer together than 30% of touchpad width, but + * really, this should be dependent on the touchpad size. Also, + * you'll need to find a touchpad that doesn't lie about it's + * size. Good luck. */ + if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 && + abs(y1 - y2) < (priv->maxy - priv->miny) * .3) { + close_point |= (1 << j); + close_point |= (1 << i); + } + } + } + + while (close_point > 0) { + nfingers += close_point & 0x1; + close_point >>= 1; + } + + /* Some trackpads touchpad only track two touchpoints but announce + * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens + * the user likely intents to do a 3 finger click, so handle it as such. + */ + if (hw->numFingers >= 3 && hw->num_mt_mask < 3) + nfingers = 3; + + return nfingers; +} + +static void +handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw) +{ + SynapticsParameters *para = &priv->synpara; + int action = 0; + int nfingers = hw->numFingers; + + /* if this is a clickpad, clickfinger handling is: + * one finger down: no action, this is a normal click + * two fingers down: F2_CLICK + * three fingers down: F3_CLICK + */ + + if (para->clickpad) + nfingers = clickpad_guess_clickfingers(priv, hw); + + switch (nfingers) { + 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 | BTN_EMULATED_FLAG; + break; + case 2: + hw->left = 0; + hw->middle = 1 | BTN_EMULATED_FLAG; + break; + case 3: + hw->left = 0; + hw->right = 1 | BTN_EMULATED_FLAG; + break; + } +} + +/* 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, + CARD32 now, 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, now, delay); + + /* If this is a clickpad and the user clicks in a soft button area, press + * the soft button instead. */ + if (para->clickpad) { + /* hw->left is down, but no other buttons were already down */ + if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) { + /* If the finger down event is delayed, the x and y + * coordinates are stale so we delay processing the click */ + if (hw->z < para->finger_low) { + hw->left = 0; + goto out; + } + if (is_inside_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + priv->clickpad_click_millis = now; + } + else if (hw->left) { + hw->left = (priv->lastButtons & 1) ? 1 : 0; + hw->middle = (priv->lastButtons & 2) ? 1 : 0; + hw->right = (priv->lastButtons & 4) ? 1 : 0; + } + } + + /* Fingers emulate other buttons. ClickFinger can only be + triggered on transition, when left is pressed + */ + if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1) + handle_clickfinger(priv, hw); + +out: + /* 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) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + valuator_mask_zero(priv->scroll_events_mask); + + if (priv->scroll.delta_y != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_vert, priv->scroll.delta_y); + priv->scroll.delta_y = 0; + } + if (priv->scroll.delta_x != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_horiz, priv->scroll.delta_x); + priv->scroll.delta_x = 0; + } + if (valuator_mask_num_valuators(priv->scroll_events_mask)) + xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask); +} + +static inline int +repeat_scrollbuttons(const InputInfoPtr pInfo, + const struct SynapticsHwState *hw, + int buttons, CARD32 now, int delay) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + int repeat_delay, timeleft; + int rep_buttons = 0; + + if (para->updown_button_repeat) + rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1)); + if (para->leftright_button_repeat) + rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1)); + + /* 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 = now + repeat_delay * 2; + } + } + else { + priv->repeatButtons = 0; + priv->nextRepeat = 0; + } + + if (priv->repeatButtons) { + timeleft = TIME_DIFF(priv->nextRepeat, now); + 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)); + if (id == 4) + priv->scroll.delta_y -= para->scroll_dist_vert; + else if (id == 5) + priv->scroll.delta_y += para->scroll_dist_vert; + else if (id == 6) + priv->scroll.delta_x -= para->scroll_dist_horiz; + else if (id == 7) + priv->scroll.delta_x += para->scroll_dist_horiz; + } + + priv->nextRepeat = now + repeat_delay; + delay = MIN(delay, repeat_delay); + } + } + + return delay; +} + +/* Update the open slots and number of active touches */ +static void +UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + int i; + + for (i = 0; i < hw->num_mt_mask; i++) { + if (hw->slot_state[i] == SLOTSTATE_OPEN) { + priv->open_slots[priv->num_active_touches] = i; + priv->num_active_touches++; + BUG_WARN(priv->num_active_touches > priv->num_slots); + } + else if (hw->slot_state[i] == SLOTSTATE_CLOSE) { + Bool found = FALSE; + int j; + + for (j = 0; j < priv->num_active_touches - 1; j++) { + if (priv->open_slots[j] == i) + found = TRUE; + + if (found) + priv->open_slots[j] = priv->open_slots[j + 1]; + } + + BUG_WARN(priv->num_active_touches == 0); + if (priv->num_active_touches > 0) + priv->num_active_touches--; + } + } + + SynapticsResetTouchHwState(hw, FALSE); +} + +static void +filter_jitter(SynapticsPrivate * priv, int *x, int *y) +{ + SynapticsParameters *para = &priv->synpara; + + priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x); + priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y); + *x = priv->hyst_center_x; + *y = priv->hyst_center_y; +} + +static void +reset_hw_state(struct SynapticsHwState *hw) +{ + hw->x = 0; + hw->y = 0; + hw->z = 0; + hw->numFingers = 0; + hw->fingerWidth = 0; +} + +/* + * 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. + * + * from_timer denotes if HandleState was triggered from a timer (e.g. to + * generate fake motion events, or for the tap-to-click state machine), rather + * than from having received a motion event. + */ +static int +HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now, + Bool from_timer) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + enum FingerState finger = FS_UNTOUCHED; + int dx = 0, dy = 0, buttons, id; + enum EdgeType edge = NO_EDGE; + int change; + int double_click = FALSE; + int delay = 1000000000; + int timeleft; + Bool inside_active_area; + Bool using_cumulative_coords = FALSE; + Bool ignore_motion; + + /* We need both and x/y, the driver can't handle just one of the two + * yet. But since it's possible to hit a phys button on non-clickpads + * without ever getting motion data first, we must continue with 0/0 for + * that case. */ + if (hw->x == INT_MIN || hw->y == INT_MAX) { + if (para->clickpad) + return delay; + else if (hw->left || hw->right || hw->middle) { + hw->x = (hw->x == INT_MIN) ? 0 : hw->x; + hw->y = (hw->y == INT_MIN) ? 0 : hw->y; + } + } + + /* If a physical button is pressed on a clickpad or a two-finger scrolling + * is ongoing, use cumulative relative touch movements for motion */ + if (para->clickpad && + ((priv->lastButtons & 7) || + (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) && + priv->last_button_area != TOP_BUTTON_AREA) { + hw->x = hw->cumulative_dx; + hw->y = hw->cumulative_dy; + using_cumulative_coords = TRUE; + } + + /* 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. */ + filter_jitter(priv, &hw->x, &hw->y); + + inside_active_area = is_inside_active_area(priv, hw->x, hw->y); + + /* Ignore motion *starting* inside softbuttonareas */ + if (priv->finger_state < FS_TOUCHED) + priv->last_button_area = current_button_area(para, hw->x, hw->y); + /* If we already have a finger down, clear last_button_area if it goes + outside of the softbuttonareas */ + else if (priv->last_button_area != NO_BUTTON_AREA && + current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA) + priv->last_button_area = NO_BUTTON_AREA; + + ignore_motion = para->touchpad_off == TOUCHPAD_OFF || + (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA); + + /* these two just update hw->left, right, etc. */ + update_hw_button_state(pInfo, hw, now, &delay); + if (priv->has_scrollbuttons) + double_click = adjust_state_from_scrollbuttons(pInfo, hw); + + /* Ignore motion the first X ms after a clickpad click */ + if (priv->clickpad_click_millis) { + if(TIME_DIFF(priv->clickpad_click_millis + + para->clickpad_ignore_motion_time, now) > 0) + ignore_motion = TRUE; + else + priv->clickpad_click_millis = 0; + } + + /* 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) + reset_hw_state(hw); + + /* no edge or finger detection outside of area */ + if (inside_active_area) { + edge = edge_detection(priv, hw->x, hw->y); + if (!from_timer) + finger = SynapticsDetectFinger(priv, hw); + else + finger = priv->finger_state; + } + + /* 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, now, 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 >= FS_TOUCHED)); + 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. + */ +#ifndef NO_DRIVER_SCALING + ScaleCoordinates(priv, hw); +#endif + } + + dx = dy = 0; + + 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 && priv->tap_button_state == TBS_BUTTON_DOWN) + buttons |= 1 << (priv->tap_button - 1); + + /* Post events */ + if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion) + 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); + } + + if (priv->has_scrollbuttons) + delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay); + + /* Process scroll events only if coordinates are + * in the Synaptics Area + */ + if (inside_active_area && + (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) { + post_scroll_events(pInfo); + priv->scroll.last_millis = hw->millis; + } + + if (double_click) { + post_button_click(pInfo, 1); + post_button_click(pInfo, 1); + } + + UpdateTouchState(pInfo, hw); + + /* 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) +{ + DBG(3, "SwitchMode called\n"); + + return XI_BadMode; +} + +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; +} + +#ifndef NO_DRIVER_SCALING +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; + } +} +#endif --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/115_evdev_only.patch/conf/70-synaptics.conf +++ xserver-xorg-input-synaptics-1.9.1/.pc/115_evdev_only.patch/conf/70-synaptics.conf @@ -0,0 +1,46 @@ +# Example xorg.conf.d snippet that assigns the touchpad driver +# to all touchpads. See xorg.conf.d(5) for more information on +# InputClass. +# DO NOT EDIT THIS FILE, your distribution will likely overwrite +# it when updating. Copy (and rename) this file into +# /etc/X11/xorg.conf.d first. +# Additional options may be added in the form of +# Option "OptionName" "value" +# +Section "InputClass" + Identifier "touchpad catchall" + Driver "synaptics" + MatchIsTouchpad "on" +# This option is recommend on all Linux systems using evdev, but cannot be +# enabled by default. See the following link for details: +# http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html +# MatchDevicePath "/dev/input/event*" +EndSection + +Section "InputClass" + Identifier "touchpad ignore duplicates" + MatchIsTouchpad "on" + MatchOS "Linux" + MatchDevicePath "/dev/input/mouse*" + Option "Ignore" "on" +EndSection + +# This option enables the bottom right corner to be a right button on clickpads +# and the right and middle top areas to be right / middle buttons on clickpads +# with a top button area. +# This option is only interpreted by clickpads. +Section "InputClass" + Identifier "Default clickpad buttons" + MatchDriver "synaptics" + Option "SoftButtonAreas" "50% 0 82% 0 0 0 0 0" + Option "SecondarySoftButtonAreas" "58% 0 0 15% 42% 58% 0 15%" +EndSection + +# This option disables software buttons on Apple touchpads. +# This option is only interpreted by clickpads. +Section "InputClass" + Identifier "Disable clickpad buttons on Apple touchpads" + MatchProduct "Apple|bcm5974" + MatchDriver "synaptics" + Option "SoftButtonAreas" "0 0 0 0 0 0 0 0" +EndSection --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/118_quell_error_msg.patch/tools/synclient.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/118_quell_error_msg.patch/tools/synclient.c @@ -0,0 +1,524 @@ +/* + * Copyright © 2002-2005,2007 Peter Osterlund + * + * 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: + * Peter Osterlund (petero2@telia.com) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "synaptics-properties.h" + +#ifndef XATOM_FLOAT +#define XATOM_FLOAT "FLOAT" +#endif + +#define SYN_MAX_BUTTONS 12 +#define SBR_MIN 10 +#define SBR_MAX 1000 + +union flong { /* Xlibs 64-bit property handling madness */ + long l; + float f; +}; + +enum ParaType { + PT_INT, + PT_BOOL, + PT_DOUBLE +}; + +struct Parameter { + char *name; /* Name of parameter */ + enum ParaType type; /* Type of parameter */ + double min_val; /* Minimum allowed value */ + double max_val; /* Maximum allowed value */ + char *prop_name; /* Property name */ + int prop_format; /* Property format (0 for floats) */ + int prop_offset; /* Offset inside property */ +}; + +static struct Parameter params[] = { + {"LeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 0}, + {"RightEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 1}, + {"TopEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 2}, + {"BottomEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 3}, + {"FingerLow", PT_INT, 0, 255, SYNAPTICS_PROP_FINGER, 32, 0}, + {"FingerHigh", PT_INT, 0, 255, SYNAPTICS_PROP_FINGER, 32, 1}, + {"MaxTapTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_TIME, 32, 0}, + {"MaxTapMove", PT_INT, 0, 2000, SYNAPTICS_PROP_TAP_MOVE, 32, 0}, + {"MaxDoubleTapTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 1}, + {"SingleTapTimeout", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 0}, + {"ClickTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 2}, + {"EmulateMidButtonTime", PT_INT, 0, 1000, SYNAPTICS_PROP_MIDDLE_TIMEOUT,32, 0}, + {"EmulateTwoFingerMinZ", PT_INT, 0, 1000, SYNAPTICS_PROP_TWOFINGER_PRESSURE, 32, 0}, + {"EmulateTwoFingerMinW", PT_INT, 0, 15, SYNAPTICS_PROP_TWOFINGER_WIDTH, 32, 0}, + {"VertScrollDelta", PT_INT, -1000, 1000, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 0}, + {"HorizScrollDelta", PT_INT, -1000, 1000, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 1}, + {"VertEdgeScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 0}, + {"HorizEdgeScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 1}, + {"CornerCoasting", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 2}, + {"VertTwoFingerScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 0}, + {"HorizTwoFingerScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 1}, + {"MinSpeed", PT_DOUBLE, 0, 255.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 0}, + {"MaxSpeed", PT_DOUBLE, 0, 255.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 1}, + {"AccelFactor", PT_DOUBLE, 0, 1.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 2}, + {"UpDownScrolling", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING, 8, 0}, + {"LeftRightScrolling", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING, 8, 1}, + {"UpDownScrollRepeat", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT, 8, 0}, + {"LeftRightScrollRepeat", PT_BOOL, 0, 1, SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT, 8, 1}, + {"ScrollButtonRepeat", PT_INT, SBR_MIN , SBR_MAX, SYNAPTICS_PROP_BUTTONSCROLLING_TIME, 32, 0}, + {"TouchpadOff", PT_INT, 0, 2, SYNAPTICS_PROP_OFF, 8, 0}, + {"LockedDrags", PT_BOOL, 0, 1, SYNAPTICS_PROP_LOCKED_DRAGS, 8, 0}, + {"LockedDragTimeout", PT_INT, 0, 30000, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT, 32, 0}, + {"RTCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 0}, + {"RBCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 1}, + {"LTCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 2}, + {"LBCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 3}, + {"TapButton1", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 4}, + {"TapButton2", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 5}, + {"TapButton3", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 6}, + {"ClickFinger1", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 0}, + {"ClickFinger2", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 1}, + {"ClickFinger3", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 2}, + {"CircularScrolling", PT_BOOL, 0, 1, SYNAPTICS_PROP_CIRCULAR_SCROLLING, 8, 0}, + {"CircScrollDelta", PT_DOUBLE, .01, 3, SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST, 0 /* float */, 0}, + {"CircScrollTrigger", PT_INT, 0, 8, SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER, 8, 0}, + {"CircularPad", PT_BOOL, 0, 1, SYNAPTICS_PROP_CIRCULAR_PAD, 8, 0}, + {"PalmDetect", PT_BOOL, 0, 1, SYNAPTICS_PROP_PALM_DETECT, 8, 0}, + {"PalmMinWidth", PT_INT, 0, 15, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 0}, + {"PalmMinZ", PT_INT, 0, 255, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 1}, + {"CoastingSpeed", PT_DOUBLE, 0, 255, SYNAPTICS_PROP_COASTING_SPEED, 0 /* float*/, 0}, + {"CoastingFriction", PT_DOUBLE, 0, 255, SYNAPTICS_PROP_COASTING_SPEED, 0 /* float*/, 1}, + {"PressureMotionMinZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 0}, + {"PressureMotionMaxZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 1}, + {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 0}, + {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 1}, + {"ResolutionDetect", PT_BOOL, 0, 1, SYNAPTICS_PROP_RESOLUTION_DETECT, 8, 0}, + {"GrabEventDevice", PT_BOOL, 0, 1, SYNAPTICS_PROP_GRAB, 8, 0}, + {"TapAndDragGesture", PT_BOOL, 0, 1, SYNAPTICS_PROP_GESTURES, 8, 0}, + {"AreaLeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 0}, + {"AreaRightEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 1}, + {"AreaTopEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 2}, + {"AreaBottomEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 3}, + {"HorizHysteresis", PT_INT, 0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 0}, + {"VertHysteresis", PT_INT, 0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 1}, + {"ClickPad", PT_BOOL, 0, 1, SYNAPTICS_PROP_CLICKPAD, 8, 0}, + {"RightButtonAreaLeft", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 0}, + {"RightButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 1}, + {"RightButtonAreaTop", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 2}, + {"RightButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 3}, + {"MiddleButtonAreaLeft", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 4}, + {"MiddleButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 5}, + {"MiddleButtonAreaTop", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 6}, + {"MiddleButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 7}, + { NULL, 0, 0, 0, 0 } +}; + +static double +parse_cmd(char *cmd, struct Parameter **par) +{ + char *eqp = strchr(cmd, '='); + + *par = NULL; + + if (eqp) { + int j; + int found = 0; + + *eqp = 0; + for (j = 0; params[j].name; j++) { + if (strcasecmp(cmd, params[j].name) == 0) { + found = 1; + break; + } + } + if (found) { + double val = atof(&eqp[1]); + + *par = ¶ms[j]; + + if (val < (*par)->min_val) + val = (*par)->min_val; + if (val > (*par)->max_val) + val = (*par)->max_val; + + return val; + } + else { + printf("Unknown parameter %s\n", cmd); + } + } + else { + printf("Invalid command: %s\n", cmd); + } + + return 0; +} + +/** Init display connection or NULL on error */ +static Display * +dp_init() +{ + Display *dpy = NULL; + XExtensionVersion *v = NULL; + Atom touchpad_type = 0; + Atom synaptics_property = 0; + int error = 0; + + dpy = XOpenDisplay(NULL); + if (!dpy) { + fprintf(stderr, "Failed to connect to X Server.\n"); + error = 1; + goto unwind; + } + + v = XGetExtensionVersion(dpy, INAME); + if (!v->present || + (v->major_version * 1000 + v->minor_version) < + (XI_Add_DeviceProperties_Major * 1000 + + XI_Add_DeviceProperties_Minor)) { + fprintf(stderr, "X server supports X Input %d.%d. I need %d.%d.\n", + v->major_version, v->minor_version, + XI_Add_DeviceProperties_Major, XI_Add_DeviceProperties_Minor); + error = 1; + goto unwind; + } + + /* We know synaptics sets XI_TOUCHPAD for all the devices. */ + touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); + if (!touchpad_type) { + fprintf(stderr, "XI_TOUCHPAD not initialised.\n"); + error = 1; + goto unwind; + } + + synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True); + if (!synaptics_property) { + fprintf(stderr, "Couldn't find synaptics properties. No synaptics " + "driver loaded?\n"); + error = 1; + goto unwind; + } + + unwind: + XFree(v); + if (error && dpy) { + XCloseDisplay(dpy); + dpy = NULL; + } + return dpy; +} + +static XDevice * +dp_get_device(Display * dpy) +{ + XDevice *dev = NULL; + XDeviceInfo *info = NULL; + int ndevices = 0; + Atom touchpad_type = 0; + Atom synaptics_property = 0; + Atom *properties = NULL; + int nprops = 0; + int error = 0; + + touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); + synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True); + info = XListInputDevices(dpy, &ndevices); + + while (ndevices--) { + if (info[ndevices].type == touchpad_type) { + dev = XOpenDevice(dpy, info[ndevices].id); + if (!dev) { + fprintf(stderr, "Failed to open device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + properties = XListDeviceProperties(dpy, dev, &nprops); + if (!properties || !nprops) { + fprintf(stderr, "No properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + while (nprops--) { + if (properties[nprops] == synaptics_property) + break; + } + if (!nprops) { + fprintf(stderr, "No synaptics properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + break; /* Yay, device is suitable */ + } + } + + unwind: + XFree(properties); + XFreeDeviceList(info); + if (!dev) + fprintf(stderr, "Unable to find a synaptics device.\n"); + else if (error && dev) { + XCloseDevice(dpy, dev); + dev = NULL; + } + return dev; +} + +static void +dp_set_variables(Display * dpy, XDevice * dev, int argc, char *argv[], + int first_cmd) +{ + int i; + double val; + struct Parameter *par; + Atom prop, type, float_type; + int format; + unsigned char *data; + unsigned long nitems, bytes_after; + + union flong *f; + long *n; + char *b; + + float_type = XInternAtom(dpy, XATOM_FLOAT, True); + if (!float_type) + fprintf(stderr, "Float properties not available.\n"); + + for (i = first_cmd; i < argc; i++) { + val = parse_cmd(argv[i], &par); + if (!par) + continue; + + prop = XInternAtom(dpy, par->prop_name, True); + if (!prop) { + fprintf(stderr, "Property for '%s' not available. Skipping.\n", + par->name); + continue; + + } + + XGetDeviceProperty(dpy, dev, prop, 0, 1000, False, AnyPropertyType, + &type, &format, &nitems, &bytes_after, &data); + + if (type == None) { + fprintf(stderr, "Property for '%s' not available. Skipping.\n", + par->name); + continue; + } + + switch (par->prop_format) { + case 8: + if (format != par->prop_format || type != XA_INTEGER) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + b = (char *) data; + b[par->prop_offset] = rint(val); + break; + case 32: + if (format != par->prop_format || + (type != XA_INTEGER && type != XA_CARDINAL)) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + n = (long *) data; + n[par->prop_offset] = rint(val); + break; + case 0: /* float */ + if (!float_type) + continue; + if (format != 32 || type != float_type) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + f = (union flong *) data; + f[par->prop_offset].f = val; + break; + } + + XChangeDeviceProperty(dpy, dev, prop, type, format, + PropModeReplace, data, nitems); + XFlush(dpy); + } +} + +/* FIXME: horribly inefficient. */ +static void +dp_show_settings(Display * dpy, XDevice * dev) +{ + int j; + Atom a, type, float_type; + int format; + unsigned long nitems, bytes_after; + unsigned char *data; + int len; + + union flong *f; + long *i; + char *b; + + float_type = XInternAtom(dpy, XATOM_FLOAT, True); + if (!float_type) + fprintf(stderr, "Float properties not available.\n"); + + printf("Parameter settings:\n"); + for (j = 0; params[j].name; j++) { + struct Parameter *par = ¶ms[j]; + + a = XInternAtom(dpy, par->prop_name, True); + if (!a) + continue; + + len = + 1 + + ((par->prop_offset * (par->prop_format ? par->prop_format : 32) / + 8)) / 4; + + XGetDeviceProperty(dpy, dev, a, 0, len, False, + AnyPropertyType, &type, &format, + &nitems, &bytes_after, &data); + if (type == None) + continue; + + switch (par->prop_format) { + case 8: + if (format != par->prop_format || type != XA_INTEGER) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + + b = (char *) data; + printf(" %-23s = %d\n", par->name, b[par->prop_offset]); + break; + case 32: + if (format != par->prop_format || + (type != XA_INTEGER && type != XA_CARDINAL)) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + + i = (long *) data; + printf(" %-23s = %ld\n", par->name, i[par->prop_offset]); + break; + case 0: /* Float */ + if (!float_type) + continue; + if (format != 32 || type != float_type) { + fprintf(stderr, " %-23s = format mismatch (%d)\n", + par->name, format); + break; + } + + f = (union flong *) data; + printf(" %-23s = %g\n", par->name, f[par->prop_offset].f); + break; + } + + XFree(data); + } +} + +static void +usage(void) +{ + fprintf(stderr, "Usage: synclient [-h] [-l] [-V] [-?] [var1=value1 [var2=value2] ...]\n"); + fprintf(stderr, " -l List current user settings\n"); + fprintf(stderr, " -V Print synclient version string and exit\n"); + fprintf(stderr, " -? Show this help message\n"); + fprintf(stderr, " var=value Set user parameter 'var' to 'value'.\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int c; + int dump_settings = 0; + int first_cmd; + + Display *dpy; + XDevice *dev; + + if (argc == 1) + dump_settings = 1; + + /* Parse command line parameters */ + while ((c = getopt(argc, argv, "lV?")) != -1) { + switch (c) { + case 'l': + dump_settings = 1; + break; + case 'V': + printf("%s\n", VERSION); + exit(0); + case '?': + default: + usage(); + } + } + + first_cmd = optind; + if (!dump_settings && first_cmd == argc) + usage(); + + dpy = dp_init(); + if (!dpy || !(dev = dp_get_device(dpy))) + return 1; + + dp_set_variables(dpy, dev, argc, argv, first_cmd); + if (dump_settings) + dp_show_settings(dpy, dev); + + XCloseDevice(dpy, dev); + XCloseDisplay(dpy); + + return 0; +} --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/118_quell_error_msg.patch/tools/syndaemon.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/118_quell_error_msg.patch/tools/syndaemon.c @@ -0,0 +1,676 @@ +/* + * Copyright © 2003-2004 Peter Osterlund + * + * 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: + * Peter Osterlund (petero2@telia.com) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#ifdef HAVE_X11_EXTENSIONS_RECORD_H +#include +#include +#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "synaptics-properties.h" + +enum KeyboardActivity { + ActivityNew, + ActivityNone, + ActivityReset +}; + +enum TouchpadState { + TouchpadOn = 0, + TouchpadOff = 1, + TappingOff = 2 +}; + +static Bool pad_disabled + /* internal flag, this does not correspond to device state */ ; +static int ignore_modifier_combos; +static int ignore_modifier_keys; +static int background; +static const char *pid_file; +static Display *display; +static XDevice *dev; +static Atom touchpad_off_prop; +static enum TouchpadState previous_state; +static enum TouchpadState disable_state = TouchpadOff; +static int verbose; + +#define KEYMAP_SIZE 32 +static unsigned char keyboard_mask[KEYMAP_SIZE]; + +static void +usage(void) +{ + fprintf(stderr, + "Usage: syndaemon [-i idle-time] [-m poll-delay] [-d] [-t] [-k]\n"); + fprintf(stderr, + " -i How many seconds to wait after the last key press before\n"); + fprintf(stderr, " enabling the touchpad. (default is 2.0s)\n"); + fprintf(stderr, " -m How many milli-seconds to wait until next poll.\n"); + fprintf(stderr, " (default is 200ms)\n"); + fprintf(stderr, " -d Start as a daemon, i.e. in the background.\n"); + fprintf(stderr, " -p Create a pid file with the specified name.\n"); + fprintf(stderr, + " -t Only disable tapping and scrolling, not mouse movements.\n"); + fprintf(stderr, + " -k Ignore modifier keys when monitoring keyboard activity.\n"); + fprintf(stderr, " -K Like -k but also ignore Modifier+Key combos.\n"); + fprintf(stderr, " -R Use the XRecord extension.\n"); + fprintf(stderr, " -v Print diagnostic messages.\n"); + fprintf(stderr, " -? Show this help message.\n"); + exit(1); +} + +static void +store_current_touchpad_state(void) +{ + Atom real_type; + int real_format; + unsigned long nitems, bytes_after; + unsigned char *data; + + if ((XGetDeviceProperty(display, dev, touchpad_off_prop, 0, 1, False, + XA_INTEGER, &real_type, &real_format, &nitems, + &bytes_after, &data) == Success) && + (real_type != None)) { + previous_state = data[0]; + } +} + +/** + * Toggle touchpad enabled/disabled state, decided by value. + */ +static void +toggle_touchpad(Bool enable) +{ + unsigned char data; + + if (pad_disabled && enable) { + data = previous_state; + pad_disabled = False; + if (verbose) + printf("Enable\n"); + } + else if (!pad_disabled && !enable && + previous_state != disable_state && previous_state != TouchpadOff) { + store_current_touchpad_state(); + pad_disabled = True; + data = disable_state; + if (verbose) + printf("Disable\n"); + } + else + return; + + /* This potentially overwrites a different client's setting, but ... */ + XChangeDeviceProperty(display, dev, touchpad_off_prop, XA_INTEGER, 8, + PropModeReplace, &data, 1); + XFlush(display); +} + +static void +signal_handler(int signum) +{ + toggle_touchpad(True); + + if (pid_file) + unlink(pid_file); + kill(getpid(), signum); +} + +static void +install_signal_handler(void) +{ + static int signals[] = { + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, + SIGBUS, SIGFPE, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, + SIGALRM, SIGTERM, +#ifdef SIGPWR + SIGPWR +#endif + }; + int i; + struct sigaction act; + sigset_t set; + + sigemptyset(&set); + act.sa_handler = signal_handler; + act.sa_mask = set; +#ifdef SA_RESETHAND + act.sa_flags = SA_RESETHAND; +#else + act.sa_flags = 0; +#endif + + for (i = 0; i < sizeof(signals) / sizeof(int); i++) { + if (sigaction(signals[i], &act, NULL) == -1) { + perror("sigaction"); + exit(2); + } + } +} + +static enum KeyboardActivity +keyboard_activity(Display * display) +{ + static unsigned char old_key_state[KEYMAP_SIZE]; + unsigned char key_state[KEYMAP_SIZE]; + int i; + int ret = ActivityNone; + + XQueryKeymap(display, (char *) key_state); + + for (i = 0; i < KEYMAP_SIZE; i++) { + if ((key_state[i] & ~old_key_state[i]) & keyboard_mask[i]) { + ret = ActivityNew; + break; + } + } + if (ignore_modifier_combos) { + for (i = 0; i < KEYMAP_SIZE; i++) { + if (key_state[i] & ~keyboard_mask[i]) { + if (old_key_state[i] & ~keyboard_mask[i]) + ret = ActivityNone; + else + ret = ActivityReset; + break; + } + } + } + for (i = 0; i < KEYMAP_SIZE; i++) + old_key_state[i] = key_state[i]; + return ret; +} + +static double +get_time(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +static void +main_loop(Display * display, double idle_time, int poll_delay) +{ + double last_activity = 0.0; + double current_time; + + keyboard_activity(display); + + for (;;) { + current_time = get_time(); + switch (keyboard_activity(display)) { + case ActivityNew: + last_activity = current_time; + break; + case ActivityNone: + /* NOP */; + break; + case ActivityReset: + last_activity = 0.0; + break; + } + + /* If system times goes backwards, touchpad can get locked. Make + * sure our last activity wasn't in the future and reset if it was. */ + if (last_activity > current_time) + last_activity = current_time - idle_time - 1; + + if (current_time > last_activity + idle_time) { /* Enable touchpad */ + toggle_touchpad(True); + } + else { /* Disable touchpad */ + toggle_touchpad(False); + } + + usleep(poll_delay); + } +} + +static void +clear_bit(unsigned char *ptr, int bit) +{ + int byte_num = bit / 8; + int bit_num = bit % 8; + + ptr[byte_num] &= ~(1 << bit_num); +} + +static void +setup_keyboard_mask(Display * display, int ignore_modifier_keys) +{ + XModifierKeymap *modifiers; + int i; + + for (i = 0; i < KEYMAP_SIZE; i++) + keyboard_mask[i] = 0xff; + + if (ignore_modifier_keys) { + modifiers = XGetModifierMapping(display); + for (i = 0; i < 8 * modifiers->max_keypermod; i++) { + KeyCode kc = modifiers->modifiermap[i]; + + if (kc != 0) + clear_bit(keyboard_mask, kc); + } + XFreeModifiermap(modifiers); + } +} + +/* ---- the following code is for using the xrecord extension ----- */ +#ifdef HAVE_X11_EXTENSIONS_RECORD_H + +#define MAX_MODIFIERS 16 + +/* used for exchanging information with the callback function */ +struct xrecord_callback_results { + XModifierKeymap *modifiers; + Bool key_event; + Bool non_modifier_event; + KeyCode pressed_modifiers[MAX_MODIFIERS]; +}; + +/* test if the xrecord extension is found */ +Bool +check_xrecord(Display * display) +{ + + Bool found; + Status status; + int major_opcode, minor_opcode, first_error; + int version[2]; + + found = XQueryExtension(display, + "RECORD", + &major_opcode, &minor_opcode, &first_error); + + status = XRecordQueryVersion(display, version, version + 1); + if (verbose && status) { + printf("X RECORD extension version %d.%d\n", version[0], version[1]); + } + return found; +} + +/* called by XRecordProcessReplies() */ +void +xrecord_callback(XPointer closure, XRecordInterceptData * recorded_data) +{ + + struct xrecord_callback_results *cbres; + xEvent *xev; + int nxev; + + cbres = (struct xrecord_callback_results *) closure; + + if (recorded_data->category != XRecordFromServer) { + XRecordFreeData(recorded_data); + return; + } + + nxev = recorded_data->data_len / 8; + xev = (xEvent *) recorded_data->data; + while (nxev--) { + + if ((xev->u.u.type == KeyPress) || (xev->u.u.type == KeyRelease)) { + int i; + int is_modifier = 0; + + cbres->key_event = 1; /* remember, a key was pressed or released. */ + + /* test if it was a modifier */ + for (i = 0; i < 8 * cbres->modifiers->max_keypermod; i++) { + KeyCode kc = cbres->modifiers->modifiermap[i]; + + if (kc == xev->u.u.detail) { + is_modifier = 1; /* yes, it is a modifier. */ + break; + } + } + + if (is_modifier) { + if (xev->u.u.type == KeyPress) { + for (i = 0; i < MAX_MODIFIERS; ++i) + if (!cbres->pressed_modifiers[i]) { + cbres->pressed_modifiers[i] = xev->u.u.detail; + break; + } + } + else { /* KeyRelease */ + for (i = 0; i < MAX_MODIFIERS; ++i) + if (cbres->pressed_modifiers[i] == xev->u.u.detail) + cbres->pressed_modifiers[i] = 0; + } + + } + else { + /* remember, a non-modifier was pressed. */ + cbres->non_modifier_event = 1; + } + } + + xev++; + } + + XRecordFreeData(recorded_data); /* cleanup */ +} + +static int +is_modifier_pressed(const struct xrecord_callback_results *cbres) +{ + int i; + + for (i = 0; i < MAX_MODIFIERS; ++i) + if (cbres->pressed_modifiers[i]) + return 1; + + return 0; +} + +void +record_main_loop(Display * display, double idle_time) +{ + + struct xrecord_callback_results cbres; + XRecordContext context; + XRecordClientSpec cspec = XRecordAllClients; + Display *dpy_data; + XRecordRange *range; + int i; + + dpy_data = XOpenDisplay(NULL); /* we need an additional data connection. */ + range = XRecordAllocRange(); + + range->device_events.first = KeyPress; + range->device_events.last = KeyRelease; + + context = XRecordCreateContext(dpy_data, 0, &cspec, 1, &range, 1); + + XRecordEnableContextAsync(dpy_data, context, xrecord_callback, + (XPointer) & cbres); + + cbres.modifiers = XGetModifierMapping(display); + /* clear list of modifiers */ + for (i = 0; i < MAX_MODIFIERS; ++i) + cbres.pressed_modifiers[i] = 0; + + while (1) { + + int fd = ConnectionNumber(dpy_data); + fd_set read_fds; + int ret; + int disable_event = 0; + int modifier_event = 0; + struct timeval timeout; + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + ret = select(fd + 1 /* =(max descriptor in read_fds) + 1 */ , + &read_fds, NULL, NULL, + pad_disabled ? &timeout : NULL + /* timeout only required for enabling */ ); + + if (FD_ISSET(fd, &read_fds)) { + + cbres.key_event = 0; + cbres.non_modifier_event = 0; + + XRecordProcessReplies(dpy_data); + + /* If there are any events left over, they are in error. Drain them + * from the connection queue so we don't get stuck. */ + while (XEventsQueued(dpy_data, QueuedAlready) > 0) { + XEvent event; + + XNextEvent(dpy_data, &event); + fprintf(stderr, "bad event received, major opcode %d\n", + event.type); + } + + if (!ignore_modifier_keys && cbres.key_event) { + disable_event = 1; + } + + if (cbres.non_modifier_event) { + if (ignore_modifier_combos && is_modifier_pressed(&cbres)) { + modifier_event = 1; + } else { + disable_event = 1; + } + } else if (ignore_modifier_keys) { + modifier_event = 1; + } + } + + if (disable_event) { + /* adjust the enable_time */ + timeout.tv_sec = (int) idle_time; + timeout.tv_usec = (idle_time - (double) timeout.tv_sec) * 1.e6; + + toggle_touchpad(False); + } + + if (modifier_event && pad_disabled) { + toggle_touchpad(True); + } + + if (ret == 0 && pad_disabled) { /* timeout => enable event */ + toggle_touchpad(True); + } + } /* end while(1) */ + + XFreeModifiermap(cbres.modifiers); +} +#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ + +static XDevice * +dp_get_device(Display * dpy) +{ + XDevice *dev = NULL; + XDeviceInfo *info = NULL; + int ndevices = 0; + Atom touchpad_type = 0; + Atom *properties = NULL; + int nprops = 0; + int error = 0; + + touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); + touchpad_off_prop = XInternAtom(dpy, SYNAPTICS_PROP_OFF, True); + info = XListInputDevices(dpy, &ndevices); + + while (ndevices--) { + if (info[ndevices].type == touchpad_type) { + dev = XOpenDevice(dpy, info[ndevices].id); + if (!dev) { + fprintf(stderr, "Failed to open device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + properties = XListDeviceProperties(dpy, dev, &nprops); + if (!properties || !nprops) { + fprintf(stderr, "No properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + while (nprops--) { + if (properties[nprops] == touchpad_off_prop) + break; + } + if (nprops < 0) { + fprintf(stderr, "No synaptics properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + break; /* Yay, device is suitable */ + } + } + + unwind: + XFree(properties); + XFreeDeviceList(info); + if (!dev) + fprintf(stderr, "Unable to find a synaptics device.\n"); + else if (error && dev) { + XCloseDevice(dpy, dev); + dev = NULL; + } + return dev; +} + +int +main(int argc, char *argv[]) +{ + double idle_time = 2.0; + int poll_delay = 200000; /* 200 ms */ + int c; + int use_xrecord = 0; + + /* Parse command line parameters */ + while ((c = getopt(argc, argv, "i:m:dtp:kKR?v")) != EOF) { + switch (c) { + case 'i': + idle_time = atof(optarg); + break; + case 'm': + poll_delay = atoi(optarg) * 1000; + break; + case 'd': + background = 1; + break; + case 't': + disable_state = TappingOff; + break; + case 'p': + pid_file = optarg; + break; + case 'k': + ignore_modifier_keys = 1; + break; + case 'K': + ignore_modifier_combos = 1; + ignore_modifier_keys = 1; + break; + case 'R': + use_xrecord = 1; + break; + case 'v': + verbose = 1; + break; + case '?': + default: + usage(); + break; + } + } + if (idle_time <= 0.0) + usage(); + + /* Open a connection to the X server */ + display = XOpenDisplay(NULL); + if (!display) { + fprintf(stderr, "Can't open display.\n"); + exit(2); + } + + if (!(dev = dp_get_device(display))) + exit(2); + + /* Install a signal handler to restore synaptics parameters on exit */ + install_signal_handler(); + + if (background) { + pid_t pid; + + if ((pid = fork()) < 0) { + perror("fork"); + exit(3); + } + else if (pid != 0) + exit(0); + + /* Child (daemon) is running here */ + setsid(); /* Become session leader */ + chdir("/"); /* In case the file system gets unmounted */ + umask(0); /* We don't want any surprises */ + if (pid_file) { + FILE *fd = fopen(pid_file, "w"); + + if (!fd) { + perror("Can't create pid file"); + exit(3); + } + fprintf(fd, "%d\n", getpid()); + fclose(fd); + } + } + + pad_disabled = False; + store_current_touchpad_state(); + +#ifdef HAVE_X11_EXTENSIONS_RECORD_H + if (use_xrecord) { + if (check_xrecord(display)) + record_main_loop(display, idle_time); + else { + fprintf(stderr, "Use of XRecord requested, but failed to " + " initialize.\n"); + exit(4); + } + } + else +#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ + { + setup_keyboard_mask(display, ignore_modifier_keys); + + /* Run the main loop */ + main_loop(display, idle_time, poll_delay); + } + return 0; +} + +/* vim: set noexpandtab tabstop=8 shiftwidth=4: */ --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/124_syndaemon_events.patch/tools/syndaemon.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/124_syndaemon_events.patch/tools/syndaemon.c @@ -0,0 +1,674 @@ +/* + * Copyright © 2003-2004 Peter Osterlund + * + * 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: + * Peter Osterlund (petero2@telia.com) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#ifdef HAVE_X11_EXTENSIONS_RECORD_H +#include +#include +#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "synaptics-properties.h" + +enum KeyboardActivity { + ActivityNew, + ActivityNone, + ActivityReset +}; + +enum TouchpadState { + TouchpadOn = 0, + TouchpadOff = 1, + TappingOff = 2 +}; + +static Bool pad_disabled + /* internal flag, this does not correspond to device state */ ; +static int ignore_modifier_combos; +static int ignore_modifier_keys; +static int background; +static const char *pid_file; +static Display *display; +static XDevice *dev; +static Atom touchpad_off_prop; +static enum TouchpadState previous_state; +static enum TouchpadState disable_state = TouchpadOff; +static int verbose; + +#define KEYMAP_SIZE 32 +static unsigned char keyboard_mask[KEYMAP_SIZE]; + +static void +usage(void) +{ + fprintf(stderr, + "Usage: syndaemon [-i idle-time] [-m poll-delay] [-d] [-t] [-k]\n"); + fprintf(stderr, + " -i How many seconds to wait after the last key press before\n"); + fprintf(stderr, " enabling the touchpad. (default is 2.0s)\n"); + fprintf(stderr, " -m How many milli-seconds to wait until next poll.\n"); + fprintf(stderr, " (default is 200ms)\n"); + fprintf(stderr, " -d Start as a daemon, i.e. in the background.\n"); + fprintf(stderr, " -p Create a pid file with the specified name.\n"); + fprintf(stderr, + " -t Only disable tapping and scrolling, not mouse movements.\n"); + fprintf(stderr, + " -k Ignore modifier keys when monitoring keyboard activity.\n"); + fprintf(stderr, " -K Like -k but also ignore Modifier+Key combos.\n"); + fprintf(stderr, " -R Use the XRecord extension.\n"); + fprintf(stderr, " -v Print diagnostic messages.\n"); + fprintf(stderr, " -? Show this help message.\n"); + exit(1); +} + +static void +store_current_touchpad_state(void) +{ + Atom real_type; + int real_format; + unsigned long nitems, bytes_after; + unsigned char *data; + + if ((XGetDeviceProperty(display, dev, touchpad_off_prop, 0, 1, False, + XA_INTEGER, &real_type, &real_format, &nitems, + &bytes_after, &data) == Success) && + (real_type != None)) { + previous_state = data[0]; + } +} + +/** + * Toggle touchpad enabled/disabled state, decided by value. + */ +static void +toggle_touchpad(Bool enable) +{ + unsigned char data; + + if (pad_disabled && enable) { + data = previous_state; + pad_disabled = False; + if (verbose) + printf("Enable\n"); + } + else if (!pad_disabled && !enable && + previous_state != disable_state && previous_state != TouchpadOff) { + store_current_touchpad_state(); + pad_disabled = True; + data = disable_state; + if (verbose) + printf("Disable\n"); + } + else + return; + + /* This potentially overwrites a different client's setting, but ... */ + XChangeDeviceProperty(display, dev, touchpad_off_prop, XA_INTEGER, 8, + PropModeReplace, &data, 1); + XFlush(display); +} + +static void +signal_handler(int signum) +{ + toggle_touchpad(True); + + if (pid_file) + unlink(pid_file); + kill(getpid(), signum); +} + +static void +install_signal_handler(void) +{ + static int signals[] = { + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, + SIGBUS, SIGFPE, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, + SIGALRM, SIGTERM, +#ifdef SIGPWR + SIGPWR +#endif + }; + int i; + struct sigaction act; + sigset_t set; + + sigemptyset(&set); + act.sa_handler = signal_handler; + act.sa_mask = set; +#ifdef SA_RESETHAND + act.sa_flags = SA_RESETHAND; +#else + act.sa_flags = 0; +#endif + + for (i = 0; i < sizeof(signals) / sizeof(int); i++) { + if (sigaction(signals[i], &act, NULL) == -1) { + perror("sigaction"); + exit(2); + } + } +} + +static enum KeyboardActivity +keyboard_activity(Display * display) +{ + static unsigned char old_key_state[KEYMAP_SIZE]; + unsigned char key_state[KEYMAP_SIZE]; + int i; + int ret = ActivityNone; + + XQueryKeymap(display, (char *) key_state); + + for (i = 0; i < KEYMAP_SIZE; i++) { + if ((key_state[i] & ~old_key_state[i]) & keyboard_mask[i]) { + ret = ActivityNew; + break; + } + } + if (ignore_modifier_combos) { + for (i = 0; i < KEYMAP_SIZE; i++) { + if (key_state[i] & ~keyboard_mask[i]) { + if (old_key_state[i] & ~keyboard_mask[i]) + ret = ActivityNone; + else + ret = ActivityReset; + break; + } + } + } + for (i = 0; i < KEYMAP_SIZE; i++) + old_key_state[i] = key_state[i]; + return ret; +} + +static double +get_time(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +static void +main_loop(Display * display, double idle_time, int poll_delay) +{ + double last_activity = 0.0; + double current_time; + + keyboard_activity(display); + + for (;;) { + current_time = get_time(); + switch (keyboard_activity(display)) { + case ActivityNew: + last_activity = current_time; + break; + case ActivityNone: + /* NOP */; + break; + case ActivityReset: + last_activity = 0.0; + break; + } + + /* If system times goes backwards, touchpad can get locked. Make + * sure our last activity wasn't in the future and reset if it was. */ + if (last_activity > current_time) + last_activity = current_time - idle_time - 1; + + if (current_time > last_activity + idle_time) { /* Enable touchpad */ + toggle_touchpad(True); + } + else { /* Disable touchpad */ + toggle_touchpad(False); + } + + usleep(poll_delay); + } +} + +static void +clear_bit(unsigned char *ptr, int bit) +{ + int byte_num = bit / 8; + int bit_num = bit % 8; + + ptr[byte_num] &= ~(1 << bit_num); +} + +static void +setup_keyboard_mask(Display * display, int ignore_modifier_keys) +{ + XModifierKeymap *modifiers; + int i; + + for (i = 0; i < KEYMAP_SIZE; i++) + keyboard_mask[i] = 0xff; + + if (ignore_modifier_keys) { + modifiers = XGetModifierMapping(display); + for (i = 0; i < 8 * modifiers->max_keypermod; i++) { + KeyCode kc = modifiers->modifiermap[i]; + + if (kc != 0) + clear_bit(keyboard_mask, kc); + } + XFreeModifiermap(modifiers); + } +} + +/* ---- the following code is for using the xrecord extension ----- */ +#ifdef HAVE_X11_EXTENSIONS_RECORD_H + +#define MAX_MODIFIERS 16 + +/* used for exchanging information with the callback function */ +struct xrecord_callback_results { + XModifierKeymap *modifiers; + Bool key_event; + Bool non_modifier_event; + KeyCode pressed_modifiers[MAX_MODIFIERS]; +}; + +/* test if the xrecord extension is found */ +Bool +check_xrecord(Display * display) +{ + + Bool found; + Status status; + int major_opcode, minor_opcode, first_error; + int version[2]; + + found = XQueryExtension(display, + "RECORD", + &major_opcode, &minor_opcode, &first_error); + + status = XRecordQueryVersion(display, version, version + 1); + if (verbose && status) { + printf("X RECORD extension version %d.%d\n", version[0], version[1]); + } + return found; +} + +/* called by XRecordProcessReplies() */ +void +xrecord_callback(XPointer closure, XRecordInterceptData * recorded_data) +{ + + struct xrecord_callback_results *cbres; + xEvent *xev; + int nxev; + + cbres = (struct xrecord_callback_results *) closure; + + if (recorded_data->category != XRecordFromServer) { + XRecordFreeData(recorded_data); + return; + } + + nxev = recorded_data->data_len / 8; + xev = (xEvent *) recorded_data->data; + while (nxev--) { + + if ((xev->u.u.type == KeyPress) || (xev->u.u.type == KeyRelease)) { + int i; + int is_modifier = 0; + + cbres->key_event = 1; /* remember, a key was pressed or released. */ + + /* test if it was a modifier */ + for (i = 0; i < 8 * cbres->modifiers->max_keypermod; i++) { + KeyCode kc = cbres->modifiers->modifiermap[i]; + + if (kc == xev->u.u.detail) { + is_modifier = 1; /* yes, it is a modifier. */ + break; + } + } + + if (is_modifier) { + if (xev->u.u.type == KeyPress) { + for (i = 0; i < MAX_MODIFIERS; ++i) + if (!cbres->pressed_modifiers[i]) { + cbres->pressed_modifiers[i] = xev->u.u.detail; + break; + } + } + else { /* KeyRelease */ + for (i = 0; i < MAX_MODIFIERS; ++i) + if (cbres->pressed_modifiers[i] == xev->u.u.detail) + cbres->pressed_modifiers[i] = 0; + } + + } + else { + /* remember, a non-modifier was pressed. */ + cbres->non_modifier_event = 1; + } + } + + xev++; + } + + XRecordFreeData(recorded_data); /* cleanup */ +} + +static int +is_modifier_pressed(const struct xrecord_callback_results *cbres) +{ + int i; + + for (i = 0; i < MAX_MODIFIERS; ++i) + if (cbres->pressed_modifiers[i]) + return 1; + + return 0; +} + +void +record_main_loop(Display * display, double idle_time) +{ + + struct xrecord_callback_results cbres; + XRecordContext context; + XRecordClientSpec cspec = XRecordAllClients; + Display *dpy_data; + XRecordRange *range; + int i; + + dpy_data = XOpenDisplay(NULL); /* we need an additional data connection. */ + range = XRecordAllocRange(); + + range->device_events.first = KeyPress; + range->device_events.last = KeyRelease; + + context = XRecordCreateContext(dpy_data, 0, &cspec, 1, &range, 1); + + XRecordEnableContextAsync(dpy_data, context, xrecord_callback, + (XPointer) & cbres); + + cbres.modifiers = XGetModifierMapping(display); + /* clear list of modifiers */ + for (i = 0; i < MAX_MODIFIERS; ++i) + cbres.pressed_modifiers[i] = 0; + + while (1) { + + int fd = ConnectionNumber(dpy_data); + fd_set read_fds; + int ret; + int disable_event = 0; + int modifier_event = 0; + struct timeval timeout; + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + ret = select(fd + 1 /* =(max descriptor in read_fds) + 1 */ , + &read_fds, NULL, NULL, + pad_disabled ? &timeout : NULL + /* timeout only required for enabling */ ); + + if (FD_ISSET(fd, &read_fds)) { + + cbres.key_event = 0; + cbres.non_modifier_event = 0; + + XRecordProcessReplies(dpy_data); + + /* If there are any events left over, they are in error. Drain them + * from the connection queue so we don't get stuck. */ + while (XEventsQueued(dpy_data, QueuedAlready) > 0) { + XEvent event; + + XNextEvent(dpy_data, &event); + fprintf(stderr, "bad event received, major opcode %d\n", + event.type); + } + + if (!ignore_modifier_keys && cbres.key_event) { + disable_event = 1; + } + + if (cbres.non_modifier_event) { + if (ignore_modifier_combos && is_modifier_pressed(&cbres)) { + modifier_event = 1; + } else { + disable_event = 1; + } + } else if (ignore_modifier_keys) { + modifier_event = 1; + } + } + + if (disable_event) { + /* adjust the enable_time */ + timeout.tv_sec = (int) idle_time; + timeout.tv_usec = (idle_time - (double) timeout.tv_sec) * 1.e6; + + toggle_touchpad(False); + } + + if (modifier_event && pad_disabled) { + toggle_touchpad(True); + } + + if (ret == 0 && pad_disabled) { /* timeout => enable event */ + toggle_touchpad(True); + } + } /* end while(1) */ + + XFreeModifiermap(cbres.modifiers); +} +#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ + +static XDevice * +dp_get_device(Display * dpy) +{ + XDevice *dev = NULL; + XDeviceInfo *info = NULL; + int ndevices = 0; + Atom touchpad_type = 0; + Atom *properties = NULL; + int nprops = 0; + int error = 0; + + touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); + touchpad_off_prop = XInternAtom(dpy, SYNAPTICS_PROP_OFF, True); + info = XListInputDevices(dpy, &ndevices); + + while (ndevices--) { + if (info[ndevices].type == touchpad_type) { + dev = XOpenDevice(dpy, info[ndevices].id); + if (!dev) { + fprintf(stderr, "Failed to open device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + properties = XListDeviceProperties(dpy, dev, &nprops); + if (!properties || !nprops) { + fprintf(stderr, "No properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + while (nprops--) { + if (properties[nprops] == touchpad_off_prop) + break; + } + if (nprops < 0) { + fprintf(stderr, "No synaptics properties on device '%s'.\n", + info[ndevices].name); + error = 1; + goto unwind; + } + + break; /* Yay, device is suitable */ + } + } + + unwind: + XFree(properties); + XFreeDeviceList(info); + if (error && dev) { + XCloseDevice(dpy, dev); + dev = NULL; + } + return dev; +} + +int +main(int argc, char *argv[]) +{ + double idle_time = 2.0; + int poll_delay = 200000; /* 200 ms */ + int c; + int use_xrecord = 0; + + /* Parse command line parameters */ + while ((c = getopt(argc, argv, "i:m:dtp:kKR?v")) != EOF) { + switch (c) { + case 'i': + idle_time = atof(optarg); + break; + case 'm': + poll_delay = atoi(optarg) * 1000; + break; + case 'd': + background = 1; + break; + case 't': + disable_state = TappingOff; + break; + case 'p': + pid_file = optarg; + break; + case 'k': + ignore_modifier_keys = 1; + break; + case 'K': + ignore_modifier_combos = 1; + ignore_modifier_keys = 1; + break; + case 'R': + use_xrecord = 1; + break; + case 'v': + verbose = 1; + break; + case '?': + default: + usage(); + break; + } + } + if (idle_time <= 0.0) + usage(); + + /* Open a connection to the X server */ + display = XOpenDisplay(NULL); + if (!display) { + fprintf(stderr, "Can't open display.\n"); + exit(2); + } + + if (!(dev = dp_get_device(display))) + exit(2); + + /* Install a signal handler to restore synaptics parameters on exit */ + install_signal_handler(); + + if (background) { + pid_t pid; + + if ((pid = fork()) < 0) { + perror("fork"); + exit(3); + } + else if (pid != 0) + exit(0); + + /* Child (daemon) is running here */ + setsid(); /* Become session leader */ + chdir("/"); /* In case the file system gets unmounted */ + umask(0); /* We don't want any surprises */ + if (pid_file) { + FILE *fd = fopen(pid_file, "w"); + + if (!fd) { + perror("Can't create pid file"); + exit(3); + } + fprintf(fd, "%d\n", getpid()); + fclose(fd); + } + } + + pad_disabled = False; + store_current_touchpad_state(); + +#ifdef HAVE_X11_EXTENSIONS_RECORD_H + if (use_xrecord) { + if (check_xrecord(display)) + record_main_loop(display, idle_time); + else { + fprintf(stderr, "Use of XRecord requested, but failed to " + " initialize.\n"); + exit(4); + } + } + else +#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ + { + setup_keyboard_mask(display, ignore_modifier_keys); + + /* Run the main loop */ + main_loop(display, idle_time, poll_delay); + } + return 0; +} + +/* vim: set noexpandtab tabstop=8 shiftwidth=4: */ --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/128_disable_three_click_action.patch/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/128_disable_three_click_action.patch/src/synaptics.c @@ -0,0 +1,3249 @@ +/* + * 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-2012 Red Hat, Inc. + * Copyright © 2011 The Chromium OS Authors + * + * 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 + * Christian Thaeter + * Stefan Bethge + * Matthias Ihmig + * Alexei Gilchrist + * Jörg Bösner + * Hartwig Felger + * Peter Osterlund + * S. Lehner + * Stefan Gmeiner + * Henry Davies for the + * Linuxcare Inc. David Kennedy + * Fred Hucht + * Fedor P. Goncharov + * Simon Thum + * + * Trademarks are the property of their respective owners. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +enum EdgeType { + 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 +}; + +/* + * We expect to be receiving a steady 80 packets/sec (which gives 40 + * reports/sec with more than one finger on the pad, as Advanced Gesture Mode + * requires two PS/2 packets per report). Instead of a random scattering of + * magic 13 and 20ms numbers scattered throughout the driver, introduce + * POLL_MS as 14ms, which is slightly less than 80Hz. 13ms is closer to + * 80Hz, but if the kernel event reporting was even slightly delayed, + * we would produce synthetic motion followed immediately by genuine + * motion, so use 14. + * + * We use this to call back at a constant rate to at least produce the + * illusion of smooth motion. It works a lot better than you'd expect. +*/ +#define POLL_MS 14 + +#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 + +#define INPUT_BUFFER_SIZE 200 + +/***************************************************************************** + * Forward declaration + ****************************************************************************/ +static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static Bool DeviceControl(DeviceIntPtr, int); +static void ReadInput(InputInfoPtr); +static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now, + Bool from_timer); +static int ControlProc(InputInfoPtr, xDeviceCtl *); +static int SwitchMode(ClientPtr, DeviceIntPtr, int); +static int DeviceInit(DeviceIntPtr); +static int DeviceOn(DeviceIntPtr); +static int DeviceOff(DeviceIntPtr); +static int DeviceClose(DeviceIntPtr); +static Bool QueryHardware(InputInfoPtr); +static void ReadDevDimensions(InputInfoPtr); +#ifndef NO_DRIVER_SCALING +static void ScaleCoordinates(SynapticsPrivate * priv, + struct SynapticsHwState *hw); +static void CalculateScalingCoeffs(SynapticsPrivate * priv); +#endif +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, + NULL, +#ifdef XI86_DRV_CAP_SERVER_FD + XI86_DRV_CAP_SERVER_FD +#endif +}; + +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 + ****************************************************************************/ +static inline void +SynapticsCloseFd(InputInfoPtr pInfo) +{ + if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) { + xf86CloseSerial(pInfo->fd); + pInfo->fd = -1; + } +} + +/** + * 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 = 255; + + 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 = 15; + + xf86IDrvMsg(pInfo, X_PROBED, + "invalid finger width range. defaulting to %d - %d\n", + priv->minw, priv->maxw); + } +} + +static Bool +SetDeviceAndProtocol(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = pInfo->private; + char *proto, *device; + int i; + + proto = xf86SetStrOption(pInfo->options, "Protocol", NULL); + device = xf86SetStrOption(pInfo->options, "Device", NULL); + + /* If proto is auto-dev, unset and let the code do the rest */ + if (proto && !strcmp(proto, "auto-dev")) { + free(proto); + proto = 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; + + return (priv->proto_ops != 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 || + priv->model == MODEL_UNIBODY_MACBOOK) { + 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; +} + +static void +calculate_tap_hysteresis(SynapticsPrivate * priv, int range, + int *fingerLow, int *fingerHigh) +{ + switch (priv->model) { + case MODEL_ELANTECH: + /* All Elantech touchpads don't need the Z filtering to get the + * number of fingers correctly. See Documentation/elantech.txt + * in the kernel. + */ + *fingerLow = priv->minp + 1; + *fingerHigh = priv->minp + 1; + break; + case MODEL_UNIBODY_MACBOOK: + *fingerLow = 70; + *fingerHigh = 75; + break; + default: + *fingerLow = priv->minp + range * (25.0 / 256); + *fingerHigh = priv->minp + range * (30.0 / 256); + break; + } +} + +/* 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; + double percent = xf86CheckPercentOption(options, optname, -1); + + if (percent >= 0.0) { + percent = xf86SetPercentOption(options, optname, -1); + result = percent / 100.0 * range + offset; + } else + result = xf86SetIntOption(options, optname, default_value); + + return result; +} + +Bool +SynapticsIsSoftButtonAreasValid(int *values) +{ + Bool right_disabled = FALSE; + Bool middle_disabled = FALSE; + + enum { + /* right button left, right, top, bottom */ + RBL = 0, + RBR = 1, + RBT = 2, + RBB = 3, + /* middle button left, right, top, bottom */ + MBL = 4, + MBR = 5, + MBT = 6, + MBB = 7, + }; + + /* Check right button area */ + if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) || + (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB]))) + return FALSE; + + /* Check middle button area */ + if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) || + (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB]))) + return FALSE; + + if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0) + right_disabled = TRUE; + + if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0) + middle_disabled = TRUE; + + if (!right_disabled && + ((values[RBL] && values[RBL] == values[RBR]) || + (values[RBT] && values[RBT] == values[RBB]))) + return FALSE; + + if (!middle_disabled && + ((values[MBL] && values[MBL] == values[MBR]) || + (values[MBT] && values[MBT] == values[MBB]))) + return FALSE; + + /* Check for overlapping button areas */ + if (!right_disabled && !middle_disabled) { + int right_left = values[RBL] ? values[RBL] : INT_MIN; + int right_right = values[RBR] ? values[RBR] : INT_MAX; + int right_top = values[RBT] ? values[RBT] : INT_MIN; + int right_bottom = values[RBB] ? values[RBB] : INT_MAX; + int middle_left = values[MBL] ? values[MBL] : INT_MIN; + int middle_right = values[MBR] ? values[MBR] : INT_MAX; + int middle_top = values[MBT] ? values[MBT] : INT_MIN; + int middle_bottom = values[MBB] ? values[MBB] : INT_MAX; + + /* If areas overlap in the Y axis */ + if ((right_bottom <= middle_bottom && right_bottom >= middle_top) || + (right_top <= middle_bottom && right_top >= middle_top)) { + /* Check for overlapping left edges */ + if ((right_left < middle_left && right_right > middle_left) || + (middle_left < right_left && middle_right > right_left)) + return FALSE; + + /* Check for overlapping right edges */ + if ((right_right > middle_right && right_left < middle_right) || + (middle_right > right_right && middle_left < right_right)) + return FALSE; + } + + /* If areas overlap in the X axis */ + if ((right_left >= middle_left && right_left <= middle_right) || + (right_right >= middle_left && right_right <= middle_right)) { + /* Check for overlapping top edges */ + if ((right_top < middle_top && right_bottom > middle_top) || + (middle_top < right_top && middle_bottom > right_top)) + return FALSE; + + /* Check for overlapping bottom edges */ + if ((right_bottom > middle_bottom && right_top < middle_bottom) || + (middle_bottom > right_bottom && middle_top < right_bottom)) + return FALSE; + } + } + + return TRUE; +} + +static void +set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset) +{ + SynapticsPrivate *priv = pInfo->private; + SynapticsParameters *pars = &priv->synpara; + int values[8]; + int in_percent = 0; /* bitmask for which ones are in % */ + char *option_string; + char *next_num; + char *end_str; + int i; + int width, height; + + if (!pars->clickpad) + return; + + option_string = xf86SetStrOption(pInfo->options, option_name, NULL); + if (!option_string) + return; + + next_num = option_string; + + for (i = 0; i < 8 && *next_num != '\0'; i++) { + long int value = strtol(next_num, &end_str, 0); + + if (value > INT_MAX || value < -INT_MAX) + goto fail; + + values[i] = value; + + if (next_num != end_str) { + if (*end_str == '%') { + in_percent |= 1 << i; + end_str++; + } + next_num = end_str; + } + else + goto fail; + } + + if (i < 8 || *next_num != '\0') + goto fail; + + width = priv->maxx - priv->minx; + height = priv->maxy - priv->miny; + + for (i = 0; in_percent && i < 8; i++) { + int base, size; + + if ((in_percent & (1 << i)) == 0 || values[i] == 0) + continue; + + size = ((i % 4) < 2) ? width : height; + base = ((i % 4) < 2) ? priv->minx : priv->miny; + values[i] = base + size * values[i] / 100.0; + } + + if (!SynapticsIsSoftButtonAreasValid(values)) + goto fail; + + memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int)); + memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int)); + + free(option_string); + + return; + + fail: + xf86IDrvMsg(pInfo, X_ERROR, + "invalid %s value '%s', keeping defaults\n", + option_name, option_string); + free(option_string); +} + +static void +set_primary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA); +} + +static void +set_secondary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA); +} + +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 */ + double accelFactor; /* 1/pixels */ + int fingerLow, fingerHigh; /* pressure */ + int emulateTwoFingerMinZ; /* pressure */ + int emulateTwoFingerMinW; /* width */ + 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; + int middle_button_timeout; + int grab_event_device = 0; + const char *source; + + /* 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; + 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 + 1; + + calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh); + + /* scaling based on defaults and a pressure of 256 */ + emulateTwoFingerMinZ = priv->minp + range * (282.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 + 1; + + /* 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 */ + tapButton1 = 1; + tapButton2 = 3; + tapButton3 = 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 */ + vertEdgeScroll = 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->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->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad); /* Probed */ + if (pars->clickpad) + pars->has_secondary_buttons = xf86SetBoolOption(opts, + "HasSecondarySoftButtons", + pars->has_secondary_buttons); + pars->clickpad_ignore_motion_time = 100; /* ms */ + /* middle mouse button emulation on a clickpad? nah, you're joking */ + middle_button_timeout = pars->clickpad ? 0 : 75; + pars->emulate_mid_button_time = + xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout); + pars->emulate_twofinger_z = + xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ); + pars->emulate_twofinger_w = + xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW); + pars->scroll_dist_vert = + xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta); + pars->scroll_dist_horiz = + xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta); + 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->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON); + + 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->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE); + pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000); + pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2); + pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3); + 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->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE); + + pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4); + pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7); + pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor); + 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); + + /* Only grab the device by default if it's not coming from a config + backend. This way we avoid the device being added twice and sending + duplicate events. + */ + source = xf86CheckStrOption(opts, "_source", NULL); + if (source == NULL || strncmp(source, "server/", 7) != 0) + grab_event_device = TRUE; + pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device); + + pars->tap_and_drag_gesture = + xf86SetBoolOption(opts, "TapAndDragGesture", TRUE); + pars->resolution_horiz = + xf86SetIntOption(opts, "HorizResolution", horizResolution); + pars->resolution_vert = + xf86SetIntOption(opts, "VertResolution", vertResolution); + if (pars->resolution_horiz <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid X resolution, using 1 instead.\n"); + pars->resolution_horiz = 1; + } + if (pars->resolution_vert <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid Y resolution, using 1 instead.\n"); + pars->resolution_vert = 1; + } + + /* Touchpad sampling rate is too low to detect all movements. + A user may lift one finger and put another one down within the same + EV_SYN or even between samplings so the driver doesn't notice at all. + + We limit the movement to 20 mm within one event, that is more than + recordings showed is needed (17mm on a T440). + */ + if (pars->resolution_horiz > 1 && + pars->resolution_vert > 1) + pars->maxDeltaMM = 20; + else { + /* on devices without resolution set the vector length to 0.25 of + the touchpad diagonal */ + pars->maxDeltaMM = diag * 0.25; + } + + + /* 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"); + } + + set_primary_softbutton_areas_option(pInfo); + if (pars->has_secondary_buttons) + set_secondary_softbutton_areas_option(pInfo); +} + +static double +SynapticsAccelerationProfile(DeviceIntPtr dev, + DeviceVelocityPtr vel, + double velocity, double thr, double 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; +} + +static int +SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + 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 */ + if (!SetDeviceAndProtocol(pInfo)) { + 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); + + set_default_parameters(pInfo); + +#ifndef NO_DRIVER_SCALING + CalculateScalingCoeffs(priv); +#endif + + + 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 (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + + return Success; + + SetupProc_fail: + SynapticsCloseFd(pInfo); + + if (priv->comm.buffer) + XisbFree(priv->comm.buffer); + 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); + if (priv && priv->scroll_events_mask) + valuator_mask_free(&priv->scroll_events_mask); + if (priv && priv->open_slots) + free(priv->open_slots); + 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 int +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 int +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)) + goto error; + + priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE); + if (!priv->comm.buffer) + goto error; + + xf86FlushInput(pInfo->fd); + + /* reinit the pad */ + if (!QueryHardware(pInfo)) + goto error; + + xf86AddEnabledDevice(pInfo); + dev->public.on = TRUE; + + return Success; + +error: + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + return !Success; +} + +static void +SynapticsReset(SynapticsPrivate * priv) +{ + int i; + + SynapticsResetHwState(priv->hwState); + SynapticsResetHwState(priv->local_hw_state); + SynapticsResetHwState(priv->comm.hwState); + + memset(priv->move_hist, 0, sizeof(priv->move_hist)); + priv->hyst_center_x = 0; + priv->hyst_center_y = 0; + memset(&priv->scroll, 0, sizeof(priv->scroll)); + priv->count_packet_finger = 0; + priv->finger_state = FS_UNTOUCHED; + priv->last_motion_millis = 0; + priv->clickpad_click_millis = 0; + priv->last_button_area = NO_BUTTON_AREA; + priv->tap_state = TS_START; + priv->tap_button = 0; + priv->tap_button_state = TBS_BUTTON_UP; + priv->moving_state = MS_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; + priv->circ_scroll_on = FALSE; + priv->circ_scroll_vert = FALSE; + priv->mid_emu_state = MBE_OFF; + priv->nextRepeat = 0; + priv->lastButtons = 0; + priv->prev_z = 0; + priv->prevFingers = 0; + priv->num_active_touches = 0; + + for (i = 0; i < priv->num_slots; i++) + priv->open_slots[i] = -1; +} + +static int +DeviceOff(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + Bool rc = Success; + + DBG(3, "Synaptics DeviceOff called\n"); + + if (pInfo->fd != -1) { + TimerCancel(priv->timer); + xf86RemoveEnabledDevice(pInfo); + SynapticsReset(priv); + + if (priv->proto_ops->DeviceOffHook && + !priv->proto_ops->DeviceOffHook(pInfo)) + rc = !Success; + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + } + dev->public.on = FALSE; + return rc; +} + +static int +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(priv->touch_axes); + priv->touch_axes = NULL; + SynapticsHwStateFree(&priv->hwState); + SynapticsHwStateFree(&priv->local_hw_state); + SynapticsHwStateFree(&priv->comm.hwState); + return RetValue; +} + +static void +InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv) +{ + int i; + + memset(labels, 0, nlabels * sizeof(Atom)); + switch (nlabels) { + default: + case 4: + labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL); + case 3: + labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL); + case 2: + labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y); + case 1: + labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X); + break; + } + + for (i = 0; i < priv->num_mt_axes; i++) { + SynapticsTouchAxisRec *axis = &priv->touch_axes[i]; + int axnum = nlabels - priv->num_mt_axes + i; + + labels[axnum] = XIGetKnownProperty(axis->label); + } +} + +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; + } +} + +static void +DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + if (!priv->has_touch) + return; + + priv->num_slots = + priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES; + + priv->open_slots = malloc(priv->num_slots * sizeof(int)); + if (!priv->open_slots) { + xf86IDrvMsg(pInfo, X_ERROR, + "failed to allocate open touch slots array\n"); + priv->has_touch = 0; + priv->num_slots = 0; + } +} + +static int +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; + int num_axes = 2; + Atom btn_labels[SYN_MAX_BUTTONS] = { 0 }; + Atom *axes_labels; + DeviceVelocityPtr pVel; + + num_axes += 2; + + num_axes += priv->num_mt_axes; + + axes_labels = calloc(num_axes, sizeof(Atom)); + if (!axes_labels) { + xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n"); + return !Success; + } + + InitAxesLabels(axes_labels, num_axes, priv); + InitButtonLabels(btn_labels, SYN_MAX_BUTTONS); + + 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, + btn_labels, + SynapticsCtrl, + GetMotionHistorySize(), num_axes, axes_labels); + + /* + * setup dix acceleration to match legacy synaptics settings, and + * etablish a device-specific profile to do stuff like pressure-related + * acceleration. + */ + 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, "(accel) MaxSpeed is now %.2f\n", + priv->synpara.max_speed); + xf86IDrvMsg(pInfo, X_CONFIG, "(accel) 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); + } + + /* X valuator */ + if (priv->minx < priv->maxx && priv->synpara.resolution_detect) { + min = priv->minx; + max = priv->maxx; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max, + priv->resx * 1000, 0, priv->resx * 1000, + Relative); + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ + if (priv->miny < priv->maxy && priv->synpara.resolution_detect) { + min = priv->miny; + max = priv->maxy; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max, + priv->resy * 1000, 0, priv->resy * 1000, + Relative); + xf86InitValuatorDefaults(dev, 1); + + xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_horiz = 2; + xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_vert = 3; + priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS); + if (!priv->scroll_events_mask) { + free(axes_labels); + return !Success; + } + + SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL, + priv->synpara.scroll_dist_horiz, 0); + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + priv->synpara.scroll_dist_vert, 0); + + DeviceInitTouch(dev, axes_labels); + + free(axes_labels); + + priv->hwState = SynapticsHwStateAlloc(priv); + if (!priv->hwState) + goto fail; + + priv->local_hw_state = SynapticsHwStateAlloc(priv); + if (!priv->local_hw_state) + goto fail; + + priv->comm.hwState = SynapticsHwStateAlloc(priv); + + InitDeviceProperties(pInfo); + XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL); + + SynapticsReset(priv); + + return Success; + + fail: + free(priv->local_hw_state); + free(priv->hwState); + free(priv->open_slots); + 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 enum EdgeType +circular_edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 enum EdgeType +edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 a finger is down, then it must have started inside the active_area, + allow the motion to complete using the entire area */ + if (priv->finger_state >= FS_TOUCHED) + return 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 Bool +is_inside_button_area(SynapticsParameters * para, int which, int x, int y) +{ + Bool inside_area = TRUE; + + if (para->softbutton_areas[which][LEFT] == 0 && + para->softbutton_areas[which][RIGHT] == 0 && + para->softbutton_areas[which][TOP] == 0 && + para->softbutton_areas[which][BOTTOM] == 0) + return FALSE; + + if (para->softbutton_areas[which][LEFT] && + x < para->softbutton_areas[which][LEFT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][RIGHT] && + x > para->softbutton_areas[which][RIGHT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][TOP] && + y < para->softbutton_areas[which][TOP]) + inside_area = FALSE; + else if (para->softbutton_areas[which][BOTTOM] && + y > para->softbutton_areas[which][BOTTOM]) + inside_area = FALSE; + + return inside_area; +} + +static Bool +is_inside_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset, + int x, int y) +{ + Bool inside_area = TRUE; + Bool right_valid, middle_valid; + int top, bottom; + + /* We don't have a left button area, so we only check the y axis */ + right_valid = para->softbutton_areas[offset][TOP] || + para->softbutton_areas[offset][BOTTOM]; + middle_valid = para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset + 1][BOTTOM]; + + if (!right_valid && !middle_valid) + return FALSE; + + /* Check both buttons are horizontally aligned */ + if (right_valid && middle_valid && ( + para->softbutton_areas[offset][TOP] != + para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset][BOTTOM] != + para->softbutton_areas[offset + 1][BOTTOM])) + return FALSE; + + if (right_valid) { + top = para->softbutton_areas[offset][TOP]; + bottom = para->softbutton_areas[offset][BOTTOM]; + } + else { + top = para->softbutton_areas[offset + 1][TOP]; + bottom = para->softbutton_areas[offset + 1][BOTTOM]; + } + + if (top && y < top) + inside_area = FALSE; + else if (bottom && y > bottom) + inside_area = FALSE; + + return inside_area; +} + +static enum SoftButtonAreas +current_button_area(SynapticsParameters * para, int x, int y) +{ + if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y)) + return BOTTOM_BUTTON_AREA; + else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y)) + return TOP_BUTTON_AREA; + else + return NO_BUTTON_AREA; +} + +static CARD32 +timerFunc(OsTimerPtr timer, CARD32 now, pointer arg) +{ + InputInfoPtr pInfo = arg; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + struct SynapticsHwState *hw = priv->local_hw_state; + int delay; +#if !HAVE_THREADED_INPUT + int sigstate = xf86BlockSIGIO(); +#else + input_lock(); +#endif + + priv->hwState->millis += now - priv->timer_time; + SynapticsCopyHwState(hw, priv->hwState); + SynapticsResetTouchHwState(hw, FALSE); + delay = HandleState(pInfo, hw, hw->millis, TRUE); + + priv->timer_time = now; + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + +#if !HAVE_THREADED_INPUT + xf86UnblockSIGIO(sigstate); +#else + input_unlock(); +#endif + + 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 = priv->local_hw_state; + int delay = 0; + Bool newDelay = FALSE; + + SynapticsResetTouchHwState(hw, FALSE); + + while (SynapticsGetHwState(pInfo, priv, hw)) { + /* Semi-mt device touch slots do not track touches. When there is a + * change in the number of touches, we must disregard the temporary + * motion changes. */ + if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) { + hw->cumulative_dx = priv->hwState->cumulative_dx; + hw->cumulative_dy = priv->hwState->cumulative_dy; + } + + /* timer may cause actual events to lag behind (#48777) */ + if (priv->hwState->millis > hw->millis) + hw->millis = priv->hwState->millis; + + SynapticsCopyHwState(priv->hwState, hw); + delay = HandleState(pInfo, hw, hw->millis, FALSE); + newDelay = TRUE; + } + + if (newDelay) { + priv->timer_time = GetTimeInMillis(); + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + } +} + +static int +HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw, + CARD32 now, int *delay) +{ + SynapticsParameters *para = &priv->synpara; + Bool done = FALSE; + int timeleft; + int mid = 0; + + if (para->emulate_mid_button_time <= 0) + return mid; + + while (!done) { + switch (priv->mid_emu_state) { + case MBE_LEFT_CLICK: + case MBE_RIGHT_CLICK: + case MBE_OFF: + priv->button_delay_millis = now; + 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, now); + 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, now); + 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_low) + return FS_UNTOUCHED; + + if (priv->finger_state == FS_BLOCKED) + return FS_BLOCKED; + + if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED) + finger = FS_TOUCHED; + else + finger = priv->finger_state; + + if (!para->palm_detect) + return finger; + + /* palm detection */ + + if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width)) + return FS_BLOCKED; + + if (priv->has_mt_palm_detect) + return finger; + + if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED) + priv->avg_width = 0; + else + priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2; + + if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) { + 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; + + return finger; +} + +static void +SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge) +{ + enum TapEvent tap; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) { + priv->tap_button = 0; + return; + } + + switch (priv->tap_max_fingers) { + case 1: + 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; + default: + priv->tap_button = 0; + return; + } + + 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, CARD32 millis) +{ + DBG(3, "SetTapState - %d -> %d (millis:%u)\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: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_2B: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_3: + priv->tap_button_state = TBS_BUTTON_DOWN; + break; + case TS_SINGLETAP: + 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, + CARD32 millis) +{ + DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n", + priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y, + millis); + + 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, + CARD32 now, enum FingerState finger, + Bool inside_active_area) +{ + SynapticsParameters *para = &priv->synpara; + Bool touch, release, is_timeout, move, press; + int timeleft, timeout; + enum EdgeType edge; + int delay = 1000000000; + + if (para->touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) + return delay; + + touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED; + release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED; + move = (finger >= FS_TOUCHED && + (priv->tap_max_fingers <= + ((priv->horiz_scroll_twofinger_on || + priv->vert_scroll_twofinger_on) ? 2 : 1)) && + (priv->prevFingers == hw->numFingers && + ((abs(hw->x - priv->touch_on.x) >= para->tap_move) || + (abs(hw->y - priv->touch_on.y) >= para->tap_move)))); + press = (hw->left || hw->right || hw->middle); + + if (touch) { + priv->touch_on.x = hw->x; + priv->touch_on.y = hw->y; + priv->touch_on.millis = now; + } + else if (release) { + priv->touch_on.millis = now; + } + 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, now); + is_timeout = timeleft <= 0; + + restart: + switch (priv->tap_state) { + case TS_START: + if (touch) + SetTapState(priv, TS_1, now); + break; + case TS_1: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_MOVE, now); + goto restart; + } + else if (is_timeout) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_MOVE, now); + 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, now); + } + break; + case TS_MOVE: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_2A: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_2B: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_SINGLETAP: + if (touch) + SetTapState(priv, TS_1, now); + else if (is_timeout) + SetTapState(priv, TS_START, now); + break; + case TS_3: + if (move) { + if (para->tap_and_drag_gesture) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (is_timeout) { + if (para->tap_and_drag_gesture) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (release) { + SetTapState(priv, TS_2B, now); + } + break; + case TS_DRAG: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (release) { + SetMovingState(priv, MS_FALSE, now); + if (para->locked_drags) { + SetTapState(priv, TS_4, now); + } + else { + SetTapState(priv, TS_START, now); + } + } + break; + case TS_4: + if (is_timeout) { + SetTapState(priv, TS_START, now); + goto restart; + } + if (touch) + SetTapState(priv, TS_5, now); + break; + case TS_5: + if (is_timeout || move) { + SetTapState(priv, TS_DRAG, now); + goto restart; + } + else if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_CLICKPAD_MOVE: + /* Disable scrolling once a button is pressed on a clickpad */ + priv->vert_scroll_edge_on = FALSE; + priv->horiz_scroll_edge_on = FALSE; + priv->vert_scroll_twofinger_on = FALSE; + priv->horiz_scroll_twofinger_on = FALSE; + + /* Assume one touch is only for holding the clickpad button down */ + if (hw->numFingers > 1) + hw->numFingers--; + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (!press) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_MOVE, now); + priv->count_packet_finger = 0; + } + break; + } + + timeout = GetTimeOut(priv); + if (timeout >= 0) { + timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now); + delay = clamp(timeleft, 1, delay); + } + return delay; +} + +#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)]) +#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e)) + +static void +store_history(SynapticsPrivate * priv, int x, int y, CARD32 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; + if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY) + priv->count_packet_finger++; +} + +/* + * 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(SynapticsPrivate *priv, const struct SynapticsHwState *hw, + enum EdgeType edge, double *dx, double *dy) +{ + *dx = hw->x - HIST(0).x; + *dy = hw->y - HIST(0).y; +} + +/* Vector length, but not sqrt'ed, we only need it for comparison */ +static inline double +vlenpow2(double x, double y) +{ + return x * x + y * y; +} + +/** + * Compute relative motion ('deltas') including edge motion. + */ +static int +ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw, + enum EdgeType edge, int *dxP, int *dyP, Bool inside_area) +{ + enum MovingState moving_state; + double dx, dy; + double vlen; + 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: + moving_state = MS_TOUCHPAD_RELATIVE; + break; + default: + break; + } + } + + if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED || + 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 || + (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) { + /* reset packet counter. */ + priv->count_packet_finger = 0; + goto out; + } + + /* To create the illusion of fluid motion, call back at roughly the report + * rate, even in the absence of new hardware events; see comment above + * POLL_MS declaration. */ + delay = MIN(delay, POLL_MS); + + if (priv->count_packet_finger <= 1) + goto out; /* skip the lot */ + + if (moving_state == MS_TOUCHPAD_RELATIVE) + get_delta(priv, hw, edge, &dx, &dy); + + out: + priv->prevFingers = hw->numFingers; + + vlen = vlenpow2(dx/priv->synpara.resolution_horiz, + dy/priv->synpara.resolution_vert); + + if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) { + dx = 0; + dy = 0; + } + + *dxP = dx; + *dyP = dy; + + return delay; +} + +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->scroll.coast_delta_y = 0.0; + priv->scroll.coast_delta_x = 0.0; + + if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) { + double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0; + + if (vert && !circ) { + double dy = + estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y); + if (pkt_time > 0) { + double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y); + } + } + } + if (horiz && !circ) { + double dx = + estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x); + if (pkt_time > 0) { + double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x); + } + } + } + if (circ) { + double da = estimate_delta_circ(priv); + + if (pkt_time > 0) { + double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + if (vert) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + else if (horiz) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + } + } + } + } + priv->scroll.packets_this_scroll = 0; +} + +static void +stop_coasting(SynapticsPrivate * priv) +{ + priv->scroll.coast_speed_x = 0; + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; +} + +static int +HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw, + enum EdgeType edge, Bool finger) +{ + SynapticsParameters *para = &priv->synpara; + int delay = 1000000000; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF || + priv->synpara.touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) { + 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 == FS_UNTOUCHED) { + stop_coasting(priv); + priv->scroll.delta_y = 0; + priv->scroll.delta_x = 0; + 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.last_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)) { + stop_coasting(priv); + priv->vert_scroll_twofinger_on = TRUE; + priv->vert_scroll_edge_on = FALSE; + priv->scroll.last_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)) { + stop_coasting(priv); + priv->horiz_scroll_twofinger_on = TRUE; + priv->horiz_scroll_edge_on = FALSE; + priv->scroll.last_x = hw->x; + DBG(7, "horiz two-finger scroll detected\n"); + } + } + } + if (finger && priv->finger_state == FS_UNTOUCHED) { + 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.last_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.last_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->scroll.coast_speed_x || priv->scroll.coast_speed_y)) { + 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->scroll.coast_speed_y == 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.last_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->scroll.coast_speed_x == 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.last_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.packets_this_scroll++; + } + + if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) { + /* + = down, - = up */ + if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) { + priv->scroll.delta_y += (hw->y - priv->scroll.last_y); + priv->scroll.last_y = hw->y; + } + } + if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) { + /* + = right, - = left */ + if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) { + priv->scroll.delta_x += (hw->x - priv->scroll.last_x); + priv->scroll.last_x = hw->x; + } + } + if (priv->circ_scroll_on) { + /* + = counter clockwise, - = clockwise */ + double delta = para->scroll_dist_circ; + double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y)); + + if (delta >= 0.005 && diff != 0.0) { + if (priv->circ_scroll_vert) + priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert; + else + priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz; + priv->scroll.last_a = angle(priv, hw->x, hw->y); + } + } + + if (priv->scroll.coast_speed_y) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddy = para->coasting_friction * dtime; + + priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_y) < ddy) { + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_y += + (priv->scroll.coast_speed_y < 0 ? ddy : -ddy); + } + } + + if (priv->scroll.coast_speed_x) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddx = para->coasting_friction * dtime; + priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_x) < ddx) { + priv->scroll.coast_speed_x = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_x += + (priv->scroll.coast_speed_x < 0 ? ddx : -ddx); + } + } + + return delay; +} + +/** + * Check if any 2+ fingers are close enough together to assume this is a + * ClickFinger action. + */ +static int +clickpad_guess_clickfingers(SynapticsPrivate * priv, + struct SynapticsHwState *hw) +{ + int nfingers = 0; + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + +#ifdef BUG_RETURN_VAL + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); +#endif + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; + + if (hw->slot_state[i] == SLOTSTATE_EMPTY || + hw->slot_state[i] == SLOTSTATE_CLOSE) + continue; + + f1 = hw->mt_mask[i]; + + for (j = i + 1; j < hw->num_mt_mask; j++) { + ValuatorMask *f2; + double x1, x2, y1, y2; + + if (hw->slot_state[j] == SLOTSTATE_EMPTY || + hw->slot_state[j] == SLOTSTATE_CLOSE) + continue; + + f2 = hw->mt_mask[j]; + + x1 = valuator_mask_get_double(f1, 0); + y1 = valuator_mask_get_double(f1, 1); + + x2 = valuator_mask_get_double(f2, 0); + y2 = valuator_mask_get_double(f2, 1); + + /* FIXME: fingers closer together than 30% of touchpad width, but + * really, this should be dependent on the touchpad size. Also, + * you'll need to find a touchpad that doesn't lie about it's + * size. Good luck. */ + if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 && + abs(y1 - y2) < (priv->maxy - priv->miny) * .3) { + close_point |= (1 << j); + close_point |= (1 << i); + } + } + } + + while (close_point > 0) { + nfingers += close_point & 0x1; + close_point >>= 1; + } + + /* Some trackpads touchpad only track two touchpoints but announce + * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens + * the user likely intents to do a 3 finger click, so handle it as such. + */ + if (hw->numFingers >= 3 && hw->num_mt_mask < 3) + nfingers = 3; + + return nfingers; +} + +static void +handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw) +{ + SynapticsParameters *para = &priv->synpara; + int action = 0; + int nfingers = hw->numFingers; + + /* if this is a clickpad, clickfinger handling is: + * one finger down: no action, this is a normal click + * two fingers down: F2_CLICK + * three fingers down: F3_CLICK + */ + + if (para->clickpad) + nfingers = clickpad_guess_clickfingers(priv, hw); + + switch (nfingers) { + 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 | BTN_EMULATED_FLAG; + break; + case 2: + hw->left = 0; + hw->middle = 1 | BTN_EMULATED_FLAG; + break; + case 3: + hw->left = 0; + hw->right = 1 | BTN_EMULATED_FLAG; + break; + } +} + +/* 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, + CARD32 now, 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, now, delay); + + /* If this is a clickpad and the user clicks in a soft button area, press + * the soft button instead. */ + if (para->clickpad) { + /* hw->left is down, but no other buttons were already down */ + if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) { + /* If the finger down event is delayed, the x and y + * coordinates are stale so we delay processing the click */ + if (hw->z < para->finger_low) { + hw->left = 0; + goto out; + } + if (is_inside_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + priv->clickpad_click_millis = now; + } + else if (hw->left) { + hw->left = (priv->lastButtons & 1) ? 1 : 0; + hw->middle = (priv->lastButtons & 2) ? 1 : 0; + hw->right = (priv->lastButtons & 4) ? 1 : 0; + } + } + + /* Fingers emulate other buttons. ClickFinger can only be + triggered on transition, when left is pressed + */ + if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1) + handle_clickfinger(priv, hw); + +out: + /* 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) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + valuator_mask_zero(priv->scroll_events_mask); + + if (priv->scroll.delta_y != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_vert, priv->scroll.delta_y); + priv->scroll.delta_y = 0; + } + if (priv->scroll.delta_x != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_horiz, priv->scroll.delta_x); + priv->scroll.delta_x = 0; + } + if (valuator_mask_num_valuators(priv->scroll_events_mask)) + xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask); +} + +static inline int +repeat_scrollbuttons(const InputInfoPtr pInfo, + const struct SynapticsHwState *hw, + int buttons, CARD32 now, int delay) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + int repeat_delay, timeleft; + int rep_buttons = 0; + + if (para->updown_button_repeat) + rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1)); + if (para->leftright_button_repeat) + rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1)); + + /* 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 = now + repeat_delay * 2; + } + } + else { + priv->repeatButtons = 0; + priv->nextRepeat = 0; + } + + if (priv->repeatButtons) { + timeleft = TIME_DIFF(priv->nextRepeat, now); + 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)); + if (id == 4) + priv->scroll.delta_y -= para->scroll_dist_vert; + else if (id == 5) + priv->scroll.delta_y += para->scroll_dist_vert; + else if (id == 6) + priv->scroll.delta_x -= para->scroll_dist_horiz; + else if (id == 7) + priv->scroll.delta_x += para->scroll_dist_horiz; + } + + priv->nextRepeat = now + repeat_delay; + delay = MIN(delay, repeat_delay); + } + } + + return delay; +} + +/* Update the open slots and number of active touches */ +static void +UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + int i; + + for (i = 0; i < hw->num_mt_mask; i++) { + if (hw->slot_state[i] == SLOTSTATE_OPEN) { + priv->open_slots[priv->num_active_touches] = i; + priv->num_active_touches++; + BUG_WARN(priv->num_active_touches > priv->num_slots); + } + else if (hw->slot_state[i] == SLOTSTATE_CLOSE) { + Bool found = FALSE; + int j; + + for (j = 0; j < priv->num_active_touches - 1; j++) { + if (priv->open_slots[j] == i) + found = TRUE; + + if (found) + priv->open_slots[j] = priv->open_slots[j + 1]; + } + + BUG_WARN(priv->num_active_touches == 0); + if (priv->num_active_touches > 0) + priv->num_active_touches--; + } + } + + SynapticsResetTouchHwState(hw, FALSE); +} + +static void +filter_jitter(SynapticsPrivate * priv, int *x, int *y) +{ + SynapticsParameters *para = &priv->synpara; + + priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x); + priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y); + *x = priv->hyst_center_x; + *y = priv->hyst_center_y; +} + +static void +reset_hw_state(struct SynapticsHwState *hw) +{ + hw->x = 0; + hw->y = 0; + hw->z = 0; + hw->numFingers = 0; + hw->fingerWidth = 0; +} + +/* + * 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. + * + * from_timer denotes if HandleState was triggered from a timer (e.g. to + * generate fake motion events, or for the tap-to-click state machine), rather + * than from having received a motion event. + */ +static int +HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now, + Bool from_timer) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + enum FingerState finger = FS_UNTOUCHED; + int dx = 0, dy = 0, buttons, id; + enum EdgeType edge = NO_EDGE; + int change; + int double_click = FALSE; + int delay = 1000000000; + int timeleft; + Bool inside_active_area; + Bool using_cumulative_coords = FALSE; + Bool ignore_motion; + + /* We need both and x/y, the driver can't handle just one of the two + * yet. But since it's possible to hit a phys button on non-clickpads + * without ever getting motion data first, we must continue with 0/0 for + * that case. */ + if (hw->x == INT_MIN || hw->y == INT_MAX) { + if (para->clickpad) + return delay; + else if (hw->left || hw->right || hw->middle) { + hw->x = (hw->x == INT_MIN) ? 0 : hw->x; + hw->y = (hw->y == INT_MIN) ? 0 : hw->y; + } + } + + /* If a physical button is pressed on a clickpad or a two-finger scrolling + * is ongoing, use cumulative relative touch movements for motion */ + if (para->clickpad && + ((priv->lastButtons & 7) || + (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) && + priv->last_button_area != TOP_BUTTON_AREA) { + hw->x = hw->cumulative_dx; + hw->y = hw->cumulative_dy; + using_cumulative_coords = TRUE; + } + + /* 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. */ + filter_jitter(priv, &hw->x, &hw->y); + + inside_active_area = is_inside_active_area(priv, hw->x, hw->y); + + /* Ignore motion *starting* inside softbuttonareas */ + if (priv->finger_state < FS_TOUCHED) + priv->last_button_area = current_button_area(para, hw->x, hw->y); + /* If we already have a finger down, clear last_button_area if it goes + outside of the softbuttonareas */ + else if (priv->last_button_area != NO_BUTTON_AREA && + current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA) + priv->last_button_area = NO_BUTTON_AREA; + + ignore_motion = para->touchpad_off == TOUCHPAD_OFF || + (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA); + + /* these two just update hw->left, right, etc. */ + update_hw_button_state(pInfo, hw, now, &delay); + if (priv->has_scrollbuttons) + double_click = adjust_state_from_scrollbuttons(pInfo, hw); + + /* Ignore motion the first X ms after a clickpad click */ + if (priv->clickpad_click_millis) { + if(TIME_DIFF(priv->clickpad_click_millis + + para->clickpad_ignore_motion_time, now) > 0) + ignore_motion = TRUE; + else + priv->clickpad_click_millis = 0; + } + + /* 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) + reset_hw_state(hw); + + /* no edge or finger detection outside of area */ + if (inside_active_area) { + edge = edge_detection(priv, hw->x, hw->y); + if (!from_timer) + finger = SynapticsDetectFinger(priv, hw); + else + finger = priv->finger_state; + } + + /* 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, now, 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 >= FS_TOUCHED)); + 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. + */ +#ifndef NO_DRIVER_SCALING + ScaleCoordinates(priv, hw); +#endif + } + + dx = dy = 0; + + 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 && priv->tap_button_state == TBS_BUTTON_DOWN) + buttons |= 1 << (priv->tap_button - 1); + + /* Post events */ + if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion) + 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); + } + + if (priv->has_scrollbuttons) + delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay); + + /* Process scroll events only if coordinates are + * in the Synaptics Area + */ + if (inside_active_area && + (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) { + post_scroll_events(pInfo); + priv->scroll.last_millis = hw->millis; + } + + if (double_click) { + post_button_click(pInfo, 1); + post_button_click(pInfo, 1); + } + + UpdateTouchState(pInfo, hw); + + /* 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) +{ + DBG(3, "SwitchMode called\n"); + + return XI_BadMode; +} + +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; +} + +#ifndef NO_DRIVER_SCALING +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; + } +} +#endif --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/129_disable_three_touch_tap.patch/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/.pc/129_disable_three_touch_tap.patch/src/synaptics.c @@ -0,0 +1,3249 @@ +/* + * 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-2012 Red Hat, Inc. + * Copyright © 2011 The Chromium OS Authors + * + * 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 + * Christian Thaeter + * Stefan Bethge + * Matthias Ihmig + * Alexei Gilchrist + * Jörg Bösner + * Hartwig Felger + * Peter Osterlund + * S. Lehner + * Stefan Gmeiner + * Henry Davies for the + * Linuxcare Inc. David Kennedy + * Fred Hucht + * Fedor P. Goncharov + * Simon Thum + * + * Trademarks are the property of their respective owners. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "synapticsstr.h" +#include "synaptics-properties.h" + +enum EdgeType { + 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 +}; + +/* + * We expect to be receiving a steady 80 packets/sec (which gives 40 + * reports/sec with more than one finger on the pad, as Advanced Gesture Mode + * requires two PS/2 packets per report). Instead of a random scattering of + * magic 13 and 20ms numbers scattered throughout the driver, introduce + * POLL_MS as 14ms, which is slightly less than 80Hz. 13ms is closer to + * 80Hz, but if the kernel event reporting was even slightly delayed, + * we would produce synthetic motion followed immediately by genuine + * motion, so use 14. + * + * We use this to call back at a constant rate to at least produce the + * illusion of smooth motion. It works a lot better than you'd expect. +*/ +#define POLL_MS 14 + +#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 + +#define INPUT_BUFFER_SIZE 200 + +/***************************************************************************** + * Forward declaration + ****************************************************************************/ +static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags); +static Bool DeviceControl(DeviceIntPtr, int); +static void ReadInput(InputInfoPtr); +static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now, + Bool from_timer); +static int ControlProc(InputInfoPtr, xDeviceCtl *); +static int SwitchMode(ClientPtr, DeviceIntPtr, int); +static int DeviceInit(DeviceIntPtr); +static int DeviceOn(DeviceIntPtr); +static int DeviceOff(DeviceIntPtr); +static int DeviceClose(DeviceIntPtr); +static Bool QueryHardware(InputInfoPtr); +static void ReadDevDimensions(InputInfoPtr); +#ifndef NO_DRIVER_SCALING +static void ScaleCoordinates(SynapticsPrivate * priv, + struct SynapticsHwState *hw); +static void CalculateScalingCoeffs(SynapticsPrivate * priv); +#endif +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, + NULL, +#ifdef XI86_DRV_CAP_SERVER_FD + XI86_DRV_CAP_SERVER_FD +#endif +}; + +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 + ****************************************************************************/ +static inline void +SynapticsCloseFd(InputInfoPtr pInfo) +{ + if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) { + xf86CloseSerial(pInfo->fd); + pInfo->fd = -1; + } +} + +/** + * 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 = 255; + + 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 = 15; + + xf86IDrvMsg(pInfo, X_PROBED, + "invalid finger width range. defaulting to %d - %d\n", + priv->minw, priv->maxw); + } +} + +static Bool +SetDeviceAndProtocol(InputInfoPtr pInfo) +{ + SynapticsPrivate *priv = pInfo->private; + char *proto, *device; + int i; + + proto = xf86SetStrOption(pInfo->options, "Protocol", NULL); + device = xf86SetStrOption(pInfo->options, "Device", NULL); + + /* If proto is auto-dev, unset and let the code do the rest */ + if (proto && !strcmp(proto, "auto-dev")) { + free(proto); + proto = 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; + + return (priv->proto_ops != 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 || + priv->model == MODEL_UNIBODY_MACBOOK) { + 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; +} + +static void +calculate_tap_hysteresis(SynapticsPrivate * priv, int range, + int *fingerLow, int *fingerHigh) +{ + switch (priv->model) { + case MODEL_ELANTECH: + /* All Elantech touchpads don't need the Z filtering to get the + * number of fingers correctly. See Documentation/elantech.txt + * in the kernel. + */ + *fingerLow = priv->minp + 1; + *fingerHigh = priv->minp + 1; + break; + case MODEL_UNIBODY_MACBOOK: + *fingerLow = 70; + *fingerHigh = 75; + break; + default: + *fingerLow = priv->minp + range * (25.0 / 256); + *fingerHigh = priv->minp + range * (30.0 / 256); + break; + } +} + +/* 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; + double percent = xf86CheckPercentOption(options, optname, -1); + + if (percent >= 0.0) { + percent = xf86SetPercentOption(options, optname, -1); + result = percent / 100.0 * range + offset; + } else + result = xf86SetIntOption(options, optname, default_value); + + return result; +} + +Bool +SynapticsIsSoftButtonAreasValid(int *values) +{ + Bool right_disabled = FALSE; + Bool middle_disabled = FALSE; + + enum { + /* right button left, right, top, bottom */ + RBL = 0, + RBR = 1, + RBT = 2, + RBB = 3, + /* middle button left, right, top, bottom */ + MBL = 4, + MBR = 5, + MBT = 6, + MBB = 7, + }; + + /* Check right button area */ + if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) || + (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB]))) + return FALSE; + + /* Check middle button area */ + if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) || + (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB]))) + return FALSE; + + if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0) + right_disabled = TRUE; + + if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0) + middle_disabled = TRUE; + + if (!right_disabled && + ((values[RBL] && values[RBL] == values[RBR]) || + (values[RBT] && values[RBT] == values[RBB]))) + return FALSE; + + if (!middle_disabled && + ((values[MBL] && values[MBL] == values[MBR]) || + (values[MBT] && values[MBT] == values[MBB]))) + return FALSE; + + /* Check for overlapping button areas */ + if (!right_disabled && !middle_disabled) { + int right_left = values[RBL] ? values[RBL] : INT_MIN; + int right_right = values[RBR] ? values[RBR] : INT_MAX; + int right_top = values[RBT] ? values[RBT] : INT_MIN; + int right_bottom = values[RBB] ? values[RBB] : INT_MAX; + int middle_left = values[MBL] ? values[MBL] : INT_MIN; + int middle_right = values[MBR] ? values[MBR] : INT_MAX; + int middle_top = values[MBT] ? values[MBT] : INT_MIN; + int middle_bottom = values[MBB] ? values[MBB] : INT_MAX; + + /* If areas overlap in the Y axis */ + if ((right_bottom <= middle_bottom && right_bottom >= middle_top) || + (right_top <= middle_bottom && right_top >= middle_top)) { + /* Check for overlapping left edges */ + if ((right_left < middle_left && right_right > middle_left) || + (middle_left < right_left && middle_right > right_left)) + return FALSE; + + /* Check for overlapping right edges */ + if ((right_right > middle_right && right_left < middle_right) || + (middle_right > right_right && middle_left < right_right)) + return FALSE; + } + + /* If areas overlap in the X axis */ + if ((right_left >= middle_left && right_left <= middle_right) || + (right_right >= middle_left && right_right <= middle_right)) { + /* Check for overlapping top edges */ + if ((right_top < middle_top && right_bottom > middle_top) || + (middle_top < right_top && middle_bottom > right_top)) + return FALSE; + + /* Check for overlapping bottom edges */ + if ((right_bottom > middle_bottom && right_top < middle_bottom) || + (middle_bottom > right_bottom && middle_top < right_bottom)) + return FALSE; + } + } + + return TRUE; +} + +static void +set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset) +{ + SynapticsPrivate *priv = pInfo->private; + SynapticsParameters *pars = &priv->synpara; + int values[8]; + int in_percent = 0; /* bitmask for which ones are in % */ + char *option_string; + char *next_num; + char *end_str; + int i; + int width, height; + + if (!pars->clickpad) + return; + + option_string = xf86SetStrOption(pInfo->options, option_name, NULL); + if (!option_string) + return; + + next_num = option_string; + + for (i = 0; i < 8 && *next_num != '\0'; i++) { + long int value = strtol(next_num, &end_str, 0); + + if (value > INT_MAX || value < -INT_MAX) + goto fail; + + values[i] = value; + + if (next_num != end_str) { + if (*end_str == '%') { + in_percent |= 1 << i; + end_str++; + } + next_num = end_str; + } + else + goto fail; + } + + if (i < 8 || *next_num != '\0') + goto fail; + + width = priv->maxx - priv->minx; + height = priv->maxy - priv->miny; + + for (i = 0; in_percent && i < 8; i++) { + int base, size; + + if ((in_percent & (1 << i)) == 0 || values[i] == 0) + continue; + + size = ((i % 4) < 2) ? width : height; + base = ((i % 4) < 2) ? priv->minx : priv->miny; + values[i] = base + size * values[i] / 100.0; + } + + if (!SynapticsIsSoftButtonAreasValid(values)) + goto fail; + + memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int)); + memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int)); + + free(option_string); + + return; + + fail: + xf86IDrvMsg(pInfo, X_ERROR, + "invalid %s value '%s', keeping defaults\n", + option_name, option_string); + free(option_string); +} + +static void +set_primary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA); +} + +static void +set_secondary_softbutton_areas_option(InputInfoPtr pInfo) +{ + set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA); +} + +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 */ + double accelFactor; /* 1/pixels */ + int fingerLow, fingerHigh; /* pressure */ + int emulateTwoFingerMinZ; /* pressure */ + int emulateTwoFingerMinW; /* width */ + 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; + int middle_button_timeout; + int grab_event_device = 0; + const char *source; + + /* 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; + 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 + 1; + + calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh); + + /* scaling based on defaults and a pressure of 256 */ + emulateTwoFingerMinZ = priv->minp + range * (282.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 + 1; + + /* 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 */ + tapButton1 = 1; + tapButton2 = 3; + tapButton3 = 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 = 0; /* Disabled by default so three-touch gestures work */ + + /* Enable vert edge scroll */ + vertEdgeScroll = 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->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->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad); /* Probed */ + if (pars->clickpad) + pars->has_secondary_buttons = xf86SetBoolOption(opts, + "HasSecondarySoftButtons", + pars->has_secondary_buttons); + pars->clickpad_ignore_motion_time = 100; /* ms */ + /* middle mouse button emulation on a clickpad? nah, you're joking */ + middle_button_timeout = pars->clickpad ? 0 : 75; + pars->emulate_mid_button_time = + xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout); + pars->emulate_twofinger_z = + xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ); + pars->emulate_twofinger_w = + xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW); + pars->scroll_dist_vert = + xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta); + pars->scroll_dist_horiz = + xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta); + 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->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON); + + 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->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE); + pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000); + pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2); + pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3); + 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->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE); + + pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4); + pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7); + pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor); + 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); + + /* Only grab the device by default if it's not coming from a config + backend. This way we avoid the device being added twice and sending + duplicate events. + */ + source = xf86CheckStrOption(opts, "_source", NULL); + if (source == NULL || strncmp(source, "server/", 7) != 0) + grab_event_device = TRUE; + pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device); + + pars->tap_and_drag_gesture = + xf86SetBoolOption(opts, "TapAndDragGesture", TRUE); + pars->resolution_horiz = + xf86SetIntOption(opts, "HorizResolution", horizResolution); + pars->resolution_vert = + xf86SetIntOption(opts, "VertResolution", vertResolution); + if (pars->resolution_horiz <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid X resolution, using 1 instead.\n"); + pars->resolution_horiz = 1; + } + if (pars->resolution_vert <= 0) { + xf86IDrvMsg(pInfo, X_ERROR, + "Invalid Y resolution, using 1 instead.\n"); + pars->resolution_vert = 1; + } + + /* Touchpad sampling rate is too low to detect all movements. + A user may lift one finger and put another one down within the same + EV_SYN or even between samplings so the driver doesn't notice at all. + + We limit the movement to 20 mm within one event, that is more than + recordings showed is needed (17mm on a T440). + */ + if (pars->resolution_horiz > 1 && + pars->resolution_vert > 1) + pars->maxDeltaMM = 20; + else { + /* on devices without resolution set the vector length to 0.25 of + the touchpad diagonal */ + pars->maxDeltaMM = diag * 0.25; + } + + + /* 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"); + } + + set_primary_softbutton_areas_option(pInfo); + if (pars->has_secondary_buttons) + set_secondary_softbutton_areas_option(pInfo); +} + +static double +SynapticsAccelerationProfile(DeviceIntPtr dev, + DeviceVelocityPtr vel, + double velocity, double thr, double 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; +} + +static int +SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + 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 */ + if (!SetDeviceAndProtocol(pInfo)) { + 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); + + set_default_parameters(pInfo); + +#ifndef NO_DRIVER_SCALING + CalculateScalingCoeffs(priv); +#endif + + + 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 (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + + return Success; + + SetupProc_fail: + SynapticsCloseFd(pInfo); + + if (priv->comm.buffer) + XisbFree(priv->comm.buffer); + 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); + if (priv && priv->scroll_events_mask) + valuator_mask_free(&priv->scroll_events_mask); + if (priv && priv->open_slots) + free(priv->open_slots); + 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 int +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 int +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)) + goto error; + + priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE); + if (!priv->comm.buffer) + goto error; + + xf86FlushInput(pInfo->fd); + + /* reinit the pad */ + if (!QueryHardware(pInfo)) + goto error; + + xf86AddEnabledDevice(pInfo); + dev->public.on = TRUE; + + return Success; + +error: + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + return !Success; +} + +static void +SynapticsReset(SynapticsPrivate * priv) +{ + int i; + + SynapticsResetHwState(priv->hwState); + SynapticsResetHwState(priv->local_hw_state); + SynapticsResetHwState(priv->comm.hwState); + + memset(priv->move_hist, 0, sizeof(priv->move_hist)); + priv->hyst_center_x = 0; + priv->hyst_center_y = 0; + memset(&priv->scroll, 0, sizeof(priv->scroll)); + priv->count_packet_finger = 0; + priv->finger_state = FS_UNTOUCHED; + priv->last_motion_millis = 0; + priv->clickpad_click_millis = 0; + priv->last_button_area = NO_BUTTON_AREA; + priv->tap_state = TS_START; + priv->tap_button = 0; + priv->tap_button_state = TBS_BUTTON_UP; + priv->moving_state = MS_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; + priv->circ_scroll_on = FALSE; + priv->circ_scroll_vert = FALSE; + priv->mid_emu_state = MBE_OFF; + priv->nextRepeat = 0; + priv->lastButtons = 0; + priv->prev_z = 0; + priv->prevFingers = 0; + priv->num_active_touches = 0; + + for (i = 0; i < priv->num_slots; i++) + priv->open_slots[i] = -1; +} + +static int +DeviceOff(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + Bool rc = Success; + + DBG(3, "Synaptics DeviceOff called\n"); + + if (pInfo->fd != -1) { + TimerCancel(priv->timer); + xf86RemoveEnabledDevice(pInfo); + SynapticsReset(priv); + + if (priv->proto_ops->DeviceOffHook && + !priv->proto_ops->DeviceOffHook(pInfo)) + rc = !Success; + if (priv->comm.buffer) { + XisbFree(priv->comm.buffer); + priv->comm.buffer = NULL; + } + SynapticsCloseFd(pInfo); + } + dev->public.on = FALSE; + return rc; +} + +static int +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(priv->touch_axes); + priv->touch_axes = NULL; + SynapticsHwStateFree(&priv->hwState); + SynapticsHwStateFree(&priv->local_hw_state); + SynapticsHwStateFree(&priv->comm.hwState); + return RetValue; +} + +static void +InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv) +{ + int i; + + memset(labels, 0, nlabels * sizeof(Atom)); + switch (nlabels) { + default: + case 4: + labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL); + case 3: + labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL); + case 2: + labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y); + case 1: + labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X); + break; + } + + for (i = 0; i < priv->num_mt_axes; i++) { + SynapticsTouchAxisRec *axis = &priv->touch_axes[i]; + int axnum = nlabels - priv->num_mt_axes + i; + + labels[axnum] = XIGetKnownProperty(axis->label); + } +} + +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; + } +} + +static void +DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + if (!priv->has_touch) + return; + + priv->num_slots = + priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES; + + priv->open_slots = malloc(priv->num_slots * sizeof(int)); + if (!priv->open_slots) { + xf86IDrvMsg(pInfo, X_ERROR, + "failed to allocate open touch slots array\n"); + priv->has_touch = 0; + priv->num_slots = 0; + } +} + +static int +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; + int num_axes = 2; + Atom btn_labels[SYN_MAX_BUTTONS] = { 0 }; + Atom *axes_labels; + DeviceVelocityPtr pVel; + + num_axes += 2; + + num_axes += priv->num_mt_axes; + + axes_labels = calloc(num_axes, sizeof(Atom)); + if (!axes_labels) { + xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n"); + return !Success; + } + + InitAxesLabels(axes_labels, num_axes, priv); + InitButtonLabels(btn_labels, SYN_MAX_BUTTONS); + + 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, + btn_labels, + SynapticsCtrl, + GetMotionHistorySize(), num_axes, axes_labels); + + /* + * setup dix acceleration to match legacy synaptics settings, and + * etablish a device-specific profile to do stuff like pressure-related + * acceleration. + */ + 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, "(accel) MaxSpeed is now %.2f\n", + priv->synpara.max_speed); + xf86IDrvMsg(pInfo, X_CONFIG, "(accel) 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); + } + + /* X valuator */ + if (priv->minx < priv->maxx && priv->synpara.resolution_detect) { + min = priv->minx; + max = priv->maxx; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max, + priv->resx * 1000, 0, priv->resx * 1000, + Relative); + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ + if (priv->miny < priv->maxy && priv->synpara.resolution_detect) { + min = priv->miny; + max = priv->maxy; + } + else { + min = 0; + max = -1; + } + + xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max, + priv->resy * 1000, 0, priv->resy * 1000, + Relative); + xf86InitValuatorDefaults(dev, 1); + + xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_horiz = 2; + xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0, + Relative); + priv->scroll_axis_vert = 3; + priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS); + if (!priv->scroll_events_mask) { + free(axes_labels); + return !Success; + } + + SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL, + priv->synpara.scroll_dist_horiz, 0); + SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL, + priv->synpara.scroll_dist_vert, 0); + + DeviceInitTouch(dev, axes_labels); + + free(axes_labels); + + priv->hwState = SynapticsHwStateAlloc(priv); + if (!priv->hwState) + goto fail; + + priv->local_hw_state = SynapticsHwStateAlloc(priv); + if (!priv->local_hw_state) + goto fail; + + priv->comm.hwState = SynapticsHwStateAlloc(priv); + + InitDeviceProperties(pInfo); + XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL); + + SynapticsReset(priv); + + return Success; + + fail: + free(priv->local_hw_state); + free(priv->hwState); + free(priv->open_slots); + 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 enum EdgeType +circular_edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 enum EdgeType +edge_detection(SynapticsPrivate * priv, int x, int y) +{ + enum EdgeType 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 a finger is down, then it must have started inside the active_area, + allow the motion to complete using the entire area */ + if (priv->finger_state >= FS_TOUCHED) + return 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 Bool +is_inside_button_area(SynapticsParameters * para, int which, int x, int y) +{ + Bool inside_area = TRUE; + + if (para->softbutton_areas[which][LEFT] == 0 && + para->softbutton_areas[which][RIGHT] == 0 && + para->softbutton_areas[which][TOP] == 0 && + para->softbutton_areas[which][BOTTOM] == 0) + return FALSE; + + if (para->softbutton_areas[which][LEFT] && + x < para->softbutton_areas[which][LEFT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][RIGHT] && + x > para->softbutton_areas[which][RIGHT]) + inside_area = FALSE; + else if (para->softbutton_areas[which][TOP] && + y < para->softbutton_areas[which][TOP]) + inside_area = FALSE; + else if (para->softbutton_areas[which][BOTTOM] && + y > para->softbutton_areas[which][BOTTOM]) + inside_area = FALSE; + + return inside_area; +} + +static Bool +is_inside_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y); +} + +static Bool +is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y) +{ + return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y); +} + +static Bool +is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset, + int x, int y) +{ + Bool inside_area = TRUE; + Bool right_valid, middle_valid; + int top, bottom; + + /* We don't have a left button area, so we only check the y axis */ + right_valid = para->softbutton_areas[offset][TOP] || + para->softbutton_areas[offset][BOTTOM]; + middle_valid = para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset + 1][BOTTOM]; + + if (!right_valid && !middle_valid) + return FALSE; + + /* Check both buttons are horizontally aligned */ + if (right_valid && middle_valid && ( + para->softbutton_areas[offset][TOP] != + para->softbutton_areas[offset + 1][TOP] || + para->softbutton_areas[offset][BOTTOM] != + para->softbutton_areas[offset + 1][BOTTOM])) + return FALSE; + + if (right_valid) { + top = para->softbutton_areas[offset][TOP]; + bottom = para->softbutton_areas[offset][BOTTOM]; + } + else { + top = para->softbutton_areas[offset + 1][TOP]; + bottom = para->softbutton_areas[offset + 1][BOTTOM]; + } + + if (top && y < top) + inside_area = FALSE; + else if (bottom && y > bottom) + inside_area = FALSE; + + return inside_area; +} + +static enum SoftButtonAreas +current_button_area(SynapticsParameters * para, int x, int y) +{ + if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y)) + return BOTTOM_BUTTON_AREA; + else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y)) + return TOP_BUTTON_AREA; + else + return NO_BUTTON_AREA; +} + +static CARD32 +timerFunc(OsTimerPtr timer, CARD32 now, pointer arg) +{ + InputInfoPtr pInfo = arg; + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + struct SynapticsHwState *hw = priv->local_hw_state; + int delay; +#if !HAVE_THREADED_INPUT + int sigstate = xf86BlockSIGIO(); +#else + input_lock(); +#endif + + priv->hwState->millis += now - priv->timer_time; + SynapticsCopyHwState(hw, priv->hwState); + SynapticsResetTouchHwState(hw, FALSE); + delay = HandleState(pInfo, hw, hw->millis, TRUE); + + priv->timer_time = now; + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + +#if !HAVE_THREADED_INPUT + xf86UnblockSIGIO(sigstate); +#else + input_unlock(); +#endif + + 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 = priv->local_hw_state; + int delay = 0; + Bool newDelay = FALSE; + + SynapticsResetTouchHwState(hw, FALSE); + + while (SynapticsGetHwState(pInfo, priv, hw)) { + /* Semi-mt device touch slots do not track touches. When there is a + * change in the number of touches, we must disregard the temporary + * motion changes. */ + if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) { + hw->cumulative_dx = priv->hwState->cumulative_dx; + hw->cumulative_dy = priv->hwState->cumulative_dy; + } + + /* timer may cause actual events to lag behind (#48777) */ + if (priv->hwState->millis > hw->millis) + hw->millis = priv->hwState->millis; + + SynapticsCopyHwState(priv->hwState, hw); + delay = HandleState(pInfo, hw, hw->millis, FALSE); + newDelay = TRUE; + } + + if (newDelay) { + priv->timer_time = GetTimeInMillis(); + priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo); + } +} + +static int +HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw, + CARD32 now, int *delay) +{ + SynapticsParameters *para = &priv->synpara; + Bool done = FALSE; + int timeleft; + int mid = 0; + + if (para->emulate_mid_button_time <= 0) + return mid; + + while (!done) { + switch (priv->mid_emu_state) { + case MBE_LEFT_CLICK: + case MBE_RIGHT_CLICK: + case MBE_OFF: + priv->button_delay_millis = now; + 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, now); + 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, now); + 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_low) + return FS_UNTOUCHED; + + if (priv->finger_state == FS_BLOCKED) + return FS_BLOCKED; + + if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED) + finger = FS_TOUCHED; + else + finger = priv->finger_state; + + if (!para->palm_detect) + return finger; + + /* palm detection */ + + if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width)) + return FS_BLOCKED; + + if (priv->has_mt_palm_detect) + return finger; + + if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED) + priv->avg_width = 0; + else + priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2; + + if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) { + 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; + + return finger; +} + +static void +SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge) +{ + enum TapEvent tap; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) { + priv->tap_button = 0; + return; + } + + switch (priv->tap_max_fingers) { + case 1: + 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; + default: + priv->tap_button = 0; + return; + } + + 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, CARD32 millis) +{ + DBG(3, "SetTapState - %d -> %d (millis:%u)\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: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_2B: + priv->tap_button_state = TBS_BUTTON_UP; + break; + case TS_3: + priv->tap_button_state = TBS_BUTTON_DOWN; + break; + case TS_SINGLETAP: + 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, + CARD32 millis) +{ + DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n", + priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y, + millis); + + 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, + CARD32 now, enum FingerState finger, + Bool inside_active_area) +{ + SynapticsParameters *para = &priv->synpara; + Bool touch, release, is_timeout, move, press; + int timeleft, timeout; + enum EdgeType edge; + int delay = 1000000000; + + if (para->touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) + return delay; + + touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED; + release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED; + move = (finger >= FS_TOUCHED && + (priv->tap_max_fingers <= + ((priv->horiz_scroll_twofinger_on || + priv->vert_scroll_twofinger_on) ? 2 : 1)) && + (priv->prevFingers == hw->numFingers && + ((abs(hw->x - priv->touch_on.x) >= para->tap_move) || + (abs(hw->y - priv->touch_on.y) >= para->tap_move)))); + press = (hw->left || hw->right || hw->middle); + + if (touch) { + priv->touch_on.x = hw->x; + priv->touch_on.y = hw->y; + priv->touch_on.millis = now; + } + else if (release) { + priv->touch_on.millis = now; + } + 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, now); + is_timeout = timeleft <= 0; + + restart: + switch (priv->tap_state) { + case TS_START: + if (touch) + SetTapState(priv, TS_1, now); + break; + case TS_1: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_MOVE, now); + goto restart; + } + else if (is_timeout) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_MOVE, now); + 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, now); + } + break; + case TS_MOVE: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_2A: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_2B: + if (touch) + SetTapState(priv, TS_3, now); + else if (is_timeout) + SetTapState(priv, TS_SINGLETAP, now); + break; + case TS_SINGLETAP: + if (touch) + SetTapState(priv, TS_1, now); + else if (is_timeout) + SetTapState(priv, TS_START, now); + break; + case TS_3: + if (move) { + if (para->tap_and_drag_gesture) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (is_timeout) { + if (para->tap_and_drag_gesture) { + if (finger == FS_TOUCHED) { + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + } + SetTapState(priv, TS_DRAG, now); + } + else { + SetTapState(priv, TS_1, now); + } + goto restart; + } + else if (release) { + SetTapState(priv, TS_2B, now); + } + break; + case TS_DRAG: + if (para->clickpad && press) { + SetTapState(priv, TS_CLICKPAD_MOVE, now); + goto restart; + } + if (move) + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (release) { + SetMovingState(priv, MS_FALSE, now); + if (para->locked_drags) { + SetTapState(priv, TS_4, now); + } + else { + SetTapState(priv, TS_START, now); + } + } + break; + case TS_4: + if (is_timeout) { + SetTapState(priv, TS_START, now); + goto restart; + } + if (touch) + SetTapState(priv, TS_5, now); + break; + case TS_5: + if (is_timeout || move) { + SetTapState(priv, TS_DRAG, now); + goto restart; + } + else if (release) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_START, now); + } + break; + case TS_CLICKPAD_MOVE: + /* Disable scrolling once a button is pressed on a clickpad */ + priv->vert_scroll_edge_on = FALSE; + priv->horiz_scroll_edge_on = FALSE; + priv->vert_scroll_twofinger_on = FALSE; + priv->horiz_scroll_twofinger_on = FALSE; + + /* Assume one touch is only for holding the clickpad button down */ + if (hw->numFingers > 1) + hw->numFingers--; + SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now); + if (!press) { + SetMovingState(priv, MS_FALSE, now); + SetTapState(priv, TS_MOVE, now); + priv->count_packet_finger = 0; + } + break; + } + + timeout = GetTimeOut(priv); + if (timeout >= 0) { + timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now); + delay = clamp(timeleft, 1, delay); + } + return delay; +} + +#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)]) +#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e)) + +static void +store_history(SynapticsPrivate * priv, int x, int y, CARD32 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; + if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY) + priv->count_packet_finger++; +} + +/* + * 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(SynapticsPrivate *priv, const struct SynapticsHwState *hw, + enum EdgeType edge, double *dx, double *dy) +{ + *dx = hw->x - HIST(0).x; + *dy = hw->y - HIST(0).y; +} + +/* Vector length, but not sqrt'ed, we only need it for comparison */ +static inline double +vlenpow2(double x, double y) +{ + return x * x + y * y; +} + +/** + * Compute relative motion ('deltas') including edge motion. + */ +static int +ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw, + enum EdgeType edge, int *dxP, int *dyP, Bool inside_area) +{ + enum MovingState moving_state; + double dx, dy; + double vlen; + 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: + moving_state = MS_TOUCHPAD_RELATIVE; + break; + default: + break; + } + } + + if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED || + 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 || + (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) { + /* reset packet counter. */ + priv->count_packet_finger = 0; + goto out; + } + + /* To create the illusion of fluid motion, call back at roughly the report + * rate, even in the absence of new hardware events; see comment above + * POLL_MS declaration. */ + delay = MIN(delay, POLL_MS); + + if (priv->count_packet_finger <= 1) + goto out; /* skip the lot */ + + if (moving_state == MS_TOUCHPAD_RELATIVE) + get_delta(priv, hw, edge, &dx, &dy); + + out: + priv->prevFingers = hw->numFingers; + + vlen = vlenpow2(dx/priv->synpara.resolution_horiz, + dy/priv->synpara.resolution_vert); + + if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) { + dx = 0; + dy = 0; + } + + *dxP = dx; + *dyP = dy; + + return delay; +} + +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->scroll.coast_delta_y = 0.0; + priv->scroll.coast_delta_x = 0.0; + + if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) { + double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0; + + if (vert && !circ) { + double dy = + estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y); + if (pkt_time > 0) { + double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y); + } + } + } + if (horiz && !circ) { + double dx = + estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x); + if (pkt_time > 0) { + double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x); + } + } + } + if (circ) { + double da = estimate_delta_circ(priv); + + if (pkt_time > 0) { + double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time; + + if (fabs(scrolls_per_sec) >= para->coasting_speed) { + if (vert) { + priv->scroll.coast_speed_y = scrolls_per_sec; + priv->scroll.coast_delta_y = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + else if (horiz) { + priv->scroll.coast_speed_x = scrolls_per_sec; + priv->scroll.coast_delta_x = + diffa(priv->scroll.last_a, + angle(priv, hw->x, hw->y)); + } + } + } + } + } + priv->scroll.packets_this_scroll = 0; +} + +static void +stop_coasting(SynapticsPrivate * priv) +{ + priv->scroll.coast_speed_x = 0; + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; +} + +static int +HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw, + enum EdgeType edge, Bool finger) +{ + SynapticsParameters *para = &priv->synpara; + int delay = 1000000000; + + if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF || + priv->synpara.touchpad_off == TOUCHPAD_OFF || + priv->finger_state == FS_BLOCKED) { + 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 == FS_UNTOUCHED) { + stop_coasting(priv); + priv->scroll.delta_y = 0; + priv->scroll.delta_x = 0; + 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.last_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)) { + stop_coasting(priv); + priv->vert_scroll_twofinger_on = TRUE; + priv->vert_scroll_edge_on = FALSE; + priv->scroll.last_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)) { + stop_coasting(priv); + priv->horiz_scroll_twofinger_on = TRUE; + priv->horiz_scroll_edge_on = FALSE; + priv->scroll.last_x = hw->x; + DBG(7, "horiz two-finger scroll detected\n"); + } + } + } + if (finger && priv->finger_state == FS_UNTOUCHED) { + 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.last_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.last_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->scroll.coast_speed_x || priv->scroll.coast_speed_y)) { + 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->scroll.coast_speed_y == 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.last_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->scroll.coast_speed_x == 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.last_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.packets_this_scroll++; + } + + if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) { + /* + = down, - = up */ + if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) { + priv->scroll.delta_y += (hw->y - priv->scroll.last_y); + priv->scroll.last_y = hw->y; + } + } + if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) { + /* + = right, - = left */ + if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) { + priv->scroll.delta_x += (hw->x - priv->scroll.last_x); + priv->scroll.last_x = hw->x; + } + } + if (priv->circ_scroll_on) { + /* + = counter clockwise, - = clockwise */ + double delta = para->scroll_dist_circ; + double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y)); + + if (delta >= 0.005 && diff != 0.0) { + if (priv->circ_scroll_vert) + priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert; + else + priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz; + priv->scroll.last_a = angle(priv, hw->x, hw->y); + } + } + + if (priv->scroll.coast_speed_y) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddy = para->coasting_friction * dtime; + + priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_y) < ddy) { + priv->scroll.coast_speed_y = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_y += + (priv->scroll.coast_speed_y < 0 ? ddy : -ddy); + } + } + + if (priv->scroll.coast_speed_x) { + double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0; + double ddx = para->coasting_friction * dtime; + priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz); + delay = MIN(delay, POLL_MS); + if (abs(priv->scroll.coast_speed_x) < ddx) { + priv->scroll.coast_speed_x = 0; + priv->scroll.packets_this_scroll = 0; + } + else { + priv->scroll.coast_speed_x += + (priv->scroll.coast_speed_x < 0 ? ddx : -ddx); + } + } + + return delay; +} + +/** + * Check if any 2+ fingers are close enough together to assume this is a + * ClickFinger action. + */ +static int +clickpad_guess_clickfingers(SynapticsPrivate * priv, + struct SynapticsHwState *hw) +{ + int nfingers = 0; + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + +#ifdef BUG_RETURN_VAL + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); +#endif + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; + + if (hw->slot_state[i] == SLOTSTATE_EMPTY || + hw->slot_state[i] == SLOTSTATE_CLOSE) + continue; + + f1 = hw->mt_mask[i]; + + for (j = i + 1; j < hw->num_mt_mask; j++) { + ValuatorMask *f2; + double x1, x2, y1, y2; + + if (hw->slot_state[j] == SLOTSTATE_EMPTY || + hw->slot_state[j] == SLOTSTATE_CLOSE) + continue; + + f2 = hw->mt_mask[j]; + + x1 = valuator_mask_get_double(f1, 0); + y1 = valuator_mask_get_double(f1, 1); + + x2 = valuator_mask_get_double(f2, 0); + y2 = valuator_mask_get_double(f2, 1); + + /* FIXME: fingers closer together than 30% of touchpad width, but + * really, this should be dependent on the touchpad size. Also, + * you'll need to find a touchpad that doesn't lie about it's + * size. Good luck. */ + if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 && + abs(y1 - y2) < (priv->maxy - priv->miny) * .3) { + close_point |= (1 << j); + close_point |= (1 << i); + } + } + } + + while (close_point > 0) { + nfingers += close_point & 0x1; + close_point >>= 1; + } + + /* Some trackpads touchpad only track two touchpoints but announce + * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens + * the user likely intents to do a 3 finger click, so handle it as such. + */ + if (hw->numFingers >= 3 && hw->num_mt_mask < 3) + nfingers = 3; + + return nfingers; +} + +static void +handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw) +{ + SynapticsParameters *para = &priv->synpara; + int action = 0; + int nfingers = hw->numFingers; + + /* if this is a clickpad, clickfinger handling is: + * one finger down: no action, this is a normal click + * two fingers down: F2_CLICK + * three fingers down: F3_CLICK + */ + + if (para->clickpad) + nfingers = clickpad_guess_clickfingers(priv, hw); + + switch (nfingers) { + 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 | BTN_EMULATED_FLAG; + break; + case 2: + hw->left = 0; + hw->middle = 1 | BTN_EMULATED_FLAG; + break; + case 3: + hw->left = 0; + hw->right = 1 | BTN_EMULATED_FLAG; + break; + } +} + +/* 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, + CARD32 now, 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, now, delay); + + /* If this is a clickpad and the user clicks in a soft button area, press + * the soft button instead. */ + if (para->clickpad) { + /* hw->left is down, but no other buttons were already down */ + if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) { + /* If the finger down event is delayed, the x and y + * coordinates are stale so we delay processing the click */ + if (hw->z < para->finger_low) { + hw->left = 0; + goto out; + } + if (is_inside_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->right = 1; + } + else if (is_inside_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) { + hw->left = 0; + hw->middle = 1; + } + priv->clickpad_click_millis = now; + } + else if (hw->left) { + hw->left = (priv->lastButtons & 1) ? 1 : 0; + hw->middle = (priv->lastButtons & 2) ? 1 : 0; + hw->right = (priv->lastButtons & 4) ? 1 : 0; + } + } + + /* Fingers emulate other buttons. ClickFinger can only be + triggered on transition, when left is pressed + */ + if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1) + handle_clickfinger(priv, hw); + +out: + /* 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) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + + valuator_mask_zero(priv->scroll_events_mask); + + if (priv->scroll.delta_y != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_vert, priv->scroll.delta_y); + priv->scroll.delta_y = 0; + } + if (priv->scroll.delta_x != 0.0) { + valuator_mask_set_double(priv->scroll_events_mask, + priv->scroll_axis_horiz, priv->scroll.delta_x); + priv->scroll.delta_x = 0; + } + if (valuator_mask_num_valuators(priv->scroll_events_mask)) + xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask); +} + +static inline int +repeat_scrollbuttons(const InputInfoPtr pInfo, + const struct SynapticsHwState *hw, + int buttons, CARD32 now, int delay) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + int repeat_delay, timeleft; + int rep_buttons = 0; + + if (para->updown_button_repeat) + rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1)); + if (para->leftright_button_repeat) + rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1)); + + /* 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 = now + repeat_delay * 2; + } + } + else { + priv->repeatButtons = 0; + priv->nextRepeat = 0; + } + + if (priv->repeatButtons) { + timeleft = TIME_DIFF(priv->nextRepeat, now); + 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)); + if (id == 4) + priv->scroll.delta_y -= para->scroll_dist_vert; + else if (id == 5) + priv->scroll.delta_y += para->scroll_dist_vert; + else if (id == 6) + priv->scroll.delta_x -= para->scroll_dist_horiz; + else if (id == 7) + priv->scroll.delta_x += para->scroll_dist_horiz; + } + + priv->nextRepeat = now + repeat_delay; + delay = MIN(delay, repeat_delay); + } + } + + return delay; +} + +/* Update the open slots and number of active touches */ +static void +UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private; + int i; + + for (i = 0; i < hw->num_mt_mask; i++) { + if (hw->slot_state[i] == SLOTSTATE_OPEN) { + priv->open_slots[priv->num_active_touches] = i; + priv->num_active_touches++; + BUG_WARN(priv->num_active_touches > priv->num_slots); + } + else if (hw->slot_state[i] == SLOTSTATE_CLOSE) { + Bool found = FALSE; + int j; + + for (j = 0; j < priv->num_active_touches - 1; j++) { + if (priv->open_slots[j] == i) + found = TRUE; + + if (found) + priv->open_slots[j] = priv->open_slots[j + 1]; + } + + BUG_WARN(priv->num_active_touches == 0); + if (priv->num_active_touches > 0) + priv->num_active_touches--; + } + } + + SynapticsResetTouchHwState(hw, FALSE); +} + +static void +filter_jitter(SynapticsPrivate * priv, int *x, int *y) +{ + SynapticsParameters *para = &priv->synpara; + + priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x); + priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y); + *x = priv->hyst_center_x; + *y = priv->hyst_center_y; +} + +static void +reset_hw_state(struct SynapticsHwState *hw) +{ + hw->x = 0; + hw->y = 0; + hw->z = 0; + hw->numFingers = 0; + hw->fingerWidth = 0; +} + +/* + * 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. + * + * from_timer denotes if HandleState was triggered from a timer (e.g. to + * generate fake motion events, or for the tap-to-click state machine), rather + * than from having received a motion event. + */ +static int +HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now, + Bool from_timer) +{ + SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private); + SynapticsParameters *para = &priv->synpara; + enum FingerState finger = FS_UNTOUCHED; + int dx = 0, dy = 0, buttons, id; + enum EdgeType edge = NO_EDGE; + int change; + int double_click = FALSE; + int delay = 1000000000; + int timeleft; + Bool inside_active_area; + Bool using_cumulative_coords = FALSE; + Bool ignore_motion; + + /* We need both and x/y, the driver can't handle just one of the two + * yet. But since it's possible to hit a phys button on non-clickpads + * without ever getting motion data first, we must continue with 0/0 for + * that case. */ + if (hw->x == INT_MIN || hw->y == INT_MAX) { + if (para->clickpad) + return delay; + else if (hw->left || hw->right || hw->middle) { + hw->x = (hw->x == INT_MIN) ? 0 : hw->x; + hw->y = (hw->y == INT_MIN) ? 0 : hw->y; + } + } + + /* If a physical button is pressed on a clickpad or a two-finger scrolling + * is ongoing, use cumulative relative touch movements for motion */ + if (para->clickpad && + ((priv->lastButtons & 7) || + (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) && + priv->last_button_area != TOP_BUTTON_AREA) { + hw->x = hw->cumulative_dx; + hw->y = hw->cumulative_dy; + using_cumulative_coords = TRUE; + } + + /* 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. */ + filter_jitter(priv, &hw->x, &hw->y); + + inside_active_area = is_inside_active_area(priv, hw->x, hw->y); + + /* Ignore motion *starting* inside softbuttonareas */ + if (priv->finger_state < FS_TOUCHED) + priv->last_button_area = current_button_area(para, hw->x, hw->y); + /* If we already have a finger down, clear last_button_area if it goes + outside of the softbuttonareas */ + else if (priv->last_button_area != NO_BUTTON_AREA && + current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA) + priv->last_button_area = NO_BUTTON_AREA; + + ignore_motion = para->touchpad_off == TOUCHPAD_OFF || + (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA); + + /* these two just update hw->left, right, etc. */ + update_hw_button_state(pInfo, hw, now, &delay); + if (priv->has_scrollbuttons) + double_click = adjust_state_from_scrollbuttons(pInfo, hw); + + /* Ignore motion the first X ms after a clickpad click */ + if (priv->clickpad_click_millis) { + if(TIME_DIFF(priv->clickpad_click_millis + + para->clickpad_ignore_motion_time, now) > 0) + ignore_motion = TRUE; + else + priv->clickpad_click_millis = 0; + } + + /* 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) + reset_hw_state(hw); + + /* no edge or finger detection outside of area */ + if (inside_active_area) { + edge = edge_detection(priv, hw->x, hw->y); + if (!from_timer) + finger = SynapticsDetectFinger(priv, hw); + else + finger = priv->finger_state; + } + + /* 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, now, 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 >= FS_TOUCHED)); + 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. + */ +#ifndef NO_DRIVER_SCALING + ScaleCoordinates(priv, hw); +#endif + } + + dx = dy = 0; + + 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 && priv->tap_button_state == TBS_BUTTON_DOWN) + buttons |= 1 << (priv->tap_button - 1); + + /* Post events */ + if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion) + 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); + } + + if (priv->has_scrollbuttons) + delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay); + + /* Process scroll events only if coordinates are + * in the Synaptics Area + */ + if (inside_active_area && + (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) { + post_scroll_events(pInfo); + priv->scroll.last_millis = hw->millis; + } + + if (double_click) { + post_button_click(pInfo, 1); + post_button_click(pInfo, 1); + } + + UpdateTouchState(pInfo, hw); + + /* 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) +{ + DBG(3, "SwitchMode called\n"); + + return XI_BadMode; +} + +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; +} + +#ifndef NO_DRIVER_SCALING +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; + } +} +#endif --- xserver-xorg-input-synaptics-1.9.1.orig/.pc/applied-patches +++ xserver-xorg-input-synaptics-1.9.1/.pc/applied-patches @@ -0,0 +1,11 @@ +02-do-not-use-synaptics-for-keyboards.patch +03-bug-return-val.patch +101_resolution_detect_option.patch +103_enable_cornertapping.patch +104_always_enable_tapping.patch +106_always_enable_vert_edge_scroll.patch +115_evdev_only.patch +118_quell_error_msg.patch +124_syndaemon_events.patch +128_disable_three_click_action.patch +129_disable_three_touch_tap.patch --- xserver-xorg-input-synaptics-1.9.1.orig/conf/11-x11-synaptics.fdi +++ xserver-xorg-input-synaptics-1.9.1/conf/11-x11-synaptics.fdi @@ -9,6 +9,7 @@ + synaptics @@ -35,6 +36,7 @@ 0 0 0 0 0 0 0 0 + --- xserver-xorg-input-synaptics-1.9.1.orig/conf/70-synaptics.conf +++ xserver-xorg-input-synaptics-1.9.1/conf/70-synaptics.conf @@ -14,7 +14,7 @@ # This option is recommend on all Linux systems using evdev, but cannot be # enabled by default. See the following link for details: # http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html -# MatchDevicePath "/dev/input/event*" + MatchDevicePath "/dev/input/event*" EndSection Section "InputClass" --- xserver-xorg-input-synaptics-1.9.1.orig/debian/NEWS +++ xserver-xorg-input-synaptics-1.9.1/debian/NEWS @@ -0,0 +1,29 @@ +xserver-xorg-input-synaptics (1.2.0-1) unstable; urgency=low + + * All the shared memory code has been removed from synclient and syndaemon. + The SHMConfig option is still available but the only use is for monitoring + the hardware state data. + If you have any script that changes the synaptics driver configuration + make sure that you don't use the -s option to synclient. + + -- Mattia Dongili Fri, 20 Nov 2009 00:32:58 +0900 + +xserver-xorg-input-synaptics (1.1.1~git20090510-1) unstable; urgency=low + + * Tapping has been disabled by default on many touchpads by upstream, see + the "NOTES" section in the synaptics(4) manpage for a short overview + about how defaults are calculated. + If you want to re-enable it, you can do so from within the X + environment by running the following commands in a terminal: + + $ synclient TapButton1=1 + $ synclient TapButton2=2 + $ synclient TapButton3=3 + + Note that the configuration will not be permanently modified, to do + so assign the above option values in your xorg.conf (see the synaptics(4) + manpage or the documents in /usr/share/doc/xserver-xorg-input-synaptics + for additional details). Another alternative is to use desktop + environment specific tools like gpointing-device-settings or touchfreeze. + + -- David Nusinow Sat, 11 Apr 2009 16:23:11 -0400 --- xserver-xorg-input-synaptics-1.9.1.orig/debian/README.source +++ xserver-xorg-input-synaptics-1.9.1/debian/README.source @@ -0,0 +1,49 @@ +------------------------------------------------------ +Quick Guide To Patching This Package For The Impatient +------------------------------------------------------ + +1. Make sure you have quilt installed +2. Unpack the package as usual with "dpkg-source -x" +3. Run the "patch" target in debian/rules +4. Create a new patch with "quilt new" (see quilt(1)) +5. Edit all the files you want to include in the patch with "quilt edit" + (see quilt(1)). +6. Write the patch with "quilt refresh" (see quilt(1)) +7. Run the "clean" target in debian/rules + +Alternatively, instead of using quilt directly, you can drop the patch in to +debian/patches and add the name of the patch to debian/patches/series. + +------------------------------------ +Guide To The X Strike Force Packages +------------------------------------ + +The X Strike Force team maintains X packages in git repositories on +git.debian.org in the pkg-xorg subdirectory. Most upstream packages +are actually maintained in git repositories as well, so they often +just need to be pulled into git.debian.org in a "upstream-*" branch. +Otherwise, the upstream sources are manually installed in the Debian +git repository. + +The .orig.tar.gz upstream source file could be generated using this +"upstream-*" branch in the Debian git repository but it is actually +copied from upstream tarballs directly. + +Due to X.org being highly modular, packaging all X.org applications +as their own independent packages would have created too many Debian +packages. For this reason, some X.org applications have been grouped +into larger packages: xutils, xutils-dev, x11-apps, x11-session-utils, +x11-utils, x11-xfs-utils, x11-xkb-utils, x11-xserver-utils. +Most packages, including the X.org server itself and all libraries +and drivers are, however maintained independently. + +The Debian packaging is added by creating the "debian-*" git branch +which contains the aforementioned "upstream-*" branch plus the debian/ +repository files. +When a patch has to be applied to the Debian package, two solutions +are involved: +* If the patch is available in one of the upstream branches, it + may be git'cherry-picked into the Debian repository. In this + case, it appears directly in the .diff.gz. +* Otherwise, the patch is added to debian/patches/ which is managed + with quilt as documented in /usr/share/doc/quilt/README.source. --- xserver-xorg-input-synaptics-1.9.1.orig/debian/changelog +++ xserver-xorg-input-synaptics-1.9.1/debian/changelog @@ -0,0 +1,1987 @@ +xserver-xorg-input-synaptics (1.9.1-1ubuntu1) cosmic; urgency=medium + + * Resynchronize on Debian + + -- Sebastien Bacher Fri, 24 Aug 2018 13:29:09 +0200 + +xserver-xorg-input-synaptics (1.9.1-1) unstable; urgency=medium + + * New upstream release. + * control: Update VCS urls. + * Use debhelper 10. + * control: Bump policy to 4.2.0, drop --disable-silent-rules. + + -- Timo Aaltonen Thu, 23 Aug 2018 16:18:23 +0300 + +xserver-xorg-input-synaptics (1.9.0-1ubuntu1) zesty; urgency=medium + + * Merge from Debian. + + -- Timo Aaltonen Tue, 07 Mar 2017 12:30:57 +0200 + +xserver-xorg-input-synaptics (1.9.0-1) unstable; urgency=medium + + [ Andreas Boll ] + * New upstream release. + * Use https URL in debian/copyright. + * Bump Standards-Version to 3.9.8, no changes needed. + + [ Julien Cristau ] + * Use https URL in debian/watch. + * Use https for Vcs-* control fields. + + -- Timo Aaltonen Fri, 18 Nov 2016 13:10:43 +0200 + +xserver-xorg-input-synaptics (1.8.3-2) unstable; urgency=medium + + * Cherry-pick upstream commit 59e5db, which renames 50-synaptics.conf + to 70-synaptics.conf. This means the synaptic driver will be used + instead of the libinput driver when both are installed. + * Drop dbg package in favor of the dbgsym. + + -- Emilio Pozuelo Monfort Thu, 26 May 2016 18:32:56 +0200 + +xserver-xorg-input-synaptics (1.8.3-1ubuntu1) yakkety; urgency=medium + + * Merge with Debian, remaining Ubuntu changes: + * debian/gbp.conf: + - Point at Ubuntu branch. + * debian/local/51-synaptics-quirks.conf: + - Add quirks for Dell Inspiron, HP Mininote. + * debian/local/66-xorg-synaptics.rules: + - Udev rules to tag Dell Inspiron, HP Mininote for quirks. + * debian/xserver-xorg-input-synaptics.install: + - Install quirks + * debian/patches/101_resolution_detect_option.patch: + - Provide an option to prevent synaptics from communicating the + touchpad size to the xserver. + * debian/patches/103_enable_cornertapping.patch: + - Enable right/middle clicks by tapping in the bottom- and top-right + corners. + * debian/patches/104_always_enable_tapping.patch: + - Enable tapping even when a physical left button is available. + * debian/patches/106_always_enable_vert_edge_scroll.patch: + - Enable vertical edge tapping by default even if we enable two-finger + scrolling as well. + * debian/patches/115_evdev_only.patch: + - Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback. + * debian/patches/118_quell_error_msg.patch: + - Don't print error "Unable to find a synaptics device" when used on system + that doesn't have a touchpad anyway. + * debian/patches/124_syndaemon_events.patch: + - Drain spurious events from XRecord connection as a work around for a buggy + X server. + * debian/patches/128_disable_three_click_action.patch: + - Disable three touch click in favor of uTouch gestures. + * debian/patches/129_disable_three_touch_tap.patch: + - Disable three touch tap in favor of uTouch gestures. + + -- Robert Ancell Wed, 25 May 2016 11:32:52 +1200 + +xserver-xorg-input-synaptics (1.8.3-1) unstable; urgency=medium + + * New upstream release. + + -- Julien Cristau Sun, 24 Jan 2016 14:31:57 +0100 + +xserver-xorg-input-synaptics (1.8.2-1ubuntu3) xenial; urgency=medium + + * Really rebuild against new xserver. + + -- Timo Aaltonen Thu, 03 Mar 2016 15:36:59 +0200 + +xserver-xorg-input-synaptics (1.8.2-1ubuntu2) xenial; urgency=medium + + * Rebuild against new xserver ABI. + + -- Timo Aaltonen Mon, 18 Jan 2016 16:00:22 +0200 + +xserver-xorg-input-synaptics (1.8.2-1ubuntu1) wily; urgency=medium + + * Merge with Debian, remaining Ubuntu changes: + * debian/gbp.conf: + - Point at Ubuntu branch. + * debian/local/51-synaptics-quirks.conf: + - Add quirks for Dell Inspiron, HP Mininote. + * debian/local/66-xorg-synaptics.rules: + - Udev rules to tag Dell Inspiron, HP Mininote for quirks. + * debian/xserver-xorg-input-synaptics.install: + - Install quirks + * debian/patches/101_resolution_detect_option.patch: + - Provide an option to prevent synaptics from communicating the + touchpad size to the xserver. + * debian/patches/103_enable_cornertapping.patch: + - Enable right/middle clicks by tapping in the bottom- and top-right + corners. + * debian/patches/104_always_enable_tapping.patch: + - Enable tapping even when a physical left button is available. + * debian/patches/106_always_enable_vert_edge_scroll.patch: + - Enable vertical edge tapping by default even if we enable two-finger + scrolling as well. + * debian/patches/115_evdev_only.patch: + - Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback. + * debian/patches/118_quell_error_msg.patch: + - Don't print error "Unable to find a synaptics device" when used on system + that doesn't have a touchpad anyway. + * debian/patches/124_syndaemon_events.patch: + - Drain spurious events from XRecord connection as a work around for a buggy + X server. + * debian/patches/128_disable_three_click_action.patch: + - Disable three touch click in favor of uTouch gestures. + * debian/patches/129_disable_three_touch_tap.patch: + - Disable three touch tap in favor of uTouch gestures. + + -- Robert Ancell Tue, 14 Jul 2015 16:30:22 +1200 + +xserver-xorg-input-synaptics (1.8.2-1) unstable; urgency=medium + + * Let uscan verify tarball signatures. + * New upstream release. + * Bump debhelper compat level to 9. + + -- Julien Cristau Sun, 03 May 2015 10:17:33 +0200 + +xserver-xorg-input-synaptics (1.8.1-1ubuntu1) utopic; urgency=medium + + * Merge from released sid. + * Drop postinst file, only needed for updating from synaptics <= 1.4.1. + * Remaining changes: + - A bunch of behavioral patches. + - Install some behavioral quirks. + + -- Maarten Lankhorst Mon, 22 Sep 2014 09:44:47 +0200 + +xserver-xorg-input-synaptics (1.8.1-1) unstable; urgency=medium + + * New upstream release + - eventcomm: ensure we're on the same clock as the server (closes: #760779) + - Limit the movement to 20 mm per event (closes: #672601) + + [ Maarten Lankhorst ] + * Drop libmtdev-dev, unused in 1.8.0. + * Add dependency on libxtst-dev again to enable support for XRecord. + (closes: #759345) + + -- maximilian attems Wed, 16 Jul 2014 22:04:44 +0200 + +xserver-xorg-input-synaptics (1.8.0-1) unstable; urgency=medium + + [ Julien Cristau ] + * Remove Cyril from Uploaders. + + -- maximilian attems Sat, 12 Jul 2014 19:20:04 +0200 + +xserver-xorg-input-synaptics (1.8.0-1~exp2ubuntu3) utopic; urgency=medium + + * Rebuild for new xorg-server abi. + + -- Maarten Lankhorst Wed, 10 Sep 2014 16:29:27 +0200 + +xserver-xorg-input-synaptics (1.8.0-1~exp2ubuntu2) utopic; urgency=medium + + * Remerge from debian-experimental to pick up libxtst-dev. + - Fixes touchpad sometimes not disabling by allowing + 'syndaemon -R' to use XRecord correctly. (LP: #1323006) + + -- Maarten Lankhorst Tue, 15 Jul 2014 15:07:15 +0200 + +xserver-xorg-input-synaptics (1.8.0-1~exp2ubuntu1) utopic; urgency=medium + + * Merge from debian-experimental. + + -- Maarten Lankhorst Wed, 02 Jul 2014 16:36:40 +0200 + +xserver-xorg-input-synaptics (1.8.0-1~exp2) experimental; urgency=medium + + * Mark libevdev-dev build dependency [linux-any]. (closes: #749286) + Thanks to Pino Toscano . + + -- maximilian attems Wed, 28 May 2014 05:46:25 +0200 + +xserver-xorg-input-synaptics (1.8.0-1~exp1) experimental; urgency=medium + + [ Maarten Lankhorst ] + * Drop libxtst-dev conflicts, add libevdev-dev. + + [ maximilian attems ] + * New upstream release (closes: #743488). + - Revert "Purge scrollbuttons (repeat)" (closes: #722006). + - support newer Thinkpads (closes: #705807) + * add myself to Uploaders. + + -- maximilian attems Fri, 23 May 2014 18:27:28 +0200 + +xserver-xorg-input-synaptics (1.7.99.1-0ubuntu1) UNRELEASED; urgency=medium + + * Merge from unreleased debian-experimental. (LP: #1294515) + + -- Maarten Lankhorst Wed, 26 Mar 2014 10:02:42 +0100 + +xserver-xorg-input-synaptics (1.7.3-1ubuntu1) trusty; urgency=low + + * Merge from debian-unstable. + * Drop long disabled patches from the archive. + - 105_correct_multifinger_click.patch + - 116_xi2_1.patch + - 117_gestures.patch + - 119_active_area_touches.patch + - 120_active_touches_num_fingers.patch + - 121_semi-mt_num_fingers.patch + - 123_order_ProcessTouch_for_numFingers.patch + * Fixes xserver 1.15 compatibility. (LP: #1276875) + + -- Robert Hooker Thu, 06 Feb 2014 11:11:26 -0500 + +xserver-xorg-input-synaptics (1.7.3-1) unstable; urgency=medium + + [ Michele Cane ] + * New upstream release. + + -- Julien Cristau Sun, 19 Jan 2014 11:46:16 +0100 + +xserver-xorg-input-synaptics (1.7.2-1) unstable; urgency=medium + + [ Michele Cane ] + * New upstream release. + * Bump Standard-Version to 3.9.5, no changes needed. + * Refresh patches. + + [ Julien Cristau ] + * Disable silent build rules and silent test failures. + + -- Julien Cristau Sun, 15 Dec 2013 19:07:48 +0100 + +xserver-xorg-input-synaptics (1.7.1-2) unstable; urgency=low + + * Add Breaks on kde-config-touchpad (<< 0.8.1-2~) because of #722058 + (closes: #722006). + + -- Julien Cristau Sun, 22 Sep 2013 16:17:34 +0200 + +xserver-xorg-input-synaptics (1.7.1-1) unstable; urgency=low + + [ Maarten Lankhorst ] + * New upstream release. + * Add -dbg package + + [ Timo Aaltonen ] + * control: Bump policy to 3.9.4, no changes. + + [ Julien Cristau ] + * Don't use BUG_RETURN_VAL if it's not defined (added in xserver 1.13) + + -- Julien Cristau Thu, 05 Sep 2013 22:27:28 +0200 + +xserver-xorg-input-synaptics (1.7.1-0ubuntu2) trusty; urgency=medium + + * Rebuild for xorg 1.15 abi. + + -- Maarten Lankhorst Wed, 05 Feb 2014 09:40:08 +0000 + +xserver-xorg-input-synaptics (1.7.1-0ubuntu1) saucy; urgency=low + + * Merge from unreleased debian git. + + -- Maarten Lankhorst Thu, 25 Jul 2013 15:35:14 +0200 + +xserver-xorg-input-synaptics (1.6.3-0ubuntu1.1) raring; urgency=low + + * Merge from unreleased debian git. + * 130_reset-num_active_touches-on-deviceoff.patch, use-sigsafe-logging.patch: + Dropped, upstream. + + -- Timo Aaltonen Fri, 08 Feb 2013 13:40:38 +0200 + +xserver-xorg-input-synaptics (1.6.2-2) unstable; urgency=low + + * Pull latest synaptics-1.6-branch: + - Reset num_active_touches on DeviceOff (closes: #681796). Fixes memory + corruption leading to crashes. + + -- Julien Cristau Mon, 01 Oct 2012 21:55:06 +0200 + +xserver-xorg-input-synaptics (1.6.2-1ubuntu6) raring; urgency=low + + * use-sigsafe-logging.patch: Upstream commit from master, use signal + safe logging with input ABI 18. (LP: #1100586) + + -- Timo Aaltonen Thu, 17 Jan 2013 10:46:59 +0200 + +xserver-xorg-input-synaptics (1.6.2-1ubuntu5) quantal; urgency=low + + * Added 130_reset-num_active_touches-on-deviceoff.patch (LP: #956071) + + -- Timo Aaltonen Thu, 30 Aug 2012 20:13:39 +0300 + +xserver-xorg-input-synaptics (1.6.2-1ubuntu4) quantal-proposed; urgency=low + + [ Maarten Lankhorst ] + * Add -dbg package + + -- Timo Aaltonen Tue, 14 Aug 2012 19:10:16 +0300 + +xserver-xorg-input-synaptics (1.6.2-1ubuntu3) quantal-proposed; urgency=low + + * control: Bump the build-depends for new xserver. + + -- Timo Aaltonen Wed, 01 Aug 2012 14:27:06 +0300 + +xserver-xorg-input-synaptics (1.6.2-1ubuntu2) quantal-proposed; urgency=low + + * Removed patches: + - 125_option_rec_revert.patch + - 126_ubuntu_xi22.patch + * Bump xserver to 1.12 minimum + + -- Maarten Lankhorst Thu, 28 Jun 2012 21:49:46 +0200 + +xserver-xorg-input-synaptics (1.6.2-1ubuntu1) quantal; urgency=low + + * Merged from Debian unstable + * Removed patches that were cherry picked commits and now in 1.6.2: + - 201-Avoid-out-of-bounds-access-by-running-num_active_tou.patch + - 202-Ignore-pre-existing-touches.patch + - 203-Fix-coasting-for-negative-ScrollDelta.patch + - 204-Reset-open-slots-array-on-device-disable.patch + - 205-Reset-hw-x-y-to-INT_MIN-and-skip-HandleState-until-w.patch + - 206-Don-t-allow-for-scroll-distances-of-0-49965.patch + * Remaining patches: + - 101_resolution_detect_option.patch + - 103_enable_cornertapping.patch + - 104_always_enable_tapping.patch + - 106_always_enable_vert_edge_scroll.patch + - 115_evdev_only.patch + - 118_quell_error_msg.patch + - 124_syndaemon_events.patch + - 125_option_rec_revert.patch + - 126_ubuntu_xi22.patch + - 128_disable_three_click_action.patch + - 129_disable_three_touch_tap.patch + + -- Maarten Lankhorst Thu, 14 Jun 2012 09:35:21 +0200 + +xserver-xorg-input-synaptics (1.6.2-1) unstable; urgency=low + + * New upstream release, from the 1.6 branch. + + -- Cyril Brulebois Wed, 13 Jun 2012 22:22:42 +0200 + +xserver-xorg-input-synaptics (1.6.1-1ubuntu2) quantal-proposed; urgency=low + + * Enable right button clickpad area by default (LP: #972727) + - Upstream has this enabled. We had it disabled for Precise due to the + feature landing well after feature freeze. This change puts us back in + sync with upstream. + + -- Chase Douglas Thu, 07 Jun 2012 10:07:18 -0700 + +xserver-xorg-input-synaptics (1.6.1-1ubuntu1) quantal; urgency=low + + * Merged from Debian unstable + * Refreshed patches for indentation changes + - 101_resolution_detect_option.patch + - 118_quell_error_msg.patch + - 124_syndaemon_events.patch + - 125_option_rec_revert.patch + * Add temporary patches, for upstream git commits on synaptics-1.6-branch + * Fix memory corruption by driver (LP: #941953) FDo #49439 + - 201-Avoid-out-of-bounds-access-by-running-num_active_tou.patch + - 202-Ignore-pre-existing-touches.patch + * Fix FDo #49966 + - 203-Fix-coasting-for-negative-ScrollDelta.patch + * Fix jumpy cursor after suspend/resume + - 204-Reset-open-slots-array-on-device-disable.patch + * Fix another resume bug + - 205-Reset-hw-x-y-to-INT_MIN-and-skip-HandleState-until-w.patch + * Fix division by 0 or infinite loop with zero scroll distance FDo #49965 + - 206-Don-t-allow-for-scroll-distances-of-0-49965.patch + + -- Maarten Lankhorst Mon, 21 May 2012 14:14:49 +0200 + +xserver-xorg-input-synaptics (1.6.1-1) unstable; urgency=low + + [ Julien Cristau ] + * The mtdev build-dep is linux-only (closes: #672572). Thanks, Pino + Toscano! + + [ Cyril Brulebois ] + * New upstream release, from the 1.6 branch. + + -- Cyril Brulebois Sun, 20 May 2012 16:50:18 +0200 + +xserver-xorg-input-synaptics (1.6.0-1) unstable; urgency=low + + * New upstream release. + + -- Cyril Brulebois Mon, 07 May 2012 01:13:08 +0200 + +xserver-xorg-input-synaptics (1.6.0-0ubuntu1) quantal; urgency=low + + * Update to upstream version 1.6.0 + - Bug fixes only + - Fully fixes touchpad bugs on suspend/resume (LP: #968845) + * Drop patches merged upstream + - 200_fix_four_tap.patch + - 201_fix_touch_count.patch + - 202_touch_record_bounds_check.patch + - 203_fix_coasting_speed.patch + - 204_monotonicise_hw_timestamp.patch + - 205_end_touches_on_disable.patch + * Refreshed 130_dont_enable_rightbutton_area.patch + + -- Chase Douglas Mon, 07 May 2012 12:22:23 -0700 + +xserver-xorg-input-synaptics (1.5.99.904-2) unstable; urgency=low + + * Add a build-dep on libmtdev-dev now that X server 1.12 is in sid, + for XI 2.2 support. + + -- Cyril Brulebois Tue, 01 May 2012 15:02:43 +0200 + +xserver-xorg-input-synaptics (1.5.99.904-1) unstable; urgency=low + + * New upstream release candidate (1.6 RC4). + + -- Cyril Brulebois Fri, 27 Apr 2012 19:52:44 +0000 + +xserver-xorg-input-synaptics (1.5.99.902-1) unstable; urgency=low + + * New upstream release candidate (1.6 RC2). + + -- Cyril Brulebois Mon, 02 Apr 2012 01:12:26 +0000 + +xserver-xorg-input-synaptics (1.5.99.902-0ubuntu5.1) precise-proposed; urgency=low + + * debian/patches/204_monotonicise_hw_timestamp.patch: + - Cherry-pick patch from upstream to ensure the timestamps used when + determining velocities are monotonic. In certain circumstances the + timestamps from hardware events can be less than the timestamps + from the preceeding artificially-generated events. + + Fixes occasional huge jumps in pointer position. + + Fixes two-finger scrolling in GTK+3 windows (such as Nautilus and + Evolution) failing some time after login. (LP: #982771) + * debian/patches/205_end_touches_on_disable.patch: + - Upstream patch to reset touch state on device disable. + + Fixes touchpad not working after suspend on some MacBook models + (LP: #968845) + + -- Christopher James Halse Rogers Tue, 24 Apr 2012 14:32:49 +1000 + +xserver-xorg-input-synaptics (1.5.99.902-0ubuntu5) precise-proposed; urgency=low + + * Fix coasting speed on multitouch touchpads (LP: #930938) + - Add patch 203_fix_coasting_speed.patch + + -- Chow Loong Jin Fri, 13 Apr 2012 16:14:04 +0800 + +xserver-xorg-input-synaptics (1.5.99.902-0ubuntu4) precise; urgency=low + + * Fix crash on Apple trackpads when touching with more than 10 fingers + (LP: #974017) + - Add temporary patch 202_touch_record_bounds_check.patch + + -- Chase Douglas Mon, 09 Apr 2012 11:44:26 -0700 + +xserver-xorg-input-synaptics (1.5.99.902-0ubuntu3) precise; urgency=low + + * Fix bad default for two touch tap as middle button click (LP: #973783) + - Fixed 104_always_enable_tapping.patch + - Refreshed 129_disable_three_touch_tap.patch + + -- Chase Douglas Wed, 04 Apr 2012 14:42:34 -0700 + +xserver-xorg-input-synaptics (1.5.99.902-0ubuntu2) precise; urgency=low + + * Fix three touch taps and clicks on some trackpads (LP: #971783) + - Add temporary patch 201_fix_touch_count.patch + + -- Chase Douglas Mon, 02 Apr 2012 11:33:58 -0700 + +xserver-xorg-input-synaptics (1.5.99.902-0ubuntu1) precise; urgency=low + + * New upstream release + * Drop patches merged upstream + - 200_fix_clickfinger_non-clickpads.patch + - 201_fix_clickfinger_clickpad_actions.patch + * Revert upstream behavior change: enabling right button area by default + - Too late to enable in Precise + * Fix erroneous tap actions on four-touch tap (LP: #963327) + - Add temporary patch 200_fix_four_tap.patch + + -- Chase Douglas Thu, 29 Mar 2012 15:31:40 -0700 + +xserver-xorg-input-synaptics (1.5.99.901-1) unstable; urgency=low + + * New upstream release candidate (1.6 RC1): + - Add support for ClickPads. + * Multitouch isn't enabled yet (it would require a newer server anyway). + + -- Cyril Brulebois Tue, 20 Mar 2012 21:40:10 +0000 + +xserver-xorg-input-synaptics (1.5.99.901-0ubuntu2) precise; urgency=low + + * Disable three touch tap in favor of uTouch gestures (LP: #961725) + - See + https://lists.ubuntu.com/archives/ubuntu-desktop/2012-February/003694.html + for details. + - Add 129_disable_three_touch_tap.patch + * Fix click actions for non-clickpad touchpads (LP: #960108) + - Add temporary patch 200_fix_clickfinger_non-clickpads.patch + * Fix click action finger count calculation (LP: #961738) + - Add temporary patch 201_fix_clickfinger_clickpad_actions.patch + + -- Chase Douglas Wed, 21 Mar 2012 16:00:26 -0700 + +xserver-xorg-input-synaptics (1.5.99.901-0ubuntu1) precise; urgency=low + + * New upstream release + - Includes ClickPad support + - Fixes ClickPad click action interference + - Re-enables ClickPad support by default (LP: #955404) + - Everything else is bug fixes + * Remove 129_clickpad.patch, it has been merged upstream + * Bump lintian standards to 3.9.3 + + -- Chase Douglas Thu, 15 Mar 2012 17:58:09 -0700 + +xserver-xorg-input-synaptics (1.5.99~git20120223-0ubuntu2) precise; urgency=low + + * Disable ClickPad support when there is no physical right button + (LP: #941046) + - This re-enables click actions, which was enabled by default previously + - Users can enable ClickPad support through xorg.conf, xinput, or synclient + - See + https://lists.ubuntu.com/archives/ubuntu-desktop/2012-February/003751.html + for the discussion guiding this change. + * Refresh ClickPad support patch with changes from upstream review + - No new functionality + * Remove bottom area mask for Dell netbook clickpads with two buttons + + -- Chase Douglas Fri, 02 Mar 2012 13:33:26 -0700 + +xserver-xorg-input-synaptics (1.5.99~git20120223-0ubuntu1) precise; urgency=low + + * Update to latest code in git (0a2fd56) + - Only includes bug fixes + * Drop temporary patches that have been merged upstream: + - 129_tmp_pointer_drift.patch + - 130_tmp_touch_count_fix.patch + * Revert tap-and-drag locking default change (LP: #934770) + - Drop 127_default_drag_lock.patch + * Add ClickPad support (LP: #932947) + - Add 129_clickpad.patch + + -- Chase Douglas Thu, 23 Feb 2012 11:54:37 -0800 + +xserver-xorg-input-synaptics (1.5.99~git20120220-0ubuntu3) precise; urgency=low + + * Fix crash on multitouch devices when disabled while typing (LP: #931344) + - Add temporary patch 130_tmp_touch_count_fix.patch + + -- Chase Douglas Tue, 21 Feb 2012 16:22:17 +0100 + +xserver-xorg-input-synaptics (1.5.99~git20120220-0ubuntu2) precise; urgency=low + + * Prevent trackpad pointer drift (LP: #921082) + - Add temporary patch 129_tmp_pointer_drift.patch from upstream + + -- Chase Douglas Mon, 20 Feb 2012 11:55:23 +0100 + +xserver-xorg-input-synaptics (1.5.99~git20120220-0ubuntu1) precise; urgency=low + + * Update to latest code in git (f9a9065) + - Scale single-touch values to multitouch axes (LP: #936856) + + -- Chase Douglas Mon, 20 Feb 2012 11:13:10 +0100 + +xserver-xorg-input-synaptics (1.5.0+git20120210-0ubuntu2) precise; urgency=low + + * Enable tap-and-drag locked drags by default + * Disable three-click action by default so three touch gestures work + + -- Chase Douglas Mon, 13 Feb 2012 20:51:56 -0800 + +xserver-xorg-input-synaptics (1.5.0+git20120210-0ubuntu1) precise; urgency=low + + * Update to latest code in git (e6032c3) + * Drop 127_multitouch.patch, merged upstream + * Drop 126_default_speed.patch, no longer necessary + * Refresh 125_option_rec_revert.patch + * Add 126_ubuntu_xi22.patch for ubuntu frankenserver support + + -- Chase Douglas Fri, 10 Feb 2012 18:27:53 -0800 + +xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu2) precise; urgency=low + + * Add some error handling so eventcomm-test doesn't segfault + - Modified 127_multitouch.patch + + -- Chase Douglas Thu, 19 Jan 2012 17:44:47 -0800 + +xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1) precise; urgency=low + + * Add multitouch support + - Add 127_multitouch.patch + * Bump Build-Depends for x11proto-input-dev to 2.1.99.5 + * Remove obsolete dependencies on utouch-grail and old xserver-xorg-core + + -- Chase Douglas Thu, 19 Jan 2012 16:39:31 -0800 + +xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1~nomt3) precise; urgency=low + + * Fix default speed due to change in motion estimation + - Add 126_default_speed.patch + + -- Chase Douglas Thu, 12 Jan 2012 03:59:25 -0800 + +xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1~nomt2) precise; urgency=low + + * Drop patch 122_revert_pressure_finger_default.patch, merged upstream + + -- Chase Douglas Mon, 09 Jan 2012 07:03:52 -0800 + +xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1~nomt1) precise; urgency=low + + * Lower xserver-xorg-dev build dep for Precise's X server + * Revert xf86OptionRec upstream changes + - This particular API change will not be in Precise's X server + + -- Chase Douglas Mon, 09 Jan 2012 07:00:49 -0800 + +xserver-xorg-input-synaptics (1.5.0+git20120101-1) experimental; urgency=low + + * New upstream snapshot: + - Merge from upstream master up to 9f9b55ab55. + * Build against Xserver 1.12 RC1. + + -- Cyril Brulebois Sun, 01 Jan 2012 07:54:51 +0100 + +xserver-xorg-input-synaptics (1.5.0-2ubuntu1~nomt1) UNRELEASED; urgency=low + + * Merge from Debian testing. Remaining Ubuntu changes: + - 101_resolution_detect_option.patch: + + Provide an option to prevent synaptics from communicating the + touchpad size to the xserver. (LP: 327428) + - 103_enable_cornertapping.patch: + + Enable right/middle clicks by tapping in the bottom- and top-right + corners. + - 104_always_enable_tapping.patch: + + Enable tapping regardless of the presence of physical buttons. + - 106_always_enable_vert_edge_scroll.patch: + + Enable vertical edge tapping by default even if we enable two-finger + scrolling as well. + - 115_evdev_only.patch: + + Only bind to /dev/input/event* evdev devices (LP 624985) + - 118_quell_error_msg.patch: + + Don't print error "Unable to find a synaptics device" when used + on system that doesn't have a touchpad anyway. (LP: 716712) + - 122_revert_pressure_finger_default.patch: + + Fix spurious right click events on some trackpads (LP: 742213) + - 124_syndaemon_events.patch: + + Drain spurious events from XRecord connection as a work around + for a buggy X server. (LP: 754470) + - debian/control: + + Depend on udev. + + Move libxtst-dev to Build-Conflicts so syndaemon does not use XRecord, + preventing a wide range of crashes in _CallCallbacks. (LP: 774978) + - debian/gbp.conf: + + Specify ‘ubuntu’ branch. + - debian/local/51-synaptics-quirks.conf: + + Split out our synaptics quirks into 51-synaptics-quirks.conf + - debian/local/66-xorg-synaptics.rules: + + Add the udev rule back for tagging specific machines to be quirked. + - debian/xserver-xorg-input-synaptics.postinst.in: + + Trigger udev to pick up new udev rules + * Temporarily drop multitouch patches for X server 1.11 bringup: + + 116_xi2_1.patch + + 117_gestures.patch + + 119_active_area_touches.patch + + 120_active_touches_num_fingers.patch + + 121_semi-mt_num_fingers.patch + + 123_order_ProcessTouch_for_numFingers.patch + * debian/patches/122_revert_pressure_finger_default.patch: + + Refresh for new upstream + + -- Christopher James Halse Rogers Mon, 19 Dec 2011 11:10:25 +1100 + +xserver-xorg-input-synaptics (1.5.0-2) unstable; urgency=low + + * Cherry-pick to fix regression on powerpc (Closes: #649002): + - b7e65f04f: Revert the switch from TEST_BIT to BitIsOn. + + -- Cyril Brulebois Mon, 12 Dec 2011 13:28:36 +0100 + +xserver-xorg-input-synaptics (1.5.0-1) unstable; urgency=low + + [ Timo Aaltonen ] + * New upstream release. + + -- Cyril Brulebois Fri, 11 Nov 2011 17:15:22 +0100 + +xserver-xorg-input-synaptics (1.4.1-1ubuntu2) oneiric; urgency=low + + * Really install the quirks files, and rename the udev rules file + to emphasize that it's about quirks. + * postinst: Bump the version check to run udevadm trigger on upgrade. + + -- Timo Aaltonen Thu, 29 Sep 2011 22:16:55 +0300 + +xserver-xorg-input-synaptics (1.4.1-1ubuntu1) oneiric; urgency=low + + * Merge from Debian unstable, remaining changes: + - 101_resolution_detect_option.patch: + + Provide an option to prevent synaptics from communicating the + touchpad size to the xserver. (LP: 327428) + - 103_enable_cornertapping.patch: + + Enable right/middle clicks by tapping in the bottom- and top-right + corners. + - 104_always_enable_tapping.patch: + + Enable tapping regardless of the presence of physical buttons. + - 106_always_enable_vert_edge_scroll.patch: + + Enable vertical edge tapping by default even if we enable two-finger + scrolling as well. + - 115_evdev_only.patch: + + Only bind to /dev/input/event* evdev devices (LP 624985) + - 116_xi2_1.patch: + - 117_gestures.patch: + + multitouch and uTouch gesture support + - 118_quell_error_msg.patch: + + Don't print error "Unable to find a synaptics device" when used + on system that doesn't have a touchpad anyway. (LP: 716712) + - 119_active_area_touches.patch: + - 120_active_touches_num_fingers.patch: + - 121_semi-mt_num_fingers.patch: + + Fix handling for SemiMultitouch trackpads with integrated buttons + (LP: 736523) + - 122_revert_pressure_finger_default.patch: + + Fix spurious right click events on some trackpads (LP: 742213) + - 123_order_ProcessTouch_for_numFingers.patch: + + Fix jumpy cursor on multitouch trackpads (LP: 751525) + - 124_syndaemon_events.patch: + + Drain spurious events from XRecord connection as a work around + for a buggy X server. (LP: 754470) + - debian/control: + + Add depends on libmtdev and libutouch-grail + + Build-depend on multitouch-enabled inputproto. + + Depend on multitouch-enabled xserver. + + Depend on udev. + + Move libxtst-dev to Build-Conflicts so syndaemon does not use XRecord, + preventing a wide range of crashes in _CallCallbacks. (LP: 774978) + - debian/gbp.conf: + + Specify ‘ubuntu’ branch. + - debian/local/51-synaptics-quirks.conf: + + Split out our synaptics quirks into 51-synaptics-quirks.conf + - debian/local/66-xorg-synaptics.rules: + + Add the udev rule back for tagging specific machines to be quirked. + - debian/xserver-xorg-input-synaptics.postinst.in: + + Trigger udev to pick up new udev rules + * Drop 114_jumpy_cursor_first_part.patch, fixed upstream. + * Update 115_evdev_only.patch to apply. + * Update 117_gestures.patch to apply. + + -- Timo Aaltonen Tue, 05 Jul 2011 16:40:31 +0300 + +xserver-xorg-input-synaptics (1.4.1-1) unstable; urgency=low + + * New upstream release: + - Fix some typos in documentation (Closes: #622663). + - Fix “syndaemon consumes 100% CPU†(LP: #754470). + * Bump Standards-Version to 3.9.2 (no changes). + * Refresh patch: + - 02-do-not-use-synaptics-for-keyboards.patch to cope with the lines + removed in d25ba5e11d. + * Drop patch: + - 03-man-no-hal.patch, superseded by upstream commit e49d5d07c7. + + -- Cyril Brulebois Tue, 28 Jun 2011 15:38:13 +0200 + +xserver-xorg-input-synaptics (1.4.0-1) unstable; urgency=low + + * New upstream release: + + Fix the FTBFS on GNU/kFreeBSD. + + -- Cyril Brulebois Sat, 05 Mar 2011 16:26:01 +0100 + +xserver-xorg-input-synaptics (1.3.99.901-1) unstable; urgency=low + + * New upstream release. + * Disable patch: 01-synaptics-dont-grab-if-not-on-current-VT.patch (no + longer applies, needs to be sent/discussed upstream anyway). + + -- Cyril Brulebois Mon, 21 Feb 2011 04:27:15 +0100 + +xserver-xorg-input-synaptics (1.3.0-2) unstable; urgency=low + + * Switch to Architecture: any, adding armhf support (Closes: #604672). + * Switch to dh: + - Use debhelper 8. + - Use dh-autoreconf. + - Bump xserver-xorg-dev build-dep for dh_xsf_substvars and xsf + debhelper sequence. + * Remove xsfbs accordingly. + * Get rid of xfree86-* packages in Conflicts/Replaces/Provides. + * Bump Standards-Version to 3.9.1 (no changes needed). + + -- Cyril Brulebois Sat, 05 Feb 2011 14:40:47 +0100 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu15) oneiric; urgency=low + + * Also build-conflict with libxtst-dev as to make sure it's not present in + e.g. local debug builds. + + -- Loïc Minier Fri, 10 Jun 2011 18:01:59 +0200 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu14) oneiric; urgency=low + + * Drop libxtst-dev build dependency so syndaemon does not use XRecord, + preventing a wide range of crashes in _CallCallbacks. (LP: #774978) + + -- Robert Hooker Mon, 23 May 2011 10:13:09 -0400 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu13) oneiric; urgency=low + + * Re-add 116_resolution_detect_option.patch as 101_resolution_detect_option.patch: + - This patch was introduced in 1.2.2-2ubuntu7 but got erroneously dropped in the + merge for 1.3.99+git20110116.0e27ce3a-0ubuntu1. + (LP: #327428) + + -- Bryce Harrington Fri, 20 May 2011 11:14:44 -0700 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu12) natty; urgency=low + + * syndaemon: Drain spurious events from XRecord connection as a work around + for a buggy X server. This keeps syndaemon from busy looping + (LP: #754470) + + -- Chase Douglas Thu, 14 Apr 2011 14:15:33 -0400 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu11) natty; urgency=low + + * Fix jumpy cursor on multitouch trackpads (LP: #751525) + - Added 123_order_ProcessTouch_for_numFingers.patch + + -- Chase Douglas Tue, 05 Apr 2011 11:03:12 -0400 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu10) natty; urgency=low + + * Ensure touch mask exists before clearing it (LP: #747126) + + -- Chase Douglas Fri, 01 Apr 2011 08:40:59 -0400 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu9) natty; urgency=low + + * Fix handling for SemiMultitouch trackpads with integrated buttons + (LP: #736523) + - Added 119_active_area_touches.patch + - Added 120_active_touches_num_fingers.patch + - Added 121_semi-mt_num_fingers.patch + * Fix spurious right click events on some trackpads (LP: #742213) + - Added 122_revert_pressure_finger_default.patch + + -- Chase Douglas Wed, 30 Mar 2011 10:54:50 -0400 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu8) natty; urgency=low + + * Disable SemiMultitouch devices, we don't support them yet (LP: #723905) + + -- Chase Douglas Tue, 15 Mar 2011 16:16:21 -0400 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu7) natty; urgency=low + + * Set event window correctly for global gesture event + (LP: #728696) + + -- Chase Douglas Fri, 04 Mar 2011 15:40:43 -0500 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu6) natty; urgency=low + + * Fix silly typo in patch name + + -- Bryce Harrington Wed, 02 Mar 2011 13:06:17 -0800 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu5) natty; urgency=low + + * Add 118_quell_error_msg.patch: Don't print error "Unable to find a + synaptics device" when used on system that doesn't have a touchpad anyway. + (LP: #716712) + * Renumber patches 200 and 201 to 116 and 117 so are consistent with the + other ubuntu patches. + + -- Bryce Harrington Wed, 02 Mar 2011 12:16:32 -0800 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu4) UNRELEASED; urgency=low + + * Add in xi 2.1 support and uTouch gesture support + - Added 200_xi2.1.patch + - Added 201_gestures.patch + - Add depends on libmtdev and libutouch-grail + + -- Chase Douglas Sun, 20 Feb 2011 19:23:11 -0500 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu3) natty; urgency=low + + * debian/control: Bump build-depend on xserver-xorg-dev to really, really + build against Xserver 1.10 + + -- Christopher James Halse Rogers Tue, 01 Feb 2011 20:46:57 +1100 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu2) natty; urgency=low + + * Rebuild against Xserver 1.10 + + -- Bryce Harrington Mon, 31 Jan 2011 18:37:55 -0800 + +xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu1) natty; urgency=low + + [ Cyril Brulebois ] + * Switch to Architecture: any, adding armhf support (Closes: #604672). + + [ Christopher James Halse Rogers ] + * Merge from (unreleased) Debian experimental. Remaining Ubuntu changes: + - Split out our synaptics quirks into 51-synaptics-quirks.conf + - Add the udev rule back for tagging specific machines to be quirked + - Trigger udev in postinst to pick up udev rules + - add Depend: on udev + - 103_enable_cornertapping.patch: Enable right/middle clicks by + tapping in the bottom- and top-right corners. + - 104_always_enable_tapping.patch: Enable tapping regardless of the + presence of physical buttons. + - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge + tapping by default even if we enable two-finger scrolling as well. + - 114_jumpy_cursor_first_part.patch: Adds a property to enable filtering + jumpy cursor problems on some machines. + - Specify ‘ubuntu’ branch in gbp.conf + - 115_evdev_only.patch: Only bind to /dev/input/event* evdev devices + (LP 624985) + * Update to git snapshot to fix build against Xserver 1.10 input API + * debian/patches/01-synaptics-dont-grab-if-not-on-current-VT.patch: + - Refresh for new upstream. + * debian/patches/114_jumpy_cursor_first_part.patch: + - Refresh for new upstream. Drop the multi-touch chunk of the quirk, + as that is handled by upstream commit a6ca4d25. + + -- Christopher James Halse Rogers Tue, 25 Jan 2011 20:01:58 +1100 + +xserver-xorg-input-synaptics (1.3.0-1) experimental; urgency=low + + [ Robert Hooker ] + * New upstream release. + * Bump xutils-dev requirement for new util-macros. + + [ Cyril Brulebois ] + * Remove TODO from xserver-xorg-input-synaptics.docs, dropped upstream. + + -- Cyril Brulebois Fri, 19 Nov 2010 23:49:14 +0100 + +xserver-xorg-input-synaptics (1.2.2-4) experimental; urgency=low + + * Merge xsfbs to get unversioned Provides. + + -- Cyril Brulebois Sat, 16 Oct 2010 14:40:22 +0200 + +xserver-xorg-input-synaptics (1.2.2-3) experimental; urgency=low + + [ Mattia Dongili ] + * add dependency to libxtst-dev to enable XRecord support in + syndaemon (closes: #598834). + + [ Cyril Brulebois ] + * Build against Xserver 1.9.1 rc1. + + -- Cyril Brulebois Wed, 13 Oct 2010 00:26:45 +0200 + +xserver-xorg-input-synaptics (1.2.2-2ubuntu7) natty; urgency=low + + * Add 116_resolution_detect_option.patch: Provide an option to prevent + synaptics from communicating the touchpad size to the xserver. This + can be used to solve problems where differences in touchpad and screen + dimensions cause the mouse pointer to skip pixels when moving. + (LP: #327428) + + -- Bryce Harrington Wed, 01 Dec 2010 17:03:44 -0800 + +xserver-xorg-input-synaptics (1.2.2-2ubuntu6) natty; urgency=low + + * Add quirk for Dell Inspiron 6400 to register taps more reliably. + The touchpad hardware occasionally sends invalid (1, 5855) coords + for taps, which the driver then interprets as a corner tap, which + by default get ignored. Instead of ignoring them, we treat them + as left-button clicks. + (LP #133060) + + -- Bryce Harrington Wed, 01 Dec 2010 12:32:39 -0800 + +xserver-xorg-input-synaptics (1.2.2-2ubuntu5) maverick; urgency=low + + * 115_evdev_only.patch was added but never applied. (LP: #624985) + + -- Chase Douglas Sat, 18 Sep 2010 15:19:21 +0200 + +xserver-xorg-input-synaptics (1.2.2-2ubuntu4) maverick; urgency=low + + [Chase Douglas] + * 115_evdev_only.patch: + - Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback + (LP: #624985) + + -- Bryce Harrington Wed, 15 Sep 2010 16:27:29 -0700 + +xserver-xorg-input-synaptics (1.2.2-2ubuntu3) maverick; urgency=low + + * Rebuild against new Xserver 1.9 ABI + + -- Christopher James Halse Rogers Mon, 09 Aug 2010 21:47:56 +1000 + +xserver-xorg-input-synaptics (1.2.2-2ubuntu2) maverick; urgency=low + + * 51-synaptics-quirks.conf, 66-xorg-synaptics.rules: + - Put back quirk for Inspiron 1120 which was probably lost in a merge. + + -- Alberto Milone Fri, 06 Aug 2010 12:26:59 +0200 + +xserver-xorg-input-synaptics (1.2.2-2ubuntu1) maverick; urgency=low + + [ Robert Hooker ] + * Merge from debian unstable, remaining changes: + - Split out our synaptics quirks into 51-synaptics-quirks.conf + - Add the udev rule back for tagging specific machines to be quirked + - 103_enable_cornertapping.patch: Enable right/middle clicks by + tapping in the bottom- and top-right corners. + - 104_always_enable_tapping.patch: Enable tapping regardless of the + presence of physical buttons. + - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge + tapping by default even if we enable two-finger scrolling as well. + - 114_jumpy_cursor_first_part.patch: Adds a property to enable filtering + jumpy cursor problems on some machines. + * Add a dependency on udev. + + [ Christopher James Halse Rogers ] + * debian/gbp.conf: + - Make git-buildpackage less annoying by specifying “ubuntu†as our debian + branch. + + -- Robert Hooker Mon, 07 Jun 2010 19:07:59 -0400 + +xserver-xorg-input-synaptics (1.2.2-2) unstable; urgency=low + + * Cherry-pick patch from upstream git to add the xorg.conf.d snippet. + * Replace our local copy with that one. + * Build-Depend on new xserver-xorg-dev, which lets us install the snippet to + /usr/share/X11/xorg.conf.d. + * Drop postinst, we don't need to trigger udev anymore. + * Sanitize installation of manpage into the package. + * Update to new xsfbs, use ${xinpdriver:Depends} in debian/control. + * Drop dependency on udev, since we have no udev rules or trigger. + + [ Cyril Brulebois ] + * Add myself to Uploaders. + * Bump Standards-Version from 3.8.3 to 3.8.4 (no changes needed). + + -- Cyril Brulebois Sat, 17 Apr 2010 17:48:26 +0200 + +xserver-xorg-input-synaptics (1.2.2-1ubuntu2) lucid; urgency=low + + * 10-synaptics.conf: Use 'MatchDevicePath "/dev/input/event*"'. + + -- Timo Aaltonen Thu, 01 Apr 2010 14:34:20 +0300 + +xserver-xorg-input-synaptics (1.2.2-1ubuntu1) lucid; urgency=low + + * Merge from Debian unstable. (LP: #546933) + * Rewrite old quirks to use ID_INPUT.tags. + + -- Timo Aaltonen Wed, 31 Mar 2010 00:27:07 +0300 + +xserver-xorg-input-synaptics (1.2.2-1) unstable; urgency=low + + [ Timo Aaltonen ] + * New upstream release. + * Add xserver-xorg-input-synaptics-dev for the header and .pc files. + (LP: #340340) + * Run udevadm trigger on postinst, and depend on udev [linux-any]. + * Add 10-synaptics.conf. + * Drop Ubuntu specific quirks from the udev rules, add a placeholder. + + [ Julien Cristau ] + * Add hurd-i386 to the Architecture field. Requested by Samuel Thibault. + * Drop references to hal from debian/NEWS, README.Debian and synaptics(4) + (closes: #564549). + * Rename the build directory to not include DEB_BUILD_GNU_TYPE for no + good reason. Thanks, Colin Watson! + + [ Brice Goglin ] + * Fix reference to the synaptics manpage in README.Debian, + closes: #568628. + + -- Timo Aaltonen Tue, 30 Mar 2010 21:51:15 +0300 + +xserver-xorg-input-synaptics (1.2.1-1) unstable; urgency=low + + [ Timo Aaltonen ] + * New upstream release. + + [ Cyril Brulebois ] + * Add ${misc:Depends} to Depends. + * Remove hardcoded libxi6 from Depends, and wrap them. + * Upload to unstable. + + -- Cyril Brulebois Fri, 08 Jan 2010 00:55:04 +0100 + +xserver-xorg-input-synaptics (1.2.0-3ubuntu1) lucid; urgency=low + + * Merge from Debian experimental, remaining changes: + - 103_enable_cornertapping.patch: Enable right/middle clicks by + tapping in the bottom- and top-right corners. + - 104_always_enable_tapping.patch: Enable tapping regardless of the + presence of physical buttons. + - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge + tapping by default even if we enable two-finger scrolling as well. + * Disabled until fixed to apply and work: + - 114_jumpy_cursor_first_part.patch + * Dropped, using udev now: + - 115_jumpy_cursor_second_part.patch + + -- Timo Aaltonen Mon, 07 Dec 2009 22:58:38 +0200 + +xserver-xorg-input-synaptics (1.2.0-3) experimental; urgency=low + + [ Julien Cristau ] + * Install an udev rule instead of an fdi file on linux. + * Build against xserver 1.7 (closes: #549109). + * Add sh4 and kfreebsd-{amd64,i386} to the arch list (closes: #557881, + #559401). + + [ Cyril Brulebois ] + * Upload to experimental. + + -- Cyril Brulebois Sat, 05 Dec 2009 21:27:48 +0100 + +xserver-xorg-input-synaptics (1.2.0-2) unstable; urgency=low + + [ Mattia Dongili ] + * Negate the match on keyboard capabilities in the default fdi file + (closes: #557252). + + -- Mattia Dongili Sat, 21 Nov 2009 13:00:37 +0900 + +xserver-xorg-input-synaptics (1.2.0-1) unstable; urgency=low + + [ Mattia Dongili ] + * Finally package the new upstream release. + * Remove 02-mention-tapping-in-manpage-notes.patch now included upstream. + * All the SHM code has been removed from synclient and syndaemon + (closes: 523652 and closes: 534333 and also closes: #548357). Also add a + note in the NEWS file. + * Do not use the synaptics driver for devices advertising keyboard + capabilities (closes: 524130). + * Refresh patch 01-synaptics-dont-grab-if-not-on-current-VT.patch. + * Add versioned Build-Depends on libxi-dev (closes: #538299). + * The driver now handles duplicate configuration in both an fdi file and + xorg.conf, see http://bugs.freedesktop.org/show_bug.cgi?id=24814 + (closes: #532995). + * Make lintian happier: update Standard-Version and debhelper dependency. + + -- Mattia Dongili Thu, 19 Nov 2009 21:24:49 +0900 + +xserver-xorg-input-synaptics (1.1.2-1ubuntu7) karmic; urgency=low + + * debian/patches: + - 115_jumpy_cursor_second_part.patch: Set AreaBottomEdge to + 4100 for Inspiron 1011 and Inspiron 1012. + + -- Alberto Milone Wed, 14 Oct 2009 16:55:16 +0200 + +xserver-xorg-input-synaptics (1.1.2-1ubuntu6) karmic; urgency=low + + * debian/patches: + - Split 114_jumpy_cursor_threshold.patch into + 114_jumpy_cursor_first_part.patch and + 115_jumpy_cursor_second_part.patch so as to keep options + which belong in the fdi file separate from the code. + - 115_jumpy_cursor_second_part.patch: + + Add quirk for Inspiron 1012. + - Remove 114_jumpy_cursor_threshold.patch_old. + + -- Alberto Milone Mon, 05 Oct 2009 12:09:14 +0200 + +xserver-xorg-input-synaptics (1.1.2-1ubuntu5) karmic; urgency=low + + * Add lpia to the list of Architectures. LP: #413579. + + -- Steve Langasek Mon, 07 Sep 2009 10:44:54 -0700 + +xserver-xorg-input-synaptics (1.1.2-1ubuntu4) karmic; urgency=low + + * debian/patches: + - 112_jumpy_cursor_quirks.patch: + + Drop patch. + - 114_jumpy_cursor_threshold.patch: + + Replace 112_jumpy_cursor_quirks.patch. + + Make the threshold an XInput property. + + Add quirk for Dell Mini10v and for HP Mini 1000 in the + fdi file. + + -- Alberto Milone Wed, 02 Sep 2009 10:44:28 +0200 + +xserver-xorg-input-synaptics (1.1.2-1ubuntu3) karmic; urgency=low + + * debian/patches: + - 112_jumpy_cursor_quirks.patch: Make sure that the quirk + affects only the Dell Mini 10v (LP: #405943). + - 103_enable_cornertapping.patch: Don't trigger middle click + when tapping on the top right corner button (LP: #386017). + + -- Alberto Milone Mon, 17 Aug 2009 10:31:14 +0200 + +xserver-xorg-input-synaptics (1.1.2-1ubuntu2) karmic; urgency=low + + * debian/patches: + - 105_correct_multifinger_click.patch: Drop patch as it breaks + the expected behavior for multitouch clicking (LP: #320585). + - 111_add_active_area.patch: Backport "Synaptics Area" property + from upstream (LP: #402863). + - 112_jumpy_cursor_quirks.patch: Add Quirk for Dell Mini 10v + touchpad (LP: #402863). + - 113_add_capabilities.patch: Backport read-only "Synaptics + Capabilities" property from upstream. + + -- Alberto Milone Wed, 22 Jul 2009 10:42:11 +0200 + +xserver-xorg-input-synaptics (1.1.2-1ubuntu1) karmic; urgency=low + + * Merge from Debian unstable, Remaining changes: + - Restore the Ubuntu patch series. (LP: #378391) + - 103_enable_cornertapping.patch: Enable right/middle clicks by + tapping in the bottom- and top-right corners. + - 104_always_enable_tapping.patch: Enable tapping regardless of the + presence of physical buttons. + - 105_correct_multifinger_click.patch: Swap the right/middle clicks + generated by multi-finger tapping. + - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge + tapping by default even if we enable two-finger scrolling as well. + - 110_remove_bound_auto_adjust.patch: Don't auto-adjust to bounds of + the hardware scroll area. + + -- Robert Hooker (Sarvatt) Sun, 28 Jun 2009 18:13:52 -0400 + +xserver-xorg-input-synaptics (1.1.2-1) unstable; urgency=low + + [ Mattia Dongili ] + * New upstream release. + * Replace 02-mention-tapping-in-manpage-notes.patch with the patch accepted + upstream (1f499d). + * Refresh patch 01-synaptics-dont-grab-if-not-on-current-VT.patch. + * Update README.Debian's date signature with the last modification time + (closes: #531999). + + -- Mattia Dongili Sat, 06 Jun 2009 13:32:59 +0900 + +xserver-xorg-input-synaptics (1.1.1~git20090510-1) unstable; urgency=low + + [ David Nusinow ] + * Add NEWS.Debian entry about enabling tapping again (closes: #497523) + + [ Mattia Dongili ] + * Link to xserver-xorg reportbug script + * Merge current upstream fixes: + - Edge calculation fixes bring back vertical scrolling (closes: #523946) + - Fix float properties handling on 64bit arches (closes: #525473) + * Remove Suggests for [kq]synaptics which are not in the repository anymore + (closes: #498299) and suggest gpointing-device-settings (replacement of + gsynaptics) and touchfreeze instead + * Remove the upstream NEWS file which doesn't exist anymore + * Updates to the README.Debian to make sure we mention how to customize the + fdi configuration + + -- Mattia Dongili Mon, 11 May 2009 23:08:15 +0900 + +xserver-xorg-input-synaptics (1.1.0-1) unstable; urgency=low + + * New upstream release. + * Upload to unstable. + + -- Julien Cristau Thu, 09 Apr 2009 01:34:19 +0100 + +xserver-xorg-input-synaptics (0.99.3-3) experimental; urgency=low + + [ Julien Cristau ] + * Get rid of the xfree86-driver-synaptics transitional package. It was + already a transitional package in etch. + + [ Timo Aaltonen ] + * Rename the source package to xserver-xorg-input-synaptics, since + it's hosted on freedesktop now and to be consistent with the rest + of the input drivers. + + -- Julien Cristau Mon, 26 Jan 2009 09:08:10 +0100 + +xfree86-driver-synaptics (0.99.3-2ubuntu4) jaunty; urgency=low + + * debian/patches/110_remove_bound_auto_adjust.patch: + - Small change to make sure that the driver does not + auto-adjust to the bounds of the hardware scroll area + as this causes problem to the touchpads of Acer Aspire + One units (LP: #320632). + + -- Alberto Milone Thu, 26 Mar 2009 17:00:52 +0100 + +xfree86-driver-synaptics (0.99.3-2ubuntu3) jaunty; urgency=low + + * debian/patches/109_override_alps_settings.patch: + - Add customised settings for ALPS touchpad (LP: #320632): + o Enable vertTwoFingerScroll by default. + o Set RightEdge to 900. + o Set ClickTime to 0. + o Set SingleTapTimeout to 0. + o Set MinSpeed to 0.45. + o Set MaxSpeed to 0.75. + * debian/patches/107_reduce_cursor_speed.patch: + - drop and merge with 109_override_alps_settings.patch. + + -- Alberto Milone (tseliot) Sat, 28 Feb 2009 10:20:08 +0100 + +xfree86-driver-synaptics (0.99.3-2ubuntu2) jaunty; urgency=low + + [Alberto Milone] + * debian/patches/105_always_enable_multifinger_click.patch: + - Drop patch + * debian/patches/105_correct_multifinger_click.patch: + - Fix inverted buttons + - Restore ability to drag and drop with physical buttons + (LP: #320632) + + -- Alberto Milone (tseliot) Sat, 31 Jan 2009 18:48:37 +0100 + +xfree86-driver-synaptics (0.99.3-2ubuntu1) jaunty; urgency=low + + * Merge from Debian experimental. + * Revive syndaemon_xinput_props.patch, now refreshed as 108. + Thanks to John S. Gruber for noticing it was dropped in haste. + (LP: #321355) + + -- Timo Aaltonen Mon, 26 Jan 2009 09:57:07 +0200 + +xfree86-driver-synaptics (0.99.3-2) experimental; urgency=low + + * Update our copy of xsfbs to get a fixed serverabi rule and generate + Provides/Depends correctly. + * Refresh patch 01-synaptics-dont-grab-if-not-on-current-VT.patch. + + -- Julien Cristau Mon, 26 Jan 2009 04:43:08 +0100 + +xfree86-driver-synaptics (0.99.3-1ubuntu2) jaunty; urgency=low + + [ Alberto Milone (tseliot) ] + * 103_enable_cornertapping.patch: + - Update patch for the new upstream release. (LP: #320632) + * 104_always_enable_tapping.patch: + - Enable tapping even when a physical left button is available. (LP: #320585) + * 105_always_enable_multifinger_click.patch: + - Enable multifinger click (where available) even when physical right/middle + buttons are available. (LP: #320585) + * 106_always_enable_vert_edge_scroll.patch: + - Enable scrolling even when doubletap is available. (LP: #320632) + * 107_reduce_cursor_speed.patch: + - Reduce the cursor speed. (LP: #320639) + + -- Timo Aaltonen Sat, 24 Jan 2009 20:29:04 +0200 + +xfree86-driver-synaptics (0.99.3-1ubuntu1) jaunty; urgency=low + + * Merge from Debian experimental. + * Drop obsolete patches: + -101_no_horizscroll.patch + -102_dont_check_abi.diff + -104_syndaemon_xinput_props.patch + -105_no_bcm5974_shmconfig.patch + * Update 103_enable_cornertapping.patch to apply. + + -- Timo Aaltonen Fri, 23 Jan 2009 14:30:34 +0200 + +xfree86-driver-synaptics (0.99.3-1) experimental; urgency=low + + [ Timo Aaltonen ] + * Run autoreconf at build time; build-depend on automake, libtool and + xutils-dev. + + [ Julien Cristau ] + * Add watch file. + * New upstream release. + * Build against xserver 1.6 rc1. + * Handle parallel builds. + + -- Julien Cristau Wed, 21 Jan 2009 22:27:10 +0100 + +xfree86-driver-synaptics (0.15.2-0ubuntu5) intrepid; urgency=low + + * Cherry-picked commit db6e63, "Return correctly on successful property + setting" + * debian/patches/104_syndaemon_xinput_props.patch: Add support for XInput + device properties to syndaemon. + * debian/patches/105_no_bcm5974_shmconfig.patch: Remove the SHMConfig + enabling line from the bcm5974 section of the fdi file. (LP: #282004) + + -- William Grant Mon, 13 Oct 2008 21:48:05 +1100 + +xfree86-driver-synaptics (0.15.2-0ubuntu4) intrepid; urgency=low + + * Cherry-picked commit 355e845, "Adjust to new property API". + (LP: #274728) + * Refresh patches 101 & 102. + * Bump the build-dep on libxi-dev and xserver-xorg-dev. + + -- Timo Aaltonen Mon, 29 Sep 2008 10:23:16 +0300 + +xfree86-driver-synaptics (0.15.2-0ubuntu3) intrepid; urgency=low + + * Also build for lpia; LP: #275158. + + -- Loic Minier Sat, 27 Sep 2008 16:11:42 +0200 + +xfree86-driver-synaptics (0.15.2-0ubuntu2) intrepid; urgency=low + + [ Alberto Milone ] + * 103_enable_cornertapping.patch: + - Revert some defaults back to previous values, to fix corner tapping + and MaxTapMove. (LP: #271823) + + -- Timo Aaltonen Tue, 23 Sep 2008 10:46:44 +0300 + +xfree86-driver-synaptics (0.15.2-0ubuntu1) intrepid; urgency=low + + * New upstream release. (LP: #262276, #268071, #247433, #262305) + * Drop 101_ubuntu.diff, superseded by input properties. + * Drop 103_fix_off_by_one.diff, applied upstream. + * 101_no_horizscroll.patch: + - Disable horizontal scrolling by defaulting to FALSE instead of changing + the fdi file. + * 102_dont_check_abi.diff: + - Remove one more check from src/synaptics.c, which was added recently. + + -- Timo Aaltonen Fri, 12 Sep 2008 01:32:57 +0300 + +xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu5) intrepid; urgency=low + + * Rebuild to fix dependency against xserver-xorg-core. + + -- Timo Aaltonen Wed, 03 Sep 2008 17:45:20 +0300 + +xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu4) intrepid; urgency=low + + * Run autoreconf at build time; build-depend on automake, libtool and + xutils-dev. + * Enable 102 again, since the problem with it was that Makefile.in was + not regenerated. + * Add 103_fix_off_by_one.diff to fix the number of arguments for + properties. Thanks William Grant! + * Pull 3d39926875446e from upstream/master: + - Re-enable TapButtons and CornerButtons to work by default. + (LP: #262292) + + -- Timo Aaltonen Sat, 30 Aug 2008 18:20:14 +0300 + +xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu3) intrepid; urgency=low + + * Disable 102 for now, since the property stuff doesn't seem to be + working right. (LP: #262986) + + -- Timo Aaltonen Sat, 30 Aug 2008 16:52:51 +0300 + +xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu2) intrepid; urgency=low + + * 102_dont_check_abi.diff: + - Don't check ABI_MAJOR, since we have all that's needed for + properties. + + -- Timo Aaltonen Sat, 30 Aug 2008 13:27:54 +0300 + +xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu1) intrepid; urgency=low + + * Merge from Debian experimental, remaining changes: + - debian/control: + + Drop the dummy package + + Change the maintainer address + - debian/patches: + 101_ubuntu.diff + + Various fixes, split this. + - fdi/11-x11-synaptics.fdi + + Modify to disable horizontal scrolling like our dexconf used to do. + * Pull in support for input properties. + * Drop 102_polling.diff, since it's upstream. + + -- Timo Aaltonen Thu, 28 Aug 2008 02:09:47 +0300 + +xfree86-driver-synaptics (0.15.0+git20080820-1) experimental; urgency=low + + * Update to latest upstream git + * Remove patches merged upstream: + - 02-scandir-dev-input.patch + - 03-escape-dashes-in-manpages.patch + - 04-delibcwrap.patch + * Update debian/rules (taken from the evdev driver) for the autotoolized build + * Refresh 01-synaptics-dont-grab-if-not-on-current-VT.patch + * Cherry pick appletouch support from upstream + + -- Mattia Dongili Sun, 24 Aug 2008 17:44:12 +0900 + +xfree86-driver-synaptics (0.14.7~git20070706-2.2) experimental; urgency=low + + * Build against xserver 1.5 rc5. + + -- Julien Cristau Mon, 14 Jul 2008 16:14:31 +0200 + +xfree86-driver-synaptics (0.14.7~git20070706-3) unstable; urgency=low + + * Add appletouch support in the fdi file (Closes: #495799). + * Install the fdi file with 0644 permissions. + * Updated xserver-xorg-input-synaptics.README.Debian to mention automatic + configuration and show a clearer example for the Server (manual) + configuration (Closes: #443995 and also Closes: #492120). + + -- Mattia Dongili Wed, 27 Aug 2008 23:05:42 +0900 + +xfree86-driver-synaptics (0.14.7~git20070706-2.1.lenny1) unstable; urgency=low + + * Add 10-synaptics.fdi, installed in /usr/share/hal/fdi/policy/20thirdparty, + to get the driver loaded automatically if a touchpad is detected + (closes: #464662). + * Add patch from xorg git to fix usage of the EVIOCGBIT ioctl() + (closes: #493101). + + -- Julien Cristau Mon, 18 Aug 2008 01:41:39 +0200 + +xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu4) intrepid; urgency=low + + * Fix the fdi file to disable horizontal scrolling like dexconf used + to do. + + -- Timo Aaltonen Mon, 18 Aug 2008 16:27:30 +0300 + +xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu3) intrepid; urgency=low + + * Enable input-hotplug: install 10-synaptics.fdi in + /usr/share/hal/fdi/policy/20thirdparty. + + -- Timo Aaltonen Fri, 01 Aug 2008 09:58:32 +0300 + +xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu2) intrepid; urgency=low + + * Rebuild against the new xserver-xorg-dev. + + -- Timo Aaltonen Mon, 07 Jul 2008 17:45:08 +0300 + +xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu1) intrepid; urgency=low + + * Merge from debian unstable, remaining changes: + - Rename the source + - debian/control: + + Drop the dummy package + + Change the maintainer address. + + Add quilt to build-deps. + - Makefile: Add -fno-stack-protector. + - debian/patches: + 100_fix_vt_handling.diff + + Something is opening the device even after we've switched away + from the VT. The easiest way to handle this is to refuse to open + unless we're on the current VT. + 101_ubuntu.diff + + Various fixes, split this. + 102_polling.diff + + Added a patch which increases the polling timeout to save some + battery life + + -- Bryce Harrington Mon, 09 Jun 2008 22:27:20 -0700 + +xfree86-driver-synaptics (0.14.7~git20070706-2.1) unstable; urgency=low + + * Non-maintainer upload, with Mattia's agreement. + * Add patch to fix the build with xserver 1.5, which removed the libc + wrapper (closes: #471494). + * Add armel and armeb to the Architecture field (closes: #461551). + + -- Julien Cristau Mon, 02 Jun 2008 12:21:39 +0200 + +xfree86-driver-synaptics (0.14.7~git20070706-2) unstable; urgency=low + + * 01-synaptics-dont-grab-if-not-on-current-VT.patch from Matthew Garrett in + Ubuntu's 0.14.6-0ubuntu10 (Closes: #437273) + * 02-scandir-dev-input.patch to scan all the event devices in /dev/input + (Closes: #455994) + * update xsfbs + + -- Mattia Dongili Tue, 01 Jan 2008 12:38:13 +0900 + +xfree86-driver-synaptics (0.14.7~git20070706-1ubuntu4) hardy; urgency=low + + * 102_polling.diff: + - Added a patch which increases the polling timeout to save some + battery life (LP: #184398). + + -- Timo Aaltonen Wed, 27 Feb 2008 11:58:01 +0200 + +xfree86-driver-synaptics (0.14.7~git20070706-1ubuntu3) hardy; urgency=low + + * debian/patches/101_ubuntu.diff: + - extend to support disabling the touchpad + - remove unused bits of the patch + + -- Scott James Remnant Tue, 22 Jan 2008 13:40:07 +0000 + +xfree86-driver-synaptics (0.14.7~git20070706-1ubuntu2) hardy; urgency=low + + * Change the source name to match Debian. + + -- Timo Aaltonen Fri, 11 Jan 2008 11:59:09 +0200 + +xserver-xorg-input-synaptics (0.14.7~git20070706-1ubuntu1) hardy; urgency=low + + * Merge with Debian unstable, remaining changes: + - Rename the source + - debian/control: + + Drop the dummy package + + Change the maintainer address. + + Add quilt to build-deps. + - Makefile: Add -fno-stack-protector. + - debian/patches: + 100_fix_vt_handling.diff + + Something is opening the device even after we've switched away + from the VT. The easiest way to handle this is to refuse to open + unless we're on the current VT. + 101_ubuntu.diff + + Various fixes, split this. + + -- Timo Aaltonen Fri, 09 Nov 2007 02:23:16 +0200 + +xfree86-driver-synaptics (0.14.7~git20070706-1) unstable; urgency=low + + * build with xserver-xorg-core 1.4 (Closes: #442314) + * update to current git + * update xsfbs + + -- Mattia Dongili Mon, 17 Sep 2007 15:25:56 +0900 + +xfree86-driver-synaptics (0.14.7~git20070517-2) unstable; urgency=low + + * use xsfbs to build the driver and automagically get xserver-xorg deps + (Closes: #425818) + + -- Mattia Dongili Thu, 31 May 2007 14:17:31 +0900 + +xfree86-driver-synaptics (0.14.7~git20070517-1) unstable; urgency=low + + * git snapshot of the driver: + - from commit e6595886d7ae3a1730cd0d9f86fd0f305c0743c2 + "[...] synaptics, alps and appletouch pads all work approximately + identically without requiring model-specific configuration" + (Closes: #351621) (Closes: #406499) + - from commit 88211ca60be7cc74015ad4158722e5950884bd78 + "Don't match wacom tablets in the event device probing." + (Closes: #383711) + - from commit 473912f50ffd2b26bd2f638f9d87f86cf60e2dd6 + "Add a config option to prevent the driver from grabbing the event + device for exclusive use" (Closes: #380705) + - from commit 2d06ab1d2b3170b6fa44b945376b2ad4cd682af9 + "Allow to easily switch between move and scroll" + (Closes: #411266) + * set Maintaner to the XSF and me as Uploader + + -- Mattia Dongili Thu, 17 May 2007 19:34:22 +0900 + +xfree86-driver-synaptics (0.14.6-2) unstable; urgency=low + + * Build using Debian's xserver-xorg-dev instead of the package's + provided subtree (which is taken from XFree86-4.3.0). + + -- Mattia Dongili Wed, 03 Jan 2007 18:38:40 +0100 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu10) gutsy; urgency=low + + * Bodge around LP: #68370. Something is opening the device even after + we've switched away from the VT. The easiest way to handle this is + to refuse to open unless we're on the current VT. + + -- Matthew Garrett Tue, 09 Oct 2007 21:08:23 +0100 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu9) gutsy; urgency=low + + * Actually include the parameter setting code + + -- Matthew Garrett Mon, 17 Sep 2007 17:56:53 +0100 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu8) gutsy; urgency=low + + * Fix the build system so it works properly on 64-bit platforms + * Add initial support for setting some paramaters via X + + -- Matthew Garrett Sun, 16 Sep 2007 18:18:17 +0100 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu7) feisty; urgency=low + + * X and Y size calculations were inverted for edge scrolling + * Additionally, the values were scaled incorrectly (LP: #95858) + + -- Matthew Garrett Sun, 25 Mar 2007 16:13:42 +0100 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu6) feisty; urgency=low + + * Set defaults based on values the kernel gives us, rather than making stuff + up in an utterly miserable manner. This upload brought to you by Milton + Nero, a deeply flavoured oat stout. + + -- Matthew Garrett Sat, 24 Mar 2007 15:08:26 +0000 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu5) feisty; urgency=low + + * Attempt to set more reasonable defaults for appletouch devices. Better + now, but still not perfect. + + -- Matthew Garrett Sat, 24 Mar 2007 02:44:11 +0000 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu4) feisty; urgency=low + + * debian/control: Update maintainer fields according to debian- + maintainer-field spec. + + -- Martin Pitt Mon, 12 Mar 2007 14:58:39 +0000 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu3) edgy; urgency=low + + * Add the ugly ALPS hack back + + -- Matthew Garrett Mon, 7 Aug 2006 22:55:42 +0100 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu2) edgy; urgency=low + + * Disable Stack Protection for this package for now (module loading fails + due to missing symbols) + + -- Rodrigo Parra Novo Mon, 31 Jul 2006 09:39:16 -0300 + +xserver-xorg-input-synaptics (0.14.6-0ubuntu1) edgy; urgency=low + + * New Upstream release + * New versioned Build-Depends on xserver-xorg-dev (>= 1.1.1) + * New versioned Build-Depends on xserver-xorg-core (>= 1.1.1) + * Fixed debian/rules to match current Makefile rules (based on Debian + xfree86-driver-synaptics package) + * Fixed Makefile and manual page, also based on Debian xfree86-driver-synaptics + * Dropped Alps patches - unfortunately they don't apply anymore + + -- Rodrigo Parra Novo Thu, 27 Jul 2006 12:18:18 -0300 + +xfree86-driver-synaptics (0.14.5-1) unstable; urgency=low + + * New upstream release. + * Motion with pressure sensitivity merged upstream. (Closes: #337350) + + -- Mattia Dongili Sun, 04 Jun 2006 19:45:35 +0200 + +xfree86-driver-synaptics (0.14.4-5) unstable; urgency=low + + * Added m68k to the Achitecture: field. + + -- Mattia Dongili Sun, 30 Apr 2006 20:17:23 +0200 + +xfree86-driver-synaptics (0.14.4-4) unstable; urgency=low + + * Upload to unstable (Closes: #362108) + * Added xorg-driver-synaptics to the Provides, Conflicts, + Replaces list to ease Ubuntu mixed migrations (et al.) + + -- Mattia Dongili Mon, 27 Mar 2006 18:16:48 +0200 + +xfree86-driver-synaptics (0.14.4-3) experimental; urgency=low + + * building the driver as a shared object, this fixes a lintian warning + and better fits in the modular xorg structure. + + -- Mattia Dongili Wed, 22 Mar 2006 22:33:39 +0100 + +xfree86-driver-synaptics (0.14.4-2) experimental; urgency=low + + * New binary package name to follow modular xorg naming. + * Now suggesting the driver's graphical configuration tools. + * README.Debian: removed note about the 2.6.8 patch I removed recently. + + -- Mattia Dongili Sun, 19 Mar 2006 17:01:09 +0100 + +xfree86-driver-synaptics (0.14.4-1) unstable; urgency=low + + * New upstream version. + * Upstream added an -K command line option to syndaemon to ignore + Modifier+AnyKey combos (closes: #335888). + * Removed the rediffed patch against Debian's 2.6.8 kernel. + + -- Mattia Dongili Mon, 07 Nov 2005 22:54:40 +0100 + +xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu4) dapper; urgency=low + + * New versioned Build-Depends on xserver-xorg-dev (>= 1.1.1) + * New versioned Build-Depends on xserver-xorg-core (>= 1.1.1) + + -- Rodrigo Parra Novo Thu, 27 Jul 2006 11:59:57 -0300 + +xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu3) dapper; urgency=low + + * Horrible workaround to avoid massive ALPS-related suckage + + -- Matthew Garrett Wed, 22 Mar 2006 21:02:26 +0000 + +xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu2) dapper; urgency=low + + * Build with -fPIC to fix FTBFS + + -- Tollef Fog Heen Wed, 11 Jan 2006 16:20:57 +0100 + +xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu1) dapper; urgency=low + + * Bounce straight back to 0.14.3. + + -- Daniel Stone Tue, 25 Oct 2005 19:12:28 +1000 + +xorg-driver-synaptics (0.14.3+revertedto+0.13.6-0ubuntu3) breezy; urgency=low + + * Change dependency on xserver-xorg to -core (closes: Ubuntu#15902). + + -- Daniel Stone Fri, 16 Sep 2005 17:42:34 +1000 + +xorg-driver-synaptics (0.14.3+revertedto+0.13.6-0ubuntu2) breezy; urgency=low + + * Build client utilities without includes from Xincludes. + + -- Daniel Stone Wed, 7 Sep 2005 16:31:51 +1000 + +xorg-driver-synaptics (0.14.3+revertedto+0.13.6-0ubuntu1) breezy; urgency=low + + * Revert to old upstream version for preview at least, as the new was quite + catastrophically broken with ALPS. Fixed some, broke a lot (closes: + Ubuntu#14480). + * Backport the confused-finger-count patch. + * Hillariously, dpkg-source ignores deleted files, which means that + Xincludes stuff keeps getting resurrected. Change $(X_INCLUDES_ROOT) to + /usr in include paths (except for the server DDK) to ensure that we get + the installed headers, not the braindead included ones. + + -- Daniel Stone Wed, 7 Sep 2005 11:07:06 +1000 + +xorg-driver-synaptics (0.14.3-1ubuntu2) breezy; urgency=low + + * Move to /usr/lib/xorg for the modular server, and build-depend on + xserver-xorg-dev. + * Switch dependency to xserver-xorg-core, version it. + * Start building synaptics_drv.so instead of .o. + + -- Daniel Stone Fri, 2 Sep 2005 12:08:27 +1000 + +xorg-driver-synaptics (0.14.3-1ubuntu1) breezy; urgency=low + + * Merge with Debian. + * New upstream release: + + Fixes confused finger count with low pressure on some HPs (closes: + Ubuntu#14155). + * Remove unnecessary files in Xincludes from x11proto-core-dev and + x11proto-input-dev. + + -- Daniel Stone Thu, 1 Sep 2005 16:24:27 +1000 + +xfree86-driver-synaptics (0.14.3-1) unstable; urgency=low + + * New upstream version. + * Updated README.Debian to reflect newer X.Org dependencies. + + -- Mattia Dongili Fri, 22 Jul 2005 21:17:37 +0200 + +xfree86-driver-synaptics (0.14.2-2) unstable; urgency=low + + * Can now install together with xorg (closes: #318118). + + -- Mattia Dongili Wed, 29 Jun 2005 12:00:00 +0200 + +xfree86-driver-synaptics (0.14.2-1) unstable; urgency=low + + * New upstream release. + * Added notes to README.Debian to run syndaemon from .xinitrc + (closes: #296485) + + -- Mattia Dongili Wed, 18 May 2005 00:24:14 +0200 + +xfree86-driver-synaptics (0.14.1-1) unstable; urgency=low + + * New upstream release. + + -- Mattia Dongili Tue, 15 Mar 2005 22:33:25 +0100 + +xfree86-driver-synaptics (0.14.0-1) unstable; urgency=low + + * New upstream release: + - now correcly removes the shared memory segment when exiting X + (closes: #263382). + + -- Mattia Dongili (ma.d.) Mon, 10 Jan 2005 21:14:44 +0100 + +xfree86-driver-synaptics (0.13.6-2) unstable; urgency=low + + * Install in /usr/X11R6/lib as all the xfree drivers on 64 bit archs. (closes: #280658) + * Provided an alternative patch for debian-kernel users. (closes: #282359) + + -- Mattia Dongili (ma.d.) Sat, 18 Dec 2004 20:07:15 +0100 + +xfree86-driver-synaptics (0.13.6-1) unstable; urgency=low + + * New upstream. + + -- Mattia Dongili (ma.d.) Sun, 24 Oct 2004 22:14:32 +0200 + +xfree86-driver-synaptics (0.13.5-2) unstable; urgency=low + + * Re-fixed synaptics_drv.o mode to 0644 (why did it reappear?). + + -- Mattia Dongili (ma.d.) Thu, 19 Aug 2004 16:09:56 +0200 + +xfree86-driver-synaptics (0.13.5-1) unstable; urgency=low + + * New upstream. + + -- Mattia Dongili (ma.d.) Sun, 08 Aug 2004 12:41:38 +0200 + +xfree86-driver-synaptics (0.13.4-1) unstable; urgency=low + + * New upstream. + + -- Mattia Dongili (ma.d.) Tue, 27 Jul 2004 22:44:16 +0200 + +xfree86-driver-synaptics (0.13.3-1) unstable; urgency=low + + * New upstream. + * Fixed synaptics_drv.o mode to 0644. + + -- Mattia Dongili (ma.d.) Thu, 20 Jun 2004 15:59:25 +0200 + +xfree86-driver-synaptics (0.13.2-1) unstable; urgency=low + + * New upstream. + * Fixed middle mouse button emulation. (closes: #247219) + * Fixed detection of ALPS touchpads when using a 2.6 linux kernel. (closes: #245682) + * Made double clicks faster. (closes: #248826) + * Uploaded with the correct upstream changelog. (closes: #248811) + + -- Mattia Dongili (ma.d.) Tue, 11 May 2004 09:59:15 +0200 + +xfree86-driver-synaptics (0.13.0-1) unstable; urgency=low + + * New upstream. + + -- Mattia Dongili (ma.d.) Tue, 27 Apr 2004 17:07:26 +0200 + +xfree86-driver-synaptics (0.12.5-1) unstable; urgency=low + + * New upstream. (closes: #239329) + + -- Mattia Dongili (ma.d.) Sat, 20 Mar 2004 12:58:37 +0100 + +xfree86-driver-synaptics (0.12.4-1) unstable; urgency=low + + * Initial Release. (closes: #219163, #231763) + + -- Mattia Dongili (ma.d.) Tue, 2 Mar 2004 11:42:05 +0100 + --- xserver-xorg-input-synaptics-1.9.1.orig/debian/compat +++ xserver-xorg-input-synaptics-1.9.1/debian/compat @@ -0,0 +1 @@ +10 --- xserver-xorg-input-synaptics-1.9.1.orig/debian/control +++ xserver-xorg-input-synaptics-1.9.1/debian/control @@ -0,0 +1,70 @@ +Source: xserver-xorg-input-synaptics +Section: x11 +Priority: optional +Maintainer: Ubuntu X-SWAT +XSBC-Original-Maintainer: Debian X Strike Force +Uploaders: Mattia Dongili , maximilian attems +Build-Depends: + debhelper (>= 10), + libx11-dev, + libxext-dev, + libxi-dev (>= 2:1.2.0), + libevdev-dev [linux-any], + x11proto-core-dev, + xserver-xorg-dev (>= 2:1.12), + pkg-config, + quilt, + xutils-dev (>= 1:7.5+4), + libxtst-dev, +Standards-Version: 4.2.0 +Vcs-Git: https://salsa.debian.org/xorg-team/driver/xserver-xorg-input-synaptics.git +Vcs-Browser: https://salsa.debian.org/xorg-team/driver/xserver-xorg-input-synaptics + +Package: xserver-xorg-input-synaptics +Architecture: any +Depends: + ${shlibs:Depends}, + ${xinpdriver:Depends}, + ${misc:Depends} +Conflicts: xorg-driver-synaptics +Replaces: xorg-driver-synaptics +Provides: ${xinpdriver:Provides}, xorg-driver-synaptics +Suggests: gpointing-device-settings, touchfreeze +Breaks: + kde-config-touchpad (<< 0.8.1-2~), +Description: Synaptics TouchPad driver for X.Org server + This package provides an input driver for the X.Org X server to enable + advanced features of the Synaptics Touchpad including: + . + * Movement with adjustable, non-linear acceleration and speed + * Button events through short touching of the touchpad + * Double-Button events through double short touching of the touchpad + * Dragging through short touching and holding down the finger on the touchpad + * Middle and right button events on the upper and lower corner of the touchpad + * Vertical scrolling (button four and five events) through moving the finger + on the right side of the touchpad + * The up/down button sends button four/five events + * Horizontal scrolling (button six and seven events) through moving the finger + on the lower side of the touchpad + * The multi-buttons send button four/five events, and six/seven events for + horizontal scrolling + * Adjustable finger detection + * Multifinger taps: two finger for middle button and three finger for right + button events. (Needs hardware support. Not all models implement this + feature.) + * Run-time configuration using shared memory. This means you can change + parameter settings without restarting the X server (see synclient(1)). + * It also provides a daemon to disable touchpad while typing at the keyboard + and thus avoid unwanted mouse movements (see syndaemon(1)). + . + +Package: xserver-xorg-input-synaptics-dev +Section: libdevel +Architecture: all +Depends: + x11proto-core-dev, + ${misc:Depends}, +Description: Synaptics TouchPad driver for X.Org server (development headers) + This package contains the development headers for the Synaptics input + driver found in xserver-xorg-input-synaptics. Non-developers likely have + little use for this package. --- xserver-xorg-input-synaptics-1.9.1.orig/debian/copyright +++ xserver-xorg-input-synaptics-1.9.1/debian/copyright @@ -0,0 +1,67 @@ +This package was debianized by Mattia Dongili on +Tue, 2 Mar 2004 11:42:05 +0100. + +It was downloaded from +git://anongit.freedesktop.org/git/xorg/driver/xf86-input-synaptics. +Release tarballs are available at +https://xorg.freedesktop.org/releases/individual/driver/ + +Copyright (c) 1997 C. Scott Ananian +Copyright (c) 1998-2000 Bruce Kalk +Copyright (c) 1999 Henry Davies +Copyright (c) 2001 Stefan Gmeiner +Copyright (c) 2002 Linuxcare Inc. David Kennedy +Copyright (c) 2003 Fred Hucht +Copyright (c) 2003 Neil Brown +Copyright (c) 2003 Jörg Bösner +Copyright (c) 2003 Hartwig Felger +Copyright (c) 2002-2007 Peter Osterlund +Copyright (c) 2004 Arne Schwabe +Copyright (c) 2004 Matthias Ihmig +Copyright (c) 2004 Alexei Gilchrist +Copyright (c) 2006-2007 Christian Thaeter +Copyright (c) 2006 Stefan Bethge +Copyright (c) 2007 Joseph P. Skudlarek +Copyright (c) 2007 Florian Loitsch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Some patches included in the Debian package are licensed differently: + debian/patches/01-synaptics-dont-grab-if-not-on-current-VT.patch + debian/patches/02-autodetect-resolution.patch + +Copyright (c) Matthew Garrett + + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991. + + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + MA 02110-1301, USA. + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL-2'. + --- xserver-xorg-input-synaptics-1.9.1.orig/debian/gbp.conf +++ xserver-xorg-input-synaptics-1.9.1/debian/gbp.conf @@ -0,0 +1,3 @@ +[DEFAULT] +debian-branch=ubuntu + --- xserver-xorg-input-synaptics-1.9.1.orig/debian/local/51-synaptics-quirks.conf +++ xserver-xorg-input-synaptics-1.9.1/debian/local/51-synaptics-quirks.conf @@ -0,0 +1,23 @@ +Section "InputClass" + Identifier "Dell Inspiron embedded buttons quirks" + MatchTag "inspiron_1011|inspiron_1012" + MatchDevicePath "/dev/input/event*" + Driver "synaptics" + Option "JumpyCursorThreshold" "90" +EndSection + +Section "InputClass" + Identifier "Dell Inspiron quirks" + MatchTag "inspiron_1120" + MatchDevicePath "/dev/input/event*" + Driver "synaptics" + Option "JumpyCursorThreshold" "250" +EndSection + +Section "InputClass" + Identifier "HP Mininote quirks" + MatchTag "mininote_1000" + MatchDevicePath "/dev/input/event*" + Driver "synaptics" + Option "JumpyCursorThreshold" "20" +EndSection --- xserver-xorg-input-synaptics-1.9.1.orig/debian/local/66-xorg-synaptics-quirks.rules +++ xserver-xorg-input-synaptics-1.9.1/debian/local/66-xorg-synaptics-quirks.rules @@ -0,0 +1,13 @@ +ACTION!="add|change", GOTO="xorg_synaptics_quirks_end" +KERNEL!="event*", GOTO="xorg_synaptics_quirks_end" + +ENV{ID_INPUT_TOUCHPAD}!="1", GOTO="xorg_synaptics_quirks_end" + +# Placeholder for platform specific quirks needing +# ID_INPUT.tags to be set. +ATTR{[dmi/id]product_name}=="Inspiron 1011", ENV{ID_INPUT.tags}="inspiron_1011" +ATTR{[dmi/id]product_name}=="Inspiron 1012", ENV{ID_INPUT.tags}="inspiron_1012" +ATTR{[dmi/id]product_name}=="Inspiron 1120", ENV{ID_INPUT.tags}="inspiron_1120" +ATTR{[dmi/id]product_name}=="HP MiniNote 1000", ENV{ID_INPUT.tags}="mininote_1000" + +LABEL="xorg_synaptics_quirks_end" --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/02-do-not-use-synaptics-for-keyboards.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/02-do-not-use-synaptics-for-keyboards.patch @@ -0,0 +1,23 @@ +do not use the synaptics driver for devices advertising themselves as keyboards +http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=524130 + +Index: xserver-xorg-input-synaptics-1.9.1/conf/11-x11-synaptics.fdi +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/conf/11-x11-synaptics.fdi ++++ xserver-xorg-input-synaptics-1.9.1/conf/11-x11-synaptics.fdi +@@ -9,6 +9,7 @@ + + + ++ + synaptics + +@@ -35,6 +36,7 @@ + + 0 0 0 0 0 0 0 0 + ++ + + + --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/03-bug-return-val.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/03-bug-return-val.patch @@ -0,0 +1,14 @@ +Index: xserver-xorg-input-synaptics-1.9.1/src/synaptics.c +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/src/synaptics.c ++++ xserver-xorg-input-synaptics-1.9.1/src/synaptics.c +@@ -2639,7 +2639,9 @@ clickpad_guess_clickfingers(SynapticsPri + uint32_t close_point = 0; /* 1 bit for each point close to another one */ + int i, j; + ++#ifdef BUG_RETURN_VAL + BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); ++#endif + + for (i = 0; i < hw->num_mt_mask - 1; i++) { + ValuatorMask *f1; --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/101_resolution_detect_option.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/101_resolution_detect_option.patch @@ -0,0 +1,141 @@ +Description: Provide an option to prevent synaptics from communicating the touchpad size to the xserver. +Bug-Ubuntu: https://launchpad.net/bugs/327428 + +Index: xserver-xorg-input-synaptics-1.9.1/include/synaptics-properties.h +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/include/synaptics-properties.h ++++ xserver-xorg-input-synaptics-1.9.1/include/synaptics-properties.h +@@ -134,6 +134,9 @@ + #define SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR "Synaptics Pressure Motion Factor" + + /* 8 bit (BOOL) */ ++#define SYNAPTICS_PROP_RESOLUTION_DETECT "Synaptics Resolution Detect" ++ ++/* 8 bit (BOOL) */ + #define SYNAPTICS_PROP_GRAB "Synaptics Grab Event Device" + + /* 8 bit (BOOL), 1 value, tap-and-drag */ +Index: xserver-xorg-input-synaptics-1.9.1/man/synaptics.man +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/man/synaptics.man ++++ xserver-xorg-input-synaptics-1.9.1/man/synaptics.man +@@ -427,6 +427,18 @@ The gesture is enabled by default and ca + TapAndDragGesture option to false. Property: "Synaptics Gestures" + . + .TP ++.BI "Option \*ResolutionDetect\*q \*q" boolean \*q ++Allow or prevent the synaptics driver from reporting the size of the ++touchpad to the X server. The X server normally uses this information ++to scale movements so that touchpad movement corresponds visually to ++mouse cursor movements on the screen. However, in some rare cases where ++the touchpad height/width ratio is significantly different from the ++laptop, it can cause the mouse cursor to skip pixels in the X or Y axis. ++This option allows disabling this scaling behavior, which can provide ++smoother mouse movement in such cases. ++Property: "Synaptics Resolution Detect" ++. ++.TP + .BI "Option \*qVertResolution\*q \*q" integer \*q + Resolution of X coordinates in units/millimeter. The value is used + together with HorizResolution to compensate unequal vertical and +@@ -880,6 +892,10 @@ FLOAT, 2 values, speed, friction. + FLOAT, 2 values, min, max. + + .TP 7 ++.BI "Synaptics Resolution Detect" ++8 bit (BOOL). ++ ++.TP 7 + .BI "Synaptics Grab Event Device" + 8 bit (BOOL). + +Index: xserver-xorg-input-synaptics-1.9.1/src/properties.c +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/src/properties.c ++++ xserver-xorg-input-synaptics-1.9.1/src/properties.c +@@ -85,6 +85,7 @@ Atom prop_palm_dim = 0; + Atom prop_coastspeed = 0; + Atom prop_pressuremotion = 0; + Atom prop_pressuremotion_factor = 0; ++Atom prop_resolution_detect = 0; + Atom prop_grab = 0; + Atom prop_gestures = 0; + Atom prop_capabilities = 0; +@@ -345,6 +346,10 @@ InitDeviceProperties(InputInfoPtr pInfo) + InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 2, + fvalues); + ++ prop_resolution_detect = ++ InitAtom(pInfo->dev, SYNAPTICS_PROP_RESOLUTION_DETECT, 8, 1, ++ ¶->resolution_detect); ++ + prop_grab = + InitAtom(pInfo->dev, SYNAPTICS_PROP_GRAB, 8, 1, + ¶->grab_event_device); +@@ -747,6 +752,12 @@ SetProperty(DeviceIntPtr dev, Atom prope + para->press_motion_min_factor = press[0]; + para->press_motion_max_factor = press[1]; + } ++ else if (property == prop_resolution_detect) { ++ if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) ++ return BadMatch; ++ ++ para->resolution_detect = *(BOOL*)prop->data; ++ } + else if (property == prop_grab) { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; +Index: xserver-xorg-input-synaptics-1.9.1/src/synaptics.c +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/src/synaptics.c ++++ xserver-xorg-input-synaptics-1.9.1/src/synaptics.c +@@ -742,6 +742,7 @@ set_default_parameters(InputInfoPtr pInf + xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ); + pars->press_motion_max_z = + xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ); ++ pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE); + + pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4); + pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7); +@@ -1290,7 +1291,7 @@ DeviceInit(DeviceIntPtr dev) + } + + /* X valuator */ +- if (priv->minx < priv->maxx) { ++ if (priv->minx < priv->maxx && priv->synpara.resolution_detect) { + min = priv->minx; + max = priv->maxx; + } +@@ -1305,7 +1306,7 @@ DeviceInit(DeviceIntPtr dev) + xf86InitValuatorDefaults(dev, 0); + + /* Y valuator */ +- if (priv->miny < priv->maxy) { ++ if (priv->miny < priv->maxy && priv->synpara.resolution_detect) { + min = priv->miny; + max = priv->maxy; + } +Index: xserver-xorg-input-synaptics-1.9.1/src/synapticsstr.h +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/src/synapticsstr.h ++++ xserver-xorg-input-synaptics-1.9.1/src/synapticsstr.h +@@ -222,6 +222,7 @@ typedef struct _SynapticsParameters { + int press_motion_max_z; /* finger pressure at which maximum pressure motion factor is applied */ + double press_motion_min_factor; /* factor applied on speed when finger pressure is at minimum */ + double press_motion_max_factor; /* factor applied on speed when finger pressure is at minimum */ ++ Bool resolution_detect; /* report pad size to xserver? */ + Bool grab_event_device; /* grab event device for exclusive use? */ + Bool tap_and_drag_gesture; /* Switches the tap-and-drag gesture on/off */ + unsigned int resolution_horiz; /* horizontal resolution of touchpad in units/mm */ +Index: xserver-xorg-input-synaptics-1.9.1/tools/synclient.c +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/tools/synclient.c ++++ xserver-xorg-input-synaptics-1.9.1/tools/synclient.c +@@ -130,6 +130,7 @@ static struct Parameter params[] = { + {"PressureMotionMaxZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 1}, + {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 0}, + {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 1}, ++ {"ResolutionDetect", PT_BOOL, 0, 1, SYNAPTICS_PROP_RESOLUTION_DETECT, 8, 0}, + {"GrabEventDevice", PT_BOOL, 0, 1, SYNAPTICS_PROP_GRAB, 8, 0}, + {"TapAndDragGesture", PT_BOOL, 0, 1, SYNAPTICS_PROP_GESTURES, 8, 0}, + {"AreaLeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 0}, --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/103_enable_cornertapping.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/103_enable_cornertapping.patch @@ -0,0 +1,18 @@ +Description: Enable right/middle clicks by tapping in the bottom- and top-right corners. +Bug-Ubuntu: https://launchpad.net/bugs/320632 + +Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +=================================================================== +--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c ++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +@@ -717,8 +717,8 @@ set_default_parameters(InputInfoPtr pInf + + 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[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2); ++ pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3); + 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); --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/104_always_enable_tapping.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/104_always_enable_tapping.patch @@ -0,0 +1,22 @@ +Description: Enable tapping even when a physical left button is available. +Bug-Ubuntu: https://launchpad.net/bugs/320585 + +Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +=================================================================== +--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c ++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +@@ -622,10 +622,10 @@ set_default_parameters(InputInfoPtr pInf + 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 tap */ ++ tapButton1 = 1; ++ tapButton2 = 3; ++ tapButton3 = 2; + + /* Enable multifinger-click if only have one physical button, + otherwise clickFinger is always button 1. */ --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/106_always_enable_vert_edge_scroll.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/106_always_enable_vert_edge_scroll.patch @@ -0,0 +1,18 @@ +Description: Enable vertical edge tapping by default even if we enable two-finger scrolling as well. +Bug-Ubuntu: https://launchpad.net/bugs/320632 + +Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +=================================================================== +--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c ++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +@@ -633,8 +633,8 @@ set_default_parameters(InputInfoPtr pInf + 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; ++ /* Enable vert edge scroll */ ++ vertEdgeScroll = TRUE; + horizEdgeScroll = FALSE; + + /* Enable twofinger scroll if we can detect doubletap */ --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/115_evdev_only.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/115_evdev_only.patch @@ -0,0 +1,16 @@ +Description: Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback. +Bug-Ubuntu: https://launchpad.net/bugs/624985 + +Index: xserver-xorg-input-synaptics-1.8.2/conf/70-synaptics.conf +=================================================================== +--- xserver-xorg-input-synaptics-1.8.2.orig/conf/70-synaptics.conf ++++ xserver-xorg-input-synaptics-1.8.2/conf/70-synaptics.conf +@@ -14,7 +14,7 @@ Section "InputClass" + # This option is recommend on all Linux systems using evdev, but cannot be + # enabled by default. See the following link for details: + # http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html +-# MatchDevicePath "/dev/input/event*" ++ MatchDevicePath "/dev/input/event*" + EndSection + + Section "InputClass" --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/118_quell_error_msg.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/118_quell_error_msg.patch @@ -0,0 +1,33 @@ +Description: Don't print error "Unable to find a synaptics device" when used on system that doesn't have a touchpad anyway. +Bug-Ubuntu: https://launchpad.net/bugs/716712 + +Index: xserver-xorg-input-synaptics-1.9.1/tools/synclient.c +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/tools/synclient.c ++++ xserver-xorg-input-synaptics-1.9.1/tools/synclient.c +@@ -298,9 +298,7 @@ dp_get_device(Display * dpy) + unwind: + XFree(properties); + XFreeDeviceList(info); +- if (!dev) +- fprintf(stderr, "Unable to find a synaptics device.\n"); +- else if (error && dev) { ++ if (error && dev) { + XCloseDevice(dpy, dev); + dev = NULL; + } +Index: xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/tools/syndaemon.c ++++ xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c +@@ -553,9 +553,7 @@ dp_get_device(Display * dpy) + unwind: + XFree(properties); + XFreeDeviceList(info); +- if (!dev) +- fprintf(stderr, "Unable to find a synaptics device.\n"); +- else if (error && dev) { ++ if (error && dev) { + XCloseDevice(dpy, dev); + dev = NULL; + } --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/124_syndaemon_events.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/124_syndaemon_events.patch @@ -0,0 +1,43 @@ +From b68ca129acfe56714a79230aa07c2b4b0249fb42 Mon Sep 17 00:00:00 2001 +From: Chase Douglas +Date: Fri, 15 Apr 2011 15:23:17 -0400 +Subject: [PATCH xf86-input-synaptics] Drain XRecord connection of any events after handling replies + +If the X server sends an event to the XRecord connection the event +will never be handled. This will cause the event queue to fill up in +Xlib and lead to syndaemon running away at 100% cpu usage. + +This change drains any events from the connection. It's not a fix for +the underlying bug in the server or Xlib, but it does paper over the +issue for now. + +https://bugs.launchpad.net/bugs/754470 +http://bugs.freedesktop.org/show_bug.cgi?id=31921 + +Signed-off-by: Chase Douglas +--- +I'm offering this up as a work around for whatever real bug exists. It +probably should not be committed upstream though. + + tools/syndaemon.c | 8 ++++++++ + 1 files changed, 8 insertions(+), 0 deletions(-) + +Index: xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c +=================================================================== +--- xserver-xorg-input-synaptics-1.9.1.orig/tools/syndaemon.c ++++ xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c +@@ -466,6 +466,14 @@ record_main_loop(Display * display, doub + event.type); + } + ++ /* If there are any events left over, they are in error. Drain them ++ * from the connection queue so we don't get stuck. */ ++ while (XEventsQueued(dpy_data, QueuedAlready) > 0) { ++ XEvent event; ++ XNextEvent(dpy_data, &event); ++ fprintf(stderr, "bad event received, major opcode %d\n", event.type); ++ } ++ + if (!ignore_modifier_keys && cbres.key_event) { + disable_event = 1; + } --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/128_disable_three_click_action.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/128_disable_three_click_action.patch @@ -0,0 +1,16 @@ +Description: Disable three touch click in favor of uTouch gestures. +Bug-Ubuntu: https://launchpad.net/bugs/961725 + +Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +=================================================================== +--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c ++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +@@ -631,7 +631,7 @@ set_default_parameters(InputInfoPtr pInf + 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; ++ clickFinger3 = 0; /* Disabled by default so three-touch gestures work */ + + /* Enable vert edge scroll */ + vertEdgeScroll = TRUE; --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/129_disable_three_touch_tap.patch +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/129_disable_three_touch_tap.patch @@ -0,0 +1,16 @@ +Description: Disable three touch tap in favor of uTouch gestures. +Bug-Ubuntu: https://launchpad.net/bugs/961725 + +Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +=================================================================== +--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c ++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c +@@ -625,7 +625,7 @@ set_default_parameters(InputInfoPtr pInf + /* Enable tap */ + tapButton1 = 1; + tapButton2 = 3; +- tapButton3 = 2; ++ tapButton3 = 0; /* Disabled by default so three-touch gestures work */ + + /* Enable multifinger-click if only have one physical button, + otherwise clickFinger is always button 1. */ --- xserver-xorg-input-synaptics-1.9.1.orig/debian/patches/series +++ xserver-xorg-input-synaptics-1.9.1/debian/patches/series @@ -0,0 +1,11 @@ +02-do-not-use-synaptics-for-keyboards.patch +03-bug-return-val.patch +101_resolution_detect_option.patch +103_enable_cornertapping.patch +104_always_enable_tapping.patch +106_always_enable_vert_edge_scroll.patch +115_evdev_only.patch +118_quell_error_msg.patch +124_syndaemon_events.patch +128_disable_three_click_action.patch +129_disable_three_touch_tap.patch --- xserver-xorg-input-synaptics-1.9.1.orig/debian/rules +++ xserver-xorg-input-synaptics-1.9.1/debian/rules @@ -0,0 +1,28 @@ +#!/usr/bin/make -f + +# Install in debian/tmp to retain control through dh_install: +override_dh_auto_install: + dh_auto_install --destdir=debian/tmp + +# Kill *.la files, and forget no-one: +override_dh_install: + find debian/tmp -name '*.la' -delete + dh_install --fail-missing + +# That's a plugin, use appropriate warning level: +override_dh_shlibdeps: + dh_shlibdeps -- --warnings=6 + +%: + dh $@ --with quilt,xsf --builddirectory=build/ + +# Debug package: +override_dh_strip: + dh_strip --dbgsym-migration='xserver-xorg-input-synaptics-dbg (<< 1.8.3-2~)' + +override_dh_auto_configure: + dh_auto_configure -- \ + --libdir=\$${exec_prefix}/lib + +override_dh_auto_test: + dh_auto_test -- VERBOSE=1 --- xserver-xorg-input-synaptics-1.9.1.orig/debian/upstream/signing-key.asc +++ xserver-xorg-input-synaptics-1.9.1/debian/upstream/signing-key.asc @@ -0,0 +1,24 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQGiBD8b0wYRBACph9kRJmP+4+JGsCgFlFoy4vFO0DCG+jmkQN0n1wdInt/N/UtA +sZToO72AUmfmYizA+IEbzBrx0UnUo3w3BDmHxUWf/akZiPUz9AA/YFY4xC3MY2OK +VN2Jz6YSce4zJ5jd2ZRobHm4HuIf/8yqSCcsv7FNfrLaTNIFRs5gYYsqZwCgwmkp +RSLRc8WAnHrTWNQDaEFM2rUEAKTjrTjMN8+KGd0BxNX7HiTSqQP++nXNwAYs1oWB +Yt82YHj9SvRCqCzD1pzJQivYnlNoWDza1VeMnfdAvkdia8z4lYbO/RunXZJvra3Z +VDm+izq+uwUAyvFuEYnNz09VSqwXKT6+XW0Xtz2vHq52r6DS6mK8cGJHZ5OhrRjq +UEYxA/9STh+QfA98xtNoRcf52E/46r7IpCj440oRVc9lMfxQZrLGQNqp7sPdIhGQ +CCo2NUII5hkhdAG71kpbfSXU4Sh32p1cU1KYCAkDFfb49bKuAs+Pff8v6FGZxTdd +AinPZr4BbsYJatk818aTCnu0+s7L8jL5GPfeyuyEMKwzVBx2mLQpUGV0ZXIgSHV0 +dGVyZXIgKFdoby1UKSA8b2ZmaWNlQHdoby10Lm5ldD6IWQQTEQIAGQUCPxvTBgQL +BwMCAxUCAwMWAgECHgECF4AACgkQ4jt+cLRn8L/0RACfWo3KTMUg+uPRqA6RXxk0 +4CWjXaMAoJeIxOpZLB3RBltPnSi7PyVQIkHFuQENBD8b0wgQBACTnqOYOWYVR8O1 +D73J6nbdAeZCbXrUkyXIuyqBOdKmX/0QJmSs7Wfsa+hPfDhj6ai0Gs2C8Qg/0Pzk +86b4p9DLkf0M6RaYjUtCJBpS59xrsV6nz6xZzQa4RRdf1YJmw2tia1MMXzxbwQU2 +bKpYEm8NsGaBURMGd02EvsMN2wI2uwADBQP/e9MjVr/85XDzAlUBN8HwYW5szTyP +8ZVcQOqOmNebkTWGLvkPrBdXmxpzrWjxPolO1WcWQjUL0bN/rbdqefT65iVHJHJZ +/cpTtgGlCPhL5JTA50ltd0I13CABYWLFmswonXES/6tcglf4rr3Nri2sOrY5HggP +ipEzOo5vdKOow/qIRgQYEQIABgUCPxvTCAAKCRDiO35wtGfwv68jAKCDvL2gkrg1 +4NfV7rNc057K1nL2GgCeKApWRgGVzaOkAp0P5tQulaDD6zM= +=7uBX +-----END PGP PUBLIC KEY BLOCK----- --- xserver-xorg-input-synaptics-1.9.1.orig/debian/watch +++ xserver-xorg-input-synaptics-1.9.1/debian/watch @@ -0,0 +1,4 @@ +#git=git://anongit.freedesktop.org/xorg/driver/xf86-input-synaptics +version=3 +opts="pgpsigurlmangle=s/$/.sig/" \ +https://xorg.freedesktop.org/releases/individual/driver/ xf86-input-synaptics-(.*)\.tar\.gz --- xserver-xorg-input-synaptics-1.9.1.orig/debian/xserver-xorg-input-synaptics-dev.install +++ xserver-xorg-input-synaptics-1.9.1/debian/xserver-xorg-input-synaptics-dev.install @@ -0,0 +1,2 @@ +usr/include/xorg/* +usr/lib/pkgconfig/xorg-synaptics.pc --- xserver-xorg-input-synaptics-1.9.1.orig/debian/xserver-xorg-input-synaptics.README.Debian +++ xserver-xorg-input-synaptics-1.9.1/debian/xserver-xorg-input-synaptics.README.Debian @@ -0,0 +1,98 @@ +xserver-xorg-input-synaptics for Debian +--------------------------------------- + +This package contains the Synaptics TouchPad driver for X.Org X server. +See also the README file for details on configuration options. + + +Runtime configuration +--------------------- + +The synclient and xinput tools allow configuring various aspects of the +driver's behaviour. See the synaptics(4) man page for details of the +configurable options. + + +Manual configuration +-------------------- + +1. Load the driver by changing the X.Org configuration file +(/etc/X11/xorg.conf) through adding the line 'Load +"synaptics"' in the module section. + +2. Add/Replace in the InputDevice section for the touchpad the +following lines: + +Section "InputDevice" + Driver "synaptics" + Identifier "touchpad" + Option "Device" "/dev/psaux" + Option "Protocol" "auto-dev" +# +# Following lines are optional thus commented in this example. +# Please read the synaptics(4) manpage for a detailed explanation +# of the various options' meaning as well as a full list of options +# +# Option "LeftEdge" "1700" +# Option "RightEdge" "5300" +# Option "TopEdge" "1700" +# Option "BottomEdge" "4200" +# Option "FingerLow" "25" +# Option "FingerHigh" "30" +# Option "MaxTapTime" "180" +# Option "MaxTapMove" "220" +# Option "VertScrollDelta" "100" +# Option "MinSpeed" "0.06" +# Option "MaxSpeed" "0.12" +# Option "AccelFactor" "0.0010" +# Option "Repeater" "/dev/ps2mouse" +EndSection + +Change the Identifier to the same name as in the ServerLayout section. +The Option "Repeater" is at the moment for testing. + +3. Add the "CorePointer" option to the InputDevice line at the ServerLayout section: + +Section "ServerLayout" +... + InputDevice "touchpad" "CorePointer" + InputDevice "USB Mouse" "SendCoreEvents" +... + + Note! You can not have more than one core pointer, so if you want + to use an external mouse too, you have to set all mouse input + devices except one to "AlwaysCore" instead of "CorePointer". + +4. Start/Restart the X Server. If the touchpad doesn't work: + a) Check the X.Org Logfiles + b) Try to start the XServer with 'startx -- -logverbose 8' for more + Output + +Other relevant information to use this driver +--------------------------------------------- + +1. For use with kernel 2.6.x you need to enable synaptics touchpad + support when configuring the kernel (CONFIG_MOUSE_PS2). You also + need support for the evdev interface (CONFIG_INPUT_EVDEV). If you + compile evdev as a module, make sure it is loaded before starting + the X server, as it will not be auto-loaded. + +2. For use with an ALPS touchpad, you need to use a 2.6.x kernel and + apply the alps patch. See the README.alps file for more details. + Note! If you use kernel 2.6.11 or later, the alps patch is already + included, so you don't have to patch your kernel. + +3. Using with kernel 2.4.x for x < 10 needs a kernel patch + (pc_keyb.c.diff.2.4.3). + +Running 'syndaemon' automatically +--------------------------------- +The easiest way to run the syndaemon binary each time you startup X you can +simply add the following line to your ~/.xinitrc file: + +/usr/bin/syndaemon -i 1 -d + +Customize the daemon options to fit your needs, the only mandatory option is +'-d' to run syndaemon in the background. See 'man 1 syndaemon' for more. + + -- Mattia Dongili , Mon, 11 May 2009 23:04:40 +0900 --- xserver-xorg-input-synaptics-1.9.1.orig/debian/xserver-xorg-input-synaptics.docs +++ xserver-xorg-input-synaptics-1.9.1/debian/xserver-xorg-input-synaptics.docs @@ -0,0 +1,3 @@ +README +docs/README.alps +docs/trouble-shooting.txt --- xserver-xorg-input-synaptics-1.9.1.orig/debian/xserver-xorg-input-synaptics.install +++ xserver-xorg-input-synaptics-1.9.1/debian/xserver-xorg-input-synaptics.install @@ -0,0 +1,6 @@ +usr/lib/xorg/modules/input/*.so +usr/bin/* +usr/share/man +usr/share/X11 +debian/local/51-synaptics-quirks.conf usr/share/X11/xorg.conf.d +debian/local/66-xorg-synaptics-quirks.rules lib/udev/rules.d --- xserver-xorg-input-synaptics-1.9.1.orig/debian/xserver-xorg-input-synaptics.links +++ xserver-xorg-input-synaptics-1.9.1/debian/xserver-xorg-input-synaptics.links @@ -0,0 +1 @@ +usr/share/bug/xserver-xorg-core/script usr/share/bug/xserver-xorg-input-synaptics/script --- xserver-xorg-input-synaptics-1.9.1.orig/include/synaptics-properties.h +++ xserver-xorg-input-synaptics-1.9.1/include/synaptics-properties.h @@ -134,6 +134,9 @@ #define SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR "Synaptics Pressure Motion Factor" /* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_RESOLUTION_DETECT "Synaptics Resolution Detect" + +/* 8 bit (BOOL) */ #define SYNAPTICS_PROP_GRAB "Synaptics Grab Event Device" /* 8 bit (BOOL), 1 value, tap-and-drag */ --- xserver-xorg-input-synaptics-1.9.1.orig/man/synaptics.man +++ xserver-xorg-input-synaptics-1.9.1/man/synaptics.man @@ -427,6 +427,18 @@ TapAndDragGesture option to false. Property: "Synaptics Gestures" . .TP +.BI "Option \*ResolutionDetect\*q \*q" boolean \*q +Allow or prevent the synaptics driver from reporting the size of the +touchpad to the X server. The X server normally uses this information +to scale movements so that touchpad movement corresponds visually to +mouse cursor movements on the screen. However, in some rare cases where +the touchpad height/width ratio is significantly different from the +laptop, it can cause the mouse cursor to skip pixels in the X or Y axis. +This option allows disabling this scaling behavior, which can provide +smoother mouse movement in such cases. +Property: "Synaptics Resolution Detect" +. +.TP .BI "Option \*qVertResolution\*q \*q" integer \*q Resolution of X coordinates in units/millimeter. The value is used together with HorizResolution to compensate unequal vertical and @@ -880,6 +892,10 @@ FLOAT, 2 values, min, max. .TP 7 +.BI "Synaptics Resolution Detect" +8 bit (BOOL). + +.TP 7 .BI "Synaptics Grab Event Device" 8 bit (BOOL). --- xserver-xorg-input-synaptics-1.9.1.orig/src/properties.c +++ xserver-xorg-input-synaptics-1.9.1/src/properties.c @@ -85,6 +85,7 @@ Atom prop_coastspeed = 0; Atom prop_pressuremotion = 0; Atom prop_pressuremotion_factor = 0; +Atom prop_resolution_detect = 0; Atom prop_grab = 0; Atom prop_gestures = 0; Atom prop_capabilities = 0; @@ -345,6 +346,10 @@ InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 2, fvalues); + prop_resolution_detect = + InitAtom(pInfo->dev, SYNAPTICS_PROP_RESOLUTION_DETECT, 8, 1, + ¶->resolution_detect); + prop_grab = InitAtom(pInfo->dev, SYNAPTICS_PROP_GRAB, 8, 1, ¶->grab_event_device); @@ -747,6 +752,12 @@ para->press_motion_min_factor = press[0]; para->press_motion_max_factor = press[1]; } + else if (property == prop_resolution_detect) { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + para->resolution_detect = *(BOOL*)prop->data; + } else if (property == prop_grab) { if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) return BadMatch; --- xserver-xorg-input-synaptics-1.9.1.orig/src/synaptics.c +++ xserver-xorg-input-synaptics-1.9.1/src/synaptics.c @@ -622,19 +622,19 @@ 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 tap */ + tapButton1 = 1; + tapButton2 = 3; + tapButton3 = 0; /* Disabled by default so three-touch gestures work */ /* 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; + clickFinger3 = 0; /* Disabled by default so three-touch gestures work */ - /* Enable vert edge scroll if we can't detect doubletap */ - vertEdgeScroll = priv->has_double ? FALSE : TRUE; + /* Enable vert edge scroll */ + vertEdgeScroll = TRUE; horizEdgeScroll = FALSE; /* Enable twofinger scroll if we can detect doubletap */ @@ -717,8 +717,8 @@ 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[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2); + pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3); 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); @@ -742,6 +742,7 @@ xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ); pars->press_motion_max_z = xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ); + pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE); pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4); pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7); @@ -1290,7 +1291,7 @@ } /* X valuator */ - if (priv->minx < priv->maxx) { + if (priv->minx < priv->maxx && priv->synpara.resolution_detect) { min = priv->minx; max = priv->maxx; } @@ -1305,7 +1306,7 @@ xf86InitValuatorDefaults(dev, 0); /* Y valuator */ - if (priv->miny < priv->maxy) { + if (priv->miny < priv->maxy && priv->synpara.resolution_detect) { min = priv->miny; max = priv->maxy; } @@ -2639,7 +2640,9 @@ uint32_t close_point = 0; /* 1 bit for each point close to another one */ int i, j; +#ifdef BUG_RETURN_VAL BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0); +#endif for (i = 0; i < hw->num_mt_mask - 1; i++) { ValuatorMask *f1; --- xserver-xorg-input-synaptics-1.9.1.orig/src/synapticsstr.h +++ xserver-xorg-input-synaptics-1.9.1/src/synapticsstr.h @@ -222,6 +222,7 @@ int press_motion_max_z; /* finger pressure at which maximum pressure motion factor is applied */ double press_motion_min_factor; /* factor applied on speed when finger pressure is at minimum */ double press_motion_max_factor; /* factor applied on speed when finger pressure is at minimum */ + Bool resolution_detect; /* report pad size to xserver? */ Bool grab_event_device; /* grab event device for exclusive use? */ Bool tap_and_drag_gesture; /* Switches the tap-and-drag gesture on/off */ unsigned int resolution_horiz; /* horizontal resolution of touchpad in units/mm */ --- xserver-xorg-input-synaptics-1.9.1.orig/tools/synclient.c +++ xserver-xorg-input-synaptics-1.9.1/tools/synclient.c @@ -130,6 +130,7 @@ {"PressureMotionMaxZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 1}, {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 0}, {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 1}, + {"ResolutionDetect", PT_BOOL, 0, 1, SYNAPTICS_PROP_RESOLUTION_DETECT, 8, 0}, {"GrabEventDevice", PT_BOOL, 0, 1, SYNAPTICS_PROP_GRAB, 8, 0}, {"TapAndDragGesture", PT_BOOL, 0, 1, SYNAPTICS_PROP_GESTURES, 8, 0}, {"AreaLeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 0}, @@ -297,9 +298,7 @@ unwind: XFree(properties); XFreeDeviceList(info); - if (!dev) - fprintf(stderr, "Unable to find a synaptics device.\n"); - else if (error && dev) { + if (error && dev) { XCloseDevice(dpy, dev); dev = NULL; } --- xserver-xorg-input-synaptics-1.9.1.orig/tools/syndaemon.c +++ xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c @@ -466,6 +466,14 @@ event.type); } + /* If there are any events left over, they are in error. Drain them + * from the connection queue so we don't get stuck. */ + while (XEventsQueued(dpy_data, QueuedAlready) > 0) { + XEvent event; + XNextEvent(dpy_data, &event); + fprintf(stderr, "bad event received, major opcode %d\n", event.type); + } + if (!ignore_modifier_keys && cbres.key_event) { disable_event = 1; } @@ -553,9 +561,7 @@ unwind: XFree(properties); XFreeDeviceList(info); - if (!dev) - fprintf(stderr, "Unable to find a synaptics device.\n"); - else if (error && dev) { + if (error && dev) { XCloseDevice(dpy, dev); dev = NULL; }