/*
 * Copyright 1995-2002 by Frederic Lepied, France. <Lepied@XFree86.org>
 * Copyright 2002-2010 by Ping Cheng, Wacom. <pingc@wacom.com>
 *
 * This program 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <config.h>

#include <unistd.h>
#include "xf86Wacom.h"
#include "Xwacom.h"
#include "wcmFilter.h"
#include "wcmTouchFilter.h"
#include <xkbsrv.h>
#include <xf86_OSproc.h>

#ifdef ENABLE_TESTS
#include "wacom-test-suite.h"
#endif

static struct _WacomDriverRec
{
	WacomDevicePtr active;     /* Arbitrate motion through this pointer */
} WACOM_DRIVER = {
	.active = NULL,
};

void wcmRemoveActive(WacomDevicePtr priv)
{
	if (WACOM_DRIVER.active == priv)
		WACOM_DRIVER.active = NULL;
}

/*****************************************************************************
 * Static functions
 ****************************************************************************/

static int applyPressureCurve(WacomDevicePtr pDev, const WacomDeviceStatePtr pState);
static void commonDispatchDevice(WacomDevicePtr priv,
				 const WacomChannelPtr pChannel);
static void sendAButton(WacomDevicePtr pDev, const WacomDeviceState* ds, int button,
			int mask, const WacomAxisData *axes);

/*****************************************************************************
 * Utility functions
 ****************************************************************************/

/**
 * @return TRUE if the device is set to abolute mode, or FALSE otherwise
 */
Bool is_absolute(WacomDevicePtr priv)
{
	return !!(priv->flags & ABSOLUTE_FLAG);
}

/**
 * Set the device to absolute or relative mode
 *
 * @param absolute TRUE to set the device to absolute mode.
 */
void set_absolute(WacomDevicePtr priv, Bool absolute)
{
	if (absolute)
		priv->flags |= ABSOLUTE_FLAG;
	else
		priv->flags &= ~ABSOLUTE_FLAG;
}

/*****************************************************************************
* wcmDevSwitchModeCall --
*****************************************************************************/

Bool wcmDevSwitchModeCall(WacomDevicePtr priv, Bool absolute)
{
	DBG(3, priv, "to mode=%s\n", absolute ? "absolute" : "relative");

	/* Pad is always in absolute mode.*/
	if (IsPad(priv))
		return absolute;
	else
		set_absolute(priv, absolute);
	return TRUE;
}

static int wcmButtonPerNotch(WacomDevicePtr priv, int value, int threshold, int btn_positive, int btn_negative)
{
	int mode = is_absolute(priv);
	int notches = value / threshold;
	int button = (notches > 0) ? btn_positive : btn_negative;
	int i;
	WacomAxisData axes = {0};

	for (i = 0; i < abs(notches); i++) {
		wcmEmitButton(priv, mode, button, 1, &axes);
		wcmEmitButton(priv, mode, button, 0, &axes);
	}

	return value % threshold;
}

static void wcmPanscroll(WacomDevicePtr priv, const WacomDeviceState *ds, int x, int y)
{
	WacomCommonPtr common = priv->common;
	int threshold = common->wcmPanscrollThreshold;
	int delta_x, delta_y;
	bool smooth_scrolling = priv->common->wcmPanscrollIsSmooth;

	if (!(priv->flags & SCROLLMODE_FLAG) || !(ds->buttons & 1)) {
		priv->wcmPanscrollState = *ds;
		priv->wcmPanscrollState.x = 0;
		priv->wcmPanscrollState.y = 0;
		return;
	}

	/* Tip has gone down down; don't send pan event yet */
	if (!(priv->oldState.buttons & 1)) {
		return;
	}

	if (!is_absolute(priv)) {
		delta_x = x;
		delta_y = y;
	}
	else {
		delta_x = (x - priv->oldState.x);
		delta_y = (y - priv->oldState.y);
	}

	DBG(6, priv, "pan x = %d, pan y = %d\n", delta_x, delta_y);

	if (smooth_scrolling) {
		WacomAxisData axes = {0};
		wcmAxisSet(&axes, WACOM_AXIS_SCROLL_X, -delta_x * PANSCROLL_INCREMENT/threshold);
		wcmAxisSet(&axes, WACOM_AXIS_SCROLL_Y, -delta_y * PANSCROLL_INCREMENT/threshold);
		wcmEmitMotion(priv, FALSE, &axes);
	} else {
		int accumulated_x = priv->wcmPanscrollState.x + delta_x;
		int accumulated_y = priv->wcmPanscrollState.y + delta_y;

		int remainder_x = wcmButtonPerNotch(priv, accumulated_x, threshold, 6, 7);
		int remainder_y = wcmButtonPerNotch(priv, accumulated_y, threshold, 4, 5);

		priv->wcmPanscrollState.x = remainder_x;
		priv->wcmPanscrollState.y = remainder_y;
	}
}

void wcmResetButtonAction(WacomDevicePtr priv, int button)
{
	WacomAction new_action = {};
	int x11_button = priv->button_default[button];

	wcmActionSet(&new_action, 0, AC_BUTTON | AC_KEYBTNPRESS | x11_button);
	wcmActionCopy(&priv->key_actions[button], &new_action);
}

void wcmResetStripAction(WacomDevicePtr priv, int index)
{
	WacomAction new_action = {};

	wcmActionSet(&new_action, 0, AC_BUTTON | AC_KEYBTNPRESS | (priv->strip_default[index]));
	wcmActionSet(&new_action, 1, AC_BUTTON | (priv->strip_default[index]));
	wcmActionCopy(&priv->strip_actions[index], &new_action);
}

void wcmResetWheelAction(WacomDevicePtr priv, int index)
{
	WacomAction new_action = {};

	wcmActionSet(&new_action, 0, AC_BUTTON | AC_KEYBTNPRESS | (priv->wheel_default[index]));
	wcmActionSet(&new_action, 1, AC_BUTTON | (priv->wheel_default[index]));
	wcmActionCopy(&priv->wheel_actions[index], &new_action);
}

/* Main event hanlding function */
int wcmReadPacket(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;
	int len, pos, cnt, remaining;

	DBG(10, common, "fd=%d\n", wcmGetFd(priv));

	remaining = sizeof(common->buffer) - common->bufpos;

	DBG(1, common, "pos=%d remaining=%d\n", common->bufpos, remaining);

	/* fill buffer with as much data as we can handle */
	SYSCALL((len = read(wcmGetFd(priv), common->buffer + common->bufpos, remaining)));

	if (len <= 0)
	{
		if (errno == EAGAIN || errno == EINTR)
			return 0;
		return -errno;
	}

	/* account for new data */
	common->bufpos += len;
	DBG(10, common, "buffer has %d bytes\n", common->bufpos);

	len = common->bufpos;
	pos = 0;

	while (len > 0)
	{
		/* parse packet */
		cnt = common->wcmModel->Parse(priv, common->buffer + pos, len);
		if (cnt <= 0)
		{
			if (cnt < 0)
				DBG(1, common, "Misbehaving parser returned %d\n",cnt);
			break;
		}
		pos += cnt;
		len -= cnt;
	}

	/* if half a packet remains, move it down */
	if (len)
	{
		DBG(7, common, "MOVE %d bytes\n", common->bufpos - pos);
		memmove(common->buffer,common->buffer+pos, len);
	}

	common->bufpos = len;

	return pos;
}


/*****************************************************************************
 * wcmSendButtons --
 *   Send button events by comparing the current button mask with the
 *   previous one.
 ****************************************************************************/

static void wcmSendButtons(WacomDevicePtr priv, const WacomDeviceState* ds, unsigned int buttons,
			   const WacomAxisData *axes)
{
	unsigned int button, mask, first_button;
	WacomCommonPtr common = priv->common;
	DBG(6, priv, "buttons=%u\n", buttons);

	 /* button behaviour (TPC button on):
		if only tip is pressed/released, send button 1 events
		if button N is pressed and tip is pressed/released, send
		button N events.
		if tip is already down and button N is pressed/released,
		send button 1 release, then button N events.
	 */

	first_button = 0; /* zero-indexed because of mask */

	/* Tablet PC buttons only apply to penabled devices */
	if (common->wcmTPCButton && IsStylus(priv))
	{
		first_button = (buttons <= 1) ? 0 : 1;

		/* tip released? release all buttons */
		if ((buttons & 1) == 0)
			buttons = 0;
		/* tip pressed? send all other button presses */
		else if ((buttons & 1) != (priv->oldState.buttons & 1))
			priv->oldState.buttons = 0;
		/* other button changed while tip is still down? release tip */
		else if ((buttons & 1) && (buttons != priv->oldState.buttons))
		{
			buttons &= ~1;
			first_button = 0;
		}
	}

	for (button = first_button; button < WCM_MAX_BUTTONS; button++)
	{
		mask = 1u << button;
		if ((mask & priv->oldState.buttons) != (mask & buttons))
			sendAButton(priv, ds, button, (mask & buttons), axes);
	}

}

static void wcmSendKeys (WacomDevicePtr priv, unsigned int current, unsigned int previous)
{
	unsigned int mask, idx;

	DBG(6, priv, "current=%u previous=%u\n", current, previous);

	for (idx = 0, mask = 0x1;
	     mask && (mask <= current || mask <= previous);
	     idx++, mask <<= 1)
	{
		if ((mask & previous) != (mask & current))
		{
			int key = 0;
			int state = !!(mask & current);

			switch (idx) {
				/* Note: the evdev keycodes are > 255 and
				 * get dropped by the server. So let's remap
				 * those to KEY_PROG1-3 instead */
				case IDX_KEY_CONTROLPANEL:
					key = KEY_PROG1;
					break;
				case IDX_KEY_ONSCREEN_KEYBOARD:
					key = KEY_PROG2;
					break;
				case IDX_KEY_BUTTONCONFIG:
					key = KEY_PROG3;
					break;
				case IDX_KEY_INFO:
					key = KEY_PROG4;
					break;
				default:
					break;
			}
			if (key)
				wcmEmitKeycode(priv, key + 8, state);
		}
	}
}

/*****************************************************************************
 * countPresses
 *   Count the number of key/button presses not released for the given key
 *   array.
 ****************************************************************************/
static int countPresses(int keybtn, const unsigned int* keys, int size)
{
	int i, act, count = 0;

	for (i = 0; i < size; i++)
	{
		act = keys[i];
		if ((act & AC_CODE) == keybtn)
			count += (act & AC_KEYBTNPRESS) ? 1 : -1;
	}

	return count;
}

static void sendAction(WacomDevicePtr priv,  const WacomDeviceState* ds,
		       int press, const WacomAction *act,
		       const WacomAxisData *axes)
{
	int i;
	int nkeys = wcmActionSize(act);
	const unsigned *keys = wcmActionData(act);

	/* Actions only trigger on press, not release */
	for (i = 0; press && i < nkeys; i++)
	{
		unsigned int action = keys[i];

		if (!action)
			break;

		switch ((action & AC_TYPE))
		{
			case AC_BUTTON:
				{
					int btn_no = (action & AC_CODE);
					int is_press = (action & AC_KEYBTNPRESS);
					if (btn_no == 1 && (priv->flags & SCROLLMODE_FLAG)) {
						/* Don't send clicks in scroll mode */
					}
					else {
						wcmEmitButton(priv, is_absolute(priv), btn_no,
							      is_press, axes);
					}
				}
				break;
			case AC_KEY:
				{
					int key_code = (action & AC_CODE);
					int is_press = (action & AC_KEYBTNPRESS);
					wcmEmitKeycode(priv, key_code, is_press);
				}
				break;
			case AC_MODETOGGLE:
				if (press)
					wcmDevSwitchModeCall(priv,
							(is_absolute(priv)) ? Relative : Absolute); /* not a typo! */
				break;
			case AC_PANSCROLL:
				priv->flags |= SCROLLMODE_FLAG;
				priv->wcmPanscrollState = *ds;
				priv->wcmPanscrollState.x = 0;
				priv->wcmPanscrollState.y = 0;
				break;
		}
	}

	/* Release all non-released keys for this button. */
	for (i = 0; !press && i < nkeys; i++)
	{
		unsigned int action = keys[i];

		switch ((action & AC_TYPE))
		{
			case AC_BUTTON:
				{
					int btn_no = (action & AC_CODE);

					/* don't care about releases here */
					if (!(action & AC_KEYBTNPRESS))
						break;

					if (countPresses(btn_no, &keys[i], nkeys - i))
						wcmEmitButton(priv, is_absolute(priv), btn_no,
							      FALSE, axes);
				}
				break;
			case AC_KEY:
				{
					int key_code = (action & AC_CODE);

					/* don't care about releases here */
					if (!(action & AC_KEYBTNPRESS))
						break;

					if (countPresses(key_code, &keys[i], nkeys - i))
						wcmEmitKeycode(priv, key_code, 0);
				}
				break;
			case AC_PANSCROLL:
				priv->flags &= ~SCROLLMODE_FLAG;
				break;
		}
	}
}

/*****************************************************************************
 * sendAButton --
 *   Send one button event, called by wcmSendButtons
 ****************************************************************************/
static void sendAButton(WacomDevicePtr priv, const WacomDeviceState* ds, int button,
			int mask, const WacomAxisData  *axes)
{
#ifdef DEBUG
	WacomCommonPtr common = priv->common;
#endif

	DBG(4, priv, "TPCButton(%s) button=%d state=%d\n",
	    common->wcmTPCButton ? "on" : "off", button, mask);

	if (wcmActionSize(&priv->key_actions[button]) == 0)
		return;

	sendAction(priv, ds, (mask != 0), &priv->key_actions[button], axes);
}

/**
 * Get the distance an axis was scrolled. This function is aware
 * of the different ways different scrolling axes work and strives
 * to produce a common representation of relative change.
 *
 * @param current  Current value of the axis
 * @param old      Previous value of the axis
 * @param wrap     Maximum value before wraparound occurs (0 if axis does not wrap)
 * @param flags    Flags defining axis attributes: AXIS_INVERT and AXIS_BITWISE
 * @return         Relative change in axis value
 */
static int getScrollDelta(int current, int old, int wrap, int flags)
{
	int delta;

	if (flags & AXIS_BITWISE)
	{
		current = log2((current << 1) | 0x01);
		old = log2((old << 1) | 0x01);
		wrap = log2((wrap << 1) | 0x01);
	}

	delta = current - old;

	if (flags & AXIS_INVERT)
		delta = -delta;

	if (wrap != 0)
	{
		/* Wraparound detection. If the distance old..current
		 * is larger than the old..current considering the
		 * wraparound, assume wraparound and readjust */
		int wrap_delta;

		if (delta < 0)
			wrap_delta =  (wrap + 1) + delta;
		else
			wrap_delta = -((wrap + 1) - delta);

		if (abs(wrap_delta) < abs(delta))
			delta = wrap_delta;
	}

	return delta;
}

/**
 * Get the scroll button/action to send given the delta of
 * the scrolling axis and the possible events that can be
 * sent.
 *
 * @param delta            Amount of change in the scrolling axis
 * @param action_positive  Array index of action to send on a positive delta
 * @param action_negative  Array index of action to send on negative delta
 * @return                 Array index of action that should be performed, or -1 if none.
 */
static int getWheelButton(int delta, int action_positive, int action_negative)
{
	if (delta > 0)
		return action_positive;
	else if (delta < 0)
		return action_negative;
	else
		return -1;
}

/**
 * Send button or actions for a scrolling axis.
 *
 * @param action     Action to send
 */
static void sendWheelStripEvent(WacomDevicePtr priv, const WacomAction *action,
				const WacomDeviceState* ds, const WacomAxisData *axes)
{
	sendAction(priv, ds, 1, action, axes);
	sendAction(priv, ds, 0, action, axes);
}

/*****************************************************************************
 * sendWheelStripEvents --
 *   Send events defined for relative/absolute wheels or strips
 ****************************************************************************/

static void sendWheelStripEvents(WacomDevicePtr priv, const WacomDeviceState* ds,
				 const WacomAxisData *axes)
{
	WacomCommonPtr common = priv->common;
	int delta = 0, idx = 0;

	DBG(10, priv, "\n");

	/* emulate events for left strip */
	delta = getScrollDelta(ds->stripx, priv->oldState.stripx, 0, AXIS_INVERT | AXIS_BITWISE);
	idx = getWheelButton(delta, STRIP_LEFT_UP, STRIP_LEFT_DN);
	if (idx >= 0 && IsPad(priv) && priv->oldState.proximity == ds->proximity)
	{
		DBG(10, priv, "Left touch strip scroll delta = %d\n", delta);
		sendWheelStripEvent(priv, &priv->strip_actions[idx], ds, axes);
	}

	/* emulate events for right strip */
	delta = getScrollDelta(ds->stripy, priv->oldState.stripy, 0, AXIS_INVERT | AXIS_BITWISE);
	idx = getWheelButton(delta, STRIP_RIGHT_UP, STRIP_RIGHT_DN);
	if (idx >= 0 && IsPad(priv) && priv->oldState.proximity == ds->proximity)
	{
		DBG(10, priv, "Right touch strip scroll delta = %d\n", delta);
		sendWheelStripEvent(priv, &priv->strip_actions[idx], ds, axes);
	}

	/* emulate events for relative wheel:
	 * positive delta = scroll up */
	delta = getScrollDelta(ds->relwheel, 0, 0, 0);
	idx = getWheelButton(delta, WHEEL_REL_UP, WHEEL_REL_DN);
	if (idx >= 0 && (IsCursor(priv) || IsPad(priv)) && priv->oldState.proximity == ds->proximity)
	{
		DBG(10, priv, "Relative wheel scroll delta = %d\n", delta);
		sendWheelStripEvent(priv, &priv->wheel_actions[idx], ds, axes);
	}

	/* emulate events for 2nd relative wheel */
	delta = getScrollDelta(ds->relwheel2, 0, 0, 0);
	idx = getWheelButton(delta, WHEEL2_REL_UP, WHEEL2_REL_DN);
	if (idx >= 0 && (IsCursor(priv) || IsPad(priv)) && priv->oldState.proximity == ds->proximity)
	{
		DBG(10, priv, "Relative wheel 2 scroll delta = %d\n", delta);
		sendWheelStripEvent(priv, &priv->wheel_actions[idx], ds, axes);
	}

	/* emulate events for left touch ring */
	delta = getScrollDelta(ds->abswheel, priv->oldState.abswheel, common->wcmMaxRing, AXIS_INVERT);
	idx = getWheelButton(delta, WHEEL_ABS_UP, WHEEL_ABS_DN);
	if (idx >= 0 && IsPad(priv) && priv->oldState.proximity == ds->proximity)
	{
		DBG(10, priv, "Left touch wheel scroll delta = %d\n", delta);
		sendWheelStripEvent(priv, &priv->wheel_actions[idx], ds, axes);
	}

	/* emulate events for right touch ring */
	delta = getScrollDelta(ds->abswheel2, priv->oldState.abswheel2, common->wcmMaxRing, AXIS_INVERT);
	idx = getWheelButton(delta, WHEEL2_ABS_UP, WHEEL2_ABS_DN);
	if (idx >= 0 && IsPad(priv) && priv->oldState.proximity == ds->proximity)
	{
		DBG(10, priv, "Right touch wheel scroll delta = %d\n", delta);
		sendWheelStripEvent(priv, &priv->wheel_actions[idx], ds, axes);
	}
}

/*****************************************************************************
 * sendCommonEvents --
 *   Send events common between pad and stylus/cursor/eraser.
 ****************************************************************************/

static void sendCommonEvents(WacomDevicePtr priv, const WacomDeviceState* ds,
			     const WacomAxisData *axes)
{
	unsigned int buttons = ds->buttons;
	int x = 0, y = 0;

	wcmAxisGet(axes, WACOM_AXIS_X, &x);
	wcmAxisGet(axes, WACOM_AXIS_Y, &y);

	/* send scrolling events if necessary */
	wcmPanscroll(priv, ds, x, y);

	/* send button events when state changed or first time in prox and button unpresses */
	if (priv->oldState.buttons != buttons || (!priv->oldState.proximity && !buttons))
		wcmSendButtons(priv, ds, buttons, axes);

	/* emulate wheel/strip events when defined */
	if ( ds->relwheel || ds->relwheel2 || (ds->abswheel != priv->oldState.abswheel) || (ds->abswheel2 != priv->oldState.abswheel2) ||
		( (ds->stripx - priv->oldState.stripx) && ds->stripx && priv->oldState.stripx) ||
			((ds->stripy - priv->oldState.stripy) && ds->stripy && priv->oldState.stripy) )
		sendWheelStripEvents(priv, ds, axes);
}

/* 1:1 copy from xf86ScaleAxis */
int wcmScaleAxis(int Cx, int to_max, int to_min, int from_max, int from_min)
{
	int X;
	int64_t to_width = to_max - to_min;
	int64_t from_width = from_max - from_min;

	if (from_width) {
		X = (int)(((to_width * (Cx - from_min)) / from_width) + to_min);
	}
	else {
		X = 0;
		/*ErrorF ("Divide by Zero in xf86ScaleAxis\n");*/
	}

	if (X > to_max)
		X = to_max;
	if (X < to_min)
		X = to_min;

	return X;
}


/* rotate x and y before post X inout events */
void wcmRotateAndScaleCoordinates(WacomDevicePtr priv, int* x, int* y)
{
	WacomCommonPtr common = priv->common;
	int tmp_coord;
	int xmax, xmin, ymax, ymin;

	/* scale into the topX/topY area we had when we initialized the
	 * valuator */
	xmin = priv->valuatorMinX;
	xmax = priv->valuatorMaxX;
	ymin = priv->valuatorMinY;
	ymax = priv->valuatorMaxY;

	/* Don't try to scale relative axes */
	if (xmax > xmin)
		*x = wcmScaleAxis(*x, xmax, xmin, priv->bottomX, priv->topX);

	if (ymax > ymin)
		*y = wcmScaleAxis(*y, ymax, ymin, priv->bottomY, priv->topY);

	/* coordinates are now in the axis rage we advertise for the device */

	if (common->wcmRotate == ROTATE_CW || common->wcmRotate == ROTATE_CCW)
	{
		tmp_coord = *x;

		*x = wcmScaleAxis(*y, xmax, xmin, ymax, ymin);
		*y = wcmScaleAxis(tmp_coord, ymax, ymin, xmax, xmin);
	}

	if (common->wcmRotate == ROTATE_CW)
		*y = ymax - (*y - ymin);
	else if (common->wcmRotate == ROTATE_CCW)
		*x = xmax - (*x - xmin);
	else if (common->wcmRotate == ROTATE_HALF)
	{
		*x = xmax - (*x - xmin);
		*y = ymax - (*y - ymin);
	}


	DBG(10, priv, "rotate/scaled to %d/%d\n", *x, *y);
}

static void wcmUpdateOldState(WacomDevicePtr priv,
			      const WacomDeviceState *ds, int currentX, int currentY)
{
	priv->oldState = *ds;
	priv->oldState.x = currentX;
	priv->oldState.y = currentY;
}

static void
wcmSendPadEvents(WacomDevicePtr priv, const WacomDeviceState* ds, const WacomAxisData *axes)
{
	if (!priv->oldState.proximity && ds->proximity)
		wcmEmitProximity(priv, TRUE, axes);

	if (axes->mask || ds->buttons || ds->relwheel || ds->relwheel2 ||
	    (ds->abswheel != priv->oldState.abswheel) || (ds->abswheel2 != priv->oldState.abswheel2))
	{
		sendCommonEvents(priv, ds, axes);

		wcmEmitMotion(priv, TRUE, axes);
	}
	else
	{
		if (priv->oldState.buttons)
			wcmSendButtons(priv, ds, ds->buttons, axes);
	}

	wcmSendKeys(priv, ds->keys, priv->oldState.keys);

	if (priv->oldState.proximity && !ds->proximity)
		wcmEmitProximity(priv, FALSE, axes);
}

/* Send events for all tools but pads */
static void
wcmSendNonPadEvents(WacomDevicePtr priv, const WacomDeviceState *ds,
		    WacomAxisData *axes)
{
	if (!is_absolute(priv))
	{
		int val;

		val = 0;
		wcmAxisGet(axes, WACOM_AXIS_X, &val);
		wcmAxisSet(axes, WACOM_AXIS_X, val - priv->oldState.x);
		val = 0;
		wcmAxisGet(axes, WACOM_AXIS_Y, &val);
		wcmAxisSet(axes, WACOM_AXIS_Y, val - priv->oldState.y);
		val = 0;
		wcmAxisGet(axes, WACOM_AXIS_PRESSURE, &val);
		wcmAxisSet(axes, WACOM_AXIS_PRESSURE, val - priv->oldState.pressure);

		if (IsCursor(priv))
		{
			val = 0;
			wcmAxisGet(axes, WACOM_AXIS_ROTATION, &val);
			wcmAxisSet(axes, WACOM_AXIS_ROTATION, val - priv->oldState.rotation);
			val = 0;
			wcmAxisGet(axes, WACOM_AXIS_THROTTLE, &val);
			wcmAxisSet(axes, WACOM_AXIS_THROTTLE, val - priv->oldState.throttle);
		} else
		{
			val = 0;
			wcmAxisGet(axes, WACOM_AXIS_TILT_X, &val);
			wcmAxisSet(axes, WACOM_AXIS_TILT_X, val - priv->oldState.tilty);
			val = 0;
			wcmAxisGet(axes, WACOM_AXIS_TILT_Y, &val);
			wcmAxisSet(axes, WACOM_AXIS_TILT_Y, val - priv->oldState.tilty);
		}
		val = 0;
		if (wcmAxisGet(axes, WACOM_AXIS_RING, &val))
			wcmAxisSet(axes, WACOM_AXIS_RING, val - priv->oldState.abswheel);
		val = 0;
		if (wcmAxisGet(axes, WACOM_AXIS_RING2, &val))
			wcmAxisSet(axes, WACOM_AXIS_RING2, val - priv->oldState.abswheel2);
	}

	/* coordinates are ready we can send events */
	if (ds->proximity)
	{
		if (!priv->oldState.proximity)
			wcmEmitProximity(priv, TRUE, axes);

		/* Move the cursor to where it should be before sending button events */
		if(!(priv->flags & BUTTONS_ONLY_FLAG) &&
		   !(priv->flags & SCROLLMODE_FLAG && (!is_absolute(priv) || priv->oldState.buttons & 1)))
		{
			wcmEmitMotion(priv, is_absolute(priv), axes);
			/* For relative events, do not repost
			 * the valuators.  Otherwise, a button
			 * event in sendCommonEvents will move the
			 * axes again.
			 */
			if (!is_absolute(priv))
			{
				memset(axes, 0, sizeof(*axes));
			}
		}

		sendCommonEvents(priv, ds, axes);
	}
	else /* not in proximity */
	{
		int buttons = 0;

		/* reports button up when the device has been
		 * down and becomes out of proximity */
		if (priv->oldState.buttons)
			wcmSendButtons(priv, ds, buttons, axes);

		if (priv->oldState.proximity)
			wcmEmitProximity(priv, FALSE, axes);
	} /* not in proximity */
}

#define IsArtPen(ds)    (ds->device_id == 0x885 || ds->device_id == 0x804 || ds->device_id == 0x100804)
#define IsAirBrush(ds)  (ds->device_id == 0x902 || ds->device_id == 0x100902)

static void
wcmUpdateSerial(WacomDevicePtr priv, unsigned int serial, int id)
{
	if (priv->cur_serial == serial && priv->cur_device_id == id)
		return;

	priv->cur_serial = serial;
	priv->cur_device_id = id;

	wcmUpdateSerialProperty(priv);
}

/*****************************************************************************
 * wcmSendEvents --
 *   Send events according to the device state.
 ****************************************************************************/

void wcmSendEvents(WacomDevicePtr priv, const WacomDeviceState* ds)
{
#ifdef DEBUG
	int is_button = !!(ds->buttons);
#endif
	int type = ds->device_type;
	int id = ds->device_id;
	unsigned int serial = ds->serial_num;
	int x = ds->x;
	int y = ds->y;
	WacomAxisData axes = {0};
	char dump[1024];

	if (priv->serial && serial != priv->serial)
	{
		DBG(10, priv, "serial number"
				" is %u but your system configured %u",
				serial, priv->serial);
		return;
	}

	wcmUpdateSerial(priv, serial, id);

	/* don't move the cursor when going out-prox */
	if (!ds->proximity)
	{
		x = priv->oldState.x;
		y = priv->oldState.y;
	}

	if (ds->proximity)
		wcmRotateAndScaleCoordinates(priv, &x, &y);

	if (!IsPad(priv)) { /* pad doesn't post x/y */
		wcmAxisSet(&axes, WACOM_AXIS_X, x);
		wcmAxisSet(&axes, WACOM_AXIS_Y, y);
		wcmAxisSet(&axes, WACOM_AXIS_PRESSURE, ds->pressure);
	}

	if (type == PAD_ID)
	{
		wcmAxisSet(&axes, WACOM_AXIS_STRIP_X, ds->stripx);
		wcmAxisSet(&axes, WACOM_AXIS_STRIP_Y, ds->stripy);
		wcmAxisSet(&axes, WACOM_AXIS_RING, ds->abswheel);
		wcmAxisSet(&axes, WACOM_AXIS_RING2, ds->abswheel2);
	} else if (IsCursor(priv))
	{
		wcmAxisSet(&axes, WACOM_AXIS_ROTATION, ds->rotation);
		wcmAxisSet(&axes, WACOM_AXIS_THROTTLE, ds->throttle);
	} else
	{
		wcmAxisSet(&axes, WACOM_AXIS_TILT_X, ds->tiltx);
		wcmAxisSet(&axes, WACOM_AXIS_TILT_Y, ds->tilty);
	}


	/* cancel panscroll */
	if (!ds->proximity)
		priv->flags &= ~SCROLLMODE_FLAG;

	if (IsStylus(priv))
	{
		if (IsAirBrush(ds))
		{
			/* Normalize abswheel airbrush data to Art Pen rotation range.
			* We do not normalize Art Pen. They are already at the range.
			*/
			int wheel = ds->abswheel * MAX_ROTATION_RANGE/
					(double)MAX_ABS_WHEEL + MIN_ROTATION;
			wcmAxisSet(&axes, WACOM_AXIS_WHEEL, wheel);
		}
		else if (IsArtPen(ds))
		{
			wcmAxisSet(&axes, WACOM_AXIS_WHEEL, ds->abswheel);
		}
	}

	wcmAxisDump(&axes, dump, sizeof(dump));
	DBG(6, priv, "%s o_prox=%d\tprox=%d\t%s\tid=%d"
		"\tserial=%u\tbutton=%s\tbuttons=%u\n",
		is_absolute(priv) ? "abs" : "rel", priv->oldState.proximity,
		ds->proximity, dump, id, serial, is_button ? "true" : "false",
		ds->buttons);

	/* when entering prox, replace the zeroed-out oldState with a copy of
	 * the current state to prevent jumps. reset the prox and button state
	 * to zero to properly detect changes.
	 */
	if(!priv->oldState.proximity)
	{
		int old_key_state = priv->oldState.keys;

		wcmUpdateOldState(priv, ds, x, y);
		priv->oldState.proximity = 0;
		priv->oldState.buttons = 0;

		/* keys can happen without proximity */
		priv->oldState.keys = old_key_state;
	}

	if (type == PAD_ID)
		wcmSendPadEvents(priv, ds, &axes);
	else {
		/* don't move the cursor if in gesture mode (except drag mode) */
		if ((type != TOUCH_ID) || wcmTouchNeedSendEvents(priv->common))
			wcmSendNonPadEvents(priv, ds, &axes);
	}

	if (ds->proximity)
		wcmUpdateOldState(priv, ds, x, y);
	else
	{
		priv->oldState = OUTPROX_STATE;
		priv->oldState.serial_num = serial;
		priv->oldState.device_id = id;
		wcmUpdateSerial(priv, 0, 0);
	}
}

/**
 * Determine whether device state has changed enough to warrant further
 * processing. The driver's "suppress" setting decides how much
 * movement/state change must occur before we process events to avoid
 * overloading the server with minimal changes (and getting fuzzy events).
 * wcmCheckSuppress ensures that events meet this standard.
 *
 * @param dsOrig Previous device state
 * @param dsNew Current device state
 *
 * @retval SUPPRESS_ALL Ignore this event completely.
 * @retval SUPPRESS_NONE Process event normally.
 * @retval SUPPRESS_NON_MOTION Suppress all data but motion data.
 */
static enum WacomSuppressMode
wcmCheckSuppress(WacomCommonPtr common,
		 const WacomDeviceState* dsOrig,
		 WacomDeviceState* dsNew)
{
	int suppress = common->wcmSuppress;
	enum WacomSuppressMode returnV = SUPPRESS_NONE;

	/* Ignore all other changes that occur after initial out-of-prox. */
	if (!dsNew->proximity && !dsOrig->proximity)
		return SUPPRESS_ALL;

	/* Never ignore proximity changes. */
	if (dsOrig->proximity != dsNew->proximity) goto out;

	if (dsOrig->buttons != dsNew->buttons) goto out;
	if (dsOrig->keys != dsNew->keys) goto out;
	if (dsOrig->stripx != dsNew->stripx) goto out;
	if (dsOrig->stripy != dsNew->stripy) goto out;

	/* FIXME: we should have different suppress values for different
	 * axes with vastly different ranges.
	 */
	if (abs(dsOrig->tiltx - dsNew->tiltx) > suppress) goto out;
	if (abs(dsOrig->tilty - dsNew->tilty) > suppress) goto out;
	if (abs(dsOrig->pressure - dsNew->pressure) > suppress) goto out;
	if (abs(dsOrig->throttle - dsNew->throttle) > suppress) goto out;
	if (abs(dsOrig->rotation - dsNew->rotation) > suppress &&
	    (1800 - abs(dsOrig->rotation - dsNew->rotation)) >  suppress) goto out;

	/* look for change in absolute wheel position
	 * or any relative wheel movement
	 */
	if (abs(dsOrig->abswheel  - dsNew->abswheel)  > suppress) goto out;
	if (abs(dsOrig->abswheel2 - dsNew->abswheel2) > suppress) goto out;
	if (dsNew->relwheel != 0) goto out;
	if (dsNew->relwheel2 != 0) goto out;

	returnV = SUPPRESS_ALL;

out:
	/* Special handling for cursor: if nothing else changed but the
	 * pointer x/y, suppress all but cursor movement. This return value
	 * is used in commonDispatchDevice to short-cut event processing.
	 */
	if ((abs(dsOrig->x - dsNew->x) > suppress) ||
			(abs(dsOrig->y - dsNew->y) > suppress))
	{
		if (returnV == SUPPRESS_ALL)
			returnV = SUPPRESS_NON_MOTION;
	}
	else /* don't move cursor */
	{
		dsNew->x = dsOrig->x;
		dsNew->y = dsOrig->y;
	}

	DBG(10, common, "level = %d"
		" return value = %u\n", suppress, returnV);
	return returnV;
}

/**
 * Find the device the current events are meant for. If multiple tools are
 * configured on this tablet, the one that matches the serial number for the
 * current device state is returned. If none match, the tool that has a
 * serial of 0 is returned.
 *
 * @param ds The current device state as read from the fd
 * @return The tool that should be used to emit the current events.
 */
static WacomToolPtr findTool(const WacomCommonPtr common,
			     const WacomDeviceState *ds)
{
	WacomToolPtr tooldefault = NULL;
	WacomToolPtr tool = NULL;

	/* 1: Find the tool (the one with correct serial or in second
	 * hand, the one with serial set to 0 if no match with the
	 * specified serial exists) that is used for this event */
	for (tool = common->wcmTool; tool; tool = tool->next)
	{
		if (tool->typeid == ds->device_type)
		{
			if (tool->serial == ds->serial_num)
				break;
			else if (!tool->serial)
				tooldefault = tool;
		}
	}

	/* Use default tool (serial == 0) if no specific was found */
	if (!tool)
		tool = tooldefault;

	return tool;
}

/**
 * Check if the given device should grab control of the pointer in
 * preference to whatever tool currently has access.
 *
 * @param priv   The device to check for access
 * @param ds     The current state of the device
 * @returns      'TRUE' if control of the pointer should be granted, FALSE otherwise
 */
static Bool check_arbitrated_control(WacomDevicePtr priv, WacomDeviceStatePtr ds)
{
	WacomDevicePtr active = WACOM_DRIVER.active;

	if (IsPad(priv)) {
		/* Pad may never be the "active" pointer controller */
		DBG(6, priv, "Event from pad; not yielding pointer control.\n");
		return FALSE;
	}

	if (active == NULL || active->oldState.device_id == ds->device_id) {
		DBG(11, priv, "Event from active device; maintaining pointer control.\n");
		return TRUE;
	}
	else if (IsCursor(active)) {
		/* Cursor devices are often left idle in range, so allow other devices
		 * to grab control if the tool has not been used for some time.
		 */
		Bool yield = (ds->time - active->oldState.time > 100) && (active->oldState.buttons == 0);
		DBG(6, priv, "Currently-active cursor %s idle; %s pointer control.\n",
		    yield ? "is" : "is not", yield ? "yielding" : "not yielding");
		return yield;
	}
	else if (IsCursor(priv)) {
		/* An otherwise idle cursor may still occasionally jitter and send
		 * events while the user is actively using other tools or touching
		 * the device. Do not allow the cursor to grab control in this
		 * particular case.
		 */
		DBG(6, priv, "Event from non-active cursor; not yielding pointer control.\n");
		return FALSE;
	}
	else {
		/* Non-touch input has priority over touch in general */
		Bool yield = !IsTouch(priv);
		DBG(6, priv, "Event from non-active %s device; %s pointer control.\n",
		    yield ? "non-touch" : "touch", yield ? "yielding" : "not yielding");
		return yield;
	}
}

/*****************************************************************************
 * wcmEvent -
 *   Handles suppression, transformation, filtering, and event dispatch.
 ****************************************************************************/

void wcmEvent(WacomCommonPtr common, unsigned int channel,
	const WacomDeviceState* pState)
{
	WacomDeviceState ds;
	WacomChannelPtr pChannel;
	WacomToolPtr tool;
	WacomDevicePtr priv;
	pChannel = common->wcmChannel + channel;

	DBG(10, common, "channel = %u\n", channel);

	/* sanity check the channel */
	if (channel >= MAX_CHANNELS)
		return;

	/* we must copy the state because certain types of filtering
	 * will need to change the values (ie. for error correction) */
	ds = *pState;

	DBG(10, common,
		"c=%u i=%d t=%d s=0x%x x=%d y=%d b=%u "
		"p=%d rz=%d tx=%d ty=%d aw=%d aw2=%d rw=%d rw2=%d "
		"t=%d px=%d st=%u cs=%d \n",
		channel,
		ds.device_id,
		ds.device_type,
		ds.serial_num,
		ds.x, ds.y, ds.buttons,
		ds.pressure, ds.rotation, ds.tiltx,
		ds.tilty, ds.abswheel, ds.abswheel2, ds.relwheel, ds.relwheel2, ds.throttle,
		ds.proximity, ds.sample,
		pChannel->nSamples);

	/* Find the device the current events are meant for */
	tool = findTool(common, &ds);
	if (!tool || !tool->device)
	{
		DBG(11, common, "no device matches with id=%d, serial=%u\n",
		    ds.device_type, ds.serial_num);
		return;
	}

	priv = tool->device;
	/* Tool on the tablet when driver starts. This sometime causes
	 * access errors to the device */
	if (!tool->enabled) {
		wcmLogSafe(priv, W_ERROR, "tool not initialized yet. Skipping event. \n");
		return;
	}

	DBG(11, common, "device_type=%d tool_id=%d for %s\n", ds.device_type, ds.device_id, priv->name);

	if (TabletHasFeature(common, WCM_ROTATION) &&
		TabletHasFeature(common, WCM_RING) &&
		ds.device_type == CURSOR_ID) /* I4 mouse */
	{
		/* convert Intuos4 mouse tilt to rotation */
		ds.rotation = wcmTilt2R(ds.tiltx, ds.tilty,
					INTUOS4_CURSOR_ROTATION_OFFSET);
		ds.tiltx = 0;
		ds.tilty = 0;
	}

	/* JEJ - Do not move this code without discussing it with me.
	 * The device state is invariant of any filtering performed below.
	 * Changing the device state after this point can and will cause
	 * a feedback loop resulting in oscillations, error amplification,
	 * unnecessary quantization, and other annoying effects. */

	/* save channel device state and device to which last event went */
	memmove(pChannel->valid.states + 1,
		pChannel->valid.states,
		sizeof(WacomDeviceState) * (common->wcmRawSample - 1));
	pChannel->valid.state = ds; /*save last raw sample */
	if (pChannel->nSamples < common->wcmRawSample) ++pChannel->nSamples;

	/* arbitrate pointer control */
	if (check_arbitrated_control(priv, &ds)) {
		if (WACOM_DRIVER.active != NULL && priv != WACOM_DRIVER.active) {
			wcmSoftOutEvent(WACOM_DRIVER.active);
			wcmCancelGesture(WACOM_DRIVER.active);
		}
		if (ds.proximity)
			WACOM_DRIVER.active = priv;
		else
			WACOM_DRIVER.active = NULL;
	}
	else if (!IsPad(priv)) {
		return;
	}

	if ((ds.device_type == TOUCH_ID) && common->wcmTouch)
	{
		wcmGestureFilter(priv, ds.serial_num - 1);
		/*
		 * When using XI 2.2 multitouch events don't do common dispatching
		 * for direct touch devices
		 */
		if (!common->wcmGesture && TabletHasFeature(common, WCM_LCD))
			return;
	}

	/* For touch, only first finger moves the cursor */
	if ((common->wcmTouch && ds.device_type == TOUCH_ID && ds.serial_num == 1) ||
	    (ds.device_type != TOUCH_ID))
		commonDispatchDevice(priv, pChannel);
}


/**
 * Return the minimum pressure based on the current minimum pressure and the
 * hardware state. This is mainly to deal with the case where heavily used
 * stylus may have a "pre-loaded" initial pressure. In that case, the tool
 * comes into proximity with a pressure > 0 to begin with and thus offsets
 * the pressure values. This preloaded pressure must be known for pressure
 * normalisation to work.
 *
 * @param priv The wacom device
 * @param ds Current device state
 *
 * @return The minimum pressure value for this tool.
 *
 * @see normalizePressure
 */
static int
rebasePressure(const WacomDevicePtr priv, const WacomDeviceState *ds)
{
	int min_pressure;

	/* set the minimum pressure when in prox */
	if (!priv->oldState.proximity)
		min_pressure = ds->pressure;
	else
		min_pressure = min(priv->minPressure, ds->pressure);

	return min_pressure;
}

/**
 * Instead of reporting the raw pressure, we normalize
 * the pressure from 0 to maxCurve. This is
 * mainly to deal with the case where heavily used
 * stylus may have a "pre-loaded" initial pressure. To
 * do so, we keep the in-prox pressure and subtract it
 * from the raw pressure to prevent a potential
 * left-click before the pen touches the tablet.
 *
 * @param priv The wacom device
 * @param ds Current device state
 *
 * @return normalized pressure
 * @see rebasePressure
 */
static int
normalizePressure(const WacomDevicePtr priv, const int raw_pressure)
{
	WacomCommonPtr common = priv->common;
	double pressure;
	int p = raw_pressure;
	int range_left = common->wcmMaxZ;

	if (common->wcmPressureRecalibration) {
		p -= priv->minPressure;
		range_left -= priv->minPressure;
	}
	/* normalize pressure to 0..maxCurve */
	if (range_left >= 1)
		pressure = wcmScaleAxis(p, priv->maxCurve, 0, range_left, 0);
	else
		pressure = priv->maxCurve;

	return (int)pressure;
}

/*
 * Based on the current pressure, return the button state with Button1
 * either set or unset, depending on whether the pressure threshold
 * conditions have been met.
 *
 * Returns the state of all buttons, but buttons other than button 1 are
 * unmodified.
 */
#define PRESSURE_BUTTON 1
static int
setPressureButton(const WacomDevicePtr priv, int buttons, const int pressure)
{
	WacomCommonPtr common = priv->common;
	int button = PRESSURE_BUTTON;

	/* button 1 Threshold test */
	/* set button1 (left click) on/off */
	if (pressure < common->wcmThreshold)
	{
		buttons &= ~button;
		if (priv->oldState.buttons & button) /* left click was on */
		{
			/* don't set it off if it is within the tolerance
			   and threshold is larger than the tolerance */
			if ((common->wcmThreshold > (priv->maxCurve * THRESHOLD_TOLERANCE)) &&
			    (pressure > common->wcmThreshold - (priv->maxCurve * THRESHOLD_TOLERANCE)))
				buttons |= button;
		}
	}
	else
		buttons |= button;

	return buttons;
}

/*
 * Broken pen with a broken tip might give high pressure values
 * all the time. We want to warn about this. To avoid getting
 * spurious warnings when the tablet is hit quickly will wait
 * until the device goes out of proximity and check if the minimum
 * pressure is still above a threshold of 20 percent of the maximum
 * pressure. Also we make sure the device has seen a sufficient number
 * of events while in proximity that it had a chance to see decreasing
 * pressure values.
 */
#define LIMIT_LOW_PRESSURE 20 /* percentage of max value */
#define MIN_EVENT_COUNT 15

static void detectPressureIssue(WacomDevicePtr priv,
				WacomCommonPtr common,
				WacomDeviceStatePtr ds)
{
	/* pen is just going out of proximity */
	if (priv->oldState.proximity && !ds->proximity) {

		int pressureThreshold = common->wcmMaxZ * LIMIT_LOW_PRESSURE / 100;
		/* check if minPressure has persisted all the time
		   and is too close to the maximum pressure */
		if (priv->oldMinPressure > pressureThreshold &&
		    priv->eventCnt > MIN_EVENT_COUNT)
			wcmLogSafe(priv, W_WARNING,
				"On %s(%u) a base pressure of %d persists while the pen is in proximity.\n"
				"\tThis is > %d percent of the maximum value (%d).\n"
				"\tThis indicates a worn out pen, it is time to change your tool. Also see:\n"
				"\thttps://github.com/linuxwacom/xf86-input-wacom/wiki/Pen-Wear.\n",
				priv->name, priv->serial, priv->minPressure, LIMIT_LOW_PRESSURE, common->wcmMaxZ);
	} else if (!priv->oldState.proximity)
		priv->eventCnt = 0;

	priv->oldMinPressure = priv->minPressure;
	priv->eventCnt++;
}

static void commonDispatchDevice(WacomDevicePtr priv,
				 const WacomChannelPtr pChannel)
{
	WacomDeviceState* ds = &pChannel->valid.states[0];
	WacomCommonPtr common = priv->common;
	WacomDeviceState filtered;
	enum WacomSuppressMode suppress;
	int raw_pressure = 0;

	/* device_type should have been retrieved and set in the respective
	 * models, e.g. wcmUSB.c. Once it comes here, something
	 * must have been wrong. Ignore the events.
	 */
	if (!ds->device_type)
	{
		DBG(11, common, "no device type matches with"
				" serial=%u\n", ds->serial_num);
		return;
	}

	DBG(10, common, "device type = %d\n", ds->device_type);

	filtered = pChannel->valid.state;

	/* Device transformations come first */
	if (priv->serial && filtered.serial_num != priv->serial)
	{
		DBG(10, priv, "serial number"
			" is %u but your system configured %u\n",
			filtered.serial_num, priv->serial);
		return;
	}

	if ((IsPen(priv) || IsTouch(priv)) && common->wcmMaxZ)
	{
		int prev_min_pressure = priv->oldState.proximity ? priv->minPressure : 0;

		detectPressureIssue(priv, common, &filtered);

		raw_pressure = filtered.pressure;
		if (!priv->oldState.proximity)
			priv->maxRawPressure = raw_pressure;

		priv->minPressure = rebasePressure(priv, &filtered);

		filtered.pressure = normalizePressure(priv, filtered.pressure);
		if (IsPen(priv)) {
			filtered.buttons = setPressureButton(priv,
							     filtered.buttons,
							     filtered.pressure);

			/* Here we run some heuristics to avoid losing button events if the
			 * pen gets pushed onto the tablet so quickly that the first pressure
			 * event read is non-zero and is thus interpreted as a pressure bias */
			if (filtered.buttons & PRESSURE_BUTTON) {
				/* If we triggered 'normally' reset max pressure to
				 * avoid to trigger again while this device is in proximity */
				priv->maxRawPressure = 0;
			} else if (priv->maxRawPressure) {
				int norm_max_pressure;

				/* If we haven't triggered normally we record the maximal pressure
				 * and see if this would have triggered with a lowered bias. */
				if (priv->maxRawPressure < raw_pressure)
					priv->maxRawPressure = raw_pressure;
				norm_max_pressure = normalizePressure(priv, priv->maxRawPressure);
				filtered.buttons = setPressureButton(priv, filtered.buttons,
								     norm_max_pressure);

				/* If minPressure is not decrementing any more or a button
				 * press has been generated or minPressure has just become zero
				 * reset maxRawPressure to avoid that worn devices
				 * won't report a button release until going out of proximity */
				if ((filtered.buttons & PRESSURE_BUTTON &&
				     priv->minPressure == prev_min_pressure) ||
				    !priv->minPressure)
					priv->maxRawPressure = 0;

			}
		}
		filtered.pressure = applyPressureCurve(priv,&filtered);
	}

	/* Optionally filter values only while in proximity */
	if (filtered.proximity && filtered.device_type != PAD_ID)
	{
		/* Start filter fresh when entering proximity */
		if (!priv->oldState.proximity)
			wcmResetSampleCounter(pChannel);

		/* Reset filter whenever the tip is touched to the
		 * screen to ensure clicks are sent from the pen's
		 * actual position. Don't reset on other buttons or
		 * tip-up, or else there may be a noticible jump/
		 * hook produced in the middle/end of the stroke.
		 */
		if ((filtered.buttons & PRESSURE_BUTTON) && !(priv->oldState.buttons & PRESSURE_BUTTON))
			wcmResetSampleCounter(pChannel);

		wcmFilterCoord(common,pChannel,&filtered);
	}

	/* skip event if we don't have enough movement */
	suppress = wcmCheckSuppress(common, &priv->oldState, &filtered);
	if (suppress == SUPPRESS_ALL)
		return;

	/* Store cursor hardware prox for next use */
	if (IsCursor(priv))
		priv->oldCursorHwProx = ds->proximity;

	/* User-requested filtering comes next */

	/* User-requested transformations come last */

	if (!is_absolute(priv) && !IsPad(priv))
	{
		/* To improve the accuracy of relative x/y,
		 * don't send motion event when there is no movement.
		 */
		double deltx = filtered.x - priv->oldState.x;
		double delty = filtered.y - priv->oldState.y;

		/* less than one device coordinate movement? */
		if (fabs(deltx)<1 && fabs(delty)<1)
		{
			/* We have no other data in this event, skip */
			if (suppress == SUPPRESS_NON_MOTION)
			{
				DBG(10, common, "Ignore non-movement relative data \n");
				return;
			}

			filtered.x = priv->oldState.x;
			filtered.y = priv->oldState.y;
		}
	}

	/* force out-prox when distance from surface exceeds wcmProxoutDist */
	if (IsTablet(priv) && !is_absolute(priv))
	{
		/* Assume the the user clicks the puck buttons while
		 * it is resting on the tablet (and taps styli onto
		 * the tablet surface). This works for both
		 * tablets that have a normal distance scale (protocol
		 * 5) as well as those with an inverted scale (protocol
		 * 4 for many many kernel versions).
		 */
		if ((IsCursor(priv) && filtered.buttons) ||
		    (IsStylus(priv) && filtered.buttons & 0x01))
			priv->wcmSurfaceDist = filtered.distance;

		DBG(10, priv, "Distance over"
				" the tablet: %d, ProxoutDist: %d current"
				" surface %d hard prox: %d\n",
				filtered.distance,
				priv->wcmProxoutDist,
				priv->wcmSurfaceDist,
				ds->proximity);

		if (priv->wcmSurfaceDist >= 0) {
			if (priv->oldState.proximity)
			{
				if (abs(filtered.distance - priv->wcmSurfaceDist)
						> priv->wcmProxoutDist)
					filtered.proximity = 0;
			}
			/* once it is out. Don't let it in until a hard in */
			/* or it gets inside wcmProxoutDist */
			else
			{
				if (abs(filtered.distance - priv->wcmSurfaceDist) >
						priv->wcmProxoutDist && ds->proximity)
					return;
				if (!ds->proximity)
					return;
			}
		}
	}
	wcmSendEvents(priv, &filtered);
}

/*****************************************************************************
 * wcmInitTablet -- common initialization for all tablets
 ****************************************************************************/

int wcmInitTablet(WacomDevicePtr priv)
{
	WacomCommonPtr common = priv->common;
	WacomModelPtr model = common->wcmModel;

	/* Initialize the tablet */
	if (model->Initialize(priv) != Success)
		return !Success;

	/* Default threshold value if not set */
	if (common->wcmThreshold <= 0 && IsPen(priv))
	{
		/* Threshold for counting pressure as a button */
		common->wcmThreshold = priv->maxCurve * DEFAULT_THRESHOLD;

		wcmLog(priv, W_PROBED, "using pressure threshold of %d for button 1\n",
			    common->wcmThreshold);
	}

	/* Calculate default panscroll threshold if not set */
	wcmLog(priv, W_CONFIG, "panscroll is %d\n", common->wcmPanscrollThreshold);
	if (common->wcmPanscrollThreshold == 0) {
		common->wcmPanscrollThreshold = common->wcmResolY * 13 / 1000; /* 13mm */
	}
	if (common->wcmPanscrollThreshold == 0) {
		common->wcmPanscrollThreshold = 1000;
	}
	wcmLog(priv, W_CONFIG, "panscroll modified to %d\n", common->wcmPanscrollThreshold);

	/* output tablet state as probed */
	if (IsPen(priv))
		wcmLog(priv, W_PROBED, "maxX=%d maxY=%d maxZ=%d "
			"resX=%d resY=%d  tilt=%s\n",
			common->wcmMaxX, common->wcmMaxY, common->wcmMaxZ,
			common->wcmResolX, common->wcmResolY,
			HANDLE_TILT(common) ? "enabled" : "disabled");
	else if (IsTouch(priv))
		wcmLog(priv, W_PROBED, "maxX=%d maxY=%d maxZ=%d "
			"resX=%d resY=%d \n",
			common->wcmMaxTouchX, common->wcmMaxTouchY,
			common->wcmMaxZ,
			common->wcmTouchResolX, common->wcmTouchResolY);

	return Success;
}

/* Send a soft prox-out event for the device */
void wcmSoftOutEvent(WacomDevicePtr priv)
{
	WacomDeviceState out = OUTPROX_STATE;

	out.device_type = DEVICE_ID(priv->flags);
	out.device_id = wcmGetPhyDeviceID(priv);
	DBG(2, priv->common, "send a soft prox-out\n");
	wcmSendEvents(priv, &out);
}

/*****************************************************************************
** Transformations
*****************************************************************************/

/**
 * Apply the current pressure curve to the current pressure.
 *
 * @return The modified pressure value.
 */
static int applyPressureCurve(WacomDevicePtr pDev, const WacomDeviceStatePtr pState)
{
	/* clip the pressure */
	int p = max(0, pState->pressure);

	p = min(pDev->maxCurve, p);

	/* apply pressure curve function */
	if (pDev->pPressCurve == NULL)
		return p;
	else
		return pDev->pPressCurve[p];
}

/*****************************************************************************
 * wcmRotateTablet
 ****************************************************************************/

void wcmRotateTablet(WacomDevicePtr priv, int value)
{
	WacomCommonPtr common = priv->common;
	WacomToolPtr tool;

	DBG(10, priv, "\n");
	common->wcmRotate = value;

	/* Only try updating properties once we're enabled, no point
	 * otherwise. */
	tool = priv->tool;
	if (tool->enabled)
		wcmUpdateRotationProperty(priv);
}

/* Common pointer refcounting utilities.
 * Common is shared across all wacom devices off the same port. These
 * functions implement basic refcounting to avoid double-frees and memleaks.
 *
 * Usage:
 *  wcmNewCommon() to create a new struct.
 *  wcmRefCommon() to get a new reference to an already exiting one.
 *  wcmFreeCommon() to unref. After the last ref has been unlinked, the
 *  struct is freed.
 *
 */

WacomCommonPtr wcmNewCommon(void)
{
	WacomCommonPtr common;
	common = calloc(1, sizeof(WacomCommonRec));
	if (!common)
		return NULL;;

	common->is_common_rec = true;
	common->refcnt = 1;
	common->wcmFlags = 0;               /* various flags */
	common->wcmProtocolLevel = WCM_PROTOCOL_4; /* protocol level */
	common->wcmTPCButton = 0;          /* set Tablet PC button on/off */
	common->wcmGestureParameters.wcmScrollDirection = 0;
	common->wcmGestureParameters.wcmTapTime = 250;
	common->wcmRotate = ROTATE_NONE;   /* default tablet rotation to off */
	common->wcmMaxX = 0;               /* max digitizer logical X value */
	common->wcmMaxY = 0;               /* max digitizer logical Y value */
	common->wcmMaxTouchX = 1024;       /* max touch X value */
	common->wcmMaxTouchY = 1024;       /* max touch Y value */
	common->wcmMaxStripX = 4096;       /* Max fingerstrip X */
	common->wcmMaxStripY = 4096;       /* Max fingerstrip Y */
	common->wcmProxoutDistDefault = PROXOUT_INTUOS_DISTANCE;
			/* default to Intuos */
	common->wcmSuppress = DEFAULT_SUPPRESS;
			/* transmit position if increment is superior */
	common->wcmRawSample = DEFAULT_SAMPLES;
			/* number of raw data to be used to for filtering */
	common->wcmPanscrollThreshold = 0;
	common->wcmPressureRecalibration = 1;
	return common;
}


void wcmFreeCommon(WacomCommonPtr *ptr)
{
	WacomCommonPtr common = *ptr;

	if (!common)
		return;

	DBG(10, common, "common refcount dec to %d\n", common->refcnt - 1);
	if (--common->refcnt == 0)
	{
		free(common->private);
		while (common->serials)
		{
			WacomToolPtr next;

			DBG(10, common, "Free common serial: %u %s\n",
					common->serials->serial,
					common->serials->name);

			free(common->serials->name);
			next = common->serials->next;
			free(common->serials);
			common->serials = next;
		}
		free(common->device_path);
		free(common->touch_mask);
		free(common);
	}
	*ptr = NULL;
}

WacomCommonPtr wcmRefCommon(WacomCommonPtr common)
{
	if (!common)
		common = wcmNewCommon();
	else
		common->refcnt++;
	DBG(10, common, "common refcount inc to %d\n", common->refcnt);
	return common;
}

#ifdef ENABLE_TESTS

TEST_CASE(test_get_scroll_delta)
{
	int test_table[][5] = {
		{ 100,  25, 0, 0,  75}, { 25,  100, 0, 0, -75},
		{-100, -25, 0, 0, -75}, {-25, -100, 0, 0,  75},
		{ 100, -25, 0, 0, 125}, {-25,  100, 0, 0,-125},
		{ 100, 100, 0, 0,   0}, {-25,  -25, 0, 0,   0},

		{23, 0, 50, 0,  23}, {0, 23, 50, 0, -23},
		{24, 0, 50, 0,  24}, {0, 24, 50, 0, -24},
		{25, 0, 50, 0,  25}, {0, 25, 50, 0, -25},
		{26, 0, 50, 0, -25}, {0, 26, 50, 0,  25},
		{27, 0, 50, 0, -24}, {0, 27, 50, 0,  24},
		{28, 0, 50, 0, -23}, {0, 28, 50, 0,  23},

		{1024, 0, 0, AXIS_BITWISE, 11}, {0, 1024, 0, AXIS_BITWISE, -11},

		{  0, 4, 256, AXIS_BITWISE, -3}, {4,   0, 256, AXIS_BITWISE,  3},
		{  1, 4, 256, AXIS_BITWISE, -2}, {4,   1, 256, AXIS_BITWISE,  2},
		{  2, 4, 256, AXIS_BITWISE, -1}, {4,   2, 256, AXIS_BITWISE,  1},
		{  4, 4, 256, AXIS_BITWISE,  0}, {4,   4, 256, AXIS_BITWISE,  0},
		{  8, 4, 256, AXIS_BITWISE,  1}, {4,   8, 256, AXIS_BITWISE, -1},
		{ 16, 4, 256, AXIS_BITWISE,  2}, {4,  16, 256, AXIS_BITWISE, -2},
		{ 32, 4, 256, AXIS_BITWISE,  3}, {4,  32, 256, AXIS_BITWISE, -3},
		{ 64, 4, 256, AXIS_BITWISE,  4}, {4,  64, 256, AXIS_BITWISE, -4},
		{128, 4, 256, AXIS_BITWISE,  5}, {4, 128, 256, AXIS_BITWISE, -5},
		{256, 4, 256, AXIS_BITWISE, -4}, {4, 256, 256, AXIS_BITWISE,  4}
	};

	for (size_t i = 0; i < ARRAY_SIZE(test_table); i++)
	{
		int delta;
		int current, old, wrap, flags;
		current = test_table[i][0];
		old     = test_table[i][1];
		wrap    = test_table[i][2];
		flags   = test_table[i][3];

		delta = getScrollDelta(current, old, wrap, flags);
		assert(delta == test_table[i][4]);

		flags |= AXIS_INVERT;
		delta = getScrollDelta(current, old, wrap, flags);
		assert(delta == -1 * test_table[i][4]);
	}
}

TEST_CASE(test_get_wheel_button)
{
	int delta;
	int action_up, action_dn;

	action_up = 300;
	action_dn = 400;

	for (delta = -32; delta <= 32; delta++)
	{
		int action;
		action = getWheelButton(delta, action_up, action_dn);
		if (delta < 0)
		{
			assert(action == action_dn);
		}
		else if (delta == 0)
		{
			assert(action == -1);
		}
		else
		{
			assert(action == action_up);
		}
	}
}


TEST_CASE(test_common_ref)
{
	WacomCommonPtr common;
	WacomCommonPtr second;

	common = wcmNewCommon();
	assert(common);
	assert(common->refcnt == 1);

	second = wcmRefCommon(common);

	assert(second == common);
	assert(second->refcnt == 2);

	wcmFreeCommon(&second);
	assert(common);
	assert(!second);
	assert(common->refcnt == 1);

	second = wcmRefCommon(NULL);
	assert(common != second);
	assert(second->refcnt == 1);
	assert(common->refcnt == 1);

	wcmFreeCommon(&second);
	wcmFreeCommon(&common);
	assert(!second && !common);
}

TEST_CASE(test_rebase_pressure)
{
	WacomDeviceRec priv = {0};
	WacomDeviceRec base = {0};
	WacomDeviceState ds = {0};
	int pressure;

	priv.minPressure = 4;
	ds.pressure = 10;

	/* Pressure in out-of-proximity means get new preloaded pressure */
	priv.oldState.proximity = 0;

	/* make sure we don't touch priv, not really needed, the compiler should
	 * honor the consts but... */
	base = priv;

	pressure = rebasePressure(&priv, &ds);
	assert(pressure == ds.pressure);

	assert(memcmp(&priv, &base, sizeof(priv)) == 0);

	/* Pressure in-proximity means rebase to new minimum */
	priv.oldState.proximity = 1;

	base = priv;

	pressure = rebasePressure(&priv, &ds);
	assert(pressure == priv.minPressure);
	assert(memcmp(&priv, &base, sizeof(priv)) == 0);
}

TEST_CASE(test_normalize_pressure)
{
	InputInfoRec pInfo = {0};
	WacomDeviceRec priv = {0};
	WacomCommonRec common = {0};
	int normalized_max = 65536;
	int pressure = 0, prev_pressure = -1;
	int i, j, k;

	priv.common = &common;
	priv.frontend = &pInfo;
	pInfo.name = strdupa("Wacom test device");
	common.wcmPressureRecalibration = 1;

	priv.minPressure = 0;

	/* Check various maxCurve values */
	for (k = 512; k <= normalized_max; k += 239) {
		priv.maxCurve = k;

		/* Some random loop to check various maxZ pressure values. Starting at
		 * 1, because if wcmMaxZ is 0 we have other problems. */
		for (j = 1; j <= 256; j += 17)
		{
			common.wcmMaxZ = j;
			prev_pressure = -1;

			for (i = 0; i <= common.wcmMaxZ; i++)
			{
				pressure = i;

				pressure = normalizePressure(&priv, pressure);
				assert(pressure >= 0);
				assert(pressure <= k);

				/* we count up, so assume normalised pressure goes up too */
				assert(prev_pressure < pressure);
				prev_pressure = pressure;
			}

			assert(pressure == k);
		}
	}

	/* If minPressure is higher than ds->pressure, normalizePressure takes
	 * minPressure and ignores actual pressure. This would be a bug in the
	 * driver code, but we might as well test for it. */
	priv.minPressure = 10;
	priv.maxCurve = normalized_max;

	prev_pressure = normalizePressure(&priv, 0);
	for (i = 0; i < priv.minPressure; i++)
	{

		pressure = normalizePressure(&priv, i);

		assert(pressure >= 0);
		assert(pressure < normalized_max);

		/* we count up, so assume normalised pressure goes up too */
		assert(prev_pressure == pressure);
	}
}

TEST_CASE(test_suppress)
{
	enum WacomSuppressMode rc;
	WacomCommonRec common = {0};
	WacomDeviceState old = {0},
			 new = {0};

	common.wcmSuppress = 2;

	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_ALL);

	/* proximity, buttons and strip send for any change */

#define test_any_suppress(field) \
	old.field = 1; \
	rc = wcmCheckSuppress(&common, &old, &new); \
	assert(rc == SUPPRESS_NONE); \
	new.field = old.field;

	test_any_suppress(proximity);
	test_any_suppress(buttons);
	test_any_suppress(stripx);
	test_any_suppress(stripy);

#undef test_any_suppress

	/* pressure, capacity, throttle, rotation, abswheel only when
	 * difference is above suppress */

	/* test negative and positive transition */
#define test_above_suppress(field) \
	old.field = common.wcmSuppress; \
	rc = wcmCheckSuppress(&common, &old, &new); \
	assert(rc == SUPPRESS_ALL); \
	old.field = common.wcmSuppress + 1; \
	rc = wcmCheckSuppress(&common, &old, &new); \
	assert(rc == SUPPRESS_NONE); \
	old.field = -common.wcmSuppress; \
	rc = wcmCheckSuppress(&common, &old, &new); \
	assert(rc == SUPPRESS_ALL); \
	old.field = -common.wcmSuppress - 1; \
	rc = wcmCheckSuppress(&common, &old, &new); \
	assert(rc == SUPPRESS_NONE); \
	new.field = old.field;

	test_above_suppress(pressure);
	test_above_suppress(throttle);
	test_above_suppress(rotation);
	test_above_suppress(abswheel);

#undef test_above_suppress

	/* any movement on relwheel counts */
	new.relwheel = 1;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_NONE);
	new.relwheel = 0;

	/* any movement on relwheel2 counts */
	new.relwheel2 = 1;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_NONE);
	new.relwheel2 = 0;

	/* x axis movement */

	/* not enough movement */
	new.x = common.wcmSuppress;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_ALL);
	assert(old.x == new.x);
	assert(old.y == new.y);

	/* only x axis above thresh */
	new.x = common.wcmSuppress + 1;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_NON_MOTION);

	/* x and other field above thres */
	new.pressure = ~old.pressure;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_NONE);

	new.pressure = old.pressure;
	new.x = old.x;

	/* y axis movement */
	new.y = common.wcmSuppress;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_ALL);
	assert(old.x == new.x);
	assert(old.y == new.y);

	new.y = common.wcmSuppress + 1;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_NON_MOTION);

	new.pressure = ~old.pressure;
	rc = wcmCheckSuppress(&common, &old, &new);
	assert(rc == SUPPRESS_NONE);
	new.pressure = old.pressure;
}


#endif

/* vim: set noexpandtab tabstop=8 shiftwidth=8: */
